每天学习一个命令: rename 批量修改文件名

虽然 Linux 下有很多方式可以重命名文件,比如 mv filename newfilename,设置可以使用 cat 命令来将一个文件输出重定向到文件 cat file > newfile,或者可以拷贝的时候重命名 cp file newfile

但是其实 Linux 下有一个命令 rename 顾名思义,就是用来重命名文件的,并且能够按照正则批量重命名文件。他的基本使用方式就是

rename [options] "s/oldname/newname/" file

这个命令可以分开几部分来讲,首先对于整体命令先不看选项(options) 部分

rename "s/oldname/newname/" file

其中包含三个部分:

  • 原字符串oldname:将要被替换的字符串;
  • 目标字符串newname:原字符替换成的目标字符串;
  • 文件file:指定要改变文件名的文件列表。

其中每一个部分都可以使用正则,以上命令的解释可以理解为对于要重命名的 file 匹配的文件列表,将文件名中的 oldname 替换为 newname。

然后再来看选项options 部分,rename 支持以下的选项:

  • -v 将重命名的内容都打印到标准输出,v 可以看成 verbose
  • -n 测试会重命名的内容,将结果都打印,但是并不真正执行重命名的过程
  • -f force 会覆盖本地已经存在的文件
  • -h -m -V 分别为帮助,帮助,版本
  • -e 比较复杂,可以通过该选项,写一些脚本来做一些复杂的事情

rename 支持通配符

?  可替代单个字符
*  可替代多个字符

当命令中最后 file 为 * 时表示,匹配当前文件夹下所有文件,如果为 ? 时则匹配只有一个字符的文件名。

rename支持正则表达式

rename 支持 perl 的正则表达式

替换文件名中特定字段

rename "s/AA/aa/" *  # 把文件名中的AA替换成aa

这一行命令的解释就是,对当前文件夹下满足 * 的所有文件,文件名中包含 AA 字符的替换为 aa 其中 "s/pattern/new/" 中的 / 一个都不能少。

修改文件后缀

rename "s/.html/.php/" *     # 把.html 后缀的改成 .php后缀
rename "s/.png/.jpg/" *      # 将 png 改为 jpg

批量修改文件后缀名

批量添加文件后缀

rename "s/$/.txt/" *     # 把所有的文件名都以txt结尾

因为支持正则表达式,那么 $ 表示的就是结尾,将结尾替换为 .txt 也就意味着给所有文件添加 .txt 的后缀

批量删除文件名

rename "s/.txt//" *      # 把所有以.txt结尾的文件名的.txt删掉

同理,结尾有 .txt 的内容替换为空,也就是删掉后缀了。

应用正则匹配的部分文件名

假如需要在批量修改的时候保留部分文件名,可以使用引用 \1 ,比如有下面格式的文件

Screenshot from 2019-01-02 15-56-49.jpg

我只希望保留其中的日期部分,那么可以

rename -n "s/Screenshot from ([0-9\\- ]+).jpg/\1.jpg/" *

() 匹配的内容取出来放到替换部分。


2018-01-31 linux , command , rename , file

v2ray

v2ray 是一个模块化的代理工具,支持 VMess,Socks,HTTP,Shadowsocks 等等协议,并且附带很多高级功能,HTTP 伪装, TLS 等等。

安装 install

root 账户下执行

