2020年3月

不久前,我偶然发现了一个有趣的 Linux 项目。这个项目旨在为 Debian 和基于 Debian 的系统创建一个小型的现场版 CD,类似于 Puppy Linux 项目 。让我们看看 DebianDog 。

DebianDog 是什么?

正如字面所述,DebianDog “是一个被塑造成一个外观和动作都像 Puppy Linux 的小型 Debian 现场版 CD。没有改动 Debian 结构和方式,Debian 的文档对 DebianDog 来说是完全有效的。你可以使用 apt-get 或 synaptic 来访问所有的 Debian 存储库。”

对于那些不熟悉 Puppy Linux 的人来说,该项目是 “多个 Linux 发行版的一个集合,建立在相同的共享准则之上”。这些准则能够实现快速、小型(300 MB 或更少)、易于使用。这里有为支持 Ubuntu, Slackware 和 Raspbian 软件包构建的 Puppy Linux 变种。

DebianDog 和 Puppy Linux 之间最大的不同是,Puppy Linux 有其自己的软件包管理器 Puppy 软件包管理器 。如上所述,DebianDog 使用 Debian 软件包管理器及其软件包。甚至 DebianDog 网站也试图澄清这一点:“它不是 Puppy Linux,并且也与基于 Debian 的 Puppy 无关。”

为什么一些人使用 DebianDog ?

安装 DebianDog(或其任何衍生产品)的主要原因是让一台较旧电脑重焕新生。在 DebianDog 上的每个版本都有一个 32 位版本。它们还有更轻量级的桌面环境/窗口管理器,例如 OpenboxTrinity Desktop 环境。它们中大多数也都有一个 systemd 的可替代版本。它们也安装更轻的应用程序,例如 PCManFM

DebianDog 有哪些可用的变体版本?

尽管 DebianDog 在本系列中是第一个,但是该项目被称为 ‘Dog Linux’ ,提供各种基于Debian 和 Ubuntu的流行发行版的 ‘Dog 变体’。

DebianDog Jessie

DebianDog 的第一个(也是最初的)版本是 DebianDog Jessie。它有两个 32 位版本 ,一个使用 Joe’s Window Manager (JWM) 作为默认桌面环境,另一个使用 XFCE 作为默认桌面环境。systemd 和 sysvinit 都是可用的。它还有一个 64 位版本。DebianDog Jessie 基于 Debian 8.0(Jessie)。Debian 8.0 的支持将于 2020 年 6 月 30 日结束,因此安装需谨慎。

StretchDog

StretchDog 基于 Debian 9.0(Stretch)。它有 32 位和 64 位两种版本可用。Openbox 是默认的窗口管理器,但是我们可以切换到 JWM 。Debian 9.0 的支持将于 2022 年 6 月 30 日结束。

BusterDog

BusterDog 是很有趣的。它基于 Debian 10(Buster)。它不使用 systemd,反而像 AntiX 一样使用 elogind 。Debian 10.0 的支持将于 2024 年 6 月结束。

MintPup

MintPup 基于 Linux Mint 17.1。现场版 CD 仅有 32 位版本。你也可以使用 “apt-get 或 synaptic 访问 Ubuntu/Mint 的存储库”。考虑到 Mint 17 已经到了生命的尽头,这一版本必须避免使用。

XenialDog

XenialDog 有 32 位64 位 两种变种,基于 Ubuntu 16.04 LTS 。两个变种都使用 Openbox 作为默认版本,JWM 可选。Ubuntu 16.04 LTS 的支持将于 2021 年 4 月结束, 因此安装需谨慎。

TrinityDog

TrintyDog 有两种变体。一个基于 Debian 8 ,另一个基于 Debian 9 。两种变体都有 32 位版本,并且都使用 Trinity Desktop Environment ,以此得名。

TrinityDog

BionicDog

你应该能够从名称猜到。BionicDog 基于 Ubuntu 18.04 LTS。主要版本有 32 位和 64 位两个版本,Openbox 是默认的窗口管理器。这里也有一个使用 Cinnamon desktop) 的版本,并且只有 64 位版本。

BionicDog

结束语

我喜欢任何 想让较旧电脑系统有用的 Linux 项目。但是,通过 DebianDog 项目提供的大多数操作系统不再受支持,或者接近它们的生命尽头。从长远来看,这就不那么有用了。

我不建议在你的主计算机上使用它。在现场版 USB 中或一台闲置的系统上尝试它。此外,如果你想使用较新的基础系统,你可以创建你自己的现场版 CD 。

似乎冥冥中自有天意,我总是在探索鲜为人知的 Linux 发行版的路上艰难地前行,像 FatDog644M Linux 以及 Vipper Linux 。虽然我不建议使用它们,但是知晓这些项目的存在的意义总是好的。

你对 DebianDog 有什么看法?你最最喜欢的 Puppy 式的操作系统是什么?请在下面的评论区中告诉我们。

如果你觉得这篇文章很有趣,请花点时间在社交媒体、黑客新闻或 Reddit 上分享。


via: https://itsfoss.com/debiandog/

作者:John Paul 选题:lujun9972 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在新冠疫情爆发之际,涌现了数以千计的开源项目,今天我们来了解其中四个。

在当前紧张的疫情环境下,保持专注和积极性是一个很大的挑战。而研究开源社区如何应对这一次疫情爆发,对于我来说却成为了一种宣泄。

从今年一月底开始,整个开源社区已经贡献了数以千计关于冠状病毒或新冠病毒的开源软件仓库,其中涉及的内容包括但不限于数据集、模型、可视化、Web 应用、移动应用,且大多数都使用了 JavaScript 和 Python 编写。

