MyBatis 中 insert 和 insertSeletive 区别

MyBatis generator 自动生成的 mapper 文件中有两个 insert 方法,insertinsertSelective,这两个方法都可以插入一条数据

对于 insert:

int insert(T record);

对于 insertSelective

int insertSelective(T record);

insertSelective 对应的 SQL 语句加入了 NULL 检验,只会插入数据不为 null 的字段,而 insert 会插入所有字段,会插入 null 数据。

也就意味着如果定义了表 default 字段,使用 insert 还是会插入 null 而忽略 default

insertSelective 当字段为 null 时会用 default 自动填充。

扩展

同理,更新 update 的操作也有对应的两个方法

  • updateByPrimaryKey 对你注入的字段全部更新(不判断是否为 Null)
  • updateByPrimaryKeySelective 会对字段进行判断再更新(如果为 Null 就忽略更新)

和 insert , insertSelective 类似,带有 selective 的方法会检查对象的值是否 null,如果为 null 则不会更新。


2018-01-30 mybatis , mysql , orm , java

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。

{
  "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 来启动客户端,如果看到日志正常表示已经连接成功。

Linux GUI

Linux 客户端可参考 https://github.com/jiangxufeng/v2rayL

安装方法:

bash <(curl -s -L http://dl.thinker.ink/install.sh)

更新及卸载方法见 GitHub 页面。

2021 年更新

Linux 下推荐自己使用 Clash 命令行,或者 Clash For Windows 的 Linux 版本。

Windows/macOS

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

Windows 下 V2RayW, MacOS 下 V2RayX。

macOS

macOS 下推荐 ClashX(ClashX Pro),或者 Clash For Windows 的 macOS 版本,使用体验非常不错。

Android

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

Play Store 链接 备用 GitHub 地址

2020 年更新,推荐一款 BifrostV 的应用,Play Store 有下载。

2021 年,Android 上推荐 Clash 这个应用。

iOS

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

推荐

也不废话了直接放链接:

  • Board 具体教程可以注册后参考其内部说明。

在花费了一些精力和防火墙躲猫猫后,我也没有太多的时间再投入到自己找服务器,自己搭建服务,遇到单点故障后也没有再继续,所以后面渐渐的用了认识人一起共建的 SS 和 SSR,而最近用了一段时间的 无界 感觉日常使用也没啥问题,所以也在此推广一下。无界不仅可以使用如上提到的所有兼容 v2ray 的 客户端,也在他们后台提供了定制版本的客户端,方便小白使用。

如果你也熟悉了 Clash 在 Android/iOS,macOS/Windows/Linux 上的使用,也欢迎使用这个服务,一键订阅使用 Board

另外也写了一篇文章来告诉你如何对对一家代理服务进行测速。你可以根据自身的情况合理的选择。

如果不折腾 v2ray 那么也推荐 Cisco 的解决方案,香港的 VPN,可以访问其官网查看具体使用,或者直接访问注册页面

常见的代理软件

  • iOS: Surge iOS, Quantumult X, Shadowrocket, Loon
  • Android: Clash for Android, Surfboard
  • macOS: Surge Mac, ClashX, Clash for Windows
  • Windows: Clash for Windows, Clash.NET
  • Linux: Clash

iOS 客户端 全区可下载

iOS 客户端 (中区无法下载)

macOS 客户端

Windows 客户端

Android 客户端

Linux 客户端

路由器

TV

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

pipenv 使用

pipenv 是目前官方 推荐使用的包管理工具。能够为项目创建和管理虚拟环境,从 Pipfile 文件添加或删除安装的包,Pipfile.lock 来锁定安装包的版本和依赖信息。

  • 不用再维护 requirements.txt, 使用 PipfilePipfile.lock 来代替
  • 在安装了 pyenv 的条件下,可以自动安装需要的 Python 版本

这里就不得不提到 pyenv 了,pyenv 能用来管理不同的 Python 版本,结合 pyenv-virtualenv 也能够快速创建虚拟环境,不过这个 pipenv 提供了另外一种思路。

安装

pip install pipenv

基本使用

  • 虚拟环境如果不存在的话,会自动创建
  • --three / --two Use Python 3/2 when creating virtualenv.

常用命令

指定 Python 版本信息

在创建虚拟环境时,我们可以使用 python 版本信息

pipenv --python 3
pipenv --python 3.6.1
pipenv --python 2.7.13

pipenv 会自动扫描系统寻找合适的版本信息,如果找不到的话,同时又安装了 pyenv, 它会自动调用 pyenv 下载对应的版本的 python

安装包

类似 pip

pipenv install requests
pipenv install requests==2.19.1
pipenv install --dev requests       # 安装开发环境依赖

如果 install 后面没有任何 package 会自动安装所有,第一次安装包会自动生成 lock 文件

兼容 requirements.txt

pipenv install -r path/to/requirements.txt

同样可以使用 PipfilePipfile.lock 文件来生成 requirements.txt

pipenv lock -r
pipenv lock -r -d       # 生成 dev requirements

所以一个基本流程就是,对于 pipenv 管理的项目,使用 pipenv lock 来冻结管理,在分享给别人之后使用 pipenv install 来安装依赖。

指定安装包源

直接修改 Pipfile 文件

[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[[source]]
url = "http://pypi.home.kennethreitz.org/simple"
verify_ssl = false
name = "home"

[dev-packages]

[packages]
requests = {version="*", index="home"}
maya = {version="*", index="pypi"}
records = "*"

激活和退出当前虚拟环境

pipenv shell
exit

图形显示包依赖关系

pipenv graph
requests==2.19.1
  - certifi [required: >=2017.4.17, installed: 2018.8.13]
  - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
  - idna [required: >=2.5,<2.8, installed: 2.7]
  - urllib3 [required: >=1.21.1,<1.24, installed: 1.23]

删除所有的安装包

pipenv uninstall --all

检查代码

pipenv 默认集成了 flake8

pipenv check --style hello.py

区别

关于 pipenv 和 pyenv 等等其他的区别,可以看这个回答 ,如果想要在 Intellij 中使用 pipenv ,2018.2 更新的版本中,也已经支持 了。

pipenv 也使用 pyenv 来做 Python 的版本管理,所以基本上,分工明确了,pyenv 用来区分 Python 版本,pipenv 用来管理包依赖。

reference


2018-01-23 python , virtualenv , pyenv , pipenv

又一款抓包分析软件 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

安装过程中选择 YES,让普通用户也能够抓包。

如果启动之后遇到

couldn't run /usr/bin/dumpcap in child process: Permission Denied.

这样的问题,

sudo dpkg-reconfigure wireshark-common

选择 “YES”, 将当前用户添加到 group

sudo adduser $USER wireshark

然后再登出登入 1

简单使用

打开 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

  1. https://askubuntu.com/a/778170/407870 


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

使用 Chevereto 自建照片分享

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

目前 Chevereto 的价格是 $39,可以免费升级到 V4 版本。作者已经发生声明,在 2021 年底将终止 Free 版本的维护。

Docker 安装

推荐使用 Docker 安装 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 , chevereto , self-hosted

关于游戏的一些想法

最近国内又火了一个答题游戏,回答正确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,哔哩哔哩 tv 版本有两个,其中一个叫做云视听小电视,这个是无法观看 UP 主上传的视频的,你需要一个超级老的版本才可以,后文自行寻找
  • 人人影视 TV 版
  • 云视听小电视
  • 银河奇异果
  • 云视听极光
  • CIBN 高清影视
  • CIBN 聚精彩
  • VST
  • 泰捷
  • 南瓜电影
  • 蜜蜂视频

本地视频

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

  • Kodi,不得不请出这个大杀器了,All in one 既能作为本地播放器,也能使用 Samba,NFS 等等协议播放局域网中的视频,同样能够作为 DLNA 服务端作为投屏工具
  • ES 文件管理器
  • 小白文件管理器 v1.2 版本
  • MX Player Pro

系统工具

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

  • 当贝桌面
  • 乐播投屏,老版本无广告,非常清爽
  • 悟空遥控

其他

其他体验还不错的应用:

  • Keep TV
  • 小鸡模拟器 TV 版

在 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 , apk , applications

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

电子书

最近文章

  • PulsedMedia 套餐区别 之前在 PlusedMedia 12 周年活动的时候因为可以免费试用一个月的 V1000 套餐,所以就下单了一个 12th Anniversary V1000 Raffle,套餐的详情如下:
  • 逃离豆瓣之豆瓣代替服务 这篇笔记新建的时间是 2020 年 12 月 29 号,想来从那个时间点开始我就一直想着怎么离开豆瓣了。过去一年时间里面陆陆续续也发现了不少不错的网站,甚至有一些比豆瓣都要好用。这里就再整理一下。
  • Proxmox 扩展 VM 虚拟机磁盘容量 之前在 Proxmox 上给 Ubuntu 划分了 64GB 的空间,运行一段时间之后磁盘空间剩余不多,就抽时间扩展一下。本文就记录一下给 Proxmox VE 的虚拟机扩展的过程。其实之前的文章里面也略微提到过一些,但是没有完整记录。
  • 现代性与大屠杀读书笔记 属于 [[20220417-21 天计划]] 的第3本书。
  • 每天学习一个命令:growpart 扩容分区 前端时间给 Proxmox VE 下的虚拟机扩容的时候留意到了这个 growpart 命令,专门用来给分区进行扩容的命令。