2024年11月

问题情景

混淆群内的小伙伴遇到这么个问题,Mailivery 这个网站登录后,明明提交的表单(邮箱和密码也正确)、请求头等等都没问题,为啥一直重定向到登录页面呢?唉,该出手时就出手啊,我也看看咋回事吧!

截图_20241113133726

登录参数分析

显而易见,需要:邮箱(有邮箱校验)、密码

打开开发者工具,随意输入邮箱和密码登录,查看登录接口的请求和方法:

截图_20241113133929

请求网址:https://app.mailivery.io/login
请求方法:POST
状态代码:302 Found

302
?!重定向吗?在登录不成功后重定向到了登录网站,要求重新登录。目测前后端不分离项目(阿巴阿巴),如果登录成功,肯定会携带登录成功时设置的 Cookie 重定向到主页或者相关主面板。

查看一下登录接口提交头:

POST /login HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 341
Content-Type: application/x-www-form-urlencoded
Cookie: XSRF-TOKEN=省略; mailivery_session=省略; ......
DNT: 1
Host: app.mailivery.io
Origin: https://app.mailivery.io
Pragma: no-cache
Referer: https://app.mailivery.io/login
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36
sec-ch-ua: "Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"

没有啥加密参数,emm……,很简单,的确是看着很简单,注意
Content-Type

application/x-www-form-urlencoded

再看看提交的参数:

_token=lCsu2Ruuw33uHlHkRlKwZG3C2tw7TQBjUoTo1yjz&paid_user_Khe2xqZLA4Tkq3py=&submitted_in_seconds=eyJpdiI6IjJ4OXdFK3ZPZklKUnNadXVtRTk0L3c9PSIsInZhbHVlIjoiNHg5NzJmUjM3UnlDOU1tanlnUHpWdz09IiwibWFjIjoiMDYxYmRkODU0YmY1ZjY0MDk4OWMzNmM5YWU5MjNmZDM4NTg5NzQ1MmM3MzBjNzQ3YjYxNTg0MjliYjFjYzM3OCIsInRhZyI6IiJ9&email=xxx%40foxmail.com&password=123

格式化看一下:

_token: lCsu2Ruuw33uHlHkRlKwZG3C2tw7TQBjUoTo1yjz
paid_user_Khe2xqZLA4Tkq3py: 
submitted_in_seconds: eyJpdiI6IjJ4OXdFK3ZPZklKUnNadXVtRTk0L3c9PSIsInZhbHVlIjoiNHg5NzJmUjM3UnlDOU1tanlnUHpWdz09IiwibWFjIjoiMDYxYmRkODU0YmY1ZjY0MDk4OWMzNmM5YWU5MjNmZDM4NTg5NzQ1MmM3MzBjNzQ3YjYxNTg0MjliYjFjYzM3OCIsInRhZyI6IiJ9
email: xxx%40foxmail.com
password: 123

表单提交嘛,多多少少也是带点验证参数,比如这里的:
_token

paid_user_Khe2xqZLA4Tkq3py

submitted_in_seconds

经过多次登录提交,发现
_token

submitted_in_seconds
的值是变动的,而
paid_user_Khe2xqZLA4Tkq3py
变动的是
paid_user_
之后的部分。并且这些参数与窗口的
Cookie
相关,如果不一致,将发生 416 错误,提示页面过期。看来这就是个反爬点了,还记得我说的吗,这个网站是前后端不分离,那么这些参数肯定也隐藏于表单,在元素中搜索特色浓重的
submitted_in_seconds

截图_20241113135213

非常的好,可以看到我们这三个参数的来源了,捋一捋:

访问 login 网站(GET) 得到三个参数,此时响应头所设置的 Cookie 无登录效力
对 login 网站(POST) 携带三个参数,根据结果重定向:
	如果登录失败,此时响应头所设置的 Cookie 也是无登录效力的,携带 Cookie 要求咱们继续登录
	如果登录成功,此时响应头所设置的 Cookie 就是具备登录效力的,携带 Cookie 直接上主页

接下来在 Python 中实现一下,创建
login.py

dashboard.py

login.py 实现登录的接口:

import requests

url = "https://app.mailivery.io/login"

_token = ''
paid_user = ''
submitted_in_seconds = ''
email = 'xxx%40163.com'
password = 'xxxxxxx'

payload=f'_token={_token}&{paid_user}=&submitted_in_seconds={submitted_in_seconds}&email={email}&password={password}'

headers = {
   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
   'Accept-Language': 'zh-CN,zh;q=0.9',
   'Host': 'app.mailivery.io',
   'Origin': 'https://app.mailivery.io',
   'Referer': 'https://app.mailivery.io/login',
   'Sec-Fetch-Dest': 'document',
   'Sec-Fetch-Mode': 'navigate',
   'Sec-Fetch-Site': 'same-origin',
   'Sec-Fetch-User': '?1',
   'Upgrade-Insecure-Requests': '1',
   'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
   'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
   'sec-ch-ua-mobile': '?0',
   'sec-ch-ua-platform': '"Windows"',
   'Cookie': 'XSRF-TOKEN=eyJpdiI6Ikk1WjFjMFRsdVhKdzBuMjBWamNVVEE9PSIsInZhbHVlIjoiblAzS0ZBOEUyK1lXWGV0ZXhuT1Y5MldLc290d2ZzL0E3dTQxT3BDMmNGR3d6aG0vamhUekozeVFCUVVVczRJSWxxQTM1ZGpvOU5KTm11bFp4NEsvWGlObUJ6V1A3WWc1WFJXcUlPYWYzYTgrSGNMZ2VtM0s1R0tGUlJ4Z0ZMSy8iLCJtYWMiOiI2NGUxYmNhMjEwMmE3ZDNmOTc4OTcxMWVlZGY3ODIyNDZhODBiYzUxZjVhMWE2YWZkMWVhOGM2YjA4MmQzYmY0IiwidGFnIjoiIn0%3D; mailivery_session=eyJpdiI6IjVwL1QwUHNhMTlTdGUyZ0ozUzY3aGc9PSIsInZhbHVlIjoiYlNuSXI2d2tKWjMrYjFpVmx1Ym5uTEVOUGhXZjFKRGhVV1VMeXRHQ1BpRWFsV0ZnYVFkNDd4Vm9wdXY1ZElqVWpVL2xhSytNdnBDYS9NNHRBWmNzRDF4ZjJtWFhPTHFJRFBLVnNYSmFPMW9HSkEweVVpQTZZVjhJU2k5WSswR0oiLCJtYWMiOiJkYzg0ZmY2ODEwZmEyMzczNzU5NGU4YzMwYjA2MDRlZTc0ZWJiNDc4ZDBhMDU4OTgyM2E3NDMzZDM3NmRmNTcxIiwidGFnIjoiIn0%3D',
   'Content-Type': 'application/x-www-form-urlencoded',
   'Connection': 'keep-alive'
}

