2024年10月


title: Nuxt.js 应用中的 modules:before 事件钩子详解
date: 2024/10/15
updated: 2024/10/15
author:
cmdragon

excerpt:
modules:before 是 Nuxt.js 中一个重要的生命周期钩子,在 Nuxt 应用初始化期间被触发。该钩子允许开发者在安装用户定义的模块之前执行某些操作,如配置或环境设置。

categories:

  • 前端开发

tags:

  • Nuxt.js
  • 生命周期
  • 钩子
  • 初始化
  • 模块
  • 配置
  • 环境设置


image
image

扫描
二维码
关注或者微信搜一搜:
编程智域 前端至全栈交流与成长

modules:before
是 Nuxt.js 中一个重要的生命周期钩子,在 Nuxt 应用初始化期间被触发。该钩子允许开发者在安装用户定义的模块之前执行某些操作,如配置或环境设置。


目录

  1. 概述
  2. modules:before 钩子的详细说明
  3. 具体使用示例
  4. 应用场景
  5. 注意事项
  6. 关键要点
  7. 总结


1. 概述

modules:before
钩子为开发者提供了一种机制,使他们能够在用户模块安装之前,修改或配置 Nuxt
应用。这确保了一些必要的设置可以在模块开始加载之前完成,避免潜在的问题。

2. modules:before 钩子的详细说明

2.1 钩子的定义与作用

  • 定义
    :
    modules:before
    是 Nuxt 生命周期的一部分,用于在用户模块被安装前的初始化阶段执行。
  • 作用
    : 允许开发者为模块设置全局配置,添加自定义功能或进行必要的环境准备。

2.2 调用时机

  • 执行环境
    : 这个钩子可在服务器端和客户端执行。
  • 挂载时机
    : 当 Nuxt 应用正在初始化并准备加载用户模块时,
    modules:before
    钩子会被调用。

2.3 返回值与异常处理

  • 返回值: 该钩子没有返回值。
  • 异常处理: 在钩子中发生的异常应当被捕获并处理,以防影响应用的初始化过程。

3. 具体使用示例

3.1 基础用法示例

下面的示例展示了如何在
modules:before
钩子中设置全局配置:

// plugins/modulesBeforePlugin.js
export default defineNuxtPlugin((nuxtApp) => {
    nuxtApp.hooks('modules:before', () => {
        console.log('Modules initialization is about to begin.');

        // 例如,设置一个全局环境变量
        process.env.MY_CUSTOM_VARIABLE = 'some value';
    });
});

在这个示例中,您会在模块初始化前输出一条日志并设置一个环境变量。

3.2 与其他钩子结合使用

modules:before
钩子可以与其他钩子结合,以实现更复杂的初始化逻辑:

// plugins/modulesBeforePlugin.js
export default defineNuxtPlugin((nuxtApp) => {
    nuxtApp.hooks('modules:before', () => {
        console.log('Preparing for module initialization.');

        // 设置数据库连接或其他配置
        configureDatabase();
    });

    nuxtApp.hooks('modules:done', () => {
        console.log('All modules have been initialized.');
    });
});

在这个例子中,我们在模块初始化之前配置数据库连接,并在模块初始化完成后记录日志。

4. 应用场景

  1. 全局配置
    : 在用户模块加载之前设置全局配置。
  2. 环境准备
    : 初始化一些依赖或环境变量,以确保后续模块加载顺利。
  3. 调试信息
    : 输出初始化过程中的调试信息,便于后续排查问题。

5. 注意事项

  • 顺序依赖
    : 如果某些模块依赖于全局配置,请确保在这之前注册信息。
  • 性能考虑
    : 尽量避免在钩子中进行大量耗时操作,以免影响应用启动速度。
  • 异常处理
    : 任何在该钩子中发生的异常都应在逻辑中妥善处理,以避免中断初始化流程。

6. 关键要点

  • modules:before
    钩子在用户模块安装前被调用,允许进行重要的初始化配置。
  • 合理使用此钩子可以提高应用的配置灵活性和稳定性。
  • 与其他钩子的配合使用可以实现更加复杂的初始化逻辑。

