<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: 刘悦的技术博客</title>
    <description>The latest articles on Forem by 刘悦的技术博客 (@liuyue).</description>
    <link>https://forem.com/liuyue</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F870727%2F74e24722-343b-4523-93bc-4c35f511bb3c.jpg</url>
      <title>Forem: 刘悦的技术博客</title>
      <link>https://forem.com/liuyue</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/liuyue"/>
    <language>en</language>
    <item>
      <title>把盏言欢,款款而谈,ChatGPT结合钉钉机器人(outgoing回调)打造人工智能群聊/单聊场景,基于Python3.10</title>
      <dc:creator>刘悦的技术博客</dc:creator>
      <pubDate>Thu, 08 Dec 2022 02:09:27 +0000</pubDate>
      <link>https://forem.com/liuyue/ba-zhan-yan-huan-kuan-kuan-er-tan-chatgptjie-he-ding-ding-ji-qi-ren-outgoinghui-diao-da-zao-ren-gong-zhi-neng-qun-liao-dan-liao-chang-jing-ji-yu-python310-4ddh</link>
      <guid>https://forem.com/liuyue/ba-zhan-yan-huan-kuan-kuan-er-tan-chatgptjie-he-ding-ding-ji-qi-ren-outgoinghui-diao-da-zao-ren-gong-zhi-neng-qun-liao-dan-liao-chang-jing-ji-yu-python310-4ddh</guid>
      <description>&lt;p&gt;就像黑火药时代里突然诞生的核弹一样，OpenAI的ChatGPT语言模型的横空出世，是人工智能技术发展史上的一个重要里程碑。这是一款无与伦比、超凡绝伦的模型，能够进行自然语言推理和对话，并且具有出色的语言生成能力。&lt;/p&gt;

&lt;p&gt;好吧，本篇的开头其实是由ChatGPT生成的：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv3u.cn%2Fv3u%2FPublic%2Fjs%2Feditor%2Fattached%2F20221207181201_92912.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv3u.cn%2Fv3u%2FPublic%2Fjs%2Feditor%2Fattached%2F20221207181201_92912.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;没办法，面对这个远超时代的AI产品，我们能说什么呢？顶礼膜拜？惊为天人？任何言语对于描述ChatGPT来说已经是苍白无力的，而辞海中的形容词在面对ChatGPT时也已经鞭长莫及。&lt;/p&gt;

&lt;p&gt;一句话：言语不能赞其伟大。&lt;/p&gt;

&lt;p&gt;本次我们利用ChatGPT的开放API接入钉钉群聊/单聊机器人，让钉钉机器人具备进行自然语言推理和对话的能力，所谓化腐朽为神奇，不过如此。&lt;/p&gt;

&lt;h2&gt;
  
  
  注册和使用OpenAi的ChatGPT
&lt;/h2&gt;

&lt;p&gt;首先注册OpenAi平台：&lt;a href="https://beta.openai.com/" rel="noopener noreferrer"&gt;https://beta.openai.com/&lt;/a&gt; ，由于ChatGPT过于火爆，导致很多地区无法正常注册，这里推荐使用北美地区的代理IP，与此同时，一定要注意，如果之后希望使用后端的API接口方式调用ChatGPT，就不要使用谷歌或者微软的三方账号进行登录，否则无法通过邮箱和秘钥交换OpenAi平台的access_token，切记。&lt;/p&gt;

&lt;p&gt;同时，接受验证码手机号也必须是北美地区的手机号，这里推荐一个北美地区的接码平台：&lt;a href="https://sms.qisms.com/index" rel="noopener noreferrer"&gt;https://sms.qisms.com/index&lt;/a&gt; 非常好用。&lt;/p&gt;

&lt;p&gt;注册成功之后，这里推荐github上开源大神rawandahmad698已经封装好的开源SDK，避免重复造轮子：&lt;a href="https://github.com/rawandahmad698/PyChatGPT" rel="noopener noreferrer"&gt;https://github.com/rawandahmad698/PyChatGPT&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;安装SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip3 install chatgptpy --upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;安装好之后，编写测试脚本：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chat = Chat(email="OpenAi邮箱", password="OpenAi密码",proxies="代理地址")  

answer = chat.ask("你好")  

print(answer)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;注意，运行代码之前，一定要使用代理proxies，并且确保是北美地区的IP地址。&lt;/p&gt;

&lt;p&gt;程序返回：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[OpenAI] Email address: ********  
[OpenAI] Password: *********  
[OpenAI] Using proxy: {'http': 'http://localhost:4780', 'https': 'http://localhost:4780'}  
[OpenAI] Beginning auth process  
[OpenAI][1] Making request to https://chat.openai.com/auth/login  
[OpenAI][1] Request was successful  
[OpenAI][2] Beginning part two  
[OpenAI][2] Grabbing CSRF token from https://chat.openai.com/api/auth/csrf  
[OpenAI][2] Request was successful  
[OpenAI][2] CSRF Token: 1b1357a34e4b0b9a74e999372fe0413ab981c9a72e030a54b3bf172bd6176c5e  
[OpenAI][3] Beginning part three  
[OpenAI][3] Making request to https://chat.openai.com/api/auth/signin/auth0?prompt=login  
[OpenAI][3] Request was successful  
[OpenAI][3] Callback URL: https://auth0.openai.com/authorize?client_id=TdJIcbe16WoTHtN95nyywh5E4yOo6ItG&amp;amp;scope=openid%20email%20profile%20offline_access%20model.request%20model.read%20organization.read&amp;amp;response_type=code&amp;amp;redirect_uri=https%3A%2F%2Fchat.openai.com%2Fapi%2Fauth%2Fcallback%2Fauth0&amp;amp;audience=https%3A%2F%2Fapi.openai.com%2Fv1&amp;amp;prompt=login&amp;amp;state=RJt9U13ATPmlt795xMNohQZcUNOytZNvHoq3JI8HGZ4&amp;amp;code_challenge=Pq97ptna00Ybak2dUmIMhR3eqmXZnZz-Fij7otMMw7U&amp;amp;code_challenge_method=S256  
[OpenAI][4] Making request to https://auth0.openai.com/authorize?client_id=TdJIcbe16WoTHtN95nyywh5E4yOo6ItG&amp;amp;scope=openid%20email%20profile%20offline_access%20model.request%20model.read%20organization.read&amp;amp;response_type=code&amp;amp;redirect_uri=https%3A%2F%2Fchat.openai.com%2Fapi%2Fauth%2Fcallback%2Fauth0&amp;amp;audience=https%3A%2F%2Fapi.openai.com%2Fv1&amp;amp;prompt=login&amp;amp;state=RJt9U13ATPmlt795xMNohQZcUNOytZNvHoq3JI8HGZ4&amp;amp;code_challenge=Pq97ptna00Ybak2dUmIMhR3eqmXZnZz-Fij7otMMw7U&amp;amp;code_challenge_method=S256  
[OpenAI][4] Request was successful  
[OpenAI][4] Current State: hKFo2SA5VzlqUDA0Mkl5TnQtNUpYcGRBU0ZfRkhQVUY1eVpWV6Fur3VuaXZlcnNhbC1sb2dpbqN0aWTZIGMzU0xvbThRUXFxMTczeVg4bF8zRFZnYVNOM2M3Q0RFo2NpZNkgVGRKSWNiZTE2V29USHROOTVueXl3aDVFNHlPbzZJdEc  
[OpenAI][5] Making request to https://auth0.openai.com/u/login/identifier?state=hKFo2SA5VzlqUDA0Mkl5TnQtNUpYcGRBU0ZfRkhQVUY1eVpWV6Fur3VuaXZlcnNhbC1sb2dpbqN0aWTZIGMzU0xvbThRUXFxMTczeVg4bF8zRFZnYVNOM2M3Q0RFo2NpZNkgVGRKSWNiZTE2V29USHROOTVueXl3aDVFNHlPbzZJdEc  
[OpenAI][5] Request was successful  
[OpenAI][5] No captcha detected  
[OpenAI][6] Making request to https://auth0.openai.com/u/login/identifier  
[OpenAI][6] Email found  
[OpenAI][7] Entering password...  
[OpenAI][7] Password was correct  
[OpenAI][7] Old state: hKFo2SA5VzlqUDA0Mkl5TnQtNUpYcGRBU0ZfRkhQVUY1eVpWV6Fur3VuaXZlcnNhbC1sb2dpbqN0aWTZIGMzU0xvbThRUXFxMTczeVg4bF8zRFZnYVNOM2M3Q0RFo2NpZNkgVGRKSWNiZTE2V29USHROOTVueXl3aDVFNHlPbzZJdEc  
[OpenAI][7] New State: c3SLom8QQqq173yX8l_3DVgaSN3c7CDE  
[OpenAI][8] Making request to https://auth0.openai.com/authorize/resume?state=c3SLom8QQqq173yX8l_3DVgaSN3c7CDE  
[OpenAI][8] All good  
[OpenAI][8] Access Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UaEVOVUpHTkVNMVFURTRNMEZCTWpkQ05UZzVNRFUxUlRVd1FVSkRNRU13UmtGRVFrRXpSZyJ9.eyJodHRwczovL2FwaS5vcGVuYWkuY29tL3Byb2ZpbGUiOnsiZW1haWwiOiJ6Y3hleTI5MTFAb3V0bG9vay5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZ2VvaXBfY291bnRyeSI6IlVTIn0sImh0dHBzOi8vYXBpLm9wZW5haS5jb20vYXV0aCI6eyJ1c2VyX2lkIjoidXNlci1IcHQ2SXF6R0k0RW43V213dGdzaUVOUjUifSwiaXNzIjoiaHR0cHM6Ly9hdXRoMC5vcGVuYWkuY29tLyIsInN1YiI6ImF1dGgwfDYzOTA3ZWRiMTQzYTFkZjQxMzk5Yzc0YyIsImF1ZCI6WyJodHRwczovL2FwaS5vcGVuYWkuY29tL3YxIiwiaHR0cHM6Ly9vcGVuYWkuYXV0aDAuY29tL3VzZXJpbmZvIl0sImlhdCI6MTY3MDQ1OTkzNywiZXhwIjoxNjcwNTQ2MzM3LCJhenAiOiJUZEpJY2JlMTZXb1RIdE45NW55eXdoNUU0eU9vNkl0RyIsInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUgbW9kZWwucmVhZCBtb2RlbC5yZXF1ZXN0IG9yZ2FuaXphdGlvbi5yZWFkIG9mZmxpbmVfYWNjZXNzIn0.PtXKhJqwudNKLIkNRc5OO6T7Tsl8ydZ8WWnCJ3Ax2c40CQibRTiGLDmfvk2gW5pVIkOpKldWYs6Jrd8UVi0Ih9VMDwS9JL6HpZKsoRaIhy6r6l7AW5vMMQN-l0ntCsgefQeGIrwtCTUsIklN8dyZDkRkympC2AzRkayAcFvFckXTHi_J5Fivr5J7We_OM4cGFJEKTLkaSw6MnYku-uYwAKPVEpFsF7fLnUBRQxn5Zz90FhdeLYEg4IUjPWKPp1iMbp_fa9qhwwtKBwogtrIVzq2t8NdUotoNYgoo2uV2xjQWC2m4V4C_xgkSzLj2TTtRJMOYKGH-lHWs2_yRQF0wOg  
[OpenAI][9] Saving access token...  
[OpenAI][8] Saved access token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;首次运行程序会通过代理自动登录OpenAi平台，并且换取token，最后将token存储在本地。&lt;/p&gt;

&lt;p&gt;随后返回ChatGPT的信息：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  mydemo git:(master) ✗ /opt/homebrew/bin/python3.10 "/Users/liuyue/wodfan/work/mydemo/test_chatgpt.py"  
Using proxies: http://localhost:4780  
你好，很高兴为你提供帮助。有什么需要我帮忙的吗？
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;至此，ChatGPT接口就调试好了。&lt;/p&gt;

&lt;h2&gt;
  
  
  配置钉钉Dingding机器人
&lt;/h2&gt;

&lt;p&gt;随后，我们来配置C端的机器人，注意这里一定要使用支持outgoing回调的企业机器人，而不是普通的机器人，参考文档：&lt;a href="https://open.dingtalk.com/document/group/enterprise-created-chatbot" rel="noopener noreferrer"&gt;https://open.dingtalk.com/document/group/enterprise-created-chatbot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;创建好企业机器人之后，获取机器人应用的Key和秘钥，同时配置好出口IP和接口地址：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv3u.cn%2Fv3u%2FPublic%2Fjs%2Feditor%2Fattached%2F20221208091235_27510.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv3u.cn%2Fv3u%2FPublic%2Fjs%2Feditor%2Fattached%2F20221208091235_27510.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;所谓出口IP即调用钉钉服务合法的ip，消息接受地址是接受C端信息的地址，这里我们使用异步非阻塞的Tornado框架来构建接受信息服务：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import hmac  
import hashlib  
import base64  
import json  
import tornado  

