Laravel 学习笔记:发送邮件

在我最初的设计中一共有两个地方需要发送邮件:

  • 第一就是用户注册,发送邮件激活
  • 第二就是当用户订阅一个书单的时候,自动给所有订阅的用户发送带有附件的电子书到其设定的邮箱中

所以接下来就记录一下使用 Laravel 发送邮件。

在 Laravel 中发送邮件并不是那么复杂。Laravel 通过 Symfony Mailer 实现了一套非常简洁的邮件 API。

Laravel 中提供了很多种方式来发送邮件:

  • [[SMTP]] 直接配置 SMTP服务器
  • [[Mailgun]] 通过 Mailgun 等发送邮件的服务提供商
  • [[Postmark]]
  • [[Amazon SES]]
  • sendmail

env 配置文件中选择使用哪个邮件发送方式。

配置

Laravel 邮件服务可以通过 config/mail.php 来配置。每一个邮件配置都有一个唯一 transport,这也就意味着你的应用可以使用不同的服务来发送不同的邮件。比如说你的应用可以使用 Postmark 来发送交易邮件,然后使用 Amazon SES 来批量发送营销邮件。

mail 配置文件中可以发现 mailers 配置数组,这个数组中可以配置不同的邮件服务。default 用来指定默认的邮件发送服务。

因为默认生成的项目在 mail 配置中默认使用了 SMTP,然后使用来环境变量,所以我们可以在 .env 配置文件中配置:

MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=mygoogle@gmail.com
MAIL_PASSWORD=<your_password>
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=mygoogle@gmail.com
MAIL_FROM_NAME="${APP_NAME}"

在 example 中,Laravel 给我们提供了一个测试的 [[mailhog]] MTA,可以用来在本地进行调试。

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

创建邮件

创建类 BookSendMail 来模拟邮件发送。

php artisan make:mail BookSendMail

文件会创建到 app/Mail 目录下。

可以看到其中一个 build() 方法就是入口方法。

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class BookSendMail extends Mailable
{
    use Queueable, SerializesModels;

    public $detail;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($detail)
    {
        $this->detail = $detail;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->subject('This is subject')->view('mail.bookSendMail');
    }
}

创建 Blade View

接下来就是创建一个 Blade 模板文件,邮件的正文部分。

文件创建在 resources/views/mail/bookSendMail.blade.php

<!DOCTYPE html>  
<html lang="en">  
<head>  
 <title>name: </title>  
</head>  
<body>  
<h1></h1>  
<p></p>  
  
<p>Thank you</p>  
</body>  
</html>

Add Route

Route::get('send-mail', function () {
    $detail = [
        'title' => 'Mail from Laravel',
        'body' => 'This is a test mail from Laravel',
    ];

    Mail::to('demo@example.com')->send(new \App\Mail\BookSendMail($detail));

    dd("Email sent");
});

然后调用 http://localhost:8080/send-mail,即可。

如果本地用了官方的 Sail 启动,可以访问 http://localhost:8025/ Mailhog 的后台,就可以看到发送的邮件了。

如果测试没有问题,那就可以直接把正式的 SMTP 配置修改到 .env 文件中。

Failover 配置

你可以配置一个额外的邮件服务来防止你的主邮件服务宕机后造成的服务不可用,配置 failover 在主服务失败之后切换到备份邮件服务器。

为了实现这个目的需要在 mail 配置文件中使用 failover transport。failover 配置也需要是一个数组。

'mailers' => [
    'failover' => [
        'transport' => 'failover',
        'mailers' => [
            'postmark',
            'mailgun',
            'sendmail',
        ],
    ],
 
    // ...
],

一旦定义了 failover 邮件,你需要设置:

'default' => env('MAIL_MAILER', 'failover'),

产生 Mailables

在 Laravel 中,每一种类型的邮件可以用 mailable 类来表现,这个类保存在 app/Mail 文件夹中。

可以使用 make:mail 来产生:

php artisan make:mail OrderShipped

产生的 mailable 中最要的一个方法就是 build(),在这个方法中可以调用不同的方法,比如说 from, subject, view, attach 来定义邮件的形式。

配置发送者

使用 mailable 中的方法:

public function build()
{
    return $this->from('example@example.com', 'Example')
                ->view('emails.orders.shipped');
}

或者使用全局 from 配置,可以在 config/mail.php 中配置

'from' => ['address' => 'example@example.com', 'name' => 'App Name'],

定义 View

mailable 类的 build 方法中,你可以使用 view 方法来指定邮件内容。

每一个邮件都可以使用 Blade 模板引擎来渲染其内容。

public function build()
{
    return $this->view('emails.orders.shipped');
}

Blade 模板可以存放在 resources/views/emails 下。

发送带附件的邮件

可以使用 attach 方法:

public function build()
{
    return $this->view('emails.orders.shipped')
                ->attach('/path/to/file');
}

使用 Markdown 引擎 Mailables

如果想要使用 Markdown template ,可以使用 --markdown 选项:

php artisan make:mail OrderShipped --markdown=emails.orders.shipped

然后在 Mailable 中可以使用:

public function build()
{
    return $this->from('example@example.com')
                ->markdown('emails.orders.shipped', [
                    'url' => $this->orderUrl,
                ]);
}

Markdown mailables 使用 Blade components 和 Markdown 语法的组合,允许用户快速构建邮件内容:

@component('mail::message')
# Order Shipped
 
Your order has been shipped!
 
@component('mail::button', ['url' => $url])
View Order
@endcomponent
 