7. 总结

modules:before
钩子是 Nuxt.js 中一个强大而灵活的功能,允许开发者在用户模块加载之前进行必要的设置和初始化操作。通过合理利用这一钩子,可以提高应用的可维护性和性能。

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:
编程智域 前端至全栈交流与成长
,阅读完整的文章:
Nuxt.js 应用中的 modules:before 事件钩子详解 | cmdragon's Blog

往期文章归档:

队列也是一种操作受限的线性数据结构,与栈很相似。

01
、定义

栈的操作受限表现为只允许在队列的一端进行元素插入操作,在队列的另一端只允许删除操作。这一特性可以总结为先进先出(First In First Out,简称FIFO)。这意味着在队列中第一个加入的元素将第一个被移除。

入队
:向队列中添加新元素的行为叫做入队;

出队
:从队列中移除元素的行为叫做出队;

队头
:在队列中允许进行元素移除行为的一端称为队头;

队尾
:在队列中运行进行元素添加行为的一端称为队尾;

空队列
:当队列中没有元素时称为空队列。

满队列
:当队列是有限容量,并且容量已用完,则称为满队列。

队列容量
:当队列是有限容量,队列容量表示队列可以容纳的最大元素数量。

队列大小
:表示当前队列中的元素数量。

02
、分类

队列根据存储方式和功能特性有两种分类方式。

1、根据存储方式分类

队列是逻辑结构,因此以存储方式的不同可以分为顺序队列和链式队列。

顺序队列就是所有的队列元素都存储在连续的地址空间中,因此可以通过数组来实现顺序队列,因为数组的特性也导致顺序队列容量是固定的,不易扩容,这也导致容易浪费空间,同时还要注意元素溢出等问题。

链式队列顾名思义就是采用链式方式存储,可以通过链表实现,因此链式队列可以做到无限扩容,大大的提高了内存利用率。

2、根据功能特性分类

根据功能特性可以分类出很多专有队列,下面我们列举几个简单介绍一下。

阻塞队列
:当空队列时会阻塞出队操作,当满队列时会阻塞入队操作。

优先队列
:队列中每个元素都有一个优先级别属性,而元素的出队操作取决于这个优先级别属性,即优先级别高则优先出队。

延迟队列
:队列中每个元素都标记一个时间记录,元素只有在指定的延时时间后才会触发出队操作。

循环队列
:当使用数组实现队列时,可以通过把队头和队尾相连接,即当队尾到达数组的尾端可以“绕回”数组的开头,通过这种巧妙的设计来提高数组空间利用率。

双端队列
:是一种两端都可以进行入队和出队操作的数据结构。

根据这些队列特性,在不同的场景中可以起到意想不到的效果。

下面我们将顺序队列和链式队列的实现进行详细讲解。

03
、实现(顺序队列)

下面我们借助数组来实现顺序队列,其核心思想是把数组的起始位置作为队头,把数组尾方向作为队尾。当发生出队行为时,需要把剩余所有数据向队头方向移动一位,为什么要做这步操作呢?

首先顺序队列内部是数组,假设数组内可以存放7个元素,此时数组已存满,因此不可以再进行添加新元素入队操作了,然后我们对数组头元素进行出队操作,此时数组因为出队会留下一个空位,如下图。

那么此时是否可以进行入队操作呢?直接告诉我们应该可以,因为数组头已经有空位了。但是我们约定了队列只能从数组尾进行入队操作,而此时数组尾并没有空位提供给新入队的元素,因此实际上无法进行入队操作。

那要如何处理呢?最简单的方法就是当发生出队操作时,后面所有的元素都向着队头方向移动一位,把队尾空出一位,这每出一个元素就可以入一个元素。

当然这也不是唯一方案,还是通过循环队列解决这一问题,有兴趣的可以研究一下。

1、ADT定义

我们首先来定义顺序队列的ADT。

