8、脚本、uuid,环境、服务器、Linux乌班图VMware、web相关之互联网ipconfig解读、全局变量与请求代理proxy、前端工程化工具gulp|webpack|vite|rollup、前端框架angular|react|vue、node含@定义|nvm|npm、Live|yapi、微前端、神策插件源码解读、aplus插件源码解读(3550行)

一、脚本、uuid
1、脚本
  (1)脚本‌是一种使用特定描述性语言编写的可执行文件,通常以文本形式保存
  (2)脚本包含了一系列指令,这些指令由计算机的‌解释器或‌脚本引擎执行,用于自动化执行常规任务,如‌自动化数据处理、‌网页开发等
2、UUID,通用唯一识别码
  (1)英文全称,Universally Unique Identifier
  (2)一种标准化128位标识符,保证全球范围内的唯一性(冲突概率极低)
  (3)JS中引用UUID,
    npm install uuid
    import { v4 as uuidv4 } from 'uuid';
    const id = uuidv4();
      
二、环境
1、生产环境:服务器
2、开发环境
  (1)前端:node
  (2)后台:ubuntu
3、window环境下的上传工具
  (1)MobaXterm
  (2)xshell
4、ubuntu环境下的上传命令
  scp -r localfile.txt username@192.168.0.1:/home/username/
  (1)scp是命令,-r是参数
  (2)localfile.txt 是文件的路径和文件名
  (3)username是服务器账号
  (4)192.168.0.1是要上传的服务器ip地址
  (5)/home/username/是要拷入的文件夹路径

三、服务器
1、安装服务器系统
  (1)给服务器--插上--系统U盘
  (2)重启服务器:拔、插服务器电源(服务器自动安装系统,安装成功时,服务器自动断电)
  (3)从服务器--拔掉--系统U盘
  (4)再次重启服务器:拔、插服务器电源
2、登陆服务器
  (1)用“网线”把“我的电脑”和“服务器”进行物理连接
  (2)在“终端工具”上,用“终端工具”的用户名和密码登录“终端工具”
  (3)在“终端工具”上,用“服务器”的用户名和密码登录“服务器”
3、给服务器配置IP
  (1)root@CyOS:/root# ifconfig mgmt0 192.168.10.156(给接口配置IP。它有65535个端口,ifconfig(配置))mgmt0(接口))
  (2)按enter键,IP配置成功
4、启动服务器
  (1)root@CyOS:/root# cd /usr/local/audit-web
  (2)root@CyOS:/usr/local/audit-web# python manage.py runserver
5、辅助步骤
  (1)Ctrl+C(退出进程)
  (2)root@CyOS:/usr/local/audit-web# pkill -f python -9(杀死python进程,出现Address already in use时,需要用到这个命令)
  (3)root@CyOS:/usr/local/audit-web# pkill -f uwsgi -9(杀死名为uwsgi的进程,-f匹配所有参数列表,-9强制)
  (4)root@CyOS:/usr/local/audit-web# iptables -P INPUT ACCEPT(关闭防火墙,P INPUT ACCEPT均大写)
  (5)root@CyOS:/usr/local/audit-web# ps aux|grep python(查看活进程,aux显示所有进程和进程状态,grep在这些里搜索)
  (6)root@CyOS:/root# ifconfig(显示或设置网络设备)/////////////////////////
  (7)root@CyOS:/root# cat /usr/local/etc/suricata/version.autogen(查看当前版本)
6、通过CMD命令窗口,给本地服务器更新文件的流程
  (1)window + r;cmd
  (2)ssh root@172.18.58.30,root为登录名
    (2-1)SSH(Secure Shell,安全外壳)之所以能够保证安全,原因在于它采用了公钥加密。整个过程是这样的:
    (2-2)远程主机收到用户的登录请求,把自己的公钥发给用户。
    (2-3)用户使用这个公钥,将登录密码加密后,发送回来。
    (2-4)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。
  (3)输入密码
  (4)wget -O /my.sh "http://192.168.80.152:3000/audit/shell?branch=develop-2.0.8&command=gulp build" 
      && chmod +x /my.sh && /my.sh && rm -f /my.sh
    (4-1)-O /my.sh:将下载的文件存储在根目录下并重命名为“my.sh”
    (4-2)chmod +x /my.sh:将执行权限赋给根目录下的“my.sh”
    (4-3)/my.sh:运行根目录下“my.sh”
    (4-4)rm -f /my.sh:删除根目录下“my.sh”
  (5)Windows运行CMD常用命令:https://blog.csdn.net/gaofengyan/article/details/89447293
  (6)在WINDOWS操作系统里执行CMD命令,可以模拟出DOS操作系统。
7、负载均衡,来源https://blog.csdn.net/yiXin_Chen/article/details/123158091
  (1)正向代理,客户端访问代理服务器,前者指定目标服务器,代理服务器代理的是客户端,服务器不知道客户端的地址
  (2)反向代理,客户端访问代理服务器,后者指定目标服务器,代理服务器代理的是服务器,客户端不知道服务器的地址
  (3)负载均衡,反向代理时,代理服务器把不同的用户访问,指派给不同的目标服务器处理,避免某一目标服务器负载过重
  (4)Nginx,性能非常好的反向代理服务器,用来做负载均衡,可以在Linux系统中运行

四、本地文件上传至服务器(以MobaXterm为例)
1、基本快捷键
  (1)复制:Ctrl + Insert
  (2)粘贴:Shift + Insert
2、连接服务器
  (1)点击左上方Session
  (2)点击左上方SSH
  (3)Remote host *:172.18.57.88;勾选方框;Specify username:root;点击OK;
  (4)password:cy888888;回车
  (5)是否保存密码:是
3、关闭服务
  (1)root@OS:~# ls
  (2)lsof -i:5000
  (3)kill 31096
4、开启服务
  (1)python /usr/local/process_monitor/process_monitor.py
5、重启服务
  (1)pkill -9 uwsgi3
6、上传
  (1)在箭头下方的输入框输入:/usr/local/audit-html/template/;enter
  (2)点击绿色向上虚线箭头,选择要上传的文件,上传
  (3)重启服务器,pkill -9 uwsgi3
7、进入文件夹
  (1)cd /usr/local/audit-html/template/
8、Portable之本地替换服务器文件的步骤,以景宝为例
  (1)下载软件并登录
  (2)进入到相关文件夹下:root@OS:~# cd /usr/local/audit-html/template/
  (3)删除原文件:rm -rf audit-with-reports.html
  (4)上传新文件:rz
  (5)关闭服务:pkill -9 uwsgi
  (6)重启服务:uwsgi3 /usr/local/audit-web/WSGI/uwsgi.ini
  (7)实时观察:tail -f /data/flask_web/logs/uwsgi.log 
9、Jenkins,基于Java开发的一种持续集成工具
  (1)软件版本发布/测试
  (2)监控外部调用

五、Linux命令与目录
1、清除终端内容
  (1)ctr+l:终端内容滚到顶部上面            
  (2)clear:终端内容滚到顶部上面
  (3)reset:清除终端内容
2、ps:显示系统进程
  (1)ps -ef:用标准格式显示进程,如ps -ef|grep python 
  (2)ps aux:用BSD格式显示进程,如ps aux|grep python
3、cd:进入目录
  (1)cd /:进入系统根目录
  (2)cd ~:进入用户根目录
  (3)cd ../:切换到上一级目录
4、ls:显示本目录下之内容
  (1)-a 显示所有文件及目录 (. 开头的隐藏文件也会列出)
  (2)-l 除文件名称外,亦将文件型态、权限、拥有者、文件大小等资讯详细列出
  (3)-r 将文件以相反次序显示(原定依英文字母次序)
  (4)-t 将文件依建立时间之先后次序列出
  (5)-A 同 -a ,但不列出 "." (目前目录) 及 ".." (父目录)
  (6)-F 在列出的文件名称后加一符号;例如可执行档则加 "*", 目录则加 "/"
  (7)-R 若目录下有文件,则以下之文件亦皆依序列出
5、ls -l运行结果说明
  示例:drwxr-xr-x 1 qiancheng qiancheng 54713 11月 19 13:08 0.js
  (1)文件属性:drwxr-xr-x。d(-文件,d目录)rwx(用户权限)r-x(组用户权限)r-x(其他用户权限);rwx-的含义:r可读,w可写,x可执行,-没有权限
  (2)文件硬链接数量:1
  (3)所有者:qiancheng(user)
  (4)所属用户组:qiancheng(group)
  (5)文件大小:54713
  (6)修改时间:11月 19 13:08
  (7)文件名:0.js(Filename)
6、关于目录的命令
  (1)目录创建:mkdir dir
  (2)目录改名:mv/cp oldDir newDir(不存在);
  (3)目录复制:mv/cp oneDir newDir(存在);
  (4)目录查找:find dir -type d;
  (5)目录清空:A、根目录清空:rm -rf /*;B、本目录清空:rm -rf *;C、子目录清空:rm -rf dir/*;
  (6)目录删除:rm -rf dir;
7、关于文件的命令  
  (1)文件创建:A、touch aaa.js;B、cat >> aaa.js
  (2)文件改名:mv aaa.js bbb.js
  (3)文件复制:cp aaa.js bbb.js
  (4)文件查找:A、find dir -type f;B、find dir -name "*.js"
  (5)文件清空:A、> aaa.js;B、cat /dev/null > aaa.js
  (6)文件删除:rm aaa.js
8、文件内容查看
  (1)nl:显示所有文件内容,并输出行号
  (2)cat:由第一行开始显示文件内容
  (3)tac:从最后一行开始显示文件内容
  (4)less:一页一页的显示文件内容,可以用上下键上下翻页
  (5)more:一页一页的显示文件内容,可以往enter键向下翻页
  (6)head:只看头几行
  (7)tail:只看尾巴几行
9、grep:与字符串相关的搜索
  (1)grep "grep" html/*.js
  (2)grep "g.\{0,4\}p" html/*.js
  (3)grep -i "grep" html/*.js (忽略大小写)
  (4)grep -w "grep" html/*.js (搜索整个词)
  (5)grep -n "grep" html/*.js (显示行号)
  (5)grep -l "grep" html/*.js (只显示文件名)
  (6)grep -r "grep" html (递归搜索当前目录及其子目录的全部文件)
  (7)grep -v "grep" html/*.js (显示不匹配字串的行)
  (8)grep -c "grep" html/*.js (统计匹配的行数)
  (9)grep -B 2 "grep" html/*.js (显示之前2行,A后,C前后各)
  (10)grep,全局正则表达式输出,global regular expression print */
10、echo:输入字符串
  (1)覆盖写入字符串(无引号):echo "字符串" > 1.js
  (2)覆盖写入字符串(有引号):echo "\"字符串\"" > 1.js
  (3)转义覆盖写入字符串(有引号):echo -e "\"这\n是\n换\n行\"" > 1.js;e开启转义,\n换行,\c:不换行
  (4)追加写入字符串:echo "字符串" >> 1.js
11、关机
  (1)shutdown:关机
  (2)shutdown -t 2:2秒后关机
  (3)shutdown -h now:立刻关机
  (4)shutdown -h 20:00:20:00关机
  (5)shutdown -h +10:10分钟后关机
  (6)shutdown -r now:系统立刻重启
  (7)shutdown -r +30:30分钟后重启
  (8)halt:关机
  (9)poweroff:关机
  (10)init 0:关机
  (11)reboot:重启
12、压缩与解压(解压导出需要加参数)
  (1)压缩:tar -czvf file.tar ./aaa/file1;zip html.zip ./aaa
  (2)解压:tar -xzvf file.tar -C ./aaa/file2;unzip -d ./aaa file.zip
  (3)tar参数之-c建立新的备份文件
  (4)tar参数之-x从备份文件中还原文件
  (5)tar参数之-z通过gzip指令处理备份文件
  (6)tar参数之-v显示指令执行过程
  (7)tar参数之-f指定备份文件
  (8)unzip参数之-d指定文件解压缩后所要存储的目录
附、其它
  (1)打开终端:Ctrl+Alt+T
  (2)查看当前目录:pwd
13、Linux一级目录:
 来源:https://www.runoob.com/linux/linux-system-contents.html
  (1)srv:存放着-服务启动-之后-需要提取-的数据
  (2)root:存放着-系统管理员-的主目录
  (3)sbin:存放着-系统管理员-使用的-系统管理程序
  (4)home:存放着-用户-的主目录
  (5)bin: 存放着-用户-的常用命令
  (6)boot:存放着-系统启动-时使用的文件
  (7)run:存放着-系统启动-以来的信息
  (8)etc:存放着-系统管理-所需要的文件
  (9)dev:存放着-外部硬件-的设备,包括硬盘,U盘,光驱,串口,打印机等等
  (10)media:存放着-自动识别-的设备,比如U盘下的ubuntu
  (11)mnt:存放着-临时挂载的-别的文件,比如光驱,进入后可以查看光驱内容
  (12)tmp:存放着-临时文件
  (13)var:存放着-经常被修改-的文件
  (14)opt:存放着-主机-额外安装-的软件,比如搜狗输入法
  (15)lib:存放着-动态连接共享库。类似于Windows 里的 DLL 文件
  (16)proc:存放着-当前内核-运行状态-的文件
  (17)selinux:防火墙,存放selinux相关的文件的
  (18)usr:存放着-应用程序。类似于 windows 下的 program files 目录,unix shared resources(共享资源) 的缩写
  (18-1)/usr/bin:系统用户使用的应用程序
  (18-2)/usr/sbin:超级用户使用的比较高级的管理程序和系统守护程序
  (18-3)/usr/src:内核源代码默认的放置目录
附、Linux文件的时间属性
  (1)atime:文件内容查看时间,Access time,使用more、cat对该文件进行查看时,atime将更新,ls -lu
  (2)mtime:文件内容修改时间,Modify time,使用vi、vim对文件进行修改后保存,mtime将更新,ls -l
  (3)ctime:文件属性变更时间,Change time,文件名、内容、大小、权限、所属组等改变时,ctime将更新,ls -lc

六、Linux,ubuntu和VMware
1、三者之间的关系
  (1)Linux是一个操作系统;
  (2)Ubuntu是一个以桌面应用为主的Linux操作系统
  (3)VMware是一个在安装的过程中可以导入ubuntu的虚拟主机
  (4)在ubuntu里,应该用Linux命令来操作里面的文件
  (5)安装步骤;点击VMware.exe文件直到结束-->点击桌面VMware图标-->点击“创建新的虚拟机”-->安装依赖(最后一步,耗时约40分钟)
  (6)4步完全卸载VMware。A、关闭VMware;B、应用卸载-->vmware-->修改-->下一步-->删除;C、C:\Program Files (x86)下删除VMware;D、C:\Users\用户名\Documents下删除Virtual Machines,内含ubuntu 
  (7)层级关系:Unix-->Linux-->Debian(得便,自由操作系统)-->Ubuntu
2、Linux的vim编辑器的安装和使用
  (1)安装:sudo apt install vim
  (2)显示文件内容:vim 路径,如sudo vim ~/.bashrc
  (3)命令模式。i 切换到输入模式;: 切换到底线命令模式;x 删除当前光标所在处的字符。
  (4)输入模式。ESC 退回命令模式。
  (5)底线命令模式。q 退出vim,回到(2)执行前的状态;w 保存文件;字母后面加! 强制。
  (6)退出vim。输入模式ESC--命令模式:--底线命令模式q!
3、ubuntu的常见命令和依赖包安装
  (1)获取超级用户权限:sudo su
  (2)重启虚拟机:sudo reboot
  (3)本地安装:sudo dpkg -i package,“dpkg”是“Debian”的包管理器
  (4)本地安装:sudo apt install package,“apt”是“Debian”和“Ubuntu”的包管理器
  (5)自由安装:wget http://url
  (6)层级关系:Unix-->Linux-->Debian(得便,自由操作系统)-->Ubuntu
4、ubuntu实现路径补全
  (1)创建文件:touch ~/.pythonrc
  (2)添加如下内容:
    import rlcompleter, readline
    readline.parse_and_bind('tab:complete')
  (3)在home目录下,.bashrc文件末尾追加如下内容:export PYTHONSTARTUP=~/.pythonrc
  (4)更新环境变量:source ~/.bashrc

七、在Ubuntu16.04(乌班图)里,安装插件,如搜狗输入法、谷歌浏览器、微信、时间同步、gulp
1、安装简体中文输入法:
  (1)设置-系统设置-语言支持-安装-密码-确认-(安装fcitx,耗时约10分钟)
  (2)语言-安装/卸载语言-(勾选)中文简体-应用-密码-确认-(安装字体,耗时约10分钟)
  (3)区域格式-汉语中国-语言-(汉语中国)拉到第一位-(键盘输入法系统)fcitx-关闭
  (4)删除包sudo apt-get remove fcitx-ui-qimpanel
  附1:Fcitx是(Free Chinese Input Toy for X)的英文缩写,可为支持 XIM 的 X 应用程序提供输入服务。
    可以输入UTF-8编码中的汉字。
2、安装搜狗输入法
  (1)下载搜狗输入法安装包http://pinyin.sogou.com/linux,进入下载目录cd Downloads
  (2)安装搜狗输入法sudo dpkg -i sogoupinyin_2.4.0.3469_amd64.deb
  (3)重启虚拟机sudo reboot
  (4)文字图标-配置当前输入法-(搜狗输入法个人版)调至第一个选项
3、1和2安装过程中或结束后,可能遇到的问题及解决
(1)问题1:
  问题:updating cache ; waiting for apt-get to exit
  解决:sudo fuser -vki /var/lib/apt/lists/lock
(2)问题2:
  问题:由于找不到vcruntime140_1.dll,无法继续执行代码重新安装程序可能会解决此问题
  解决:下载文件vcruntime140_1.dll,复制到32/64位版本电脑的的C:\Windows\system64/32下
(3)问题3:
  问题:搜狗输入时出现乱码
  解决:killall fcitx
(4)问题4:
  问题:Windows和Ubuntu16之间不能复制粘贴
  解决:sudo apt-get autoremove open-vm-tools ; sudo apt-get install open-vm-tools-desktop ; Ctrl+c ; sudo reboot
4、安装谷歌浏览器
  (1)sudo wget http://www.linuxidc.com/files/repo/google-chrome.list -P /etc/apt/sources.list.d/
  (2)wget -q -O - https://dl.google.com/linux/linux_signing_key.pub  | sudo apt-key add -
  (3)sudo apt-get update
  (4)sudo apt-get install google-chrome-stable
  (5)/usr/bin/google-chrome-stable
  (6)在屏幕左侧的图标上,右键“锁定到启动器”
5、安装微信(可登陆,2021年11月11日)
  (1)安装 deepin-wine:wget -O- https://deepin-wine.i-m.dev/setup.sh | sh
    以下主要来源:https://blog.csdn.net/sinat_39369871/article/details/110095705
  (2)下载微信安装包:http://packages.deepin.com/deepin/pool/non-free/d/deepin.com.wechat/,点击最后一个
  (3)一次运行下列命令(复制以下内容,粘贴到终端,按回车)
    #!/bin/bash
    mkdir ./deepintemp
    cd ./deepintemp
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine/deepin-wine_2.18-22~rc0_all.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine/deepin-wine32_2.18-22~rc0_i386.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine/deepin-wine32-preloader_2.18-22~rc0_i386.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine-helper/deepin-wine-helper_1.2deepin8_i386.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine-plugin/deepin-wine-plugin_1.0deepin2_amd64.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine-plugin-virtual/deepin-wine-plugin-virtual_1.0deepin3_all.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine-uninstaller/deepin-wine-uninstaller_0.1deepin2_i386.deb
    wget http://packages.deepin.com/deepin/pool/non-free/u/udis86/udis86_1.72-2_i386.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine/deepin-fonts-wine_2.18-22~rc0_all.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine/deepin-libwine_2.18-22~rc0_i386.deb
    wget https://packages.deepin.com/deepin/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_1.5.1-2_amd64.deb --no-check-certificate
    wget https://packages.deepin.com/deepin/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_1.5.1-2_i386.deb --no-check-certificate
    sudo dpkg --add-architecture i386
    sudo apt update
    sudo dpkg -i *.deb
    sudo apt install -fy
    rm -vfr ./deepintemp
  (4)安装微信 sudo dpkg -i deepin.com.wechat_2.6.8.65deepin0_i386.deb
6、解决微信不能输入中文的问题
  (1)cd /opt/deepinwine/tools/
  (2)sudo chmod 777 run.sh  #文件默认为只读,修改权限
  (3)vim run.sh   #编辑脚本,加入以下内容:
    export GTK_IM_MODULE="fcitx"
    export QT_IM_MODULE="fcitx" 
    export XMODIFIERS="@im=fcitx"
  (4)重启微信,切换为系统自带的fcitx输入法即可输入中文,比如搜狗输入法。
7、系统时间跟本地时间同步
  (1)sudo timedatectl set-local-rtc 1
  (2)sudo timedatectl set-timezone Asia/Shanghai
  (3)data(查看系统时间,可以不执行)
8、全局安装gulp
    在ubuntu里,全局安装的gulp才能运行gulp任务,npm全局安装gulp会出错,以下用cnpm安装
  (1)sudo  npm install -g cnpm -registry=https://registry.npm.taobao.org
  (2)sudo cnpm install -g gulp
  (3)gulp

八、web相关
附、500错误
  (1)现象:前端带着base64数据向后台发出了post请求,后台没有请求记录,前端收到500
  (2)原因:后台服务器已满
