2024年4月

3月7日,基于 WireGuard 的知名 VPN 厂商
Tailscale 的官方网站 tailscale.com 因 TLS 证书过期而中断服务约90分钟

虽然影响有限,但这起事件还是在 Hacker News 等论坛引发了热烈的讨论和反思。

网友们纷纷表达了自己的看法。smackeyacky 感慨道:“
我说过一次,现在我要再说一次:证书过期已然成为新时代服务中断的头号杀手。

Tailscale 联合创始人 bradfitz 也第一时间在 Hacker News 回应,介绍了事发原因和应对过程。原来,他们在去年12月进行了一次大规模的网站迁移,涉及底层架构、域名解析等多个环节的重构。为了支持 IPv6,他们还特意搭建了额外的代理服务器。

但令人始料未及的是,这个看似无伤大雅的改动,竟然埋下了祸根。由于代理服务器终止了 TLS 连接,加上 DNS 配置的疏忽,监控系统没能及时发现证书已经过期的警告。bradfitz 坦言,这次事故暴露出团队在变更管理和风险评估方面还有很大的提升空间。

这波 “辩解”,你买账吗?让我们来看看网友们怎么说。

j45 就提出了质疑:如果 IPv6 如此重要,为什么当初要选择一家压根不支持的供应商呢?bradfitz 只能苦笑,表示公司内部在这个问题上尚未达成共识。

lmeyerov 则一针见血地指出,关键脚本和文档不应该放在营销页面上,那无异于 “身败名裂”。

更有意思的是,大家对 Tailscale 下一步的打算出谋划策。amluto 建议他们最好换成 TCP 代理,这样可以充分利用 Let's Encrypt 的 HTTP 验证方式。agwa 的想法则更大胆:
要不要试试每天自动更新证书?
延长过期时间虽然安全,但如此频繁的滚动更新,应该也不是什么难事儿吧?

光说不练假把式,Tailscale 到底准备如何破局?bradfitz 表示,除了之前提到的监控改进,他们还计划进一步简化网络拓扑,减少对特殊方案的依赖。同时,为了防患于未然,还要像 “古人” 那样设置每月提醒,专人盯防,避免证书过期时 “完犊子”。

但 bradfitz 也不忘自信满满地补充:Tailscale 的设计目标,本来就是打造一个弹性十足的网状架构。即便控制平面偶尔抽风,用户的连接状态也不会受到影响。这次事故恰恰印证了他们的优势所在。

Tailscale 本次的应对,可谓是基础设施领域的 “教科书”。他们没有避重就轻、诿过于人,而是
勇于承担责任
;他们没有急于修补、敷衍了事,而是深刻反思、触及本质。这种开诚布公、虚心接纳的态度,值得每一个技术团队学习。

回到本次事件,笔者认为,Tailscale 暴露出的问题绝非个案。在快速迭代的今天,任何平台都难免经历这样那样的折腾。但关键是要时刻保持警惕,尊重专业,关注细节。一念之差,可能就是业务中断、口碑受损的起点。

尤其值得警惕的是 “身败名裂” 式的设计。当一个看似不起眼的页面或服务,成为了整个系统的 “生死簿”,我们就必须格外留心。是否应该适当解耦?是否需要特别优化?唯有未雨绸缪,才能降低 “黑天鹅” 事件的影响。

对创业公司而言,技术固然重要,但更要注重全局观。
什么是真正的刚需?什么又是可以简化的?
架构师们需要时刻问自己这样的问题。盲从所谓 “最佳实践”,搞出一堆 “金玉其外败絮其中” 的玩意儿,那就本末倒置了。

总而言之,Tailscale 的 “证书门”,为我们敲响了警钟:
安全可用是一切的基础
。必须通过周密的设计、严谨的态度,才能获得用户的信赖。相信 Tailscale 能够从这次事件中汲取教训,以更成熟、专业的姿态,打造真正韧性十足的服务,在 VPN 领域一路高歌。

尽管 Tailscale 因 TLS 证书过期导致了90分钟的服务中断,但这恰恰凸显了它的一大优势——多数用户几乎没有受到影响。Tailscale 的分布式架构使得用户端的连接不依赖于中心节点始终在线。这种弹性设计是 Tailscale 区别于传统 VPN 的关键所在。短暂的中心服务中断并不能否定 Tailscale 的价值,反而凸显了其良好的容错性。

vue3 快速入门系列 - vue3 路由


vue3 基础
上加入路由。

vue3 需要使用
vue-router V4
,相对于 v3,大部分的 Vue Router API 都没有变化。

Tip
:不了解路由的同学可以看一下笔者之前的文章:
vue2 路由

参考:
vue2 路由官网

vue3 路由官网

vue-router V4

在 Vue Router API 从 v3(Vue2)到 v4(Vue3)的重写过程中,大部分的 Vue Router API 都没有变化,但是在迁移你的程序时,你可能会遇到一些破坏性的变化 ——
从 Vue2 迁移

vue3 需要使用 vue-router 4.x.x 版本。安装:

PS hello_vue3> npm i vue-router

changed 37 packages, and audited 69 packages in 3s

8 packages are looking for funding
  run `npm fund` for details

1 moderate severity vulnerability

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

版本:

"dependencies": {
    "vue": "^3.4.15",
    "vue-router": "^4.3.0"
  },

第一个示例

在vue3项目中加入路由。

步骤如下:

  • 创建路由
    src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
]

// new Router 变成 createRouter
const router = createRouter({
  // mode: 'history' 配置已经被一个更灵活的 history 配置所取代
  // 必填。否则报错:Uncaught Error: Provide the "history" option when calling "createRouter()"
  history: createWebHistory(),
  routes
})

export default router

Tip
:new Router 变成
createRouter
来创建路由;其中
模式
需要通过调用方法创建,
必填

  • 创建两个组件
<template>
  <div>
    <h1>About</h1>
    // 可以通过设置router-link-active类来为被选中的路由添加样式
    <router-link to="/">to Home</router-link>
  </div>