from tornado.options import define, options  
define('port', default=8000, help='default port',type=int)  

class Robot(tornado.web.RequestHandler):  

    async def post(self):  


        timestamp = self.request.headers.get('timestamp', None)  

        sign = self.request.headers.get('sign', None)  
        app_secret = '钉钉机器人秘钥'  
        app_secret_enc = app_secret.encode('utf-8')  
        string_to_sign = '{}\n{}'.format(timestamp, app_secret)  
        string_to_sign_enc = string_to_sign.encode('utf-8')  
        hmac_code = hmac.new(app_secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()  
        my_sign = base64.b64encode(hmac_code).decode('utf-8')  
        if sign != my_sign:  
            return self.finish({"errcode":1,"msg":"签名有误"})  
        data = json.loads(self.request.body)  
        text = data['text']["content"]  
        atUsers = data.get("atUsers",None)  
        uid = data.get("senderStaffId",None)  
        return self.finish({"errcode":0,"msg":text})  

urlpatterns = [  
    (r"/robot_chat/",Robot),  
]  


# 创建Tornado实例  
application = tornado.web.Application(urlpatterns,debug=True)  


if __name__ == "__main__":  
    tornado.options.parse_command_line()  
    application.listen(options.port)  
    tornado.ioloop.IOLoop.instance().start()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这里我们通过Robot异步控制器来接受所有来自钉钉客户端的信息，即人类对机器人说的话，需要注意的是，后端服务需要对请求头中的timestamp和sign进行验证，以判断是否是来自钉钉的合法请求，避免其他仿冒钉钉调用开发者的HTTPS服务传送数据。&lt;/p&gt;

&lt;p&gt;所以这里一旦签名有问题，就结束逻辑：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

timestamp = self.request.headers.get('timestamp', None)  

sign = self.request.headers.get('sign', None)  
app_secret = '钉钉机器人秘钥'  
app_secret_enc = app_secret.encode('utf-8')  
string_to_sign = '{}\n{}'.format(timestamp, app_secret)  
string_to_sign_enc = string_to_sign.encode('utf-8')  
hmac_code = hmac.new(app_secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()  
my_sign = base64.b64encode(hmac_code).decode('utf-8')  
if sign != my_sign:  
    return self.finish({"errcode":1,"msg":"签名有误"})


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;最后该接口会返回发信人id（uid）以及具体信息内容（text）。&lt;/p&gt;

&lt;p&gt;至此，后端接受服务就配置好了。&lt;/p&gt;

&lt;p&gt;下面就是后端推送服务，首先，根据官方文档：&lt;a href="https://open.dingtalk.com/document/orgapp-server/obtain-the-access%5C_token-of-an-internal-app?spm=ding%5C_open%5C_doc.document.0.0.5f255239xgW3zE#topic-2056397" rel="noopener noreferrer"&gt;https://open.dingtalk.com/document/orgapp-server/obtain-the-access\_token-of-an-internal-app?spm=ding\_open\_doc.document.0.0.5f255239xgW3zE#topic-2056397&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;需要获取钉钉接口的token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def get_token(self):  

        res = requests.post("https://api.dingtalk.com/v1.0/oauth2/accessToken",data=json.dumps({"appKey":self._appKey,"appSecret":self._appSecret}),headers={"Content-Type":"application/json"})  

        token = res.json()["accessToken"]  

        return token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;随后，根据文档：&lt;a href="https://open.dingtalk.com/document/group/chatbots-send-one-on-one-chat-messages-in-batches?spm=ding%5C_open%5C_doc.document.0.0.22e749acXECz5m#topic-2080109" rel="noopener noreferrer"&gt;https://open.dingtalk.com/document/group/chatbots-send-one-on-one-chat-messages-in-batches?spm=ding\_open\_doc.document.0.0.22e749acXECz5m#topic-2080109&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我们来配置单聊推送：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 单聊  
    def send_message(self,uid,message):  

        res = requests.post("https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",data=json.dumps({"robotCode":self._appKey,"userIds":[uid],"msgKey":"sampleText","msgParam":'{"content":"'+message+'"}'}),headers={"Content-Type":"application/json","x-acs-dingtalk-access-token":self._token})  

        print(res.text)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;具体效果：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv3u.cn%2Fv3u%2FPublic%2Fjs%2Feditor%2Fattached%2F20221208091231_17838.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv3u.cn%2Fv3u%2FPublic%2Fjs%2Feditor%2Fattached%2F20221208091231_17838.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;接着，继续根据官方文档：&lt;a href="https://open.dingtalk.com/document/robots/guide-to-user-access-for-intra-enterprise-robot-group-chat" rel="noopener noreferrer"&gt;https://open.dingtalk.com/document/robots/guide-to-user-access-for-intra-enterprise-robot-group-chat&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;配置群聊推送方法：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 群聊  
    def send_user(self,uid,message):  

        data = {  
        "at": {  
            "atUserIds": [  
                uid  
            ]  
        },  
        "text": {  
            "content": message  
        },  
        "msgtype": "text"  
        }  

        res = requests.post(self._webhook,data=json.dumps(data),headers={"Content-Type":"application/json"})  

        print(res.text)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;群聊效果：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv3u.cn%2Fv3u%2FPublic%2Fjs%2Feditor%2Fattached%2F20221208091206_79912.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv3u.cn%2Fv3u%2FPublic%2Fjs%2Feditor%2Fattached%2F20221208091206_79912.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这里需要注意的是，单聊是通过接口的方式进行推送，而群内聊天是通过webhook方式进行推送，关于webhook，请移玉步至：&lt;a href="https://v3u.cn/a_id_132" rel="noopener noreferrer"&gt;使用python3.7配置开发钉钉群自定义机器人(2020年新版攻略)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;完整代码：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import requests  
import json  

from pychatgpt import Chat  

class DingDing:  


    def __init__(self,appKey=None,appSecret=None) -&amp;gt; None:  

        self._appKey = appKey  

        self._appSecret = appSecret  

        self._token = self.get_token()  

        # 机器人webhook地址  
        self._webhook = ""  




    def get_token(self):  

        res = requests.post("https://api.dingtalk.com/v1.0/oauth2/accessToken",data=json.dumps({"appKey":self._appKey,"appSecret":self._appSecret}),headers={"Content-Type":"application/json"})  

        token = res.json()["accessToken"]  

        return token  

    # 单聊  
    def send_message(self,uid,message):  

        res = requests.post("https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",data=json.dumps({"robotCode":self._appKey,"userIds":[uid],"msgKey":"sampleText","msgParam":'{"content":"'+message+'"}'}),headers={"Content-Type":"application/json","x-acs-dingtalk-access-token":self._token})  

        print(res.text)  

    # 群聊  
    def send_user(self,uid,message):  

        data = {  
        "at": {  
            "atUserIds": [  
                uid  
            ]  
        },  
        "text": {  
            "content": message  
        },  
        "msgtype": "text"  
        }  

        res = requests.post(self._webhook,data=json.dumps(data),headers={"Content-Type":"application/json"})  

        print(res.text)  




if __name__ == '__main__':  

    dingding = DingDing("appkey","appSecret")  

    #chat = Chat(email="OpenAi邮箱", password="OpenAi密码",proxies="代理地址")  

    #answer = chat.ask("你好")  

    # 单聊  
    #dingding.send_message('uid',answer)  

    # 群聊  
    #dingding.send_user('uid',answer)  

    #print(answer)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;至此，后端推送服务就配置好了。&lt;/p&gt;

&lt;h2&gt;
  
  
  结语
&lt;/h2&gt;

&lt;p&gt;最后，奉上Github项目地址，与众亲同飨：&lt;a href="https://github.com/zcxey2911/Python%5C_ChatGPT%5C_ForDingding%5C_OpenAi" rel="noopener noreferrer"&gt;https://github.com/zcxey2911/Python\_ChatGPT\_ForDingding\_OpenAi&lt;/a&gt; ，毫无疑问，ChatGPT是NLP领域历史上最伟大的项目，没有之一，伟大，就是技术层面的极致，你同意吗？&lt;/p&gt;

</description>
    </item>
    <item>
      <title>python花式读取大文件(10g/50g/1t)遇到的性能问题（面试向）</title>
      <dc:creator>刘悦的技术博客</dc:creator>
      <pubDate>Mon, 08 Aug 2022 12:20:27 +0000</pubDate>
      <link>https://forem.com/liuyue/pythonhua-shi-du-qu-da-wen-jian-10g50g1tyu-dao-de-xing-neng-wen-ti-mian-shi-xiang--53fn</link>
      <guid>https://forem.com/liuyue/pythonhua-shi-du-qu-da-wen-jian-10g50g1tyu-dao-de-xing-neng-wen-ti-mian-shi-xiang--53fn</guid>
      <description>&lt;p&gt;最近无论是面试还是笔试，有一个高频问题始终阴魂不散，那就是给一个大文件，至少超过10g,在内存有限的情况下（低于2g），该以什么姿势读它？&lt;/p&gt;

&lt;p&gt;所有人都知道，用python读文件有一套”标准流程“：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def retrun\_count(fname):
    """计算文件有多少行
    """
    count = 0
    with open(fname) as file:
        for line in file:
            count += 1
    return count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;为什么这种文件读取方式会成为标准？这是因为它有两个好处：&lt;/p&gt;

&lt;p&gt;with 上下文管理器会自动关闭打开的文件描述符&lt;br&gt;&lt;br&gt;
在迭代文件对象时，内容是一行一行返回的，不会占用太多内存&lt;/p&gt;

&lt;p&gt;但这套标准做法并非没有缺点。如果被读取的文件里，根本就没有任何换行符，那么上面的第二个好处就不成立了。当代码执行到 for line in file 时，line 将会变成一个非常巨大的字符串对象，消耗掉非常可观的内存。  &lt;/p&gt;

&lt;p&gt;如果有一个 5GB 大的文件 big_file.txt，它里面装满了随机字符串。只不过它存储内容的方式稍有不同，所有的文本都被放在了同一行里  &lt;/p&gt;

&lt;p&gt;如果我们继续使用前面的 return_count 函数去统计这个大文件行数。那么在一台pc上，这个过程会足足花掉 65 秒，并在执行过程中吃掉机器 2GB 内存&lt;/p&gt;

&lt;p&gt;为了解决这个问题，我们需要暂时把这个“标准做法”放到一边，使用更底层的 file.read() 方法。与直接循环迭代文件对象不同，每次调用 file.read(chunk_size) 会直接返回从当前位置往后读取 chunk_size 大小的文件内容，不必等待任何换行符出现。  &lt;/p&gt;

&lt;p&gt;所以，如果使用 file.read() 方法，我们的函数可以改写成这样:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def return\_count\_v2(fname):

    count = 0
    block\_size = 1024 \* 8
    with open(fname) as fp:
        while True:
            chunk = fp.read(block\_size)
            # 当文件没有更多内容时，read 调用将会返回空字符串 ''
            if not chunk:
                break
            count += 1
    return count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;在新函数中，我们使用了一个 while 循环来读取文件内容，每次最多读取 8kb 大小，这样可以避免之前需要拼接一个巨大字符串的过程，把内存占用降低非常多。  &lt;/p&gt;

&lt;p&gt;利用生成器解耦代码&lt;/p&gt;

&lt;p&gt;假如我们在讨论的不是 Python，而是其他编程语言。那么可以说上面的代码已经很好了。但是如果你认真分析一下 return_count_v2 函数，你会发现在循环体内部，存在着两个独立的逻辑：数据生成（read 调用与 chunk 判断） 与 数据消费。而这两个独立逻辑被耦合在了一起。  &lt;/p&gt;

&lt;p&gt;为了提升复用能力，我们可以定义一个新的 chunked_file_reader 生成器函数，由它来负责所有与“数据生成”相关的逻辑。这样 return_count_v3 里面的主循环就只需要负责计数即可。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def chunked\_file\_reader(fp, block\_size=1024 \* 8):
    """生成器函数：分块读取文件内容
    """
    while True:
        chunk = fp.read(block\_size)
        # 当文件没有更多内容时，read 调用将会返回空字符串 ''
        if not chunk:
            break
        yield chunk


def return\_count\_v3(fname):
    count = 0
    with open(fname) as fp:
        for chunk in chunked\_file\_reader(fp):
            count += 1
    return count

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;进行到这一步，代码似乎已经没有优化的空间了，但其实不然。iter(iterable) 是一个用来构造迭代器的内建函数，但它还有一个更少人知道的用法。当我们使用 iter(callable, sentinel) 的方式调用它时，会返回一个特殊的对象，迭代它将不断产生可调用对象 callable 的调用结果，直到结果为 setinel 时，迭代终止。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def chunked\_file\_reader(file, block\_size=1024 \* 8):
    """生成器函数：分块读取文件内容，使用 iter 函数
    """
    # 首先使用 partial(fp.read, block\_size) 构造一个新的无需参数的函数
    # 循环将不断返回 fp.read(block\_size) 调用结果，直到其为 '' 时终止
    for chunk in iter(partial(file.read, block\_size), ''):
        yield chunk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;最后只需要两行代码，就构造出了一个可复用的分块读取方法，和一开始的”标准流程“按行读取 2GB 内存/耗时 65 秒 相比，使用生成器的版本只需要 7MB 内存 / 12 秒就能完成计算。效率提升了接近 4 倍，内存占用更是不到原来的 1%，简直完美。&lt;/p&gt;

</description>
      <category>python</category>
      <category>生成器</category>
      <category>文件</category>
      <category>超大文件</category>
    </item>
    <item>
      <title>彻底弄清楚session,cookie,sessionStorage,localStorage的区别及应用场景（面试向）</title>
      <dc:creator>刘悦的技术博客</dc:creator>
      <pubDate>Mon, 08 Aug 2022 12:17:40 +0000</pubDate>
      <link>https://forem.com/liuyue/che-di-nong-qing-chu-sessioncookiesessionstoragelocalstoragede-qu-bie-ji-ying-yong-chang-jing-mian-shi-xiang--4717</link>
      <guid>https://forem.com/liuyue/che-di-nong-qing-chu-sessioncookiesessionstoragelocalstoragede-qu-bie-ji-ying-yong-chang-jing-mian-shi-xiang--4717</guid>
      <description>&lt;p&gt;客户端状态保持是一个老生常谈的问题了，归根结底追踪浏览器的用户身份及其相关数据无非就是以下四种方式：session,cookie,sessionStorage,localStorage&lt;/p&gt;

&lt;p&gt;首先cookie和session:&lt;/p&gt;

&lt;p&gt;Cookie机制：如果不在浏览器中设置过期时间，cookie被保存在内存中，生命周期随浏览器的关闭而结束，这种cookie简称会话cookie。如果在浏览器中设置了cookie的过期时间，cookie被保存在硬盘中，关闭浏览器后，cookie数据仍然存在，直到过期时间结束才消失。  &lt;/p&gt;

&lt;p&gt;Cookie是服务器发给客户端的特殊信息，cookie是以文本的方式保存在客户端，每次请求时都带上它  &lt;/p&gt;

&lt;p&gt;Session机制：当服务器收到请求需要创建session对象时，首先会检查客户端请求中是否包含sessionid。如果有sessionid，服务器将根据该id返回对应session对象。如果客户端请求中没有sessionid，服务器会创建新的session对象，并把sessionid在本次响应中返回给客户端。通常使用cookie方式存储sessionid到客户端，在交互中浏览器按照规则将sessionid发送给服务器。如果用户禁用cookie，则要使用URL重写，可以通过response.encodeURL(url) 进行实现；API对encodeURL的结束为，当浏览器支持Cookie时，url不做任何处理；当浏览器不支持Cookie的时候，将会重写URL将SessionID拼接到访问地址后。  &lt;/p&gt;

&lt;p&gt;3、存储内容：cookie只能保存字符串类型，以文本的方式；session通过类似与Hashtable的数据结构来保存，能支持任何类型的对象(session中可含有多个对象)  &lt;/p&gt;

&lt;p&gt;4、存储的大小：cookie：单个cookie保存的数据不能超过4kb；session大小没有限制。  &lt;/p&gt;

&lt;p&gt;5、安全性：cookie：针对cookie所存在的攻击：Cookie欺骗，Cookie截获；session的安全性大于cookie。  &lt;/p&gt;

&lt;p&gt;原因如下：  &lt;/p&gt;

&lt;p&gt;（1）sessionID存储在cookie中，若要攻破session首先要攻破cookie；  &lt;/p&gt;

&lt;p&gt;（2）sessionID是要有人登录，或者启动session_start才会有，所以攻破cookie也不一定能得到sessionID；  &lt;/p&gt;

&lt;p&gt;（3）第二次启动session_start后，前一次的sessionID就是失效了，session过期后，sessionID也随之失效。  &lt;/p&gt;

&lt;p&gt;（4）sessionID是加密的  &lt;/p&gt;

&lt;p&gt;（5）综上所述，攻击者必须在短时间内攻破加密的sessionID，并非易事。  &lt;/p&gt;

&lt;p&gt;6、应用场景：  &lt;/p&gt;

&lt;p&gt;cookie：  &lt;/p&gt;

&lt;p&gt;（1）判断用户是否登陆过网站，以便下次登录时能够实现自动登录（或者记住密码）。如果我们删除cookie，则每次登录必须从新填写登录的相关信息。  &lt;/p&gt;

&lt;p&gt;（2）保存上次登录的时间等信息。  &lt;/p&gt;

&lt;p&gt;（3）保存上次查看的页面  &lt;/p&gt;

&lt;p&gt;（4）浏览计数  &lt;/p&gt;

&lt;p&gt;session：Session用于保存每个用户的专用信息，变量的值保存在服务器端，通过SessionID来区分不同的客户。  &lt;/p&gt;

&lt;p&gt;（1）网上商城中的购物车  &lt;/p&gt;

&lt;p&gt;（2）保存用户登录信息  &lt;/p&gt;

&lt;p&gt;（3）将某些数据放入session中，供同一用户的不同页面使用  &lt;/p&gt;

&lt;p&gt;（4）防止用户非法登录  &lt;/p&gt;

&lt;p&gt;7、缺点：cookie：  &lt;/p&gt;

&lt;p&gt;（1）大小受限  &lt;/p&gt;

&lt;p&gt;（2）用户可以操作（禁用）cookie，使功能受限  &lt;/p&gt;

&lt;p&gt;（3）安全性较低  &lt;/p&gt;

&lt;p&gt;（4）有些状态不可能保存在客户端。  &lt;/p&gt;

&lt;p&gt;（5）每次访问都要传送cookie给服务器，浪费带宽。  &lt;/p&gt;

&lt;p&gt;（6）cookie数据有路径（path）的概念，可以限制cookie只属于某个路径下。  &lt;/p&gt;

&lt;p&gt;session：  &lt;/p&gt;

&lt;p&gt;（1）Session保存的东西越多，就越占用服务器内存，对于用户在线人数较多的网站，服务器的内存压力会比较大。  &lt;/p&gt;

&lt;p&gt;（2）依赖于cookie（sessionID保存在cookie），如果禁用cookie，则要使用URL重写，不安全  &lt;/p&gt;

&lt;p&gt;（3）创建Session变量有很大的随意性，可随时调用，不需要开发者做精确地处理，所以，过度使用session变量将会导致代码不可读而且不好维护。&lt;/p&gt;

&lt;p&gt;说白了，这两种状态保持方式都差强人意，于是webStroage应运而生&lt;/p&gt;

&lt;p&gt;WebStorage的目的是克服由cookie所带来的一些限制，当数据需要被严格控制在客户端时，不需要持续的将数据发回服务器。  &lt;/p&gt;

&lt;p&gt;WebStorage两个主要目标：（1）提供一种在cookie之外存储会话数据的路径。（2）提供一种存储大量可以跨会话存在的数据的机制。  &lt;/p&gt;

&lt;p&gt;HTML5的WebStorage提供了两种API：localStorage（本地存储）和sessionStorage（会话存储）。  &lt;/p&gt;

&lt;p&gt;1、生命周期：localStorage:localStorage的生命周期是永久的，关闭页面或浏览器之后localStorage中的数据也不会消失。localStorage除非主动删除数据，否则数据永远不会消失。  &lt;/p&gt;

&lt;p&gt;sessionStorage的生命周期是在仅在当前会话下有效。sessionStorage引入了一个“浏览器窗口”的概念，sessionStorage是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭，即使刷新页面或者进入同源另一个页面，数据依然存在。但是sessionStorage在关闭了浏览器窗口后就会被销毁。同时独立的打开同一个窗口同一个页面，sessionStorage也是不一样的。  &lt;/p&gt;

&lt;p&gt;2、存储大小：localStorage和sessionStorage的存储数据大小一般都是：5MB  &lt;/p&gt;

&lt;p&gt;3、存储位置：localStorage和sessionStorage都保存在客户端，不与服务器进行交互通信。  &lt;/p&gt;

&lt;p&gt;4、存储内容类型：localStorage和sessionStorage只能存储字符串类型，对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理  &lt;/p&gt;

&lt;p&gt;5、获取方式：localStorage：window.localStorage;；sessionStorage：window.sessionStorage;。  &lt;/p&gt;

&lt;p&gt;6、应用场景：localStoragese：常用于长期登录（+判断用户是否已登录），适合长期保存在本地的数据（令牌）。sessionStorage：敏感账号一次性登录；  &lt;/p&gt;

&lt;p&gt;WebStorage的优点：  &lt;/p&gt;

&lt;p&gt;（1）存储空间更大：cookie为4KB，而WebStorage是5MB；  &lt;/p&gt;

&lt;p&gt;（2）节省网络流量：WebStorage不会传送到服务器，存储在本地的数据可以直接获取，也不会像cookie一样美词请求都会传送到服务器，所以减少了客户端和服务器端的交互，节省了网络流量；  &lt;/p&gt;

&lt;p&gt;（3）对于那种只需要在用户浏览一组页面期间保存而关闭浏览器后就可以丢弃的数据，sessionStorage会非常方便；  &lt;/p&gt;

&lt;p&gt;（4）快速显示：有的数据存储在WebStorage上，再加上浏览器本身的缓存。获取数据时可以从本地获取会比从服务器端获取快得多，所以速度更快；  &lt;/p&gt;

&lt;p&gt;（5）安全性：WebStorage不会随着HTTP header发送到服务器端，所以安全性相对于cookie来说比较高一些，不会担心截获，但是仍然存在伪造问题；  &lt;/p&gt;

&lt;p&gt;（6）WebStorage提供了一些方法，数据操作比cookie方便；  &lt;/p&gt;

&lt;p&gt;setItem (key, value) —— 保存数据，以键值对的方式储存信息。&lt;/p&gt;

</description>
      <category>前端</category>
      <category>session</category>
      <category>cooki</category>
      <category>localstorage</category>
    </item>
    <item>
      <title>见微知著，细节上雕花：SVG生成矢量格式网站图标(Favicon)探究</title>
      <dc:creator>刘悦的技术博客</dc:creator>
      <pubDate>Tue, 07 Jun 2022 08:44:40 +0000</pubDate>
      <link>https://forem.com/liuyue/jian-wei-zhi-zhu-xi-jie-shang-diao-hua-svgsheng-cheng-shi-liang-ge-shi-wang-zhan-tu-biao-favicontan-jiu-104g</link>
      <guid>https://forem.com/liuyue/jian-wei-zhi-zhu-xi-jie-shang-diao-hua-svgsheng-cheng-shi-liang-ge-shi-wang-zhan-tu-biao-favicontan-jiu-104g</guid>
      <description>&lt;p&gt;原文转载自「刘悦的技术博客」&lt;a href="https://v3u.cn/a_id_215"&gt;https://v3u.cn/a_id_215&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Favicon是favorites icon的缩写，也被称为website icon（站点图标）、page icon（页面图标）或者urlicon（URL图标）。Favicon是与某个站点或网页相关联的图标，网站设计者可以多种方式建立这种图标，几乎所有浏览器都支持此功能，浏览器可以将favicon显示于浏览器的地址栏中，也可置于书签列表的网站名前，还可以放在标签式浏览界面中的页标题前：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M58P85FP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607140637_57231.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M58P85FP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607140637_57231.png" alt="" width="880" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我们定义一个favicon的方法是将一个名为“favicon.ico”的文件置于Web服务器的根目录下，IE的收藏夹（即书签）可以自动显示该文件。后来出现了一种更为灵活的方法，即使用HTML来为任何一个网页指示其图标所存储的位置：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="icon" href="favicon.ico" type="image/x-icon"  /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;大多数情况下使用ICO格式，现代浏览器也支持PNG（便携式网络图片）格式，和GIF（图形交换格式）动画图像格式：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="icon" href="/favicon.png" type="image/png"  /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;现如今，我们可以使用另外一种矢量格式来创建favicon了，那就是SVG。&lt;/p&gt;

&lt;h2&gt;
  
  
  SVG格式favicon
&lt;/h2&gt;

&lt;p&gt;SVG 能够在不损失质量的情况下放大和缩小，并且尺寸上比以往任何图片格式都小，它们还可以嵌入 CSS，甚至嵌入动画和媒体查询。这意味着如果在浏览器应用程序或书签栏中使用 SVG 图标，由于 SVG 内部提供的深色偏好样式，用户有可能获取与站点主题相关的（浅色或深色）图标。&lt;/p&gt;

&lt;p&gt;首先在站点根目录建立favicon.svg文件：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;svg xmlns="http://www.w3.org/2000/svg" width="400" height="344" viewBox="0 0 400 344" &amp;gt;  


&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接下来，添加svg路径path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;svg xmlns="http://www.w3.org/2000/svg" width="400" height="344" viewBox="0 0 400 344" &amp;gt;  
&amp;lt;path d="M108.731 4.224c-5.097 2.044-13.302 4.448-18.232 5.342C74.059 12.548 20 32.992 20 36.228c0 3.085 25.52 16.117 57.488 29.357 22.898 9.483 222.126 9.483 245.024 0C354.48 52.345 380 39.313 380 36.228c0-3.253-54.096-23.687-70.663-26.691-5.021-.911-13.792-3.429-19.493-5.596-13.064-4.967-168.627-4.724-181.113.283M8 48.043c0 1.917 1.745 4.563 3.878 5.882 2.133 1.318 12.366 18.074 22.741 37.236 10.374 19.161 20.693 36.07 22.931 37.575 4.769 3.207 11.4.038 8.783-4.197-.989-1.6 3.291-12.214 9.51-23.587C88.818 77.225 89.097 77.722 54 61.968 23.556 48.302 8 43.593 8 48.043M344.002 62.66c-16.498 7.701-30.539 14.544-31.202 15.207-1.381 1.381 22.346 46.434 27.1 51.455 1.729 1.827 9.039-7.173 16.243-20C363.347 96.495 371.941 82.85 375.241 79c3.299-3.85 4.774-7 3.276-7-1.497 0 .07-5.4 3.483-12 8.403-16.25 1.474-15.765-37.998 2.66m37.606 16.215c-30.721 50.032-31.314 60.191-3.03 51.963l19.422-5.65 1.188-29.206c1.51-37.145-2.738-41.278-17.58-17.107M2.521 73C1.195 77.95.085 92.215.055 104.699L0 127.398l20.74 6.08c28.87 8.463 29.274 6.83 8.326-33.606C9.326 61.767 6.341 58.746 2.521 73m110.146 10.683C114.729 89.379 195.183 156 200 156c4.817 0 85.271-66.621 87.333-72.317C288.12 81.51 252.327 80 200 80s-88.12 1.51-87.333 3.683m-30.303 26.46C66.423 139.145 65.223 144 74 144c3.3 0 6 1.8 6 4s-1.993 4-4.428 4c-6.934 0-1.717 9.949 35.654 68 53.738 83.475 79.385 120.055 82.199 117.241 3.682-3.681 3.198-168.384-.501-170.67-1.692-1.045-24.642-4.975-51-8.732C115.566 154.083 91.3 149.353 88 147.33c-4.176-2.56-2.352-3.068 6-1.671 45.724 7.648 75.402 10.539 78.312 7.629 2.804-2.804-53.996-56.062-70.691-66.283-3.286-2.012-9.608 5.585-19.257 23.138m178.116 8.755c-47.058 40.072-44.179 41.765 45.52 26.761 8.352-1.397 10.176-.889 6 1.671-3.3 2.023-27.6 6.735-54 10.471-59.576 8.43-53.48-2.339-54.84 96.88l-1.16 84.6 10.877-6.43c5.982-3.537 10.194-7.114 9.359-7.948-.835-.835 20.553-35.13 47.529-76.211 57.798-88.02 61.073-93.746 54.809-95.834-6.667-2.222-5.599-8.858 1.426-8.858 8.814 0 7.469-6.312-6.923-32.493-16.217-29.497-15.114-29.636-58.597 7.391M10.316 139.489c-1.371 2.219.939 7.291 5.134 11.273 4.196 3.981 15.486 16.398 25.089 27.593C92.117 238.484 140.91 292 144.153 292c3.177 0-28.76-53.245-40.041-66.755-5.318-6.37-32.442-49.301-40.787-64.558-4.781-8.74-11.38-13.47-23.25-16.667-9.117-2.455-18.981-5.386-21.921-6.514-2.939-1.128-6.467-.236-7.838 1.983m352.939 3.821C346.065 149.445 320 175.954 320 187.302c0 4.451-31.767 49.864-37.519 53.637-2.465 1.616-3.113 2.966-1.44 3 1.672.033-3.551 9.988-11.607 22.121-33.436 50.36-6.59 24.004 97.718-95.933 30.446-35.007 29.884-38.873-3.897-26.817" fill-rule="evenodd"/&amp;gt;  
&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;随后，在网站头部标签中，修改icon声明：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="icon" href="/favicon.svg" type="image/svg+xml" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;随后就可以在标签页中观察到svg格式的favicon了：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nr-BMQF1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607140608_94474.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nr-BMQF1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607140608_94474.png" alt="" width="664" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这里需要注意的是，两种不同的favicon声明方式并不是非此即彼，它们可以共存：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="icon" href="/favicon.png" type="image/png"  /&amp;gt;  
&amp;lt;link rel="icon" href="/favicon.svg" type="image/svg+xml" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;以Chrome为例子，浏览器会优先选择svg格式的图标进行展示，如果声明了png的favicon，那么png格式的图标也会被下载：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NsRS5vsP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607150607_39702.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NsRS5vsP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607150607_39702.png" alt="" width="880" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;所以，如果出于性能层面考虑，可以只写一种声明，用来节约系统资源。&lt;/p&gt;

&lt;h2&gt;
  
  
  SVG样式
&lt;/h2&gt;

&lt;p&gt;有的时候，在页面对比度层面，我们可以为svg图标设置前景色或者背景色来突出favicon：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;svg xmlns="http://www.w3.org/2000/svg" width="400" height="344" viewBox="0 0 400 344" style="background-color:#dedede"&amp;gt;&amp;lt;path style="fill:#231f20;stroke:none;" d="M108.731 4.224c-5.097 2.044-13.302 4.448-18.232 5.342C74.059 12.548 20 32.992 20 36.228c0 3.085 25.52 16.117 57.488 29.357 22.898 9.483 222.126 9.483 245.024 0C354.48 52.345 380 39.313 380 36.228c0-3.253-54.096-23.687-70.663-26.691-5.021-.911-13.792-3.429-19.493-5.596-13.064-4.967-168.627-4.724-181.113.283M8 48.043c0 1.917 1.745 4.563 3.878 5.882 2.133 1.318 12.366 18.074 22.741 37.236 10.374 19.161 20.693 36.07 22.931 37.575 4.769 3.207 11.4.038 8.783-4.197-.989-1.6 3.291-12.214 9.51-23.587C88.818 77.225 89.097 77.722 54 61.968 23.556 48.302 8 43.593 8 48.043M344.002 62.66c-16.498 7.701-30.539 14.544-31.202 15.207-1.381 1.381 22.346 46.434 27.1 51.455 1.729 1.827 9.039-7.173 16.243-20C363.347 96.495 371.941 82.85 375.241 79c3.299-3.85 4.774-7 3.276-7-1.497 0 .07-5.4 3.483-12 8.403-16.25 1.474-15.765-37.998 2.66m37.606 16.215c-30.721 50.032-31.314 60.191-3.03 51.963l19.422-5.65 1.188-29.206c1.51-37.145-2.738-41.278-17.58-17.107M2.521 73C1.195 77.95.085 92.215.055 104.699L0 127.398l20.74 6.08c28.87 8.463 29.274 6.83 8.326-33.606C9.326 61.767 6.341 58.746 2.521 73m110.146 10.683C114.729 89.379 195.183 156 200 156c4.817 0 85.271-66.621 87.333-72.317C288.12 81.51 252.327 80 200 80s-88.12 1.51-87.333 3.683m-30.303 26.46C66.423 139.145 65.223 144 74 144c3.3 0 6 1.8 6 4s-1.993 4-4.428 4c-6.934 0-1.717 9.949 35.654 68 53.738 83.475 79.385 120.055 82.199 117.241 3.682-3.681 3.198-168.384-.501-170.67-1.692-1.045-24.642-4.975-51-8.732C115.566 154.083 91.3 149.353 88 147.33c-4.176-2.56-2.352-3.068 6-1.671 45.724 7.648 75.402 10.539 78.312 7.629 2.804-2.804-53.996-56.062-70.691-66.283-3.286-2.012-9.608 5.585-19.257 23.138m178.116 8.755c-47.058 40.072-44.179 41.765 45.52 26.761 8.352-1.397 10.176-.889 6 1.671-3.3 2.023-27.6 6.735-54 10.471-59.576 8.43-53.48-2.339-54.84 96.88l-1.16 84.6 10.877-6.43c5.982-3.537 10.194-7.114 9.359-7.948-.835-.835 20.553-35.13 47.529-76.211 57.798-88.02 61.073-93.746 54.809-95.834-6.667-2.222-5.599-8.858 1.426-8.858 8.814 0 7.469-6.312-6.923-32.493-16.217-29.497-15.114-29.636-58.597 7.391M10.316 139.489c-1.371 2.219.939 7.291 5.134 11.273 4.196 3.981 15.486 16.398 25.089 27.593C92.117 238.484 140.91 292 144.153 292c3.177 0-28.76-53.245-40.041-66.755-5.318-6.37-32.442-49.301-40.787-64.558-4.781-8.74-11.38-13.47-23.25-16.667-9.117-2.455-18.981-5.386-21.921-6.514-2.939-1.128-6.467-.236-7.838 1.983m352.939 3.821C346.065 149.445 320 175.954 320 187.302c0 4.451-31.767 49.864-37.519 53.637-2.465 1.616-3.113 2.966-1.44 3 1.672.033-3.551 9.988-11.607 22.121-33.436 50.36-6.59 24.004 97.718-95.933 30.446-35.007 29.884-38.873-3.897-26.817" fill-rule="evenodd"/&amp;gt;&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NKo1pp7m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607150655_85827.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NKo1pp7m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607150655_85827.png" alt="" width="590" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;也可以通过设置svg的viewBox属性来等比例缩小或者放大图标：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;svg xmlns="http://www.w3.org/2000/svg" width="400" height="344" viewBox="-10 0 425 369" style="background-color:#dedede"&amp;gt;&amp;lt;path style="fill:#231f20;stroke:none;" d="M108.731 4.224c-5.097 2.044-13.302 4.448-18.232 5.342C74.059 12.548 20 32.992 20 36.228c0 3.085 25.52 16.117 57.488 29.357 22.898 9.483 222.126 9.483 245.024 0C354.48 52.345 380 39.313 380 36.228c0-3.253-54.096-23.687-70.663-26.691-5.021-.911-13.792-3.429-19.493-5.596-13.064-4.967-168.627-4.724-181.113.283M8 48.043c0 1.917 1.745 4.563 3.878 5.882 2.133 1.318 12.366 18.074 22.741 37.236 10.374 19.161 20.693 36.07 22.931 37.575 4.769 3.207 11.4.038 8.783-4.197-.989-1.6 3.291-12.214 9.51-23.587C88.818 77.225 89.097 77.722 54 61.968 23.556 48.302 8 43.593 8 48.043M344.002 62.66c-16.498 7.701-30.539 14.544-31.202 15.207-1.381 1.381 22.346 46.434 27.1 51.455 1.729 1.827 9.039-7.173 16.243-20C363.347 96.495 371.941 82.85 375.241 79c3.299-3.85 4.774-7 3.276-7-1.497 0 .07-5.4 3.483-12 8.403-16.25 1.474-15.765-37.998 2.66m37.606 16.215c-30.721 50.032-31.314 60.191-3.03 51.963l19.422-5.65 1.188-29.206c1.51-37.145-2.738-41.278-17.58-17.107M2.521 73C1.195 77.95.085 92.215.055 104.699L0 127.398l20.74 6.08c28.87 8.463 29.274 6.83 8.326-33.606C9.326 61.767 6.341 58.746 2.521 73m110.146 10.683C114.729 89.379 195.183 156 200 156c4.817 0 85.271-66.621 87.333-72.317C288.12 81.51 252.327 80 200 80s-88.12 1.51-87.333 3.683m-30.303 26.46C66.423 139.145 65.223 144 74 144c3.3 0 6 1.8 6 4s-1.993 4-4.428 4c-6.934 0-1.717 9.949 35.654 68 53.738 83.475 79.385 120.055 82.199 117.241 3.682-3.681 3.198-168.384-.501-170.67-1.692-1.045-24.642-4.975-51-8.732C115.566 154.083 91.3 149.353 88 147.33c-4.176-2.56-2.352-3.068 6-1.671 45.724 7.648 75.402 10.539 78.312 7.629 2.804-2.804-53.996-56.062-70.691-66.283-3.286-2.012-9.608 5.585-19.257 23.138m178.116 8.755c-47.058 40.072-44.179 41.765 45.52 26.761 8.352-1.397 10.176-.889 6 1.671-3.3 2.023-27.6 6.735-54 10.471-59.576 8.43-53.48-2.339-54.84 96.88l-1.16 84.6 10.877-6.43c5.982-3.537 10.194-7.114 9.359-7.948-.835-.835 20.553-35.13 47.529-76.211 57.798-88.02 61.073-93.746 54.809-95.834-6.667-2.222-5.599-8.858 1.426-8.858 8.814 0 7.469-6.312-6.923-32.493-16.217-29.497-15.114-29.636-58.597 7.391M10.316 139.489c-1.371 2.219.939 7.291 5.134 11.273 4.196 3.981 15.486 16.398 25.089 27.593C92.117 238.484 140.91 292 144.153 292c3.177 0-28.76-53.245-40.041-66.755-5.318-6.37-32.442-49.301-40.787-64.558-4.781-8.74-11.38-13.47-23.25-16.667-9.117-2.455-18.981-5.386-21.921-6.514-2.939-1.128-6.467-.236-7.838 1.983m352.939 3.821C346.065 149.445 320 175.954 320 187.302c0 4.451-31.767 49.864-37.519 53.637-2.465 1.616-3.113 2.966-1.44 3 1.672.033-3.551 9.988-11.607 22.121-33.436 50.36-6.59 24.004 97.718-95.933 30.446-35.007 29.884-38.873-3.897-26.817" fill-rule="evenodd"/&amp;gt;&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;不仅仅支持内嵌，我们也可以将样式进行外联操作：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;svg id="svg" xmlns="http://www.w3.org/2000/svg" width="400" height="344" viewBox="-10 0 425 369"&amp;gt;  
&amp;lt;style&amp;gt;  
#path{fill:#231f20;stroke:none;}  
#svg{background-color:#dedede}  
&amp;lt;/style&amp;gt;  
&amp;lt;path id="path" d="M108.731 4.224c-5.097 2.044-13.302 4.448-18.232 5.342C74.059 12.548 20 32.992 20 36.228c0 3.085 25.52 16.117 57.488 29.357 22.898 9.483 222.126 9.483 245.024 0C354.48 52.345 380 39.313 380 36.228c0-3.253-54.096-23.687-70.663-26.691-5.021-.911-13.792-3.429-19.493-5.596-13.064-4.967-168.627-4.724-181.113.283M8 48.043c0 1.917 1.745 4.563 3.878 5.882 2.133 1.318 12.366 18.074 22.741 37.236 10.374 19.161 20.693 36.07 22.931 37.575 4.769 3.207 11.4.038 8.783-4.197-.989-1.6 3.291-12.214 9.51-23.587C88.818 77.225 89.097 77.722 54 61.968 23.556 48.302 8 43.593 8 48.043M344.002 62.66c-16.498 7.701-30.539 14.544-31.202 15.207-1.381 1.381 22.346 46.434 27.1 51.455 1.729 1.827 9.039-7.173 16.243-20C363.347 96.495 371.941 82.85 375.241 79c3.299-3.85 4.774-7 3.276-7-1.497 0 .07-5.4 3.483-12 8.403-16.25 1.474-15.765-37.998 2.66m37.606 16.215c-30.721 50.032-31.314 60.191-3.03 51.963l19.422-5.65 1.188-29.206c1.51-37.145-2.738-41.278-17.58-17.107M2.521 73C1.195 77.95.085 92.215.055 104.699L0 127.398l20.74 6.08c28.87 8.463 29.274 6.83 8.326-33.606C9.326 61.767 6.341 58.746 2.521 73m110.146 10.683C114.729 89.379 195.183 156 200 156c4.817 0 85.271-66.621 87.333-72.317C288.12 81.51 252.327 80 200 80s-88.12 1.51-87.333 3.683m-30.303 26.46C66.423 139.145 65.223 144 74 144c3.3 0 6 1.8 6 4s-1.993 4-4.428 4c-6.934 0-1.717 9.949 35.654 68 53.738 83.475 79.385 120.055 82.199 117.241 3.682-3.681 3.198-168.384-.501-170.67-1.692-1.045-24.642-4.975-51-8.732C115.566 154.083 91.3 149.353 88 147.33c-4.176-2.56-2.352-3.068 6-1.671 45.724 7.648 75.402 10.539 78.312 7.629 2.804-2.804-53.996-56.062-70.691-66.283-3.286-2.012-9.608 5.585-19.257 23.138m178.116 8.755c-47.058 40.072-44.179 41.765 45.52 26.761 8.352-1.397 10.176-.889 6 1.671-3.3 2.023-27.6 6.735-54 10.471-59.576 8.43-53.48-2.339-54.84 96.88l-1.16 84.6 10.877-6.43c5.982-3.537 10.194-7.114 9.359-7.948-.835-.835 20.553-35.13 47.529-76.211 57.798-88.02 61.073-93.746 54.809-95.834-6.667-2.222-5.599-8.858 1.426-8.858 8.814 0 7.469-6.312-6.923-32.493-16.217-29.497-15.114-29.636-58.597 7.391M10.316 139.489c-1.371 2.219.939 7.291 5.134 11.273 4.196 3.981 15.486 16.398 25.089 27.593C92.117 238.484 140.91 292 144.153 292c3.177 0-28.76-53.245-40.041-66.755-5.318-6.37-32.442-49.301-40.787-64.558-4.781-8.74-11.38-13.47-23.25-16.667-9.117-2.455-18.981-5.386-21.921-6.514-2.939-1.128-6.467-.236-7.838 1.983m352.939 3.821C346.065 149.445 320 175.954 320 187.302c0 4.451-31.767 49.864-37.519 53.637-2.465 1.616-3.113 2.966-1.44 3 1.672.033-3.551 9.988-11.607 22.121-33.436 50.36-6.59 24.004 97.718-95.933 30.446-35.007 29.884-38.873-3.897-26.817" fill-rule="evenodd"/&amp;gt;&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  暗黑模式
&lt;/h2&gt;

&lt;p&gt;通过在样式中添加媒体查询，我们可以为svg的图标添加“暗黑模式”，首先创建好光明模式：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;svg id="svg" xmlns="http://www.w3.org/2000/svg" width="400" height="344" viewBox="-10 0 425 369"&amp;gt;  
&amp;lt;style&amp;gt;  
#path{fill:#231f20;stroke:none;}  
#svg{background-color:#dedede}  
&amp;lt;/style&amp;gt;  
&amp;lt;path id="path" d="M108.731 4.224c-5.097 2.044-13.302 4.448-18.232 5.342C74.059 12.548 20 32.992 20 36.228c0 3.085 25.52 16.117 57.488 29.357 22.898 9.483 222.126 9.483 245.024 0C354.48 52.345 380 39.313 380 36.228c0-3.253-54.096-23.687-70.663-26.691-5.021-.911-13.792-3.429-19.493-5.596-13.064-4.967-168.627-4.724-181.113.283M8 48.043c0 1.917 1.745 4.563 3.878 5.882 2.133 1.318 12.366 18.074 22.741 37.236 10.374 19.161 20.693 36.07 22.931 37.575 4.769 3.207 11.4.038 8.783-4.197-.989-1.6 3.291-12.214 9.51-23.587C88.818 77.225 89.097 77.722 54 61.968 23.556 48.302 8 43.593 8 48.043M344.002 62.66c-16.498 7.701-30.539 14.544-31.202 15.207-1.381 1.381 22.346 46.434 27.1 51.455 1.729 1.827 9.039-7.173 16.243-20C363.347 96.495 371.941 82.85 375.241 79c3.299-3.85 4.774-7 3.276-7-1.497 0 .07-5.4 3.483-12 8.403-16.25 1.474-15.765-37.998 2.66m37.606 16.215c-30.721 50.032-31.314 60.191-3.03 51.963l19.422-5.65 1.188-29.206c1.51-37.145-2.738-41.278-17.58-17.107M2.521 73C1.195 77.95.085 92.215.055 104.699L0 127.398l20.74 6.08c28.87 8.463 29.274 6.83 8.326-33.606C9.326 61.767 6.341 58.746 2.521 73m110.146 10.683C114.729 89.379 195.183 156 200 156c4.817 0 85.271-66.621 87.333-72.317C288.12 81.51 252.327 80 200 80s-88.12 1.51-87.333 3.683m-30.303 26.46C66.423 139.145 65.223 144 74 144c3.3 0 6 1.8 6 4s-1.993 4-4.428 4c-6.934 0-1.717 9.949 35.654 68 53.738 83.475 79.385 120.055 82.199 117.241 3.682-3.681 3.198-168.384-.501-170.67-1.692-1.045-24.642-4.975-51-8.732C115.566 154.083 91.3 149.353 88 147.33c-4.176-2.56-2.352-3.068 6-1.671 45.724 7.648 75.402 10.539 78.312 7.629 2.804-2.804-53.996-56.062-70.691-66.283-3.286-2.012-9.608 5.585-19.257 23.138m178.116 8.755c-47.058 40.072-44.179 41.765 45.52 26.761 8.352-1.397 10.176-.889 6 1.671-3.3 2.023-27.6 6.735-54 10.471-59.576 8.43-53.48-2.339-54.84 96.88l-1.16 84.6 10.877-6.43c5.982-3.537 10.194-7.114 9.359-7.948-.835-.835 20.553-35.13 47.529-76.211 57.798-88.02 61.073-93.746 54.809-95.834-6.667-2.222-5.599-8.858 1.426-8.858 8.814 0 7.469-6.312-6.923-32.493-16.217-29.497-15.114-29.636-58.597 7.391M10.316 139.489c-1.371 2.219.939 7.291 5.134 11.273 4.196 3.981 15.486 16.398 25.089 27.593C92.117 238.484 140.91 292 144.153 292c3.177 0-28.76-53.245-40.041-66.755-5.318-6.37-32.442-49.301-40.787-64.558-4.781-8.74-11.38-13.47-23.25-16.667-9.117-2.455-18.981-5.386-21.921-6.514-2.939-1.128-6.467-.236-7.838 1.983m352.939 3.821C346.065 149.445 320 175.954 320 187.302c0 4.451-31.767 49.864-37.519 53.637-2.465 1.616-3.113 2.966-1.44 3 1.672.033-3.551 9.988-11.607 22.121-33.436 50.36-6.59 24.004 97.718-95.933 30.446-35.007 29.884-38.873-3.897-26.817" fill-rule="evenodd"/&amp;gt;&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这里，背景色为白色，前景色为黑色：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wdkMLzsZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607150647_31636.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wdkMLzsZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607150647_31636.png" alt="" width="790" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;随后添加暗黑模式：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;svg id="svg" xmlns="http://www.w3.org/2000/svg" width="400" height="344" viewBox="-10 0 425 369"&amp;gt;  
&amp;lt;style&amp;gt;  
#path{fill:#231f20;stroke:none;}  
#svg{background-color:#dedede}  
@media (prefers-color-scheme: dark) {  
      #path{fill:#dedede;stroke:none;}  
      #svg{background-color:#231f20}  
}  
&amp;lt;/style&amp;gt;  
&amp;lt;path id="path" d="M108.731 4.224c-5.097 2.044-13.302 4.448-18.232 5.342C74.059 12.548 20 32.992 20 36.228c0 3.085 25.52 16.117 57.488 29.357 22.898 9.483 222.126 9.483 245.024 0C354.48 52.345 380 39.313 380 36.228c0-3.253-54.096-23.687-70.663-26.691-5.021-.911-13.792-3.429-19.493-5.596-13.064-4.967-168.627-4.724-181.113.283M8 48.043c0 1.917 1.745 4.563 3.878 5.882 2.133 1.318 12.366 18.074 22.741 37.236 10.374 19.161 20.693 36.07 22.931 37.575 4.769 3.207 11.4.038 8.783-4.197-.989-1.6 3.291-12.214 9.51-23.587C88.818 77.225 89.097 77.722 54 61.968 23.556 48.302 8 43.593 8 48.043M344.002 62.66c-16.498 7.701-30.539 14.544-31.202 15.207-1.381 1.381 22.346 46.434 27.1 51.455 1.729 1.827 9.039-7.173 16.243-20C363.347 96.495 371.941 82.85 375.241 79c3.299-3.85 4.774-7 3.276-7-1.497 0 .07-5.4 3.483-12 8.403-16.25 1.474-15.765-37.998 2.66m37.606 16.215c-30.721 50.032-31.314 60.191-3.03 51.963l19.422-5.65 1.188-29.206c1.51-37.145-2.738-41.278-17.58-17.107M2.521 73C1.195 77.95.085 92.215.055 104.699L0 127.398l20.74 6.08c28.87 8.463 29.274 6.83 8.326-33.606C9.326 61.767 6.341 58.746 2.521 73m110.146 10.683C114.729 89.379 195.183 156 200 156c4.817 0 85.271-66.621 87.333-72.317C288.12 81.51 252.327 80 200 80s-88.12 1.51-87.333 3.683m-30.303 26.46C66.423 139.145 65.223 144 74 144c3.3 0 6 1.8 6 4s-1.993 4-4.428 4c-6.934 0-1.717 9.949 35.654 68 53.738 83.475 79.385 120.055 82.199 117.241 3.682-3.681 3.198-168.384-.501-170.67-1.692-1.045-24.642-4.975-51-8.732C115.566 154.083 91.3 149.353 88 147.33c-4.176-2.56-2.352-3.068 6-1.671 45.724 7.648 75.402 10.539 78.312 7.629 2.804-2.804-53.996-56.062-70.691-66.283-3.286-2.012-9.608 5.585-19.257 23.138m178.116 8.755c-47.058 40.072-44.179 41.765 45.52 26.761 8.352-1.397 10.176-.889 6 1.671-3.3 2.023-27.6 6.735-54 10.471-59.576 8.43-53.48-2.339-54.84 96.88l-1.16 84.6 10.877-6.43c5.982-3.537 10.194-7.114 9.359-7.948-.835-.835 20.553-35.13 47.529-76.211 57.798-88.02 61.073-93.746 54.809-95.834-6.667-2.222-5.599-8.858 1.426-8.858 8.814 0 7.469-6.312-6.923-32.493-16.217-29.497-15.114-29.636-58.597 7.391M10.316 139.489c-1.371 2.219.939 7.291 5.134 11.273 4.196 3.981 15.486 16.398 25.089 27.593C92.117 238.484 140.91 292 144.153 292c3.177 0-28.76-53.245-40.041-66.755-5.318-6.37-32.442-49.301-40.787-64.558-4.781-8.74-11.38-13.47-23.25-16.667-9.117-2.455-18.981-5.386-21.921-6.514-2.939-1.128-6.467-.236-7.838 1.983m352.939 3.821C346.065 149.445 320 175.954 320 187.302c0 4.451-31.767 49.864-37.519 53.637-2.465 1.616-3.113 2.966-1.44 3 1.672.033-3.551 9.988-11.607 22.121-33.436 50.36-6.59 24.004 97.718-95.933 30.446-35.007 29.884-38.873-3.897-26.817" fill-rule="evenodd"/&amp;gt;&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9OQqjsS---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607150655_72904.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9OQqjsS---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607150655_72904.png" alt="" width="774" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;颜色正好相反，背景色为黑色，前景色为白色，动态切换效果是这样的：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GGZMCwCp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607150613_38416.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GGZMCwCp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220607150613_38416.gif" alt="" width="480" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这细节，也是没谁了。&lt;/p&gt;

&lt;h2&gt;
  
  
  Safari
&lt;/h2&gt;

&lt;p&gt;没错，又是Safari，来看看svg格式的Favicon支持列表：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SVG Favicons on IE is fully supported on None of the versions, partially supported on None of the versions, and not supported on 5.5-11 IE versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Edge is fully supported on 80-99, partially supported on None of the versions, and not supported on 12-79 Edge versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Firefox is fully supported on 41-100, partially supported on 4-40, and not supported on 2-3 Firefox versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Chrome is fully supported on 80-103, partially supported on None of the versions, and not supported on 4-79 Chrome versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Safari is fully supported on None of the versions, partially supported on None of the versions, and not supported on 3.2-15.4 Safari versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Opera is fully supported on 44-83, partially supported on None of the versions, and not supported on 9.5-66 Opera versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Safari on iOS is fully supported on None of the versions, partially supported on None of the versions, and not supported on 3.2-15.4 Safari on iOS versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Android Browser is fully supported on None of the versions, partially supported on None of the versions, and not supported on 2.3-99 Android Browser versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Opera Mobile is fully supported on None of the versions, partially supported on None of the versions, and not supported on 10-64 Opera Mobile versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Chrome for Android is fully supported on 97-100, partially supported on None of the versions, and not supported on below 97 Chrome for Android versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Firefox for Android is fully supported on None of the versions, partially supported on None of the versions, and not supported on 95-98 Firefox for Android versions.&lt;br&gt;&lt;br&gt;
SVG Favicons on Samsung Internet is fully supported on 13-16, partially supported on None of the versions, and not supported on 4-12 Samsung Internet versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;连三星浏览器都支持了，然而，只有Safari不支持，不愧是苹果生态圈，可谓独领风骚，无出其右。&lt;/p&gt;

&lt;p&gt;尝试苹果独有的mask-icon的声明方式另辟蹊径：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="mask-icon" href="/favicon.svg" color="#990000"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;但这样的声明方式只支持单色的svg文件，同时也无法使用媒体查询的特性来控制图标颜色模式，所以，很明显，mask-icon是鸡肋，味同嚼蜡，蜡要沿着嘴角流下来了。&lt;/p&gt;

&lt;p&gt;事实上，就算是苹果官网apple.com，也并没有使用mask-icon，就算坐等Safari支持原生svg favicon，苹果最快也要每六个月左右才更新一次 Safari，那时候，黄花菜都凉了。&lt;/p&gt;

&lt;p&gt;除此之外，在Safari中调试svg的favicon也是令人痛苦的，需要走下面的流程：&lt;/p&gt;

&lt;p&gt;1、退出Safari.&lt;br&gt;&lt;br&gt;
2、进入~/Library/Safari/Favicon Cache/目录&lt;br&gt;&lt;br&gt;
3、删除所有文件&lt;br&gt;&lt;br&gt;
4、进入~/Library/Safari/Touch Icons Cache/目录，重复第三步&lt;br&gt;&lt;br&gt;
5、进入~/Library/Safari/Template Icons/目录，重复第三步&lt;br&gt;&lt;br&gt;
6、清空废纸篓&lt;br&gt;&lt;br&gt;
7、再次打开Safari&lt;/p&gt;

&lt;p&gt;才能查看Safari下最新的Favicon效果。&lt;/p&gt;

&lt;h2&gt;
  
  
  结语
&lt;/h2&gt;

&lt;p&gt;鹌鹑嗉里寻豌豆，鹭鸶腿上劈精肉，于细枝末节处雕花，见微知著，对着favicon一番把玩之后，favicon.ico文件最好留着别删，因为也许还会有人在用IE访问您的站点也未可知，最后，代码开源于&lt;a href="https://github.com/zcxey2911/svg%5C_website%5C_logo%EF%BC%8C%E8%81%8A%E5%8D%9A%E8%AF%B8%E5%90%9B%E4%B8%80%E6%99%92%E3%80%82"&gt;https://github.com/zcxey2911/svg\_website\_logo，聊博诸君一晒。&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原文转载自「刘悦的技术博客」 &lt;a href="https://v3u.cn/a_id_215"&gt;https://v3u.cn/a_id_215&lt;/a&gt;&lt;/p&gt;

</description>
      <category>svg</category>
      <category>svgico</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>当我们进行性能优化，我们在优化什么（LightHouse优化实操）</title>
      <dc:creator>刘悦的技术博客</dc:creator>
      <pubDate>Thu, 02 Jun 2022 13:15:57 +0000</pubDate>
      <link>https://forem.com/liuyue/dang-wo-men-jin-xing-xing-neng-you-hua-wo-men-zai-you-hua-shi-yao-lighthouseyou-hua-shi-cao--34o1</link>
      <guid>https://forem.com/liuyue/dang-wo-men-jin-xing-xing-neng-you-hua-wo-men-zai-you-hua-shi-yao-lighthouseyou-hua-shi-cao--34o1</guid>
      <description>&lt;p&gt;原文转载自「刘悦的技术博客」&lt;a href="https://v3u.cn/a_id_214"&gt;https://v3u.cn/a_id_214&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;好的互联网产品不仅仅在功能上要高人一筹，在性能层面也需要出类拔萃，否则金玉其外败絮其中，页面是美轮美奂了，结果首屏半天加载不出来，难免让用户乘兴而来，败兴而归。&lt;/p&gt;

&lt;p&gt;幸运的是，前端的性能优化有诸多有迹可循的理论和方法，其中相对权威的，无疑是LightHouse。&lt;/p&gt;

&lt;p&gt;LightHouse 是一个开源的自动化工具，它作为 Chrome 浏览器的扩展程序运行，提供一套完整的站点评分标准，我们可以依据此标准对站点进行基准测试，从而达到优化的效果。&lt;/p&gt;

&lt;p&gt;怎么打开LightHouse？可以在Chrome浏览器开发人员工具中找到LightHouse。&lt;/p&gt;

&lt;p&gt;要打开“开发人员工具”，请选择：&lt;br&gt;&lt;br&gt;
“顶部菜单→查看→开发人员→开发人员工具”&lt;br&gt;&lt;br&gt;
或者使用快捷键：&lt;/p&gt;

&lt;p&gt;Mac系统上的“⌥+⌘+I”&lt;br&gt;&lt;br&gt;
Win系统上的“F12+Ctrl+Shift+I”。  &lt;/p&gt;

&lt;p&gt;随后点击生成报告按钮即可：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RcuWPOJo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220602190650_38711.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RcuWPOJo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220602190650_38711.png" alt="" width="880" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LightHouse评分大体上有四大指标，分别为：性能、无障碍、最佳做法以及SEO。&lt;/p&gt;
&lt;h2&gt;
  
  
  性能指标（Performance）
&lt;/h2&gt;

&lt;p&gt;性能指标里又分为六个小指标：&lt;/p&gt;

&lt;p&gt;Largest Contentful Paint 【简称LCP: 最大内容渲染】&lt;br&gt;&lt;br&gt;
FCP最大内容渲染时间标记了渲染出最大文本或图片的时间。&lt;br&gt;&lt;br&gt;
Total Blocking Time 【简称TBT: 总阻塞时间】&lt;br&gt;&lt;br&gt;
TBT测量了FCP（首次内容渲染）和TTI（可交互时间）之间的总耗时。TTI可能会被主线程阻塞以至于无法及时响应用户。大于50ms的任务称为长任务，当任意长任务出现时，主线程则称为被阻塞状态。由于浏览器不会打断正在进行中的长任务，所以，如果用户在执行长任务时和页面有交互事件时，浏览器必须等到该长任务完成才能响应。TBT计算的是在FCP到TTI之间所有长任务时间内总和。&lt;br&gt;&lt;br&gt;
First Contentful Paint 【简称FCP: 首次内容渲染】&lt;/p&gt;

&lt;p&gt;FCP测量了从页面开始加载到页面任意部分的内容渲染到屏幕上。&lt;/p&gt;

&lt;p&gt;Speed Index 【简称SI: 速度指数】&lt;br&gt;&lt;br&gt;
SI速度指数表明了网页内容的可见填充速度。lighthouse首先捕获页面加载的视屏，然后对比帧与帧之间视觉效果变化（通过计算结构相似指数SSMI来比较)。&lt;br&gt;&lt;br&gt;
Time to Interactive 【简称TTI: 可交互时间】&lt;br&gt;&lt;br&gt;
可交互时间是指网页需要多长时间才能提供完整交互功能。TTI测量了从页面开始加载到页面的主要附属资源加载完毕，并且可以足够快速回应用户输入的所用时间。&lt;br&gt;&lt;br&gt;
Cumulative Layout Shift 【简称CLS: 累积布局偏移】&lt;br&gt;&lt;br&gt;
CLS累积布局偏移旨在测量可见元素在视口内的移动情况。CLS值越小越好。&lt;/p&gt;
&lt;h2&gt;
  
  
  性能优化手段
&lt;/h2&gt;

&lt;p&gt;有哪些手段可以提高这些性能指标？&lt;/p&gt;

&lt;p&gt;首先需要优化的是页面“资源”，这里的资源指的是页面中加载的一切元素，包含但不限于：js文件、css文件、图片、视频等。&lt;/p&gt;

&lt;p&gt;对于js文件来说，首先要做的是业务分拆，不同页面只加载对应需要的文件，并且做到单页面只加载一个js文件，减少Http请求数，多余的文件要做合并压缩操作，但其实这里有一个基础问题，就是如果js文件本身就很庞大，压缩比例再高，也是杯水车薪，举个例子，一般情况下Jquery官方的压缩版就已经高达80kb左右了，这样的体积很难有再次压缩的优化空间，所以还不如直接摒弃Jquery，换成别的功能上可以替换的库，比如zepto，后者的体积只有26kb，是前者的四分之一。随后进行压缩合并操作，首先安装：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install uglify-js -g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;以本站为例，业务上用到的js库分别为zepto.min.js、my.js、lazyload.min.js、wordcloud2.min.js iconfont.js，将这五个js文件进行合并压缩：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uglifyjs zepto.min.js my.js lazyload.min.js wordcloud2.min.js iconfont.js -o ./1-min.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如此，最后得到一个体积为59kb的1-min.js文件，当然这是业务层面的压缩，还可以通过修改服务器进行gzip压缩：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location ~ .*.(jpg|gif|png|bmp|js|css)$ {  
                gzip on;  
                gzip_http_version 1.1;  
                gzip_comp_level 3;  
                gzip_types text/plain application/json application/x-javascript application/css application/xml application/xml+rss text/javascript application/x-httpd-php image/jpeg image/gif image/png image/x-ms-bmp;  
                }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;加载方式上，尽量使用预加载：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="preload" as="script" href="1-min.js" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;同时，对于一些站外js比如广告，或者一些js特效，我们可以对其进行延时加载的操作，即首屏加载好之后，再加载这些逻辑：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script nonce="EDNnf03nceIOfn39fn3e9h3sdfa"&amp;gt;  
(function() {  
var done = false;  
var script = document.createElement('script');  
script.async = true;  
script.type = 'text/javascript';  
script.src = '//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';  

var createScript = setTimeout(  
function(){  
document.getElementsByTagName('HEAD').item(0).appendChild(script);  
WordCloud(canvas, options);  
}, 7000  
);  

script.onreadystatechange = script.onload = function(e) {  
if (!done &amp;amp;&amp;amp; (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {  
(adsbygoogle = window.adsbygoogle || []).push({});  
}  
};  
})();  
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上面的逻辑就是首屏完成7秒后再加载Google广告和标签云特效。&lt;/p&gt;

&lt;p&gt;对于css文件的处理，原理和js文件差不多，宗旨也是分拆，缩小体积，并且压缩：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cssMinifier(['./bootstrap.min.css', '../js/kindeditor/plugins/code/prettify_dark.css', './style.css'], './tidy_min.css');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;优化后，得到体积为17kb的tidy_min.css文件。&lt;/p&gt;

&lt;p&gt;对于图片文件，不仅是首图，所有图片最好都采用新的图片格式Webp，用以减少其体积，具体操作方法请移步：&lt;a href="https://v3u.cn/a_id_190"&gt;石火电光追风逐日|前端优化之次时代图片压缩格式WebP的项目级躬身实践(Python3 PIL+Nginx)&lt;/a&gt;。对于特定的图片，比如Logo，使用svg格式图片，请移步：&lt;a href="https://v3u.cn/a_id_207"&gt;Logo小变动，心境大不同，SVG矢量动画格式网站Logo图片制作与实践教程(Python3)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;同时，对于图片一律声明宽高属性，并且使用支持lazyload.js组件推迟对屏幕外图片的加载。&lt;/p&gt;

&lt;p&gt;使用viewport标签加快移动端的载入速度：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;  
    &amp;lt;meta name="applicable-device" content="pc,mobile"/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  无障碍（Accessibility）
&lt;/h2&gt;

&lt;p&gt;访问无障碍检测所有用户是否都能有效地访问内容并浏览网站，无障碍性的每个指标项测试结果为pass或者fail，与性能指标项的计算方式不同，当页面只是部分通过某项指标时，页面的这项指标将不会得分。例如，如果页面中的一些元素有屏幕阅读器友好的命名，而其他的元素没有，那么这个页面的 screenreader-friendly-names 指标项得分为0。&lt;/p&gt;

&lt;p&gt;一般情况下，优化无障碍其实是对于站点标签的优化，比如页面元素是否具备title标签、title元素是否按降序排列、是否声明了页面语言类型、元素是否具备alt标签等等，值得一提的是，页面对比度也是无障碍评分重要的一环，假如背景色是white，那么前景色最好选择高对比度的颜色，比如black。&lt;/p&gt;

&lt;h2&gt;
  
  
  最佳做法（Best Practice）
&lt;/h2&gt;

&lt;p&gt;最佳做法检测可以改善网页的代码健康状况的一些最佳做法，评分的分值由相关指标的加权平均值计算而来。&lt;/p&gt;

&lt;p&gt;最佳做法指标我们可以理解为就是站点安全性的指标，多数情况下，需要保证协议为HTTPS，同时要开启CSP网页安全政策防止XSS攻击。&lt;/p&gt;

&lt;p&gt;CSP 的实质就是白名单制度，开发者明确告诉客户端，哪些外部资源可以加载和执行，等同于提供白名单。它的实现和执行全部由浏览器完成，开发者只需提供配置。CSP 大大增强了网页的安全性。攻击者即使发现了漏洞，也没法注入脚本，除非还控制了一台列入了白名单的可信主机。&lt;/p&gt;

&lt;p&gt;开启方法：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  搜索引擎优化（SEO）
&lt;/h2&gt;

&lt;p&gt;搜索引擎优化检测搜索引擎对网页内容的理解程度是怎样的，评分的分值由相关指标的加权平均值计算而来。&lt;/p&gt;

&lt;p&gt;说白了，就是站点页面是否适合搜素引擎蜘蛛的抓取以及收录，以本站为例，搜索引擎需要的标签如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;head&amp;gt;  
    &amp;lt;meta http-equiv="Content-Type" content="text/html;charset=utf-8"&amp;gt;  
    &amp;lt;meta http-equiv="X-UA-Compatible" content="IE=edge"&amp;gt;  
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;  
    &amp;lt;meta name="applicable-device" content="pc,mobile"/&amp;gt;  
    &amp;lt;title&amp;gt;当我们进行性能优化，我们在优化什么（LightHouse优化实操）-刘悦&amp;lt;/title&amp;gt;  
    &amp;lt;meta name="description" content="好的互联网产品不仅仅在功能上要高人一筹，在性能层面也需要出类拔萃，否则金玉其外败絮其中，页面是美轮美奂了，结果首屏半天加载不出来，难免让用户乘兴而来，败兴而归。幸运的是，前端的性能优化有诸多有迹可循的理论和方法，其中相对权威的，无疑是LightHouse。LightHouse是一个开源的自动化工具，它作为Chrome浏览器的扩展程序运行，提供一套完整的站点评分标准，我们可以依"&amp;gt;  
    &amp;lt;meta content="刘悦" name="Author"&amp;gt;  
    &amp;lt;link rel="canonical" href="https://v3u.cn/a_id_214"/&amp;gt;  
    &amp;lt;link rel="miphtml" href="https://v3u.cn/mipa_id_214"&amp;gt;  
    &amp;lt;link rel="stylesheet" href="/v3u/Public/css/tidy_min.css?v=11"/&amp;gt;  
    &amp;lt;link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/&amp;gt;  
    &amp;lt;link rel="icon" href="favicon.ico" type="image/x-icon"/&amp;gt;  
    &amp;lt;link rel="stylesheet" href="/v3u/Public/css/share.min.css?v=1"&amp;gt;  
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;包括页面标题、描述、作者、页面唯一标识等等元素。&lt;/p&gt;

&lt;p&gt;当我们完成上面这些指标的优化之后，就可以，坐下来，欣赏这紫禁烟花一万重了：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YwWsNw5I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220602200644_24294.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YwWsNw5I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220602200644_24294.gif" alt="" width="320" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;正是：东风夜放花千树，更吹落，星如雨。&lt;/p&gt;

&lt;h2&gt;
  
  
  结语
&lt;/h2&gt;

&lt;p&gt;前端的性能分析和优化方式，无论是传统性能还是感官性能完全可以根据LightHouse按图索骥。过程中可以针对某些指标进行一定的取舍，虽然本站在LightHouse的优化实践中取得了一定的效果，但路漫漫其修远兮，吾将上下而求索。&lt;/p&gt;

&lt;p&gt;原文转载自「刘悦的技术博客」 &lt;a href="https://v3u.cn/a_id_214"&gt;https://v3u.cn/a_id_214&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>青山不遮，毕竟东流，集成Web3.0身份钱包MetaMask以太坊一键登录(Tornado6+Vue.js3)</title>
      <dc:creator>刘悦的技术博客</dc:creator>
      <pubDate>Wed, 01 Jun 2022 01:07:54 +0000</pubDate>
      <link>https://forem.com/liuyue/qing-shan-bu-zhe-bi-jing-dong-liu-ji-cheng-web30shen-fen-qian-bao-metamaskyi-tai-fang-jian-deng-lu-tornado6vuejs3-5f96</link>
      <guid>https://forem.com/liuyue/qing-shan-bu-zhe-bi-jing-dong-liu-ji-cheng-web30shen-fen-qian-bao-metamaskyi-tai-fang-jian-deng-lu-tornado6vuejs3-5f96</guid>
      <description>&lt;p&gt;原文转载自「刘悦的技术博客」&lt;a href="https://v3u.cn/a_id_213"&gt;https://v3u.cn/a_id_213&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;上世纪九十年代，海湾战争的时候，一位美军军官担心他们的五角大楼会被敌人的一枚导弹干掉，从而导致在全球的美军基地处于瘫痪状态。这时候，有一位天才的科学家说，最好的中心就是没有中心。是的，这就是最朴素的去中心化思想，于是互联网出现了。一个没有互联网的时代是无法想象的，互联网的核心就是把一个信息分成若干的小件，用不同的途径传播出去，怎么方便怎么走。&lt;/p&gt;

&lt;p&gt;三十年后的今天，去中心化身份逐渐被广泛采用。用户的部分在线活动在链上是公开的，可通过加密钱包搜索到，用户在链上创造、贡献、赚取和拥有的东西，都反映了他们的喜好，也逐渐积累成该用户的身份和标识。&lt;/p&gt;

&lt;p&gt;当我们的用户厌倦了传统的电子邮件/密码注册流程时，他们会选择Google、GitHub等社交登录方式，这种方式虽然节约了用户的时间，但登录信息也会被第三方平台记录，也就是说我们用平台账号做了什么，平台都会一目了然，甚至还会对我们的行为进行分析、画像。那么有没有一种登录方式，它的所有信息都只保存在客户端和后端，并不牵扯三方平台授权，最大化的保证用户隐私呢？Web3.0给我们提供了一种选择：MetaMask。&lt;/p&gt;

&lt;h2&gt;
  
  
  MetaMask
&lt;/h2&gt;

&lt;p&gt;MetaMask是用于与以太坊区块链进行交互的软件加密货币钱包。MetaMask允许用户通过浏览器插件或移动应用程序访问其以太坊钱包，然后可以使用这些扩展程序与去中心化应用程序进行交互。当然了，首先需要拥有一个MetaMask钱包，进入&lt;a href="https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn"&gt;https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;安装metamask浏览器插件：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f_GgTbQy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220531170510_40241.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f_GgTbQy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220531170510_40241.png" alt="" width="880" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;随后点开插件，创建账号，记录密码、钱包地址、以及助记词等信息。&lt;/p&gt;

&lt;p&gt;安装好插件之后，我们就可以利用这个插件和网站应用做交互了。&lt;/p&gt;

&lt;h2&gt;
  
  
  钱包登录流程
&lt;/h2&gt;

&lt;p&gt;登录逻辑和传统的三方登录还是有差异的，传统三方登录一般是首先跳转三方平台进行授权操作，随后三方平台将code验证码返回给登录平台，登录平台再使用code请求三方平台换取token，再通过token请求用户账号信息，而钱包登录则是先在前端通过Web3.js浏览器插件中保存的私钥对钱包地址进行签名操作，随后将签名和钱包地址发送到后端，后端利用Web3的库用同样的算法进行验签操作，如果验签通过，则将钱包信息存入token，并且返回给前端。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zLOyM5Sn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220531190527_32835.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zLOyM5Sn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220531190527_32835.png" alt="" width="880" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  前端签名操作
&lt;/h2&gt;

&lt;p&gt;首先需要下载前端的Web3.0操作库，&lt;a href="https://docs.ethers.io/v4/%EF%BC%8C%E9%9A%8F%E5%90%8E%E9%9B%86%E6%88%90%E5%88%B0%E7%99%BB%E5%BD%95%E9%A1%B5%E9%9D%A2%E4%B8%AD%EF%BC%9A"&gt;https://docs.ethers.io/v4/，随后集成到登录页面中：&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script src="{{ static_url("js/ethers-v4.min.js") }}"&amp;gt;&amp;lt;/script&amp;gt;  
&amp;lt;script src="{{ static_url("js/axios.js") }}"&amp;gt;&amp;lt;/script&amp;gt;  
&amp;lt;script src="{{ static_url("js/vue.js") }}"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这里我们基于Vue.js配合Axios使用。&lt;/p&gt;

&lt;p&gt;接着声明登录激活方法：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sign_w3:function(){  

                    that = this;  
                    ethereum.enable().then(function () {  

    this.provider = new ethers.providers.Web3Provider(web3.currentProvider);  

    this.provider.getNetwork().then(function (result) {  
        if (result['chainId'] != 1) {  

            console.log("Switch to Mainnet!")  

        } else { // okay, confirmed we're on mainnet  

            this.provider.listAccounts().then(function (result) {  
                console.log(result);  
                this.accountAddress = result[0]; // figure out the user's Eth address  
                this.provider.getBalance(String(result[0])).then(function (balance) {  
                    var myBalance = (balance / ethers.constants.WeiPerEther).toFixed(4);  
                    console.log("Your Balance: " + myBalance);  
                });  

                // get a signer object so we can do things that need signing  
                this.signer = provider.getSigner();  

                var rightnow = (Date.now()/1000).toFixed(0)  
        var sortanow = rightnow-(rightnow%600)  

        this.signer.signMessage("Signing in to "+document.domain+" at "+sortanow, accountAddress, "test password!")  
            .then((signature) =&amp;gt; {               that.handleAuth(accountAddress,signature);  
            });  

                console.log(this.signer);  
            })  
        }  
    })  
})  

                },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;通过使用signMessage方法返回签名，这里加签过程中使用基于时间戳的随机数防止未签名，当前端签名生成好之后，立刻异步请求后台接口：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//检查验证  
                handleAuth:function(accountAddress, signature){  


                    this.myaxios("/checkw3/","post",{"public_address":accountAddress,"signature":signature}).then(data =&amp;gt;{  

                        if(data.errcode==0){  
                            alert("欢迎:"+data.public_address);  
                            localStorage.setItem("token",data.token);  
                            localStorage.setItem("email",data.public_address);  
                            window.location.href = "/";  
                        }else{  
                            alert("验证失败");  
                        }  
                 });  



                }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这里将当前账户的钱包地址和签名传递给后端，如图所示：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2ZT19Hbl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220531180518_60721.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2ZT19Hbl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220531180518_60721.png" alt="" width="880" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;完整页面代码：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;  
&amp;lt;html lang="en"&amp;gt;  

&amp;lt;head&amp;gt;  
    &amp;lt;meta charset="utf-8"&amp;gt;  
    &amp;lt;title&amp;gt;Edu&amp;lt;/title&amp;gt;  
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"&amp;gt;  
    &amp;lt;link rel="stylesheet" href="{{ static_url("css/min.css") }}" &amp;gt;  
    &amp;lt;link rel="icon" href="/static/img/favicon.68cbf4197b0c.png"&amp;gt;  
    &amp;lt;script src="{{ static_url("js/ethers-v4.min.js") }}"&amp;gt;&amp;lt;/script&amp;gt;  
    &amp;lt;script src="{{ static_url("js/axios.js") }}"&amp;gt;&amp;lt;/script&amp;gt;  
    &amp;lt;script src="{{ static_url("js/vue.js") }}"&amp;gt;&amp;lt;/script&amp;gt;  
&amp;lt;/head&amp;gt;  

&amp;lt;body&amp;gt;  
    &amp;lt;div&amp;gt;  

    {% include "head.html" %}  

    &amp;lt;div id="app"  class="container main-content"&amp;gt;  

 &amp;lt;div class="row justify-content-center"&amp;gt;  
&amp;lt;div class="col-md-10 col-lg-8 article"&amp;gt;  
&amp;lt;div class="article-body page-body mx-auto" style="max-width: 400px;"&amp;gt;  
&amp;lt;h1 class="text-center mb-4"&amp;gt;Sign-in&amp;lt;/h1&amp;gt;  
&amp;lt;div class="socialaccount_ballot"&amp;gt;  
&amp;lt;div class="text-center mb-3"&amp;gt;  
&amp;lt;ul class="list-unstyled"&amp;gt;  
    &amp;lt;li&amp;gt;  
&amp;lt;a @click="sign_w3()" title="GitHub" class="socialaccount_provider github btn btn-secondary btn-lg w-100" href="JavaScript:void(0)"&amp;gt;Connect With &amp;lt;strong&amp;gt;Meta Mask&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;  
&amp;lt;/li&amp;gt;  
&amp;lt;li&amp;gt;  
&amp;lt;a title="GitHub" class="socialaccount_provider github btn btn-secondary btn-lg w-100" href="https://github.com/login/oauth/authorize?client_id=249b69d8f6e63efb2590&amp;amp;redirect_uri=http://localhost:8000/github_back/"&amp;gt;Connect With &amp;lt;strong&amp;gt;GitHub&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;  
&amp;lt;/li&amp;gt;  
&amp;lt;/ul&amp;gt;  
&amp;lt;/div&amp;gt;  
&amp;lt;div class="text-center text-muted my-3"&amp;gt;— or —&amp;lt;/div&amp;gt;  
&amp;lt;/div&amp;gt;  

&amp;lt;div class="form-group"&amp;gt;  
&amp;lt;div id="div_id_login" class="form-group"&amp;gt;  
&amp;lt;label for="id_login" class=" requiredField"&amp;gt;  
Email&amp;lt;span class="asteriskField"&amp;gt;*&amp;lt;/span&amp;gt;  
&amp;lt;/label&amp;gt;  
&amp;lt;div class=""&amp;gt;  
&amp;lt;input type="email" v-model="email" placeholder="" autocomplete="email" autofocus="autofocus" class="textinput textInput form-control" &amp;gt;  
&amp;lt;/div&amp;gt;  
&amp;lt;/div&amp;gt;  
&amp;lt;/div&amp;gt;  
&amp;lt;div class="form-group"&amp;gt;  
&amp;lt;div id="div_id_password" class="form-group"&amp;gt;  
&amp;lt;label for="id_password" class=" requiredField"&amp;gt;  
Password&amp;lt;span class="asteriskField"&amp;gt;*&amp;lt;/span&amp;gt;  
&amp;lt;/label&amp;gt;  
&amp;lt;div class=""&amp;gt;  

&amp;lt;input type="password" v-model="password" placeholder="" autocomplete="current-password" minlength="8" maxlength="99" class="textinput textInput form-control" &amp;gt;  
&amp;lt;/div&amp;gt;  
&amp;lt;/div&amp;gt;  
&amp;lt;/div&amp;gt;  

&amp;lt;div class="text-center"&amp;gt;  
&amp;lt;button  class="btn btn-primary btn-lg text-wrap px-5 mt-2 w-100" name="jsSubmitButton" @click="sign_on"&amp;gt;Sign-In&amp;lt;/button&amp;gt;  
&amp;lt;/div&amp;gt;  



&amp;lt;/div&amp;gt;  
&amp;lt;/div&amp;gt;  
&amp;lt;/div&amp;gt;  


    &amp;lt;/div&amp;gt;  

    {% include "foot.html" %}  

    &amp;lt;/div&amp;gt;  

    &amp;lt;script&amp;gt;  

        const App = {  
            data() {  
                return {  
                    email:"",  
                    password:"",  

                    provider:null,  
                    accountAddress:"",  
                    signer:null  
                };  
            },  
            created: function() {  

            },  
            methods: {  
                //metamask登录  
                sign_w3:function(){  

                    that = this;  
                    ethereum.enable().then(function () {  

    this.provider = new ethers.providers.Web3Provider(web3.currentProvider);  

    this.provider.getNetwork().then(function (result) {  
        if (result['chainId'] != 1) {  

            console.log("Switch to Mainnet!")  

        } else { // okay, confirmed we're on mainnet  

            this.provider.listAccounts().then(function (result) {  
                console.log(result);  
                this.accountAddress = result[0]; // figure out the user's Eth address  
                this.provider.getBalance(String(result[0])).then(function (balance) {  
                    var myBalance = (balance / ethers.constants.WeiPerEther).toFixed(4);  
                    console.log("Your Balance: " + myBalance);  
                });  

                // get a signer object so we can do things that need signing  
                this.signer = provider.getSigner();  

                var rightnow = (Date.now()/1000).toFixed(0)  
        var sortanow = rightnow-(rightnow%600)  

        this.signer.signMessage("Signing in to "+document.domain+" at "+sortanow, accountAddress, "test password!")  
            .then((signature) =&amp;gt; {               that.handleAuth(accountAddress,signature);  
            });  

                console.log(this.signer);  
            })  
        }  
    })  
})  

                },  
                //检查验证  
                handleAuth:function(accountAddress, signature){  


                    this.myaxios("/checkw3/","post",{"public_address":accountAddress,"signature":signature}).then(data =&amp;gt;{  

                        if(data.errcode==0){  
                            alert("欢迎:"+data.public_address);  
                            localStorage.setItem("token",data.token);  
                            localStorage.setItem("email",data.public_address);  
                            window.location.href = "/";  
                        }else{  
                            alert("验证失败");  
                        }  
                 });  



                },  
                sign_on:function(){  

                if(this.email == ""){  
                    alert("邮箱不能为空");  
                    return false;  
                }  

                if(this.password == ""){  
                    alert("密码不能为空");  
                    return false;  
                }  

                //登录  
                this.myaxios("/user_signon/","get",{"email":this.email,"password":this.password}).then(data =&amp;gt;{  

                    if(data.errcode != 0){  

                    alert(data.msg);  

                    }else{  
                    alert(data.msg);  
                    localStorage.setItem("token",data.token);  
                    localStorage.setItem("email",data.email);  
                    window.location.href = "/";   

                    //localStorage.removeItem("token")  
                    }  

                 });  

                 },  

            },  
        };  
const app = Vue.createApp(App);  
app.config.globalProperties.myaxios = myaxios;  
app.config.globalProperties.axios = axios;  
app.config.compilerOptions.delimiters = ['${', '}']  
app.mount("#app");  

    &amp;lt;/script&amp;gt;  

&amp;lt;/body&amp;gt;  

&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tornado后端验签：
&lt;/h2&gt;

&lt;p&gt;有人说，既然钱包私钥是存储在浏览器中，也就是保存在客户端，那签名已经通过私钥生成了，为什么还要过一遍后端呢？这不是多此一举吗？事实上，攻击者完全可能获取到前端生成的所有信息，所以签名一定必须得是后端提供，或者至少有一步后端验证，比如著名的&lt;a href="https://v3u.cn/a_id_119"&gt;微信小程序获取openid问题&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;后端我们使用异步框架Tornado，配合web3库进行调用，首先安装依赖：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip3 install tornado==6.1  
pip3 install web3==5.29.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;随后创建异步视图方法：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from tornado.web import url  
import tornado.web  
from tornado import httpclient  
from .base import BaseHandler

from web3.auto import w3  
from eth_account.messages import defunct_hash_message  
import time  

class CheckW3(BaseHandler):  

    async def post(self):  

        public_address = self.get_argument("public_address")  
        signature = self.get_argument("signature")  

        domain = self.request.host  
        if ":" in domain:  
            domain = domain[0:domain.index(":")]  

        now = int(time.time())  
        sortanow = now-now%600  

        original_message = 'Signing in to {} at {}'.format(domain,sortanow)  
        print("[+] checking: "+original_message)  
        message_hash = defunct_hash_message(text=original_message)  
        signer = w3.eth.account.recoverHash(message_hash, signature=signature)  

        if signer == public_address:  
            try:  
                user = await self.application.objects.get(User,email=public_address)  
            except Exception as e:  
                user = await self.application.objects.create(User,email=public_address,password=create_password("third"),role=1)  

            myjwt = MyJwt()  
            token = myjwt.encode({"id":user.id})  
            self.finish({"msg":"ok","errcode":0,"public_address":public_address,"token":token})  
        else:  
            self.finish({"msg":"could not authenticate signature","errcode":1})


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这里通过recoverHash方法对签名进行反编译操作，如果反编译后的钱包地址和前端传过来的钱包地址温和，那么说明当前账户的身份验证通过：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZrU7k_uH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220531190531_18895.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZrU7k_uH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://v3u.cn/v3u/Public/js/editor/attached/20220531190531_18895.png" alt="" width="880" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;当验签通过之后，利用钱包地址在后台创建账号，随后将钱包地址、token等信息返回给前端，前端将其保存在stroage中即可。&lt;/p&gt;

&lt;h2&gt;
  
  
  结语
&lt;/h2&gt;

&lt;p&gt;没错，将至已至，未来已来，是时候将Web3.0区块链技术融入产品了，虽然有些固有的思维方式依然在人们的脑海挥之不去，但世界却在时不我待地变化着，正是：青山遮不住，毕竟东流去！项目开源在&lt;a href="https://github.com/zcxey2911/Tornado6%5C_Vuejs3%5C_Edu"&gt;https://github.com/zcxey2911/Tornado6\_Vuejs3\_Edu&lt;/a&gt; ，与君共觞。&lt;/p&gt;

&lt;p&gt;原文转载自「刘悦的技术博客」 &lt;a href="https://v3u.cn/a_id_213"&gt;https://v3u.cn/a_id_213&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