bash <(curl -L -s https://install.direct/go.sh)

该脚本会自动安装 unzipdaemon。脚本执行成功后,进行如下操作:

  1. 编辑文件 vim /etc/v2ray/config.json 来配置
  2. 运行 service v2ray start 来启动 v2ray
  3. 使用 service v2ray start|stop|status|reload|restart|force-reload 来控制 v2ray

配置 config

v2ray 使用 JSON 格式的配置文件,大致配置格式如下:

{
  "log": {},
  "dns": {},
  "routing": {},
  "policy": {},
  "inbound": {},
  "outbound": {},
  "inboundDetour": [],
  "outboundDetour": [],
  "transport": {}
}

更加详细的配置详解可以参考官方的文档

v2ray 支持以下协议,默认的协议为 VMess

  • Blackhole
  • Dokodemo-door
  • Freedom
  • HTTP
  • Shadowsocks
  • Socks
  • VMess

如果想要修改 clients 下面的 id,可以访问 https://www.uuidgenerator.net/ 网站生成 UUID,对于服务端配置来说,主要关心 inbound 中配置,包括端口,协议,和 id 以及 alterId。这些配置需要和客户端一致。

{
  "log" : {   //日志配置
    "access": "/var/log/v2ray/access.log", //访问日志
    "error": "/var/log/v2ray/error.log",   //错误日志
    "loglevel": "warning" //日志等级
  },
  "inbound": {   // 主传入连接
    "port": 10800,  // 端口
    "protocol": "vmess",  //协议
    "settings": {
      "clients": [
        {
          "id": "d931e571-c9d2-4527-9223-9ef1cdeaf4b2",  // 客户端需要和服务端一致
          "level": 1,
          "alterId": 64   // 客户端需要和服务端一致
        }
      ]
    }
  },
  "outbound": {
    "protocol": "freedom",
    "settings": {}
  },
  "outboundDetour": [
    {
      "protocol": "blackhole",
      "settings": {},
      "tag": "blocked"
    }
  ],
  "routing": {
    "strategy": "rules",
    "settings": {
      "rules": [
        {
          "type": "field",
          "ip": [
            "0.0.0.0/8",
            "10.0.0.0/8",
            "100.64.0.0/10",
            "127.0.0.0/8",
            "169.254.0.0/16",
            "172.16.0.0/12",
            "192.0.0.0/24",
            "192.0.2.0/24",
            "192.168.0.0/16",
            "198.18.0.0/15",
            "198.51.100.0/24",
            "203.0.113.0/24",
            "::1/128",
            "fc00::/7",
            "fe80::/10"
          ],
          "outboundTag": "blocked"
        }
      ]
    }
  }
}

观察服务端配置基本上能看到两大块重点,一个重点就是 inbound 另一个重点就是 outbound,对应客户端也是同样的配置,对于服务端来说,他的流入就是客户端的流出,对于服务端来说,需要在 inbound 中配置,然后客户端需要在 outbound 中配置和服务端一致的配置,然后就能连入。

客户端配置

根据自己的系统选择下载客户端版本: https://github.com/v2ray/v2ray-core/releases

客户端配置中,需要特殊关心的就是 outbound 中内容,需要关心服务器地址,端口,ID,和 alterId。

javascript { "inbound": { "port": 1080, // 监听端口 "protocol": "socks", // 入口协议为 SOCKS 5 "settings": { "auth": "noauth" //socks的认证设置,noauth 代表不认证,由于 socks 通常在客户端使用,所以这里不认证 } }, "outbound": { "protocol": "vmess", // 出口协议 "settings": { "vnext": [ { "address": "serveraddr.com", // 服务器地址,请修改为你自己的服务器 ip 或域名 "port": 16823, // 服务器配置端口 "users": [ { "id": "b831381d-6324-4d53-ad4f-8cda48b30811", // 用户ID,必须与服务器端配置相同 "alterId": 64 // 此处的值也应当与服务器相同 } ] } ] } } }

总的来说,各大客户端只需要关心几个配置,服务器地址,配置协议,端口,及认证方式。对于 VMess 协议,需要知道 id,alterId。

Linux

下载对应 Linux 的客户端,执行 ./v2ray --config=config.json 来启动客户端,如果看到日志正常表示已经连接成功。

Windows/MacOS

类似 Linux,启动客户端配置本地,其他选择还有

Windows 下 V2RayW, MacOS 下 V2RayX

Android

Android 客户端现在为止有 V2RayNG、Actinium,本人测试前一个比较好用,但是也存在问题,长时间连接容易掉线。

Play Store 链接 备用 GitHub 地址

iOS

使用 ShadowRocket,美区下载,支持 VMess 协议。如果没有 ShadowRocket ,其他可供选择的还有 Kitsunebi 和 ShadowRay ,如果App Store搜不到请用美区账号。

安装 ShadowRocket 还有一个简单的方法,搜索 爱思助手

reference


2018-01-26 linux , windows , mac , socks , http , shadowsocks , proxy , vmess

自建网络硬盘 ownCloud

ownCloud 是一个文件分享服务,可以将个人的文件内容,比如文本,图片,音频等等存储到一个中心服务器上,类似于 Dropbox。但是与 Dropbox 不同之处在于 ownCloud 是开源的,任何人都可以检视其源代码并且可以为之贡献代码,这意味着他将文件的控制权交给了个人,敏感的文件任何人都无法查看,但于此同时他也将文件的安全交给了个人管理。

ownCloud 安装

安装之前确保有 sudo 权限,并且 ownCloud 需要

  • web 服务器,比如 nginx 或者 Apache
  • 数据库 MySQL
  • PHP

安装

apt install nginx mysql-server php7.0 php-bz2 php-curl php-gd php-imagick php-intl php-mbstring php-xml php-zip

更多的安装详细教程可以查看 DigitalOcean 的教程

Nextcloud

按照教程 使用 snap 安装。

或者手动安装

reference


2018-01-25 linux , cloud , drive , owncloud

使用 Celery Once 来防止Celery重复执行同一个任务

使用 Celery 的时候发现有的时候 Celery 会将同一个任务执行两遍,我遇到的情况是相同的任务在不同的worker中被分别执行,并且时间只相差几毫秒。这问题我一直以为是自己哪里处理的逻辑有问题,后来发现其他人也有类似的问题,然后基本上出问题的都是使用 Redis 作为 Broker 的,而我这边一方面不想将 Redis 替换掉,就只能在 task 执行的时候加分布式锁了。

不过在 Celery 的 issue 中搜索了一下,有人使用 Redis 实现了分布式锁,然后也有人使用了 Celery Once。 大致看了一下 Celery Once ,发现非常符合现在的情况,就用了下。

Celery Once 也是利用 Redis 加锁来实现,他的使用非常简单,参照 GitHub 的使用很快就能够用上。Celery Once 在 Task 类基础上实现了 QueueOnce 类,该类提供了任务去重的功能,所以在使用时,我们自己实现的方法需要将 QueueOnce 设置为 base

@task(base=QueueOnce, once={'graceful': True})

后面的 once 参数表示,在遇到重复方法时的处理方式,默认 graceful 为 False,那样 Celery 会抛出 AlreadyQueued 异常,手动设置为 True,则静默处理。

另外如果要手动设置任务的 key,可以指定 keys 参数

@celery.task(base=QueueOnce, once={'keys': ['a']})
def slow_add(a, b):
    sleep(30)
    return a + b

总得来说,分为几步

第一步,安装

pip install -U celery_once

第二步,增加配置

from celery import Celery
from celery_once import QueueOnce
from time import sleep

celery = Celery('tasks', broker='amqp://guest@localhost//')
celery.conf.ONCE = {
  'backend': 'celery_once.backends.Redis',
  'settings': {
    'url': 'redis://localhost:6379/0',
    'default_timeout': 60 * 60
  }
}

第三步,修改 delay 方法

example.delay(10)
# 修改为
result = example.apply_async(args=(10))

第四步,修改 task 参数

@celery.task(base=QueueOnce, once={'graceful': True, keys': ['a']})
def slow_add(a, b):
    sleep(30)
    return a + b

更多详细的参数可以参考GitHub,或者直接阅读源码。

reference


2018-01-24 celery , celery-once , redis , broker , queue , task , unique , python

又一款抓包分析软件 wireshark

Wireshark 是一款网络分析工具,也是学习网络协议的工具,原先介绍过的 Charlesmitmproxy 等HTTP抓包工具,都局限于 HTTP/HTTPS 请求,对于更底层的 TCP/IP,UDP 等协议就无能为力了。Wireshark 可以抓取网卡上的网络包,并实时展示,Wireshark 包括了过滤器,协议显示等等工具。

Wireshark 和其他工具的区别,比如 Charles,mitmproxy,Fiddler 等。Charles, mitmproxy,Fiddler是专门用来捕获HTTP,HTTPS请求的。Wireshark 能获取HTTP,也能获取HTTPS,但是不能解密HTTPS,所以Wireshark看不懂HTTPS中的内容。总结,如果是处理HTTP,HTTPS 还是用Charles, mitmproxy, Fiddler 等, 其他协议比如TCP,UDP,IP,ICMP 等就用Wireshark

安装

各大系统的安装文件:https://www.wireshark.org/download.html

Linux 下可以使用 PPA

sudo add-apt-repository ppa:wireshark-dev/stable && sudo apt-get update
sudo apt-get install wireshark

简单使用

打开 Wireshark 就可以看到很多网络硬件可以选择,任选其中一块网卡就能够抓取经过这个网卡的所有流量包。常见的设备名字,或者网卡名字有这样几个:

  • eth0 物理网卡,一般连接网线会获取到IP地址
  • eth1 第二块网卡
  • wlan0 是无线网卡,一般连接无线网会获取到IP地址
  • lo 设备虚拟端口,自身回环,一般指向 127.0.0.1

还有一些设备名字可以参考之前的文章。比如我笔记本使用无线网卡连接了WIFI,那么进入 Wireshark 之后选择 wlan0 设备,自动进入抓包,可以看到经过无线网卡的所有请求包。

wireshark-windows

Wireshark 的界面大致可以分成三个部分,最上面的部分为原始数据包预览,可以在该面板中看到抓取的包大致内容,包括序号,耗时,原始地址,目标地址,协议,长度,基本信息等等,分别使用不同的颜色标记了,这个颜色可以在设置 View -> Coloring Rules 中设置,根据不同的协议,或者自定义一些过滤规则,将关心的内容以不同的颜色标记出。

面板中间是封包详细信息 (Packet Details Pane),这个面板是最重要的,用来查看协议中的每一个字段。各行信息分别为

  • Frame: 物理层的数据帧概况
  • Ethernet II: 数据链路层以太网帧头部信息
  • Internet Protocol Version 4: 互联网层IP包头部信息
  • Transmission Control Protocol: 传输层T的数据段头部信息,此处是TCP
  • Hypertext Transfer Protocol: 应用层的信息,此处是HTTP协议

面板最下面一栏是数据包真正传输的内容,以十六进制和 ASCII 显示出来。

过滤器

https://wiki.wireshark.org/CaptureFilters

两种过滤器的目的是不同的。

捕捉过滤器(CaptureFilters):用于决定将什么样的信息记录在捕捉结果中。需要在开始捕捉前设置。捕捉过滤器是数据经过的第一层过滤器,它用于控制捕捉数据的数量,以避免产生过大的日志文件。

显示过滤器(DisplayFilters):在捕捉结果中进行详细查找。他们可以在得到捕捉结果后随意修改。显示过滤器是一种更为强大(复杂)的过滤器。它允许您在日志文件中迅速准确地找到所需要的记录。

两种过滤器使用的语法是完全不同的。

http://openmaniak.com/cn/wireshark_filters.php

三次握手

Wireshark实际分析下三次握手的过程

在wireshark中输入http过滤, 然后选中GET /tankxiao HTTP/1.1的那条记录,右键然后点击”Follow TCP Stream”,

这样做的目的是为了得到与浏览器打开网站相关的数据包

可以看到wireshark截获到了三次握手的三个数据包。第四个包才是HTTP的, 这说明HTTP的确是使用TCP建立连接的。

第一次握手数据包

客户端发送一个TCP,标志位为SYN,序列号为0, 代表客户端请求建立连接。

第二次握手的数据包

服务器发回确认包, 标志位为 SYN,ACK. 将确认序号(Acknowledgement Number)设置为客户的ISN加1以.即0+1=1

第三次握手的数据包

客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1


2018-01-16 wireshark , charles , mitmproxy , proxy

每天学习一个命令:sed

sed 是字符流编辑器,一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非使用重定向存储输出。sed 主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

awk 的典型示例是将数据转化为格式化报表。

行编辑器ed

awk 的起源追溯到sed和grep,再往前追溯就到了ed,最初的unix行编辑器。关于ed编辑器可以参考之前的[文章]

sed使用参数

sed [-neifr] [命令]

选项与参数:

  • -n :只有经过sed 特殊处理的那一行(或者命令)才会被列出来。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。
  • -e :直接在命令列模式上进行 sed 的命令编辑;
  • -f :从文件执行 sed 命令,-f filename 则可以运行 filename 内的 sed 命令;
  • -r :sed 默认支持正则表达式,使用 -r 开启扩展的正则表达式
  • -i :直接修改读取的文件内容,而不是输出到终端。

命令说明: [n1[,n2]]command

n1, n2 :在n1到n2行之间使用命令,举例来说,如果我的命令是需要在 10 到 20 行之间进行的,则 10,20[命令行为]

command:

a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~ c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行! d :删除 i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行); p :列印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~ s :替换,通常这个 s 的命令可以搭配正则 `1,20s/old/new/g`

实例

行删除及增加

以行为单位的新增/删除

将 /etc/passwd 的内容列出并且列印行号,同时,请将第 2~5 行删除!

nl /etc/passwd | sed '2,5d'
1 root:x:0:0:root:/root:/bin/bash
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

sed 的命令为 ‘2,5d’ , d 就是删除。sed 后面接的命令,请务必以 ‘’ 两个单引号括住喔!

只要删除第 2 行

nl /etc/passwd | sed '2d' 

要删除第 3 到最后一行

nl /etc/passwd | sed '3,$d' 

在第二行后(即是加在第三行)添加内容

nl /etc/passwd | sed '2a drink tea'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
drink tea
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin

那如果是要在第二行前

nl /etc/passwd | sed '2i drink tea' 

如果是要增加两行以上,在第二行后面加入两行字

nl /etc/passwd | sed '2a Drink tea or ......\
> drink beer ?'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
Drink tea or ......
drink beer ?
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin

以行为单位替换

以行为单位的替换与显示

将第2-5行的内容替换为自己的内容

nl /etc/passwd | sed '2,5c No 2-5 number'
1 root:x:0:0:root:/root:/bin/bash
No 2-5 number
6 sync:x:5:0:sync:/sbin:/bin/sync

显示特定行

仅列出 /etc/passwd 文件内的第 5-7 行

nl /etc/passwd | sed -n '5,7p'
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

这个 sed 的以行为单位的显示功能,就能够将某一个文件内的某些行号选择出来显示。

数据的搜寻并显示

搜索 /etc/passwd有root关键字的行

nl /etc/passwd | sed '/root/p'
1  root:x:0:0:root:/root:/bin/bash
1  root:x:0:0:root:/root:/bin/bash
2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
3  bin:x:2:2:bin:/bin:/bin/sh
4  sys:x:3:3:sys:/dev:/bin/sh
5  sync:x:4:65534:sync:/bin:/bin/sync

如果root找到,除了输出所有行,还会输出匹配行。

使用-n的时候将只打印包含正则的行。

nl /etc/passwd | sed -n '/root/p'
1  root:x:0:0:root:/root:/bin/bash

输出指定的行数 (输出2-5行的数据)

sed -n '2,5p' file

数据搜寻删除

删除/etc/passwd所有包含root的行,其他行输出

nl /etc/passwd | sed  '/root/d'
2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
3  bin:x:2:2:bin:/bin:/bin/sh

搜索执行命令

搜索/etc/passwd,找到root对应的行,执行后面花括号中的一组命令,每个命令之间用分号分隔,这里把bash替换为blueshell,再输出这行:

nl /etc/passwd | sed -n '/root/{s/bash/blueshell/;p}'
1  root:x:0:0:root:/root:/bin/blueshell

如果只替换/etc/passwd的第一个bash关键字为blueshell,就退出

nl /etc/passwd | sed -n '/bash/{s/bash/blueshell/;p;q}'    
1  root:x:0:0:root:/root:/bin/blueshell

最后的q是退出。

数据的搜寻并替换

sed ‘s/要被取代的字串/新的字串/g’

多点编辑

一条sed命令,删除/etc/passwd第三行到末尾的数据,并把bash替换为blueshell

nl /etc/passwd | sed -e '3,$d' -e 's/bash/blueshell/'
1  root:x:0:0:root:/root:/bin/blueshell
2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh

-e表示多点编辑,第一个编辑命令删除/etc/passwd第三行到末尾的数据,第二条命令搜索bash替换为blueshell。

直接修改文件内容

sed 可以启用 -i 选项直接修改文件的内容,不必使用管道命令或者重定向。

sed -i 's/\.$/\!/g' filename.txt         # 将文件每一行最后的. 替换为!
sed -i '$a # add to last' filename.txt   # 每一行后面($) 增加(a) 后面的内容

sed 可以直接修改文件内容,这样对于大文本,可以不需要使用 vim 打开在进行编辑,直接使用 sed 行读取编辑就能够实现行修改和替换的作用。

reference


2018-01-15 linux , command , sed , editor

使用 Chevereto 自建照片分享

Chevereto 是一款分享照片的程序,可以非常轻松得在自己的服务器上搭建照片分享程序,功能强大,外观精美。Chevereto 本身是收费使用的,一次性付费,终身使用,但是其开源版本可以免费使用。

安装

在安装之前请先检查需要的系统配置,至少保证 VPS 安装有

  • nginx 或者 Apache web server
  • MySQL
  • PHP

安装依赖

apt-get install nginx mysql-server php7.0 php7.0-common php7.0-curl php7.0-mysql php7.0-gd php7.0-xml php7.0-mbstring

从官网下载最新版本 压缩包

wget https://github.com/Chevereto/Chevereto-Free/archive/1.0.9.tar.gz

Nginx 配置

新建虚拟主机,修改域名 A 记录指向 VPS,然后配置对应的 vim /etc/nginx/sites-enabled/photo.einverne.info

由于 chevereto 默认提供基于 Apache 环境的伪静态规则,故 nginx 的配置是不能用的,需要自己添加规则

server {
    listen 80;
    listen [::]:80;

    root /var/www/photo.einverne.info/html;
    index index.php index.html index.htm;

    server_name server_domain_or_IP;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    }

    location ~ /\.ht {
        deny all;
    }
}

nginx 配置中还要注意一个 vim /etc/nginx/nginx.conf 配置中增加:

server { client_max_body_size 20M; //other lines… }

修改完重新加载 Nginx 配置 /etc/init.d/nginx reload

配置 MySQL

安装完 MySQL 之后需要为 Chevereto 新建一个数据库:

mysql -u root -p          # 登录mysql
create database photo;    # 创建 photo 数据库

修改 PHP 配置

默认的PHP上传大小在配置中略有不同,如果想要增大每张照片上传的大小,不仅上面 Nginx 中需要配置,同理 PHP 配置中也需要修改如下 vim /etc/php/7.0/fpm/php.ini

max_execution_time
max_input_time
memory_limit
post_max_size
upload_max_filesize

修改完重新加载PHP配置 /etc/init.d/php7.0-fpm reload

在做完这一系列配置之后,将之前下载的压缩包,在 /var/www/photo.einverne.info/html/ 目录下解压,然后使用域名访问。如果一切都没有问题,那么 Chevereto 会显示要求数据配置。要求填写:数据库名、数据库用户名、数据库用户密码,还有数据库表头。

这几项在前面安装时都已经完成,新建的数据库名,还有 MySQL 的用户名和密码,最后的数据表头名可以不变。然后下一步会填写管理员的一些信息,最后完成就好。

使用

设置中文,网上很多说需要修改密码,其实,在设置管理员面板中能够直接修改语言为中文。

修改图片存储路径:默认是在/images文件夹内,修改方法为在config.php修改define(‘DIR_IM’,’images/‘); ,这一步其实现在也能够在设置中直接修改。

Error

如果上传遇到问题,界面上显示

Server error (Internal server error)

一般的情况就是 Nginx 或者 PHP 的上传大小设置不对,上传的图片大于了 Nginx 或者 PHP 能够处理的大小,这是时候调整上传的大小就可以。调试方法如下;

没有给出任何信息,查看 nginx 错误日志

tailf /var/log/nginx/error.log

发现如下错误

2018/02/05 19:21:12 [error] 2693#2693: *8 client intended to send too large body: 1318270 bytes, client: 172.xxx.xxx.xxx, server: photo.einverne.info, request: "POST /json HTTP/1.1", host: "photo.einverne.info", referrer: "http://photo.einverne.info/dashboard/settings/system"

解决办法

vi /etc/nginx/nginx.conf

添加

client_max_body_size 20M;

然后 /etc/init.d/nginx reload 重新加载 nginx 服务配置。

reference


2018-01-15 php , photo , google , flickr

关于游戏的一些想法

最近国内又火了一个答题游戏,回答正确12题平分多少多少万奖金。先不说他们都抄袭 HQ Trivia 这款App,但我这两天一直在想一个问题,为什么这一类的应用能火起来,难道就是因为最后平分100万,200万,现金带来的刺激吗?我想答案一定不是的,除了最开始的玩家或许还能分到几十块钱,在入场玩家越来越多的情况下,每个人瓜分到的奖励一定是越来越少的。那到底是什么能让一个应用一夜之间火到大江南北?

很多人之前说过是主播带火了”吃鸡”游戏,这一点我是认同的,但这应该只是能火的其中一个因素,也就是在正常的宣传情况下,带来的口碑一层层的堆叠,能少能有游戏能够做好老少咸宜,并且也能够在不同人群中拥有大多数的好评。所以游戏本身的可玩性,娱乐性是应该需要得到保证的,玩家能够在游戏中得到乐趣,并且通过游戏提供的场景能够扩展出来不同的玩法,而这些玩法又足够好玩。这里就要说道在吃鸡游戏前面1分钟左右的等待时间,有个叫平底锅的主播用来喊麦,还有人在游戏中组队交友。

然后这就说道了能够火,并且能够让绝大部分人能够接受的第二大原因,就是这个游戏需要有足够的社交属性,不管是陌生人社交,还是熟悉的好友的社交。在足够话题性的同时,能够让三五好友聚到一起开黑,说语音,开黑。这是这个时代的需要,在吃鸡火起来之前的“王者荣耀”我想也是因为能够拉上三五亲朋好友组个队,带个节奏,尤其在过年短短的几天时间内,好多亲朋好友从五湖四海聚到一家,每个人从事的职业和活动没有太多交集,唯有一局王者荣耀才能让气氛足够的活跃起来。所以这里也要提到另一个能够流行的原因了,也就是让绝大部分人获取的方法足够简单。

Steam 上的绝地求生或许正是因为他对设备要求过高,而过滤掉很大一部分玩家,才有了网易在国内能够通过荒野行动,快速的聚集了近2亿的玩家,让足够多的人,只用下载一个App,不到几分钟的时间就上手玩,才有能力让这个游戏走的足够远。

再其次,如果一个游戏想要不被这个时代淘汰,不断的进化,并且有能力让超越游戏的内容,比如游戏竞技,或者游戏的Cosplay出现,才能够立于不败之地,回想以前玩过的War 3,孵化出了Dota,Dota2,还有 LOL,可是暴雪自己却没有赶上,也略遗憾。而其实这一点也很难做到,无数的手机益智类游戏,火玩一波,再来一波,愤怒的小鸟,植物大战僵尸都已经存在回忆中了。

说道这里再回头来看答题分奖金这个应用,他能火正是也正是因为他足够有话题性,也足够有社交属性,失散多年的好友,同学,亲戚可能都被拉来一起玩,并且我从来没有看到过玩一个答题游戏,能够在短短几天时间内,开黑群,语音发答案,自动搜索答案助手等等延伸品出现。再这个游戏也足够被绝大多数人接受,官方打的宣传语就是,答题不仅能赢钱还能学习知识。这一些因素促成了这个游戏必定成为2018年开年的热点,但其实呢?我也再想另外一个问题,这个模式能够持续多长时间,爆红之后带来的用户如何沉淀。

首先这个应用能够吸引足够的目标,并且能吸引一定的广告主,这是一定的,但如果到足够需要花半个小时时间来赢取2 、 3 元的奖励,到最后还能剩下多少衷心的用户。我觉得如果使用这样的方式来吸引用户来吸取内容,未免不是一个极好的宣传方式,但如果频繁开场次,绝大多数的用户会厌倦了每天9点定时打开手机等待一串废话。

2018年的农历新年马上就要到来了,可想而知这样一个应用必定还有爆发的一次机会,但以后如何走,就只能拭目以待了。


2018-01-14 思考 , app , game , hq

Android 电视盒子可用的应用备份

用盒子也已经很多年了,几年来家里,自己用,也积累了一些常用的应用。这两天又拿到了 T1 盒子,又才想起来整理这样一份单子,这样不用每一次都一遍一遍的尝试了。记得以前 VST ,泰捷视频都还很不错的时候,再后来广电发了禁令,再后来这片市场混乱发展,各家大型网站优酷,爱奇艺又不敢公开大搞,却又在背后偷偷摸摸。再到现在几乎被什么芒果,CIBN垄断,内容没什么可看,却什么都要收费。我始终抱有一个观点,如果电视盒子这一块开放发展,国内的厂家完全能够占领全世界的盒子市场,好几年前用的 Android 盒子就已经能够满足我的大部分需求,并且应用设计也早 Google 自己推出 Android TV 盒子以及规范 Android TV 应用好多年。可惜这一块市场被一道禁令打到了地下。

Android 盒子安装应用的方法,大概可以分为这几个:

  • 局域网拷贝 APK
  • 用 U 盘
  • 沙发管家,或者当贝市场中安装
  • 局域网 adb 安装

当然对于一个新的设备,通过 adb 安装一个应用市场,然后通过应用市场下载其他应用是最简单的方式。

市场

Apple 有 App Store, Google 有 Play Store,电视盒子直到现在依然还在乱斗:

  • 奇珀市场 http://down.7po.com/
  • 沙发管家 http://www.shafa.com/
  • 当贝市场 智能电视应用市场

地址就不都给了,Google 搜一下很快。

直播

电视直播的应用,以前用 VST 和 泰捷还行,不过现在已经废了,然后现在 HDP 做的也还不错。

2018年3月3号更新,发现了一款叫做 超级直播 的应用,非常好用。使用超级直播的时候,在播放页面点击设置,然后选择 “二维码扫一扫开启更多功能”,并在二维码显示之后,连续多次点击出现的二维码多次,即可解锁隐藏功能。隐藏功能开启6000以后的频道,包括可以查看多个台湾、香港的频道,频道范围在 6xxx 可能上下有些偏差,65xx 开始会有抢先观看的电影。

一下排名按照易用程度:

  • 超级直播
  • HDP直播
  • 直播狗
  • 小薇直播
  • 电视家3.0

放一张截图

斐讯t1_直播

网络视频

网络视频是我用的最多的了,我本人用 哔哩哔哩 最多

  • bilibili
  • 银河奇异果
  • 云视听极光
  • CIBN 高清影视
  • CIBN 聚精彩
  • VST
  • 泰捷

本地视频

盒子带 samba ,能读局域网内 samba 共享的视频,那就需要一个本地播放器,最好支持的解码格式越多越好,在 Android 手机上我买过 MX Player Pro,不过免费的 KMPlayer 也不错。

  • ES 文件管理器
  • 小白文件管理器
  • MX Player Pro

系统工具

说到乐播投屏这个应用,还是我去实体店,然后有一个店员向我展示一个只有魔方大小的投影仪时,用的应用,将手机的屏幕投影到投影仪上,我就记住了这个投屏应用,回来发现,iOS 投屏还是不错的。

  • 乐播投屏
  • 悟空遥控

在 Android TV 上使用 YouTube

今天突然想到,我平时看的最多的 YouTube ,是有电视版的啊,最近又把路由器更新了一下,局域网使用 YouTube 完全没问题啊,然后就下载了几个 YouTube for TV 的应用,发现只有下面这一个不需要依赖 Play Service,然后,在侧边栏设置中,有一个关联码,和手机关联,然后就可以非常轻松的将手机上的 YouTube 视频投送到电视盒子上,然后再到投影仪上。太舒服了。

YouTube for TV

版本:1.12.10 发布时间:2018-01-12

下载地址: https://apkpure.com/youtube-tv-watch-record-live-tv/com.google.android.apps.youtube.unplugged


2018-01-13 android , tv , adb , packages

okhttp 使用

OkHttp是一个非常高效的HTTP客户端,默认情况下:

  • 支持HTTP/2,允许对同一主机的请求共用一个套接字。
  • 如果HTTP/2 不可用,连接池会减少请求延迟。
  • 透明的GZIP可以减少下载流量。
  • 响应的缓存避免了重复的网络请求。

同步 GET 方法

阻塞请求

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    Headers responseHeaders = response.headers();
    for (int i = 0; i < responseHeaders.size(); i++) {
      System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
    }

    System.out.println(response.body().string());
}

异步 GET 请求

响应可读时回调 Callback 接口,读取响应时阻塞当前线程

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Request request, Throwable throwable) {
        throwable.printStackTrace();
      }

      @Override public void onResponse(Response response) throws IOException {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

        Headers responseHeaders = response.headers();
        for (int i = 0; i < responseHeaders.size(); i++) {
          System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }

        System.out.println(response.body().string());
      }
    });
}