之前我们分享过一些关于开源硬件创客们在帮助遏制冠状病毒传播方面做的贡献,现在将继续分享四个由开源社区作出的应对冠状病毒和新冠病毒的项目,这体现了开发者们和整个开源社区在当下对整个世界的影响力。

1. PennSignals:CHIME

 title=

新冠病毒流行病医院影响模型 COVID-19 Hospital Impact Model for Epidemics CHIME)是由宾夕法尼亚大学 Penn Medicine 机构的数据科学家们建立的开源在线应用,它可以让医院更好地了解本次新型冠状病毒对医院需求的影响。

医院的管理人员可以通过 CHIME 来大致预测未来几天和几周内将会有多少患者需要住院治疗,并推算 ICU 和呼吸机的需求量。只需要输入当前住院患者数以及一些相关的变量,就可以看到未来几天内需求的变化情况。

CHIME 主要使用 Python 开发,并通过 Pandas 这个开源库实现了大部分的数据转换和数值计算,最终得出相关的估计值。Pandas 的背后有一个强大的团队进行支持,它是在数据分析方面被广泛使用的 Python 库之一。和很多其它的开源项目一样,Pandas 团队的收入大部分来源于用户的资金支持

2. Locale.ai:实时新冠病毒可视化

 title=

实时显示世界各地病例数量分布的地图可以让我们直观了解新冠病毒的规模和扩散程度。Locale.ai 就开发了这样一个开源、可交互的新冠病毒已知病例可视化分布图,这个图会根据最新的可靠数据实时进行更新。

这个项目有趣的地方在于,它的数据是是通过 GitHub 用户 ExpDev07 创建的开源 API 进行检索的,这个 API 的数据来源是约翰·霍普金斯大学的开源数据集,而约翰·霍普金斯大学这份聚合了多个来源的数据集则是 GitHub 上新冠病毒相关的最受欢迎的项目。这就是开源项领域中分支带来的一大好处。

Locale.ai 的这个图表通过 Vue.js 开发。Vue.js 是一个在 Web 应用开发方面非常流行的框架,它是由尤雨溪创造并维护的。值得一提的是,尤雨溪是少数以全职参与开源项目维护的人之一。

3. BlankerL:DXY-COVID-19-Crawler

 title=

DXY-COVID-19-Crawler 建立于今年 1 月份,是开源社区对新冠病毒最早发起响应的项目之一。当时该病毒主要在中国范围内传播,中国医学界通过丁香园网站来进行病例的报告和跟踪。为了使这些疫情信息具有更高的可读性和易用性,GitHub 用户 BlankerL 开发了一个爬虫,系统地从丁香园网站获取数据,并通过 API 和数据仓库的方式对外公开。这些数据也被学术研究人员用于研究病毒传播趋势和制作相关的可视化图表。到目前为止,DXY-COVID-19-Crawler 这个项目已经获得了超过 1300 个星标和近 300 次的复刻。

BlankerL 使用 Python 和 Beautiful Soup 库开发了这个爬虫。Beautiful Soup 是 Python 开发者用于从页面 HTML DOM 中提取信息的库,它由 Leonard Richardson 维护,这位作者另外还全职担任软件架构师。

4. 东京新冠病毒工作组网站

 title=

世界各地很多城市都在网络上持续更新当地的新冠病毒信息。东京都政府则为此创建了一个综合性的网站,让东京当地居民、在东京设有办事处的公司以及到东京的游客了解最新情况,并采取相应的预防措施。

这个网站的不同之处在于它是由东京都政府开源的。这个项目受到了来自 180 多名用户的贡献,日本的长野市、千叶市、福冈市还对这个网站进行了改造。这个项目是城市公共建设更好地服务大众的有力示范。

这个开源网站也使用了很多开源技术。通过 Tidelift,我留意到项目中存在了 1365 个依赖项,而这都是由 38 个由开发者明确使用的直接依赖项所依赖的。也就是说,超过一千多个开源项目(包括 Nuxt.jsPrettierBabelAjv 等等)都为东京向市民共享信息提供了帮助。

 title=

其它项目

除此以外,还有很多应对新冠病毒的重要项目正在公开进行当中。在这次研究中,开源社区应对流行病以及利用开源技术开展工作的方式让我深受启发。接下来的一段时间都是应对疫情的关键时期,我们也可以继续在开源社区中寻找到更大的动力。

如果你也参与到了新冠病毒相关的开源项目当中,欢迎在评论区分享。


via: https://opensource.com/article/20/3/open-source-software-covid19

作者:Jeff Stern 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

本文是该系列的第三篇。

在我们的即时消息应用中,消息表现为两个参与者对话的堆叠。如果你想要开始一场对话,就应该向应用提供你想要交谈的用户,而当对话创建后(如果该对话此前并不存在),就可以向该对话发送消息。

就前端而言,我们可能想要显示一份近期对话列表。并在此处显示对话的最后一条消息以及另一个参与者的姓名和头像。

在这篇帖子中,我们将会编写一些 端点 endpoint 来完成像“创建对话”、“获取对话列表”以及“找到单个对话”这样的任务。

首先,要在主函数 main() 中添加下面的路由。

router.HandleFunc("POST", "/api/conversations", requireJSON(guard(createConversation)))
router.HandleFunc("GET", "/api/conversations", guard(getConversations))
router.HandleFunc("GET", "/api/conversations/:conversationID", guard(getConversation))

这三个端点都需要进行身份验证,所以我们将会使用 guard() 中间件。我们也会构建一个新的中间件,用于检查请求内容是否为 JSON 格式。

JSON 请求检查中间件