Thanks,<br>

@endcomponent

发送邮件方法

Mail facade 有一个 to 方法,接受一个邮件地址,或者一个用户实例,或者一组用户。

如果你传入一个对象,mailer 会自动使用其 emailname 属性来决定目的地址。

    public function store(Request $request)
    {
        $order = Order::findOrFail($request->order_id);
 
        // Ship the order...
 
        Mail::to($request->user())->send(new OrderShipped($order));
    }

发送邮件时指定邮件服务提供方,默认情况 Laravel 发送邮件会使用默认配置的 default,如果要指定 mailer 可以:

Mail::mailer('postmark')
        ->to($request->user())
        ->send(new OrderShipped($order));

reference


2022-03-29 laravel , email , sendmail , mailer , mailgun , smtp

DMARC 报告

在搭建了自己的邮件服务器之后,经常收到 Gmail 和 Outlook 的 Report,类似:

Report domain: example.com Submitter: google.com Report-ID: 73941XXXXX

Report Domain: example.com Submitter: protection.outlook.com Report-ID: 200aa9XXXXXXXXXX

所以再整理一下 DMARC 报告。

在之前介绍 DMARC 的文章中介绍过其中 ruaruf 两个配置的作用,这两个配置分别用来配置邮箱地址,用来接收 DMARC aggregate report 和 DMARC Failure report。

DMARC aggregate report

默认情况下,邮件服务提供商,比如 Gmail,Outlook 等等,会每隔 24 小时发送一次 DMARC aggregate report 到 rua 指定的邮件地址。每一份报告中都写明了一段时间内 DMARC 的验证次数和合规问题。

DMARC 报告中提供了该域名下邮件发送整体情况的表现。如果有人伪装该域名进行钓鱼邮件的发送,那么也会在报告中有所体现。

如果你是一个邮件服务器的管理员,你马上就会被收件箱中的邮件无数的 DMARC report 邮件所淹没。根据邮件的不同使用情况,每一天都会收到成百上千的 reports。

在附件里面是两份从 Google 和 Outlook 收到的 report。可以看到格式是 XML 格式,其中包含了验证的次数,验证的结果等等信息。

对比

下面是 DMARC aggregate 报告和 DMARC failure 报告的对比:

  • aggregate 报告提供一组邮件的聚合数据,failure 报告提供单一邮件的详细信息;
  • 要接收 aggregate 报告,设置 rua 标签;要接收 failure 报告,设置 ruf 标签;
  • aggregate 报告不是实时的:在缺省的情况下,这些报告被每天发送;failure 报告则在 DMARC 验证失败的情况下几乎被立即发送;
  • aggregate 报告使用 XML 格式,failure 报告使用普通文本格式;
  • aggregate 报告不包含可用来辨认个人的信息 (personally identifiable information, PII),比如接收者邮件地址;failure 报告包含 PII;
  • 所有支持 DMARC 的邮箱服务提供商都支持 aggregate 报告,然而仅有一些邮箱服务提供商支持 failure 报告。

附录

Google 的报告:

<?xml version="1.0" encoding="UTF-8" ?>
<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <email>noreply-dmarc-support@google.com</email>
    <extra_contact_info>https://support.google.com/a/answer/2466580</extra_contact_info>
    <report_id>73941XXXX1096975</report_id>
    <date_range>
      <begin>1650499200</begin>
      <end>1650585599</end>
    </date_range>
  </report_metadata>
  <policy_published>
    <domain>example.com</domain>
    <adkim>s</adkim>
    <aspf>s</aspf>
    <p>reject</p>
    <sp>reject</sp>
    <pct>100</pct>
  </policy_published>
  <record>
    <row>
      <source_ip>200.15.100.100</source_ip>
      <count>1</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim>
        <spf>pass</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>example.com</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>example.com</domain>
        <result>pass</result>
        <selector>dkim</selector>
      </dkim>
      <spf>
        <domain>example.com</domain>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
</feedback>

Outlook 的报告:

<?xml version="1.0"?>
<feedback xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <version>1.0</version>
  <report_metadata>
    <org_name>Outlook.com</org_name>
    <email>dmarcreport@microsoft.com</email>
    <report_id>200aa9xxxxxxxxxxxx16af8b6a8bbaf5</report_id>
    <date_range>
      <begin>1650499200</begin>
      <end>1650585600</end>
    </date_range>
  </report_metadata>
  <policy_published>
    <domain>example.com</domain>
    <adkim>s</adkim>
    <aspf>s</aspf>
    <p>reject</p>
    <sp>reject</sp>
    <pct>100</pct>
    <fo>0</fo>
  </policy_published>
  <record>
    <row>
      <source_ip>200.10.106.108</source_ip>
      <count>1</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim>
        <spf>pass</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <envelope_to>outlook.com</envelope_to>
      <envelope_from>example.com</envelope_from>
      <header_from>example.com</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>example.com</domain>
        <selector>dkim</selector>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>example.com</domain>
        <scope>mfrom</scope>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
</feedback>

2022-03-29 dmarc , email , spf , self-hosted , email-hosting , dns-record

Laravel 学习笔记:文件上传

上传文件是一个网页应用必不可少的一部分,这里就记录一下 Laravel 中如何上传,并展示图片。

拆分开来主要分为如下几个步骤:

  • 创建数据库 Model,用一个 Model 实体来保存上传图片的路径以及相关的 meta 信息
  • 添加 Controller 层用来处理保存图片逻辑,以及持久化的过程
  • 创建前端 Form 表单,并提交 POST 请求,提交图片