</template>
<template>
  <div>
    <h1>Home</h1>
    <router-link to="/about">to About</router-link>
  </div>
</template>
  • App.vue 中引入
    <router-view>
    告诉 Vue Router 在哪里渲染匹配到的组件。
<template>
  <router-view></router-view>
</template>

<script lang="ts" setup name="App">

</script>
  • main.ts 通过 use 使用路由
import {createApp} from 'vue'
import App from './App.vue'
// 会自动加载 ./router/index.ts
import router from './router'

createApp(App)
// 将 Vue Router 插件安装到 Vue 实例中,以便在整个应用程序中使用 Vue Router 的功能
// Vue.use(MyPlugin) - 调用 `MyPlugin.install(Vue)`
.use(router)
.mount('#app')

接着就可以通过浏览器体验:

Home
// 点击,调整到 about 路由
to About
About
// 点击,调整到 home 路由
to Home

Tip
: 通过
.use(router)
在 vue 开发者工具中就会看到
路由tab

命名路由

Tip
:
vue2 路由
-> 命名路由

路径有时太麻烦,可以使用
命名路由
替代。

例如将 About 从路径改为名称跳转。核心代码如下:

// 定义 name
{ path: '/about', component: About, name: 'guanyu' },

// 跳转
:to="{name: 'guangyu'}"

Tip
:to 目前有2种写法,感觉字符串方式很痛快,对象还需要写好多,但是到子路由或传递参数,会发现还是对象好用。

// 传递字符串 - 理解为目标路由的路径
to="/"
// 传递对象
:to="{path: '/'}"
:to="{name: 'guangyu'}"

嵌套路由

Tip
:和 vue2 中路由用法相同,详情请看:
vue2 路由
-> 嵌套路由

新建一个 Article 组件,里面定义一个 router-view。请看示例:

  • Article.vue
<template>
    <div>
        <h1>Article</h1>
        // path 需要将一级路由路径加上,例如 /article,不能只写 detail。该 name 也方便。
        // query 效果:http://localhost:5173/article/detail?id=1
        <router-link :to="{ path: '/article/detail', query: { id: 1 } }">文章 id1 详情</router-link><br>
        <router-link :to="{ path: '/article/detail', query: { id: 2 } }">文章 id2 详情</router-link><br>
        // 注:将对象换成字符串,效果相同
        <router-link to="/article/detail?id=3">文章 id3 详情</router-link><br>
        <router-view></router-view>
    </div>
</template>

<script lang="ts" setup name="App">
// 可以不引入
// import {RouterView,RouterLink} from 'vue-router'
</script>

Tip
:可以不引入
import {RouterView,RouterLink} from 'vue-router'

  • Detail.vue
<template>
    <div>
        <h1>文章id: {{ $route.query.id }}</h1>
    </div>
</template>
  • 增加路由和子路由。子路由的 path 无需增加
    /
const routes = [
  { path: '/home', component: Home,},
  {
    path: '/article',
    component: Article,
    children: [
      {
        path: 'detail',
        component: Detail
      }
    ]
  },
]
  • Home.vue 增加Article入口
<router-link :to="{name: 'guanyu'}">About</router-link>
<br>
<router-link :to="{path: '/article'}">Article</router-link>

测试:进入Home,点击 Article,点击
文章 id1 详情
,显示
文章id: 1
,测试通过。

路由 query 参数

在”嵌套路由“中我们是这样取得 query 参数:
<h1>文章id: {{ $route.query.id }}</h1>

js 中通过
useRoute
hooks 取得 $route。请看示例:

<template>
    <div>
        <h1>文章id: {{ $route.query.id }}</h1>
        <h1>文章id: {{ query.id }}</h1>
    </div>
</template>

<script lang="ts" setup name="App">
import {toRefs} from 'vue'
// 返回当前的路由地址。相当于在模板中使用 $route。
// useRouter 返回路由器实例。相当于在模板中使用 $router
import {useRoute} from 'vue-router'

const route = useRoute()

// route: Proxy
console.log('route: ', route);

// 错误:解构需要用到 toRefs,否则页面不会更新
// const {query} = route

// 正确:解构
const {query} = toRefs(route)

</script>

Tip
:如果需要解构,需使用 toRefs。若想将 query.id 中的 query 去掉,可以使用后面章节的
路由 props 属性
,代码将更优雅

路由 params 参数

Tip
:请看
vue2 路由
-> $route.params

将上节 query 参数示例改成 params。

  • params需要增加
    占位符
    ,比如
    :id
{
    path: '/article',
    component: Article,
    children: [
      {
        name: 'xiangxi',
        path: 'detail/:id',
        component: Detail
      }
    ]
},
  • id传递方式调整一下,不用 query 那种方式:
<router-link to="/article/detail/4">文章 id4 详情</router-link><br>
  • 接收 id
<h1>文章id: {{ $route.params.id }}</h1>


:params 不能传数组或对象;
/a/:b/:c
,则你必须传 /a/1/2,如果传 /a/1 则报错,如果有时没有c 可传,可以改成
/a/:b/:c?

对象形式

将 to 改成对象形式:

 <router-link :to="{
    path: '/article/detail/4',
    params: {
        id: 5
    }
}">文章 id5 详情</router-link><br>

浏览器报错更容易理解,说 path 被忽略:

// vscode 报错:
对象字面量只能指定已知属性,并且“params”不在类型“RouteLocationPathRaw”中。

// 浏览器报错
[Vue Router warn]: Path "/article/detail/4" was passed with params but they will be ignored. Use a named route alongside params instead.

将 path 改成 name即可:

<router-link :to="{
    // path: '/article/detail/4',
    name: 'xiangxi',
    params: {
        id: 5
    }
}">文章 id5 详情</router-link><br>

路由 props 属性

不就是想接收 params 或 query 传来的参数的,还得写这么一大块代码,太麻烦:

<template>
    <div>
        <h1>文章id: {{ query.id }}</h1>
    </div>
</template>