func requireJSON(handler http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if ct := r.Header.Get("Content-Type"); !strings.HasPrefix(ct, "application/json") {
            http.Error(w, "Content type of application/json required", http.StatusUnsupportedMediaType)
            return
        }
        handler(w, r)
    }
}

如果 请求 request 不是 JSON 格式,那么它会返回 415 Unsupported Media Type(不支持的媒体类型)错误。

创建对话

type Conversation struct {
    ID                string   `json:"id"`
    OtherParticipant  *User    `json:"otherParticipant"`
    LastMessage       *Message `json:"lastMessage"`
    HasUnreadMessages bool     `json:"hasUnreadMessages"`
}

就像上面的代码那样,对话中保持对另一个参与者和最后一条消息的引用,还有一个 bool 类型的字段,用来告知是否有未读消息。

type Message struct {
    ID             string    `json:"id"`
    Content        string    `json:"content"`
    UserID         string    `json:"-"`
    ConversationID string    `json:"conversationID,omitempty"`
    CreatedAt      time.Time `json:"createdAt"`
    Mine           bool      `json:"mine"`
    ReceiverID     string    `json:"-"`
}

我们会在下一篇文章介绍与消息相关的内容,但由于我们这里也需要用到它,所以先定义了 Message 结构体。其中大多数字段与数据库表一致。我们需要使用 Mine 来断定消息是否属于当前已验证用户所有。一旦加入实时功能,ReceiverID 可以帮助我们过滤消息。

接下来让我们编写 HTTP 处理程序。尽管它有些长,但也没什么好怕的。

func createConversation(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Username string `json:"username"`
    }
    defer r.Body.Close()
    if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    input.Username = strings.TrimSpace(input.Username)
    if input.Username == "" {
        respond(w, Errors{map[string]string{
            "username": "Username required",
        }}, http.StatusUnprocessableEntity)
        return
    }

    ctx := r.Context()
    authUserID := ctx.Value(keyAuthUserID).(string)

    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        respondError(w, fmt.Errorf("could not begin tx: %v", err))
        return
    }
    defer tx.Rollback()

    var otherParticipant User
    if err := tx.QueryRowContext(ctx, `
        SELECT id, avatar_url FROM users WHERE username = $1
    `, input.Username).Scan(
        &otherParticipant.ID,
        &otherParticipant.AvatarURL,
    ); err == sql.ErrNoRows {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    } else if err != nil {
        respondError(w, fmt.Errorf("could not query other participant: %v", err))
        return
    }

    otherParticipant.Username = input.Username

    if otherParticipant.ID == authUserID {
        http.Error(w, "Try start a conversation with someone else", http.StatusForbidden)
        return
    }

    var conversationID string
    if err := tx.QueryRowContext(ctx, `
        SELECT conversation_id FROM participants WHERE user_id = $1
        INTERSECT
        SELECT conversation_id FROM participants WHERE user_id = $2
    `, authUserID, otherParticipant.ID).Scan(&conversationID); err != nil && err != sql.ErrNoRows {
        respondError(w, fmt.Errorf("could not query common conversation id: %v", err))
        return
    } else if err == nil {
        http.Redirect(w, r, "/api/conversations/"+conversationID, http.StatusFound)
        return
    }

    var conversation Conversation
    if err = tx.QueryRowContext(ctx, `
        INSERT INTO conversations DEFAULT VALUES
        RETURNING id
    `).Scan(&conversation.ID); err != nil {
        respondError(w, fmt.Errorf("could not insert conversation: %v", err))
        return
    }

    if _, err = tx.ExecContext(ctx, `
        INSERT INTO participants (user_id, conversation_id) VALUES
            ($1, $2),
            ($3, $2)
    `, authUserID, conversation.ID, otherParticipant.ID); err != nil {
        respondError(w, fmt.Errorf("could not insert participants: %v", err))
        return
    }

    if err = tx.Commit(); err != nil {
        respondError(w, fmt.Errorf("could not commit tx to create conversation: %v", err))
        return
    }

    conversation.OtherParticipant = &otherParticipant

    respond(w, conversation, http.StatusCreated)
}

在此端点,你会向 /api/conversations 发送 POST 请求,请求的 JSON 主体中包含要对话的用户的用户名。

因此,首先需要将请求主体解析成包含用户名的结构。然后,校验用户名不能为空。

type Errors struct {
    Errors map[string]string `json:"errors"`
}

这是错误消息的结构体 Errors,它仅仅是一个映射。如果输入空用户名,你就会得到一段带有 422 Unprocessable Entity(无法处理的实体)错误消息的 JSON 。

{
    "errors": {
        "username": "Username required"
    }
}

然后,我们开始执行 SQL 事务。收到的仅仅是用户名,但事实上,我们需要知道实际的用户 ID 。因此,事务的第一项内容是查询另一个参与者的 ID 和头像。如果找不到该用户,我们将会返回 404 Not Found(未找到) 错误。另外,如果找到的用户恰好和“当前已验证用户”相同,我们应该返回 403 Forbidden(拒绝处理)错误。这是由于对话只应当在两个不同的用户之间发起,而不能是同一个。

然后,我们试图找到这两个用户所共有的对话,所以需要使用 INTERSECT 语句。如果存在,只需要通过 /api/conversations/{conversationID} 重定向到该对话并将其返回。

如果未找到共有的对话,我们需要创建一个新的对话并添加指定的两个参与者。最后,我们 COMMIT 该事务并使用新创建的对话进行响应。

获取对话列表

端点 /api/conversations 将获取当前已验证用户的所有对话。