创建数据库 Model

首先使用 artisan 创建一个 Model 和 migration:

php artisan make:model Photo -m

这行命令会创建一个数据库 Migration 文件,在 database/migrations 下:

然后修改 migration 文件,创建数据库 schema:

public function up()
{
    Schema::create('photos', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('path');
        $table->timestamps();
    });
}

上面的操作便是创建了一张表叫做 photos,其中包含了 id, name, path 和时间四列

执行数据库变更,会根据数据库的配置自动创建表:

php artisan migrate

Controller 上传逻辑

创建 route,打开 web.php:

Route::get('upload-image', [UploadImageController::class, 'index']);
Route::post('save', [UploadImageController::class, 'save']);

然后创建 Controller

php artisan make:controller UploadImageController

内容:

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use App\Models\Image;
 
 
class UploadImageController extends Controller
{
    public function index()
    {
        return view('image');
    }
 
    public function save(Request $request)
    {
         
        $validatedData = $request->validate([
         'image' => 'required|image|mimes:jpg,png,jpeg,gif,svg|max:2048',
 
        ]);
 
        $name = $request->file('image')->getClientOriginalName();
 
        $path = $request->file('image')->store('public/images');
 
 
        $save = new Photo;
 
        $save->name = $name;
        $save->path = $path;
 
        $save->save();
 
        return redirect('upload-image')->with('status', 'Image Has been uploaded');
 
    }
}

store 方法就把图片保存到了 images 目录中。

为了安全起见,可以修改一下文件路径:

$filename= date('YmdHi').$file->getClientOriginalName();
$file-> move(public_path('public/Image'), $filename);

Blade View

resources/views 下创建 image.blade.php,其中最重要的 form 部分:

<div class="card-body">
    <form method="POST" enctype="multipart/form-data" id="upload-image" action="" >

        <div class="row">

            <div class="col-md-12">
                <div class="form-group">
                    <input type="file" name="image" placeholder="Choose image" id="image">
                @error('image')
                    <div class="alert alert-danger mt-1 mb-1"></div>
                @enderror
                </div>
            </div>

            <div class="col-md-12">
                <button type="submit" class="btn btn-primary" id="submit">Submit</button>
            </div>
        </div>     
    </form>

</div>

2022-03-28 laravel , file-upload

Laravel 学习笔记:开发环境搭建

Laravel 提供了多种安装方式:

  • 可以通过官方提供的 [[Laravel Sail]] 初始化环境,Laravel Sail 是一个轻量的命令行工具可以和 Docker 开发环境交互。这意味着如果要使用 Sail 本地需要安装 Docker 环境。
  • 通过 [[Composer]] 安装,Composer 是一个 PHP 环境下的依赖管理器工具
  • Laravel Installer

Laravel Sail

Sail 的核心是项目中的 docker-compose.yml 文件。

curl -s "https://laravel.build/example-app" | bash

cd example-app
 
./vendor/bin/sail up

设置 alias:

alias sail="bash vendor/bin/sail"

之后就可以直接使用 sail up 命令。

一旦应用启动之后,可以访问本地:http://localhost .

不过在这里,我不清楚为什么我本地的 80 端口始终无法访问,所以我只能按照 docker-compose.yml 中的配置,将 APP_PORT 修改成 8080 端口,才能正常访问到。

Laravel Sail 启动之后会自动拉取如下组件:

  • [[MySQL]]
  • [[Redis]]
  • [[mailhog]],mailhog 是一个本地开发测试的邮件服务器,非常方便在本地测试 SMTP
  • [[meilisearch]]
  • [[Selenium]]
  • Laravel 应用本身

邮件

Laravel Sail 中包含的还包含一个邮件测试服务器 MailHog ,该服务用于在本地开发期间拦截应用发送的所有邮件并提供一个 Web 界面在浏览器中预览这些邮件信息,方便测试和调试。

    mailhog:
        image: 'mailhog/mailhog:latest'
        ports:
            - '${FORWARD_MAILHOG_PORT:-1025}:1025'
            - '${FORWARD_MAILHOG_DASHBOARD_PORT:-8025}:8025'
        networks:
            - sail

MailHog 的 SMTP 服务器的默认端口是 1025。

Sail 运行时可以通过 http://localhost:8025 访问 MailHog Web 界面。

Sail 相关命令

启动

sail up

重新构建:

sail build --no-cache

执行 php 命令:

sail php --version

Composer

如果本地电脑已经安装了 PHP 和 Composer,可以直接通过 Composer 来初始化 Laravel 项目。一旦项目创建了,可以使用 Artisan CLI 的 serve 命令启动 Laravel 本地开发服务器。

composer create-project laravel/laravel example-app
 
cd example-app
 
php artisan serve

Installer

还可以通过 Laravel 安装器来初始化项目:

composer global require laravel/installer
 
laravel new example-app
 
cd example-app
 
php artisan serve

注意,这里需要将 laravel 加入系统的环境变量。

  • macOS: $HOME/.composer/vendor/bin
  • Windows: %USERPROFILE%\AppData\Roaming\Composer\vendor\bin
  • GNU / Linux Distributions: $HOME/.config/composer/vendor/bin or $HOME/.composer/vendor/bin

开发环境

当 Laravel 安装好之后,我本地使用 JetBrains 提供的 PhpStorm,然后安装了 Laravel Idea 插件,可以按照官网提供的方式来申请开源许可。