1、计算机(电脑)发展史
  (1)1936年:图灵提出“图灵机”理论,定义计算机逻辑模型,成为现代计算机基础
  (2)1940年,图灵等研制专用计算机“炸弹”,能够破译德国的“英格玛”密码电文
  (3)1943年,英国研制专用计算机“巨人”,能够破译德国的“洛伦兹”密码电文
  (4)1946年,ENIAC在美国诞生,首台通用电子计算机(电子管,每秒5千次运算,重30吨)
    A、能通过编程处理多种类型任务(如科学计算、数据处理、逻辑控制等)
    B、区别于仅能执行单一特定功能的专用计算机
  (5)1947年,贝尔实验室发明晶体管,替代电子管,计算机进入第二代(体积缩小、可靠性提升)
  (6)1949年,英国EDSAC运行首个存储程序(冯・诺依曼架构落地),现代计算机原型形成 
  (7)1953年,IBM701上市,首台通用商用电子计算机(企业数据处理开端)
  (8)1958年,德州仪器发明集成电路(IC),计算机向小型化、低功耗发展(第三代萌芽)
  (9)1960年,DEC推出PDP-1,首台交互式小型计算机(用户可直接操作,实验室普及)
  (10)1964年,IBM的System/360问世,首台兼容机(集成电路),统一硬件、软件标准(第三代标志)
  (11)1969年,ARPANET诞生,美国国防部资助的网络,互联网前身(四节点连通)
  (12)1971年,英特尔4004微处理器发布,首台单芯片CPU(4位,时钟频率108KHz)
  (13)1972年,Intel8008推出,首个8位微处理器,用于计算器和终端设备
  (14)1975年,Altair8800上市,首台商用PC(基于Intel8080,开启个人计算机时代)
  (15)1979年,VisiCalc电子表格软件发布,推动PC从极客玩具变为商业工具
  (16)1984年,苹果发布System1.0操作系统,首次普及图形用户界面(GUI)和鼠标操作(用户体验革新)
  (17)1985年,微软发布Windows1.0操作系统,可多任务操作、用窗口和图标管理程序
  附1、图灵简介
    A、1912年,6月23日,“计算机科学之父”图灵出生于英国伦敦
    B、1936年,提出了计算机的理论模型
    C、1939年—1945年,协助英国军方破解德国的著名密码系统英格玛(Enigma)
    D、1952年,因同性恋被定罪,随后接受化学阉割(雌激素注射)
    E、1954年,6月7日,图灵服毒自杀,享年42岁
    F、1990年,世界卫生组织将同性恋从精神病名册中删除‌
    G、2001年,中国将同性恋从精神病名册中删除‌
    H、2013年,英国女王向图灵颁发了皇家赦免
  附2、冯・诺依曼简介 
    A、1903年,12月28日,“终极天才”冯・诺依曼出生于匈牙利布达佩斯
    B、1913年,掌握微积分,展现数学神童天赋
    C、1926年,获数学博士学位
    D、1928年,发表博弈论奠基论文,提出“极小极大定理”
    E、1930年,受邀赴美普林斯顿大学任教
    F、1932年,出版《量子力学的数学基础》,统一量子理论数学框架
    G、1937年,入籍美国
    H、1943年,加入曼哈顿计划,负责原子弹内爆机制计算
    I、1944年,接触ENIAC项目,开始研究计算机设计
    J、1945年,撰写EDVAC报告,提出“存储程序”计算机架构
    K、1946年,与摩尔学院团队研制IAS计算机原型
    L、1951年,设计MANIAC计算机,用于氢弹数值模拟 
    M、1952年,提出计算机可靠性理论,发展容错计算概念
    N、1953年,研究天气数值预测,开创计算机气象学
    O、1957年,2月8日,病逝,享年53岁
2、互联网发展史
  (1)1969年,阿帕网(ARPANET)诞生于美国,首先用于军事连接,是互联网(internet,因特网,全球通信网络总和)的前身
  (2)1973年-1984年,TCP/IP协议被开发并标准化,成为互联网的核心通信协议
  (3)1989年,欧洲提出互联网的子集-万维网的构想
    A、World Wide Web,简称WWW、Web;
    B、是互联网的上层应用(子集),通过HTTP等协议提供网页服务,涵盖浏览器、服务器、URL、HTML等技术体系
    C、互联网还支持邮件、FTP等其他服务
  (4)1990年,英国科学家蒂姆・伯纳斯・李,在瑞士的一台计算机上,首次实现万维网的客户端和服务器
  (5)1991年,万维网向公众开放
  (6)1994年,Web1.0兴起,主要特征是大量使用静态HTML网页发布信息,用户通过浏览器单向获取信息
  (7)2004年,Web2.0兴起,强调用户与服务器的交互、用户间交互及跨网站数据互联
  (8)2014年起,Web3.0以去中心化、语义网、人工智能为特征,强调用户对数据的控制权与参与权
3、专业术语
  (1)网络,web、net、network
  (2)超链接,hyperlink,从一个网页指向另一个网页的链接关系
  (3)超文本,Hypertext,是超链接的载体;<a href='url'>文本或图像</a>
  (4)超媒体,hypermedia,是超文本和多媒体(文本、声音、图像)在信息浏览环境下的结合 
4、用户与内外网安全连接
  (1)外网,iNode智能客户端
    A、iNode智能客户端是H3C(华三通信)自行设计开发的基于Windows的多业务接入客户端软件,
    B、提供802.1x、Portal(门户网站)、VPN等多种认证方式,
    C、可以与H3C以太网交换机、路由器、VPN网关等网络设备共同组网,实现对宽带接入、VPN接入和无线接入的用户认证,
    D、是对用户终端进行身份验证、安全状态评估以及安全策略实施的主体,
    E、可以按照企业接入安全策略的要求,实现基于角色/身份的权限和安全控制。
  (2)内网,MotionPro安全隧道
    A、企业用户服务器安全保护软件
    B、这是一种VPN软件,Virtual Private Network,虚拟专用网络
    C、站点地址1,tech.vpn.cntv.cn;站点地址2,202.18.19.157
    D、motion,运动
  (3)本机,奇安信天擎
    A、致力于一体化“终端安全”解决方案的终端安全管理系统
    B、帮助政企客户准确识别、保护和监管终端,并确保这些终端在任何时候都能可信、安全、合规地访问数据和业务
    C、高性能病毒查杀、漏洞防护、主动防御引擎,深度融合威胁情报、大数据分析和安全可视化
    D、帮助政企客户构建持续有效的终端安全
5、打开CMD界面,通过命令与计算机交互
  (1)CMD,Command Prompt,命令提示符
  (2)找到界面:
    A、window + r 
    B、cdm + enter
  (3)ping命令,
    A、作用,判断该主机是否可达
    B、原理,它通过向指定的网络地址发送一定长度的数据包,‌并等待接收返回的数据包来检测网络的连通性
    C、命令示例,命令后面只能有IP地址或域名,不能有协议名称和端口号
      a、ping www.baidu.com,检查你的电脑是否能正常访问百度服务器
      b、ping 192.168.0.101,测试你的设备是否能与局域网内的另一台设备正常通信
      c、ping localhost,本地计算机的网络协议栈是否正常工作
      d、ping 127.0.0.1,本地计算机的网络协议栈是否正常工作
  (4)ipconfig命令,
    A、作用,查看本网络配置信息
    B、输出内容,按“网络适配器”分类,每个适配器对应一项网络连接
    C、ipconfig,显示简化版的网络配置信息,仅包含网络适配器的关键参数(如IP地址、子网掩码、网关)
    D、ipconfig/all,显示完整版的网络配置信息,包含适配器的详细参数(如MAC地址、DNS服务器、DHCP信息、物理描述等)    
6、通过ipconfig命令,与计算机交互的结果解读
  (1)专用名词英文简称
    A、PPP,Point-to-Point Protocol,点对点协议
    B、P2P,Peer-to-Peer,对等体直接通信,点对点连接
    C、MAC,Media Access Control Address,媒体访问控制地址
  (2)MAC地址,网络设备(具备网络接口的硬件设备)的唯一标识符
    A、获取
      a、ipconfig/all
      b、物理地址,就是MAC地址
    B、写法,分隔符不能混写
      a、00:1A:2B:3C:4D:5E
      b、00-1A-2B-3C-4D-5E
    C、含义,
      a、前3段,标识网卡制造商,
      b、后3段,确保同一厂商内设备不重复
  (3)IPv4地址,
    A、约43亿个,即256*256*256*256=42,9496,7296,其中公网地址约37亿个,私有地址约0.18亿个,保留地址约5.82亿个
    B、私有地址
      a、A类,第1段为网络部分,第2、3、4段为主机部分,子网掩码为255.0.0.0;10.0.0.0-10.255.255.255,约1600万个
      b、B类,第1、2段为网络部分,第3、4段为主机部分,子网掩码为255.255.0.0;172.16.0.0-172.31.255.255,约100万个
      c、C类,第1、2、3段为网络部分,第4段为主机部分,子网掩码为255.255.255.0;192.168.0.0-192.168.255.255,约6.5万个
    C、具有IP地址的设备
      a、网络终端设备,如电脑、手机、服务器、智能音箱
      b、网络中间设备,如路由器、交换机、防火墙、网关、调制解调器
      c、其他网络节点,如打印机、网络摄像头、智能电表、虚拟机 
    D、来源,在ipconfig结果中的IPv4地址,多是私有地址,来自于手动设置或由路由器自动分配 
    E、作用,用于跨子网、跨网络的通信
  (4)子网掩码(Subnet Mask)
    A、作用,把IP地址划分为网络部分和主机部分
    B、用法,在相同子网掩码的计算下,两台主机IP地址的网络部分
      a、若相同,通过局域网链路直接通信,发送数据包
      b、若不同,将数据交由默认网关转发,跨网段通信
  (5)适配器(Adapter,硬件设备)
    A、PPP适配器,拨号上网、远程接入 
    B、以太网适配器,
      a、作用,为同子网内的设备通信提供链路层支持
      b、用法,设备A的以太网适配器封装数据,经网线发送至交换机,交换机查到地址后,经网线发送数据至设备B,其以太网适配器解封数据
  (6)默认网关(Default Gateway,通常就是路由器,硬件设备)
    A、作用,负责转发跨子网的数据包
    B、常见用法,电信公司的光纤接入光猫(调制解调器)完成光电转换后,再连接到用户的路由器,处理跨网通信
    C、远程看摄像头,
      a、摄像头把视频上传至云端,用户通过手机APP向云端请求视频,云端返回视频至用户手机APP
      b、摄像头、云端、手机APP都有IP地址,通过默认网关,实现跨网络通信
  (7)从本地硬件连接到广域跨网交互的全流程(目标为互联网中的公网设备,如访问百度服务器)
    A、物理连接
      a、本地设备通过以太网适配器(有线)或无线网卡(无线),经网线、路由器等物理介质接入局域网,建立物理链路
      b、MAC地址作为设备在链路层的唯一标识,用于局域网内设备的物理区分
    B、本机局域网参数配置
      a、设备获取本机的私有IP地址、子网掩码、网关IP
      b、系统内置IANA私有网段规则,可识别目标IP是否为公网网段
    C、目标域名解析
      a、用户输入域名(如www.baidu.com)
      b、设备向DNS服务器发送解析请求
      c、获取对应的公网IP地址
    D、网段判断
      a、系统判断目标IP为公网网段
      b、确认需跨广域网传输,触发跨网流程,数据需经网关转发
    E、链路层交互
      a、设备通过ARP协议广播查询网关IP对应的MAC地址
      b、获取后用网关MAC封装数据帧,将数据发送至网关
    F、跨网转发与NAT转换
      a、网关接收数据后,通过NAT技术将本机私有IP转换为网关的公网IP
      b、记录转换映射关系
    G、远程链路建立
      a、若网络接入方式为拨号上网,网关通过PPP协议与运营商建立远程链路
      b、完成身份认证(账号密码)和数据封装,接入公网
    H、公网传输与路由转发
      a、转换后的公网数据通过运营商骨干网、公网路由逐级转发,最终到达目标服务器
      b、服务器响应数据按原路径回传,经网关NAT反向转换
      c、通过局域网链路层交互传回源设备
  (8)从本地硬件连接到本地跨网交互的全流程(目标为同一局域网内不同子网的设备,如本地路由器下子网A的设备访问子网B的设备)
    A、物理连接
      a、本地设备通过网卡、网线接入局域网
      b、MAC地址作为链路层标识
    B、本机局域网参数配置
      a、设备获取本机私有IP、子网掩码、网关IP
      b、记录私有网段规则
    C、目标IP确认
      a、明确目标设备IP为同一局域网内的私有IP
      b、确认目标IP属于不同子网
    D、网段判断
      a、通过本机IP与子网掩码、目标IP与子网掩码的与运算
      b、确认目标IP属于不同子网,需通过网关转发
    E、链路层交互
      a、设备通过ARP协议广播查询网关IP的MAC地址
      b、用网关MAC封装数据帧,发送至网关
    F、本地网关转发
      a、网关接收数据后,根据自身路由表,将数据转发至目标子网的对应端口
      b、目标子网的网关接收数据后,通过ARP获取目标设备的MAC地址
      c、完成子网内数据帧封装
    G、目标子网链路层交互
      a、目标设备所在子网的交换机通过MAC地址表转发数据帧,目标设备接收数据并处理
      b、响应数据按原路径回传:经目标子网网关→主路由器→源设备子网
      c、最终通过ARP获取源设备MAC,完成交互
  (9)从本地硬件连接到同一子网交互的全流程(目标为同一局域网子网内的设备,如同一家庭路由器下的电脑访问手机)
    A、物理连接
      a、设备通过网卡、网线或无线信号接入同一子网
      b、MAC地址作为链路层唯一标识
    B、本机局域网参数配置
      a、设备获取本机私有IP、子网掩码
      b、无需依赖网关即可完成子网内通信
    C、目标IP确认
      a、明确目标设备IP为同一子网内的私有IP
    D、网段判断
      a、通过与运算确认目标IP与本机在同一子网
      b、无需网关转发。
    E、链路层交互
      a、设备通过ARP协议广播:“谁拥有192.168.1.11?请回复MAC地址”
      b、目标设备收到广播后,以单播形式回复自身MAC地址
    F、数据帧封装与转发
      a、源设备用目标MAC地址封装数据帧,通过交换机转发
      b、目标设备接收数据帧,校验MAC地址匹配后解析数据
      c、响应数据以同样方式回传,完成交互
      
九、全局变量与请求代理proxy
1、Webpack(手动配置)
  (1)通过包手动加载环境变量文件,并注入到 process.env
    const webpack = require('webpack');
    const dotenv = require('dotenv');
    // 加载指定环境的 .env 文件(如 .env.development)
    const env = dotenv.config({ path: `.env.${process.env.NODE_ENV}` }).parsed;
    // 仅暴露以 APP_ 开头的变量(避免敏感信息泄露)
    const envVariables = Object.keys(env)
      .filter(key => key.startsWith('APP_'))
      .reduce((acc, key) => {
        acc[`process.env.${key}`] = JSON.stringify(env[key]);
        return acc;
      }, {});
    module.exports = {
      plugins: [
        new webpack.DefinePlugin(envVariables) // 使用过滤后的 envVariables
      ]
    };
  (2)需手动过滤变量前缀(如 APP_),否则所有变量都会暴露到前端,存在安全风险
  (3)在组件中通过 process.env.XXX 访问(需与 DefinePlugin 中定义的变量名一致)
2、Vue CLI(基于 Webpack)
  (1)通过vue-cli-service自动加载环境变量文件.env、.env.development、.env.production,并注入到process.env
  (2)仅暴露以 VUE_APP_ 开头的变量到前端代码。
  (3)在组件中通过process.env.VUE_APP_XXX访问,例如:console.log(process.env.VUE_APP_BASE_API);
3、Vite
  (1)通过内置环境变量处理自动加载环境变量文件.env、.env.development、.env.production,并注入到import.meta.env
  (2)仅暴露以VITE_开头的变量到前端代码
  (3)在组件中通过import.meta.env.VITE_XXX访问,例如:console.log(import.meta.env.VITE_API_KEY);
4、前端开发阶段的请求步骤(请求代理proxy)
  (1)JS拼接URL并调用浏览器的请求API发起请求。然后,本项目内,下面3个方案,谁在前谁处理请求
  (2)Mock.js:前端代码拦截浏览器的请求API,返回数据,不触发真实网络请求
  (3)devServer.proxy:开发服务器拦截收到的请求,转发到目标服务器
    A、根据devServer.proxy的key拦截请求
    B、根据target寻找目标服务器(此步骤可能包含域名解析)
    C、根据pathRewrite或rewrite修改请求路径
    D、向目标服务器发出请求
  (4)直接发出:此步骤包含域名解析

十、gulp(JS打包工具,构建工具,自动化工具)!!!
附、前端工程化工具,如Grunt、Gulp、Webpack、Rollup、Parcel、Vite,用于解决前端开发中的构建、打包、优化等问题
  A、Grunt,2012年2月诞生首个版本
  B、Gulp,2013年2月诞生首个版本
  C、Webpack,2013年诞生首个版本
  D、Rollup,2015年诞生首个版本
  E、Parcel,2017年11月诞生首个版本
  F、Vite,2020年3月诞生首个版本
