武士先生
IP:广东
0关注数
23粉丝数
0获得的赞
工作年
编辑资料
链接我:

创作·177

全部
问答
动态
项目
学习
专栏
武士先生

TypeScript与面向对象

什么是面向对象面向对象(OOP)是一种编程范式,是写代码的一种”方式“或者说是”思想“。对象对象是一种包含数据(属性),和行为(方法)的单元。我们可以将不同的物体抽象为不同的对象来帮助我们编写程序。面向对象与函数式编程面向对象编程和函数式编程是两种截然不同的编程思想。我们不能说面向对象优于函数式编程,但是面向对象能够让团队进行更好的合作。类与对象创建一个类首先,面向对象编程是围绕对象的,现在我们要创建一个对象,我们先尝试创建一个类。类(class)是对象(object)的工厂,我们可以通过class来创建有同一属性和方法的对象。在声明类中我们规定:使用class关键字声明一个类类名命名方式为大驼峰命名法声明属性名和类型在构造函数中初始化属性。class Account { //注意,class的名称命名方式为大驼峰命名法 id: number; //我们需要先声明class的属性和类型 name: string; balance: number; constructor(id: number, name: string, balance: number) { //我们需要对属性进行初始化(通常情况下我们使用构造函数来传入值) this.id = id; this.name = name; this.balance = balance; } // 存款 deposit(amount: number): void { if (amount <= 0) throw new Error("Invalid amount"); this.balance += amount; } }其中需要注意的是,我们不能给构造函数的返回值设置类型,因为它始终返回类的实例(对象)constructor Person(name: string, age: number): Person让我们来看看ts编译后生成的js文件"use strict"; class Account { constructor(id, name, balance) { this.id = id; this.name = name; this.balance = balance; } deposit(amount) { if (amount <= 0) throw new Error("Invalid amount"); this.balance += amount; } }我们可以看到,在js中并没有对属性的类型注释,它们只存在于ts。使用类创建一个对象我们使用new关键字来创建一个对象let account = new Account(1, "kevin", 100);//属性会传入构造函数,构造函数会返回一个实例对象 account.deposit(100);//调用存款方法 console.log(account.balance);//200我们来打印一下这个对象:console.log(account); //Account { id: 1, name: 'kevin', balance: 200 }对于对象,我们想像之前缩小类型范围一样使用typeof关键字// if (typeof account === '...'){}//对对象使用typeof永远会返回object console.log(typeof account);//object所以我们需要使用instanceof运算符console.log(account instanceof Account); //truereadonly只读属性我们可能希望有些属性是不会被改变的,比如我们的id:account.id = 1;我们可以在类中使用readonly关键字class Account { readonly id: number; name: string; balance: number; constructor(id: number, name: string, balance: number) { this.id = id; this.name = name; this.balance = balance; } //... } let account = new Account(1, "kevin", 100); account.id = 1;//报错:无法分配到 "id" ,因为它是只读属性。ts(2540)可选属性在Account类中,我们希望添加一个nickname属性。但并不是所有账户有nickname这个属性。如果我们不做处理,而这个账户又没有nickname属性,怎么办呢?我们可以让这个属性成为一个可选属性。只需要在冒号前添加?nickname?: string;class Account { readonly id: number; name: string; balance: number; nickname?: string;//可选属性 constructor(id: number, name: string, balance: number) { this.id = id; this.name = name; this.balance = balance; } // ... }修饰词ts中类有三个修饰符:public private protected(这个修饰符我们放在后面讲)public在默认情况下,声明的属性和方法都是公共的,所以我们不需要再去声明它。public的属性能被实例对象调用。private在上面的例子中,我们能够在外部改变account对象的balance。这肯定是不行的,我们希望改变balance只会通过方法,而不是直接修改属性。我们使用private修饰词,让其属性和方法只能在该类中使用:class Account { readonly id: number; name: string; private balance: number;//报错:声明了但没有使用。是因为从变量中“读取”的唯一东西是本身。如果从其他内容中读取,则将其视为已使用。(先不管) nickname?: string; constructor(id: number, name: string, balance: number) { this.id = id; this.name = name; this.balance = balance; } // 存款 deposit(amount: number): void { if (amount <= 0) throw new Error("Invalid amount"); this.balance += amount; } } let account = new Account(1, "kevin", 100); //console.log(account.balance);//报错:属性“balance”为私有属性,只能在类“Account”中访问。ts(2341)对于private修饰符,我们并不是要用它来修饰像密码等这样的敏感数据,而是要使用这个修饰符写出更加健壮的代码对于private修饰的属性,我们通常在属性名前添加下划线_private _balance: number;对于private修饰的属性,我们无法在外部访问它。你也许会想到声明一个返回这个属性的方法。但是我们有更好的方法,那就是getter访问器。我们会在下面讲到,不过我们现在先尝试使用private修饰方法。private calculateTax() {}当你使用实例对象的代码补全时,你会发现并没有私有属性和方法的代码。这也是一个好处。参数属性通常情况下我们会觉得下面的代码太冗长了class Account { readonly id: number; name: string; private balance: number;//报错:声明了但没有使用。是因为从变量中“读取”的唯一东西是本身。如果从其他内容中读取,则将其视为已使用。(先不管) nickname?: string; constructor(id: number, name: string, balance: number) { this.id = id; this.name = name; this.balance = balance; } // 存款 deposit(amount: number): void { if (amount <= 0) throw new Error("Invalid amount"); this.balance += amount; } }我们可以使用参数属性来减少代码量:class Account { nickname?: string; constructor( public readonly id: number, public name: string, private _balance: number//报错:声明了但没有使用。是因为从变量中“读取”的唯一东西是本身。如果从其他内容中读取,则将其视为已使用。(先不管) ) {} // 存款 deposit(amount: number): void { if (amount <= 0) throw new Error("Invalid amount"); this._balance += amount; } }你可以将修饰参数的关键字写入构造函数中,注意如果属性是默认的public需要写上public。这时,我们就不需要在类中声明属性,也不需要在构造函数中设置默认值,构造函数会将传入参数设为属性的默认值。关于上面那个+=报错,起初我还以为是ts有bug,然后还去github给ts提了个issue。结果过了半个多小时就得到了回复:从变量中“读取”的唯一东西是本身。如果从其他内容中读取,则将其视为已使用。附上链接:+= doesn't count a private member as being used · Issue #51371 · microsoft/TypeScript (github.com)getter和settergetter访问器在前面,我们讲到还有更简单和规范的方法来取到私有属性。那就是getter(访问器)使用get关键字声明一个方法balance,返回值为_balance。值得注意的是,虽然这里看上去是一个方法,但我们调用时就像是在调用一个属性。并且我们可以发现private _balance: number没有报错了(因为之前声明了私有属性没有使用)。而public声明的在类中没有调用也不报错是因为公有属性,实例可以调用。而私有属性不行。class Account { nickname?: string; constructor( public readonly id: number, public name: string, private _balance: number ) {} get balance(): number { return this._balance; } // 存款 deposit(amount: number): void { if (amount <= 0) throw new Error("Invalid amount"); this._balance += amount; } } let account = new Account(1, "kevin", 100); console.log(account.balance); //100setter修改器我们使用set关键字声明一个带有参数的函数,这样可以对属性进行修改class Account { nickname?: string; constructor( public readonly id: number, public name: string, private _balance: number ) {} get balance(): number { return this._balance; } set balance(value: number) { if (value < 0) throw new Error("Invalid value"); this._balance = value; } // 存款 deposit(amount: number): void { if (amount <= 0) throw new Error("Invalid amount"); this._balance += amount; } } let account = new Account(1, "kevin", 100); account.balance = 888; console.log(account.balance);//888当然,在这里的代码中我们不需要setter修改器,因为我们使用private的目的就是不想直接改变balance的值。我们这里只需要把这里看做一个setter的例子就好了。索引签名在js中,我们可以创建一个空对象,并且给空对象的属性赋值来给对象添加属性,而在ts中这是不行的。因为ts对对象的类型非常严格。let person = {}; person.name = "";//ts中会报错:类型“{}”上不存在属性“name”。ts(2339)但有些情况,我们也需要动态的向对象添加属性,这就是我们使用索引签名的地方假设有一场演唱会,对象的键值对为座位号和观众名字。我们不可能在类中写出所有人的座位号和名字。有可能这场演唱会有成千上万个观众。但是我们前面讲了,在ts中我们不能直接向对象添加属性。所以我们需要用到索引签名。class SeatAssignment { [seatNumber: string]: string; } let seats = new SeatAssignment(); seats.A1 = "kevin"; console.log(seats); //SeatAssignment { A1: 'kevin' } seats["A1"] = "qian"; seats.B1 = "kun"; console.log(seats); //SeatAssignment { A1: 'qian', B1: 'kun' }我们使用中括号[]将键名括起来,这表明这个键是一个动态的。并且给键声明类型,然后给值声明类型。这就是索引签名。在接下来,我们就可以像在js中那样使用对象了。静态成员假设我们有一个全球共享数据的应用,比如uber。我们记录已经上车的顾客的数量:class Rides { activeRides: number = 0; start() { this.activeRides++; } stop() { this.activeRides--; } } let ride1 = new Rides(); ride1.start(); let ride2 = new Rides(); ride2.start(); console.log(ride1.activeRides, ride2.activeRides); //1 1我们可以看到,我们输出的值为1,1。这是因为每次创建一个实例都会分配一个新的内存空间。所以在两个对象中使用方法时,他们使用的都是自己的属性。但这不是我们想要的,我们希望他们的某些属性是使用的同一个,比如activeRides,因为我们这是一个全球共享数据的应用。这就是我们使用静态成员的地方。我们使用static来修饰静态成员,这时候成员只能被类调用,而不是实例对象。class Rides { static activeRides: number = 0; start() { Rides.activeRides++; } stop() { Rides.activeRides--; } } let ride1 = new Rides(); ride1.start(); let ride2 = new Rides(); ride2.start(); console.log(Rides.activeRides); //2值得注意的是,我们在start方法和stop方法中将this改变为了Rides,这是因为this指向的是当前对象。而静态成员只能在类中调用。但是现在,我们发现,我们依然可以在外部直接修改activeRides,这是我们不愿意看到的。所以我们像之前那样使用getter和privateclass Rides { private static _activeRides: number = 0; static get activeRides(): number { return Rides._activeRides; } start() { Rides._activeRides++; } stop() { Rides._activeRides--; } } // Rides.activeRides = 10; //报错:无法分配到 "activeRides" ,因为它是只读属性。ts(2540) let ride1 = new Rides(); ride1.start(); let ride2 = new Rides(); ride2.start(); console.log(Rides.activeRides); //2继承继承的实现比如我们有两个类Student和Teacher,这两个类中有许多共性。我们不需要在两个类中写相同的代码,我们应该提高代码的复用性。所以我们新建了一个名叫Person的类,在里面编写两个类共有的属性和方法。比如name,age等等。然后再通过继承extend来拿到Person声明的属性。在这种情况,我们称Person为父类,基类,或者超类。Student,Teacher为子类,派生类。举个例子:class Person { constructor(public firstName: string, public lastName: string) {} get fullName(): string { return this.firstName + " " + this.lastName; } walk(): void { console.log(this.fullName + "is walking"); } } class Student extends Person { constructor(public studentId: number, firstName: string, lastName: string) { super(firstName, lastName); } takeTest(): void { console.log(this.fullName + "is talking a test"); } } let student1 = new Student(1, "Kevin", "Qian"); student1.walk(); //Kevin Qianis walking student1.takeTest(); //Kevin Qianis talking a test 值得注意的是,当我们继承了类,如果要使用构造函数,就需要使用super()来调用父类的构造函数。并且通过子类的构造函数传入super。之前我们说过:当我们使用访问修饰符时,比如public,private,我们实际上是创建一个参数属性。所以ts会创建一个属性并初始化它。所以在Student类中,我们不需要在firstName和lastName中使用public修饰符,因为我们已经在Person中声明并初始化,而且继承了它。现在我们在同一个文件声明了两个类,而关于最佳实践,我们应该实现每个类都有单独的一个文件。我们会在模块化中讲到。方法覆盖/重写方法假如我们声明一个Teacher类,继承Person类。但是我们希望调用fullName时在前面加上professor,这时就需要用到方法重写。class Person { constructor(public firstName: string, public lastName: string) {} get fullName(): string { return this.firstName + " " + this.lastName; } walk(): void { console.log(this.fullName + "is walking"); } } class Student extends Person { constructor(public studentId: number, firstName: string, lastName: string) { super(firstName, lastName); } takeTest(): void { console.log(this.fullName + "is talking a test"); } } class Teacher extends Person { override get fullName(): string { return "Professor " + super.fullName; } } let teacher = new Teacher("Dam", "Sim"); console.log(teacher.fullName); //Professor Dam Sim我们在类中重新声明同名方法,在这个类中使用的方法就是我们新写的方法。值得注意的是:我们应该使用override关键字来告诉ts编译器我们正在重写方法。以及super指向的是父类,我们可以使用super来简化代码。你可能会发现我们不使用override时也不会报错,**但作为最佳实践,我们应该在tsconfig.json中打开"noImplicitOverride": true, 选项。**这时,当我们重写方法时,必须使用override(重写修饰符)来声明。多态性面向对象的非常核心的一点就是多态性。这代表一个对象可以有多种形态。举个例子:class Person { constructor(public firstName: string, public lastName: string) {} get fullName(): string { return this.firstName + " " + this.lastName; } walk(): void { console.log(this.fullName + "is walking"); } } class Student extends Person { constructor(public studentId: number, firstName: string, lastName: string) { super(firstName, lastName); } takeTest(): void { console.log(this.fullName + "is talking a test"); } } class Teacher extends Person { override get fullName(): string { return "Professor " + super.fullName; } } printNames([new Student(1, "kevin", "qian"), new Teacher("Mosh", "Hamedani")]); //kevin qian //Professor Mosh Hamedani function printNames(people: Person[]) { for (let person of people) {//let person: Person console.log(person.fullName); } }我们声明了一个函数,传入类型为Person的对象组成的数组。我们看到,每个迭代的对象的类型都为Person,但是我们可以向函数中传入Student和Teacher的实例对象。这就是类的多态性,这是一个非常强大的功能,因为如果我们再次声明一个继承Person的类,我们可以在不改写函数的情况下传入这个类的实例对象。现在我们新声明一个继承Person的类,并且改写fullName方法:class Person { constructor(public firstName: string, public lastName: string) {} get fullName(): string { return this.firstName + " " + this.lastName; } walk(): void { console.log(this.fullName + "is walking"); } } class Student extends Person { constructor(public studentId: number, firstName: string, lastName: string) { super(firstName, lastName); } takeTest(): void { console.log(this.fullName + "is talking a test"); } } class Teacher extends Person { override get fullName(): string { return "Professor " + super.fullName; } } class Principal extends Person { override get fullName(): string { return "Principal " + super.fullName; } } printNames([ new Student(1, "kevin", "qian"), new Teacher("Mosh", "Hamedani"), new Principal("sim", "red"), ]); // kevin qian // Professor Mosh Hamedani // Principal sim red function printNames(people: Person[]) { for (let person of people) { //let person: Person console.log(person.fullName); } }我们可以发现,我们在没有改变之前代码的情况下增强了我们的程序。所以我们实现新的功能时,只需要编写新的代码。这也为我们引入了开闭原则开放封闭原则(OCP,Open Closed Principle)是所有面向对象原则的核心。软件设计本身所追求的目标就是封装变化、降低耦合,而开放封闭原则正是对这一目标的最直接体现。其他的设计原则,很多时候是为实现这一目标服务的.一个软件实体, 如类, 模块, 函数等应该对扩展开放, 对修改封闭。实际上多态性就是对开闭原则的一个遵守,我们不可能100%的遵守开闭原则,而且这样的代价也可能是昂贵的。不过在在这里我们应该知道:多态性能够引导我们遵守这个原则。在之前我们说到重写方法时要用到override关键字。这也是对多态行为的一种遵守。private和protected我们知道,private修饰符修饰的属性和方法只允许我们在该类中使用。而protected允许我们在继承的类中使用。但我们应该尽可能不使用protected(除非你知道你在干什么),因为这会在程序中创造耦合。抽象类假设我们要写一个程序对各种形状的物体进行渲染,比如圆形,三角形。在进行上面的学习后,我们可能会这样写:class Shape { constructor(public color: string) {} render() {} } class Circle extends Shape { constructor(public radius: number, color: string) { super(color); } override render(): void { console.log("Rendering a circle"); } } let shape = new Shape("red"); shape.render();我们将Shape类作为Circle类的父类,但我们发现Shape类可以生成实例对象,这是不符合逻辑的,因为我们没有办法渲染一个”形状“。所以,我们在这里需要用到抽象类abstract:abstract class Shape { constructor(public color: string) {} abstract render(): void; } class Circle extends Shape { constructor(public radius: number, color: string) { super(color); } override render(): void { console.log("Rendering a circle"); } }我们在clsss关键字前使用abstract关键字来声明一个抽象类。抽象方法也要用abstract声明(并不是说抽象类只能用抽象方法。),并且抽象方法不能具有实现。所以我们要把{}去掉。另外,我们还应该声明其返回值类型为void,如果不声明它会具有隐式的any类型。但抽象方法的返回值类型永远为void。此外,需要记住的是:抽象方法只能在抽象类中存在。接口假如我们要编写一个日历,我们知道有谷歌日历,outlook日历等等。根据上面我们学的,我们可能会使用抽象类来进行编写:abstract class Calendar { constructor(public name: string) {} abstract addEvent(): void; abstract removeEvent(): void; }我们编译后查看js文件:"use strict"; class Calendar { constructor(name) { this.name = name; } } //# sourceMappingURL=demo.js.map我们可以看到,这就是一个普通的类,因为js中没抽象类的概念。另外,在这里我们可以使用接口来实现:可能有些人使用I开头来命名接口,但我们更多的会直接使用名称。interface Calendar { name: string; addEvent(): void; removeEvent(): void; }我们发现接口的代码更简洁。编译后:"use strict"; //# sourceMappingURL=demo.js.map我们发现并没有生成接口代码,因为在js中没有接口的概念,这只会在ts编译器中进行类型检查。那我们应该使用抽象类还是接口呢?这取决于是否提供了任何逻辑和供子类实现的方法。如果没有就使用接口。因为我们的代码会更简洁,不管是在ts中还是js中。相反的,如果我们有实现逻辑的方法或者供子类实现的方法,我们就不能使用接口了,因为接口不能有方法实现。我们只能声明方法,指定方法的签名。我们也可以使用extends继承接口:interface Calendar { name: string; addEvent(): void; removeEvent(): void; } interface CloudCalendar extends Calendar { sync(): void; }在类中,我们应该这样使用:interface Calendar { name: string; addEvent(): void; removeEvent(): void; } interface CloudCalendar extends Calendar { sync(): void; } class GoogleCalendar implements CloudCalendar { constructor(public name: string) {} sync(): void {} addEvent(): void {} removeEvent(): void {} }我们在通过implements使用接口后,应该还在class中同样声明属性和方法。
0
0
0
浏览量530
武士先生

深入浅出 Quill 系列之选型篇:Quill 富文本编辑器的实践

富文本编辑器大概是最复杂、使用场景却极广的组件了。可以说富文本编辑器让 Web 数据录入充满了无限的想象空间,如果只有文本框、下拉框这些纯文本的数据录入组件,那么Web的数据录入能力将极大地受限。我们将无法在网页上插入图片、视频这些富文本内容,更无法插入自定义的内容。富文本编辑器让 Web 内容编辑变得更轻松、更高效,我们几乎可以在富文本编辑器中插入任何你想插入的内容,图片、视频、超链接、公式、代码块,都不在话下,甚至还可以插入表格、PPT、思维导图,甚至3D模型这种超复杂的自定义内容。富文本编辑器的场景在 Web 上也是随处可见,写文章、写评论、意见反馈、录需求单,都需要使用到富文本。本文从富文本编辑器的使用场景、技术选型,再到对 Quill 的扩展,以及 Quill 的基本原理,跟大家分享 Quill 富文本编辑器的那些事儿。本文主要由以下部分组成:富文本编辑器的使用场景技术选型我们为什么选择 Quill如何扩展 QuillQuill 基本原理以下内容来自 Kagol 在 华为 HWEB 大前端技术分享会 上的演讲(2021年5月28日)。1 富文本编辑器的使用场景博客文章Wiki 词条工作项描述测试用例步骤反馈意见评论…2 技术选型我们的需求:开源协议友好Angular 框架或框架无关灵活可扩展支持插入/编辑表格和图片插件丰富,生态好2.1 选型分析首先排除官方不维护的UEditor然后排除 React 框架专属的Draft.js和Slate接着排除开源协议不友好的CKEditor由于我们的业务场景丰富,需要富文本插入/编辑表格的功能,所以还需要排除不支持表格的Trix,弱支持表格的Etherpad和Prosemirror,以及表格功能收费的TinyMCE最后只剩下Quill和wangEditor两款编辑器可选,wangEditor的扩展性和生态不如Quill,所以最终选择Quill作为富文本组件的基座3 为什么选择 Quill?BSD 协议,商业友好文档详细,上手快API 驱动,扩展性好插件丰富,生态好3.1 文档详细Document:quilljs.com/介绍 Quill 的 API:介绍如何扩展 Quill:3.2 上手快安装 Quill:npm i quill引入样式:@import 'quill/dist/quill.snow.css';引入 Quill:import Quill from 'quill';初始化 Quill:new Quill('#editor', { theme: 'snow' });效果图:3.3 API 驱动,扩展性好3.4 插件丰富,生态好4 扩展 Quill4.1 插入标签比如我想在编辑器里插入标签4.2 上传附件比如我想在编辑器里插入附件4.3 插入表情比如我想在编辑器中插入表情类似语雀的评论:www.yuque.com/yuque/blog/…4.4 个性分割线比如我想插入B站这种个性化的分割线4.5 超链接卡片比如我想插入知乎这样的超链接卡片4.6 如何插入表情?我们从如何插入表情入手,一起看看怎么在 Quill 中插入自定义的内容。要在 Quill 中插入表情,只需要以下四步:第一步:自定义工具栏按钮第二步:自定义 Blot 内容 EmojiBlot第三步:在 Quill 注册 EmojiBlot第四步:调用 Quill 的 API 插入表情4.6.1 第一步:自定义工具栏按钮const quill = new Quill('#editor', {   theme: 'snow',   modules: {     // 配置工具栏模块     toolbar: {       container: [ …, [ 'emoji' ] ], // 增加一个按钮       handlers: {         // 添加按钮的处理逻辑         emoji() {           console.log('插入表情');         }       }     },   } });4.6.2 给工具栏按钮增加图标// 扩展 Quill 内置的 icons 配置 const icons = Quill.import('ui/icons'); icons.emoji = ‘<svg>…</svg>’; // 图标的 svg 可以从 iconfont 网站复制效果如下:工具栏上已经多了一个表情的按钮,并且能够响应鼠标点击事件,下一步就是要编写插入表情的具体逻辑,这涉及到 Quill 的自定义内容相关的知识。4.6.3 第二步:自定义 Blot 内容 EmojiBlotQuill 中的 Blot 就是一个普通的 ES6 Class,由于表情和图片的差别就在于:Quill 内置的图片格式不支持自定义宽高,而我们要插入的表情是需要特定的宽高的。因此我们可以基于 Quill 内置的 image 格式来扩展。emoji.tsimport Quill from 'quill'; const ImageBlot = Quill.import('formats/image'); // 扩展 Quill内置的 image 格式 class EmojiBlot extends ImageBlot {   static blotName = 'emoji'; // 定义自定义 Blot 的名字(必须全局唯一)   static tagName = 'img'; // 自定义内容的标签名   // 创建自定义内容的 DOM 节点   static create(value): any {     const node = super.create(value);     node.setAttribute('src', ImageBlot.sanitize(value.url));     if (value.width !== undefined) {       node.setAttribute('width', value.width);     }     if (value.height !== undefined) {       node.setAttribute('height', value.height);     }     return node;   } // 返回 options 数据   static value(node): any {     return {       url: node.getAttribute('src'),       width: node.getAttribute('width'),       height: node.getAttribute('height')     };   } } export default EmojiBlot;4.6.4 第三步:在 Quill 注册 EmojiBlot有了 EmojiBlot,要将其插入 Quill 编辑器中,还需要将这个 ES6 类注册到 Quill 中。import EmojiBlot from './formats/emoji'; Quill.register('formats/emoji', EmojiBlot);4.6.5 第四步:调用 Quill 的 API 插入表情EmojiBlot 注册到 Quill 中之后,Quill 就能认识它了,也就可以调用 Quill 的 API 将其插入到编辑器中。emoji(): void {   console.log('插入表情');   // 获取当前光标位置   const index = this.quill.getSelection().index;   // 在当前光标处插入 emoji(blotName)   this.quill.insertEmbed(index, 'emoji', {     url: 'assets/emoji/good.png',     width: '64px',   }); },4.7 效果图4.8 Demo 源码源码链接:gitee.com/kagol/quill…5 Quill 基本原理最后讲一讲 Quill 的基本原理。5.1 基本原理使用 Delta 数据模型描述富文本内容及其变化,以保证行为的可预测通过 Parchment 对 DOM 进行抽象,以保证平台一致性通过 Mutation Observe 监听 DOM 节点的变化,将 DOM 的更改同步到 Delta 数据模型中5.2 Quill 如何表达编辑器内容?5.2.1 Delta 数据模型通过 Delta 数据模型来描述富文本内容及其变化Delta 是 JSON 的一个子集,只包含一个 ops 属性,它的值是一个对象数组,每个数组项代表对编辑器的一个操作(以编辑器初始状态为空为基准)。{   "ops": [     { "insert": "Hello " },     { "insert": "World", "attributes": { "bold": true } },     { "insert": "\n" }   ] }5.2.2 修改编辑器内容比如我们把加粗的"World"改成红色的文字"World",这个动作用 Delta 描述如下:{   "ops": [     { "retain": 6 },     { "retain": 5, "attributes": { "color": "#ff0000" } }   ] }意思是:保留编辑器最前面的6个字符,即保留"Hello "不动,保留之后的5个字符"World",并将这些字符设置为字体颜色为"#ff0000"。5.2.3 删除编辑器内容如果要删除"World"呢?{   "ops": [     { "retain": 6 },     { "delete": 5 }   ] }即:保留前面6个字符('Hello '),删除之后的5个字符('World')5.3 Quill 如何渲染内容?渲染富文本内容的基本原理:遍历 Delta 数组,将其中描述的内容一个一个应用(插入/格式化/删除)到编辑器中。5.4 Quill 如何扩展编辑器的能力?扩展 Quill 的方式:通过自定义 Blot 格式来扩展编辑器的内容通过自定义模块来扩展编辑器的功能
0
0
0
浏览量208
武士先生

TypeScript中的难点:

为什么使用泛型假如我们有这样一个程序:class KeyValue { constructor(public key: number, public value: string) {} } let keyValue = new KeyValue(1, "kevin");那么如果我们想让key传入的是string类型呢?我们可能会想到新建一个类,或者使用联合类型,或者使用any类型。但这不是我们应该做的,新建一个类会写重复的代码。使用联合类型不仅会得不到正确的代码补全,并且当key为新类型时,还需要在原有代码上增添新类型。使用any类型会让我们失去使用ts的意义。这时候我们应该用到泛型(generic)。在类中使用泛型class KeyValue<T> { constructor(public key: T, public value: string) {} } let keyValue1 = new KeyValue<number>(1, "kevin"); let keyValue2 = new KeyValue<string>("2", "kevin");当我们使用泛型时,我们应该先声明,声明的方法就是在类名后使用尖括号<>然后在里面声明一个或多个类型变量,当声明多个泛型时使用逗号隔开。这个变量可以是任意字母。不过我们通常使用大写字母开头,并且喜欢使用单个字母。很多时候你会看到T,实际上这来源于c++的temple模板类。当我们使用的时候会向上面那样传入类型,实际上我们传入类型时ts也会进行自动推断类型。这时候我们没有多写代码,同时我们也得到了安全的类型以及智能的代码补全。现在我们将这个需要实现可复用的类进行完善:class KeyValue<K, V> { constructor(public key: K, public value: V) {} } let keyValue1 = new KeyValue<string, boolean>("2", true); let keyValue2 = new KeyValue(1, "kevin"); 在函数和方法中使用泛型在函数中使用:function wrapInArray<T>(value: T) { return [value]; } let numebrs = wrapInArray("1"); let numebrs1 = wrapInArray(1);在方法中使用:class ArrayUtils { wrapInArray<T>(value: T) { return [value]; } } let utils = new ArrayUtils(); let numbers = utils.wrapInArray(1);接口泛型interface Result<T> { data: T; }假如我们想要像下面那样,使用接口描述一个请求的返回值:interface Result<T> { data: T | null; error: null | string; } function fetch<T>(url: string): Result<T> { return { data: null, error: null }; }我们可以这样写:interface Result<T> { data: T | null; error: null | string; } function fetch<T>(): Result<T> { return { data: null, error: null }; } interface User { username: string; } interface Product { title: string; } let result = fetch<Product>(); result.data?.title;这时候当我们写result.data时ts会根据我们传入的类型来进行代码补全的提示。比如我们这里传入的Product接口,那么这时返回值中的data的类型为Product,一个只有属性名为title的对象,所以会提示我们输入title。至于为什么是可选属性,因为data的属性为T|null可能为null类型。限制泛型类型有时候我们需要在构造函数中使用泛型,现在让我们写一个简单的函数function echo<T>(value: T): T { return value; } echo("1");我们可以发现我们能向echo中传入任何类型的值,我们可以使用extends来限制它。这样只能传入继承的类型。就像这样:function echo<T extends string | number>(value: T): T { return value; } echo("1"); echo(1); //echo(true); //报错:类型“boolean”的参数不能赋给类型“string | number”的参数。ts(2345)我们也可以使泛型继承一个接口:interface Person { name: string; } function echo<T extends Person>(value: T): T { return value; } echo({ name: "kevin" });我们也可以使用类来限制泛型:class Person { constructor(public name: string) {} } class Customer extends Person {} function echo<T extends Person>(value: T): T { return value; } echo({ name: "kevin" }); echo(new Person("person")); echo(new Customer("customer"));传入的值可以是符合类型的对象,实例对象或者子类的实例对象。泛型类和继承现在我们有这么段代码:假如我们有一个商店类,使用add方法添加产品。为了让object不被外部调用,我们声明其为私有属性,向开头添加下划线_并且使其数组初始化为空数组。interface Product { name: string; price: number; } class Store<T> { private _object: T[] = []; add(obj: T): void { this._object.push(obj); } }接下来我们会通过三种场景来扩展这个类传递泛型Tinterface Product { name: string; price: number; } class Store<T> { private _object: T[] = []; add(obj: T): void { this._object.push(obj); } } class CompressibleStore<T> extends Store<T> { compress() {} } let store = new CompressibleStore<Product>(); store.compress(); store.add({ name: "nice", price: 22 });我们在继承一个声明了泛型的类时,我们这个类也需要声明泛型,因为在这里T作为的是值,而类型变量T在这里还没有声明,所以我们需要先声明它。限制泛型类型我们新声明了一个提供查找方法的类。我们在这个类中提供了find方法,然后想要使用Store的_object属性,所以我们将修饰词改为protected。数组的find方法如果找到了这个元素会返回元素本身,如果没有找到则返回undefined,所以我们的find方法返回值为T | undefinedinterface Product { name: string; price: number; } class Store<T> { protected _object: T[] = []; add(obj: T): void { this._object.push(obj); } } class CompressibleStore<T> extends Store<T> { compress() {} } class SearchableStore<T extends { name: string }> extends Store<T> { find(name: string): T | undefined { return this._object.find((obj) => obj.name === name); } } new SearchableStore<Product>(); new SearchableStore<{ name: string }>();混合泛型类型属性也就是在继承时直接传入泛型interface Product { name: string; price: number; } class Store<T> { protected _object: T[] = []; add(obj: T): void { this._object.push(obj); } } // 1、传递泛型T class CompressibleStore<T> extends Store<T> { compress() {} } // 2、限制泛型类型 class SearchableStore<T extends { name: string }> extends Store<T> { find(name: string): T | undefined { return this._object.find((obj) => obj.name === name); } } // 3、修复泛型类型属性 class ProductStore extends Store<Product> { filterByCategory(): Product[] { return []; } }泛型与键操作符假设我们在Store中声明了find方法,希望能查找obj。我们的property为键,类型为string,value为值,类型为unknown,因为我们不知道值为什么类型。返回值为T(obj的类型)或者undefined(没有找到)。我们让find的条件为obj[property] === value这时会报错(元素隐式具有 "any" 类型,因为类型为 "string" 的表达式不能用于索引类型 "unknown"。在类型 "unknown" 上找不到具有类型为 "string" 的参数的索引签名。ts(7053))。因为在使用obj[property]时ts会认为我们在使用索引签名。我们处理的只是T类型的实质类型。所以我们应该将property的类型改为keyof T,如果传入Product,这代表类型为'name' | 'price'。这时我们传入property的值只能为name或者price了。interface Product { name: string; price: number; } class Store<T> { protected _object: T[] = []; add(obj: T): void { this._object.push(obj); } // T is Product // keyof T => 'name' | 'price' find(property: keyof T, value: unknown): T | undefined { return this._object.find((obj) => obj[property] === value); } } let store = new Store<Product>(); store.add({ name: "kevin", price: 2 }); store.find("name", "kevin"); store.find("price", 2); // store.find("nonExistingProperty", 2); //类型“"nonExistingProperty"”的参数不能赋给类型“keyof Product”的参数。ts(2345)类型映射如果我们现在要添加一个接口,它的属性和上面的Product一样,只是属性被修饰为readonly,我们可能会这样写:interface Product { name: string; price: number; } interface ReadonlyProduct { readonly name: string; readonly price: number; }但是我们不应该写这么重复的代码,我们应该使用类型映射:interface Product { name: string; price: number; } type ReadonlyProduct = { readonly [K in keyof Product]: Product[K]; };类型映射应该使用type声明,并且我们通过索引签名和for in循环来声明属性和类型。并且在属性前添加readonly关键字。interface Product { name: string; price: number; } type ReadonlyProduct = { readonly [K in keyof Product]: Product[K]; }; let product: ReadonlyProduct = { name: "k", price: 2, }; // product.name = "kevin"; //无法分配到 "name" ,因为它是只读属性。ts(2540)如果我们希望其他的接口也是只读的,我们可以使用泛型:interface Product { name: string; price: number; } type ReadOnly<T> = { readonly [K in keyof T]: T[K]; }; let product: ReadOnly<Product> = { name: "k", price: 2, }; // product.name = "kevin"; //无法分配到 "name" ,因为它是只读属性。ts(2540)现在我们根据上面的经验,创建一个可选属性的类型映射和可能为null类型的类型映射:type Optional<T> = { [K in keyof T]?: T[K]; }; type Nullable<T> = { [K in keyof T]: T[K] | null; };这些类型映射是非常有用的,实际上ts内置了这些类型映射(工具类型TypeScript: Documentation - Utility Types (typescriptlang.org))
0
0
0
浏览量532
武士先生

深入浅出 Quill 系列之使用篇2:通过 Quill API 实现对编辑器内容的完全控制

引言这是深入浅出 Quill 系列的第2篇。上一篇我们介绍了 Quill 的基本使用和配置,相信大家能够使用 Quill 搭建一个简单的富文本编辑器啦。不过实际的业务场景可能更复杂,有更多定制的需求,Quill 能否满足呢?Quill 是一款 API 驱动的富文本编辑器,它的内容可以通过API实现完全的掌控,我们一起来看看吧。1 对内容的控制富文本编辑器最基本的操作就是对内容的增/删/改/查,比如:在编辑器某处增加一些文本选中编辑器中的一部分内容,将其删除选中一部分文本,给它添加某种格式获取其中一部分内容,对其进行转换以上操作直接通过键盘和鼠标很容易操作,但是通过 API 如何实现呢?1.1 删先看删的部分,通过deleteText()方法实现,该方法主要有两个入参:index 从哪儿删除length 删除多少内容比如我想把下面的上一篇删除:this.quill.deleteText(0, 3);又比如我想删除编辑器里的所有内容,但我们不知道里面一共有多少内容,是不是需要一个一个数一下呢?其实是不需要的,Quill 提供了一个查询编辑器总字符数的方法getLength()(后面介绍查的部分也会讲到)。所以删除所有内容也很简单:this.quill.deleteText(0, this.quill.getLength());还有一种常见的情况,就是我们想删除编辑器中选中的内容,这要如何实现呢?Quill 提供了一个获取编辑器选区的方法getSelection()(后面介绍对选区的控制时会讲到)可以轻松实现:// 获取选区内容所在的index和length const { index, length } = this.quill.getSelection(); this.quill.deleteText(index, length);是不是非常方便呢?1.2 查再来看查的部分,Quill 托管了编辑器里所有的内容,因此它对里面的内容了如指掌,Quill 知道:指定位置有什么内容有多少内容它的格式是什么可以使用getText()方法获取纯文本内容,它的使用方式和前面介绍过的deleteText()类似:// 获取指定位置的文本 this.quill.getText(0, 6); // 不传入任何参数,可以获取全部文本 this.quill.getText(); // 获取选中文本 const { index, length } = this.quill.getSelection(); this.quill.getText(index, length);都知道有什么内容了,拿到内容的长度就很简单了:const length = this.quill.getText().length;Quill 提供了一个简便的方法getLength(),可以直接拿到全部文本的长度:const length = this.quill.getLength();要获取选中文本的长度,可以使用之前介绍过的getSelection()方法:const length = this.quill.getSelection().length;1.3 增1.3.1 插入文本往编辑器里增加格式化的内容是最常见的需求,Quill 针对该场景提供了非常丰富的 API,最基础的就是insertText()方法。该方法既可以增加纯文本,又可以增加带格式的文本。插入纯文本需要传入两个参数:index 从哪个位置插入文本text 插入什么文本this.quill.insertText(0, 'Quill 是一款 API 驱动的富文本编辑器');插入带格式的文本需要额外传入两个参数:format 格式的名字value 格式的值比如我想在当前光标后面插入一个带超链接的Quill:const range = this.quill.getSelection(); if (range) { this.quill.insertText(range.index, 'Quill', 'link', 'https://quilljs.com/'); }1.3.2 插入嵌入内容插入嵌入内容的方法insertEmbed(),这个方法很强大,后续我会给大家分享如何使用这个方法在编辑器中插入龙、插入贪吃蛇游戏等好玩的内容。这个方法和insertText()的区别在于没有第二个参数,因为它不需要插入文本。比如插入B站风格的分割线:const index = this.quill.getSelection().index; this.quill.insertEmbed(index, 'divider', { url: 'assets/images/divider.png', width: '660px', }); 比如插入龙:const index = this.quill.getSelection().index; this.quill.insertEmbed(index, 'dragon', { id: 'canvas-dragon', }); 比如插入贪吃蛇小游戏:const index = this.quill.getSelection().index; this.quill.insertEmbed(index, 'snake', { id: 'canvas-snake', });1.3.3 用纯文本替换现有内容这两个方法都是在现有内容的基础上新增文本。如果要直接用新的内容替换现有文本,要怎么做呢?使用以下两个set方法即可:setText 设置纯文本setContents 设置带格式的文本setText()方法只有一个参数:text 需要插入的纯文本this.quill.setText('Hello Quill!');如果text参数传入空字符串,则会清空编辑器内容:this.quill.setText('');1.3.4 用 delta 数据替换现有内容setContents()方法非常强大,可以使用指定的 delta 数据来渲染编辑器的内容。比如我们想要将当前富文本的内容变成一个贪吃蛇游戏:this.quill.setContents([ { insert: { snake: { id: 'snake' } } } ]);一般 delta 数据会存储在数据库中,使用 delta 来初始化编辑器内容时,可以使用该方法。1.4 改setContents()方法还有一个兄弟叫updateContents(),这俩兄弟本领都非常强。updateContents()方法可以使用 delta 更新编辑器中的指定内容。比如我想把选中的Quill内容变成QuillJS,并加上超链接,不使用updateContents()方法的情况下,我们需要调用多个方法:const { index, length } = this.quill.getSelection(); this.quill.deleteText(index, length); this.quill.insertText(index, 'QuillJS', 'link', 'https://quilljs.com/');我们再来看看使用updateContents()方法如何实现:this.quill.updateContents([ { retain: index }, { delete: length }, { insert: 'QuillJS', attributes: { link: 'https://quilljs.com/' } } ]);两种方法的效果一样,但是后者只需要调用一个方法。updateContents()方法可以赋予我们通过操作 delta 这个 JSON 数据来操作编辑器内容,而不用手动调用 API 去改变内容,在某些场景下这将是一个极大的便利。2 对格式的控制2.1 删除了可以删除编辑器内容外,我们可能还需要清除某部分内容的格式,清除格式可以使用removeFormat()方法,该方法的使用方式和deleteText()几乎是一样的,不再赘述。// 清除指定位置和长度的文本的格式 this.quill.removeFormat(0, 6); // 清除全部文本的格式 this.quill.removeFormat(0, this.quill.getLength()); // 清除选中文本的格式 const { index, length } = this.quill.getSelection(); this.quill.removeFormat(index, length);2.2 查获取单一格式getText()方法只能拿到纯文本,并不知道里面有什么格式,要想获取指定文本的格式,可以使用getFormat()方法,使用方式都一样。// 获取选中文本的格式 const { index, length } = this.quill.getSelection(); const format = this.quill.getFormat(inde比如粗体的格式:{ bold: true }超链接的格式:{ link: "https://juejin.cn/post/6976023288753586184" }获取 Delta 格式不过getFormat()方法只能拿到单一的格式,如果想知道指定内容的全部格式信息,需要使用一个更加强大的API:getContents(),这个方法能获取内容的 delta 形式,而 delta 格式不仅描述了有什么内容,还描述了这些内容的格式是什么。比如以下选中的内容,我们看看它的内容和格式是什么。调用getContents()方法:const { index, length } = this.quill.getSelection(); const contents = this.quill.getContents(index, length); console.log('contents:', contents);打印了以下信息:{ ops: [ { insert: '删除内容' }, { attributes: { header: 2 }, insert: '\n' }, // 标题二格式 { insert: '先看' }, { attributes: { code: true }, insert: '删' }, // 行内代码格式 { insert: '的部分,通过' }, { attributes: { code: true }, insert: 'deleteText()' }, // 行内代码格式 { insert: '方法实现,该方法主要有两个入参:\nindex 从哪儿删除' }, { attributes: { list: 'bullet' }, insert: '\n' }, // 无序列表格式 { insert: 'length 删除多少内容' }, { attributes: { list: 'bullet' }, insert: '\n' }, // 无序列表格式 { insert: '比如我想把下面的' }, { attributes: { code: true }, insert: 'Hello ' }, // 行内代码格式 { insert: '删除:\nthis.quill.deleteText(0, 6);' }, { attributes: { 'code-block': true }, insert: '\n' }, // 代码块格式 { insert: '\n' } ] }从以上 delta 结构我们很容易得出编辑器内容的格式信息:删除内容是标题二格式删/deleteText()/Hello 是行内代码格式index 从哪儿删除和length 删除多少内容是无序列表格式this.quill.deleteText(0, 6);是代码块格式其他内容都是纯文本格式是不是一目了然呢?2.3 增除了删除和查找格式之外,还可以设置格式,Quill提供了3个设置格式的方法:format(format, value) 设置选中文本的格式formatLine(index, length, format, value) 设置行(块级)格式formatText(index, length, format, value) 设置指定位置的文本格式// 设置选中文本为粉色 this.quill.format('color', 'pink'); // 设置第10-20个字符为粉色 this.quill.formatText(10, 10, 'color', 'pink'); // 设置第一行为有序列表 this.quill.formatLine(0, 1, 'list', 'ordered');3 对选区的控制3.1 查3.1.1 查询选区信息获取当前选区或光标的方法getSelection(),我们在前面已经使用过多次,说明这个方法是一个非常实用的高频方法。该方法不需要传入参数,返回当前选区信息:index 选区开始位置length 选区长度{ index: 0, length: 3 }如果只有光标,没有选择任何内容,则返回的length为0。如果编辑器没有光标,则返回null。3.1.2 查询文本相对定位位置除了查询选区位置和长度,还可以使用getBounds()方法查询指定位置的文本在编辑器容器中的相对定位位置,该方法有两个入参:index 选区开始位置length 选区长度比如我想看下编辑器开头的三个字符的位置:const bounds = this.quill.getBounds(0, 3);返回结果:{ bottom: 49.100006103515625 height: 22.5 left: 18 right: 66 top: 26.600006103515625 width: 48 }3.2 增除了查看当前选区信息,我们还可以使用setSelection()方法手动设置选区和光标位置,该方法有两个参数:index 选区开始位置length 选区长度如果只设置第一个参数,将只设置光标位置,不选中文本:// 将光标定位到第10个字符后面 this.quill.setSelection(10);两个参数同时设置将选中文本:// 选中第1到10个字符 this.quill.setSelection(0, 10);选区和光标是后续操作的基础,所以该方法和getSelection()方法一样,是一个非常常用的方法。4 小结我们做一个简单的小结:对内容的控制删除内容 deleteText(index, length)查找内容 getText(index, length)获取编辑器内容长度 getLength()插入文本内容 insertText(index, text, format, value)插入嵌入内容 insertEmbed(index, format, value)使用纯文本替换内容 setText(text)用 delta 数据替换现有内容 setContents(delta)用 delta 更新内容 updateContents(delta)对格式的控制删除格式 removeFormat(index, length)查找单一格式 getFormat(index, length)获取 Delta 格式 getContents(index, length)设置选中文本的格式 format(format, value)设置行格式 formatLine(index, length, format, value)设置文本格式 formatText(index, length, format, valu对选区的控制获取选区/光标信息 getSelection()获取指定文本的相对定位 getBounds(index, range)设置选区/光标 setSelection(index, range)5 案例:查找替换功能最后我们用一个查找替换的案例来温故下之前介绍过的 API。// 待查找文本 const str = 'Quill'; const length = str.length; // 匹配目标文本的正则 const reg = new RegExp(str, 'g'); let match; while ((match = reg.exec(this.quill.getText())) !== null) { // 目标文本在文档中的位置 const index = match.index; // 匹配到目标文本之后,我们可以对该文本做高亮或替换的处理 // 高亮 this.quill.formatText(index, length, 'background', '#ef0fff'); // 替换 this.quill.deleteText(index, length); this.quill.insertText(index, 'QuillJS', 'link', 'https://quilljs.com/'); }查找替换动画演示效果:深入浅出 Quill 系列之使用篇到此结束,相信你已经能够灵活使用 Quill 搭建自己的富文本编辑器,为了帮助大家更深入地理解 Quill,接下来我将会开始解析 Quill 的实现原理,敬请期待!
0
0
0
浏览量510
武士先生

深入浅出 Quill 系列之使用篇1:Quill 基本使用和配置

引言Quill 是一款 API 驱动、支持格式和模块定制的开源 Web 富文本编辑器,目前在 GitHub 的 Star 数是 38k。深入浅出 Quill 系列打算按照使用 -> 原理 -> 实践的思路去讲 Quill 富文本编辑器,主要分成以下7篇。深入浅出 Quill 系列之使用篇1:Quill 基本使用和配置深入浅出 Quill 系列之使用篇2:通过 Quill API 实现对编辑器内容的完全控制深入浅出 Quill 系列之原理篇1:现代富文本编辑器 Quill 的模块化机制深入浅出 Quill 系列之原理篇2:现代富文本编辑器 Quill 的内容渲染机制深入浅出 Quill 系列之实践篇1:如何将龙插入到编辑器中?深入浅出 Quill 系列之实践篇2:整个贪吃蛇游戏到编辑器里玩儿吧深入浅出 Quill 系列之选型篇:Quill 富文本编辑器的实践本文是第1篇,我们先从 Quill 的基本使用开始吧!1 极简方式使用 Quill快速开始三部曲:安装引入使用// 安装 npm i quill<div id="editor"></div>// 引入 import Quill from 'quill'; // 使用 const quill = new Quill('#editor');虽然我们已经初始化了 Quill 实例,但是在页面中却什么也看不到。虽然看上去什么也没有,但是我们点击空白处,会发现有一个光标,并且可以输入内容,并给内容增加格式(由于没有工具栏,只能通过 Quill 快捷键Ctrl+B增加格式),以下是动画效果:虽然只是一个极简版的富文本编辑器,不过加上边框和按钮,就是一个基础版的掘金评论框(还差插入表情和图片)😜这是使用 Quill 最简单的方式。2 加一些配置选项吧2.1 配置编辑器容器元素 containerQuill 类一共有两个参数,第一个参数是必选的编辑器容器元素container,可以是一个CSS选择器,比如前面的#editor,也可以是一个DOM元素,比如:const container = document.getElementById('editor'); // const container = document.querySelector('#editor'); // const container = $('#editor').get(0); const quill = new Quill(container);如果容器里面已经有一些 HTML 元素,那么初始化 Quill 的时候,那些元素也会渲染出来,比如:<div id="editor"> <p>Quill: An API Driven Rich Text Editor</p> <h2>BUILT FOR DEVELOPERS</h2> <p>Granular access to the editor's content, changes and events through a simple API. Works consistently and deterministically with JSON as both input and output.</p> <h2>CROSS PLATFORM</h2> <p>Supports all modern browsers on desktops, tablets and phones. Experience the same consistent behavior and produced HTML across platforms.</p> <h2>FITS LIKE A GLOVE</h2> <p>Used in small projects and giant Fortune 500s alike. Start simple with the Quill core then easily customize or add your own extensions later if your product needs grow.</p> </div>渲染出来的编辑器效果:2.2 配置选项 options第二个参数是可选的配置选项options,options是一个JSON对象,比如我们想给我们的编辑器增加一个主题,使它不再那么单调。const quill = new Quill('#editor', { theme: 'snow' });另外需要引入该主题对应的样式:@import 'quill/dist/quill.snow.css';这时我们看到编辑器已经有一个工具栏。并且可以通过工具栏对编辑器的内容进行操作,比如给Quill增加一个超链接:除了snow主题,Quill 还内置了一个bubble气泡主题,配置方式和snow主题一样:引入主题样式在options里配置主题// 引入bubble主题样式 @import 'quill/dist/quill.bubble.css';const quill = new Quill('#editor', { theme: 'bubble' // 配置 bubble 主题 });效果如下:bubble主题没有显性的工具栏,它会在你选中编辑器中的文本时,在选中文本的下方显示一个气泡工具栏,从而对文本进行格式化操作,比如给选中的段落增加引用格式:3 更多配置选项Quill 不仅仅可以配置主题,options一共支持8个配置选项:bounds 编辑器内浮框的边界debug debug级别formats 格式白名单modules 模块placeholder 占位文本readOnly 只读模式scrollingContainer 滚动容器theme 主题3.1 formats 格式白名单这个配置项非常有用,比如刚刚提到的掘金评论框,我们发现评论框里只能插入纯文本,其他格式都不允许,即使时粘贴进来的格式化文本也会变成纯文本。在 Quill 里很容易实现,只需要配置formats为空数组即可。const quill = new Quill('#editor', { theme: 'snow', formats: [] });注意这里的formats格式白名单,控制的是内容实际的格式,和设置格式的渠道无关,比如formats设置为空,那么无论是:通过工具栏设置格式还是通过快捷键(比如Ctrl+B)设置格式亦或是粘贴带格式的文本都是无法设置格式的。如果我们想保留一部分格式,比如只保留粗体和列表两种格式:const quill = new Quill('#editor', { theme: 'snow', formats: [ 'bold', 'list' ] });Quill 一共支持11种行内格式:backgroundboldcolorfontcodeitaliclinksizestrikescriptunderline7种块级格式:blockquoteheaderindentlistaligndirectioncode-block3种嵌入格式:formulaimagevideo不配置formats选项,会默认支持所有的21种格式。3.2 placeholder 占位文本我们发现掘金的评论框在没有输入内容时,会有一个平等表达,友善交流的占位文本。这可以很容易地通过配置placeholder选项实现。const quill = new Quill('#editor', { formats: [], placeholder: '平等表达,友善交流', });3.3 readOnly 只读模式通过配置readOnly可以实现:初始状态编辑器是阅读态,不可以编辑,可以通过点击编辑按钮让编辑器变成编辑态。3.4 modules 模块配置这个配置项放在最后并不代表它不重要,恰恰相反,这是 Quill 中最重量级也是最常用的配置。Quill 一共有6个内置模块:Clipboard 粘贴版History 操作历史Keyboard 键盘事件Syntax 语法高亮Toolbar 工具栏Uploader 文件上传每个模块的用途详见Quill内置模块章节。modules选项可以用来配置这些模块。在后面原理篇的文章中,我也会给大家详细介绍 Quill 模块的工作原理,敬请期待!3.4.1 配置 toolbar 模块Quill 默认只在工具栏中显示一部分格式化按钮,里面没有插入图片的按钮,我们可以通过配置toolbar模块来增加。const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: [ // 默认的 [{ header: [1, 2, 3, false] }], ['bold', 'italic', 'underline', 'link'], [{ list: 'ordered'}, { list: 'bullet' }], ['clean'], // 新增的 ['image'] ] } });如果想做一个掘金这样的编辑器,也非常简单。掘金的富文本编辑器主要包含以下工具栏按钮:标题加粗斜体引用超链接插入图片插入视频行内代码代码块无序列表有序列表删除线对齐方式公式有些掘金编辑器的功能,Quill 是没有的,所以没展示出来。使用 Quill 实现,需要这样配置toolbar模块。const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: [ { header: [1, 2, 3, 4, 5, 6, false] }, // 标题 'bold', // 加粗 'italic', // 斜体 'blockquote', // 引用 'link', // 超链接 'image', // 插入图片 'video', // 插入视频 'code', // 行内代码 'code-block', // 代码块 { list: 'bullet' }, // 无序列表 { list: 'ordered'}, // 有序列表 'strike', // 删除线 { 'align': [] }, // 对齐方式 'formula' // 公式 ] } }); 稍微修改下样式,就能做出一个和掘金富文本编辑器差不多的富文本编辑器啦,效果如下:以下是和掘金实际的富文本编辑器的对比图:对比以上效果对比图,除了工具栏的 icon 有差异之外,其他几乎是一样的。3.4.2 配置 keyboard 模块除了工具栏模块,我们还可以配置别的模块,比如快捷键模块keyboard,keyboard模块默认支持很多快捷键,比如:加粗的快捷键是Ctrl+B;超链接的快捷键是Ctrl+K;撤销/回退的快捷键是Ctrl+Z/Y。但它不支持删除线的快捷键,如果我们想定制删除线的快捷键,假设是Ctrl+Shift+S,可以这样配置:const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: [ // 默认的 [{ header: [1, 2, 3, false] }], ['bold', 'italic', 'underline', 'link'], [{ list: 'ordered'}, { list: 'bullet' }], ['clean'], ['image'] ], // 新增的 keyboard: { bindings: { strike: { key: 'S', ctrlKey: true, shiftKey: true, handler: function(range, context) { // 获取当前光标所在文本的格式 const format = this.quill.getFormat(range); // 增加/取消删除线 this.quill.format('strike', !format.strike); } }, } }, } });3.4.3 配置 history 模块Quill 内置的history模块,每隔1s会记录一次操作历史,并放入历史操作栈(最大100)中,便于撤销/回退操作。如果我们不想记录得那么频繁,想2s记录一次,另外我们想增加操作栈的大小,最大记录200次操作历史,可以这样配置:const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: [ // 默认的 [{ header: [1, 2, 3, false] }], ['bold', 'italic', 'underline', 'link'], [{ list: 'ordered'}, { list: 'bullet' }], ['clean'], ['image'] ], keyboard: { bindings: { strike: { key: 'S', ctrlKey: true, shiftKey: true, handler: function(range, context) { // 获取当前光标所在文本的格式 const format = this.quill.getFormat(range); // 增加/取消删除线 this.quill.format('strike', !format.strike); } }, } }, // 新增的 history: { delay: 2000, // 2s记录一次操作历史 maxStack: 200, // 最大记录200次操作历史 } } });小结本文主要介绍了 Quill 的基本用法,以及如何通过 options 选项配置 Quill,options 包含丰富的选项,可以快速配置出一个掘金文章编辑器。Quill 是一个 API 驱动的富文本编辑器,下篇我将给大家介绍更加丰富的 Quill API,如何通过 Quill API 操纵编辑器内容。
0
0
0
浏览量511
武士先生

深入浅出 Quill 系列之原理篇1:现代富文本编辑器 Quill 的模块化机制

引言如果还没有接触过 Quill,建议先阅读以下两篇文章,了解下它的基本概念。深入浅出 Quill 系列之使用篇1:Quill 基本使用和配置深入浅出 Quill 系列之使用篇2:通过 Quill API 实现对编辑器内容的完全控制通过阅读本文,你将收获:了解 Quill 模块是什么,怎么配置 Quill 模块为什么要创建 Quill 模块,怎么创建自定义 Quill 模块Quill 模块如何与 Quill 进行通信深入了解 Quill 的模块化机制1 Quill 模块初探使用 Quill 开发过富文本应用的人,应该都对 Quill 的模块有所了解。比如,当我们需要定制自己的工具栏按钮时,会配置工具栏模块:var quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: [['bold', 'italic'], ['link', 'image']] } });其中的 modules 参数就是用来配置模块的。toolbar 参数用来配置工具栏模块,这里传入一个二维数组,表示分组后的工具栏按钮。渲染出来的编辑器将包含4个工具栏按钮:要看以上 Demo,请怒戳配置工具栏模块。1.1 Quill 模块是一个普通的 JS 类那么 Quill 模块是什么呢?我们为什么要了解和使用 Quill 模块呢?Quill 模块其实就是一个普通的 JavaScript类,有构造函数,有成员变量,有方法。以下是工具栏模块的大致源码结构:class Toolbar { constructor(quill, options) { // 解析传入模块的工具栏配置(就是前面介绍的二维数组),并渲染工具栏 } addHandler(format, handler) { this.handlers[format] = handler; } ... }可以看到工具栏模块就是一个普通的 JS 类。在构造函数中传入了 quill 的实例和 options 配置,模块类拿到 quill 实例就可以对编辑器进行控制和操作。比如:工具栏模块会根据 options 配置构造工具栏容器,将按钮/下拉框等元素填充到该容器中,并绑定按钮/下拉框的处理事件。最终的结果就是在编辑器主体上方渲染了一个工具栏,可以通过工具栏按钮/下拉框给编辑器内的元素设置格式,或者在编辑器中插入新元素。Quill 模块的功能很强大,我们可以利用它来扩展编辑器的能力,实现我们想要的功能。除了工具栏模块之外,Quill 还内置了一些很实用的模块,我们一起来看看吧。1.2 Quill 内置模块Quill 一共内置6个模块:Clipboard 粘贴版History 操作历史Keyboard 键盘事件Syntax 语法高亮Toolbar 工具栏Uploader 文件上传Clipboard、History、Keyboard 是 Quill 必需的内置模块,会自动开启,可以配置但不能取消。其中:Clipboard 模块用于处理复制/粘贴事件、HTML 元素节点的匹配以及 HTML 到 Delta 的转换。History 模块维护了一个操作的堆栈,记录了每一次的编辑器操作,比如插入/删除内容、格式化内容等,可以方便地实现撤销/重做等功能。Keyboard 模块用于配置键盘事件,为实现快捷键提供便利。Syntax 模块用于代码语法高亮,它依赖外部库 highlight.js,默认关闭,要使用语法高亮功能,必须安装 highlight.js,并手动开启该功能。其他模块不多做介绍,想了解可以参考 Quill 的模块文档。1.3 Quill 模块的配置刚才提到 Keyboard 键盘事件模块,我们再举一个例子,加深对 Quill 模块配置的理解。Keyboard 模块默认支持很多快捷键,比如:加粗的快捷键是 Ctrl+B;超链接的快捷键是 Ctrl+K;撤销/回退的快捷键是 Ctrl+Z/Y。但它不支持删除线的快捷键,如果我们想定制删除线的快捷键,假设是 Ctrl+Shift+S,我们可以这样配置:modules: { keyboard: { bindings: { strike: { key: 'S', ctrlKey: true, shiftKey: true, handler: function(range, context) { const format = this.quill.getFormat(range); this.quill.format('strike', !format.strike); } }, } }, toolbar: [['bold', 'italic', 'strike'], ['link', 'image']] }要看以上 Demo,请怒戳配置键盘模块。在使用 Quill 开发富文本编辑器的过程中,我们会遇到各种模块,也会创建很多自定义模块,所有模块都是通过 modules 参数进行配置的。接下来我们将尝试创建一个自定义模块,加深对 Quill 模块和模块配置的理解。2 创建自定义模块通过上一节的介绍,我们了解到其实 Quill 模块就是一个普通的JS类,并没有什么特殊的,在该类的初始化参数中会传入 Quill 实例和该模块的 options 配置参数,然后就可以控制并增强编辑器的功能。当 Quill 内置模块无法满足我们的需求时,就需要创建自定义模块来实现我们想要的功能。比如:我们想实现一个统计编辑器当前字数的功能,就可以通过自定义模块来实现的,下面我们将一步一步介绍如何将该功能封装成独立的 Counter 模块。创建一个 Quill 模块分三步:2.1 第一步:创建模块类新建一个 JS 文件,里面是一个普通的 JavaScript 类。class Counter { constructor(quill, options) { console.log('quill:', quill); console.log('options:', options); } } export default Counter;这是一个空类,什么都没有,只是在初始化方法中打印了 Quill 实例和模块的 options 配置信息。2.2 第二步:配置模块参数modules: { toolbar: [ ['bold', 'italic'], ['link', 'image'] ], counter: true }我们先不传配置数据,只是简单地将该模块启用起来,结果发现并没有打印信息。2.3 第三步:注册模块要使用一个模块,需要在 Quill 初始化之前先调用 Quill.register 方法注册该模块类(后面我们详细介绍其中的原理),并且由于我们需要扩展的是模块(module),所以前缀需要以 modules 开头:import Quill from 'quill'; import Counter from './counter'; Quill.register('modules/counter', Counter);这时我们能看到信息已经打印出来。2.4 添加模块的逻辑这时我们在 Counter 模块中加点逻辑,用于统计当前编辑器内容的字数:constructor(quill, options) { this.container = quill.addContainer('ql-counter'); quill.on(Quill.events.TEXT_CHANGE, () => { const text = quill.getText(); // 获取编辑器中的纯文本内容 const char = text.replace(/\s/g, ''); // 使用正则表达式将空白字符去掉 this.container.innerHTML = `当前字数:${char.length}`; }); }在 Counter 模块的初始化方法中,我们调用 Quill 提供的addContainer方法,为编辑器增加一个空的容器,用于存放字数统计模块的内容,然后绑定编辑器的内容变更事件,这样当我们在编辑器中输入内容时,字数能实时统计。在 Text Change 事件中,我们调用 Quill 实例的 getText 方法获取编辑器里的纯文本内容,然后用正则表达式将其中的空白字符去掉,最后将字数信息插入到字符统计的容器中。展示的大致效果如下:要看以上 Demo,请怒戳自定义字符统计模块。3 模块加载机制对 Quill 模块有了初步的理解之后,我们就会想知道 Quill 模块是如何运作的,下面将从 Quill 的初始化过程切入,通过工具栏模块的例子,深入探讨 Quill 的模块加载机制。(本小结涉及 Quill 源码的解析,有不懂的地方欢迎留言讨论)3.1 Quill 类的初始化当我们执行 new Quill() 的时候,会执行 Quill 类的 constructor 方法,该方法位于 Quill 源码的 core/quill.js 文件中。初始化方法的大致源码结构如下(移除模块加载无关的代码):constructor(container, options = {}) { this.options = expandConfig(container, options); // 扩展配置数据,包括增加主题类等 ... this.theme = new this.options.theme(this, this.options); // 1.使用options中的主题类初始化主题实例 // 2.增加必需模块 this.keyboard = this.theme.addModule('keyboard'); this.clipboard = this.theme.addModule('clipboard'); this.history = this.theme.addModule('history'); this.theme.init(); // 3.初始化主题,这个方法是模块渲染的核心(实际的核心是其中调用的addModule方法),会遍历配置的所有模块类,并将它们渲染到DOM中 ... }Quill 在初始化时,会使用 expandConfig 方法对传入的 options 进行扩展,加入主题类等元素,用于初始化主题。(不配置主题也会有默认的 BaseTheme 主题)之后调用主题实例的 addModule 方法将内置必需模块挂载到主题实例中。最后调用主题实例的 init 方法将所有模块渲染到 DOM。(后面会详细介绍其中的原理)如果是 snow 主题,此时将会看到编辑器上方出现工具栏:如果是 bubble 主题,那么当选中一段文本时,会出现工具栏浮框:接下来我们以工具栏模块为例,详细介绍 Quill 模块的加载和渲染原理。3.2 工具栏模块的加载以 snow 主题为例,当初始化 Quill 实例时配置以下参数:{ theme: 'snow', modules: { toolbar: [['bold', 'italic', 'strike'], ['link', 'image']] } }Quill 的 constructor 方法中获取到的 this.theme 是 SnowTheme 类的实例,执行 this.theme.init() 方法时调用的是其父类 Theme 的 init 方法,该方法位于 core/theme.js 文件。init() { // 遍历Quill options中的modules参数,将所有用户配置的modules挂载到主题类中 Object.keys(this.options.modules).forEach(name => { if (this.modules[name] == null) { this.addModule(name); } }); }它会遍历 options.modules 参数中的所有模块,调用 BaseTheme 的 addModule 方法,该方法位于 themes/base.js 文件。addModule(name) { const module = super.addModule(name); if (name === 'toolbar') { this.extendToolbar(module); } return module; }该方法会先执行其父类的 addModule 方法,将所有模块初始化,如果是工具栏模块,则会在工具栏模块初始化之后对工具栏模块进行额外的处理,主要是构建 icons 和绑定超链接快捷键。我们再回过头来看下 BaseTheme 的 addModule 方法,该方法是模块加载的核心。该方法前面我们介绍 Quill 的初始化时已经见过,加载三个内置必需模块时调用过。其实所有模块的加载都会经过该方法,因此有必要研究下这个方法,该方法位于 core/theme.js。addModule(name) { const ModuleClass = this.quill.constructor.import(`modules/${name}`); // 导入模块类,创建自定义模块的时候需要通过Quill.register方法将类注册到Quill,才能导入 // 初始化模块类 this.modules[name] = new ModuleClass( this.quill, this.options.modules[name] || {}, ); return this.modules[name]; }addModule 方法会先调用 Quill.import 方法导入模块类(通过 Quill.register 方法注册过的才能导入)。然后初始化该类,将其实例挂载到主题类的 modules 成员变量中(此时该成员变量已有内置必须模块的实例)。以工具栏模块为例,在 addModule 方法中初始化的是 Toolbar 类,该类位于 modules/toolbar.js 文件。class Toolbar { constructor(quill, options) { super(quill, options); // 解析modules.toolbar参数,生成工具栏结构 if (Array.isArray(this.options.container)) { const container = document.createElement('div'); addControls(container, this.options.container); quill.container.parentNode.insertBefore(container, quill.container); this.container = container; } else { ... } this.container.classList.add('ql-toolbar'); // 绑定工具栏事件 this.controls = []; this.handlers = {}; Object.keys(this.options.handlers).forEach(format => { this.addHandler(format, this.options.handlers[format]); }); Array.from(this.container.querySelectorAll('button, select')).forEach( input => { this.attach(input); }, ); ... } }工具栏模块初始化时会先解析 modules.toolbar 参数,调用 addControls 方法生成工具栏按钮和下拉框(基本原理就是遍历一个二维数组,将它们以按钮/下拉框形式插入到工具栏中),并为它们绑定事件。function addControls(container, groups) { if (!Array.isArray(groups[0])) { groups = [groups]; } groups.forEach(controls => { const group = document.createElement('span'); group.classList.add('ql-formats'); controls.forEach(control => { if (typeof control === 'string') { addButton(group, control); } else { const format = Object.keys(control)[0]; const value = control[format]; if (Array.isArray(value)) { addSelect(group, format, value); } else { addButton(group, format, value); } } }); container.appendChild(group); }); }工具栏模块就这样被加载并渲染到富文本编辑器中,为编辑器操作提供便利。现在对模块的加载过程做一个小结:模块加载的起点是 Theme 类的 init 方法,该方法将 option.modules 参数里配置的所有模块加载到主题类的成员变量 modules 中,并与内置必需模块合并;addModule 方法会先通过 import 方法导入模块类,然后通过 new 关键字创建模块实例;创建模块实例时会执行模块的初始化方法,执行模块的具体逻辑。以下是模块与编辑器实例的关系图:总结本文先通过2个例子简单介绍了 Quill 模块的配置方法,让大家对 Quill 模块有个直观初步的印象。然后通过字符统计模块这个简单的例子介绍如何开发自定义 Quill 模块,对富文本编辑器的功能进行扩展。最后通过剖析 Quill 的初始化过程,逐步切入 Quill 模块的加载机制,并详细阐述了工具栏模块的加载过程。
0
0
0
浏览量578
武士先生

为什么需要TypeScript以及如何配置与debug?

为什么我们需要TypeScript?什么是TypeScript?在我们学习一项技能前,我们应该先了解这是个什么东西。TypeScript(后文简称为TS)是JavaScript(后文简称为JS)的超集。支持JS包括实验特性的所有写法,你也可以在TS文件中写纯JS代码。此外,TS也有很多JS没有的新特性,比如抽象类,枚举等等。而TS相较JS最重要的功能就是类型,实际上你可以认为TS就是带类型的JS。当然,在JS中也有如string,number等基本类型,而对于一个对象,它的类型都是object,而在TS中,允许你声明对象的类型。可能你会觉得这很麻烦,跟JS相比太不灵活了,但实际上TS的类型并不死板,你可以使用泛型让其变得灵活。TS的出现解决了什么?有些人认为JS的初衷就是能够简单快速的编写web应用,而TS相对来说复杂太多。而伴随着web应用的功能的增多,代码复杂度也相应的增加。JS作为动态类型的缺点就大于优点了。在JS中如果是由于类型而导致的错误,我们得在运行后才能找到问题,但是使用TS我们能够在写代码时就发现问题,这带来的时间收益无疑是巨大的。我能用TS来干什么?有很多朋友想要使用一种编程语言做一个完整的web项目,因为这样我们能够少花很多精力。要知道,JS是一门一专多能的语言,而TS是JS的超集,你能在任何可以使用JS的地方使用TS。你可以使用TS来构建web应用,使用vue,react来编写前端(在浏览器中js几乎是你的唯一选择),使用node+nestjs来编写后端(在大多数时候完全够用,并且你能够通过TS学习设计模式)。你也可以使用electricon来编写桌面端程序(比如vscode就是使用electricon+ts编写的),使用babylon.js来写web游戏或者在web中进行模型展示等等。TS编写起来太慢了?光看代码,TS确实比JS写得更多。但在vscode中,我们使用TS能够得到完善的代码补全的提示,比如这个数组的元素为number类型,我们得到的代码补全提示都是number类型的方法:而且我们写程序时,写代码的时间占少数,有很多时间是在找bug或者进行测试。而TS能够帮助我们在编写代码时就提前避免错误,也大大减少了bug数,要让编程的效率高,我们应该在减少bug和进行测试下功夫。而不是单纯的看代码量的多少。比如我们这里的函数有可能就没有返回值,在JS中我们是没有这个提示的,(当然,这需要配置tsconfig.json文件。在后面的文章中会提到这个配置)所以为了让项目遵循"软件工程学",我们得学习TS并且在项目中使用它。并且,你可以发现各个框架,库都开始使用TS来代替JS了。而我们使用TS能够得到更好的代码补全的提示。TS的安装npm install -g typescript我们先使用npm或者pnpm来全局安装ts如何运行TS文件首先我们需要知道的是ts暂时还不能在浏览器上直接运行(TS团队正在为此做出努力),也就是说所有ts文件最后都是要编译成js文件的。我们使用ts的编译器只是为了使用它的类型检测以及一些特性。在项目中,我们会借助打包工具(如vite,webpack)将其编译成js文件并打包。但在我们学习ts时,使用ts的tsc指令进行编译就行了。生成js文件(一次性)tsc 文件名然后使用node指令来运行js文件node demo.js监听,每次保存会自动编译后修改js文件tsc 文件名 -w或者安装ts-nodenpm install -g ts-node npm install -D tslib @types/node然后使用ts-node指令运行文件ts-node demo.tstsconfig.json文件配置tsconfig.json文件是ts编译器的配置文件,我们可以通过这个配置文件来规范我们的代码,比如:“如果重写方法必须使用override关键字”。这将能够使我们能够写出更加规范的代码。下面我们做一些简单的配置:生成tsconfig文件在终端输入tsc --init"target": "es2016"输出的js文件的标准,建议使用es2016"rootDir": "./src",ts的根文件目录"outDir": "./dist",ts生成的js文件的目录"removeComments": true,如果报错,不生成js文件"sourceMap": true,为编译好的js文件创建相应的映射文件(使用debug和断点)debug与lunch.json文件VS Code中的debug是一个非常好用工具,此外,VS Code对Node.js运行时具有内置的调试支持(其他语言需要额外下载扩展),并且可以调试 JavaScript、TypeScript 或任何其他转换为 JavaScript 的语言。那我们该怎样使用它呢?在vscode中点击debug,并创建lunch.json文件,在program下添加"preLaunchTask": "tsc: 构建 - tsconfig.json",注意:tsc后对于中文用户是构建,而对于其他语言的用户可能的是其他的字符,如英文为build你可以使用ctrl+shift+B来查看任务(如果你使用的搜狗输入法,请先切换输入法,因为有按键的冲突){  "version": "0.2.0",  "configurations": [   {      "type": "node",      "request": "launch",      "name": "Launch Program",      "skipFiles": [        "<node_internals>/**"     ],      "program": "${file}",      "preLaunchTask": "tsc: 构建 - tsconfig.json",      "outFiles": [        "${workspaceFolder}/**/*.js"     ]   } ] }现在我们配置好了文件,打开demo.ts文件,写入下面的代码:let age: number = 20; if (age < 50) age += 10;我们将第一行加入断点,这时候debug(可以使用F5快捷键)将会从第一行进行左侧有VARLABLES/变量其中,我们可以看到当前的age为undefined,那是因为let age = 20实际上是两行代码:let age; age = 20;我们可以在watch/监视中指定想要监视的变量age通过F10(第二个蓝色按钮)来执行下一行现在我们进行到第二行,可以看到age的值变成了20但我们想看到age变为30,不过由于程序结束了,我们无法在debug中得到age:30我们可以新加一行代码,让debug拿到age变化的值后再结束程序
0
0
0
浏览量551
武士先生

TypeScript与高级类型

类型别名type现在我们有这样一个代码,如果要再声明一个同样类型的对象,我们需要再重复声明一次类型。我们应该尽可能复用我们的代码。let man: { readonly name: string; age: number; retire: (date: Date) => void; } = { age: 22, name: "kevin", retire: (date: Date) => { console.log(date); }, };所以我们使用了type关键字type Man = { readonly name: string; age: number; retire: (date: Date) => void; }; let man: Man = { age: 22, name: "kevin", retire: (date: Date) => { console.log(date); }, };当然,你也可以使用接口interface关键字,这个关键字我们会在后面讲。联合类型|我们可以使用联合类型,来声明一个变量的类型为多个类型的子集。使用|来声明联合类型:但是,这时候,我们发现使用ts的代码补全时,只有number和string类型的共同的方法。所以,我们要想办法将类型范围“缩小”:可以看到:现在的代码补全就是对应类型的方法了我们再查看编译出的js代码:"use strict"; const func = (param) => { if (typeof param === "number") return param; else return parseInt(param) * 1.2; };可以看到,我们所以联合类型只是ts编译器对变量进行的类型检查交叉类型&我们可以使用&来表示同时满足多个类型,也许你会想到这样声明一行代码:let number_string: number & string;但这是不合理的,因为没有值能够既是number类型又是string类型我们通常用它来声明一个类型:type Draggable = { drag: () => void; }; type Resizable = { resize: () => void; }; type UIWidget = Draggable & Resizable; let textBox: UIWidget = { drag: () => {}, resize: () => {}, };字面量类型假设我们有一个变量为quantitylet quantity: number;这个变量的类型为number,也就是说我们可以赋值任何数值给这个变量。但也许我们就只想给它赋值固定的数值?ts允许我们使用字面量来声明类型,如string,number,boolean类型的值let quantity: 50 = 100;//报错:不能将类型“100”分配给类型“50”。ts(2322)或许你觉得这没有什么用,但当我们使用联合类型时,或许就有用了:let quantity: 50 | 100 = 100;当然,我们应该这样写:type Quantity = 50 | 100; let quantity: Quantity = 100;对于字符串和布尔类型:type Metric = "cm" | "m"; type Bools = true | false;null类型只有null为null类型,只有undefined为undefined类型假如我们有一个函数:const greet = (name: string) => { console.log(name.toUpperCase()); }; greet(null);//报错:类型“null”的参数不能赋给类型“string”的参数。ts(2345)如果你想去除这个报错,你可以(不推荐)在tscofig.json中找到"strictNullChecks": true,打开并该为false或许你真的想传入null类型,你可以声明联合类型:当然,这里你不能光声明类型,因为在你使用name.toUpperCase()时它会报错:name可能为null。const greet = (name: string | null | undefined) => { if (name) console.log(name.toUpperCase()); else console.log("hello"); }; greet(null); greet(undefined);可选链/可选属性访问符?现在假定我们有一个函数,用来输出客户的生日,或许你不知道客户的名字。我们将name的类型定义为与null和undefined联合的类型type Customer = { birthday: Date; }; function getCustomer(id: number): Customer | null | undefined { return id === 0 ? null : { birthday: new Date() }; } let customer = getCustomer(0); if (customer !== null && customer !== undefined) console.log(customer.birthday);在ts中,我们可以通过可选链/可选属性访问符(在.前使用?),当这个方法/属性被定义时我们调用,如果为null或者undefinedts会返回undefined,并且有短路运算的特性。type Customer = { birthday: Date; }; function getCustomer(id: number): Customer | null | undefined { return id === 0 ? null : { birthday: new Date() }; } let customer = getCustomer(0); console.log(customer?.birthday);//undefined现在我们让Customer类型中的birthday属性为可选,然后链式调用getFullYear方法。注意加上可选属性访问符?。因为这时的customer?.birthday可能为null或者undefined:type Customer = { birthday?: Date; }; function getCustomer(id: number): Customer | null | undefined { return id === 0 ? null : { birthday: new Date() }; } let customer = getCustomer(0); console.log(customer?.birthday?.getFullYear());//注意getFullYear()前的可选属性访问符同时,我们也可以在数组和调用方法中使用:function tryGetFirstElement<T>(arr?: T[]) { return arr?.[0]; // equivalent to // return (arr === null || arr === undefined) ? // undefined : // arr[0]; }async function makeRequest(url: string, log?: (msg: string) => void) { log?.(`Request started at ${new Date().toISOString()}`); // roughly equivalent to // if (log != null) { // log(`Request started at ${new Date().toISOString()}`); // } const result = (await fetch(url)).json(); log?.(`Request finished at at ${new Date().toISOString()}`); return result; }无效合并/Nullish Coalescing??假如我们有这样的代码:let speed: number | null = null; let ride = { speed: speed ? speed !== null : 30, };在ts中,我们可以使用无效合并来简化代码:let speed: number | null = null; let ride = { speed: speed ?? 30, };当处理 null 或者 undefined 时,它可以作为一种「倒退」到默认值的方式类型断言有些时候,我们比ts编译器更清楚变量/常量的类型let phone = document.getElementById("phone");//let phone: HTMLElement | null假设我们真的有一个元素的id为phone,那么变量phone的类型就应该为HTMLElement而不是HTMLElement | null并且前面的经验也告诉我们这不利于代码补全。我们可以使用as关键字let phone = document.getElementById("phone") as HTMLElement;并且这时候的代码推断的提示也是HTMLElement类型的方法。当然,我们也可以使用尖括号来进行断言<>let phone = <HTMLElement>document.getElementById("phone");unknown比any更安全之前我们已经讲过,any虽然会让我们避免报错,但大量的使用any会让我们失去使用ts的意义。同样的ts也不鼓励我们使用any,而是推荐我们使用unknown,意思是”不知道是什么类型“。不过我们直接使用unknown时会报错:function reder(document: unknown) { document.toUpperCase();//报错:对象的类型为 "unknown"。ts(2571) }这是 unknown 类型的主要价值主张:TypeScript 不允许我们对类型为 unknown 的值执行任意操作。相反,我们必须首先执行某种类型检查以缩小我们正在使用的值的类型范围。像前面提到的那样,我们进行类型范围的缩小:function reder(document: unknown) { if (typeof document === "string") document.toUpperCase(); }但typeof只能用于基本类型,而像自定义类型,我们需要用到instanceof关键字class Behavior { move() {} } function reder(document: unknown) { if (document instanceof Behavior) document.move(); }never类型never代表永远不会发生的类型。它是 TypeScript 中的底层类型。它自然被分配的一些例子:一个从来不会有返回值的函数(如:如果函数内含有 while(true) {});一个总是会抛出错误的函数(如:function foo() { throw new Error('Not Implemented') },foo 的返回类型是 never);例如:function processEvents(): never { while (true) {} } processEvents(); console.log("never do");//这行代码在vscode中会呈灰色,如果不使用never类型,这会看上去能够执行我们还可以在tsconfig.json中找到"allowUnreachableCode": true, 打开并改为false这表示我们不允许不能到达的代码现在我们得到了一个提示,这对我们是很有用的:这也是为什么我们要使用never。同样的:let bar: (err: string) => never = (err: string) => { throw new Error(err + ":Throw my hands in the air like I just dont care"); }; bar("..."); console.log("never do"); //检测到无法访问的代码。ts(7027)
0
0
0
浏览量541
武士先生

深入浅出 Quill 系列之原理篇2:现代富文本编辑器 Quill 的内容渲染机制

引言在 Web 开发领域,富文本编辑器( Rich Text Editor )是一个使用场景非常广,又非常复杂的组件。要从0开始做一款好用、功能强大的富文本编辑器并不容易,基于现有的开源库进行开发能节省不少成本。Quill 是一个很不错的选择。上一篇文章给大家介绍了 Quill 的模块化机制,本文主要介绍 Quill 内容渲染相关的基本原理,主要包括:Quill 描述编辑器内容的方式Quill 将 Delta 渲染到 DOM 的基本原理Scroll 类管理所有子 Blot 的基本原理1 Quill 如何描述编辑器内容?1.1 Quill 简介Quill 是一款API驱动、易于扩展和跨平台的现代 Web 富文本编辑器。目前在 Github 的 star 数已经超过25k。Quill 使用起来也非常方便,简单几行代码就可以创建一个基本的编辑器:<script> var quill = new Quill('#editor', { theme: 'snow' }); </script>1.2 Quill 如何描述格式化的文本当我们在编辑器里面插入一些格式化的内容时,传统的做法是直接往编辑器里面插入相应的 DOM,通过比较 DOM 树来记录内容的改变。直接操作 DOM 的方式有很多不便,比如很难知道编辑器里面某些字符或者内容到底是什么格式,特别是对于自定义的富文本格式。Quill 在 DOM 之上做了一层抽象,使用一种非常简洁的数据结构来描述编辑器的内容及其变化:Delta。Delta 是 JSON 的一个子集,只包含一个 ops 属性,它的值是一个对象数组,每个数组项代表对编辑器的一个操作(以编辑器初始状态为空为基准)。比如编辑器里面有"Hello World":用 Delta 进行描述如下:{ "ops": [ { "insert": "Hello " }, { "insert": "World", "attributes": { "bold": true } }, { "insert": "\n" } ] }意思很明显,在空的编辑器里面插入"Hello ",在上一个操作后面插入加粗的"World",最后插入一个换行"\n"。1.3 Quill 如何描述内容的变化Delta 非常简洁,但却极富表现力。它只有3种动作和1种属性,却足以描述任何富文本内容和任意内容的变化。3种动作:insert:插入retain:保留delete:删除1种属性:attributes:格式属性比如我们把加粗的"World"改成红色的文字"World",这个动作用 Delta 描述如下:{ "ops": [ { "retain": 6 }, { "retain": 5, "attributes": { "color": "#ff0000" } } ] }意思是:保留编辑器最前面的6个字符,即保留"Hello "不动,保留之后的5个字符"World",并将这些字符设置为字体颜色为"#ff0000"。如果要删除"World",相信聪明的你也能猜到怎么用 Delta 描述,没错就是你猜到的:{ "ops": [ { "retain": 6 }, { "delete": 5 } ] }1.4 Quill 如何描述富文本内容最常见的富文本内容就是图片,Quill 怎么用 Delta 描述图片呢?insert 属性除了可以是用于描述普通字符的字符串格式之外,还可以是描述富文本内容的对象格式,比如图片:{ "ops": [ { "insert": { "image": "https://quilljs.com/assets/images/logo.svg" } }, { "insert": "\n" } ] }比如公式:{ "ops": [ { "insert": { "formula": "e=mc^2" } }, { "insert": "\n" } ] }Quill 提供了极大的灵活性和可扩展性,可以自由定制富文本内容和格式,比如幻灯片、思维导图,甚至是3D模型。2 setContent 如何将 Delta 数据渲染成 DOM?上一节我们介绍了 Quill 如何使用 Delta 描述编辑器内容及其变化,我们了解到 Delta 只是普通的 JSON 结构,只有3种动作和1种属性,却极富表现力。那么 Quill 是如何应用 Delta 数据,并将其渲染到编辑器中的呢?2.1 setContents 初探Quill 中有一个 API 叫 setContents,可以将 Delta 数据渲染到编辑器中,本期将重点解析这个 API 的实现原理。还是用上一期的 Delta 数据作为例子:const delta = { "ops": [ { "insert": "Hello " }, { "insert": "World", "attributes": { "bold": true } }, { "insert": "\n" } ] }当使用 new Quill() 创建好 Quill 的实例之后,我们就可以调用它的 API 啦。const quill = new Quill('#editor', { theme: 'snow' });我们试着调用下 setContents 方法,传入刚才的 Delta 数据:quill.setContents(delta);编辑器中就出现了我们预期的格式化文本:2.2 setContents 源码通过查看 setContents 的源码,发现就调用了 modify 方法,主要传入了一个函数:setContents(delta, source = Emitter.sources.API) { return modify.call( this, () => { delta = new Delta(delta); const length = this.getLength(); const deleted = this.editor.deleteText(0, length); const applied = this.editor.applyDelta(delta); ... // 为了方便阅读,省略了非核心代码 return deleted.compose(applied); }, source, ); }使用 call 方法调用 modify 是为了改变其内部的 this 指向,这里指向的是当前的 Quill 实例,因为 modify 方法并不是定义在 Quill 类中的,所以需要这么做。我们先不看 modify 方法,来看下传入 modify 方法的匿名函数。该函数主要做了三件事:把编辑器里面原有的内容全部删除应用传入的 Delta 数据,将其渲染到编辑器中返回1和2组合之后的 Delta 数据我们重点看第2步,这里涉及到 Editor 类的 applyDelta 方法。2.3 applyDelta 方法解析根据名字大概能猜到该方法的目的是:把传入的 Delta 数据应用和渲染到编辑器中。它的实现我们大概也可以猜测就是:循环 Delta 里的 ops 数组,一个一个地应用到编辑器中。它的源码一共54行,大致如下:applyDelta(delta) { let consumeNextNewline = false; this.scroll.update(); let scrollLength = this.scroll.length(); this.scroll.batchStart(); const normalizedDelta = normalizeDelta(delta); normalizedDelta.reduce((index, op) => { const length = op.retain || op.delete || op.insert.length || 1; let attributes = op.attributes || {}; // 1.插入文本 if (op.insert != null) { if (typeof op.insert === 'string') { // 普通文本内容 let text = op.insert; ... // 为了阅读方便,省略非核心代码 this.scroll.insertAt(index, text); ... // 为了阅读方便,省略非核心代码 } else if (typeof op.insert === 'object') { // 富文本内容 const key = Object.keys(op.insert)[0]; // There should only be one key if (key == null) return index; this.scroll.insertAt(index, key, op.insert[key]); } scrollLength += length; } // 2.对文本进行格式化 Object.keys(attributes).forEach(name => { this.scroll.formatAt(index, length, name, attributes[name]); }); return index + length; }, 0); ... // 为了阅读方便,省略非核心代码 this.scroll.batchEnd(); this.scroll.optimize(); return this.update(normalizedDelta); }和我们猜测的一样,该方法就是用 Delta 的 reduce 方法对传入的 Delta 数据进行迭代,将插入内容和删除内容的逻辑分开了,插入内容的迭代里主要做了两件事:插入普通文本或富文本内容:insertAt格式化该文本:formatAt至此,将 Delta 数据应用和渲染到编辑器中的逻辑,我们已经解析完毕。 下面做一个总结:setContents 方法本身没有什么逻辑,仅仅是调用了 modify 方法而已在传入 modify 方法的匿名函数中调用了 Editor 对象的 applyDelta 方法applyDelta 方法对传入的 Delta 数据进行迭代,并依次插入/格式化/删除 Delta 数据所描述的编辑器内容3 Scroll 如何管理所有的 Blot 类型?上一节我们介绍了 Quill 将 Delta 数据应用和渲染到编辑器中的原理:通过迭代 Delta 中的 ops 数据,将 Delta 行一个一个渲染到编辑器中。了解到最终内容的插入和格式化都是通过调用 Scroll 对象的方法实现的,Scroll 对象到底是何方神圣?在编辑器的操作中发挥了什么作用?3.1 Scroll 对象的创建‍上一节的解析终止于 applyDelta 方法,该方法最终调用了 this.scroll.insertAt 将 Delta 内容插入到编辑器中。applyDelta 方法定义在 Editor 类中,在 Quill 类的 setContents 方法中被调用,通过查看源码,发现 this.scroll 最初是在 Quill 的构造函数中被赋值的。this.scroll = Parchment.create(this.root, { emitter: this.emitter, whitelist: this.options.formats });Scroll 对象是通过调用 Parchment 的 create 方法创建的。前面两期我们简单介绍了 Quill 的数据模型 Delta,那么 Parchment 又是什么呢?它跟 Quill 和 Delta 是什么关系?这些疑问我们先不解答,留着后续详细讲解。先来简单看下 create 方法是怎么创建 Scroll 对象的,create 方法最终是定义在 parchment 库源码中的 registry.ts 文件中的,就是一个普通的方法:export function create(input: Node | string | Scope, value?: any): Blot { // 传入的 input 就是编辑器主体 DOM 元素(.ql-editor),里面包含了编辑器里所有可编辑的实际内容 // match 是通过 query 方法查询到的 Blot 类,这里就是 Scroll 类 let match = query(input); if (match == null) { throw new ParchmentError(`Unable to create ${input} blot`); } let BlotClass = <BlotConstructor>match; let node = input instanceof Node || input['nodeType'] === Node.TEXT_NODE ? input : BlotClass.create(value); // 最后返回 Scroll 对象 return new BlotClass(<Node>node, value); }create 方法的入参是编辑器主体 DOM 元素 .ql-editor,通过调用同文件中的 query 普通方法,查询到 Blot 类是 Scroll 类,查询的大致逻辑就是在一个 map 表里查,最后通过 new Scroll() 返回 Scroll 对象实例,赋值给 this.scroll。{ ql-cursor: ƒ Cursor(domNode, selection), ql-editor: ƒ Scroll(domNode, config), // 这个就是 Scroll 类 ql-formula: ƒ FormulaBlot(), ql-syntax: ƒ SyntaxCodeBlock(), ql-video: ƒ Video(), }3.2 Scroll 类详解Scroll 类是我们解析的第一个 Blot 格式,后续我们将遇到各种形式的 Blot 格式,并且会定义自己的 Blot 格式,用于在编辑器中插入自定义内容,这些 Blot 格式都有类似的结构。可以简单理解为 Blot 格式是对 DOM 节点的抽象,而 Parchment 是对 HTML 文档的抽象,就像 DOM 节点是构成 HTML 文档的基本单元一样,Blot 是构成 Parchment 文档的基本单元。比如:DOM 节点是<div>,对其进行封装变成<div class="ql-editor">,并在其内部封装一些属性和方法,就变成 Scroll 类。Scroll 类是所有 Blot 的根 Blot,它对应的 DOM 节点也是编辑器内容的最外层节点,所有编辑器内容都被包裹在它之下,可以认为 Scroll 统筹着其他 Blot 对象(实际 Scroll 的父类 ContainerBlot 才是幕后总 BOSS,负责总的调度)。<div class="ql-editor" contenteditable="true"> <p> Hello <strong>World</strong> </p> ... // 其他编辑器内容 </div>Scroll 类定义在 Quill 源码中的 blots/scroll.js 文件中,之前 applyDelta 方法中通过 this.scroll 调用的 insertAt / formatAt / deleteAt / update / batchStart / batchEnd / optimize 等方法都在 Scroll 类中。以下是 Scroll 类的定义:class Scroll extends ScrollBlot { constructor(domNode, config) { super(domNode); ... } // 标识批量更新的开始,此时执行 update / optimize 都不会进行实际的更新 batchStart() { this.batch = true; } // 标识批量更新的结束 batchEnd() { this.batch = false; this.optimize(); } // 在制定位置删除制定长度的内容 // 比如:deleteAt(6, 5) 将删除 "World" // 在 Quill 的 API 中对应 deleteText(index, length, source) 方法 deleteAt(index, length) {} // 设置编辑器的可编辑状态 enable(enabled = true) { this.domNode.setAttribute('contenteditable', enabled); } // 在制定位置用制定格式格式化制定长度的内容 // 比如:formatAt(6, 5, 'bold', false) 将取消 "World" 的粗体格式 // 在 Quill 的 API 中对应 formatText(index, length, name, value, source) 方法 formatAt(index, length, format, value) { if (this.whitelist != null && !this.whitelist[format]) return; super.formatAt(index, length, format, value); this.optimize(); } // 在制定位置插入内容 // 比如:insertAt(11, '\n你好,世界'); // 在 Quill 的 API 中对应 insertText(index, text, name, value, source) // Quill 中的 insertText 其实是 Scroll 的 insertAt 和 formatAt 的复合方法 insertAt(index, value, def) {} // 在某个 Blot 前面插入 Blot insertBefore(blot, ref) {} // 弹出当前位置 Blot 路径最外面的叶子 Blot(会改变原数组) leaf(index) { return this.path(index).pop() || [null, -1]; } // 实际上调用的是父类 ContainerBlot 的 descendant 方法 // 目的是得到当前位置所在的 Blot 对象 line(index) { if (index === this.length()) { return this.line(index - 1); } return this.descendant(isLine, index); } // 获取某一范围的 Blot 对象 lines(index = 0, length = Number.MAX_VALUE) {} // TODO optimize(mutations = [], context = {}) { if (this.batch === true) return; super.optimize(mutations, context); if (mutations.length > 0) { this.emitter.emit(Emitter.events.SCROLL_OPTIMIZE, mutations, context); } } // 实际上调用的是父类 ContainerBlot 的 path 方法 // 目的是得到当前位置的 Blot 路径,并排除 Scroll 自己 // Blot 路径就和 DOM 节点路径是对应的 // 比如:DOM 节点路径 div.ql-editor -> p -> strong, // 对应 Blot 路径就是 [[Scroll div.ql-editor, 0], [Block p, 0], [Bold strong, 6]] path(index) { return super.path(index).slice(1); // Exclude self } // TODO update(mutations) { if (this.batch === true) return; ... } } Scroll.blotName = 'scroll'; Scroll.className = 'ql-editor'; Scroll.tagName = 'DIV'; Scroll.defaultChild = 'block'; Scroll.allowedChildren = [Block, BlockEmbed, Container]; export default Scroll;Scroll 类上定义的静态属性 blotName 和 tagName 是必须的,前者用于唯一标识该 Blot 格式,后者对应于一个具体的 DOM 标签,一般还会定义一个 className,如果该 Blot 是一个父级 Blot,一般还会定义 allowedChildren 用来限制允许的子级 Blot 白名单,不在白名单之内的子级 Blot 对应的 DOM 将无法插入父类 Blot 对应的 DOM 结构里。Scroll 类中除了定义了插入 / 格式化 / 删除内容的方法之外,定义了一些很实用的用于获取当前位置 Blot 路径和 Blot 对象的方法,以及触发编辑器内容更新的事件。相应方法的解析都在以上源码的注释里,其中 optimize 和 update 方法涉及 Quill 中的事件和状态变更相关逻辑,放在后续单独进行解析。关于 Blot 格式的规格定义文档可以参阅以下文章:github.com/quilljs/par…我也是初次使用 Quill 进行富文本编辑器的开发,难免有理解不到位的地方,欢迎大家提意见和建议。总结本文主要介绍了 Quill 如何使用 Delta 这种数据结构来描述编辑器的内容及其变化,Delta 是一种基于 JSON 的数据结构,不仅能描述编辑器内容及其变化,而且提供了一系列方法来操作富文本内容。接着重点介绍了 setContent 方法,这个方法最主要的作用就是解析 Delta 数据,并将其渲染到 DOM 中。Quill 最核心的模块化机制和内容渲染机制的原理就给大家介绍完了,后续我将通过几个有趣的实践项目,带大家拓展自己的 Quill 模块和内容格式,为自己的编辑器增加定制化能力。
0
0
0
浏览量788
武士先生

设计模式与TypeScript装饰器(Decorator)

什么是装饰器在内部,装饰器只是一个函数。在js执行时,将目标(类,函数,属性等)传入装饰器,并执行。首先装饰器是js/ts的一个实验属性,我们需要在tsconfig.json中找到"experimentalDecorators": true,并打开它。类装饰器首先装饰器的函数名我们通常情况下使用大驼峰命名法,然后函数的参数类型取决于这个装饰器作用于哪里。比如这是个类装饰器,参数的类型就为Function,它是作用于构造函数的。这里的参数也是指的传入的类的构造函数。在使用类装饰器的时候将其写在类的上面(注意没有括号)function Component(constructor: Function): void { console.log("Component decorator called"); constructor.prototype.uniqueId = Date.now(); constructor.prototype.inserInDOM = () => { console.log("Inserting the component in the DOM"); }; } @Component class ProfileComponent {} //Component decorator called装饰器工厂现在我们想要创建参数化的装饰器// 装饰器工厂/Decorator factory function Component(value: number) { return (constructor: Function) => { console.log("Component decorator called"); constructor.prototype.options = value; constructor.prototype.uniqueId = Date.now(); constructor.prototype.inserInDOM = () => { console.log("Inserting the component in the DOM"); }; }; } @Component(1) //Component decorator called number 1 class ProfileComponent {}这看起来就像一个工厂,用于创建装饰器。这样的函数叫做装饰器工厂。我们现在让参数为对象:type ComponentOptions = { selector: string; }; // 装饰器工厂/Decorator factory function Component(options: ComponentOptions) { return (constructor: Function) => { console.log("Component decorator called"); constructor.prototype.options = options; constructor.prototype.uniqueId = Date.now(); constructor.prototype.inserInDOM = () => { console.log( "Inserting the component in the DOM:" + constructor.prototype.options.selector ); }; }; } @Component({ selector: "#my-profile" }) class ProfileComponent { inserInDOM() {}//需要声明 } let profileComponent = new ProfileComponent(); profileComponent.inserInDOM(); //Inserting the component in the DOM:#my-profile使用多个装饰器我们可以同时使用多个装饰器type ComponentOptions = { selector: string; }; // 装饰器工厂/Decorator factory function Component(options: ComponentOptions) { return (constructor: Function) => { console.log("Component decorator called"); constructor.prototype.options = options; constructor.prototype.uniqueId = Date.now(); constructor.prototype.inserInDOM = () => { console.log( "Inserting the component in the DOM:" + constructor.prototype.options.selector ); }; }; } function Pipe(constructor: Function) { console.log("Pipe decorator called"); constructor.prototype.pipe = true; } @Component({ selector: "#my-profile" }) @Pipe class ProfileComponent { inserInDOM() {} } // Pipe decorator called // Component decorator called需要注意的是,我们的装饰器是按照相反的顺序应用的。这背后的想法来自数学:在数学中如果我们有f(g(x))这样的表达式,然后我们会先求得g(x)的值然后把它传给f(x)。方法装饰器方法装饰器有三个参数:参数说明参数一普通方法是构造函数的原型对象 Prototype,静态方法是构造函数参数二方法名称参数三属性特征我们这里不会用到target和methodName,但由于我们又在tsconfig.josn中进行了配置,我们可以关闭这个配置,也可以使用带下划线_的前缀来忽略这个报错。function Log( _target: any, _methodName: string, descriptor: PropertyDescriptor ) { const original = descriptor.value as Function; descriptor.value = function () { console.log("Before"); original.call(this, "传入数据"); console.log("After"); }; } class Person { @Log say(message: string) { console.log("Person says " + message); } } let person = new Person(); person.say("dom"); // Before // Person says 传入数据 // After我们发现target的类型使用的any,虽然我们建议尽量不用any,但也不是完全不用,我们在这里并不知道target的类型。method的类型为string,descriptor属性特征的类型为PropertyDescriptor。我们在覆盖descriptor.value前将原方法保留并在新方法中调用。我们发现我们在实例对象传的参数会被忽略。因为在我们的新方法中没有参数,而是直接调用的保存好的原函数original。如果我们像不被覆盖,我们可以这样写:function Log( _target: any, _methodName: string, descriptor: PropertyDescriptor ) { const original = descriptor.value as Function; descriptor.value = function (message: string) { console.log("Before"); original.call(this, message); console.log("After"); }; } class Person { @Log say(message: string) { console.log("Person says " + message); } } let person = new Person(); person.say("dom"); // Before // Person says dom // After为了让这个装饰器能够在多个方法中使用,我们可以这样做:function Log( _target: any, _methodName: string, descriptor: PropertyDescriptor ) { const original = descriptor.value as Function; descriptor.value = function (...args: any) { console.log("Before"); original.call(this, ...args); console.log("After"); }; }值得注意的是:我们在写新方法时应该用函数表达式声明,而不是箭头函数声明。因为箭头函数没有自己的this,他们的this指向当前实例对象在访问器中使用装饰器在getter和setter访问器中我们应该如何使用装饰器呢?访问器与方法类似,所以我们和使用方法装饰器的时候一样,唯一不同的是,访问器不能使用descriptor的value属性,在使用get访问器的时候我们要使用get属性。而不是valuefunction Capitalize( _target: any, _methodName: string, descriptor: PropertyDescriptor ) { const original = descriptor.get; descriptor.get = function () { const result = original?.call(this); return typeof result === "string" ? result.toUpperCase() : result; }; } class Person { constructor(public firstName: string, public lastName: string) {} @Capitalize get fullName(): string { return `${this.firstName} ${this.lastName}`; } } let person = new Person("Kevin", "Qian"); console.log(person.fullName); //KEVIN QIAN属性装饰器这里我们使用了装饰器工厂属性装饰器的参数为:参数说明参数一普通方法是构造函数的原型对象 Prototype,静态方法是构造函数参数二属性名称function MinLength(length: number) { return (target: any, propertyName: string) => { let value: string;// 注意要先声明 const descriptor: PropertyDescriptor = { get() { return value; }, set(newValue: string) { if (newValue.length < length) throw new Error(`${propertyName} should be at least ${length}`); value = newValue; }, }; Object.defineProperty(target, propertyName, descriptor);// 我们通过这个方法来改变我们的原属性。 }; } class User { @MinLength(4) password: string; constructor(password: string) { this.password = password; } } let user = new User("5678"); console.log(user.password); //5678 // user.password = "22"; //Error: password should be at least 4 // let errUser = new User("111"); //Error: password should be at least 4我们可以看到,传入构造函数的值如果长度小于4,则会报错。并且在重新赋值属性的时候,装饰器会被重新调用。进行一个验证,当长度小于4的时候报错。参数装饰器我们并不常使用参数装饰器,但如果你正在设计一个框架供其他人使用,你可能会用到参数装饰器。我们通常情况将其用于存储这些参数的一些元数据type WatchedParameter = { methodName: string; parameterIndex: number; }; const watchedParameters: WatchedParameter[] = []; function Watch(_target: any, methodName: string, parameterIndex: number) { watchedParameters.push({ methodName, parameterIndex, }); } class Vehicle { move(@Watch _speed: number) {} } console.log(watchedParameters); //[ { methodName: 'move', parameterIndex: 0 } ]
0
0
0
浏览量518
武士先生

TypeScript中有哪些简单特性?

类型与类型推断当我们初学ts的时候,我们可能会写出这样的程序let price: number = 123_456_789;//当数字太长,你可以使用下划线来分割以获取更好的阅读性 let item: string = "TypeSript"; let isNeed: boolean = true;但实际上,ts编译器可以推断或者检测变量的类型例如,我们已经将price初始化为一个数字,那么ts编译器将能够检测到这是一个numebr类型,这时候我们声明类型就是多余的了。当我们声明了却没有初始化变量时,ts会将其推断为any类型(any类型表示所有类型,是所有类型的父类型,在ts中请尽量少的使用它)这个时候我们可以给title赋不同类型的值,这并不是我们想看到的,所以我们应该在声明没有初始化的变量时,定义变量的类型在我们声明函数时,我们可能不知道属性的类型具体是什么,这时候ts会给我们这个提示这时候我们有两种选择:1.将属性的类型显式声明为any。2.ctrl+P搜索tsconfig.json文件,并找到"noImplicitAny": true,将其去掉注释并改为false(不建议,除非有大量的错误,但那样你可能会失去使用ts的意义)Array同样的,如果我们声明一个空数组const arr = [];//它的类型为any[]由前面的经验,我们明白,我们应该给arr声明类型const arr: number[] = [];//类型为number[]ts有一个特性:在使用某个确定类型的变量时,你能够得到相应类型方法的代码补全提示。这也是我们为什么使用ts的一个重要原因Tuples/元组ts有一个名为Tuples/元组的新类型,这是一个拥有固定长度的数组,并且每个元素都有一个特定的类型。我们经常会在处理一对值时使用它。let user: [number, string] = [20, "kevin"];//类型为[number, string] 注意,这里的类型与元素的位置是对应的 //let user: [number, string] = ["kevin", 20]; //错误:不能将类型“string”分配给类型“number”。同时,当我们声明第三个元素时,将会报错,因为元组的元素长度是固定的。let user: [number, string] = [20, "kevin", 0]; //错误:不能将类型“[number, string, number]”分配给类型“[number, string]”。源具有 3 个元素,但目标仅允许 2 个。ts(2322)关于元组,你应该知道的是:在内部,使用的是普通的js数组,如果我们编译代码,那么得到的只是一个普通的js数组:"use strict"; let user = [20, "kevin"]; //# sourceMappingURL=demo.js.map关于元组,有一个需要注意的是,虽然元组是固定长度的数组,但你仍然可以使用push添加元素。这也许是一个漏洞,希望将来会得到解决。let user: [number, string] = [20, "kevin"]; user.push("demo");//只要类型为 number|string 并不会报错关于元组的最佳实践:当数组只有两个值的时候可以使用(如键值对),但当你有更多的元素,使用元组可能让你的代码的阅读性更差。Enum/枚举枚举与数组类似,在以前我们定义三个常量可能会这样做:const small = 1; const medium = 2; const large = 3;不过在ts中,我们可以使用枚举:enum SizeList {  Small,//默认为0  Medium,//后面的值依次增长  Large, }当然,你可以显式的设置值enum SizeList {  Small = 1,//定义为1  Medium,//值为2,以此类推  Large, }或者将值声明为其他类型,不过这时候,你需要声明每个常量的值:enum SizeList {  Small = "s",  Medium = "m",  Large = "l", }使用枚举:enum SizeList {  Small = 1,  Medium,  Large, } let mySize: SizeList = SizeList.Medium; console.log(mySize);//2我们看到编译后的js文件,代码似乎有些长:"use strict"; var SizeList; (function (SizeList) {    SizeList[SizeList["Small"] = 1] = "Small";    SizeList[SizeList["Medium"] = 2] = "Medium";    SizeList[SizeList["Large"] = 3] = "Large"; })(SizeList || (SizeList = {})); let mySize = SizeList.Medium; console.log(mySize); //# sourceMappingURL=demo.js.map我们可以通过一个小技巧,让ts编译器生成更加优化的代码:在enum前添加constconst enum SizeList {  Small = 1,  Medium,  Large, } let mySize: SizeList = SizeList.Medium; console.log(mySize);下面是编译后的js文件:"use strict"; let mySize = 2; console.log(mySize); //# sourceMappingURL=demo.js.map总结:我们可以通过枚举表示相关常量的列表,如果我们const声明enum,编译器将生成更加优化的代码。function在声明函数时,根据最佳实践,我们应该对函数的属性和返回值的类型进行声明:function taxation(income: number) {}//它的类型为function taxation(income: number): void //我们应该这样做: function taxation(income: number): number {  return 0; }//它的类型为function taxation(income: number): number对于上面的函数,我们并没有在函数中使用到传入的参数,我们希望ts能够给我们警告,我们可以在tsconfig.json中配置它:找到"noUnusedParameters": true,并打开它这时候我们就得到一个警告了我们写这个函数:function taxation(income: number): number {  if (income < 50_000) return income * 1.2; }//函数缺少结束 return 语句,返回类型不包括 "undefined"。出现这个报错的原因是,只有在符合条件时才会返回income*1.2,而不符合则返回undefined(在js中,未赋值的变量都为undefined),而undefined不为number类型。我们可以去除函数的返回值类型function taxation(income: number) {  if (income < 50_000) return income * 1.2; }//function taxation(income: number): number | undefined这样不会报错,但这样并不符合ts的使用标准,我们不希望会返回undefined,我们希望我们这样做的时候ts对我们进行警告我们可以在tsconfig.json中找到"noImplicitReturns": true,并打开它。这样,你就得到想要的警告了。对于上面的函数,我们应该这样写:function taxation(income: number): number {  if (income < 50_000) return income * 1.2;  return income * 1.3; }在函数中,我们可能声明了变量/常量却没有使用,我们同样希望得到提示:function taxation(income: number): number {  let a = 1;  if (income < 50_000) return income * 1.2;  return income * 1.3; }我们可以在tsconfig.json中找到"noUnusedLocals": true,并打开它。这时候我们将得到一个提示:已声明“a”,但从未读取其值。ts(6133)现在我们的函数有两个参数,但我们在调用的时候传入了三个,这时候ts会给我们报错,而在js中并不会报错function taxation(income: number, taxYear: number): number {  if (taxYear < 2022) return income * 1.2;  return income * 1.3; } ​ taxation(300_000, 2023, 1);//报错:应有 2 个参数,但获得 3 个。ts(2554)我们可能会想让taxYear属性为可选属性,我们可以在冒号前使用?来声明可选属性。但通常情况下,ts会给我们报错:function taxation(income: number, taxYear?: number): number {  if (taxYear < 2022) return income * 1.2;  return income * 1.3; }//对象可能为“未定义”。ts(2532)因为如果我们没有传入taxYear,那么taxYear在函数中就为undefined,但undefined转换为数字后为NAN。并不能进行比较对此,我们有两个解决方法:1.旧版写法(不推荐):function taxation(income: number, taxYear?: number): number {  if ((taxYear || 2022) < 2022) return income * 1.2;  return income * 1.3; }2.es6写法(推荐):function taxation(income: number, taxYear = 2023): number {  if (taxYear < 2022) return income * 1.2;  return income * 1.3; } ​ taxation(300_000, 2024);//taxYear的默认值为2023,调用时传入2024,将会覆盖掉默认值总结:作为最佳实践,我们应该始终正确的注释函数,参数类型,返回值类型。并且启用tsconfig.json中的"noUnusedParameters": true,(没有未使用的参数),"noUnusedLocals": true,(没有未使用的变量),"noImplicitReturns": true,(没有隐式返回)Object当我们像js那样使用对象时,ts会给我们报错let man = { age: 22 }; man.name = "kevin";//报错:类型“{ age: number; }”上不存在属性“name”。ts(2339)在ts中使用对象时,我们也应该像function那样,明确的声明对象的类型:let man:{    name:string,    age:number } = { age: 22 };//类型 "{ age: number; }" 中缺少属性 "name",但类型 "{ name: string; age: number; }" 中需要该属性。ts(2741) man.name = "kevin";这时候同样会报错,因为我们在声明对象时没有对对象的属性进行声明。也就是没有声明name属性。我们可以使用ts的特性,让其属性为可选属性let man: {  name?: string;  age: number; } = { age: 22 }; man.name = "kevin";但是,我们不应该这样使用。我们不能盲目地使用ts的特性。我们在写代码的同时,也应该考虑代码的合理性,毕竟没有一个man的name为undefined所以,我们还是应该这样使用:let man: {  name: string;  age: number; } = { age: 22, name: "kevin" };只读属性在声明对象时,我们可能希望某些属性不被更改。对此,我们可以使用readonly关键字,它表示(只读的)let man: {  readonly name: string;  age: number; } = { age: 22, name: "kevin" }; man.name = "qian";//无法分配到 "name" ,因为它是只读属性。ts(2540)对象的方法在声明方法时,我们同样的要给函数声明函数签名(也叫类型签名,或方法签名,定义了函数或方法的输入与输出)let man: {  readonly name: string;  age: number;  retire: (date: Date) => void;//函数签名 } = {  age: 22,  name: "kevin",  retire: (date: Date) => {    console.log(date); }, };
0
0
0
浏览量541
武士先生

深入浅出 Quill 系列之实践篇1:如何将龙插入到编辑器中?

1 在富文本编辑器中插入自定义内容之前给大家分享了如何在 Quill 中插入自定义的内容,我们一起来回顾下:第一步:自定义工具栏按钮第二步:自定义 Blot 内容第三步:在 Quill 注册自定义 Blot第四步:调用 Quill 的 API 插入自定义内容我们试着按照这个步骤来将龙插入到编辑器中。2 第一步:自定义工具栏按钮这个非常简单: const TOOLBAR_CONFIG = [ [{ header: ['1', '2', '3', false] }], ['bold', 'italic', 'underline', 'link'], [{ list: 'ordered' }, { list: 'bullet' }], ['clean'], ['card', 'divider', 'emoji', 'file', 'tag'], ['dragon'], // 新增的 ];自定义工具栏按钮图标: const dragonIcon = `<svg>...</svg>`; const icons = Quill.import('ui/icons'); icons.dragon = dragonIcon;增加工具栏按钮事件: const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: { container: TOOLBAR_CONFIG, handlers: { ... // 增加一个空的事件 dragon(value): void { console.log('dragon~~'); }, }, } }, });3 第二步:自定义 Blot 内容(核心)之前的分享提到:Quill 中的 Blot 就是一个普通的 ES6 Class因此我们需要编写一个类。dragon.ts import Quill from 'quill'; const BlockEmbed = Quill.import('blots/block/embed'); class DragonBlot extends BlockEmbed { static blotName = 'dragon'; static tagName = 'canvas'; static create(value): any { const node = super.create(value); const { id, width, height } = value; node.setAttribute('id', id || DragonBlot.blotName); if (width !== undefined) { node.setAttribute('width', width); } if (height !== undefined) { node.setAttribute('height', height); } // 绘制龙的逻辑,参考大帅老师的文章:https://juejin.cn/post/6963476650356916254 new Dragon(node); return node; } } export default DragonBlot;4 第三步:在 Quill 注册自定义 Blot有了 DragonBlot,还需要将其注册到 Quill 中才能使用: import DragonBlot from './formats/dragon'; Quill.register('formats/dragon', DragonBlot);5 第四步:调用 Quill 的 API 插入自定义内容最后一步,见证奇迹的时刻到了! const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: { container: TOOLBAR_CONFIG, handlers: { ... dragon(value): void { console.log('dragon~~'); const index = this.quill.getSelection().index; // 插入自定义内容 this.quill.insertEmbed(index, 'dragon', { id: 'canvas-dragon', }); }, }, } }, });效果图:总结本文是一个 Quill 的综合案例,从自定义工具栏按钮,到自定义 Blot 编辑器内容格式,再到调用 Quill 实例的 insertEmbed 方法,完成在富文本编辑器中插入由 Canvas 绘制的龙这种复杂的自定义内容。下一期我会带大家在富文本编辑器中插入贪吃蛇游戏,巩固在 Quill 中自定义内容的知识,大家也可以提前思考下如何实现。
0
0
0
浏览量211
武士先生

深入浅出 Quill 系列之实践篇2:整个贪吃蛇游戏到编辑器里玩儿吧

1 依然是插入自定义内容按照以下四个步骤来就行:第一步:自定义工具栏按钮第二步:自定义 Blot 内容第三步:在 Quill 注册自定义 Blot第四步:调用 Quill 的 API 插入自定义内容2 第一步:自定义工具栏按钮这个非常简单: const TOOLBAR_CONFIG = [ [{ header: ['1', '2', '3', false] }], ['bold', 'italic', 'underline', 'link'], [{ list: 'ordered' }, { list: 'bullet' }], ['clean'], ['card', 'divider', 'emoji', 'file', 'tag'], ['dragon', 'snake'], // 新增的 ];自定义工具栏按钮图标: const snakeIcon = `<svg>...</svg>`; const icons = Quill.import('ui/icons'); icons.snake = snakeIcon;增加工具栏按钮事件: const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: { container: TOOLBAR_CONFIG, handlers: { ... // 增加一个空的事件 snake(value): void { console.log('snake~~'); }, }, } }, });3 第二步:自定义 Blot 内容 SnakeBlotsnake.tsimport Quill from 'quill'; import GreedySnake from '../../shared/greedy-snake'; const BlockEmbed = Quill.import('blots/block/embed'); class SnakeBlot extends BlockEmbed { static blotName = 'snake'; static tagName = 'canvas'; static create(value): any { const node = super.create(value); const { id, width, height } = value; node.setAttribute('id', id || SnakeBlot.blotName); if (width !== undefined) { node.setAttribute('width', width); } if (height !== undefined) { node.setAttribute('height', height); } // 绘制贪吃蛇游戏的代码参考掘金文章:https://juejin.cn/post/6959789039566192654 new GreedySnake(node).start(); return node; } } export default SnakeBlot; 3.1 绘制贪吃蛇greedy-snake.ts // 大小为64 * 40 export default class GreedySnake { canvas; ctx; maxX; maxY; itemWidth; direction; speed; isStop; isOver; isStart; score; timer; j; canChange; grid; snake; food; // mask; // scoreDom; constructor(container) { this.canvas = typeof container === 'string' ? document.querySelector(container) : container; this.canvas.setAttribute('width', 640); this.canvas.setAttribute('height', 400); this.canvas.setAttribute('style', 'border: solid 2px #ddd'); this.ctx = this.canvas.getContext('2d'); this.maxX = 64; // 最大行 this.maxY = 40; // 最大列 this.itemWidth = 10; // 每个点的大小 this.direction = 'right'; // up down right left 方向 this.speed = 150; // ms 速度 this.isStop = false; // 是否暂停 this.isOver = false; // 是否结束 this.isStart = false; // 是否开始 this.score = 0; // 分数 this.timer = null; // 移动定时器 this.j = 1; this.canChange = true; this.grid = new Array(); // this.scoreDom = document.querySelector('#score'); // this.mask = document.querySelector('#mask'); for (let i = 0; i < this.maxX; i++) { for (let j = 0; j < this.maxY; j++) { this.grid.push([i, j]); } } this.drawGridLine(); this.getDirection(); document.addEventListener('keydown', (event) => { if (event.keyCode === 13) { if (!this.isStart) return; this.start(); } }); } // 开始 start(): void { if (this.timer) { clearTimeout(this.timer); } if (!this.isStart) { this.isStart = true; } this.score = 0; this.speed = 150; this.isStop = false; this.isOver = false; this.direction = 'right'; this.createSnake(); this.createFood(); this.draw(); this.move(); // this.mask.style.display = 'none'; } // 创建蛇主体 createSnake(): void { this.snake = [ [4, 25], [3, 25], [2, 25], [1, 25], [0, 25] ]; } // 移动 move(): void { if (this.isStop) { return; } let [x, y] = this.snake[0]; switch (this.direction) { case 'left': x--; break; case 'right': x++; break; case 'up': y--; break; case 'down': y++; break; } // 如果下一步不是食物的位置 if (x !== this.food[0] || y !== this.food[1]) { this.snake.pop(); } else { this.createFood(); } if (this.over([x, y])) { this.isOver = true; // this.mask.style.display = 'block'; // this.mask.innerHTML = '结束'; return; } if (this.completed()) { // this.mask.style.display = 'block'; // this.mask.innerHTML = '恭喜您,游戏通关'; return; } this.snake.unshift([x, y]); this.draw(); this.canChange = true; this.timer = setTimeout(() => this.move(), this.speed); } // 暂停游戏 stop(): void { if (this.isOver) { return; } this.isStop = true; // this.mask.style.display = 'block'; // this.mask.innerHTML = '暂停'; } // 继续游戏 continue(): void { if (this.isOver) { return; } this.isStop = false; this.move(); // this.mask.style.display = 'none'; } getDirection(): void { // 上38 下40 左37 右39 不能往相反的方向走 document.onkeydown = (e) => { // 在贪吃蛇移动的间隔内不能连续改变两次方向 if (!this.canChange) { return; } switch (e.keyCode) { case 37: if (this.direction !== 'right') { this.direction = 'left'; this.canChange = false; } break; case 38: if (this.direction !== 'down') { this.direction = 'up'; this.canChange = false; } break; case 39: if (this.direction !== 'left') { this.direction = 'right'; this.canChange = false; } break; case 40: if (this.direction !== 'up') { this.direction = 'down'; this.canChange = false; } break; case 32: // 空格暂停与继续 if (!this.isStop) { this.stop(); } else { this.continue(); } break; } }; } createPos(): any { // tslint:disable-next-line: no-bitwise const [x, y] = this.grid[(Math.random() * this.grid.length) | 0]; for (const item of this.snake) { if (item[0] === x && item[1] === y) { return this.createPos(); } } // for (let i = 0; i < this.snake.length; i++) { // if (this.snake[i][0] === x && this.snake[i][1] === y) { // return this.createPos(); // } // } return [x, y]; } // 生成食物 createFood(): void { this.food = this.createPos(); // 更新分数 // this.scoreDom.innerHTML = 'Score: ' + this.score++; if (this.speed > 50) { this.speed--; } } // 结束 over([x, y]): boolean { if (x < 0 || x >= this.maxX || y < 0 || y >= this.maxY) { return true; } if (this.snake.some(v => v[0] === x && v[1] === y)) { return true; } } // 完成 completed(): boolean { if (this.snake.length === this.maxX * this.maxY) { return true; } } // 网格线 drawGridLine(): void { for (let i = 1; i < this.maxY; i++) { this.ctx.moveTo(0, i * this.itemWidth); this.ctx.lineTo(this.canvas.width, i * this.itemWidth); } for (let i = 1; i < this.maxX; i++) { this.ctx.moveTo(i * this.itemWidth, 0); this.ctx.lineTo(i * this.itemWidth, this.canvas.height); } this.ctx.lineWidth = 1; this.ctx.strokeStyle = '#ddd'; this.ctx.stroke(); } // 绘制 draw(): void { // 清空画布 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.drawGridLine(); this.ctx.fillStyle = '#000'; this.ctx.fillRect( this.food[0] * this.itemWidth + this.j, this.food[1] * this.itemWidth + this.j, this.itemWidth - this.j * 2, this.itemWidth - + this.j * 2 ); // tslint:disable-next-line: no-bitwise this.j ^= 1; this.ctx.fillStyle = 'green'; this.ctx.fillRect( this.snake[0][0] * this.itemWidth + 0.5, this.snake[0][1] * this.itemWidth + 0.5, this.itemWidth - 1, this.itemWidth - 1 ); this.ctx.fillStyle = 'red'; for (let i = 1; i < this.snake.length; i++) { this.ctx.fillRect( this.snake[i][0] * this.itemWidth + 0.5, this.snake[i][1] * this.itemWidth + 0.5, this.itemWidth - 1, this.itemWidth - 1 ); } } }4 第三步:在 Quill 注册自定义 Blot有了 SnakeBlot,还需要将其注册到 Quill 中才能使用: import SnakeBlot from './formats/snake'; Quill.register('formats/snake', SnakeBlot);5 第四步:调用 Quill 的 API 插入自定义内容调用完 API 就可以玩贪吃蛇游戏啦,开心到飞起! const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: { container: TOOLBAR_CONFIG, handlers: { ... snake(value): void { console.log('snake~~'); const index = this.quill.getSelection().index; // 插入自定义内容 this.quill.insertEmbed(index, 'snake', { id: 'canvas-snake', }); }, }, } }, });效果图:总结在富文本中插入自定义内容的思路都是类似的,定义一个自定义 Blot 内容格式,然后注册这个自定义 Blot,调用 Quill 提供的方法进行内容插入。大家可以尝试按照以上思路,尝试在 Quill 中插入:ECharts 图表PDF 文档PPT 文档深入浅出 Quill 系列还有最后一篇,主要给大家分享富文本编辑器的技术选型,也是对前面内容的一个总结,敬请期待!
0
0
0
浏览量566
武士先生

TypeScript进阶指南

TypeScript进阶指南是为广大开发者提供的一份全面而深入的资源,旨在帮助他们掌握TypeScript的各种关键概念和高级特性。本专栏涵盖了TypeScript的基础知识、配置与调试技巧,以及深入讨论了TypeScript中的简单特性、高级类型、面向对象编程、泛型、设计模式和装饰器等难点。通过详细的解释、示例代码和实际场景的应用,读者将能够加深对TypeScript的理解,并在实际项目中更加灵活和高效地使用TypeScript。无论您是初学者还是有一定经验的开发者,本专栏都将成为您的理想学习伴侣,帮助您在TypeScript的世界中不断进阶。
0
0
0
浏览量1349
武士先生

Quill深入浅出:前端富文本编辑器解密

Quill深入浅出专栏将带您逐步探索Quill富文本编辑器的使用、原理和实践。在使用篇中,您将学习Quill的基本使用方法和配置,并通过Quill API实现对编辑器内容的完全控制。在原理篇中,我们将深入了解Quill的模块化机制和内容渲染机制,揭示现代富文本编辑器的内部工作原理。在实践篇中,我们将通过实例,教您如何在Quill编辑器中插入自定义内容,如何应用Quill的高级特性。无论您是初学者还是有经验的前端开发者,本专栏将为您打开Quill富文本编辑器的大门,助您更深入地理解和应用这一强大工具。
0
0
0
浏览量1091
武士先生

记一次uniapp的经历

前言这几天在做一个app,打比赛用,使用的是uni+uView的组件库。这个组件库是半道加进来的,学弟推荐的,我看有组件的话确实会方便很多,而且他都是按需引入,不占用额外空间,挺好的,我也就直接拿来用了。使用感想:感觉这套技术栈跟vue+element差不多了emm,就是某些官方api还不太一样,还是比较顺手的。组件库传送门: uView - 多平台快速开发的UI框架 但是,不熟悉组件的api以及参数,让我踩了坑,非常难受emm,还是写个记录,长长记性。本次内容:分享一次因粗心踩的坑,一个自己进行封装的经历坑:uView组件库,uToast组件传送门:Toast 消息提示 使用方法:// 将这个标签放在页面中 <u-toast ref="uToast" /> // 需要消息提示的话,加上如下代码 this.$refs.uToast.show({ title: '登录成功', type: 'success', url: '/pages/user/index' })参数:title:消息提示显示的文本type:主题类型,不填默认为default(一共有6中主题色)url:toast结束跳转的url,不填不跳转更多详细api请点击上方toast传送门去到该组件库官网看我遇到的坑,便是填写了url,他执行结束后不跳转。说一下我的大致使用情况:登录成功后弹出消息提示登录成功,完后跳转到一个tab页面上去。但是他只给弹出登录成功,不报错也不跳转。当时真的人傻了,找了好多地方,也打了断点,所有的事情都告诉我一切正常。于是我开始怀疑起我的电脑emm,我将整个程序打包,发给了我的学弟,让他在他电脑上帮我调试一下,结果与我电脑一致(nice!那就是电脑没坏,还能用),在我吃完饭回到电脑前准备跟这个问题决一死战的时候,学弟发来的一张截图让我人呆滞了。参数说明类型默认值可选值isTabtoast结束后,跳转tab页面时需要配置为trueBooleanfasletrue没错,就是这个表格。我才知道这个组件要是跳转到tab页面的话还需要额外配置一个参数,赶忙加上,于是我又从坑中站了起来。关于toast结束跳转URL如果配置了url参数,在toast结束的时候,就会用uni.navigateTo(默认)或者uni.switchTab(需另外设置isTab为true)如果配置了params参数,就会在跳转时自动在URL后面拼接上这些参数,具体用法如下:this.$refs.uToast.show({ title: '操作成功', url: '/pages/user/index', params: { id: 1, menu: 3 } })这个故事告诉我们,大家使用之前一定要摸清楚api,不要学习泽泽!!!封装uni.getStorage函数这个应该不算坑,我只是觉得他这个使用起来不是很方便(好像是相当不方便)传送门:uni.getStorage 它的作用就是从本地缓存中异步获取指定 key 对应的内容。我的需求就是要从缓存中获取一些我之前存入进去的东西,但是我取一个还好,某一处需要取两个的话要把这个函数写两次,反正我本人是觉得很麻烦的(即使CV不麻烦,但在程序员眼中,代码相似程度有点高),那为什么不尝试着封装一下呢?函数本来的样子:uni.getStorage({ key: 'storage_key', success: function (res) { console.log(res.data); } });他是根据key,去取缓存中的数据,完后返回值res.data中就是我们需要的数据了。思路就是:传一个key给他,同时接好res.data的值改造前:uni.getStorage({ key: 'token', success:(res) => { console.log('token:'+ res.data) this.token = res.data } }) uni.getStorage({ key: 'username', success:(res) => { console.log('username' + res.data) this.userInfo.username = res.data } })改造后:getCache(a){ let b = ''; console.log('a:'+a) uni.getStorage({ key:a, success:(res)=>{ console.log('res.data:'+res.data); b = res.data; return res.data; } }) return b; },需要调用的时候这样:let username = this.getCache('username')是不是非常的方便呢?封装,yyds最后踩坑可以迟到,但永远不会缺席,我们能做的就是每次细心一点,并且把犯过的错,背过的锅记录在小本本上,再也不去犯第二次。毕竟,神也会犯错呀!祝愿天下的程序员都少一点bug吧。改进上面的封装函数自己今天使用的时候发现,这样封装只能单页面内使用,可是一个项目中,我有好几处都要使用,就只能复制粘贴。那我不又成了cv程序员???于是我继续开始封装,想把它以组件的方法进行封装。cache.js,我给他放在了common文件夹内 export function getCache(a){ let b = ''; console.log('a:'+a) uni.getStorage({ key:a, success:(res)=>{ console.log('res.data:'+res.data); b = res.data; return res.data; } }) return b; } 使用方法:在需要使用这个函数的页面进行引入就好,方法类似于我们按需引入组件。import {getCache} from 'common/cache.js'使用的时候直接进行调用就好。this.username = getCache('username')
0
0
0
浏览量325
武士先生

恭喜你,Node后端学习经验值+1

感想遇到很多都是模块化的写法,之前自己写的时候确实这方面不是很注重,因为自己写自己看,当然怎么舒服怎么来了,它提醒我要优雅一点~,但是确实,这么写之后香的一批~一些工具知识Koa初始化1.1 全局安装脚手架工具cnpm install -g koa-generator # or yarn global add koa-generator 1.2 进入到项目文件夹目录,执行生成命令# koa2+项目名 koa2 manager-server1.3 安装依赖npm install # or cnpm install # or yarn使用pm2部署Koa项目并实现启动、关闭、自动重启1. 全局安装npm install -g pm22. 启动项目进入项目目录,然后使用pm2启动项目。这里要特别注意:启动单文件时用(app.js是项目文件名)pm2 start app.js #启动单文件但是在koa2中需要这样启动:pm2 start ./bin/www #启动koa2项目3. pm2自动重启把pm2的服务先停下,然后起来的时候带上–watch就可以了pm2 start ./bin/www --watch4. pm2相关命令(www是项目名)pm2 list #查看所用已启动项目 pm2 start #启动 pm2 restart www #重启 pm2 stop www #停止 pm2 delete www #删除以下为封装的两个工具函数利用log4js封装日志输出首先在utils文件夹中建好你的log4j.js文件,引入log4js const log4js = require('log4js')下面是定义的level等级,截止到目前只用到了三种,但是教程里定义的确实是很多,可能是我没用到。const levels = { 'trace':log4js.levels.TRACE, 'debug':log4js.levels.DEBUG, 'info':log4js.levels.INFO, 'warn':log4js.levels.WARN, 'error':log4js.levels.ERROR, 'fatal':log4js.levels.FATAL, }接下来就是要用log4js.configure去添加一些配置。appenders: 追加器 appenders:{ console:{ type: 'console' }, info:{ type:'file', filename:'logs/all-logs.log' }, error:{ type: 'dateFile', filename:'logs/log', pattern:'yyyy-MM-dd.log', alwaysIncludePattern:true // 设置文件名称是filename+pattern } },可以看到我们在追加器上,让info和error的日志输出都存到文件里,方便我们查阅和记录,其中error的日志我们用当天的年月日来作为记录,info的日志我们用一个文件来存储就好categories: 输出种类 categories:{ default: { appenders: ['console'], level: 'debug' }, info:{ appenders: ['info','console'], level: 'info' }, error:{ appenders: ['error','console'], level: 'error' } }default默认,这个单词都很熟悉了,默认输出level就是debug,我们自己用来打印东西用的一个输出等级我们要注意一下,此时appenders里的我们只能添加上面appenders里定义好的东西。下面是三个函数,分别为debug、error、info,将他们导出出去方便使用:exports.debug = (content) =>{ let logger = log4js.getLogger() logger.level = levels.debug logger.debug(content) } exports.error = (content) =>{ let logger = log4js.getLogger('error') logger.level = levels.error logger.error(content) } exports.info = (content) =>{ let logger = log4js.getLogger('info') logger.level = levels.info logger.info(content) }随后我们去app.js里将原本的日志输出换成我们写好的。位置大约一个在logger那,一个在error那。分别换上我们的info和error就行啦。记得先引入log4js!封装工具函数工具函数目前具有的功能:成功或者失败返回状态码初步的分页结构先来定义我们的状态码:const CODE = { SUCCESS:200, PARAM_ERROR:10001, //参数错误 USER_ACCOUNT_ERROR:20001, //账号或者密码错误 USER_LOGIN_ERROR:30001, //用户未登录 BUSINESS_ERROR:40001, //业务请求失败 AUTH_ERROR:50001, //认证失败或者TOKEN过期 }下一个肯定是60001,别猜,我说哒! 下面函数均用module.exports导出: success(data='',msg='',code=CODE.SUCCESS){ log4js.debug(data); return{ code,data,msg } }, fail(msg='',code=CODE.ERROR){ log4js.debug(msg); return{ code,data,msg } }我这里特意将之前日志输出的函数引入过来,在此打印方便我们调试,如果成功的话,应该重点看返回的数据,如果失败的话,我们肯定不返回数据,重点要看当时的msg,查明失败原因。 pager({pageNum=1,pageSize=10}){ pageNum*=1; pageSize*=1; const skipIndex = (pageNum-1)*pageSize; return{ page:{ pageNum, pageSize }, skipIndex } },这个函数是分页结构的函数,默认一页是十条数据,利用算数将pageNum和pageSize转换成number类型。skipIndex是我们用来查索引的,这里来解释一下:一页十条数据,假如我们现在是第三页,(3-1) * 10=20,所以我们下一个索引就从20开始查起就好了。
0
0
0
浏览量355
武士先生

fdm-cli,一个致力于管理项目初始化模板的工具脚手架

阅读本文你将获得一个(好用的)项目模板初始化工具这个(好用的)工具的使用方法给作者点一个 star 的机会一、不愿再复制粘贴小N每次在写项目的时候,遇到所用技术栈相同的时候,总习惯于去git上将之前开发过的项目clone下来。又或是直接在本地CV文件,再将其里面的无用文件删掉,保留基础文件以及一些用顺手的工具函数,在此基础上,进行新的开发。小E在团队里负责的事情比较多,每次起新项目的时候,他就很痛苦。因为要对照着团队的规范,一项一项的配置,通常要配置好久。但是又怕直接用之前的git仓库会带来一些无用依赖,只好从零开始慢慢配置,耗费不少的时间。小D是一位后端工程师,他要为自己的后端项目写一些前端界面。但是从零开始搭建很耗费精力,亟需一个工具将前期步骤准备好,直接进入开发阶段。类似于小N小E小D这样的情况,在身边比比皆是,那为何没有一个工具来帮助管理这个情况呢?于是,fdm-cli,一个致力于解决项目模板初始化管理的脚手架工具,诞生了!获取方式: npm install fdm-cli -g二、fdm-cli是一个怎样的工具?Github:github.com/wangenze267…作为一个管理工具,它目前暂时具有以下命令:为了测试,我们建个Ned文件夹,里面有个ned.txt文件,接着我们在外边的test目录下执行fdm list查看当前的模板清单列表接着,我们将Ned作为模板,保存起来,运行fdm save ./Ned,输入描述保存成功后,执行fdm list,再次查看模板列表,可以看见我们保存的模板已经在其中了。删掉Ned文件夹,执行fdm create命令。会提示我们先输入项目名,完后提供给我们模板库的选项,我们可以选择一个来建立我们的初始化项目,我们这里直接选择刚刚保存的 Ned 模板就好。创建成功后,我们可以看到这正是我们之前保存的想要作为模板的项目文件。
0
0
0
浏览量102
武士先生

用CSS做一个好看的Loading加载效果

前言先祝大家新年快乐呀~最近因为年度人气创作者榜单真是把写文章鸽了好久,觉得天天发朋友圈肯定有人屏蔽我了hhh,所以这不,这个活动结束了,我也来继续码字了,害,生活嘛。CSS确实是魅力大的离谱,可能最近一段时间关注我文章的会知道,我发了好多跟CSS有关的文章了,有的是看到网上有这种效果,自己进行复刻,有的是利用一个自己刚看到的知识点,运用它去做一些好玩的东西,总之,CSS真的太好玩啦!实现吃豆人的大嘴巴先来实现左边的大嘴巴,我是用了两个这种形状的东西,完后给下面的那个margin-top:-50px,它俩就实现了重叠,之后用动画效果,让上面顺时针旋转,下方与之相反,旋转90°就可以实现嘴巴张开合上的动作了。 width: 0px; height: 0px; border-right: 25px solid transparent; border-top: 25px solid #279fcf; border-left: 25px solid #279fcf; border-bottom: 25px solid #279fcf; border-radius: 25px;旋转动画分别是:@keyframes rotate_pacman_up { 0% { -webkit-transform: rotate(270deg); transform: rotate(270deg); } 50% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } 100% { -webkit-transform: rotate(270deg); transform: rotate(270deg); } }@keyframes rotate_pacman_down { 0% { -webkit-transform: rotate(90deg); transform: rotate(90deg); } 50% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(90deg); transform: rotate(90deg); } }之后将动画加到我们刚刚生成的那两个元素的css中即可(运用这个属性就行:animation),因为我是div套了div,所以我用的是伪元素来选择的:分别是first-of-type和nth-child(2),选中了第一个跟第二个div来作为吃豆人的嘴巴。要注意的是,两个嘴巴的动画时间要同步,否则这个嘴可就上下乱窜了: animation: rotate_pacman_up 0.75s 0s infinite现在的效果是这样的:实现吃豆人的豆子同样,我们在刚刚的两个div同级的地方,又建了三个div,我真是太喜欢div了~万物皆可div!!!豆子相对于嘴巴来说就很简单,首先他是圆的,其次呢,它的动画是向左移动的,具备这两个条件,就可以了。我们直接使用伪元素选中3、4、5三个div,加上这段css即可。 background-color: #279fcf; width: 15px; height: 15px; border-radius: 100%; margin: 2px; width: 10px; height: 10px; position: absolute; transform: translate(0, -6.25px); top: 25px; left: 100px; 会不会有人问:为什么你就知道定位的时候是-6.25px呢?,最后解释最后解释。接下来加上向左移动的动画:@keyframes pacman-balls { 75% { opacity: 0.7; } 100% { -webkit-transform: translate(-100px, -6.25px); transform: translate(-100px, -6.25px); } }似乎,要是走一段变得透明一些是不是会更好?这个有待考虑~最后将动画分别用选择器挂到第3、4、5个div上就行啦,同时要注意,动画的起始时间不要设置成一样的,否则它们就同步啦!!!我这里设置的分别是0.33/0.66/0.99秒哦~就像这样: animation: pacman-balls 1s 0.33/0.66/0.99s infinite linear看一下最终效果吧~关于那个-6.25px其实.....我研究了好半天,我把图给大家,要是能算出来也帮我算算,我最后是实验出来的,6到7都差不多,但是6.25更顺眼一些emmm,但是实验到7px的时候,小圆的圆心就有点明显偏上了,所以emm就6.25吧(差不多~,还有就是我想写6.5的,但是总觉得6.25好听点emm)。
0
0
0
浏览量1043
武士先生

都2021年了,节流是不是也应该了解一下了?

前言昨天发了有关防抖的文章,很多人都问为啥节流不一起写了呢?这当然是因为昨天那个是上实验课摸鱼写的,节流不没摸出来嘛,嘿嘿。不过不要慌,节流,它来了!个人理解:节流的作用与防抖相似,都是为了限制事件的频繁触发。如何实现节流实现节流,就要先了解节流的原理。节流就是,在一定的时间内,只执行一次事件。或者说是,每隔一段时间,只执行一次事件。要与防抖做一下区分哦~ 防抖是,不管你触发了几次,我只看你最后触发的那一次,并且在若干时间后去执行此次事件。我了解到的节流,有两种方式,分别是时间戳与定时器。那么我们来实现一下看看吧~时间戳使用时间戳,当触发事件的时候,我们记录当前的时间戳,然后减去之前记录的时间戳(最一开始值一定不要忘了设为 0 ),如果大于设置的时间周期(也就是那个间隔的时间段),就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。看完是不是觉得很简单?那我们来写一下。具体的例子还是使用防抖的那个,懒得再写一个啦,嘿嘿~ 将防抖函数换成节流就可 function throttle(todo, time) { var pre = 0; return function() { var now = +new Date(); var that = this; if (now - pre > time) { todo.apply(that); pre = now; } } }使用的话还是跟之前相同:btn.onclick = throttle(shake,3000);为什么设置3000,当然想让效果看起来明显一些,下面来看看效果吧:好了好了,别在心里数123了,我们接着往下来。定时器当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。上代码:function throttle(todo, time) { var timeout,that; return function() { that = this; if (!timeout) { timeout = setTimeout(function(){ timeout = null; todo.apply(that) }, time) } } }看看效果:可以看出,它是过了3s才生效的,但是不知道为什么,这种方式给我的体验感不是很好,可能是因为不是点击的下一刻就有了反馈吧~这块要加上一个过渡动画,是不是会好很多?比较既然有两种方法,那就要说一下他们的区别。第一种:在例子中可以清楚的看到,点击触发后立即有反馈(执行第一次),连续触发直到过了等待时间才会进行下一次执行,停止触发就不会执行。第二种:过了等待时间才会执行第一次,停止触发还会执行一次。在网上看到了这样的形容词:有头无尾和无头有尾,是不是很形象。
0
0
0
浏览量1727
武士先生

知道这个,再也不用写一堆el-table-column了

前言最近在写一个练手项目,接触到了一个问题,就是el-table中的项太多了,我写了一堆el-table-column,导致代码太长了,看起来特别费劲,后来发现了一个让人眼前一亮的方法,瞬间挽救了我的眼睛。下面就来分享一下!进入正题上面就是table中的全部项,去除第一个复选框,最后一个操作的插槽,一共七项,也就是说el-table-column一共要写9对。这简直不能忍!这个图只作举一个例子用,跟上面不产生对应关系。其中就有5个el-form-item,就这么一大堆。所以,我当时就想,可不可以用v-for去渲染el-table-column这个标签呢?保留复选框和最后的操作插槽,我们只需要渲染中间的那几项就行。经过我的实验,确实是可以实现的。这么写之后就开始质疑之前的我为什么没有这个想法? 要不就能少写一堆💩啦 实现代码如下(标签部分):<el-table-column v-for="item in columns" :key="item.prop" :prop="item.prop" :label="item.label" :formatter="item.formatter" :width="item.width"> </el-table-column>思路是这样,把标签需要显示的定义在一个数组中,遍历数组来达到我们想要的效果,formatter是我们完成提交的数据和页面显示数据的一个转换所用到的。具体写法在下面js部分有写。定义数组的写法是vue3 composition api的写法,这个思路的话,用Vue2的写法也能实现的,重要的毕竟是思想(啊,我之前还是想不到这种思路)。再吐槽一下下,这种写法每写一个函数或者变量就要return回去,也挺麻烦的感觉,hhhhh实现代码如下(JS部分):const columns = reactive([ { label:'用户ID', prop:'userId' }, { label:'用户名', prop:'userName' }, { label:'用户邮箱', prop:'userEmail' }, { label:'用户角色', prop:'role', formatter(row,column,value){ return { 0:"管理员", 1:"普通用户" }[value] } }, { label:'用户状态', prop:'state', formatter(row,column,value){ return { 1:"在职", 2:"离职", 3:"试用期" }[value] } }, { label:'注册时间', prop:'createTime' }, { label:'最后登陆时间', prop:'lastLoginTime' } ])
0
0
0
浏览量669
武士先生

感谢Vue3,让我遇见了这些知识

前言最近在学习Vue3,现在来说一下自己的学习感受,并且分享一些小知识点。可能这些知识点并不是那么属于Vue3的知识范畴,但是这是我在学习过程中遇到并且记录下来的,对于我而言,是Vue3让我遇到了这些知识,哈哈哈哈,我就这么归类辣!感想这次使用的是Vite,之前都是用的vue-cli,Vite使用起来就一个字,快!认真的去做一个自己的全栈项目,所以能封装起来用的地方,都尽量去封起来了,减少以后的代码量。后端使用的是koa,也是第一次自己尝试去写后端。等这个项目写完,会继续分享一些踩坑小技巧的。开始了,奇奇怪怪的小知识封装两种习惯使用request我们平时封装请求的时候,一般都会结合自己习惯来封装,冷不丁换人封装的时候,使用起来就非常不舒服,我问了问我的小伙伴们,大都是习惯以下这两种方式,所以我就对axios进行了封装,使他能够满足我们的使用习惯。this.$request({ obj })将参数用一个对象的方式进行解构。this.$request({ methods: 'get', url: '/login', data:{ name:'Ned' } }).then((res)=>{ console.log(res) })封装如下/** * axios二次封装 */ import axios from 'axios' import config from './../config' import router from './../router'; import { ElMessage } from 'element-plus' const TOKEN_INVALID = 'Token认证失败,请从新登陆' const NETWORK_ERROR = '网络请求异常,请稍后重试' // 创建axios实例对象,添加全局配置 const service = axios.create({ baseURL:config.baseApi, timeout:8000 }) // 请求拦截器 service.interceptors.request.use((req) => { // to do const headers = req.headers if(!headers.Authorization) headers.Authorization = 'Bear token' return req }) // 响应拦截器 service.interceptors.response.use((res) => { // to do /** * 注意状态码一共有两个 * 一为http状态码 * 二为接口返回状态码 */ const { code, data, msg } = res.data if(code === 200){ return data }else if(code === 40001){ ElMessage.error(TOKEN_INVALID) setTimeout(() => { router.push('/login') }, 15000); return Promise.reject(TOKEN_INVALID) }else{ ElMessage.error(msg || NETWORK_ERROR) return Promise.reject(msg || NETWORK_ERROR) } }) /** * 请求核心函数 * @param {*} options 请求配置 */ function request(options){ // 判断get/post options.method = options.method || 'get' // 防止有时候写了GET if(options.method.toLowerCase() === 'get'){ // 如果是get就将data直接赋值给params // 类型转换 options.params = options.data; } if(config.env === 'prod'){ service.defaults.baseURL = config.baseApi }else{ service.defaults.baseURL = config.mock ? config.mockApi:config.baseApi } return service(options) } export default requestthis.$request.get/post('/api',{ obj })将get/post用.的方式取出,一个参数是接口路径,另一个参数是数据对象。this.$request.get('/login',{name:Ned}).then((res)=>{ console.log(res) })在上一步的封装上,支持这种习惯增加如下:['get','post','put','delete','patch'].forEach((item)=>{ request[item] = (url,data,options) =>{ return request({ url, data, method:item, ...options }) } })路由跳转的三种方式在此之前确实我就知道两种,Composition API的这种方式是我所不知道的,涨知识了!router-link<router-link to"/login">去登陆</router-link>传统跳转(options API)<template> <el-button @click="goLogin">去登陆</el-button> </template> <script> export default{ name:'home', methods:{ goLogin(){ this.$router.push('/login') } } } </script>Composition API跳转<script setup> import { useRouter } from 'vue-router' let router = userRouter() const goLogin = ()=>{ router.push('/login') } </script>注意:setup是钩子函数,如果像第三个例子写的话,里面的函数都属于钩子范畴,可以像第二种方式那样,setup类似于methods,完后将它return出去应该也是可以的。下面为上面注意中,方法的实现代码大体上还是options API的写法,只是setup的思想属于composition API范畴,应该属于框架迁移过程中的写法吧<template> <el-button @click="goLogin">去登陆</el-button> </template> <script> import { useRouter } from 'vue-router' export default{ setup(){ let router = useRouter() function goLogin(){ router.push('./login') } return { goLogin } } } </script>配置config我们的请求地址通常应该会有三种,在前后端开发分离的情况,第一种就是mock地址,方便我们在没有后端的情况下进行前端开发,第二种是测试地址,也就是后端写好了之后,我们请求的从mock地址变成了后端写好后挂载的服务地址,最后,当测试没问题了,部署到线上,我们请求的就是部署好的线上地址了。所以文件中的三种环境分别与开发环境,测试环境和部署环境相对应。mock的重要性,我觉得mock很重要,他可以帮助我们在没有后端的时候,依据接口文档进行前端开发并测试。以下为程序介绍: 默认是生产环境 prod,如果有值就赋值到env变量上,当做当前的环境变量。最后将EnvConfig以解构的方式导出,方便根据当前环境直接调取两种Apiconst env = import.meta.env.MODE || 'prod' const EnvConfig = { dev:{ baseApi:'', mockApi:'' }, test:{ baseApi:'', mockApi:'' }, prod:{ baseApi:'', mockApi:'' } } export default { env:env, mock:true, ...EnvConfig[env] }关于storage大家都应该很熟悉了,他可以与jwt token联系起来,来做一些权限的操作,或者当我们的数据需要跨组件共享的时候,也会用到它。跨组件数据共享: 在Vue中使用Vuex,但是存在一个问题,因为数据此时存在js内存中,页面刷新后数据就会丢失,所以我们用Vuex和storage联合起来完善此功能。下面是对storage进行封装,方便自己使用:它自身带的api好像不支持清除单独的数据,只能全清理掉,所以我特意封装了个单独的emm,希望不是我没注意到它自带,那就哭了。setItem()getItem()clearItem()clearAll() import config from './../config' export default { setItem(key,val){ let storage = this.getStorage() storage[key] = val window.localStorage.setItem(config.namespace,JSON.stringify(storage)) }, getItem(key){ return this.getStorage()[key] }, getStorage(){ return JSON.parse(window.localStorage.getItem(config.namespace)) || "{}" }, clearItem(key){ let storage = this.getStorage() delete storage[key] window.localStorage.setItem(config.namespace,JSON.stringify(storage)) }, clearAll(){ window.localStorage.clear() } }
0
0
0
浏览量320
武士先生

Grafana Loki 组件介绍

Loki 日志系统由以下3个部分组成:Loki是主服务器,负责存储日志和处理查询。Promtail是专为loki定制的代理,负责收集日志并将其发送给 loki 。Grafana用于 UI展示。DistributorDistributor 是客户端连接的组件,用于收集日志。在 promtail 收集并将日志发送给Loki 之后, Distributor 就是第一个接收它们的组件,每秒可以接收数百万次写入。Distributor会对接收到的日志流进行正确性校验,并将验证后的chunk日志块分批并行发送到Ingester。Loki使用一致性哈希来保证数据流和Ingester的一致性,他们共同在一个哈希环上,哈希环的信息可以存放到etcd、Consul或者内存中。当使用Consul作为哈希环的实现时,所有Ingester通过一组token注册到环中,每个token是一个随机的32-bit无符号整数,同时Ingester会上报其状态到哈希环中。由于所有的Distributor使用相同的hash环,写请求可以发送至任意节点。为了保证结果的一致性,Distributor会等待收到至少一半加一个Ingester的回复后才响应客户端。IngesterIngester 接收来自Distributor的日志流,并将日志压缩后存放到所连接的存储后端。Ingester接受日志流并 每个Ingester 的生命周期有PENDING, JOINING, ACTIVE, LEAVING 和 UNHEALTHY 五种状态。处于JOINING和ACTIVE状态的Ingester可以接受写请求,处于ACTIVE和LEAVING状态时可以接受读请求。Ingester 将收到的日志流在内存中打包成 chunks ,并定期同步到存储后端。由于存储的数据类型不同,Loki 的数据块和索引可以使用不同的存储。当满足以下条件时,chunks 会被标记为只读:当前 chunk 达到配置的最大容量当前 chunk 长时间没有更新发生了定期同步当旧的 chunk 经过了压缩并被打上了只读标志后,新的可写的 chunk 就会生成。QuerierQuerier 用来查询日志,可以直接从 Ingester 和后端存储中查询数据。当客户端给定时间区间和标签选择器之后,Querier 就会查找索引来确定所有匹配 chunk ,然后对选中的日志进行 grep并返回查询结果。查询时,Querier先访问所有Ingester用于获取其内存数据,只有当内存中没有符合条件的数据时,才会向存储后端发起同样的查询请求。需要注意的是,对于每个查询,单个 Querier 会 grep 所有相关的日志。目前 Cortex 中已经实现了并行查询,该功能可以扩展到 Loki,通过分布式的 grep 加速查询。此外,由于副本因子的存在,Querier可能会接收到重复的数据,所以其内置了去重的功能,对拥有同样时间戳、标签组和消息内容的日志进行去重处理。Query FrontendQuery frontend 是可选组件,其提供了Querier的API并可用于读加速。当系统中有该组件时,所有的读请求都会经由Query frontend而非Querier处理。Query frontend是无状态的,生产环境中推荐 2 副本来达到调度的均衡。Query frontend会对请求做一些调整,并将请求放入一个内部的队列中。在该场景中,Querier作为workers 不断从队列中获取任务、执行任务,并将结果返回给Query frontend用于聚合。
0
0
0
浏览量134
武士先生

ES6的promise使用说明书

前言好几天前就想写一个promise的笔记了,但是一直以来就仅仅知道它是用来解决回调地狱问题的,没有一个详细的了解,所以在这几天学习的时候,针对它名下的几个方法,做了一个简要的使用介绍。promise:这就是我的说明书! 我:可能说的不是太全,多包涵~先来了解一下它什么是promise?它是一个类?一个对象?一个数组?我们先打印它来看一看吧:console.dir(Promise);打印完了,我们来正式认识一下它。promise是一个构造函数,是ES6提出的异步编程解决方案,用来解决回调地狱这种问题,从打印可以看出,它有reject、all、resolve等方法,它的原型上有catch、then等方法。还有一种说法来自于网络: promise,意为承诺,承诺过一段时间给你结果。promise有三种状态,分别为pending(等待),fulfiled(成功),rejected(失败),状态一旦经过改变,就不会在变。// fn1执行,如果a>10 执行fn2 ,如果a == 11,执行fn3 function fn1(a, fn2) { if (a > 10 && typeof fn2 == 'function') { fn2(a,function(){ if(a == 11){ console.log('this is fn3') } }) } } fn1(11, function(a, fn3) { console.log('this is fn2') fn3() }) 上面说了promise的提出是用来解决回调地狱的问题,那么什么是回调地狱呢?可以参考一下我这段代码,不断的嵌套回调函数之后,代码就会变得非常繁琐,看代码的时候眼睛不舒服,脑子也不舒服,这种嵌套回调非常多的情况,就叫做回调地狱。如何使用promisevar p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ console.log('执行完成'); resolve('写啥都行'); }, 3000); });Promise的构造函数接收一个function,并且这个函数需要传入两个参数:resolve :异步操作执行成功后的回调函数reject:异步操作执行失败后的回调函数then还记得上面我写的那个嵌套非常多的例子吗?啊,不记得,那你翻一翻~Promise的优势就在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。所以,从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是状态,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:function fn1(a){ var p = new Promise(function(resolve, reject){ //做一些异步操作 resolve(a); }); return p; } fn1(11) .then(function(a){ return new Promise(function(resolve, reject){ if(a > 10){ console.log('a大于10') } resolve(a); }) }) .then(function (a){ if(a == 11){ console.log('a等于11') } })我将上面那个改写了一下。最后那个没有return出来是我后面没有继续then了。这其实就是链式写法。then就相当于我们之前的callback。then方法中,不光可以return promise对象,也可以return数据:function fn1(a){ var p = new Promise(function(resolve, reject){ //做一些异步操作 resolve(a); }); return p; } fn1(11) .then(function(a){ if(a > 10){ console.log('a大于10') } return a }) .then(function (a){ if(a == 11){ console.log('a等于11') } })reject把promise的状态从pending改成rejected,之后我们就可以在then中执行失败情况的回调,来看这个例子:function fn1(a){ var p = new Promise(function(resolve, reject){ if(a>10){ resolve(a); }else { reject(a); } }); return p; } fn1(9) .then((a) => { console.log('a大于10') }, (err) => { console.log('a小于10') })then可以接收两个参数,分别对应着resolve的回调和reject的回调,所以在调整传入的a的值,我们可以得到两个结果。即a大于10和a小于10catch对其他语言有了解的人应该可以知道,catch是用来抓取异常的,那么在promise里,它的作用也一样,它就如同then的第二个参数,对应着reject的回调写法是这样:.then((a) => { console.log('a大于10') }).catch((err) => { console.log('a小于10') })效果和写在then的第二个参数里面是一样的。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死进程,而是会进到这个catch方法中。就很像 try catch再来看这段代码function fn1(a){ var p = new Promise(function(resolve, reject){ //做一些异步操作 resolve(a); }); return p; } fn1(11) .then(function(a){ if(a > 10){ console.log('a大于10') } return b }) .catch(function (err){ console.log('发生了错误:' + err) })这段代码中 本来想return a的 结果写成了b,正常来说浏览器会报错,不会向下执行了,在我们用了catch后,浏览器会打印出a大于10和发生了错误:ReferenceError: b is not defined。也就是说即使是上面出错了,还是进到catch方法里面去了,而且把错误原因传到了err参数中,使得程序继续执行下去。allall方法提供了多个任务并行,执行异步操作的能力,并且在所有异步操作执行完后才执行回调。all方法接收的参数是一个数组,其中每个都是promise对象let p = Promise.all([fn1, fn2, fn3]) p.then(funciton(){ // 三个都成功则成功 }, function(){ // 只要有一个失败,则失败 })有了all,我们就可以并行执行多个异步操作,有一个场景是很适合用这个的,打开一个网页,需要加载各类资源,所有的都加载完后,再进行页面的初始化。raceall方法的效果实际上是谁跑的慢,以谁为准执行回调,那么相对的就有另一个方法谁跑的快,以谁为准执行回调,这就是race方法。拿上面的fn123举例子,假如他们分别是1、2、3秒执行完,那么在第一秒结束的时候就会输出fn1执行后的结果,在两秒跟三秒的时候会分别输出fn2、fn3的结果。这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作。例如图片请求,我们将一个延迟请求(假如是3秒)跟图片请求同时使用race方法调用,在3秒的时候如果请求成功了,就会resolve进入then方法,如果失败了就会进入catch方法输出图片资源请求失败的错误。
0
0
0
浏览量1032
武士先生

JavaScript中的设计模式-策略模式

前言设计模式在我们编程中是十分重要的!设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。最近在学习设计模式,铁铁们一起来卷嘛?什么是设计模式?在软件设计过程中,针对特定问题的简洁而优雅的解决方案。把之前的经验总结并且合理运用到某处场景上,能够解决实际的问题。设计模式五大设计原则(SOLID)S-单一职责原则 即一个程序只做好一件事O-开放封闭原则 可扩展开放,对修改封闭L-里氏置换原则 子类能覆盖父类,并能出现在父类出现的地方I-接口独立原则 保持接口的单一独立D-依赖导致原则 使用方法只关注接口而不关注具体类的实现为什么需要设计模式?易读性 使用设计模式能够提升我们的代码可读性,提升后续开发效率可拓展性 使用设计模式对代码解耦,能很好的增强代码的yi修改性和拓展性复用性 使用设计模式可以复用已有的解决方案,无需重复相同工作可靠性 使用设计模式能够增加系统的健壮性,使代码编写真正工程化策略模式定义:定义一系列的算法,把它们一个个的封装起来,并且使它们可以相互转换。把看似毫无联系的代码提取封装、复用,使之更容易被理解和拓展它就像我们解决问题的思路,有很多种,那我们就应该选择最适合解决当前业务的那一种。应用场景:要完成一件事情,有不同的策略。例如绩效计算、表单验证规则。来看这段代码:const calculateBonus = (level, salary) => { switch (level) { case 's': { return salary * 4; } case 'a': { return salary * 3; } case 'b': { return salary * 2; } default: { return 0 } } return strategies[level][salary]; }; calculateBonus['s',20000];函数calculateBonus接收level跟salary两个参数,运用switch case来计算绩效。但是在以后我们又要增加新绩效的时候,例如我们想加一个p绩效,我们要深入到代码中,去在switch case中去增加一个 case p的逻辑,这样子我们相当于每次业务变更都要去改造这个函数,就是不太好的情况。使用switch还好,代码看起来很清晰,如果是if else呢 不敢想象了~再看策略模式:const strategies = { s: (salary) => { return salary * 4; }, a: (salary) => { return salary * 3; }, b: (salary) => { return salary * 2; }, }; const caculateBonus = (level, salary) => { return strategies[level][salary]; }; caculateBonus('s',20000)也是有着一个caculateBonus函数,接收level跟salary两个参数,但是我们构建了一个策略表,在策略表中维护这个计算规则。这时候如果要加入一个新绩效等级,就去表中加入一个新等级,再加入它的计算规则即可,并且可以将表提取出来放到另一个文件中,甚至是放到公网上下发,因为这个表是可以不用程序员们来维护的,可以做成页面交给公司其他人员去维护,就有了前面说的那个下发的功能,可以去给别人展示。计算绩效的时候去读取策略表,完后根据表中规则来进行计算就行。
0
0
0
浏览量1584
武士先生

简单的了解一下递归

前言在编程中,递归大家肯定都不陌生了吧,今天我们来总结总结有关于递归的东西。什么?! 你陌生, 去刷题去,完后你就熟了。递归的定义程序调用自身的编程技巧称为递归递归长什么样子提到举例子我们肯定第一个想到的就是阶乘。n! = n * (n-1) * (n-2) * ...* 1(n>0) 5! = 5 * 4 * 3 * 2 * 1阶乘是我们从小学数学就接触的东西,没想到它现在还陪伴着我们🙄用代码实现一下:function recursion(n) { if (n == 1) return n return n * recursion(n - 1) } console.log(recursion(5)) // 5 * 4 * 3 * 2 * 1 = 120再举一个例子吧,也是我们初高中课本上的,著名的河内塔问题:这个问题也是用递归来解决的:function hanoi( n, p1, p2, p3) { if(1 == n) console.log('盘子从' + p1 + '移动到' + p3); else { hanoi(n-1, p1, p3, p2); console.log('盘子从' + p1 + '移动到' + p3); hanoi(n-1, p2, p1, p3); } } hanoi(3,'p1','p2','p3');总结一哈既然要写递归,就要知道它具备什么条件,从上面的例子不难看出,递归是具有边界条件的,阶乘中的n==1和河内塔中的1==n都是边界条件,递归还具有两个部分,边界条件满足的时候进入返回过程,边界条件不满足的时候,再次进入递归过程。那么它还具有什么特点呢?它必须有一个出口条件,也就是要有一处作为结束,变成非递归的样子处理。递归处理的子问题要同原问题一样,并且逐渐变得简单。总结一下:我们写的递归要具有边界条件,和基于边界条件分别做出的两部分处理过程。并且使用递归是要逐渐使得问题变得简单,最终用非递归的方法作为结尾哦。最后我觉得,递归是思考过后对于问题提出的一种解决方案。在你提出用递归解决的时候,就已经知道他的边界条件是什么了,只需要写出递归的那部分,随后使用非递归的方式作为程序出口即可。
0
0
0
浏览量1621
武士先生

用了那么久this了,还不了解它?

前言this想必大家都很不陌生了,在例如Vue.js中,各种this,唰唰唰的写,但是有没有遇到this指向出错的问题呢?我有,我猜应该也会有人跟我一样。所以,我总结了一些this的基础概念和基本使用在这里,供大家参考。this出现在哪里全局上下文中的thisconsole.log(this)来打印出来全局执行上下文中的 this,最终输出的是 window 对象。所以可以得出这样一个结论:全局执行上下文中的 this 是指向 window 对象的。这也是 this 和作用域链的唯一交点,作用域链的最底端包含了 window 对象,全局执行上下文中的 this 也是指向 window 对象函数上下文中的this在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window。通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身function foo(){ console.log(this) }; foo(); // window let a = { b:0, fn:function(){ console.log(this) } } a.fn(); //{ b:0, fn:f() }this指向总结当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数new 关键字构建好了一个新对象,并且构造函数中的 this 其实就是新对象本身嵌套函数中的 this 不会继承外层函数的 this 值。var myObj = { name : "Ned", showThis: function(){ console.log(this); // myObj var bar = function(){ this.name = "阿泽"; console.log(this) // window } bar(); } }; myObj.showThis(); console.log(myObj.name); // Ned console.log(window.name); // 阿泽解决this不继承的方法内部函数使用箭头函数将在外层函数中创建一个变量,用来存储this,内层函数通过作用域链即可访问var myObj = { name : "Ned", showThis:function(){ console.log(this); // myObj var bar = ()=>{ this.name = "阿泽"; console.log(this) // window } bar(); } }; myObj.showThis(); console.log(myObj.name); // 阿泽 console.log(window.name); // var myObj = { name : "Ned", showThis:function(){ console.log(this); // myObj var self = this; var bar = function (){ self.name = "阿泽"; console.log(self) // window } bar(); } }; myObj.showThis(); console.log(myObj.name); // 阿泽 console.log(window.name); // 改变this指向的方法call 和 apply 的共同点都能够改变函数执行时的上下文,将一个对象的方法交给另一个对象来执行,并且是立即执行的调用 call 和 apply 的对象,必须是一个函数 Functioncall 和 apply 的区别call 的写法Function.call(obj,param1,param2,...)需要注意以下几点:调用 call 的对象,必须是个函数 Function。call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。如果不传,则默认为全局对象 window。第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上。但是如果将所有的参数作为数组传入,它们会作为一个整体映射到 Function 对应的第一个参数上,之后参数都为空。function func (a,b,c) {} func.call(obj, 1,2,3) // func 接收到的参数实际上是 1,2,3 func.call(obj, [1,2,3]) // func 接收到的参数实际上是 [1,2,3],undefined,undefined // 其实func还是接收了三个参数,只不过咱们只传了一个过去,这个应该很容易理解的apply 的写法Function.apply(obj[,argArray])需要注意的是:它的调用者必须是函数 Function,并且只接收两个参数,第一个参数的规则与 call 一致。第二个参数,必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上。这也是 call 和 apply 之间,很重要的一个区别。func.apply(obj, [1,2,3]) // func 接收到的参数实际上是 1,2,3 func.apply(obj, { 0: 1, 1: 2, 2: 3, length: 3 }) // func 接收到的参数实际上是 1,2,3call 和 apply 的用途下面会分别列举 call 和 apply 的一些使用场景。声明:例子中没有哪个场景是必须用 call 或者必须用 apply 的,看个人习惯就好。call 的使用场景对象的继承如下面这个例子:function superClass () { this.a = 1; this.print = function () { console.log(this.a); } } function subClass () { superClass.call(this); // 执行superClass,并将superClass方法中的this指向subClass this.print(); } subClass(); // 1subClass 通过 call 方法,继承了 superClass 的 print 方法和 a 变量。此外,subClass 还可以扩展自己的其他方法。bindbind 的用法在 MDN 上的解释是:bind() 方法创建一个新的函数,在调用时设置 this 关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。它的语法如下:Function.bind(thisArg[, arg1[, arg2[, ...]]])bind 方法 与 apply 和 call 比较类似,也能改变函数体内的 this 指向。不同的是,bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用,来看下面这个例子:function add (c) { return this.a + this.b + c; } var obj = {a:1,b:2} add.bind(obj, 5); // 这时,并不会返回 8 add.bind(sub, 5)(); // 调用后,返回 8如果 bind 的第一个参数是 null 或者 undefined,this 就指向全局对象 window。最后"abc"的使用,具体还是要看个人运用,理解了之后,那它们就变成了工具,怎么顺手怎么来了~顺便说一下本人,我还是喜欢apply多一点hhh,用它的次数多一点,所以在场景用谁都行的时候,我一般都会选择apply。
0
0
0
浏览量1159
武士先生

JavaScript数组去重问题

前言👀数组去重应该是一个很常见的问题了,既然是常见的,那我们就更应该来学习一下!免得自己不会,尴尬呀~ 嘿嘿开始研究🐱‍🏍原始🧶数组去重,最开始我的思路是这样:定义一个新数组,完后两层for循环,如果数据第一次出现,就push到新数组里,如果重复就break掉,利用j的值与res长度相等这一点来判断数据唯一,最后返回新数组就行了。var arr = [1,1,2,3,4,5,6,7,4,3,'1',8,'3','1','3','66'] function unique(arr){ var res = [] for(var i = 0; i < arr.length; i++){ for(var j = 0; j < res.length; j ++){ if(arr[i] === res[j]){ break } } // 如果数据第一次出现,那么执行完上面for语句后,j的值应该等于res的长度才对 if(j === res.length){ res.push(arr[i]) } } return res; } console.log(unique(arr));利用indexOf优化原始方法✍我们先来简单了解一下indexOf:indexOf(item,start) 方法可返回数组中某个指定的元素位置。该方法将从头到尾地检索数组,看它是否含有对应的元素。开始检索的位置在数组 start 处或数组的开头(没有指定 start 参数时)。如果找到一个 item,则返回 item 的第一次出现的位置。开始位置的索引为 0。如果在数组中没找到指定元素则返回 -1。看到这大家都明白我们利用的是哪一点了吧,没错,就是加粗的那一句话:如果在数组中没找到指定元素则返回 -1。var arr = [1,1,2,3,4,5,6,7,4,3,'1',8,'3','1','3','66'] function unique(arr){ var res = [] for(var i = 0; i < arr.length; i++){ if(res.indexOf(arr[i]) === -1){ res.push(arr[i]) } } return res; } console.log(unique(arr));再次优化,filter方法🎉filter,顾名思义,过滤的意思,该方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。思路:用filter代替一层循环与indexOf配合,达到过滤效果,直接返回去重过后的数组。var arr = [1,1,2,3,4,5,6,7,4,3,'1',8,'3','1','3','66'] function unique(arr){ var res = arr.filter(function(item,index,arr){ return arr.indexOf(item) === index }) return res; } console.log(unique(arr));换种思路?变成有序数组✨不知道刷过几天力扣的小伙伴们有没有这种感觉,看见题目中出现数组,眼睛就立刻往前瞄了瞄,看看是有序数组还是无序数组~回到这个问题上,我们将要去重的数组变成有序,重复的数据肯定都挨着了,用一个变量存放上一个元素值,再循环判断当前值与上一个元素值是否相同,如果不相同,就将它添加到res中。var arr = [1,1,2,3,4,5,6,7,4,3,'1',8,'3','1','3','66'] function unique(arr){ var res = [] var pre arr = arr.sort() for(var i = 0; i < arr.length; i++){ if(!i || pre !== arr[i]){ res.push(arr[i]) } pre = arr[i] } return res; } console.log(unique(arr));再再次优化,filter🧨刚刚悟了~,filter好像也可以把排序这里重写一下,变得更为简洁,我们直接看代码:var arr = [1,1,2,3,4,5,6,7,4,3,'1',8,'3','1','3','66'] function unique(arr){ var res = arr.sort().filter(function(item,index,arr){ return !index || item !== arr[index - 1] }) return res; } console.log(unique(arr));ES6,Set来袭🧸ES6给我们带来了很多好处,其中,map、set尤为优秀。Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。所以我们可以利用Set的这一特性,来进行去重处理。var arr = [1,1,2,3,4,5,6,7,4,3,'1',8,'3','1','3','66'] function unique(arr){ return Array.from(new Set(arr)) } console.log(unique(arr));注:Set是对象,所以要转成数组进行返回。懂解构赋值的你,可以再简化一点🧵var arr = [1,1,2,3,4,5,6,7,4,3,'1',8,'3','1','3','66'] function unique(arr){ return [...new Set(arr)] } console.log(unique(arr));想了解一下解构赋值的也可以先康康这个:解构运算符的理解与运用 之前学习,记录的笔记🎨继续优秀下去(箭头函数)🏆var arr = [1,1,2,3,4,5,6,7,4,3,'1',8,'3','1','3','66'] var unique = (arr) => [...new Set(arr)] console.log(unique(arr));最后📖从最开始的好几行代码,到最后利用箭头函数,可以一行就写完,足以见得,只有不断学习,才能写出更优雅简洁的代码。那我们,作为开发者,要努力学习,才能更好的去使用这门语言呀🎈
0
0
0
浏览量333
武士先生

JavaScript中的设计模式-代理模式

前言设计模式在我们编程中是十分重要的!设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。什么是设计模式?在软件设计过程中,针对特定问题的简洁而优雅的解决方案。把之前的经验总结并且合理运用到某处场景上,能够解决实际的问题。设计模式五大设计原则(SOLID)S-单一职责原则 即一个程序只做好一件事O-开放封闭原则 可扩展开放,对修改封闭L-里氏置换原则 子类能覆盖父类,并能出现在父类出现的地方I-接口独立原则 保持接口的单一独立D-依赖导致原则 使用方法只关注接口而不关注具体类的实现为什么需要设计模式?易读性 使用设计模式能够提升我们的代码可读性,提升后续开发效率可拓展性 使用设计模式对代码解耦,能很好的增强代码的yi修改性和拓展性复用性 使用设计模式可以复用已有的解决方案,无需重复相同工作可靠性 使用设计模式能够增加系统的健壮性,使代码编写真正工程化代理模式定义:为一个对象提供一个代用品或者占位符,以便控制对它的访问。替身对象可对请求预先进行处理,再决定是否转交给本体对象。假如项目中一个图片过大,短时间内加载不出来的时候,用户只能看见白屏,体验感就会非常糟糕,这时候我们就可以用一个替身来暂时替代图片。 当真身被访问的时候,我们先访问替身对象,让替身代替真身去做一些事情,之后在转交给真身处理。应用场景:当我们不方便直接访问某个对象时,或不满足需求时,可考虑使用一个替身对象来控制该对象的访问。来看这段代码:// 原生函数 const rawImage = (() => { const imgNode = document.createElement("img"); document.body.appendChild(imgNode); return { setSrc:(src)=> { imgNode.src = "./loading.gif"; const img = new Image(); img.src = src; img.onload = () => { imgNode.src = this.src; } } } })(); rawImage.setSrc("http://xxx.gif");这个xxx.gif的加载时间可能要到10s左右,我们首先预加载了一个loading图,用户看到的过程是最开始看见loading,当xxx加载完成后,也就是onload执行完后,就会做一个替换的效果,完成了这个功能。思考:这段代码是不是耦合性太强了啊?是的,loading的逻辑跟我们实际加载的逻辑是耦合在一起的,我们要做两个事情,一个是设置loading,一个是设置图片链接。那我们给他分开,用代理函数去做预处理,加载loading,用原生函数提供一个设置图片链接的功能。实现一下:// 原生函数 const rawImage = (() => { const imgNode = document.createElement("img"); document.body.appendChild(imgNode); return { setSrc:(src)=> { imgNode.src = src; }, }; })(); // 代理函数 const proxyImage = (() => { const img = new Image(); img.onload = () =>{ rawImage.setSrc(this.src); }; return { setSrc:(src) => { rawImage.setSrc("./loading.gif"); img.src = src; }, }; })(); proxyImage.setSrc("http://xxx.gif");我们通常会进行一些请求的预处理的时候使用代理模式。
0
0
0
浏览量2012
武士先生

关于TCP与UDP你应该知道的

TCP/IP四层协议族1. 网络连接OSI七层模型TCP/IP四层协议族对应网络协议应用层(Application)应用层HTTP、TFTP, FTP, NFS, WAIS、SMTP表示层(Presentation)Telnet, Rlogin, SNMP, Gopher会话层(Session)SMTP, DNS传输层(Transport)传输层TCP, UDP网络层(Network)网络层IP, ICMP, ARP, RARP, AKP, UUCP数据链路层(Data Link)数据链路层FDDI, Ethernet, Arpanet, PDN, SLIP, PPP物理层(Physical)IEEE 802.1A, IEEE 802.2到IEEE 802.11OSI 模型,全称为 Open System Interconnection,即开放系统互联模型,这个是由 ISO(International Organization for Standardization) 国际标准化组织提出的。 它主要是用来解决当时各个网络技术供应商在协议上无法统一的问题,通过将整个网络体系结构抽象为 7层,从最底层的物理层、数据链路层一直到最上面的应用层都做了定义。TCP/IP,即 TCP/IP Protocol Suite(协议套件)是一个以TCP协议和IP协议为核心的通信模型,该模型采用协议堆栈的方式来实现许多通信协议,并将通讯体系抽象为4层。 TCP/IP 模型最早发源于美国国防部(缩写为DoD)的ARPA网项目,此后就交由IETF组织来维护。2. 网络层:简化的IP网络三层传输 2.1 IP头信息IP 头是 IP 数据包开头的信息,包含 IP 版本、源 IP 地址、目标 IP 地址、生存时间等信息2.2 IP层数据传输步骤1. 上层将含有“data”的数据包交给网络层;2. 网络层再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层;3. 底层通过物理网络将数据包传输给主机 B;4. 数据包被传输到主机 B 的网络层,在这里主机 B 拆开数据包的 IP 头信息,并将拆开来的数据部分交给上层;最终,含有“data”信息的数据包就到达了主机 B 的上层了。3. 传输层:简化的UDP四层网络连接 3.1 UDP作用IP 通过 IP 地址信息把数据包发送给指定的电脑,而 UDP 通过端口号把数据包分发给正确的程序3.2 UDP头信息端口号会被装进 UDP 头里面,UDP 头再和原始数据包合并组成新的 UDP 数据包。UDP 头中除了目的端口,还有源端口号等信息3.3 UDP层传输步骤1. 上层将含有“data”的数据包交给传输层;2. 传输层会在数据包前面附加上 UDP 头,组成新的 UDP 数据包,再将新的 UDP 数据包交给网络层;3. 网络层再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层;4. 数据包被传输到主机 B 的网络层,在这里主机 B 拆开 IP 头信息,并将拆开来的数据部分交给传输层;5. 在传输层,数据包中的 UDP 头会被拆开,并根据 UDP 中所提供的端口号,把数据部分交给上层的应用程序;最终,含有“data”信息的数据包就旅行到了主机 B 上层应用程序这3.4 UDP数据传输的特点在使用 UDP 发送数据时,有各种因素会导致数据包出错,虽然 UDP 可以校验数据是否正确,但是对于错误的数据包,UDP 并不提供重发机制,只是丢弃当前的包,而且 UDP 在发送之后也无法知道是否能达到目的地3.5 UDP的应用UDP 不能保证数据可靠性,但是传输速度却非常快,所以 UDP 会应用在一些关注速度、但不那么严格要求数据完整性的领域,如在线视频、互动游戏等4. 传输层:简化的TCP四层网络连接4.1 TCP头信息TCP 头除了包含了目标端口和本机端口号外,还提供了用于排序的序列号,以便接收端通过序号来重排数据包4.2 TCP连接的生命周期1. 建立建立阶段(三次握手) 客户端向服务器端发送建立连接请求,服务器端做出应答,客户端告知服务器端收到应答 目的:对于客户端和服务器端发出的消息都能收到回复,才是可靠的2.数据传输阶段 对于单个数据包:接收端需要对每个数据包进行确认操作。 也就是接收端在接收到数据包之后,需要发送确认数据包给发送端。 所以当发送端发送了一个数据包之后,在规定时间内没有接收到接收端反馈的确认消息, 则判断为数据包丢失,并触发发送端的重发机制 对于大文件:一个大的文件在传输过程中会被拆分成很多小的数据包,这些数据包到达接收端后, 接收端会按照 TCP 头中的序号为其排序,从而保证组成完整的数据3. 断开连接阶段(四次挥手) 可由客户端或者服务器端任何一端发起断开请求 客户端和服务器端都需要是断开就绪状态,才能真正断开。 如:客户端发起断开请求,服务器端确认,返回断开确认信息,客户端处于断开就绪状态。当服务器端处 理完毕,向客户端发送断开请求,客户端返回信息可以断开,服务器端就处于 断开就绪状态,此时客户端和服务器端都是断开就绪状态,则连接断开 4. 代码解释    SYN表示建立连接    FIN表示关闭连接    ACK表示响应    PSH表示有 DATA数据传输    RST表示连接重置wireshark 过滤语句:ip.addr == 127.0.0.1 && ip.dst == 127.0.0.1 && tcp.port == 531004.3 TCP数据传输的特点TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议· 对于数据包丢失的情况,TCP 提供重传机制;· TCP 引入了数据包排序机制,用来保证把乱序的数据包组合成一个完整的文件。
0
0
0
浏览量1510
武士先生

手把手带你快速入门Electron

看完本文你可学会📢对于electron有一些基本认识能够了解到electron的各个模块的基本作用了解进程通信,如何进行互相通信逐渐学会整活(这个才是目的)介绍这个最近真的看见的越来越多了,所以自己也在最近去学习了解了它,也在做一个小demo项目去熟练它,下面就跟我一起,简单了解一下它吧。官网:Electron 写一个自己的桌面应用真的好shuai创建项目🪁我采用的是vue create命令来建立的,有vue基础的朋友们应该都很熟悉了,我简单再说一下配置,采用的是vue3,勾选router,路由模式是hash,不用勾选vuex,就这样简简单单就好。完后我们要引入electron,执行npm install electron@18.0.3,完后等待进度条,这样我们就引入成功啦。接下来为了打包,我们在引入一个插件:electron-builder,使用vue add electron-builder就可以了。为了后续可能更一个electron实战系列,先做个建文件夹的操作吧~ 可能是画饼另外一种方便我们写demo或者入门瞎搞的,可以直接在文件夹中npm init -y之后引入electron依赖(依旧是npm install electron@18.0.3)即可我在最近刚刚学习的时候也是这么来的在做下文例子的时候,或者是在实验的时候就可以这么操作哦~至此,我们的项目如何建立算是介绍完成了。一些基本知识🎟️app模块const {app} from 'electron'来说一下它的生命周期吧:ready  应用程序初始化完成browser-window-created 窗口创建完成触发before-quit 窗口关闭之前will-quit 窗口关闭了 但是程序还没有关闭, 即将关闭quit 应用程序关闭触发还有一些,例如ready to showwhenReady我们都可以在对应的生命周期进行一些操作BrowserWindow模块看到这个名字我首先的反应就是它肯定跟我们看到的窗口有关,不知道大家是不是?没错他就是跟我们的窗口有关,下面简单介绍一下它的配置:const mainWin = new BrowserWindow({ width:800, height:600, resizable:true, // 规定窗口是否可以改变尺寸 默认是true // maxWidth:1000, //最大宽度 // maxHeight:800, //最大高度 // minWidth:600, //最小宽度 // minHeight:400, // 最小高度 show:false, // 设置窗口是否显示 默认true webPreferences:{ nodeIntegration:true, // 是否支持开启node,默认false contextIsolation:false // 是否开启上下文隔离 默认是true } Tray模块我们右下角(如果你没调整菜单栏的话),在启动应用的时候会有小图标,这个我们也是可以做到的~const {Tray,Menu} from 'electron' const tray = new Tray('icon-desk.png') tray.setToolTip('我的应用') const menu = Menu.buildFromTemplate([ { label:'设置', click:() => { console.log('我点击设置') } }, { label:'退出', click:() => { app.quit() } } ]) tray.setContextMenu(menu)setToolTip 是鼠标悬浮上去显示的一个小窗提示menu就是我们右键弹出来的一个菜单列表了,我们可以对其内容进行配置,也可以在click中处理点击之后的逻辑。一些方法再来介绍一些经常使用的方法:在使用方法前记得先new个对象const mainWin = new BrowserWindow(.....loadFile方法  它是用于加载本地文件,可以使用相对路径 也可以使用绝对路径mainWin.loadFile('./index.html') //html文件里随便放点东西,,,,还可以试试别的文件哦loadUrl方法  它是用于加载链接(远程文件)mainWin.loadUrl('https://juejin.cn/')show方法  它是用于控制展示我们的窗口有心的小伙伴可以看到上文中配置我们在show中写的是false,没错,我是在下文用show方法进行展示的,不是在最开始直接加载出来~mainWin.show()进程通信这个东西有点意思的,真的,这里要介绍两个新的模块,ipcRenderer与ipcMain前者是用在子进程中的,后者是用在主进程中的。一个最简单的demo,就是在一个html页面中,点击按钮,子进程传值到主进程(父进程)中打印出来,或者加上,主进程再回复过去,在子进程中打印出来。我们写一个点击事件,定义事件为renderer-send 传递过去document.querySelector('.btn').addEventListener('click',function(){ ipcRenderer.send('renderer-send','渲染进程传递过来的') })主进程接收,打印数据,定义事件为main-send,并返回接收信息ipcMain.on('renderer-send',(event,data) => { console.log(data) event.reply('main-send','我接受到了,现在给你') })子进程接收,打印出来ipcRenderer.on('main-send',(event,data) => { console.log(data) })最后🎉最近在整活了,可能后续真的会出一个electron的实战系列吧,也是对自己学习实践过程的一个总结,那还请敬请期待
0
0
0
浏览量271
武士先生

Node.js的非阻塞I/O

写在最前老早就想学Node.js了,觉得前端一定要是懂服务端知识,并且可以自己独立完成一个小型全栈项目的,但是碍于时间因素,上学期学了点基础之后,就再也没空学,正好今天科二过了,Node.js我又来卷你了!!!I/O就是input/output,一个系统的输入输出阻塞I/O和非阻塞I/O的区别就在于系统的接收输入,在到输出期间,能不能接收其他输入举个栗子去食堂吃饭:我们都要排队打饭   我们的流程是:排队------>等前面的人打饭-------->轮到我们自己打饭------->开始吃饭出去吃饭:餐厅点菜   现在我们的流程是:坐下------->点菜------->等待-------->开始吃饭 尝试开始在markdown中使用一些奇奇怪怪的东西排队打饭 vs 餐厅点菜对于点菜人员的我们来说: 排队打饭是阻塞I/O 餐厅点菜是非阻塞I/O继续来看最上面那句话:系统的接收输入,在到输出期间,能不能接收其他输入在栗子中,系统=食堂打饭的阿姨或者是餐厅服务生,输入=点菜,输出=端菜(上菜)食堂阿姨只能一份一份的打饭---------> 阻塞I/O服务生点完菜之后还可以服务其他客人------->非阻塞I/O其实,这个问题小学老师就教过我们小芳帮妈妈做家务,需要做:用洗衣机洗衣服(20分钟)、扫地(10分钟)、整理书桌(10分钟)、晾衣服(5分钟)。你能不能设计一个巧妙合理的新顺序,使小芳最少花( )分钟可以完成这些事?A.20 B.25 C.30 D.35没想到吧?(其实我也没想到~)在这个过程中,我们用洗衣机洗衣服=输入,晾衣服=输出,在洗衣机洗衣服的过程中,我们是可以去做其他事情的,所以这个属于非阻塞I/O哟。思考理解非阻塞I/O,首先要确定的是什么?我们要确定有一个输入/输出(input/output)的系统。思考在I/O过程中,能不能进行其他I/O。         能------>非阻塞        不能------->阻塞写个栗子先新建一个index.js,再打开我们前端人的vs-code,打开终端,执行npm install glob安装一个glob包,来帮我们更加方便去观察I/O是否阻塞。先来看阻塞I/O代码先贴上:index.jsconst glob = require('glob'); var result = null; console.time('glob') result = glob.sync(__dirname + '/**/*') console.timeEnd('glob') console.log(result) 首先,先用require引入我们的glob包,接下来,用glob.sync去做一个打印目录的一个操作,将结果打印的同时,利用time/timeEnd,去记录时间,看node执行这个操作共花费多少时间。在终端输入node index.js直接运行这个文件看第一行,在我的电脑上执行共花费20.93毫秒啊,这个数量在一个服务端来说,已经不小了。再看非阻塞I/O直接上代码:const glob = require('glob'); var result = null; console.time('glob') glob(__dirname + '/**/*',function(err,res){ result = res; // console.log(result) console.log('got result'); }) console.timeEnd('glob') console.log('今天你卷了没?')这次采用一个回调函数的方式来进行操作,因为result的打印出来实在太多行了,我们将其替换为打印出'got result',并在计时完成后进行一个打印语句的操作,让我们来看看结果:首先还是我们的时间:3.258ms,跟之前的20.93比起来,简直少了不要太多好嘛,完后是我们输出的语句,最后才打印了我们想要的操作结果,也就是说,它在输入输出之间进行了别的操作,对结果无影响,且用时少了好多!结论我的理解:非阻塞I/O让我们减少了许多等待的时间,并且在等待时间内,我们还可以进行一些其他的操作(欢迎大佬给予指点!!!!)凡事没有绝对,不是说非阻塞I/O就一定是好的,还是拿餐厅举例子,比如服务员中间发生意外,所有的客人都要等这一个服务员,这样就会影响餐厅整体质量(可以理解为服务器奔溃);阻塞I/O由于有多个服务员,一对一的服务,即使有一个发生意外,也不会影响整体质量,而雇多个服务员也要相应的付出成本。
0
0
0
浏览量1437
武士先生

Grafana Loki 配置解析

Grafana Loki 配置文件是一个YML文件,在Grafana Loki 快速尝鲜的示例中是loki-config.yaml,该文件包含关于Loki 服务和各个组件的配置信息。由于配置数量实在太多,没法全部翻译,只能后期有需要了再补充。如下是Grafana Loki 快速尝鲜一文中,安装完Loki后的默认配置:auth_enabled: false server: http_listen_port: 3100 grpc_listen_port: 9096 common: instance_addr: 127.0.0.1 path_prefix: /tmp/loki storage: filesystem: chunks_directory: /tmp/loki/chunks rules_directory: /tmp/loki/rules replication_factor: 1 ring: kvstore: store: inmemory query_range: results_cache: cache: embedded_cache: enabled: true max_size_mb: 100 schema_config: configs: - from: 2020-10-24 store: boltdb-shipper object_store: filesystem schema: v11 index: prefix: index_ period: 24h ruler: alertmanager_url: http://localhost:9093 # By default, Loki will send anonymous, but uniquely-identifiable usage and configuration # analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/ # # Statistics help us better understand how Loki is used, and they show us performance # levels for most users. This helps us prioritize features and documentation. # For more information on what's sent, look at # https://github.com/grafana/loki/blob/main/pkg/usagestats/stats.go # Refer to the buildReport method to see what goes into a report. # # If you would like to disable reporting, uncomment the following lines: #analytics: # reporting_enabled: false 配置文件占位符说明<boolean>布尔值,true或false<int>与[1-9]+[0-9]*匹配的整数<duration>与[0-9]+(ns|us|µs|ms|[smh])匹配的时间<labelname>与[a-zA-Z_][a-zA-Z0-9_]*匹配的字符串<labelvalue>unicode字符串<filename>相对路径或绝对路径<host>主机名或IP地址<string> string 字符串<secret>表示秘钥字符串Loki支持的配置预览# 要运行的组件列表,默认为all # 允许的值有all, compactor, distributor, ingester, querier, query-scheduler, # ingester-querier, query-frontend, index-gateway, ruler, table-manager, read, write # CLI 参数: -target [target: <string> | default = "all"] # 开启通过X-Scope-OrgID头认证, 如果是true必须存在该头,false则OrgID被设置为fake # CLI 参数: -auth.enabled [auth_enabled: <boolean> | default = true] # 为优化垃圾回收而预留的虚拟内存。以较大的内存为代价来减少垃圾回收次数 # CLI flag: -config.ballast-bytes [ballast_bytes: <int> | default = 0] # 配置已启动模块的服务 [server: <server>] # 配置distributor(分发器) [distributor: <distributor>] # 配置查询器。仅用于以all启动或者只运行了querier的时候 [querier: <querier>] # 配置后,将从query-frontend 分离查询队列 [query_scheduler: <query_scheduler>] # query-frontend端配置 [frontend: <frontend>] # 配置查询缓存和查询拆分 [query_range: <query_range>] # 配置ruler [ruler: <ruler>] # 配置ingester(拉取器) 客户端,仅在all、distributor、querier下有效 [ingester_client: <ingester_client>] # ingester 自身的配置 [ingester: <ingester>] # 配置索引,使得查询不需要经常跟对象存储交互 [index_gateway: <index_gateway>] # 配置各种存储,需要在schema_config中配置使用哪种存储 [storage_config: <storage_config>] # 配置如何缓存块,以及在保存到存储之前等待多久时间 [chunk_store_config: <chunk_store_config>] # 配置块索引结构和存储位置 [schema_config: <schema_config>] # compactor压缩组件配置,有利于提高性能。 # -boltdb.shipper.compactor. 已经过期,请使用-compactor. [compactor: <compactor>] # 配置全局或者每个租户的限制,比如ingestion_rate_mb,用来限制拉取速率 [limits_config: <limits_config>] # frontend_worker 主要配置frontend工作进程的最大并发数,frontend地址等 [frontend_worker: <frontend_worker>] # 数据库相关的配置 [table_manager: <table_manager>] # 仅当kvstore时,配置memberlist有效。 # 当至少定义了一个包含至少一个join_members的memberlist配置时,除非在组件配置部分中指定, # 否则所有需要ring的所有组件都会自动选择类型为memberlist的kvstore [memberlist: <memberlist>] # 运行时配置,负责重新加载配置文件 [runtime_config: <runtime_config>] # 跟踪配置 [tracing: <tracing>] # 分析配置,包括使用报告等 [analytics: <analytics>] # 多个模块之间共享的通用配置 # 如果在其他部分中给出了更具体的配置,则将忽略本部分中的相关配置 [common: <common>] # SIGTERM 和 shutdown 之间等待多长时间。 # 收到SIGTERM后,Loki将通过/ready端点报告503服务不可用 # CLI flag: -shutdown-delay [shutdown_delay: <duration> | default = 0s]-config.file-config.file 指定配置文件,逗号分割指定多个文件,但只会加载第一个文件。没有配置该参数,Loki会在当前工作目录和config/子目录中查找config.yaml。-print-config-stderr-print-config-stderr 打印配置文件-log-config-reverse-order-log-config-reverse-order反向打印配置文件,下图为打印的配置最后部分-config.expand-env=true-config.expand-env=true 开启环境变量引用,允许在Loki配置中引用环境变量的值${VAR}当环境变量不存在的时候,使用空字符串替代${VAR:-default_value}当环境变量不存在的时候,使用default_value替代比如:${USER:-http://localhost:9093},USER变量不存在,显示的值是这样的Loki 配置server配置已启动模块的服务# HTTP 服务器网络监听类型,默认tcp # CLI flag: -server.http-listen-network [http_listen_network: <string> | default = "tcp"] # HTTP服务器监听地址 # CLI flag: -server.http-listen-address [http_listen_address: <string> | default = ""] # HTTP服务器监听端口 # CLI flag: -server.http-listen-port [http_listen_port: <int> | default = 3100] # http最大连接数,<=0为禁用 # CLI flag: -server.http-conn-limit [http_listen_conn_limit: <int> | default = 0] # gRPC 监听类型 # CLI flag: -server.grpc-listen-network [grpc_listen_network: <string> | default = "tcp"] # gRPC监听地址 # CLI flag: -server.grpc-listen-address [grpc_listen_address: <string> | default = ""] # gRPC 监听端口 # CLI flag: -server.grpc-listen-port [grpc_listen_port: <int> | default = 9095] # gRPC同时最大连接数,<=0 为禁用 # CLI flag: -server.grpc-conn-limit [grpc_listen_conn_limit: <int> | default = 0] # 以逗号分隔的要使用的密码套件列表。如果为空,则使用默认的Go密码套件 # CLI flag: -server.tls-cipher-suites [tls_cipher_suites: <string> | default = ""] # 使用的最低TLS版本。允许值:VersionTLS10, VersionTLS11,VersionTLS12, VersionTLS13 # 如果为空,使用GO TLS 的最小版本 # CLI flag: -server.tls-min-version [tls_min_version: <string> | default = ""] http_tls_config: # HTTP服务cert路径 # CLI flag: -server.http-tls-cert-path [cert_file: <string> | default = ""] # HTTP服务key路径 # CLI flag: -server.http-tls-key-path [key_file: <string> | default = ""] # HTTP TLS Client Auth type. # CLI flag: -server.http-tls-client-auth [client_auth_type: <string> | default = ""] # HTTP TLS Client CA path. # CLI flag: -server.http-tls-ca-path [client_ca_file: <string> | default = ""] grpc_tls_config: # GRPC TLS server cert path. # CLI flag: -server.grpc-tls-cert-path [cert_file: <string> | default = ""] # GRPC TLS server key path. # CLI flag: -server.grpc-tls-key-path [key_file: <string> | default = ""] # GRPC TLS Client Auth type. # CLI flag: -server.grpc-tls-client-auth [client_auth_type: <string> | default = ""] # GRPC TLS Client CA path. # CLI flag: -server.grpc-tls-ca-path [client_ca_file: <string> | default = ""] # Register the intrumentation handlers (/metrics etc). # CLI flag: -server.register-instrumentation [register_instrumentation: <boolean> | default = true] # 正常停机超时时间 # CLI flag: -server.graceful-shutdown-timeout [graceful_shutdown_timeout: <duration> | default = 30s] # Read timeout for HTTP server # CLI flag: -server.http-read-timeout [http_server_read_timeout: <duration> | default = 30s] # Write timeout for HTTP server # CLI flag: -server.http-write-timeout [http_server_write_timeout: <duration> | default = 30s] # Idle timeout for HTTP server # CLI flag: -server.http-idle-timeout [http_server_idle_timeout: <duration> | default = 2m] # 能够接收的gRPC消息大小限制 # CLI flag: -server.grpc-max-recv-msg-size-bytes [grpc_server_max_recv_msg_size: <int> | default = 4194304] # 发送gRPC消息大小限制 # CLI flag: -server.grpc-max-send-msg-size-bytes [grpc_server_max_send_msg_size: <int> | default = 4194304] # gRPC调用并发数限制(0不限制) # CLI flag: -server.grpc-max-concurrent-streams [grpc_server_max_concurrent_streams: <int> | default = 100] # 空闲gRPC连接关闭时间,默认无穷 # CLI flag: -server.grpc.keepalive.max-connection-idle [grpc_server_max_connection_idle: <duration> | default = 2562047h47m16.854775807s] # 连接在关闭之前可能存在的最长时间的持续时间,默认无穷 # CLI flag: -server.grpc.keepalive.max-connection-age [grpc_server_max_connection_age: <duration> | default = 2562047h47m16.854775807s] # 连接最长使用时间后的附加时间,在该时间后,连接将被强制关闭,默认无穷 # CLI flag: -server.grpc.keepalive.max-connection-age-grace [grpc_server_max_connection_age_grace: <duration> | default = 2562047h47m16.854775807s] # 连接保活时间,默认2h # CLI flag: -server.grpc.keepalive.time [grpc_server_keepalive_time: <duration> | default = 2h] # 保活检查后,空闲连接关闭时间, 默认: 20s # CLI flag: -server.grpc.keepalive.timeout [grpc_server_keepalive_timeout: <duration> | default = 20s] # 客户端发送保活请求之间等待的最小时间,如果请求频繁,服务端将发送GOAWAY并关闭连接 # CLI flag: -server.grpc.keepalive.min-time-between-pings [grpc_server_min_time_between_pings: <duration> | default = 10s] # 如果为true,服务端允许保活即使没有活动流(RPCs),如果fasle,服务端将发送GOAWAY并关闭连接 # CLI flag: -server.grpc.keepalive.ping-without-stream-allowed [grpc_server_ping_without_stream_allowed: <boolean> | default = true] # 以给定的格式,输出日志,有效格式:logfmt, json # CLI flag: -log.format [log_format: <string> | default = "logfmt"] # 仅记录给定级别或以上的日志,可用级别:debug, info, warn, error # CLI flag: -log.level [log_level: <string> | default = "info"] # 可选,记录源ip # CLI flag: -server.log-source-ips-enabled [log_source_ips_enabled: <boolean> | default = false] # 存储源ip的header 字段,仅在 server.log-source-ips-enabled=true时有效 # 未配Forwarded, X-Real-IP 和 X-Forwarded-For,就使用header # CLI flag: -server.log-source-ips-header [log_source_ips_header: <string> | default = ""] #用于匹配源ip,仅当server.log-source-ips-enabled=true时有效 # CLI flag: -server.log-source-ips-regex [log_source_ips_regex: <string> | default = ""] # 可选,记录请求头 # CLI flag: -server.log-request-headers [log_request_headers: <boolean> | default = false] # 在info 级别记录请求,如果开启了server.log-request-headers,也记录 # CLI flag: -server.log-request-at-info-level-enabled [log_request_at_info_level_enabled: <boolean> | default = false] # 逗号分割,要排除的header列表,仅当server.log-request-headers=true时有效 # CLI flag: -server.log-request-headers-exclude-list [log_request_exclude_headers_list: <string> | default = ""] # 所有路由的基本路径,比如/v1/ # CLI flag: -server.path-prefix [http_path_prefix: <string> | default = ""]distributor配置分发器,distributorring: kvstore: # ring 后端存储,支持consul, etcd,inmemory, memberlist, multi # CLI flag: -distributor.ring.store [store: <string> | default = "consul"] # 存储中的key前缀,应该以/结尾 # CLI flag: -distributor.ring.prefix [prefix: <string> | default = "collectors/"] # Consul配置,仅kvstore为consul时有效 # The CLI flags prefix for this block configuration is: distributor.ring [consul: <consul>] # ETCD v3 配置,仅在kvstore选择etcd时有效 # The CLI flags prefix for this block configuration is: distributor.ring [etcd: <etcd>] multi: # multi-client 时,使用的主要后端存储 # CLI flag: -distributor.ring.multi.primary [primary: <string> | default = ""] # multi-client 时,辅助后端存储 # CLI flag: -distributor.ring.multi.secondary [secondary: <string> | default = ""] # 镜像写入辅助存储 # CLI flag: -distributor.ring.multi.mirror-enabled [mirror_enabled: <boolean> | default = false] # 辅助存储写入超时时间 # CLI flag: -distributor.ring.multi.mirror-timeout [mirror_timeout: <duration> | default = 2s] # ring的心跳周期,0为禁用 # CLI flag: -distributor.ring.heartbeat-period [heartbeat_period: <duration> | default = 5s] # 心跳检测超时后,分发器被认为在ring内是不健康的,0为永不(超时禁用) # CLI flag: -distributor.ring.heartbeat-timeout [heartbeat_timeout: <duration> | default = 1m] # 要从中读取的网络接口名称 # CLI flag: -distributor.ring.instance-interface-names [instance_interface_names: <list of strings> | default = [<private network interfaces>]] rate_store: # 向ingester请求的最大并发数 # CLI flag: -distributor.rate-store.max-request-parallelism [max_request_parallelism: <int> | default = 200] # 来自ingester的更新流速间隔 # CLI flag: -distributor.rate-store.stream-rate-update-interval [stream_rate_update_interval: <duration> | default = 1s] # 更新速率时,distributor 与 特定ingester之间的通信超时时间 # CLI flag: -distributor.rate-store.ingester-request-timeout [ingester_request_timeout: <duration> | default = 500ms] # 是否开启debug # CLI flag: -distributor.rate-store.debug [debug: <boolean> | default = false] # 实验性,自定义写入失败的日志 write_failures_logging: # 实验性会改变的配置,允许的每秒日志记录大小 # Default: 1KB. # CLI flag: -distributor.write-failures-logging.rate [rate: <int> | default = 1KB] # 实验性会改变的配置,insight=true key是否记录,默认false # CLI flag: -distributor.write-failures-logging.add-insights-label [add_insights_label: <boolean> | default = false]querier配置查询器。仅用于以all启动或者只运行了querier的时候# 提供实时跟踪请求的最长持续时间 # CLI flag: -querier.tail-max-duration [tail_max_duration: <duration> | default = 1h] # 发送超过最小成功查询请求数之前等待的时间 # CLI flag: -querier.extra-query-delay [extra_query_delay: <duration> | default = 0s] # 查询的最大回顾时间,超过该时间的查询不会发送到ingester,0表示所有请求发送到ingester # CLI flag: -querier.query-ingesters-within [query_ingesters_within: <duration> | default = 3h] engine: # 过时,使用querier.query-timeout代替。查询超时时间 # CLI flag: -querier.engine.timeout [timeout: <duration> | default = 5m] # 查看日志的最大时间,仅用于实时日志查询 # CLI flag: -querier.engine.max-lookback-period [max_look_back_period: <duration> | default = 30s] # 查询的最大并发数 # CLI flag: -querier.max-concurrent [max_concurrent: <int> | default = 10] # 为true,仅查询存储,不对接ingester。这在只针对存储数据运行独立的查询池时非常有用 # CLI flag: -querier.query-store-only [query_store_only: <boolean> | default = false] # 为true,仅查询ingester,而不查询存储。这在对象存储不可用时,非常有用 # CLI flag: -querier.query-ingester-only [query_ingester_only: <boolean> | default = false] # 如果为true,则允许查询跨越多个租户 # CLI flag: -querier.multi-tenant-queries-enabled [multi_tenant_queries_enabled: <boolean> | default = false] # 当为true时,将强制执行通过header发送的查询限制 # CLI flag: -querier.per-request-limits-enabled [per_request_limits_enabled: <boolean> | default = false]query_scheduler配置后,将从query-frontend 分离查询队列# 每个租户中每个query_scheduler的最大未处理请求数 # 如果超过了该限制,正在进行的请求将失败,报429错误 # CLI flag: -query-scheduler.max-outstanding-requests-per-tenant [max_outstanding_requests_per_tenant: <int> | default = 100] # 队列最大嵌套数,0意味着禁止 # CLI flag: -query-scheduler.max-queue-hierarchy-levels [max_queue_hierarchy_levels: <int> | default = 3] # 如果查询者在没有发送有关优雅关闭通知的情况下断开连接,查询调度程序将查询者保留在租户的shard中,直到过了延时时间。 # 当 shuffle-sharding开启的时候,这个功能有助于减小影响范围 # CLI flag: -query-scheduler.querier-forget-delay [querier_forget_delay: <duration> | default = 0s] # 该配置用于将错误返回到query-frontend的gRPC客户端 # The CLI flags prefix for this block configuration is: # query-scheduler.grpc-client-config [grpc_client_config: <grpc_client>] # 为true 表示创建query schedulers,并将本身也置于ring中。 # 如果没有frontend_address 或者 scheduler_address,该值被设置为true # CLI flag: -query-scheduler.use-scheduler-ring [use_scheduler_ring: <boolean> | default = false] # hash ring 配置,只有在use_scheduler_ring为true时有效 scheduler_ring: kvstore: # 使用ring时的存储,支持consul, etcd,inmemory, memberlist, multi # CLI flag: -query-scheduler.ring.store [store: <string> | default = "consul"] # 存储中key的前缀,应该以/结尾 # CLI flag: -query-scheduler.ring.prefix [prefix: <string> | default = "collectors/"] # Consul 客户端的配置。kvstore选择consul的时候有效 # The CLI flags prefix for this block configuration is: query-scheduler.ring [consul: <consul>] # ETCD v3 客户端配置,kvstore选择etcd时有效 # The CLI flags prefix for this block configuration is: query-scheduler.ring [etcd: <etcd>] multi: # multi-client 时主存储 # CLI flag: -query-scheduler.ring.multi.primary [primary: <string> | default = ""] # multi-client时的辅助存储 # CLI flag: -query-scheduler.ring.multi.secondary [secondary: <string> | default = ""] # 镜像写入辅助存储 # CLI flag: -query-scheduler.ring.multi.mirror-enabled [mirror_enabled: <boolean> | default = false] # 镜像写入辅助存储的超时时间 # CLI flag: -query-scheduler.ring.multi.mirror-timeout [mirror_timeout: <duration> | default = 2s] # ring的心跳周期,0为禁用 # CLI flag: -query-scheduler.ring.heartbeat-period [heartbeat_period: <duration> | default = 15s] # 心跳检测超时后,压缩器被认为在ring内是不健康的,0为永不(超时禁用) # CLI flag: -query-scheduler.ring.heartbeat-timeout [heartbeat_timeout: <duration> | default = 1m] # token 存储路径,如果为空,则在关闭时不存储令牌,在启动时恢复令牌 # CLI flag: -query-scheduler.ring.tokens-file-path [tokens_file_path: <string> | default = ""] # 为true表示可实现区域感知并跨不同可用性区域复制数据块 # CLI flag: -query-scheduler.ring.zone-awareness-enabled [zone_awareness_enabled: <boolean> | default = false] # 要在ring 中注册的实例id # CLI flag: -query-scheduler.ring.instance-id [instance_id: <string> | default = "<hostname>"] # 要从中读取地址的网络接口的名称 # CLI flag: -query-scheduler.ring.instance-interface-names [instance_interface_names: <list of strings> | default = [<private network interfaces>]] # 在ring 中的端口,默认使用server.grpc-listen-port # CLI flag: -query-scheduler.ring.instance-port [instance_port: <int> | default = 0] # 在ring 中的 IP 地址 # IP address to advertise in the ring. # CLI flag: -query-scheduler.ring.instance-addr [instance_addr: <string> | default = ""] # 运行该实例的可用性区域,如果zone-awareness 是开启的,则必选 # CLI flag: -query-scheduler.ring.instance-availability-zone [instance_availability_zone: <string> | default = ""] # 开启实例 IPv6 地址 # CLI flag: -query-scheduler.ring.instance-enable-ipv6 [instance_enable_ipv6: <boolean> | default = false]frontendquery-frontend端配置# 记录比指定时间慢的查询,0表示禁用, <0 开启所有查询 # Log queries that are slower than the specified duration. Set to 0 to disable. # Set to < 0 to enable on all queries. # CLI flag: -frontend.log-queries-longer-than [log_queries_longer_than: <duration> | default = 0s] # 下游prometheus的最大请求体 # Max body size for downstream prometheus. # CLI flag: -frontend.max-body-size [max_body_size: <int> | default = 10485760] # true表示开启查询统计信息跟踪,每个查询都会记录一条带有一些统计信息的消息 # True to enable query statistics tracking. When enabled, a message with some # statistics is logged for every query. # CLI flag: -frontend.query-stats-enabled [query_stats_enabled: <boolean> | default = false] # 每个租户每个frontend的最大请求数。超出这个数返回HTTP 429 # Maximum number of outstanding requests per tenant per frontend; requests # beyond this error with HTTP 429. # CLI flag: -querier.max-outstanding-requests-per-tenant [max_outstanding_per_tenant: <int> | default = 2048] #如果租户重复发送查询,导致查询器崩溃或因内存不足错误而终止, #则崩溃的查询器将与查询前端断开连接,并立即将新的查询器分配给租户的碎片 #这就使得哈希分片可以减少对租户的影响的假设失效。 #该选项通过在查询器因崩溃而断开连接和实际将崩溃的查询器从租户的分片中移除之间配置延迟来缓解影响 # In the event a tenant is repeatedly sending queries that lead the querier to # crash or be killed due to an out-of-memory error, the crashed querier will be # disconnected from the query frontend and a new querier will be immediately # assigned to the tenant’s shard. This invalidates the assumption that shuffle # sharding can be used to reduce the impact on tenants. This option mitigates # the impact by configuring a delay between when a querier disconnects because # of a crash and when the crashed querier is actually removed from the tenant's # shard. # CLI flag: -query-frontend.querier-forget-delay [querier_forget_delay: <duration> | default = 0s] # DNS 主机名用于查找query-schedulers # CLI flag: -frontend.scheduler-address [scheduler_address: <string> | default = ""] # query-schedulers解析评率,用于查找新的query-scheduler实例 # 如果配置了scheduler-ring,也用于确定获取scheduler-ring地址的频率 # How often to resolve the scheduler-address, in order to look for new # query-scheduler instances. Also used to determine how often to poll the # scheduler-ring for addresses if the scheduler-ring is configured. # CLI flag: -frontend.scheduler-dns-lookup-period [scheduler_dns_lookup_period: <duration> | default = 10s] #将查询转发到单个query-scheduler时的并发数 # Number of concurrent workers forwarding queries to single query-scheduler. # CLI flag: -frontend.scheduler-worker-concurrency [scheduler_worker_concurrency: <int> | default = 5] # grpc_client配置用于在两个Loki组件之间通信的grpc客户端,见后面的grpc_client配置 # The grpc_client block configures the gRPC client used to communicate between # two Loki components. # The CLI flags prefix for this block configuration is: # frontend.grpc-client-config [grpc_client_config: <grpc_client>] # 在关机之前等待请求完成的时间,需要跟查询器超时时间和优雅关机保持一致 # Time to wait for inflight requests to finish before forcefully shutting down. # This needs to be aligned with the query timeout and the graceful termination # period of the process orchestrator. # CLI flag: -frontend.graceful-shutdown-timeout [graceful_shutdown_timeout: <duration> | default = 5m] # 要从中读取地址的网络接口名称。这个地址被发送到query-scheduler和querier, # querier使用它将查询响应发送回query-frontend # Name of network interface to read address from. This address is sent to # query-scheduler and querier, which uses it to send the query response back to # query-frontend. # CLI flag: -frontend.instance-interface-names [instance_interface_names: <list of strings> | default = [<private network interfaces>]] # 是否压缩http响应 # Compress HTTP responses. # CLI flag: -querier.compress-http-responses [compress_responses: <boolean> | default = false] # 下游Loki的URL # URL of downstream Loki. # CLI flag: -frontend.downstream-url [downstream_url: <string> | default = ""] # 尾部代理的查询器 URL # URL of querier for tail proxy. # CLI flag: -frontend.tail-proxy-url [tail_proxy_url: <string> | default = ""] # TLS配置 # The TLS configuration. [tail_tls_config: <tls_config>]query_range配置查询缓存和查询拆分# 过期配置,使用-querier.split-queries-by-interval 替代 # Deprecated: Use -querier.split-queries-by-interval instead. CLI flag: # -querier.split-queries-by-day. Split queries by day and execute in parallel. [split_queries_by_interval: <duration>] # 将输入的查询进行变换,使其起始位置和结束位置与其步长对齐 # Mutate incoming queries to align their start and end with their step. # CLI flag: -querier.align-querier-with-step [align_queries_with_step: <boolean> | default = false] results_cache: # 缓存配置 # The cache block configures the cache backend. # The CLI flags prefix for this block configuration is: frontend [cache: <cache_config>] # 是否压缩,默认为空,禁用压缩。支持值有 'snappy' 和'' # Use compression in cache. The default is an empty value '', which disables # compression. Supported values are: 'snappy' and ''. # CLI flag: -frontend.compression [compression: <string> | default = ""] # 是否缓存查询结果 # Cache query results. # CLI flag: -querier.cache-results [cache_results: <boolean> | default = false] # 单个请求的最大重试次数。除外,还会返回下游错误 # Maximum number of retries for a single request; beyond this, the downstream # error is returned. # CLI flag: -querier.max-retries-per-request [max_retries: <int> | default = 5] # 基于存储分片配置和查询AST执行查询并行化,此功能仅由分块存储引擎支持 # Perform query parallelisations based on storage sharding configuration and # query ASTs. This feature is supported only by the chunks storage engine. # CLI flag: -querier.parallelise-shardable-queries [parallelise_shardable_queries: <boolean> | default = true] # 过期,查询前端转发到下游查询器的标头列表 # Deprecated. List of headers forwarded by the query Frontend to downstream # querier. # CLI flag: -frontend.forward-headers-list [forward_headers_list: <list of strings> | default = []] # 下游查询器指定的响应格式,可以是json或protobuf,两者还是通过GRPC路由 # The downstream querier is required to answer in the accepted format. Can be # 'json' or 'protobuf'. Note: Both will still be routed over GRPC. # CLI flag: -frontend.required-query-response-format [required_query_response_format: <string> | default = "json"] # 是否缓存索引统计信息查询结果 # Cache index stats query results. # CLI flag: -querier.cache-index-stats-results [cache_index_stats_results: <boolean> | default = false] # 如果未指定缓存配置,并且cache_index_stats_results是true,那么使用这个配置 # If a cache config is not specified and cache_index_stats_results is true, the # config for the results cache is used. index_stats_results_cache: # 缓存块配置 # The cache block configures the cache backend. # The CLI flags prefix for this block configuration is: # frontend.index-stats-results-cache [cache: <cache_config>] # 缓存压缩 # Use compression in cache. The default is an empty value '', which disables # compression. Supported values are: 'snappy' and ''. # CLI flag: -frontend.index-stats-results-cache.compression [compression: <string> | default = ""]ruler配置ruler规则# Grafana 实例的基础URL # Base URL of the Grafana instance. # CLI flag: -ruler.external.url [external_url: <url>] # 仪表板数据源UID # Datasource UID for the dashboard. # CLI flag: -ruler.datasource-uid [datasource_uid: <string> | default = ""] # 添加到所有警报的额外标签 # Labels to add to all alerts. [external_labels: <list of Labels>] #grpc_client配置用于在两个Loki组件之间通信的grpc客户端 # The grpc_client block configures the gRPC client used to communicate between # two Loki components. # The CLI flags prefix for this block configuration is: ruler.client [ruler_client: <grpc_client>] # 评估规则的频率 # How frequently to evaluate rules. # CLI flag: -ruler.evaluation-interval [evaluation_interval: <duration> | default = 1m] # 轮询规则更改的频率 # How frequently to poll for rule changes. # CLI flag: -ruler.poll-interval [poll_interval: <duration> | default = 1m] # 过期,使用-ruler-storage # Deprecated: Use -ruler-storage. CLI flags and their respective YAML config # options instead. storage: # 后端存储类型 # Method to use for backend rule storage (configdb, azure, gcs, s3, swift, # local, bos, cos) # CLI flag: -ruler.storage.type [type: <string> | default = ""] # Configures backend rule storage for Azure. # The CLI flags prefix for this block configuration is: ruler.storage [azure: <azure_storage_config>] # Configures backend rule storage for AlibabaCloud Object Storage (OSS). # The CLI flags prefix for this block configuration is: ruler [alibabacloud: <alibabacloud_storage_config>] # Configures backend rule storage for GCS. # The CLI flags prefix for this block configuration is: ruler.storage [gcs: <gcs_storage_config>] # Configures backend rule storage for S3. # The CLI flags prefix for this block configuration is: ruler [s3: <s3_storage_config>] # Configures backend rule storage for Baidu Object Storage (BOS). # The CLI flags prefix for this block configuration is: ruler.storage [bos: <bos_storage_config>] # Configures backend rule storage for Swift. # The CLI flags prefix for this block configuration is: ruler.storage [swift: <swift_storage_config>] # Configures backend rule storage for IBM Cloud Object Storage (COS). # The CLI flags prefix for this block configuration is: ruler.storage [cos: <cos_storage_config>] # Configures backend rule storage for a local file system directory. local: # Directory to scan for rules # CLI flag: -ruler.storage.local.directory [directory: <string> | default = ""] #用于存储临时规则文件的文件路径 # File path to store temporary rule files. # CLI flag: -ruler.rule-path [rule_path: <string> | default = "/rules"] # 要向其发送通知的Alertmanager URL的逗号分隔列表 # 每个Alertmanager URL在配置中被视为一个单独的组 # 通过“-duler.alertmanager discovery”使用DNS解析,可以支持每个组HA中的多个alertmanager # Comma-separated list of Alertmanager URLs to send notifications to. Each # Alertmanager URL is treated as a separate group in the configuration. Multiple # Alertmanagers in HA per group can be supported by using DNS resolution via # '-ruler.alertmanager-discovery'. # CLI flag: -ruler.alertmanager-url [alertmanager_url: <string> | default = ""] # 使用DNS SRV记录来发现Alertmanager主机 # Use DNS SRV records to discover Alertmanager hosts. # CLI flag: -ruler.alertmanager-discovery [enable_alertmanager_discovery: <boolean> | default = false] # 刷新Alertmanager主机的DNS解析之间需要等待多长时间 # How long to wait between refreshing DNS resolutions of Alertmanager hosts. # CLI flag: -ruler.alertmanager-refresh-interval [alertmanager_refresh_interval: <duration> | default = 1m] # 如果启用了对警报管理器的请求,则将使用V2 API # If enabled requests to Alertmanager will utilize the V2 API. # CLI flag: -ruler.alertmanager-use-v2 [enable_alertmanager_v2: <boolean> | default = false] # 警报重新配置列表 # List of alert relabel configs. [alert_relabel_configs: <relabel_config...>] # 要发送到Alertmanager的通知的队列容量 # Capacity of the queue for notifications to be sent to the Alertmanager. # CLI flag: -ruler.notification-queue-capacity [notification_queue_capacity: <int> | default = 10000] # 向Alertmanager发送通知时的HTTP超时持续时间 # HTTP timeout duration when sending notifications to the Alertmanager. # CLI flag: -ruler.notification-timeout [notification_timeout: <duration> | default = 10s] alertmanager_client: # 客户端证书路径,用于服务器身份验证,还要配置key路径 # Path to the client certificate, which will be used for authenticating with # the server. Also requires the key path to be configured. # CLI flag: -ruler.alertmanager-client.tls-cert-path [tls_cert_path: <string> | default = ""] # 客户端证书key路径,要求客户端证书配置 # Path to the key for the client certificate. Also requires the client # certificate to be configured. # CLI flag: -ruler.alertmanager-client.tls-key-path [tls_key_path: <string> | default = ""] # Path to the CA certificates to validate server certificate against. If not # set, the host's root CA certificates are used. # CLI flag: -ruler.alertmanager-client.tls-ca-path [tls_ca_path: <string> | default = ""] # Override the expected name on the server certificate. # CLI flag: -ruler.alertmanager-client.tls-server-name [tls_server_name: <string> | default = ""] # Skip validating server certificate. # CLI flag: -ruler.alertmanager-client.tls-insecure-skip-verify [tls_insecure_skip_verify: <boolean> | default = false] # Override the default cipher suite list (separated by commas). Allowed # values: # # Secure Ciphers: # - TLS_RSA_WITH_AES_128_CBC_SHA # - TLS_RSA_WITH_AES_256_CBC_SHA # - TLS_RSA_WITH_AES_128_GCM_SHA256 # - TLS_RSA_WITH_AES_256_GCM_SHA384 # - TLS_AES_128_GCM_SHA256 # - TLS_AES_256_GCM_SHA384 # - TLS_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 # # Insecure Ciphers: # - TLS_RSA_WITH_RC4_128_SHA # - TLS_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_RSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 # CLI flag: -ruler.alertmanager-client.tls-cipher-suites [tls_cipher_suites: <string> | default = ""] # Override the default minimum TLS version. Allowed values: VersionTLS10, # VersionTLS11, VersionTLS12, VersionTLS13 # CLI flag: -ruler.alertmanager-client.tls-min-version [tls_min_version: <string> | default = ""] # HTTP Basic 认证的username,会覆盖URL中设置的username # HTTP Basic authentication username. It overrides the username set in the URL # (if any). # CLI flag: -ruler.alertmanager-client.basic-auth-username [basic_auth_username: <string> | default = ""] # HTTP Basic 认证的password,会覆盖URL中设置的password # HTTP Basic authentication password. It overrides the password set in the URL # (if any). # CLI flag: -ruler.alertmanager-client.basic-auth-password [basic_auth_password: <string> | default = ""] # HTTP头认证类型 # HTTP Header authorization type (default: Bearer). # CLI flag: -ruler.alertmanager-client.type [type: <string> | default = "Bearer"] # HTTP 头授权凭证 # HTTP Header authorization credentials. # CLI flag: -ruler.alertmanager-client.credentials [credentials: <string> | default = ""] # HTTP头授权凭证文件 # HTTP Header authorization credentials file. # CLI flag: -ruler.alertmanager-client.credentials-file [credentials_file: <string> | default = ""] # for状态评估的最大时间 # Max time to tolerate outage for restoring "for" state of alert. # CLI flag: -ruler.for-outage-tolerance [for_outage_tolerance: <duration> | default = 1h] # 警报和恢复 for 状态之间的最短持续时间。仅当配置的 for 时间大于这个宽限期时,才维护 # Minimum duration between alert and restored "for" state. This is maintained # only for alerts with configured "for" time greater than the grace period. # CLI flag: -ruler.for-grace-period [for_grace_period: <duration> | default = 10m] # 向 Alertmanager重新发送警报之前等待的最短时间 # Minimum amount of time to wait before resending an alert to Alertmanager. # CLI flag: -ruler.resend-delay [resend_delay: <duration> | default = 1m] # 使用环形后端分发规则评估 # Distribute rule evaluation using ring backend. # CLI flag: -ruler.enable-sharding [enable_sharding: <boolean> | default = false] # 使用的分片策略。支持:default, shuffle-sharding # The sharding strategy to use. Supported values are: default, shuffle-sharding. # CLI flag: -ruler.sharding-strategy [sharding_strategy: <string> | default = "default"] # 决定如何对规则和组进行分片的分片算法 # The sharding algorithm to use for deciding how rules & groups are sharded. # Supported values are: by-group, by-rule. # CLI flag: -ruler.sharding-algo [sharding_algo: <string> | default = "by-group"] # 关闭时花在搜索待处理规则上的时间 # Time to spend searching for a pending ruler when shutting down. # CLI flag: -ruler.search-pending-for [search_pending_for: <duration> | default = 5m] # Loki 规则使用的Ring # Ring used by Loki ruler. The CLI flags prefix for this block configuration is # 'ruler.ring'. ring: kvstore: # ring 使用的后端存储 # Backend storage to use for the ring. Supported values are: consul, etcd, # inmemory, memberlist, multi. # CLI flag: -ruler.ring.store [store: <string> | default = "consul"] # The prefix for the keys in the store. Should end with a /. # CLI flag: -ruler.ring.prefix [prefix: <string> | default = "rulers/"] # Configuration for a Consul client. Only applies if the selected kvstore is # consul. # The CLI flags prefix for this block configuration is: ruler.ring [consul: <consul>] # Configuration for an ETCD v3 client. Only applies if the selected kvstore # is etcd. # The CLI flags prefix for this block configuration is: ruler.ring [etcd: <etcd>] multi: # multi-client 时主存储 # Primary backend storage used by multi-client. # CLI flag: -ruler.ring.multi.primary [primary: <string> | default = ""] # multi-client 时辅助存储 # Secondary backend storage used by multi-client. # CLI flag: -ruler.ring.multi.secondary [secondary: <string> | default = ""] # 镜像写入辅助存储 # Mirror writes to secondary store. # CLI flag: -ruler.ring.multi.mirror-enabled [mirror_enabled: <boolean> | default = false] # 镜像写入辅助存储的超时时间 # Timeout for storing value to secondary store. # CLI flag: -ruler.ring.multi.mirror-timeout [mirror_timeout: <duration> | default = 2s] # ring的心跳周期,0为禁用 # Interval between heartbeats sent to the ring. 0 = disabled. # CLI flag: -ruler.ring.heartbeat-period [heartbeat_period: <duration> | default = 5s] # 心跳检测超时后,这个ruler再环内被认为是不健康的 # The heartbeat timeout after which ruler ring members are considered # unhealthy within the ring. 0 = never (timeout disabled). # CLI flag: -ruler.ring.heartbeat-timeout [heartbeat_timeout: <duration> | default = 1m] # 要从中读取地址的网络接口名称 # Name of network interface to read addresses from. # CLI flag: -ruler.ring.instance-interface-names [instance_interface_names: <list of strings> | default = [<private network interfaces>]] # 如果在没有从另一个lifecycler转移令牌的情况下加入,则lifecycler将生成并放入环中的令牌数 # The number of tokens the lifecycler will generate and put into the ring if # it joined without transferring tokens from another lifecycler. # CLI flag: -ruler.ring.num-tokens [num_tokens: <int> | default = 128] # 刷新规则组的时间段 # Period with which to attempt to flush rule groups. # CLI flag: -ruler.flush-period [flush_period: <duration> | default = 1m] # 开启规则api # Enable the ruler API. # CLI flag: -ruler.enable-api [enable_api: <boolean> | default = true] # 该规则可以评估的租户列表,逗号分割。如果指定,只有这些租户会被规则处理,否则该规则处理所有的租户 # Comma separated list of tenants whose rules this ruler can evaluate. If # specified, only these tenants will be handled by ruler, otherwise this ruler # can process rules from all tenants. Subject to sharding. # CLI flag: -ruler.enabled-tenants [enabled_tenants: <string> | default = ""] # 该规则不评估的租户列表 # Comma separated list of tenants whose rules this ruler cannot evaluate. If # specified, a ruler that would normally pick the specified tenant(s) for # processing will ignore them instead. Subject to sharding. # CLI flag: -ruler.disabled-tenants [disabled_tenants: <string> | default = ""] # 将 ruler 查询完成的总耗时作为每个用户的指标报告,并做为info 级别的日志信息 # Report the wall time for ruler queries to complete as a per user metric and as # an info level log message. # CLI flag: -ruler.query-stats-enabled [query_stats_enabled: <boolean> | default = false] # 再导出的指标中禁用rule_group # Disable the rule_group label on exported metrics. # CLI flag: -ruler.disable-rule-group-label [disable_rule_group_label: <boolean> | default = false] wal: # 预写日志(WAL)文件目录,每个租户都有一个自己的目录 # The directory in which to write tenant WAL files. Each tenant will have its # own directory one level below this directory. # CLI flag: -ruler.wal.dir [dir: <string> | default = "ruler-wal"] # WAL 压缩进程的频率 # Frequency with which to run the WAL truncation process. # CLI flag: -ruler.wal.truncate-frequency [truncate_frequency: <duration> | default = 1h] # Minimum age that samples must exist in the WAL before being truncated. # CLI flag: -ruler.wal.min-age [min_age: <duration> | default = 5m] # 开始压缩之前在WAL中存在的最大年龄 # Maximum age that samples must exist in the WAL before being truncated. # CLI flag: -ruler.wal.max-age [max_age: <duration> | default = 4h] wal_cleaner: # 用于清理的WAL的最小文件年龄 # The minimum age of a WAL to consider for cleaning. # CLI flag: -ruler.wal-cleaner.min-age [min_age: <duration> | default = 12h] # Deprecated: CLI flag -ruler.wal-cleaer.period. # Use -ruler.wal-cleaner.period instead. # # How often to run the WAL cleaner. 0 = disabled. # CLI flag: -ruler.wal-cleaner.period [period: <duration> | default = 0s] # 远程写入配置,用来发送规则样本到Prometheus远程端点 # Remote-write configuration to send rule samples to a Prometheus remote-write # endpoint. remote_write: # Deprecated: Use 'clients' instead. Configure remote write client. [client: <RemoteWriteConfig>] # 配置远程写入客户端。以远程客户端id做为map的key # Configure remote write clients. A map with remote client id as key. [clients: <map of string to RemoteWriteConfig>] # 是否启用远程写入功能 # Enable remote-write functionality. # CLI flag: -ruler.remote-write.enabled [enabled: <boolean> | default = false] # 刷新远程配置之间等待的最短时间 # Minimum period to wait between refreshing remote-write reconfigurations. # This should be greater than or equivalent to # -limits.per-user-override-period. # CLI flag: -ruler.remote-write.config-refresh-period [config_refresh_period: <duration> | default = 10s] # 规则评估配置 # Configuration for rule evaluation. evaluation: # 评估模式。可以是'local'或者'remote'。 # The evaluation mode for the ruler. Can be either 'local' or 'remote'. If set # to 'local', the ruler will evaluate rules locally. If set to 'remote', the # ruler will evaluate rules remotely. If unset, the ruler will evaluate rules # locally. # CLI flag: -ruler.evaluation.mode [mode: <string> | default = "local"] # 在规则评估之前等待的随机持续时间的上限,以避免在规则的并发执行过程中发生争用。 # 对于给定的规则,抖动会一致的计算。设为0表示禁用 # Upper bound of random duration to wait before rule evaluation to avoid # contention during concurrent execution of rules. Jitter is calculated # consistently for a given rule. Set 0 to disable (default). # CLI flag: -ruler.evaluation.max-jitter [max_jitter: <duration> | default = 0s] query_frontend: # GRPC listen address of the query-frontend(s). Must be a DNS address # (prefixed with dns:///) to enable client side load balancing. # CLI flag: -ruler.evaluation.query-frontend.address [address: <string> | default = ""] # Set to true if query-frontend connection requires TLS. # CLI flag: -ruler.evaluation.query-frontend.tls-enabled [tls_enabled: <boolean> | default = false] # Path to the client certificate, which will be used for authenticating with # the server. Also requires the key path to be configured. # CLI flag: -ruler.evaluation.query-frontend.tls-cert-path [tls_cert_path: <string> | default = ""] # Path to the key for the client certificate. Also requires the client # certificate to be configured. # CLI flag: -ruler.evaluation.query-frontend.tls-key-path [tls_key_path: <string> | default = ""] # Path to the CA certificates to validate server certificate against. If not # set, the host's root CA certificates are used. # CLI flag: -ruler.evaluation.query-frontend.tls-ca-path [tls_ca_path: <string> | default = ""] # Override the expected name on the server certificate. # CLI flag: -ruler.evaluation.query-frontend.tls-server-name [tls_server_name: <string> | default = ""] # Skip validating server certificate. # CLI flag: -ruler.evaluation.query-frontend.tls-insecure-skip-verify [tls_insecure_skip_verify: <boolean> | default = false] # Override the default cipher suite list (separated by commas). Allowed # values: # # Secure Ciphers: # - TLS_RSA_WITH_AES_128_CBC_SHA # - TLS_RSA_WITH_AES_256_CBC_SHA # - TLS_RSA_WITH_AES_128_GCM_SHA256 # - TLS_RSA_WITH_AES_256_GCM_SHA384 # - TLS_AES_128_GCM_SHA256 # - TLS_AES_256_GCM_SHA384 # - TLS_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 # # Insecure Ciphers: # - TLS_RSA_WITH_RC4_128_SHA # - TLS_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_RSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 # CLI flag: -ruler.evaluation.query-frontend.tls-cipher-suites [tls_cipher_suites: <string> | default = ""] # Override the default minimum TLS version. Allowed values: VersionTLS10, # VersionTLS11, VersionTLS12, VersionTLS13 # CLI flag: -ruler.evaluation.query-frontend.tls-min-version [tls_min_version: <string> | default = ""]ingester_client配置ingester(拉取器) 客户端,仅在all、distributor、querier下有效# 连接池的配置 # Configures how connections are pooled. pool_config: # 多久清理一次,已删除的拉取器客户端 # How frequently to clean up clients for ingesters that have gone away. # CLI flag: -distributor.client-cleanup-period [client_cleanup_period: <duration> | default = 15s] # 在定期清理期间,对每个拉取器客户端进行健康检查 # Run a health check on each ingester client during periodic cleanup. # CLI flag: -distributor.health-check-ingesters [health_check_ingesters: <boolean> | default = true] # 检测到一个无效的客户端后,删除该客户端的时间。设置该值表示允许二次健康检查来恢复丢失的客户端。 # How quickly a dead client will be removed after it has been detected to # disappear. Set this to a value to allow time for a secondary health check to # recover the missing client. # CLI flag: -ingester.client.healthcheck-timeout [remote_timeout: <duration> | default = 1s] # 客户端的远程请求超时时间 # The remote request timeout on the client side. # CLI flag: -ingester.client.timeout [remote_timeout: <duration> | default = 5s] # 跟拉取器连接的gRPC客户端配置 # Configures how the gRPC connection to ingesters work as a client. # The CLI flags prefix for this block configuration is: ingester.client [grpc_client_config: <grpc_client>]ingesteringester 自身的配置# 配置拉取器的生命周期如何运行以及在哪里注册发现 # Configures how the lifecycle of the ingester will operate and where it will # register for discovery. lifecycler: ring: kvstore: # Backend storage to use for the ring. Supported values are: consul, etcd, # inmemory, memberlist, multi. # CLI flag: -ring.store [store: <string> | default = "consul"] # The prefix for the keys in the store. Should end with a /. # CLI flag: -ring.prefix [prefix: <string> | default = "collectors/"] # Configuration for a Consul client. Only applies if the selected kvstore # is consul. [consul: <consul>] # Configuration for an ETCD v3 client. Only applies if the selected # kvstore is etcd. [etcd: <etcd>] multi: # Primary backend storage used by multi-client. # CLI flag: -multi.primary [primary: <string> | default = ""] # Secondary backend storage used by multi-client. # CLI flag: -multi.secondary [secondary: <string> | default = ""] # Mirror writes to secondary store. # CLI flag: -multi.mirror-enabled [mirror_enabled: <boolean> | default = false] # Timeout for storing value to secondary store. # CLI flag: -multi.mirror-timeout [mirror_timeout: <duration> | default = 2s] # The heartbeat timeout after which ingesters are skipped for reads/writes. # 0 = never (timeout disabled). # CLI flag: -ring.heartbeat-timeout [heartbeat_timeout: <duration> | default = 1m] # 要写入和读取的拉取器数量 # The number of ingesters to write to and read from. # CLI flag: -distributor.replication-factor [replication_factor: <int> | default = 3] # true 开启区域感知,并在不同的可用区域中复制拉取器样本 # True to enable the zone-awareness and replicate ingested samples across # different availability zones. # CLI flag: -distributor.zone-awareness-enabled [zone_awareness_enabled: <boolean> | default = false] # 要排除的区别列表,逗号分隔 # Comma-separated list of zones to exclude from the ring. Instances in # excluded zones will be filtered out from the ring. # CLI flag: -distributor.excluded-zones [excluded_zones: <string> | default = ""] 每个拉取器的token数量 # Number of tokens for each ingester. # CLI flag: -ingester.num-tokens [num_tokens: <int> | default = 128] # Period at which to heartbeat to consul. 0 = disabled. # CLI flag: -ingester.heartbeat-period [heartbeat_period: <duration> | default = 5s] # Heartbeat timeout after which instance is assumed to be unhealthy. 0 = # disabled. # CLI flag: -ingester.heartbeat-timeout [heartbeat_timeout: <duration> | default = 1m] # 在生成后观察令牌以解决冲突。在使用 gossiping ring 时很有用 # Observe tokens after generating to resolve collisions. Useful when using # gossiping ring. # CLI flag: -ingester.observe-period [observe_period: <duration> | default = 0s] # Period to wait for a claim from another member; will join automatically # after this. # CLI flag: -ingester.join-after [join_after: <duration> | default = 0s] # 内部就绪检查通过但尚未成功通过就绪端点后的最小等待时间 # 在实例就绪后和进行滚动更新之前,用于减慢部署控制器(例如 Kubernetes) # 以便给其余集群实例足够的时间来接收环更新 # Minimum duration to wait after the internal readiness checks have passed but # before succeeding the readiness endpoint. This is used to slowdown # deployment controllers (eg. Kubernetes) after an instance is ready and # before they proceed with a rolling update, to give the rest of the cluster # instances enough time to receive ring updates. # CLI flag: -ingester.min-ready-duration [min_ready_duration: <duration> | default = 15s] # Name of network interface to read address from. # CLI flag: -ingester.lifecycler.interface [interface_names: <list of strings> | default = [<private network interfaces>]] # Enable IPv6 support. Required to make use of IP addresses from IPv6 # interfaces. # CLI flag: -ingester.enable-inet6 [enable_inet6: <boolean> | default = false] # 退出前的休眠持续时间,确保指标删除 # Duration to sleep for before exiting, to ensure metrics are scraped. # CLI flag: -ingester.final-sleep [final_sleep: <duration> | default = 0s] # File path where tokens are stored. If empty, tokens are not stored at # shutdown and restored at startup. # CLI flag: -ingester.tokens-file-path [tokens_file_path: <string> | default = ""] # The availability zone where this instance is running. # CLI flag: -ingester.availability-zone [availability_zone: <string> | default = ""] # 完全关闭后从环中注销。 # 在与 -distributor.extend-writes=false 一起使用时,禁用它可以实现一致性命名的滚动重启 # Unregister from the ring upon clean shutdown. It can be useful to disable # for rolling restarts with consistent naming in conjunction with # -distributor.extend-writes=false. # CLI flag: -ingester.unregister-on-shutdown [unregister_on_shutdown: <boolean> | default = true] # When enabled the readiness probe succeeds only after all instances are # ACTIVE and healthy in the ring, otherwise only the instance itself is # checked. This option should be disabled if in your cluster multiple # instances can be rolled out simultaneously, otherwise rolling updates may be # slowed down. # CLI flag: -ingester.readiness-check-ring-health [readiness_check_ring_health: <boolean> | default = true] # 在环中广播的IP地址 # IP address to advertise in the ring. # CLI flag: -ingester.lifecycler.addr [address: <string> | default = ""] # port to advertise in consul (defaults to server.grpc-listen-port). # CLI flag: -ingester.lifecycler.port [port: <int> | default = 0] # ID to register in the ring. # CLI flag: -ingester.lifecycler.ID [id: <string> | default = "<hostname>"] # 在回退到刷新之前尝试传输块的次数,设置0或者小于0,表示禁用 # Number of times to try and transfer chunks before falling back to flushing. If # set to 0 or negative value, transfers are disabled. # CLI flag: -ingester.max-transfer-retries [max_transfer_retries: <int> | default = 0] # 每个流可以同时进行多少次刷新 # How many flushes can happen concurrently from each stream. # CLI flag: -ingester.concurrent-flushes [concurrent_flushes: <int> | default = 32] # 拉取器多久检查一次是否有块要刷新。 # 第一次刷新检查被延迟检查周期的0.8倍,上下浮动1% # How often should the ingester see if there are any blocks to flush. The first # flush check is delayed by a random time up to 0.8x the flush check period. # Additionally, there is +/- 1% jitter added to the interval. # CLI flag: -ingester.flush-check-period [flush_check_period: <duration> | default = 30s] # 取消刷新的超时时间 # The timeout before a flush is cancelled. # CLI flag: -ingester.flush-op-timeout [flush_op_timeout: <duration> | default = 10m] # 块刷新后再内存中保留的时间 # How long chunks should be retained in-memory after they've been flushed. # CLI flag: -ingester.chunks-retain-period [chunk_retain_period: <duration> | default = 0s] #如果块没有达到最大块大小,在刷新之前,块应该在内存中保留多长时间而没有更新 #这意味着,只要没有收到进一步的活动,半空的块在一定时间后仍将被刷新 # How long chunks should sit in-memory with no updates before being flushed if # they don't hit the max block size. This means that half-empty chunks will # still be flushed after a certain period as long as they receive no further # activity. # CLI flag: -ingester.chunks-idle-period [chunk_idle_period: <duration> | default = 30m] # 当超过该阈值时,头部的块将被切割和压缩 # The targeted _uncompressed_ size in bytes of a chunk block When this threshold # is exceeded the head block will be cut and compressed inside the chunk. # CLI flag: -ingester.chunks-block-size [chunk_block_size: <int> | default = 262144] #这是一个期望的大小,而不是确切的大小,如果由于其他原因(例如 chunk_idle_period)而刷新块, # 则块可能稍微大一些或小一些。 # 值为 0 会创建具有固定 10 个块的块,非零值会创建具有可变数量块以满足目标大小的块 # A target _compressed_ size in bytes for chunks. This is a desired size not an # exact size, chunks may be slightly bigger or significantly smaller if they get # flushed for other reasons (e.g. chunk_idle_period). A value of 0 creates # chunks with a fixed 10 blocks, a non zero value will create chunks with a # variable number of blocks to meet the target size. # CLI flag: -ingester.chunk-target-size [chunk_target_size: <int> | default = 1572864] # 压缩块算法(none, gzip, lz4-64k, snappy,lz4-256k, lz4-1M, lz4, flate, zstd) # The algorithm to use for compressing chunk. (none, gzip, lz4-64k, snappy, # lz4-256k, lz4-1M, lz4, flate, zstd) # CLI flag: -ingester.chunk-encoding [chunk_encoding: <string> | default = "gzip"] # The maximum duration of a timeseries chunk in memory. If a timeseries runs for # longer than this, the current chunk will be flushed to the store and a new # chunk created. # CLI flag: -ingester.max-chunk-age [max_chunk_age: <duration> | default = 2h] # Forget about ingesters having heartbeat timestamps older than # `ring.kvstore.heartbeat_timeout`. This is equivalent to clicking on the # `/ring` `forget` button in the UI: the ingester is removed from the ring. This # is a useful setting when you are sure that an unhealthy node won't return. An # example is when not using stateful sets or the equivalent. Use # `memberlist.rejoin_interval` > 0 to handle network partition cases when using # a memberlist. # CLI flag: -ingester.autoforget-unhealthy [autoforget_unhealthy: <boolean> | default = false] # Parameters used to synchronize ingesters to cut chunks at the same moment. # Sync period is used to roll over incoming entry to a new chunk. If chunk's # utilization isn't high enough (eg. less than 50% when sync_min_utilization is # set to 0.5), then this chunk rollover doesn't happen. # CLI flag: -ingester.sync-period [sync_period: <duration> | default = 0s] # Minimum utilization of chunk when doing synchronization. # CLI flag: -ingester.sync-min-utilization [sync_min_utilization: <float> | default = 0] # The maximum number of errors a stream will report to the user when a push # fails. 0 to make unlimited. # CLI flag: -ingester.max-ignored-stream-errors [max_returned_stream_errors: <int> | default = 10] # How far back should an ingester be allowed to query the store for data, for # use only with boltdb-shipper/tsdb index and filesystem object store. -1 for # infinite. # CLI flag: -ingester.query-store-max-look-back-period [query_store_max_look_back_period: <duration> | default = 0s] # The ingester WAL (Write Ahead Log) records incoming logs and stores them on # the local file systems in order to guarantee persistence of acknowledged data # in the event of a process crash. wal: # Enable writing of ingested data into WAL. # CLI flag: -ingester.wal-enabled [enabled: <boolean> | default = true] # Directory where the WAL data is stored and/or recovered from. # CLI flag: -ingester.wal-dir [dir: <string> | default = "wal"] # Interval at which checkpoints should be created. # CLI flag: -ingester.checkpoint-duration [checkpoint_duration: <duration> | default = 5m] # When WAL is enabled, should chunks be flushed to long-term storage on # shutdown. # CLI flag: -ingester.flush-on-shutdown [flush_on_shutdown: <boolean> | default = false] # Maximum memory size the WAL may use during replay. After hitting this, it # will flush data to storage before continuing. A unit suffix (KB, MB, GB) may # be applied. # CLI flag: -ingester.wal-replay-memory-ceiling [replay_memory_ceiling: <int> | default = 4GB] # Shard factor used in the ingesters for the in process reverse index. This MUST # be evenly divisible by ALL schema shard factors or Loki will not start. # CLI flag: -ingester.index-shards [index_shards: <int> | default = 32] # Maximum number of dropped streams to keep in memory during tailing. # CLI flag: -ingester.tailer.max-dropped-streams [max_dropped_streams: <int> | default = 10] # Path where the shutdown marker file is stored. If not set and # common.path_prefix is set then common.path_prefix will be used. # CLI flag: -ingester.shutdown-marker-path [shutdown_marker_path: <string> | default = ""]index_gateway# Defines in which mode the index gateway server will operate (default to # 'simple'). It supports two modes: # - 'simple': an index gateway server instance is responsible for handling, # storing and returning requests for all indices for all tenants. # - 'ring': an index gateway server instance is responsible for a subset of # tenants instead of all tenants. # CLI flag: -index-gateway.mode [mode: <string> | default = "simple"] # Defines the ring to be used by the index gateway servers and clients in case # the servers are configured to run in 'ring' mode. In case this isn't # configured, this block supports inheriting configuration from the common ring # section. ring: kvstore: # Backend storage to use for the ring. Supported values are: consul, etcd, # inmemory, memberlist, multi. # CLI flag: -index-gateway.ring.store [store: <string> | default = "consul"] # The prefix for the keys in the store. Should end with a /. # CLI flag: -index-gateway.ring.prefix [prefix: <string> | default = "collectors/"] # Configuration for a Consul client. Only applies if the selected kvstore is # consul. # The CLI flags prefix for this block configuration is: index-gateway.ring [consul: <consul>] # Configuration for an ETCD v3 client. Only applies if the selected kvstore # is etcd. # The CLI flags prefix for this block configuration is: index-gateway.ring [etcd: <etcd>] multi: # Primary backend storage used by multi-client. # CLI flag: -index-gateway.ring.multi.primary [primary: <string> | default = ""] # Secondary backend storage used by multi-client. # CLI flag: -index-gateway.ring.multi.secondary [secondary: <string> | default = ""] # Mirror writes to secondary store. # CLI flag: -index-gateway.ring.multi.mirror-enabled [mirror_enabled: <boolean> | default = false] # Timeout for storing value to secondary store. # CLI flag: -index-gateway.ring.multi.mirror-timeout [mirror_timeout: <duration> | default = 2s] # Period at which to heartbeat to the ring. 0 = disabled. # CLI flag: -index-gateway.ring.heartbeat-period [heartbeat_period: <duration> | default = 15s] # The heartbeat timeout after which compactors are considered unhealthy within # the ring. 0 = never (timeout disabled). # CLI flag: -index-gateway.ring.heartbeat-timeout [heartbeat_timeout: <duration> | default = 1m] # File path where tokens are stored. If empty, tokens are not stored at # shutdown and restored at startup. # CLI flag: -index-gateway.ring.tokens-file-path [tokens_file_path: <string> | default = ""] # True to enable zone-awareness and replicate blocks across different # availability zones. # CLI flag: -index-gateway.ring.zone-awareness-enabled [zone_awareness_enabled: <boolean> | default = false] # Instance ID to register in the ring. # CLI flag: -index-gateway.ring.instance-id [instance_id: <string> | default = "<hostname>"] # Name of network interface to read address from. # CLI flag: -index-gateway.ring.instance-interface-names [instance_interface_names: <list of strings> | default = [<private network interfaces>]] # Port to advertise in the ring (defaults to server.grpc-listen-port). # CLI flag: -index-gateway.ring.instance-port [instance_port: <int> | default = 0] # IP address to advertise in the ring. # CLI flag: -index-gateway.ring.instance-addr [instance_addr: <string> | default = ""] # The availability zone where this instance is running. Required if # zone-awareness is enabled. # CLI flag: -index-gateway.ring.instance-availability-zone [instance_availability_zone: <string> | default = ""] # Enable using a IPv6 instance address. # CLI flag: -index-gateway.ring.instance-enable-ipv6 [instance_enable_ipv6: <boolean> | default = false] # Deprecated: How many index gateway instances are assigned to each tenant. # Use -index-gateway.shard-size instead. The shard size is also a per-tenant # setting. # CLI flag: -replication-factor [replication_factor: <int> | default = 3]storage_config# The alibabacloud_storage_config block configures the connection to Alibaba # Cloud Storage object storage backend. # The CLI flags prefix for this block configuration is: common [alibabacloud: <alibabacloud_storage_config>] # The aws_storage_config block configures the connection to dynamoDB and S3 # object storage. Either one of them or both can be configured. [aws: <aws_storage_config>] # The azure_storage_config block configures the connection to Azure object # storage backend. [azure: <azure_storage_config>] # The bos_storage_config block configures the connection to Baidu Object Storage # (BOS) object storage backend. [bos: <bos_storage_config>] # Deprecated: Configures storing indexes in Bigtable. Required fields only # required when bigtable is defined in config. bigtable: # Bigtable project ID. # CLI flag: -bigtable.project [project: <string> | default = ""] # Bigtable instance ID. Please refer to # https://cloud.google.com/docs/authentication/production for more information # about how to configure authentication. # CLI flag: -bigtable.instance [instance: <string> | default = ""] # The grpc_client block configures the gRPC client used to communicate between # two Loki components. # The CLI flags prefix for this block configuration is: bigtable [grpc_client_config: <grpc_client>] # If enabled, once a tables info is fetched, it is cached. # CLI flag: -bigtable.table-cache.enabled [table_cache_enabled: <boolean> | default = true] # Duration to cache tables before checking again. # CLI flag: -bigtable.table-cache.expiration [table_cache_expiration: <duration> | default = 30m] # Configures storing chunks in GCS. Required fields only required when gcs is # defined in config. [gcs: <gcs_storage_config>] # Deprecated: Configures storing chunks and/or the index in Cassandra. cassandra: # Comma-separated hostnames or IPs of Cassandra instances. # CLI flag: -cassandra.addresses [addresses: <string> | default = ""] # Port that Cassandra is running on # CLI flag: -cassandra.port [port: <int> | default = 9042] # Keyspace to use in Cassandra. # CLI flag: -cassandra.keyspace [keyspace: <string> | default = ""] # Consistency level for Cassandra. # CLI flag: -cassandra.consistency [consistency: <string> | default = "QUORUM"] # Replication factor to use in Cassandra. # CLI flag: -cassandra.replication-factor [replication_factor: <int> | default = 3] # Instruct the cassandra driver to not attempt to get host info from the # system.peers table. # CLI flag: -cassandra.disable-initial-host-lookup [disable_initial_host_lookup: <boolean> | default = false] # Use SSL when connecting to cassandra instances. # CLI flag: -cassandra.ssl [SSL: <boolean> | default = false] # Require SSL certificate validation. # CLI flag: -cassandra.host-verification [host_verification: <boolean> | default = true] # Policy for selecting Cassandra host. Supported values are: round-robin, # token-aware. # CLI flag: -cassandra.host-selection-policy [host_selection_policy: <string> | default = "round-robin"] # Path to certificate file to verify the peer. # CLI flag: -cassandra.ca-path [CA_path: <string> | default = ""] # Path to certificate file used by TLS. # CLI flag: -cassandra.tls-cert-path [tls_cert_path: <string> | default = ""] # Path to private key file used by TLS. # CLI flag: -cassandra.tls-key-path [tls_key_path: <string> | default = ""] # Enable password authentication when connecting to cassandra. # CLI flag: -cassandra.auth [auth: <boolean> | default = false] # Username to use when connecting to cassandra. # CLI flag: -cassandra.username [username: <string> | default = ""] # Password to use when connecting to cassandra. # CLI flag: -cassandra.password [password: <string> | default = ""] # File containing password to use when connecting to cassandra. # CLI flag: -cassandra.password-file [password_file: <string> | default = ""] # If set, when authenticating with cassandra a custom authenticator will be # expected during the handshake. This flag can be set multiple times. # CLI flag: -cassandra.custom-authenticator [custom_authenticators: <list of strings> | default = []] # Timeout when connecting to cassandra. # CLI flag: -cassandra.timeout [timeout: <duration> | default = 2s] # Initial connection timeout, used during initial dial to server. # CLI flag: -cassandra.connect-timeout [connect_timeout: <duration> | default = 5s] # Interval to retry connecting to cassandra nodes marked as DOWN. # CLI flag: -cassandra.reconnent-interval [reconnect_interval: <duration> | default = 1s] # Number of retries to perform on a request. Set to 0 to disable retries. # CLI flag: -cassandra.max-retries [max_retries: <int> | default = 0] # Maximum time to wait before retrying a failed request. # CLI flag: -cassandra.retry-max-backoff [retry_max_backoff: <duration> | default = 10s] # Minimum time to wait before retrying a failed request. # CLI flag: -cassandra.retry-min-backoff [retry_min_backoff: <duration> | default = 100ms] # Limit number of concurrent queries to Cassandra. Set to 0 to disable the # limit. # CLI flag: -cassandra.query-concurrency [query_concurrency: <int> | default = 0] # Number of TCP connections per host. # CLI flag: -cassandra.num-connections [num_connections: <int> | default = 2] # Convict hosts of being down on failure. # CLI flag: -cassandra.convict-hosts-on-failure [convict_hosts_on_failure: <boolean> | default = true] # Table options used to create index or chunk tables. This value is used as # plain text in the table `WITH` like this, "CREATE TABLE # <generated_by_cortex> (...) WITH <cassandra.table-options>". For details, # see https://cortexmetrics.io/docs/production/cassandra. By default it will # use the default table options of your Cassandra cluster. # CLI flag: -cassandra.table-options [table_options: <string> | default = ""] # Deprecated: Configures storing index in BoltDB. Required fields only required # when boltdb is present in the configuration. boltdb: # Location of BoltDB index files. # CLI flag: -boltdb.dir [directory: <string> | default = ""] # Configures storing the chunks on the local file system. Required fields only # required when filesystem is present in the configuration. [filesystem: <local_storage_config>] # The swift_storage_config block configures the connection to OpenStack Object # Storage (Swift) object storage backend. [swift: <swift_storage_config>] # Deprecated: grpc_store: # Hostname or IP of the gRPC store instance. # CLI flag: -grpc-store.server-address [server_address: <string> | default = ""] hedging: # If set to a non-zero value a second request will be issued at the provided # duration. Default is 0 (disabled) # CLI flag: -store.hedge-requests-at [at: <duration> | default = 0s] # The maximum of hedge requests allowed. # CLI flag: -store.hedge-requests-up-to [up_to: <int> | default = 2] # The maximum of hedge requests allowed per seconds. # CLI flag: -store.hedge-max-per-second [max_per_second: <int> | default = 5] # Configures additional object stores for a given storage provider. # Supported stores: aws, azure, bos, filesystem, gcs, swift. # Example: # storage_config: # named_stores: # aws: # store-1: # endpoint: s3://foo-bucket # region: us-west1 # Named store from this example can be used by setting object_store to store-1 # in period_config. [named_stores: <named_stores_config>] # The cos_storage_config block configures the connection to IBM Cloud Object # Storage (COS) backend. [cos: <cos_storage_config>] # Cache validity for active index entries. Should be no higher than # -ingester.max-chunk-idle. # CLI flag: -store.index-cache-validity [index_cache_validity: <duration> | default = 5m] # The cache block configures the cache backend. # The CLI flags prefix for this block configuration is: store.index-cache-read [index_queries_cache_config: <cache_config>] # Disable broad index queries which results in reduced cache usage and faster # query performance at the expense of somewhat higher QPS on the index store. # CLI flag: -store.disable-broad-index-queries [disable_broad_index_queries: <boolean> | default = false] # Maximum number of parallel chunk reads. # CLI flag: -store.max-parallel-get-chunk [max_parallel_get_chunk: <int> | default = 150] # The maximum number of chunks to fetch per batch. # CLI flag: -store.max-chunk-batch-size [max_chunk_batch_size: <int> | default = 50] # Configures storing index in an Object Store # (GCS/S3/Azure/Swift/COS/Filesystem) in the form of boltdb files. Required # fields only required when boltdb-shipper is defined in config. boltdb_shipper: # Directory where ingesters would write index files which would then be # uploaded by shipper to configured storage # CLI flag: -boltdb.shipper.active-index-directory [active_index_directory: <string> | default = ""] # Shared store for keeping index files. Supported types: gcs, s3, azure, cos, # filesystem # CLI flag: -boltdb.shipper.shared-store [shared_store: <string> | default = ""] # Prefix to add to Object Keys in Shared store. Path separator(if any) should # always be a '/'. Prefix should never start with a separator but should # always end with it # CLI flag: -boltdb.shipper.shared-store.key-prefix [shared_store_key_prefix: <string> | default = "index/"] # Cache location for restoring index files from storage for queries # CLI flag: -boltdb.shipper.cache-location [cache_location: <string> | default = ""] # TTL for index files restored in cache for queries # CLI flag: -boltdb.shipper.cache-ttl [cache_ttl: <duration> | default = 24h] # Resync downloaded files with the storage # CLI flag: -boltdb.shipper.resync-interval [resync_interval: <duration> | default = 5m] # Number of days of common index to be kept downloaded for queries. For per # tenant index query readiness, use limits overrides config. # CLI flag: -boltdb.shipper.query-ready-num-days [query_ready_num_days: <int> | default = 0] index_gateway_client: # The grpc_client block configures the gRPC client used to communicate # between two Loki components. # The CLI flags prefix for this block configuration is: # boltdb.shipper.index-gateway-client.grpc [grpc_client_config: <grpc_client>] # Hostname or IP of the Index Gateway gRPC server running in simple mode. # CLI flag: -boltdb.shipper.index-gateway-client.server-address [server_address: <string> | default = ""] # Whether requests sent to the gateway should be logged or not. # CLI flag: -boltdb.shipper.index-gateway-client.log-gateway-requests [log_gateway_requests: <boolean> | default = false] # Use boltdb-shipper index store as backup for indexing chunks. When enabled, # boltdb-shipper needs to be configured under storage_config # CLI flag: -boltdb.shipper.use-boltdb-shipper-as-backup [use_boltdb_shipper_as_backup: <boolean> | default = false] [ingestername: <string> | default = ""] [mode: <string> | default = ""] [ingesterdbretainperiod: <duration>] # Build per tenant index files # CLI flag: -boltdb.shipper.build-per-tenant-index [build_per_tenant_index: <boolean> | default = false] # Configures storing index in an Object Store # (GCS/S3/Azure/Swift/COS/Filesystem) in a prometheus TSDB-like format. Required # fields only required when TSDB is defined in config. tsdb_shipper: # Directory where ingesters would write index files which would then be # uploaded by shipper to configured storage # CLI flag: -tsdb.shipper.active-index-directory [active_index_directory: <string> | default = ""] # Shared store for keeping index files. Supported types: gcs, s3, azure, cos, # filesystem # CLI flag: -tsdb.shipper.shared-store [shared_store: <string> | default = ""] # Prefix to add to Object Keys in Shared store. Path separator(if any) should # always be a '/'. Prefix should never start with a separator but should # always end with it # CLI flag: -tsdb.shipper.shared-store.key-prefix [shared_store_key_prefix: <string> | default = "index/"] # Cache location for restoring index files from storage for queries # CLI flag: -tsdb.shipper.cache-location [cache_location: <string> | default = ""] # TTL for index files restored in cache for queries # CLI flag: -tsdb.shipper.cache-ttl [cache_ttl: <duration> | default = 24h] # Resync downloaded files with the storage # CLI flag: -tsdb.shipper.resync-interval [resync_interval: <duration> | default = 5m] # Number of days of common index to be kept downloaded for queries. For per # tenant index query readiness, use limits overrides config. # CLI flag: -tsdb.shipper.query-ready-num-days [query_ready_num_days: <int> | default = 0] index_gateway_client: # The grpc_client block configures the gRPC client used to communicate # between two Loki components. # The CLI flags prefix for this block configuration is: # tsdb.shipper.index-gateway-client.grpc [grpc_client_config: <grpc_client>] # Hostname or IP of the Index Gateway gRPC server running in simple mode. # CLI flag: -tsdb.shipper.index-gateway-client.server-address [server_address: <string> | default = ""] # Whether requests sent to the gateway should be logged or not. # CLI flag: -tsdb.shipper.index-gateway-client.log-gateway-requests [log_gateway_requests: <boolean> | default = false] # Use boltdb-shipper index store as backup for indexing chunks. When enabled, # boltdb-shipper needs to be configured under storage_config # CLI flag: -tsdb.shipper.use-boltdb-shipper-as-backup [use_boltdb_shipper_as_backup: <boolean> | default = false] [ingestername: <string> | default = ""] [mode: <string> | default = ""] [ingesterdbretainperiod: <duration>] # Experimental. Whether TSDB should cache postings or not. The # index-read-cache will be used as the backend. # CLI flag: -tsdb.enable-postings-cache [enable_postings_cache: <boolean> | default = false]chunk_store_config# The cache block configures the cache backend. # The CLI flags prefix for this block configuration is: store.chunks-cache [chunk_cache_config: <cache_config>] # The cache block configures the cache backend. # The CLI flags prefix for this block configuration is: store.index-cache-write [write_dedupe_cache_config: <cache_config>] # Cache index entries older than this period. 0 to disable. # CLI flag: -store.cache-lookups-older-than [cache_lookups_older_than: <duration> | default = 0s] # This flag is deprecated. Use -querier.max-query-lookback instead. # CLI flag: -store.max-look-back-period [max_look_back_period: <duration> | default = 0s]schema_config[configs: <list of period_configs>]compactor# Directory where files can be downloaded for compaction. # CLI flag: -compactor.working-directory [working_directory: <string> | default = ""] # The shared store used for storing boltdb files. Supported types: gcs, s3, # azure, swift, filesystem, bos, cos. If not set, compactor will be initialized # to operate on all the object stores that contain either boltdb-shipper or tsdb # index. # CLI flag: -compactor.shared-store [shared_store: <string> | default = ""] # Prefix to add to object keys in shared store. Path separator(if any) should # always be a '/'. Prefix should never start with a separator but should always # end with it. # CLI flag: -compactor.shared-store.key-prefix [shared_store_key_prefix: <string> | default = "index/"] # Interval at which to re-run the compaction operation. # CLI flag: -compactor.compaction-interval [compaction_interval: <duration> | default = 10m] # Interval at which to apply/enforce retention. 0 means run at same interval as # compaction. If non-zero, it should always be a multiple of compaction # interval. # CLI flag: -compactor.apply-retention-interval [apply_retention_interval: <duration> | default = 0s] # (Experimental) Activate custom (per-stream,per-tenant) retention. # CLI flag: -compactor.retention-enabled [retention_enabled: <boolean> | default = false] # Delay after which chunks will be fully deleted during retention. # CLI flag: -compactor.retention-delete-delay [retention_delete_delay: <duration> | default = 2h] # The total amount of worker to use to delete chunks. # CLI flag: -compactor.retention-delete-worker-count [retention_delete_worker_count: <int> | default = 150] # The maximum amount of time to spend running retention and deletion on any # given table in the index. # CLI flag: -compactor.retention-table-timeout [retention_table_timeout: <duration> | default = 0s] # Store used for managing delete requests. Defaults to -compactor.shared-store. # CLI flag: -compactor.delete-request-store [delete_request_store: <string> | default = ""] # The max number of delete requests to run per compaction cycle. # CLI flag: -compactor.delete-batch-size [delete_batch_size: <int> | default = 70] # Allow cancellation of delete request until duration after they are created. # Data would be deleted only after delete requests have been older than this # duration. Ideally this should be set to at least 24h. # CLI flag: -compactor.delete-request-cancel-period [delete_request_cancel_period: <duration> | default = 24h] # Constrain the size of any single delete request. When a delete request > # delete_max_interval is input, the request is sharded into smaller requests of # no more than delete_max_interval # CLI flag: -compactor.delete-max-interval [delete_max_interval: <duration> | default = 0s] # Maximum number of tables to compact in parallel. While increasing this value, # please make sure compactor has enough disk space allocated to be able to store # and compact as many tables. # CLI flag: -compactor.max-compaction-parallelism [max_compaction_parallelism: <int> | default = 1] # Number of upload/remove operations to execute in parallel when finalizing a # compaction. NOTE: This setting is per compaction operation, which can be # executed in parallel. The upper bound on the number of concurrent uploads is # upload_parallelism * max_compaction_parallelism. # CLI flag: -compactor.upload-parallelism [upload_parallelism: <int> | default = 10] # The hash ring configuration used by compactors to elect a single instance for # running compactions. The CLI flags prefix for this block config is: # compactor.ring compactor_ring: kvstore: # Backend storage to use for the ring. Supported values are: consul, etcd, # inmemory, memberlist, multi. # CLI flag: -compactor.ring.store [store: <string> | default = "consul"] # The prefix for the keys in the store. Should end with a /. # CLI flag: -compactor.ring.prefix [prefix: <string> | default = "collectors/"] # Configuration for a Consul client. Only applies if the selected kvstore is # consul. # The CLI flags prefix for this block configuration is: compactor.ring [consul: <consul>] # Configuration for an ETCD v3 client. Only applies if the selected kvstore # is etcd. # The CLI flags prefix for this block configuration is: compactor.ring [etcd: <etcd>] multi: # Primary backend storage used by multi-client. # CLI flag: -compactor.ring.multi.primary [primary: <string> | default = ""] # Secondary backend storage used by multi-client. # CLI flag: -compactor.ring.multi.secondary [secondary: <string> | default = ""] # Mirror writes to secondary store. # CLI flag: -compactor.ring.multi.mirror-enabled [mirror_enabled: <boolean> | default = false] # Timeout for storing value to secondary store. # CLI flag: -compactor.ring.multi.mirror-timeout [mirror_timeout: <duration> | default = 2s] # Period at which to heartbeat to the ring. 0 = disabled. # CLI flag: -compactor.ring.heartbeat-period [heartbeat_period: <duration> | default = 15s] # The heartbeat timeout after which compactors are considered unhealthy within # the ring. 0 = never (timeout disabled). # CLI flag: -compactor.ring.heartbeat-timeout [heartbeat_timeout: <duration> | default = 1m] # File path where tokens are stored. If empty, tokens are not stored at # shutdown and restored at startup. # CLI flag: -compactor.ring.tokens-file-path [tokens_file_path: <string> | default = ""] # True to enable zone-awareness and replicate blocks across different # availability zones. # CLI flag: -compactor.ring.zone-awareness-enabled [zone_awareness_enabled: <boolean> | default = false] # Instance ID to register in the ring. # CLI flag: -compactor.ring.instance-id [instance_id: <string> | default = "<hostname>"] # Name of network interface to read address from. # CLI flag: -compactor.ring.instance-interface-names [instance_interface_names: <list of strings> | default = [<private network interfaces>]] # Port to advertise in the ring (defaults to server.grpc-listen-port). # CLI flag: -compactor.ring.instance-port [instance_port: <int> | default = 0] # IP address to advertise in the ring. # CLI flag: -compactor.ring.instance-addr [instance_addr: <string> | default = ""] # The availability zone where this instance is running. Required if # zone-awareness is enabled. # CLI flag: -compactor.ring.instance-availability-zone [instance_availability_zone: <string> | default = ""] # Enable using a IPv6 instance address. # CLI flag: -compactor.ring.instance-enable-ipv6 [instance_enable_ipv6: <boolean> | default = false] # Number of tables that compactor will try to compact. Newer tables are chosen # when this is less than the number of tables available. # CLI flag: -compactor.tables-to-compact [tables_to_compact: <int> | default = 0] # Do not compact N latest tables. Together with -compactor.run-once and # -compactor.tables-to-compact, this is useful when clearing compactor backlogs. # CLI flag: -compactor.skip-latest-n-tables [skip_latest_n_tables: <int> | default = 0] # Deprecated: Use deletion_mode per tenant configuration instead. [deletion_mode: <string> | default = ""]limits_config# Whether the ingestion rate limit should be applied individually to each # distributor instance (local), or evenly shared across the cluster (global). # The ingestion rate strategy cannot be overridden on a per-tenant basis. # - local: enforces the limit on a per distributor basis. The actual effective # rate limit will be N times higher, where N is the number of distributor # replicas. # - global: enforces the limit globally, configuring a per-distributor local # rate limiter as 'ingestion_rate / N', where N is the number of distributor # replicas (it's automatically adjusted if the number of replicas change). The # global strategy requires the distributors to form their own ring, which is # used to keep track of the current number of healthy distributor replicas. # CLI flag: -distributor.ingestion-rate-limit-strategy [ingestion_rate_strategy: <string> | default = "global"] # Per-user ingestion rate limit in sample size per second. Units in MB. # CLI flag: -distributor.ingestion-rate-limit-mb [ingestion_rate_mb: <float> | default = 4] # Per-user allowed ingestion burst size (in sample size). Units in MB. The burst # size refers to the per-distributor local rate limiter even in the case of the # 'global' strategy, and should be set at least to the maximum logs size # expected in a single push request. # CLI flag: -distributor.ingestion-burst-size-mb [ingestion_burst_size_mb: <float> | default = 6] # Maximum length accepted for label names. # CLI flag: -validation.max-length-label-name [max_label_name_length: <int> | default = 1024] # Maximum length accepted for label value. This setting also applies to the # metric name. # CLI flag: -validation.max-length-label-value [max_label_value_length: <int> | default = 2048] # Maximum number of label names per series. # CLI flag: -validation.max-label-names-per-series [max_label_names_per_series: <int> | default = 30] # Whether or not old samples will be rejected. # CLI flag: -validation.reject-old-samples [reject_old_samples: <boolean> | default = true] # Maximum accepted sample age before rejecting. # CLI flag: -validation.reject-old-samples.max-age [reject_old_samples_max_age: <duration> | default = 1w] # Duration which table will be created/deleted before/after it's needed; we # won't accept sample from before this time. # CLI flag: -validation.create-grace-period [creation_grace_period: <duration> | default = 10m] # Enforce every sample has a metric name. # CLI flag: -validation.enforce-metric-name [enforce_metric_name: <boolean> | default = true] # Maximum line size on ingestion path. Example: 256kb. Any log line exceeding # this limit will be discarded unless `distributor.max-line-size-truncate` is # set which in case it is truncated instead of discarding it completely. There # is no limit when unset or set to 0. # CLI flag: -distributor.max-line-size [max_line_size: <int> | default = 0B] # Whether to truncate lines that exceed max_line_size. # CLI flag: -distributor.max-line-size-truncate [max_line_size_truncate: <boolean> | default = false] # Alter the log line timestamp during ingestion when the timestamp is the same # as the previous entry for the same stream. When enabled, if a log line in a # push request has the same timestamp as the previous line for the same stream, # one nanosecond is added to the log line. This will preserve the received order # of log lines with the exact same timestamp when they are queried, by slightly # altering their stored timestamp. NOTE: This is imperfect, because Loki accepts # out of order writes, and another push request for the same stream could # contain duplicate timestamps to existing entries and they will not be # incremented. # CLI flag: -validation.increment-duplicate-timestamps [increment_duplicate_timestamp: <boolean> | default = false] # Maximum number of active streams per user, per ingester. 0 to disable. # CLI flag: -ingester.max-streams-per-user [max_streams_per_user: <int> | default = 0] # Maximum number of active streams per user, across the cluster. 0 to disable. # When the global limit is enabled, each ingester is configured with a dynamic # local limit based on the replication factor and the current number of healthy # ingesters, and is kept updated whenever the number of ingesters change. # CLI flag: -ingester.max-global-streams-per-user [max_global_streams_per_user: <int> | default = 5000] # Deprecated. When true, out-of-order writes are accepted. # CLI flag: -ingester.unordered-writes [unordered_writes: <boolean> | default = true] # Maximum byte rate per second per stream, also expressible in human readable # forms (1MB, 256KB, etc). # CLI flag: -ingester.per-stream-rate-limit [per_stream_rate_limit: <int> | default = 3MB] # Maximum burst bytes per stream, also expressible in human readable forms (1MB, # 256KB, etc). This is how far above the rate limit a stream can 'burst' before # the stream is limited. # CLI flag: -ingester.per-stream-rate-limit-burst [per_stream_rate_limit_burst: <int> | default = 15MB] # Maximum number of chunks that can be fetched in a single query. # CLI flag: -store.query-chunk-limit [max_chunks_per_query: <int> | default = 2000000] # Limit the maximum of unique series that is returned by a metric query. When # the limit is reached an error is returned. # CLI flag: -querier.max-query-series [max_query_series: <int> | default = 500] # Limit how far back in time series data and metadata can be queried, up until # lookback duration ago. This limit is enforced in the query frontend, the # querier and the ruler. If the requested time range is outside the allowed # range, the request will not fail, but will be modified to only query data # within the allowed time range. The default value of 0 does not set a limit. # CLI flag: -querier.max-query-lookback [max_query_lookback: <duration> | default = 0s] # The limit to length of chunk store queries. 0 to disable. # CLI flag: -store.max-query-length [max_query_length: <duration> | default = 30d1h] # Limit the length of the [range] inside a range query. Default is 0 or # unlimited # CLI flag: -querier.max-query-range [max_query_range: <duration> | default = 0s] # Maximum number of queries that will be scheduled in parallel by the frontend. # CLI flag: -querier.max-query-parallelism [max_query_parallelism: <int> | default = 32] # Maximum number of queries will be scheduled in parallel by the frontend for # TSDB schemas. # CLI flag: -querier.tsdb-max-query-parallelism [tsdb_max_query_parallelism: <int> | default = 512] # Maximum number of bytes assigned to a single sharded query. Also expressible # in human readable forms (1GB, etc). # CLI flag: -querier.tsdb-max-bytes-per-shard [tsdb_max_bytes_per_shard: <int> | default = 600MB] # Cardinality limit for index queries. # CLI flag: -store.cardinality-limit [cardinality_limit: <int> | default = 100000] # Maximum number of stream matchers per query. # CLI flag: -querier.max-streams-matcher-per-query [max_streams_matchers_per_query: <int> | default = 1000] # Maximum number of concurrent tail requests. # CLI flag: -querier.max-concurrent-tail-requests [max_concurrent_tail_requests: <int> | default = 10] # Maximum number of log entries that will be returned for a query. # CLI flag: -validation.max-entries-limit [max_entries_limit_per_query: <int> | default = 5000] # Most recent allowed cacheable result per-tenant, to prevent caching very # recent results that might still be in flux. # CLI flag: -frontend.max-cache-freshness [max_cache_freshness_per_query: <duration> | default = 1m] # Do not cache requests with an end time that falls within Now minus this # duration. 0 disables this feature (default). # CLI flag: -frontend.max-stats-cache-freshness [max_stats_cache_freshness: <duration> | default = 0s] # Maximum number of queriers that can handle requests for a single tenant. If # set to 0 or value higher than number of available queriers, *all* queriers # will handle requests for the tenant. Each frontend (or query-scheduler, if # used) will select the same set of queriers for the same tenant (given that all # queriers are connected to all frontends / query-schedulers). This option only # works with queriers connecting to the query-frontend / query-scheduler, not # when using downstream URL. # CLI flag: -frontend.max-queriers-per-tenant [max_queriers_per_tenant: <int> | default = 0] # Number of days of index to be kept always downloaded for queries. Applies only # to per user index in boltdb-shipper index store. 0 to disable. # CLI flag: -store.query-ready-index-num-days [query_ready_index_num_days: <int> | default = 0] # Timeout when querying backends (ingesters or storage) during the execution of # a query request. When a specific per-tenant timeout is used, the global # timeout is ignored. # CLI flag: -querier.query-timeout [query_timeout: <duration> | default = 1m] # Split queries by a time interval and execute in parallel. The value 0 disables # splitting by time. This also determines how cache keys are chosen when result # caching is enabled. # CLI flag: -querier.split-queries-by-interval [split_queries_by_interval: <duration> | default = 30m] # Limit queries that can be sharded. Queries within the time range of now and # now minus this sharding lookback are not sharded. The default value of 0s # disables the lookback, causing sharding of all queries at all times. # CLI flag: -frontend.min-sharding-lookback [min_sharding_lookback: <duration> | default = 0s] # Max number of bytes a query can fetch. Enforced in log and metric queries only # when TSDB is used. The default value of 0 disables this limit. # CLI flag: -frontend.max-query-bytes-read [max_query_bytes_read: <int> | default = 0B] # Max number of bytes a query can fetch after splitting and sharding. Enforced # in log and metric queries only when TSDB is used. The default value of 0 # disables this limit. # CLI flag: -frontend.max-querier-bytes-read [max_querier_bytes_read: <int> | default = 0B] # Enable log-volume endpoints. [volume_enabled: <boolean>] # The maximum number of aggregated series in a log-volume response # CLI flag: -limits.volume-max-series [volume_max_series: <int> | default = 1000] # Deprecated. Duration to delay the evaluation of rules to ensure the underlying # metrics have been pushed to Cortex. # CLI flag: -ruler.evaluation-delay-duration [ruler_evaluation_delay_duration: <duration> | default = 0s] # Maximum number of rules per rule group per-tenant. 0 to disable. # CLI flag: -ruler.max-rules-per-rule-group [ruler_max_rules_per_rule_group: <int> | default = 0] # Maximum number of rule groups per-tenant. 0 to disable. # CLI flag: -ruler.max-rule-groups-per-tenant [ruler_max_rule_groups_per_tenant: <int> | default = 0] # The default tenant's shard size when shuffle-sharding is enabled in the ruler. # When this setting is specified in the per-tenant overrides, a value of 0 # disables shuffle sharding for the tenant. # CLI flag: -ruler.tenant-shard-size [ruler_tenant_shard_size: <int> | default = 0] # Disable recording rules remote-write. [ruler_remote_write_disabled: <boolean>] # Deprecated: Use 'ruler_remote_write_config' instead. The URL of the endpoint # to send samples to. [ruler_remote_write_url: <string> | default = ""] # Deprecated: Use 'ruler_remote_write_config' instead. Timeout for requests to # the remote write endpoint. [ruler_remote_write_timeout: <duration>] # Deprecated: Use 'ruler_remote_write_config' instead. Custom HTTP headers to be # sent along with each remote write request. Be aware that headers that are set # by Loki itself can't be overwritten. [ruler_remote_write_headers: <headers>] # Deprecated: Use 'ruler_remote_write_config' instead. List of remote write # relabel configurations. [ruler_remote_write_relabel_configs: <relabel_config...>] # Deprecated: Use 'ruler_remote_write_config' instead. Number of samples to # buffer per shard before we block reading of more samples from the WAL. It is # recommended to have enough capacity in each shard to buffer several requests # to keep throughput up while processing occasional slow remote requests. [ruler_remote_write_queue_capacity: <int>] # Deprecated: Use 'ruler_remote_write_config' instead. Minimum number of shards, # i.e. amount of concurrency. [ruler_remote_write_queue_min_shards: <int>] # Deprecated: Use 'ruler_remote_write_config' instead. Maximum number of shards, # i.e. amount of concurrency. [ruler_remote_write_queue_max_shards: <int>] # Deprecated: Use 'ruler_remote_write_config' instead. Maximum number of samples # per send. [ruler_remote_write_queue_max_samples_per_send: <int>] # Deprecated: Use 'ruler_remote_write_config' instead. Maximum time a sample # will wait in buffer. [ruler_remote_write_queue_batch_send_deadline: <duration>] # Deprecated: Use 'ruler_remote_write_config' instead. Initial retry delay. Gets # doubled for every retry. [ruler_remote_write_queue_min_backoff: <duration>] # Deprecated: Use 'ruler_remote_write_config' instead. Maximum retry delay. [ruler_remote_write_queue_max_backoff: <duration>] # Deprecated: Use 'ruler_remote_write_config' instead. Retry upon receiving a # 429 status code from the remote-write storage. This is experimental and might # change in the future. [ruler_remote_write_queue_retry_on_ratelimit: <boolean>] # Deprecated: Use 'ruler_remote_write_config' instead. Configures AWS's # Signature Verification 4 signing process to sign every remote write request. ruler_remote_write_sigv4_config: [region: <string> | default = ""] [access_key: <string> | default = ""] [secret_key: <string> | default = ""] [profile: <string> | default = ""] [role_arn: <string> | default = ""] # Configures global and per-tenant limits for remote write clients. A map with # remote client id as key. [ruler_remote_write_config: <map of string to RemoteWriteConfig>] # Timeout for a remote rule evaluation. Defaults to the value of # 'querier.query-timeout'. [ruler_remote_evaluation_timeout: <duration>] # Maximum size (in bytes) of the allowable response size from a remote rule # evaluation. Set to 0 to allow any response size (default). [ruler_remote_evaluation_max_response_size: <int>] # Deletion mode. Can be one of 'disabled', 'filter-only', or # 'filter-and-delete'. When set to 'filter-only' or 'filter-and-delete', and if # retention_enabled is true, then the log entry deletion API endpoints are # available. # CLI flag: -compactor.deletion-mode [deletion_mode: <string> | default = "filter-and-delete"] # Retention period to apply to stored data, only applies if retention_enabled is # true in the compactor config. As of version 2.8.0, a zero value of 0 or 0s # disables retention. In previous releases, Loki did not properly honor a zero # value to disable retention and a really large value should be used instead. # CLI flag: -store.retention [retention_period: <duration> | default = 0s] # Per-stream retention to apply, if the retention is enable on the compactor # side. # Example: # retention_stream: # - selector: '{namespace="dev"}' # priority: 1 # period: 24h # - selector: '{container="nginx"}' # priority: 1 # period: 744h # Selector is a Prometheus labels matchers that will apply the 'period' # retention only if the stream is matching. In case multiple stream are # matching, the highest priority will be picked. If no rule is matched the # 'retention_period' is used. [retention_stream: <list of StreamRetentions>] # Feature renamed to 'runtime configuration', flag deprecated in favor of # -runtime-config.file (runtime_config.file in YAML). # CLI flag: -limits.per-user-override-config [per_tenant_override_config: <string> | default = ""] # Feature renamed to 'runtime configuration'; flag deprecated in favor of # -runtime-config.reload-period (runtime_config.period in YAML). # CLI flag: -limits.per-user-override-period [per_tenant_override_period: <duration> | default = 10s] # Deprecated: Use deletion_mode per tenant configuration instead. [allow_deletes: <boolean>] shard_streams: [enabled: <boolean>] [logging_enabled: <boolean>] [desired_rate: <int>] [blocked_queries: <blocked_query...>] # Define a list of required selector labels. [required_labels: <list of strings>] # Minimum number of label matchers a query should contain. [minimum_labels_number: <int>] # The shard size defines how many index gateways should be used by a tenant for # querying. If the global shard factor is 0, the global shard factor is set to # the deprecated -replication-factor for backwards compatibility reasons. # CLI flag: -index-gateway.shard-size [index_gateway_shard_size: <int> | default = 0] # Allow user to send structured metadata (non-indexed labels) in push payload. # CLI flag: -validation.allow-structured-metadata [allow_structured_metadata: <boolean> | default = false]frontend_worker# Address of query frontend service, in host:port format. If # -querier.scheduler-address is set as well, querier will use scheduler instead. # Only one of -querier.frontend-address or -querier.scheduler-address can be # set. If neither is set, queries are only received via HTTP endpoint. # CLI flag: -querier.frontend-address [frontend_address: <string> | default = ""] # Hostname (and port) of scheduler that querier will periodically resolve, # connect to and receive queries from. Only one of -querier.frontend-address or # -querier.scheduler-address can be set. If neither is set, queries are only # received via HTTP endpoint. # CLI flag: -querier.scheduler-address [scheduler_address: <string> | default = ""] # How often to query DNS for query-frontend or query-scheduler address. Also # used to determine how often to poll the scheduler-ring for addresses if the # scheduler-ring is configured. # CLI flag: -querier.dns-lookup-period [dns_lookup_duration: <duration> | default = 3s] # Number of simultaneous queries to process per query-frontend or # query-scheduler. # CLI flag: -querier.worker-parallelism [parallelism: <int> | default = 10] # Force worker concurrency to match the -querier.max-concurrent option. # Overrides querier.worker-parallelism. # CLI flag: -querier.worker-match-max-concurrent [match_max_concurrent: <boolean> | default = true] # Querier ID, sent to frontend service to identify requests from the same # querier. Defaults to hostname. # CLI flag: -querier.id [id: <string> | default = ""] # The grpc_client block configures the gRPC client used to communicate between # two Loki components. # The CLI flags prefix for this block configuration is: querier.frontend-client [grpc_client_config: <grpc_client>]table_manager# If true, disable all changes to DB capacity # CLI flag: -table-manager.throughput-updates-disabled [throughput_updates_disabled: <boolean> | default = false] # If true, enables retention deletes of DB tables # CLI flag: -table-manager.retention-deletes-enabled [retention_deletes_enabled: <boolean> | default = false] # Tables older than this retention period are deleted. Must be either 0 # (disabled) or a multiple of 24h. When enabled, be aware this setting is # destructive to data! # CLI flag: -table-manager.retention-period [retention_period: <duration> | default = 0s] # How frequently to poll backend to learn our capacity. # CLI flag: -table-manager.poll-interval [poll_interval: <duration> | default = 2m] # Periodic tables grace period (duration which table will be created/deleted # before/after it's needed). # CLI flag: -table-manager.periodic-table.grace-period [creation_grace_period: <duration> | default = 10m] index_tables_provisioning: # Enables on demand throughput provisioning for the storage provider (if # supported). Applies only to tables which are not autoscaled. Supported by # DynamoDB # CLI flag: -table-manager.index-table.enable-ondemand-throughput-mode [enable_ondemand_throughput_mode: <boolean> | default = false] # Table default write throughput. Supported by DynamoDB # CLI flag: -table-manager.index-table.write-throughput [provisioned_write_throughput: <int> | default = 1000] # Table default read throughput. Supported by DynamoDB # CLI flag: -table-manager.index-table.read-throughput [provisioned_read_throughput: <int> | default = 300] write_scale: # Should we enable autoscale for the table. # CLI flag: -table-manager.index-table.write-throughput.scale.enabled [enabled: <boolean> | default = false] # AWS AutoScaling role ARN # CLI flag: -table-manager.index-table.write-throughput.scale.role-arn [role_arn: <string> | default = ""] # DynamoDB minimum provision capacity. # CLI flag: -table-manager.index-table.write-throughput.scale.min-capacity [min_capacity: <int> | default = 3000] # DynamoDB maximum provision capacity. # CLI flag: -table-manager.index-table.write-throughput.scale.max-capacity [max_capacity: <int> | default = 6000] # DynamoDB minimum seconds between each autoscale up. # CLI flag: -table-manager.index-table.write-throughput.scale.out-cooldown [out_cooldown: <int> | default = 1800] # DynamoDB minimum seconds between each autoscale down. # CLI flag: -table-manager.index-table.write-throughput.scale.in-cooldown [in_cooldown: <int> | default = 1800] # DynamoDB target ratio of consumed capacity to provisioned capacity. # CLI flag: -table-manager.index-table.write-throughput.scale.target-value [target: <float> | default = 80] read_scale: # Should we enable autoscale for the table. # CLI flag: -table-manager.index-table.read-throughput.scale.enabled [enabled: <boolean> | default = false] # AWS AutoScaling role ARN # CLI flag: -table-manager.index-table.read-throughput.scale.role-arn [role_arn: <string> | default = ""] # DynamoDB minimum provision capacity. # CLI flag: -table-manager.index-table.read-throughput.scale.min-capacity [min_capacity: <int> | default = 3000] # DynamoDB maximum provision capacity. # CLI flag: -table-manager.index-table.read-throughput.scale.max-capacity [max_capacity: <int> | default = 6000] # DynamoDB minimum seconds between each autoscale up. # CLI flag: -table-manager.index-table.read-throughput.scale.out-cooldown [out_cooldown: <int> | default = 1800] # DynamoDB minimum seconds between each autoscale down. # CLI flag: -table-manager.index-table.read-throughput.scale.in-cooldown [in_cooldown: <int> | default = 1800] # DynamoDB target ratio of consumed capacity to provisioned capacity. # CLI flag: -table-manager.index-table.read-throughput.scale.target-value [target: <float> | default = 80] # Enables on demand throughput provisioning for the storage provider (if # supported). Applies only to tables which are not autoscaled. Supported by # DynamoDB # CLI flag: -table-manager.index-table.inactive-enable-ondemand-throughput-mode [enable_inactive_throughput_on_demand_mode: <boolean> | default = false] # Table write throughput for inactive tables. Supported by DynamoDB # CLI flag: -table-manager.index-table.inactive-write-throughput [inactive_write_throughput: <int> | default = 1] # Table read throughput for inactive tables. Supported by DynamoDB # CLI flag: -table-manager.index-table.inactive-read-throughput [inactive_read_throughput: <int> | default = 300] inactive_write_scale: # Should we enable autoscale for the table. # CLI flag: -table-manager.index-table.inactive-write-throughput.scale.enabled [enabled: <boolean> | default = false] # AWS AutoScaling role ARN # CLI flag: -table-manager.index-table.inactive-write-throughput.scale.role-arn [role_arn: <string> | default = ""] # DynamoDB minimum provision capacity. # CLI flag: -table-manager.index-table.inactive-write-throughput.scale.min-capacity [min_capacity: <int> | default = 3000] # DynamoDB maximum provision capacity. # CLI flag: -table-manager.index-table.inactive-write-throughput.scale.max-capacity [max_capacity: <int> | default = 6000] # DynamoDB minimum seconds between each autoscale up. # CLI flag: -table-manager.index-table.inactive-write-throughput.scale.out-cooldown [out_cooldown: <int> | default = 1800] # DynamoDB minimum seconds between each autoscale down. # CLI flag: -table-manager.index-table.inactive-write-throughput.scale.in-cooldown [in_cooldown: <int> | default = 1800] # DynamoDB target ratio of consumed capacity to provisioned capacity. # CLI flag: -table-manager.index-table.inactive-write-throughput.scale.target-value [target: <float> | default = 80] inactive_read_scale: # Should we enable autoscale for the table. # CLI flag: -table-manager.index-table.inactive-read-throughput.scale.enabled [enabled: <boolean> | default = false] # AWS AutoScaling role ARN # CLI flag: -table-manager.index-table.inactive-read-throughput.scale.role-arn [role_arn: <string> | default = ""] # DynamoDB minimum provision capacity. # CLI flag: -table-manager.index-table.inactive-read-throughput.scale.min-capacity [min_capacity: <int> | default = 3000] # DynamoDB maximum provision capacity. # CLI flag: -table-manager.index-table.inactive-read-throughput.scale.max-capacity [max_capacity: <int> | default = 6000] # DynamoDB minimum seconds between each autoscale up. # CLI flag: -table-manager.index-table.inactive-read-throughput.scale.out-cooldown [out_cooldown: <int> | default = 1800] # DynamoDB minimum seconds between each autoscale down. # CLI flag: -table-manager.index-table.inactive-read-throughput.scale.in-cooldown [in_cooldown: <int> | default = 1800] # DynamoDB target ratio of consumed capacity to provisioned capacity. # CLI flag: -table-manager.index-table.inactive-read-throughput.scale.target-value [target: <float> | default = 80] # Number of last inactive tables to enable write autoscale. # CLI flag: -table-manager.index-table.inactive-write-throughput.scale-last-n [inactive_write_scale_lastn: <int> | default = 4] # Number of last inactive tables to enable read autoscale. # CLI flag: -table-manager.index-table.inactive-read-throughput.scale-last-n [inactive_read_scale_lastn: <int> | default = 4] chunk_tables_provisioning: # Enables on demand throughput provisioning for the storage provider (if # supported). Applies only to tables which are not autoscaled. Supported by # DynamoDB # CLI flag: -table-manager.chunk-table.enable-ondemand-throughput-mode [enable_ondemand_throughput_mode: <boolean> | default = false] # Table default write throughput. Supported by DynamoDB # CLI flag: -table-manager.chunk-table.write-throughput [provisioned_write_throughput: <int> | default = 1000] # Table default read throughput. Supported by DynamoDB # CLI flag: -table-manager.chunk-table.read-throughput [provisioned_read_throughput: <int> | default = 300] write_scale: # Should we enable autoscale for the table. # CLI flag: -table-manager.chunk-table.write-throughput.scale.enabled [enabled: <boolean> | default = false] # AWS AutoScaling role ARN # CLI flag: -table-manager.chunk-table.write-throughput.scale.role-arn [role_arn: <string> | default = ""] # DynamoDB minimum provision capacity. # CLI flag: -table-manager.chunk-table.write-throughput.scale.min-capacity [min_capacity: <int> | default = 3000] # DynamoDB maximum provision capacity. # CLI flag: -table-manager.chunk-table.write-throughput.scale.max-capacity [max_capacity: <int> | default = 6000] # DynamoDB minimum seconds between each autoscale up. # CLI flag: -table-manager.chunk-table.write-throughput.scale.out-cooldown [out_cooldown: <int> | default = 1800] # DynamoDB minimum seconds between each autoscale down. # CLI flag: -table-manager.chunk-table.write-throughput.scale.in-cooldown [in_cooldown: <int> | default = 1800] # DynamoDB target ratio of consumed capacity to provisioned capacity. # CLI flag: -table-manager.chunk-table.write-throughput.scale.target-value [target: <float> | default = 80] read_scale: # Should we enable autoscale for the table. # CLI flag: -table-manager.chunk-table.read-throughput.scale.enabled [enabled: <boolean> | default = false] # AWS AutoScaling role ARN # CLI flag: -table-manager.chunk-table.read-throughput.scale.role-arn [role_arn: <string> | default = ""] # DynamoDB minimum provision capacity. # CLI flag: -table-manager.chunk-table.read-throughput.scale.min-capacity [min_capacity: <int> | default = 3000] # DynamoDB maximum provision capacity. # CLI flag: -table-manager.chunk-table.read-throughput.scale.max-capacity [max_capacity: <int> | default = 6000] # DynamoDB minimum seconds between each autoscale up. # CLI flag: -table-manager.chunk-table.read-throughput.scale.out-cooldown [out_cooldown: <int> | default = 1800] # DynamoDB minimum seconds between each autoscale down. # CLI flag: -table-manager.chunk-table.read-throughput.scale.in-cooldown [in_cooldown: <int> | default = 1800] # DynamoDB target ratio of consumed capacity to provisioned capacity. # CLI flag: -table-manager.chunk-table.read-throughput.scale.target-value [target: <float> | default = 80] # Enables on demand throughput provisioning for the storage provider (if # supported). Applies only to tables which are not autoscaled. Supported by # DynamoDB # CLI flag: -table-manager.chunk-table.inactive-enable-ondemand-throughput-mode [enable_inactive_throughput_on_demand_mode: <boolean> | default = false] # Table write throughput for inactive tables. Supported by DynamoDB # CLI flag: -table-manager.chunk-table.inactive-write-throughput [inactive_write_throughput: <int> | default = 1] # Table read throughput for inactive tables. Supported by DynamoDB # CLI flag: -table-manager.chunk-table.inactive-read-throughput [inactive_read_throughput: <int> | default = 300] inactive_write_scale: # Should we enable autoscale for the table. # CLI flag: -table-manager.chunk-table.inactive-write-throughput.scale.enabled [enabled: <boolean> | default = false] # AWS AutoScaling role ARN # CLI flag: -table-manager.chunk-table.inactive-write-throughput.scale.role-arn [role_arn: <string> | default = ""] # DynamoDB minimum provision capacity. # CLI flag: -table-manager.chunk-table.inactive-write-throughput.scale.min-capacity [min_capacity: <int> | default = 3000] # DynamoDB maximum provision capacity. # CLI flag: -table-manager.chunk-table.inactive-write-throughput.scale.max-capacity [max_capacity: <int> | default = 6000] # DynamoDB minimum seconds between each autoscale up. # CLI flag: -table-manager.chunk-table.inactive-write-throughput.scale.out-cooldown [out_cooldown: <int> | default = 1800] # DynamoDB minimum seconds between each autoscale down. # CLI flag: -table-manager.chunk-table.inactive-write-throughput.scale.in-cooldown [in_cooldown: <int> | default = 1800] # DynamoDB target ratio of consumed capacity to provisioned capacity. # CLI flag: -table-manager.chunk-table.inactive-write-throughput.scale.target-value [target: <float> | default = 80] inactive_read_scale: # Should we enable autoscale for the table. # CLI flag: -table-manager.chunk-table.inactive-read-throughput.scale.enabled [enabled: <boolean> | default = false] # AWS AutoScaling role ARN # CLI flag: -table-manager.chunk-table.inactive-read-throughput.scale.role-arn [role_arn: <string> | default = ""] # DynamoDB minimum provision capacity. # CLI flag: -table-manager.chunk-table.inactive-read-throughput.scale.min-capacity [min_capacity: <int> | default = 3000] # DynamoDB maximum provision capacity. # CLI flag: -table-manager.chunk-table.inactive-read-throughput.scale.max-capacity [max_capacity: <int> | default = 6000] # DynamoDB minimum seconds between each autoscale up. # CLI flag: -table-manager.chunk-table.inactive-read-throughput.scale.out-cooldown [out_cooldown: <int> | default = 1800] # DynamoDB minimum seconds between each autoscale down. # CLI flag: -table-manager.chunk-table.inactive-read-throughput.scale.in-cooldown [in_cooldown: <int> | default = 1800] # DynamoDB target ratio of consumed capacity to provisioned capacity. # CLI flag: -table-manager.chunk-table.inactive-read-throughput.scale.target-value [target: <float> | default = 80] # Number of last inactive tables to enable write autoscale. # CLI flag: -table-manager.chunk-table.inactive-write-throughput.scale-last-n [inactive_write_scale_lastn: <int> | default = 4] # Number of last inactive tables to enable read autoscale. # CLI flag: -table-manager.chunk-table.inactive-read-throughput.scale-last-n [inactive_read_scale_lastn: <int> | default = 4]runtime_config# How often to check runtime config files. # CLI flag: -runtime-config.reload-period [period: <duration> | default = 10s] # Comma separated list of yaml files with the configuration that can be updated # at runtime. Runtime config files will be merged from left to right. # CLI flag: -runtime-config.file [file: <string> | default = ""]tracing# Set to false to disable tracing. # CLI flag: -tracing.enabled [enabled: <boolean> | default = true]analytics# Enable anonymous usage reporting. # CLI flag: -reporting.enabled [reporting_enabled: <boolean> | default = true] # URL to which reports are sent # CLI flag: -reporting.usage-stats-url [usage_stats_url: <string> | default = "https://stats.grafana.org/loki-usage-report"]common[path_prefix: <string> | default = ""] storage: # The s3_storage_config block configures the connection to Amazon S3 object # storage backend. # The CLI flags prefix for this block configuration is: common [s3: <s3_storage_config>] # The gcs_storage_config block configures the connection to Google Cloud # Storage object storage backend. # The CLI flags prefix for this block configuration is: common.storage [gcs: <gcs_storage_config>] # The azure_storage_config block configures the connection to Azure object # storage backend. # The CLI flags prefix for this block configuration is: common.storage [azure: <azure_storage_config>] # The alibabacloud_storage_config block configures the connection to Alibaba # Cloud Storage object storage backend. [alibabacloud: <alibabacloud_storage_config>] # The bos_storage_config block configures the connection to Baidu Object # Storage (BOS) object storage backend. # The CLI flags prefix for this block configuration is: common.storage [bos: <bos_storage_config>] # The swift_storage_config block configures the connection to OpenStack Object # Storage (Swift) object storage backend. # The CLI flags prefix for this block configuration is: common.storage [swift: <swift_storage_config>] filesystem: # Directory to store chunks in. # CLI flag: -common.storage.filesystem.chunk-directory [chunks_directory: <string> | default = ""] # Directory to store rules in. # CLI flag: -common.storage.filesystem.rules-directory [rules_directory: <string> | default = ""] hedging: # If set to a non-zero value a second request will be issued at the provided # duration. Default is 0 (disabled) # CLI flag: -common.storage.hedge-requests-at [at: <duration> | default = 0s] # The maximum of hedge requests allowed. # CLI flag: -common.storage.hedge-requests-up-to [up_to: <int> | default = 2] # The maximum of hedge requests allowed per seconds. # CLI flag: -common.storage.hedge-max-per-second [max_per_second: <int> | default = 5] # The cos_storage_config block configures the connection to IBM Cloud Object # Storage (COS) backend. # The CLI flags prefix for this block configuration is: common.storage [cos: <cos_storage_config>] [persist_tokens: <boolean>] [replication_factor: <int>] ring: kvstore: # Backend storage to use for the ring. Supported values are: consul, etcd, # inmemory, memberlist, multi. # CLI flag: -common.storage.ring.store [store: <string> | default = "consul"] # The prefix for the keys in the store. Should end with a /. # CLI flag: -common.storage.ring.prefix [prefix: <string> | default = "collectors/"] # Configuration for a Consul client. Only applies if the selected kvstore is # consul. # The CLI flags prefix for this block configuration is: common.storage.ring [consul: <consul>] # Configuration for an ETCD v3 client. Only applies if the selected kvstore # is etcd. # The CLI flags prefix for this block configuration is: common.storage.ring [etcd: <etcd>] multi: # Primary backend storage used by multi-client. # CLI flag: -common.storage.ring.multi.primary [primary: <string> | default = ""] # Secondary backend storage used by multi-client. # CLI flag: -common.storage.ring.multi.secondary [secondary: <string> | default = ""] # Mirror writes to secondary store. # CLI flag: -common.storage.ring.multi.mirror-enabled [mirror_enabled: <boolean> | default = false] # Timeout for storing value to secondary store. # CLI flag: -common.storage.ring.multi.mirror-timeout [mirror_timeout: <duration> | default = 2s] # Period at which to heartbeat to the ring. 0 = disabled. # CLI flag: -common.storage.ring.heartbeat-period [heartbeat_period: <duration> | default = 15s] # The heartbeat timeout after which compactors are considered unhealthy within # the ring. 0 = never (timeout disabled). # CLI flag: -common.storage.ring.heartbeat-timeout [heartbeat_timeout: <duration> | default = 1m] # File path where tokens are stored. If empty, tokens are not stored at # shutdown and restored at startup. # CLI flag: -common.storage.ring.tokens-file-path [tokens_file_path: <string> | default = ""] # True to enable zone-awareness and replicate blocks across different # availability zones. # CLI flag: -common.storage.ring.zone-awareness-enabled [zone_awareness_enabled: <boolean> | default = false] # Instance ID to register in the ring. # CLI flag: -common.storage.ring.instance-id [instance_id: <string> | default = "<hostname>"] # Name of network interface to read address from. # CLI flag: -common.storage.ring.instance-interface-names [instance_interface_names: <list of strings> | default = [<private network interfaces>]] # Port to advertise in the ring (defaults to server.grpc-listen-port). # CLI flag: -common.storage.ring.instance-port [instance_port: <int> | default = 0] # IP address to advertise in the ring. # CLI flag: -common.storage.ring.instance-addr [instance_addr: <string> | default = ""] # The availability zone where this instance is running. Required if # zone-awareness is enabled. # CLI flag: -common.storage.ring.instance-availability-zone [instance_availability_zone: <string> | default = ""] # Enable using a IPv6 instance address. # CLI flag: -common.storage.ring.instance-enable-ipv6 [instance_enable_ipv6: <boolean> | default = false] [instance_interface_names: <list of strings>] [instance_addr: <string> | default = ""] # the http address of the compactor in the form http://host:port # CLI flag: -common.compactor-address [compactor_address: <string> | default = ""] # the grpc address of the compactor in the form host:port # CLI flag: -common.compactor-grpc-address [compactor_grpc_address: <string> | default = ""]consul# Hostname and port of Consul. # CLI flag: -<prefix>.consul.hostname [host: <string> | default = "localhost:8500"] # ACL Token used to interact with Consul. # CLI flag: -<prefix>.consul.acl-token [acl_token: <string> | default = ""] # HTTP timeout when talking to Consul # CLI flag: -<prefix>.consul.client-timeout [http_client_timeout: <duration> | default = 20s] # Enable consistent reads to Consul. # CLI flag: -<prefix>.consul.consistent-reads [consistent_reads: <boolean> | default = false] # Rate limit when watching key or prefix in Consul, in requests per second. 0 # disables the rate limit. # CLI flag: -<prefix>.consul.watch-rate-limit [watch_rate_limit: <float> | default = 1] # Burst size used in rate limit. Values less than 1 are treated as 1. # CLI flag: -<prefix>.consul.watch-burst-size [watch_burst_size: <int> | default = 1] # Maximum duration to wait before retrying a Compare And Swap (CAS) operation. # CLI flag: -<prefix>.consul.cas-retry-delay [cas_retry_delay: <duration> | default = 1s]etcd# The etcd endpoints to connect to. # CLI flag: -<prefix>.etcd.endpoints [endpoints: <list of strings> | default = []] # The dial timeout for the etcd connection. # CLI flag: -<prefix>.etcd.dial-timeout [dial_timeout: <duration> | default = 10s] # The maximum number of retries to do for failed ops. # CLI flag: -<prefix>.etcd.max-retries [max_retries: <int> | default = 10] # Enable TLS. # CLI flag: -<prefix>.etcd.tls-enabled [tls_enabled: <boolean> | default = false] # Path to the client certificate, which will be used for authenticating with the # server. Also requires the key path to be configured. # CLI flag: -<prefix>.etcd.tls-cert-path [tls_cert_path: <string> | default = ""] # Path to the key for the client certificate. Also requires the client # certificate to be configured. # CLI flag: -<prefix>.etcd.tls-key-path [tls_key_path: <string> | default = ""] # Path to the CA certificates to validate server certificate against. If not # set, the host's root CA certificates are used. # CLI flag: -<prefix>.etcd.tls-ca-path [tls_ca_path: <string> | default = ""] # Override the expected name on the server certificate. # CLI flag: -<prefix>.etcd.tls-server-name [tls_server_name: <string> | default = ""] # Skip validating server certificate. # CLI flag: -<prefix>.etcd.tls-insecure-skip-verify [tls_insecure_skip_verify: <boolean> | default = false] # Override the default cipher suite list (separated by commas). Allowed values: # # Secure Ciphers: # - TLS_RSA_WITH_AES_128_CBC_SHA # - TLS_RSA_WITH_AES_256_CBC_SHA # - TLS_RSA_WITH_AES_128_GCM_SHA256 # - TLS_RSA_WITH_AES_256_GCM_SHA384 # - TLS_AES_128_GCM_SHA256 # - TLS_AES_256_GCM_SHA384 # - TLS_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 # # Insecure Ciphers: # - TLS_RSA_WITH_RC4_128_SHA # - TLS_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_RSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 # CLI flag: -<prefix>.etcd.tls-cipher-suites [tls_cipher_suites: <string> | default = ""] # Override the default minimum TLS version. Allowed values: VersionTLS10, # VersionTLS11, VersionTLS12, VersionTLS13 # CLI flag: -<prefix>.etcd.tls-min-version [tls_min_version: <string> | default = ""] # Etcd username. # CLI flag: -<prefix>.etcd.username [username: <string> | default = ""] # Etcd password. # CLI flag: -<prefix>.etcd.password [password: <string> | default = ""]memberlist# Name of the node in memberlist cluster. Defaults to hostname. # CLI flag: -memberlist.nodename [node_name: <string> | default = ""] # Add random suffix to the node name. # CLI flag: -memberlist.randomize-node-name [randomize_node_name: <boolean> | default = true] # The timeout for establishing a connection with a remote node, and for # read/write operations. # CLI flag: -memberlist.stream-timeout [stream_timeout: <duration> | default = 10s] # Multiplication factor used when sending out messages (factor * log(N+1)). # CLI flag: -memberlist.retransmit-factor [retransmit_factor: <int> | default = 4] # How often to use pull/push sync. # CLI flag: -memberlist.pullpush-interval [pull_push_interval: <duration> | default = 30s] # How often to gossip. # CLI flag: -memberlist.gossip-interval [gossip_interval: <duration> | default = 200ms] # How many nodes to gossip to. # CLI flag: -memberlist.gossip-nodes [gossip_nodes: <int> | default = 3] # How long to keep gossiping to dead nodes, to give them chance to refute their # death. # CLI flag: -memberlist.gossip-to-dead-nodes-time [gossip_to_dead_nodes_time: <duration> | default = 30s] # How soon can dead node's name be reclaimed with new address. 0 to disable. # CLI flag: -memberlist.dead-node-reclaim-time [dead_node_reclaim_time: <duration> | default = 0s] # Enable message compression. This can be used to reduce bandwidth usage at the # cost of slightly more CPU utilization. # CLI flag: -memberlist.compression-enabled [compression_enabled: <boolean> | default = true] # Gossip address to advertise to other members in the cluster. Used for NAT # traversal. # CLI flag: -memberlist.advertise-addr [advertise_addr: <string> | default = ""] # Gossip port to advertise to other members in the cluster. Used for NAT # traversal. # CLI flag: -memberlist.advertise-port [advertise_port: <int> | default = 7946] # The cluster label is an optional string to include in outbound packets and # gossip streams. Other members in the memberlist cluster will discard any # message whose label doesn't match the configured one, unless the # 'cluster-label-verification-disabled' configuration option is set to true. # CLI flag: -memberlist.cluster-label [cluster_label: <string> | default = ""] # When true, memberlist doesn't verify that inbound packets and gossip streams # have the cluster label matching the configured one. This verification should # be disabled while rolling out the change to the configured cluster label in a # live memberlist cluster. # CLI flag: -memberlist.cluster-label-verification-disabled [cluster_label_verification_disabled: <boolean> | default = false] # Other cluster members to join. Can be specified multiple times. It can be an # IP, hostname or an entry specified in the DNS Service Discovery format. # CLI flag: -memberlist.join [join_members: <list of strings> | default = []] # Min backoff duration to join other cluster members. # CLI flag: -memberlist.min-join-backoff [min_join_backoff: <duration> | default = 1s] # Max backoff duration to join other cluster members. # CLI flag: -memberlist.max-join-backoff [max_join_backoff: <duration> | default = 1m] # Max number of retries to join other cluster members. # CLI flag: -memberlist.max-join-retries [max_join_retries: <int> | default = 10] # If this node fails to join memberlist cluster, abort. # CLI flag: -memberlist.abort-if-join-fails [abort_if_cluster_join_fails: <boolean> | default = false] # If not 0, how often to rejoin the cluster. Occasional rejoin can help to fix # the cluster split issue, and is harmless otherwise. For example when using # only few components as a seed nodes (via -memberlist.join), then it's # recommended to use rejoin. If -memberlist.join points to dynamic service that # resolves to all gossiping nodes (eg. Kubernetes headless service), then rejoin # is not needed. # CLI flag: -memberlist.rejoin-interval [rejoin_interval: <duration> | default = 0s] # How long to keep LEFT ingesters in the ring. # CLI flag: -memberlist.left-ingesters-timeout [left_ingesters_timeout: <duration> | default = 5m] # Timeout for leaving memberlist cluster. # CLI flag: -memberlist.leave-timeout [leave_timeout: <duration> | default = 20s] # How much space to use for keeping received and sent messages in memory for # troubleshooting (two buffers). 0 to disable. # CLI flag: -memberlist.message-history-buffer-bytes [message_history_buffer_bytes: <int> | default = 0] # IP address to listen on for gossip messages. Multiple addresses may be # specified. Defaults to 0.0.0.0 # CLI flag: -memberlist.bind-addr [bind_addr: <list of strings> | default = []] # Port to listen on for gossip messages. # CLI flag: -memberlist.bind-port [bind_port: <int> | default = 7946] # Timeout used when connecting to other nodes to send packet. # CLI flag: -memberlist.packet-dial-timeout [packet_dial_timeout: <duration> | default = 2s] # Timeout for writing 'packet' data. # CLI flag: -memberlist.packet-write-timeout [packet_write_timeout: <duration> | default = 5s] # Enable TLS on the memberlist transport layer. # CLI flag: -memberlist.tls-enabled [tls_enabled: <boolean> | default = false] # Path to the client certificate, which will be used for authenticating with the # server. Also requires the key path to be configured. # CLI flag: -memberlist.tls-cert-path [tls_cert_path: <string> | default = ""] # Path to the key for the client certificate. Also requires the client # certificate to be configured. # CLI flag: -memberlist.tls-key-path [tls_key_path: <string> | default = ""] # Path to the CA certificates to validate server certificate against. If not # set, the host's root CA certificates are used. # CLI flag: -memberlist.tls-ca-path [tls_ca_path: <string> | default = ""] # Override the expected name on the server certificate. # CLI flag: -memberlist.tls-server-name [tls_server_name: <string> | default = ""] # Skip validating server certificate. # CLI flag: -memberlist.tls-insecure-skip-verify [tls_insecure_skip_verify: <boolean> | default = false] # Override the default cipher suite list (separated by commas). Allowed values: # # Secure Ciphers: # - TLS_RSA_WITH_AES_128_CBC_SHA # - TLS_RSA_WITH_AES_256_CBC_SHA # - TLS_RSA_WITH_AES_128_GCM_SHA256 # - TLS_RSA_WITH_AES_256_GCM_SHA384 # - TLS_AES_128_GCM_SHA256 # - TLS_AES_256_GCM_SHA384 # - TLS_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 # # Insecure Ciphers: # - TLS_RSA_WITH_RC4_128_SHA # - TLS_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_RSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 # CLI flag: -memberlist.tls-cipher-suites [tls_cipher_suites: <string> | default = ""] # Override the default minimum TLS version. Allowed values: VersionTLS10, # VersionTLS11, VersionTLS12, VersionTLS13 # CLI flag: -memberlist.tls-min-version [tls_min_version: <string> | default = ""]grpc_client# gRPC client max receive message size (bytes). # CLI flag: -<prefix>.grpc-max-recv-msg-size [max_recv_msg_size: <int> | default = 104857600] # gRPC client max send message size (bytes). # CLI flag: -<prefix>.grpc-max-send-msg-size [max_send_msg_size: <int> | default = 104857600] # Use compression when sending messages. Supported values are: 'gzip', 'snappy' # and '' (disable compression) # CLI flag: -<prefix>.grpc-compression [grpc_compression: <string> | default = ""] # Rate limit for gRPC client; 0 means disabled. # CLI flag: -<prefix>.grpc-client-rate-limit [rate_limit: <float> | default = 0] # Rate limit burst for gRPC client. # CLI flag: -<prefix>.grpc-client-rate-limit-burst [rate_limit_burst: <int> | default = 0] # Enable backoff and retry when we hit rate limits. # CLI flag: -<prefix>.backoff-on-ratelimits [backoff_on_ratelimits: <boolean> | default = false] backoff_config: # Minimum delay when backing off. # CLI flag: -<prefix>.backoff-min-period [min_period: <duration> | default = 100ms] # Maximum delay when backing off. # CLI flag: -<prefix>.backoff-max-period [max_period: <duration> | default = 10s] # Number of times to backoff and retry before failing. # CLI flag: -<prefix>.backoff-retries [max_retries: <int> | default = 10] # Initial stream window size. Values less than the default are not supported and # are ignored. Setting this to a value other than the default disables the BDP # estimator. # CLI flag: -<prefix>.initial-stream-window-size [initial_stream_window_size: <int> | default = 63KiB1023B] # Initial connection window size. Values less than the default are not supported # and are ignored. Setting this to a value other than the default disables the # BDP estimator. # CLI flag: -<prefix>.initial-connection-window-size [initial_connection_window_size: <int> | default = 63KiB1023B] # Enable TLS in the gRPC client. This flag needs to be enabled when any other # TLS flag is set. If set to false, insecure connection to gRPC server will be # used. # CLI flag: -<prefix>.tls-enabled [tls_enabled: <boolean> | default = false] # Path to the client certificate, which will be used for authenticating with the # server. Also requires the key path to be configured. # CLI flag: -<prefix>.tls-cert-path [tls_cert_path: <string> | default = ""] # Path to the key for the client certificate. Also requires the client # certificate to be configured. # CLI flag: -<prefix>.tls-key-path [tls_key_path: <string> | default = ""] # Path to the CA certificates to validate server certificate against. If not # set, the host's root CA certificates are used. # CLI flag: -<prefix>.tls-ca-path [tls_ca_path: <string> | default = ""] # Override the expected name on the server certificate. # CLI flag: -<prefix>.tls-server-name [tls_server_name: <string> | default = ""] # Skip validating server certificate. # CLI flag: -<prefix>.tls-insecure-skip-verify [tls_insecure_skip_verify: <boolean> | default = false] # Override the default cipher suite list (separated by commas). Allowed values: # # Secure Ciphers: # - TLS_RSA_WITH_AES_128_CBC_SHA # - TLS_RSA_WITH_AES_256_CBC_SHA # - TLS_RSA_WITH_AES_128_GCM_SHA256 # - TLS_RSA_WITH_AES_256_GCM_SHA384 # - TLS_AES_128_GCM_SHA256 # - TLS_AES_256_GCM_SHA384 # - TLS_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 # # Insecure Ciphers: # - TLS_RSA_WITH_RC4_128_SHA # - TLS_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_RSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 # CLI flag: -<prefix>.tls-cipher-suites [tls_cipher_suites: <string> | default = ""] # Override the default minimum TLS version. Allowed values: VersionTLS10, # VersionTLS11, VersionTLS12, VersionTLS13 # CLI flag: -<prefix>.tls-min-version [tls_min_version: <string> | default = ""] # The maximum amount of time to establish a connection. A value of 0 means # default gRPC client connect timeout and backoff. # CLI flag: -<prefix>.connect-timeout [connect_timeout: <duration> | default = 5s] # Initial backoff delay after first connection failure. Only relevant if # ConnectTimeout > 0. # CLI flag: -<prefix>.connect-backoff-base-delay [connect_backoff_base_delay: <duration> | default = 1s] # Maximum backoff delay when establishing a connection. Only relevant if # ConnectTimeout > 0. # CLI flag: -<prefix>.connect-backoff-max-delay [connect_backoff_max_delay: <duration> | default = 5s]tls_config# Path to the client certificate, which will be used for authenticating with the # server. Also requires the key path to be configured. # CLI flag: -frontend.tail-tls-config.tls-cert-path [tls_cert_path: <string> | default = ""] # Path to the key for the client certificate. Also requires the client # certificate to be configured. # CLI flag: -frontend.tail-tls-config.tls-key-path [tls_key_path: <string> | default = ""] # Path to the CA certificates to validate server certificate against. If not # set, the host's root CA certificates are used. # CLI flag: -frontend.tail-tls-config.tls-ca-path [tls_ca_path: <string> | default = ""] # Override the expected name on the server certificate. # CLI flag: -frontend.tail-tls-config.tls-server-name [tls_server_name: <string> | default = ""] # Skip validating server certificate. # CLI flag: -frontend.tail-tls-config.tls-insecure-skip-verify [tls_insecure_skip_verify: <boolean> | default = false] # Override the default cipher suite list (separated by commas). Allowed values: # # Secure Ciphers: # - TLS_RSA_WITH_AES_128_CBC_SHA # - TLS_RSA_WITH_AES_256_CBC_SHA # - TLS_RSA_WITH_AES_128_GCM_SHA256 # - TLS_RSA_WITH_AES_256_GCM_SHA384 # - TLS_AES_128_GCM_SHA256 # - TLS_AES_256_GCM_SHA384 # - TLS_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA # - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 # - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 # - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 # # Insecure Ciphers: # - TLS_RSA_WITH_RC4_128_SHA # - TLS_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_RSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_RC4_128_SHA # - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 # CLI flag: -frontend.tail-tls-config.tls-cipher-suites [tls_cipher_suites: <string> | default = ""] # Override the default minimum TLS version. Allowed values: VersionTLS10, # VersionTLS11, VersionTLS12, VersionTLS13 # CLI flag: -frontend.tail-tls-config.tls-min-version [tls_min_version: <string> | default = ""]cache_config# (deprecated: use embedded-cache instead) Enable in-memory cache (auto-enabled # for the chunks & query results cache if no other cache is configured). # CLI flag: -<prefix>.cache.enable-fifocache [enable_fifocache: <boolean> | default = false] # The default validity of entries for caches unless overridden. # CLI flag: -<prefix>.default-validity [default_validity: <duration> | default = 1h] background: # At what concurrency to write back to cache. # CLI flag: -<prefix>.background.write-back-concurrency [writeback_goroutines: <int> | default = 10] # How many key batches to buffer for background write-back. # CLI flag: -<prefix>.background.write-back-buffer [writeback_buffer: <int> | default = 10000] # Size limit in bytes for background write-back. # CLI flag: -<prefix>.background.write-back-size-limit [writeback_size_limit: <int> | default = 1GB] memcached: # How long keys stay in the memcache. # CLI flag: -<prefix>.memcached.expiration [expiration: <duration> | default = 0s] # How many keys to fetch in each batch. # CLI flag: -<prefix>.memcached.batchsize [batch_size: <int> | default = 1024] # Maximum active requests to memcache. # CLI flag: -<prefix>.memcached.parallelism [parallelism: <int> | default = 100] memcached_client: # Hostname for memcached service to use. If empty and if addresses is unset, # no memcached will be used. # CLI flag: -<prefix>.memcached.hostname [host: <string> | default = ""] # SRV service used to discover memcache servers. # CLI flag: -<prefix>.memcached.service [service: <string> | default = "memcached"] # EXPERIMENTAL: Comma separated addresses list in DNS Service Discovery # format: # https://cortexmetrics.io/docs/configuration/arguments/#dns-service-discovery # CLI flag: -<prefix>.memcached.addresses [addresses: <string> | default = ""] # Maximum time to wait before giving up on memcached requests. # CLI flag: -<prefix>.memcached.timeout [timeout: <duration> | default = 100ms] # Maximum number of idle connections in pool. # CLI flag: -<prefix>.memcached.max-idle-conns [max_idle_conns: <int> | default = 16] # The maximum size of an item stored in memcached. Bigger items are not # stored. If set to 0, no maximum size is enforced. # CLI flag: -<prefix>.memcached.max-item-size [max_item_size: <int> | default = 0] # Period with which to poll DNS for memcache servers. # CLI flag: -<prefix>.memcached.update-interval [update_interval: <duration> | default = 1m] # Use consistent hashing to distribute to memcache servers. # CLI flag: -<prefix>.memcached.consistent-hash [consistent_hash: <boolean> | default = true] # Trip circuit-breaker after this number of consecutive dial failures (if zero # then circuit-breaker is disabled). # CLI flag: -<prefix>.memcached.circuit-breaker-consecutive-failures [circuit_breaker_consecutive_failures: <int> | default = 10] # Duration circuit-breaker remains open after tripping (if zero then 60 # seconds is used). # CLI flag: -<prefix>.memcached.circuit-breaker-timeout [circuit_breaker_timeout: <duration> | default = 10s] # Reset circuit-breaker counts after this long (if zero then never reset). # CLI flag: -<prefix>.memcached.circuit-breaker-interval [circuit_breaker_interval: <duration> | default = 10s] redis: # Redis Server or Cluster configuration endpoint to use for caching. A # comma-separated list of endpoints for Redis Cluster or Redis Sentinel. If # empty, no redis will be used. # CLI flag: -<prefix>.redis.endpoint [endpoint: <string> | default = ""] # Redis Sentinel master name. An empty string for Redis Server or Redis # Cluster. # CLI flag: -<prefix>.redis.master-name [master_name: <string> | default = ""] # Maximum time to wait before giving up on redis requests. # CLI flag: -<prefix>.redis.timeout [timeout: <duration> | default = 500ms] # How long keys stay in the redis. # CLI flag: -<prefix>.redis.expiration [expiration: <duration> | default = 0s] # Database index. # CLI flag: -<prefix>.redis.db [db: <int> | default = 0] # Maximum number of connections in the pool. # CLI flag: -<prefix>.redis.pool-size [pool_size: <int> | default = 0] # Username to use when connecting to redis. # CLI flag: -<prefix>.redis.username [username: <string> | default = ""] # Password to use when connecting to redis. # CLI flag: -<prefix>.redis.password [password: <string> | default = ""] # Enable connecting to redis with TLS. # CLI flag: -<prefix>.redis.tls-enabled [tls_enabled: <boolean> | default = false] # Skip validating server certificate. # CLI flag: -<prefix>.redis.tls-insecure-skip-verify [tls_insecure_skip_verify: <boolean> | default = false] # Close connections after remaining idle for this duration. If the value is # zero, then idle connections are not closed. # CLI flag: -<prefix>.redis.idle-timeout [idle_timeout: <duration> | default = 0s] # Close connections older than this duration. If the value is zero, then the # pool does not close connections based on age. # CLI flag: -<prefix>.redis.max-connection-age [max_connection_age: <duration> | default = 0s] # By default, the Redis client only reads from the master node. Enabling this # option can lower pressure on the master node by randomly routing read-only # commands to the master and any available replicas. # CLI flag: -<prefix>.redis.route-randomly [route_randomly: <boolean> | default = false] embedded_cache: # Whether embedded cache is enabled. # CLI flag: -<prefix>.embedded-cache.enabled [enabled: <boolean> | default = false] # Maximum memory size of the cache in MB. # CLI flag: -<prefix>.embedded-cache.max-size-mb [max_size_mb: <int> | default = 100] # The time to live for items in the cache before they get purged. # CLI flag: -<prefix>.embedded-cache.ttl [ttl: <duration> | default = 1h] fifocache: # Maximum memory size of the cache in bytes. A unit suffix (KB, MB, GB) may be # applied. # CLI flag: -<prefix>.fifocache.max-size-bytes [max_size_bytes: <string> | default = "1GB"] # deprecated: Maximum number of entries in the cache. # CLI flag: -<prefix>.fifocache.max-size-items [max_size_items: <int> | default = 0] # The time to live for items in the cache before they get purged. # CLI flag: -<prefix>.fifocache.ttl [ttl: <duration> | default = 1h] # Deprecated (use ttl instead): The expiry duration for the cache. # CLI flag: -<prefix>.fifocache.duration [validity: <duration> | default = 0s] # Deprecated (use max-size-items or max-size-bytes instead): The number of # entries to cache. # CLI flag: -<prefix>.fifocache.size [size: <int> | default = 0] [purgeinterval: <duration>] # The maximum number of concurrent asynchronous writeback cache can occur. # CLI flag: -<prefix>.max-async-cache-write-back-concurrency [async_cache_write_back_concurrency: <int> | default = 16] # The maximum number of enqueued asynchronous writeback cache allowed. # CLI flag: -<prefix>.max-async-cache-write-back-buffer-size [async_cache_write_back_buffer_size: <int> | default = 500]period_config# The date of the first day that index buckets should be created. Use a date in # the past if this is your only period_config, otherwise use a date when you # want the schema to switch over. In YYYY-MM-DD format, for example: 2018-04-15. [from: <daytime>] # store and object_store below affect which <storage_config> key is used. Which # index to use. Either tsdb or boltdb-shipper. Following stores are deprecated: # aws, aws-dynamo, gcp, gcp-columnkey, bigtable, bigtable-hashed, cassandra, # grpc. [store: <string> | default = ""] # Which store to use for the chunks. Either aws (alias s3), azure, gcs, # alibabacloud, bos, cos, swift, filesystem, or a named_store (refer to # named_stores_config). Following stores are deprecated: aws-dynamo, gcp, # gcp-columnkey, bigtable, bigtable-hashed, cassandra, grpc. [object_store: <string> | default = ""] # The schema version to use, current recommended schema is v12. [schema: <string> | default = ""] # Configures how the index is updated and stored. index: # Table prefix for all period tables. [prefix: <string> | default = ""] # Table period. [period: <duration>] # A map to be added to all managed tables. [tags: <map of string to string>] # Configured how the chunks are updated and stored. chunks: # Table prefix for all period tables. [prefix: <string> | default = ""] # Table period. [period: <duration>] # A map to be added to all managed tables. [tags: <map of string to string>] # How many shards will be created. Only used if schema is v10 or greater. [row_shards: <int>]alibabacloud_storage_config# Name of OSS bucket. # CLI flag: -common.storage.oss.bucketname [bucket: <string> | default = ""] # oss Endpoint to connect to. # CLI flag: -common.storage.oss.endpoint [endpoint: <string> | default = ""] # alibabacloud Access Key ID # CLI flag: -common.storage.oss.access-key-id [access_key_id: <string> | default = ""] # alibabacloud Secret Access Key # CLI flag: -common.storage.oss.secret-access-key [secret_access_key: <string> | default = ""]bos_storage_config# Name of BOS bucket. # CLI flag: -<prefix>.bos.bucket-name [bucket_name: <string> | default = ""] # BOS endpoint to connect to. # CLI flag: -<prefix>.bos.endpoint [endpoint: <string> | default = "bj.bcebos.com"] # Baidu Cloud Engine (BCE) Access Key ID. # CLI flag: -<prefix>.bos.access-key-id [access_key_id: <string> | default = ""] # Baidu Cloud Engine (BCE) Secret Access Key. # CLI flag: -<prefix>.bos.secret-access-key [secret_access_key: <string> | default = ""]local_storage_config# Directory to store chunks in. # CLI flag: -local.chunk-directory [directory: <string> | default = ""]
0
0
0
浏览量940
武士先生

『快速入门electron』之实现窗口拖拽

看完本文你可学会对于进程通信有基本的一个了解学会自定义的顶部栏如何实现拖拽功能实现拖拽功能首先我们当然要在配置中将他自带的顶部栏禁用掉... frame:false, ...然后去到我们的index.html,去用div画个header<div class="header"></div>随便给个黑色就行接着我们要监听三个事件:mousedown mouseup mousemove来确定鼠标点击、鼠标移动以及鼠标松开时候的坐标。这时候我们来设定一些变量let isDown = false; // 鼠标状态 let baseX = 0,baseY = 0; //监听坐标接下来是mousedown事件:header.addEventListener('mousedown',function(e){ isDown = true baseX = e.x baseY = e.y })可以看到每次点击黑色的顶部栏都有坐标在右边打印出来开启控制台快捷键 ctrl shift i然后我们要做的就是在移动中获取窗口实时的位置,首先要来明白这一点把屏幕当成坐标轴,我们最后需要的其实是(screenX - baseX) , (screenY - baseY)所以我们这样写 document.addEventListener('mousemove',function(e){ if(isDown){ const x = e.screenX - baseX const y = e.screenY - baseY ipcRenderer.send('move-application',{ posX:x, posY:y }) } })算出x,y的坐标并发送到主进程,不知道这个如何通信的也可以去看我之前快速入门那个文章。下面在主进程中接收,并使用setPosition方法实时设置位置就行了。ipcMain.on('move-application',(event,pos) => { mainWin && mainWin.setPosition(pos.posX,pos.posY) })我们打印一下pos 看看:可以看到我们控制台上疯狂滚动的坐标,证明我们的操作没有错误。最后不要忘记鼠标抬起的时候将isDown设置回去。document.addEventListener('mouseup',function(){ isDown = false })至此,我们的拖拽就成功了,学会了吗?
0
0
0
浏览量1752
武士先生

微信小程序利用百度api达成植物识别

看完本文你可学会:如何使用百度智能云如何在微信小程序中去接入百度api,达成植物识别的效果如何使用百度智能云?官网:cloud.baidu.com进入官网,在用户中心下选择图像识别完后就可以点创建应用新用户可以跳转到领取免费资源页面去领取白给的使用次数填写应用名字什么的就不说了,创建完应用后,我们可以得到如下两个我们需要的东西:API KeySecret Key第一次使用前也可以去看看文档跟教学视频的根据API Key与Secret Key获取token进入到文档中我们发现,我们需要通过上述两个key来获取token,进而完成后续的操作,所以我们先来获取一下token贴一张如何获取token的文档根据文档,我们只需要携带这几个参数请求这个接口就好// 获取token getToken(){ wx.request({ url: `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${this.data.apiKey}&client_secret=${this.data.secretKey}`, success:(res)=>{ const token = res.data.access_token this.getResult(token) } }) },利用api进行植物识别这里要注意一下图像其他的按照文档写就可以,没什么特殊的地方//获取识别结果 getResult(token){ wx.request({ url: 'https://aip.baidubce.com/rest/2.0/image-classify/v1/plant?access_token=' + token, method:'POST', data:{ image:this.data.base64Img }, header:{ 'Content-Type':'application/x-www-form-urlencoded' }, success:(res)=>{ console.log(res); }, }) },
0
0
0
浏览量1154
武士先生

JavaScript的继承,原型和原型链

前言想必,学过 java 和 C++ 的小伙伴们,对于继承这个词应该不陌生,最近我也是一直在巩固JavaScript的知识,今天就来一起学习一下JavaScript里的继承吧。继承是什么?首先我们要明确继承的概念:继承就是一个对象可以访问另外一个对象中的属性和方法B继承了A,所以B也有A具有的color属性,这个是不是我们接触CSS的时候,会有样式继承这个东西,可以这么理解一下下~继承的目的?继承的目的我觉得殊途同归,都是实现了父类的设计,并且进行代码复用。继承的方式java、c++等:java是通过class类,C++是通过:而我们的JavaScript,是通过原型链 ,ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 的继承依然和基于类的继承没有一点关系。原型与原型链JavaScript 只有一种结构:对象。JavaScript 的每个对象都包含了一个隐藏属性__proto__,我们就把该隐藏属性 proto 称之为该对象的原型 (prototype),proto 指向了内存中的另外一个对象,我们就把 proto 指向的对象称为该对象的原型,那么该对象就可以直接访问其原型对象的方法或者属性。我们可以看到使用 C.name 和 C.color 时,给人的感觉属性 name 和 color 都是对象 C 本身的属性,但实际上这些属性都是位于原型对象上,我们把这个查找属性的路径称为原型链每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。查到到null,证明链子到头啦~总结一下:继承就是一个对象可以访问另外一个对象中的属性和方法,在JavaScript 中,我们通过原型和原型链的方式来实现了继承特性。继承的方式构造函数如何创建对象有一点java基础的看这块会不会特别得劲~,我当初是学过java之后接触到的这个概念,就很顺利的就理解了。function DogFactory(type, color) { this.type = type; this.color = color } var dog = new DogFactory('Dog','Black')创建实例的过程var dog = {}; dog.__proto__ = DogFactory.prototype; DogFactory.call(dog,'Dog','Black');观察这个图,我们可以看到执行流程分为三步:首先,创建了一个空白对象 dog;然后,将 DogFactory 的 prototype 属性设置为 dog 的原型对象,这就是给 dog 对象设置原型对象的关键一步;最后,再使用 dog 来调用 DogFactory,这时候 DogFactory 函数中的 this 就指向了对象 dog,然后在 DogFactory 函数中,利用 this 对对象 dog 执行属性填充操作,最终就创建了对象 dog。每个函数对象中都有一个公开的 prototype 属性,当你将这个函数作为构造函数来创建一个新的对象时,新创建对象的原型对象就指向了该函数的 prototype 属性,所以通过该构造函数创建的任何实例都可以通过原型链找到构造函数的prototype上的属性实例的proto属性 == 构造函数的proyotype也就是说dog.__proto == DogFactory.prototype原型链继承原理: 实现的本质是将子类的原型指向了父类的实例优点:父类新增原型方法/原型属性,子类都能访问到简单容易实现缺点:不能实现多重继承来自原型对象的所有属性被所有实例共享创建子类实例时,无法向父类构造函数传参//父类型 function Person(name, age) { this.name = name, this.age = age, this.play = [1, 2, 3] this.setName = function () { } } Person.prototype.setAge = function () { } //子类型 function Student(price) { this.price = price this.setScore = function () { } } Student.prototype = new Person('wang',23) // 子类型的原型为父类型的一个实例对象 var s1 = new Student(15000) var s2 = new Student(14000) console.log(s1,s2)借用构造函数实现继承原理:在子类型构造函数中通用call()调用父类型构造函数特点:解决了原型链继承中子类实例共享父类引用属性的问题创建子类实例时,可以向父类传递参数可以实现多重继承(call多个父类对象)缺点:实例并不是父类的实例,只是子类的实例只能继承父类的实例属性和方法,不能继承父类原型属性和方法无实现函数复用,每个子类都有父类实例函数的副本,影响性能function Person(name, age) { this.name = name, this.age = age, this.setName = function () {} } Person.prototype.setAge = function () {} function Student(name, age, price) { Person.call(this, name, age) // 相当于: /* this.Person(name, age) this.name = name this.age = age*/ this.price = price } var s1 = new Student('Tom', 20, 15000)ww原型链+借用构造函数的组合继承原理:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。优点:可以继承实例属性/方法,也可以继承原型属性/方法不存在引用属性共享问题可传参父类原型上的函数可复用缺点:调用了两次父类构造函数,生成了两份实例function Person(name, age) { this.name = name, this.age = age, this.setAge = function () { } } Person.prototype.setAge = function () { console.log("111") } function Student(name, age, price) { Person.call(this,name,age) this.price = price this.setScore = function () { } } Student.prototype = new Person() Student.prototype.constructor = Student//组合继承也是需要修复构造函数指向的 var s1 = new Student('Tom', 20, 15000) var s2 = new Student('Jack', 22, 14000) console.log(s1) console.log(s1.constructor) //StudentES6 class继承原理: ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。我当时第一次见的时候,还以为是java 其实我还是觉得,class写起来得劲多了,哈哈哈优点:语法简单易懂,操作更方便缺点:并不是所有的浏览器都支持class关键字class Person { //调用类的构造方法 constructor(name, age) { this.name = name this.age = age } //定义一般的方法 showName() { console.log("调用父类的方法") console.log(this.name, this.age); } } let p1 = new Person('kobe', 39) console.log(p1) //定义一个子类 class Student extends Person { constructor(name, age, salary) { super(name, age)//通过super调用父类的构造方法 this.salary = salary } showName() {//在子类自身定义方法 console.log("调用子类的方法") console.log(this.name, this.age, this.salary); } } let s1 = new Student('wade', 38, 1000000000) console.log(s1) s1.showName()最后其实我没有在平时写的项目中,用过继承,所以不太懂具体的应用场景,希望大佬们可以指点一下。
0
0
0
浏览量1993
武士先生

关于对JavaScript变量提升的理解

js变量提升JavaScript是单线程语言,所以执行肯定是按顺序执行。但是并不是逐行的分析和执行,而是一段一段地分析执行,会先进行编译阶段然后才是执行阶段。在编译阶段阶段,代码真正执行前的几毫秒,会检测到所有的变量和函数声明,所有这些函数和变量声明都被添加到名为Lexical Environment的JavaScript数据结构内的内存中。所以这些变量和函数能在它们真正被声明之前使用。先从一个简单的例子来入手: a = 2; var a; console.log(a);以上的代码会输出什么,假如这段代码是从上到下执行的话,肯定会输出undefined,然而JavaScript却不是自上而下执行的语言。这段代码的输出结果是2,意外吗?那么,为什么会这样,这个关键点就在于--变量提升。他会将当前作用域的所有变量的声明,提升到程序的顶部,因此,上面的代码等价于以下代码,这样是不是就能明白一些了? var a; a = 2; console.log(a);那么我们再看一个例子: console.log(a); var a = 2;这段代码又会输出什么?输出2吗?其实这段代码会输出undefined。这又是为什么呢?刚刚说过,JavaScript会将变量的声明提升到顶部,但是赋值语句是不会提升的,对于js来说,var a = 2是分为两步解析的:var a;a = 2;而js只会提升 var a 这句,所以刚刚的语句等价于: var a; console.log(a); a = 2;那么,为什么会有变量提升?为什么会出现变量提升这个现象呢?因为js与其他语言一样,都要经历编译跟执行阶段。而js在编译阶段的时候,会搜集所有的变量声明并且提前声明变量,而其他的语句都不会改变他们的顺序,因此,在编译阶段的时候,第一步就已经执行了,而第二部则是在执行阶段执行到该语句的时候才执行。变量声明js的变量声明应该大体上可以分三种:var声明、let与const声明和函数声明。函数声明与其他声明一起出现的时候,就可能会引起一些冲突。我们接着往下看: fn(); function fn () { console.log('fn'); } var fn = 2;你觉得会输出什么?这么写会报错吗?其实输出的结果是fn。这就解释了我们刚刚的问题,当函数声明与其他声明一起出现的时候,是以谁为准呢?答案就是,函数声明高于一切,毕竟函数是js的贵族阶级。那么多个函数声明怎么办呢? fn(); function fn () { console.log('1'); } function fn () { console.log('2'); }以上代码输出结果为2。这是因为有多个函数声明的时候,是由最后的函数声明来替代前面的。还有最后一个例子了: fn(); var fn = function () { console.log('fn'); }经过了上面的理解,再看这个是不是就很简单了呀?这个跟第二个例子是一样的,var fn = function() {}这种格式我们叫做函数表达式。它其实也分为两部分:var fn;fn = function() {};参考例2,我们可以知道,这个的结果应该是报错了(因为fn声明但未赋值,因此fn是undefined)。总结那么,来总结一下吧。js会将变量的声明提升到js顶部执行,对于var a = 2这种语句,会拆分开,将var a这步进行提升。变量提升的本质其实是js引擎在编译的时候,就将所有的变量声明了,因此在执行的时候,所有的变量都已经完成声明。当有多个同名变量的时候,函数声明会覆盖其他的声明。如果有多个函数声明,则由最后一个函数声明覆盖之前的所有声明。
0
0
0
浏览量1005
武士先生

我的第一次PR,一个炫酷的个人介绍页面

前言一直想做个关于介绍的个人页面,挂在域名的根路径下面,当home页用,还不想手动的去自己从0到1的去做一个,觉得那很浪费时间,直到我前几天逛github,发现了这个项目,瞬间觉得,它就是我想要的样子~个人简介页面模板女生自用99新????引起了我极大的好奇心,连忙点进去。这个项目是Vue做的,READEME里面给与了充足的介绍,跟效果图,demo网址等,给了小白充足的了解去把这个项目更改成为自己的一个介绍网页,里面的功能也是十分的齐全。如果有需要可以直接点击传送门:Jiaocz/Personal-page我也是十分的欣喜,终于找到了我喜欢的样子,于是直接git clone,将项目拉下来,进去照着注释,文档,各种改了改,于是他最后成了这个样子:传送门查看效果:Ned这里我又要说一件事情了,这个项目原来,下方的icon不是我截屏中的这样,而是这个效果:而我心中的页面,下方想要放一些自己的站点,点击可以直接跳转的那种,并且我觉得他原来做的动效也很好看,可以保留,于是就想着,我能不能自己给他改动一下,使得🐟和🐻可以兼得呢?是不是做出来还可以提个PR,满足我的开源梦~我的第一次PR说做就做,我去找到了有关icon的函数,查了一下相关的配置,发现我这个需求其实不难实现。用到的原本的数据格式是这样:[ { name: "Github", icon: "icon brands fa-github", desc: "Github", content: "https://www.wangez.site/", show: false, // jump: true, } ]下面函数是获知用户点击了哪个icon,传入对应的name,从而去将show改成true,将其内部的content显示出来。showContact: function (name) { let num = 0; for (let i = 0; i < this.contacts.length; i++) { if (this.contacts[i].name == name) { num = i; break; } } this.hideContact(false); this.defaults = 0; clearTimeout(this.timer); this.timer = setTimeout(() => { this.contacts[num].show = true; this.backButton = true; }, 500); }我在这个数据中加入一个jump字段,也是有着true,false,两个值,如果根据name得到的那组数据中的jump字段是true,就将其跳转,false我们就不管,继续执行他原本的动效操作。showContact: function (name) { let num = 0; for (let i = 0; i < this.contacts.length; i++) { if (this.contacts[i].name == name) { num = i; break; } } if (this.contacts[num].jump == true) { window.open(this.contacts[num].content); return; } this.hideContact(false); this.defaults = 0; clearTimeout(this.timer); this.timer = setTimeout(() => { this.contacts[num].show = true; this.backButton = true; }, 500); }改完,测试完的我,就兴致勃勃的提交了我人生中的第一个PR,后来跟项目作者在github上交流了几次,也做了部分修改后,我的PR成功被采纳了!!!!!虽然改动的地方不多,可能还不够完善,但是今后我会持续发光发热,争取把我的想法输出在这个项目中,也是给那些像我一样,懒的自己做,只想找一个自定义更丰富的个人页面的人一个满意的结果。至于我的页面我的icon跟作者使用的不同,使用的阿里矢量库中的icon,所以我将其中的a标签换成了span,并更改了一些样式,可能后续的我会把我下方icon的部分也提交到仓库中,供给更多的朋友们使用。
0
0
0
浏览量1224
武士先生

中秋想看月亮还不想出门怎么办

前言中秋节,团圆的日子,先祝各位节日快乐,身体健康!在吃过团圆饭后,我们是不是还有一个传统的习俗,就是赏月。古时候,人们都住在庭院里,没有现在的高楼大厦,吃完饭在院子里一坐,谈话赏月。一壶茶,一家人,岂不快哉~但是问题来了,现如今我们的生活节奏太快了,楼房遍地都是,很少人有雅兴去能够静下心来去做赏月这回事,很有可能是当天晚上因为出去办事,顺便照个相,发个pyq,赏月结束。但是大家放心,我们是谁?伟大的程序员啊,我们当然可以做到,足不出户的赏月~开始造月首先我们先来一个黑色的背景板,当作夜空,在其中放上一个div,接下来造月用。<!DOCTYPE html> <html> <head> <title></title> <style type="text/css"> html,body{ margin: 0; padding: 0; } body{ height: 100vh; background-color: black; } </style> </head> <body> <div class="moon" title="这是一个月亮"></div> </body> </html>接下来就是为这个div月亮添加样式了,月亮嘛,得是白色,圆的是吧,所以我们添加上这些CSS样式,因为居中好看一些,所以我们在body中添加了样式使得月亮垂直水平居中。body{ display: flex; height: 100vh; justify-content: center; background-color: black; align-items: center; } .moon{ width: 8em; height: 8em; border-radius: 50%; background-color: white; }这个时候我们的月亮是这样子的。嗯,是一个月亮的样子了,但是我觉得这还不够,一个月亮怎么能够没有光辉呢?加上!光辉这里,我的思路是加上白色的阴影,这样在黑色的衬托下就会显得像有光辉一样。至此,我们中秋的圆月就造完了,下面再做两个月亮不是圆月的样子。代码如下:html,body{ margin: 0; padding: 0; } body{ height: 100vh; display: flex; flex-wrap: wrap; justify-content: space-around; align-items: center; background-color: black; } .moon1,.moon2{ width: 8em; height: 8em; border-radius: 50%; background-color: white; } .moon1{ box-shadow: 0 0 40px white; } .moon2{ position: relative; box-shadow: 0 0 10px white; } .moon2::before{ content:''; width: 8em; height: 8em; border-radius: 50%; background-color: black; position: absolute; left: 2em; top: 0; z-index: 1; } .moon3{ box-sizing: border-box; width: 8em; height: 8em; border-width: 0 0 0 1.5em; border-style: solid; border-color: white; border-radius: 50%; } <div class="moon1" title="这个是刚刚做的圆月"></div> <div class="moon2" title="这个月亮右侧不能设置光辉"></div> <div class="moon3" title="这个月亮由边框设置的,不能设置光辉"></div>再谈月亮有心的小伙子可以看出,第二和第三个月亮的实现方式并不一样。那我们再来说一下他们是如何实现的吧。第二个月亮我们用两个纸片,用剪刀裁剪成相同大小的圆,把他们重叠在一起,错开位置,这样的形状就是我们想要的了。在css中也是一样的道理,利用伪元素,制造一个相同大小的圆,完后用黑色的圆去遮盖白色圆的右边(利用相对定位)。下面再贴一次代码,方便大家看(只贴了css部分).moon1,.moon2{ width: 8em; height: 8em; border-radius: 50%; background-color: white; } .moon2{ position: relative; box-shadow: 0 0 10px white; } .moon2::before{ content:''; width: 8em; height: 8em; border-radius: 50%; background-color: black; position: absolute; left: 2em; top: 0; z-index: 1; }但是这样的圆的圆弧一侧是没有光辉的,因为那里是遮盖形成的。第三个月亮第三个月亮我们是用边框来做的。设置元素的左边框的宽度,再设置一个圆角使他变成一个弧形,别忘了把边框的颜色改成白色,这样就好啦。注意,这个不能设置光辉,也就是阴影哦~ 要不,要不就露馅啦! (加个boder-shadow去试试吧,嘿嘿)下面也贴一下它的代码,方便观察:.moon3{ box-sizing: border-box; width: 8em; height: 8em; border-width: 0 0 0 1.5em; border-style: solid; border-color: white; border-radius: 50%; }
0
0
0
浏览量932
武士先生

快速了解ES6模块化少不了这篇文章

前言在之前的JavaScript中是没有模块化概念的,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。如果要进行模块化操作,就需要引入第三方的类库。随着技术的发展,前后端分离,前端的业务变的越来越复杂化,于是才有了ES6模块化的诞生。为什么要有模块化,或者模块化的好处是什么呢?大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块间的相互调用,利人利己。 可以将一段复杂的程序拆解开来,方便维护可拓展。前端模块化规范在ES6模块化诞生之前,JavaScript社区尝试并提出了AMD、CMD、commonJS等模块化规范。但是,这些模块化规范,存在一定的差异性与局限性,并不能通用。例如:AMD和CMD适用于浏览器端的JavaScript模块化commonJS适用于服务器端的JavaScript模块化 Node.js 就是遵循的这个规范导入其它模块使用require()导出使用module.exports对象太多的模块化规范给开发者增加了学习的难度与开发的成本。所以,ES6模块化规范诞生了!什么是ES6模块化规范ES6模块化规范是浏览器端与服务端通用的模块化开发规范。它的出现极大的降低了前端开发者的模块化学习成本,开发者不需要在额外学习AMD、CMD或者commonJS等模块化规范。ES6中模块化规范中定义:每个js文件都是一个独立的模块导入其他模块成员使用import关键字向外共享模块成员使用export关键字在node.js中体验ES6模块化node.js中默认仅支持commonJS模块化规范,若想在node中进行体验,要按照如下两步骤进行配置:确保安装了v14.15.1或者更高版本的node.js 可以使用在cmd窗口中使用node -v命令查看当前版本号哦~在package.json的根节点中添加"type":"module"节点 不知道如何添加的小伙伴看这里: 首先我们要在一个空文件夹内,执行npm init -y,这时候我们就能看见已经自动生成了package.json文件了 完后在vs-code打开,在内添加"type":"module"节点即可 小提示:type值默认为commonJS,所以我们平时node遵循的模块化规范都是commonJSES6模块化的基本语法ES6的模块化主要包含如下3种用法:默认导出与默认导入按需导出与按需导入直接导入并执行模块中的代码默认导出语法:export default 默认导出的成员let n1 = 10 // 定义模块私有成员 n1 let n2 = 20 // 定义模块私有成员 n2 因为没有共享出去,所以外界访问不到 function show(){ // 定义模块私有方法 show } export default { // 使用export default 默认导出语法 向外共享n1 和 show 两个成员 n1, show }注意事项每个模块中,只允许用唯一的一次 export default,否则会报错!默认导入语法:import接收名称form模块标识符// 从 m1.js 模块中导入 export default 向外共享的成员 // 并使用 m1 进行接收 import m1 form './m1.js' console.log(m1) // 输出为: { n1: 10, show: [Function:show]}注意事项默认导入的时候,接收名字可以任意写,注意是合法的成员名称就行。// m1 合法 不报错 import m1 form './m1.js' // 成员名称不能用数字开头,所以会直接报错 import 123 form './m1.js'按需导出语法:export按需导出的成员// 向外按需导出变量 s export let s = 'Ned' // 向外按需导出方法 show export function show(){}按需导入语法:import {s} from模块标识符 import { s, show } form './m1.js' console.log(s) // Ned console.log(show) // [Function: show]注意事项每个模块中可以使用多次按需导出按需导入的成员名称必须跟按需导出的名称一致按需导入时,可以使用as关键字进行重命名按需导入可以和默认导入一起使用重命名:import { s as str } form './m1.js'使用as关键字,将s重命名为str,所以接下来我们使用str就好了,不能再使用s这个名字。按需导入和默认导入一起使用:import info,{ s as str } form './m1.js'info就是默认导入,后面带大括号的就是按需导入。直接导入并执行模块中的代码如果只想单纯的执行某个模块中的代码,并不需要得到其内部向外共享的成员,可以这样做:// m1.js: for(let i = 0; i < 10; i++){ console.log(i) } ------------------------- // 直接导入并执行模块中的代码 import './m1.js'没错,就是直接导入即可。最后这篇文章简单介绍一下模块化的概念和语法,过几天我还会出一篇文章来告诉大家模块化在实际应用里是如何使用的。在肝了在肝了ing😭 感兴趣的朋友可以留个赞,我们一同进步!
0
0
0
浏览量734
武士先生

用CSS实现超美星空特效

前言最近真是越来越对CSS感兴趣了,于是再来整一手,夜晚的星星,再配合上皎洁的月光,这唯美的星空,它来了!今天带领大家,用CSS实现一下,这美丽的星空。开始实现星空我是找了张图片,这毕竟功力有限,目前还不能人造星~,下面说一下如何将它放置在夜空中,并实现眨眼睛的效果:运用了一个span标签,将此图片作为其背景图,来生成星星:var screenW = document.documentElement.clientWidth; var screenH = document.documentElement.clientHeight; for(var i=0; i<150; i++){ var span = document.createElement('span'); document.body.appendChild(span); var x = parseInt(Math.random() * screenW); var y = parseInt(Math.random() * screenH); span.style.left = x + 'px'; span.style.top = y + 'px'; span.style.zIndex = "0"; var scale = Math.random() * 1.5; span.style.transform = 'scale('+ scale + ', ' + scale + ')'; }先获取屏幕宽高,完后用上随机数使得星星的位置随机,大小随机,频率随机。会眨眼睛的才是好星星星星还要是一眨一眨的,才好看,所以我们给它加上一个动画,更改其的透明度就好:@keyframes flash { 0%{opacity: 0;} 100%{opacity: 1;} }但是我们很快发现一个问题,就是它太过于整齐划一:我们在生成星星的时候,给它每一个的延迟频率随机一下,这样就能保证它们有一种参差错落的感觉。var rate = Math.random() * 1.5; span.style.animationDelay = rate + 's';现在来看看我们美丽的星空吧~:最后再给星星加一个hover效果,将其放大一点,完后旋转个180span:hover{ transform: scale(3, 3) rotate(180deg) !important; transition: all 1s; }开始实现月亮一个美丽的夜晚,天空中除了星星,应当还有月亮:月亮主要是两个动画,一个是从左下往右上移动,达到一个月亮升起的效果,另一个是随着升起,月亮周围的光辉变得越来越亮眼。做法:将月亮放到一个容器中,用容器来做移动的特效,月亮本身只关注光辉就好。<div id="wrapper"> <div id="circle"></div> </div>#wrapper { position:absolute; top:50px; left:80%; width:200px; height:200px; margin-left:-100px; animation: moonline 5s linear; } @-webkit-keyframes moonline { 0% {top:250px;left:0%;opacity:0;} 30% {top:150px;left:40%;opacity:0.5;} 80% {top:50px;left:80%;opacity:1;} } #circle { position: absolute; top: 0; left: 0; width: 200px; height: 200px; background-color: #EFEFEF; box-shadow:0 0 40px #FFFFFF; border-radius: 100px; animation:moonlight 5s linear; } @-webkit-keyframes moonlight { 0% {-webkit-box-shadow:0 0 10px #FFFFFF;} 30% {-webkit-box-shadow:0 0 15px #FFFFFF;} 40% {-webkit-box-shadow:0 0 20px #FFFFFF;} 50% {-webkit-box-shadow:0 0 25px #FFFFFF;} 100% {-webkit-box-shadow:0 0 30px #FFFFFF;} }看一下最终效果:
0
0
0
浏览量1078
武士先生

用Node.js实现一个本地的石头剪刀布游戏

前言前一段日子学了个石头剪刀布游戏,自己在本地进行了实现,想挂在自己服务器上让他形成一个外网可访问的游戏的时候,出了问题,是接口请求路径不对的问题,现在还不知道什么原因,等解决之后我还会更一下。所需要准备的Node.js环境(没有的可以去官网下一下,傻瓜式安装就好)基础的html、css、js能力入门级的Node.js就好(因为我也是这个级别)一个你熟悉的代码编写工具开始上手操作首先我们需要一个html页面来作游戏结果的返回以及玩家操作。需求分析:我们需要一个地方来做游戏结果的返回还需要三个按钮来给用户做操作交互下面来看index.html文件<div id="output" style="height: 400px; width: 600px; background: #eee"></div> <button id="rock" style="height: 40px; width: 80px">石头</button> <button id="scissor" style="height: 40px; width: 80px">剪刀</button> <button id="paper" style="height: 40px; width: 80px">布</button>我定义了一个div,来作为显示游戏结果的地方,定义了三个按钮,分别代表剪刀、石头、布。接下来我们应该做的就是通过接口的方式,提交我们用户的操作并且获取游戏结果,将他显示在刚刚的div里。const $button = { rock: document.getElementById('rock'), scissor: document.getElementById('scissor'), paper: document.getElementById('paper') } const $output = document.getElementById('output') Object.keys($button).forEach(key => { $button[key].addEventListener('click', function () { fetch(`http://${location.host}/game?action=${key}`) .then((res) => { return res.text() }) .then((text) => { $output.innerHTML += text + '<br/>'; }) }) })之后我们去建立一个game.js文件,写一下游戏的判断逻辑。module.exports = function (palyerAction){ if(['rock','scissor','paper'].indexOf(palyerAction) == -1){ throw new Error('invalid playerAction'); } // 计算出电脑出的结果 var computerAction; var random = Math.random() * 3; if(random < 1){ computerAction = "rock" }else if(random > 2){ computerAction = "scissor" }else{ computerAction = "paper" } if(computerAction == palyerAction){ return 0; }else if( (computerAction == "rock" && palyerAction == "scissor") || (computerAction == "scissor" && palyerAction == "paper") || (computerAction == "paper" && palyerAction == "rock") ){ return -1; }else{ return 1; } }大致的逻辑很简单,通过随机数让电脑出拳,之后判断胜负并返回。下面看一下用node.js写的简单交互的地方,index.js:const querystring = require('querystring'); const http = require('http'); const url = require('url'); const fs = require('fs'); const game = require('./game'); let playerWon = 0; let playerLastAction = null; let sameCount = 0; http .createServer(function (request, response) { const parsedUrl = url.parse(request.url); if (parsedUrl.pathname == '/favicon.ico') { response.writeHead(200); response.end(); return; } if (parsedUrl.pathname == '/game') { const query = querystring.parse(parsedUrl.query); const playerAction = query.action; if (playerWon >= 3 || sameCount == 9) { response.writeHead(500); response.end('我再也不和你玩了!'); return } if (playerLastAction && playerAction == playerLastAction) { sameCount++; } else { sameCount = 0; } playerLastAction = playerAction if (sameCount >= 3) { response.writeHead(400); response.end('你作弊!'); sameCount = 9; return } // 执行游戏逻辑 const gameResult = game(playerAction); // 先返回头部 response.writeHead(200); // 根据不同的游戏结果返回不同的说明 if (gameResult == 0) { response.end('平局!'); } else if (gameResult == 1) { response.end('你赢了!'); // 玩家胜利次数统计+1 playerWon++; } else { response.end('你输了!'); } } // 如果访问的是根路径,则把游戏页面读出来返回出去 if (parsedUrl.pathname == '/') { fs.createReadStream(__dirname + '/index.html').pipe(response); } }) .listen(3000)在cmd窗口中输入node index.js就可以在浏览器的localhost:3000端口中看见这个游戏啦!有一些node基础的同学们应该看起来很容易,毕竟我也不咋会emm。那,来看一下效果吧。没有做丝毫美化,实在是懒欸。
0
0
0
浏览量335
武士先生

Grafana Loki 快速尝鲜

Grafana Loki 是一个支持水平扩展、高可用的聚合日志系统,跟其他的聚合日志系统不同,Loki只对日志的元数据-标签进行索引,日志数据会被压缩并存储在对象存储中,甚至可以存储在本地文件系统中,能够有效降低成本;多租户,Loki允许多个租户共享一个Loki实例,租户数据完全隔离;通过插件支持第三方agent;通过LogQL查询日志,类似与PromQL;告警,能跟Prometheus、Grafana的告警系统无缝集成;可以与Mimir、Tempo无缝集成,实现日志长期存储和链路跟踪。如下图所示是基于Loki部署的典型聚合日志系统架构有3部分组成:Agent客户端,比如Promtail、Grafana Agent,系统通过agent抓取日志,添加标签后将日志转换为流,并通过HTTP API 将流推送到LokiLoki,主服务器,负责接收和存储日志以及查询,支持不同模式的部署Grafana,用于查询和显示日志数据,也可以使用LogCLI或者Loki API 查询日志Loki 安装Loki支持Helm、Docker、Tanka、Local、Istio、通过源码6种安装方式,本地测试最方便快捷就是使用Docker方式了,这里介绍下Docker的方式。首先创建一个用于存放Loki配置文件的目录,进入该目录执行以下命令:Mac/Linuxwget https://raw.githubusercontent.com/grafana/loki/v2.9.1/cmd/loki/loki-local-config.yaml -O loki-config.yaml docker run --name loki -d -v $(pwd):/mnt/config -p 3100:3100 grafana/loki:2.9.1 -config.file=/mnt/config/loki-config.yaml wget https://raw.githubusercontent.com/grafana/loki/v2.9.1/clients/cmd/promtail/promtail-docker-config.yaml -O promtail-config.yaml docker run --name promtail -d -v $(pwd):/mnt/config -v /var/log:/var/log --link loki grafana/promtail:2.9.1 -config.file=/mnt/config/promtail-config.yaml1、macOS 14 不支持 wget 命令,你可以直接通过地址下载文件,并改名2、命令里面/var/log是本地的一个系统日志,这里要换成自己服务的日志路径Windowscd "<local-path>" wget https://raw.githubusercontent.com/grafana/loki/v2.9.1/cmd/loki/loki-local-config.yaml -O loki-config.yaml docker run --name loki -v <local-path>:/mnt/config -p 3100:3100 grafana/loki:2.9.1 --config.file=/mnt/config/loki-config.yaml wget https://raw.githubusercontent.com/grafana/loki/v2.9.1/clients/cmd/promtail/promtail-docker-config.yaml -O promtail-config.yaml docker run -v <local-path>:/mnt/config -v /var/log:/var/log --link loki grafana/promtail:2.9.1 --config.file=/mnt/config/promtail-config.yaml安装完成后,访问http://localhost:3100/metrics 能看到Loki指标情况。配置日志读取我们想要简单快速的看效果,那么就使用静态配置日志路径的方式,打开promtail-config.yaml,修改如下配置scrape_configs: - job_name: local static_configs: - targets: - localhost labels: job: 服务名 __path__: /data/logs/日志路径/*log配置Grafana在Grafana中添加Loki数据源,如图填好Loki地址,测试通过后保存通过Explore 查询日志:通过Label filters,可以选择要查询的日志,通过文件名,还是通过任务名称。支持按时间范围查询和实时方式查看日志。一些问题在尝鲜过程中,也碰到了一些问题,自己新建的一个Eureka Demo始终不能在Grafana上查询到日志,没有对应的Job和filename;使用实时日志观察日志的时候,容易断掉,不显示日志;这两问题不知道什么原因,不过也不影响使用。
0
0
0
浏览量191
武士先生

学弟的一张图,让我重学了一遍函数声明和函数表达式!

前言今天下午,在我们微信群里,学弟突然发出来这样一个图:我点开一看,这不是函数声明跟函数表达式的知识点吗?前一阵子还看过相关文章。看了几眼,我说到:第一个输出应该是最后的函数声明然后呢? 没有然后了,真的,犹豫代表着此时的我不会。看来卷的还不够!先贴上答案,如果你跟我一样犹豫不决,不知道结果,那么跟我一起再次学习一遍吧。开始复习首先,确定问题:函数表达式和函数声明的区别,以及困扰住我们的优先问题函数声明和函数表达式的区别函数声明首先我们要知道,当函数声明与变量命名冲突的时候,要保持着函数声明优先的原则fn(); function fn () { console.log(‘fn’); } var fn = 2;例如这样,不会报错,会输出fn。如果你不知道为什么调用函数可以在函数声明之前,看这里 是因为javascript代码是一段一段预载的,在一段代码预载完成后,会把函数声明提前到代码段的前面执行,以便在代码段的任何地方调用,所以前面的代码无错那么,多个同名的函数声明,会如何呢?我们接着来看一下fn(); function fn () { console.log(‘1’); } function fn () { console.log(‘2’); }输出结果为2。这是因为有多个函数声明的时候,是由最后的函数声明来替代前面的这也是我上文中读了几眼代码直接说第一个输出为最后的函数声明的原因!函数表达式fn(); var fn = function () { console.log('fn'); }有用过的同学可能看到这里,说我知道,完后写出了上面这段代码,但其实这段代码是不对的,会爆出fn is not a function这个错误。这是什么原因呢?这其实就是函数声明和函数表达式的区别之一因为函数表达式相当于把一个函数当做值,赋予一个变量,而这个变量在未声明的时候,是不能使用的正确的函数表达式:var fn = function () { console.log('fn'); } fn();复习回来,再战!先把代码贴出来方便阅读:// 下面的代码输出什么 function method(foo){ console.log(foo) var foo = 'A' var foo = function () { console.log('B') } foo() function foo() { console.log('C') } foo() } var foo = 1 method(foo)经过了上面的复习,我们知道,JavaScript会将函数声明提前,所以我们来整理一下这段代码:function method(foo){ function foo() { console.log('C') } console.log(foo) var foo = 'A' var foo = function () { console.log('B') } foo() foo() } var foo = 1 method(foo)现在,再利用我们刚刚学的知识,来分析打印的都是什么数据:首先,第一个打印的是我们输出C的foo函数,第二个和第三个调用foo函数的时候,函数表达式的变量已经声明了!所以函数发生了覆盖,现在的foo函数已经是输出B的foo函数了。现在让我们来整理一下结果:function foo() { console.log('C') } B B
0
0
0
浏览量1047
武士先生

『程序员对联』抽取你的专属新春对联

前言各位掘友们,Ned在这里给各位兄弟姐妹们拜年啦~新春这个文章说实话,经历了蛮多的,一开始其实我是想用three.js做个老虎的,但是呢,失败了。废了好几个小时才搞出来两个虎爪,害,都怪我three.js的能力连基本上手都算不上,没咋玩明白,下次一定给大家做点好康的~于是我来实现我的第二个创意了,程序员对联,对联的来源于github上一博主(仓库地址),我看他仓库里写了这么多创意满满的对联,我就拿来在过年之时,跟掘友们分享一下~对联我是将那位博主的对联做成了数组来存储:大致是这些字段英文不好,拼音来凑,见谅,见谅 { title:'作者有话说', heng:'快点去抽', top:'想到啥抽到啥快点抽', bottom:'抽不到抽不到气死你' }大致的排版有横批、上下联、抽取按钮、title标签,我是用cdn引入vue来做的,按钮跟标签都是直接拉的组件hhh,这么说来我好像只是付出了些许创意?这个css写的真是太辣鸡了,就不放出来丢人了,我看百度图片里对联都差不多样子,嗯,大致也许它就长这样子!,对没错来抽一发点击网址,点击按钮:点击抽取你的新春对联,即可获得程序员的专属对联,有个坏处是字体加载太慢了(可能跟我垃圾服务器的垃圾带宽有关系)随机数生成器利用简单的一个随机数生成器生成处于[0,对联数组长度]的随机数 randomNum(minNum, maxNum) { switch (arguments.length) { case 1: return parseInt(Math.random() * minNum + 1, 10); break; case 2: return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10); break; default: return 0; break; } },抽取函数将数组值赋值到绑定对象即可,特别的简单~ getDuilian() { let num = this.randomNum(0,this.duilians.length) this.yours = this.duilians[num] },展开效果后加的一个展开效果,就是一个改变高度的动画,为了不让字体因为高度变化被挤到别的地方所以要加overflow: hidden,word-break: keep-all两个属性.ani{ -webkit-animation: ani 1.5s linear both; animation: ani 1.5s linear both; } @keyframes ani { from{ max-height: 0; } to { max-height: 100%; } }为了能够每次点击抽取都能触发一次动画,所以用一个三目运算符绑定他的class:ani?'ani':'',在抽取函数里调整好true or false 即可。对联数据一共有21种对联哦!加上作者有话说一共22个,哈哈哈,掘友们,你们抽到心仪的了嘛~来看看都有啥吧:{ title: '相亲版', top: '捋顺红橙黄绿,不得美人', bottom: '远离世纪佳缘,方得始终', heng: '欣欣向蓉' }, { title: '年度版', top: '说南道北,几个变量难取名', bottom: '思前想后,一行注释上头条', heng: '穷逼 VIP' }, { title: '生活版', top: '上班写 JavaScript 处处 $ 高亮', bottom: '回家撸 PHP 行行 new 对象', heng: '见码眼开' }, { title: '苦逼版', top: '数据库异常屡屡 500', bottom: '浏览器崩溃句句 400', heng: '删库跑路' }, { title: '辛酸版', top: '敲一夜代码,流下两三行泪水,掏空四肢五体,六杯咖啡七桶泡面,还有八个测试九层审批,可谓十分艰难;', bottom: '经十年苦读,面过九八家公司,渐忘七情六欲,五年相亲四个对象,乃知三番加班两次约会,新年一鸣惊人', heng: '谁能懂我' }, { title: '祈福版', top: '文档注释一应具全', bottom: '脊柱腰椎早日康复', heng: '鞠躬尽瘁' }, { title: '生活版', top: '西瓜包子带一斤三个', bottom: '大米白面少二十四克', heng: '1024' }, { title: '新手程序员', top: '红红火火过大年', bottom: '烫烫屯屯码三天', heng: '!@#$%^&*()' }, { title: '高级程序员', top: '坐北朝南一个需求满足东西', bottom: '思前想后几行代码安抚中央', heng: '一代键客' }, { title: '学生版', top: '读码上万行', bottom: '下键如有神', heng: '运鼠帷幄' }, { title: '送产品版(和平版)', top: '谈业务定需求必能安内攘外', bottom: '促稳定寻发展才好升职加薪', heng: '团结一致' }, { title: '老板送程序员版', top: '百个功能愿你一气呵成', bottom: '一年年终奖你十月工资', heng: '画饼充饥' }, { title: '老板送程序员版', top: '百个功能愿你一气呵成', bottom: '一年年终奖你十月工资', heng: '画饼充饥' }, { title: '隔壁老王送程序员', top: '少赚钱多说话,免得死得早', bottom: '别加班勤陪聊,不会戴绿帽', heng: '人艰不拆' }, { title: '前端版', top: '微博知乎占头条谁与争锋', bottom: '桌面移动待前端一统江湖', heng: '瞬息万变' }, { title: '后台版', top: '存数据订接口如探囊取物', bottom: '锁异步释内存似手到擒来', heng: '后方安定' }, { title: '梦想版', top: '抬头不见八阿哥', bottom: '低头迎娶白富美', heng: '人生巅峰' }, { title: '形象版', top: '格子衣,牛仔裤,背跨双肩包', bottom: '文化衫,运动鞋,眼戴八百度', heng: '员媛猿' }, { title: '社区', top: '一年三百四十五天天天打代码', bottom: '十兆九千八百七行行行见bug', heng: '生不如死' }, { title: '娶你为妻可好', top: '待我代码编成', bottom: '娶你为妻可好', heng: '没钱买房' },最后其实这个灵感主要来源于网上基于cnn的对对联的人工智能,但是py咱也写不出来,只好换个创意,搞一个咱程序员的对联~到这里整个程序员对联的介绍就完事了,再次提前给各位拜年了!
0
0
0
浏览量499
武士先生

解构运算符的理解与运用

解构符号前言最近一直在学JavaScript,看到了ES6中的解构符号,觉得这个给我们的代码简洁性带来了一个飞跃式的提升,而且它已经运用在了企业开发中,假如未来你工作中,别人在用,你却读不懂别人的代码,这造成的影响还是很大的。因此,好好学习一下吧。你可以不用,但是你不能不懂✔JavaScript ES6中,有很多特性都是为了简化代码,方便程序员去书写的。解构运算符就是其中很好的特性,它可以通过减少赋值语句的使用,或者减少访问数据下标、对象属性的方式,使得代码更加简洁,增强了代码的可读性。解构符号的作用解构赋值是对赋值运算符的扩展,他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构使用方法基本使用 let [a,b,c] = [1,2,3]; // let a = 1, b = 2, c = 3;嵌套使用// 数组 let [a, [[b], c]] = [1, [[2], 3]]; console.log(a); // 1 console.log(b); // 2 console.log(c); // 3 // 对象 let obj = { x: ['hello', {y: 'world'}] }; let { x: [x,{ y }] } = obj; console.log(x); // hello console.log(y); // world忽略// 数组 let [a, , b] = [1, 2, 3]; console.log(a); // 1 console.log(b); // 3 // 对象 let obj = { x: ['hello', { y: 'world' }] }; let { x: [x, { }] } = obj; console.log(x); // hello不完全解构// 数组 let [a = 1, b] = []; console.log(a); // 1 console.log(b); // undefined // 对象 let obj = { x: [{ y: 'world' }] }; let { x: [{ y }, x] } = obj; console.log(x); // undefined console.log(y); // world剩余运算符// 数组 let [a, ...b] = [1, 2, 3]; console.log(a); // 1 console.log(b); // [2,3] // 对象 let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}; console.log(a); // 10 console.log(b); // 20 console.log(rest); // { c: 30, d: 40 }字符串 let [a, b, c, d, e] = 'hello'; console.log(a); // h console.log(b); // e console.log(c); // l console.log(d); // l console.log(e); // o解构默认值// 当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。 let [a = 2] = [undefined]; console.log(a); // 2 // 对象 let {a = 10, b = 5} = {a: 3}; console.log(a); // 3 console.log(b); // 5交换变量的值. let a = 1; let b = 2; [a,b] = [b,a]; console.log(a); // 2 console.log(b); // 1解构赋值的应用// 1. 浅克隆与合并 let name = { name: "aaa" } let age = { age: 'bbb' } let person = { ...name, ...age } console.log(person) // { name: "aaa", age: 'bbb' } let a = [1,2,3]; let b = [4,5]; let c = [...a,...b]; console.log(c); // [1,2,3,4,5] // 2. 提取JSON数据 let JsonData = { id: 10, status: "OK", data: [111, 222] } let { id, status, data: numbers } = JsonData; console.log(id, status, numbers); //10 "OK" [111, 222] // 3. 函数参数的定义 // 参数有序 function fun1([a, b, c]) { console.log(a, b, c) } fun1([1, 2, 3]); // 1 2 3 // 参数无序 function fun2({ x, y, z }) { console.log(x, y, z) } fun2({ z: 3, x: 2, y: 1 }); // 2 1 3 // 参数有默认值 function fun3 ([a=1,b]) { console.log(a,b); } fun3([,3]) // 1 3浅谈应用提取json数据上面列出了几种解构赋值的应用,其中我们最常用的应该是第二种,提取json数据,后端传给前端的数据就是json数据,前端通常要将数据赋值给一个对象,就是使用的这种方法。可扩展运算符...我在leetcode上刷题的时候使用过,我是用arr.push(...arr1)来合并两个数组的,有点像上面的浅克隆与合并。比起以往我们合并数组的操作,这个简直不要太简单。第88题,合并两个有序数组。var merge = function(nums1, m, nums2, n) { nums1.length=m; nums2.length=n; nums1.push(...nums2); let arr=nums1.sort((a,b)=>{ return a-b; }) return arr; };...这个运算符是将数组中的数据遍历出来,并拷贝到当前对象当中。arr.push(...arr1)这个方法会将arr1的数组元素全部解析出来,然后依次添加到arr中去,完成两个数组的合并。交换变量值再来看看交换变量值这个应用,我依稀记得一位学长的面试题:不占用额外内存空间的情况下,交换a与b的值。当时有两种解法,一是使用异或,二是用数学方法,将ab相加,再分别减之,(a=a+b,b=a-b,a=a-b),现在使用解构符号的这个方法[a,b] = [b,a],是不是也可以呢?
0
0
0
浏览量2012
武士先生

JavaScript | 让数组扁平化的三个方法!

前言我们在平时写程序的时候是不是都会遇到一种问题,面对嵌套层级很多的数组,我们看着就非常反感,想给它变成就单纯的一层数组,或者简化掉它的层级结构,使得它变成我们想要的样子。所以,数组扁平化,他来了!最近还在整理有关数据类型转换的事情,数组对象、对象数组、字符串、数组,反正奇奇怪怪的,我平时遇到的时候反正是很烦!!!所以想整理出来给大家避坑!什么是扁平化定义: 数组的扁平化,就是将一个嵌套多层的数组 (嵌套有多少层都行)转换为只有一层的数组。举个例子如下:var arr = [1, [2, [3, 4]]]; console.log(flatten(arr)) // [1, 2, 3, 4]其中,faltten函数的作用,就是让数组扁平化。所以,我们已经知道这个函数起的是什么作用了,下面我们去试着写一个出来!造轮子的每一天~递归首先,我们循环这个数组,将不为数组的元素推入result中,如果元素还是数组,那么就使用递归来解决。// 递归 var arr = [1, [2, [3, 4]]]; function flatten(arr) { var result = []; for (var i = 0, len = arr.length; i < len; i++) { if (Array.isArray(arr[i])) { result = result.concat(flatten(arr[i])) } else { result.push(arr[i]) } } return result; } console.log(flatten(arr))toString()如果数组中存储元素都为数字,那么我们可以取巧,使用toString方法来解决。[1, [2, [3, 4]]].toString() // "1,2,3,4"调用toString方法后,返回的是如上面一样的字符串。我们使用split,以逗号为分割成数组,这样就可以实现扁平化了呀!var arr = [1,[2,[3,4]]]; function flatten(arr){ return arr.toString().split(',').map(function(item){ return item; }) } console.log(flatten(arr)); //[ '1', '2', '3', '4' ]但是我们很快发现不对,我们转成的是字符串数组,我们要的是数字,所以还需要做一下小修改。将return item改成return item * 1就好,我们这样得到的结果就是个数字的数组了。...扩展运算符ES6增加了这个运算符,用于取出对象所有可遍历的属性,放到当前对象内,我们来试试这个方法。var arr = [1,[2,[3,4]]]; console.log([].concat(...arr)); //[ 1, 2, [ 3, 4 ] ]上面代码的意思是,将取出arr中所有可以遍历的属性,将他们放到一个新数组中。我们拿结果和之前比对,发现只扁平了一层。那么我们是否可以写一个函数,让它循环的扁平下去呢?答案是,可以。var arr = [1,[2,[3,4]]]; function flatten(arr){ while(arr.some(item => Array.isArray(item))){ arr = [].concat(...arr); } return arr; } console.log(flatten(arr));去查找数组里是否有属性是数组,反复的去扁平,最后返回的数组就是我们想要的了。
0
0
0
浏览量749
武士先生

Grafana Promtail 配置解析

运行时打印配置-print-config-stderr 通过 ./promtail 直接运行Promtail时能够快速输出配置-log-config-reverse-order 配置通过反向输出,这样再Grafana中就能从上到下正确读取配置文件参考-config.file 通过该配置指定需要加载的文件,-config.expand-env=true 通过该配置可以在配置文件中引用环境变量,比如${VAR},VAR是环境变量的名称。每个变量引用在启动时都会替换为环境变量的值。替换区分大小写,发生在解析YAML文件之前。除非指定默认值或自定义错误文本,否则对未定义变量的引用将替换为空字符串。指定默认值使用:${VAR:-default_value}占位符说明<boolean>: true 或 false<int>: 匹配 [1-9]+[0-9]* 正则的任何整数<duration>: 匹配[0-9]+(ms|[smhdwy])正则的时间<labelname>: 匹配[a-zA-Z_][a-zA-Z0-9_]* 正则的字符串<labelvalue>: Unicode编码字符串<filename>: 相对于当前工作目录的有效路径或绝对路径<host>: 主机名或 ip:port端口号组成的字符串<string>: 字符串<secret>: 表示密码字符串config.yml# Configures global settings which impact all targets. # 影响所有target的全局配置 [global: <global_config>] # Configures the server for Promtail. # Promtail server端配置 [server: <server_config>] # Describes how Promtail connects to multiple instances # of Grafana Loki, sending logs to each. # 描述Promtail如何连接到多个Loki实例,并向每个实例发送日志 # WARNING: If one of the remote Loki servers fails to respond or responds # with any error which is retryable, this will impact sending logs to any # other configured remote Loki servers. Sending is done on a single thread! # It is generally recommended to run multiple Promtail clients in parallel # if you want to send to multiple remote Loki instances. #如果其中一个远程Loki服务器未能响应或响应任何可重试的错误, #这将影响向任何其他配置的远程Loki Server发送日志。 #发送是在一个线程上完成的!如果要发送到多个远程Loki实例,通常建议并行运行多个Promtail客户端。 clients: - [<client_config>] # Describes how to save read file offsets to disk # 描述如何将读取文件偏移量保存到磁盘 [positions: <position_config>] scrape_configs: - [<scrape_config>] # Configures global limits for this instance of Promtail # 配置此Promtail实例的全局限制 [limits_config: <limits_config>] # 目标监视配置 [target_config: <target_config>] # 其他promtail配置 [options: <options_config>] # Configures tracing support # 配置跟踪支持 [tracing: <tracing_config>]global# Configure how frequently log files from disk get polled for changes. # 配置轮询磁盘中日志文件的更改频率 [file_watch_config: <file_watch_config>]file_watch_config# Minimum frequency to poll for files. Any time file changes are detected, the # poll frequency gets reset to this duration. # 轮询文件的最小频率,检测到任何文件更改时,轮询频率都会重置为此持续时间 [min_poll_frequency: <duration> | default = "250ms"] # Maximum frequency to poll for files. Any time no file changes are detected, # the poll frequency doubles in value up to the maximum duration specified by # this value. # # The default is set to the same as min_poll_frequency. # 轮询文件的最大频率,未检测到任何文件变更,轮询频率都会加倍,直到制定的最大值 # 默认设置为与min_poll_frequency相同 [max_poll_frequency: <duration> | default = "250ms"]server# Disable the HTTP and GRPC server. # 禁用HTTP 和 GRPC服务 [disable: <boolean> | default = false] # Enable the /debug/fgprof and /debug/pprof endpoints for profiling. #启用/debug/fgprof和/debug/prof端点进行分析 [profiling_enabled: <boolean> | default = false] # HTTP server listen host # HTTP服务监听地址 [http_listen_address: <string>] # HTTP server listen port (0 means random port) # HTTP服务监听端口,0表示随机端口 [http_listen_port: <int> | default = 80] # gRPC server listen host #gRPC服务监听端口 [grpc_listen_address: <string>] # gRPC server listen port (0 means random port) # gRPC服务监听端口,0表示随机端口 [grpc_listen_port: <int> | default = 9095] # Register instrumentation handlers (/metrics, etc.) # 注册检测程序 [register_instrumentation: <boolean> | default = true] # Timeout for graceful shutdowns # 正常关机超时时间 [graceful_shutdown_timeout: <duration> | default = 30s] # Read timeout for HTTP server # HTTP 服务读取超时时间 [http_server_read_timeout: <duration> | default = 30s] # Write timeout for HTTP server # HTTP 服务写入超时时间 [http_server_write_timeout: <duration> | default = 30s] # Idle timeout for HTTP server # HTTP 服务空闲超时时间 [http_server_idle_timeout: <duration> | default = 120s] # Max gRPC message size that can be received # gRPC能够接收的最大消息 [grpc_server_max_recv_msg_size: <int> | default = 4194304] # Max gRPC message size that can be sent # gRPC能够发送的最大消息 [grpc_server_max_send_msg_size: <int> | default = 4194304] # Limit on the number of concurrent streams for gRPC calls (0 = unlimited) # gRPC并发调用的最大数量,0 不限制 [grpc_server_max_concurrent_streams: <int> | default = 100] # Log only messages with the given severity or above. Supported values [debug, # info, warn, error] # 日志级别 [log_level: <string> | default = "info"] # Base path to server all API routes from (e.g., /v1/). # 所有API路径前缀,比如 /v1/ [http_path_prefix: <string>] # Target managers check flag for Promtail readiness, if set to false the check is ignored # 是否健康检查 [health_check_target: <bool> | default = true] # Enable reload via HTTP request. # 通过HTTP请求开启重载 [enable_runtime_reload: <bool> | default = false]clients配置Promtail 如何连接到 Loki实例# The URL where Loki is listening, denoted in Loki as http_listen_address and # http_listen_port. If Loki is running in microservices mode, this is the HTTP # URL for the Distributor. Path to the push API needs to be included. # Example: http://example.com:3100/loki/api/v1/push # Loki监听的URL,在Loki中表示为http_listen_address和http_listen_port # 如果Loki在微服务模式下运行,如下是HTTP示例:http://example.com:3100/loki/api/v1/push url: <string> # Custom HTTP headers to be sent along with each push request. # Be aware that headers that are set by Promtail itself (e.g. X-Scope-OrgID) can't be overwritten. # 每个请求发送的HTTP头 #请注意,Promtail本身设置的头(例如X-Scope-OrgID)不能被覆盖 headers: # Example: CF-Access-Client-Id: xxx [ <labelname>: <labelvalue> ... ] # The tenant ID used by default to push logs to Loki. If omitted or empty # it assumes Loki is running in single-tenant mode and no X-Scope-OrgID header # is sent. # 默认情况下用于将日志推送到Loki的租户ID。 # 如果省略或为空,则假定Loki在单租户模式下运行,并且不发送X-Scope-OrgID标头。 [tenant_id: <string>] # Maximum amount of time to wait before sending a batch, even if that # batch isn't full. # 一批发送之前等待的最长时间,即使该批未满 [batchwait: <duration> | default = 1s] # Maximum batch size (in bytes) of logs to accumulate before sending # the batch to Loki. # 向 Loki 发送批次之前要累积的日志的最大批大小(以字节为单位) [batchsize: <int> | default = 1048576] # If using basic auth, configures the username and password # sent. # 基础认证,配置用户名和密码 basic_auth: # The username to use for basic auth [username: <string>] # The password to use for basic auth [password: <string>] # The file containing the password for basic auth [password_file: <filename>] # Optional OAuth 2.0 configuration # Cannot be used at the same time as basic_auth or authorization # 可选的 # OAuth 2.0配置不能与basic_auth或授权同时使用 oauth2: # Client id and secret for oauth2 [client_id: <string>] [client_secret: <secret>] # Read the client secret from a file # It is mutually exclusive with `client_secret` # 从文件中读取client secret,跟client_secret排斥 [client_secret_file: <filename>] # Optional scopes for the token request # 可选,Token请求作用域 scopes: [ - <string> ... ] # The URL to fetch the token from # 获取token的URL地址 token_url: <string> # Optional parameters to append to the token URL # 可选,token URL地址附加参数 endpoint_params: [ <string>: <string> ... ] # Bearer token to send to the server. # 发送到服务端的 Bearer token [bearer_token: <secret>] # File containing bearer token to send to the server. # 发送到服务端的包含bearer token的文件 [bearer_token_file: <filename>] # HTTP proxy server to use to connect to the server. # 连接到服务端的HTTP代理 [proxy_url: <string>] # If connecting to a TLS server, configures how the TLS # authentication handshake will operate. tls_config: # The CA file to use to verify the server [ca_file: <string>] # The cert file to send to the server for client auth [cert_file: <filename>] # The key file to send to the server for client auth [key_file: <filename>] # Validates that the server name in the server's certificate # is this value. [server_name: <string>] # If true, ignores the server certificate being signed by an # unknown CA. [insecure_skip_verify: <boolean> | default = false] # Configures how to retry requests to Loki when a request # fails. # 配置请求失败时如何重试对Loki的请求 # Default backoff schedule: # 默认策略 # 0.5s, 1s, 2s, 4s, 8s, 16s, 32s, 64s, 128s, 256s(4.267m) # For a total time of 511.5s(8.5m) before logs are lost # 在日志丢失之前总共持续511.5秒(8.5分钟) backoff_config: # Initial backoff time between retries # 重试之间的初始回退时间 [min_period: <duration> | default = 500ms] # Maximum backoff time between retries # 重试之间的最大回退时间 [max_period: <duration> | default = 5m] # Maximum number of retries to do # 要执行的最大重试次数 [max_retries: <int> | default = 10] # Disable retries of batches that Loki responds to with a 429 status code (TooManyRequests). This reduces # impacts on batches from other tenants, which could end up being delayed or dropped due to exponential backoff. # 在 Loki 返回 429 状态码(大量请求)的情况下禁用批量的重试 # 这减少了对其他租户批量的影响,这些租户最终可能会因指数回退而延迟或减少 [drop_rate_limited_batches: <boolean> | default = false] # Static labels to add to all logs being sent to Loki. # Use map like {"foo": "bar"} to add a label foo with # value bar. # These can also be specified from command line: # -client.external-labels=k1=v1,k2=v2 # (or --client.external-labels depending on your OS) # labels supplied by the command line are applied # to all clients configured in the `clients` section. # NOTE: values defined in the config file will replace values # defined on the command line for a given client if the # label keys are the same. # 要添加到发送到Loki的所有日志中的静态标签 # 比如{"foo": "bar"},添加 foo标签 bar值 # 也可以通过命令行指定:-client.external-labels=k1=v1,k2=v2 ,或者 --client.external-labels # 命令行提供的标签应用于“客户端”部分中配置的所有客户端 # 如果标签键相同,则配置文件中定义的值将替换命令行中为给定客户端定义的值 external_labels: [ <labelname>: <labelvalue> ... ] # Maximum time to wait for a server to respond to a request # 等待服务器响应请求的最长时间 [timeout: <duration> | default = 10s]positionspositions配置Promtail将保存的文件,该文件指示它已读取文件的进度。当Promtail重新启动时,需要此文件以从停止的地方继续读取# Location of positions file [filename: <string> | default = "/var/log/positions.yaml"] # How often to update the positions file [sync_period: <duration> | default = 10s] # Whether to ignore & later overwrite positions files that are corrupted # 是否要忽略并稍后覆盖损坏的positions文件 [ignore_invalid_yaml: <boolean> | default = false]scrape_configsscrape_configs配置了Promtail如何使用指定的发现方法从一系列目标中提取日志。Promtail使用与Prometheus相同的scrape_configs。这意味着如果您已经拥有一个Prometheus实例,那么配置将非常相似# Name to identify this scrape config in the Promtail UI. # 在Promtail UI中标识的唯一名称 job_name: <string> # Describes how to transform logs from targets. # 描述如何从目标转换日志 [pipeline_stages: <pipeline_stages>] # Defines decompression behavior for the given scrape target. #定义给定刮取目标的解压缩行为 decompression: # Whether decompression should be tried or not. # 是否尝试解压缩 [enabled: <boolean> | default = false] # Initial delay to wait before starting the decompression. # 开始解压前等待的初始延迟 # Especially useful in scenarios where compressed files are found before the compression is finished. # 在压缩完成之前发现压缩文件的情况下尤其有用 [initial_delay: <duration> | default = 0s] # Compression format. Supported formats are: 'gz', 'bz2' and 'z. # 压缩格式,支持'gz', 'bz2' and 'z [format: <string> | default = ""] # Describes how to scrape logs from the journal. # 描述如何从journal中拉取日志 [journal: <journal_config>] # Describes from which encoding a scraped file should be converted. # 描述应从哪种编码将提取的文件转换为编码的方法 [encoding: <iana_encoding_name>] # Describes how to receive logs from syslog. # 描述如何从syslog中接收日志 [syslog: <syslog_config>] # Describes how to receive logs via the Loki push API, (e.g. from other Promtails or the Docker Logging Driver) # 描述如何通过Loki 推送API 接收日志 [loki_push_api: <loki_push_api_config>] # Describes how to scrape logs from the Windows event logs. [windows_events: <windows_events_config>] # Configuration describing how to pull/receive Google Cloud Platform (GCP) logs. [gcplog: <gcplog_config>] # Configuration describing how to get Azure Event Hubs messages. [azure_event_hub: <azure_event_hub_config>] # Describes how to fetch logs from Kafka via a Consumer group. [kafka: <kafka_config>] # Describes how to receive logs from gelf client. [gelf: <gelf_config>] # Configuration describing how to pull logs from Cloudflare. [cloudflare: <cloudflare>] # Configuration describing how to pull logs from a Heroku LogPlex drain. [heroku_drain: <heroku_drain>] # Describes how to relabel targets to determine if they should # be processed. # 描述是否重写这些目标 relabel_configs: - [<relabel_config>] # Static targets to scrape. # 静态目标拉取 static_configs: - [<static_config>] # Files containing targets to scrape. # 目标文件拉取 file_sd_configs: - [<file_sd_configs>] # Describes how to discover Kubernetes services running on the # same host. # k8s拉取配置 kubernetes_sd_configs: - [<kubernetes_sd_config>] # Describes how to use the Consul Catalog API to discover services registered with the # consul cluster. # consul 拉取配置 consul_sd_configs: [ - <consul_sd_config> ... ] # Describes how to use the Consul Agent API to discover services registered with the consul agent # running on the same host as Promtail. # 通过consul aget api来发现拉取配置 consulagent_sd_configs: [ - <consulagent_sd_config> ... ] # Describes how to use the Docker daemon API to discover containers running on # the same host as Promtail. # 通过Docker 守护API来发现拉取的配置 docker_sd_configs: [ - <docker_sd_config> ... ]pipeline_stages管道阶段用于转换日志条目及其标签。管道在发现过程结束后执行。pipeline_stages对象由与以下列表中列出的条目相对应的阶段列表组成。在大多数情况下,您使用regex或json阶段从日志中提取数据。提取的数据将转换为临时映射对象。然后,数据可用于Promtail,例如用作标签的值或输出。此外,除了docker和cri之外的任何阶段都可以访问提取的数据。- [ <docker> | <cri> | <regex> | <json> | <template> | <match> | <timestamp> | <output> | <labels> | <metrics> | <tenant> | <replace> ]dockerDocker阶段解析Docker容器中的日志内容,并通过名称定义为一个空对象:docker: {}docker阶段将匹配并解析以下格式的日志行:`{"log":"level=info ts=2019-04-30T02:12:41.844179Z caller=filetargetmanager.go:180 msg=\"Adding target\"\n","stream":"stderr","time":"2019-04-30T02:12:41.8443515Z"}`自动将time提取到日志时间戳中,stream提取到标签中,并将log字段提取到输出中,这非常有帮助,因为docker以这种方式包装应用程序日志,以便仅对日志内容进行进一步的管道处理。Docker 阶段只是一个便捷的包装器定义:- json: expressions: output: log stream: stream timestamp: time - labels: stream: - timestamp: source: timestamp format: RFC3339Nano - output: source: outputCRI解析来自CRI容器的日志,使用空对象按名称定义:cri: {}CRI阶段将匹配和格式化以下日志:2019-01-01T01:00:00.000000001Z stderr P some log messagetime 提取为日志时间戳,stream提取为标签,将剩余的消息打包到消息中CRI阶段包装器定义:- regex: expression: "^(?s)(?P<time>\\S+?) (?P<stream>stdout|stderr) (?P<flags>\\S+?) (?P<content>.*)$" - labels: stream: - timestamp: source: time format: RFC3339Nano - output: source: contentregex采用正则表达式提取日志regex: # The RE2 regular expression. Each capture group must be named. # RE2 正则表达式,必须命名每个拉取组 expression: <string> # Name from extracted data to parse. If empty, uses the log message. # 要分析的提取数据的名称。如果为空,则使用日志消息 [source: <string>]json将日志解析为JSON,并使用JMESPath从JSON中提取数据json: # Set of key/value pairs of JMESPath expressions. The key will be # the key in the extracted data while the expression will be the value, # evaluated as a JMESPath from the source data. # 做为JMESPath表达式的 key/valu键值,key是提取数据中的键,value是表达式 expressions: [ <string>: <string> ... ] # Name from extracted data to parse. If empty, uses the log message. # 从提取的数据中解析的名称。如果为空,则使用日志消息。 [source: <string>]template使用GO的 text/template语言操作值template: # Name from extracted data to parse. If key in extract data doesn't exist, an # entry for it will be created. # 分析数据的名称,如果key在数据中不存在,将创建一个条目 source: <string> # Go template string to use. In additional to normal template # functions, ToLower, ToUpper, Replace, Trim, TrimLeft, TrimRight, # TrimPrefix, TrimSuffix, and TrimSpace are available as functions. # 要使用的Go模板字符串。 template: <string>比如:template: source: level template: '{{ if eq .Value "WARN" }}{{ Replace .Value "WARN" "OK" -1 }}{{ else }}{{ .Value }}{{ end }}'match当日志条目与可配置的LogQL流选择器匹配时,匹配阶段有条件地执行一组阶段match: # LogQL stream selector. selector: <string> # Names the pipeline. When defined, creates an additional label in # the pipeline_duration_seconds histogram, where the value is # concatenated with job_name using an underscore. #命名管道。定义后,在pipeline_duration_seconds直方图中创建一个附加标签,其中该值使用下划线与job_name连接。 [pipeline_name: <string>] # Nested set of pipeline stages only if the selector # matches the labels of the log entries: # 仅当选择器与日志条目标签匹配时,才使用的管道阶段的嵌套集合 stages: - [ <docker> | <cri> | <regex> <json> | <template> | <match> | <timestamp> | <output> | <labels> | <metrics> ]timestamptimestamp阶段从提取的地图中解析数据,并覆盖Loki存储的日志的最终时间值。如果不存在此阶段,Promtail将把日志条目的时间戳与读取该日志条目的时刻相关联。timestamp: # Name from extracted data to use for the timestamp. source: <string> # Determines how to parse the time string. Can use # pre-defined formats by name: [ANSIC UnixDate RubyDate RFC822 # RFC822Z RFC850 RFC1123 RFC1123Z RFC3339 RFC3339Nano Unix # UnixMs UnixUs UnixNs]. format: <string> # IANA Timezone Database string. [location: <string>]outputoutput阶段从提取的映射中获取数据,并设置将由Loki存储的日志条目的内容。output: # Name from extracted data to use for the log entry. source: <string>labelslabels阶段从提取的map中获取数据,并在将发送给Loki的日志条目上设置其他标签。labels: # Key is REQUIRED and the name for the label that will be created. # Value is optional and will be the name from extracted data whose value # will be used for the value of the label. If empty, the value will be # inferred to be the same as the key. [ <string>: [<string>] ... ]metrics允许从提取的数据中定义度量# A map where the key is the name of the metric and the value is a specific # metric type. metrics: [<string>: [ <counter> | <gauge> | <histogram> ] ...] counter定义一个计数器指标,其值只会增加# The metric type. Must be Counter. type: Counter # Describes the metric. [description: <string>] # Key from the extracted data map to use for the metric, # defaulting to the metric's name if not present. [source: <string>] config: # Filters down source data and only changes the metric # if the targeted value exactly matches the provided string. # If not present, all data will match. [value: <string>] # Must be either "inc" or "add" (case insensitive). If # inc is chosen, the metric value will increase by 1 for each # log line received that passed the filter. If add is chosen, # the extracted value must be convertible to a positive float # and its value will be added to the metric. action: <string> gauge定义一个gauge度量,值可上下# The metric type. Must be Gauge. type: Gauge # Describes the metric. [description: <string>] # Key from the extracted data map to use for the metric, # defaulting to the metric's name if not present. [source: <string>] config: # Filters down source data and only changes the metric # if the targeted value exactly matches the provided string. # If not present, all data will match. [value: <string>] # Must be either "set", "inc", "dec"," add", or "sub". If # add, set, or sub is chosen, the extracted value must be # convertible to a positive float. inc and dec will increment # or decrement the metric's value by 1 respectively. action: <string> histogram定义一个histogram度量,值为条形# The metric type. Must be Histogram. type: Histogram # Describes the metric. [description: <string>] # Key from the extracted data map to use for the metric, # defaulting to the metric's name if not present. [source: <string>] config: # Filters down source data and only changes the metric # if the targeted value exactly matches the provided string. # If not present, all data will match. [value: <string>] # Must be either "inc" or "add" (case insensitive). If # inc is chosen, the metric value will increase by 1 for each # log line received that passed the filter. If add is chosen, # the extracted value must be convertible to a positive float # and its value will be added to the metric. action: <string> # Holds all the numbers in which to bucket the metric. buckets: - <int> tenanttenant阶段是一个操作阶段,它为从提取的数据映射中的字段中提取的日志条目设置租户IDtenant: # Either label, source or value config option is required, but not all (they # are mutually exclusive). # Name from labels to whose value should be set as tenant ID. [ label: <string> ] # Name from extracted data to whose value should be set as tenant ID. [ source: <string> ] # Value to use to set the tenant ID when this stage is executed. Useful # when this stage is included within a conditional pipeline with "match". [ value: <string> ]replacereplace阶段是使用正则表达式解析日志行并替换日志行的解析阶段。replace: # The RE2 regular expression. Each named capture group will be added to extracted. # Each capture group and named capture group will be replaced with the value given in # `replace` expression: <string> # Name from extracted data to parse. If empty, uses the log message. # The replaced value will be assigned back to soure key [source: <string>] # Value to which the captured group will be replaced. The captured group or the named # captured group will be replaced with this value and the log line will be replaced with # new replaced values. An empty value will remove the captured group from the log line. [replace: <string>]static_configsstatic_configs允许指定目标列表和为它们设置的公共标签。这是在scrape配置中指定静态目标的规范方法。# Configures the discovery to look on the current machine. # This is required by the prometheus service discovery code but doesn't # really apply to Promtail which can ONLY look at files on the local machine # As such it should only have the value of localhost, OR it can be excluded # entirely and a default value of localhost will be applied by Promtail. # 当前机器上发现配置。 #这是prometheus服务发现代码所要求的,但并不真正适用于Promtail,因为Promtail只能查看本地计算机上的文件。 # 因此,它应该只有localhost值,或者可以完全排除它,Promtail将应用默认值localhost targets: - localhost # Defines a file to scrape and an optional set of additional labels to apply to # all streams defined by the files from __path__. #定义一个要刮取的文件和一组可选的附加标签,以应用于__path__中的文件定义的所有流。 labels: # The path to load logs from. Can use glob patterns (e.g., /var/log/*.log). # 加载的日志路径 __path__: <string> # Used to exclude files from being loaded. Can also use glob patterns. # 从加载中排除文件 __path_exclude__: <string> # Additional labels to assign to the logs # 要分配的其他标签 [ <labelname>: <labelvalue> ... ]kubernetes_sd_configKubernetes SD configurations allow retrieving scrape targets from Kubernetes’ REST API and always staying synchronized with the cluster state.Kubernetes SD配置允许从Kubernetes REST API 中检索目标,以便与集群保持同步One of the following role types can be configured to discover targets:以下role 类型之一可以配置为发现目标nodeThe node role discovers one target per cluster node with the address defaulting to the Kubelet’s HTTP port.node 角色将发现的每个集群节点做为一个目标,地址默认为Kubelete的HTTP端口The target address defaults to the first existing address of the Kubernetes node object in the address type order of NodeInternalIP, NodeExternalIP, NodeLegacyHostIP, and NodeHostName.目标地址默认为Kubernetes节点对象的第一个现有地址,地址类型顺序为NodeInternalIP、NodeExternalIP、NodeLegacyHostIP和NodeHostNameAvailable meta labels:__meta_kubernetes_node_name: The name of the node object._meta_kubernetes_node_label: Each label from the node object._meta_kubernetes_node_labelpresent: true for each label from the node object._meta_kubernetes_node_annotation: Each annotation from the node object._meta_kubernetes_node_annotationpresent: true for each annotation from the node object._meta_kubernetes_node_address<address_type>: The first address for each node address type, if it exists.In addition, the instance label for the node will be set to the node name as retrieved from the API server.可用元标签:__meta_kubernetes_node_name: 节点对象的名称_meta_kubernetes_node_label: 节点对象的每个标签_meta_kubernetes_node_labelpresent: 对于节点对象中的每个标签为true_meta_kubernetes_node_annotation: 节点对象中的每个注释_meta_kubernetes_node_annotationpresent: 对于节点对象的每个注释为true。_meta_kubernetes_node_address<address_type>: 每个节点地址类型的第一个地址(如果存在)此外,节点的实例标签将设置为从API服务器检索到的节点名称。serviceThe service role discovers a target for each service port of each service. This is generally useful for blackbox monitoring of a service. The address will be set to the Kubernetes DNS name of the service and respective service port.service 角色将每个发现的服务做为一个目标。这对于服务的黑盒监视通常很有用。地址将设置为服务的Kubernetes DNS名称和相应的服务端口Available meta labels:__meta_kubernetes_namespace: The namespace of the service object._meta_kubernetes_service_annotation: Each annotation from the service object._meta_kubernetes_service_annotationpresent: “true” for each annotation of the service object.__meta_kubernetes_service_cluster_ip: The cluster IP address of the service. (Does not apply to services of type ExternalName)__meta_kubernetes_service_external_name: The DNS name of the service. (Applies to services of type ExternalName)_meta_kubernetes_service_label: Each label from the service object._meta_kubernetes_service_labelpresent: true for each label of the service object.__meta_kubernetes_service_name: The name of the service object.__meta_kubernetes_service_port_name: Name of the service port for the target.__meta_kubernetes_service_port_protocol: Protocol of the service port for the target.podThe pod role discovers all pods and exposes their containers as targets. For each declared port of a container, a single target is generated. If a container has no specified ports, a port-free target per container is created for manually adding a port via relabeling.pod 角色将发现的所有pod 做为目标。每个容器的端口将生成一个目标,如果容器没有指定的端口,则会为每个容器创建一个无端口目标,用于通过重新标记手动添加端口。Available meta labels:__meta_kubernetes_namespace: The namespace of the pod object.__meta_kubernetes_pod_name: The name of the pod object.__meta_kubernetes_pod_ip: The pod IP of the pod object._meta_kubernetes_pod_label: Each label from the pod object._meta_kubernetes_pod_labelpresent: truefor each label from the pod object._meta_kubernetes_pod_annotation: Each annotation from the pod object._meta_kubernetes_pod_annotationpresent: true for each annotation from the pod object.__meta_kubernetes_pod_container_init: true if the container is an InitContainer__meta_kubernetes_pod_container_name: Name of the container the target address points to.__meta_kubernetes_pod_container_port_name: Name of the container port.__meta_kubernetes_pod_container_port_number: Number of the container port.__meta_kubernetes_pod_container_port_protocol: Protocol of the container port.__meta_kubernetes_pod_ready: Set to true or false for the pod’s ready state.__meta_kubernetes_pod_phase: Set to Pending, Running, Succeeded, Failed or Unknown in the lifecycle.__meta_kubernetes_pod_node_name: The name of the node the pod is scheduled onto.__meta_kubernetes_pod_host_ip: The current host IP of the pod object.__meta_kubernetes_pod_uid: The UID of the pod object.__meta_kubernetes_pod_controller_kind: Object kind of the pod controller.__meta_kubernetes_pod_controller_name: Name of the pod controller.endpointsThe endpoints role discovers targets from listed endpoints of a service. For each endpoint address one target is discovered per port. If the endpoint is backed by a pod, all additional container ports of the pod, not bound to an endpoint port, are discovered as targets as well.Available meta labels:__meta_kubernetes_namespace: The namespace of the endpoints object.__meta_kubernetes_endpoints_name: The names of the endpoints object.For all targets discovered directly from the endpoints list (those not additionally inferred from underlying pods), the following labels are attached:If the endpoints belong to a service, all labels of the role: service discovery are attached.For all targets backed by a pod, all labels of the role: pod discovery are attached.ingressThe ingress role discovers a target for each path of each ingress. This is generally useful for blackbox monitoring of an ingress. The address will be set to the host specified in the ingress spec.Available meta labels:__meta_kubernetes_namespace: The namespace of the ingress object.__meta_kubernetes_ingress_name: The name of the ingress object._meta_kubernetes_ingress_label: Each label from the ingress object._meta_kubernetes_ingress_labelpresent: true for each label from the ingress object._meta_kubernetes_ingress_annotation: Each annotation from the ingress object._meta_kubernetes_ingress_annotationpresent: true for each annotation from the ingress object.__meta_kubernetes_ingress_scheme: Protocol scheme of ingress, https if TLS config is set. Defaults to http.__meta_kubernetes_ingress_path: Path from ingress spec. Defaults to /.See below for the configuration options for Kubernetes discovery:# The information to access the Kubernetes API. # The API server addresses. If left empty, Prometheus is assumed to run inside # of the cluster and will discover API servers automatically and use the pod's # CA certificate and bearer token file at /var/run/secrets/kubernetes.io/serviceaccount/. # API服务器地址。如果为空,Prometheus在集群内部运行 # 将自动发现API服务并使用在/var/run/secrets/kubernetes.io/serviceaccount/.路径下的CA证书和 bearer token文件 [ api_server: <host> ] # The Kubernetes role of entities that should be discovered. # Kubernetes中应该被发现的实体角色 role: <role> # Optional authentication information used to authenticate to the API server. # Note that `basic_auth`, `bearer_token` and `bearer_token_file` options are # mutually exclusive. # password and password_file are mutually exclusive. # Optional HTTP basic authentication information. basic_auth: [ username: <string> ] [ password: <secret> ] [ password_file: <string> ] # Optional bearer token authentication information. [ bearer_token: <secret> ] # Optional bearer token file authentication information. [ bearer_token_file: <filename> ] # Optional proxy URL. [ proxy_url: <string> ] # TLS configuration. tls_config: [ <tls_config> ] # Optional namespace discovery. If omitted, all namespaces are used. # 可选的,发现命名空间,省略使用所有的命名空间 namespaces: names: [ - <string> ] # Optional label and field selectors to limit the discovery process to a subset of available # resources. # 可选的标签和字段选择器,用于将发现过程限制为可用资源的子集 #See # https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/ # and https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ to learn # more about the possible filters that can be used. The endpoints role supports pod, # service, and endpoint selectors. Roles only support selectors matching the role itself; # for example, the node role can only contain node selectors. # Note: When making decisions about using field/label selectors, make sure that this # is the best approach. It will prevent Promtail from reusing single list/watch # for all scrape configurations. This might result in a bigger load on the Kubernetes API, # because for each selector combination, there will be additional LIST/WATCH. # On the other hand, if you want to monitor a small subset of pods of a large cluster, # we recommend using selectors. The decision on the use of selectors or not depends # on the particular situation. [ selectors: [ - role: <string> [ label: <string> ] [ field: <string> ] ]]Where must be endpoints, service, pod, node, or ingress.See this example Prometheus configuration file for a detailed example of configuring Prometheus for Kubernetes.You may wish to check out the 3rd party Prometheus Operator, which automates the Prometheus setup on top of Kubernetes.包含endpoints, service, pod, node, 或 ingress有关为Kubernetes配置Prometheus的详细示例,请参阅此示例Prometheus配置文件。其他第三方的 Prometheus Operator,能够自动配置Kubernetes上的Prometheus
0
0
0
浏览量331
武士先生

都2021年了,不会还有人不知道防抖吧?

前言在我们写项目遇到不听话的用户的时候,例如一个登录按钮,他就偏偏要反复点好几次,调用了/login接口好多次,是不是很烦?因为涉及到ajax请求,就会有这样的情况,假设 1 秒触发了 60 次,每个回调就必须在 1000 / 60 = 16.67ms 内完成,否则就会有卡顿出现。那么只有请求需要写防抖吗?当然不是,凡是(可能)涉及到频繁事件触发的地方,都需要写防抖。window 的 resize、scroll mousedown、mousemove keyup、keydown click事件 ……我们简单的写一个小demo,看一下没有防抖跟有防抖的效果。代码:// html <button id="btn" onclick="shake()"> <span id="container">点击我</span> </button> // js var count = 1; var container = document.getElementById('container'); var btn = document.getElementById('btn') function shake() { container.innerHTML = count++; };如何实现防抖实现防抖,就要了解防抖的原理。防抖就是,不管你触发了几次,我只看你最后触发的那一次,并且在若干时间后去执行此次事件。根据这个原理,我们可以写出这段代码:function preventShake(todo,time){ let timeout; return function () { clearTimeout(timeout) timeout = setTimeout(todo, time); } }设置一个延迟操作的事件,并且如果再次触发就把之前的延迟取消掉,重新进入计时。将它运用在刚刚的例子上:var count = 1; var container = document.getElementById('container'); var btn = document.getElementById('btn') function shake() { container.innerHTML = count++; }; function preventShake(todo,time){ var timeout; return function () { clearTimeout(timeout); timeout = setTimeout(todo, time); } } btn.onclick = preventShake(shake,1000);我们已经实现了基础的它,那么我们继续优化一下吧!this指向优化看似上面的结果没有什么太大问题,但是我们打印一下原先的shake和使用了preventShake后的this就会知道,他们指向的并不是一个东西。原先指向的是<button id="btn"> <span id="container">点击我</span> </button>使用了preventShake后this指向的是Window对象!于是我们要加一步,就是改变this指向。欸,这个是不是又是一篇文章??function preventShake(todo,time){ var timeout; return function () { var that = this; clearTimeout(timeout); timeout = setTimeout(function(){ todo.apply(that); }, time); } }这样就解决了this指向可能带来的问题。最后至此,这个简易的防抖函数就写完了,可能我想的还不太完善,希望大家给予建议,我也会及时学习,之后将其完善,争取做的更好。感兴趣的朋友可以留个赞,我们一同进步!
0
0
0
浏览量797
武士先生

网络安全 | 了解XSS的基本知识

前言今天到了分享我最喜欢的东西,一些网络安全有关的东西。小时候就特别喜欢黑客,觉得特帅~ 大学报计算机相关专业可能也是这方面的原因,但是阴差阳错,没有走网络安全的方向,但是不影响我们学习了解一些网络安全方面的知识。今天来说一说,XSS攻击的事情。阿泽有幸,之前用php搭建的博客(typecho),被我同学挂了脚本,xss了一下,完后,什么什么的,啊,你们懂得。都没了从那以后,我就知道,网络安全,是不可避免的,是一定要考虑到的,是一定要去了解的东西。身处网络时代,谁又能置身事外!什么是xss攻击?XSS又叫CSS(Cross Site Script),跨站脚本攻击:指恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。那么,当当前的网页弹出1的时候,我们就知道,这个网页是可以执行js脚本的了,那么我们是不是就可以通过document去盗取一些信息,来帮助我们更进一步的去潜入进去呢?例如盗取一下cookie等。先来看一下XSS有几种吧:持久型:一些可以提交的地方,文章评论,个人信息填写等,如果没有加过滤的话,嵌入的脚本就会被提交到服务器上,之后用户每次访问都会触发脚本(例如图中的alert(1),每个用户打开都会弹出1)非持久型:反射型跨站脚本漏洞,是最普遍的类型。大多是链接的方式,需要用户点击,才能返回脚本进行操作(用户访问服务器-->点击跨站链接--->返回脚本代码)DOM型:客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM XSS漏洞(很少见emm,反正我没见过hhh,日后见到了来补上,再细说)XSS可能会带来的危害使网页无法正常运行:这个就是我们上面中图示的内容,如果不仅仅是一个alert(1)呢?外部来一个永久循环,这个网页的弹窗就会永远关不掉,导致无法正常的实现整体业务流程。获取cookie信息:这个我们也在上文中说过,如果网页上可以执行js脚本的话,那么我们是可以通过document.cookie来实现获取用户cookie的。试想下如果像QQ空间说说中能够写入xss攻击语句,那岂不是看了你说说的人的号你都可以登录 一个字:爽~劫持流量恶意跳转:像这样,在网页中想办法插入一句像这样的语句,访问的网页就会直接跳转到百度去。<script>window.location.href="http://www.baidu.com";</script>XSS的攻击应该具备这样的条件网页内部有输入框,内容可存储在服务器上输入框内的内容,才能被提交到他的服务器上,才能改变这个网页内部的文件内容。前提是这个输入,没有被过滤,才会成功哦! 可以自己写demo尝试一下,不建议直接在网络上开搞emm,自己玩玩就行哈~XSS防御措施(对用户输入内容和服务端返回内容进行过滤和转译)现代大部分浏览器都自带 XSS 筛选器(vue/react等主流框架已经避免类似问题,vue举例:不能在template中写script标签,无法在js中通过ref或append等方式动态改变或添加script标签)过滤:对诸如script、img、a等标签进行过滤。编码:像一些常见的符号,如<>在输入的时候要对其进行转换编码,这样做浏览器是不会对该标签进行解释执行的,同时也不影响显示效果。最后学习网络安全是为了保护好自己,在这个网络时代,避免自己的信息被不法分子盗取,而不是说我们学会如何去攻击别人。
0
0
0
浏览量270

履历