2022-03-26 laravel , php , dev , ide , phpstorm , jetbrains , docker

Laravel 21 天学习计划

从上周开始和朋友进行一个以 21 天为一个周期的计划,每个人都制定了一个 21 天的目标,从计划到完成每一个步骤都计分,最后按照打分给每个小伙伴奖励或者惩罚。

所以我从我的计划列表中搜寻了一下大致看了一些 TODO,很多细碎的任务大都花不了一两天时间,所以就思考了一下有没有什么目标适合这个时间段,后来发现最近自建的一些项目好像都是用一个框架写成的,比如有一个可以自建的 PT 站 [[UNIT3D]],比如非常轻量的论坛 [[Flarum]],还有 [[Koel]] 一个在线的音乐播放器,还有很多很多。所以想来 21 天可以用来熟悉一下这个框架,也可以用来快速实现一些想法。所以就有了这个开篇。

什么是 Laravel

[[laravel]] 是一个 PHP 编写的 Web 框架,如果类比的话,那 Laravel 之于 PHP,就像 Spring 生态之于 Java。

目标

我学习 Laravel 的目标有两个:

  • 至少可以看懂接触到的开源项目,如果有机会可以改动以满足自己的需求,比如 Flarum 的插件等
  • 二来可以用 Laravel 来快速实现一些想法,之前用 Python Web,一直没有好好学前端,所以界面非常糟糕,所以这一次可以借着一起顺便熟悉一下 Laravel 的 Blade 模板引擎,以及 [[TailwindCSS]],然后制作一个可以在线分享书单,并定时发送邮件的网站
    • 我平时用豆瓣使用比较多,我认为豆瓣中最最要的有两部分,一部分是其词条,另一部分是其用户,词条的存在可以让我不断的发现同导演同演员的其他作品,同作家的其他图书,而用户的存在可以让我以某一个话题发现同主题的内容,所以我非常喜欢豆列这个功能。
    • 然而豆瓣近两年的一系列行为让我非常不满意,删词条,不明理由封禁用户,让我非常不满意
    • 所以想借着学习 Laravel 的过程,实现一个简单的豆列
  • 豆列的功能包含:
    • 用户可以用其来组织一个书单
    • 如果其他用户订阅了该书单,那么有电子书的情况下,每隔两周都会自动将书单内容发送到用户自定义的邮箱中

计划

学习计划非常简单,我提前看了一下 Laravel 的官方文档,非常详细,几乎包括了这个框架用户要了解的所有内容。所以我按照官网的组织结构,列了一些重点。

[[laravel 21天挑战计划]]

  • 首先比较重要的就是搭建一个开发环境。
  • 其次就是先上手使用,通过编码了解其工作原理,一次请求是如何在其中流转的
    • [[2022-03-28-laravel-file-upload]]
    • [[2022-03-29-laravel-send-email Laravel 学习笔记:发送邮件]]
    • [[2022-03-30-laravel-queue]]
  • 在了解一定使用基础之后,去了解一些底层实现原理
    • [[Laravel 学习笔记 —— 服务容器]]
    • [[Laravel 服务提供器]]
    • [[Laravel Facades]]
  • 最后是如何部署上线

学习资源

官方:

网站教程

图书

准备和文档一起交叉阅读:

  • [[Laravel: Up & Running: A Framework for Building Modern PHP Apps]]

视频

YouTube 上有非常多的视频,但我认为现阶段我只需要注重在官方文档即可。如果实在遇到无法实践的再寻求 YouTube,比如之前不知道怎么调试 PHP,找了半天文字材料都不行之后才去寻求视频,毕竟视频材料还是效率比较低的。


2022-03-25 php , laravel , website , learning-note , notes , phpstorm , docker , sail

Warp 终端初体验

Warp 是一个 Rust 编写,使用 GPU 渲染的终端(terminal)应用。目标是提升开发者的效率。

最近 Warp 发布了新闻稿,筹集了 2300 万美元的资金全力用来构建这个终端。

它之前筹集了 600 万美元的种子轮融资,由 GV 领投,Neo 和 BoxGroup 参投。还筹集了 1700 万美元的 A 轮融资,由 Figma 的联合创始人兼首席执行官 Dylan Field 领投。由企业家主导的这一轮投资,其参与者包括 Elad Gil、LinkedIn 前首席执行官 Jeff Weiner 和 Salesforce 的联合创始人兼联合首席执行官 Marc Benioff。

Warp 想要解决的问题

简化配置

上手体验的过程也可以知道,Warp 基本上没什么配置,基本上所有的功能都是内建快捷键调用,对新手非常友好。

速度

早在之前的文章中我就 提到过 因为使用越来越多的 zsh 插件,导致每一次打开新的 Tab 终端都要有一个明显的停顿,虽然后来使用 zinit 优化了一下插件加载,但是还是会感觉到一小点的卡顿,希望 Warp 能解决这个问题吧。

通过终端自身的能力,可以精减掉一些 zsh 的插件。

更加智能

Warp 的创始人 Zach Lloyd 说过,走过任何一个开发人员的桌面,都会看到一个打开的终端,还有代码编辑器,VS Code 完成了代码编辑器的重新定义,Warp 就去重新定义终端。

Warp 内置的命令自动补全,还有尚且不是很完善的 AI 搜索都是朝着智能化的方向前进的。GitHub Copilot 已经让我非常惊讶其代码自动补全的能力,我相信 Warp 未来在 AI 自动补全方面也会令人耳目一新。