response = requests.request("POST", url, headers=headers, data=payload, allow_redirects=False, proxies={
    'http': None,
    'https': None
})

print(response.headers)
print(response.status_code)

注意,我说过,Cookie 和三个参数是有紧密联系的,上述 Cookie 是通过对登录页面 GET 时得到的,三个参数同理。

三个参数的值我已省略,特别特别要注意的是:请求时,
allow_redirects
这个值最好设置为 False,因为重定向底层和浏览器不同,如果为 True,你会发现你的 Cookie 一直都是失效的。因为产生了如下的重定向过程:

邮箱密码正确的情况:
login -> dashboard -> login
邮箱密码错误的情况:
login -> login
参数未校验的情况:
login -> 页面过期

为什么最终又到 login 了???

我猜测可能是反爬点,动态加载未加载好,太快了,Cookie 或者三个参数在后端都还是未准备好的状况。

我们只需要取得跳转到 dashboard 前响应头中的 Set-Cookie 的值,也就是
XSRF-TOKEN

mailivery_session

因此我们停止使用 requests 的重定向。

接下来看一下 dashboard.py 这个文件,主要测试一下取得 Cookie 是否有效:

import requests

url = "https://app.mailivery.io/campaign/dashboard"

payload={}
xsrf_token = 'xxx'
mailivery_session = 'xxx'

headers = {
   'Cookie': f'XSRF-TOKEN={xsrf_token}; mailivery_session={mailivery_session}',
   'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
   'Accept': '*/*',
   'Host': 'app.mailivery.io',
   'Connection': 'keep-alive'
}

response = requests.request("GET", url, headers=headers, data=payload, proxies={
    'http': None,
    'https': None
})

print(response.text)

全套模拟

我们最终的效果是:自动获取三个参数和初始的 Cookie,然后根据邮箱和密码登录,再携带 Cookie 访问 Dashboard。

  1. 自动获取这个很简单,通过 lxml 中的 etree 解析,Cookie 通过 requests 响应头得到。

  2. 然后去登录,也很简单,携带好参数和上一级得到的 Cookie,如果登录密码这些没错,将得到有效的 Cookie。

  3. 最后携带上一级得到的有效 Cookie 进行登录。

如果说你觉得到此结束了,那很抱歉,你再进行上述的实现后,将发现 Cookie 无效!!!

略带一笔,自动获取的表单参数和初始 Cookie 如果有问题,请求时将响应 416。如果没问题,你在取到后立马进行登录,请求将响应 200。200 就一定是好事吗?我已经说过了,要重定向到 dashboard 页面,那么应该是 302 才对!所以我们在取参数到登录这个请求中进行延时,等待后端缓过神,比如 3 秒?4秒?都是可以的。或者你根据响应状态码增加重试机制,尝试到出现 302 的情况。

完整代码就不提供啦,自行琢磨吧~

前言

在工作我们经常会出现有多个文件,为了节省资源会将多个文件放在一起进行压缩处理;为了让大家进一步了解我先将springboot处理的方法总结如下,有不到之处敬请大家批评指正!

一、文件准备:

https://qnsc.oss-cn-beijing.aliyuncs.com/crmebimage/public/product/2024/11/12/be353210028a3da732c8ba34073fb4ca.jpeg
https://qnsc.oss-cn-beijing.aliyuncs.com/crmebimage/public/product/2024/11/13/5bbf579109db2641249deab4be4340f6.jpeg
https://qnsc.oss-cn-beijing.aliyuncs.com/crmebimage/public/product/2024/11/13/1808773678128361474.xlsx

二、处理步骤:

1.创建一个springboot web项目 这一步在此省略.....

2.需要的方法及类的编写

(1)业务方法-TestService
public interfaceTestService {void compressFiles(List<String>fileUrls, HttpServletResponse response);
}
(2)业务方法实现类-TestServiceImpl
@Service
@Slf4j
public class TestServiceImpl implementsTestService {

@Override
public void compressFiles(List<String>fileUrls, HttpServletResponse response) {try (ZipOutputStream zipOut = newZipOutputStream(response.getOutputStream())) {for(String fileUrl : fileUrls) {//1.从网络下载文件并写入 ZIP try{
URL url
= newURL(fileUrl);
HttpURLConnection connection
=(HttpURLConnection) url.openConnection();
connection.setRequestMethod(
"GET");
connection.connect();
//2.检查响应码 if (connection.getResponseCode() !=HttpURLConnection.HTTP_OK) {throw new IOException("Failed to download file: " +fileUrl);
}
//3.从 URL 中提取文件名 String pathStr = fileUrl.substring(fileUrl.lastIndexOf('/') + 1);//4.创建 ZIP 条目 ZipEntry zipEntry = newZipEntry(pathStr);
zipOut.putNextEntry(zipEntry);
//5.读取文件的输入流 try (InputStream inputStream = newBufferedInputStream(connection.getInputStream())) {byte[] buffer = new byte[1024];intlength;while ((length = inputStream.read(buffer)) >= 0) {
zipOut.write(buffer,
0, length);
}
}
zipOut.closeEntry();
}
catch(IOException e) {
log.error(
"Error processing file URL: " +fileUrl, e);throw newRuntimeException(e);
}
}
       // 6.响应信息设置处理
response.setContentType(
"application/octet-stream");
response.setHeader(
"Content-Disposition", "attachment;filename=test.zip");
response.flushBuffer();
}
catch(IOException e) {
log.error(
"Error compressing files", e);throw newRuntimeException(e);
}
}
}
(3)控制器类的编写-TestController
/*** @Project:
* @Description:
*
@author: songwp
* @Date: 2024/11/13 14:50
*
*/@RequestMapping("test")
@RestController
@Slf4j
public classTestController {

@Autowired
privateTestService testService;/*** 文件压缩
*
*
@paramfileUrls 要压缩的文件 URL 列表
*
@paramresponse 响应对象*/@GetMapping("/fileToZip")public void zip(@RequestParam("fileUrls") List<String>fileUrls, HttpServletResponse response) {
testService.compressFiles(fileUrls, response);
}
}