<script lang="ts" setup name="App">
import {toRefs} from 'vue'
import {useRoute} from 'vue-router'

const route = useRoute()
const {query} = toRefs(route)
</script>

可以通过
props
解决。细节如下:

props 布尔

  • 定义 props
{
    name: 'xiangxi',
    path: 'detail/:id',
    component: Detail,
    // 通过 props 属性来将路由参数传递给组件
    // 底层好些这样:<Detail id=5/>
    props: true
}
  • 直接通过 defineProps 接收
<template>
    <div>
        <h1>文章id: {{id }}</h1>
    </div>
</template>

<script lang="ts" setup name="App">
    defineProps(['id'])
</script>

props 函数

如果需要接收 query,需要用 props 函数,参数是 route,返回需要接收的对象:

// RouteLocationNormalized 是 Vue Router 中的一个类型,它用于描述路由的位置信息
import { type RouteLocationNormalized } from 'vue-router';

{
    name: 'xiangxi',
    path: 'detail',
    component: Detail,
    // 通过 props 属性来将路由参数传递给组件
    // props: true
    props(route: RouteLocationNormalized ) {
        return route.query
    }
}
  • 触发路由从 params 改成 query:
<router-link :to="{
    name: 'xiangxi',
    query: {
        id: 5
    }
}">文章 id5 详情</router-link><br>
  • 接收方式不变:
<template>
    <div>
        <h1>文章id: {{id }}</h1>
    </div>
</template>

<script lang="ts" setup name="App">
    defineProps(['id'])
</script>

Tip
:其实
props: true
就相当于下面这段代码:

props(route: RouteLocationNormalized ) {
    return route.params
}

props 对象

props 还可以写成对象,但用得较少:

props: {
    id: 100
}

replace

HTML5的历史API包含了pushState(),replaceState()和popstate事件

路由默认是 push。比如启动
第一个示例
,未点击 home 或 about 导航时,浏览器左上方
既不能前进也不能后退
,因为栈中只有当前页面,
指针
没地方去。在你点击home和about导航后,就可以前进和后退,即使刷新页面,这个历史记录也不会变。

<router-link :to="{name: 'guanyu'}">About</router-link>
<br>
<router-link :to="{path: '/article'}">Article</router-link>

vue-router 的 replace 作用和用法和 react
replace
相同。

现在点 About 就会直接替换

<router-link replace :to="{name: 'guanyu'}">About</router-link>

编程式导航

Tip

vue2 路由
-> 编程式导航

三秒后跳转到 article:

<script lang="ts" setup name="App">
import { useRouter } from 'vue-router'
const router = useRouter()

type Path = string

// 说vue2 中编程式导航重复跳转会报错,vue3中没这个问题
function to(path: Path){
  router.push(path)
}
setTimeout(() => {
  to('/article')
}, 3000)
</script>