协作

Warp 的创始人在总结其过去 20 年的程序生涯的时候说过现存的终端存在的两个痛点:

  • 难用
  • 只能一个人用

想来和他自身作为 Google Docs 的开发有着非常大的关系。当前的任何桌面程序如果加上了多人协作都是一个非常大的想象空间。那为什么终端不行?

在 Warp 中用户可以共享自己的命令行,设置,和历史。

上手体验

可以点击 这里 下载体验。

登录不上问题

如果在国内,大概率会卡在登录的界面(在还没有开放 beta testing 的时候会卡在输入邀请码的界面)。

这个时候需要本地其一个 1080 端口的代理,然后使用代理来启动 Warp:

export HTTP_PROXY=http://127.0.0.1:1080
export HTTPS_PROXY=http://127.0.0.1:1080

/Applications/Warp.app/Contents/MacOS/stable

在 macOS 上推荐使用 Clash for windows

几个重要的快捷键

  • ctrl + shift + r 可以从社区或者自定义的命令中搜索并执行长命令
  • cmd + p 非常常见的 Command Palette,现在很多应用都使用这个涉及,我现在在用的 Obsidian 也是,可以调用出应用内置的命令
  • ctrl + r 搜索历史
  • cmd + d 切分窗口
  • ctrl + cmd + t 用来切换主题!

更多的快捷键可以在设置中查看。

自动补全

Warp 自带了命令的补全,效果还不错。

warp auto suggestions

Warp 提供了对于命令结果的快速复制,以及分享,点击 Share 可以非常轻松的把命令结果分享给别人。

warp command copy

比如对于我上面的 git --help 的结果可以到 这里查看

当然如果没有 Warp,我会在 Tmux 中用 Tmux 的复制粘贴来实现这个需要。

存在的问题

  • 一个终端竟然需要登录!当然我能理解这样的一个商业模式,毕竟这款终端可能未来会商业化,向用户收费,但如果要让我付费,可能我会去看看其带给我的体验是不是要好过目前的方案。并且前期在测试阶段为了不扩大 bug 可能产生的负面影响,所以在有限人群内测试都是可以理解的。
  • 隐私问题,就像评论中指出来的那样,Warp 集成了 Telemetry,并且也被指责收集过多用户数据。
  • Warp 破坏掉了我之前的 fzf 的一些操作,比如我之前设定 fe 就是 fuzzy find 当前目录,然后输入模糊搜索词,回车之后就会立即用 vim 编辑,但是在 Warp 中并不能用 fzf。

当然这可能和 fe 使用的 zsh 脚本有关系,有时间再看看怎么解决。

which fe
fe () {
    local files
    IFS=$'\n' files=($(fzf-tmux --query="$1" --multi --select-1 --exit-0))
    [[ -n "$files" ]] && ${EDITOR:-vim} "${files[@]}"
}

总结

Warp 不需要用户查看文档也可以有一个不错的上手体验,更不像其他终端那样需要一个非常复杂的配置文档才能将其调教得比较好用。

如果你看完这篇文章觉得 Warp.dev 还不错,可以点击 我的邀请1邀请2 来尝试一下。

相关联终端

  • 在 macOS 上我一直都用的 kitty 这样一款终端,通过简单的配置,日常用起来目前还没有遇到比较大的问题,配合 zsh, tmux 还算得上顺手
  • [[Mac 应用 iTerm2]] 是一款比较正统的终端
  • [[Mac 应用 alacritty 终端]] 也是一款 GPU 加速终端
  • [[hyper.is]] 一款基于 Electron 的终端
  • [[wezterm-terminal]] 是另一款 GPU 加速的终端,用 Rust 和 Lua 编写的,轻度体验了一下确实比较快,但是最近还没来得及深度体验。
  • fig.io 是一款可以嵌入到其他 IDE 内部使用的终端,也实现了命令的自动补全等等功能。

reference


2022-03-23 terminal , macos , warp , rust , gpu , command , iterm , tmux , vim , zsh

Ubuntu 上命令行设置时区

一台新的 Ubuntu 服务器通常时区可能不是想要的时区,可以通过如下步骤设定时区。

检查当前时区,在命令行下输入 date:

date

可以查看当前的时间。

输入 timedatectl 可以查看更具体的时区。

使用 timedatectl

修改为东八区北京时间。

sudo timedatectl set-timezone "Asia/Shanghai"

ln

也可以通过软链接来修改系统的时区,在 Linux 下 /etc/localtime 中定义了系统要使用的时区。正确的配置在 /usr/share/zonefine 目录中

mv /etc/localtime /etc/localtime-backup
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

修改完成后可以通过 timedatectl 来验证。


2022-03-22 ubuntu , server , timezone

什么是 DMARC

DMARC 是 Domain-based Message Authentication Reporting & Conformance 的缩写,是一个标准的电子邮件验证标准。1 DMARC 帮助邮箱管理员防止黑客或其他攻击者伪装(Spoofing)其组织和域名。Spoofing(伪装)是一种电子邮件攻击方式,攻击者通过伪装邮件地址中的 From 字段,来假装发件人。DMARC 会检查电子邮件是否来自邮件中声称的发送者。

DMARC 构建在 [[SPF]] 和 [[DKIM]] 基础之上,来防止域名欺诈。

之前提到过自建域名邮箱 的文章中就配置过 DMARC,这篇文章就对 DMARC 具体展开讲讲。

DMARC 是什么?