ADT Queue{

数据对象:D 是一个非空的元素集合,D = {a1, a2, ..., an},其中 ai 表示队列中的第i个元素,n是队列的长度。

数据关系:D中的元素通过它们的索引(位置)进行组织,索引是从0到n-1的整数,并且遵循元素先进先出的原则。

基本操作:[

Init(n) :初始化一个指定容量的空队列。

Capacity:返回队列容量。

Length:返回队列长度。

Head:返回队头元素,当为空队列则报异常。

Tail:返回队尾元素,当为空队列则报异常。

IsEmpty():返回是否为空队列。

IsFull():返回是否为满队列。

Enqueue():入队即添加元素,当为满队列则报异常。

Dequeue():出队即返回队头元素并把其从队列中移除,当为空队列则报异常。

]

}

定义好队列ADT,下面我们就可以开始自己实现的队列。

2、初始化Init

首先定义3个变量用于存放队列元素数组、队列容量以及队尾索引,而没有定义队头索引是因为队头索引永远等于0。

初始化结构主要做几件事。

  • 初始化队列的容量;

  • 初始化存放队列元素数组;

  • 初始化队尾索引;

具体实现代码如下:

//存放队列元素
private T[] _array;
//队列容量
private int _capacity;
//队尾索引,为-1表示空队列
private int _tail;
//初始化队列为指定容量
public MyselfQueueArray<T> Init(int capacity)
{
    //初始化队列容量为capacity
    _capacity = capacity;
    //初始化指定长度数组用于存放队列元素
    _array = new T[_capacity];
    _tail = -1;
    //返回队列
    return this;
}

3、获取队列容量 Capacity

这个比较简单直接把队列容量私有字段返回即可。

//队列容量
public int Capacity
{
    get
    {
        return _capacity;
    }
}

4、获取队列长度 Length

我们并没有定义队列长度的私有字段,因为队尾索引即表示数组最后一个元素索引,即可以代表队列长度,因此只需用队尾索引加1即可得到队列长度,同时需要注意判断队列是否为空,如果为空则报错。

//队列长度
public int Length
{
    get
    {
        if (IsEmpty())
        {
            return 0;
        }
        //队列长度等于队尾索引加1
        return _tail + 1;
    }
}

5、获取队头元素 Head

基于我们上面的约定,队头元素永远对应数组的第一个元素,因此可以直接获取索引为0的数组元素。空队列则报错。具体代码如下:

//获取队头元素
public T Head
{
    get
    {
        if (IsEmpty())
        {
            //空队列,不可以进行获取队头元素操作
            throw new InvalidOperationException("空队列");
        }
        return _array[0];
    }
}

6、获取队尾元素 Tail

因为我们定义了队尾索引私有变量,因此可以直接通过队尾索引获取。具体代码如下:

//获取队尾元素
public T Tail
{
    get
    {
        if (IsEmpty())
        {
            //空队列,不可以进行获取队头元素操作
            throw new InvalidOperationException("空队列");
        }
        return _array[_tail];
    }
}

7、获取是否空队列 IsEmpty

是否空队列只需判断队尾索引是否小于0即可。

//是否空队列
 public bool IsEmpty()
 {
     //队尾索引小于0表示空队列
     return _tail < 0;
 }

8、获取是否满队列 IsFull

是否满队列只需判断队尾索引是否与队列容量减1相等,代码如下:

//是否满队列
public bool IsFull()
{
    //队头索引等于容量大小减1表示满队列
    return _tail == _capacity - 1;
}

9、入队 Enqueue

入队只需向队列内部数组尾添加一个新元素即可,因此先把队尾索引先后移动一位,然后再把新元素赋值给队尾元素,同时还需要检查是否为满队列,如果是满队列则报错,具体实现代码如下:

//入队
public void Enqueue(T value)
{
    if (IsFull())
    {
        //满队列,不可以进行入队列操作
        throw new InvalidOperationException("满队列");
    }
    //队尾索引向后移动1位
    _tail++;
    //给队尾元素赋值新值
    _array[_tail] = value;
}

10、出队 Dequeue

出队则大致分为以下几步:

  • 判断是否为空队列,空队列则报错;

  • 取出队头元素暂存,重置队头元素为默认值;

  • 把队头后面所有元素向队头方向移动一位;

  • 重置队尾元素为默认值;

  • 队尾索引向队头方向移动一位,即队尾索引减1;

  • 返回暂存的队头元素;

