普通视图

发现新文章,点击刷新页面。
今天 — 2025年4月3日首页
  • ✇游钓四方的博客
  • 产品被拒游钓四方的博客
    晚上下了班打开电脑刚坐下就看到了一封 Google 邮件,首先看到了发件人 “Chrome Web Store”,当时就心想提交审核一个多星期了,终于看到一点音信了。点开后,还没等我高兴,便看到了: 解决BUG 被拒的原因非常低级,声明了但未使用的 scripting 权限。 scripting 权限是 Manifest V3 中引入的一个重要权限,主要用于动态脚本执行chrome.scripting.executeScript()和动态样式注入chrome.scripting.insertCSS() 而在EasyFill中,使用的是静态声明: content_scripts: [ { matches: ['<all_urls>'], js: ['content-scripts/content.js'] } ] 删除scripting参数后,重新打包并再次向 Chrome Web Store 提交了扩展。 就这么一个小BUG,浪费了我一个星期的审核时间,太耽误事了,当时为了解决 Shadow DOM 才使用 scripting,直到
     

产品被拒

2025年4月3日 00:05

晚上下了班打开电脑刚坐下就看到了一封 Google 邮件,首先看到了发件人 “Chrome Web Store”,当时就心想提交审核一个多星期了,终于看到一点音信了。点开后,还没等我高兴,便看到了:

Chrome 应用商店:"EasyFill"被拒通知

解决BUG

被拒的原因非常低级,声明了但未使用的 scripting 权限。

scripting 权限是 Manifest V3 中引入的一个重要权限,主要用于动态脚本执行chrome.scripting.executeScript()和动态样式注入chrome.scripting.insertCSS()

而在EasyFill中,使用的是静态声明:

content_scripts: [
  {
    matches: ['<all_urls>'],
    js: ['content-scripts/content.js']
  }
]

删除scripting参数后,重新打包并再次向 Chrome Web Store 提交了扩展。

就这么一个小BUG,浪费了我一个星期的审核时间,太耽误事了,当时为了解决 Shadow DOM 才使用 scripting,直到现在这个问题也没有解决,希望下个版本可以解决问题

产品谍照:

EasyFill·我的信息

昨天以前首页
  • ✇游钓四方的博客
  • 注册 Chrome Web Store 开发者游钓四方的博客
    年前曾尝试过 Chrome 扩展开发,《写一个Chrome表单自动化插件》,但是由于没有注册 Chrome Web Store 开发者,无法上传到 Chrome 应用商店。 注册 WildCard Chrome 注册开发者需要五美元,由于我没有境外信用卡就一直卡在这,2022 年我在杭州办过一张中信的双币卡,年费很高,后来经济紧张时注销了,现在急着用外币还挺麻烦,折腾一圈,最终无脑选择了 WildCard,尽管网上对它负面评论铺天盖地。 WildCard 开卡费用是 10.99 美元/年,实际付款 79.71 人民币,按照今天的市场汇率 7.23,实际多付了 0.24,而且这只是开卡费用,充值另算。 开卡后我充值了 10 美元,支付宝付款 75.07,到账金额 10 美元: \[\frac{2.77}{75.07} \times 100\% \approx 3.69\%\] 四个点我能接受(接不接受都要受着),这个开卡费不便宜,毕竟钱不是大风刮来的,所以注册时,我创建了两个号,推荐注册返现两美金… 注册 Chrome Web Store 开发者 注册账号就很容易
     

注册 Chrome Web Store 开发者

2025年3月20日 00:03

年前曾尝试过 Chrome 扩展开发,《写一个Chrome表单自动化插件》,但是由于没有注册 Chrome Web Store 开发者,无法上传到 Chrome 应用商店。

注册 WildCard

Chrome 注册开发者需要五美元,由于我没有境外信用卡就一直卡在这,2022 年我在杭州办过一张中信的双币卡,年费很高,后来经济紧张时注销了,现在急着用外币还挺麻烦,折腾一圈,最终无脑选择了 WildCard,尽管网上对它负面评论铺天盖地。

WildCard 开卡账单

WildCard 开卡费用是 10.99 美元/年,实际付款 79.71 人民币,按照今天的市场汇率 7.23,实际多付了 0.24,而且这只是开卡费用,充值另算。

开卡后我充值了 10 美元,支付宝付款 75.07,到账金额 10 美元:

\[\frac{2.77}{75.07} \times 100\% \approx 3.69\%\]

四个点我能接受(接不接受都要受着),这个开卡费不便宜,毕竟钱不是大风刮来的,所以注册时,我创建了两个号,推荐注册返现两美金…

注册 Chrome Web Store 开发者

Chrome Web Store 账单

注册账号就很容易了,Google 绑卡付钱就行。但是如果要销售发布就很麻烦:

个人交易者声明

  • 您需要提供一个手机号码以验证是您本人在操作
    • 您将通过手机接收代码
  • 用于证明是您本人的身份证件
  • 可接受的文件包括:
    • 驾照
    • 护照
    • 州身份证明
    • 绿卡
  • 您需要提供一份显示您的姓名和当前地址的文件
  • 可接受的文件包括:
    • 由政府签发的文件或带照片的身份证件
    • 公共事业缴费单或话费账单(日期在过去 60 天内)
    • 银行对账单(日期在过去 60 天内)
    • 租赁合同或抵押贷款合同

因为 Google 已退出中国市场,不支持交易。而我是美国 Visa 卡,面对这样的要求不容易做到。

日后再说吧,往后这段时间,我打算把博客评论表单自动填充插件重构一下,然后上架 Chrome 应用商店。

  • ✇游钓四方的博客
  • 利用 Go + COS + GitHub 重构 RSS 爬虫游钓四方的博客
    之前我写过一篇《利用Go+Github Actions写个定时RSS爬虫》来实现这一目的,主要是用 GitHub Actions + Go 进行持续的 RSS 拉取,再把结果上传到 GitHub Pages 站点 但是遇到一些网络延迟、TLS 超时问题,导致订阅页面访问速度奇慢,抓取的数据也不完整,后来时断时续半个月重构了代码,进一步增强了并发和容错机制 在此感谢 GPT o1 给予的帮助,我已经脱离老本行很多年了,重构的压力真不小,有空就利用下班的时间进行调试,在今天凌晨 03:00 我终于写完了 1. 为什么要重构 旧版本主要基于 GitHub Actions 的定时触发,抓取完后把结果存放进 _data/rss_data.json 然后 Jekyll 就可以直接引用这个文件来展示订阅,但是这个方案有诸多不足: 网络不稳定导致的抓取失败 由于原先的重试机制不够完善,GitHub Actions 在国外,RSS 站点大多在国内,一旦连接超时就挂,一些 RSS 无法成功抓取 单线程串行,速度偏慢 旧版本一次只能串行抓取 RS
     

利用 Go + COS + GitHub 重构 RSS 爬虫

2025年3月12日 03:26

之前我写过一篇《利用Go+Github Actions写个定时RSS爬虫》来实现这一目的,主要是用 GitHub Actions + Go 进行持续的 RSS 拉取,再把结果上传到 GitHub Pages 站点

但是遇到一些网络延迟、TLS 超时问题,导致订阅页面访问速度奇慢,抓取的数据也不完整,后来时断时续半个月重构了代码,进一步增强了并发和容错机制

在此感谢 GPT o1 给予的帮助,我已经脱离老本行很多年了,重构的压力真不小,有空就利用下班的时间进行调试,在今天凌晨 03:00 我终于写完了

1. 为什么要重构

旧版本主要基于 GitHub Actions 的定时触发,抓取完后把结果存放进 _data/rss_data.json 然后 Jekyll 就可以直接引用这个文件来展示订阅,但是这个方案有诸多不足:

  1. 网络不稳定导致的抓取失败

    由于原先的重试机制不够完善,GitHub Actions 在国外,RSS 站点大多在国内,一旦连接超时就挂,一些 RSS 无法成功抓取

  2. 单线程串行,速度偏慢

    旧版本一次只能串行抓取 RSS,效率低,数量稍多就拉长整体执行时间,再加上外网到内地的延时,更显迟缓

  3. 日志不够完善

    出错时写到的日志文件只有大概的错误描述,无法区分是解析失败、头像链接失效还是RSS本身问题,排查不便

  4. 访问速度影响大

    这是主要的重构原因!在旧版本里,抓取后的 JSON 数据是要存储到 Github 仓库的,虽然有 CDN 加持,但 GitHub Pages 的定时任务会引起连锁反应,当新内容刷新时容易出现访问延迟,极端情况下网页都挂了

    重构后,在此基础上进行了大幅重构,引入了并发抓取 + 指数退避重试 + GitHub/COS 双端存储的能力,抓取稳定性和页面访问速度都得到显著提升

2. 主要思路

2.1 整体流程

先看个简单的流程图

        +--------------------------+
        | 1. 读取RSS列表(双端可选)  |
        +------------+-------------+
                     |
                     v
           +---------------------+
           | 2. 并发抓取RSS,限流   |
           |  (max concurrency)  |
           +-------+-------------+
                   |
                   v
        +------------------------------+
        | 3. 指数退避算法 (重试解析失败)  |
        +------------------------------+
                   |
                   v
           +-------------------+
           | 4. 结果整合排序    |
           +--------+----------+
                    |
                    v
        +-------------------------+
        | 5. 上传 RSS (双端可选)   |
        +-------------------------+
                    |
                    v
           +--------------------+
           | 6. 写日志到GitHub   |
           +--------------------+
  1. 并发抓取 + 限流
    通过 Go 的 goroutine 并发抓取 RSS,同时用一个 channel 来限制最大并发数

  2. 指数退避重试
    每个 RSS 如果第一次抓取失败,则会间隔几秒后再次重试,且间隔呈指数级递增(1s -> 2s -> 4s),最多重试三次,极大提高成功率

  3. 灵活存储
    RSS_SOURCE: 可以决定从 COS 读取一个远程 txt 文件(里面存放 RSS 列表),或直接从 GitHub 的 data/rss.txt 读取
    SAVE_TARGET: 可以把抓取结果上传到 GitHub,或者传到腾讯云 COS

  4. 日志自动清理
    每次成功写入日志后,会检查 logs/ 目录下的日志文件,若超过 7 天就自动删除,避免日志越积越多

2.2 指数退避

上一次写指数退避,还是在养老院写PHP的时候,时过境迁啊,这段算法我调试了很久,其实不难,也就是说失败一次,就等待更长的时间再重试,配置如下:

  • 最大重试次数: 3
  • 初始等待: 1秒
  • 等待倍数: 2.0

也就是说失败一次就加倍等待,下次若依然失败就再加倍,如果三次都失败则放弃处理

// fetchAllFeeds 并发抓取所有RSS链接,返回抓取结果及统计信息
//
// Description:
//
//   该函数读取传入的所有RSS链接,使用10路并发进行抓取
//   在抓取过程中对解析失败、内容为空等情况进行统计
//   若抓取的RSS头像缺失或无法访问,将替换为默认头像
//
// Parameters:
//   - ctx           : 上下文,用于控制网络请求的取消或超时
//   - rssLinks      : RSS链接的字符串切片,每个链接代表一个RSS源
//   - defaultAvatar : 备用头像地址,在抓取头像失败或不可用时使用
//
// Returns:
//   - []feedResult         : 每个RSS链接抓取的结果(包含成功的Feed及其文章或错误信息)
//   - map[string][]string  : 各种问题的统计记录(解析失败、内容为空、头像缺失、头像不可用)
func fetchAllFeeds(ctx context.Context, rssLinks []string, defaultAvatar string) ([]feedResult, map[string][]string) {
	// 设置最大并发量,以信道(channel)信号量的方式控制
	maxGoroutines := 10
	sem := make(chan struct{}, maxGoroutines)

	// 等待组,用来等待所有goroutine执行完毕
	var wg sync.WaitGroup

	resultChan := make(chan feedResult, len(rssLinks)) // 用于收集抓取结果的通道
	fp := gofeed.NewParser()                           // RSS解析器实例

	// 遍历所有RSS链接,为每个RSS链接开启一个goroutine进行抓取
	for _, link := range rssLinks {
		link = strings.TrimSpace(link)
		if link == "" {
			continue
		}
		wg.Add(1)         // 每开启一个goroutine,对应Add(1)
		sem <- struct{}{} // 向sem发送一个空结构体,表示占用了一个并发槽

		// 开启协程
		go func(rssLink string) {
			defer wg.Done()          // 协程结束时Done
			defer func() { <-sem }() // 函数结束时释放一个并发槽

			var fr feedResult
			fr.FeedLink = rssLink

			// 抓取RSS Feed, 无法解析时,使用指数退避算法进行重试, 有3次重试, 初始1s, 倍数2.0
			feed, err := fetchFeedWithRetry(rssLink, fp, 3, 1*time.Second, 2.0)
			if err != nil {
				fr.Err = wrapErrorf(err, "解析RSS失败: %s", rssLink)
				resultChan <- fr
				return
			}

			if feed == nil || len(feed.Items) == 0 {
				fr.Err = wrapErrorf(fmt.Errorf("该订阅没有内容"), "RSS为空: %s", rssLink)
				resultChan <- fr
				return
			}

			// 获取RSS的头像信息(若RSS自带头像则用RSS的,否则尝试从博客主页解析)
			avatarURL := getFeedAvatarURL(feed)
			fr.Article = &Article{
				BlogName: feed.Title,
			}

			// 检查头像可用性
			if avatarURL == "" {
				// 若头像链接为空,则标记为空字符串
				fr.Article.Avatar = ""
			} else {
				ok, _ := checkURLAvailable(avatarURL)
				if !ok {
					fr.Article.Avatar = "BROKEN" // 无法访问,暂记为BROKEN
				} else {
					fr.Article.Avatar = avatarURL // 正常可访问则记录真实URL
				}
			}

			// 只取最新一篇文章作为结果
			latest := feed.Items[0]
			fr.Article.Title = latest.Title
			fr.Article.Link = latest.Link

			// 解析发布时间,如果 RSS 解析器本身给出了 PublishedParsed 直接用,否则尝试解析 Published 字符串
			pubTime := time.Now()
			if latest.PublishedParsed != nil {
				pubTime = *latest.PublishedParsed
			} else if latest.Published != "" {
				if t, e := parseTime(latest.Published); e == nil {
					pubTime = t
				}
			}
			fr.ParsedTime = pubTime
			fr.Article.Published = pubTime.Format("02 Jan 2006")

			resultChan <- fr
		}(link)
	}

	// 开启一个goroutine等待所有抓取任务结束后,关闭resultChan
	go func() {
		wg.Wait()
		close(resultChan)
	}()

	// 用于统计各种问题
	problems := map[string][]string{
		"parseFails":   {}, // 解析 RSS 失败
		"feedEmpties":  {}, // 内容 RSS 为空
		"noAvatar":     {}, // 头像地址为空
		"brokenAvatar": {}, // 头像无法访问
	}
	// 收集抓取结果
	var results []feedResult

	for r := range resultChan {
		if r.Err != nil {
			errStr := r.Err.Error()
			switch {
			case strings.Contains(errStr, "解析RSS失败"):
				problems["parseFails"] = append(problems["parseFails"], r.FeedLink)
			case strings.Contains(errStr, "RSS为空"):
				problems["feedEmpties"] = append(problems["feedEmpties"], r.FeedLink)
			}
			results = append(results, r)
			continue
		}

		// 对于成功抓取的Feed,如果头像为空或不可用则使用默认头像
		if r.Article.Avatar == "" {
			problems["noAvatar"] = append(problems["noAvatar"], r.FeedLink)
			r.Article.Avatar = defaultAvatar
		} else if r.Article.Avatar == "BROKEN" {
			problems["brokenAvatar"] = append(problems["brokenAvatar"], r.FeedLink)
			r.Article.Avatar = defaultAvatar
		}
		results = append(results, r)
	}
	return results, problems
}