func getConversations(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    authUserID := ctx.Value(keyAuthUserID).(string)

    rows, err := db.QueryContext(ctx, `
        SELECT
            conversations.id,
            auth_user.messages_read_at < messages.created_at AS has_unread_messages,
            messages.id,
            messages.content,
            messages.created_at,
            messages.user_id = $1 AS mine,
            other_users.id,
            other_users.username,
            other_users.avatar_url
        FROM conversations
        INNER JOIN messages ON conversations.last_message_id = messages.id
        INNER JOIN participants other_participants
            ON other_participants.conversation_id = conversations.id
                AND other_participants.user_id != $1
        INNER JOIN users other_users ON other_participants.user_id = other_users.id
        INNER JOIN participants auth_user
            ON auth_user.conversation_id = conversations.id
                AND auth_user.user_id = $1
        ORDER BY messages.created_at DESC
    `, authUserID)
    if err != nil {
        respondError(w, fmt.Errorf("could not query conversations: %v", err))
        return
    }
    defer rows.Close()

    conversations := make([]Conversation, 0)
    for rows.Next() {
        var conversation Conversation
        var lastMessage Message
        var otherParticipant User
        if err = rows.Scan(
            &conversation.ID,
            &conversation.HasUnreadMessages,
            &lastMessage.ID,
            &lastMessage.Content,
            &lastMessage.CreatedAt,
            &lastMessage.Mine,
            &otherParticipant.ID,
            &otherParticipant.Username,
            &otherParticipant.AvatarURL,
        ); err != nil {
            respondError(w, fmt.Errorf("could not scan conversation: %v", err))
            return
        }

        conversation.LastMessage = &lastMessage
        conversation.OtherParticipant = &otherParticipant
        conversations = append(conversations, conversation)
    }

    if err = rows.Err(); err != nil {
        respondError(w, fmt.Errorf("could not iterate over conversations: %v", err))
        return
    }

    respond(w, conversations, http.StatusOK)
}

该处理程序仅对数据库进行查询。它通过一些联接来查询对话表……首先,从消息表中获取最后一条消息。然后依据“ID 与当前已验证用户不同”的条件,从参与者表找到对话的另一个参与者。然后联接到用户表以获取该用户的用户名和头像。最后,再次联接参与者表,并以相反的条件从该表中找出参与对话的另一个用户,其实就是当前已验证用户。我们会对比消息中的 messages_read_atcreated_at 两个字段,以确定对话中是否存在未读消息。然后,我们通过 user_id 字段来判定该消息是否属于“我”(指当前已验证用户)。

注意,此查询过程假定对话中只有两个用户参与,它也仅仅适用于这种情况。另外,该设计也不很适用于需要显示未读消息数量的情况。如果需要显示未读消息的数量,我认为可以在 participants 表上添加一个unread_messages_count INT 字段,并在每次创建新消息的时候递增它,如果用户已读则重置该字段。

接下来需要遍历每一条记录,通过扫描每一个存在的对话来建立一个 对话切片 slice of conversations 并在最后进行响应。

找到单个对话

端点 /api/conversations/{conversationID} 会根据 ID 对单个对话进行响应。

func getConversation(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    authUserID := ctx.Value(keyAuthUserID).(string)
    conversationID := way.Param(ctx, "conversationID")

    var conversation Conversation
    var otherParticipant User
    if err := db.QueryRowContext(ctx, `
        SELECT
            IFNULL(auth_user.messages_read_at < messages.created_at, false) AS has_unread_messages,
            other_users.id,
            other_users.username,
            other_users.avatar_url
        FROM conversations
        LEFT JOIN messages ON conversations.last_message_id = messages.id
        INNER JOIN participants other_participants
            ON other_participants.conversation_id = conversations.id
                AND other_participants.user_id != $1
        INNER JOIN users other_users ON other_participants.user_id = other_users.id
        INNER JOIN participants auth_user
            ON auth_user.conversation_id = conversations.id
                AND auth_user.user_id = $1
        WHERE conversations.id = $2
    `, authUserID, conversationID).Scan(
        &conversation.HasUnreadMessages,
        &otherParticipant.ID,
        &otherParticipant.Username,
        &otherParticipant.AvatarURL,
    ); err == sql.ErrNoRows {
        http.Error(w, "Conversation not found", http.StatusNotFound)
        return
    } else if err != nil {
        respondError(w, fmt.Errorf("could not query conversation: %v", err))
        return
    }

    conversation.ID = conversationID
    conversation.OtherParticipant = &otherParticipant

    respond(w, conversation, http.StatusOK)
}

这里的查询与之前有点类似。尽管我们并不关心最后一条消息的显示问题,并因此忽略了与之相关的一些字段,但是我们需要根据这条消息来判断对话中是否存在未读消息。此时,我们使用 LEFT JOIN 来代替 INNER JOIN,因为 last_message_id 字段是 NULLABLE(可以为空)的;而其他情况下,我们无法得到任何记录。基于同样的理由,我们在 has_unread_messages 的比较中使用了 IFNULL 语句。最后,我们按 ID 进行过滤。

如果查询没有返回任何记录,我们的响应会返回 404 Not Found 错误,否则响应将会返回 200 OK 以及找到的对话。


本篇帖子以创建了一些对话端点结束。

在下一篇帖子中,我们将会看到如何创建并列出消息。


via: https://nicolasparada.netlify.com/posts/go-messenger-conversations/

作者:Nicolás Parada 选题:lujun9972 译者:PsiACE 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

过去,我眼中的华为是神秘、大公司体制的代名词,但是,这一次 HDC.Cloud 华为开发者大会的参会体验和所见所闻,打破了我对于华为的认知,如今的华为与开放、创新等关键词紧密联系在一起。

一、引语

