環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
摘錄自2018年8月16日中央社報導
加拿大政府今天(16日)表示,將逐步限用和水生昆蟲及蜜蜂死亡有關連的2款類尼古丁農藥。環保人士認為這是一大勝利,但對販售殺蟲劑的企業而言卻是最新挫敗。
加拿大衛生署(Health Canada)病蟲害管制局(PMRA)表示,未來三到五年之間將逐步禁止在戶外使用賽速安(Thiamethoxam)及可尼丁(Clothianidin)。
加拿大衛生署這項措施將有90天諮詢期,最後在2019年底做出決定。
加拿大衛生署也計畫在今年底之前做出最終決定,是否逐步禁止第3種的類尼古丁農藥,拜耳生產的益達胺(imidacloprid)。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※南投搬家公司費用需注意的眉眉角角,別等搬了再說!
※新北清潔公司,居家、辦公、裝潢細清專業服務
※教你寫出一流的銷售文案?
摘錄自2018年8月16日中央社報導
環境部一名官員今天(16日)說,泰國將在6個月內禁止進口432種廢棄電子用品。中國今年禁止高科技廢棄物進口以來,泰國成為最新呼應的國家。
泰國下達禁令數週前,區域鄰國越南表示,將停止核發新執照給廢棄物進口,並將取締非法運送廢紙、塑膠和金屬。
泰國環境部一名高級官員今天告訴路透社,禁令涵蓋432種電子廢棄物,從電路板到老舊電視機和收音機零件等等,將在6個月內生效。
他並說,禁令是環境部長素拉薩(Surasak Kanchanarat)昨天主持會議時做成決定。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※幫你省時又省力,新北清潔一流服務好口碑
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!
使用Spring Bboot是快樂並且簡單的,不需要繁瑣的配置就能夠完成一套非常強大的應用。
Spring Boot 2.3.1 發佈於:2020/06/12,現在已經提交到 Spring 倉庫和 Maven 中央倉庫了。
這個版本包括 127 個 bug 修復、Spring Boot 文檔改進增強、依賴升級等,另外還新增了一些新特性:
•提供基於新的 Maven 坐標 com.oracle.database 對 Oracle JDBC driver 的依賴管理;
•優化 Spring Cloud 的 CachedRandomPropertySource 不能正確適配的問題;•限制使用定製的 YAML 類型;
•增強對 NoSuchMethodErrors 異常失敗分析,能显示基類從哪被加載的;•提供更佳的錯誤消息,如果 Docker 停止運行了;
•優化 SystemEnvironmentPropertyMapper 類;
•提供更佳的診斷信息,當構建 OCI 鏡像失敗時 Docker 響應的 500 錯誤;
•支持通過 alwaysUseFullPath=true 參數來配置 UrlPathHelper;•支持在 Elasticsearch URIs 中使用用戶信息;
•支持在 Spring WebFlux 框架中使用歡迎頁面;
這個小版本還增加了蠻多東西的,大家也沒有必要跟着版本走,可以根據需要進行升級。疫情也擋不住外國友人更新的熱情。
使用STS,可以去官方網站下載最新版。網站地址 https://Spring.io/tools/sts/ Spring Tool Suite™是基於eclipse開發的專門為Spring開發使用的工具包。
選擇Spring Starter Project,
輸入工程名 對應的Name 打包方式 對應的Packaging,可以選擇jar或者war的方式。
輸入組織名 對應的Group 輸入描述 對應的Description
輸入包名 對應的Package 點擊next,然後選擇web和mysql
這裏的版本用的是2.3.1 如果沒有本地maven庫或者私庫會下載很長時間。
進入 Chapter0301Application 添加
@RestController @SpringBootApplication public class Chapter0301Application { @RequestMapping("/") String home() { return "歡迎使用Spring Boot!"; } public static void main(String[] args) { SpringApplication.run(Chapter0301Application.class, args); } }
使用@RestController 相當於@Controller 和 @RequestBody。是Sspring Bboot 基於Sspring MVC的基礎上進行了改進, 將@Controller 與@ResponseBody 進行了合併形成的一個新的註解。 @EnableAutoConfiguration 作用 從classpath中搜索所有META-INF/spring.factories配置文件然後,將其中org.springframework.boot.autoconfigure.EnableAutoConfiguration key對應的配置項加載到spring容器 只有spring.boot.enableautoconfiguration為true(默認為true)的時候,才啟用自動配置 @EnableAutoConfiguration還可以進行排除,排除方式有2種,一是根據class來排除(exclude),二是根據class name(excludeName)來排除 其內部實現的關鍵點有
1.ImportSelector 該接口的方法的返回值都會被納入到spring容器管理中
2.SpringFactoriesLoader 該類可以從classpath中搜索所有META-INF/spring.factories配置文件,並讀取配置
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.1.RELEASE) 2020-06-23 13:30:11.611 INFO 9916 --- [ main] com.cloud.sky.Chapter0301Application : Starting Chapter0301Application on DADI-PC with PID 9916 (D:\java\microservice\chapter0301\target\classes started by Administrator in D:\java\microservice\chapter0301) 2020-06-23 13:30:11.614 INFO 9916 --- [ main] com.cloud.sky.Chapter0301Application : No active profile set, falling back to default profiles: default 2020-06-23 13:30:12.415 INFO 9916 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2020-06-23 13:30:12.423 INFO 9916 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2020-06-23 13:30:12.424 INFO 9916 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.36] 2020-06-23 13:30:12.512 INFO 9916 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2020-06-23 13:30:12.512 INFO 9916 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 830 ms 2020-06-23 13:30:12.665 INFO 9916 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2020-06-23 13:30:12.809 INFO 9916 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2020-06-23 13:30:12.818 INFO 9916 --- [ main] com.cloud.sky.Chapter0301Application : Started Chapter0301Application in 1.492 seconds (JVM running for 3.109) 2020-06-23 13:30:20.675 INFO 9916 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2020-06-23 13:30:20.676 INFO 9916 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2020-06-23 13:30:20.680 INFO 9916 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
打開瀏覽器訪問 http://localhost:8080/ 可以得到如下頁面
構建的過程中遇到問題
[INFO] Scanning for projects... [ERROR] [ERROR] Some problems were encountered while processing the POMs: [FATAL] Non-parseable POM D:\java\apache-maven-3.1.1\repo\org\jetbrains\kotlin\kotlin-bom\1.3.72\kotlin-bom-1.3.72.pom: entity reference names can not start with character ')' (position: START_TAG seen ...ost,s="";function qs(n){var u=D.URL;var t=u.match(eval(\'/(\\?|#|&)... @1:243) @ D:\java\apache-maven-3.1.1\repo\org\jetbrains\kotlin\kotlin-bom\1.3.72\kotlin-bom-1.3.72.pom, line 1, column 243 @ [ERROR] The build could not read 1 project -> [Help 1] [ERROR] [ERROR] The project com.cloudskyme:chapter0301:0.0.1 (D:\java\microservice\chapter0301\pom.xml) has 1 error [ERROR] Non-parseable POM D:\java\apache-maven-3.1.1\repo\org\jetbrains\kotlin\kotlin-bom\1.3.72\kotlin-bom-1.3.72.pom: entity reference names can not start with character ')' (position: START_TAG seen ...ost,s="";function qs(n){var u=D.URL;var t=u.match(eval(\'/(\\?|#|&)... @1:243) @ D:\java\apache-maven-3.1.1\repo\org\jetbrains\kotlin\kotlin-bom\1.3.72\kotlin-bom-1.3.72.pom, line 1, column 243 -> [Help 2] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException [ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/ModelParseException
修改maven默認源配置
我使用的是阿里的maven倉庫,國外的東西沒個代理還真麻煩。
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
<repository>
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
然後執行 mvn help:system
成功可以看到如下界面:
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※帶您來了解什麼是 USB CONNECTOR ?
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?
目錄
我們用golang做odbc驅動開發的任務並不多,隔段時間可能會來一個。每次開發會忘記如何配置數據源和對應的數據源鏈接參數配置。這裏做一個整理。
ODBC(Open Database Connectivity)是一組對數據庫訪問的標準API,其最大的優點是以統一的方式處理所有的數據庫。
JDBC(Java Database Connectivity)是Java與數據庫的接口規範,允許Java程序發送SQL指令並處理結果。比較常見JdbcTemplate
打開控制面板找到管理工具,當前目錄有兩個ODBC的配置工具。分別是:ODBC Data Sources (32-bit)、ODBC 數據源(64 位)。顧名思義一個是32位,一個是64位。在配置ACCESS數據源時需要選擇32位。
我們也可以直接在系統目錄下找到對應的可執行文件。
1)32位:%windir%\syswow64\odbcad32.exe
2)64位:%windir%\system32\odbcad32.exe
題外話:syswow64 目錄存放的是32位的程序,system32目錄存放的是64位的程序,在註冊dll的時候需要注意下。ITDragon 在接觸驅動開發之前一直都弄反了。這篇文章做了通俗易懂地解釋https://www.cnblogs.com/hbccdf/p/dllchecktoolandsyswow64.html
網上收集整理,不保證正確性,僅供參考。
| 數據庫 | 連接參數 |
|---|---|
| MySQL | driver={mysql};database=數據庫;uid=賬號;pwd=密碼; |
| MSSQL Server | driver={sql server};server=服務器;database=數據庫;uid=ITDragon;pwd=密碼; |
| Access | driver={microsoft access driver (*.mdb)};dbq=mdb文件全路徑;uid=ITDragon;pwd=密碼; |
| SQLite | driver={SQLite3 ODBC Driver};database=db文件全路徑 |
| PostgreSQL | driver={PostgreSQL ANSI};server=服務器;uid=賬號;pwd=密碼;database=數據庫; |
| DBase | driver={microsoft dbase driver (*.dbf)};driverid=277;dbq=dbf文件全路徑; |
| Oracle | driver={microsoft odbc for oracle};server=服務器;uid=ITDragon;pwd=密碼; |
| MS text | driver={microsoft text driver (* .txt; *.csv)};dbq=文件全路徑;extensions=asc,csv,tab,txt;PersistSecurityInfo=false; |
| Visual Foxpro | driver={microsoft Visual Foxpro driver};sourcetype=DBC;sourceDB=*.dbc;Exclusive=No; |
第一步:雙擊ODBC 數據源(64 位),可以選擇用戶DSN(系統只對當前用戶生效),也可以選擇系統DSN(能登錄當前系統的用戶都生效)
第二步:點擊添加,選擇事先安裝好的MySQL ODBC xx Driver 驅動後點擊完成。注意不同版本之間對某些sql語法的支持略有不同(之前吃過這個虧,我的環境有問題,客戶環境沒問題)。
第三步:完善基本鏈接信息後點擊Test,提示鏈接成功後點擊OK完成配置。
因為ODBC驅動配置已經將數據庫的連接地址、賬號、密碼、數據庫都已經配置完成,連接參數只需要指定驅動名稱即可:DSN=ITDragon_MySQL
Golang偽代碼:
import (
"database/sql"
_ "github.com/alexbrainman/odbc"
)
db, err := sql.Open("odbc", "DSN=ITDragon_MySQL")
if err != nil {
panic(err)
}
defer db.Close()
第一步:雙擊ODBC 數據源(64 位),可以選擇用戶DSN(系統只對當前用戶生效),也可以選擇系統DSN(能登錄當前系統的用戶都生效)
第二步:點擊添加,選擇SQL Server。這一項我ITDragon 並沒有手動安裝,應該是安裝SQL Server數據庫的時候自動安裝上去的。
第三步:鍵盤輸入需要連接的SQLServer服務器,如果是本機,就輸入計算機名。如果是遠程主機就需要輸入IP,Port 。不要點擊下拉框,會卡死。
第四步:選擇SQL Server驗證方式
第五步:選擇默認數據庫,也可以在寫sql語句時將表名的全路徑寫全(我ITDragon 習慣用寫全)
第六步:可以考慮修改SQL Server的系統消息語言,數據的加密,執行字符數據翻譯,修改日誌保存路徑等。也可以默認。
第七步:點擊完成,彈出“按照以下配置創建新的ODBC數據源”,點擊測試數據源,提示測試成功。點擊確定完成創建。
Golang偽代碼:
import (
"database/sql"
_ "github.com/alexbrainman/odbc"
)
db, err := sql.Open("odbc", "driver={sql server};server=DESKTOP-HKC2IA3;DSN=ODBCName;uid=xxx;pwd=xxx;")
if err != nil {
panic(err)
}
defer db.Close()
第一步:雙擊ODBC Data Sources (32-bit),而不是64位。可以選擇用戶DSN(系統只對當前用戶生效),也可以選擇系統DSN(能登錄當前系統的用戶都生效)
第二步:選擇Microsoft Access Driver (*.mdb)。點擊數據庫下發的選擇按照,選擇mdb文件,點擊確定完成配置。
我們其實可以不用配置Access的數據源,直接用DBQ指定mdb的文件路徑,再用pwd輸入密碼訪問。
Golang偽代碼:
import (
"database/sql"
_ "github.com/alexbrainman/odbc"
)
db, err := sql.Open("odbc", "DRIVER={Microsoft Access Driver (*.mdb)};DBQ=全路徑.MDB;pwd=xx;")
if err != nil {
panic(err)
}
defer db.Close()
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※台北網頁設計公司全省服務真心推薦
※想知道最厲害的網頁設計公司"嚨底家"!
※新北清潔公司,居家、辦公、裝潢細清專業服務
※推薦評價好的iphone維修中心
如果你想從頭學習Jmeter,可以看看這個系列的文章哦
https://www.cnblogs.com/poloyy/category/1746599.html
我們通過實際栗子去講述理論知識點
| 字段 | 含義 |
| Apply to | 應用範圍,選默認的 main sample only 就行了 |
| Field to check |
可提取的字段 |
| Names of created variables |
|
| Regular Expression |
正則表達式 |
| Template | 從找到的匹配項中創建字符串的模板 |
| Match No.(0 for Random) |
|
| Default Value |
|
| Use empty default value |
勾選后,提取不到值時,則返回空字符串 |
| 屬性 | 含義 |
| Body | 響應體,不包括響應頭;最常用 |
| Body (unescaped) | 響應體,替換了所有HTML轉義符;不建議使用 |
| Body as a Document | 從不同類型的文件中提取文本;影響性能 |
| Request Headers | 請求頭 |
| Response Headers | 響應頭 |
| URL | URL |
| Response Code | 響應碼 |
| Response Message | 響應信息 |
這個栗子,我都會以這個地址的接口來完成 JSON 提取器的實戰慄子,大家可以註冊個賬號玩一玩哦
http://api.yesapi.cn/docs.php?keyword=%E4%BC%9A%E5%91%98&channel=api
下面多個栗子都以這個測試計劃為基礎哦
登錄是執行其他接口的前置接口,所以要獲取用戶登錄后的 token、uuid
下面的栗子都以這個 JSON 字符串為基礎,从里面提取結果
這 JSON 字符串也是某個接口的響應內容,貨真價實,感興趣也可以自己玩一玩:http://api.yesapi.cn/docs-api-App.User.GetList.html
{ "ret": 200, "msg": "V2.5.1 YesApi App.User.GetList", "data": { "total": 4, "err_msg": "", "err_code": 0, "users": [ { "role": "user", "status_desc": "正常", "reg_time": "2020-06-22 20:45:05", "role_desc": "普通會員", "ext_info": { "yesapi_nickname": "", "yesapi_points": 0 }, "uuid": "0564CE592B4CE914365D8922F6FC4CEC", "username": "luojunjiess286", "status": 0 }, { "role": "user", "status_desc": "正常", "reg_time": "2020-06-22 14:27:17", "role_desc": "普通會員", "ext_info": { "yesapi_nickname": "", "yesapi_points": 0 }, "uuid": "0164DC0680F84DCE40D3DD4A36640ECA", "username": "luojunjiessa", "status": 0 }, { "role": "admin", "status_desc": "正常", "reg_time": "2020-03-23 22:48:32", "role_desc": "管理員", "ext_info": { "yesapi_nickname": "", "yesapi_points": 0 "yesapi_reg_source": "" }, "uuid": "079BF6BB82AFCFC7084F96AECAF0519F", "username": "luojunjiess", "status": 0 } ] } }
就是正則表達式里只有一個 ( ) ,且 Match No. 不是 -1
uuid1= uuid1_g=1 uuid1_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid1_g1=0564CE592B4CE914365D8922F6FC4CEC
如果正則匹配到值,但是沒有填模板,則返回空
uuid2="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid2_g=1 uuid2_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid2_g1=0564CE592B4CE914365D8922F6FC4CEC
uuid3=0564CE592B4CE914365D8922F6FC4CEC uuid3_g=1 uuid3_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid3_g1=0564CE592B4CE914365D8922F6FC4CEC
uuid4=null uuid4_g=1 uuid4_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid4_g1=0564CE592B4CE914365D8922F6FC4CEC
$2$ 模板並不存在,其實就是 uuid4_g2 變量不存在,即使勾了使用空默認值,也返回 null,
有兩種情況
手動分成四部分
uuid1_1=0564CE592B4CE914365D8922F6FC4CEC uuid1_1_g=1 uuid1_1_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid1_1_g1=0564CE592B4CE914365D8922F6FC4CEC uuid1_2=0164DC0680F84DCE40D3DD4A36640ECA uuid1_2_g=1 uuid1_2_g0="uuid":"0164DC0680F84DCE40D3DD4A36640ECA" uuid1_2_g1=0164DC0680F84DCE40D3DD4A36640ECA uuid1_3=079BF6BB82AFCFC7084F96AECAF0519F uuid1_3_g=1 uuid1_3_g0="uuid":"079BF6BB82AFCFC7084F96AECAF0519F" uuid1_3_g1=079BF6BB82AFCFC7084F96AECAF0519F uuid1_matchNr=3
info1= info1_g=2 info1_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286" info1_g1=0564CE592B4CE914365D8922F6FC4CEC info1_g2=luojunjiess286
info2=0564CE592B4CE914365D8922F6FC4CEC info2_g=2 info2_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286" info2_g1=0564CE592B4CE914365D8922F6FC4CEC info2_g2=luojunjiess286
info3=0564CE592B4CE914365D8922F6FC4CECluojunjiess286 info3_g=2 info3_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286" info3_g1=0564CE592B4CE914365D8922F6FC4CEC info3_g2=luojunjiess286 info4=0564CE592B4CE914365D8922F6FC4CEC,luojunjiess286 info4_g=2 info4_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286" info4_g1=0564CE592B4CE914365D8922F6FC4CEC info4_g2=luojunjiess286
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
chromedp是go寫的,支持Chrome DevTools Protocol 的一個驅動瀏覽器的庫。並且它不需要依賴其他的外界服務(比如 Selenium 和 PhantomJs)。
Chrome DevTools Protocol (CDP) 的主頁在:https://chromedevtools.github.io/devtools-protocol/。 它提供一系列的接口來查看,檢查,調整並且檢查 Chromium 的性能。Chrome 的開發者工具就是使用這一系列的接口,並且 Chrome 開發者工具來維護這些接口。
所謂 CDP 的協議,本質上是什麼呢?本質上是基於 websocket 的一種協議。比如
在我們打開 webtool 調試工具的時候,其實調試工具也是一個web頁面,兩個web頁面通過websocket建立了一個聯繫。
所以我們如果寫了一個客戶端程序,也和目標頁面創建一個基於 CDP 的 websocket連接,我們也可以通過這個協議來對頁面進行操作。
在chrome的開發者工具中
打開實驗選項 Protocol Monitor
重啟chrome,在console的更多裏面就可以打開對應的 Monitor
我們從 Protocol Monitor 面板中可以看到,其中有幾個字樣,Method,Request,Response。
這裏的 Method 就是對應官網 https://chromedevtools.github.io/devtools-protocol/ 左側每個Domain的 Event。
這裏的每個Method方法可能是調試頁面給目標頁面發送的,但是更多是目標頁面給調試頁面發送的消息。所以我們需要讀懂每個Method的內容。不過很可惜,我個人感覺官網的每個Method文檔的描述寫的實在是太簡單了,也沒有看到更詳細的描述,只能通過名字和事件來猜測每個Method意思了。
chromedp的使用最快的方法就是看 https://github.com/chromedp/examples 這個項目
基本我們可以熟悉最常用的幾個方法了:
我們嘗試打開 https://www.cnblogs.com/ 的首頁,然後獲取所有文章的標題和鏈接:
package main
import (
"context"
"fmt"
"log"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/chromedp"
)
func main() {
ctx, cancel := chromedp.NewContext(
context.Background(),
chromedp.WithLogf(log.Printf),
)
defer cancel()
var nodes []*cdp.Node
err := chromedp.Run(ctx,
chromedp.Navigate("https://www.cnblogs.com/"),
chromedp.WaitVisible(`#footer`, chromedp.ByID),
chromedp.Nodes(`.//a[@class="titlelnk"]`, &nodes),
)
if err != nil {
log.Fatal(err)
}
fmt.Println("get nodes:", len(nodes))
// print titles
for _, node := range nodes {
fmt.Println(node.Children[0].NodeValue, ":", node.AttributeValue("href"))
}
}
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
作者:凹凸曼 – 暖暖
SVG 即 Scalable Vector Graphics 可縮放矢量圖形,使用XML格式定義圖形。
SVG 的應用十分廣泛,得益於 SVG 強大的各種特性。
可利用 SVG 矢量的特點,描出深圳地鐵的輪廓:
SVG 可依據一定的規則,轉成 iconfont 使用:
利用 SVG 的 foreignObject 標籤實現截圖功能,原理:foreignObject 內部嵌入 HTML 元素:
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="120" height="60">
<p style="font-size:20px;margin:0;">凹凸實驗室 歡迎您</p>
</foreignObject>
</svg>
截圖實現流程:
<foreignObject></foreignObject> 這對標籤;foreignObject 即可;Blob 構建 svg 對象;URL.createObjectURL(svg) 取出 URL。由於微信編輯器不允許嵌入 <style><script><a> 標籤,利用SVG SMIL 可進行微信公眾號極具創意的圖文排版設計,包括動畫與交互。
但是也要注意,標籤里不允許有id,否則會被過濾或替換掉。
點擊 “凹凸實驗室” 后,圍繞 “凹凸實驗室” 中心旋轉 360度,點擊0.5秒后 出現 https://aotu.io/ ,動畫只運行一次。
下圖為 GIF循環演示:
代碼如下:
<svg width="360" height="300" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- 點擊后 運行transform旋轉動畫,restart="never"表示只運行一次 -->
<animateTransform attributeName="transform" type="rotate" begin="click" dur="0.5s" from="0 100 80" to="360 100 80" fill="freeze" restart="never" />
<g>
<text font-family="microsoft yahei" font-size="20" x="50" y="80">
凹凸實驗室
</text>
</g>
<g style="opacity: 0;">
<!-- 同一個初始位置以及大致的寬高,觸發點擊事件 -->
<text font-family="microsoft yahei" font-size="20" x="50" y="80">https://aotu.io/</text>
<!-- 點擊后 運行transform移動動畫,改變文本的位置 -->
<animateTransform attributeName="transform" type="translate" begin="click" dur="0.1s" to="0 40" fill="freeze" restart="never" />
<!-- 點擊0.5秒后 運行opacity显示動畫 -->
<animate attributeName="opacity" begin="click+0.5s" from="0" to="1" dur="0.5s" fill="freeze" restart="never" />
</g>
</g>
</svg>
以上是鄙人對SVG的大致印象,最近的需求開發再次刷新了我的認知,那就是 SVG實現非比例縮放 以及 小程序不支持SVG標籤的處理,下面容我來講述一番。
我們熟知的 iconfont,可通過改變字體大小縮放,但是這是 比例縮放,那如何實現 SVG 的非比例縮放呢?
如下圖所示,如何將 一隻兔子 非比例縮放?
划重點:實現非比例縮放主要涉及三個知識點:viewport、viewBox和preserveAspectRatio,viewport 與viewBox 結合可實現縮放的功能,viewBox 與 preserveAspectRatio 結合可實現非比例的功能。
viewport 表示SVG可見區域的大小。
viewport 就像是我們的显示器屏幕大小,超出區域則隱藏,原點位於左上角,x 軸水平向右,y 軸垂直向下。
通過類似CSS的屬性 width、height 指定視圖大小:
<svg width="400" height="200"></svg>
viewBox值有4個数字:x, y, width, height 。
其中 x:左上角橫坐標,y:左上角縱坐標,width:寬度,height:高度。
原點默認位於左上角,x 軸水平向右,y 軸垂直向下。
<svg width="400" height="200" viewBox="0 0 200 100"></svg>
显示器屏幕的畫面,可以特寫,可以全景,這就是 viewBox。
viewBox 可以想象成截屏工具選中的那個框框,和 viewport 作用的結果就是 把框框中的截屏內容再次在 显示器 中全屏显示。
(圖片來源:SVG 研究之路 (23) – 理解 viewport 與 viewbox)
上圖的紅色框框和藍色框框,恰好和显示器的比例相同,如果是下圖的綠色框框,怎樣在显示器屏幕中显示呢?
preserveAspectRatio 作用的對象是 viewBox,使用方法如下:
preserveAspectRatio="[defer] <align> [<meetOrSlice>]"
// 例如 preserveAspectRatio="xMidYMid meet"
其中 defer 此時不是重點,暫且忽略,主要了解 align 和 meetOrSlice 的 用法:
align:由兩個名詞組成,分別代表 viewbox 與 viewport 的 x 方向、y方向的對齊方式。| 值 | 含義 |
|---|---|
xMin |
viewport 和 viewBox 左邊對齊 |
xMid |
viewport 和 viewBox x軸中心對齊 |
xMax |
viewport 和 viewBox 右邊對齊 |
YMin |
viewport 和 viewBox 上邊緣對齊。注意Y是大寫。 |
YMid |
viewport 和 viewBox y軸中心點對齊。注意Y是大寫。 |
YMax |
viewport 和 viewBox 下邊緣對齊。注意Y是大寫。 |
meetOrSlice:表示如何維持高寬的比例,有三個值 meet、slice、none。
meet – 默認值,保持縱橫比縮放 viewBox 適應 viewport,可能會有餘留的空白。slice – 保持縱橫比同時比例小的方向放大填滿 viewport,超出的部分被剪裁掉。none – 扭曲縱橫比以充分適應 viewport。例子1:preserveAspectRatio="xMidYMid meet" 表示 綠色框框 與 显示器的 x 方向、y方向的 中心點 對齊;
例子2:preserveAspectRatio="xMidYMin slice" 表示 綠色框框 與 显示器的 x 方向 中心點 對齊,Y 方向 上邊緣對齊,保持比例放大填滿 显示屏 后超出部分隱藏;
例子3:preserveAspectRatio="xMidYMid slice" 表示 綠色框框 與 显示器的 x 方向、y方向的 中心點 對齊,保持比例放大填滿显示屏 后超出部分隱藏;
例子4:preserveAspectRatio="none" 不管三七二十一,隨意縮放綠色框框,填滿 显示屏即可;這就是非比例縮放的答案了。
微信小程序官方不支持 SVG 標籤的,但是決定曲線救國,相當於自己實現了一個SVG標籤:使用小程序內置的 Canvas 渲染器, 在 Cax 中實現 SVG 標準的子集,使用 JSX 或者 HTM(Hyperscript Tagged Markup) 描述 SVG 結構行為表現。
但是今天我想講講其他的。
我們知道,小程序雖然不支持 SVG 標籤,但是支持 svg 轉成 base64 後作為 background-image 的 url,如 background-image: url("data:image/svg+xml.......) 。
但是我這邊還有個需求,隨時更改 SVG 每個路徑的顏色,即 顏色可配置:
來迴轉 Base64 肯定是比較麻煩的,有沒有更好的方式呢?
直接貼答案:對於SVG圖形,還有更好的實現方式,就是直接使用SVG XML格式代碼,無需進行base64轉換。
直接使用 SVG XML 格式代碼,首先要了解 Data URI的格式。
划重點:base64非必選項,不指定的時候,後面的 <data> 將使用 URL編碼。
百分號編碼(Percent-encoding), 也稱作URL編碼(URL encoding),是特定上下文的統一資源定位符 (URL)的編碼機制。
原理:ASCII 字符 = % + 兩位 ASCII 碼(十六進制)。
例如,字符 a 對應的 ASCII 碼為 0x61,那麼 URL 編碼后得到 %61 。
前言:
Data URI 的格式中的 <data> 完全使用URL 編碼也是可以的,如 encodeURIComponent('<svg version="1.1" viewBox= …</svg>')。
但是和轉義前原始SVG相比,可讀性差了很多,而且佔用體積也變大了。
如果深入了解URL 編碼的話,<data> 沒必要全部編碼的。
正文:
RFC3986文檔規定,URL中只允許包含 未保留字符 以及 所有保留字符。
:/?#[]@ (分隔Url的協議、主機、路徑等組件) 和 !$&'()*+,;= (用於在每個組件中起到分隔作用的,如&符號用於分隔查詢多個鍵值對)。%、空格、雙引號"、尖號 <>等等。綜上所述,只需要對 受限字符或不安全字符 進行編碼即可。
svgToUrl (svgData) {
encoded = encoded
.replace(/<!--(.*)-->/g, '') // 親測必須去掉註釋
.replace(/[\r\n]/g, ' ') // 親測最好去掉換行
.replace(/"/g, `'`) // 單引號是保留字符,雙引號改成單引號減少編碼
.replace(/%/g, '%25')
.replace(/&/g, '%26')
.replace(/#/g, '%23')
.replace(/{/g, '%7B')
.replace(/}/g, '%7D')
.replace(/</g, '%3C')
.replace(/>/g, '%3E')
return `data:image/svg+xml,${encoded}`
}
如果使用在 CSS 中,可利用 SASS版本3.3以上 的 三個API 對 SVG字符串做替換處理。
str_insert(string, insert, index): 從 $string 第 $index 插入字符 $insert;str_index(string, substring): 返回 $substring 在 $string 中第一個位置;str_slice(string, start_at, end_at = nil): 返回從字符 $string 中第 $start_at 開始到 $end_at 結束的一個新字符串。前人已有總結,可前往 https://github.com/leeenx/sass-svg/blob/master/sass-encodeuri.scss 查看完整代碼。
一般從 Sketch 導出 SVG ,冗餘代碼比較多,有條件的話建議使用 SVGO 壓縮SVG的原本體積,比如清除換行、重複空格;刪除文檔聲明;刪除註釋;刪除desc描述等等。
SVG強大的地方在於,出其不意,炫酷,與眾不同。
無論是微信公眾號花式排版,foreignObject 標籤實現截圖,實現非比例縮放,或者 背景圖直接使用 SVG XML 格式代碼,還是上文沒有提及的路徑動畫、描邊動畫、圖形裁剪、濾鏡等等,都可以玩出新的花樣。
SVG 一個屬性可成就一篇文章,學習 SVG 可以說是在挑戰自己,歡迎加入 SVG 的學習隊列。
歡迎關注凹凸實驗室博客:aotu.io
或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章:
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※南投搬家公司費用需注意的眉眉角角,別等搬了再說!
※新北清潔公司,居家、辦公、裝潢細清專業服務
※教你寫出一流的銷售文案?
1、首先要在 添加好友 這個按鈕上添加一個事件,也就是在detail.wxml的添加好友這個按鈕的哪裡,添加一個點擊事件 handleAddFriend
並且添加好友還要考慮,現在是已登陸狀態還是未登陸狀態的,只有是登陸狀態的時候,才可以發起添加好友的請求的
所以就要先判斷一下它是否已經登陸了
因為只要是登陸之後,就會把用戶的id寫入到全局的userinfo下面的
handleAddFriend(){ if( app.userInfo._id){ } else{ wx.showToast({ title: '請先登陸', duration : 2000, // 然後不要讓它显示圖標 icon : 'none', success: ()=>{ // 如果成功的話就直接跳轉到我的頁面去 // 但是注意了這裏不能用 navigator to,因為它主要是跳轉 // 普通的頁面,而這裏“我的頁面”其實是同tabbar來進行配置的 } }) } }
這個時候就可以查找一下 小程序文檔 中關於“路由”的介紹了
可以看到要用wx.switchtab來進行操作了
然後因為我們設置了那個提示“請先登陸”是維持兩秒鐘,所以我們也要設置這個跳轉到我的頁面中的時間也是兩秒鐘
handleAddFriend(){ if( app.userInfo._id){ } else{ wx.showToast({ title: '請先登陸', duration : 2000, // 然後不要讓它显示圖標 icon : 'none', success: ()=>{ // 如果成功的話就直接跳轉到我的頁面去 // 但是注意了這裏不能用 navigator to,因為它主要是跳轉 // 普通的頁面,而這裏“我的頁面”其實是同tabbar來進行配置的 setTimeout(()=>{ wx.switchTab({ url: '/pages/user/user', }) } , 2000); } }) } }
上面的加入 沒登陸的情況也寫好了,下面就是對已經登陸了之後的設計了
就要在數據可以中建立一個message集合,主要是用來存儲好友消息,或者是系統的消息給這個用戶的一個信息集合的
這個集合裏面的每一個信息,包含了userID也就是這個好友請求或者是信息是發送給哪一個人的
然後還有一個其他想要加他好友的用戶id list數組,因為每個人都可以給這個人發起好友請求的,這就是對於數據庫1的建立了
所以在已經登陸之後,先查看一下有沒有這個發起好友的信息了,如果還有的話,就在數據庫中創立這個字段了
這個數據庫裏面的userid字段存的其實就是我們要加的這個人的id標識了,然後這個人的id我們可以從這個人的詳情頁面(detail)下的data中來獲得的
通過where就可以定位到在數據庫中這個用戶對應的字段了,然後用get就可以開始對這個字段裏面的東西進行查詢了
如果這個信息已經存在了就做更新操作,如果不存在的話就做創建操作即可了
if( app.userInfo._id){ db.collection('mesasge').where({ userId : this.data.detail._id }).get().then((res)=>{ if( res.data.length){//更新 } else{ //tianjia1 db.collection('message').add({ data : { userId : this.data.detail._id, list : [ app.userInfo._id] } }) } }); }
之後就可以查看數據可以中的message 集合
這樣的話,說明就調用成功了
二、更新message 信息
因為如果已經申請過了的話,就不能再往list裏面添加自己的id了,所以就要檢測一下現在是否在list中,
可以直接調用數組的include方法來進行查詢,如果找到了的話,就提示“已申請過了”如果沒找到的話就要往這個數組裡面進行數據更新了
查看了微信開放文檔之後會發現,如果是單個數據進行更新的話可以直接用doc好到之後進行更新就好了,但是如果對大量的數據進行批量的更新的話
因為在客戶端的更新能力還是有限的,所以就要到服務端上來完成了,也就是用雲函數來完成了(其實這個方法我們已經寫好了,也就是雲函數update了
else{ wx.cloud.callFunction({ // name也就是我們要修改的數據庫的名字,data就是在雲函數中 // 想要的參數了 name : 'updata', data : { collection : 'message', where :{ userId : this.data.detail._id }, data : { } } }) }
注意了,在調用雲函數的時候,前面的data和後面的data是不一樣的,錢買你的是給雲函數的參數,但是後面的是我們要修改的數據了
在要修改list的數據到時候,就涉及了要對數組進行添加,也就是push操作了,其實在數據可以中也內置了一些的方法,commend.push等等
注意:在detail.js文件中,如果找到了這個數據流,但是沒有申請的話,就是進行更新,在更新的時候用到了update雲更新函數,
給這個雲函數傳入的data中
data : `{list : _.unshift(' ${app.userInfo._id} ')}`
注意:最外面那層 並不是 單引號,而是 鍵盤 Esc下面的那個標點
三、添加好友功能之監聽message消息
在數據庫加入了一個 帶有list和userid的數據,所以在userid這個人登陸小程序之後,就應該可以看到有沒有人給他發送消息了
並且還是要實時的更新,就是這個人在登陸狀態的話,也可以直接收到了,也就是要實現實時的監聽數據庫中list的實時變化了
在開發者文檔中:雲開發-》實時數據的推送:
https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/database/realtime.html
(它的意思就是我們可以監聽到數據庫發送的變化
可以直接查看demo
const db = wx.cloud.database() const watcher = db.collection('todos') // 按 progress 降序 .orderBy('progress', 'desc') // 取按 orderBy 排序之後的前 10 個 .limit(10) .where({ team: 'our dev team' }) .watch({ onChange: function(snapshot) { console.log('docs\'s changed events', snapshot.docChanges) console.log('query result snapshot after the event', snapshot.docs) console.log('is init data', snapshot.type === 'init') }, onError: function(err) { console.error('the watch closed because of error', err) } }) // ... // 等到需要關閉監聽的時候調用 close() 方法 watcher.close()
我們可以在user頁面中進行檢測即可,也就是在登陸之後進行檢測了
我們創建了一個方法 getMessage()。只要用戶登陸了之後就可以進行觸發了,在onReady裏面的登陸成功代碼之後即可了
也就是在數據庫定位到uuseid是這個用戶的數據之後,得到了之後就可以用watch方法來進行監聽了
getMessage(){ db.collection('message').where({ userId : app.userInfo._id }).watch({ onChange: function (snapshot) { console.log(snapshot); } }); }
這個onChange就是進行監聽的函數了,我們在遇到陌生的一定要傳入參數的函數的時候,最好是把這個參數用console.log打印出來看看我們的想要的數據在哪個位置裏面的
注意了:如果是按照上面這樣的話,是會報錯的,以為缺少了錯誤返回的 onError函數的,示例的demo裏面的格式是怎麼樣的,最好就用怎麼樣的
不然可能都是會報錯的
但是會發現,我們沒拿到有用的數據
就可以用多賬號來調試一下了
(這裏用的多賬號最好還是用真實的賬號把,因為虛擬的出現的問題挺大的)
(然後還要設置給message權限是第一個,允許全部人看的那種,才可以看到在別的賬號上的加好友信息的
在別的賬號上面的話就可以看到打印的信息了,可以看到我們得到的消息其實是挺亂的,所以最好用判斷來搞一下
測試之後會發現,得到的 snapshot 數據中有一個 docChanges 數組的,只要有申請,就會有显示了
所以我們可以通過對數組的長度進行一個判斷
然後再對這個list進行判斷,通過長度來進行判斷,如果有長度的話說明就有消息了
有消息的話就要給用戶一個提示,就是在下面的tabbar中的消息圖標右上角添加一個紅色的小點
===其實這個功能在微信小程序中其實就已經幫我們設計好了
微信文檔-》API-》界面-》tabbar-》wx.showTabBarRedDot
https://developers.weixin.qq.com/miniprogram/dev/api/ui/tab-bar/wx.showTabBarRedDot.html
它需要定義一個index屬性,來指定放紅點的是tabbar中的哪一項的(它是從0開始的,所以我們設置為2即可了)
也就是說這個用戶拿到了這個list之後,通過這個list的長度來判斷有沒有消息,然後設置紅點提示,並且還要把這個得到的list用到消息頁面中去的
所以就涉及到了,怎麼把這個得到的list共享到消息中去,這個和之前的userInfo是類似的,點開全局的app.js
this.userMessage = []
這裏創立的是一個數組來的,不是對象了
然後在user.js裏面,判斷這個得到的list的長度,設置tabbar上面的小紅心,然後把得到的list賦值給全局的userMessage
但是如果檢測到這個list是空的話,就要把在tabbar上面的小紅心取消掉了
**然後還要讓我們全局的userMessage等於一個空的數組即可了
這樣,這個監聽的函數就完成了:
getMessage(){ db.collection('message').where({ userId : app.userInfo._id }).watch({ onChange: function (snapshot) { // console.log(snapshot) if( snapshot.docChanges.length){ //這裏就可以直接拿到message裏面所對應的消息列表了 let list = snapshow.docChanges[0].doc.list; if( list.length ){ wx.showTabBarRedDot({ index: 2, }); app.userMessage = list; } else{ wx.hideTabBarRedDot({ index: 2, }) app.userMessage = []; } } }, onError: function (err) { console.error('the watch closed because of error', err) } }); }
然後因為watch是實時監聽的,我們在數據庫裏面把給的信息刪掉的話
這個紅點也就會消失了
這就是因為正在實時的監聽着
四、下面搞的就是如何把共享的userMessage在消息頁面中渲染出來
===消息頁面和removeList組件布局
首先 切換編譯模式到消息頁面中,先做布局
<view class="message"> <view> <text>暫無消息:</text> </view> <view> <text>消息列表:</text> <view>第一條消息</view> <view>第二條消息</view> </view> </view>
之後就是先判斷有沒有消息。所以就要在js看i嗎添加一個東西,這裏添加的是一個userMessage數組,就是用來接收我們的那個全局的list的,
如果這個數組是空的話,說明就是沒有消息了,反之,所以就可以通過這個來進行判斷了
**之後就要測試一下message這個頁面裏面文件的生命周期了
這就是為了測試,在tabbar中的生命周期是怎麼樣的
在message.js裏面
onReady: function () {
console.log(1)
},
/**
* 生命周期函數--監聽頁面显示
*/
onShow: function () {
console.log(2)
},
在點擊了下main的tabbar的圖標之後,
打印的結果:
但是當我們幾點了個人頁面之後,再點擊消息頁面的時候,只打印了2
也就是說在tabbar裏面的onreay並不會再次的觸發(但是普通頁面的onreay是會被再次觸發的),但是他也是會觸發onshow的
所以基於這個就可以在onshow中添加東西,來監聽現在的消息變化情況了
因為如果想要進入消息頁面的話,就得先登陸,所以這力又要一個判斷了,如果沒登陸得話,就讓他跳轉到登陸頁面去的
(這個功能就和我們的detail裏面很像的,就可以直接COPY過來了)
const app = getApp() Page({ /** * 頁面的初始數據 */ data: { userMessage : [], logged : false }, /** * 生命周期函數--監聽頁面加載 */ onLoad: function (options) { }, /** * 生命周期函數--監聽頁面初次渲染完成 */ onReady: function () { // console.log(1) }, /** * 生命周期函數--監聽頁面显示 */ onShow: function () { // console.log(2) if( app.userInfo._id ){ this.setData({ logged : true, userMessage : app.userMessage }); }else{ wx.showToast({ title: '請先登陸', duration: 2000, // 然後不要讓它显示圖標 icon: 'none', success: () => { // 如果成功的話就直接跳轉到我的頁面去 // 但是注意了這裏不能用 navigator to,因為它主要是跳轉 // 普通的頁面,而這裏“我的頁面”其實是同tabbar來進行配置的 setTimeout(() => { wx.switchTab({ url: '/pages/user/user', }) }, 2000); } }) } }, /** * 生命周期函數--監聽頁面隱藏 */ onHide: function () { }, /** * 生命周期函數--監聽頁面卸載 */ onUnload: function () { }, /** * 頁面相關事件處理函數--監聽用戶下拉動作 */ onPullDownRefresh: function () { }, /** * 頁面上拉觸底事件的處理函數 */ onReachBottom: function () { }, /** * 用戶點擊右上角分享 */ onShareAppMessage: function () { } })
message.js
雖然我們吧list引入進來了,下面就是吧這個list显示出來了
我們用虛擬賬號,給我的主號提交了兩個申請來進行測試了
之後在message.wxml中對我們的信息進行打印:
<!--miniprogram/pages/message/message.wxml--> <view class="message" wx:if="{{ logged }}"> <view wx:if="{{ !userMessage.length }}"> <text class="message-text">暫無消息:</text> </view> <view wx:else> <text class="message-text">消息列表:</text> <view wx:for="{{ userMessage }}" wx:key="{{index}}">{{item}}</view> </view> </view>
我們還要進行優化,就是可以得到一個列表,有頭像有昵稱,等信息的,別還要有刪除的功能的
為了練習一下組件的功能,雖然這個刪除的功能可以直接在這個文件裏面寫,但是我們還是吧這個刪除變成一個組件的形式l
新建一個removeList的刪除組件,然後就是找到message的JSON文件引入這個組件
這個組件其實是可以拖拽的?
在文檔-》組件-》視圖容器 movable-area和movable-view相互配合的
demo:
<movable-area> <movable-view x="{{x}}" y="{{y}}" direction="all">text</movable-view> </movable-area>
我們設置的結構和樣式:
<!--components/removeList/removeList.wxml--> <movable-area class="area"> <movable-view class="view">小喵喵</movable-view> </movable-area>
/* components/removeList/removeList.wxss */ .area{width:800rpx; height: 150rpx; margin: 20rpx ; position: relative;background: blue;} .view{width:630rpx; height:100%; background: red;position: absolute;left:150rpx;top:0; line-height: 150rpx;text-indent: 10rpx;}
效果;
但是這個時候還是不能進行拖拽的,其中的direction定義的就是拖拽的方向
添加了這個屬性之後:
<!--components/removeList/removeList.wxml--> <movable-area class="area"> <movable-view direction="horizontal" class="view">小喵喵</movable-view> </movable-area>
就可以進行拖拽了
注意;下面設置的z-Index是在設置級別,z-index大的會覆蓋在小的上面的
<!--components/removeList/removeList.wxml--> <movable-area class="area"> <movable-view direction="horizontal" class="view">小喵喵</movable-view> <image src="" /> <view class="delete">刪除</view> </movable-area>
/* components/removeList/removeList.wxss */ .area{width:800rpx; height: 150rpx; margin: 20rpx ; position: relative;border-bottom: 1px #cdcdcd dashed} .view{width:630rpx; height:100%; position: absolute;left:150rpx;top:0; line-height: 150rpx;text-indent: 10rpx;z-index: 2;} image{ width: 100rpx; height: 100rpx; border-radius: 50%; position: absolute; left: 0; top: 0; z-index: 1; } .delete{ width: 200rpx; height: 150rpx; background: red; color: white; position: absolute; right: 0; top: 0; z-index: 1; }
但是得到的效果是:
會發現不管怎麼拖拉,都擋不住後面的刪除–原因就是class==view這塊沒加背景色,雖然層級夠了
給他在wxss裏面添加一個 background:white即可了
刪除那個文字要居中的話,
.delete{ width: 170rpx; height: 100rpx; background: red; color: white; position: absolute; right: 0; top: 0; z-index: 1; line-height: 100rpx; }
效果圖:
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※幫你省時又省力,新北清潔一流服務好口碑
※別再煩惱如何寫文案,掌握八大原則!
[Redis源碼系列]在Liunx安裝和常見API
為什麼要從Redis源碼分析
String底層實現——動態字符串SDS
Redis的雙向鏈表一文全知道
面試官:說說Redis的Hash底層 我:……(來自閱文的面試題)
hello,大家好,周五見了。前面幾周我們一起看了Redis底層數據結構,如動態字符串SDS,雙向鏈表Adlist,字典Dict,如果有對Redis常見的類型或底層數據結構不明白的請看上面傳送門。
今天我們來看下ZSET的底層架構,如果不知道ZSET是什麼的,可以看上面傳送門第一篇。簡單來說,ZSET是Redis提供的根據數據和分數來判斷其排名的數據結構。最常見的就是微信運動的排名,每個用戶對應自己的步數,每天晚上可以給出用戶的排名。
有小夥伴可能會想,如果是實現排名的話,各種排序方法都可以實現的,沒必要引入Redis的ZSET結構啊?
當然,如果是採用排序方法的話,是可以實現相同功能的,但是代碼裏面需要硬編碼,會添加工作量,還會提供代碼的Bug哦,哈哈哈。而且Redis的底層是C實現的,直接操作內存,速度也會比Java方法實現提升。
綜上,使用Redis的ZSET結構,好處多多。那話不多說,開始把。在正式開始之前,我們需要引入下跳躍表的概念,其是ZSET結構的底層實現。以下可能有點枯燥,我盡量說的簡單點哈。
對於數據量大的鏈表結構,插入和刪除比較快,但是查詢速度卻很慢。那是因為無法直接獲取某個節點,需要從頭節點開始,藉助某個節點的next指針來獲取下一節點。即使數據是有序排放的,想要查詢某個數據,只能從頭到尾遍歷變量,查詢效率會很低,時間複雜度為O(n)。
如果我們需要快速查詢鏈表有啥辦法呢?有同學說用數組存放,但是如果不改數據結構呢?
我們可以先想想在有序數組結構中有二分法,每次將範圍都縮小一半,這樣查詢速度提升了很多,那麼在鏈表中能不能也使用這種思想。
這就到了今天講的主角——跳躍表。(一點也生硬的引出概念)
先看下圖有序單向鏈表,存放了1,2,3,4,5,6,7這7個元素。
我們可以在鏈表中抽取部分節點,下圖抽取了1,3,5,7四個節點,也就是每兩個節點提取了一個節點到上級,抽取出來的叫做索引。
注意不是每次都能抽取到這麼完美,這其實就跟拋硬幣一樣,每個硬幣的正反兩面的概率是一樣的,都是1/2。當數據量小的時候,正反的概率可能差別較大。但是隨着數據量的加大,正反的概率越來越接近於1/2。類比過來是一個意思,每個節點的機會都是一樣的,要麼停留原級,要麼提取到上級,概率都是1/2。但是隨着節點數量的增加,抽取的節點越來越接近與1/2。
我們可以在鏈表中抽取部分節點,下圖抽取了1,5兩個節點,也就是每兩個節點提取了一個節點到上級,抽取出來的叫做索引。
我們假設要查找值為6的節點,先從三級索引開始,找到值為1的節點,發現比5小,根據值為1節點的next指針,找到值為5的節點,5後面沒有其他的三級索引啦。
於是順着往下找,到了二級索引,根據值為5的節點的next指針找到值為7的節點,發現比6小,說明要找到的節點6在此範圍內。
再接着到了一級索引位置,根據值為5的節點next指針指向值為6的節點,發現是想要查詢的數據,所以查詢過程結束。
根據上面的查詢過程(下圖的藍色連線),我們發現其採用的核心思想是二分法,不斷縮小查詢範圍,如果在上層索引找到區間,則順延深入到下一層找到真正的數據。
從上面的整個過程中可以看出,數據量小的時候,這種拿空間換時間,消耗內存方法的並不是最優解。所以Redis的zset結構在數據量小的時候採用壓縮表,數據量大的時候採用跳躍表。
像這種鏈表加多級索引的結構,就是跳躍表。這名字起的形象,過程是跳躍着來查詢的。
下圖簡單來說是對跳躍表的改進和再封裝,首先引入了表頭的概念,這與雙向鏈表,字典結構一樣,都是對數據的封裝,因為他們都是採用的指針,而指針必然導致在計算長度,獲取最後節點的數據問題上會產生查詢太慢的性能問題,所以封裝表頭是為了在這些問題上提升速度,浪費的只是添加,刪除等操作的時間,與此對比,是可以忽略的。
其次是引入管理所有節點的層數數組,我們可以看到有32層,即32個數組,這和後面的數據節點結構是一樣的。引入它是為了便於直接根據此數組的層數定位到每個元素。
再其次是數據節點的每個level都有層級和span(也就是下圖箭頭指針上的数字,其是為了方便統計兩個節點相距多少長度)。
最後就是數據節點的後退指針backward,引入目的是Level數組只有前指針,即只能指向下一個節點地址,而後退指針是為了能往回找節點。
上圖主要分為3大塊:(這邊大致看下就行,下面將對各模塊進行代碼詳細解釋)
主要包括四個屬性,分別是頭指針header,尾指針tail,節點長度length,所有節點的最大level。
header:指向跳躍表的表頭節點,通過這個指針地址可以直接找到表頭,時間複雜度為O(1)。
tail:指向跳躍表的表尾節點,通過這個指針可以直接找到表尾,時間複雜度為O(1)。
length:記錄跳躍表的長度,即不包含表頭節點,整個跳躍表中有多少個元素。
level:記錄當前跳躍表內,所有節點層數最大的level(排除表頭節點)。
其對象值為空,level數組為32層,目的是為了管理真正的數據節點。關於具體的level有哪些屬性放在數據節點來說。
主要包括四個屬性對象值obj,分數score,後退指針backward和level數組。每個數據的Level數組有多少層,是隨機產生的,這跟上面說過的跳躍表是一樣的。
成員對象obj:真正的實際數據,每個節點的數據都是唯一的,但是節點的分數可能相同。兩個相同分數的節點是按照成員對象在字典中的大小進行排序的,成員對象較小的節點會排在前面,成員對象較大的節點會排在後面。
分數score:各個節點中的数字是節點所保存的分數,在跳躍表中,節點按各自所保存的分數從小到大排列。
後退指針backward:用於從表尾向表頭遍歷,每個節點只有一個後退指針,即每次只能後退一步。
層級level:節點中用1,2,3等字樣標記節點的各個層,L1代表第一層,L2代表第二層,L3代表第三層,並以此類推。
typedef struct zskiplist { //表頭的頭指針header和尾指針tail struct zskiplistNode *header, *tail; //一共有多少個節點length unsigned long length; // 所有節點最大的層級level int level; } zskiplist;
//跳錶的具體節點 typedef struct zskiplistNode { sds ele; //具體的數據,對應張三 double score;//分數,對應70 struct zskiplistNode *backward;//後退指針backward //層級數組 struct zskiplistLevel { struct zskiplistNode *forward;//前進指針forward unsigned int span;//跨度span } level[]; } zskiplistNode;
redis關於跳躍表的API都定義在t_zset.c文件中。
千萬不要看到源碼分析就跑開了,一定要看哦。
創建空的跳躍表,其實就是創建表頭和管理所有的節點的level數組。首先,定義一些變量,嘗試分配內存空間。其次是初始化表頭的level和length,分別賦值1和0。接着創建管理所有節點的Level的數組,是調用zslCreateNode函數,輸入參數為數組大小宏常量ZSKIPLIST_MAXLEVEL(32),分數為0,對象值為NULL。(此為跳躍表得以實現重點)。再接着就是為此數組每個元素的前指針forword和跨度span初始化。最後初始化尾指針並返回值。
可以參照下面的圖解和源碼:
//創建一個空表頭的跳躍表 zskiplist *zslCreate(void) { int j; zskiplist *zsl; //嘗試分配內存空間 zsl = zmalloc(sizeof(*zsl)); //初始化level和length zsl->level = 1; zsl->length = 0; //調用下面的方法zslCreateNode,傳入的參數有數組長度ZSKIPLIST_MAXLEVEL 32 //分數0,對象值NuLL //這一步就是創建管理所有節點的數組 //並且設置表頭的頭頭指針為此對象的地址 zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL); //為這32個數組賦值前指針forward和跨度span for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) { zsl->header->level[j].forward = NULL; zsl->header->level[j].span = 0; } //設置尾指針 zsl->header->backward = NULL; zsl->tail = NULL; //返回對象 return zsl; } zskiplistNode *zslCreateNode(int level, double score, sds ele) { zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel)); zn->score = score; zn->ele = ele; return zn; }
比如有下圖6個元素,需要插入值為趙六,分數為101的元素,我們大致想一想,大致的步驟包括找到要插入的位置,新建一個數據節點,然後調整與之相關的頭尾指針的level數組。那就看看redis咋做的,和我們想的一樣不一樣呢?
噔噔噔噔,答案揭曉。當然了大框架是相同的。
正文開始了:(先來圖片)
1.遍歷管理所有節點的level數組,從最大的level開始,即3,挨個對比值,如果有分數比他大的值或者分數相同,但是數據的值比他大,記錄到數組裡面,同時記錄跨度。
這樣說太抽象了。拿上圖舉個例子,從表頭的level即3開始,首先到張三的L3,發現分數70,比目標分數101小跳過,根據其前指針找到趙六的L3,發現分數102,比目標分數101大,將趙六L3記錄在待更新數組update中,同時記錄跨度span為4。接着到下一層,張三的L2層,發現分數70比目標分數101小跳過,根據前指針找到王五的L2,發現分數90,比目標分數101小跳過,根據前指針找到趙六的L2,發現分數102比目標分數101大,將趙六的L2記錄到待更新數組update中,同時記錄跨度span為2。最後到下一層,張三的L1層,邏輯和剛才一樣的,也是記錄趙六的L1層和跨度span為1。
2.為新節點隨機生成層級數level(通過位運算),如果生成的level大於目前level最大值3,則將將大於部分挨個遍歷,並將跨度等信息記錄到上面update表中。
比如,新節點生成的level為5,目前level最大值為3,說明這個節點只會有一個,並且跨越了之前的所有節點,那麼我們將從第四層和第五層都遍歷下,記錄到待更新數組update中。
3.準備工作都做好了,找到了該節點將插入到哪一位置,處於哪一層,每層對應的跨度是多少,下面就要新增數據節點了。把上兩步的信息都添加到新節點上,並且調整位置前後指針即可。
4.最後就是一些收尾工作,比如修改表頭的層級level,節點大小length和尾指針tail等屬性。
綜上,整個流程就已經結束了。可能看着有點複雜,可以對照下面代碼來。
//插入節點,輸入參數為 //zsl:表頭 //score:插入元素的分數score //ele:插入元素的具體數據ele zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) { //使用update數組記錄每層待插入元素的前一個元素 zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; //記錄前置節點與第一個節點之間的跨度,即元素在列表中的排名-1 unsigned int rank[ZSKIPLIST_MAXLEVEL]; int i, level; serverAssert(!isnan(score)); x = zsl->header; //從最大的level開始遍歷,從頂到底,找到每一層待插入的位置 for (i = zsl->level-1; i >= 0; i--) { /* store rank that is crossed to reach the insert position */ rank[i] = i == (zsl->level-1) ? 0 : rank[i+1]; //直接找到第一個分數比該元素大的位置 //或者分數與該元素相同但是對象的ASSICC碼比該元素大的位置 while (x->level[i].forward && (x->level[i].forward->score < score || (x->level[i].forward->score == score && sdscmp(x->level[i].forward->ele,ele) < 0))) { //將已走過元素的跨越元素進行計數,得到元素在列表中排名,或者是已搜尋的路徑長度 rank[i] += x->level[i].span; x = x->level[i].forward; } //記錄待插入位置 update[i] = x; } //隨機產生一個層數,在1到32之間,層數越高,生成的概率越低 level = zslRandomLevel(); //如果產生的層數大於現有的最高層數,則超出層數都需要初始化 if (level > zsl->level) { //開始循環 for (i = zsl->level; i < level; i++) { rank[i] = 0; //該元素作為這些層的第一個節點,前節點就是header update[i] = zsl->header; //初始化后這些層每層有兩個元素,走一步就是跨越所有元素 update[i]->level[i].span = zsl->length; } zsl->level = level; } //創建節點 x = zslCreateNode(level,score,ele); for (i = 0; i < level; i++) { //將新節點插入到各層鏈表中 x->level[i].forward = update[i]->level[i].forward; update[i]->level[i].forward = x; // rank[0]是第0層的前置節點P1(也就是底層插入節點前面那個節點)與第一個節點的跨度 // rank[i]是第i層的前置節點P2(這一層里在插入節點前面那個節點)與第一個節點的跨度 // 插入節點X與後置節點Y的跨度f(X,Y)可由以下公式計算 // 關鍵在於f(P1,0)-f(P2,0)+1等於新節點與P2的跨度,這是因為跨度呈扇形形向下延伸到最底層 // 記錄節點各層跨越元素情況span, 由層與層之間的跨越元素總和rank相減而得 x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]); // 插入位置前一個節點的span在原基礎上加1即可(新節點在rank[0]的后一個位置) update[i]->level[i].span = (rank[0] - rank[i]) + 1; } /* increment span for untouched levels */ for (i = level; i < zsl->level; i++) { update[i]->level[i].span++; } // 第0層是雙向鏈表, 便於redis常支持逆序類查找 x->backward = (update[0] == zsl->header) ? NULL : update[0]; if (x->level[0].forward) x->level[0].forward->backward = x; else zsl->tail = x; zsl->length++; return x; }
int zslRandomLevel(void) { int level = 1; while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF)) level += 1; return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL; }
擔心大家忘了這張圖,再粘貼一遍。如下圖,這部分邏輯比較簡單,就不寫了,具體參考代碼分析。
//得到節點的排名 //輸入參數為表頭結構zsl,分數score,真正的數據ele unsigned long zslGetRank(zskiplist *zsl, double score, sds ele) { zskiplistNode *x; unsigned long rank = 0; int i; //先獲取表頭的頭指針,即找到管理所有節點的level數組 x = zsl->header; //從表頭的level,即最大值開始循環遍歷 for (i = zsl->level-1; i >= 0; i--) { //如果找到分數小於目標分數的,排名加上其跨度 //或者分數相同,但是具體數據小於目標數據的,排名也加上跨度 while (x->level[i].forward && (x->level[i].forward->score < score || (x->level[i].forward->score == score && sdscmp(x->level[i].forward->ele,ele) <= 0))) { rank += x->level[i].span; x = x->level[i].forward; } //確保在第i層找到分值相同,且對象相同時才會返回排位值 if (x->ele && sdscmp(x->ele,ele) == 0) { return rank; } } return 0; }
該篇主要講了Redis的ZSET數據類型的底層實現跳躍表,先從跳躍表是什麼,引出跳躍表的概念和數據結構,剖析了其主要組成部分,進而通過多幅過程圖解釋了Redis是如何設計跳躍表的,最後結合源碼對跳躍表進行描述,如創建過程,添加節點過程,獲取某個節點排名過程,中間穿插例子和過程圖。
如果覺得寫得還行,麻煩給個贊,您的認可才是我寫作的動力!
如果覺得有說的不對的地方,歡迎評論指出。
好了,拜拜咯。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※帶您來了解什麼是 USB CONNECTOR ?
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?