具体实现代码如下:

//出队
public T Dequeue()
{
    if (IsEmpty())
    {
        //空队列,不可以进行出队列操作
        throw new InvalidOperationException("空队列");
    }
    //取出队头元素
    var value = _array[0];
    //对头元素重置为默认值
    _array[0] = default;
    //队头元素后面所有元素都向队头移动一位
    for (int i = 0; i < _tail; i++)
    {
        _array[i] = _array[i + 1];
    }
    //队尾元素重置为默认值
    _array[_tail] = default;
    //队尾索引向队头方向移动一位
    _tail--;
    //返回队头元素
    return value;
}

04
、实现(链式队列)

我们借助链表来实现链式队列,其核心思想是把链表尾节点作为队尾,把链表首元节点作为队头。

1、ADT定义

相对于顺序队列的ADT来说,链式队列的ADT少了两个方法即获取队列容量和是否满队列,这也是链表特性带来的好处。

2、初始化Init

首先需要定义链表节点类,包含两个属性数据域和指针域。

然后需要定义3个变量用于存放队头节点、队尾节点以及队列长度。

而初始化结构主要初始化3个变量初始值,具体实现如下:

public class MyselfQueueNode<T>
{
    //数据域
    public T Data;
    //指针域,即下一个节点
    public MyselfQueueNode<T> Next;
    public MyselfQueueNode(T data)
    {
        Data = data;
        Next = null;
    }
}
public class MyselfQueueLinkedList<T>
{
    //队头节点即首元节点
    private MyselfQueueNode<T> _head;
    //队尾节点即尾节点
    private MyselfQueueNode<T> _tail;
    //队列长度
    private int _length;
    //初始化队列
    public MyselfQueueLinkedList<T> Init()
    {
        //初始化队头节点为空
        _head = null;
        //初始化队尾节点为空
        _tail = null;
        //初始化队列长度为0
        _length = 0;
        //返回队列
        return this;
    }
}

3、获取队列长度 Length

这个比较简单直接把队列长度私有字段返回即可。

//队列长度
public int Length
{
    get
    {
        return _length;
    }
}

4、获取队头元素 Head

获取队头元素可以通过队头节点数据域直接返回,但是要注意判断队列是否为空队列,如果为空队列则报异常。具体代码如下:

//获取队头元素
public T Head
{
    get
    {
        if (IsEmpty())
        {
            //空队列,不可以进行获取队头元素操作
            throw new InvalidOperationException("空队列");
        }
        //返回队头节点数据域
        return _head.Data;
    }
}

5、获取队尾元素 Tail

获取队尾元素可以通过队尾节点数据域直接返回,但是要注意空栈则报异常。具体代码如下:

//获取队尾元素
public T Tail
{
    get
    {
        if (IsEmpty())
        {
            //空队列,不可以进行获取队尾元素操作
            throw new InvalidOperationException("空队列");
        }
        //返回队尾节点数据域
        return _tail.Data;
    }
}

6、获取是否空队列 IsEmpty

是否空队列只需判断队头节点和队尾节点是否都为空即可。

//是否空队列
public bool IsEmpty()
{
    //队头节点为null和队尾节点都为空表示空队列
    return _head == null && _tail == null;
}

7、入队 Enqueue

入队大致分为以下几步:

  • 需要先创建一个新节点;

  • 如果原队尾节点不为空,则把原队尾节点指针域指向新节点;

  • 把原队尾节点更新为新节点;

  • 如果队头节点为空,则说明这是第一个元素,所以队头和队尾都是同一个节点,因此要把队尾节点赋值给队头节点;

  • 队列长度加1;

具体实现代码如下:

//入队
public void Enqueue(T value)
{
    //创建新的队尾节点
    var node = new MyselfQueueNode<T>(value);
    //如果队尾节点不为空,则把新的队尾节点连接到尾节点后面
    if (_tail != null)
    {
        _tail.Next = node;
    }
    //队尾节点变更为新的队尾节点
    _tail = node;
    //如果队头节点为空,则为其赋值为队尾节点
    if (_head == null)
    {
        _head = _tail;
    }
    //队列长度加1
    _length++;
}