三、方法调用展示

(1)存放到桌面

(2)解压response.zip文件

1. 内存架构和子系统

1.1 如何控制访问?

image-20241113103538311

访问控制

  • 存储单元的访问是通过
    访问晶体管(access transistors)
    进行控制的。访问晶体管像开关一样,可以连接或断开存储单元和位线(bitline)的连接。
  • 存取控制由
    字线(wordline)
    控制。当字线激活时,访问晶体管开启,允许存储单元的数据流入或流出位线。

DRAM(Dynamic random access memory)的结构

  • DRAM 中的存储单元通常由
    一个电容和一个晶体管组成。电容用来存储数据(1或0)
    ,而晶体管作为访问开关。
  • 由于
    电容会泄漏电荷

    DRAM 需要周期性刷新数据来保持信息的完整性。

SRAM 的结构

  • SRAM 中的存储单元由
    交叉耦合反相器(cross-coupled inverters)
    组成,通常是两个反相器互相连接,形成一个稳定的双稳态结构。
  • SRAM 需要
    4 个晶体管用于存储,2 个晶体管用于访问。
  • SRAM 不需要像 DRAM 那样周期性刷新数据
    ,因为它的电路结构使其能够长时间保持数据,直到被改写。

差异

  • DRAM
    的电路结构相对简单,占用的物理空间小,因此具有更高的存储密度,但需要刷新电路。
  • SRAM
    的电路结构更复杂,占用更多的空间,因此存储密度较低,但其访问速度较快且不需要刷新。

1.2 内存架构

image-20241113104147042

DIMM:
背面和正面装有
DRAM 芯片
的印刷电路板。

Rank:一组 DRAM 芯片
,它们协同工作以响应请求并保持数据总线满载。

64位
数据总线需要
8x8 DRAM 芯片

4x16 DRAM 芯片
...

Bank
:在
一次请求期间
忙碌的一个
rank
的子集。

行缓冲区(Row buffer):
从组中读取的最后一行(如 8 KB),作用类似于
缓存。

image-20241113104732682

通道(Channel)
:每个通道通过命令总线(cmd bus)、地址总线(addr bus)和数据总线(data bus)与处理器相连,用于发送命令、地址和数据。这些总线允许
处理器并行地与多个内存模块进行交互
,从而提高系统的
并行性

内存控制器(MemCtrl)
:处理器通过内存控制器(MemCtrl)来管理对内存的访问。内存控制器负责
内存请求的调度,并通过通道将数据发送到内存的特定区域。

Rank
:在存储器模块中,
每个通道包含一个或多个rank

rank是由多个bank组成的
,它们在处理数据时可以
同时访问

Bank

每个rank包含多个bank,bank是rank的子集,每次访问期间只有一个bank处于繁忙状态。
每个bank可以
独立
进行数据的存储和访问,使得系统能够在
不同的bank之间并行处理数据
,进一步提高内存的效率和带宽。

这种分层结构允许内存系统在
不同的bank和rank之间进行并行访问
,提高了内存带宽和数据处理效率。这种设计被广泛应用于DRAM中,以减少延迟并提高数据吞吐量。

1.3 DRAM 阵列访问

image-20241113105156088

这是一个
\(16Mb\)
的DRAM阵列,即有
\(4096\times4096\)
阵列的bits。

行访问选通 (Row Access Strobe, RAS):
由于该DRAM阵列有4096行,因此有
\(log_{2}^{4096}=12 bits\)
行地址,在访问数据时,
12bits 行地址位最先到达。

列访问选通(Column Access Strobe,CAS):
由于该DRAM阵列有4096列,因此有
\(log_{2}^{4096}=12 bits\)
列地址,在访问数据时,
12bits 列地址位比行地址位后到达。

行访问选通到达后,DRAM读取一行数据(即4096bits)至
行缓冲区(Row buffer)
,随后列访问选通到达,经过
列解码器(Column decoder)
从 Row buffer 读取数据返回 CPU。

1.4 内存 Bank 的组织和运行

image-20241113111737048

读取访问顺序:

  1. 解码行地址并驱动字线(word-lines)
  2. 选定位并驱动位线(bit-lines) - 整行读取
  3. 放大行数据
  4. 解码列地址并选择行的子集 - 发送至输出端
  5. 位线(bit-lines)预充电
    - 用于下一次访问

1.5 DRAM 主存储器

  • 主存储器存储在 DRAM 单元中,其存储密度要高得多

  • DRAM 单元会随着时间的推移而丢失状态 - 必须定期刷新,因此被称为动态存储器

  • DRAM 存取时间长,能源开销大

1.6 DRAM vs. SRAM

DRAM:

  • 访问速度较慢
    (电容器)
  • 密度较高(1T 1C cell)
  • 成本较低
  • 需要刷新
    (功率、性能、电路)
  • 制造时需要将电容器和逻辑器件放在一起

SRAM:

  • 访问速度较快(无电容器)
  • 密度较低
    (6T cell)
  • 成本较高
  • 无需刷新
  • 制造时与逻辑工艺兼容(无电容器)

1.7 Rank 的组织结构

  • DIMM、rank、bank、array
    -> 在存储组织中形成一个层次结构

  • 由于电气限制,
    总线上只能连接几个 DIMM

  • 一个 DIMM 可有 1~4 ranks


  • 提高能效
    ,应使用
    宽输出
    DRAM 芯片 ——
    每次请求只激活
    \(4\times16bits\)
    芯片比激活
    \(16\times 4bits\)
    芯片更好


  • 高容量
    ,应使用
    窄输出
    DRAM 芯片 —— 由于通道上的
    rank 数有限
    ,使用
    \(16 \times 4bits\ 2Gb\)
    芯片比使用
    \(4 \times 16bits\ 2Gb\)
    芯片可提高每个 rank 的容量