1、gulp的作用,借助于node环境,进行本地开发和打包生产
2、gulp的API
  (1)gulp.src(globs[, options])导入文件,参数为路径。webpack为entry自动导入文件
    ----globs参数匹配文件路径(路径包括文件名,分为具体路径和通配符路径)。当有多个路径时,该参数为一个数组。
    ----options为可选参数。通常情况下我们不需要用到。
    ----* 匹配文件路径中的0个或多个字符,但不会匹配路径分隔符,除非路径分隔符出现在末尾
    ----** 匹配路径中的0个到多个目录及其子目录,需要单独出现,即它左右不能有其他东西了。如果出现在末尾,也能匹配文件。
    ----gulp.src(['js/*.js','css/*.css','*.html'])
    ----使用数组的方式还有一个好处就是可以很方便的使用排除模式,在数组中的单个匹配模式前加上!即是排除模式,
      它会在匹配的结果中排除这个匹配,要注意一点的是不能在数组中的第一个元素中使用排除模式
    ----gulp.src([*.js,'!b*.js']) //匹配所有js文件,但排除掉以b开头的js文件
    ----gulp.src(['!b*.js',*.js]) //不会排除任何文件,因为排除模式不能出现在数组的第一个元素中
  (2)gulp.dest(path[,options])导出文件的目录,参数为路径
    ----path为写入文件的路径
    ----options为一个可选的参数对象,通常我们不需要用到
    ----gulp.dest()传入的路径参数,只能用来指定要生成的文件的目录,生成的文件名是由导入到它的文件流决定的。
    ----gulp.dest(path)生成的文件路径是我们传入的path参数后面再加上gulp.src()中有通配符开始出现的那部分路径。例如:
    ----gulp.src('script/**/*.js') .pipe(gulp.dest('dist')); //最后生成的文件路径为 dist/**/*.js
  (3)gulp.task(name[, deps], fn)定义任务,参数为任务名
    ----name 为任务名
    ----deps 是当前定义的任务需要依赖的其他任务,为一个数组。当前定义的任务会在所有依赖的任务执行完毕后才开始执行。
    ----fn 为任务函数,我们把任务要执行的代码都写在里面。
    ----gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { //定义一个有依赖的任务 //Do something});
    //gulp中执行多个任务,可以通过任务依赖来实现。例如我想要执行one,two,three这三个任务,那我们就可以定义一个空的任务,然后把那三个任务当做这个空的任务的依赖就行了,只要执行default任务,就相当于把one,two,three这三个任务执行了
    ----gulp.task('default',['one','two','three']);
    ----如果任务相互之间没有依赖,任务会按你书写的顺序来执行,如果有依赖的话则会先执行依赖的任务。
    ----但是如果某个任务所依赖的任务是异步的,就要注意了,gulp并不会等待那个所依赖的异步任务完成,而是会接着执行后续的任务。
  (4)gulp.watch(globs[, opts], tasks)或gulp.watch(glob[, opts], cb)用来监视文件的变化,参数为路径,实际项目中,用Gaze插件代替
    ----globs参数匹配文件路径(路径包括文件名,分为具体路径和通配符路径)。当有多个路径时,该参数为一个数组。
    ----opts 为一个可选的配置对象,传给gaze的参数,通常不需要用到
    ----tasks 为文件变化后要执行的任务,为一个数组
    ----cb参数为一个函数。每当监视的文件发生变化时,就会调用这个函数,并且会给它传入一个对象,该对象包含了文件变化的一些信息,type属性为变化的类型,可以是added,changed,deleted;path属性为发生变化的文件的路径
    ----gulp.watch('js/**/*.js', function(event){
        console.log(event.type); //变化类型 added为新增,deleted为删除,changed为改变
        console.log(event.path); //变化的文件的路径
      });
3、gulp的插件
  (1)gulp-load-plugins,加载其它插件
  (2)gulp-inject,把文件插入到注释的位置
  (3)gulp-useref,把HTML引用的多个文件如CSS、JS合并起来,再用gulp-if进行分类
  (4)gulp-clean,把原来的文件清空
  (5)gulp-if,判断文件类型
  (6)gulp-htmlmin,压缩HTML代码
  (7)gulp-clean-css,压缩CSS代码
  (8)gulp-uglify,压缩JS代码
  (9)gulp-ng-annotate,解决angular中,依赖注入出错的问题
  (10)gulp-angular-templatecache,html压缩成js后,用此插件指定该js所属模块
  (11)gulp-rev,为静态文件随机添加一串hash值,同时生成manifest.json保存新旧文件名对应关系
  (12)gulp-rev-collector,根据gulp-rev生成的manifest.json文件中的映射,去替换文件名称
4、gulp应用实例
(1)全局内容
  var Gaze = require('gaze').Gaze;
  var gaze = null;
  var gaze1 = null;
  function getMenus(exclude){
    var last_menus = [];
    var all_menus = eval(
      fs.readFileSync('src/config/project/cy/menu.js', 'utf8');
    ); //menu.js自执行函数,返回一个数组
    if(exclude){
      var firstGrade = Object.keys(exclude);
      var length1 = all_menus.length;
      for(var i=0;i<length1;i++){
        var sub1 = deepCopy(all_menus[i]);
        if(firstGrade.indexOf(sub1.title) === -1){
          var subs2 = sub1.subs;
          var length2 = sub1.subs.length;
          var secondGrade = Object.keys(exclude[sub1.title]);
          for(var j=0; j<length2; j++){
            if(secondGrade.indexOf(subs2[j].title) > -1){
              subs2.splice(j,1);
              j--;
            }else{
              var subs3 = subs2.subs;
              var length3 = subs2.subs.length;
              var thirdGrade = Object.keys(exclude[sub1.title][subs2[j].title]);
              for(var jj=0; jj<length2; jj++){
                if(thirdGrade.indexOf(subs3[jj].title) > -1){
                  subs3.splice(jj,1);
                  jj--;
                }
              }
            }
          }
          last_menus.push(sub1)
        }
      }
    }else{
      last_menus = deepCopy(all_menus);
    }
    //另外,计算图标位置的函数可以写在全局,在last_menus内调用
    var allData = `(function () {
      var base_dir = 'cy';
      var menus = last_menus;
      angular
        .module('app')
        .constant('menus', menus);
    })();`
    fs.writeFile(
      'src/config/menus.js',
      allData,
      'utf8',
      function () {}
    );
  }
(2)本地开发任务
  function testServeConfig(configDirname) {//实际上,下面(1)(2)两种情况是特殊的http请求,剩下的http请求都是直接向本机发送url
    var array = ['/app','/oauth','/status'];//(1)跨域代理:开发过程中,以这些item开始的url,将向后台服务器发送以item开始的url。这种情况主要用于获取动态数据。
    var proxy_ = array.map(function (value) {
      var a = url.parse('https://172.18.10.23' + value);
      a.route = value;
      a.rejectUnauthorized = false;
      return proxy(a);
    });
    browserSync.init(
      {
        port: 8900,
        notify: false,
        open: false,
        server: {
          baseDir: ['src'],
          directory: false,
          index: 'index.html',
          middleware: proxy_,
          routes: {
            '/audit-html/static/img': 'src/img',//(2)本地代理:开发过程中,以key开始的url,将向本地服务器发送以value开始的url。这种情况主要用于获取静态资源,如html文件、css的背景图片和img标签的src属性。
          }
        }
      },
      function () {
        reload();
        //渲染index.html
        //执行main.js,根据menus服务,执行$stateProvider.state(it.state, it.cfg);关联状态、页面、控制器
      }
    );
  }
  function testServe(moduleDirName, configDirname) {
    fs.exists('devServerConfig.json', function (exists) {
      if (gaze && gaze1) {
        gaze.close();
        gaze1.close();
        browserSync.exit();
      }
      inject_file(moduleDirName, configDirname);//注入文件,(不应该通过menu.js)将各文件夹下的js和css文件注入到index.html,
      watch_file(moduleDirName, configDirname);//监听文件,监听各文件夹下的js和css文件
      if (exists) {
        testServeConfig(configDirname);//配置服务
      } else {
        fs.writeFile(
          'devServerConfig.json',
          '{"address":"http://192.168.80.152:7300"}',
          'utf8',
          function () {
            testServeConfig(configDirname);//配置服务
          }
        );
      }
    });
  }
  gulp.task('default', function () {
    var exclude = {
      '一级标题1':{
        '二级标题1':{
          '三级标题1':{
            '四级标题1':''
          }
        },
      }
    }
    getMenus(exclude);//把参数包含的标题,从总数据中排除,获取最终的menus服务
    testServe('cy','cy');//注入文件、监听文件、配置服务
  });
(3)打包生产任务
  gulp.task('templates', function () {
    gulp
      .src(tpl_html)
      .pipe(
        $.htmlmin({
          collapseWhitespace: true,
          removeComments: true,
          minifyCSS: true
        })
      )
      .pipe(
        $.angularTemplatecache({ /* html压缩成js后,用此插件指定该js所属模块 */
          module: 'app'
        })
      )
      .pipe($.uglify())
      .pipe(gulp.dest('.tmp'));
  });
  gulp.task('buildAll', function () {
    gulp
      .src(['src/index.html'])
      .pipe(
        $.inject(gulp.src('.tmp/templates.js'), {
          starttag: '<!-- inject:partials -->',
          relative: true
        })
      )
      .pipe($.useref())
      .pipe($.if('*.js', $.ngAnnotate())) /* 解决angular中,依赖注入出错的问题 */
      .pipe($.if('*.js',$.uglify())) /* 压缩JS代码 */
      .pipe($.if(
        '*.css',
        autoprefixer({
          browsers: ['last 8 versions'],
          cascade: false
        })
      ))
      .pipe($.if('*.css', $.cleanCss()))/* 压缩CSS代码 */
      .pipe(gulp.dest('dist'));
  });
  gulp.task('revCssJS', function () {
    gulp
      .src(['./dist/style/*.css', './dist/script/*.js'])
      .pipe(rev()) //添加hash后缀
      .pipe(gulp.dest('./dist')) //原路导出“添加hash后缀”后的文件
      .pipe(rev.manifest()) //生成映射文件
      .pipe(gulp.dest('./rev')); //将映射文件导出到rev
  });
  gulp.task('revHtml', function () {
    gulp
      .src(['./rev/*.json', './src/productTpl/index.html'])
      .pipe(revCollector()) //用前者的映射关系替换后者相应的文件
      .pipe(gulp.dest('./template')); //将替换后的文件导出
  });
  gulp.task('build', function () {
    runSequence(
      'clean:start',
      'template',
      'images-cy',
      'copy',
      'buildAll', //产生all.min.js
      'revCssJS',
      'revHtml',
      'clean:final'/* ,
      function () {
        copyConfiguration('cy');
      } */
    );
  });

十一、webpack(JS打包工具,构建工具,自动化工具)!!!
附、前端工程化工具,如Grunt、Gulp、Webpack、Rollup、Parcel、Vite,用于解决前端开发中的构建、打包、优化等问题
1、webpack各版本发布年份(发行时间)
  (1)Webpack1:2014年
  (2)Webpack2:2016年,支持ESModule,新增Tree-Shaking(剔除未使用代码)
  (3)Webpack3:2017年6月20日,新增ScopeHoisting(作用域提升,减少代码体积)和Magic Comment(魔法注释,用于控制代码分割)
  (4)Webpack4:2018年2月25日,新增mode属性(区分开发/生产环境),支持WebAssembly,支持多种模块类型,实现"0配置"基础打包
  (5)Webpack5:2020年10月10日,优化缓存机制,增强Tree-Shaking,内置静态资源处理,支持模块联邦等
2、常见包
  (1)webpack:是打包的命令,也是模块打包器。将entry输入的文件,经过链式裂变导入、用module.rules编译打包后,
    根据mode取值不同而选择压不压缩最终的js,最后通过output把js显性输出到指定目录。
  (2)webpack-cli:是webpack命令行的工具,能使webpack命令带参数在命令行中运行,cli即命令行接口(Command Line Interface)。
  (3)webpack-dev-server:是开启本地node服务器的命令,也是一个小型的node服务器。将entry输入的文件用module.rules编译打包后,
    根据mode取值不同而选择压不压缩最终的js,最后通过output把js隐性输出到内存并自动打开浏览器,同时监听entry及其import引入的文件,
    一旦发生变化就自动编译、打包、隐性输出代码,手动刷新浏览器,可以看到最新效果。
3、常见疑问释疑
  (1)直接运行的包,如果本目录或全局安装了该包,可以直接运行,不需要npm和npx,如webpack、webpack-dev-server。
  (2)在编译的过程中,根据babel-loader的配置处理js的兼容,根据process.env.NODE_ENV取值不同,选择package.json里browserslist的不同配
    置项来处理css的兼容问题,根据插件配置决定最终的css压不压缩和输出目录,根据url-loader的配置决定最终的img输出目录和公共路径。
4、webpack优化方法
  (1)数组一个,只执行数组选项中的一个,oneOf:[]
  (2)一个数组,将多次使用的加载器放到一个数组里,供展开使用
  (3)懒加载,当需要使用文件时才加载,比如点击某个按钮后才加载某个文件
  (4)多进程,见下面配置thread-loader
  (5)使用cache,缓存,当只有js/css文件发生改变时,只打包js/css文件到最终目录里,用最新的js/css文件名替换掉index.html上原来的
    文件名,同时删掉原来的js/css文件
  (6)使用HMR,模块热替换,在程序运行过程中替换、添加或删除“模块”,而无需重新“加载整个页面”,Hot Module Replacement的缩写
    自悟,单页面应用,每个页面都由多个模块构成,哪个模块更新,就替换掉哪个模块,不会重新构建所有模块,最后重新渲染当前页面
  (7)使用dll,动态链接库,把第三方类库单独打包,生成映射库,然后每次只打包项目自身代码,Dynamic Link Library的缩写,
  (8)使用externals,排除第三方类库,用CND引入第三方库
  (9)使用gzip压缩,Webpack用compression-webpack-plugin对静态资源进行压缩,上传至服务器;服务器端根据请求头返回gzip资源,
    浏览器根据响应头解压gzip资源
    来源,https://blog.csdn.net/weixin_47516343/article/details/125392505
  (10)弃用sourcemap,资源地图,module.exports = {  productionSourceMap: false, }//打包时不会生成 .map 文件,加快打包速度 
  (11)使用webpack-bundle-analyzer,webpack打包分析,对webpack的打包性能进行分析
  (12)使用webpack的speed-measure-webpack-plugin看打包速度
5、常见术语释义
  (1)chunk,代码块
  (2)polyfill,兜底,在计算机中,变相实现不能直接实现的操作
  (3)bootstrap,n.[计]引导程序,辅助程序;vt.启动(电脑)
  (4)process,对象是一个 global 变量,提供有关当前 node.js 进程的信息并对其进行控制
  (5)dll,动态链接库英文为DLL,是Dynamic Link Library的缩写。(Dynamic,[daɪˈnæmɪk],有活力的)
6、package.json
  (1)webpack开发/打包命令,npm run dev/build
    {
      "scripts": {
        "dev": "cross-env envNum=0 webpack-dev-server --config build/webpack.dev.conf.js",
        //用webpack-dev-server开启代理服务器,process.env.envNum的值为0
        "build": "cross-env envNum=1 webpack build/webpack.dev.conf.js"//用webpack打包
        //cross-env,在windows环境下可改为​​set,在其它环境下可省略
        //envNum的值,可以用于区分开发、生产环境
      },
      "dependencies": {},
      "devDependencies": { 
        "babel-core": "6.26.0",//把ES6+换为ES5的核心插件
        "babel-preset-es2015": "^6.24.1",//2017年Babel宣布ES2015/ES2016/ES2017被废弃
        "babel-preset-env": "^1.7.0",//根据配置的目标浏览器或运行环境,自动将ES2015+转换为ES5。
        "babel-loader": "7.1.5",//将ES2015+转换为ES5。
        "babel-eslint": "8.2.6",//ES6+语法检测。
        "url-loader": "1.0.1",//将引⼊的图⽚以base64编码并打包到⽂件中,最终只需要引⼊这个dataURL就能访问图⽚了。
        "vue-loader": "15.3.0",//解析和转换.vue文件,提取出其中的逻辑代码script、样式代码style、以及HTML模版template,再分别把它们交给对应的 Loader 去处理。
        "vue-style-loader": "4.1.2",//除了支持客户端渲染,还支持服务端渲染
      }
    }
  (2)webpack-dev-server命令下,各参数的含义
    config,修改默认的配置文件
    content-base,与path、publicPath类似
    compress,开启gzip压缩
    hot,不刷新浏览器
    inline,刷新浏览器
    hot --inline,失败则刷新浏览器
    progress,显示打包的进度
    quiet,控制台中不输出打包的信息
    HRM:Hot-Module-Replacement,模块热更新。在应用运行时,无需刷新页面,便能替换、增加、删除必要的模块
7、webpack.config.js文件示例
  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'bundle.js',
      clean: true //每次构建前清空输出目录
    },
    //开发工具:生成 source-map,便于调试
    devtool: 'inline-source-map',
    //开发服务器配置(包含代理)
    devServer: {
      static: './dist', //静态文件目录
      port: 3000, //开发服务器端口
      open: true, //自动打开浏览器
      hot: true, //启用热模块替换(HMR)
      compress: true, //启用 gzip 压缩
      //代理配置(核心部分)
      proxy: {
        '/api': { //这是统一代理,非常重要!!!
          target: 'https://api.example.com', 
          changeOrigin: true, 
          rewrite: (path) => path.replace(/^\/api/, '')
        },
        '^/service/(user|order|cart)': { //这是统一代理,非常重要!!!
          target: 'http://192.168.1.100:8080', 
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/service/, '/api/v1')
        },
        '/search': {
          target: 'https://search.example.com',
          changeOrigin: true,
          pathRewrite: { '^/search': '/query' } //将'/search?keyword=test' 转换为 '/query?keyword=test'
        },
        '/ws': {
          target: 'ws://localhost:8081',
          ws: true, //启用 WebSocket 代理
          changeOrigin: true
        }
      }
    },
    //插件配置
    plugins: [
      new HtmlWebpackPlugin({
        template: './src/index.html', //模板文件
        title: 'Webpack Proxy Example' //页面标题
      })
    ],
    //模块解析规则
    module: {
      rules: [
        {
          test: /\.css$/i,
          use: ['style-loader', 'css-loader'] //处理 CSS 文件
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i,
          type: 'asset/resource' //处理图片资源
        }
      ]
    }
  };

十二、vite,一种新型前端构建工具(JS打包工具,构建工具,自动化工具)!!!
附、前端工程化工具,如Grunt、Gulp、Webpack、Rollup、Parcel、Vite,用于解决前端开发中的构建、打包、优化等问题
1、Webpack 打包机制与性能瓶颈
  (1)模块打包能力
    A、通过静态分析模块依赖关系,将多种模块规范(ES6、CommonJS、AMD)的代码转换为浏览器兼容的JS
    B、最终输出为一个或多个经过优化的 bundle 文件
  (2)大型应用构建性能问题
    A、在大型项目中,即使启用模块热替换(HMR),
    B、仍需重新构建完整的依赖图并生成新bundle,导致热更新延迟达到秒级,显著影响开发体验
2、Vite 的核心架构
  (1)开发服务器,直接利用浏览器原生ES模块系统,“跳过打包步骤”,实现按需加载和毫秒级热更新
  (2)生产构建,基于Rollup进行打包,默认集成代码分割、Tree-shaking 和CSS压缩等优化策略,生成高性能静态资源
3、Vite 的核心优势
  (1)依赖预构建
    A、工具选择,使用 Go 编写的 esbuild 预编译第三方依赖
    B、格式转换,将 CommonJS/UMD 依赖统一转为 ESM 格式
    C、请求优化,合并多模块为单文件,减少 HTTP 请求
    D、性能表现,速度较传统工具(如 Webpack)快 10-100 倍
  (2)按需编译,开发阶段仅编译当前访问的模块,极大缩短启动时间(通常低于 1 秒)
  (3)HMR,基于原生 ESM 的 HMR(热更新) 仅替换变更模块,无需刷新页面,更新速度达毫秒级
3、补充说明
  (1)SFC支持:
    A、SFC,单文件组件,Single File Component
    B、Vite 为 Vue/React 单文件组件提供开箱即用的按需编译
  (2)工具分工:
    A、开发阶段用 esbuild 追求速度
    B、生产环境用 Rollup 保证输出质量
4、vite的命令
  附、全局安装vite,(c)npm install -g create-vite
  (1)Vite,启动开发服务器
  (2)vite build,构建生产版本
  (3)vite optimize,预构建依赖
  (4)vite preview,本地预览构建产物
  (5)使用Vite时,按需动态加载,用const components = import.meta.glob('../components/*.vue');
5、静态资源处理
  (1)将资源引入为URL,import imgUrl from './img.png',
    应当为,import zanIcon from '@/assets/images/zan.png';
    imgUrl在开发时会是/img.png,在生产构建后会是/assets/img.2d8efhg.png
  (2)显式引入URL,使用?url后缀显式导入为一个URL,import workletURL from 'extra-scalloped-border/worklet.js?url'
  (3)将资源引入为字符串,import shaderString from './shader.glsl?raw'
  (4)导入脚本作为Worker,import Worker from './shader.js?worker'
  (5)public目录,静态资源默认放在<root>/public里,如public/icon.png,在源码中被引用为/icon.png,
    public中的资源不应该被JavaScript文件引用
6、构建生产版本并部署,
  (1)运行vite build命令,
  (2)使用<root>/index.html作为其构建入口点,
  (3)生成能够静态部署的应用程序包dist文件夹,
  (4)部署文件夹到服务器
7、vite.config.js示例,来源ai-web
  //附、参考文档,https://cn.vitejs.dev/guide/why.html
  //附、环境变量、环境对象的设置与获取
  // (1)设置,.env.development,示例如下
  //    VITE_APP_BASE_API = 'http://10.51.29.56:7010/ai-access-server/'
  // (2)获取,request.js,示例如下
  //    const service = axios.create({
  //      baseURL: import.meta.env.VITE_APP_BASE_API, //axios中请求配置有baseURL选项,表示请求URL公共部分
  //      timeout: 180000, //超时
  //    })
  import { fileURLToPath, URL } from 'node:url'
  import path from 'path'
  import { defineConfig, loadEnv } from 'vite' //定义配置,加载环境
  import vue from '@vitejs/plugin-vue'
  import { viteMockServe } from 'vite-plugin-mock'
  import createVitePlugins from './vite/plugins'
  import legacy from '@vitejs/plugin-legacy' //为传统浏览器提供支持
  export default defineConfig((params)=>{ //https://vitejs.dev/config/
    // console.log( params ); 
    // { 
    //   mode: 'development', //环境类型、NODE_ENV的设置与获取(无需设置,用params.mode获取)
    //   command: 'serve', 
    //   ssrBuild: false 
    // }
    const envConfig = loadEnv(params.mode, process.cwd());
    // 默认不加载.env文件,Vite的loadEnv函数可以加载指定的.env文件
    // loadEnv(mode, envDir, prefixes)同步函数的参数说明
    //  1、mode,根据启动命令,确定mode值,加载对应的.env文件。配置文件提供一切
    //     A、为development时,加载.env.development文件;
    //     B、为production时,加载.env.production文件
    //     附、启动命令有,npm run dev;npm run build
    //  2、envDir,当前工作目录,
    //     A、相对于package.json的文件夹地址,
    //     B、可通过process.cwd()获取
    //     C、console.log(process.cwd()); //C:\Users\Haier\Desktop\ai-web; 
    //     D、cwd,Current Working Directory,即当前工作目录 
    //  3、prefixes,根据prefixes值,返回文件里的项
    //     A、缺失时,返回对应文件中,以字符串`VITE_`开始的项;
    //     B、为''时,返回对应文件中,所有项和内置项;
    //     C、为字符串时,返回对应文件中,以该字符串开始的项
    const { VITE_BASE_URL, VITE_OUTPUT_DIR, VITE_USE_MOCK }  = envConfig
    return {
      base: VITE_BASE_URL,// 在开发环境或生产环境中,请求静态资源时,都会加上。比如/app/image.jpg中的/app
      // 来源,https://cn.vitejs.dev/config/shared-options.html#define
      // define,定义全局常量,在开发环境下定义在全局,在构建时被静态替换
      // define,全局常量获取,console.log(__APP_ENV__);
      define: {//对于字符串以外的数据类型(如布尔值)最好使用JSON.stringify进行处理,以确保在不同环境下的正确替换
        'process.env': envConfig,
        __APP_ENV__: envConfig.APP_ENV,
        __APP_VERSION__: JSON.stringify('v1.0.0'),
        __API_URL__: 'window.__backend_api_url',
        //__DEBUG_MODE__: JSON.stringify(true),
      },
      // vite中define数据和环境变量有什么区别?
      //   1、define,在构建时,静态地注入到代码中
      //   2、环境变量,在不同环境下,项目参数可能变化
      //   3、环境变量的优先级高于define
      plugins: [
        createVitePlugins(),
        viteMockServe({
          // 问:在vite.config.js中,在plugins中引入mock.js,devServer.proxy还有效吗
          // 答:1个请求用3种方案中的1种来处理,优先级从大往小为,devServer.proxy、Mock.js、直接发出(浏览器跨域)
          // 在“开发环境或生产环境”中,请求静态资源时,下面配置为true的,会被模拟
          mockPath: "./src/mock/",//导入此路径下的-虚拟数据
          prodEnabled: false,//生产环境,禁用-模拟数据-服务
          localEnabled: VITE_USE_MOCK,//true,开发环境,启用-本地模拟数据-服务,network有相关记录
        }),
      ],
      build: {
        outDir: VITE_OUTPUT_DIR
      },
      css: {
        preprocessorOptions: {
          scss: {
            additionalData: `@use "@/assets/css/variables.scss" as *;`,
          },
        },
      },
      resolve: {
        alias: {//配置别名,定义路由字符串,路由定义,
          '@': path.resolve(__dirname, './src')//将所有的参数拼接到一起
        },
        extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
      },
      server: {
        port: 80,
        host: true,
        // host:true与host:'0.0.0.0',效果一样,1、当前设备可访问,2、外部设备可通过当前设备的IP地址访问
        // host:'localhost'与host:'127.0.0.1',效果一样,只能当前设备访问
        open: true,
        proxy: {
          // 以下3种匹配可以共存,如果多个规则匹配中同一个请求,只有先定义的规则生效。字符串和正则都是匹配path的起始部分
          '/app': { //这是统一代理,非常重要!!!
            target: 'https://reg.cctv.com',
            changeOrigin: true,
            rewrite: (path) => path.replace(/^\/app/, ''), 
            // rewrite: {"^/app": ""}, //前缀匹配'/api'等效于正则匹配'^/api'
          },
          '^/(play|create_task|task_status)': { //这是统一代理,非常重要!!!
            target: 'http://192.168.116.111:6000',
            changeOrigin: true,
          },
          '/download':{ //这是单个匹配
            target: 'http://192.168.116.222:8000',
            changeOrigin: true,
          },
        }
      },
    }
  })
  