8、出队 Dequeue

出队则大致分为以下几步:

  • 判断是否空队列,空队列则报错;

  • 获取队头节点数据域暂存;

  • 更新头节点为原队头节点对应的下一个节点;

  • 如果队头节点为空,则说明为空队列,队尾节点也要置空;

  • 队列长度减1;

  • 返回暂存的队头节点数据;

具体实现代码如下:

//出队
public T Dequeue()
{
    if (IsEmpty())
    {
        //空队列,不可以进行出队列操作
        throw new InvalidOperationException("空队列");
    }
    //获取队头节点数据
    var data = _head.Data;
    //把队头节点变更为原队头节点对应的下一个节点
    _head = _head.Next;
    //如果队列为空,表明为空队列,同时更新队尾为空
    if (_head == null)
    {
        _tail = null; 
    }
    //队列长度减1
    _length--;
    //返回队头节点数据
    return data;
}


:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。
https://gitee.com/hugogoos/Planner

作为一名 90 后,我对口袋妖怪(宝可梦)游戏有着特殊的感情,满满的都是回忆。如果你也喜欢宝可梦主题的游戏,这款开源的宝可梦自走棋游戏 pokemonAutoChess 一定要试试,它采用战棋(自走棋)玩法,玩家可以将小精灵作为“棋子”布置在战场上,然后它们会自动进行战斗。你只需运筹帷幄,考虑如何选择宠物、分配物品和站位的策略,轻松惬意且好玩。

上周好玩的开源项目还有不少,上榜的还有让数学动起来的 Python 框架 Manim,能够制作清晰、直观地展示抽象的数学概念的动画。该项目最初是由 Grant Sanderson 发起的个人开源项目,起因是想锻炼自己的编程技巧,并用它制作数学视频,经过不断的开发和迭代,目前已获得 6.6w 星(3b1b/manim)。本期收录的是更稳定、更活跃的开源社区版(ManimCommunity/manim)。如果你厌倦了单调的浏览器新标签页,不妨试试 materialYouNewTab 插件,为你的浏览器注入新活力!

最后,可离线部署的翻译 API 服务 LibreTranslate、美观的投资组合追踪工具 Wealthfolio 和全天候录制屏幕的 AI 助手,它们全都是离线优先的本地应用,保护你的个人隐私。

  • 本文目录
    • 1. 热门开源项目
      • 1.1 宝可梦自走棋游戏:pokemonAutoChess
      • 1.2 让数学动起来的 Python 框架:Manim
      • 1.3 极简的新标签页插件:materialYouNewTab
      • 1.4 美观的投资组合追踪工具:Wealthfolio
      • 1.5 全天候录制屏幕的 AI 助手:screenpipe
    • 2. HelloGitHub 热评
      • 2.1 用 Go 生成样式美观的 PDF 文件:Maroto
      • 2.2 可离线部署的翻译 API 服务:LibreTranslate
    • 3. 结尾

1. 热门开源项目

1.1 宝可梦自走棋游戏:pokemonAutoChess

主语言:TypeScript

Star:700

周增长:300

这是一款基于宝可梦(Pokemon)主题的在线自走棋游戏。它免费、开源、非盈利,结合了宠物小精灵和自走棋玩法,玩家可以选择不同的神奇宝贝和策略进行对战,支持多人对战、物品、进化和排名等机制,增加了游戏的可玩性。

GitHub 地址→
github.com/keldaanCommunity/pokemonAutoChess

1.2 让数学动起来的 Python 框架:Manim

主语言:Python

Star:23k

周增长:2k

该项目是用于创建数学动画的开源引擎,能够清晰、直观地展示抽象的数学概念。它是一个强大的数学动画创作工具,可以通过编程生成各式各样的数学动画和可视化内容,支持丰富的几何图形、数学符号和动画效果,适用于教育视频、学术演示和科研展示。本项目是基于 3b1b/manim 项目的社区版,由开源社区维护和更新,相较于原项目更稳定、响应贡献更加及时。

from manim import *