DMARC 建立在广泛使用的 SPF 和 DKIM 协议上, 并且添加了域名对齐检查和报告发送功能。这样可以有效保护域名免受钓鱼攻击。

来自 dmarc.org 的示意图:

为什么 DMARC 如此重要?

根据 dmarc.org 的说法:

随着社交网络和电子商务的繁荣,垃圾邮件发送者和钓鱼攻击发起者基于利益的原因,想要入侵用户的账户,破解用户的信用卡等。Email 的相对容易攻击的特性备受罪犯们的青睐。只是简单地把企业的 logo 嵌入到 email 中,就能获取用户的信任。

用户很难辨别一封假的 email,邮件提供商也很难判断哪些邮件有可能会伤害用户。邮件发送者基本上对邮件认证的问题一无所知,因为他们缺少合理的反馈机制。那些尝试部署 SPF 和 DKIM 的企业的进展非常慢,因为没有监督进度和除错的机制。

DMARC 解决了这些问题。它帮助 email 发送者和接收者来共同保护 email,避免了昂贵的入侵损失。

DMARC 记录是什么?

DMARC 记录作为 TXT 记录发布到 DNS 中。指示邮件收件服务当验证失败时,应当如何处理收到的邮件。

比如以下发布在域名 “sender.exampledomain.com” 上的 DMARC 记录:

v=DMARC1;p=reject;pct=100;rua=mailto:postmaster@exampledomain.com

在这个例子中表示发件人要求收件人如果遇到验证未通过的邮件,则完全拒绝,并且发送相关报告到 postmaster@exampledomain.com。如果发件人在测试配置,”reject” 可以被替换成 “quarantine”,表示验证未通过的 邮件将被隔离。

DMARC 记录标签

DMARC 记录使用可扩展的 “标签-值” 语法。

这是一个典型的 DMARC 记录:

v=DMARC1; p=none; rua=mailto:dmarc_report@example.com; ruf=mailto:dmarc_report@example.com; pct=100; adkim=s; aspf=s;

它由多个 key-value 标签组成。这些标签会告诉邮件服务提供商如何发送 DMARC 报告:

  • v  是 DMARC 协议版本,它的值必须是  DMARC1
  • p  是策略,策略将被应用到验证失败的邮件上,可以设置成 ‘none’, ‘quarantine’, 或者 ‘reject’
    • ‘none’ 如果接收服务器接收到的邮件没有通过 DMARC 校验,不会做任何操作,邮件会正常被递交
    • ‘quarantine’ 用来隔离可疑邮件,如果接收服务器收到的邮件没有通过验证,那么会把邮件单独隔离
    • ‘reject’ 告诉接收方拒收可疑邮件
  • rua  是一组用来接收报告的电子邮件地址(DMARC aggregate)
  • ruf  是一系列用来接受失败报告的电邮地址(DMARC failure)
  • pct  对失败邮件应用策略的百分比
  • adkim  制定 DKIM 的对齐策略,可选 sr
  • aspf  制定 SPF 的对齐策略,可选 sr

如果不想配置报告失败的邮箱,可以

v=DMARC1; p=none;, p=reject;, p=quarantine;

DMARC 策略

DMARC 策略是在 DMARC 记录中制定的 p 标签。它指示邮件服务提供商应该如何处理验证失败的邮件。

DMARC 策略可以取三个值中的一个:none (monitor), quarantine  和  reject。

– none: 邮件提供商对验证失败邮件不采取任何处理。这个模式用来收集 DMARC 报告。 – quarantine: 邮件提供商把验证失败邮件放到垃圾邮件文件夹。 – reject: 邮件提供商拒收验证失败邮件。

DMARC alignment options

DMARC 对齐策略。DMARC 通过检验 header 部分是否和发送的域名相匹配,这个动作称为 alignment,这个部分验证依赖 SPF 和 DKIM。

用户可以在 DMARC 中配置两种 alignment 模式,strictrelaxed。上面提到的 aspfadkim 选项就是这个作用。sr 分别是两个缩写。

验证方式 Strict Relaxed
SPF SPF 验证域名,和邮件 Header 中 From 一致 Header 中的 From 必须匹配域名或子域名
DKIM From 中的地址和 DKIM 配置一致 From 必须匹配 DKIM signature d= 后配置的域名或子域名

Envelope sender address: 是 Return-Path 头的邮件地址,收件人是看不到该地址的

From: 地址,收件人看到的地址

SPF alignment example

Envelop sender address Header From strict relaxed
admin@example.com admin@example.com Pass Pass
admin@mail.example.com admin@example.com Fail Pass
admin@example.com admin@example.org Fail Fail

DKIM alignment example

DKIM signature domain 中的 d= 后面部分。

From DKIM d= strict relaxed
admin@example.com example.com Pass Pass
admin@mail.example.com example.com Fail Pass
admin@example.org example.com Fail Fail

发布 DMARC 记录

发布 DMARC 记录需要你有权限修改域名的 DNS 记录。

在 DNS 记录后台,需要创建一条 DMARC 记录。

创建一条 TXT 记录:

Type: TXT Host: _dmarc TXT Value: (DMARC record generated above) TTL: 1 hour

以 Cloudflare 后台为例:

cloudflare dmarc

如果用其他的域名服务的话,界面类似。

保存更改。现在你已经在域名 yourdomain.com 上面成功发布 DMARC 记录了。

如果不知道怎么写 DMARC 记录也可以在这个网站 通过表单的形式生成。

reference