十三、Rollup,JS模块打包工具
1、Rollup的功能
  (1)Tree-Shaking,将没用到的代码剔除
  (2)兼容CommonJS模块
  (3)兼容ES模块
2、配置文件
  (1)配置文件示例,rollup.config.js
    import json from“ rollup - plugin - json”
    import resolve from“ rollup - plugin - node - resolve”
    import commonjs from‘ rollup - plugin - commonjs’
    export default {
      //input:”src/index.js”,  //指定入口文件路径
      //多入口打包
      //input:[‘src/index.js’,’src/album.js’],
      input: {
        foo: ’src / index.js’,
        bar: ’src / ablum.js’
      },
      output: {
        //file:”dist/bundle.js”, //指定输出的文件名
        //format:”iife”,  //指定输出的格式
        //下述是代码拆分需要使用的模式
        dir: ’dist’,
        format: ’amd’
      },
      plugins: [
        json(),
        resolve(), //rollup不能让node支持ESM规范,rollup通过resolve,让node支持ESM规范;webpack能让node支持ESM规范
        commonjs(), 
        babel({ babelHelpers: 'bundled' })
      ]
    }
  (2)运行配置文件,
    rollup,按照以下顺序运行配置文件:rollup.config.mjs -> rollup.config.cjs -> rollup.config.js
    rollup --config my.config.js,运行配置文件:my.config.js
3、API,扩展Rollup本身或者进行一些高级操作
  (1)rollup.rollup,参数为输入选项对象,返回一个Promise,该Promise解析为具有各种属性和方法的bundle对象
  (2)rollup.watch,当它检测到磁盘上某个模块已经改变,它会重新构建bundle(捆)
4、其他,在模块化方面,browserify和node都用commonjs规范(require导入,module.exports导出),ES6用ESM规范(import导入,exports导出)

十四、前端框架和环境!!!
 附、软件发布前的三个步骤
  (1)内测,Alpha(α,阿尔法)
  (2)公测,Beta(β,贝塔)
  (3)正式发布,Gamma(γ,伽玛)
1、JS版本
  (1)1995年,发布JavaScript语言;美国人布兰登·艾克创造
  (2)1997年,发布ECMAscript1
  (3)1999年,发布ECMAscript3
  (4)2009年,发布ECMAscript5
  (5)2012年,发布TypeScript
  (6)2015年,发布ECMAscript6