1.8 Banks 和 Arrays 的组织结构

  • 一个 rank 被分成多个 banks(4~16 个)
    ,以提高 rank 内的并行性,通过在不同的bank之间进行操作,可以实现并行访问。
  • ranks 和 banks 提供内存级并行性
    ,通过将数据分散在不同的ranks和banks中,内存系统能够同时处理多个内存请求,从而提升内存的并行处理能力。
  • 一个 bank 由多个 arrays(subarrays、tiles、mats)组成
  • 为了最大限度地提高密度,bank 中的 arrays 要做得很大:
    为了在有限空间内存储更多数据,每个 bank 内的 array 被设计得很大。这意味着 array 中的行很宽,因此
    行缓冲区(row buffer)
    也很宽。例如,当内存请求为
    \(64Bytes\)
    时,实际上可能会读取 $8KBytes $的数据(称为
    过量读取(overfetch)
    ),以充分利用宽行缓冲区的特性。
  • 每个array每个周期向输出引脚提供一位数据
    :为了实现更高的存储密度,每个 array 在每个时钟周期内只提供1位数据到输出引脚。这种设计虽然降低了单次传输的数据量,但提高了系统的总存储密度,适合需要存储大量数据的情况。

这种组织方式通过多个层次(ranks、banks、arrays)的划分,实现了高密度的存储,同时也通过并行访问多个banks和ranks,提升了内存系统的并行性和性能。

1.9 行缓冲区(Row Buffers)

  • 每个 bank 都有一个行缓冲器
  • 行缓冲区在DRAM中充当缓存的作用。
    • 行缓冲器命中(Row buffer hit):
      约 20 ns 访问时间(只需将数据从行缓冲区移至引脚)
    • 空行缓冲区访问(Empty row buffer access)
      :约 40ns(首先需要读取array,然后将数据从行缓冲区移到引脚)。
    • 行缓冲区冲突(Row buffer conflict)
      :约 60ns(首先需要预充电 bit-lines,然后读取新行,再将数据移到引脚)。
  • 另外,还需在队列中等待(数十ns),并且还要经历地址/命令/数据传输延迟(约10ns)。

1.10 构建更大的存储器

我们需要更大的存储阵列,但是大阵列意味着访问速度慢。

如何在保证存储器容量大的同时不使其变得非常慢?

Idea:
将存储器划分为更小的阵列
,并将这些阵列与输入/输出总线互连。

  • 大容量存储器通常是分层的阵列结构。
  • DRAM的分层结构:通道(Channel)→ Rank → 存储体 → 子阵列(Subarrays)→ 矩阵(Mats)

2. DRAM 子系统组织

image-20241113120452070

2.1 通用内存结构

image-20241113121111881

上图展示了一个通用的内存结构,主要包括以下组件:

  1. Memory Controller(内存控制器)
    :负责管理内存数据的读写操作。图中显示了两个内存控制器,每个控制器通过一个通道(channel)与内存模块相连。
  2. Channel(通道)
    :内存控制器和内存之间的数据传输通道,允许多个控制器访问不同的内存模块,提高内存的带宽。
  3. DRAM(动态随机存取存储器)
    :DRAM模块通过多个“rank”组织,每个rank又包含若干个“bank”,进一步提高并行性和效率。
  4. Rank
    :内存中的一个逻辑组织单元,由多个物理内存芯片组成,便于内存控制器的访问管理。
  5. Bank
    :内存的更小单位,支持多路并发访问。每个bank内又包含多个行(row)和列(column)。
  6. Row(行)和 Column(列)
    :bank内存储数据的基本单位,通过行和列的地址确定具体的数据位置。
  7. Cache Line(缓存行)
    :CPU读取数据的基本单位,一次读取的数据块大小。通过cache line的设计提高数据传输效率。

2.2 通用原则:交替访问(Banking)

交替访问(Interleaving)(banking 存储库)

  • Problem
    :一个单一的大型内存阵列访问时间长,且无法实现并行的多次访问。
  • Goal
    :降低内存阵列的访问延迟,并实现并行的多次访问。
  • Idea
    :将一个大型阵列划分为多个可以独立访问的存储库(bank),这些 bank 可以在同一个周期或连续的周期内进行访问。
    • 每个 bank 都比整个内存存储要小。
    • 对不同 bank 的访问可以重叠。
    • 访问延迟是可控的。

2.3 内存 Banking 示例

  • 内存分为多个存储库(banks)
    :这些存储库可以独立访问,从而实现并行的多次访问。这种结构将内存划分为多个bank,每个bank可以独立完成数据的读写操作,减少了等待时间。
  • 共享地址和数据总线
    :多个存储库共享地址总线和数据总线,这样设计可以
    减少内存芯片的引脚数量
    ,从而降低硬件复杂度和成本。
  • 每周期完成一个bank的访问
    :通过并行访问不同的bank,在每个周期内可以启动和完成对一个存储库的访问,提升了整体的内存访问效率。
  • 支持N个并发访问
    :如果所有N个访问请求都指向不同的bank,那么系统可以支持N个并发的内存访问请求。这意味着如果内存访问请求均匀分布到不同的bank上,可以充分利用内存带宽,实现高效的并行处理。

image-20241113121924489

  • 图中显示了16个存储库(Bank 0 至 Bank 15)。
  • 每个bank都有其独立的
    内存数据寄存器(MDR)

    内存地址寄存器(MAR)
    ,用于存储正在传输的数据和地址信息。
  • 这些存储库通过数据总线与CPU相连,数据总线负责数据的传输,地址总线负责地址信息的传输。

2.4 DRAM 子系统

image-20241113122454253

图中间的处理器通过两个独立的内存通道与内存模块相连,
每个通道内可以插入多条内存条(DIMM,即双列直插内存模块)
,通道负责将数据传输到内存和处理器之间。

2.4.1 分解 DIMM(模块)

image-20241113123047830

上图中展示了DIMM的
正面

背面

  • Rank 0
    :位于DIMM的正面,由8个芯片组成。
  • Rank 1
    :位于DIMM的背面,也由8个芯片组成。

2.4.2 分解 Rank

image-20241113123439602

每个
Memory channel
包括:

  • Addr/Cmd
    :表示地址和指令信号,用于向不同的Rank发送内存访问请求。
  • CS(Chip Select)
    :选择信号,用于选择哪个Rank进行操作。在图中,CS <0:1> 表示控制信号,选择Rank 0 或 Rank 1。
  • Data <0:63>
    :数据总线,提供64位数据通道,用于传输数据。

image-20241113134945294

一个内存 Rank 内部是由
多个芯片(Chip)
构成的:

Rank 0被分解为多个芯片,从Chip 0到Chip 7。每个芯片负责部分数据位:

  • Chip 0
    负责数据位0到7(<0:7>)。
  • Chip 1
    负责数据位8到15(<8:15>)。
  • 依此类推,直到
    Chip 7
    负责数据位56到63(<56:63>)。

64位数据通道
:所有芯片通过各自负责的8位数据位共同组成了64位的数据通道(Data <0:63>),从而实现并行数据的传输。这种设计允许Rank内多个芯片同时工作,提高了数据访问效率。

2.4.3 分解 Chip

image-20241113135401115

一个内存 Chip 内部是由
多个存储库(Banks)
构成的:

Chip 0
:图的左侧的 Chip 0,它负责8位的数据通道(<0:7>),表示该芯片只传输数据的0到7位。

内部存储单元(Banks)
:在右侧的放大图中,Chip 0进一步被分解为8个独立的存储单元(称为“Banks”)。每个Bank都可以独立地存储和读取数据,允许芯片同时处理多个数据请求,提高数据传输的并行性。

2.4.4 分解 Bank

image-20241113135739648

一个内存 Bank 内部是由
多个大小为 1Byte 的 Arrays
构成的:

  • 先读取一个行,将该行缓存到 Row-buffer 中
  • 再根据 column 地址从 Row-buffer 中取出大小为 1Byte(即 8bits)的数据

2.4.5 深入挖掘:DRAM Bank 操作

第1次访问(Row,Column 0):

Step 1:
Row address 0到达,word-lines 被激活,bit-lines 被连接到感测放大器。

image-20241113141418354

Step 2:
感测放大器感知该行内容,捕捉数据放入 Row Buffer。

image-20241113141741481

Step 3:
Column address 0到达,选择列。

image-20241113142842806

Step 4:
最后数据被读出。

image-20241113142927434

第2次访问(Row 0,Column 1):

由于
Row Buffer HIT
,数据很快被读取出。

image-20241113143304271

第3次访问(Row 0,Column 85):

同样由于
Row Buffer HIT
,数据很快被读取出。

image-20241113143436434

第4次访问(Row 1,Column 0):

出现
Row Buffer Conflict(冲突)

image-20241113143617153

Step 1:

行写回
,即
预充电
,使得下次访问的数据可靠,
增加了延迟

image-20241113143838280

Step 2:
Row address 1到达,word-lines 被激活,bit-lines 被连接到感测放大器。

image-20241113144012392

Step 3:
感测放大器感知该行内容,捕捉数据放入 Row Buffer。

image-20241113144113002

Step 4:
Column address 0到达,选择列。

image-20241113144255737

Step 5:
最后数据被读出。

image-20241113144410131

2.5 DRAM Bank 内部有 Sub-Banks

image-20241113144958401

左图 (a) 是逻辑抽象图,展示了一个 DRAM bank 的传统表示法,其中包含行(row)、行解码器(row-decoder)和一个大的行缓冲区(row-buffer)。在逻辑抽象中,bank 似乎是一个整体,有着多达 32K 行的数据。

右图 (b) 是实际的物理实现。一个 DRAM bank 实际上被分割为多个子阵列 (Subarray),每个子阵列拥有自己的局部行缓冲区 (local row-buffer),并且每个子阵列由512行组成。这里显示了从第1个子阵列到第64个子阵列的结构。这些子阵列通过全局解码器 (global decoder) 连接到整个 bank 的全局行缓冲区 (global row-buffer)。

  • 逻辑抽象
    :在逻辑上,bank 被认为是一个单一的整体结构,所有行共享一个行缓冲区。
  • 物理实现
    :实际上,bank 被划分成多个子阵列以提升访问效率。每个子阵列具有自己的局部行缓冲区,使得在不同子阵列之间可以并行处理数据,以提高并行度和性能。

这种设计通过将 bank 分为多个子阵列来增强访问速度和并行性,从而减少等待时间并提升DRAM性能。

2.6 Example:传输缓存块

image-20241113145432165

  • 传输一个 64B 的高速缓存块需要 8 个 I/O 周期。
  • 在此过程中,8 列按顺序被读取。

image-20241113145505258

Step 1:

image-20241113145609008

Step 2:

image-20241113145631827

Step 3:

image-20241113145705599

Step 4:

image-20241113145733585

3. 内存控制器

3.1 打开/关闭页策略(Open/Closed Page Policies)

  • 如果访问流具有
    局部性
    ,则
    行缓冲区会保持打开状态
    • 行缓冲命中成本低
      (打开页策略(open-page policy))
    • 行缓冲未命中是存储库冲突,代价高昂,因为预充电在关键路径上
  • 如果访问流
    几乎没有局部性
    ,则
    位线(bit-lines)在访问后立即进行预充电(关闭页策略(close-page policy))
    • 几乎每次访问都是行缓冲未命中
    • 预充电通常不在关键路径上
  • 现代内存控制器策略介于这两者之间(通常是专有的)

3.2 读写操作

  • 读取和写入操作使用同一条总线。

  • 在切换读取和写入操作时,必须反转总线方向;这需要时间,并导致总线空闲。

  • 因此,写入操作通常以突发方式进行;写缓冲区会存储待处理的写入,直到达到高水位标记。

  • 写入操作会一直进行,直到达到低水位标记。

  • 高水位标记(High Water Mark)
    :这是缓冲区中数据达到的一个预定的上限。当缓冲区中的数据量达到这个标记时,系统就会触发某些操作,例如开始写入数据(如在存储器中)或停止进一步的数据写入到缓冲区中,以避免缓冲区溢出。

  • 低水位标记(Low Water Mark)
    :这是缓冲区中的数据量达到的一个预定的下限。当缓冲区中的数据量减少到这个标记时,系统可以重新开启数据写入或进行其他操作,确保缓冲区不会因为数据不足而影响性能。

3.3 地址映射策略

  • 可将连续的缓存行放置在
    同一行
    中,以
    提高行缓冲区命中率
  • 可将连续的缓存行放置在
    不同行
    中,以
    提高并行性
  • 地址映射策略示例:
    • row : rank : ​bank : ​channel : column : blkoffset
    • row : column : rank : bank : channel : blkoffset