2022-03-20 dmarc , email , domain , spf , dkim

Go 语言学习笔记 4:Go 语言的控制语句

常见的语句:

  • condition,条件
  • for-loop,循环
  • goto,跳转(特殊)

statement vs expression

下文中使用 statement 和 expression 来表达一些语句的区别:

  • statement, 通常用来指代一个操作,可以是赋值操作,等等
  • expression 通常用来指代一个值,这个值可以是一个语句的返回,也可以是一个函数的返回

条件语句

通用:

if InitSimpleStatement; Condition {
    // do something
} else {
    // other
}
  • 和其他语言一样 else 是可选的
  • InitSimpleStatement 是可选的,必须是 Simple Statement,通常这部分是一个简单变量的定义或者赋值
  • Condition 必须是一个返回值是 boolean 值的 expression,这部分可以添加 () 或者不加。
  • 如果没有 InitSimpleStatement,那么后面的 ; 也是可选的

举例:

if a < 4 {
    return 0
} else {
    return 1
}

更复杂一些的:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())

    if n := rand.Int(); n%2 == 0 {
        fmt.Println(n, "is an even number.")
    } else {
        fmt.Println(n, "is an odd number.")
    }

    n := rand.Int() % 2 // this n is not the above n.
    if n % 2 == 0 {
        fmt.Println("An even number.")
    }

    if ; n % 2 != 0 {
        fmt.Println("An odd number.")
    }
}

switch-case

一种特殊的条件分支语句。

switch i {  
case 0:  
   fmt.Printf("0")  
case 1:  
   fmt.Printf("1")  
case 2:  
   fmt.Printf("2")  
case 3:  
   fallthrough  
case 4, 5, 6:  
   fmt.Printf("4,5,6")  
default:  
   fmt.Printf("Default")  
}

for loop

for 循环的格式:

for InitSimpleStatement; Condition; PostSimpleStatement {
    // do something
}
  • for 循环的语法和其他语言相差不多,但不需要 ()
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

几个变种:

var i = 0
for ; i < 10; {
    fmt.Println(i)
    i++
}
for i < 20 {
    // do something
    i++
}
for i := 0; ; i++ {
    if i >= 10 {
        break
    }
}

使用 for range 遍历 map:

for key, value := range oldMap {
    newMap[key] = value
}

goto 跳转语句和 Label 定义

Java 语言不支持 goto 关键字,但是 goto 是作为关键字保留了下来,但是 Java 中可以使用 label 关键字来达到 goto 的效果。

Go 语言支持 goto statement,goto 关键字必须跟一个 label 来表达跳转到的地方。

package main

import "fmt"

func main() {
    i := 0
Here: // declare a label
    fmt.Println(i)
    i++
    if i < 10 {
        goto Here // goto label
    }
}

需要注意的是 label 对于最里层的代码块是不可见的。

breakcontinue 关键字后面都可以接 label

defer 关键字

defer 关键字会推迟方法的执行,知道外层的方法返回之后再执行。

defer 的调用的参数会立即

A defer statement defers the execution of a function until the surrounding function returns.

The deferred call’s arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.

package main

import "fmt"

func main() {
    defer fmt.Println("world")

    fmt.Println("hello")
}

reference


2022-03-09 golang , programming , control-flow

充分利用 Oracle 机器避免被回收

最近收到 Oracle(甲骨文) 一封名为「Oracle Cloud Infrastructure Compute - Resource Maintenance Reminder」 的邮件,大致意思就是如果 Oracle Always Free 的机器使用率比较低的话,Oracle 就会判定 Compute Instances 处于 Idle (闲置)状态,Oracle 将保留回收实例的权力。

如何判定机器是处于闲置状态呢,在 Oracle 给出的官方文档 是这样描述的:

• CPU utilization for the 95th percentile is less than 10% ,95% 时间 CPU 利用率低于 10% • Network utilization is less than 10% ,网络利用率低于 10% •Memory utilization is less than 10% (applies to A1 shapes only),内存利用率低于 10%(仅适用于 A1 类型的 ARM 实例)

Oracle 给出的描述非常清晰,就目前我的理解,应该是只要上述三个条件中的一个满足了就会触发 Oracle 的回收机制,首先 Oracle 会发送一封警告邮件 Action Recommended,标题就是上面的。然后会告知用户,7 天之内如果没有满足上述标准就会回收实例。

2E8g

Oracle Cloud Infrastructure Customer,
Oracle Cloud Infrastructure (OCI) will be reclaiming idle Always Free compute resources from Always Free customers. Reclaiming idle resources allows OCI to efficiently provide services to Always Free customers. Your account has been identified as having one or more compute instances that have been idle for the past 7 days. These idle instances will be stopped one week from today, January 30,2023. If your idle Always Free compute instance is stopped, you can restart it as long as the associated compute shape is available in your region. You can keep idle compute instances from being stopped by converting your account to Pay As You Go (PAYG). With PAYG, you will not be charged as long as your usage for all OCI resources remains within the Always Free limits.

解决方案

因为我之前申请的两台 AMD 和一台 24 G 内存的 ARM,都不敢将生产的东西放在上面,怕得就是 Oracle 突然变卦,所以上面跑的程序都比较少,现在看起来 Oracle 至少在变卦之前还是会发出声明的,可以将一些 Docker 迁移到机器上跑跑了。

解决方案其实也比较简单,一个就是按照官网所提供的方式升级成付费账号,用多少支付多少(Pay As You Go (PAYG));另外一个方案就是真实地把机器利用起来,跑一些程序,或者将起作为 [[k3s]] 节点加入集群跑起来,看看满足其使用的最低要求。