2.3 并发抓取 + 限流

为避免一下子开几十上百个协程导致阻塞,可以配合一个带缓存大小的 channel

maxGoroutines := 10
sem := make(chan struct{}, maxGoroutines)

for _, rssLink := range rssLinks {
    // 启动 goroutine 前先写入一个空 struct
    sem <- struct{}{}
    go func(link string) {
        // goroutine 执行结束后释放 <-sem
        defer func() { <-sem }()
        fetchFeedWithRetry(link, parser, 3, 1*time.Second, 2.0)
        // ...
    }(rssLink)
}

3. 对比旧版本的改进

  1. 容错率显著提升

    遇到网络抖动、超时等问题,能以10路并发限制式自动重试,很少出现直接拿不到数据

  2. 抓取速度更快

    以 10 路并发为例,对于数量多的 RSS,速度提升明显

  3. 日志分类更细

    分清哪条 RSS 是解析失败,哪条头像挂了,哪条本身有问题,后续维护比只给个403 Forbidden方便太多

  4. 支持 COS

    可将最终 data.json 放在 COS 上进行 CDN 加速;也能继续放在 GitHub,视自己需求而定

  5. 自动清理过期日志

    每次抓取后检查 logs/ 目录下 7 天之前的日志并删除,不用手工清理了

4. Go 生成的 JSON 和日志长啥样

4.1 RSS

抓取到的文章信息会按时间降序排列,示例:

{
  "items": [
    {
      "blog_name": "obaby@mars",
      "title": "品味江南(三)–虎丘塔 东方明珠",
      "published": "10 Mar 2025",
      "link": "https://oba.by/2025/03/19714",
      "avatar": "https://oba.by/wp-content/uploads/2020/09/icon-500-100x100.png"
    },
    {
      "blog_name": "风雪之隅",
      "title": "PHP8.0的Named Parameter",
      "published": "10 May 2022",
      "link": "https://www.laruence.com/2022/05/10/6192.html",
      "avatar": "https://www.laruence.com/logo.jpg"
    }
  ],
  "updated": "2025年03月11日 07:15:57"
}

4.2 日志

程序每次运行完毕后,把抓取统计和问题列表写到 GitHub 仓库 logs/YYYY-MM-DD.log:

[2025-03-11 07:15:57] 本次订阅抓取结果统计:
[2025-03-11 07:15:57] 共 25 条RSS, 成功抓取 24 条.
[2025-03-11 07:15:57] ✘ 有 1 条订阅解析失败:
[2025-03-11 07:15:57] - https://tcxx.info/feed
[2025-03-11 07:15:57] ✘ 有 1 条订阅头像无法访问, 已使用默认头像:
[2025-03-11 07:15:57] - https://www.loyhome.com/feed

5. 照葫芦画瓢

如果你也想玩玩 LhasaRSS

  1. 准备一份 RSS 列表(TXT):

    格式:每行一个 URL
    如果 RSS_SOURCE = GITHUB,则可以放在项目中的 data/rss.txt
    如果 RSS_SOURCE = COS,就把它上传到某个 https://xxx.cos.ap-xxx.myqcloud.com/rss.txt

  2. 配置好环境变量:

    默认所有数据保存到 Github,所以 COS API 环境变量不是必要的

     env:
         TOKEN:                    ${{ secrets.TOKEN }}                    # GitHub Token
         NAME:                     ${{ secrets.NAME }}                     # GitHub 用户名
         REPOSITORY:               ${{ secrets.REPOSITORY }}               # GitHub 仓库名
         TENCENT_CLOUD_SECRET_ID:  ${{ secrets.TENCENT_CLOUD_SECRET_ID }}  # 腾讯云 COS SecretID
         TENCENT_CLOUD_SECRET_KEY: ${{ secrets.TENCENT_CLOUD_SECRET_KEY }} # 腾讯云 COS SecretKey
         RSS:                      ${{ secrets.RSS }}                      # RSS 列表文件
         DATA:                     ${{ secrets.DATA }}                     # 抓取后的数据文件
         DEFAULT_AVATAR:           ${{ secrets.DEFAULT_AVATAR }}           # 默认头像 URL
         RSS_SOURCE                ${{ secrets.RSS_SOURCE }}               # 可选参数 GITHUB or COS
         SAVE_TARGET               ${{ secrets.SAVE_TARGET }}              # 可选参数 GITHUB or COS
    


  3. 部署并运行

    只需 go run . 或在 GitHub Actions workflow_dispatch 触发 运行结束后,就会在 data 文件夹更新 data.json,日志则写进 GitHub logs/ 目录,并且自动清理旧日志

注:如果你依旧想完全托管在 COS 上,需要把 RSS_SOURCE 和 SAVE_TARGET 都写为 COS,然后使用 GitHub Actions 去调度