3.4 调度策略

  • FCFS(先到先服务)
    :处理队列中第一个可以执行的读或写请求。


    • 在FCFS策略中,系统按照请求到达的顺序依次执行。只要一个请求准备好(符合执行条件),就会立即被执行。这个方法简单,但在内存访问中可能
      无法充分利用行缓冲(row buffer)命中
      ,导致性能不高。
  • First Ready - FCFS(优先行缓冲命中 - 先到先服务)
    :优先处理行缓冲命中的请求,如果可能的话。


    • 这个策略首先检查是否有请求可以命中行缓冲(即当前行已经在内存的行缓冲区中)。如果有,就优先处理这些命中请求,因为这样可以
      减少行开销,提高访问速度
      。如果没有行缓冲命中,则按照先到先服务的方式处理。
  • Stall Time Fair(等待时间公平)
    :优先处理行缓冲命中的请求,除非其他线程被忽略了。

  • 这个策略在优先行缓冲命中的基础上增加了公平性。如果多个线程在竞争内存访问,则该策略会在尽量优先行缓冲命中的同时,保证所有线程都能得到公平的访问机会,不让某些线程一直被延迟。这种方法有助于平衡多线程环境下的资源分配。

3.5 刷新(Refresh)

DRAM(动态随机存取存储器)中的每个存储单元都由电容存储电荷来表示数据。由于电容电荷会随时间逐渐泄漏,因此需要定期刷新来补充电荷,以确保数据不丢失。

  • 刷新时间窗口
    :所有DRAM单元必须在64毫秒内刷新一次,防止数据因电荷泄漏而丢失。

  • 自动刷新
    :当对某一行执行读或写操作时,该行会自动进行刷新,帮助延长数据保持时间。

  • 刷新指令的影响
    :每次刷新指令会刷新一定数量的行。在刷新过程中,内存暂时不可用,这可能导致微小的延迟。

  • 刷新频率
    :内存控制器通常会平均每7.8微秒发出一次刷新指令,以分散刷新负担,避免集中刷新带来的性能影响。

【引言】

本文将介绍如何使用鸿蒙NEXT框架开发一个简单的光强仪应用,该应用能够实时监测环境光强度,并给出相应的场景描述和活动建议。

【环境准备】

电脑系统:windows 10

开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806

工程版本:API 12

真机:mate60 pro

语言:ArkTS、ArkUI

【功能实现】

1. 项目结构

本项目主要由以下几个部分组成:

  • LightIntensityItem 类
    :用于定义光强度范围及其相关信息,包括光强度的起始值、终止值、类型、描述和建议活动。通过构造函数初始化这些属性,便于后续使用。
  • LightIntensityMeter 组件
    :这是光强仪的核心,包含状态管理、传感器初始化和光强度更新等功能。组件使用
    @State
    装饰器来管理当前光强度值和类型,并在组件即将出现时获取传感器列表。
  • 传感器数据处理
    :通过监听环境光传感器的数据,实时更新当前光强度值,并根据光强度范围更新当前类型。这一过程确保了用户能够获得最新的环境光信息。

2. 界面布局

光强仪的用户界面使用了鸿蒙系统的布局组件,包括
Column

Row
。界面展示了当前光强度值和类型,并通过仪表组件直观地显示光强度。用户可以清晰地看到光强度的变化,并获得相应的场景描述和活动建议。

  • 仪表组件
    :用于显示当前光强度值,采用了动态更新的方式,确保用户能够实时看到光强度的变化。
  • 信息展示
    :通过遍历光强度范围列表,展示每个类型的光强度范围、描述和建议活动。这一部分为用户提供了实用的信息,帮助他们根据环境光条件做出相应的决策。

3. 总结

通过本案例,开发者可以学习到如何在鸿蒙系统中使用传感器服务和组件化开发方式,构建一个功能完整的光强仪应用。该应用不仅能够实时监测光强度,还能根据不同的光强度范围提供实用的建议,提升用户体验。

【完整代码】

import { sensor } from '@kit.SensorServiceKit'; // 导入传感器服务套件
import { BusinessError } from '@kit.BasicServicesKit'; // 导入业务错误类

// 定义一个光强度项类,用于存储不同光强度范围的信息
class LightIntensityItem {
  luxStart: number; // 光感强度范围起点
  luxEnd: number; // 光感强度范围终点
  type: string; // 类型
  description: string; // 场景描述
  recommendation: string; // 建议活动

  // 构造函数,初始化对象属性
  constructor(luxStart: number, luxEnd: number, type: string, description: string, recommendation: string) {
    this.luxStart = luxStart;
    this.luxEnd = luxEnd;
    this.type = type;
    this.description = description;
    this.recommendation = recommendation;
  }
}

// 使用装饰器定义组件,该组件是光强度计
@Entry
@Component
struct LightIntensityMeter {
  @State currentType: string = ""; // 当前光强度类型
  @State currentIntensity: number = 0; // 当前光强度值
  @State lightIntensityList: LightIntensityItem[] = [// 不同光强度范围的列表
    new LightIntensityItem(0, 1, '极暗', '夜晚户外,几乎没有光源。', '不宜进行任何活动,适合完全休息。'),
    new LightIntensityItem(1, 10, '很暗', '夜晚室内,只有微弱的灯光或月光。', '只适合睡觉,避免使用电子设备。'),
    new LightIntensityItem(10, 50, '暗', '清晨或傍晚,自然光较弱。', '轻松休闲,避免长时间阅读,适合放松。'),
    new LightIntensityItem(50, 100, '较暗', '白天阴天,室内光线柔和。', '日常生活,短时间阅读,适合轻度活动。'),
    new LightIntensityItem(100, 300, '适中', '白天多云,室内光线适中。', '工作学习,适度阅读,适合大部分室内活动。'),
    new LightIntensityItem(300, 500, '较亮', '白天晴朗,室内光线充足。', '正常工作学习,长时间阅读,适合大部分活动。'),
    new LightIntensityItem(500, 1000, '亮', '阴天室外,自然光较强。', '户外活动,注意防晒,适合户外休闲。'),
    new LightIntensityItem(1000, 100000, '爆表了', '夏季正午直射阳光,自然光极其强烈。',
      '尽可能避免直视太阳,户外活动需戴太阳镜,注意防晒。'),
  ];

  // 当组件即将出现时调用的方法
  aboutToAppear(): void {
    sensor.getSensorList((error: BusinessError) => { // 获取传感器列表
      if (error) { // 如果有错误
        console.error('获取传感器列表失败', error); // 打印错误信息
        return;
      }
      this.startLightIntensityUpdates(); // 没有错误则开始监听光强度变化
    });
  }