2、Photoshop各版本
  (1)经典版本(早期)
    A:1990年:Photoshop 1.0(首个正式版,仅支持 Mac)
    B:1991年:Photoshop 2.0(引入路径功能)
    C:1994年:Photoshop 3.0(首次支持图层)
    D:1996年:Photoshop 4.0(动作功能、调整图层)
    E:1998年:Photoshop 5.0(历史记录、文字编辑)
    F:2000年:Photoshop 6.0(矢量形状、图层样式)
    G:2002年:Photoshop 7.0(修复画笔、文本增强)
  (2)Creative Suite(CS)时代
    A:2003年:Photoshop CS(版本 8.0,Camera Raw 支持)
    B:2005年:Photoshop CS2(版本 9.0,智能对象、红眼工具)
    C:2007年:Photoshop CS3(版本 10.0,优化 Intel Mac 支持)
    D:2008年:Photoshop CS4(版本 11.0,3D 功能增强)
    E:2010年:Photoshop CS5(版本 12.0,内容识别填充)
    F:2012年:Photoshop CS6(版本 13.0,暗色界面、视频编辑)
  (3)Creative Cloud(CC)时代,(转为订阅制,按年份命名)
    A:2013年:Photoshop CC(版本 14.0,智能锐化、相机防抖)
    B:2014年:Photoshop CC 2014(版本 15.0,路径模糊、焦点模糊)
    C:2015年:Photoshop CC 2015(版本 16.0,设计空间模式)
    D:2017年:Photoshop CC 2017(版本 18.0,SVG 字体支持
    E:2018年:Photoshop CC 2018(版本 19.0,可变字体、弯度钢笔工具)
    F:2019年:Photoshop CC 2019(版本 20.0,内容识别填充增强)
    G:2020年:Photoshop 2020(版本 21.0,对象选择工具、云文档)
    H:2021年:Photoshop 2021(版本 22.0,天空替换、Neural Filters)
    I:2022年:Photoshop 2022(版本 23.0,悬停自动蒙版、共享协作)
    J:2023年:Photoshop 2023(版本 24.0,AI 生成式填充(Firefly))
    K:2024年:Photoshop 2024(版本 25.0,更新AI 生成式填充(Firefly 2.0)、改进的对象选择工具)
3、angular版本,Google发行
  (1)AngularJS,首版,2009年
  (2)AngularJS,1.0版,2010年,
  (3)Angular,2.0版,2016年09月
  (4)Angular,4.0版,2017年03月
  (5)Angular,5.0版,2017年11月
  (6)Angular,6.0版,2018年05月
  (7)Angular,7.0版,2018年10月
  (8)Angular,8.0版,2019年05月
  (9)Angular,9.0版,2020年02月
  (10)Angular,10.0版,2020年06月
  (11)Angular,11.0版,2020年11月
  (12)Angular,12.0版,2021年05月
  (13)Angular,13.0版,2021年12月
  (14)Angular,14.0版,2022年06月
  (15)Angular,15.0版,2022年11月
  (16)Angular,16.0版,2023年05月
4、vue版本
 来源,https://github.com/vuejs/core/blob/main/CHANGELOG.md
  (1)vue,1.0.0版,2015年10月27日
  (2)vue,2.0.0版,2016年10月01日
  (3)vue,3.0.0版,2020年01月04日,预发布,setup(props, context)作为组件选项
    A、vue,3.0.0-beta.20,2020年07月08日,此前setup(props, context)作为组件选项,替代beforeCreate和created,与vue2的组件写法并存
    B、vue,3.0.0-beta.21,2020年07月14日,此后通过<script setup>简化了该组件选项的写法,与vue2的组件写法二选一
  (4)vue,3.0.0版,2020年09月18日,正式发布
  (5)vue,3.1.0版,2021年06月07日
  (6)vue,3.2.0版,2021年08月09日,<script setup> 正式使用
  (7)vue,3.3.0版,2023年05月11日
  (8)vue,3.4.0版,2023年12月29日
 附、vue-cli版本
  (1)vue-cli,3.0.0版,2018年08月10日
  (2)vue-cli,4.0.0版,2019年10月16日
  (3)vue-cli,4.5.0版,2020年07月24日,开始默认使用vue3
  (4)vue-cli,5.0.0版,2022年02月17日
 附、uni-app版本
  (1)2018年8月,uni-app1.0.0 版本发布,
  (2)2021年9月,uni-app2.0.0 版本发布,
  (3)2023年1月,uni-app3.0.0 版本发布,
5、react版本,FaceBook发行
   附、2016年10月,发布Next.js,它是React的框架
    来源,https://github.com/facebook/react/releases
    react中国,https://react.docschina.org
    所有版本简介,https://github.com/facebook/react/blob/main/CHANGELOG.md#1702-march-22-2021
    reactdom版本,https://cdn.bootcdn.net/ajax/libs/react-dom/16.6.0/cjs/react-dom.development.js
  (1)React,0.3.0版,2013年05月29日
  (2)React,0.14.8版,2016年03月29日
  (3)React,15.0.0版,2016年04月07日
    A、15.1.0版,出现错误边界,error boundaries  
    B、15.2.3版,出现纯函数组件,PureComponent
  (4)React,16.0.0版,2017年09月26日,
    A、新增componentDidCatch(--记录错误--)
    B、新增纤维fiber架构,
    C、弃用旧虚拟DOM,
    D、解决了递归调用无法中断和卡顿掉帧的问题
  (5)React,16.3.0版,2018年03月29日,
    A、沿用旧生命周期componentWillMount,componentWillReceiveProps,componentWillUpdate,
    B、新增新生命周期getDerivedStateFromProps,getSnapshotBeforeUpdate,
    C、沿用方案、新增方案只能二选一,
    D、组件自身state更新,shouldComponentUpdate()>render()>getSnapshotBeforeUpdate()>componentDidUpdate()
    E、传递过来的props更新,getDerivedStateFromProps()>shouldComponentUpdate()>render()>getSnapshotBeforeUpdate()>componentDidUpdate()
  (6)React,16.4.0版,2018年06月24日,
    A、新增Suspense(--组件--)
  (7)React,16.6.0版,2018年10月23日,
    A、新增getDerivedStateFromError(--处理错误--)
    B、新增React.memo()
    C、新增React.lazy()
  (8)React,16.8.0版,2019年02月06日
    A、新增钩子函数Hooks,可以
    B、避免组件继承React实例
    C、实现状态管理
    D、弃用生命周期
  (9)React,17.0.0版,2020年10月20日
    A、并没有添加任何面向开发人员的新特性
  (10)React,18.0.0版,2022年03月29日,
    A、新增useTransition(--处理过渡--)
  (11)React,18.3.1版,2024年04月26日,最新版本  
6、SvelteJS,2016年创建,解决传统前端框架在运行时性能上的瓶颈 // /svelt/苗条的,纤细的;https://www.svelte.cn/
7、SolidJS,2018年创建,制作交互式Web应用程序的JS框架 // /ˈsɒlid/坚固的,实心的;https://www.solidjs.cn/
  (1)3个核心API,createSignal、createMemo、createEffect,以createEffect为例,实现逐词插入数据,类似于发报效果
    import { createEffect, onMount } from 'solid-js';
    import { Marked } from '@ts-stack/markdown';
    type Props = {
      message: MessageType;
      chatflowid: string;
    };
    Marked.setOptions({ isNoP: true });
    export const BotBubble = (props: Props) => {
      let botMessageEle: HTMLDivElement | undefined;
      onMount(() => { });
      var i = 0;
      createEffect(() => {
        if (botMessageEle) {
          let before = props.message.message;
          let reg = /"answer": "(.+)",/g; 
          let after = [];
          if(!reg.test(before)){
            after.push(before) 
          }else{
            before.replace(reg,function(regAll,A1){
              after.push(A1) 
            });
          }
          const interval = setInterval(() => {
            botMessageEle.innerHTML = Marked.parse(after[i]);
            i++;
            if(i>after.length-2)clearInterval(interval)
          },100);
        }
      });
      return (
        <div class="flex flex-col justify-start">
          {props.message.message && (
            <span
              ref={botMessageEle}
            />
          )}
        </div>
      );
    };
  (2)3个生命周期,onMount(可以在此处向后台请求数据)、onCleanup、onError
  (3)PascalCase,帕斯卡命名法,将变量的所有单词的首字母大写
  (4)splitProps,属性的使用
    //以下案例来源,chat-embed项目下ShortTextInput.tsx文件
    //Omit<Type, Keys>TypeScript创建新类型,从现有类型(Type)中排除指定属性(Keys)
    import { splitProps } from 'solid-js';
    import { JSX } from 'solid-js/jsx-runtime';
    type ShortTextInputProps = {
      ref: HTMLInputElement | HTMLTextAreaElement | undefined;
      onInput: (value: string) => void;
      fontSize?: number;
      disabled?: boolean;
    } & Omit<JSX.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onInput'>;
    export const ShortTextInput = (props: ShortTextInputProps) => {
      const [local, others] = splitProps(props, ['ref', 'onInput']);
      const handleInput = (e) => {
        if (props.ref) {
          e.currentTarget.scrollTo(0, e.currentTarget.scrollHeight);
          local.onInput(e.currentTarget.value);
        }
      };
      return (
        <textarea
          ref={props.ref}
          onInput={handleInput}
          {...others}
        />
      );
    };
    //以下案例来源,chat-embed项目下TextInput.tsx文件
    import { ShortTextInput } from './ShortTextInput';
    export const TextInput = (props: Props) => {
      let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined;
      return (
        <ShortTextInput
          ref={inputRef as HTMLTextAreaElement}
          onInput={handleInput}
        />
      );
    };
8、node,介绍!!!
  来源,https://nodejs.org/zh-cn/download/releases/
  来源,https://nodejs.org/en/blog/release/page/1
  来源,https://pnpm.io/zh/motivation
  (1)node安装包、node.js、node.exe
    A、node安装包,
      a、运行后会启动安装向导
      b、在系统中部署完整的JS运行时环境node.js
      c、node.msi是官方提供的Windows安装包
      d、node.tar.xz是官方提供的Linux二进制包
    B、node.js
      a、是基于Chrome V8引擎的JS运行时环境
      b、支持JS在服务器端运行
    C、node.exe
      a、是node.js的核心可执行文件
      b、作为JS解释器,为前后端提供统一的执行环境
    D、在前端开发中
      a、node.js提供工具链支持,如通过npm管理依赖包
      b、node.js环境驱动Webpack、Vite等工具实现代码打包与热更新
    E、在后端开发中
      a、node.js通过Express等框架开发API
      b、node.js环境处理HTTP请求与数据库交互
  (2)node版本,内含npm版本,发布年份(发行时间)
    附、LTS,“Long-Term Support”的英文缩写,意为长期支持
    A、2009年,node出现
    B、2010年,npm出现(Node Package Manager,node包管理器)
    C、2011年,Windows版node出现,(node.js框架Express.js出现)
    D、2012年,原作者离开
    E、2013年,Node发布0.10版本
    F、2014年,Node发布0.12版,(node.js框架Koa.js出现)
    G、2015年,Node发布4.0.0版、5.0.0版,内含npm的2.14.2版、3.3.6版
    H、2015年,node发布4.0.0版、5.0.0版,内含npm的2.14.2版、3.3.6版
    I、2016年,node发布6.0.0版、7.0.0版,内含npm的3.8.6版、3.10.8版
    J、2017年,node发布8.0.0版、9.0.0版,内含npm的5.0.0版、5.5.1版
    K、2018年,node发布10.0.0版、11.0.0版,内含npm的5.6.0版、6.4.1版
    L、2019年,node发布12.0.0版、13.0.0版,内含npm的6.9.0版、6.12.0版
      node13.2.0开始支持ES6模块,此后无需用vue-loader把.vue文件转化为ES6模块
    M、2020年,node发布14.0.0版、15.0.0版,内含npm的6.14.4版、7.0.2版 
    N、2021年,node发布16.0.0版、17.0.0版,内含npm的7.10.0版、8.1.0版
    O、2022年,node发布18.0.0版、19.0.0版,内含npm的8.6.0版、8.19.2版 
    P、2023年,node发布20.0.0版、21.0.0版,内含npm的9.6.4版、10.2.0版
    Q、2024年,node发布22.0.0版、23.0.0版,内含npm的10.5.1版、10.9.0版 
9、nvm,node版本管理工具,Node Version Manager
  (1)发布
    A、2010年,美国开发者(网名 creationix)创建
    B、2015年,Windows版首版本诞生
  (2)下载与安装
    A、下载,https://gitcode.com/gh_mirrors/nv/nvm-windows/releases
    B、安装,C:\Users\Haier\AppData\Roaming\nvm
  (3)设置镜像
    A、nvm node_mirror https://npmmirror.com/mirrors/node/
    B、nvm npm_mirror https://npmmirror.com/mirrors/npm/
  (4)常见命令
    A、nvm -v,查看nvm版本号;node -v,查看当前node版本号
    B、nvm list available,能安装的版本(不完整)
    C、nvm install 16.20.0,安装版本-node@16.20.0
    D、nvm use 16.20.0,使用版本-node@16.20.0
    E、nvm ls(list),已安装版本列表,前面带*号的为在用版本 
  (5)上面命令,在vscode编辑器里运行更有效,也可以“以管理员身份运行”
    A、点击任务栏上的放大镜,右键“命令提示符”,点击“以管理员身份运行” 
    B、点击任务栏上的放大镜,输入cmd,点击“以管理员身份运行”
10、npm,node包管理工具,Node Package Manager
  (1)淘宝npm镜像使用方法
    来源,https://blog.csdn.net/quuqu/article/details/64121812
    A、临时使用npm镜像
      a、npm --registry https://registry.npm.taobao.org install express
      b、验证 npm info express
    B、持久使用npm镜像
      a、配置,npm config set registry https://registry.npm.taobao.org
      b、验证,npm config get registry
    C、持久使用pnpm镜像
      a、配置,pnpm config set registry https://registry.npm.taobao.org/</script>
      b、验证,pnpm config get registry
  (2)node包管理工具之npm、cnpm,yarn、tyarn,pnpm
    A、npm:node.js自带的包管理工具
      a、2010年发布
      b、生成package-lock.json文件,从npm5.0(内置于2017年发布的node8.0.0)开始
      c、按顺序依次下载每个包,速度慢
      d、下面4个包管理工具可通过npm或手动安装
        d1、npm安装。npm install xxx
        d2、手动安装。从工具的官方渠道下载安装资源,用电脑系统包管理器安装 
    B、cnpm:即c(china,中国)npm
      a、2014年发布,淘宝推出的npm替代工具
      b、不生成lock文件
      c、安装,npm install -g cnpm --registry=https://registry.npm.taobao.org
    C、yarn:纱线、故事
      a、2016年脸书谷歌等联合发布,并行下载包,速度块
      b、生成yarn.lock文件
    D、tyarn:即t(taobao,淘宝)yarn
      a、2022年发布,淘宝推出的yarn替代工具 
      b、不生成lock文件
    E、pnpm:即p(performant,高性能的)npm
      a、2016年11月发布,乌克兰人Zoltan Kochan推出的npm和yarn的改进工具
      b、将不同版本间有差异的文件添加到仓库,不会因为一个文件的改变,而复制整个新版本包的内容
      c、所有文件都会存储在硬盘上的某一位置,允许跨项目地共享同一版本的依赖,避免重复安装相同的依赖
      d、生成pnpm-lock.yaml文件
  (3)npm install 安装命令示例,包名和后面的参数,位置可以互换
    A、npm install 把package.json中的(开发devDependencies|开发生产dependencies)依赖配置,下载到本地项目的node_modules
    B、npm install X 会把X包安装到项目的node_modules目录中,不会将模块依赖写入package.json中
      如果package.json中存在X包,本地没有安装,那么运行此命令,会从远程仓库下载package.json指定版本的X包
    C、npm install X -g 会把X包安装到全局的node_modules目录中,不会将模块依赖写入package.json中
    D、npm install X --save 会把X包安装到项目的node_modules目录中,会在package.json的dependencies属性下添加X,--save简写为-S
    E、npm install X@3.10.3 --save-dev 会把X包安装到项目的node_modules目录中,会在package.json的devDependencies属性下添加X,--save-dev简写为-D 
  (4)npm其它命令
    A、npm help <command>可查看某条命令的详情
    B、npm list <package>可以检测当前目录下node_modules子目录里边有没有这个包
    C、npm update <package>可以把当前目录下node_modules子目录里边的对应模块更新至最新版本
    D、npm run start/stop/test可以简写为npm start/stop/test
    E、获取npm的安装路径,npm config get prefix
  (5)包的运行  
    A、npm,必须在根目录下运行包,必须写入到package.json里面,且已安装
    B、npx,可以在任何地方运行包,不必写入到package.json里面,不必安装,从本目录开始逐层向上直至全局(nodejs环境变量)寻找该包,
      a、找到就运行,没有找到就临时安装并直接运行,运行结束后删掉;
      b、如果想运行层级以外的版本,则加上版本号即可,如npx webpack-dev-server@3.10.3
  (6)“npm run dev”的运行说明
    {
      "scripts": {
        "dev": "xxx abc",
        "serve": "vue-cli-service serve",
      },
    }
    注意:直接执行“xxx abc”命令会报错,因为操作系统里只有npm相关的命令
    A、在运行“npm install xxx”时,会在“node_modules/.bin”目录下,为包“xxx”创建名为“xxx”的3个文件,三者都是用node执行1个js文件
      a、没有后缀名的是对应Unix系的shell脚本,
      b、.cmd为后缀名的是windows bat脚本,
      c、.ps1为后缀名的则是PowerShell脚本(可以跨平台)
    B、“npm run dev”运行时,
      a、npm首先在"scripts"字段里,找到“dev”对应的“xxx”字段,
      b、然后在“node_modules/.bin”目录下,找到名为“xxx”的文件,该文件从“node_modules/xxx”文件里引入相应文件,并运行"xxx abc"
    C、运行后,缺包(出错、报错、错误)排查
      a、node版本是否太新或太旧
      b、本公司的公共包是否下载到对应位置 
11、与node相关!!!(导出与导入、路径符号与url分隔符、path.join()与path.resolve()、@说明、@定义、url解析)
  (1)导出与导入
    A、node模块块开发:导出模块module.exports = {},导入模块require('./url/moduleName')
    B、ES6模块块开发:导出模块export default xxx,导入模块import xxx from './url/moduleName'
  (2)文件路径符与url分隔符
    A、文件路径符
      a、\反斜杠,主要用于window系统,如\表示路径分隔符,.\表示当前目录,..\表示上级目录
      b、/正斜杠,主要用于非window系统,如/表示路径分隔符,./表示当前目录,../表示上级目录
    B、url分隔符
      a、/正斜杠,用于一切系统的url中,如/表示网站根目录
      b、当请求的路径是/blog/article时,服务器会在文件系统中找,/var/www/html + /blog/article = /var/www/html/blog/article 
  (3)path.join()与path.resolve()的区别
    A、path.join() //从左到右拼接路径,对最终路径进行规范化处理,如解析.、..,统一分隔符
    B、path.resolve() //从右向左拼接路径,直到遇到第一个绝对路径,如C:\、\、/为止;没有则以当前工作目录作为起点
    C、示例
      const path = require('path');
      console.log(__dirname); //c:\Users\Haier\Desktop\z_vue 
      console.log(path.join()); //.
      console.log(path.join('/a', '/b')); //\a\b
      console.log(path.join(__dirname, './src')); //c:\Users\Haier\Desktop\z_vue\src
      console.log(path.resolve()); //c:\Users\Haier\Desktop\z_vue
      console.log(path.resolve('/a', '/b')); //c:\b
      console.log(path.resolve(__dirname, './src')); //c:\Users\Haier\Desktop\z_vue\src
  (4)@说明
    A、~是构建工具的特殊标记,@是构建工具中配置的路径别名,~@告诉构建工具按项目配置的路径规则查找资源 
    B、在html中,使用@
    C、在CSS的SCSS/Sass/Less预处理器的@import语法中,对@有专门定义,因此后面必须使用~@,在CSS的其它情况下最好使用~@
    D、@与~@使用示例
      <img src="@/assets/logo.png" alt="" />
      <style lang="scss" scoped>
        @import "~@/styles/mixin.scss";
        .bg {
          background: url(@/assets/logo.png) no-repeat;
        }
      </style>
  (5)@定义示例
    module.exports = {
      context: '/',
      entry: {},
      output: {},
      resolve: {
        alias: { //别名
          //以下解析方式相同
          '@': path.join(__dirname, '..', 'src') 
          '@': path.join(__dirname, '../src')
        }
      },
    }
    export default defineConfig(({command,mode})=>{
      return {
        resolve: {
          alias: { //别名
            //以下键名写法相同,但带引号更安全
            @: path.resolve(__dirname, './src') 
            '@': path.resolve(__dirname, './src')
          },
        },
      }
    })
  (4)url解析,问号传参给服务器,井号传参给浏览器
    var url_ = 'http://www.zhu.cn:80/ccc/index.html?name=zxt&age=26#33';
    var url = require('url');
    console.log(url.parse(url_, true));
    /* winUrl{
      protocol: 'http:',
      slashes: true,
      auth: null,
      host: 'www.zhu.cn:80',
      port: '80',
      hostname: 'www.zhu.cn',
      hash: '#33',
      search: '?name=zxt&age=26',
      query: { name: 'zxt', age: '26' },//当true不存在时query: 'name=zxt&age=26',
      pathname: '/ccc/index.html',
      path: '/ccc/index.html?name=zxt&age=26',
      href: 'http://www.zhu.cn:80/ccc/index.html?name=zxt&age=26#33' 
    } */
12、npm运行出错与解决
  (1)问题1
    A、现象,运行npm install,下面情况导致报错
      "dependencies": {
        "swiper": "^8.1.0",//一级插件
        "vue-awesome-swiper": "^4.1.1",//内部有二级插件swiper,但版本为"^5.2.0"
      },
    B、解决
      初步解决:更改一级以适应二级,问题消失,但安装结束后,node_modules被自动删除;
      最终解决:运行“yarn”,彻底解决这个问题 
  (2)问题2
    A、现象,运行npm run dev,页面卡顿
    B、解决,把mock文件夹里,新生成的压缩文件如fkdsfsdlsdf.js删除;或者运行node -v,看node版本是不是在16以上 
  (3)问题3
    A、现象,运行npm run dev
      跳转到localhost/#/sys/score,可以打开页面
      跳转到localhost:81/#/sys/score,不可以打开页面
    B、解答,以前开发时
      在80端口设置了永久标志,在81端口没设置永久标志
      用router.beforeEach拦截时,前者通过,后者卡住
  (4)问题4
    A、现象,运行npm run dev,报错如下
      [Vue warn]: Invalid prop: custom validator check failed for prop "type".    vue.runtime.esm.js?
    B、解决
      A、报错原因,iview支持数字类型的输入,vue自带的vue.runtime.esm.js不支持数字类型的输入
      B、解决方案,在浏览器安装https://github.com/vuejs/vue-devtools,取代vue.runtime.esm.js,给vue检查错误
      C、该错误不影响运行
13、Live Server,一个快速启动的本地服务器
  (1)在vscode的扩展中找到并安装
  (2)在项目中-使用步骤
    A、右键根目录下index.html
    B、点击Open with Live Server
    C、改动该项目任意文件的内容,保存,自动刷新
  (3)在demo中-使用步骤
    A、右键任意.html文件
    B、点击Open with Live Server
    C、改动该文件的内容,保存,自动刷新(无需点击浏览器的圆箭头)
  (4)主要功能
    A、实时重新加载,自动检测文件变化,并立即在浏览器中刷新页面
    B、跨域代理(需配置)
  (5)地址栏显示,
    A、Open with Live Server,http://127.0.0.1:5500/0.html
    B、Open In Default Browser,file:///C:/Users/Haier/Desktop/z_vue/0.html
14、yapi,可本地部署的API管理平台
  (1)下载
    A、nodejs(7.6+)
    B、mongodb(2.6+),默认安装在C盘,应改在D盘,且路径中的目录名不能有空格
  (2)安装步骤及注意事项
    A、卸载nodejs,
    B、删除npm,C:\Users\XXXXX\AppData\Roaming\npm
    C、安装node,遇到“Custom Setup”时,选择“npm package manager”
    D、安装mongodb,遇到“Installing MongoDB Compass”时,去掉默认的勾选
    E、每次重新搭建yapi之前,需要删除my-yapi目录、卸载并安装1次mongodb
  (3)安装并运行yapi
    A、安装yapi脚手架,npm install -g yapi-cli --registry https://registry.npm.taobao.org
    B、搭建yapi项目,yapi server,想把yapi搭建在哪个目录下,就在哪个目录下运行此命令。
    C、访问yapi项目,http://127.0.0.1:9090/
    D、配置并连接数据库,即填写公司名称、点击开始部署
    E、启动yapi,在my-yapi目录下运行node vendors/server/app.js
      --另、在谷歌浏览器上装corss-request插件后,可以在在yapi上发送请求
  (4)登录
    A、http://127.0.0.1:3000(仅限自己)或者http://172.18.10.12:3000(自己和别人)
    B、登录/注册,选登录
    C、账号名:"admin@admin.com",
    D、密码:"ymfe.org"
    E、登录
  (5)yapi的层级
    A、分组
    B、项目
    C、分类
    D、接口

十五、微前端
1、介绍
 (1)Micro Frontends,是一种前端架构模式,将大型Web应用拆分成多个独立、自治的小型前端应用,称为“微应用”
 (2)通过框架集成这些微应用,形成一个统一的用户体验
 (3)它借鉴了后端微服务的思想,旨在解决传统单体前端应用在团队协作、技术栈升级、代码维护等方面的痛点
2、核心思想
  (1)拆分:按业务功能或团队边界拆分前端应用,每个微应用独立开发、测试、部署
  (2)自治:微应用可使用不同技术栈(如 React、Vue),拥有独立的生命周期
  (3)集成:通过主框架(如 single-spa、Qiankun)将微应用整合到统一界面中
3、关键特点
  (1)技术栈无关,不同微应用可使用不同框架(如 React 与 Vue 共存),逐步升级技术栈
  (2)独立部署,微应用可单独开发、测试、上线,无需协调整个项目
  (3)团队自治,每个微应用由独立团队负责,减少跨团队协作成本
  (4)增量升级,逐步替换旧模块,避免 “全量重构” 风险
  (5)代码隔离,微应用间共享状态可控,减少命名冲突和依赖问题
4、常见实现方式
  (1)路由分发式:主框架通过路由规则将请求分发给不同微应用(如 single-spa)
  (2)iframe 集成:使用 iframe 嵌入子应用,隔离性强但通信复杂
  (3)组件加载式:动态加载微应用的组件(如 Web Components)
  (4)构建时集成:通过构建工具(如 Module Federation)在编译阶段整合微应用(Webpack 5 支持)
5、典型场景
  (1)大型企业应用:如后台管理系统,不同模块由不同团队维护
  (2)多技术栈共存:旧项目使用 AngularJS,新项目想迁移到 React/Vue
  (3)多团队协作:多个前端团队并行开发,避免代码冲突
  (4)渐进式升级:逐步重构旧系统,而非推倒重来
6、优缺点
  (1)优点
    A、提高开发效率,降低团队协作成本
    B、技术栈灵活,支持按需升级
    C、故障隔离,单个微应用崩溃不影响整体
  (2)缺点
    A、架构复杂度增加,需要统一的规范和工具链
    B、跨应用通信和状态管理更复杂
    C、性能开销:多个微应用可能增加资源加载时间
7、相关工具
  (1)single-spa:JavaScript 微前端框架,支持多种框架集成
  (2)Qiankun:蚂蚁集团开源的微前端框架,基于 single-spa,中文文档完善
  (3)Webpack Module Federation:Webpack 5 内置的模块联邦,支持运行时动态加载
  (4)MicroApp:轻量级微前端框架,主打高性能和简单易用
8、常见实现方式-路由分发式 
  附、豆包:路由分发式,主框架通过路由规则将请求分发给不同微应用。请给出目录结构和主应用、微应用入口文件示例
  (1)目录结构
    A、前端根目录结构
      root-project/                  # 根项目(主应用)
      ├── package.json
      ├── webpack.config.js          # 主应用构建配置
      ├── public/
      │   └── index.html             # 主应用 HTML 模板
      └── src/
          ├── index.js               # 主应用入口:注册微应用
          ├── App.js                 # 主应用根组件
          ├── router/                # 主应用路由配置
          │   └── index.js
          └── micro-apps/            # 微应用配置
              ├── config.js          # 微应用注册配置
              └── utils.js           # 微应用工具函数
      micro-apps/                    # 微应用(独立项目,可单独仓库)
      ├── goods-app/                 # Vue 微应用(商品列表)
      │   ├── package.json
      │   ├── src/
      │   │   ├── main.js            # 微应用入口:暴露生命周期
      │   │   ├── router/
      │   │   │   └── index.js       # 微应用路由配置
      │   │   └── App.vue
      │   └── webpack.config.js      # 微应用构建配置
      ├── cart-app/                  # React 微应用(购物车)
      │   ├── package.json
      │   ├── src/
      │   │   ├── index.js           # 微应用入口:暴露生命周期
      │   │   ├── App.js
      │   │   └── routes.js
      │   └── webpack.config.js
      └── user-app/                  # Angular 微应用(用户中心)
          ├── package.json
          ├── src/
          │   ├── main.ts            # 微应用入口:暴露生命周期
          │   ├── app/
          │   │   ├── app.module.ts
          │   │   └── app.routing.ts
          │   └── qiankun.ts         # 乾坤生命周期封装
          └── angular.json
    B、服务器根目录结构
      /var/www/html/                  # 服务器根目录(对应域名:https://example.com)
      ├── main-app/                   # 主应用部署目录
      │   ├── index.html              # 主应用入口 HTML
      │   ├── static/                 # 主应用静态资源
      │   │   ├── js/                 # 主应用打包 JS
      │   │   └── css/                # 主应用打包 CSS
      │   └── favicon.ico             # 主应用图标
      ├── micro-apps/                 # 微应用统一存放目录
      │   ├── goods-app/              # Vue 微应用(商品列表)
      │   │   ├── index.html          # 微应用入口 HTML
      │   │   ├── js/                 # 微应用打包 JS
      │   │   └── css/                # 微应用打包 CSS
      │   │
      │   ├── cart-app/               # React 微应用(购物车)
      │   │   ├── index.html
      │   │   ├── js/
      │   │   └── css/
      │   │
      │   └── user-app/               # Angular 微应用(用户中心)
      │       ├── index.html
      │       ├── js/
      │       └── css/
      └── nginx.conf 
    C、Nginx 配置(路由转发核心)
      # nginx.conf
      server {
        listen 80;
        server_name example.com;
        root /var/www/html;
        # 主应用路由:直接指向主应用目录
        location / {
          root /var/www/html/main-app;
          try_files $uri $uri/ /index.html; # 支持 SPA 路由
        }
        # 微应用路由转发:/goods → 指向 goods-app 目录
        location /goods {
          alias /var/www/html/micro-apps/goods-app;
          try_files $uri $uri/ /goods/index.html; # 微应用 SPA 路由支持
        }
        # 微应用路由转发:/cart → 指向 cart-app 目录
        location /cart {
          alias /var/www/html/micro-apps/cart-app;
          try_files $uri $uri/ /cart/index.html;
        }
        # 微应用路由转发:/user → 指向 user-app 目录
        location /user {
          alias /var/www/html/micro-apps/user-app;
          try_files $uri $uri/ /user/index.html;
        }
      }   
  (2)主应用核心代码
    A、微应用配置(src/micro-apps/config.js)
      // 微应用配置(路由规则与微应用映射)
      export default [
        {
          name: 'goods-app',
          entry: process.env.NODE_ENV === 'development' 
            ? '//localhost:8081'  // 开发环境
            : '/micro-apps/goods-app/',  // 生产环境
          container: '#micro-app-container',
          activeRule: '/goods',  // 关键:路由匹配规则
          props: { /* 传递给微应用的参数 */ },
        },
        {
          name: 'cart-app',
          entry: process.env.NODE_ENV === 'development' 
            ? '//localhost:8082' 
            : '/micro-apps/cart-app/',
          container: '#micro-app-container',
          activeRule: '/cart',
        },
        {
          name: 'user-app',
          entry: process.env.NODE_ENV === 'development' 
            ? '//localhost:8083' 
            : '/micro-apps/user-app/',
          container: '#micro-app-container',
          activeRule: '/user',
        },
      ];
    B、主应用入口(src/index.js)
      import React from 'react';
      import ReactDOM from 'react-dom/client';
      import { BrowserRouter } from 'react-router-dom';
      import App from './App';
      import { registerMicroApps, start } from 'qiankun';
      import microApps from './micro-apps/config'; // 微应用配置
      // 注册微应用
      registerMicroApps(microApps, {
        beforeLoad: (app) => console.log(`Loading app: ${app.name}`),
        beforeMount: (app) => console.log(`Mounting app: ${app.name}`),
        afterUnmount: (app) => console.log(`Unmounting app: ${app.name}`),
      });
      // 启动 Qiankun
      start();
      // 渲染主应用
      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(
        <BrowserRouter>
          <App />
        </BrowserRouter>
      );
    C、主应用根组件(src/App.js)
      import { Link, Routes, Route } from 'react-router-dom';
      function App() {
        return (
          <div className="main-app">
            {/* 主应用导航 */}
            <nav>
              <Link to="/goods">商品列表</Link> |
              <Link to="/cart">购物车</Link> |
              <Link to="/user">用户中心</Link>
            </nav>
            {/* 微应用挂载容器 */}
            <div id="micro-app-container" />
            {/* 主应用自有路由(可选) */}
            <Routes>
              <Route path="/" element={<Home />} />
              <Route path="/about" element={<About />} />
            </Routes>
          </div>
        );
      }  
  (3)微应用核心代码
    A、Vue 微应用(商品列表)
      a、入口文件(goods-app/src/main.js)
        import Vue from 'vue';
        import App from './App.vue';
        import createRouter from './router'; 
        const isMicroApp = window.__POWERED_BY_QIANKUN__;
        let instance = null;
        function render(props = {}) {
          instance = new Vue({
            router: createRouter(isMicroApp ? '/goods' : ''), 
            render: h => h(App),
          }).$mount(props.container?.querySelector('#app') || '#app');
        }
        // 独立运行时直接渲染
        if (!isMicroApp) {
          render();
        }
        // 暴露生命周期钩子
        export async function bootstrap() {}
        export async function mount(props) {
          render(props);
        }
        export async function unmount() {
          instance.$destroy();
          instance = null;
        }
      b、路由配置(goods-app/src/router/index.js)
        import VueRouter from 'vue-router';
        import GoodsList from '../views/GoodsList.vue';
        import List from '../views/List.vue';
        export default function createRouter(base = '') {
          return new VueRouter({
            mode: 'history',
            base,
            routes: [
              { path: '/', name: 'GoodsList', component: GoodsList },
              { path: '/list', name: 'List', component: List },
            ],
          });
        }
    B、React 微应用(购物车)
      入口文件(cart-app/src/index.js)
      import React from 'react';
      import ReactDOM from 'react-dom/client';
      import { BrowserRouter } from 'react-router-dom';
      import App from './App';
      // 微应用生命周期
      let root = null;
      function render(props) {
        const { container } = props;
        root = ReactDOM.createRoot(
          container ? container.querySelector('#root') : document.getElementById('root')
        );
        root.render(
          <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/cart' : '/'}>
            <App />
          </BrowserRouter>
        );
      }
      // 独立运行时直接渲染
      if (!window.__POWERED_BY_QIANKUN__) {
        render({});
      }
      // 暴露生命周期钩子
      export async function bootstrap() {}
      export async function mount(props) {
        render(props);
      }
      export async function unmount() {
        root.unmount();
        root = null;
      }
  (4)关键机制说明
    A、路由匹配流程
      a、主应用监听 URL 变化,当路径匹配到 activeRule(如 /goods)时,加载对应微应用
      b、微应用内部路由基于 basename(如 /goods),确保子路由正确匹配(如 /goods/list)
    B、生命周期管理
      a、微应用通过 bootstrap、mount、unmount 钩子与主应用通信,控制渲染和卸载
    C、独立运行支持
      a、微应用通过 window.__POWERED_BY_QIANKUN__ 判断运行环境,支持独立开发和调试
  (5)部署建议
    A、主应用部署在根路径(如 https://example.com)
    B、微应用分别部署在子路径(如 https://example.com/micro-apps/goods-app/)
    C、生产环境通过 Nginx 等服务器配置路径转发,确保资源正确加载

十六、神策(本文档基于1.26.5版本)
  注意,先做项目(了解源码的功能),再看源码(了解功能的实现)
  作用,分析用户的行为
  来源,https://manual.sensorsdata.cn/sa/latest/zh_cn/tech_sdk_client_web_use-7545024.html
1、集成文档
  (1)自动生成
  (2)同步载入
  (3)CommonJS规范加载
  (4)ES6模块化引入
  (5)AMD规范加载
2、基础api介绍(属性-注册、获取、设置、上报)
  (1)SDK初始化参数,16项
  (2)注册公共属性
    // 以下项目旧代码备份
    // var i=0
    // sensors.init({
    //   server_url: server_url, //神策数据接收地址
    //   //web_url: web_url, //神策分析后台地址,神策1.10及以上版本,不需要配置这个参数,
    //   is_track_single_page: true, //单页面配置,默认开启,若页面中有锚点设计,需要将该配置删除,否则触发锚点会多触发 $pageview 事件
    //   use_client_time: true, 
    //   send_type: 'beacon',
    //   // heatmap: {
    //   //   clickmap: 'default', //(1/3)开启点击图,自动采集a input button textarea 四种元素的$WebClick事件,'not_collect'表示关闭
    //   //   scroll_notice_map: 'default', //(2/3)开启触达图,自动采集$WebStay事件,'not_collect'表示关闭
    //   // },
    //   show_log: true, //console会打印采集信息
    //   // preset_properties: { //子配置项 true 表示采集,false 表示不采集,未设置的参数取默认值
    //   //   latest_traffic_source_type: false, //是否采集 $latest_traffic_source_type 最近一次流量来源类型
    //   //   latest_search_keyword: false, //是否采集 $latest_search_keyword 最近一次搜索引擎关键字
    //   //   latest_referrer: false, //是否采集 $latest_referrer 最近一次前向地址
    //   // },
    // });
    // sensors.registerPage({
    //   current_url: location.href,
    //   referrer: document.referrer,
    //   description1: 'server_url字段为空或非神策地址,在开发者的控制台,依然可以看',
    //   description2: '到神策插件采集的数据,只是神策服务器接收不到该数据,没法分析!',
    //   prop_number_: function() {
    //     return ++i;
    //   },
    // });
    // sensors.quick('autoTrack'); //(3/3)用于采集$pageview事件
  (3)获取预置属性
    sensors.quick('isReady',function(){
      var presetProperties = sensors.getPresetProperties();
      console.log( presetProperties );
    });
  (4)设置用户属性
    A、直接设置用户的属性,如果存在则覆盖。
      sensors.setProfile({email:'xxx@xx'});
    B、如果不存在则设置,存在就不设置。
      sensors.setOnceProfile({email:'xxx@xx'});
    C、给数组属性添加值
      sensors.appendProfile({catrgory: ['玉米','白菜']});
      sensors.appendProfile({catrgory: '玉米'});//给 category 增加一个值
    D、对当前用户的属性做递增或者递减
      sensors.incrementProfile({'navClick': -1});//表示navClick递减
    E、删除当前用户及他的所有属性
      sensors.deleteProfile();
    F、删除当前用户的一些属性
      sensors.unsetProfile(['email','location']);
      sensors.unsetProfile('email');
  (5)物品元数据上报
    A、直接设置一个物品,如果已存在则覆盖
      sensors.setItem("food","2",{name:"玉米",flavour:"甜"});
      setItem(item_type,item_id,properties)
        除物品ID与物品所属类型外,其他物品属性需在properties中定义
        物品属性中,属性名称与属性值的约束条件与事件属性相同
        item_type:必选
        item_id:必选
        properties:可选
    B、sensors.deleteItem("food","2");
      deleteItem(item_type,item_id)
        如果物品不可被推荐需要下线,删除该物品即可,如不存在则忽略
        除物品ID与物品所属类型外,不解析其他物品属性
        item_type:必选
        item_id:必选
3、全埋点(标签埋点、属性埋点)
  附1、heatmap相关参数,https://manual.sensorsdata.cn/sa/latest/zh_cn/tech_sdk_client_web_all_use-7545310.html
  附2、server_url获取步骤,登录神策-基本设置-数据接入-(客户端埋点)生成导入代码-生成-server_url
  附3、示例,全埋点三个事件(元素点击$WebClick、视区停留$WebStay、页面浏览$pageview),其中$WebClick默认采集4种元素(a input button textarea)
    <script>
      //var sensors = window['sensorsDataAnalytic201505'];
      import sensors from 'sa-sdk-javascript';
      var server_url = 'https://10.50.16.15/api';
      var web_url = 'https://10.50.16.15/api';
      var i=0;
      sensors.init({
        server_url: server_url,// 数据接收地址
        web_url: web_url,// 神策分析后台地址,神策1.10及以上版本,不需要配置这个参数,
        is_track_single_page: true, // 单页面配置,默认开启,若页面中有锚点设计,需要将该配置删除,否则触发锚点会多触发 $pageview 事件
        use_client_time: true,
        send_type: 'beacon',
        heatmap: {
          clickmap:'default',//(1/3)开启点击图,$WebClick事件默认采集4种元素,'not_collect'表示关闭
          scroll_notice_map:'default',//(2/3)开启视区停留(触达图),自动采集$WebStay事件,'not_collect'表示关闭
          loadTimeout: 3000,
          collect_url: function(){
            //如果只采集首页
            if(location.href === 'example.com/index.html' || location.href === 'example.com/'){
              return true;
            }
          },
          //此参数针对预置 $WebClick 事件(包括 quick('trackHeatMap') quick('trackAllHeatMap') 触发的)生效。
          collect_element: function(element_target){
            // 如果这个元素有属性sensors-disable=true时候,不采集。
            if(element_target.getAttribute('sensors-disable') === 'true'){
              return false;
            }else{
              return true;
            }
          },
          //此参数针对预置 $WebClick 事件(包括 quick('trackHeatMap') quick('trackAllHeatMap') 触发的)生效。
          custom_property: function( element_target ){
            //比如您需要给有 data=test 属性的标签的点击事件增加自定义属性 name:'aa' ,则代码如下:
            if(element_target.getAttribute('data') === 'test'){
              return {
                name:'aa'
              }
            }
          },
          collect_input: function(element_target){
            if(element_target.id === 'a'){ //如果元素的 id 是a,就采集这个元素里的内容。
              return true;
            }
          },
          element_selector: 'not_use_id',
          renderRefreshTime: 1000
        },
        scrollmap: {
          collect_url: function(){//如果只采集首页 
            if(location.href === 'example.com/index.html' || location.href === 'example.com/'){
              return true;
            }
          },
        },
        show_log: true, // console会打印采集信息
      });
      sensors.quick('autoTrack'); //(3/3)自动采集$pageview事件
      sensors.quick('autoTrack', { //添加额外的属性
        platform: 'h5'
      })
    </script>
  (1)扩展至div元素,采集规则为
    A、div为叶子结点(无子元素)时采集div的点击
    B、div中有且只有样式标签(['mark','strong','b','em','i','u','abbr','ins','del','s','sup'])时,点击div或者样式标签都采集div的点击
      heatmap:{ 
        collect_tags:{
          div: true
        }
      }
  (2)扩展至任意元素,示例
    heatmap: {
      clickmap:'default',
      collect_tags: {
        div: { //div通过配置最多可以采集3层嵌套
          max_level: 1 //默认是1,即只支持叶子div。可配置范围是[1, 2, 3],非该范围配置值,会被当作1处理。
        },
        get_vtrack_config: true, //无限层级的div
        li: true,
        img: true
        //...其他标签
      }
    }
  (3)扩展至特殊属性,示例
    <div data-sensors-click>我是测试元素</div>
    <li data-sensors-click>我是测试元素</li>
  (4)扩展至自定义属性,示例
    heatmap: {
      clickmap:'default',
      track_attr: ['prop1', 'prop2', "prop3"],
    }
    <p prop1>prop1</p>
    <p><span prop2>prop2</span></p>
    <p><strong prop3>prop3</strong></p>
  (5)代码埋点(这是局部埋点)
    示例1,jQuery
      <div id="submit_order">提交订单</div>
      <script type="text/javascript">
        $('#submit_order').on('click', function() { //代码埋点
          sensors.quick('trackHeatMap', this, { //触发元素点击事件
            customProp1: 'test1', //如果需要添加自定义属性需要将SDK升级到 1.13.7 及以上版本。
            customProp2: 'test2'
          });
        });
      </script>
    示例2,vue
      <div v-on:click="track">点击</div>
      <script>
        export default {
          methods: {
            track: function(event) {//代码埋点
              sensors.quick('trackHeatMap', event.target, {//触发元素点击事件
                customProp1: 'test1', //如果需要添加自定义属性需要将SDK升级到 1.13.7 及以上版本。
                customProp2: 'test2'
              });
            }
          }
        }
      </script>
4、高级功能
    附、高级功能清单
      A、属性插件化,
      B、批量发送,
      C、预置属性是否采集,
      D、关闭页面时发送数据丢失的解决方案,
      E、单页面中事件的自动采集
  (1)属性插件化,
    作用,给指定的事件添加、修改或删除属性,
    实现,registerPropertyPlugin,包含properties和isMatchedWithFilter
      如果不配置后者,配置的 properties 方法始终执行
      如果配置了后者,仅当该方法返回 true 时,配置的 properties 方法才会得到执行
    示例如下
      A、直接修改
        sensors.registerPropertyPlugin({ //对所有类型数据,修改属性值,这种用法会引发意想不到的情况
          properties: function(data){
            data.properties['aaa'] = 'bbb';
          }
        });
        sensors.registerPropertyPlugin({ //删除所有事件中的 platform 属性
          properties: function(data){
            delete data.properties['platform'];
          }
        });
        sensors.registerPropertyPlugin({ //直接在properties里进行筛选和属性修改
          properties: function(data){
            if(data.event === '$pageview'){
              data.properties['$url'] = 'http://xxxx';
            }
          }
        });
      B、先筛选后修改
        sensors.registerPropertyPlugin({ //修改事件名为 $pageview 下的 $url 属性
          isMatchedWithFilter: function(data){
            return data.event === "$pageview";
          }
          properties: function(data){
            data.properties['$url'] = 'http://xxx';
          }
        });
        sensors.registerPropertyPlugin({
          isMatchedWithFilter: function(data){
            return data.type.slice(0, 4) === 'item' ||  data.type.slice(0, 7) === 'profile';
          }
          properties: function(data){
            delete data.properties['aaa'] = 'bbb';
          }
        });
  (2)批量发送
    示例如下
      sensors.init({
        batch_send:true,//开启批量发送
        batch_send:{//或者
          datasend_timeout: 6000, //一次请求超过多少毫秒的话自动取消,防止请求无响应。
          send_interval: 6000, //间隔多少毫秒发一次数据。
          storage_length: 200 //存储localStorage条数最大值,默认:200。如localStorage条数超过该值,则使用image方式立即发送数据。v1.24.8 以上支持。
        },
      });
    写入策略
      触发事件就写入localStorage
    发送策略
      定时触发发送
      遇到$pageview、使用quick('autoTrack')、使用$SignUp,立即存储并且发送
    重复策略
      必须请求success后,才会删除数据,不然会一直请求,直到数据满一定数量
  (3)预置属性是否采集
    sensors.init({
      preset_properties: { //子配置项 true 表示采集,false 表示不采集,未设置的参数取默认值
        latest_utm: true, //是否采集 $latest_utm 最近一次广告系列相关参数
        latest_traffic_source_type: true, //是否采集 $latest_traffic_source_type 最近一次流量来源类型
        latest_search_keyword: true, //是否采集 $latest_search_keyword 最近一次搜索引擎关键字
        latest_referrer: true, //是否采集 $latest_referrer 最近一次前向地址
        latest_referrer_host: false, //是否采集 $latest_referrer_host 最近一次前向地址,默认值先true后false
        latest_landing_page: false, //是否采集 $latest_landing_page 最近一次落地页地址,默认值false。
        url: true, //是否采集 $url 页面地址作为公共属性,默认值先false后true
        title: true, //是否采集 $title 页面标题作为公共属性,默认值先false后true
      },
      source_channel:'',//默认获取的来源是根据utm_source等ga标准来的
      //utm_source,指定流量的来源,例如搜索引擎、社交媒体网站等
      //utm,是Urchin(小脏孩,免费的分析工具) Tracking Module 的简称
    });
  (4)关闭页面时发送数据丢失的解决方案
    A、改为服务端发送事件
      在服务端中埋点采集
    B、采集跳转后页面的$pageview事件
      给A页面的a标签的href属性添加某个参数,如B页面www.xxx.com?urlfrom=123,
      跳转到B页面后,采集这个页面的$pageview事件,在神策后台中查看Web浏览事件,根据url是否包含urlfrom参数来筛选结果
    C、使用setTimeout
      // 点击链接执行
      function targetLinkIcon( url ){
        setTimeout(function(){ //延迟跳转页面,给 SDK 发送数据提供时间
          window.location.href = url; 
        },500);
        sensors.track('demo',{}); //神策自定义事件的方法
      }
    D、beacon的方式发送数据
      配置
        sensors.init({
          send_type: 'beacon',
        });
      原理说明
        来源,https://blog.csdn.net/Ed7zgeE9X/article/details/131545832
        Beacon API是HTML5提供的新型浏览器API,可以在不影响当前页面加载和性能的情况下,在浏览器后台异步发送数据
        借助Beacon API,开发人员可以在页面卸载或关闭时向服务器发送数据,从而实现一些监控和日志记录功能 
        //以下前端代码
          var data = "Hello, Beacon API!";
          var url = "https://example.com/endpoint";
          navigator.sendBeacon(url, data); //导航员.发送信标
        //以下后端代码
          app.post('/endpoint', function(req, res) {
            var data = req.body;
            // 处理接收到的数据
            // ...
            res.sendStatus(200); // 返回响应状态码
          });
  (5)单页面中事件的自动采集
    A、自动模式
      sensors.init({
        is_track_single_page: true,
        is_track_single_page: function (){
          return true 时候,使用默认发送的 $pageview
          return false 时候,不执行默认的 $pageview
          return {} 时候,把对象中的属性,覆盖$pageview里的默认属性
        }
      });
    C、手动模式
      在 react 中可以在全局的 onUpdate 里来调用
        onUpdate: function(){
          sensors.quick('autoTrackSinglePage');
        }
      vue 项目在路由切换的时候调用
        router.afterEach((to,from) => {
          Vue.nextTick(() => {
            sensors.quick("autoTrackSinglePage");
          });
        });
        //注意:vue下因为首页打开时候就会默认触发页面更新,所以需要去掉默认加的 sa.quick('autoTrack')。
        //此方法也可添加自定义属性,sensors.quick("autoTrackSinglePage",{platForm:"H5"});
5、安全合规
  (1)安全,
    本地存储加密,不支持埋点数据加密
      sensors.init({
        encrypt_cookie: true //开启cookie加密配置,默认false
      });
  (2)合规,
    延迟初始化
      if(同意隐私条款){
        sensors.init({});
        sensors.quick('autoTrack'); 
      }
    动态开启/关闭采集,必须在init后调用
      sensors.init({});
      sensors.disableSDK()// 禁用API执行
      sensors.enableSDK()// 恢复API执行
6、用户关联
  (1)简易用户关联,
    方法:sensors.login("登录 ID");
    时机:
      A、用户注册成功时
      B、用户登录成功时
      C、已登录用户每次启动App时
    获取匿名ID
      sensors.quick('isReady',function(){
        var anonymousID = sensors.quick('getAnonymousID');
      });
    修改匿名ID
      sensors.identify(id, true): 会把这个id保存在浏览器的cookie中,该域名下的页面都会默认使用这个id
  (2)全域用户关联
    方法
      sensors.login("登录 ID");
    时机
      A、用户注册成功时
      B、用户登录成功时
    多用户ID关联,
      sensors.bind("$identity_mobile","187****8991"),后续采集的事件,均包含缓存的ID信息
      第1个参数从详细的预置id key列表中获取,https://manual.sensorsdata.cn/sa/latest/zh_cn/tech_sdk_client_web_idm3-109576372.html
      第2个参数为对应的关联用户ID
    多用户ID取消关联,
      sensors.unbind("$identity_mobile","187****8991")
    重置匿名ID,
      sensors.resetAnonymousIdentity();
      sensors.resetAnonymousIdentity('id-xxxxxxx-xxxxx'); // 修改为指定的匿名ID
    获取全域用户的ID,
      sensors.getPresetProperties()
      sensors.quick('isReady',function(){
        sensors.getPresetProperties()
      });
7、插件集成
  (1)使用内置插件,
    内置插件
      Amp、SensorsChannel、Deeplink、PageLeave、PageLoad、RegisterPropertyPageHeight、SiteLinke
    使用
      sensors.use('PageLeave', option);
      sensors.init({
        ...初始化参数
      })
  (2)使用外置插件,
    外置插件
      AesEncryption、Exposure、SessionEvent、SiteLinkerConcatUtm、WechatWebViewChannel、SensorsABTest、Popup(H5版)WebPopup(Web版)、GeneralEncryption、CustomEventsSender、SfInstantEvent、ChannelUtm、SMEncryption
    使用
      import sessionEvent from '/dist/web/plugin/session-event/index.es6.js';
      sensors.use(sessionEvent);
      sensors.init({
        ...初始化参数
      })
8、多域名打通
  (1)示例
    sensors.init({});
    sensors.use('SiteLinker',
      {
        linker: [ // 在神策分析环境中实现用户统一,从而实现跨域打通
          { part_url: 'sensorsdata.cn', after_hash: false },
          { part_url: 'example.com', after_hash: false }
        ],
        re_login: true, //如果从已登录的页面跳转过来,即使当前网页已经登录,当前网页仍然会以之前网页的登录id再次登录
      }
    );
    sensors.quick('autoTrack');
  (2)打通结果
  (3)原理说明
    A、浏览器中的Cookie包含用户id和域名
    B、多域名间共享用户id
    C、只有a标签跳转可以实现跨域打通
  (4)实施方案
    A、linker参数的part_url属性配置‘需要打通的网域’
    B、神策检查本网域中的a标签的链接,如果链接包含part_url,将从Cookie中提取本网域下的Distinct ID拼接到链接上
    C、跳转到目标网域后,目标网域截取网址中的链接器_sasdk参数,用其替换自身的Distinct ID
    D、反过来也一样
9、渠道追踪与广告(以下数据自动生成)
  注意,从搜索引擎的搜索结果向目标网页跳转时,如果搜索引擎不携带相关参数,会导致下列数据的部分字段为空
  (1)用户相关属性
  (2)事件相关属性
  (3)流量来源类型
  (4)搜索引擎关键词
  (5)场景示例
  (6)常见问题
10、曝光采集,
  包含名称、配置、属性
  (1)初始化与注销
    初始化
      import Exposure from '/dist/web/plugin/exposure/index.es6';
      var exposure = sensors.use(Exposure, {
        area_rate: 0,
        stay_duration: 2,
        repeated: true
      });
    注销
      exposure.removeExposureView(document.getElementById('exposure_ele'))
  (2)注册曝光元素
    方案1
      <div
        data-sensors-exposure-event-name="home_top_banner"
        data-sensors-exposure-config-area_rate="1" 
        data-sensors-exposure-config-stay_duration="2"
        data-sensors-exposure-config-repeated="true"
        data-sensors-exposure-property-propA="valueA"
        data-sensors-exposure-property-propB="valueB"
      ></div>
    方案2
      <div id="exposure_div"></div>
      var exposureDiv = document.getElementById("exposure_div");
      exposureDiv.setAttribute("data-sensors-exposure-event-name", "exposure_ele"); //1、名称
      exposureDiv.setAttribute( //设置曝光元素配置及自定义属性
        "data-sensors-exposure-option",
        JSON.stringify({
          config: { //2、配置
            area_rate: 0.5,
            stay_duration: 0,
            repeated: true
          },
          properties: { //3、属性
            d: "abc",
            e: false
          },
          listener: { //4、曝光的回调
            shouldExpose: function (ele, properties) { //是否触发曝光事件
              // ele 为当前曝光的元素
              // properties 为曝光元素的元素信息及该元素的曝光自定义属性
              // 触发曝光事件则返回 true
              // 不触发曝光事件则返回 false
              return true;
            },
            didExpose: function (ele, properties) { //已触发曝光回调
              // ele 为当前曝光的元素
              // properties 为曝光元素的元素信息及该元素的曝光自定义属性
            }
          }
        })
      );
11、谷歌AMP
  (1)说明,AMP提供了可分析用户行为数据的<amp-analytics>元素,通过该元素实现对神策埋点数据的采集
  (2)集成,在head中引入拓展组件<amp-analytics>的脚本
    <script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
  (3)采集,在body中引入<amp-analytics>标签,标签type="sensorsanalytics",通过编辑标签内部的json配置,来进行行为数据采集
    <amp-analytics type="sensorsanalytics" id="sensorsanalytics1">
      <script type="application/json">
        "requests": {
          "event": "https://jssdkdata.debugbox.sensorsdata.cn/sa?project=liangshuang" //数据接收地址
        },
        "triggers": { //下面的key相当于上面的$WebClick、$WebStay、$pageview
          "trackPageview": { //页面浏览事件采集
            "on": "visible",
            "request": "event",
            "vars":{
              "event":"amp_pageview", //自定义事件名称
              "amp_properties":"%7B%22platform%22%3A%22amp%22%2C%22color%22%3A%22red%22%7D" //自定义属性
            }
          },
          "id_test1": { //自定义事件采集
            "on": "click",
            "selector": "#test1", //点击事件需要监听的元素选择器
            "request": "event",
            "vars":{
              "event":"click_test1", //自定义事件名称
              "amp_properties":"%7B%22platform%22%3A%22amp%22%2C%22color%22%3A%22red%22%7D" //自定义属性
            }
          },
          "class_test2": { //自定义事件采集
            "on": "click",
            "selector": ".test2", //点击事件需要监听的元素选择器
            "request": "event",
            "vars":{
              "event":"click_test2", //自定义事件名称
              "amp_properties":"%7B%22platform%22%3A%22amp%22%2C%22color%22%3A%22red%22%7D" //自定义属性
            }
          }
        }
      </script>
    </amp-analytics>
  (4)amp_properties值的生成步骤
    var prop = {
      platform:'amp',
      color:'red'
    }
    var amp_properties = encodeURIComponent(JSON.stringify(prop))
  (5)AMP网页的三种访问方式
    AMP查看器访问
    代理/缓存访问
    普通浏览器直接访问
  (6)AMP页面和非AMP页面的用户统一
    sensors.init({});
    sensors.use('Amp'); //引入AMP插件
    sensors.quick('autoTrack');
  (7)检测AMP集成是否成功
    触发一条埋点事件,查看network是否有sa.gif的请求发出
12、深度链接deeplink
  (1)需求,客户希望通过H5将用户引流至移动 App,借助于深度链接可以提高活动推广的效果
13、常见问题FAQ
  附、常见问题解答(frequently-asked questions,简称FAQ)是使新用户熟悉规则的一种方法
  (1)单页面的页面标题$title问题
    router.beforeEach((to, from, next) => {
      document.title = '新页面的 title 值';
      next()
    })
  (2)异步集成时,使用“内置(但)未使用插件”
    sensors.quick('isReady',function(){
      sensors.use('PageLeave')
      sensors.quick('autoTrack')
    })
附、采集数据的含义
  注释参考1;https://manual.sensorsdata.cn/sa/latest/zh_cn/%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F-149553285.html
  注释参考2;https://manual.sensorsdata.cn/sa/latest/zh_cn/id-key-150668129.html
  注释参考3;https://manual.sensorsdata.cn/sa/latest/zh_cn/%E9%A2%84%E7%BD%AE%E5%B1%9E%E6%80%A7%E6%80%BB%E8%A1%A8%E6%A0%BC-152305885.html
  注释参考4;https://manual.sensorsdata.cn/sa/latest/zh_cn/kafka-154632771.html
  [web-sdk-log]: {
      "identities": {
          //identities:全域用户关联业务中用户标识字段,可包含多个用户标识,具体可以参考全域用户关联,
          //https://manual.sensorsdata.cn/sa/latest/zh_cn/%E6%A0%87%E8%AF%86%E7%94%A8%E6%88%B7%E2%80%94%E2%80%94%E5%85%A8%E5%9F%9F%E7%94%A8%E6%88%B7%E5%85%B3%E8%81%94-150667578.html
          "$identity_cookie_id": "18dcb37ea531b29-088c1d7f7926fa8-1e525637-2104200-18dcb37ea54bd9"//Web cookie ID
      },
      "distinct_id": "18dcb37ea531b29-088c1d7f7926fa8-1e525637-2104200-18dcb37ea54bd9",//类型是字符串,对用户的标识,对未登录用户,可以填充设备标识、CookieID 等,对于登录用户,则应该填充注册账号;这里的例子,假设是一个匿名用户,所以填充的是一个设备 ID;
      "lib": {//SDK
          "$lib": "js",//SDK 类型
          "$lib_method": "code",//埋点方式
          "$lib_version": "1.26.5"//SDK 版本
      },
      "properties": {//属性
          "$timezone_offset": -480,//时区偏移量
          "$screen_height": 1169,//屏幕高度 
          "$screen_width": 1800,//屏幕宽度
          "$viewport_height": 363,//视区高度
          "$viewport_width": 1752,//视区宽度
          "$lib": "js",//SDK 类型
          "$lib_version": "1.26.5",//SDK 版本
          "$referrer": "http://test.cctv.com/",//前向地址,上一页面的$url信息
          "$url": "http://test.cctv.com/#/home",//页面地址
          "$url_path": "/",//页面路径
          "$title": "智策",//页面标题
          "$latest_referrer": "取值异常",//最近一次站外地址
          "$latest_search_keyword": "取值异常",//最近一次搜索引擎关键词
          "$latest_traffic_source_type": "取值异常",//最近一次流量来源类型
          "$is_first_day": false,//是否首日访问
          "$is_first_time": false,//是否首次触发事件
          "$referrer_host": "test.cctv.com",//前向域名
          "tmzone": 8,//时区
          "b": "Chrome",//浏览器名称
          "o": "MacIntel",//操作系统名称
          "lng": "zh-CN",//操作系统语言
          "ism": "不是移动端",//移动系统
          "param": "#/home",//页面URL #后部分
          "logtype": 1,//1-访问日志,2-交互日志
          "single_page": true,//是否单页面应用
          "show_log": true,//控制台是否显示日志
          "v_id": "121212"//(自定义字段)
      },
      "anonymous_id": "18dcb37ea531b29-088c1d7f7926fa8-1e525637-2104200-18dcb37ea54bd9",//login_id、anonymous_id:类型是字符串,对用户的标识,对未登录用户,只有 anonymous_id,而无 login_id 信息;
      "type": "track",//表示一条数据的具体操作(小结部分会详细介绍),track 表明是记录一个事件 
      "event": "$pageview",//事件名,需是合法的变量名,即不能以数字开头,且只包含:大小写字母、数字、下划线和 $,其中以 $ 开头的表明是系统的预置事件,自定义事件名请不要以 $ 开头,且 event 字段长度最大为 100;//事件($WebClick、$WebStay、$pageview)
      "time": 1709626986084,//类型是数值,事件发生的实际时间戳,精确到毫秒;
      "_track_id": 319126084,//前端 SDK track 的时候上报的随机值,用于去重判断,并不会写入 events 表
      "_flush_time": 1709626986084//发送数据时的时间
  }
  
十七、神策源码解读
  注意,先做项目(了解源码的功能),再看源码(了解功能的实现)
  注意,神策数据分为神策采集和神策分析,sd.use、sd.init、页面事件,三者一起添加监听事件
  来源,https://juejin.cn/post/6974267640797724679
1、客户自有数据有三类
  (1)前端操作,通过插件sensorsdata.full.js,采集、发送数据,该插件适用于PC、安卓、苹果、小程序等一切前端
  (2)后端日志
  (3)业务数据
2、插件采集数据的方式
  (1)代码埋点
  (2)全埋点
  (3)可视化全埋点
  (4)预置属性
3、在项目中使用
  (1)在项目中引入采集插件,即1万多行的sensorsdata.full.js
  (2)在项目中插入采集代码,把采集数据发送给神策数据服务器
  (3)用神策数据的账号、密码登录神策数据网站,查看采集数据
4、存储方式
  (1)cookie
  (2)localStorage
  (3)sessionStorage
5、发送方式
  (1)ajax,通过ajax异步POST请求将数据发送出去
  (2)image,图片发送也是默认的发送方式,创建一个img标签,将数据放在请求的URL中,以问号传参的方式发送
  (3)sendBeacon,浏览器会取消unload事件里的卸载、刷新、跳转逻辑,发出请求可以通过navigator.sendBeacon(导航员.发送信标)或同步ajax
6、全局变量
  var sd = {}; //1,sensors-data
  var ee = {}; //6711,event-emit
  var sdk = new EventEmitter(); //6710,sensors-data-kit
  var _ = extend({}, W, business); //7545
7、ee,与事件相关,
  //以下在全局添加属性
  ee.spa = spa;
  ee.initSystemEvent
  ee.EVENT_LIST
  ee.sdk = sdk; //6715
  ee.sdk.on,监听事件
  ee.sdk.emit,触发事件
8、sd,与框架相关
  //以下在implementCore里添加属性,
  sd._ = _;
  sd.ee = ee;
  sd.on = eventEmitterFacade;//调用时,最终调用ee.sdk.on
  sd.use = use;
  sd.readyState = readyState;
  //以下在use里添加属性
  sd.modules = sd.modules || {}; //存放插件
  //以下暴露自身,并执行sd.init,同时注意区分变量和字符串
  window[sensorsDataAnalytic201505] = sd;
  window['sensorsDataAnalytic201505'] = sd;//11415
  sd.init();
9、真实项目中的配置,默认自动开启日志输出功能方便调试,
  来源,https://manual.sensorsdata.cn/sa/latest/tech_sdk_client_web_policy-109576387.html
  <script>
    window.sensors_data_pre_config = {
      is_compliance_enabled: true //是否同意启用
    }
  </script>
  <script charset='UTF-8' src="./sensorsdata.min.js"></script> //sd.init,在插件同步加载结束时,不传参执行1次
  <script>
    if(isClientAgree){//同意隐私条款
      sensors.init({ //sd.init,启用插件时,传参执行1次
        server_url: 'http://test-syg.datasink.sensorsdata.cn/sa?token=xxxxx&project=xxxxxx',
        is_track_single_page: true, //单页面配置,默认开启,若页面中有锚点设计,需要将该配置删除,否则触发锚点会多触发$pageview事件
        use_client_time: true,
        send_type: 'beacon',
        heatmap: {
          //是否开启点击图,'default'表示开启,自动采集 $WebClick 事件,可以设置'not_collect'表示关闭
          clickmap: 'default',
          //是否开启触达图,'not_collect'表示关闭,不会自动采集 $WebStay 事件,可以设置'default'表示开启
          scroll_notice_map: 'not_collect'
        } 
      });
      sensors.quick('autoTrack'); 
    }
  </script>
10、页面监听与发射
   附、sd.init = function(para) {
      ee.initSystemEvent();
      sd.detectMode();
    };
  (1)监听
    A、sd.init
      a、sd.detectMode();
    B、function detectMode() {//检测模式
      a、trackMode();
    C、function trackMode() {//跟踪模式
      a、listenSinglePage();
    D、function listenSinglePage(trackFn) {//监听单个页面
        if (sd.para.is_track_single_page) {
          spa.on('switch', function(last_url) {//监听(后出现,立即执行)
            var sendData = function(extraData) {
              extraData = extraData || {};
              if (last_url !== location.href) {//浏览器前进或后退(用户触发)
                pageInfo.pageProp.referrer = getURL(last_url);
                var data = extend({
                  $url: getURL(),
                  $referrer: getURL(last_url)
                }, extraData);
                isFunction(trackFn) ? trackFn(data) : sd.quick && sd.quick('autoTrack', data);//执行quick
              }
            };
            if (typeof sd.para.is_track_single_page === 'boolean') {
              sendData();
            } else if (typeof sd.para.is_track_single_page === 'function') {
              var returnValue = sd.para.is_track_single_page();
              if (isObject(returnValue)) {
                sendData(returnValue);
              } else if (returnValue === true) {
                sendData();
              }
            }
          });
        }
      }
    E、function quick() {
      a、commonWays[arg0].apply(commonWays, arg1);//执行autoTrack
    F、autoTrack: function(para, callback) {//自动跟踪
      a、sd.track(
    G、function track(
      a、saEvent.send({
    H、send: function() {
    I、request: function(data, dataKeys) {
    J、function ajax$1(para) {
    K、function ajax(para) {
  (2)发射
    A、sd.init
      a、ee.initSystemEvent();
    B、ee.initSystemEvent = function() {
        addSinglePageEvent(function(url) {
          spa.emit('switch', url);//发射(先出现,不立即执行)
        });
      };
    C、function addSinglePageEvent(callback) {
        var current_url = location.href;
        //以下重定义方法,两种操作时执行,执行发射逻辑
        var historyPushState = window.history.pushState;
        var historyReplaceState = window.history.replaceState;  
        if (isFunction(window.history.pushState)) {
          window.history.pushState = function() {//浏览器前进或后退(用户触发)
            historyPushState.apply(window.history, arguments);
            callback(current_url);//执行spa.emit('switch',但不出现
            current_url = location.href;
          };
        }
        if (isFunction(window.history.replaceState)) {
          window.history.replaceState = function() {
            historyReplaceState.apply(window.history, arguments);
            callback(current_url);
            current_url = location.href;
          };
        } 
        //以下添加事件,五种操作时执行,执行发射逻辑
        var singlePageEvent;
        if (window.document.documentMode) {//IE浏览器
          singlePageEvent = 'hashchange';
        } else {//非IE浏览器
          singlePageEvent = historyPushState ? 'popstate' : 'hashchange'; // html5 : html5以下
        }
        addEvent(window, singlePageEvent, function() {
          callback(current_url);//执行spa.emit('switch',但不出现
          current_url = location.href;
        });
        // (6)两种操作的作用,给“浏览器当前页签”添加(或修改)状态,以下是两种操作,
        //   附、它们是HTML5的一个API
        //   A、pushState
        //   B、replaceState
        // (7)五种操作的作用,给“浏览器当前页签”的“不同历史记录”激活,以下是五种操作
        //   A、浏览器的后退(向左)
        //   B、浏览器的前进(向右)
        //   C、history.back(向左)
        //   D、history.forward(向右)
        //   E、history.go(负左,正右,0当前)
        // (8)两种操作(手动)、五种操作(手动)、onpopstate(自动),三者之间的关系
        //   A、两种操作,只能改变当前页面状态,不会触发window.onpopstate(event)事件
        //   B、五种操作,与两种操作相关与否,都会触发window.onpopstate(event)事件
        //   C、五种操作,与两种操作无关时,还会触发http请求
      }
    D、function addEvent(target, eventName, eventHandler, useCapture) {
        var register_event = function(element, type, handler) {
          if (useCapture === undefined && type === 'click') {
            useCapture = true;
          }
          if (element && element.addEventListener) {
            element.addEventListener(//被五种操作中的一种操作触发
              type,//popstate
              function(e) {
                e._getPath = fixEvent._getPath;
                handler.call(this, e);
              },
              useCapture
            );
          } else {
            var ontype = 'on' + type;
            var old_handler = element[ontype];
            element[ontype] = makeHandler(element, handler, old_handler, type);
          }
        };
        register_event.apply(null, arguments);
      }    
  (3)ajax的使用
    附、定义,function ajax(para) {
    A、function ajax$1(para) {
        return ajax(para);
      }
      a、AjaxSend.prototype.start = function() {
      b、request: function(data, dataKeys) {
      c、var business = {
    B、var SADeepLink = {
        init: function init(sd) {
          this.sd._.ajax({
        },
      };
      a、var index$5 = createPlugin$5(SADeepLink, 'Deeplink', 'sdkReady');
    C、function debugPath(data) {
        _$6.ajax({});
      }
      a、debugPath(JSON.stringify(data));
  附、hashchange,该事件在当前URL的锚部分发生修改时触发。它在本插件的应用可能如下
   a、监听hashchange事件
   b、当url上的hash改变时,触发hashchange事件,向后台发送相关数据    
11、源码摘录
  (1)extend,initPara--extend--each,外部数组遍历用forEach,内部对象遍历用for in,后面属性覆盖前面属性
    var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
    var hasOwnProperty$2 = Object.prototype.hasOwnProperty;
    function initPara(para) {
      extend(sdPara, para || sd.para || {});
      sd.para = sdPara;
    }
    function extend(obj) {
      each(Array.prototype.slice.call(arguments, 1), function(source) {
        for (var prop in source) {
          if (hasOwnProperty$1.call(source, prop) && source[prop] !== void 0) {
            obj[prop] = source[prop];
          }
        }
      });
      return obj;
    }
    function each(obj, iterator, context) {
      if (obj == null) {
        return false;
      }
      if (nativeForEach && obj.forEach === nativeForEach) {
        obj.forEach(iterator, context);//在函数的定义处执行
      } else if (isArray(obj)) {
        for (var i = 0, l = obj.length; i < l; i++) {
          i in obj && iterator.call(context, obj[i], i, obj); // iterator定义里只有一个参数,这里传进去三个参数,在不改变iterator定义的情况下,第二、三个参数是无用的
        }
      } else {
        for (var key in obj) {
          if (hasOwnProperty$2.call(obj, key)) {
            iterator.call(context, obj[key], key, obj);
          }
        }
      }
    }
  (2)与use相关
    //溯源插件,index$d-createPlugin$d-wrapPluginInitFn$d(重写init)
    //执行插件,use(Plugin)->Plugin.init()-sd.on->eventEmitterFacade->ee[splitEvent[0]].on(splitEvent[1],callback)->ee.EVENT_LIST
    //注册拦截器,registerInterceptor
    //用2个for循环对2组插件进行遍历,并将每个插件放进sd.modules中
    var builtinPlugins = [index$1, index$2, index$3, index$4, index$5, index$6, index$7, index$8, index$9, index$a, 
                 index$b, index$c, index$d, index$e, index$f, index$g, index$h, index$i, index$j, index$k]; // 共20项
    var autoUsePlugins = [index, index$d, index$e, index$g, index$f, index$2, index$6, index$3, index$7, index$h, 
                 index$i, index$j, index$k]; // 共13项
    for (var i = 0; i < builtinPlugins.length; i++) {
      var p = builtinPlugins[i];
      if (sd._.isString(p.plugin_name)) {
        sd.modules[p.plugin_name] = p; //不需要执行plugin.init,把插件存入sd.modules
      } else {
        sd._.isArray(p.plugin_name) &&
          sd._.each(p.plugin_name, function(v) {
            sd.modules[v] = p;
          });
      }
    }
    for (i = 0; i < autoUsePlugins.length; i++) { 
      sd.use(autoUsePlugins[i]); //需要执行plugin.init,把插件存入sd.modules
    }
    function use(plugin, option) {
      function initPlugin() {
        !curPlugin.plugin_is_init && curPlugin.init(sd, option); 
        curPlugin.plugin_is_init = true;
        sd.modules = sd.modules || {};
        sd.modules[curPlugin.plugin_name || 'unnamed_' + nonameCount++] = curPlugin; //把插件存入sd.modules
        return curPlugin;
      }
      return initPlugin();
    }
    var index$d = createPlugin$d(utm, 'Utm', 'sdkAfterInitPara'); //utm是plugin的实参
    function createPlugin$d(plugin, name, lifeCycle) { //添加版本
      wrapPluginInitFn$d(plugin, name, lifeCycle);
      plugin.plugin_version = sdkversion_placeholder$e;
      return plugin;
    }
    function wrapPluginInitFn$d(plugin, name, lifeCycle) { //重写init
      if (name) {
        plugin.plugin_name = name;
      }
      if (lifeCycle && plugin.init) {
        var initFn = plugin.init; //存储旧的
        plugin.init = function(sd, option) { //赋值新的
          if ((sd.readyState && sd.readyState.state >= 3) || !sd.on) {
            return initPlugin();
          }
          sd.on(lifeCycle, initPlugin);
          function initPlugin() {
            initFn.call(plugin, sd, option);
          }
        };
      }
      return plugin;
    }
  (3)与sd.init相关
    sd.init = function(para) { //8669
      ee.sdk.emit('beforeInit');
      if (sd.readyState && sd.readyState.state && sd.readyState.state >= 2) {
        return false;
      }
      if (is_compliance_enabled) { //是否同意启用
        implementCore(true); //给sd添加属性
        checkState(); //给sd的方法重新定义
      }
      ee.initSystemEvent(); //初始化系统事件
      sd.setInitVar(); //设置初始化变量
      sd.readyState.setState(2); //设置全局状态
      sd.initPara(para); //约130行,暂未了解
      ee.sdk.emit('initPara');
      ee.sdk.emit('afterInitPara');
      ee.sdk.emit('initAPI');
      ee.sdk.emit('afterInitAPI');
      sd.detectMode(); //检测模式,暂未了解
      iOSWebClickPolyfill(); //IOS系统兼容处理-可能
      ee.sdk.emit('afterInit');
      ee.sdk.emit('ready');
    }; 
    function implementCore(isRealImp) {
      if (isRealImp) {
        sd.events = events;
        sd.bridge = bridge;
        sd.SDKJSBridge = SDKJSBridge;
        sd.JSBridge = DeprecatedJSBridge;
        sd.store = store;
        sd.unlimitedDiv = unlimitedDiv;
        sd.customProp = customProp;
        sd.vtrackcollect = vtrackcollect;
        sd.vapph5collect = vapph5collect;
        sd.detectMode = detectMode;
        sd.registerFeature = registerFeature;
        sd.registerInterceptor = registerInterceptor;
        sd.commonWays = commonWays;
        registerFeature(new CoreFeature(sd));
        registerFeature(new HeatCollectFeature(sd));
        registerInterceptor('viewStage', heatCollectInterceptor);
      }
      var imp = isRealImp ? functions : saEmpty;
      for (var f in imp) {
        sd[f] = imp[f];
      }
      sd._ = _;
      sd.on = eventEmitterFacade;
      sd.ee = ee;
      sd.use = use;
      sd.lib_version = sdkversion_placeholder;
    }
    function checkState() {
      each(methods, function(method) {
        var oldFunc = sd[method];
        sd[method] = function() {
          if (sd.readyState.state < 3) {
            if (!isArray(sd._q)) {
              sd._q = [];
            }
            sd._q.push([method, arguments]);
            return false;
          }
          return oldFunc.apply(sd, arguments);
        };
      });
    } 
    ee.initSystemEvent = function() {
      addSinglePageEvent(function(url) {
        spa.emit('switch', url);
      });
    };
    
十八、aplus.js
附、meta标签
  <html>
    <head>
      <meta charset="UTF-8">
      <meta name="description" content="免费的 Web 教程">
      <meta name="keywords" content="HTML,CSS,JavaScript">
      <meta name="author" content="YK Investment">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta name="my-name" content="name的等号后面是属性值,由开发者确定">
      <meta name="your-name" content="特定的属性值如keywords、viewport">
      <meta name="your-name" content="会被网络爬虫或浏览器识别!">
    </head>
  </html>
  <script>
    //name的等号后面是属性值,由开发者确定,特定的属性值如keywords、viewport,会被网络爬虫或浏览器识别!
    //js获取meta某name的content
    //方法1
    var metas = document.getElementsByTagName('meta');
    for (var i = 0; i < metas.length; i++) {
      if (metas[i].getAttribute('name') === 'my-name') {
        console.log(metas[i].getAttribute('content'));
      }
    }
    //方法2
    var names = document.getElementsByName('your-name');
    for (var i = 0; i < names.length; i++) {
      console.log(names[i].getAttribute('content'));
    }
  </script>
附、通用术语
  (1)PV,页面浏览量或点击量,page view
  (2)UV,独立访客数,unique visitor
  (3)QPS,每秒查询率,Query Per Second
  (4)TPS,每秒吞吐量,Throughput Per Second,系统在单位时间内处理请求的数量
  (5)RT,响应时间,Response Time
1、aplus.js说明
  (1)源码地址,https://js.data.cctv.com/__aplus_plugin_cctv.js,aplus_plugin_aplus_u.js
  (2)使用方法,https://help.aliyun.com/product/194063.html,费了好大劲才找到
  (3)二次封装,__aplus_plugin_cctv.js和aplus_plugin_aplus_u.js,是领导给我的
  (4)相关域名
    A、收数,https://log-api.aplus.emas-poc.com/,aplus.js的服务器
    B、友盟,https://developer.umeng.com/docs/67963/detail/74526
    C、分享,https://blog.naaln.com/2017/08/alibaba-data-track-1/
    D、术语,https://zhuanlan.zhihu.com/p/650155427
2、aplus.js术语
  来源,https://blog.csdn.net/qq_38397338/article/details/125246947
  (1)SPM,全称超级位置模型,Super Position Model,记录和描述用户的具体点击位置信息,如外站类型、应用id、频道id、页面id
    A、spm-cnt,当前页面的SPM编码(spm_id)
    B、spm-url,当前页面的来源位置的SPM编码(url_spm_id)
    C、spm-pre,来源页面的来源位置的SPM编码(pre_spm_id)
    D、由a.b.c.d四段构成,各分段分别代表 
      a:站点/业务
      b:页面
      c:页面区块
      d:区块内点
      其中a,b位是必须具备的,且均需在SPM申请中心,如a1.b1.c1.d1
  (2)SCM,全称超级内容模型,Super Content Model
  (3)Quick Tracking,快速追踪(全域采集与增长分析),阿里云推出的(基于aplus.js的)企业级流量统计分析产品
  (4)黄金令箭,用户按照约定的格式向日志服务器发送请求
    A、系统自动采集的页面浏览(PV, Page View)日志
    B、自定义日志(时间内容自定义),通过调用黄金令箭日志接口主动上报日志
  (5)aplus-auto-exp,曝光时,aplus自动发送黄金令箭
  (6)aplus-auto-clk,点击时,aplus自动发送黄金令箭
  (7)自定义属性,data-spm、data-spmA、data-spmB
  (8)自动点击,spmC、spmD
3、aplus.js采集内容包括
  (1)ID,cookieID、淘宝会员数字id(不一定需要登录)、nickname(若已登录)
  (2)URL,当前页URL、来源页URL
  (3)SPM,当前页SPM、来源页点击位置SPM、来源页的来源页点击位置SPM编码(前提为,已部署了SPM)
  (4)信息,标题、浏览器名称版本、屏幕分辨率、aplus版本
  (5)反作弊验证码
   附、交互行为使用「黄金令箭」统计
4、aplus.js自动埋点,当某个元素出现在可视区域,或者点击某个元素的时候,aplus会自动发送黄金令箭
  来源,https://blog.csdn.net/qq_38397338/article/details/125246947
  (1)html埋点,
    A、标识站点ID,<meta name="data-spm" content="申请的A位编码">
    B、标识页面ID,<body data-spm="注册的B位编码"></body>
    C、标识容器ID,<div data-spm="自定义或注册的C位编码"></div>
    D、标识链接ID,<a data-spm="d开头的自定义编码串" href=""></a>
    E、html埋点,<meta name="aplus-auto-exp" 
      content='[{"logkey":"/abc1","tag":"div","filter":"data-name","props":["name","age","address"]}]'>
      当带有data-name属性的div标签被曝光时,自动采集这个标签的曝光信息,及当前元素的"name",“age”,"address"这几个属性
  (2)js埋点
    A、示例
      handleClick(clkType) {
        var q = (window.goldlog_queue || (window.goldlog_queue = []));
        q.push({
          action: 'aplus.record', //发送日志请求。放在这里调用,是为了避免aplus未完成初始化
          arguments: [
            '/aplus.99.3',//(埋点方案中的事件编码)
            'CLK', 
            'clickType='+clkType, 
            'POST'
          ]
        });
        q.push({
          action: 'aplus.setMetaInfo', //识别需要曝光的元素
          arguments: ['aplus-auto-exp', 
          [
            { //aplus自动曝光,发送黄金令箭
              cssSelector: '.auto-exp-component', //需要曝光的元素class
              positionSelector: '.parent',//如果页面模块是元素内滚动(某个区块内有滚动条)则需要增加positionSelector辅助定位曝光元素
              logkey: 'test_event_id', //事件管理中的事件id
              props: ['data-itemid'], //你要曝光的元素身上自定义属性
            },
            ...
          ], ],
        });
        q.push({
          action: 'aplus.setMetaInfo',
          arguments: ['aplus-auto-exp', 
            JSON.stringify([
              {
                "logkey": "/abc1",
                "tag": "div",
                "filter": "data-name",
                "props": ["name", "age", "address"]
              }
            ])
          ]
        });
        q.push({
          'action':'aplus.sendPV',
          'arguments':[{
            is_auto: false
          }, {
            page_title: "首页", //默认为pageConfig中的值,如果这里设置了,则为这里设置的值 (非必传)
            page_name: "yourCurrentPageName", //默认为pageConfig中的值,如果这里设置了,则为这里设置的值 (非必传)
            //如果您设置了duration参数(单位须为毫秒),QuickTracking会做为分析时的「事件属性-时长(s)」处理
            duration: 1111111,
            //自定义事件属性
            x: 111,
            y: 222
          }]
        });
      }
      <button onClick={this.handleClick('one')}>HJLJ ONE</button>
    B、参数说明,goldlog.record(logkey, gmkey, gokey, req_method)
      logkey{String},即完整的令箭编码,如上例中的 “/a1.jingyao.clicktest”
      gmkey{String},关键业务类型,目前的约定的元值有五个,
        点击类操作"CLK"、
        曝光类事件"EXP"、
        滑屏类事件"SLD"、
        其它事件"OTHER"(特指除点击和曝光事件外的其他自定义事件)、
        也可以为空值,但不建议留空
      gokey{String},附加的自定义kv对,如本例中clicktype=one
      req_method{String},可选值有’GET’(默认)、‘POST’
        如果入参为POST,则令箭请求会优先navigator.sendBeacon post的形式发出,可以保证页面跳转的时候不会断
        如果当前浏览器不支持sendBeacon则降级为get img的形式发出
5、Quick Tracking的基本概念,全域采集与增长分析-产品概述
  来源,https://help.aliyun.com/document_detail/250932.html
  (1)行为采集
    A、系统事件(APP有,小程序有,Web无),应用启动($$_app_start)、应用退出($$_app_end)、分享($$_share)
    B、页面事件,采集页面浏览行为的事件,包含页面事件标识码和页面编码
    C、自定义事件,
    D、属性,包含全局属性和事件属性
  (2)用户标识,包含设备和用户
6、Web可视化分析使用文档-集成,全域采集与增长分析-操作指南-采集管理-可视化埋点
  附、相关概念
   A、可视化分析,利用图形、图像、动画等直观方式来展现数据的分析方法
   B、可视化埋点,用于分析用户在应用程序或网站上的行为和交互的技术
  来源,https://help.aliyun.com/document_detail/281212.html
  (1)第一步:确认SDK支持投屏
    注、支持投屏,意味着一个设备能够将它的画面复制并显示在另一个设备上
    A、用户将发射端SDK集成到自己的APP或软件应用中
    B、配套投屏接收端设备
  (2)第二步:确认SDK已经集成
    A、首先,将以下脚本放在head标签内
      <script>
        (function(w, d, s, q) {
          w[q] = w[q] || [];
          var f = d.getElementsByTagName(s)[0],j = d.createElement(s);
          j.async = true;
          j.id = 'beacon-aplus';
          j.src = '<sdk地址>';
          f.parentNode.insertBefore(j, f);
        })(window, document, 'script', 'aplus_queue');
      </script>
    B、其次,配置必要的meta
      <html>
        <head>
          <meta name="appKey" content="<QuickA+申请应用时颁发的appKey>">
          <meta name="aplus-rhost-v" content="<日志收数域名>">
          <meta name="aplus-vt-cfg-url" content="<已发布的配置地址>">
        </head>
      </html>
    C、说明
      appKey:QuickA+申请应用时颁发的appKey
      aplus-rhost-v:日志收数域名
      aplus-vt-cfg-url:已发布的配置地址
  (3)第三步:配置SPM
    注、必须使用自动点击、自动曝光上报数据
    A、配置页面级别spm(spmB)
      第一种:通过给body标签添加自定义属性data-pagename
        <body data-pagename=${yourPageName}></body>
      第二种:手动调用API设置spmB
        aplus_queue.push({
          action: 'aplus.setPageName',
          arguments: [${yourPageName}]
        });
    B、自动点击配置spm(spmC、spmD)
      注、通过每一自动点击配置项的spmC和spmD属性来配置spm
      aplus_queue.push({
        action:'aplus.setMetaInfo',
        arguments:['aplus-auto-clk',[{
          cssSelector:'.header',
          logkey:'banner-clk',
          spmC:"header",
          spmD:"banner"
        },{
          cssSelector:'.component-category-industry',
          logkey:'category-clk',
          props:['categorytype','title'],
          spmC:"component-category",
          spmD:"industry"
        },{
          cssSelector:'.component-category-common',
          logkey:'category-clk',
          props:['categorytype','title'],
          spmC:"component-category",
          spmD:"business"
        }]]
      });
    C、自动曝光配置spm(spmC、spmD)
      注、通过每一自动曝光配置项的spmC和spmD属性来配置spm
      aplus_queue.push({
        action:'aplus.setMetaInfo',
        arguments:['aplus-auto-exp',[{
          cssSelector:'.header',
          logkey:'banner-exp',
          spmC:"header",
          spmD:"banner"
        },{
          cssSelector:'.component-category-industry',
          logkey:'category-exp',
          props:['categorytype','title'],
          spmC:"component-category",
          spmD:"industry"
        },{
          cssSelector:'.component-category-common',
          logkey:'category-exp',
          props:['categorytype','title'],
          spmC:"component-category",
          spmD:"business"
        }]]
      });
    D、自定义属性配置spm(spmC、spmD)
      <body data-pagename=${yourPageName}>
        <div data-spmc="c111">
          <a href="链接1" data-spmd="d1" />
          <a href="链接2" data-spmd="d2" />
        </div>
        <div data-spmc="c222">
          <a href="链接3" data-spmd="d1" />
          <a href="链接4" data-spmd="d2" />
        </div>
      </body>
      data-pagename:spmB
      data-spmc:spmC
      data-spmd:spmD
7、引入&初始化SDK,全域采集与增长分析-开发参考-SDK参考-Web SDK
  来源,https://help.aliyun.com/document_detail/473456.html
  (1)参数准备
    A、appkey:在应用列表中获取
    B、收数域名:在“管理控制台-采集信息”模块中获取
    C、SDK链接:在“管理控制台-采集信息”模块中获取
  (2)SDK引入&初始化
    A、当您得到集成SDK代码地址以后,在页面head标签内加入集成代码,确保aplus_queue不被污染
      (function(w, d, s, q) {
        w[q] = w[q] || [];
        var f = d.getElementsByTagName(s)[0], j=d.createElement(s);
        j.async = true;
        j.id = 'beacon-aplus';
        j.src = 'SDK链接';
        f.parentNode.insertBefore(j, f);
      })(window, document, 'script', 'aplus_queue');
    B、设置域名和appkey,以下代码紧跟SDK引入代码
      //集成应用的appKey
      aplus_queue.push({
        action: 'aplus.setMetaInfo',
        arguments: ['appKey', '您的appkey']
      });
      //如果是私有云部署还需要在上面那段JS后面紧接着添加日志域名埋点
      //通常私有云日志服务端域名类似于:xxx-web-api.xxx.com.cn, 具体域名在“管理控制台-采集信息”模块中获取
      //2.x版本SDK设置收数域名
      aplus_queue.push({
        action: 'aplus.setMetaInfo',
        arguments: ['trackDomain', '您的收数域名'] 
      });
      //1.x版本SDK设置收数域名
      aplus_queue.push({
        action: 'aplus.setMetaInfo',
        arguments: ['aplus-rhost-v', '您的收数域名']
      });
8、基础功能,全域采集与增长分析-开发参考-SDK参考-Web SDK
  来源,https://help.aliyun.com/document_detail/602428.html
  (1)原理
    A、SDK 提供一种指令形态的埋点调用方式,您通过对 aplus 环境变量的指令队列 aplus_queue发送指令,
    B、由 aplus 环境变量来执行指令,进而完成您的需求,:
    C、指令格式如下
      aplus_queue.push({
        'action': "$APIName",
        'arguments': [$arguments]//arguments为指定API的入参,
      })
  (2)action 参数代表发送指令的 API 名称,其入参为一个字符串,取值为枚举值,可用的枚举值如下
    A、setMetaInfo:覆盖SDK的已有默认设置
    B、appendMetaInfo: 追加SDK的默认配置
    C、getMetaInfo:获取SDK的当前配置
    D、record:发送事件日志
    E、sendPV:发送页面日志
  (3)arguments参数,为action中指定API的入参,格式是一个数组,数组内的元素顺序与API定义的入参顺序一致
  (4)示例
    A、变更SDK的默认设置
      aplus_queue.push({
        action: 'aplus.setMetaInfo',
        arguments: [metaName, metaValue]
      });
    B、获取SDK的当前配置
      aplus.getMetaInfo(metaName);
    C、发送事件日志
      aplus_queue.push({
        action: 'aplus.record',
        arguments: [
          trackerEventCode, //(埋点方案中的事件编码)
          eventType, 
          eventParams
        ]
      });
    D、发送页面日志
      aplus_queue.push({
        action: 'aplus.sendPV',
        arguments: [pageEventConfig, userData]
      });
  (5)日志打印
    aplus_queue.push({
      action: 'aplus.setMetaInfo',
      arguments: ['DEBUG', true]
    });
  (6)应用基础信息配置
    在SDK引入部分,可以修改或者追加一些默认设置
    //集成应用的appKey
    aplus_queue.push({
      action: 'aplus.setMetaInfo',
      arguments: ['appKey', 'xxxxxxx']
    })
    aplus_queue.push({
      action: 'aplus.setMetaInfo',
      arguments: ['aplus-rhost-v', 'quickaplus-Web-api.xxx.com.cn']
    });
    //开启调试模式
    aplus_queue.push({
      action: 'aplus.setMetaInfo',
      arguments: ['DEBUG', true]
    });
9、埋点API,全域采集与增长分析-开发参考-SDK参考-Web SDK
  来源,https://help.aliyun.com/document_detail/602417.html
  (1)设备ID设置
    A、自动生成:默认逻辑,网站的设备ID只有浏览器发生变化或用户主动清除cookie和缓存时,设备ID会发生改变
    B、手动上传:上传方式为赋值给"_dev_id",上传的长度要在24-36字符
    C、示例
      a、如采集用户ID是异步行为,需要先阻止SDK上报,设置BLOCK埋点
        aplus_queue.push({
          action: 'aplus.setMetaInfo',
          arguments: ['_hold', 'BLOCK']
        });
      b、设置_dev_id
        aplus_queue.push({
          action: 'aplus.setMetaInfo',
          arguments: ['_dev_id', '自定义设备ID']
        });
      c、因为采集用户ID是异步行为,故需要先设置BLOCK,再设置START
       设置_hold=START后,事先被block住的日志会携带上用户信息逐条发出
        aplus_queue.push({
          action: 'aplus.setMetaInfo',
          arguments: ['_hold', 'START']
        });
  (2)账号ID设置
    A、在用户登录时,以及登录态进入H5时,都需要设置账号ID
    B、因为设置后的每一条日志都将携带账号ID,但退出H5再进入后触发的事件不会携带账号ID
    C、所以需要在用户登录时,以及登录态进入H5时设置账号ID
    D、示例
      a、用户登录时,获取到用户登录账号信息 or 用户已登录,通过cookie或者localstorage获取用户登录账号
        function demoLogin() {
          /*************************如果同步场景***********************************/
          aplus_queue.push({
            action: 'aplus.setMetaInfo',
            arguments: ['_user_id', '用户的账号id']
          });
          /******************如果是异步场景,并且日志必须依赖用户账号***********************/
          //先通过设置_hold=BLOCK阻塞采集上报
          aplus_queue.push({
            action: 'aplus.setMetaInfo',
            arguments: ['_hold', 'BLOCK']
          });
          ...
          function callback() {
            //获取异步回调结果中的用户账号id
            aplus_queue.push({
              action: 'aplus.setMetaInfo',
              arguments: ['_user_id', '用户的账号id']
            });
            //再通过设置_hold=START允许采集上报
            aplus_queue.push({
              action: 'aplus.setMetaInfo',
              arguments: ['_hold', 'START']
            });
          };
          ...
        };
      b、用户登出时,重置用户账号id
        function demoLogOff() {
          aplus_queue.push({
            action: 'aplus.setMetaInfo',
            arguments: ['_user_id', '']
          });
        };
  (3)设备ID和账号ID获取
    A、设备ID的获取
      a、SDK自动生成的设备ID,获取方式如下:
        在当前域名的cookie下存储名为cna的字段,可以通过解析document.cookie获取
      b、通过_dev_id方式自定义上传的设备ID,获取方式如下:
        开发者通过setMetaInfo设置_dev_id可以自定义设备ID,可通过aplus.getMetaInfo('_dev_id')读取
    B、账号ID的获取
      a、开发者通过setMetaInfo设置_user_id可以自定义用户账号ID,可通过aplus.getMetaInfo('_user_id')获取
  (4)设置用户属性
    A、通过预制事件编码 $$_user_profile 上报用户属性,事件类型为其他事件
    B、在上报用户属性之前,需要先设置_user_id上报用户账号
    C、示例
      aplus_queue.push({
        'action':'aplus.record',
        'arguments':[
          '$$_user_profile(埋点方案中的事件编码)', 
          'OTHER', //特指除点击和曝光事件外的其他自定义事件
          { //此外内容不可改变
            name: 'sss', //用户属性1
            gender: 'male', //用户属性2    
            class: '3', //用户属性3
          }
        ]
      });
10、常见场景与埋点建议,全域采集与增长分析-开发参考-SDK参考-Web SDK
  来源,https://help.aliyun.com/document_detail/603464.html
  (1)页面跳转前的事件发送
    A、当用户在网页「点击href属性www.xxxx.com的a标签」时,触发的点击事件可能会因为页面立刻跳转而未发送出去,
    B、若希望该场景下尽量保证数据的发送,可以进行页面延迟跳转,
    C、事例代码如下
      //点击链接
      function targetLinkCLK(url) {
        // 延迟页面跳转,给SDK预留发数时间
        setTimeout(function(){
          window.location.href = url;
        }, 500);
        aplus_queue.push({
          action: 'aplus.record',
          arguments: [
            'track_alink_clk', //(埋点方案中的事件编码)
            'CLK', 
            {
              param1: xxxx,
              param2: xxxx
            }
          ]
        });
      }
 
十九、aplus.js源码阅读
1、外框
  ! function(e) { //e,总参数
    function t(a) { //公函数,
      if (o[a]) return o[a].exports;
      var n = o[a] = { 
        exports: {}, 
        id: a,
        loaded: !1
      };
      return e[a].call(n.exports, n, n.exports, t), n.loaded = !0, n.exports //公函数执行,参数的某项执行
    }
    var o = {}; //总数据
    return t.m = e, t.c = o, t.p = "", t(0) //公函数执行
  }([function(e, t, o) { //对象,属性,公函数。公函数执行,参数的某项执行
    "use strict";
    ! function() {
      var e = window.goldlog || (window.goldlog = {});
      e._aplus_plugin_cctv || (
        e._aplus_plugin_cctv = {
          status: "complete"
        }, o(1).run()
      )
    }()
  }, function(e, t, o) {
    "use strict";
    function a() {
      var e = l.getCookie("userSeqId");
      if (e) {
        var t = document.getElementById("tb-beacon-aplus") || document.getElementById("beacon-aplus");
        if (t) {
          var o = t.getAttribute("exparams"),
            a = "uidaplus=" + e;
          o = o ? o.replace(/&aplus&/, "&" + a + "&aplus&") : a + "&aplus&sidx=aplusSidex", t.setAttribute("exparams", o)
        }
      }
      return e
    }
    function n() {
      var e = {};
      try {
        var t = goldlog.getMetaInfo("aplus-rhost-g-map");
        "string" == typeof t ? e = JSON.parse(t) : "object" == typeof t && (e = t)
      } catch (t) {
        e = {}
      }
      return e
    }
    function r(e, t) {
      var o = n();
      return o && o[t] ? "//" + o[t] + t : e
    }
    var s = o(2),
      l = o(3);
    t.run = function() {}
  }]);
2、功能扩展-单页面重新获取信息
  来源,https://help.aliyun.com/document_detail/473456.html?spm=a2c4g.602417.0.0.b24b256fiPDTu3
  (1)当您得到集成SDK代码-地址-以后,在页面head标签内加入集成代码,确保aplus_queue不被污染
    (function(w, d, s, q) { 
      w[q] =w[q] || []; 
      var f=d.getElementsByTagName(s)[0],j=d.createElement(s); 
      j.async=true; 
      j.id='beacon-aplus'; 
      j.src='SDK链接'; 
      f.parentNode.insertBefore(j, f);
    })(window, document, 'script', 'aplus_queue');
    //扩充功能-应该-写在这里
  (2)扩充功能 
    function pushData(){
      // var aaa = window.goldlog.getMetaInfo("aplus-auto-exp")
      window.goldlog_queue.push({
        action: 'aplus.sendPV',//发送页面日志
        arguments:[
          {
            is_auto: true
          }, {
            page_title: "首页", //默认为pageConfig中的值,如果这里设置了,则为这里设置的值 (非必传)
            page_name: "yourCurrentPageName", //默认为pageConfig中的值,如果这里设置了,则为这里设置的值 (非必传)
            //如果您设置了duration参数(单位须为毫秒),QuickTracking会做为分析时的「事件属性-时长(s)」处理
            duration: 1000000000,
            //自定义事件属性
            x: 111,
            y: 222
          }
        ]
      });
    }
    function isFunction(myFunction){
      return Object.prototype.toString.call(myFunction) === "[object Function]"
    } 
    function addEveryPageEvent() {
      var historyPushState = window.history.pushState;
      var historyReplaceState = window.history.replaceState;  
      if (isFunction(window.history.pushState)) {
        window.history.pushState = function() {
          console.log( 'pushState' );
          historyPushState.apply(window.history, arguments);
          pushData()
        };
      }
      if (isFunction(window.history.replaceState)) {
        window.history.replaceState = function() {
          console.log( 'replaceState' );
          historyReplaceState.apply(window.history, arguments);
          pushData()
        };
      }
      if (window.addEventListener) {
        window.addEventListener('popstate', function(){
          console.log( 'popstate' );
          pushData()
        }, false);
      }
    }
    addEveryPageEvent() 
  (3)扩充功能不应该写在这里  
    e.init = function() {
      i.initLoad.init_watchGoldlogQueue("metaQueue"), n(90)(function() {
      })
      //扩充功能-不应该-写在这里
    }
3、使用说明
  来源,https://help.aliyun.com/document_detail/473456.html?spm=a2c4g.602417.0.0.b24b256fiPDTu3
  (1)在QuickTracking后台,为每一个Web应用生成了专属的集成代码,可以根据产品内的引导进行集成
  (2)参数准备
    A、appkey,在应用列表中获取
    B、收数域名,在“管理控制台-采集信息”模块中获取
    C、SDK链接,在“管理控制台-采集信息”模块中获取
  

  

posted @ 2019-11-25 19:37  WEB前端工程师_钱成  阅读(4577)  评论(0)    收藏  举报