相关文档

  • ✇游钓四方的博客
  • Blog Function Update 2025 (2)游钓四方的博客
    Update details 移除红灯笼 新增 sitemap.xml 和 sitemap.txt,自动生成,不再手动更新! 之前我一直使用 xml-sitemaps 手动生成sitemap.xml,但每当 URL 新增或变更都需要手动提交。实在麻烦!所以,今日用 Liquid 实现自动生成,一劳永逸 sitemap.xml 优化策略 首页优先级最高 (1.0),其他页面次之 (0.8) 新文章优先级高(30 天内 0.9,半年内 0.8,一年内 0.6),让新内容更容易被搜索引擎收录 旧文章优先级降低(1 年以上 0.4,2 年以上 0.2),减少搜索引擎对老旧内容的爬取 动态调整 changefreq,确保新内容频繁爬取,而老文章爬取频率降低 --- layout: null --- <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> {% assign now = site.time
     

Blog Function Update 2025 (2)

2025年2月6日 13:03

Update details

  • 移除红灯笼
  • 新增 sitemap.xmlsitemap.txt,自动生成,不再手动更新!

之前我一直使用 xml-sitemaps 手动生成sitemap.xml,但每当 URL 新增或变更都需要手动提交。实在麻烦!所以,今日用 Liquid 实现自动生成,一劳永逸

sitemap.xml 优化策略

  • 首页优先级最高 (1.0),其他页面次之 (0.8)
  • 新文章优先级高(30 天内 0.9,半年内 0.8,一年内 0.6),让新内容更容易被搜索引擎收录
  • 旧文章优先级降低(1 年以上 0.4,2 年以上 0.2),减少搜索引擎对老旧内容的爬取
  • 动态调整 changefreq,确保新内容频繁爬取,而老文章爬取频率降低
---
layout: null
---
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  {% assign now = site.time | date: "%s" | plus: 0 %}
  
  {% for page in site.pages %}
    {% if page.url == "/" %}
    <!-- 首页优先级最高 -->
      {% assign page_priority = "1.0" %}
    {% else %}
      {% assign page_priority = "0.8" %}
    {% endif %}
    
    <url>
      <loc>{{ site.url }}{{ page.url | replace:'index.html','' }}</loc>
      <lastmod>{{ site.time | date_to_xmlschema }}</lastmod>
      <changefreq>weekly</changefreq>
      <priority>{{ page_priority }}</priority>
    </url>
  {% endfor %}
  
  <!-- 根据发布时间动态调整 priority 和 changefreq -->
  {% for post in site.posts %}
    {% assign post_time = post.date | date: "%s" | plus: 0 %}
    {% assign diff = now | minus: post_time %}
    {% assign days_old = diff | divided_by: 86400 %}
    
    {% if days_old < 30 %}
      {% assign priority = "0.9" %}
      {% assign changefreq = "daily" %}
    {% elsif days_old < 180 %}
      {% assign priority = "0.8" %}
      {% assign changefreq = "weekly" %}
    {% elsif days_old < 365 %}
      {% assign priority = "0.6" %}
      {% assign changefreq = "monthly" %}
    {% elsif days_old < 730 %}
      {% assign priority = "0.4" %}
      {% assign changefreq = "yearly" %}
    {% else %}
      {% assign priority = "0.2" %}
      {% assign changefreq = "never" %}
    {% endif %}
    
    <url>
      <loc>{{ site.url }}{{ post.url }}</loc>
      <lastmod>{{ post.date | date_to_xmlschema }}</lastmod>
      <changefreq>{{ changefreq }}</changefreq>
      <priority>{{ priority }}</priority>
    </url>
  {% endfor %}
</urlset>

sitemap.txt 兼容旧版爬虫

sitemap.txt 适用于不支持 XML 的搜索引擎(如某些旧版爬虫)

---
layout: null
permalink: /sitemap.txt
---
{% for page in site.pages %}
{{ site.url }}{{ page.url | replace:'index.html','' }}
{% endfor %}

{% for post in site.posts %}
{{ site.url }}{{ post.url }}
{% endfor %}

在 robots.txt 里声明 Sitemap

确保搜索引擎能找到 Sitemap,需要在 robots.txt 文件中声明 sitemap.xmlsitemap.txt

User-agent: *
Allow: /

User-agent: MJ12bot
Disallow: /
User-agent: AhrefsBot
Disallow: /
User-agent: SemrushBot
Disallow: /
User-agent: dotbot
Disallow: /

Sitemap: https://lhasa.icu/sitemap.xml
Sitemap: https://lhasa.icu/sitemap.txt

  • ✇游钓四方的博客
  • Blog Function Update 2025 (1)游钓四方的博客
    由于郑州最近的雨夹雪天气,已经一周没有骑行了,实在憋得不行,给自己找点事做,今天中午下班时更新了一下博客 Update details 修复了柱形图显示错位 移除了骑行页面的活动天数 新增了全年骑行总时长、全年骑行总公里数 柱形图的宽度不再由骑行时长来计算,而是由骑行公里数来计算显示 新增春节快乐红灯笼(移动端不支持) 移除 node-sass 包,由 sass 代替 Fix Bugs:柱形图显示错位 当前的柱形图仅为有骑行数据的周生成柱形图,导致柱形图与日历中的周对齐错位,所以即某周没有骑行数据时,柱形图也要生成一根柱子 function generateBarChart() { const barChartElement = document.getElementById('barChart'); // 清空柱形图内容 barChartElement.innerHTML = ''; const today = getChinaTime(); const startDate = getStartDate(today
     

Blog Function Update 2025 (1)

2025年1月26日 00:10

由于郑州最近的雨夹雪天气,已经一周没有骑行了,实在憋得不行,给自己找点事做,今天中午下班时更新了一下博客

Update details

  • 修复了柱形图显示错位
  • 移除了骑行页面的活动天数
  • 新增了全年骑行总时长、全年骑行总公里数
  • 柱形图的宽度不再由骑行时长来计算,而是由骑行公里数来计算显示
  • 新增春节快乐红灯笼(移动端不支持)
  • 移除 node-sass 包,由 sass 代替

Fix Bugs:柱形图显示错位

当前的柱形图仅为有骑行数据的周生成柱形图,导致柱形图与日历中的周对齐错位,所以即某周没有骑行数据时,柱形图也要生成一根柱子

function generateBarChart() {
    const barChartElement = document.getElementById('barChart');
    // 清空柱形图内容
    barChartElement.innerHTML = '';

    const today = getChinaTime();
    const startDate = getStartDate(today, 21);

    // 创建所有周的时间范围
    const weeklyData = {};
    let currentWeekStart = new Date(startDate);
    currentWeekStart.setUTCHours(0, 0, 0, 0);

    // 按周计算未来 4 周的日期范围
    for (let i = 0; i < 4; i++) {
        const weekStart = new Date(currentWeekStart);
        const weekEnd = new Date(weekStart);
        // 一周结束日期为开始日期 +6 天
        weekEnd.setUTCDate(weekStart.getUTCDate() + 6);
        const weekKey = `${weekStart.toISOString().split('T')[0]} - ${weekEnd.toISOString().split('T')[0]}`;

        // 初始化每周骑行数据为 0
        weeklyData[weekKey] = 0;
        // 移动到下一周
        currentWeekStart.setUTCDate(currentWeekStart.getUTCDate() + 7);
    }

    // 累加每周的骑行距离
    processedActivities.forEach(activity => {
        const activityDate = new Date(activity.activity_time);
        // 活动所在周的开始日期
        const weekStart = getWeekStartDate(activityDate);
        const weekEnd = new Date(weekStart);
        weekEnd.setUTCDate(weekStart.getUTCDate() + 6);

        const weekKey = `${weekStart.toISOString().split('T')[0]} - ${weekEnd.toISOString().split('T')[0]}`;
        if (weeklyData[weekKey] !== undefined) {
            weeklyData[weekKey] += parseFloat(activity.riding_distance);
        }
    });

    // 获取最大骑行距离(用于柱形图比例)
    const maxDistance = Math.max(...Object.values(weeklyData), 0);

    // 创建并显示每周的柱形图
    Object.keys(weeklyData).forEach(week => {
        // 当前周的骑行距离
        const distance = weeklyData[week];
        const barContainer = document.createElement('div');
        barContainer.className = 'bar-container';

        const bar = document.createElement('div');
        bar.className = 'bar';

        // 计算柱形图的宽度
        const width = maxDistance > 0 ? (distance / maxDistance) * 190 : 0;
        bar.style.setProperty('--bar-width', `${width}px`);

        const distanceText = document.createElement('div');
        distanceText.className = 'cycling-kilometer';
        distanceText.innerText = '0 km';

        const messageBox = createMessageBox();
        const clickMessageBox = createMessageBox();

        barContainer.style.position = 'relative';
        bar.appendChild(distanceText);
        barContainer.appendChild(bar);
        barContainer.appendChild(messageBox);
        barContainer.appendChild(clickMessageBox);
        barChartElement.appendChild(barContainer);

        // 动画效果:逐渐显示柱形图宽度
        bar.style.width = '0';
        bar.offsetHeight;
        bar.style.transition = 'width 1s ease-out';
        bar.style.width = `${width}px`;

        distanceText.style.opacity = '1';
        // 动态更新柱形图的数值
        animateText(distanceText, 0, distance, 1000, true);
        setupBarInteractions(bar, messageBox, clickMessageBox, distance);
    });
}

// 动态文本显示
function animateText(element, startValue, endValue, duration, isDistance = false) {
    const startTime = performance.now();
    function update() {
        const elapsed = performance.now() - startTime;
        const progress = Math.min(elapsed / duration, 1);
        const currentValue = (progress * endValue).toFixed(2);
        element.innerText = isDistance ? `${currentValue} km` : `${currentValue}h`;
        if (progress < 1) {
            requestAnimationFrame(update);
        } else {
            element.innerText = isDistance ? `${endValue.toFixed(2)} km` : `${endValue.toFixed(2)}h`;
        }
    }
    update();
}

New:全年骑行总时长、全年骑行总公里数

// 显示总活动数和总公里数
function displayTotalActivities(activities) {
    // 全年骑行时长
    const ridingTimeThisYear = document.getElementById('totalCount');
    // 全年骑行公里数
    const milesRiddenThisYear = document.getElementById('milesRiddenThisYear');
    // 动态年标题《2025 骑行总时长》
    const totalTitleElement = document.getElementById('totalTitle');

    if (!ridingTimeThisYear || !milesRiddenThisYear || !totalTitleElement) return;

    const ridingTimeThisYearValue = ridingTimeThisYear.querySelector('#ridingTimeThisYearValue');
    const milesRiddenThisYearValue = milesRiddenThisYear.querySelector('#milesRiddenThisYearValue');

    const totalCountSpinner = ridingTimeThisYear.querySelector('.loading-spinner');
    const milesRiddenThisYearSpinner = milesRiddenThisYear.querySelector('.loading-spinner');

    totalCountSpinner.classList.add('active');
    milesRiddenThisYearSpinner.classList.add('active');

    const currentYear = new Date().getFullYear();
    totalTitleElement.textContent = `${currentYear} 骑行总时长`;

    // 筛选全年活动数据
    const filteredActivities = activities.filter(activity => {
        const activityYear = new Date(activity.activity_time).getFullYear();
        return activityYear === currentYear;
    });

    // 计算全年活动时间的总和(单位:小时)
    const totalMovingTime = filteredActivities.reduce((total, activity) => {
        return total + parseFloat(activity.moving_time) || 0;
    }, 0);

    // 计算全年总公里数
    const totalKilometers = calculateTotalKilometers(filteredActivities);

    // 动画效果
    animateCount(ridingTimeThisYearValue, totalMovingTime, 1000, 50, false);
    animateCount(milesRiddenThisYearValue, totalKilometers, 1000, 50, true);

    setTimeout(() => {
        console.log(totalKilometers.toFixed(2));
        ridingTimeThisYearValue.textContent = `${totalMovingTime.toFixed(2)} h`;
        milesRiddenThisYearValue.textContent = `${totalKilometers.toFixed(2)} km`;
        totalCountSpinner.classList.remove('active');
        milesRiddenThisYearSpinner.classList.remove('active');
    }, 1000);
}

// 加载数据并生成日历
(async function() {
    const today = getChinaTime();
    const startDate = getStartDate(today, 21);

    const activities = await loadActivityData();
    // 显示4周的日历
    generateCalendar(activities, startDate, 4);

    // 显示全年骑行时长和公里数
    displayTotalActivities(activities);
})();

New:春节快乐红灯笼

两年前在冲浪时下载的,已经是第二次用了:

// default.html
include lantern.html

// main.scss
@use 'lantern'

Fix Bugs:移除 node-sass 包

node-sass 是基于 LibSass 库构建的,而 LibSass 从 2019 年就停止了更新。所以,Sass 团队放弃了这个项目,重构了 sass(Dart 编写)

sass 相对 node-sass 的优点

  1. 原生支持 Dart

    sass 是由 Dart 编写,它不再依赖 C++ 编译器,安装和构建速度更快

  2. 不再依赖编译

    node-sass 需要本地编译,会遇到编译问题,尤其是 Windows 系统上。而 sass 是纯 JavaScript 实现,跨平台时不会有编译问题

{
    "devDependencies": {
        "sass": "^1.83.4",
    }
}

Show

  • ✇游钓四方的博客
  • Tencent CDN 流量被恶意盗刷游钓四方的博客
    看到这张图的时候,我很震惊。这个CDN流量包是我昨天凌晨刚买的,直到此刻才发现我的CDN流量被恶意盗刷了。 事情是这样的,前天23号我在写新功能,本地调试调用了很多资源,当时看到消耗了90G的流量,我没有在意,以为是调试的问题。因为那天我写了一天代码,不停地调用Tencent COS,而COS还套了一层Tencent CDN。当时我以为是正常消耗,眼看流量不够,我又充了一个CDN加速包。 然而就在今晚22:45,我骑行回来关闭了免打扰模式,邮箱忽然弹出通知,腾讯云提示我CDN流量不足?我当时非常震惊,因为这是我24号凌晨刚买的流量包啊! 看到这张图时,我火了,在独立博客圈彻底火了,2天内请求数42万?赶超月光博客! 回想过去,我在博客圈认识的人一只手能数过来,更谈不上得罪谁。这事也怪我,之前COS没有任何防护,几乎处于裸奔状态。 由于我的博客托管在Github Pages,主机问题大可不必考虑,我能做的只有设置黑白名单和周期限流。 不再裸奔,已老实。 UPDATE 凌晨 02:32 知道怎么回事了,24年后,大陆境内出现一窝狗,利用PCDN恶意流量攻击!
     

Tencent CDN 流量被恶意盗刷

2024年7月25日 00:44

来自腾讯云的邮件

看到这张图的时候,我很震惊。这个CDN流量包是我昨天凌晨刚买的,直到此刻才发现我的CDN流量被恶意盗刷了。

事情是这样的,前天23号我在写新功能,本地调试调用了很多资源,当时看到消耗了90G的流量,我没有在意,以为是调试的问题。因为那天我写了一天代码,不停地调用Tencent COS,而COS还套了一层Tencent CDN。当时我以为是正常消耗,眼看流量不够,我又充了一个CDN加速包。

然而就在今晚22:45,我骑行回来关闭了免打扰模式,邮箱忽然弹出通知,腾讯云提示我CDN流量不足?我当时非常震惊,因为这是我24号凌晨刚买的流量包啊!

腾讯云 数据分析控制台

看到这张图时,我火了,在独立博客圈彻底火了,2天内请求数42万?赶超月光博客!

腾讯云 访问分布

TOP ONE 60.221.195.144

回想过去,我在博客圈认识的人一只手能数过来,更谈不上得罪谁。这事也怪我,之前COS没有任何防护,几乎处于裸奔状态。

由于我的博客托管在Github Pages,主机问题大可不必考虑,我能做的只有设置黑白名单和周期限流。

不再裸奔,已老实。

UPDATE 凌晨 02:32

知道怎么回事了,24年后,大陆境内出现一窝狗,利用PCDN恶意流量攻击!

  • 攻击的主要IP来源于山西、江苏和安徽联通等地的固定网段

  • 攻击时间非常规律,集中在19:50到23:00之间

  • 攻击者会针对体积较大的静态文件进行持续攻击

自7月初以来,已转头无差别地对大陆中小型网站展开攻击。

建议将山西等地的IP段暂时屏蔽,减少恶意流量的影响。

目前,GitHub上已经有相关项目 ban-pcdn-ip 用于收集这些恶意IP段。

  • ✇游钓四方的博客
  • 喜提新车 Wilier Cento 10SL游钓四方的博客
    得这辆车纯属缘分,前段时间在网上认识一个宁波的好大哥,没想到去年我们一起参加过同一个比赛,大哥是在宁波鄞州区开自行车店的,聊了许久大哥给我推荐一辆神车Wilier Cento 10SL!这是他朋友的爱车,财富自由润加拿大了,一些不方便带走的东西就卖掉了,这辆车刚到店里第一天,机缘巧合我就赶上了! 配置如下: Wilier Cento 10SL RIM STEMMA SL 把立 BARRA SL 弯把 WIND BREAK 50框 碳辐条 SHIMANO 105 R7000夹气,其他全车Ultegra 8000 算上平踏、水杯架 整车重为7.45KG,一对平踏重0.3KG,换DA夹气分分钟上6! 这台车最吸引我的地方就是,圈刹!SL后最后一代顶级圈刹车,我在网上找了许久都找不到同款,这台车已经停产几年了,太稀有了, 上图
     

喜提新车 Wilier Cento 10SL

2024年7月12日 22:33

得这辆车纯属缘分,前段时间在网上认识一个宁波的好大哥,没想到去年我们一起参加过同一个比赛,大哥是在宁波鄞州区开自行车店的,聊了许久大哥给我推荐一辆神车Wilier Cento 10SL!这是他朋友的爱车,财富自由润加拿大了,一些不方便带走的东西就卖掉了,这辆车刚到店里第一天,机缘巧合我就赶上了!

配置如下:

  • Wilier Cento 10SL RIM
  • STEMMA SL 把立
  • BARRA SL 弯把
  • WIND BREAK 50框 碳辐条
  • SHIMANO 105 R7000夹气,其他全车Ultegra 8000

算上平踏、水杯架 整车重为7.45KG,一对平踏重0.3KG,换DA夹气分分钟上6!

这台车最吸引我的地方就是,圈刹!SL后最后一代顶级圈刹车,我在网上找了许久都找不到同款,这台车已经停产几年了,太稀有了,

上图

车到了

我装车的时候把刹车线芯插坏了

我装好了

寄回来之前,技师称重

骑行照

  • ✇游钓四方的博客
  • 搞个公众号游钓四方的博客
    改名记录: 2024年02月08日 “阿川的博客”改名“游钓四方的博客” 2019年05月28日 “阿川的个人博客”改名“阿川的博客” 2018年10月26日 注册“阿川的个人博客” 今天捡回了18年注册的公众号,数据重新导了一遍,这手动整理几年的文章数据,我多少有些疲惫 这次熬的有点久了,明歇一天,再写个脚本让 Github Pages 文章自动同步到微信公众号,得想个办法 公众号还需要做个人认证,不然内置超链接是个麻烦事 公众号前端页面也需要重新写一个,腾讯自带UI限制文章数量 任重而道远啊!加油。
     

搞个公众号

2024年3月10日 15:51

改名记录:

  • 2024年02月08日 “阿川的博客”改名“游钓四方的博客”
  • 2019年05月28日 “阿川的个人博客”改名“阿川的博客”
  • 2018年10月26日 注册“阿川的个人博客”

今天捡回了18年注册的公众号,数据重新导了一遍,这手动整理几年的文章数据,我多少有些疲惫

这次熬的有点久了,明歇一天,再写个脚本让 Github Pages 文章自动同步到微信公众号,得想个办法

公众号还需要做个人认证,不然内置超链接是个麻烦事

公众号前端页面也需要重新写一个,腾讯自带UI限制文章数量

任重而道远啊!加油。

  • ✇游钓四方的博客
  • 解决Jekyll时区数据源游钓四方的博客
    由于Jekyll默认使用UTC时区,导致博客更新时间不准确。这里需要写入上海时间:timezone: Asia/Shanghai,但是我在本地调试时需要在配置内注释掉,不然就会报错 jekyll 3.9.3 | Error: No source of timezone data could be found. Please refer to https://tzinfo.github.io/datasourcenotfound for help resolving this error. 上传到仓库 Github pages 不会出现这样的问题。老是注释调试挺麻烦的,Google搜出来的解决方案都是瞎扯淡,也不知道都是哪复制粘贴就发出来的。 gem install tzinfo-data Gemfile 直接指定版本 gem 'tzinfo-data', '>= 1.2021a' 写入配置 timezone: Asia/Shanghai,确保调试的电脑时区也正常,开始运行 bundle exec jekyll serve
     

解决Jekyll时区数据源

2024年2月11日 23:07

由于Jekyll默认使用UTC时区,导致博客更新时间不准确。这里需要写入上海时间:timezone: Asia/Shanghai,但是我在本地调试时需要在配置内注释掉,不然就会报错

  • jekyll 3.9.3 | Error: No source of timezone data could be found. Please refer to https://tzinfo.github.io/datasourcenotfound for help resolving this error.

上传到仓库 Github pages 不会出现这样的问题。老是注释调试挺麻烦的,Google搜出来的解决方案都是瞎扯淡,也不知道都是哪复制粘贴就发出来的。

gem install tzinfo-data

Gemfile 直接指定版本

gem 'tzinfo-data', '>= 1.2021a'

写入配置 timezone: Asia/Shanghai,确保调试的电脑时区也正常,开始运行

bundle exec jekyll serve

  • ✇游钓四方的博客
  • 腾讯云COS文件跨域游钓四方的博客
    今天换博客主要文字了,”仓耳今楷”,字体更美观更适合阅读。但是过程中遇到点问题 @font-face { font-family: 仓耳今楷01-W04; src: url("https://api.lhasa.icu/assets/font/tsanger01W04.ttf") format("truetype"); } 这段CSS写的是没有问题的,但是不生效,控制台报错跨域 has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. 腾讯云COS跨域访问CORS配置如下: 配置好后又遇到麻烦了,字体太大了,一个字体文件17.9M!网站都脱垮了 这里做一下处理,取子集压缩文字,需要用到 FontSmaller 和 现代汉语常用3500汉字 取子集压缩之后字体文件大小为1.94M
     

腾讯云COS文件跨域

2024年2月5日 17:33

今天换博客主要文字了,”仓耳今楷”,字体更美观更适合阅读。但是过程中遇到点问题

@font-face {
    font-family: 仓耳今楷01-W04;
    src: url("https://api.lhasa.icu/assets/font/tsanger01W04.ttf")  format("truetype");
}

这段CSS写的是没有问题的,但是不生效,控制台报错跨域

  • has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

腾讯云COS跨域访问CORS配置如下:

腾讯云COS跨域访问CORS设置

配置好后又遇到麻烦了,字体太大了,一个字体文件17.9M!网站都脱垮了

网站被拖垮了

这里做一下处理,取子集压缩文字,需要用到 FontSmaller现代汉语常用3500汉字

取子集压缩之后字体文件大小为1.94M

取子集压缩后的效果

  • ✇游钓四方的博客
  • 我回来了游钓四方的博客
    一晃几年过去了,一切都物是人非,说起来有点哽咽…哎… 最重要的域名也在今年搞丢了,续费保护期也过了,曾经二三百的域名,现在要二万块…先拿github的用用,以后再说吧 一时间,脑子蒙蒙的,纵有千言万语却不知道说些什么。总之,接下来我会把丢掉的博客再捡起来,曾经的朋友你们还在吗?留言系统还没有弄好,可以联系Email Email:haibao1027@gmail.com
     

我回来了

2024年1月22日 08:00

一晃几年过去了,一切都物是人非,说起来有点哽咽…哎…

最重要的域名也在今年搞丢了,续费保护期也过了,曾经二三百的域名,现在要二万块…先拿github的用用,以后再说吧

一时间,脑子蒙蒙的,纵有千言万语却不知道说些什么。总之,接下来我会把丢掉的博客再捡起来,曾经的朋友你们还在吗?留言系统还没有弄好,可以联系Email

  • ✇游钓四方的博客
  • 爱上Go语言:变量定义与内建变量类型游钓四方的博客
    变量的定义 package main import "fmt" func variableZeroValue() { // var 关键字表示定义变量,定义变量时,名在前,类型在后 // int默认值为0,string为空字符串 var a int var s string fmt.Printf("%d %q\n", a, s) } func variableInitialValue() { // Golang语法严格,定义的变量必须被使用,否则报错:declared but not used var a, b int = 3, 4 var s string = "abc" fmt.Println(a, b, s) } func variableTtypeDeduction() { // 定义多个变量的同时,也可以给其定义类型 var a, b, c, s = 3, 4, true, "def" fmt.Println(a, b, c, s) } func variableShorter() { // : 冒号也表示定义变量,注意定义变量时才用!!!
     

爱上Go语言:变量定义与内建变量类型

2020年12月18日 08:00

变量的定义

package main

import "fmt"

func variableZeroValue() {
	// var 关键字表示定义变量,定义变量时,名在前,类型在后
	// int默认值为0,string为空字符串
	var a int
	var s string
	fmt.Printf("%d %q\n", a, s)
}

func variableInitialValue() {
	// Golang语法严格,定义的变量必须被使用,否则报错:declared but not used
	var a, b int = 3, 4
	var s string = "abc"
	fmt.Println(a, b, s)
}
func variableTtypeDeduction() {
	// 定义多个变量的同时,也可以给其定义类型
	var a, b, c, s = 3, 4, true, "def"
	fmt.Println(a, b, c, s)
}

func variableShorter() {
	// : 冒号也表示定义变量,注意定义变量时才用!!!
	a, b, c, s := 3, 4, true, "def"
	// 若变量重复定义则会报错:no new variables on left side of :=
	b = 5
	fmt.Println(a, b, c, s)
}

// 在函数内定义的变量,它作用域只在函数内,但函数外也可以定义变量
// 不管函数内还是函数外都可以使用 var() 集中定义变量
var (
	id   = 1
	user = "achuan"
)

// 需要注意的是,在函数外定义变量只可以用var关键字进行定义,不能使用冒号
// 否则报错:syntax error: non-declaration statement outside function body
//ges := 20

// 注意上面定义的id,user变量,它们的作用域不是"全局变量",而是包内部的变量
// Golang没有全局变量这个说法,只是包内部的

func main() {
	fmt.Println("Hi achuan")
	variableZeroValue()
	variableInitialValue()
	variableTtypeDeduction()
	variableShorter()
	fmt.Println(id, user)
}

内建变量类型

布尔与字符串

  • bool、string

bool再了解不过了吧,它的值只有两种:true、false,if和for语句的条件部分都是布尔的值,并且==<等比较操作也会产生bool值

string是一个不可改变的字节序列,字符串可以包含任意的数据

整数型

  • (u)int、(u)int8、(u)int16、(u)int32、(u)int64、uintptr

加(u)是无符号整数,不加(u)是有符号整数,有符号整数还分两类,一类规定长度的,如int8、int32、int64。还有一类是不规定长度的,不规定长度的是根据操作系统来,在32位操作系统里面它就是32位,在64位系统它就是64位

还要一个uintptr类型,这个ptr就是指针,这个指针的长度也是跟着操作系统来的

uintptr类型是无符号整型,用于存放一个指针,等过几天再详细学一下

字符型

  • byte、rune

Golang字符型有两种,一种是uint8,另一种就是rune

byte就是uint8的别名,即一个字节长度,常用来处理ASCII字符

rune同等于int32,代表一个UTF-8字符,即4个字节吗,常用于处理中文或其他复合字符

浮点型

  • float32、float64

Golang提供了两种浮点型:float32和float64,它们的算术规范是由IEEE754国际标准定义,现代CPU都实现来这个规范。

浮点数能够表示的极限范围可以在math包中获取,math.MaxFloat32表示float32的最大值,约3.4e38,math.MaxFloat64大约是1.8e308,两个类型最小的非负值大约是1.4e-45和4.9e-324

float32大约可以提供小数点后6位的精度,而float64可以提供小数点后15位的精度。

Golang也提供了两种大小的复数:complex64和complex128,分别由float32和float64组成。内置函数complex从指定的实部和虚部构建复数,内置函数realimag用来获取复数的实部和虚部

例如欧拉公式:

package main

import (
	"fmt"
	"math"
	"math/cmplx"
)

func euler() {
	fmt.Printf(cmplx.Exp(1i*math.Pi)+1)
}

func main() {
	euler()
}

强制类型转换

Golang是一种强类型语言,不同类型的数据不能赋值,不能在函数中传参,我个人是很喜欢这种。因很多错误在编译期间就被揪出来来,不像PHP等弱类型语言,很多错误只有运行时才能被发现,如下面这个例子:

用Golang求出直角三角形斜边值5

Alt text

package main

import (
	"fmt"
	"math"
)

func triangle() {
	var a, b int = 3, 4
	var c int
	// 参数类型不一致报错:cannot use a * a + b * b (type int) as type float64 in argument to math.Sqrt
	// c = int(math.Sqrt(a*a + b*b))
	// 因为Sqrt参数要求float64类型,Golang语法严格,必须强制性转换
	c = int(math.Sqrt(float64(a*a + b*b)))
	fmt.Println(c)
}

func main() {
	triangle()
}

// 5

  • ✇游钓四方的博客
  • Docker Compose快速构建LNMP笔记游钓四方的博客
    目录结构 / ├── data 数据库数据目录 │ ├── esdata ElasticSearch 数据目录 │ ├── mongo MongoDB 数据目录 │ ├── mysql MySQL8 数据目录 │ └── mysql5 MySQL5 数据目录 ├── services 服务构建文件和配置文件目录 │ ├── elasticsearch ElasticSearch 配置文件目录 │ ├── mysql MySQL8 配置文件目录 │ ├── mysql5 MySQL5 配置文件目录 │ ├── nginx Nginx 配置文件目录 │ ├── php
     

Docker Compose快速构建LNMP笔记

2020年9月25日 08:00

目录结构

/
├── data                        数据库数据目录
│   ├── esdata                  ElasticSearch 数据目录
│   ├── mongo                   MongoDB 数据目录
│   ├── mysql                   MySQL8 数据目录
│   └── mysql5                  MySQL5 数据目录
├── services                    服务构建文件和配置文件目录
│   ├── elasticsearch           ElasticSearch 配置文件目录
│   ├── mysql                   MySQL8 配置文件目录
│   ├── mysql5                  MySQL5 配置文件目录
│   ├── nginx                   Nginx 配置文件目录
│   ├── php                     PHP5.6 - PHP7.3 配置目录
│   ├── php54                   PHP5.4 配置目录
│   └── redis                   Redis 配置目录
├── logs                        日志目录
├── docker-compose.sample.yml   Docker 服务配置示例文件
├── env.smaple                  环境配置示例文件
└── www                         PHP 代码目录

快速使用

如果当前用户不是root,为了避免频繁输入root密码,需要将当前用户加入docker组

# 创建Docker组  注:安装Docker时就自动创建了,如果没有则手动创建
$ sudo groupadd docker
# 当前用户加入Docker组
$ sudo gpasswd -a ${USER} docker
# 将当前用户的group切换到docker用户组
$ newgrp docker

Clone项目

$ gh repo clone achuanya/dnmp

拷贝文件

$ cd dnmp
# 复制环境变量文件
$ cp env,sample .env
# 复制docker-compose配置文件
$ cp docker-compose.sample.yml docker-compose.yml
# 创建并后台运行
$ docker-compose up -d

PHP与扩展

切换Nginx使用的PHP版本

1.比如,从php切换到php56,那就先在docker-compose.yml文件中查看PHP56有没有被注释掉,删掉注释后启动,再更改Nginx配置文件:

fastcgi_pass   php:9000;
更改为:
fastcgi_pass   php56:9000;

其中phpphp56docker-compose.yml文件中容器的NAME名称

2.让其配置生效还需再重新加载Nginx配置文件

$ docker exec -it nginx nginx -s reload

这里有两个Nginx,第一个是容器NAME名称,第二个是容器中的Nginx程序

在宿主机安装PHP扩展

1.如果要安装更多PHP扩展,在根目录找到.env环境配置文件,如以下PHP扩展配置

# 安装扩展应当使用英文逗号隔开
PHP56_EXTENSIONS=pdo_mysql,mysqli,mbstring,gd,curl,opcache,redis

2.保存完成后,重新构建镜像

$ docker-compose build php

在Docker中安装扩展

$ docker exec -it php /bin/sh
# 安装redis扩展
$ install-php-extensions redis

支持安装扩展列表

此扩展来自Michele Locati,请前往查看最新支持的PHP扩展

  • https://github.com/mlocati/docker-php-extension-installer

在宿主机中使用命令行

PHP CLI

1.参考根目录bash.alias.sample示例文件,将PHP CLI函数拷贝到/etc/profile系统环境变量文件

# 刷新系统环境变量
$ source /etc/profile

2.在宿主机中执行PHP命令了

 ~ [06:24:00]
achuan$ php -v
PHP 7.4.7 (cli) (built: Jun 11 2020 19:07:15) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.7, Copyright (c), by Zend Technologies
    
 ~ [06:24:04]
achuan$ php56 -v
PHP 5.6.40 (cli) (built: Jan 31 2019 01:30:45) 
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies

Composer

1.首先确定Composer缓存目录,Composer配置文件在根目录中的data/composer

2.参考根目录bash.alias.sample示例文件,将PHP CLI函数拷贝到/etc/profile系统环境变量文件

# 刷新系统环境变量
$ source /etc/profile

3.之后就可以在宿主机使用Composer命令了

$ cd /work/dnmp/www
$ composer -V
Composer version 1.10.13 2020-09-09 11:46:34

4.第一次使用Composer后data/composer目录下会生成config.json全局配置文件,可指定镜像,例如中国全量镜像:

{
    "config": {},
    "repositories": {
        "packagist": {
            "type": "composer",
            "url": "https://packagist.phpcomposer.com"
        }
    }
}

或使用命令修改Composer的全局配置文件

$ composer config -g repo.packagist composer https://packagist.phpcomposer.com

管理命令

容器的创建、启动与构建

$ docker-compose
	up # 创建并且启动所有容器
	up -d # 创建并且后台运行所有容器
	up nginx php mysql # 创建并且启动多个容器
	
	start # 启动容器
	stop # 停止容器
	restart # 重启容器
	build # 构建容器
	
	rm # 停止并且删除容器
	down # 停止并且删除容器、网络、图像与挂载卷

快捷命令

1.参考根目录bash.alias.sample示例文件,将Composer函数拷贝到/etc/profile系统环境变量文件

# 刷新系统环境变量
$ source /etc/profile

2.例如,进入php容器

$ dphp

  • ✇游钓四方的博客
  • (转)一个关于if else容易迷惑的问题游钓四方的博客
    本文转自Laruence 这个本来是之前在微博上有个同学说他经常用来面试别人,大概是说,对于如下代码,你觉得会输出啥: $a = true;if ($a) { echo "true";} else label: { echo "false";} 当时觉得有点偏,没想写,今天中午又有人问我,我想那就介绍下这个原因吧. 首先,上面的代码输出truefalse, 如果你知道原因,那就不用继续往下看了,如果不知道,那么: 这块让人比较迷惑的原因可能是因为,我们会很直观的认为: label : { statement;} 应该是一个整体, 就好比类似: if ($a) {} else switch($a) {} 或者: if ($a) {} else do {} while (!$a); 因为在PHP的语法设计中,if else本质上是: if_stmt: if_stmt_without_else T_ELSE statement 也就是说,else后面可以接一切statement,如果条件不成立,执行流就跳到else后面的statement,而whil
     

(转)一个关于if else容易迷惑的问题

2020年8月23日 08:00

本文转自Laruence


这个本来是之前在微博上有个同学说他经常用来面试别人,大概是说,对于如下代码,你觉得会输出啥:

$a = true;if ($a) {  echo "true";} else label: {  echo "false";}

当时觉得有点偏,没想写,今天中午又有人问我,我想那就介绍下这个原因吧.

首先,上面的代码输出truefalse, 如果你知道原因,那就不用继续往下看了,如果不知道,那么:

这块让人比较迷惑的原因可能是因为,我们会很直观的认为:

label : {  statement;}

应该是一个整体, 就好比类似:

if ($a) {} else switch($a) {}

或者:

if ($a) {} else do {} while (!$a);

因为在PHP的语法设计中,if else本质上是:

if_stmt: if_stmt_without_else T_ELSE statement

也就是说,else后面可以接一切statement,如果条件不成立,执行流就跳到else后面的statement,而while, switch都可以归约为statement。

但label这块稍微有点特别(可以说是一个设计违反直觉的”缺陷”吧), 在zend_language_parser.y中:

statement:  ...  | T_DO statement T_WHILE '(' expr ')' ';' {...}  | T_SWITCH '(' expr ')' switch_case_list {...}  | T_STRING : { $$ = zend_ast_create(ZEND_AST_LABEL, $1); }

大家可以看到, do while, switch 都会联合他们的body归约为statement(语句),但标签(label)有点不同,”label :”本身会规约为一条statement, 这就导致了这个看起来比较迷惑的问题的出现,他本质上就变成了:

$a = true;if ($a) { echo "true";} else { label: ;  //单独的一条语句}echo "false";

最后多说一句,我忘了之前在那看到的,说是这个世界上本无elseif,有的只不过是else (if statement),本质上其实就跟这个意思是一样的。 就是,else后面可以接语句(statement)。

善用这个结合switch, for, do while等,有的时候可以让我们的代码更精简。 比如,我们要遍历处理一个数组,当数组的长度为零的时候,要做点其他事,那很多人可能会这么写:

if (count($array)) {  for ($i = 0; $i < count($array); $i++) {  }} else {  //数组为空的逻辑}

但你也可以写成:

if (count($array) == 0) {   //数组为空的逻辑} else for ($i = 0; $i < count($array); $i++) {}

至于这俩中写法孰好孰坏, 那就是萝卜白菜了。

最后,大家如果在实际中遇到类似让大家觉得迷惑的问题,可以留言,也许以后也可以单独成文。

  • ✇游钓四方的博客
  • 使用开源项目免费申请JetBrains开源许可证游钓四方的博客
    JetBrains公司旗下产品都太棒了,我是深陷其中不可自拔,离了这个软件我写啥都不舒服,最近更新一下PHPSTORM之后发现网上买的教育认证老是失效,最终我在网上发现可以用开源项目免费申请到JetBrains全家桶且为Ultimate版本!免费订阅有效期为一年,许可证到期前不久,JetBrains会有电子邮件进行续订提醒!不过申请还是有要求的 你必须是项目的发起人或是活跃的 commiter 你的项目需要积极开发3个月以上 定期发布版本 符合开源的定义,不能包含有关商业性质的内容 如何申请 JetBrains开源许可证申请 填写表单就行了,提交申请后我两天后就收到了JetBrains官方的电子邮件。
     

使用开源项目免费申请JetBrains开源许可证

2020年8月22日 08:00

JetBrains公司旗下产品都太棒了,我是深陷其中不可自拔,离了这个软件我写啥都不舒服,最近更新一下PHPSTORM之后发现网上买的教育认证老是失效,最终我在网上发现可以用开源项目免费申请到JetBrains全家桶且为Ultimate版本!免费订阅有效期为一年,许可证到期前不久,JetBrains会有电子邮件进行续订提醒!不过申请还是有要求的

  • 你必须是项目的发起人或是活跃的 commiter
  • 你的项目需要积极开发3个月以上
  • 定期发布版本
  • 符合开源的定义,不能包含有关商业性质的内容

如何申请

JetBrains开源许可证申请

填写表单就行了,提交申请后我两天后就收到了JetBrains官方的电子邮件。

成功

  • ✇游钓四方的博客
  • JetBrains官方推中文简体包了!游钓四方的博客
    今天我在Github TranslatorX这个IDE汉化项目的Issues下看到有个JetBrains员工说关于中文包的事: Hi, I’m a JetBrains employee, and we’re working on initial support for the Chinese language pack. If you’re interested in, feel free to contact me: ignatov@jetbrains.com Cheers, Sergey. 然后前不久1-17号官方中文插件在2020.1的EAP版本已经推出了,相见恨晚啊! 然而朋友说中文IDE没有灵魂,这这这… Chinese (Simplified) Language Pack 2020-05-08更新 在Plugins里面搜索Chinese,安装后重启即可。
     

JetBrains官方推中文简体包了!

2020年4月26日 08:00

今天我在Github TranslatorX这个IDE汉化项目的Issues下看到有个JetBrains员工说关于中文包的事:

Hi, I’m a JetBrains employee, and we’re working on initial support for the Chinese language pack. If you’re interested in, feel free to contact me: ignatov@jetbrains.com

Cheers, Sergey.

然后前不久1-17号官方中文插件在2020.1的EAP版本已经推出了,相见恨晚啊!

然而朋友说中文IDE没有灵魂,这这这…

消息

2020-05-08更新

在Plugins里面搜索Chinese,安装后重启即可。

  • ✇游钓四方的博客
  • ThinkPHP6.0下载安装与配置游钓四方的博客
    ThinkPHP 6.0 要求 PHP >= 7.1.0 6.0版本开始,必须通过Composer方式安装和更新,无法使用Git下载安装。 安装Composer $ curl -sS https://getcomposer.org/installer | php # 全局调用 $ sudo mv composer.phar /usr/local/bin/composer /usr/src [09:22:13] achuan$ composer ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ /_/ Composer version 1.10.5 2020-04-10
     

ThinkPHP6.0下载安装与配置

2020年4月14日 08:00
  • ThinkPHP 6.0 要求 PHP >= 7.1.0
  • 6.0版本开始,必须通过Composer方式安装和更新,无法使用Git下载安装。

安装Composer

$ curl -sS https://getcomposer.org/installer | php

# 全局调用
$ sudo mv composer.phar /usr/local/bin/composer

 /usr/src [09:22:13]
achuan$ composer
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.10.5 2020-04-10 11:44:22

# 使用阿里云镜像
$ composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

安装

# 稳定版
$ composer create-project topthink/think tp6

# 开发版
$ composer create-project topthink/think=6.0.x-dev tp6

# 更新已安装的旧版本核心框架
$ composer update topthink/framework

配置

# apache配置
$ sudo vim /etc/httpd/conf/extra/httpd-vhosts.conf
<VirtualHost *:80>
    ServerName tp6.io
    DocumentRoot /home/achuan/language/php/tp6/public
    <Directory  "/home/achuan/language/php/tp6/">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

$ sudo vim /etc/hosts
127.0.0.1 tp6.io

# 不进行apache配置也可以测试运行 默认端口为:8000
$ php think run -p 1081

# 环境变量定义 可以更名为.env进行修改生效,该文件默认开启调试模式。
$ mv example.env .env

# 对外访问
$ sudo chmod -R 777 public

# 安装多应用模式扩展 think-multi-app
$ composer require topthink/think-multi-app
# 创建一个名为 index 的应用,返回Successed则成功
$ php think build index
# 多应用模式部署后,必须要删除controller目录,因为系统根据该目录作为判断是否为单应用
$ rm -rf controller

目录结构

单应用模式

www  WEB部署目录(或者子目录)
├─app           应用目录
│  ├─controller      控制器目录
│  ├─model           模型目录
│  ├─ ...            更多类库目录
│  │
│  ├─common.php         公共函数文件
│  └─event.php          事件定义文件
│
├─config                配置目录
│  ├─app.php            应用配置
│  ├─cache.php          缓存配置
│  ├─console.php        控制台配置
│  ├─cookie.php         Cookie配置
│  ├─database.php       数据库配置
│  ├─filesystem.php     文件磁盘配置
│  ├─lang.php           多语言配置
│  ├─log.php            日志配置
│  ├─middleware.php     中间件配置
│  ├─route.php          URL和路由配置
│  ├─session.php        Session配置
│  ├─trace.php          Trace配置
│  └─view.php           视图配置
│
├─view            视图目录
├─route                 路由定义目录
│  ├─route.php          路由定义文件
│  └─ ...   
│
├─public                WEB目录(对外访问目录)
│  ├─index.php          入口文件
│  ├─router.php         快速测试文件
│  └─.htaccess          用于apache的重写
│
├─extend                扩展类库目录
├─runtime               应用的运行时目录(可写,可定制)
├─vendor                Composer类库目录
├─.example.env          环境变量示例文件
├─composer.json         composer 定义文件
├─LICENSE.txt           授权说明文件
├─README.md             README 文件
├─think                 命令行入口文件

默认应用文件

├─app           应用目录
│  │
│  ├─BaseController.php    默认基础控制器类
│  ├─ExceptionHandle.php   应用异常定义文件
│  ├─common.php            全局公共函数文件
│  ├─middleware.php        全局中间件定义文件
│  ├─provider.php          服务提供定义文件
│  ├─Request.php           应用请求对象
│  └─event.php             全局事件定义文件

相关文档:

  • ✇游钓四方的博客
  • Manjaro KDE 调教日记游钓四方的博客
    更换源 # 更新数据源 $ sudo pacman -Sy # 选清华源 mirrors.tuna.tsinghua.edu.cn $ sudo pacman-mirrors -i -c China -m rank $ sudo pacman -Syu # 添加Arch源 $ sudo vi /etc/pacman.conf # 把下面这几行写进去 [archlinuxcn] SigLevel = Optional TrustedOnly Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch $ sudo pacman -Syy && sudo pacman -S archlinuxcn-keyring 基础设置 $ sudo pacman -S vim git rpm yay unzip snapd you-get annie $ sudo systemctl enable --now snapd.socket # Git代理,需配合Qv2ray使用 $ git config --g
     

Manjaro KDE 调教日记

2020年4月9日 08:00

更换源

# 更新数据源
$ sudo pacman -Sy
# 选清华源 mirrors.tuna.tsinghua.edu.cn
$ sudo pacman-mirrors -i -c China -m rank
$ sudo pacman -Syu

# 添加Arch源
$ sudo vi /etc/pacman.conf
# 把下面这几行写进去
[archlinuxcn]
SigLevel = Optional TrustedOnly
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch
$ sudo pacman -Syy && sudo pacman -S archlinuxcn-keyring

基础设置

$ sudo pacman -S vim git rpm yay unzip snapd you-get annie
$ sudo systemctl enable --now snapd.socket

# Git代理,需配合Qv2ray使用
$ git config --global https.proxy https://127.0.0.1:8888

# 主目录改为英文
$ sudo pacman -S xdg-user-dirs-gtk
$ export LANG=en_US &&  xdg-user-dirs-gtk-update

# 将时区设置为中国上海
$ timedatectl set-timezone Asia/Shanghai

# 细长的等宽字体
$ yay -S ttf-iosevka

export http_proxy="socks5://127.0.0.1:1080"
export ALL_PROXY=socks5://127.0.0.1:1080



# 禁用封锁
$ sudo vim /etc/security/faillock.conf
deny = 0

# 虚拟终端字体问题
$ sudo pacman -S terminus-font
$ sudo vim /etc/vconsole.conf
FONT=ter-132n

自动挂载NTFS硬盘

# 查看磁盘分区的UUID
$ sudo blkid -o list
# 5016CF88CCD20C21 就是我的UUID,同时要记录一下device和fs_type等会要用
device                   fs_type    label       mount point                  UUID
-------------------run----------------------------------------------------------------------------------------------
/dev/sdb1                ntfs       File        /run/media/achuan/File          5016CF88CCD20C21

# /dev/sdb1挂载点
$ mkdir ~/File
# 打开fastab文件,看到以下文件内容
$ sudo vim /etc/fstab
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a device; this may
# be used with UUID= as a more robust way to name devices that works even if
# disks are added and removed. See fstab(5).
#
# <file system>             <mount point>  <type>  <options>  <dump>  <pass>
UUID=8a9b74b7-c33a-413b-b654-80f3a16b5e12 /home          ext4    defaults,noatime,discard 0 2
UUID=b23b3470-26c1-4b39-9358-43278c73763e /              ext4    defaults,noatime,discard 0 1
UUID=ad103e33-78b7-4b33-8632-03c3fe6364fc /boot          ext4    defaults,noatime,discard 0 2
UUID=b0d5e88d-136a-4fa6-b164-5d70e5073b5d /opt           ext4    defaults,noatime,discard 0 2
tmpfs                                     /tmp           tmpfs   defaults,noatime,mode=1777 0 0

# 从这个文件内容可以看出文件有6列
 - 第一列file system选项是UUID
 - 第二列mount point选项是挂载点
 - 第三列type选项是所要挂载设备的文件系统或者文件系统类型
 - 第四列options选项是挂载选项,常见参数如下
 
|  配置选项    | 选项说明                                            
|-------------|-----------------------------------------------------
| async/sync  | 设置是否为同步方式运行,默认为async
| auto/noauto | 当下载mount -a命令时,此系统是否被主动挂载,默认为auto
| rw/ro       | 是否以只读或读写模式挂载
| exec/noexec | 限制此文件系统内是否能够进行“执行”操作
| user/nouser | 是否允许用户使用mount命令挂载
| suid/nosuid | 是否允许SUID的存在
| userquota   | 启动文件系统支持磁盘配额模式
| grpquota    | 启动文件系统对群组磁盘配额模式的支持
| defaults    | 同时具有rw、suid、suid、dev、exec、auto、nouser、async等默认参数的设置
 deepin-wine-wechat
 - 第五列dump选项是文件系统备份选项。0备份,1备份
 - 第六列pass选项是磁盘检查设置,其值是一个顺序,0不检查,1检查(根目录永远都为1)其它分区从2开始,数字越小越先检查,如果有两个分区的数字相同,同时检查

# 这是挂载/dev/sdb1的挂载配置,插入一行保存退出
UUID=5016CF88CCD20C21                     /home/achuan/File ntfs   defaults 0 0

# 如果/etc/fstab配置不对,会导致系统无法启动!一定要检查一下是否能正确挂载!如果改挂了,找个U盘改回来就行了。
$ sudo mount -a

常用软件

Vim配置

# Lightline
$ git clone https://github.com/itchyny/lightline.vim ~/.vim/pack/plugins/start/lightline

# 更换 PaperColor_dark.vim
$ mv -f ~/.vim/pack/plugins/start/lightline/autoload/lightline/colorscheme/PaperColor_dark.vim ~/.vim/pack/plugins/start/lightline/plugin/lightline.vim

# 编辑全局配置并写入以下配置 # 用户个人配置 ~/.vimrc
$ sudo vim /etc/vimrc

" 语法高亮
syntax on
" 底部状态显示
set showmode
" 使用UTF-8编码
set encoding=utf-8  
" 启用256色
set t_Co=256
" 开启文件类型检查,并且载入与该类型对应的缩进规则
filetype indent on
" 按回车后,下一行缩进自动同上
set autoindent
" 按TAB,Vim显示的空格数量
set tabstop=4
" 在文本上按下>>(增加一级缩进)、<<(取消一级缩进)或者==(取消全部缩进)时,每一级的字符数
set shiftwidth=4
" 由于 TAB 键在不同的编辑器缩进不一致,该设置自动将 TAB 转为空格
set expandtab
" TAB转为多少个空格
set softtabstop=4
" 显示行号
set number
" 光标所在当前行高亮
set cursorline
" 设置行宽,即一行显示多少个字符
set textwidth=80
" 自动折行,即太长的行分成几行显示
set wrap
" 只有遇到指定的符号(比如空格、连词号和其他标点符号),才发生折行。也就是说,不会在单词内部折行
set linebreak
" 是否显示状态栏。0 表示不显示,1 表示只在多窗口时显示,2 表示显示
set laststatus=2
" 在状态栏显示光标的当前位置(位于哪一行哪一列)
set  ruler
" 搜索时,高亮显示匹配结果
set hlsearch
" 输入搜索模式时,每输入一个字符,就自动跳到第一个匹配的结果
set incsearch
" 搜索时忽略大小写
set ignorecase
" 如果有一个大写字母,则切换到大小写敏感查找
set smartcase 
" 保存撤销历史
set undofile
" 出错时,发出视觉提示
set visualbell
" 保存Vim历史操作次数
set history=1000
" 打开文件监视。如果在编辑过程中文件发生外部改变(比如被别的编辑器编辑了),就会发出提示
set autoread
" 如果行尾有多余的空格(包括 Tab 键),该配置将让这些空格显示成可见的小方块
set listchars=tab:»■,trail:■
set list
" 命令模式下,底部操作指令按下 Tab 键自动补全。第一次按下 Tab,会显示所有匹配的操作指令的清单;第二次按下 Tab,会依次选择各个指令
set wildmenu
set wildmode=longest:list,full

Oh-My-Zsh

$ sudo pacman -S zsh
$ sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
# 切换到zsh
$ chsh -s /bin/zsh
# 安装3den主题
$ sudo vim ~/.zshrc
ZSH_THEME="3den"

小狼毫输入法

# 搜狗装了十几回,它那个兼容性真让我抓狂,还是小狼毫香。 
$ sudo  pacman -S ibus ibus-rime
$ yay -S ibus-qt

配置

# 默认是繁体,需要可以改为简体中文
$ vim ~/.config/ibus/rime/luna_pinyin.custom.yaml
# luna_pinyin.custom.yaml
patch:
  switches:                   # 注意缩进
    - name: ascii_mode
      reset: 0                # reset 0 的作用是当从其他输入法切换到本输入法重设为指定状态
      states: [ 中文, 西文 ]   # 选择输入方案后通常需要立即输入中文,故重设 ascii_mode = 0
    - name: full_shape
      states: [ 半角, 全角 ]   # 而全/半角则可沿用之前方案的用法。
    - name: simplification
      reset: 1                # 增加这一行:默认启用「繁→簡」转换。
      states: [ 漢字, 汉字 ]

# 编辑系统环境变量并写入以下配置
$ sudo vim /etc/profile
export GTK_IM_MODULE=ibus
export XMODIFIERS=@im=ibus
export QT_IM_MODULE=ibus
ibus-daemon -d -x

搜狗输入法

2020-2-11 Update:现在已经有fcitx5包了,体验确实提升不少,ibus没有搜狗香了哈哈哈

$ sudo pacman -Sy fcitx fcitx-configtool 
$ yay -Sy fcitx-sogoupinyin

# 编辑系统环境变量并写入以下配置
$ sudo vim /etc/profile
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS="@im=fcitx"

微信 TIM 完美解决方案

# QQ
$ yay -S deepin-wine-qq
# 自动切换前确保 deepin-wine5 包的支持,完成后就可以启动使用了
$ /opt/apps/com.qq.im.deepin/files/run.sh -d
# 高分辨率屏幕支持
$ env WINEPREFIX="$HOME/.deepinwine/Deepin-QQ" deepin-wine5 winecfg
# 默认使用文泉驿微米黑(wqy-microhei)字体,可以使用其他字体替代,直接将字体文件或字体链接文件放置到字体文件夹就会生效,不会影响系统字体
$ cd ~/.deepinwine/Deepin-QQ/drive_c/windows/Fonts

# 新版TIM
$ yay -S com.qq.tim.spark
# 微信
$ yay -S com.qq.weixin.deepin
# 注意:如果是 N 卡用户,可能需要用安装 lib32-nvidia-libgl 才能使用
# 中文方块乱码:WINE_CMD="LC_ALL=zh_CN.UTF-8 deepin-wine5"
# KDE/Plasma桌面 需要安装 xsettingsd,然后设置到 /usr/bin/xsettingsd 自启动
$ ln -s /usr/bin/xsettingsd ~/.config/plasma-workspace/env/xsettingsd

# KDE字体设置 DPI 120
$ env WINEPREFIX=$HOME/.deepinwine/Deepin-WeChat deepin-wine5 winecfg

# 添加字体到Fonts目录
$ cp MicrosoftYaheiConfig.ttf ~/.deepinwine/Deepin-WeChat/drive_c/windows/Fonts

# 修改系统注册表且修改以下两行
$ vim ~/.deepinwine/Deepin-WeChat/system.reg
"MS Shell Dlg"="MicrosoftYahei"
"MS Shell Dlg 2"="MicrosoftYahei"

# 注册字体并添加一下代码
$ vim MicrosoftYaheiConfig.reg
REGEDIT4
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink]
"Lucida Sans Unicode"="MicrosoftYahei.ttc"
"Microsoft Sans Serif"="MicrosoftYahei.ttc"
"MS Sans Serif"="MicrosoftYahei.ttc"
"Tahoma"="MicrosoftYahei.ttc"
"Tahoma Bold"="MicrosoftYahei.ttc"
"MicrosoftYahei"="MicrosoftYahei.ttc"
"Arial"="MicrosoftYahei.ttc"
"Arial Black"="MicrosoftYahei.ttc"

# 注册
$ WINEPREFIX=~/.deepinwine/Deepin-WeChat deepin-wine5 regedit MicrosoftYaheiConfig.reg

开发

# 谷歌浏览器
$ sudo pacman -S google-chrome
# 火狐浏览器并汉化
$ sudo pacman -S firefox firefox-i18n-zh-cn
# 在浏览器的地址栏输入
about:config
# 搜索
intl.locale.requested
# 将其值修改为
zh_CN
# 360安全浏览器
$ yay -S browser360
# Opera 自带梯子的浏览器
$ sudo pacman -Sy opera
# 博主感觉最好的Markdown编辑器
$ sudo pacman -S typora
# phpstorm
$ yay -S phpstorm
# GoLand
$ sudo pacman -S goland
# CLion
$ sudo pacman -S clion clion-cmake make clion-lldb
# idea
$ sudo pacman -S intellij-idea-ultimate-edition
# Visual Studio Code
$ sudo pacman -S visual-studio-code-bin
# Postman
$ sudo pacman -S postman-bin
# Mycli 具有自动完成和语法突出显示功能的MySQL / MariaDB / Percona客户端
$ sudo pacman -S mycli
# 具有自动完成功能和语法突出显示功能的Redis客户端
$ yay -S iredis
# 开源图形化的Redis客户端管理软件
$ sudo snap install redis-desktop-manager
# Java JDK
$ sudo pacman -S jdk8-openjdk java-14-openjdk
$ archlinux-java status
$ sudo archlinux-java set java-14-openjdk
# Navicat Premium 150.0.10
# 链接: https://pan.baidu.com/s/1ihWcDY2Vs9igWuDfKh5giA  密码: nnft
$ chmod +x navicat15-premium-cs.AppImage
$ ./navicat15-premium-cs.AppImage
# 后来发现的,对不起Navicat我投入了DataGrip的怀抱
$ yay -S datagrip

# Jekyll
$ sudo pacman -S ruby
# 缺包了就装 bundle add
$ sudo vim /etc/profile
# 把ruby写入到系统环境变量
export PATH="$PATH:/home/achuan/.gem/ruby/3.0.0/bin/"
$ source /etc/profile
$ sudo gem update
$ sudo gem install jekyll bundle bundler

办公

# Thunderbird (邮件收发和RSS订阅,KDE预装)
$ sudo pacman -S thunderbird thunderbird-i18n-zh-cn
# XMind思维导图 (需要JAVA8+)
$ yay -S xmind
# KDE下最好用的PDF阅读器
$ sudo pacman -S okular
# WPS\字体\中文语言包
$ sudo pacman -S wps-office ttf-wps-fonts wps-office-mui-zh-cn
# 如果WPS不能输入中文
$ sudo vim /usr/bin/wps
# 写入以下配置
export GTK_IM_MODULE=ibus
export XMODIFIERS=@im=ibus
export QT_IM_MODULE=ibus
ibus-daemon -d -x

娱乐

# Spotify
$ sudo pacman -S spotify
# 网易云音乐
$ sudo pacman -S netease-cloud-music
# Telegram
$ sudo pacman -S telegram-desktop
# Asciinema 在云端记录并分享你的终端会话
$ sudo pacman -S asciinema
# 开源的游戏平台(可以打美服LOL)
$ sudo pacman -S lutris

工具

# 查看内核版本
$ uname -r
# 安装时对应注意Linux内核版本
$ sudo pacman -S virtualbox virtualbox-guest-dkms
# vboxusers扩展包
$ yay -S virtualbox-ext-oracle virtualbox-guest-iso
# 添加当前用户到virtualbox用户组
$ sudo gpasswd -a $USER vboxusers
# 激活内核模块
$ sudo modprobe vboxdrv
# 坚果云
$ sudo pacman -S nutstore
# 百度网盘
$ yay -S baidunetdisk
# 新一代网络工具包
$ sudo pacman -S iproute2
# Snap Store
$ sudo snap install snap-store
# 程序启动器
$ sudo pacman -S albert
# 桌面面板
$ sudo pacman -S latte-dock
# 迅雷
$ yay -S deepin-wine-thunderspeed
# Teamviewer
$ sudo pacman -S teamviewer
# 如果无法打开或不能联网执行
$ sudo teamviewer --daemon enable
# 支持快捷键下拉的终端模拟器 (KDE预装默认F12唤醒)
$ sudo pacman -S yakuake
# 深度取色器
$ sudo pacman -S deepin-picker
# 深度录屏
$ sudo pacman -S deepin-screen-recorder
# 截图工具
$ sudo pacman -S flameshot-git
# Motrix (支持下载 HTTP、FTP、BT、磁力链、百度网盘等资源)
$ sudo pacman -S motrix
$ git clone git@github.com:sbwtw/deepin-repair-tools.git
# 全平台多线程下载管理器,恢复断/死下载、安排和转换下载、内置视频转换器、支持各大流行浏览器插件
$ yay -S xdman
# 腾讯播放器
$ yay -S debtap
# 升级 debtap
$ sudo debtap -u
$ wget https://dldir1.qq.com/qqtv/linux/Tenvideo_universal_1.0.10_amd64.deb
# 将deb包转换成pkg包
$ sudo debtap Tenvideo_universal_1.0.10_amd64.deb
# 安装pkg包
$ sudo pacman -U sogoupinyin-2.3.1.0112-1-x86_64.pkg.tar.xz

# xDroid 安卓模拟器
https://www.linzhuotech.com/index.php/home/index/xdroid.html
$ tar xvf xDroidInstall-x86_64-v3.0007.tar.gz
$ cd xDroidInstall-x86_64/
$ sh install.sh

# SwitchHosts(hosts管理)
https://github.com/oldj/SwitchHosts/releases
$ chmod +x SwitchHosts._linux_x86_64_3.5.4.5517.AppImage
$ ./SwitchHosts._linux_x86_64_3.5.4.5517.AppImage

开发环境

Apache

$ sudo pacman -S apache
# 设置Apache开机启动服务
$ sudo systemctl enable httpd
# 启动apache服务
$ sudo systemctl start httpd

配置参数

$ cd /etc/httpd/conf
# 备份源文件
$ sudo cp httpd.conf httpd.conf.backup
$ sudo vim httpd.conf 
# 开启重写
LoadModule rewrite_module modules/mod_rewrite.so

</IfModule>
    ServerAdmin achuan@achuan.io
    ServerName io:80
<Directory />
DocumentRoot "/home/achuan/www"
<Directory />
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>
<Directory "/home/achuan/www">
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

$ sudo systemctl restart httpd
# 报错
[Sat Apr 25 00:36:21.725913 2020] [core:error] [pid 106186:tid 140238307444480] (13)Permission denied: [client 127.0.0.1:53420] AH00035: access to / denied (filesystem path '/home/achuan/www') because search permissions are missing on a component of the path

# 很常见的权限问题
$ sudo chmod +x /home/achuan

<IfModule dir_module>
    DirectoryIndex index.php index.html
</IfModule>

$ sudo systemctl restart httpd
# 让apache支持php
Include conf/extra/php7_module.conf
LoadModule php7_module modules/libphp7.so
Include conf/extra/httpd-vhosts.conf

$ sudo systemctl restart httpd
# 重启后直接无法启动了,查看httpd状态看看
$ sudo systemctl startus httpd
[pid 113670:tid 140226396240832] Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe.  You need to recompile PHP.

# Arch Wiki上说Apache 2.4.7不支持非线程安全版PHP。php-apache中包含的libphp7.so不支持mod_mpm_event,仅支持mod_mpm_prefork。需要在/etc/httpd/conf/httpd.conf中注释掉。
$ sudo vim httpd.conf
# 取消以下行的注释:
# LoadModule mpm_event_module modules/mod_mpm_event.so
# 取消以下行的注释:
LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
$ sudo systemctl restart httpd

# 配置hosts
$ sudo vim /etc/hosts
# Virtual hosts
127.0.0.1  io
127.0.0.1  phpmyadmin.io
127.0.0.1  acphp.io
127.0.0.1  tp.io

# 配置虚拟主机
$ sudo vim extra/httpd-vhosts.conf
# Virtual Hosts
<VirtualHost _default_:80>
    DocumentRoot "/home/achuan/www/"
    ServerName io
</VirtualHost>

# phpMyAdmin.io
<VirtualHost *:80>
    ServerName phpmyadmin.io
    DocumentRoot /home/achuan/Carry/phpMyAdmin/
    <Directory  "/home/achuan/Carry/phpMyAdmin/">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

# tp.io
<VirtualHost *:80>
    ServerName tp.io
    DocumentRoot /home/achuan/language/php/tp/public
    <Directory  "/home/achuan/language/php/tp/">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

$ sudo systemctl restart httpd

PHP

$ sudo pacman -S php php-apache
$ php -v
PHP 7.4.5 (cli) (built: Apr 15 2020 17:14:40) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

安装Redis并编译扩展

$ sudo pacman -S redis
# 设置Redis开机启动服务
$ sudo systemctl enable redis
$ sudo systemctl start redis

# 编译Redis扩展
$ git clone https://github.com/phpredis/phpredis.git
$ cd phpredis
# 下载下来后默认这develop分支,需要手动切换到主分支
$ git checkout master
$ /usr/bin/phpize
$ sudo ./configure --with-php-config=/usr/bin/php-config
$ sudo make && sudo make install

配置参数

$ cd /etc/php
# 备份源文件
$ sudo cp php.ini php.ini.backup
$ sudo vim php.ini

error_reporting = E_ALL
display_errors = On
short_open_tag = On
display_startup_errors = On
memory_limit = 128M
post_max_size = 32M
date.timezone = Asia/Shanghai

extension=curl
extension=ftp
extension=imap
extension=mysqli
extension=pdo_mysql
extension=sockets
extension=zip
extension=redis

Composer

$ curl -sS https://getcomposer.org/installer | php
# 全局调用
$ sudo mv composer.phar /usr/local/bin/composer
# 使用阿里云镜像
$ composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

MySQL

$ sudo pacman -S mysql
# 初始化
$ sudo mysqld --initialize --user=mysql --basedir=/usr --datadir=/var/lib/mysql
# 我遇到了问题,初始化没有提供密码,不管它直接改。
$ sudo vim /etc/mysql/my.cnf
在[mysqld]中写入
skip-grant-tables
$ sudo systemctl restart mysqld
$ mysql -uroot -p
# 设置MySQL开机启动服务
$ sudo systemctl enable mysqld
# 改密码
# 在这我遇到个问题,如果不先刷新权限,SQL语句就会报错
ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)

mysql> ALTER user 'root'@'localhost' IDENTIFIED BY 'achuan.io';
Query OK, 0 rows affected (0.02 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)

> QUIT
Bye

GoLand

安装

$ sudo pacman -S go

配置

# 配置环境变量
$ sudo vim /etc/profile
export GOPATH=$HOME/Important/go
$ source /etc/profile

Qv2ray 科学上网

  • 搬瓦工方案库存监控页面
  • 我的VPS配置:CN2 1核 1GB 20GB 1TB 1Gbps 洛杉矶 $49.99
  • 要求:Ubuntu 16+ / Debian 8+ / CentOS 7+ 系统
  • 推荐使用 Debian 9 系统,脚本会自动启用 BBR 优化

v2ray安装

$ yum install curl -y
$ bash <(curl -s -L https://git.io/v2ray.sh)

快速管理

# 查看 V2Ray 配置信息
$ v2ray info
# 修改 V2Ray 配置
$ v2ray config
# 生成 V2Ray 配置文件链接
$ v2ray link
# 生成 V2Ray 配置信息链接
$ v2ray infolink
# 生成 V2Ray 配置二维码链接
$ v2ray qr
# 修改 Shadowsocks 配置
$ v2ray ss
# 查看 Shadowsocks 配置信息
$ v2ray ssinfo
# 生成 Shadowsocks 配置二维码链接
$ v2ray ssqr
# 查看 V2Ray 运行状态
$ v2ray status
# 启动 V2Ray
$ v2ray start
# 停止 V2Ray
$ v2ray stop
# 重启 V2Ray
$ v2ray restart 
# 查看 V2Ray 运行日志
$ v2ray log
# 更新 V2Ray
$ v2ray update
# 更新 V2Ray 管理脚本
$ v2ray update.sh
# 卸载 V2Ray
$ v2ray uninstall

配置文件路径

# V2Ray 配置文件路径
/etc/v2ray/config.json
# Caddy 配置文件路径
/etc/caddy/Caddyfile
# 脚本配置文件路径
/etc/v2ray/233blog_v2ray_backup.conf

v2ray客户端

推荐俩v2ray客户端

  • Qv2ray

    三大平台GUI客户端,使用C++17/Qt5、支持订阅、扫描二维码、自定义路由编辑

  • v2rayL

    linux GUI客户端,支持订阅、vemss、ss等协议,自动更新订阅、透明代理

# AppImage版本
$ wget https://github.com/Qv2ray/Qv2ray/releases/download/v2.5.0/Qv2ray.v2.5.0.linux-x64.AppImage

# 因为政策原因Qv2ray并不自带v2ray核心
$ sudo pacman -S qv2ray v2ray

# 代理扩展
SwitchyOmega
# SwitchyOmega配置
https://raw.githubusercontent.com/wiki/FelisCatus/SwitchyOmega/GFWList.bak

# GoFW
# 境外网站加速器,Github一个项目,利用BootCDN加载境外网站某些静态资源
https://github.com/xmcp/GoFW

Pacman

更新

# 全面更新
$ pacman -Syyu
# 更新所有包
$ pacman -Syu
# 更新包数据源
$ pacman -Sy
# 更新已安装的包
$ pacman -Su

搜索安装

# 安装
$ pacman -S
# 搜索含关键字的包
$ pacman -Ss
# 同步包后再执行安装
$ pacman -Sy
# 安装本地包 (扩展名:pkg.tar.gz)
$ pacman -U
# 搜索已安装的包
$ pacman -Qs
# 升级全部包
$ pacman -Syu
# 只下载,不安装
$ pacman -Sw

显示删除

# 删除包,不会删除其依赖
$ pacman -R
# 删除包,及其所有没有被其它包使用的依赖
$ pacman -Rs
# 删除一个包,包括所有依赖
$ pacman -Rsc
# 清理未安装的包 (包文件目录:/var/cache/pacman/pkg/)
$ pacman -Sc
# 清理所有缓存文件
$ pacman -Scc
# 显示包信息
$ pacman -Si
# 查询本地包的详情信息
$ pacman -Qi
# 列出所有不再作为依赖的包
$ pacman -Qdt
# 列出所有明确安装而且不被其他包依赖的包
$ pacman -Qet

Snap

# 查看版本信息
$ snap --version
# 找出所有snap应用
$ snap find
# 安装应用
$ snap install
# 重启应用
$ snap restart
# 升级应用
$ snap refresh
# 查看安装的应用
$ snap list
# 卸载应用
$ snap remove

you-get

  • Github:https://github.com/soimort/you-get

  • 命令行程序,提供便利的方式来下载网络上的媒体信息

# 下载视频
$ you-get -i
# 使用 --http-proxy/-x为you-get设置HTTP代理:
$ you-get -x 127.0.0.1:8888
# 加载okie,目前支持两种cookie格式:Mozilla cookies.sqlite 和 Netscape cookies.txt.
$ you-get -c
# 获得页面所有可下载URL列表,支持JSON格式
$ you-get -u

annie

  • Github:https://github.com/iawia002/annie
  • 这是个国产的命令行下载器,用GO构建,目前支持的网站有
# 下载,如果URL包含特殊字符,需要用引号引起来
$ annie [URL]
# 显示视频质量等信息
$ annie -i https://www.bilibili.com/video/BV1FV411d7u7
 Site:      哔哩哔哩 bilibili.com
 Title:     bilibili献给新一代的演讲《后浪》 P1 bilibili献给新一代的演讲《后浪》
 Type:      video
 Streams:   # All available quality
     [80]  -------------------
     Quality:         高清 1080P
     Size:            65.55 MiB (68738121 Bytes)
     # download with: annie -f 80 ...
# 下载1080P列表所有视频,若只下载第一个去掉 -p
$ annie -f 80 -p https://www.bilibili.com/video/BV1FV411d7u7

最后再来一张图啊哈哈哈~

我的桌面

后续报错

JetBrains DataGrip的JavaFx报错

  • tried to use preview panel provider (javafx webview), but it is unavailable. reverting to default.

打开Markdown文件就会报错,为在Medium上找到的一个解决方案,重装一下PHPSTORM JRE

$ yay -S phpstorm-jre

Increasing the amount of inotify watchers

  • /home/achuan/.gem/ruby/2.7.0/gems/rb-inotify-0.10.1/lib/rb-inotify/watcher.rb:74:in `initialize’: No space left on device - Failed to watch “/home/achuan/github/achuanya.github.io/.jekyll-cache/Jekyll/Cache/Jekyll–Converters–Markdown/e6”: The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource. (Errno::ENOSPC)

使用$ jekyll server提示被限额了,增加限额永久化:

$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
$ sudo sysctl -p

  • ✇游钓四方的博客
  • (转)谈 Linux,Windows 和 Mac游钓四方的博客
    本文转自当然我在扯淡 这段时间受到很多人的来信。他们看了我很早以前写的推崇 Linux 的文章,想知道如何“抛弃 Windows,学习 Linux”。天知道他们在哪里找到那么老的文章,真是好事不出门…… 我觉得我有责任消除我以前的文章对人的误导,洗清我这个“Linux 狂热分子”的恶名。我觉得我已经写过一些澄清的文章了,可是怎么还是有人来信问 Linux 的问题。也许因为感觉到“舆论压力”,我把文章都删了。 简言之,我想对那些觉得 Linux 永远也学不会的“菜鸟”们说: 1. Linux 和 Unix 里面包含了一些非常糟糕的设计。不要被 Unix 的教条主义者吓倒。学不会有些东西很多时候不是你的错,而是 Linux 的错,是“Unix 思想” 的错。不要浪费时间去学习太多工具的用法,钻研稀奇古怪的命令行。那些貌似难的,复杂的东西,特别要小心分析。 2. Windows 避免了 Unix,Linux 和 Mac OS X 的很多问题。微软是值得尊敬的公司,是真正在乎程序开发工具的公司。我收回曾经对微软的鄙视态度。请菜鸟们吸收 Windows 设计里
     

(转)谈 Linux,Windows 和 Mac

2020年4月7日 08:00

本文转自当然我在扯淡


这段时间受到很多人的来信。他们看了我很早以前写的推崇 Linux 的文章,想知道如何“抛弃 Windows,学习 Linux”。天知道他们在哪里找到那么老的文章,真是好事不出门…… 我觉得我有责任消除我以前的文章对人的误导,洗清我这个“Linux 狂热分子”的恶名。我觉得我已经写过一些澄清的文章了,可是怎么还是有人来信问 Linux 的问题。也许因为感觉到“舆论压力”,我把文章都删了。

简言之,我想对那些觉得 Linux 永远也学不会的“菜鸟”们说:

  1. 1. Linux 和 Unix 里面包含了一些非常糟糕的设计。不要被 Unix 的教条主义者吓倒。学不会有些东西很多时候不是你的错,而是 Linux 的错,是“Unix 思想” 的错。不要浪费时间去学习太多工具的用法,钻研稀奇古怪的命令行。那些貌似难的,复杂的东西,特别要小心分析。

  2. 2. Windows 避免了 Unix,Linux 和 Mac OS X 的很多问题。微软是值得尊敬的公司,是真正在乎程序开发工具的公司。我收回曾经对微软的鄙视态度。请菜鸟们吸收 Windows 设计里面好的东西。另外 Visual Studio 是非常好的工具,会带来编程效率的大幅度提升。请不要歧视 IDE。要正视 Emacs,VIM 等文本编辑器的局限性。当然,这些正面评价不等于说你应该为微软工作。就像我喜欢 iPhone,但是却不一定想给 Apple 工作一样。

  3. 3. 学习操作系统最好的办法是学会(真正的)程序设计思想,而不是去“学习”各种古怪的工具。所有操作系统,数据库,Internet,以至于 WEB 的设计思想(和缺陷),几乎都能用程序语言的思想简单的解释。

先说说我现在对 Linux 和相关工具(比如 TeX)的看法吧。我每天上班都用 Linux,可是回家才不想用它呢。上班的时候,我基本上只是尽我所能的改善它,让它不要给我惹麻烦。Unix 有许许多多的设计错误,却被当成了教条,传给了一代又一代的程序员,恶性循环。Unix 的 shell,命令,配置方式,图形界面,都是相当糟糕的。每一个新版本的 Ubuntu 都会在图形界面的设计上出现新的错误,让你感觉历史怎么会倒退。其实这只是表面现象。Linux 所用的图形界面(X Window)在本质上几乎是没救的。我不想在这里细说 Unix 的缺点,在它出现的早期,已经有人写了一本书,名叫 Unix Hater’s Handbook,里面专门有一章叫做 The X-Windows Disaster。它分析后指出,X Window 貌似高明的 client-server 设计,其实并不像说的那么好。

这本书汇集了 Unix 出现的年代,很多人对它的咒骂。有趣的是,这本书有一个“反序言”,是 Unix 的创造者之一 Dennis Ritchie 写的。我曾经以为这些骂 Unix 的人都是一些菜鸟。他们肯定是智商太低,或者被 Windows 洗脑了,不能理解 Unix 的高明设计才在那里骂街。现在理解了程序语言的设计原理之后,才发现他们说的那些话里面居然大部分是实话!其实他们里面有些人在当年就是世界顶尖的编程高手,自己写过操作系统和编译器,功底不亚于 Unix 的创造者。在当年他们就已经使用过设计更加合理的系统,比如 Multics,Lisp Machine 等。

可惜的是,在现在的操作系统书籍里面,Multics 往往只是被用来衬托 Unix 的“简单”和伟大。Unix 的书籍喜欢在第一章讲述这样的历史:“Multics 由于设计过于复杂,试图包罗万象,而且价格昂贵,最后失败了。” 可是 Multics 失败了吗?Multics,Oberon,IBM System/38, Lisp Machine,…… 在几十年前就拥有了 Linux 现在都还没有的好东西。Unix 里面的东西,什么虚拟内存,文件系统,…… 基本上都是从 Multics 学来的。Multics 的机器,一直到 2000 年都还在运行。Unix 不但“窜改”了历史教科书,而且似乎永远不吸取教训,到现在还没有实现那些早期系统早就有的好东西。Unix 的设计几乎完全没有一致性和原则。各种工具程序功能重复,冗余,没法有效地交换数据。可是最后 Unix 靠着自己的“廉价”,“宗教”和“哲学”,战胜了别的系统在设计上的先进,统治了程序员的世界。

如果你想知道这些“失败的”操作系统里面有哪些我们现在都还没有的先进技术,可以参考这篇文章:Oberon - The Overlooked Jewel。它介绍的是 Niklaus Wirth(也就是 Pascal 语言的设计者)的 Oberon 操作系统。

胜者为王,可是 Unix 其实是一个暴君,它不允许你批评它的错误。它利用其它程序员的舆论压力,让每一个系统设计上的错误,都被说成是用户自己的失误。你不敢说一个工具设计有毛病,因为如果别人听到了,就会以为你自己不够聪明,说你“人笨怪刀钝”。这就像是“皇帝的新装”里的人们,明明知道皇帝没穿衣服,还要说“这衣服这漂亮”!总而言之,“对用户友好”这个概念,在 Unix 的世界里是被歧视,被曲解的。Unix 的狂热分子很多都带有一种变态的“精英主义”。他们以用难用的工具为豪,鄙视那些使用“对用户友好”的工具的人。

我曾经强烈的推崇 FVWM,TeX 等工具,可是现在擦亮眼睛看来,它们给用户的界面,其实也是非常糟糕的设计,跟 Unix 一脉相承。他们把程序设计的许多没必要的细节和自己的设计失误,无情的暴露给用户。让用户感觉有那么多东西要记,仿佛永远也没法掌握它。实话说吧,当年我把 TeXbook 看了两遍,做完了所有的习题(包括最难的“double bend”习题)。几个月之后,几乎全部忘记干净。为什么呢?因为 TeX 的语言是非常糟糕的设计,它没有遵循程序语言设计的基本原则。

这里有一个鲜为人知的小故事。TeX 之所以有一个“扩展语言”,是 Scheme 的发明者 Guy Steele 的建议。那年夏天,Steele 在 Stanford 实习。他听说 Knuth 在设计一个排版系统,就强烈建议他使用一种扩展语言。后来 Knuth 采纳了他的建议。不幸的是 Steele 几个月后就离开了,没能帮助 Knuth 完成语言的设计。Knuth 老爹显然有我所说的那种“精英主义”,他咋总是设计一些难用的东西,写一些难懂的书?

一个好的工具,应该只有少数几条需要记忆的规则,就像象棋一样。而这些源于 Unix 的工具却像是“魔鬼棋”或者“三国杀”,有太多的,无聊的,人造的规则。有些人鄙视图形界面,鄙视 IDE,鄙视含有垃圾回收的语言(比如 Java),鄙视一切“容易”的东西。他们却不知道,把自己沉浸在别人设计的繁复的规则中,是始终无法成为大师的。就像一个人,他有能力学会各种“魔鬼棋”的规则,却始终无法达到象棋大师的高度。所以,容易的东西不一定是坏的,而困难的东西也不一定是好的。学习计算机(或者任何其它工具),应该“只选对的,不选难的”。记忆一堆的命令,乌七八糟的工具用法,最后脑子里什么也不会留下。学习“原理性”的东西,才是永远不会过时的。

Windows 技术设计上的很多细节,也许在早期是同样糟糕的。但是它却向着更加结构化,更加简单的方向发展。Windows 的技术从 OLE,COM,发展到 .NET,再加上 Visual Studio 这样高效的编程工具,这些带来了程序员和用户效率的大幅度提高,避免了 Unix 和 C 语言的很多不必存在的问题。Windows 程序从很早的时候就能比较方便的交换数据。比如,OLE 让你可以把 Excel 表格嵌入到 Word 文档里面。不得不指出,这些是非常好的想法,是超越“Unix 哲学”的。相反,由于受到“Unix 哲学”的误导,Unix 的程序间交换数据一直以来都是用字符串,而且格式得不到统一,以至于很多程序连拷贝粘贴都没法正确进行。Windows 的“配置”,全都记录在一个中央数据库(注册表)里面,这样程序的配置得到大大的简化。虽然在 Win95 的年代,注册表貌似老是惹麻烦,但现在基本上没有什么问题了。相反,Unix 的配置,全都记录在各种稀奇古怪的配置文件里面,分布在系统的各个地方。你搞不清楚哪个配置文件记录了你想要的信息。每个配置文件连语法都不一样!这就是为什么用 Unix 的公司总是需要一个“系统管理员”,因为软件工程师们才懒得记这些麻烦的东西。

再来比较一下 Windows 和 Mac 吧。我认识一个 Adobe 的高级设计师。他告诉我说,当年他们把 Photoshop 移植到 Intel 构架的 Mac,花了两年时间。只不过换了个处理器,移植个应用程序就花了两年时间,为什么呢?因为 Xcode 比起 Visual Studio 真是差太多了。而 Mac OS X 的一些设计原因,让他们的移植很痛苦。不过他很自豪的说,当年很多人等了两年也没有买 Intel 构架的 Mac,就是因为他们在等待 Photoshop。最后他直言不讳的说,微软其实才是真正在乎程序员工具的公司。相比之下,Apple 虽然对用户显得友好,但是对程序员的界面却差很多。Apple 尚且如此,Linux 对程序员就更差了。可是有啥办法呢,有些人就是受虐狂。自己痛过之后,还想让别人也痛苦。就像当年的我。

我当然不是人云亦云。微软在程序语言上的造诣和投入,我看得很清楚。我只是通过别人的经历,来验证我已经早已存在的看法。所以一再宣扬别的系统都是向自己学习的 Apple 受到这样的评价,我也一点不惊讶。Mac OS X 毕竟是从 Unix 改造而来的,还没有到脱胎换骨的地步。我有一个 Macbook Air,一个 iPhone 5,和一个退役的,装着 Windows 7 的 T60。我不得不承认,虽然我很喜欢 Macbook 和 iPhone 的硬件,但我发现 Windows 在软件上的很多设计其实更加合理。

我为什么当年会鄙视微软?这很简单。我就是跟着一群人瞎起哄而已!他们说 Linux 能拯救我们,给我们自由。他们说微软是邪恶的公司…… 到现在我身边还有人无缘无故的鄙视微软,却不知道理由。可是 Unix 是谁制造的呢?是 AT&T。微软和 AT&T 哪个更邪恶呢?我不知道。但是你应该了解一下 Unix 的历史。AT&T 当年发现 Unix 有利可图,找多少人打了多少年官司?说微软搞垄断,其实 AT&T 早就搞过垄断了,还被拆散成了好几个公司。想想世界上还有哪一家公司,独立自主的设计出这从底至上全套家什:程序语言,编译器,IDE,操作系统,数据库,办公软件,游戏机,手机…… 我不得不承认,微软是值得尊敬的公司。

公司还不都一样,都是以利益为本的。我们程序员就不要被他们利用,作为利益斗争的炮灰啦。见到什么好就用什么,就学什么。自己学到的东西,又不属于那些垄断企业。我们都有自由的头脑。

当然我不是在这里打击 Linux 和 Mac 而鼓吹 Windows。这些系统的纷争基本上已经不关我什么事。我只是想告诉新人们,去除头脑里的宗教,偏激,仇恨和鄙视。每次仇恨一个东西,你就失去了向它学习的机会。

后记:“对用户友好”是一个值得研究,却又研究得非常不够的东西。很多 UI 的设计者,把东西设计的很漂亮,但是却不方便,不顺手。如果你想了解我认为怎样的设计才是“对用户友好的”,可以参考这篇博客《什么是“对用户友好”》

  • ✇游钓四方的博客
  • (转)为什么犹太人如此优秀?游钓四方的博客
    本文转自有个老外叫马绍飞 虽然我几乎在每一个回复下都说“本人是土生土长的纽约人”,但我同时也是德系(Ashkenazi)犹太人。我选择回答这个问题并不是为了吹嘘或贬低自己的民族,而是想尽可能地消除很多人对德系犹太人的误解。 首先,我们一定要弄清楚的是,那些所谓的“优秀”犹太人到底是谁呢?犹太人分布在全球各地,总人口数最多一千五百万,还不到全世界人口比例的0.2%。不过,犹太人分成了很多不同的民族和宗教派别。爱因斯坦、弗洛伊德、马克思、罗斯柴尔德,还有一些诺贝尔奖得主等著名的犹太人大多属于“世俗德系犹太人”这个群体,“世俗”意味着没有什么明确的宗教信仰。据我观察,亚洲人经常给这一部分犹太人贴上“努力”、”聪明”、“有钱”、“优秀”等标签,而欧美洋人们通常认为我们“小气”、“贪婪”、“狡猾”、“虚伪”等等。 其次,我也希望大家不要盲目地吹捧犹太人,因为并不是所有的德系犹太人都很了不起。我们像其他民族一样有自己优缺点。不过确实有很多人对我们很感兴趣,我非常感谢这些人对我们民族的尊重。 在东亚人民眼中,德系犹太人好像是一群特别能吃苦耐劳、又极具商业头脑的土豪。但在我和我身边人的眼中,我
     

(转)为什么犹太人如此优秀?

2020年3月18日 08:00

本文转自有个老外叫马绍飞


虽然我几乎在每一个回复下都说“本人是土生土长的纽约人”,但我同时也是德系(Ashkenazi)犹太人。我选择回答这个问题并不是为了吹嘘或贬低自己的民族,而是想尽可能地消除很多人对德系犹太人的误解。

首先,我们一定要弄清楚的是,那些所谓的“优秀”犹太人到底是谁呢?犹太人分布在全球各地,总人口数最多一千五百万,还不到全世界人口比例的0.2%。不过,犹太人分成了很多不同的民族和宗教派别。爱因斯坦、弗洛伊德、马克思、罗斯柴尔德,还有一些诺贝尔奖得主等著名的犹太人大多属于“世俗德系犹太人”这个群体,“世俗”意味着没有什么明确的宗教信仰。据我观察,亚洲人经常给这一部分犹太人贴上“努力”、”聪明”、“有钱”、“优秀”等标签,而欧美洋人们通常认为我们“小气”、“贪婪”、“狡猾”、“虚伪”等等。

其次,我也希望大家不要盲目地吹捧犹太人,因为并不是所有的德系犹太人都很了不起。我们像其他民族一样有自己优缺点。不过确实有很多人对我们很感兴趣,我非常感谢这些人对我们民族的尊重。

在东亚人民眼中,德系犹太人好像是一群特别能吃苦耐劳、又极具商业头脑的土豪。但在我和我身边人的眼中,我们德系犹太人不过是个有点古怪、无法融入周围社会的游牧民族。说实话,作为犹太人,我心里其实一直都有点自卑感,因为我自己的先辈们移民到美国的时候,真的是一穷二白。我长大之后,才逐渐意识到德系犹太人的成功率竟然是全球最高的。我估计这可能是因为我们的价值观跟大众不太一样吧。

有哪里不一样呢?举个例子,大部分人努力学习是为了获得更好的挣钱机会,或者是给自己更多的职业选择。但犹太人恰恰相反,整体来说,德系犹太人努力挣钱是为了获得更多的学习时间。

Alt text

犹太人追求智慧并不是为了提高自己的经济地位,而更是为了让自己的生活变得更加丰富。上图中可能看出,虔诚(Orthodox)犹太人一天到晚都在读塔木德经,甚至到老年都不放弃学习。其实这个现象已经在以色列产生了严重的经济问题,因为很多虔诚犹太人去钻以色列的福利制度空子,然后拿政府的钱回家读书。

世俗犹太人对智慧的这种忠贞不渝的态度和生活习惯,主要是传承于信仰犹太教的祖辈。但是十九世纪欧洲的“犹太启蒙运动”导致不少德系犹太人逐渐抛弃犹太教。放弃宗教学习后,世俗德系犹太人把自己的学习精力都转向了科学技术、金融、学术、哲学、艺术、音乐、外语等各种领域。

但除了热爱学习之外,犹太人的高成功率还包括一些其他因素,比如:

1、 欧洲中世纪期间,梵蒂冈的教皇宣称任何涉及发放贷款的工作都很不纯洁,所以禁止信仰基督教的人们从事银行工作。只有“肮脏龌龊”的犹太人才有权利发放贷款,这样银行企业自然而然地被犹太人垄断了。梵蒂冈教皇搬了石头砸了自己的脚,不过这个巨大的错误却有利于德系犹太人的长期经济回报。

2、 犹太人的集体主义思想比较强。虽然犹太人对政治、宗教、巴以冲突等方面都持有完全不同的意见,但犹太人整体都互相帮助、互相热爱。我们确实喜欢吵架,但我们依然把其他的犹太人视为自己的亲人。

3、 我们的价值观比大部分西方人稍微保守一点。比如我们大多数人不会花天酒地、吸毒约炮、吃喝嫖赌等等。德系犹太人也非常重视结婚生子、对家庭负责等生活习惯,这一点跟中国人的价值观比较相似。

4、 德系犹太人的历史非常悲惨。近两千年的历史中,德系犹太人在欧洲各地不断被屠杀、驱逐、掠夺、强奸等等。为了能生存下来,犹太人必须努力奋斗,有时甚至是不择手段。基督教徒相信只要向上帝祈祷就能帮你的生活变得更好,但犹太人倒认为上帝对你的日常生活来说连个屁都不算。犹太人觉得只有通过自己的努力,才能改变自己的命运。

还有人说德系犹太人的高成功率跟旧约本身有关系,我倒是觉得犹太教跟基督教或伊斯兰教本质上并没有太大的区别,这三个一神教其实都有良好的一面和残暴的一面,有理性的一面也有反智的一面。德系犹太人的生活方式确实受到犹太教的影响,但比起旧约的内容,他们的高成功率更多是由于历史原因以及德系犹太人的独特生活习惯。最直观的就是,传统德系犹太人和传统非洲犹太人都信仰旧约,但这两个民族的成功率截然不同….

与其他民族和宗教派别的教学方式相比,德系犹太人的教学方式更重视“疑问”,而不怎么重视“回答”,因为很多问题并没有绝对正确的答案。从小长辈们就经常让我们问很多问题,跟同学们一起来进行辩论,从而不断地锻炼自己的独立思考能力。在这种环境下,小朋友们很快就会发现这个世界含有无限的奥秘以及无穷无尽的知识。对我们来说,追求智慧本身才是生命的意义。

德系犹太人热爱学习的主要原因是为了追求智慧,而不是为了考高分、挣钱、竞争、炫耀、装逼等等。实际上,很多世俗德系犹太人倒是更趋向于谦虚低调、勤俭朴素的生活方式,而不怎么爱去购买豪宅豪车或者最新发布的苹果手机,因为这些东西对我们来说毫无意义。在纽约和加州确实有一部分笑贫不笑娼、纸醉金迷的犹太人富豪,但这一部分人毕竟还是少数,因为我们的文化很重视谦虚。大部分德系犹太人把自己的钱都留在银行或者投入孩子的教育。犹太人也会用自己的钱来帮助周围的社区发展、创立非政府组织、建设新的图书馆等,或者把钱捐赠给艺术、教育、脱贫组织,以及慈善机构等等。根据犹太教的传统价值观,挣钱本身并没有什么不好,在这一方面我们的信仰跟基督教徒确实有区别。可是按照犹太教的规则,有钱人一定要用自己的钱来帮助穷人,所以贫穷犹太人从来不会被淘汰。虽然我们世俗犹太人并没有认真地信奉犹太教,但我们依然把这些传统的价值观都传承给了后代。我们对犹太教的态度是“取其精华,去其糟粕”,就像当代中国人对儒家思想以及中国传统社会道德的态度一样吧。

我好像有点跑题了。除了上面提到的原因外,犹太人的高成功率还有另外一个原因,那就是德系犹太人根深蒂固的思维方式。这一点一言难尽,但我会尽量给大家解释一下:

如果你把金钱、面子、社会地位等方面都排在智慧之前,你必然会活得很不开心,你的生活也没什么意义。此外,你在这种思想下也无法挣钱,因为缺乏智慧的人没有独立思考的能力。你的创造力不够全面、特殊技能不够发达、目光太短浅。相反的,如果你把智慧、教育、想象力、激情等方面都放在物质欲望的前面,你不仅可以填满你心里的空虚,也可以凭借丰富的知识和开阔的视野来得到更靠谱的工作机会,并给自己打造更美好的前途。

举个例子,假设你对医学不怎么感兴趣,但你为了挣钱而选择去学医,你将来必定会成为一个没什么工作激情、心情沮丧的三流医生。但如果你是因为想了解人体构造、传染病的症状等去学医,通过自己的努力,你就可以成为一个心满意足、高薪酬的优秀医生。这样的人也有能力给全社会带来巨大的帮助。

爱因斯坦的解释可能会更清楚一点吧:“对知识本身的追求,对正义近乎偏执的热爱,以及对个人独立的渴望,这些都是传统犹太人的特点。而我由衷感恩,我是其中的一员。

Alt text

❌
❌