Recent Questions - Stack Overflow Posted: 15 Mar 2022 04:24 AM PDT Need insight on how to replicate design in CSS Posted: 15 Mar 2022 04:22 AM PDT I need some advice/pointers on how to approach this design in CSS : https://imgur.com/a/eFzyAPC I'm trying to learn CSS, but I am a bit overwhelmed by the complexity of this design. Any pointers would be appreciated. This is what I've tried: https://jsfiddle.net/4fs91j8t/23/ , however as you can see I can't seem to get the spacing right for each tile, as it overflows into the next. HTML: <div class="card-labels"> <div class="label-title"> Product details </div> <div class="label-title"> Category </div > <div class="label-title"> Quantity </div> <div class="label-title"> Price </div> <div class="label-title"> Delivery </div> <div class="label-title"> Actions </div> </div> <div class="card-product"> <div class="product-details"> <img src="https://images.pexels.com/photos/10164553/pexels-photo-10164553.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260" class="product-image"> <div class="product-description"> <div class="product-title"> Sample product </div> <div class="product-seller"> Seller </div> </div> </div> </div> CSS: body{ background-color: #F3F6FA; } .card-labels { width: 80%; margin: auto; display: flex; flex-direction: row; justify-content: space-between; } .card-product { width: 80%; height: 160px; margin: auto; background-color: #FFFFFF; border-radius: 35px; } .product-details { width: 400px; display: flex; flex-direction: row; justify-content: start; align-items: center; } .product-image{ width: 120px; height: 120px; margin: 20px; } | Javascript Choices are not printing Posted: 15 Mar 2022 04:22 AM PDT I did console.log(questionEl) and it is printing well as I coded (currentQuiozData.question) but the choices are not working. They don't bring currentQuizData.a so printing only Question. const quiz = document.getElementById("quiz"); const answerEls = document.querySelectorAll(".answer"); const questionEl = document.getElementById("question"); const a_text = document.getElementById("a_text"); const b_text = document.getElementById("b_text"); const c_text = document.getElementById("c_text"); const d_text = document.getElementById("d_text"); const submitBtn = document.getElementById("submit"); let currentQuiz = 0; let answer = undefined; loadQuiz(); function loadQuiz() { deselectAnswers(); const currentQuizData = quizData [currentQuiz]; questionEl.innerText = currentQuizData.question; a_text.ineerText = currentQuizData.a; b_text.ineerText = currentQuizData.b; c_text.ineerText = currentQuizData.c; d_text.ineerText = currentQuizData.d; }; <div class="quiz-header"> <h2 id="question">Question text</h2> <ul> <li> <input type="radio" id="a" name="answer" class="answer" /> <label id="a_text" for="a">Question</label> </li> <li> <input type="radio" id="b" name="answer" class="answer" /> <label id="b_text" for="b">Question</label> </li> <li> <input type="radio" id="c" name="answer" class="answer" /> <label id="c_text" for="c">Question</label> </li> <li> <input type="radio" id="d" name="answer" class="answer" /> <label id="d_text" for="d">Question</label> </li> </ul> </div> | Regex to capture portion of filename Posted: 15 Mar 2022 04:22 AM PDT I have the below regex statement within my python script that I use to help build a SQL BULK INSERT statement to populate several tables in a SQL DB via CSVs. for filename in gl.glob("(Compressed)_*.csv"): filekey = re.match(r"^\(Compressed\)_([A-Z0-9-]+_[0-9A-z]+)_[0-9]{8}_[0-9]{6}.csv$", filename).group(1) The filenames do vary although they follow a similar pattern where they begin with the string "(Compressed)_" followed by an alphanumeric "identifier", and then a date "15032022" and time "100028" string. Examples of the filenames are: - (Compressed)_A123_10_15032022_100028.csv
- (Compressed)_T985_4160_15032022_100028.csv
- (Compressed)_GDAF_Fault_MSG_15032022_100028.csv
- (Compressed)_T600_INC_Header_15032022_100028.csv
The sections highlighted in bold are the strings that I'd extract. So far this works for all the examples shown above. However I've come across a different filename structure that isn't captured. New Filename: - (Compressed)_GXXY_20_TIC008416_FTR_01_02_20_to_19_02_20_11032022_153430.csv
Again, I only want to extract the bold part of the above CSV as this would be my identifier. How can I amend my regex statement to capture this new format while retaining the capture of the original filenames too? | how to render html template in javascript from flask Posted: 15 Mar 2022 04:22 AM PDT and my script is : const starting = 3; let time = starting; countdownEl = document.getElementById('countdown'); setInterval(updateCountdown,1000); function updateCountdown(){ let seconds = time; countdownEl.innerHTML = ${seconds} ; if (time > 0) { time--; } if (time == 0) { window.location="{{ url_for('disinfect') }}"; } } | Getting System.ArgumentOutOfRangeException: Length cannot be less than zero Posted: 15 Mar 2022 04:22 AM PDT System.ArgumentOutOfRangeException: Length cannot be less than zero. Parameter name: length at System.String.Substring(Int32 startIndex, Int32 length) Getting above exception from below code. I am already checking for -1. int tableMasterIndex = tableMaster.IndexOf(':'); if (tableMasterIndex == -1) { continue; } int tableMasterId = int.Parse(tableMaster.Substring(0, tableMasterIndex)); string[] codeValues = tableMaster.Substring(tableMasterIndex + 1).Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); | clang-format file not working in Visual Studio code Posted: 15 Mar 2022 04:21 AM PDT I have put a .clang-format file in my workspace. But that formating is not applied on my code. I have checked the settings and they are set to None for fallback-style and file for clang_format_style. The good news is that the fallback Style doesn't work anymore. Which means when I try to Format the Document or Selection nothing happens. Please help. Thanks | Hibernate query hangs on one machine only Posted: 15 Mar 2022 04:21 AM PDT I have a Java application deployed on Glassfish server. During the move from one Windows 7 to another PC with Windows 10 I have encountered a very strange problem. As I launch local dev environment, connecting to the same database, with the same versions of war artifacts, on the new machine, everything seems to be working fine, but when I try to access one function, it just freezes. No error messages, the http request is never answered. I connected to the debug port on Glassfish and tracked down exactly the line that never executes. It's the hibernate Query object .list() method. It's supposed to execute a SELECT. I tried deploying artefacts, tried different versions, confirmed that they ARE being deployed, added logging to make sure that the code stops where I think it does, no change in the behavior. The same artifact, copied to my old machine works without a problem. The difference between environments, aside from OS, is the fact that new machine is connecting via VPN, while the old one is on the local network, however I don't think this could matter, as obviously the connection to DB is established. Environment: - Glassfish 2.1.1
- Java 1.8
- Hibernate version: 5.4.21
- DB: JNDI Oracle
- JDBC version: 19.3.0.0
| Making a program to visualize sensor data Posted: 15 Mar 2022 04:21 AM PDT I have a task to make a program capable of visualizing data coming from a variety of sensors through the serial port in a real-time fashion. The measurements can last up to a month, the horizontal and vertical axes should be scalable mid-run and there should also be a possibility of writing information to a csv-file mid-run. I tried using Streamlit for this but it doesn't really support some of the features that i've specified here. Any ideas as to what could get the job done in this case? Tkinter sounds like a possibility. Thank you. | How To Play Music From Any Music Platform like Spotify, amazon music, etc. Using Python Posted: 15 Mar 2022 04:21 AM PDT I am new to python programming so forgive me if this question is too basic. I am trying to play music from a music platform like Spotify using python. I was able to play songs using python, but I had to download the mp3 of that song and then play it. There are a lot of songs I like & I can't keep downloading all of them, so I thought that I can play music from any other music platform like Spotify, amazon music, etc. I tried with the 'spotipy' module but it didn't work. Any help would be appreciated. | Creating tabs with html and javascript Posted: 15 Mar 2022 04:22 AM PDT I'm new in these things so this might be an easy one. I want to create tabs with the following code from w3schools. The original one is here: https://www.w3schools.com/howto/howto_js_tabs.asp
I don't want images to be in the tabcontent section. So I made a new class named tabimage and added it to the javascript. Also I've added new events with the image's ids at the html. But it keeps showing other tabs when clicked. Can anyone help? function openCity(evt, cityName) { var i, tabcontent, tablinks; tabcontent = document.getElementsByClassName("tabcontent tabimage"); for (i = 0; i < tabcontent.length; i++) { tabcontent[i].style.display = "none"; } tablinks = document.getElementsByClassName("tablinks"); for (i = 0; i < tablinks.length; i++) { tablinks[i].className = tablinks[i].className.replace("active", ""); } document.getElementById(cityName).style.display = "block"; evt.currentTarget.className += "active"; } body {font-family: Open Sans;} .order { display: grid; /* grid-template-columns: 1fr 1fr;*/ } /* Style the tab */ .tab { overflow: hidden; border: 1px solid #ccc; background-color: #f1f1f1; } /* Style the buttons inside the tab */ .tab button { background-color: inherit; float: left; border: none; outline: none; cursor: pointer; padding: 14px 16px; transition: 0.3s; font-size: 17px; } /* Change background color of buttons on hover */ .tab button:hover { background-color: #ddd; } /* Create an active/current tablink class */ .tab button.active { background-color: #ccc; } /* Style the tab content */ .tabcontent { display: none; padding: 6px 12px; border: 1px solid #ccc; border-top: none; } .tabimage { display: none; padding: 6px 12px; border: 1px solid #ccc; border-top: none; } <div class="order"> <div class="tab"> <button class="tablinks" onclick="openCity(event, 'London'); openCity(event, 'London1')">London</button> <button class="tablinks" onclick="openCity(event, 'Paris'); openCity(event, 'Paris1')">Paris</button> <button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button> </div> <div id="London" class="tabcontent"> <h3>London</h3> <p>London is the capital city of England.</p> </div> <div id="Paris" class="tabcontent"> <h3>Paris</h3> <p>Paris is the capital of France.</p> </div> <div id="Tokyo" class="tabcontent"> <h3>Tokyo</h3> <p>Tokyo is the capital of Japan.</p> </div> <img src="https://cdn.shopify.com/s/files/1/0044/8718/4457/files/800x600_Wallpaper_Blue_Sky.png?v=1646897329" class="tabimage" id="London1"> <img src="https://cdn.shopify.com/s/files/1/0044/8718/4457/files/Auckland_Skyline_800x600_80c6b2c0-28bc-4885-8440-a6da7de39c82.jpg?v=1646897329" class="tabimage" id="Paris1"> </div> | Show api data in JSX, React Posted: 15 Mar 2022 04:22 AM PDT trying to load the props data into the jsx, when I try to iterate it inside the return, getting an error of .map not defined. const SavedListTable = (props) => { console.log(props); const {listData, setListData} = props; if(setListData.length > 0) { return( setListData.map((setListData, index) => { console.log(setListData) return( <div> <tr> <td >{setListData.name}</td> <td >Project</td> <td>00 May 2022</td> </tr> </div> ) }) ) } this is the response from for console.log(props) {notes: Array(8)} notes: (8) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] [[Prototype]]: Object | Simulator overload in Xcode 13.3 (13E113) Posted: 15 Mar 2022 04:21 AM PDT When launching Xcode today, I was prompted that additional components needed to be installed. Had no other choice but to accept and man did I get more than I bargained for! The list goes on an on, next is the iPad Air (5th generation) with just as many and so on. So far I have relaunched Xcode and the simulator, but to no avail. Any one else experiencing this? | Confusion matrix for FASTER R-CNN Posted: 15 Mar 2022 04:21 AM PDT I have trained my faster r-cnn model but i need precision, recall value. I have searched on google and I have found something about it. I have found this link(https://github.com/svpino/tf_object_detection_cm ) and I have implemented it like enter image description here Step1 : python infer_detections.py --input_tfrecord_paths=C:/tensorflow1msff/models/research/object_detection/test.record --output_tfrecord_path=C:/tensorflow1msff/models/research/object_detection/inference_graph/detection_tfrecord --inference_graph=C:/tensorflow1msff/models/research/object_detection/inference_graph/frozen_inference_graph.pb but Step 2 : I have failed enter image description here python confusion_matrix.py--detections_record=C:/tensorflow1msff/models/research/object_detection/inference_graph/detection_tfrecord.record--label_map=C:/tensorflow1msff/models/research/object_detection/training/labelmap.pbtxt-- output_path=C:/tensorflow1msff/models/research/object_detection/confusion_matrix.csv https://github.com/svpino/tf_object_detection_cm/issues/12 As you can see Picture 2 : There is a syntax error. And Can you help me how can I fix that ? File "confusion_matrix.py", line 159 for record in tf.python_io.tf_record_iterator(<tfrecord_file>): ^ SyntaxError: invalid syntax | How to understand sched_period in the cfs schedule class? Posted: 15 Mar 2022 04:22 AM PDT As far as I understand, __sched_period is the time incurred in making the task runnable again. When the processes are less than sched_nr_latency, the CFS scheduler splits time into periods in which each process is anticipated to run once. u64 __sched_period(unsigned long nr_running) { if (unlikely(nr_running > sched_nr_latency)) return nr_running * sysctl_sched_min_granularity; else return sysctl_sched_latency; } Does each process have a maximum of 'sysctl_sched_latency/2' time to run or do we have to perform 'sysctl_sched_latency/2' for each process if we have two processes with the same weight? What about context switching time, scheduler cost calculation, and other factors? What does the sched_period consist of? | Makefile only recompile changed files Posted: 15 Mar 2022 04:21 AM PDT I am trying to write a makefile but it does not do exactly what I want. I have the following files: a.h, b.h, c.h, d.h, main.h a_functions.c, b_functions.c, c_functions.c, d_functions.c, main.c main.c includes main.h And every x_functions.c includes x.h I want make to recompile only the files that have been edited. This is how my code looks like now: CC = gcc CFLAG = -Wall -Werror OBJ = program1 CPPFLAGS += -MD -MP SRC = $(wildcard *.c) all: $(SRC:%.c=%.o) $(CC) $(SRC) -o $(OBJ) -include $(SRC:%.c=%.d) What do I need to change to make it work? I have tried to find an answer on google but I can't find something that works (or something that I understand how to use) | Need guidance in using ORDER BY when using LISTAGG Posted: 15 Mar 2022 04:22 AM PDT SELECT B.ID, LTRIM(LISTAGG(ITEM_ID,';')WITHIN GROUP(ORDER BY B.PG_NO), '0') as PROCESS_ID FROM table1 A JOIN table2 B ON A.CAS_ID=B.CAS_ID WHERE B.DATE = '2022-03-03' AND B.ID IS NOT NULL AND P_CD NOT IN ('1','2','5','7') AND A.ID IN(12690222,24515955) GROUP BY B.ID When I run the above query, I'm getting results as mentioned below: ID PROCESS_ID 12690222 5544973696;5544973696;5544973696;5544973696 24515955 777239598;777239598;777239598 I want to display only the distinct values in PROCESS_ID column and the results should be ordered by PG_NO column. Kindly guide me how to achieve this. | How would you do that opening effect with React Native? Posted: 15 Mar 2022 04:21 AM PDT Imagine a feed of items and then you click on one item and it opens in this way. We need to create a news app that has this identical animation (check sec 00:13 of video linked). enter link description here | Use return data from one function in another and pass it to Smarty template Posted: 15 Mar 2022 04:21 AM PDT What I'm trying to do is to have one datatable probably as a template and passing different datas on different pages. Example functions.php function force() { global $DBconn; $q = "SELECT * FROM table"; $res = $DBconn->query($q); return $this->result; } function force_1() { global $DBconn; $q = "SELECT * FROM table_1"; $res = $DBconn->query($q); return $this->result; } function table() { echo ' <table class="sortable"> <thead> <tr> <th> Title </th> </tr> </thead> <tbody>'; foreach ($this->result as $key) { echo ' <tr> <td> $key->name </td> </tr>'; } echo '</tbody> </table>'; } return true; } Then in the page.php $table = table(); $smarty->assign("table", $table); and page.tpl <div> {$table} </div> Currently nothing showed on page, not even an error or something. So: - Is this the way that should be done? With using the returned result in either function and then pass it to the smarty template?
| Maven -- Why are dependencies versions not overwritten? Posted: 15 Mar 2022 04:22 AM PDT My project depends on spring-boot-starter-data-mongodb and I just want to upgrade mongodb driver's version. Here is my pom: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.9</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-sync</artifactId> <version>4.5.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> </dependencies> </project> Here is final dependency tree: [INFO] --- maven-dependency-plugin:3.1.2:tree (default-cli) @ demo --- [INFO] com.demo:demo:jar:0.0.1-SNAPSHOT [INFO] +- org.mongodb:mongodb-driver-sync:jar:4.5.0:compile [INFO] | +- org.mongodb:bson:jar:4.2.3:compile [INFO] | \- org.mongodb:mongodb-driver-core:jar:4.2.3:compile [INFO] \- org.springframework.boot:spring-boot-starter-data-mongodb:jar:2.5.9:compile [INFO] +- org.springframework.boot:spring-boot-starter:jar:2.5.9:compile [INFO] | +- org.springframework.boot:spring-boot:jar:2.5.9:compile [INFO] | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.5.9:compile [INFO] | +- org.springframework.boot:spring-boot-starter-logging:jar:2.5.9:compile [INFO] | | +- ch.qos.logback:logback-classic:jar:1.2.10:compile [INFO] | | | \- ch.qos.logback:logback-core:jar:1.2.10:compile [INFO] | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.1:compile [INFO] | | | \- org.apache.logging.log4j:log4j-api:jar:2.17.1:compile [INFO] | | \- org.slf4j:jul-to-slf4j:jar:1.7.33:compile [INFO] | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile [INFO] | +- org.springframework:spring-core:jar:5.3.15:compile [INFO] | | \- org.springframework:spring-jcl:jar:5.3.15:compile [INFO] | \- org.yaml:snakeyaml:jar:1.28:compile [INFO] \- org.springframework.data:spring-data-mongodb:jar:3.2.8:compile [INFO] +- org.springframework:spring-tx:jar:5.3.15:compile [INFO] +- org.springframework:spring-context:jar:5.3.15:compile [INFO] | \- org.springframework:spring-aop:jar:5.3.15:compile [INFO] +- org.springframework:spring-beans:jar:5.3.15:compile [INFO] +- org.springframework:spring-expression:jar:5.3.15:compile [INFO] +- org.springframework.data:spring-data-commons:jar:2.5.8:compile [INFO] \- org.slf4j:slf4j-api:jar:1.7.33:compile [INFO] ------------------------------------------------------------------------ My question is why bson and mongodb-driver-core's version are still 4.2.3 instead of 4.5.0. mongodb-driver-sync will depends on bson and mongodb-driver-core, and the versions are 4.5.0. Here is the pom of mongodb-driver-sync. <project> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-sync</artifactId> <version>4.5.0</version> <dependencies> <dependency> <groupId>org.mongodb</groupId> <artifactId>bson</artifactId> <version>4.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-core</artifactId> <version>4.5.0</version> <scope>compile</scope> </dependency> </dependencies> </project> | Copyng and renaming multiple files in a .bat file with FOR ... DO xcopy Posted: 15 Mar 2022 04:21 AM PDT I want to move several thousand files (.jpg and .pdf) from one location to another and then rename each file. My 'copy_rename_docs.csv' input file contains two columns: - the first column containing the original path AND file names for each file that I want to move and rename
- and the second column containing the new paths AND file names that I want to move and rename each file to
like this: \\myapp\data\e3\9b\e39bf5e8d745833418d3edcd97c6c7589716970b,\\myapp\test_migration\e3\9b\Test File-xxx - SS1.pdf \\myapp\data\09\f5\09f58d1a1345f67a36154e99ffedb27308fa4292,\\myapp\test_migration\09\f5\nxf7.pdf so "e39bf5e8d745833418d3edcd97c6c7589716970b" is a SHA1 hash of "Test File-xxx - SS1.pdf" To copy and rename the files I tried this batch file: @echo off FOR /F "tokens=1,2 delims=," %%a IN (copy_rename_docs.csv) DO (xcopy /e /i "%%a" "%%b\*") pause exit But instead of copying and renaming the files to this: \\myapp\test_migration\e3\9b\Test File-xxx - SS1.pdf \\myapp\test_migration\09\f5\nxf7.pdf It is creating a FOLDER for each file with the FILE names as the FOLDER names and copying the files with their original file names into each FOLDER, like this: \\myapp\test_migration\e3\9b\Test File-xxx - SS1.pdf\e39bf5e8d745833418d3edcd97c6c7589716970b \\myapp\test_migration\09\f5\nxf7.pdf\09f58d1a1345f67a36154e99ffedb27308fa4292 What I want is: \\myapp\test_migration\e3\9b\Test File-xxx - SS1.pdf \\myapp\test_migration\09\f5\nxf7.pdf What's wrong with my batch file? And is xcopy the best tool for this? | Method with multiprocessing tool in python works worse than methoud without that Posted: 15 Mar 2022 04:21 AM PDT I am trying to use multiprocessing to speed up dealing with lots of files instead of reading them one by one. I did a test to learn before that. Below is my code: from multiprocessing.pool import Pool from time import sleep, time def print_cube(num): aa1 = num * num aa2 = num * num * num return aa1, aa2 def main1(): start = time() x = [] y = [] p = Pool(16) for j in range(1, 5): results = p.apply_async(print_cube, args = (j, )) x.append(results.get()[0]) y.append(results.get()[1]) end = time() return end - start, x, y def main2(): start = time() x = [] y = [] for j in range(1, 5): results = print_cube(j) x.append(results[0]) y.append(results[1]) end = time() return end - start, x, y if __name__ == "__main__": print("Method1{0}time : {1}{2}x : {3}{4}y : {5}".format('\n' ,main1()[0], '\n', main1()[1], '\n', main1()[2])) print("Method2{0}time : {1:.6f}{2}x : {3}{4}y : {5}".format('\n' ,main2()[0], '\n', main2()[1], '\n', main2()[2])) And the result is: Method1 time : 0.1549079418182373 x : [1, 4, 9, 16] y : [1, 8, 27, 64] Method2 time : 0.000000 x : [1, 4, 9, 16] y : [1, 8, 27, 64] Method1 uses multiprocessing and consumes more CPU, but costs more time than method2. Even if the number of cycles j goes to 5000 or greater, method2 works better than method1. Can anybody tell me what's wrong with my code? | How to send text within username input field on a https://discord.com/register webpage with Selenium and Python3 Posted: 15 Mar 2022 04:21 AM PDT I'm trying to select the username text box in the https://discord.com/register website I tried: driver.find_element ( by=By.CSS_SELECTOR ...) driver.find_element ( by=By.CLASS_NAME ...) driver.find_element ( by=By.CSS_XPATH ...) They all didn't work . Can anyone help me out with this? | I want to implement login and register functionality using Auth0 Posted: 15 Mar 2022 04:22 AM PDT | Error: FIREBASE FATAL ERROR: Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com Posted: 15 Mar 2022 04:22 AM PDT I'm making an app that writes a waiting list in the firebase realtime databse, but when I try to run this simple bit of code, I get this error (Note- I'm running this on web): class FirebaseMethods { FirebaseDatabase database = FirebaseDatabase.instance; void createPartyWaiting(String partyCode, String partyHostName) async { partyCode = partyCode.trim(); DataSnapshot data = await database.ref("e").get(); print(data.value); } } and i'm calling it as- await FirebaseMethods().createPartyWaiting("PartyCode123", "Adam"); But when I call it, I get this error- Error: FIREBASE FATAL ERROR: Cannot parse Firebase url. Please use https://<YOUR FIREBASE>.firebaseio.com at Re (https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js:1:22344) at Ws (https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js:1:148180) at au (https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js:1:166314) at K.xu.INTERNAL.registerComponent.Y.setServiceProps.Reference [as instanceFactory] (https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js:1:187315) I'm initializing the web like this (I've censored the actual values)- void main() async { WidgetsFlutterBinding.ensureInitialized(); if (kIsWeb) { Firebase.initializeApp( options: const FirebaseOptions( apiKey: "apikey", authDomain: "authDomain", projectId: "projectId", storageBucket: "storageBucket", messagingSenderId: "messagingSenderId", appId: "appId")); } runApp(const MyApp()); } I've also found out that there's this optional arguement to the initilaziation method- But after adding my databaseUrl here, I got this error- Error: [core/duplicate-app] A Firebase App named "[DEFAULT]" already exists at Object.throw_ [as throw] (http://localhost:53659/dart_sdk.js:5063:11) at firebase_core_web.FirebaseCoreWeb.new.initializeApp (http://localhost:53659/packages/firebase_core_web/firebase_core_web.dart.lib.js:248:27) What am I doing wrong, and how do I fix this? Thanks! | Is it possible to scrape amazon reviews by brand? Posted: 15 Mar 2022 04:22 AM PDT In order to do some scalable web scraping on amazon product reviews, I want to find out, if there is a possibility to scrape all product reviews for a particular brand, without knowing all the ASIN and product description information. Currently I am using a self-made R-scraper. In order to scrape the reviews, I need to collect ASIN and product description from the product page. As I am scraping a lot of products with just 5 to 30 review texts, this takes some time and includes a lot of manual work. Here is my R-Code (Note, that Primavera is the brand name): library(pacman) pacman::p_load(RCurl, XML, dplyr, rvest, purrr) #### SCRAPE scrape_amazon <- function(page_num) { url_reviews <- paste0("https://www.amazon.de/Primavera-Bio-Geschenkset-Zitrusdüfte-17ml/product-reviews/B00F43W2I6/ref=cm_cr_getr_d_paging_btm_next_3?ie=UTF8&reviewerType=all_reviews&pageNumber=", page_num) doc <- read_html(url_reviews) map_dfr(doc %>% html_elements("[id^='customer_review']"), ~ data.frame( review_title = .x %>% html_element(".review-title") %>% html_text2(), review_text = .x %>% html_element(".review-text-content") %>% html_text2(), review_star = .x %>% html_element(".review-rating") %>% html_text2(), date = .x %>% html_element(".review-date") %>% html_text2() %>% gsub(".*vom ", "", .), author = .x %>% html_element(".a-profile-name") %>% html_text2(), helpful_votes = .x %>% html_element(".a-size-base.a-color-tertiary.cr-vote-text") %>% html_text2(), verified = .x %>% html_element(".a-size-mini.a-color-state.a-text-bold") %>% html_text2(), page = page_num )) %>% as_tibble %>% return() } # loop extract datalist = list() i = 1 for (i in 1:20) { df_in <- scrape_amazon(page_num = i) datalist[[i]] <- df_in } output = do.call(rbind, datalist) | React native bottom tab bar pushing itself up when opening keyboard Posted: 15 Mar 2022 04:22 AM PDT We are using createBottomTabNavigator. In one of the tab contains search bar at the top. While clicking on that search bar, we are opening the keyboard. But the keyboard pushing up the bottom tab bar also. We need the bottom tab bar remains at the bottom when opening keyboard. - One of the solution I have tried is, in android manifest, I have changed android:windowSoftInputMode="adjustPan" or "adjustNothing". It is working fine as expected. But we are using chat layout in another tab which needs "adjustResize". So I have to keep "adjustResize" for windowSoftInputMode.
- As another solution, I tried to change windowSoftInputMode inside component itself. SO I have tried with this - https://www.npmjs.com/package/react-native-android-keyboard-adjust. But no use.
- As another one, I tried to create a TabBarComponent like mentioned here https://github.com/react-navigation/react-navigation/issues/618. But not working as expected.
const SignedIn = createBottomTabNavigator( { Followers: { screen: FollowerStack, ... }, Search: { screen: SearchStack, }, Home: { screen: HomeStack, }, Bookmarks: { screen: BookmarkStack, }, Profile: { screen: ProfileStack, } }, { initialRouteName: "Home", tabBarPosition: 'bottom', swipeEnabled: false, animationEnabled: false, tabBarOptions: { keyboardHidesTabBar: true, showIcon: true, showLabel: false, activeTintColor: "red", inactiveTintColor: "gray", adaptive: true, safeAreaInset: { bottom: "always" }, style: { position: 'relative', backgroundColor: "#F9F8FB", height: TAB_NAVIGATOR_DYNAMIC_HEIGHT, paddingTop: DeviceInfo.hasNotch() ? "5%" : "0%", minHeight: TAB_NAVIGATOR_DYNAMIC_HEIGHT, width: '100%', bottom: 0 } } } ); - Is there any other properties existed for making the bottom tab bar sticky at the bottom? or
- Is it possible to change the android manifest windowSoftInputMode from inside component? Please comment below if you required any other code part for reference. Thanks for any help.
| How to fix HttpException: Connection closed before full header was received Posted: 15 Mar 2022 04:22 AM PDT I have recently upgraded my flutter version in my app. But when I want to debug the application, it shows me the following error. Error connecting to the service protocol: HttpException: Connection closed before full header was received, URI = http://127.0.0.1:50795/ws Is there anyone facing the same issue after upgrading the flutter version? If is there any workaround, please share. | Sorting an array of objects based on a property value (int) [duplicate] Posted: 15 Mar 2022 04:21 AM PDT This question deals with my algorithm and why it isn't working. More specifically, I would like to know how it can be improved to do what I want it to do. That is why it is different from the suggested duplicate question. I am trying to create a function that sorts an array of objects based on a property value (int) that they all share in common, "indexFound". As you may suspect, I am trying to place elements with a lower indexFound at the beginning of the array. function organizeTokens(list) { for (i = 0; i < list.length - 1; i++) { if (list[i].indexFound < list[i + 1].indexFound) { // do nothing } else if (list[i].indexFound > list[i + 1].indexFound) { var tempVal = list[i]; list[i] = list[i + 1]; list[i + 1] = tempVal; } else { // should not happen unless we are comparing the same token } } }; As it stands, this code is not making any difference when I feed it an array of objects. The elements are still not in the order that they should be. Am I approaching this in the right way? Am I missing something obvious? EDIT: ------------------------------------------------------------------- Example input: organizeTokens([{value: "if", indexFound: 7}, {value: "a", indexFound: 0}]) Expected output: [{value: "a", indexFound: 0}, {value: "if", indexFound: 7}] Actual output: [{value: "if", indexFound: 7}, {value: "a", indexFound: 0}] | Intuitive Understanding of GCD algorithm Posted: 15 Mar 2022 04:22 AM PDT What's an intuitive way to understand how this algorithm finds the GCD? function gcd(a, b) { while (a != b) if (a, b) a -= b; else b -= a; return a; } | How does sum of squared difference algorithm work? Posted: 15 Mar 2022 04:21 AM PDT I have a two images a and b, where b is a block of image a. I want to find b using block matching. How do I go about it? | |
SegmentFault 最新的文章 Posted: 15 Mar 2022 01:14 AM PDT css 的 filter属性竟然如此好玩 Posted: 14 Mar 2022 09:58 PM PDT css 的 filter属性竟然如此好玩背景 在此之前我对css 里面的filter 属性不是很了解, 只知道使用这个属性来改变svg 图片的颜色, 最近恰好查了很多相关文档并做了大量实验, 并有了一些启发与想法, 索性就在这里分享出来。 一、filter 滤镜 "滤镜"这个名字很贴切了, 可以理解成为元素添加各种显示效果, 先不用记各种名词咱们直接看效果, 使用方法 & 效果图: <style> #lulu { filter: grayscale(1); } </style> <body> <img id="lulu" src="./img/头像.jpeg" /> </body>
看上图里的这些效果, 比如第一排第一个, 我们会想到在某些特定的纪念日网站整体会变成灰色的样式, 应该就是用的这个属性, 第二排的第一张就可以用与某些事物被"雷击"? 二、做一个'抖动'特效 看到这张图我第一个想法就是做个抖动的特效, 就是那种很动感的效果:
当然配合上一旋转效果也不错:
原理就是两个图片层叠在一起, 上面的图片进行放大与旋转动画: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> .box { position: relative; border: 1px solid gray; display: flex; overflow: hidden; width: 110px; padding: 0px; margin-top: 100px; margin-left: 300px; } .box>img { width: 100px; height: 100px; margin-left: 6px; filter: invert(1); } .mk { position: absolute; top: 0; left: 0; opacity: 0.5; animation: cc 0.5s linear infinite; } @keyframes cc { from { transform: scale(1.2); } to { transform: scale(1); } } .mk2 { position: absolute; top: 0; left: 0; opacity: 0.5; animation: cc2 0.5s linear infinite alternate; border-radius: 50%; overflow: hidden; } @keyframes cc2 { from { transform: scale(2.2) rotate(30deg); } to { transform: scale(1) rotate(0deg); } } </style> </head> <body> <div class="box"> <img src="./img/头像.jpeg" /> <img class="mk" src="./img/头像.jpeg" /> </div> <div class="box"> <img src="./img/头像.jpeg" /> <img class="mk2" src="./img/头像.jpeg" /> </div> </body> </html>
三、drop-shadow 阴影 filter 属性通过设置drop-shadow 为元素添加阴影, 可是早就有box-shadow 属性了呀, 那这两个属性有什么区别了? <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> #w1 { width: 50px; height: 50px; font-size: 36px; font-weight: 900; box-shadow: 0 0 2px red; } #w2 { width: 50px; height: 50px; font-size: 36px; font-weight: 900; filter: drop-shadow(0px 0px 2px red); } </style> </head> <body> <p id="w1">九</p> <p id="w2">九</p> </body> </html>
上图可知, box-shadow 是针对整个dom元素 进行阴影的产生, 但是drop-shadow 会忽略掉"透明"的部分。 四、drop-shadow 复制 (做一个看图猜人物游戏) 注意: 我这里使用的都是svg 图片。 既然与box-shadow 都有为元素设置阴影的能力, 那么box-shadow 有复制自身样的能力drop-shadow 是都也有? 所谓box-shadow 的复制自身样式如图所示, box-shadow 可以制作n个与元素本身形状相同或不同的样式, 下图右侧红色的方块就是左图的阴影: 再看一下drop-shadow 的表现: 看到上面的图我第一反应就是"猜人物"小游戏, 我们把人物的轮廓也就是右图显示出来, 然后在公布答案的时候展示左侧的原图即可。 赋值gif图有bug 赋值gif图会有bug, 效果如下: 五、drop-shadow 批量复制 box-shadow 属性是可以写多个属性值的, 我一般会利用这个属性进行一个单一样式的dom 元素的复制 效果如下图: drop-shadow 有点'狠', 他的每一次复制都是基于上次的整体效果进行的阴影投射: 上图可以看出, 第一个复制后是出现了横排的2个, 第二次投射是产生了下方的两个, 并且每次投射都是叠加的, 下面我们看一组更夸张的: 可想而知这种增长方式有多可怕, 稍微写几遍就可以覆盖满屏幕了。 "找不同"小游戏 我们可以做一片阴影, 但是其中某个我们单独做一个样式进行覆盖, 考考大家的眼力, 就如图例所示: 这里就是利用drop-shadow 产生阴影, 然后再进行一点修改, 正确答案在这里: 所以只要再写两段代码, 就可以让这个8x8 变成16x16那么多, 应该还挺好玩的。 六、drop-shadow 与 box-shadow的联合 drop-shadow 与 box-shadow 都有投射的能力, 那么他两个属性共同作用于一个元素会是怎样的:
box-shadow 会基于drop-shadow 属性产生的全部投影进行透射阴影, 第一排是drop-shadow 的投影, 第二排是box-shadow 的投影, 具体怎么玩我还没想到太适合的。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> #wrap { position: relative; height: 350px; width: 500px; margin: 50px auto; overflow: hidden; } #n { border: 1px solid gray; width: 50px; box-shadow: 0 200px; transform: rotate(10deg); filter: drop-shadow(70px 0) drop-shadow(140px 0px); } </style> </head> <body> <div id="wrap"> <img id="n" src="./svg/人.svg" / </div> </body> </html>
七、drop-shadow 复制后的'运动' 既然可以投射出那么多投影, 那么如果我元素进行旋转的话, 投影是否也会进行旋转? 并且它是以什么规律运动的那? 下面演示的是, 物体投影 + 物体本身旋转: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> #wrap { position: relative; height: 350px; width: 400px; margin: 250px auto; } #n { width: 20px; filter: drop-shadow(25px 0) drop-shadow(50px 0px) drop-shadow(100px 0); animation: rr 2s linear infinite; } @keyframes rr { 0% { transform: rotate(0); } 100% { transform: rotate(360deg); } } </style> </head> <body> <div id="wrap"> <img id="n" src="./svg/人.svg" /> </div> </body> </html>
上面是整体以'元素'本身为旋转点进行旋转, 那要如何让'元素'的每个投影都以自身为原点旋转那? 这里的思路就是, 在img 外包裹一层div , 我们对外层div 进行投影, 内部的img 负责旋转: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> #wrap { position: relative; height: 350px; width: 400px; margin: 250px auto; } #box { width: 20px; height: 20px; filter: drop-shadow(25px 0) drop-shadow(50px 0px) drop-shadow(100px 0); } #n { width: 20px; animation: rr 2s linear infinite; } @keyframes rr { 0% { transform: rotate(0); } 100% { transform: rotate(360deg); } } </style> </head> <body> <div id="wrap"> <div id="box"> <img id="n" src="./svg/人.svg" /> </div> </div> </body> </html>
八、filter属性着色(svg + png)图片 改变svg 颜色最直接的方法就是改其本身的fill 属性, 这里不做探讨, 这里要研究的是到底为什么filter 可以改变图片的颜色, 是什么原理? 这里我们就一起探究一下(这里只讨论纯色图片)。 轮廓的形成 并不是所有的图片被赋予drop-shadow 属性后都会呈现出物体的轮廓, 投影会忽略透明背景的地方, 所以png 这种可以定义透明背景的图片才可以被投射出相应的轮廓而不是矩形轮廓, svg同理。 比如jpg 图片无法设置透明的背景, 所以其投影效果就与box-shadow 相同了。 svg + png 投影变色 我们可以利用drop-shadow 制作一个指定颜色的投影, 然后只要将元素本身隐藏, 只留下投影就ok了。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> #box2 { display: inline-block; overflow: hidden; } #glasses2 { filter: drop-shadow(200px 0 red); transform: translateX(-200px); } </style> </head> <body> <div> <div id="box2"> <img id="glasses2" src="./img/太阳镜.png" /> </div> </div> </body> </html>
颜色的叠加变色有点强! 有没有办法是直接改变元素本身的颜色? 我尝试将drop-shadow 投射在自身位置, 但是投射的阴影永远在元素后面, 我尝试将元素的opacity 改小, 阴影也会随之变小, 如果设置opacity:0 则投影也不可见了。 不管什么颜色无非是三原色合成的颜色, filter 属性可以定义那么多种滤镜, 那是不是代表着某些滤镜效果的叠加态就是我们想要的目标颜色: 手动生成那么多的属性不现实, 顺着这个思路我找到了一个真的这样做的网站: 为图片混合调色官网 - 需要多点几次
Compute Filters 按钮, 直到生成差异度较小的属性。 - 如果我们的元素不是纯黑色, 需要先赋予
filter: brightness(0) saturate(100%) 将其变为纯黑, 因为不同的底色需要变成目标颜色的filter 属性不同。 - 当然啦这里属于头脑一波, 实际项目中不会这样去做的。
九、局部清晰 这里所谓的局部清晰可以想象为, 某张图全部都是模糊的, 但是我们把一个放大镜放在某处, 此处就会变得清晰, 先看我做的效果: 这里的原理是这样的, 一共两层, 下层是模糊滤镜的图片,上层是一个圆形的div , 并且这个div 的背景图是图片的清晰版, 设置background-position , 在拖动div 的同时, 实时变换背景的background-position 位置, 就实现了图里的效果。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> * { box-sizing: border-box; } #wrap { position: relative; } #acc { position: absolute; left: 0; right: 0; width: 540px; filter: blur(7px); pointer-events: none; } #mk { z-index: 2; height: 100px; width: 100px; border-radius: 50%; overflow: hidden; border: 1px solid blue; position: absolute; left: 0; top: 0; background-image: url("./img/利姆露jpeg.jpeg"); background-size: 540px 562px; background-position: 0 0; background-repeat: no-repeat; } </style> </head> <body> <div id="wrap"> <div id="mk"></div> <img id="acc" src="./img/利姆露jpeg.jpeg" /> </div> <script> function drag(elementId) { const element = document.getElementById(elementId); const position = { offsetX: 0, offsetY: 0, state: 0, } function getEvent(event) { return event || window.event; } element.addEventListener( "mousedown", function (event) { var e = getEvent(event); position.offsetX = e.offsetX; position.offsetY = e.offsetY; position.state = 1; }, false ); document.addEventListener( "mousemove", function (event) { var e = getEvent(event); if (position.state) { position.endX = e.clientX; position.endY = e.clientY; element.style.top = position.endY - position.offsetY + "px"; element.style.left = position.endX - position.offsetX + "px"; element.style.backgroundPositionX = "-" + element.style.left; element.style.backgroundPositionY = "-" + element.style.top; } }, false ); element.addEventListener( "mouseup", function (event) { position.state = 0; }, false ); } drag("mk"); </script> </body> </html>
end 这次就是这样, 希望与你一起进步。 | 5种限流算法,7种限流方式,挡住突发流量? Posted: 14 Mar 2022 06:52 PM PDT 大家好啊,我是阿朗,最近工作中需要用到限流,这篇文章介绍常见的限流方式。 文章持续更新,可以关注公众号程序猿阿朗或访问未读代码博客。 本文 Github.com/niumoo/JavaNotes 已经收录,欢迎Star。 前言最近几年,随着微服务的流行,服务和服务之间的依赖越来越强,调用关系越来越复杂,服务和服务之间的稳定性越来越重要。在遇到突发的请求量激增,恶意的用户访问,亦或请求频率过高给下游服务带来较大压力时,我们常常需要通过缓存、限流、熔断降级、负载均衡等多种方式保证服务的稳定性。其中限流是不可或缺的一环,这篇文章介绍限流相关知识。 1. 限流限流顾名思义,就是对请求或并发数进行限制;通过对一个时间窗口内的请求量进行限制来保障系统的正常运行。如果我们的服务资源有限、处理能力有限,就需要对调用我们服务的上游请求进行限制,以防止自身服务由于资源耗尽而停止服务。 在限流中有两个概念需要了解。 - 阈值:在一个单位时间内允许的请求量。如 QPS 限制为10,说明 1 秒内最多接受 10 次请求。
- 拒绝策略:超过阈值的请求的拒绝策略,常见的拒绝策略有直接拒绝、排队等待等。
2. 固定窗口算法固定窗口算法又叫计数器算法,是一种简单方便的限流算法。主要通过一个支持原子操作的计数器来累计 1 秒内的请求次数,当 1 秒内计数达到限流阈值时触发拒绝策略。每过 1 秒,计数器重置为 0 开始重新计数。 2.1. 代码实现下面是简单的代码实现,QPS 限制为 2,这里的代码做了一些优化,并没有单独开一个线程去每隔 1 秒重置计数器,而是在每次调用时进行时间间隔计算来确定是否先重置计数器。 /** * @author https://www.wdbyte.com */ public class RateLimiterSimpleWindow { // 阈值 private static Integer QPS = 2; // 时间窗口(毫秒) private static long TIME_WINDOWS = 1000; // 计数器 private static AtomicInteger REQ_COUNT = new AtomicInteger(); private static long START_TIME = System.currentTimeMillis(); public synchronized static boolean tryAcquire() { if ((System.currentTimeMillis() - START_TIME) > TIME_WINDOWS) { REQ_COUNT.set(0); START_TIME = System.currentTimeMillis(); } return REQ_COUNT.incrementAndGet() <= QPS; } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { Thread.sleep(250); LocalTime now = LocalTime.now(); if (!tryAcquire()) { System.out.println(now + " 被限流"); } else { System.out.println(now + " 做点什么"); } } } }
运行结果: 20:53:43.038922 做点什么 20:53:43.291435 做点什么 20:53:43.543087 被限流 20:53:43.796666 做点什么 20:53:44.050855 做点什么 20:53:44.303547 被限流 20:53:44.555008 被限流 20:53:44.809083 做点什么 20:53:45.063828 做点什么 20:53:45.314433 被限流
从输出结果中可以看到大概每秒操作 3 次,由于限制 QPS 为 2,所以平均会有一次被限流。看起来可以了,不过我们思考一下就会发现这种简单的限流方式是有问题的,虽然我们限制了 QPS 为 2,但是当遇到时间窗口的临界突变时,如 1s 中的后 500 ms 和第 2s 的前 500ms 时,虽然是加起来是 1s 时间,却可以被请求 4 次。 简单修改测试代码,可以进行验证: // 先休眠 400ms,可以更快的到达时间窗口。 Thread.sleep(400); for (int i = 0; i < 10; i++) { Thread.sleep(250); if (!tryAcquire()) { System.out.println("被限流"); } else { System.out.println("做点什么"); } }
得到输出中可以看到连续 4 次请求,间隔 250 ms 没有却被限制。: 20:51:17.395087 做点什么 20:51:17.653114 做点什么 20:51:17.903543 做点什么 20:51:18.154104 被限流 20:51:18.405497 做点什么 20:51:18.655885 做点什么 20:51:18.906177 做点什么 20:51:19.158113 被限流 20:51:19.410512 做点什么 20:51:19.661629 做点什么
3. 滑动窗口算法我们已经知道固定窗口算法的实现方式以及它所存在的问题,而滑动窗口算法是对固定窗口算法的改进。既然固定窗口算法在遇到时间窗口的临界突变时会有问题,那么我们在遇到下一个时间窗口前也调整时间窗口不就可以了吗? 下面是滑动窗口的示意图。 上图的示例中,每 500ms 滑动一次窗口,可以发现窗口滑动的间隔越短,时间窗口的临界突变问题发生的概率也就越小,不过只要有时间窗口的存在,还是有可能发生时间窗口的临界突变问题。 3.1. 代码实现下面是基于以上滑动窗口思路实现的简单的滑动窗口限流工具类。 package com.wdbyte.rate.limiter; import java.time.LocalTime; import java.util.concurrent.atomic.AtomicInteger; /** * 滑动窗口限流工具类 * * @author https://www.wdbyte.com */ public class RateLimiterSlidingWindow { /** * 阈值 */ private int qps = 2; /** * 时间窗口总大小(毫秒) */ private long windowSize = 1000; /** * 多少个子窗口 */ private Integer windowCount = 10; /** * 窗口列表 */ private WindowInfo[] windowArray = new WindowInfo[windowCount]; public RateLimiterSlidingWindow(int qps) { this.qps = qps; long currentTimeMillis = System.currentTimeMillis(); for (int i = 0; i < windowArray.length; i++) { windowArray[i] = new WindowInfo(currentTimeMillis, new AtomicInteger(0)); } } /** * 1. 计算当前时间窗口 * 2. 更新当前窗口计数 & 重置过期窗口计数 * 3. 当前 QPS 是否超过限制 * * @return */ public synchronized boolean tryAcquire() { long currentTimeMillis = System.currentTimeMillis(); // 1. 计算当前时间窗口 int currentIndex = (int)(currentTimeMillis % windowSize / (windowSize / windowCount)); // 2. 更新当前窗口计数 & 重置过期窗口计数 int sum = 0; for (int i = 0; i < windowArray.length; i++) { WindowInfo windowInfo = windowArray[i]; if ((currentTimeMillis - windowInfo.getTime()) > windowSize) { windowInfo.getNumber().set(0); windowInfo.setTime(currentTimeMillis); } if (currentIndex == i && windowInfo.getNumber().get() < qps) { windowInfo.getNumber().incrementAndGet(); } sum = sum + windowInfo.getNumber().get(); } // 3. 当前 QPS 是否超过限制 return sum <= qps; } private class WindowInfo { // 窗口开始时间 private Long time; // 计数器 private AtomicInteger number; public WindowInfo(long time, AtomicInteger number) { this.time = time; this.number = number; } // get...set... } }
下面是测试用例,设置 QPS 为 2,测试次数 20 次,每次间隔 300 毫秒,预计成功次数在 12 次左右。 public static void main(String[] args) throws InterruptedException { int qps = 2, count = 20, sleep = 300, success = count * sleep / 1000 * qps; System.out.println(String.format("当前QPS限制为:%d,当前测试次数:%d,间隔:%dms,预计成功次数:%d", qps, count, sleep, success)); success = 0; RateLimiterSlidingWindow myRateLimiter = new RateLimiterSlidingWindow(qps); for (int i = 0; i < count; i++) { Thread.sleep(sleep); if (myRateLimiter.tryAcquire()) { success++; if (success % qps == 0) { System.out.println(LocalTime.now() + ": success, "); } else { System.out.print(LocalTime.now() + ": success, "); } } else { System.out.println(LocalTime.now() + ": fail"); } } System.out.println(); System.out.println("实际测试成功次数:" + success); }
下面是测试的结果。 当前QPS限制为:2,当前测试次数:20,间隔:300ms,预计成功次数:12 16:04:27.077782: success, 16:04:27.380715: success, 16:04:27.684244: fail 16:04:27.989579: success, 16:04:28.293347: success, 16:04:28.597658: fail 16:04:28.901688: fail 16:04:29.205262: success, 16:04:29.507117: success, 16:04:29.812188: fail 16:04:30.115316: fail 16:04:30.420596: success, 16:04:30.725897: success, 16:04:31.028599: fail 16:04:31.331047: fail 16:04:31.634127: success, 16:04:31.939411: success, 16:04:32.242380: fail 16:04:32.547626: fail 16:04:32.847965: success, 实际测试成功次数:11
4. 滑动日志算法滑动日志算法是实现限流的另一种方法,这种方法比较简单。基本逻辑就是记录下所有的请求时间点,新请求到来时先判断最近指定时间范围内的请求数量是否超过指定阈值,由此来确定是否达到限流,这种方式没有了时间窗口突变的问题,限流比较准确,但是因为要记录下每次请求的时间点,所以占用的内存较多。 4.1. 代码实现下面是简单实现的 一个滑动日志算法,因为滑动日志要每次请求单独存储一条记录,可能占用内存过多。所以下面这个实现其实不算严谨的滑动日志,更像一个把 1 秒时间切分成 1000 个时间窗口的滑动窗口算法。 package com.wdbyte.rate.limiter; import java.time.LocalTime; import java.util.HashSet; import java.util.Set; import java.util.TreeMap; /** * 滑动日志方式限流 * 设置 QPS 为 2. * * @author https://www.wdbyte.com */ public class RateLimiterSildingLog { /** * 阈值 */ private Integer qps = 2; /** * 记录请求的时间戳,和数量 */ private TreeMap<Long, Long> treeMap = new TreeMap<>(); /** * 清理请求记录间隔, 60 秒 */ private long claerTime = 60 * 1000; public RateLimiterSildingLog(Integer qps) { this.qps = qps; } public synchronized boolean tryAcquire() { long now = System.currentTimeMillis(); // 清理过期的数据老数据,最长 60 秒清理一次 if (!treeMap.isEmpty() && (treeMap.firstKey() - now) > claerTime) { Set<Long> keySet = new HashSet<>(treeMap.subMap(0L, now - 1000).keySet()); for (Long key : keySet) { treeMap.remove(key); } } // 计算当前请求次数 int sum = 0; for (Long value : treeMap.subMap(now - 1000, now).values()) { sum += value; } // 超过QPS限制,直接返回 false if (sum + 1 > qps) { return false; } // 记录本次请求 if (treeMap.containsKey(now)) { treeMap.compute(now, (k, v) -> v + 1); } else { treeMap.put(now, 1L); } return sum <= qps; } public static void main(String[] args) throws InterruptedException { RateLimiterSildingLog rateLimiterSildingLog = new RateLimiterSildingLog(3); for (int i = 0; i < 10; i++) { Thread.sleep(250); LocalTime now = LocalTime.now(); if (rateLimiterSildingLog.tryAcquire()) { System.out.println(now + " 做点什么"); } else { System.out.println(now + " 被限流"); } } } }
代码中把阈值 QPS 设定为 3,运行可以得到如下日志: 20:51:17.395087 做点什么 20:51:17.653114 做点什么 20:51:17.903543 做点什么 20:51:18.154104 被限流 20:51:18.405497 做点什么 20:51:18.655885 做点什么 20:51:18.906177 做点什么 20:51:19.158113 被限流 20:51:19.410512 做点什么 20:51:19.661629 做点什么
5. 漏桶算法漏桶算法中的漏桶是一个形象的比喻,这里可以用生产者消费者模式进行说明,请求是一个生产者,每一个请求都如一滴水,请求到来后放到一个队列(漏桶)中,而桶底有一个孔,不断的漏出水滴,就如消费者不断的在消费队列中的内容,消费的速率(漏出的速度)等于限流阈值。即假如 QPS 为 2,则每 1s / 2= 500ms 消费一次。漏桶的桶有大小,就如队列的容量,当请求堆积超过指定容量时,会触发拒绝策略。 下面是漏桶算法的示意图。 由介绍可以知道,漏桶模式中的消费处理总是能以恒定的速度进行,可以很好的保护自身系统不被突如其来的流量冲垮;但是这也是漏桶模式的缺点,假设 QPS 为 2,同时 2 个请求进来,2 个请求并不能同时进行处理响应,因为每 1s / 2= 500ms 只能处理一个请求。 6. 令牌桶算法令牌桶算法同样是实现限流是一种常见的思路,最为常用的 Google 的 Java 开发工具包 Guava 中的限流工具类 RateLimiter 就是令牌桶的一个实现。令牌桶的实现思路类似于生产者和消费之间的关系。 系统服务作为生产者,按照指定频率向桶(容器)中添加令牌,如 QPS 为 2,每 500ms 向桶中添加一个令牌,如果桶中令牌数量达到阈值,则不再添加。 请求执行作为消费者,每个请求都需要去桶中拿取一个令牌,取到令牌则继续执行;如果桶中无令牌可取,就触发拒绝策略,可以是超时等待,也可以是直接拒绝本次请求,由此达到限流目的。 下面是令牌桶限流算法示意图。 思考令牌桶的实现可以以下特点。 - 1s / 阈值(QPS) = 令牌添加时间间隔。
- 桶的容量等于限流的阈值,令牌数量达到阈值时,不再添加。
- 可以适应流量突发,N 个请求到来只需要从桶中获取 N 个令牌就可以继续处理。
- 有启动过程,令牌桶启动时桶中无令牌,然后按照令牌添加时间间隔添加令牌,若启动时就有阈值数量的请求过来,会因为桶中没有足够的令牌而触发拒绝策略,不过如 RateLimiter 限流工具已经优化了这类问题。
6.1. 代码实现Google 的 Java 开发工具包 Guava 中的限流工具类 RateLimiter 就是令牌桶的一个实现,日常开发中我们也不会手动实现了,这里直接使用 RateLimiter 进行测试。 引入依赖: <exclusion> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.0.1-jre</version> </exclusion>
RateLimiter 限流体验: // qps 2 RateLimiter rateLimiter = RateLimiter.create(2); for (int i = 0; i < 10; i++) { String time = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME); System.out.println(time + ":" + rateLimiter.tryAcquire()); Thread.sleep(250); }
代码中限制 QPS 为 2,也就是每隔 500ms 生成一个令牌,但是程序每隔 250ms 获取一次令牌,所以两次获取中只有一次会成功。 17:19:06.797557:true 17:19:07.061419:false 17:19:07.316283:true 17:19:07.566746:false 17:19:07.817035:true 17:19:08.072483:false 17:19:08.326347:true 17:19:08.577661:false 17:19:08.830252:true 17:19:09.085327:false
6.2. 思考虽然演示了 Google Guava 工具包中的 RateLimiter 的实现,但是我们需要思考一个问题,就是令牌的添加方式,如果按照指定间隔添加令牌,那么需要开一个线程去定时添加,如果有很多个接口很多个 RateLimiter 实例,线程数会随之增加,这显然不是一个好的办法。显然 Google 也考虑到了这个问题,在 RateLimiter 中,是在每次令牌获取时才进行计算令牌是否足够的。它通过存储的下一个令牌生成的时间,和当前获取令牌的时间差,再结合阈值,去计算令牌是否足够,同时再记录下一个令牌的生成时间以便下一次调用。 下面是 Guava 中 RateLimiter 类的子类 SmoothRateLimiter 的 resync() 方法的代码分析,可以看到其中的令牌计算逻辑。 void resync(long nowMicros) { // 当前微秒时间 // 当前时间是否大于下一个令牌生成时间 if (nowMicros > this.nextFreeTicketMicros) { // 可生成的令牌数 newPermits = (当前时间 - 下一个令牌生成时间)/ 令牌生成时间间隔。 // 如果 QPS 为2,这里的 coolDownIntervalMicros 就是 500000.0 微秒(500ms) double newPermits = (double)(nowMicros - this.nextFreeTicketMicros) / this.coolDownIntervalMicros(); // 更新令牌库存 storedPermits。 this.storedPermits = Math.min(this.maxPermits, this.storedPermits + newPermits); // 更新下一个令牌生成时间 nextFreeTicketMicros this.nextFreeTicketMicros = nowMicros; } }
7. Redis 分布式限流Redis 是一个开源的内存数据库,可以用来作为数据库、缓存、消息中间件等。Redis 是单线程的,又在内存中操作,所以速度极快,得益于 Redis 的各种特性,所以使用 Redis 实现一个限流工具是十分方便的。 下面的演示都基于Spring Boot 项目,并需要以下依赖。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置 Redis 信息。 spring: redis: database: 0 password: port: 6379 host: 127.0.0.1 lettuce: shutdown-timeout: 100ms pool: min-idle: 5 max-idle: 10 max-active: 8 max-wait: 1ms
7.1. 固定窗口限流Redis 中的固定窗口限流是使用 incr 命令实现的,incr 命令通常用来自增计数;如果我们使用时间戳信息作为 key,自然就可以统计每秒的请求量了,以此达到限流目的。 这里有两点要注意。 - 对于不存在的 key,第一次新增时,value 始终为 1。
- INCR 和 EXPIRE 命令操作应该在一个原子操作中提交,以保证每个 key 都正确设置了过期时间,不然会有 key 值无法自动删除而导致的内存溢出。
由于 Redis 中实现事务的复杂性,所以这里直接只用 lua 脚本来实现原子操作。下面是 lua 脚本内容。 local count = redis.call("incr",KEYS[1]) if count == 1 then redis.call('expire',KEYS[1],ARGV[2]) end if count > tonumber(ARGV[1]) then return 0 end return 1
下面是使用 Spring Boot 中 RedisTemplate 来实现的 lua 脚本调用测试代码。 /** * @author https://www.wdbyte.com */ @SpringBootTest class RedisLuaLimiterByIncr { private static String KEY_PREFIX = "limiter_"; private static String QPS = "4"; private static String EXPIRE_TIME = "1"; @Autowired private StringRedisTemplate stringRedisTemplate; @Test public void redisLuaLimiterTests() throws InterruptedException, IOException { for (int i = 0; i < 15; i++) { Thread.sleep(200); System.out.println(LocalTime.now() + " " + acquire("user1")); } } /** * 计数器限流 * * @param key * @return */ public boolean acquire(String key) { // 当前秒数作为 key key = KEY_PREFIX + key + System.currentTimeMillis() / 1000; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class); //lua文件存放在resources目录下 redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limiter.lua"))); return stringRedisTemplate.execute(redisScript, Arrays.asList(key), QPS, EXPIRE_TIME) == 1; } }
代码中虽然限制了 QPS 为 4,但是因为这种限流实现是把毫秒时间戳作为 key 的,所以会有临界窗口突变的问题,下面是运行结果,可以看到因为时间窗口的变化,导致了 QPS 超过了限制值 4。 17:38:23.122044 true 17:38:23.695124 true 17:38:23.903220 true # 此处有时间窗口变化,所以下面继续 true 17:38:24.106206 true 17:38:24.313458 true 17:38:24.519431 true 17:38:24.724446 true 17:38:24.932387 false 17:38:25.137912 true 17:38:25.355595 true 17:38:25.558219 true 17:38:25.765801 true 17:38:25.969426 false 17:38:26.176220 true 17:38:26.381918 true
7.3. 滑动窗口限流通过对上面的基于 incr 命令实现的 Redis 限流方式的测试,我们已经发现了固定窗口限流所带来的问题,在这篇文章的第三部分已经介绍了滑动窗口限流的优势,它可以大幅度降低因为窗口临界突变带来的问题,那么如何使用 Redis 来实现滑动窗口限流呢? 这里主要使用 ZSET 有序集合来实现滑动窗口限流,ZSET 集合有下面几个特点: - ZSET 集合中的 key 值可以自动排序。
- ZSET 集合中的 value 不能有重复值。
- ZSET 集合可以方便的使用 ZCARD 命令获取元素个数。
- ZSET 集合可以方便的使用 ZREMRANGEBYLEX 命令移除指定范围的 key 值。
基于上面的四点特性,可以编写出基于 ZSET 的滑动窗口限流 lua 脚本。 --KEYS[1]: 限流 key --ARGV[1]: 时间戳 - 时间窗口 --ARGV[2]: 当前时间戳(作为score) --ARGV[3]: 阈值 --ARGV[4]: score 对应的唯一value -- 1. 移除时间窗口之前的数据 redis.call('zremrangeByScore', KEYS[1], 0, ARGV[1]) -- 2. 统计当前元素数量 local res = redis.call('zcard', KEYS[1]) -- 3. 是否超过阈值 if (res == nil) or (res < tonumber(ARGV[3])) then redis.call('zadd', KEYS[1], ARGV[2], ARGV[4]) return 1 else return 0 end
下面是使用 Spring Boot 中 RedisTemplate 来实现的 lua 脚本调用测试代码。 @SpringBootTest class RedisLuaLimiterByZset { private String KEY_PREFIX = "limiter_"; private String QPS = "4"; @Autowired private StringRedisTemplate stringRedisTemplate; @Test public void redisLuaLimiterTests() throws InterruptedException, IOException { for (int i = 0; i < 15; i++) { Thread.sleep(200); System.out.println(LocalTime.now() + " " + acquire("user1")); } } /** * 计数器限流 * * @param key * @return */ public boolean acquire(String key) { long now = System.currentTimeMillis(); key = KEY_PREFIX + key; String oldest = String.valueOf(now - 1_000); String score = String.valueOf(now); String scoreValue = score; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class); //lua文件存放在resources目录下 redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limiter2.lua"))); return stringRedisTemplate.execute(redisScript, Arrays.asList(key), oldest, score, QPS, scoreValue) == 1; } }
代码中限制 QPS 为 4,运行结果信息与之一致。 17:36:37.150370 true 17:36:37.716341 true 17:36:37.922577 true 17:36:38.127497 true 17:36:38.335879 true 17:36:38.539225 false 17:36:38.745903 true 17:36:38.952491 true 17:36:39.159497 true 17:36:39.365239 true 17:36:39.570572 false 17:36:39.776635 true 17:36:39.982022 true 17:36:40.185614 true 17:36:40.389469 true
这里介绍了 Redis 实现限流的两种方式,当然使用 Redis 也可以实现漏桶和令牌桶两种限流算法,这里就不做演示了,感兴趣的可以自己研究下。 8. 总结这篇文章介绍实现限流的几种方式,主要是窗口算法和桶算法,两者各有优势。 单机限流与分布式限流 上面演示的基于代码形式的窗口算法和桶算法限流都适用于单机限流,如果需要分布式限流可以结合注册中心、负载均衡计算每个服务的限流阈值,但这样会降低一定精度,如果对精度要求不是太高,可以使用。 而 Redis 的限流,由于 Redis 的单机性,本身就可以用于分布式限流。使用 Redis 可以实现各种可以用于限流算法,如果觉得麻烦也可以使用开源工具如 redisson,已经封装了基于 Redis 的限流。 其他限流工具 文中已经提到了 Guava 的限流工具包,不过它毕竟是单机的,开源社区中也有很多分布式限流工具,如阿里开源的 Sentinel 就是不错的工具,Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 一如既往,文章中的代码存放在:github.com/niumoo/JavaNotes 参考Redis INCR:https://redis.io/commands/incr Rate Limiting Wikipedia:https://en.wikipedia.org/wiki/Rate_limiting SpringBoot Redis:https://www.cnblogs.com/lenve/p/10965667.html 订阅可以微信搜一搜程序猿阿朗或访问未读代码博客阅读。 本文 Github.com/niumoo/JavaNotes 已经收录,欢迎Star。 | petite-vue源码剖析-双向绑定`v-model`的工作原理 Posted: 14 Mar 2022 12:03 AM PDT 前言双向绑定v-model 不仅仅是对可编辑HTML元素(select , input , textarea 和附带[contenteditable=true] )同时附加v-bind 和v-on ,而且还能利用通过petite-vue附加给元素的_value 、_trueValue 和_falseValue 属性提供存储非字符串值的能力。 深入v-model 工作原理export const model: Directive< HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement > = ({ el, exp, get, effect, modifers }) => { const type = el.type // 通过`with`对作用域的变量/属性赋值 const assign = get(`val => { ${exp} = val }`) // 若type为number则默认将值转换为数字 const { trim, number = type ==== 'number'} = modifiers || {} if (el.tagName === 'select') { const sel = el as HTMLSelectElement // 监听控件值变化,更新状态值 listen(el, 'change', () => { const selectedVal = Array.prototype.filter .call(sel.options, (o: HTMLOptionElement) => o.selected) .map((o: HTMLOptionElement) => number ? toNumber(getValue(o)) : getValue(o)) assign(sel.multiple ? selectedVal : selectedVal[0]) }) // 监听状态值变化,更新控件值 effect(() => { value = get() const isMultiple = sel.muliple for (let i = 0, l = sel.options.length; i < i; i++) { const option = sel.options[i] const optionValue = getValue(option) if (isMulitple) { // 当为多选下拉框时,入参要么是数组,要么是Map if (isArray(value)) { option.selected = looseIndexOf(value, optionValue) > -1 } else { option.selected = value.has(optionValue) } } else { if (looseEqual(optionValue, value)) { if (sel.selectedIndex !== i) sel.selectedIndex = i return } } } }) } else if (type === 'checkbox') { // 监听控件值变化,更新状态值 listen(el, 'change', () => { const modelValue = get() const checked = (el as HTMLInputElement).checked if (isArray(modelValue)) { const elementValue = getValue(el) const index = looseIndexOf(modelValue, elementValue) const found = index !== -1 if (checked && !found) { // 勾选且之前没有被勾选过的则加入到数组中 assign(modelValue.concat(elementValue)) } else if (!checked && found) { // 没有勾选且之前已勾选的排除后在重新赋值给数组 const filered = [...modelValue] filteed.splice(index, 1) assign(filtered) } // 其它情况就啥都不干咯 } else { assign(getCheckboxValue(el as HTMLInputElement, checked)) } }) // 监听状态值变化,更新控件值 let oldValue: any effect(() => { const value = get() if (isArray(value)) { ;(el as HTMLInputElement).checked = looseIndexOf(value, getValue(el)) > -1 } else if (value !== oldValue) { ;(el as HTMLInputElement).checked = looseEqual( value, getCheckboxValue(el as HTMLInputElement, true) ) } oldValue = value }) } else if (type === 'radio') { // 监听控件值变化,更新状态值 listen(el, 'change', () => { assign(getValue(el)) }) // 监听状态值变化,更新控件值 let oldValue: any effect(() => { const value = get() if (value !== oldValue) { ;(el as HTMLInputElement).checked = looseEqual(value, getValue(el)) } }) } else { // input[type=text], textarea, div[contenteditable=true] const resolveValue = (value: string) => { if (trim) return val.trim() if (number) return toNumber(val) return val } // 监听是否在输入法编辑器(input method editor)输入内容 listen(el, 'compositionstart', onCompositionStart) listen(el, 'compositionend', onCompositionEnd) // change事件是元素失焦后前后值不同时触发,而input事件是输入过程中每次修改值都会触发 listen(el, modifiers?.lazy ? 'change' : 'input', () => { // 元素的composing属性用于标记是否处于输入法编辑器输入内容的状态,如果是则不执行change或input事件的逻辑 if ((el as any).composing) return assign(resolveValue(el.value)) }) if (trim) { // 若modifiers.trim,那么当元素失焦时马上移除值前后的空格字符 listen(el, 'change', () => { el.value = el.value.trim() }) } effect(() => { if ((el as any).composing) { return } const curVal = el.value const newVal = get() // 若当前元素处于活动状态(即得到焦点),并且元素当前值进行类型转换后值与新值相同,则不用赋值; // 否则只要元素当前值和新值类型或值不相同,都会重新赋值。那么若新值为数组[1,2,3],赋值后元素的值将变成[object Array] if (document.activeElement === el && resolveValue(curVal) === newVal) { return } if (curVal !== newVal) { el.value = newVal } }) } } // v-bind中使用_value属性保存任意类型的值,在v-modal中读取 const getValue = (el: any) => ('_value' in el ? el._value : el.value) const getCheckboxValue = ( el: HTMLInputElement & {_trueValue?: any, _falseValue?: any}, // 通过v-bind定义的任意类型值 checked: boolean // checkbox的默认值是true和false ) => { const key = checked ? '_trueValue' : '_falseValue' return key in el ? el[key] : checked } const onCompositionStart = (e: Event) => { // 通过自定义元素的composing元素,用于标记是否在输入法编辑器中输入内容 ;(e.target as any).composing = true } const onCompositionEnd = (e: Event) => { const target = e.target as any if (target.composing) { // 手动触发input事件 target.composing = false trigger(target, 'input') } } const trigger = (el: HTMLElement, type: string) => { const e = document.createEvent('HTMLEvents') e.initEvent(type, true, true) el.dispatchEvent(e) }
compositionstart 和compositionend 是什么?
compositionstart 是开始在输入法编辑器上输入字符触发,而compositionend 则是在输入法编辑器上输入字符结束时触发,另外还有一个compositionupdate 是在输入法编辑器上输入字符过程中触发。
当我们在输入法编辑器敲击键盘时会按顺序执行如下事件:
compositionstart -> (compositionupdate -> input )+ -> compositionend -> 当失焦时触发change 当在输入法编辑器上输入ri 后按空格确认日 字符,则触发如下事件
compositionstart(data="") -> compositionupdate(data="r") -> input -> compositionupdate(data="ri") -> input -> compositionupdate(data="日") -> input -> compositionend(data="日") 由于在输入法编辑器上输入字符时会触发input 事件,所以petite-vue中通过在对象上设置composing 标识是否执行input 逻辑。 事件对象属性如下: readonly target: EventTarget // 指向触发事件的HTML元素 readolny type: DOMString // 事件名称,即compositionstart或compositionend readonly bubbles: boolean // 事件是否冒泡 readonly cancelable: boolean // 事件是否可取消 readonly view: WindowProxy // 当前文档对象所属的window对象(`document.defaultView`) readonly detail: long readonly data: DOMString // 最终填写到元素的内容,compositionstart为空,compositionend事件中能获取如"你好"的内容 readonly locale: DOMString
编码方式触发事件DOM Level2的事件中包含HTMLEvents, MouseEvents、MutationEvents和UIEvents,而DOM Level3则增加如CustomEvent等事件类型。 enum EventType { // DOM Level 2 Events UIEvents, MouseEvents, // event.initMouseEvent MutationEvents, // event.initMutationEvent HTMLEvents, // event.initEvent // DOM Level 3 Events UIEvent, MouseEvent, // event.initMouseEvent MutationEvent, // event.initMutationEvent TextEvent, // TextEvents is also supported, event.initTextEvent KeyboardEvent, // KeyEvents is also supported, use `new KeyboardEvent()` to create keyboard event CustomEvent, // event.initCustomEvent Event, // Basic events module, event.initEvent }
- HTMLEvents包含
abort , blur , change , error , focus , load , reset , resize , scroll , select , submit , unload , input - UIEvents包含
DOMActive , DOMFocusIn , DOMFocusOut , keydown , keypress , keyup - MouseEvents包含
click , mousedown , mousemove , mouseout , mouseover , mouseup - MutationEvents包含
DOMAttrModified ,DOMNodeInserted ,DOMNodeRemoved ,DOMCharacterDataModified ,DOMNodeInsertedIntoDocument ,DOMNodeRemovedFromDocument ,DOMSubtreeModified
创建和初始化事件对象MouseEvent方法1 const e: Event = document.createEvent('MouseEvent') e.initMouseEvent( type: string, bubbles: boolean, cancelable: boolean, view: AbstractView, // 指向与事件相关的视图,一般为document.defaultView detail: number, // 供事件回调函数使用,一般为0 screenX: number, // 相对于屏幕的x坐标 screenY: number, // 相对于屏幕的Y坐标 clientX: number, // 相对于视口的x坐标 clientY: number, // 相对于视口的Y坐标 ctrlKey: boolean, // 是否按下Ctrl键 altKey: boolean, // 是否按下Ctrl键 shiftKey: boolean, // 是否按下Ctrl键 metaKey: boolean, // 是否按下Ctrl键 button: number, // 按下按个鼠标键,默认为0.0左,1中,2右 relatedTarget: HTMLElement // 指向于事件相关的元素,一般只有在模拟mouseover和mouseout时使用 )
方法2 const e: Event = new MouseEvent('click', { bubbles: false, // ...... })
KeyboardEventconst e = new KeyboardEvent( typeArg: string, // 如keypress { ctrlKey: true, // ...... } )
https://developer.mozilla.org... Event的初始方法/** * 选项的属性 * @param {string} name - 事件名称, 如click,input等 * @param {boolean} [cancelable=false] - 指定事件是否可冒泡 * @param {boolean} [cancelable=false] - 指定事件是否可被取消 * @param {boolean} [composed=false] - 指定事件是否会在Shadow DOM根节点外触发事件回调函数 */ const e = new Event('input', { name: string, bubbles: boolean = false, cancelable: boolean = false, composed: boolean = false })
CustomEvent方法1 const e: Event = document.createEvent('CustomEvent') e.initMouseEvent( type: string, bubbles: boolean, cancelable: boolean, detail: any )
方法2 /** * 选项的属性 * @param {string} name - 事件名称, 如click,input等,可随意定义 * @param {boolean} [cancelable=false] - 指定事件是否可冒泡 * @param {boolean} [cancelable=false] - 指定事件是否可被取消 * @param {any} [detail=null] - 事件初始化时传递的数据 */ const e = new CustomEvent('hi', { name: string, bubbles: boolean = false, cancelable: boolean = false, detail: any = null })
HTMLEventsconst e: Event = document.createEvent('HTMLEvents') e.initMouseEvent( type: string, bubbles: boolean, cancelable: boolean )
添加监听和发布事件element.addEventListener(type: string) element.dispatchEvent(e: Event)
针对petite-vue进行分析const onCompositionEnd = (e: Event) => { const target = e.target as any if (target.composing) { // 手动触发input事件 target.composing = false trigger(target, 'input') } } const trigger = (el: HTMLElement, type: string) => { const e = document.createEvent('HTMLEvents') e.initEvent(type, true, true) el.dispatchEvent(e) }
当在输入法编辑器操作完毕后会手动触发input事件,但当事件绑定修饰符设置为lazy 后并没有绑定input 事件回调函数,此时在输入法编辑器操作完毕后并不会自动更新状态,我们又有机会可以贡献代码了:) // change事件是元素失焦后前后值不同时触发,而input事件是输入过程中每次修改值都会触发 listen(el, modifiers?.lazy ? 'change' : 'input', () => { // 元素的composing属性用于标记是否处于输入法编辑器输入内容的状态,如果是则不执行change或input事件的逻辑 if ((el as any).composing) return assign(resolveValue(el.value)) })
外番:IE的事件模拟var e = document.createEventObject() e.shiftKey = false e.button = 0 document.getElementById('click').fireEvent('onclick', e)
总结整合LayUI等DOM-based框架时免不了使用this.$ref 获取元素实例,下一篇《petite-vue源码剖析-ref的工作原理》我们一起来探索吧! 尊重原创,转载请注明来自:https://www.cnblogs.com/fsjoh... 肥仔John | Gitlab CI/CD教程及npm包构建发布实战 Posted: 12 Mar 2022 02:03 AM PST !!!实践过程中请留意文档版本号跟你实际使用Gitlab的版本号 👉🏻中文文档-14.8-pre 👉🏻Gitlab版本—12.9 前置知识- yaml语法,教程👉🏻YAML 入门教程
- Docker相关知识,教程👉🏻Docker教程
- linux命令,教程👉🏻Linux 命令大全
自定义配置目录默认配置文件目录是在mono repo 的根目录,文件名为.gitlab-ci.yml 。 若需要自定义设置CI脚本文件的路径,如下: 流水线配置.gitlab-ci.yml 文件中对流水线配置大致可以分为2个环节:
- 全局配置,运行在单个
stage 之前或者之后。 - 单个
stage 配置
结构大致可以如下: # 指定脚本执行的镜像环境,如下为node环境为14.17.1 image: node:14.17.1 # 单个job执行之前执行 before_script: - echo '====== 准备构建中 =========' # 配置单个stage的执行顺序,串行 stages: - install - build # 单个stage配置 # 安装依赖 npm_install: only: - master stage: install script: - yarn - ls -al # 单个stage配置 # 构建 webpack_build: only: - master stage: build script: - yarn build # 单个job全部执行完之后执行 after_script: - echo "====== 构建结束 ========="
重要概念Pipeline流水线,一次流水线相当于一次构建任务,里面可以包含多个阶段,比如install -> eslint -> build -> deploy等流程 ; stages表示构建阶段,每个stage串行同步执行。一旦有一个stage中的一个job失败了,那么下一个stage的任务便不会执行。如果当前stage定义了多个任务,那么其中一个任务失败,另外一个任务还是会被继续执行。但是只有当所有 stages 成功完成后,该构建任务 (Pipeline) 才算成功。 jobsjob表示某个stage里面执行的工作 ,一个stage里面可以定义多个job 。 jobs有如下特点 : - 相同 stage 中的jobs 会并行执行
- 相同 stage 中的 jobs 都执行成功时,该 stage 才会成功
- 如果任何一个job 失败,那么该 stage 失败,即该构建任务 (Pipeline) 失败
gitlab runner执行构建任务的一个服务,里面包含了持续集成的的环境,一般由docker创建。它可以在不同的主机上部署,也可以在同一个主机上设置多个gitlab-runner ,还可以根据不同的环境设置不同的环境,比如我们需要区分研发环境,测试环境以及正式环境等。 关键字imageCI/CD脚本运行环境的docker镜像,镜像就是一种文件存储形式,可以理解为是一个环境的集合,内含多种文件。如指定node环境镜像: # 最新版本node环境 image: node:@latest
tags指定gitlab 在执行脚本时使用哪个runner。 before_script在单个stage 执行之前执行的脚本内容,内容以数组形式配置,如上例子中: before_script: - echo '====== 准备构建中 ========='
stagesCI允许我们进行自定义的流水线阶段配置,可以将一个流水线拆分为多个阶段(stage ),stages会串行执行。 stages: - install - build
script执行脚本,脚本内容以数组形式配置。如上例子中stage为npm_install阶段执行的脚本为: script: - yarn - ls -al
先执行yarn命令安装依赖,结束后查看了当前目录下文件及目录的具体信息,是个串行执行的过程。 cache缓存多个流水线任务之间共用的文件和目录,缓存相关概念下文详情讲述。 only & except设置流水线任务执行时机:使用 only 来定义job 何时运行,使用 except 定义job 不 运行的时间。 指定分支触发执行时机 job: only: - branches@gitlab-org/gitlab except: - main@gitlab-org/gitlab - /^release/.*$/@gitlab-org/gitlab
此示例为 gitlab-org/gitlab 上的所有分支运行 job ,除了 main 和以 release/ 开头的分支。 在合并请求时触发 job1: script: - echo "This job runs in merge request pipelines" only: - merge_requests
在push的时候触发 job1: script: - echo "branch push" only: - pushes
手动触发 在Gitlab Runner/pipeline里面点击run pipeline时触发 job1: only: - web
根据git提交消息或者判断分支触发执行时机 build: script: - yarn build except: variables: - $CI_COMMIT_MESSAGE =~ /test/ || $CI_COMMIT_BRANCH == "main"
git commit 消息为"test"的push跟提交分支为"main"的push不触发此job。 根据文件修改判断执行时机 build: script: yarn build except: changes: - "*.md"
只要有md文件修改就不执行此job。
retryjob重试次数,默认为0,最大重试次数为2,其中when 可设置在特定失败原因的情况下执行。 rules:if此字段可以在单个流水线job或者workflow字段下进行配置。 rules 关键词下可以进行if语句配置,如果if满足的话可执行某些自定义配置。
rules: - if: $CI_COMMIT_REF_NAME =~ /feature/
注意: only & except 和rules:if 都是用来决定单个job执行时机的,在配置时只能存在一个,否则会报错。 workflow和rules 配合用来控制流水线的执行动作,在最外层进行配置,workflow: rules 接受这些关键字: if :检查此规则以确定何时运行流水线。when :指定当if为 true 时要做什么。
- 要运行流水线,请设置为
always 。 - 要阻止流水线运行,请设置为
never 。
variables :如果未定义,则使用在别处定义的变量。适用版本13.11 ~14.0
当没有规则为 true 时,流水线不会运行。 以下示例中,前两天规则都匹配到不执行时机,当else时,流水线执行。 workflow: rules: - if: '$CI_PIPELINE_SOURCE == "schedule"' when: never - if: '$CI_PIPELINE_SOURCE == "push"' when: never - when: always
when控制上一个stage成功或者失败时,当前stage的行为。 on_success (默认值): 上一个stage成功了才会执行当阶段任务,或者之前失败的任务配置了allow_failure: true 。on_failure :只有上一个阶段任务失败了才会执行当前任务。always :无论上一个阶段的jobs状态如何,都会触发当前阶段的任务。never :不运行当前任务。manual :在gitlab网页中手动点击触发。
模块化使用关键字include 引入其他yml 文件中的配置。 include: - '/yml/job1_install.yml' - '/yml/job2_lint.yml' - '/yml/job3_build.yml' - '/yml/job4_deploy.yml'
缓存重要概念在 GitLab CI/CD 中,我们所使用的 runner 是以 docker 的形式运行不同的任务。普通的 cache 机制(即不指定URL,No URL provided, cache will not be downloaded from shared cache server. Instead a local version of cache will be extracted. ),其 cache 是存储在本地,所以如果两个 job 实际运行的位置是不在宿主机上的,其相互之间的缓存是无法共享的。 分布式缓存分布式缓存需要runner配置支持,开启后需要在cache中配置s3ServerAddress、s3BucketName等信息进行缓存跨runner共享。 缓存路径在配置cache时,paths 跟files 的文件/目录都是以项目的根目录为相对位置的,在store cache的时候也是以项目名区分缓存路径的,应用缓存的时候会在项目下配合key值去应用缓存,即使是分布式缓存也是按照这个策略。 缓存文件信息上会有最后更新时间(重要信息)、文件权限、cache 中设置的key 等,其中更新时间跟缓存策略有联系,如果恶意篡改服务器时间,可能会出现依赖前后不一致导致打出来的包不符合预期的情况。 缓存绑定文件缓存绑定到当前版本的文件。当这些文件之一发生变化时,将计算一个新的缓存键并创建一个新的缓存,如下: cache-job: script: - echo "This job uses a cache." cache: key: files: - Gemfile.lock - package.json paths: - vendor/ruby - node_modules
此时的 key 是根据最近更改了每个列出的文件的提交计算得出的 SHA 。如果在任何提交中都没有更改任何文件,则key 就是默认值 default 。 多文件缓存cache可以配置多个key ,适用于13.10 ~ 13.12版本,其他版本可以在key 下的files 和paths 配置多个路径/文件来实现。 禁用缓存使用cache: {} 来禁用缓存。 继承缓存缓存配置可复用的情况下使用继承写法,可以在当前job下覆盖(重写)某个策略或者设置优先级 cache: &global_cache key: $CI_COMMIT_REF_SLUG paths: - node_modules/ - public/ - vendor/ policy: pull-push job: cache: # 继承全局缓存 <<: *global_cache # 重写缓存策略 policy: pull
回退缓存键13.4版本以上可应用回退缓存键,功能类似缓存备份。你可以使用 CACHE_FALLBACK_KEY 变量来指定一个备份缓存key,他会在你指定key的缓存不存在时去查找应用备份缓存。 variables: CACHE_FALLBACK_KEY: fallback-key job1: script: - echo cache: key: "$CI_COMMIT_REF_SLUG" paths: - binaries/
手动清除缓存您可以在 GitLab UI 中清除缓存: - 在顶部栏上,选择 菜单 > 项目 并找到您的项目。
- 在左侧边栏上,选择 CI/CD > 流水线 页面。
- 在右上角,选择 清除 Runner 缓存。
在下一次提交时,您的 CI/CD 作业使用新的缓存。 实战:构建发布组件库到npm仓库编写.gitlab-ci.yml 基本流程,如下: image: node:14.17.1 before_script: - echo '====== 准备构建中 =========' stages: - install - lint - build - deploy ### 配置缓存 cache: key: files: - package.json - packages/ghost-weapp-ui/package.json paths: - node_modules/ - packages/ghost-weapp-ui/node_modules/ ### 直接缓存.npm,.npm中缓存了所有依赖,因为gitlab缓存是分项目的,所以两种方法个人觉得没有什么区别 # - .npm/ # eslint检测 job_lint: only: - master stage: lint before_script: - echo 'eslint检测' - ls -a script: - cd packages/ghost-weapp-ui - ls -a - yarn lint - echo 'eslint检测完成' retry: 0 when: 'on_success' # 安装依赖 job_install: only: - master stage: install before_script: - echo '安装依赖' script: - yarn config set registry https://registry.npm.taobao.org/ - yarn install - cd packages/ghost-weapp-ui - ls -a - yarn install - ls -a - echo '依赖安装完成' retry: 0 # 打包编译 job_build: only: - master stage: build before_script: - echo '开始打包' script: - cd packages/ghost-weapp-ui - ls -a - yarn - ls -a - yarn build - echo '构建完成' when: 'on_success' retry: 0 # 发布 job_deploy: only: - master stage: deploy before_script: - echo '更新补丁版本,准备发布' script: - cd packages/ghost-weapp-ui - node deploy.js ${CI_COMMIT_REF_NAME} when: 'on_success' retry: 0 after_script: - echo "====== 发布完成 ========="
编写部署脚本,其中主要是模拟npm login 流程替换为使用token进行登录并且执行npm publish ,发布到npm的流程。 const fs = require('fs') const path = require('path') const os = require('os') const { exec } = require('child_process') // 替换为自己npm账号的authToken // token获取方法:vim ~/.npmrc const npmrcText = `registry=https://registry.npmjs.org/ home=https://www.npmjs.org //registry.npmjs.org/:_authToken=${authToken} ` // 获取命令中第三个参数,此例子中为'master' // node deploy.js ${CI_COMMIT_REF_NAME}, 分支名为master const env = process.argv[2] // 拼接命令,执行npm publish,打tag function deploy() { fs.writeFileSync(path.resolve(os.homedir(), '.npmrc'), npmrcText) const argsArray = ['publish'].concat(['--tag', env === 'master' ? 'latest' : 'beta']) execa('npm', argsArray); } async function execa(a, arry = []) { return new Promise((resolve, reject) => { exec(`${a} ${arry.join(' ')}`, (err, stdout, stderr) => { if (err) { console.error(err); reject(err); } resolve(stdout) }) }) } deploy();
执行结果: 收到npm发布成功反馈邮件: 以上就是gitlab CI/CD的相关知识点以及实战发布npm包的示例,感谢阅读! | 走进开源项目 - urlcat 源码分析 Posted: 13 Mar 2022 09:31 AM PDT 在《走进开源项目 - urlcat》中,对项目整体进行了分析,对如何做开源也有了进一步的了解,该篇再深入研究下 urlcat 源码。 该项目到底做了什么? // 常规写法一 const API_URL = 'https://api.example.com/'; function getUserPosts(id, blogId, limit, offset) { const requestUrl = `${API_URL}/users/${id}/blogs/${blogId}/posts?limit=${limit}&offset=${offset}`; // send HTTP request } // 常规写法二 const API_URL = 'https://api.example.com/'; function getUserPosts(id, blogId, limit, offset) { const escapedId = encodeURIComponent(id); const escapedBlogId = encodeURIComponent(blogId); const path = `/users/${escapedId}/blogs/${escapedBlogId}`; const url = new URL(path, API_URL); url.search = new URLSearchParams({ limit, offset }); const requestUrl = url.href; // send HTTP request } // 使用 urlcat 之后的写法 const API_URL = 'https://api.example.com/'; function getUserPosts(id, limit, offset) { const requestUrl = urlcat(API_URL, '/users/:id/posts', { id, limit, offset }); // send HTTP request }
源码共 267 行,其中注释占了近 110 ,代码只有 157 行。注释跟代码接近 1:1 ,接下来我们逐段分析。 第一段import qs, { IStringifyOptions } from 'qs'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type ParamMap = Record<string, any>; export type UrlCatConfiguration = Partial<Pick<IStringifyOptions, 'arrayFormat'> & { objectFormat: Partial<Pick<IStringifyOptions, 'format'>> }>
该项目是在 qs 项目的基础上并使用 typescript 进行开发,其中定义了 2 个类型,有几个不太了解知识点 type 、 Recode 、Partial 和 Pick 。 interface 与 type 的区别- 相同点:都可以描述对象或者函数,且可以使用
extends 进行拓展 不同点: type 可以声明基本类型别名,联合类型,和元组等类型,但 interface 不行 // 基本类型别名 type Name = string | number; // 联合类型 interface Common { name: string; } interface Person<T> extends Common { age: T; sex: string; } type People<T> = { age: T; sex: string; } & Common; type P1 = Person<number> | People<number>; // 元组 type P2 = [Person<number>, People<number>];
跟 typeof 结合使用 const name = "小明"; type T= typeof name;
Record 的用途Reacord 是 TypeScript 的一种工具类。
// 常规写法 interface Params { [name: string]: any; } // 高级写法 type Params = Recode<string, any>
Partial 的用途将传入的属性变为可选项 interface DataModel { name: string age: number address: string } let store: DataModel = { name: '', age: 0, address: '' } function updateStore ( store: DataModel, payload: Partial<DataModel> ):DataModel { return { ...store, ...payload } } store = updateStore(store, { name: 'lpp', age: 18 })
Pick 的用途从类型 Type 中,挑选一组属性组成一个新的类型返回。这组属性由 Keys 限定, Keys 是字符串或者字符串并集。 interface Person { name: string age: number id: string } // 幼儿没有id type Toddler = Pick<Person, 'name' | 'age'>
第二段/** * Builds a URL using the base template and specified parameters. * * @param {String} baseTemplate a URL template that contains zero or more :params * @param {Object} params an object with properties that correspond to the :params * in the base template. Unused properties become query params. * * @returns {String} a URL with path params substituted and query params appended * * @example * ```ts * urlcat('http://api.example.com/users/:id', { id: 42, search: 'foo' }) * // -> 'http://api.example.com/users/42?search=foo * ``` */ export default function urlcat(baseTemplate: string, params: ParamMap): string; /** * Concatenates the base URL and the path specified using '/' as a separator. * If a '/' occurs at the concatenation boundary in either parameter, it is removed. * * @param {String} baseUrl the first part of the URL * @param {String} path the second part of the URL * * @returns {String} the result of the concatenation * * @example * ```ts * urlcat('http://api.example.com/', '/users') * // -> 'http://api.example.com/users * ``` */ export default function urlcat(baseUrl: string, path: string): string; /** * Concatenates the base URL and the path specified using '/' as a separator. * If a '/' occurs at the concatenation boundary in either parameter, it is removed. * Substitutes path parameters with the properties of the @see params object and appends * unused properties in the path as query params. * * @param {String} baseUrl the first part of the URL * @param {String} path the second part of the URL * @param {Object} params Object with properties that correspond to the :params * in the base template. Unused properties become query params. * * @returns {String} URL with path params substituted and query params appended * * @example * ```ts * urlcat('http://api.example.com/', '/users/:id', { id: 42, search: 'foo' }) * // -> 'http://api.example.com/users/42?search=foo * ``` */ export default function urlcat( baseUrl: string, pathTemplate: string, params: ParamMap ): string; /** * Concatenates the base URL and the path specified using '/' as a separator. * If a '/' occurs at the concatenation boundary in either parameter, it is removed. * Substitutes path parameters with the properties of the @see params object and appends * unused properties in the path as query params. * * @param {String} baseUrl the first part of the URL * @param {String} path the second part of the URL * @param {Object} params Object with properties that correspond to the :params * in the base template. Unused properties become query params. * @param {Object} config urlcat configuration object * * @returns {String} URL with path params substituted and query params appended * * @example * ```ts * urlcat('http://api.example.com/', '/users/:id', { id: 42, search: 'foo' }, {objectFormat: {format: 'RFC1738'}}) * // -> 'http://api.example.com/users/42?search=foo * ``` */ export default function urlcat( baseUrlOrTemplate: string, pathTemplateOrParams: string | ParamMap, maybeParams: ParamMap, config: UrlCatConfiguration ): string; export default function urlcat( baseUrlOrTemplate: string, pathTemplateOrParams: string | ParamMap, maybeParams: ParamMap = {}, config: UrlCatConfiguration = {} ): string { if (typeof pathTemplateOrParams === 'string') { const baseUrl = baseUrlOrTemplate; const pathTemplate = pathTemplateOrParams; const params = maybeParams; return urlcatImpl(pathTemplate, params, baseUrl, config); } else { const baseTemplate = baseUrlOrTemplate; const params = pathTemplateOrParams; return urlcatImpl(baseTemplate, params, undefined, config); } }
这部分代码是利用 TypeScript 定义重载函数类型,采用连续多个重载声明 + 一个函数实现的方式来实现,其作用是为了保证在调用该函数时,函数的参数及返回值都要兼容所有的重载。 例如下图,第三个参数类型在重载函数类型中并不存在。 第三段以下代码是核心,作者通过职责分离的方式,将核心方法代码简化。 // 核心方法 function urlcatImpl( pathTemplate: string, params: ParamMap, baseUrl: string | undefined, config: UrlCatConfiguration ) { // 第一步 path('/users/:id/posts', { id: 1, limit: 30 }) 返回 "/users/1/posts" 和 limit: 30 const { renderedPath, remainingParams } = path(pathTemplate, params); // 第二步 移除 Null 或者 Undefined 属性 const cleanParams = removeNullOrUndef(remainingParams); // 第三步 {limit: 30} 转 limit=30 const renderedQuery = query(cleanParams, config); // 第四步 拼接返回 /users/1/posts?limit=30 const pathAndQuery = join(renderedPath, '?', renderedQuery); // 第五步 当 baseUrl 存在时,执行完整 url 拼接 return baseUrl ? joinFullUrl(renderedPath, baseUrl, pathAndQuery) : pathAndQuery; }
总结做开源并不一定要造个更好的轮子,但可以让这个轮子变得更好。通过该项目,也发现自己在 TypeScript 方面的不足,继续学习,再接再厉。 参考文章拓展阅读 | 什么是 LRU 算法? Posted: 13 Mar 2022 06:02 PM PDT 缓存 是我们写代码过程中常用的一种手段,是一种空间换时间的做法。就拿我们经常使用的 HTTP 协议,其中也存在强缓存和协商缓存两种缓存方式。当我们打开一个网站的时候,浏览器会查询该请求的响应头,通过判断响应头中是否有 Cache-Control 、Last-Modified 、ETag 等字段,来确定是否直接使用之前下载的资源缓存,而不是重新从服务器进行下载。
下面就是当我们访问百度时,某些资源命中了协商缓存,服务端返回 304 状态码,还有一部分资源命中了强缓存,直接读取了本地缓存。 但是,缓存并不是无限制的,会有大小的限制。无论是我们的 cookie (不同浏览器有所区别,一般在 4KB 左右),还是 localStorage (和 cookie 一样,不同浏览器有所区别,有些浏览器为 5MB ,有些浏览器为 10MB ),都会有大小限制。 这个时候就需要涉及到一种算法,需要将超出大小限制的缓存进行淘汰,一般的规则是淘汰掉最近没有被访问到的缓存,也就是今天要介绍的主角:LRU (Least recently used :最近最少使用)。当然除了 LRU,常见的缓存淘汰还有 FIFO(first-in, first-out :先进先出) 和 LFU(Least frequently used :最少使用)。 什么是 LRU?LRU (Least recently used :最近最少使用)算法在缓存写满的时候,会根据所有数据的访问记录,淘汰掉未来被访问几率最低的数据。也就是说该算法认为,最近被访问过的数据,在将来被访问的几率最大。 为了方便理解 LRU 算法的全流程,画了一个简单的图: - 假设我们有一块内存,一共能够存储 5 数据块;
- 依次向内存存入A、B、C、D、E,此时内存已经存满;
- 再次插入新的数据时,会将在内存存放时间最久的数据A淘汰掉;
- 当我们在外部再次读取数据B时,已经处于末尾的B会被标记为活跃状态,提到头部,数据C就变成了存放时间最久的数据;
- 再次插入新的数据G,存放时间最久的数据C就会被淘汰掉;
算法实现下面通过一段简单的代码来实现这个逻辑。 class LRUCache { list = [] // 用于标记先后顺序 cache = {} // 用于缓存所有数据 capacity = 0 // 缓存的最大容量 constructor (capacity) { // 存储 LRU 可缓存的最大容量 this.capacity = capacity } }
基本的结构如上所示,LRU需要实现的就是两个方法:get 和 put 。 class LRUCache { // 获取数据 get (key) { } // 存储数据 put (key, value) { } }
我们现在看看如何进行数据的存储: class LRUCache { // 存储数据 put (key, value) { // 存储之前需要先判断长度是否达到上限 if (this.list.length >= this.capacity) { // 由于每次存储后,都会将 key 放入 list 最后, // 所以,需要取出第一个 key,并删除cache中的数据。 const latest = this.list.shift() delete this.cache[latest] } // 写入缓存 this.cache[key] = value // 写入缓存后,需要将 key 放入 list 的最后 this.list.push(key) } }
然后,在每次获取数据时,都需要更新 list ,将当前获取的 key 放到 list 的最后。 class LRUCache { // 获取数据 get (key) { if (this.cache[key] !== undefined) { // 如果 key 对应的缓存存在 // 在返回缓存之前,需要重新激活 key this.active(key) return this.cache[key] } return undefined } // 重新激活key,将指定 key 移动到 list 最后 active (key) { // 先将 key 在 list 中删除 const idx = this.list.indexOf(key) if (idx !== -1) { this.list.splice(idx, 1) } // 然后将 key 放到 list 最后面 this.list.push(key) } }
这个时候,其实还没有完全实现,因为除了 get 操作,put 操作也需要将对应的 key 重新激活。 class LRUCache { // 存储数据 put (key, value) { if (this.cache[key]) { // 如果该 key 之前存在,将 key 重新激活 this.active(key) this.cache[key] = value // 而且此时缓存的长度不会发生变化 // 所以不需要进行后续的长度判断,可以直接返回 return } // 存储之前需要先判断长度是否达到上限 if (this.list.length >= this.capacity) { // 由于每次存储后,都会将 key 放入 list 最后, // 所以,需要取出第一个 key,并删除cache中的数据。 const latest = this.list.shift() delete this.cache[latest] } // 写入缓存 this.cache[key] = value // 写入缓存后,需要将 key 放入 list 的最后 this.list.push(key) } }
可能会有人觉得这种算法在前端没有什么应用场景,说起来,在 Vue 的内置组件 keep-alive 中就使用到了 LRU 算法。 后续应该还会继续介绍一下 LFU 算法,敬请期待…… | Spring Cloud Ribbon 中的 7 种负载均衡策略 Posted: 13 Mar 2022 05:49 PM PDT 负载均衡通器常有两种实现手段,一种是服务端负载均衡器,另一种是客户端负载均衡器,而我们今天的主角 Ribbon 就属于后者——客户端负载均衡器。 服务端负载均衡器的问题是,它提供了更强的流量控制权,但无法满足不同的消费者希望使用不同负载均衡策略的需求,而使用不同负载均衡策略的场景确实是存在的,所以客户端负载均衡就提供了这种灵活性。 然而客户端负载均衡也有其缺点,如果配置不当,可能会导致服务提供者出现热点,或者压根就拿不到任何服务的情况,所以我们本文就来了解一下这 7 种内置负载均衡策略的具体规则。 Ribbon 介绍Ribbon 是 Spring Cloud 技术栈中非常重要的基础框架,它为 Spring Cloud 提供了负载均衡的能力,比如 Fegin 和 OpenFegin 都是基于 Ribbon 实现的,就连 Nacos 中的负载均衡也使用了 Ribbon 框架。 Ribbon 框架的强大之处在于,它不仅内置了 7 种负载均衡策略,同时还支持用户自定义负载均衡策略,所以其开放性和便利性也是它得以流行的主要原因。 服务端负载均衡器和客户端负载均衡器的区别如下图所示:
客户端负载均衡器的实现原理是通过注册中心,如 Nacos,将可用的服务列表拉取到本地(客户端),再通过客户端负载均衡器(设置的负载均衡策略)获取到某个服务器的具体 ip 和端口,然后再通过 Http 框架请求服务并得到结果,其执行流程如下图所示: 负载均衡设置以 Nacos 中的 Ribbon 负载均衡设置为例,在配置文件 application.yml 中设置如下配置即可: springcloud-nacos-provider: # nacos中的服务id ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #设置负载均衡策略
因为 Nacos 中已经内置了 Ribbon,所以在实际项目开发中无需再添加 Ribbon 依赖了,这一点我们在 Nacos 的依赖树中就可以看到,如下图所示:
Ribbon 默认的负载均衡策略是轮询模式,我们配置 3 个服务提供者的执行结果如下图所示:
然后,我们再将 Ribbon 负载均衡策略设置为随机模式,配置内容如下: springcloud-nacos-provider: # nacos中的服务id ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置随机负载均衡
重启客户端,执行结果如下图所示:
7种负载均衡策略1.轮询策略轮询策略:RoundRobinRule,按照一定的顺序依次调用服务实例。比如一共有 3 个服务,第一次调用服务 1,第二次调用服务 2,第三次调用服务3,依次类推。 此策略的配置设置如下: springcloud-nacos-provider: # nacos中的服务id ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #设置负载均衡
2.权重策略权重策略:WeightedResponseTimeRule,根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。 它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。 此策略的配置设置如下: springcloud-nacos-provider: # nacos中的服务id ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
3.随机策略随机策略:RandomRule,从服务提供者的列表中随机选择一个服务实例。 此策略的配置设置如下: springcloud-nacos-provider: # nacos中的服务id ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置负载均衡
4.最小连接数策略最小连接数策略:BestAvailableRule,也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。 此策略的配置设置如下: springcloud-nacos-provider: # nacos中的服务id ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #设置负载均衡
5.重试策略重试策略:RetryRule,按照轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null。 此策略的配置设置如下: ribbon: ConnectTimeout: 2000 # 请求连接的超时时间 ReadTimeout: 5000 # 请求处理的超时时间 springcloud-nacos-provider: # nacos 中的服务 id ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置负载均衡
6.可用性敏感策略可用敏感性策略:AvailabilityFilteringRule,先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。 此策略的配置设置如下: springcloud-nacos-provider: # nacos中的服务id ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.AvailabilityFilteringRule
7.区域敏感策略区域敏感策略:ZoneAvoidanceRule,根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似。 此策略的配置设置如下: springcloud-nacos-provider: # nacos中的服务id ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule
项目源码https://gitee.com/mydb/spring-cloud-alibaba-example 总结Ribbon 为客户端负载均衡器,相比于服务端负载均衡器的统一负载均衡策略来说,它提供了更多的灵活性。Ribbon 内置了 7 种负载均衡策略:轮询策略、权重策略、随机策略、最小连接数策略、重试策略、可用性敏感策略、区域性敏感策略,并且用户可以通过继承 RoundRibbonRule 来实现自定义负载均衡策略。 是非审之于己,毁誉听之于人,得失安之于数。 公众号:Java中文社群 Java面试合集:https://gitee.com/mydb/interview
| |
No comments:
Post a Comment