说实话,网上有一些教程让用户禁用 oracle cloud agent(root 模式下执行 snap remove oracle-cloud-agent),禁止 Oracle 监控实例的运行状况。因为可以猜测的就是 Oracle 会在 VM 实例中跑监控程序,然后批量地监控用户机器的使用情况,包括 CPU,内存,网络等等,如果把这个 agent 卸载掉了是否 Oracle 就检测不到了。说实话我是不推荐这个方法的,因为 VM 运行在 Oracle 的机房,要检测还是能通过其他手段来检测到的。

所以个人还是推荐将 VM 好好利用起来,个人在过去尝试过的 Self-hosted 应用,非常不错的都在这里 管理了起来,也可以在 GitHub Awesome Self hosted 项目里面找找自己感兴趣的利用起来。

利用 lookbusy 生成虚拟负载

lookbusy 是一个可以虚拟产生 CPU、内存、磁盘等负载的工具。

lookbusy 常用的一些使用方法。

CPU 使用

  • -c: 每个 cpu 的期望利用率,以百分比为单位(默认 50 )。若选择 curve 使用模式,则应给出 MIN-MAX 形式的范围。
  • -n:保持忙碌的 CPU 数量(默认值:自动检测出的全部核心数)
  • -r: 第一种模式:fixed,保持指定百分比的 CPU 利用率,第二种模式:curve,CPU 利用率随给定区间上下浮动。
  • -p: 曲线内峰值利用率的偏移量,默认单位是秒,可以添加 m、h、d 作为其他单位
  • -P: 曲线内峰值利用率的偏移量,可以添加 m、h、d 作为其他单位

内存使用

  • -m: 以字节为单位,后面是 KB、MB 或 GB(其他单位)
  • -M: 单位为毫秒(默认值为 1000)

磁盘使用

  • -d:用于磁盘流失的文件大小(以字节为单位,后跟 KB、MB、GB 或 TB(其他单位))
  • -b:用于 I/O 的块大小(以字节为单位,后跟 KB、MB 或 GB)
  • -D:迭代之间的休眠时间,以毫秒为单位(默认值为 100)
  • -f: 要用作缓冲区的文件 / 目录的路径(默认 /tmp)

使用举例:

lookbusy -c 50 # 占用所有 CPU 核心各 50%
lookbusy -c 50 -n 2 # 占用两个 CPU 核心各 50%
lookbusy -c 50-80 -r curve # 占用所有 CPU 核心在 50%-80% 左右浮动
lookbusy -m 128MB -M 1000 # 每 1000 毫秒,循环释放并分配 128MB 内存
lookbusy -d 1GB -b 1MB -D 10 # 每 10 毫秒,循环进行 1MB 磁盘写入,临时文件不超过 1GB
nohup lookbusy -c 50 -D 10 > /dev/null 2>&1 & #占用所有 CPU 核心各 50%,且后台运行,不记录日志

在 Oracle ARM 机器上可以直接使用 docker-compose 启动。


2022-03-08 oracle , vps , self-hosted , docker , linux

电子书

本站提供服务

最近文章

  • Dinox 又一款 AI 语音实时转录工具 前两天介绍过 [[Voicenotes]],也是一款 AI 转录文字的笔记软件,之前在调查 Voicenotes 的时候就留意到了 Dinox,因为是在小红书留意到的,所以猜测应该是国内的某位独立开发者的作品,整个应用使用起来也比较舒服,但相较于 Voicenotes,Dinox 更偏向于一个手机端的笔记软件,因为他整体的设计中没有将语音作为首选,用户也可以添加文字的笔记,反而在 Voicenotes 中,语音作为了所有笔记的首选,当然 Voicenotes 也可以自己编辑笔记,但是语音是它的核心。
  • 音流:一款支持 Navidrom 兼容 Subsonic 的跨平台音乐播放器 之前一篇文章介绍了Navidrome,搭建了一个自己在线音乐流媒体库,把我本地通过 [[Syncthing]] 同步的 80 G 音乐导入了。自己也尝试了 Navidrome 官网列出的 Subsonic 兼容客户端 [[substreamer]],以及 macOS 上面的 [[Sonixd]],体验都还不错。但是在了解的过程中又发现了一款中文名叫做「音流」(英文 Stream Music)的应用,初步体验了一下感觉还不错,所以分享出来。
  • 泰国 DTV 数字游民签证 泰国一直是 [[Digital Nomad]] 数字游民青睐的选择地,尤其是清迈以其优美的自然环境、低廉的生活成本和友好的社区氛围而闻名。许多数字游民选择在泰国清迈定居,可以在清迈租用廉价的公寓或民宿,享受美食和文化,并与其他数字游民分享经验和资源。
  • VoceChat 一款可以自托管的在线聊天室 VoceChat 是一款使用 Rust(后端),React(前端),Flutter(移动端)开发的,开源,支持独立部署的在线聊天服务。VoceChat 非常轻量,后端服务只有 15MB 的大小,打包的 Docker 镜像文件也只有 61 MB,VoceChat 可部署在任何的服务器上。
  • 结合了 Google 和 AI 的对话搜索引擎:Perplexity AI 在日本,因为 SoftBank 和 Perplexity AI 开展了合作 ,所以最近大量的使用 Perplexity ,这一篇文章就总结一下 Perplexity 的优势和使用技巧。