  // 开始监听环境光传感器的数据
  private startLightIntensityUpdates(): void {
    sensor.on(sensor.SensorId.AMBIENT_LIGHT, (data) => { // 监听环境光传感器
      console.info(`data.intensity: ${data.intensity}`); // 打印光强度值
      this.currentIntensity = data.intensity; // 更新当前光强度值
      for (const item of this.lightIntensityList) { // 遍历光强度列表
        if (data.intensity >= item.luxStart && data.intensity <= item.luxEnd) { // 判断当前光强度属于哪个范围
          this.currentType = item.type; // 更新当前光强度类型
          break;
        }
      }
    }, { interval: 10000000 }); // 设置传感器更新间隔,单位为纳秒(10000000纳秒=1秒)
  }

  // 组件构建方法
  build() {
    Column() { // 创建一个垂直布局容器
      Text("光强仪")// 显示标题
        .width('100%')// 设置宽度为100%
        .height(44)// 设置高度为44
        .backgroundColor("#fe9900")// 设置背景颜色
        .textAlign(TextAlign.Center)// 设置文本对齐方式为中心
        .fontColor(Color.White); // 设置字体颜色为白色

      Row() { // 创建一个水平布局容器
        Gauge({
          // 创建一个仪表组件
          value: this.currentIntensity > 1000 ? 1000 : this.currentIntensity, // 设置仪表值
          min: 0, // 最小值
          max: 1000 // 最大值
        }) { // 仪表内部布局
          Column() { // 创建一个垂直布局容器
            Text(`${Math.floor(this.currentIntensity)}`)// 显示当前光强度值
              .fontSize(25)// 设置字体大小
              .fontWeight(FontWeight.Medium)// 设置字体粗细
              .fontColor("#323232")// 设置字体颜色
              .height('30%')// 设置高度为父容器的30%
              .textAlign(TextAlign.Center)// 设置文本对齐方式为中心
              .margin({ top: '22.2%' })// 设置上边距
              .textOverflow({ overflow: TextOverflow.Ellipsis })// 设置文本溢出处理方式
              .maxLines(1); // 设置最大行数为1

            Text(`${this.currentType}`)// 显示当前光强度类型
              .fontSize(16)// 设置字体大小
              .fontColor("#848484")// 设置字体颜色
              .fontWeight(FontWeight.Regular)// 设置字体粗细
              .width('47.4%')// 设置宽度为父容器的47.4%
              .height('15%')// 设置高度为父容器的15%
              .textAlign(TextAlign.Center)// 设置文本对齐方式为中心
              .backgroundColor("#e4e4e4")// 设置背景颜色
              .borderRadius(5); // 设置圆角半径
          }.width('100%'); // 设置列宽度为100%
        }
        .startAngle(225) // 设置仪表起始角度
        .endAngle(135) // 设置仪表结束角度
        .height(250) // 设置仪表高度
        .strokeWidth(18) // 设置仪表边框宽度
        .description(null) // 设置描述为null
        .trackShadow({ radius: 7, offsetX: 7, offsetY: 7 }) // 设置阴影效果
        .padding({ top: 30 }); // 设置内边距
      }.width('100%').justifyContent(FlexAlign.Center); // 设置行宽度为100%并居中对齐

      Column() { // 创建一个垂直布局容器
        ForEach(this.lightIntensityList, (item: LightIntensityItem, index: number) => { // 遍历光强度类型数组
          Row() { // 创建一个水平布局容器
            Text(`${item.luxStart}~${item.luxEnd}Lux `)// 显示每个类型的光强度范围
              .fontSize('25lpx')// 设置字体大小
              .textAlign(TextAlign.Start)// 设置文本对齐方式为左对齐
              .fontColor("#3d3d3d")// 设置字体颜色
              .width('220lpx') // 设置宽度

            Text(`${item.description}\n${item.recommendation}`)// 显示每个类型的描述和建议活动
              .fontSize('23lpx')// 设置字体大小
              .textAlign(TextAlign.Start)// 设置文本对齐方式为左对齐
              .fontColor("#3d3d3d")// 设置字体颜色
              .layoutWeight(1) // 设置布局权重
          }.width('660lpx') // 设置行宽度
          .padding({ bottom: 10, top: 10 }) // 设置上下内边距
          .borderWidth({ bottom: 1 }) // 设置下边框宽度
          .borderColor("#737977"); // 设置下边框颜色
        });
      }.width('100%'); // 设置列宽度为100%
    }
    .height('100%') // 设置容器高度为100%
    .width('100%'); // 设置容器宽度为100%
  }
}

说明

该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。

该系统文章,我会尽量说的非常详细,做到不管新手、老手都能看懂。

说明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理系统。

友情提醒:本篇文章是属于系列文章,看该文章前,建议先看之前文章,可以更好理解项目结构。

qq群:801913255

有兴趣的朋友,请关注我吧(*^▽^*)。

关注我,学不会你来打我

前言

这篇文章有点长,内容丰富,如果你对该文章感兴趣,请耐心观看。

一、什么是路由守卫,它的作用是什么

什么是路由守卫:
它是控制路由菜单访问的一种机制,当一个用户点击一个路由菜单时,那么路由守卫就会对其进行“保护”,常见的守卫方式有

beforeEach:路由菜单访问前守卫。

afterEach:路由菜单访问后守卫。

路由守卫的作用:
了解什么是路由守卫后,其实我们大致可以得出它大致有以下作用。

1、身份认证:在进入模块之前,验证用户身份是否正确,列如:登录是否过期,用户是否登录等。

2、权限控制:控制用户、角色对应模块的访问权限。

3、日志记录:由于路由守卫能监控到用户对于模块访问前和访问后的动作,那么我们可以用来记录用户的访问日志等。

4、数据预加载:在很多时候,些许数据需要在我们访问页面前,加载完成。

5、路由动画:可以在路由访问前后,加载一个过渡动画,提高用户体验。

二、路由守卫的使用

在使用之前,我们需要安装状态管理库和状态持久化插件以及路由加载进度条。它可以共享程序中的一些状态。

1:安装npm install pinia  状态存储库

2:安装npm install pinia-plugin-persistedstate  状态持久化插件

3:安装 npm install nprogress  进度条插件

4:安装 npm install @types/nprogress

书接上一篇:
Vue3中菜单和路由的结合使用,实现菜单的动态切换