编程式导航使用频率大于声明式导航(
<router-link :to="...">

to也支持
对象
,和声明式导航用法相同,更多介绍请看:
vuer-router v4 编程式导航

其他

路由组件和一般组件

路由组件
通常放在 pages 或 views 文件夹中,
一般组件
通常放在 components 文件夹中 —— 一般开源的项目都会这样分类

看一个组件是哪种,需要看其如何用。比如定义一个 Demo.vue,如果通过标签
<Demo/>
这种写法来使用,就属于一般组件,如果该组件通过路由渲染,则称为路由组件。

卸载和挂载

通过导航,视觉上消失的路由组件,默认被卸载,需要用的时候在挂载。


第一个示例
中给 About.vue 增加两个生命周期钩子,再次切换 Home 和 About 组件,就能看到效果:

<template>
  <div>
    <h1>About</h1>
    <router-link to="/">Home</router-link>
  </div>
</template>

<script lang="ts" setup name="App">
import {onMounted, onUnmounted} from 'vue'
onMounted(() => {
  console.log('About 挂载了');
})
onUnmounted(() => {
  console.log('About 卸载了');
})
</script>

路由模式

history 模式
url 美观,后期上线,需要服务端配合处理路径问题,否则刷新会有404。当用户在浏览器中直接访问一个路由,或者刷新页面时,如果服务器端没有正确配置,可能会导致 404 错误,因为此时服务器会尝试去寻找对应的文件或路由路径,而实际上这个路径是由前端控制的,并不一定存在于服务器端的文件系统中。为了解决这个问题,你需要在服务器端配置一个通配符路由,将所有的路由请求都指向你的应用的入口文件(比如 index.html),这样就会确保 Vue Router 能够正确地处理路由请求。

如果你使用的是 Node.js 服务器,可以使用 Express 框架来进行配置,示例代码如下所示:

const express = require('express');
const path = require('path');
const app = express();

// 静态资源目录,例如你的 CSS、JavaScript 文件等
app.use(express.static(path.join(__dirname, 'public')));

// 通配符路由,将所有的路由请求都指向 index.html
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// 启动服务器,监听端口
const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

这样配置后,无论用户访问哪个路由,服务器都会返回 index.html,然后 Vue Router 就可以根据路由配置来正确地渲染相应的组件,避免了刷新页面时出现的 404 错误。

Hash 模式
在 SEO 优化方面相对较差。

  • 比如
    不利于搜索引擎爬虫
    :Hash 模式下,URL 中的哈希部分(#后面的内容)不会被包含在 HTTP 请求中,因此在服务器接收请求时,哈希部分对于服务器来说是不可见的。这意味着搜索引擎爬虫无法直接获取到 URL 中的实际内容,因为爬虫主要是通过 HTTP 请求获取页面内容的,所以无法获取到 hash 后面的内容,这样就会导致搜索引擎无法正确地索引和解析页面。

虽然使用 history 模式相对于 hash 模式在 SEO 优化方面有所改善,但它仍然是单页应用(SPA),可以和服务端渲染结合

没有匹配到指定的路径 /

配置如下路由,第一次打开,浏览器控制台有警告:
main.ts:9 [Vue Router warn]: No match found for location with path "/"

const routes = [
  { path: '/home', component: Home },
  { path: '/about', component: About, name: 'guanyu' }
]

可以通过重定向解决。就像这样:

const routes = [
  { path: '/', redirect: '/home'},
  { path: '/home', component: Home,},

1. Stable diffusion 初学者指南

想掌握Stable Diffusion AI技术吗?

这份初学者指南专为完全没接触过Stable Diffusion或任何AI图像生成器的新手设计。跟随本指南,你将了解Stable Diffusion的基本情况,并获得一些实用的入门技巧。

什么是Stable diffusion?

Stable Diffusion AI是一种基于潜在扩散模型的AI图像生成技术,它能够创造出既逼真又具有艺术风格的图像。这项技术的强大之处在于其生成的图像既可以与真实照片相媲美,也可以模仿专业艺术家的风格和技巧。无论是想要制作出栩栩如生的自然风景,还是想要创作出具有特定艺术风格的插图,Stable Diffusion AI都能够满足用户的需求。

更令人兴奋的是,Stable Diffusion AI是免费提供给用户的。这意味着无论你是学生、独立开发者还是艺术家,都可以在自己的PC上下载并运行这个模型,无需支付任何费用。这极大地降低了AI图像生成技术的门槛,使得更多的人能够接触并利用这项技术进行创作。

要开始使用Stable Diffusion AI,你只需要具备一台性能足够的PC,并按照官方提供的指南进行安装和配置。一旦设置完成,你就可以开始尝试输入各种文本提示,观察并调整生成的图像,以满足你的创作需求。

如何使用 Stable Diffusion?

使用Stable Diffusion AI生成图像的过程相对直观和简单。首先,你需要为模型提供一个描述性的文本,也就是所谓的“Prompt”。这个提示将作为模型生成图像的基础,指导AI理解你想要的图像类型和风格。

例如,如果你想要生成一个关于“姜饼屋”的图像,你可以给出如下的提示:

gingerbread house, diorama, in focus, white background, toast , crunch cereal

在这个提示中,你提供了关于图像的几个关键信息:主题是“姜饼屋”,图像应该是一个“立体模型”,焦点应该集中在“姜饼屋”上,背景应该是“白色”的,同时你还希望图像中有“吐司”和“脆麦片”的元素。这些描述将帮助Stable Diffusion AI更准确地理解你的意图,并生成符合你要求的图像。

当你输入了这个提示后,Stable Diffusion AI会通过其深度学习算法分析这些关键词,并结合其训练中学习到的图像知识库,生成一张图像。这张图像将会尝试反映出你所描述的所有元素,比如姜饼屋的结构、立体感、白色背景以及包含的吐司和麦片等。

生成的图像可能不会完全符合你的预期,但这正是AI图像生成的有趣之处。你可以通过对提示进行微调,比如改变描述的顺序、添加或删除某些关键词,来尝试获得不同的结果。这个过程就像是与AI进行一场创意对话,你可以不断尝试和调整,直到找到最满意的图像。

image-20240408194617268

Stable Diffusion 的优势是什么?

和其他的AI图像生成工具相比,如 DALLE 和 MidJourney。为什么选择 Stable Diffusion?

Stable Diffusion AI 的优点是

  • 开源性
    :Stable Diffusion AI的一个显著特点是其开源性。这意味着它不仅免费供用户使用,而且其背后的代码和模型也是公开的。这种开放性鼓励了全球的技术爱好者和开发者社区参与到模型的改进和创新中来。用户和开发者可以自由地修改、优化和扩展模型的功能,创造出新的应用场景和工具。此外,开源性还促进了知识共享和技术进步,让更多的人能够访问和利用这一先进的AI技术。
  • 低功耗设计
    :Stable Diffusion AI特别注重在低功耗计算机上的运行效率。这一点对于那些没有高性能硬件资源的用户来说尤为重要。由于Stable Diffusion AI能够在不需要昂贵硬件的情况下运行,这使得它更加亲民和易于获取。用户可以在普通的个人电脑甚至是一些较为便宜的设备上运行Stable Diffusion AI,从而降低了使用AI图像生成技术的门槛。这种设计不仅使得Stable Diffusion AI更加普及,也更加环保,因为它减少了对能源的需求和消耗。
  • 强大的生成能力
    :尽管Stable Diffusion AI注重低功耗设计,但这并不影响其生成高质量图像的能力。Stable Diffusion AI能够根据用户的文本提示生成细节丰富、视觉效果出色的图像。无论是逼真的摄影风格还是独特的艺术创作,Stable Diffusion AI都能够胜任,满足不同用户的需求。
  • 社区支持
    :由于Stable Diffusion AI的开源特性,它拥有一个活跃的社区支持。用户可以在社区中找到大量的教程、资源和经验分享,这些都有助于用户更好地理解和使用Stable Diffusion AI。社区的支持也为用户解决使用过程中遇到的问题提供了便利,使得学习和使用Stable Diffusion AI变得更加容易。

Stable Diffusion AI 是免费的吗?

Stable Diffusion AI的灵活性在于它既可以在本地计算机上免费运行,也可以通过在线服务使用,尽管后者可能涉及一定的成本。

Stable Diffusion 能做什么?

1.从文本生成图像

Stable Diffusion 最基本的用法是文本到图像 (txt2img)。以下是您可以使用 Stable Diffusion 生成的一些图像示例。
动漫风格

image-20240408195036875

写实风格

image-20240408194959152

景观

image-20240408195134651

艺术风格

image-20240408195350160

2.从另一个图像生成图像

图像到图像 (img2img) 使用 Stable Diffusion AI 将一个图像转换为另一个图像。


3.照片编辑

使用Stable Diffusion AI进行图像修复是一种非常实用的功能,它允许用户重新生成或修改AI生成的图像或真实图像的特定部分。这个过程与Adobe Photoshop中的“内容感知填充”功能相似,但Stable Diffusion AI提供了一个免费的选择,这对于预算有限的用户或者希望减少成本的个人和小型企业来说是一个巨大的优势。

4.制作视频

使用Stable Diffusion AI制作视频是一项新兴的技术,它为用户提供了两种主要的方法来创造独特的视觉内容。下面将详细介绍这两种方法:

  1. 从文本提示制作视频
    : 这种方法涉及到使用文本提示来指导AI生成视频内容。用户可以输入一系列描述性的关键词或者一个详细的场景描述,AI将根据这些提示生成相应的视频片段。例如,如果你输入“宁静的山谷,清晨,薄雾,鸟儿歌唱”,Stable Diffusion AI将尝试生成一个展现这一场景的视频。这种方法的优势在于它允许用户发挥创意,创造出原本不存在的场景和故事。Deforum是一个流行的工具,它使用这种方法来制作视频,用户可以通过社交媒体分享和展示他们的作品。
  2. 对视频进行风格化
    : 第二种方法是利用Stable Diffusion AI对现有的视频进行风格化处理。这意味着用户可以拿一个普通的视频,然后应用AI生成的艺术风格,从而创造出一种全新的视觉效果。例如,你可以将一个日常风景的视频转换成印象派或未来主义风格的短片。这种方法为用户提供了一种强大的工具,使他们能够将自己的艺术风格应用到视频内容上,或者模仿特定艺术家的风格。

如何使用 Stable Diffusion AI?

在线生成器

对于绝对的初学者,或者那些不想花太多时间在设置和配置上的用户,免费的在线生成器是一个非常好的起点。这些生成器通常具有用户友好的界面,使得即使是没有技术背景的人也能快速上手并开始生成图像。使用在线生成器,你只需访问相应的网站,输入你的文本提示,然后AI就会为你生成图像。这种方式无需下载任何软件,也不需要担心后续的更新和维护问题。

然而,免费在线生成器的功能可能会有一些限制,比如较少的定制选项和对生成图像的控制。如果你发现自己需要更多的功能和灵活性,那么你可能需要考虑升级到更高级的工具。

高级 GUI

  • 当你对Stable Diffusion AI有了基本的了解,并且想要更深入地探索其功能时,高级图形用户界面(GUI)将是一个很好的选择。这些工具提供了一整套高级功能,让你能够更精细地控制图像生成的过程。以下是一些高级GUI可能提供的功能:


    • 高级提示技术
      :允许你使用更复杂的文本提示来指导AI,从而生成更精确和细致的图像。
    • Inpainting
      :这是一种技术,可以让你重新生成图像的一小部分,非常适合进行局部修改和修复。
    • 图像到图像的生成
      :基于你提供的输入图像生成新的图像,这可以让你在现有图像的基础上进行创作。
    • 通过指令编辑图像
      :直接给出指令来调整图像的特定元素,比如改变颜色、添加元素或者调整布局。

    AUTOMATIC6666661是一个受欢迎的高级GUI选项,它提供了一个直观的界面和强大的功能。如果你想在本地运行这样的工具,可以根据适用于Windows和Mac的安装指南来进行设置。如果你有合适的PC,这将是一个不错的选择,因为它可能会提供更好的性能和更多的控制选项。另外,你也可以选择在Google Colab云服务器上设置和运行,这通常涉及到查看快速入门指南来了解如何配置和使用云服务。

如何构建一个好的Prompt?

制作一个有效的提示对于使用Stable Diffusion AI生成高质量的图像至关重要。一个好的提示能够引导AI更准确地理解你的创作意图,并生成符合你期望的图像。以下是一些关于如何制作好提示的建议和技巧:

  1. 详细描述
    :在制作提示时,尽可能详细地描述你想要表达的主题。包括关于场景、对象、动作、情感和环境等方面的具体信息。详细的描述有助于AI更好地理解你的创作目标,并生成更加精确的图像。
  2. 使用关键字
    :在你的提示中包含强烈的关键字,这些关键字能够定义图像的样式和内容。例如,如果你想要生成一幅具有“赛博朋克”风格的场景,你的提示中应该包含“霓虹灯”、“高科技”、“城市”、“未来主义”等关键字。
  3. 利用提示生成器
    :对于初学者来说,使用提示生成器是一个学习和实践制作提示的好方法。这些工具可以提供分步指导和重要的关键字建议,帮助你理解如何构建有效的提示。就像学习一门新语言一样,掌握一组强大的关键字及其预期效果对于提高创作能力至关重要。
  4. 重用和修改现有提示
    :浏览提示集合,找到你喜欢的图像和对应的提示。尝试重用这些提示,并在此基础上进行修改,以观察不同元素如何影响最终的图像。这种方法可以帮助你理解为什么某些提示能够生成高质量的图像,并从中学习如何改进自己的提示。
  5. 使用图像收集网站
    :利用像PlaygroundAI这样的图像收集网站,选择你喜欢的图像,并尝试重新混合提示。虽然这可能需要一些时间和耐心,但它可以帮助你发现新的创意和灵感。
  6. 将提示视为起点
    :记住,提示只是创作过程的起点。不要害怕修改和调整提示,以满足你的具体需求。通过不断试验和优化,你将能够更好地掌握如何制作有效的提示,并创造出更加令人满意的图像。

构建良好提示的经验法则

在使用Stable Diffusion AI或其他AI图像生成工具时,遵循一些基本规则可以帮助你获得更好的结果。这里再次强调两个关键原则,以确保你的提示能够有效地指导AI生成你想要的图像。

1. 详细具体

详细具体的描述是制作有效提示的关键。由于AI无法直接读取你的思维,它依赖于你提供的文本提示来理解你的创作意图。因此,你需要尽可能地提供详尽的信息,以便AI能够准确地构建图像。

  • 场景描述
    :描述你想要的场景,包括时间、地点、环境和氛围。例如,“一个阳光明媚的海滩,金色沙滩,清澈的蓝天,椰子树随风摇曳。”
  • 对象细节
    :详细描述图像中的主要对象,包括它们的外观、颜色、大小和位置。例如,“一只橘色的猫坐在窗台上,凝视着外面的花园。”
  • 动作和表情
    :如果适用,描述对象的动作和表情。例如,“孩子们在公园里欢笑追逐,脸上洋溢着快乐的笑容。”

通过提供这样的细节,你可以帮助AI更好地理解你的创作愿景,并生成更加符合预期的图像。

2. 使用强有力的关键字

强有力的关键字可以帮助AI捕捉到你的创作意图中的关键元素,并将其体现在生成的图像中。关键字应该具有明确的视觉意义,能够指导AI生成特定的风格、色彩或情感。

  • 风格关键字
    :使用描述艺术风格或视觉风格的关键字。例如,“印象派”,“未来主义”,“超现实主义”等。
  • 色彩关键字
    :指定特定的颜色或色调,如“鲜艳的”,“柔和的”,“冷色调”等。
  • 情感关键字
    :描述你希望图像传达的情感或氛围,如“神秘”,“宁静”,“活力四射”等。

结合这两个原则,你可以创建出既详细又具有指导性的提示,从而提高AI生成图像的质量和准确性。记住,实践是提高技能的关键,不断尝试不同的提示和关键字组合,你将能够更好地掌握这个过程,并创造出更加令人满意的视觉作品。

这些参数是什么,我应该更改它们吗?

在使用Stable Diffusion AI或类似的AI图像生成器时,理解并掌握一些关键的参数设置是非常重要的。这些参数可以帮助你更精确地控制生成过程,并优化最终的图像输出。以下是一些你可以在大多数在线生成器中更改的参数,以及它们的作用和建议设置:

  1. 图像尺寸

    • 这是输出图像的分辨率。标准尺寸通常是512×512像素,但你可以根据自己的需求调整尺寸。
    • 更改图像尺寸会影响图像的纵横比,进而影响图像的布局和构图。例如,如果你想要生成一个全身像,使用纵向尺寸(如768×1024像素)可能会更合适。
    • 增大图像尺寸可以提供更多的细节,但同时也需要更多的计算资源和时间。
  2. 采样步骤

    • 这是指AI在生成图像时执行的迭代次数。一般来说,至少使用20个步骤。
    • 如果生成的图像看起来模糊或细节不足,增加采样步骤的数量可能会帮助改善图像质量。但是,这也会相应增加生成图像所需的时间。
  3. CFG 刻度

    • CFG(Classifier Free Guidance)刻度用于控制图像生成过程中对提示的遵循程度。典型值为7。
    • 增加CFG刻度的值会使生成的图像更严格地遵循文本提示,可能产生更符合描述的结果。然而,过高的值也可能导致图像过于僵硬或缺乏创造性。
  4. 种子值

    • 种子值用于控制图像生成的随机性。如果种子值设置为-1,AI将生成一个随机的图像。
    • 如果你希望每次都能得到相同的图像,可以指定一个具体的种子值。这对于调试和比较不同设置的影响非常有用。

我应该生成多少张图片?

在使用Stable Diffusion AI或其他AI图像生成工具进行创作时,测试提示并生成多个图像是一个明智的策略。这种方法可以帮助你更全面地评估提示的效果,并找到最佳的创作方案。以下是一些关于测试提示时的建议和注意事项:

  1. 生成多个图像

    • 当你在测试一个新的提示或对现有提示进行修改时,生成多个图像可以让你看到不同结果,从而更好地理解提示的效果范围。
    • 生成的图像数量取决于你所做的更改的大小。对于较大的更改,生成2-4张图像可以提供一个快速的概览,帮助你判断新提示的大致方向。对于较小的调整,一次生成4张或更多的图像可以增加发现满意结果的机会。
  2. 评估图像结果

    • 不要仅根据一张图像就判断一个提示的有效性。有些提示可能在某些情况下效果不佳,但在其他情况下却能产生出色的图像。
    • 花时间仔细查看每一张生成的图像,并思考它们与你的创作目标的契合程度。考虑图像的质量、风格、细节和创意等方面。
  3. 持续迭代

    • 将提示测试视为一个持续的迭代过程。根据生成的图像结果,不断调整和优化你的提示。
    • 如果你发现某个提示在某些情况下效果很好,试着找出成功的原因,并考虑是否可以将这些元素应用到其他提示中。
  4. 记录和比较

    • 记录你使用过的提示和相应的图像结果。这可以帮助你跟踪哪些提示是有效的,哪些需要改进。
    • 比较不同提示生成的图像,注意哪些元素是一致的,哪些是变化的。这可以帮助你理解哪些部分是提示中的关键要素,哪些部分是AI的创造性发挥。

什么是自定义模型?

Stability AI发布的官方模型,也就是基础模型,为使用AI生成图像提供了一个强大的起点。这些基础模型经过广泛的训练,能够生成多样化的图像内容,适应各种不同的应用场景。随着技术的发展,Stability AI不断推出更新的基础模型版本,如Stable Diffusion 1.4、1.5、2.0和2.1等,每个新版本都在性能和功能上有所提升。

在基础模型的基础上,可以进一步训练定制模型。这些定制模型通过使用额外的数据集进行训练,能够生成具有特定风格或包含特定对象的图像。这种定制化的训练过程为用户提供了极大的灵活性,使得AI图像生成技术可以更好地满足个人或特定行业的需求。

我应该使用哪种模型?

  • Stable Diffusion的三个主要版本:


    • V1 版本
      :包括1.4和1.5型号,这些是早期的稳定版本,对于初学者来说非常友好。它们提供了良好的图像质量和多样化的生成能力,是开始学习和实践的好选择。
    • V2 版本
      :包括2.0和2.1型号,这些版本在V1的基础上进行了改进和优化。虽然它们可能提供更高级的功能和更好的图像质量,但同时也可能更复杂,需要用户有一定的理解和经验。对于刚开始接触Stable Diffusion的用户,可能需要花费更多的时间来学习和掌握V2版本的使用。
    • Stable Diffusion XL (SDXL)
      :SDXL是一系列更大、更强大的模型,包括SDXL beta、SDXL 0.9和最新的SDXL 1.0。这些模型旨在生成更高分辨率的图像,提供更细致的细节和更广泛的创意空间。然而,由于它们的复杂性和资源需求,SDXL模型可能更适合有经验的用户或特定的项目需求。

如何训练新模型?

使用户不仅可以利用现有的模型生成图像,还可以通过特定的方法来创建和训练具有独特风格的自定义模型。以下是训练自定义模型的两种主要方法:Dreambooth和嵌入。

Dreambooth

Dreambooth是一种强大的自定义训练方法,它允许用户通过微调整个模型的权重来创建具有特定风格或主题的自定义模型。这种方法的核心在于使用大量的目标图像来训练模型,使其能够捕捉到特定的视觉特征和风格。

使用Dreambooth的优势在于:

  • 个性化
    :你可以创建一个完全根据你的个人喜好和需求定制的模型。
  • 微调能力
    :通过调整模型的权重,你可以精确控制生成图像的风格和特征。
  • 适用性广
    :无论是想要模仿特定的艺术风格,还是创建具有特定主题的图像,Dreambooth都能够提供支持。

embedding

Embedding方法是一种相对简单的方式来调整现有模型,使其生成特定风格或主题的图像。这种方法不会改变模型的基本结构,而是通过添加关键字来引导模型生成新的主题或风格。

使用嵌入的优势在于:

  • 操作简便
    :不需要进行复杂的模型训练,只需添加或修改关键字即可。
  • 灵活性
    :可以快速尝试不同的关键字组合,以找到最佳的图像生成效果。

如何使用 Stable Diffusion 制作高清图片?

在使用Stable Diffusion v1模型时,遵循特定的图像尺寸指南可以帮助确保生成的图像质量。原始分辨率为512×512像素,这是大多数Stable Diffusion模型默认的输出大小。如果你打算生成不同纵横比的图像,应该避免将宽度和高度设置得离512像素太远,以防止图像质量下降或出现重复的主题。

生成初始图像后,如果你希望获得更高分辨率的图像,可以使用AI升级器进行放大。

点我查看更多精彩内容:www.flydean.com

mydocker-logs.png

本文为从零开始写 Docker 系列第十篇,实现类似 docker logs 的功能,使得我们能够查查看容器日志。


完整代码见:
https://github.com/lixd/mydocker
欢迎 Star


推荐阅读以下文章对 docker 基本实现有一个大致认识:


开发环境如下:

root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.2 LTS
Release:	20.04
Codename:	focal
root@mydocker:~# uname -r
5.4.0-74-generic


注意:需要使用 root 用户

1. 概述

上一篇已经实现了
mydocker ps
命令,可以查看到当前运行中的容器了。

本篇主要实现
mydocker logs
,让我们可以随时查看容器日志。

一般来说,对于容器中运行的进程,使日志打印到标准输出是一个非常好的实现方案,因此需要将容器中的标准输出保存下来,以便需要的时候访问。

我们就以此作为思路来实现 mydocker logs 命令:

  • 启动时将容器进程的标准输出挂载到
    /var/lib/mydocker/containers/{containerId}/{containerId}-json.log
    文件中
  • mydocker logs 则读取这个文件以获取容器日志

实际上 docker 实现也类似,他会把容器日志存储在
var/lib/docker/containers/{containerId}/{containerId}-json.log
文件中。

2. 实现

具体实现包括两部分:

  • 1)重定向输出到文件
  • 2)实现 mydocker logs 命令

输出重定向

首先,需要修改一下原来的实现,在创建后台运行容器的时候,把进程的标准输出重新定向一下到日志文件。

前台容器依旧打印到 Stdout 即可

func NewParentProcess(tty bool, volume, containerId string) (*exec.Cmd, *os.File) {
// 省略其他内存
	if tty {
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	} else {
		// 对于后台运行容器,将 stdout、stderr 重定向到日志文件中,便于后续查看
		dirPath := fmt.Sprintf(InfoLocFormat, containerId)
		if err := os.MkdirAll(dirURL, constant.Perm0622); err != nil {
			log.Errorf("NewParentProcess mkdir %s error %v", dirURL, err)
			return nil, nil
		}
		stdLogFilePath := dirPath + LogFile
		stdLogFile, err := os.Create(stdLogFilePath)
		if err != nil {
			log.Errorf("NewParentProcess create file %s error %v", stdLogFilePath, err)
			return nil, nil
		}
		cmd.Stdout = stdLogFile
		cmd.Stderr = stdLogFile
	}
// ...
}

实现 logs 命令

在 main_command.go 中添加一个 logCommand:

var logCommand = cli.Command{
    Name:  "logs",
    Usage: "print logs of a container",
    Action: func(context *cli.Context) error {
       if len(context.Args()) < 1 {
          return fmt.Errorf("please input your container name")
       }
       containerName := context.Args().Get(0)
       logContainer(containerName)
       return nil
    },
}

并加到 main 函数中。

func main(){
  // 省略其他内容
  app.Commands = []cli.Command{
       initCommand,
       runCommand,
       commitCommand,
       listCommand,
       logCommand,
    }
}

具体实现如下:

func logContainer(containerName string) {
    logFileLocation := fmt.Sprintf(container.InfoLocFormat, containerName) + container.LogFile
    file, err := os.Open(logFileLocation)
    defer file.Close()
    if err != nil {
       log.Errorf("Log container open file %s error %v", logFileLocation, err)
       return
    }
    content, err := ioutil.ReadAll(file)
    if err != nil {
       log.Errorf("Log container read file %s error %v", logFileLocation, err)
       return
    }
    _, err = fmt.Fprint(os.Stdout, string(content))
    if err != nil {
       log.Errorf("Log container Fprint  error %v", err)
       return
    }
}

实现很简单,根据 containerId 拼接出完整路径,读取文件内容并重定向到标准输出即可。

3. 测试

启动一个后台容器

root@mydocker:~/feat-logs/mydocker# go build .
root@mydocker:~/feat-logs/mydocker# ./mydocker run -d -name mytop top
{"level":"info","msg":"createTty false","time":"2024-01-26T11:25:53+08:00"}
{"level":"info","msg":"resConf:\u0026{ 0  }","time":"2024-01-26T11:25:53+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-26T11:25:53+08:00"}
{"level":"error","msg":"mkdir dir /root/merged error. mkdir /root/merged: file exists","time":"2024-01-26T11:25:53+08:00"}
{"level":"error","msg":"mkdir dir /root/upper error. mkdir /root/upper: file exists","time":"2024-01-26T11:25:53+08:00"}
{"level":"error","msg":"mkdir dir /root/work error. mkdir /root/work: file exists","time":"2024-01-26T11:25:53+08:00"}
{"level":"info","msg":"mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir=/root/busybox,upperdir=/root/upper,workdir=/root/work /root/merged]","time":"2024-01-26T11:25:53+08:00"}
{"level":"info","msg":"command all is top","time":"2024-01-26T11:25:53+08:00"}

查看容器列表

root@mydocker:~/feat-logs/mydocker# ./mydocker ps
{"level":"error","msg":"read file /var/lib/mydocker/containers/0439540405/config.json error open /var/lib/mydocker/containers/0439540405/config.json: no such file or directory","time":"2024-01-26T11:26:13+08:00"}
{"level":"error","msg":"get container info error open /var/lib/mydocker/containers/0439540405/config.json: no such file or directory","time":"2024-01-26T11:26:13+08:00"}
ID           NAME         PID         STATUS      COMMAND     CREATED
0439540405   mytop        171754      running     top         2024-01-26 11:25:53

容器 Id 为 0439540405,查看对应目录是否生成了日志文件

root@mydocker:~/feat-logs/mydocker# ls /var/lib/mydocker/containers/0439540405/
0439540405-json.log config.json

其中的
0439540405-json.log
就是日志文件,
config.json
则是上一次添加的容器信息记录文件。

查看日志文件内容

root@mydocker:~/feat-logs/mydocker# cat /var/lib/mydocker/containers/0439540405/0439540405-json.log
Mem: 1793456K used, 241932K free, 1064K shrd, 91276K buff, 1354272K cached
CPU:  0.4% usr  0.0% sys  0.0% nic 99.3% idle  0.0% io  0.0% irq  0.2% sirq
Load average: 0.01 0.01 0.00 1/141 4
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND

说明,日志存储是正常的。

接下里执行
mydocker logs
看看是否能查询到日志

root@mydocker:~/feat-logs/mydocker# ./mydocker logs 0439540405
Mem: 1793424K used, 241964K free, 1064K shrd, 91316K buff, 1354280K cached
CPU:  0.0% usr  0.0% sys  0.0% nic  100% idle  0.0% io  0.0% irq  0.0% sirq
Load average: 0.00 0.00 0.00 1/141 4
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND

可以看到,mydocker logs 命令成功运行并输出了容器的日志。

至此,说明我们的 mydocker logs 命令实现是 ok 的。

4. 小结

本篇主要实现
mydocker logs
命令,和 docker 实现基本类似:

  • 容器启动把 stdout、stderr 重定向到
    /var/lib/mydocker/container/{containerId}/{containerId}-json.log
    文件
  • 执行 mydocker logs 则根据容器 Id 找到对应文件,读取文件内容并打印


【从零开始写 Docker 系列】
持续更新中,搜索公众号【
探索云原生
】订阅,阅读更多文章。




完整代码见:
https://github.com/lixd/mydocker
欢迎关注~

相关代码见
feat-logs
分支,测试脚本如下:

需要提前在 /root 目录准备好 busybox.tar 文件,具体见第四篇第二节。

# 克隆代码
git clone -b feat-logs https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试 
./mydocker run -d -name c1 top
# 查看容器 Id
./mydocker ps
# 根据 Id 查询日志
./mydocker logs ${containerId}

在17.10预览版2中,我们刚刚发布了预览支持,可以直接在 Visual Studio 的工作文件中查看 GitHub 和 Azure DevOps 的拉取请求注释。作为开发者社区中最受欢迎的 Git 工具特性建议之一,我们需要您的帮助来确保我们在正确的轨道上!

开始使用 Pull Request Comments

1 确保您的 Visual Studio 版本是17.10预览2或更高版本,并且已经在 Visual Studio 中使用 GitHub 或 Azure DevOps 帐户登录。您可能需要在 Tools > Options > Preview Features > Pull Request Comments 中启用该功能。

2 签出一个有活动拉取请求的分支,等待“Show comments in files”信息栏。或者在顶层菜单中选择 Git> GitHub or Azure DevOps > Show Comments in Files

3 现在,您将能够在活动的文件中看到您的注释,并从注释本身或工具栏在它们之间导航。

请注意,您无法查看已删除的文件或 Visual Studio 解决方案资源管理器不支持的任何文件类型。此外,我们正在跟踪一个已知的错误,在评论中  @mentions 呈现id(guid)而不是用户的名字。

渴望您的反馈

这个预览版只是开发者社区中所有建议的第一步,我们需要您的反馈来继续推动这一进程。一些未来的规划如下:

- 在 diff 视图中查看注释

- 添加注释和管理评审

- 通过支持搜索和过滤的列表导航注释

请分享您对该功能的看法,以及您在本次调查中最期待的内容。

保持联系

我们感谢您花时间报告问题/建议,并希望您在使用 Visual Studio 时继续给我们反馈,告诉我们您喜欢什么以及我们可以改进什么。您的反馈对于帮助我们使 Visual Studio 成为最好的工具至关重要!您可以通过开发者社区与我们分享反馈,通过发送反馈来报告问题或分享您的建议,推动对新功能或现有功能的改进。

通过在 YouTube, Twitter, LinkedIn, Twitch 和 Microsoft Learn 上关注我们与 Visual Studio 团队保持联系。

原文链接:https://devblogs.microsoft.com/visualstudio/see-your-pull-request-comments-with-the-latest-preview/