class SquareToCircle(Scene):
    def construct(self):
        circle = Circle()
        square = Square()
        square.flip(RIGHT)
        square.rotate(-3 * TAU / 8)
        circle.set_fill(PINK, opacity=0.5)

        self.play(Create(square))
        self.play(Transform(square, circle))
        self.play(FadeOut(square))

# manim -p -ql example.py SquareToCircle

GitHub 地址→
github.com/ManimCommunity/manim

1.3 极简的新标签页插件:materialYouNewTab

主语言:JavaScript

Star:1.2k

这是一款受 Google 的 Material You 设计语言启发的新标签页浏览器插件,注重简约和实用,拒绝花里胡哨。它为用户提供了一个清新美观的浏览器新标签页,支持多种主题和实用工具。

GitHub 地址→
github.com/XengShi/materialYouNewTab

1.4 美观的投资组合追踪工具:Wealthfolio

主语言:TypeScript

Star:4k

周增长:200

该项目是采用 Tauri 框架开发的桌面投资追踪工具,拥有美观、易用的界面。它专注于投资数据存储和展示,不提供交易功能。财务数据保存在本地保护个人隐私,但用户需手动添加或导入交易信息(如买入、卖出、价格等),支持汇总收益、设置财务目标,并兼容多种货币和投资类型(股票、基金、加密货币)。

GitHub 地址→
github.com/afadil/wealthfolio

1.5 全天候录制屏幕的 AI 助手:screenpipe

主语言:Rust

Star:7.6k

周增长:600

这是一款开箱即用、可离线的桌面 AI 应用。它可以录制屏幕内容、捕获截图和音频,并将数据存储至本地数据库,结合 LLMs 的能力,实现自动记录、上下文感知的 AI 助手,支持中文 OCR、集成 Ollama 和 Llama 等功能。

GitHub 地址→
github.com/mediar-ai/screenpipe

2. HelloGitHub 热评

在本章节中,我们将为大家介绍本周 HelloGitHub 网站上的热门开源项目。同时,期待您与我们分享使用这些开源项目的心得与体验。

2.1 用 Go 生成样式美观的 PDF 文件:Maroto

主语言:Go

这一个 Go 语言开发的用于创建 PDF 文件的库,其灵感来源于 Bootstrap 框架。它允许你像使用 Bootstrap 创建网站一样,轻松编写和生成不同样式的 PDF 文件。

项目详情→
hellogithub.com/repository/0f9b1528f0e44db79222d01823373cdf

2.2 可离线部署的翻译 API 服务:LibreTranslate

主语言:Python

该项目是基于开源的离线翻译引擎 Argos Translate 构建的翻译 API 服务。它不依赖第三方翻译服务,可轻松自建翻译 API 服务,支持自动语言检测、API 密钥和访问频率限制等功能。

项目详情→
hellogithub.com/repository/a414dc09995f4b5188cf5acbe54c9107

3. 结尾

以上就是本期「GitHub 热点速览」的全部内容,希望你能够在这里找到自己感兴趣的开源项目,如果你有其他好玩、有趣的 GitHub 开源项目想要分享,欢迎来
HelloGitHub
与我们交流和讨论。

往期回顾

获取QQ邮箱授权码

打开QQ邮箱,进入
设置->账号
页面:
image


POP3/IMAP/SMTP
中开启
SMTP服务
,然后点击
授权码
复制授权码:
image

QQ邮箱服务器的参数如下,详细内容参考
SMTP/IMAP服务

  • 接收邮件服务器:
    imap.qq.com
    ,使用SSL,端口号993
  • 发送邮件服务器:
    smtp.qq.com
    ,使用SSL,端口号465或587

网易邮箱服务器的参数如下,详细内容参考
网易邮箱服务器参数如何设置?

image

安装 MailKit

在项目中安装 MailKit 库,可以通过NuGet包管理器安装它或者使用以下命令:

dotnet add package MailKit

MailKit
是在
MimeKit
之上构建的跨平台邮件客户端库,目标是成为 .NET 的最佳电子邮件框架。

配置邮件服务器信息

配置邮件服务器信息,包括主机、端口、用户名、密码等,封装成 EmailData 类:

/// <summary>
/// 邮件数据
/// </summary>
class EmailData
{
    /// <summary>
    /// 发件人
    /// </summary>
    public string From { get; set; }
    /// <summary>
    /// 授权码
    /// </summary>
    public string Password { get; set; }
    /// <summary>
    /// 收件人
    /// </summary>
    public string To { get; set; }
    /// <summary>
    /// 主题
    /// </summary>
    public string Subject { get; set; }
    /// <summary>
    /// 纯文本内容
    /// </summary>
    public string TextBody { get; set; }
    /// <summary>
    /// HTML内容
    /// </summary>
    public string HtmlBody { get; set; }

    /// <summary>
    /// 发送邮件服务器
    /// </summary>
    public HostInfo SMTP { get; set; }
    /// <summary>
    /// 接受邮件服务器
    /// </summary>
    public HostInfo IMAP { get; set; }
}
/// <summary>
/// 服务器信息
/// </summary>
class HostInfo
{
    /// <summary>
    /// 服务器地址
    /// </summary>
    public string Host { get; set; }
    /// <summary>
    /// 服务器端口
    /// </summary>
    public int Port { get; set; }
}

实现邮件收发方法

邮件收发方法如下,这里只接收最新的10封邮件便于实现交互逻辑:

static async Task SendEmail(EmailData data)
{
    try
    {
        // 创建一个新的 MIME 消息对象
        var message = new MimeMessage();

        // 设置发件人
        message.From.Add(MailboxAddress.Parse(data.From));

        // 设置收件人
        message.To.Add(MailboxAddress.Parse(data.To));

        // 设置主题
        message.Subject = data.Subject;

        // 设置正文
        message.Body = new BodyBuilder
        {
            TextBody = data.TextBody,
            HtmlBody = data.HtmlBody
        }.ToMessageBody();

        // 使用 SMTP 客户端发送邮件
        using (var client = new SmtpClient())
        {
            await client.ConnectAsync(data.SMTP.Host, data.SMTP.Port, SecureSocketOptions.StartTls);

            // 注:用户名和密码应妥善保管,不要硬编码到源码中
            await client.AuthenticateAsync(data.From, data.Password);

            // 发送邮件
            await client.SendAsync(message);

            // 断开与服务器的连接
            await client.DisconnectAsync(true);
        }

        Console.WriteLine("邮件已成功发送!");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"邮件发送失败:{ex.Message}");
    }
}

static async Task GetEmail(EmailData data)
{
    try
    {
        // 连接到 IMAP 服务器
        using (var client = new ImapClient())
        {
            await client.ConnectAsync(data.IMAP.Host, data.IMAP.Port, true); // 通常使用 TLS 加密

            // 认证用户
            await client.AuthenticateAsync(data.From, data.Password);

            // 选择收件箱
            var inbox = client.Inbox;
            await inbox.OpenAsync(FolderAccess.ReadOnly);

            // 获取邮件数量
            int totalMessages = inbox.Count;
            // 确保开始位置不会小于1
            int start = Math.Max(totalMessages - 5, 1); 
            int end = totalMessages;


            // 获取最新的10封邮件的信息
            var messages = inbox.Fetch(start, end, MessageSummaryItems.Envelope | MessageSummaryItems.UniqueId);

            // 遍历邮件信息并打印出来
            foreach (var summary in messages)
            {
                var uid = summary.UniqueId;
                var message = await inbox.GetMessageAsync(uid);
                Console.WriteLine($"Subject: {message.Subject}");
                Console.WriteLine($"From: {message.From}");
                Console.WriteLine($"To: {message.To}");
                Console.WriteLine($"Date: {message.Date}");
            }
            // 断开连接
            await client.DisconnectAsync(true);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"接收邮件失败:{ex.Message}");
    }
}

注意接受邮件时 message.Body 部分的内容需要特殊的解析规则,不能直接打印 ToString() 方法。

测试邮件收发

使用时关键信息替换成自己的:

static async Task Main(string[] args)
{
    var data = new EmailData
    {
        From = "qqqqqq@qq.com",
        Password = "**********",
        To = "qqqqqq@qq.com",
        Subject = "来自 .NET Core 的测试邮件",
        TextBody = "这是纯文本消息内容。",
        HtmlBody = "<h1>这是HTML消息</h1><p>这封邮件是通过MailKit从 .NET Core 发送的。</p>",
        SMTP = new HostInfo()
        {
            Host = "smtp.qq.com",
            Port = 587
        },
        IMAP = new HostInfo()
        {
            Host = "imap.qq.com",
            Port = 993
        }

    };

    await SendEmail(data);
    await GetEmail(data);
}

参考文章

TCP通讯简介

TCP(传输控制协议,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它确保数据包按顺序传输,并在必要时进行重传,以保证数据的完整性和准确性。TCP通过三次握手建立连接,通过四次挥手释放连接,确保通信双方在传输数据前已准备好,并在传输结束后正确关闭连接。TCP广泛应用于需要高可靠性的网络应用,如网页浏览、文件传输和电子邮件等。

Demo效果

启动两个应用,一个当服务端,一个当客户端。

开启服务端:

image-20241014112528767

开启客户端:

image-20241014112558142

客户端向服务端发送消息:

image-20241014112646168

服务端向客户端发送消息:

image-20241014112730780

Demo代码

启动服务端:

[RelayCommand]
private async Task StartServer()
{
    System.Net.IPAddress Ip = System.Net.IPAddress.Parse(IpAddress);
    _tcpServer = new TcpListener(Ip, Port);
    _tcpServer.Start();
    Message += "Server started. Waiting for a connection...\r\n";

    // 接受客户端连接
    _tcpServer_Client = await _tcpServer.AcceptTcpClientAsync();
    Message += "客户端已连接\r\n";

    // Handle client communication
    _ = HandleClientAsync(_tcpServer_Client);
}
private async Task HandleClientAsync(TcpClient client)
{
    var stream = client.GetStream();
    var buffer = new byte[1024];
    int bytesRead;

    while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
    {
        var message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
        Message+=$"Received from client: {message}\r\n";

        // Echo the message back to the client
        //var response = Encoding.UTF8.GetBytes($"Echo: {message}");
        //await stream.WriteAsync(response, 0, response.Length);
    }

    Message += "Client disconnected...\r\n";
    stream.Close();
}

启动客户端:

 [RelayCommand]
 private async Task StartClient()
 {
     System.Net.IPAddress Ip = System.Net.IPAddress.Parse(IpAddress);
     _tcpClient = new TcpClient();
     await _tcpClient.ConnectAsync(Ip, Port);
     Message += "Connected to server...\r\n";
     
     _ = HandleServerCommunicationAsync(_tcpClient);
 }
private async Task HandleServerCommunicationAsync(TcpClient client)
{
    var stream = client.GetStream();
    var buffer = new byte[1024];
    int bytesRead;

    while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
    {
        var message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
        Message += $"Received from server: {message}\r\n";
       
    }

    Message += "Disconnected from server...\r\n";
    stream.Close();
}

向服务端发消息:

 [RelayCommand]
 private async Task SendMessageToServer()
 {
     if (_tcpClient == null || !_tcpClient.Connected)
     {
         Message += "Not connected to server.\r\n";
         return;
     }

     var stream = _tcpClient.GetStream();
     var data = Encoding.UTF8.GetBytes(Text);
     await stream.WriteAsync(data, 0, data.Length);
     Message += $"Sent: {Text}\r\n";
 }

向客户端发消息:

[RelayCommand]
private async Task SendMessageToClient()
{
    if (_tcpServer_Client == null || !_tcpServer_Client.Connected)
    {
        Message += "Not connected to client.\r\n";
        return;
    }

    var stream = _tcpServer_Client.GetStream();
    var data = Encoding.UTF8.GetBytes(Text);
    await stream.WriteAsync(data, 0, data.Length);
    Message += $"Sent: {Text}\r\n";
}

全部代码已上传至https://github.com/Ming-jiayou/AvaloniaTCP。

希望通过我的点滴分享,能够让对Avalonia感兴趣的朋友,更快入门。