创建一个路由文件index.ts,存放在指定文件夹下,由于我写的是从0到1搭建框架,我放在了以下目录中

内容如下:

import { createRouter, createWebHashHistory, NavigationGuardNext, RouteLocationNormalized } from 'vue-router'import { routes }from './module/base-routes'import NProgressfrom 'nprogress'import'nprogress/nprogress.css'NProgress.configure({ showSpinner:false})const router =createRouter({
history: createWebHashHistory(),
//开发环境 routes
})
/**
路由守卫,访问路由菜单前拦截
* @param to 目标
* @param from 来至
*/router.beforeEach((to: RouteLocationNormalized,from: RouteLocationNormalized, next: NavigationGuardNext) =>{
NProgress.start();
if(to.meta.requireAuth) {
next();
}
else if (to.matched.length == 0) {
next({ path:
'/panel'})
}
else{
next();
}
})

router.afterEach(()
=>{
NProgress.done();
})

export
default router

代码解释:

router.beforeEach:就是每次在访问路由前,都会进入的方法,在该方法中,我添加了一个进度条和路由访问后的拍断。
router.afterEach:就是每次在访问路由后,都会进入的方法,在该方法中,添加了一个进度条结束的方法。

然后我们在main中,全局注册路由和状态管理

做好以上这些,路由守卫就完成。

如果,你是按照我的系列文章所搭建的前端框架,那么你要在以下2个文件中,做出改动(没有,请忽略)。

1、在HelloWorld.vue文件中把import router, { routes } from "../router/module/base-routes";替换成import  { routes } from "../router/module/base-routes"; 并加入import router from "../router/index";

2、在base-routes.ts文件中删除以下代码

//创建路由,并且暴露出去
const router =createRouter({
history: createWebHashHistory(),
//开发环境//history:createWebHistory(),//正式环境 routes
})
export
default router

做好这些,我们启动项目,检查每次点击路由菜单,是否进入路由守护拦截中。

明白的伙伴,请抓紧去填充你的内容吧。

三、请求拦截、响应拦截


我们OverallAuth2.0使用的是Vue3+.net8 WebApi创建的项目,所以我们会使用到后端接口。那么我们前端该如何和后端建立数据交互关系?建立关系会该如何处理返回信息?不要着急,耐心往下看。

首先要安装组合式api请求插件axios

安装命令:npm install axios

然后按照下图,新建文件及文件夹

http.ts文件内容如下

import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';//声明模型参数
type TAxiosOption ={
timeout: number;
baseURL:
string;
}
//配置赋值 const config: TAxiosOption ={
timeout:
5000,
baseURL:
"https://localhost:44327/", //本地api接口地址 }classHttp {
service;
constructor(config: TAxiosOption) {
this.service =axios.create(config)/*请求拦截*/ this.service.interceptors.request.use((config: InternalAxiosRequestConfig) =>{//可以在这里做请求拦截处理 如:请求接口前,需要传入的token debugger;returnconfig
}, (error: any)
=>{returnPromise.reject(error);
})
/*响应拦截*/ this.service.interceptors.response.use((response: AxiosResponse<any>) =>{
debugger;
switch(response.data.code) {case 200:returnresponse.data;case 500://这里面可以写错误提示,反馈给前端 returnresponse.data;case 99991:returnresponse.data;case 99992:returnresponse.data;case 99998:returnresponse.data;default:break;
}
}, (error: any)
=>{returnPromise.reject(error)
})
}
/*GET 方法*/ get<T>(url: string, params?: object, _object = {}): Promise<any>{return this.service.get(url, { params, ..._object })
}
/*POST 方法*/post<T>(url: string, params?: object, _object = {}): Promise<any>{return this.service.post(url, params, _object)
}
/*PUT 方法*/put<T>(url: string, params?: object, _object = {}): Promise<any>{return this.service.put(url, params, _object)
}
/*DELETE 方法*/delete<T>(url: string, params?: any, _object = {}): Promise<any>{return this.service.delete(url, { params, ..._object })
}
}

export
default new Http(config)

以上代码关键点都有注释说明

user.ts中的内容如下

import Http from '../http';

export
const TestAutofac =function () {return Http.get('/api/SysUser/TestAutofac');
}

这个就是我们之前搭建后端框架,演示示例的接口地址。

做完以上工作,整个响应拦截和请求拦截的基本代码编写完成,接下来就是测试。

四、测试

在用户界面添加以下代码(也可以新建vue页面)

<template>
  <div>用户</div>
</template>

<script lang="ts">import { defineComponent, onMounted }from "vue";
import { TestAutofac }
from "../../api/module/user";
export
defaultdefineComponent({
setup() {
//初始加载 onMounted(() =>{
TestAutofacMsg();
});
//调用接口 const TestAutofacMsg = async () =>{var result = awaitTestAutofac();
console.log(result);
};
return{};
},
components: {},
});
</script>

点击用户菜单,测试请求拦截是否成功

拦截成功,但出现如下错误(跨域问题)

上面问题是跨域问题导致,我们要在接口端配置跨域地址。

打开之前我们的后端框架,创建如下文件CrossDomainPlugIn.cs,路径和jwt鉴权,全局异常捕获插件放在同一个位置。

 /// <summary>
 ///跨域配置插件/// </summary>
 public static classCrossDomainPlugIn
{
/// <summary> ///跨域/// </summary> /// <param name="services"></param> public static void InitCors(thisIServiceCollection services)
{
//允许一个或多个来源可以跨域 services.AddCors(options =>{
options.AddPolicy(
"Access-Control-Allow-Origin", policy =>{var result = AppSettingsPlugIn.GetNode("CustomCorsPolicy:WhiteList").Split(',');//设定允许跨域的来源,有多个可以用','隔开 policy.WithOrigins(result)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
}
}

然后再Program.cs文件中,添加

//跨域配置
builder.Services.InitCors();和app.UseCors("Access-Control-Allow-Origin");代码

appsettings.json配置中添加如下配置,地址是前端访问地址

/*跨越设置*/
"AllowedHosts": "*",
"CustomCorsPolicy": {
"WhiteList": "http://localhost:8080"
},

启动后端接口,在测试下

以上就是本篇文章的全部内容,感谢耐心观看

后端WebApi
预览地址:http://139.155.137.144:8880/swagger/index.html

前端vue 预览地址:http://139.155.137.144:8881

关注公众号:发送【权限】,获取前后端代码

有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。

关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界