突如其来的全球性疫情的爆发,打乱了这个世界很多既定的事情。作为一个 IT “会虫”,我每年要参加很多重要的行业峰会,但如今的疫情形势,显然现场参会不再是一个好主意。在年前,我参加了华为的 HDC.Cloud 媒体预热会,近距离了解了原计划在春节后举办 HDC.Cloud,对这次计划中的大会充满了期待,也准备亲自去了解一些有趣的话题。

在目前看起来还是愈演愈烈的疫情之下,全球各类 IT 行业的峰会纷纷停摆,或从线下改为线上,或延期观望,甚至直接取消的也比比皆是。

而这次华为的 HDC.Cloud 也在调整之后,决定将原本预计参会规模达上万人的行业顶级峰会改为线上举办。说实话,在参会之前,我对线上会议能不能如以往般直面演讲者获得最新的重要信息还是有一定的疑虑的——不过事实上证明,至少在华为的 IT 基础设施加持之下,效果要比预期的好,只是心疼我滚烫的笔记本电脑。

作为中国的一家开源社区,华为的开源举措和战略是我最为关注的,因此,在这篇文章中,我将向大家分享我对华为在开源方面的观察和感受。

二、软件开源

可以说,以华为这些年来的表现来看,已经将开源和开放作为其主要的生态战略来推进,这在华为的很多宣传和会议上,尤其是这次的 HDC.Cloud 上更为显著。几乎在每个话题和演讲中,都会或多或少地提及开源和开放生态。

首先说开源软件,除了华为捐赠给 Apache 基金会的 ServiceCombCarbonDataMetaModel 等若干顶级项目之外,华为在 GitHub 上的几个企业账户中还有很多项目:

让我们再梳理下 openEuler 社区的几大里程碑事件:

  • 2019 年 9 月 18 日,华为宣布操作系统 、数据库等的开源计划,openEuler 开源社区上线(社区地址:https://openeuler.org/zh/)。
  • 2019 年 12 月 31 日,openEuler 源代码正式上线,至 3 月 25 日,下载 ISO 的累计次数:5479,贡献代码的总人数 220、总次数 5324。
  • 2020 年 3 月 27 日,openEuler 开源社区正式发布 openEuler 20.03 LTS(长期支持)版本。该版本是自开源社区上线以来的首个 LTS 版本。

当前,openEuler 社区秘书处、技术委员会、安全委员会等组织已开始运作,社区参与者申请并成立了 30 多个项目组。

这些其实并不出乎我们的预期,不过在这次会议上,大家之前关注颇多的 openEuler 这次披露了一个重要消息,它成功地和国内 4 家重要的操作系统厂家麒麟软件、普华基础软件、统信软件、中科院软件所等达成了合作,共同宣布基于 openEuler 的商用版本操作系统正式发布,以加速鲲鹏相关技术在各行业的落地和应用。

openEuler 社区后续将每两年发布一次 LTS 版本,每 6 个月发布一次社区创新版本,并提供 CVE 漏洞管理能力。 openEuler也将始终坚持 Upstream First 策略,所有能力第一时间向上游开源社区推送。

openEuler 20.03 LTS 版本基于华为长期的技术积累和产品实践,在稳定性、安全性上达到了业界领先水平,并通过软件全栈的技术优化,充分释放多样化计算平台算力。

这说明了什么?openEuler 不仅仅是一个普通的 Linux 发行版,而是作为上游发行版,以开放的姿态迎来了国内各大操作系统厂商的支持。这在我的印象中,还是国内第一次跨越一家一地的操作系统层面的大合作,其开放形态可见一斑,也映射出了大家对鲲鹏计算生态的看好。

三、硬件开放

除了软件的开源,华为硬件开放方面也取得了长足的进展,这体现在了华为为其通用计算处理器“鲲鹏”和人工智能处理器“昇腾”打造的开放生态上。

很多人知道开源软件,但是对硬件怎么开放不甚了然。随着互联网技术的发展,开源这个词已经逐渐被大众所接触和理解。以粗浅的解释来说,所谓开源软件就是将软件的源代码公开出来。有些人会疑问,那么硬件怎么开源呢?

开源硬件和非开源硬件有很多区别,但是开源硬件联盟(OSHWA)定义了一个大多数人同意的定义,如果你熟悉开源软件,这不会听上去太奇怪:“开源硬件(OSHW)是一个指有形的造物:机器、设备或者其它物理东西的术语——其设计向公众发布,任何人可以制造、修改、分发并使用那些造物。”

为了营造开放的计算生态,华为聚焦做好处理器、板卡和主板等底层基础硬件设施,并开发硬件设施的相关资源,使能合作伙伴优先发展自有品牌的计算产品整机。目前全球已经有黄河、长虹天宫、紫光恒越、同方、湘江等11家整机厂商基于鲲鹏主板推出自有品牌的服务器及PC产品。对于个人来说,我们可能很快就可以用上基于鲲鹏处理器的 PC 和服务器产品了。

多样化计算时代,计算产品能够有新的选择,得益于华为一直以来在技术研发方面的高度投入。从2004年华为开始投资研发第一颗嵌入式处理芯片以来,目前已总共投入超过2万名的工程师,在去年9月的华为全联接大会上,华为基于“鲲鹏+昇腾”双引擎正式发布了计算战略,以“一云两翼双引擎”的计算产业布局,致力于为世界提供最强算力。最终形成了以“鲲鹏+昇腾”为核心的基础芯片族,满足世界对通用计算和智能计算的最新需求。

三、开发者扶持

再次投入 2 亿美元的沃土计划 2.0

作为开源的土壤,开发者也是屡屡被华为提在口中的。在华为开发者大会 2020(Cloud)上,华为高调宣布“沃土计划 2.0”的进一步举措,将在 2020 年投入 2 亿美元推动鲲鹏计算产业发展,并公布面向高校、初创企业、开发人员及合作伙伴的扶持细则。

  • 高校教研扶持:面向高校,提供1亿人民币的扶持金额,包括人才培养,云资源及样机支持,同时华为与教育部签署3年协议投入 10 亿人民币,共同推进人才的培养。
  • 初创企业及开发人员扶持:面向初创企业及个体开发人员,以云资源及线下活动的方式发放 1 亿人民币的补助,每家初创公司最高可获得 75 万人民币的云券。
  • 合作伙伴发展扶持:面向合作伙伴,提供6亿人民币的扶持总额,其中 3 亿人民币一次性研发费用补贴,3 亿人民币的云资源。

华为云与计算 BG 总裁侯金龙在《与全球开发者共成长,共赢计算新时代》主题演讲时表示:“开发者是改变世界的重要力量,也是企业创新的引擎和产业生态的灵魂。华为去年发布了沃土计划 2.0,计划 5 年内发展 500 万开发者,目前华为云开发者已经增长到 160 万。”

华为云与计算 BG 总裁侯金龙在主题演讲

打造鲲鹏的开发工具平台

为了让每位开发者用上并用好鲲鹏,华为在大会上发布了 CloudIDE 开发环境服务、鲲鹏加速库和开发者社区。

站在鲲鹏开发者的角度,如果在本地做开发构建,则需要做交叉编译;而如果在鲲鹏环境做原生编译,则面临着和其它工作的割裂。而现在,有了支持鲲鹏原生开发环境的华为云 CloudIDE,这个痛点就迎刃而解。华为云 DevCloud 研发总监王亚伟介绍, CloudIDE 只需要几十秒时间就能快速创建鲲鹏原生的云端开发环境,开发者可以直接在浏览器内连接到云端实例,进行编码、调试、测试,10 分钟就能快速开发鲲鹏云原生应用。

作为华为云 DevCloud 开发平台组件之一,CloudIDE 是轻量化的基于 Web 的集成开发环境服务,支持鲲鹏原生和多语言,打通了开发态和运行态。目前,已有超过 40 万用户在 DevCloud 上开发了超过 50 万个项目。而且,2020 年 CloudIDE 对所有鲲鹏实例全年免费。

此外,华为还提供了鲲鹏加速库。它分为基础,压缩,加解密,存储,多媒体五大类,应用开发者可以在代码修改量不到1%的情况下,将性能提升大于 10% 甚至在加解密等场景下将性能提升超过 100%。

如今,鲲鹏开发者们还有了一个活跃的开发者社区,汇聚集了全栈软硬件知识、鲲鹏产业资讯、政策发布、认证查询等信息,华为还为社区提供全天候 7x24H 的专家在线服务。

四、结语

华为在计算产业的发展策略“硬件开放,软件开源,使能合作伙伴”,同时华为也是业界唯一一家同时拥有“CPU、NPU、存储控制、网络互连、智能管理”五大关键芯片的厂商。

对于华为来说,惟有以软件开源+硬件开放,才能发掘更大的潜能、释放更大的算力,为数字经济时代提供最具创新性的核心生产力。随着华为持续不断地提供各种资源,帮助广大开发者学习和使用鲲鹏,会帮助更多的伙伴逐步实现应用的迁移。

华为是国内难得的高研发投入企业,并不断推出开放的策略,对于整个产业中的我们每个人来说,都是一个重大的利好消息。学习、理解、利用华为所开放出来的这些技术,对于我们这些从业者来说,都是不可多得的好资料。也希望更多的企业能够向华为学习,从闭源、封闭,渐渐走向开放。

如果你是一名系统管理员或者开发者,当你在终端工作时有时会需要编辑一个文件。在 Linux 系统中有几种文件编辑器,你可以根据需求选择合适的文件编辑器。在这里,我想推荐 Vim 编辑器。

为什么推荐 Vim 编辑器

相对于创建新文件,你更多是修改已经存在的文件。在这种情况下,Vim 快捷键可以有效地满足你的需求。

下列文章可以帮助你了解对文件和目录的操作。

什么是 Vim

Vim 是被 Linux 管理员和开发者广泛使用的最流行和功能强大的编辑器之一。它可以通过高度的自定义配置来提高文本编辑效率。它是在众多 Unix 默认安装的 Vi 编辑器的升级版。

Vim 通常被称为“程序员的编辑器”,但并不限于此,它也可用于编辑任何类型的文件。它具有许多功能,例如:多次撤销、多窗口和缓冲区、语法高亮、命令行编辑、文件名补全、可视选择等等。你可以使用 :help 命令来获取在线帮助。

理解 Vim 的模式

Vim 有两种模式,详细介绍如下:

命令模式: 当启动 Vim 编辑器后,默认处在命令模式下。你可以在文件中移动并且修改内容,剪切、复制和粘贴文件的一部分,同时发出命令执行更多操作(按 ESC 键进入命令模式)

插入模式: 插入模式用于在给定的文档位置插入文本(按 i 键进入插入模式)

我如何知道我正使用哪种 Vim 模式呢?

如果你正在使用插入模式,你会在编辑器的底部看到 INSERT。如果编辑器底部没有显示任何内容,或者在编辑器底部显示了文件名,则处于 “命令模式”。

命令模式下的光标移动

Vim 快捷键允许你使用不同的方式来移动光标:

  • G – 跳转到文件最后一行
  • gg – 跳转到文件首行
  • $ – 跳转到行末尾
  • 0(数字 0) – 跳转到行开头
  • w – 跳转到下一个单词的开始(单词的分隔符可以是空格或其他符号)
  • W – 跳转到下一个单词的开始(单词的分隔符只能是空格)
  • b – 跳转到下一个单词的末尾(单词的分隔符可以是空格或其他符号)
  • B – 跳转到下一个单词的末尾(单词的分隔符只能是空格)
  • PgDn 键 – 向下移动一页
  • PgUp 键 – 向上移动一页
  • Ctrl+d – 向下移动半页
  • Ctrl+u – 向上移动半页

插入模式:插入文字

下面的 Vim 快捷键允许你根据需要在光标的不同位置插入内容。

  • i – 在光标之前插入
  • a – 在光标之后插入
  • I – 在光标所在行的开头插入。当光标位于行中间时,这个键很有用
  • A – 在光标所在行的末尾插入。
  • o – 在光标所在行的下面插入新行
  • O – 在光标所在行的上面插入新行
  • ea – 在单词的末尾插入

拷贝、粘贴和删除一行

  • yy – 复制一行
  • p / P – 将内容粘贴到光标之后 / 之前
  • dd – 删除一行
  • dw – 删除一个单词

在 Vim 中搜索和替换匹配的模式

  • /模式 – 向后搜索给定的模式
  • ?模式 – 向前搜索给定的模式
  • n – 向后重复搜索之前给定的模式
  • N – 向前重复搜索之前给定的模式
  • :%s/旧模式/新模式/g – 将文件中所有的旧模式替换为新模式
  • :s/旧模式/新模式/g – 将当前行中所有的旧模式替换为新模式
  • :%s/旧模式/新模式/gc – 逐个询问是否文件中的旧模式替换为新模式

如何在 Vim 编辑器中跳转到特定行

你可以根据需求以两种方式达到该目的,如果你不知道行号,建议采用第一种方法。

通过打开文件并运行下面的命令来显示行号

:set number

当你设置好显示行号后,按 :n 跳转到相应的行号。例如,如果你想跳转到第 15 行,请输入:

:15

如果你已经知道行号,请使用以下方法在打开文件时直接跳转到相应行。例如,如果在打开文件时直接跳转到 20 行,请输入下面的命令:

$ vim +20 [文件名]

撤销操作/恢复上一次操作/重复上一次操作

  • u – 撤销更改
  • Ctrl+r – 恢复更改
  • . – 重复上一条命令

保存和退出 Vim

  • :w – 保存更改但不退出 vim
  • :wq – 写并退出
  • :q! – 强制退出

via: https://www.2daygeek.com/basic-vim-commands-cheat-sheet-quick-start-guide/

作者:Magesh Maruthamuthu 选题:lujun9972 译者:萌新阿岩 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

一份优质的文档可以让很多用户对你的项目路人转粉。

好的代码很多时候并不代表一切。或许你能用最精巧的代码解决了世界上最迫切需要解决的问题,但如果你作为一个开源开发者,没能用准确的语言将你的作品公之于世,你的代码也只能成为沧海遗珠。因此,技术写作和文档编写是很重要的技能。

一般来说,项目中的文档是最受人关注的部分,很多用户会通过文档来决定自己是否应该对某个项目开始学习或研究。所以,我们不能忽视技术写作和文档编写的工作,尤其要重点关注其中的“ 入门 Getting Started ”部分,这会对你项目的发展起到关键性的作用。

对于很多人来说,写作是一件令人厌烦甚至恐惧的事情。我们这些工程师出身的人,更多学习的是“写代码”而不是学习“为代码写文档”。不少人会把英语作为自己的第二语言或者第三语言,他们可能会对英语写作感到不自信甚至害怕(我的母语是汉语,英语是作为我的第二语言学习的,所以我也能感受到这种痛苦)。

但如果你希望自己的项目能在全球范围内产生一定的影响力,英语就是你必须使用的语言,这是一个无法避免的现实。但不必害怕,我在写这篇文章的时候就考虑到了这些可能带来的挑战,并给出了我的一些建议。

五条有用的写作建议

这五条建议你马上就可以用起来,尽管看起来似乎有些浅显,但在技术写作时却经常被忽视。

  1. 使用主动语态:感受一下主动语态下的“你可以这样更改配置(You can change these configurations by…)”和被动语态下的“配置可以这样更改(These configurations can be changed by…)”有什么不同之处。
  2. 使用简洁明了的句子:可以借助 Hemingway App 或者 Grammarly 这样的工具,尽管它们并不开源。
  3. 保持条理性:你可以在文档中通过写标题、划重点、引链接等方式,把各类信息划分为不同的部分,避免将所有内容都杂糅在一大段冗长的文字当中。
  4. 提高可读性:除了单纯的文字之外,运用图表也是从多种角度表达的手段之一。
  5. 注意拼写和语法:必须记得检查文档中是否有拼写错误或者语法错误。

只要在文档的写作和编辑过程中应用到这些技巧,你就能够和读者建立起沟通和信任。

  • 高效沟通:对于工程师们来说,阅读长篇大论的冗长文字,还不如去看小说。在阅读技术文档时,他们总是希望能够从中快速准确地获取到有用的信息。因此,技术文档的最佳风格应该是精简而有效的,不过这并不代表文档中不能出现类似幽默、emoji 甚至段子这些东西,这些元素可以当你的文档更有个性、更使人印象深刻。当然,具体的实现方式就因人而异了
  • 建立信任:你需要取得文档读者们的信任,这在一个项目的前期尤为重要。读者对你的信任除了来源于你代码的质量,还跟你文档编写的质量有关。所以你不仅要打磨代码,还要润色好相关的文档,这也是上面第 5 点建议拼写和语法检查的原因。

从编写“入门”文档开始

现在,最需要花费功夫的应该就是“入门”部分了,这是一篇技术文档最重要的部分,二八定律在这里得到了充分体现:访问一个项目的大部分流量都会落在项目文档上,而访问项目文档的大部分流量则会落在文档的“入门”部分中。因此,如果文档的“入门”部分写得足够好,项目就会吸引到很多用户,反之,用户会对你的项目敬而远之。

那么如何写好“入门”部分呢?我建议按照以下三步走:

  1. 任务化:入门指南应该以任务为导向。这里的任务指的是对于开发者来说可以完成的离散的小项目,而不应该包含太多涉及到体系结构、核心概念等的抽象信息,因此在“入门”部分只需要提供一个简单明了的概述就可以了。也不要在“入门”部分大谈这个项目如何优秀地解决了问题,这个话题可以放在文档中别的部分进行说明。总而言之,“入门”部分最好是给出一些主要的操作步骤,这样显得开门见山。
  2. 30 分钟内能够完成:这一点的核心是耗时尽可能短,不宜超过 30 分钟,这个时间上限是考虑到用户可能对你的项目并不了解。这一点很重要,大部分愿意浏览文档的人都是有技术基础的,但对你的项目也仅仅是一知半解。首先让这些读者尝试进行一些相关操作,在收到一定效果后,他们才会愿意花更多时间深入研究整个项目。因此,你可以从耗时这个角度来评估你的文档“入门”部分有没有需要改进之处。
  3. 有意义的任务:这里“有意义”的含义取决于你的开源项目。最重要的是认真思考并将“入门”部分严格定义为一项任务,然后交给你的读者去完成。这个项目的价值应该在这项有意义的任务中有所体现,不然读者可能会感觉这是一个浪费时间的行为。

提示:假如你的项目是一个分布式数据库,那么达到“整个集群在某些节点故障的情况下可以不中断地保持可用”的目标就可以认为是“有意义”的;假如你的项目是一个数据分析工具或者是商业智能工具,“有意义”的目标也可以是“加载数据后能快速生成多种可视化效果的仪表板”。总之,无论你的项目需要达到什么“有意义”的目标,都应该能在笔记本电脑上本地快速实现。

Linkerd 入门就是一个很好的例子。Linkerd 是一个开源的 Kubernetes 服务网格 Service Mesh ,当时我对 Kubernetes 了解并不多,也不熟悉服务网格。但我在自己的笔记本电脑上很轻松地就完成了其中的任务,同时也加深了对服务网格的理解。

上面提到的三步过程是一个很有用的框架,对一篇文档“入门”部分的设计和量化评估很有帮助。今后你如果想将你的开源项目产品化,这个框架还可能对 实现价值的时间 time-to-value 产生影响。

其它核心部分

认真写好“入门”部分之后,你的文档中还需要有这五个部分:架构设计、生产环境使用指导、使用案例、参考资料以及未来展望,这五个部分在一份完整的文档中是必不可少的。

  • 架构设计:这一部分需要深入探讨整个项目架构设计的依据,“入门”部分中那些一笔带过的关键细节就应该在这里体现。在产品化过程中,这个部分将会是产品推广计划的核心,因此通常会包含一些可视化呈现的内容,期望的效果是让更多用户长期参与到项目中来。
  • 生产环境使用指导:对于同一个项目,在生产环境中部署比在笔记本电脑上部署要复杂得多。因此,指导用户认真使用就尤为重要。同时,有些用户可能对项目很感兴趣,但对生产环境下的使用有所顾虑,而指导和展示的过程则正好能够吸引到这类潜在的用户。
  • 使用案例 社会认同 social proof 的力量是有目共睹的,所以很有必要列出正在生产环境使用这个项目的其他用户,并把这些信息摆放在显眼的位置。这个部分的浏览量甚至仅次于“入门”部分。
  • 参考资料:这个部分是对项目的一些详细说明,让用户得以进行详细的研究以及查阅相关信息。一些开源作者会在这个部分事无巨细地列出项目中的每一个细节和 边缘情况 edge case ,这种做法可以理解,但不推荐在项目初期就在这个部分花费过多的时间。你可以采取更折中的方式,在质量和效率之间取得平衡,例如提供一些相关社区的链接、Stack Overflow 上的标签或单独的 FAQ 页面。
  • 未来展望:你需要制定一个简略的时间表,规划这个项目的未来发展方向,这会让用户长期保持兴趣。尽管项目在当下可能并不完美,但要让用户知道你仍然有完善这个项目的计划。这个部分也能让整个社区构建一个强大的生态,因此还要向用户提供表达他们对未来展望的看法的交流区。

以上这几个部分或许还没有在你的文档中出现,甚至可能会在后期才能出现,尤其是“使用案例”部分。尽管如此,还是应该在文档中逐渐加入这些部分。如果用户对“入门”部分已经感觉良好,那以上这几个部分将会提起用户更大的兴趣。

最后,请在“入门”部分、README 文件或其它显眼的位置注明整个项目所使用的许可证。这个细节会让你的项目更容易通过最终用户的审核。

花 20% 的时间写作

一般情况下,我建议把整个项目 10% 到 20% 的时间用在文档写作上。也就是说,如果你是全职进行某一个项目的,文档写作需要在其中占半天到一天。

再细致一点,应该将写作纳入到常规的工作流程中,这样它就不再是一件孤立的琐事,而是日常的事务。文档写作应该随着工作进度同步进行,切忌将所有写作任务都堆积起来最后完成,这样才可以帮助你的项目达到最终目标:吸引用户、获得信任。


特别鸣谢云原生计算基金会的布道师 Luc Perkins 给出的宝贵意见。

本文首发于 COSS Media 并经许可发布。


via: https://opensource.com/article/20/3/documentation

作者:Kevin Xu 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出