POST 提交信息

public static final MediaType MEDIA_TYPE_MARKDOWN
  = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    String postBody = ""
        + "Releases\n"
        + "--------\n"
        + "\n"
        + " * _1.0_ May 6, 2013\n"
        + " * _1.1_ June 15, 2013\n"
        + " * _1.2_ August 11, 2013\n";

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

流方式提交请求体

流的方式提交请求体

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + " × " + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

POST 提交文件

POST 方式提交文件

public static final MediaType MEDIA_TYPE_MARKDOWN
  = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    File file = new File("README.md");

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

提交表单

public void run() throws Exception {
    RequestBody formBody = new FormEncodingBuilder()
        .add("search", "Jurassic Park")
        .build();
    Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

分块POST提交

private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBuilder()
        .type(MultipartBuilder.FORM)
        .addPart(
            Headers.of("Content-Disposition", "form-data; name=\"title\""),
            RequestBody.create(null, "Square Logo"))
        .addPart(
            Headers.of("Content-Disposition", "form-data; name=\"image\""),
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();

    Request request = new Request.Builder()
        .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

2018-01-12 okhttp

电子书

Google+

最近文章

  • 使用 alembic 迁移数据库结构 Alembic 是一个处理数据库更改的工具,它利用 SQLAlchemy 来实现形成迁移。 因为 SQLAlchemy 只会在我们使用时根据 metadata create_all 方法来创建缺少的表 ,它不会根据我们对代码的修改而更新数据库表中的列。它也不会自动帮助我们删除表。 Alembic 提供了一种更新 / 删除表,更改列名和添加新约束的方法。因为 Alembic 使用 SQLAlchemy 执行迁移,它们可用于各种后端数据库。
  • 每天学习一个命令:iotop 查看 Linux 下每个进程 IO 占用 iotop 是一个用来监控磁盘 I/O 的类似 top 的工具,Linux 下 IO 统计工具,比如 iostat, nmon 等只能统计到每个设备的读写情况,如果想要知道哪一个进程占用比较高的 IO 就要使用 iotop。 iotop 使用 Python 语言编写,要求 Python >= 2.5,Linux Kernel >= 2.6.20.
  • 修正关于 HTTP Header 的错误认识 HTTP 请求的 Header 是不区分大小写的!,一直以为 HTTP 请求的请求头是有区分大小的,知道今天调试发现 Spring 将 header 全部处理成小写,然后有人提了 Bug 58464 然后看到 Stackoverflow 上面有人回答。
  • 解决 failed to create bus connection no such file or directory 错误 今天在修改 hostname 使用 sudo hostnamectl set-hostname ds 命令时遇到问题:
  • Vim 的颜色主题 Retro groove color scheme for Vim