Model的定义优先还是DB的schema优先?

背景

最近负责把项目的代码从PHP5.6升级到PHP7.3。
层出不穷的问题请见前几篇博客。
当然之前遇到的大多数是PHP版本升级后的兼容问题,
而这次遇到了Yii重构导致现行代码出错的问题。

找不到的 ActiveModel Property

PHP5.6 => PHP7.3, yii1.1.15 => Yii1.1.21。
升级之后在Error Log观察到了类似Property "MyselfDefinedModel.id" is not defined的例外。

显然翻查DB的schema,我们在对应的MyselfDefinedModel的Table中并没有设定id作为Primary Key。
甚至没有id这个column,那么这个Id是怎么来的呢?

经过一番寻找,
在更新后的Yii1.1.21中我们发现了一些微妙的变化。

1
2
3
4
5
6
7
8
9
10
11
12
// yii 1.1.21
// framework/yiilite.php
if(($modelPk=$model->primaryKey())!==null || $table->primaryKey===null){
$table->primaryKey=$modelPk;
...
}

// yii 1.1.15
if($table->primaryKey===null) {
$table->primaryKey=$model->primaryKey();
...
}

可以发现,在新版本的Yii中,无论$table->primaryKey是否为null,我们始终会用$model->primaryKey()的结果去赋值给$table->primaryKey

然而我们自定义的MyselfDefinedModel 继承自BaseModel, 而我们在BaseModel中定义了public function primaryKey() {return 'id'}的方法。

于是,没有做任何override的MyselfDefinedModel自动继承了primaryKey()的方法,当Acitverecord去寻找期PrimaryKey的时候,新版本的Yii便让这个值成为了PrimaryKey。

1
2
3
4
5
6
7
8
9
10
class BaseModel extend CActiveRecord
{
...
public function primaryKey() {return 'id'};
...
}

class MyselfDefinedModel extend BaseModel
{
}

解决方法

不更改Yii自身的情况下,我们可以在MyselfDefinedModel中overridepublic function primaryKey() 然后返回正确的primary Key。

1
2
3
4
5
6
class MyselfDefinedModel extend BaseModel
{
...
public function primaryKey() {return 'other_id'};
...
}

毕竟,忘记在子类中override PrimaryKey本身就存在问题。
但是,一旦修改起来,PrimaryKey不是id的表出奇的多,那不如去把Yii改了?

如此产生了以下的新问题,Yii的关于获取PrimaryKey的变化,会不会是一次失败的重构?

后话

于是屁颠屁颠跑到了github的yii1.1 repository,提出了issue。表示自己怀疑这段重构有问题。

并表示具体做法是把之前某个commit的重构给revert。从而让DB schema 优先于model中的设定。

很幸运的是这个问题不过10分钟就收到了回复。有人站起来指出model配置优先于DB schema的必要性。

简而言之是model中的public function primaryKey() 方法是唯一可以重新指定primaryKey的地方,所以必须要优先于DB schema。

嗯但是DB schema 放着好好的不用,到底有哪些情况下我们需要重新指定我们的PrimaryKey呢?

于是,当我们需要对PrimaryKey加一些校验的时候,我们希望动态生成这些校验值附加到我们自定义的PrimaryKey上,然后通过校验的PrimaryKey再去访问数据。(具体联想到GlobalId也可以用这种方法实现)

问题Close。以上。

PHP7.3关于session_id()的变化

PHP7.3 以前

首先看下面一段code。

1
2
3
session_save_path("/your_session_path");
session_start();
echo(session_id());

直观上讲,我们定义session的存放路径。开始一个session,然后去获取这个session的id。

如果是一个新的session,我们将获取到新生成的session_id, 作为PHPSESSID存放在Cookie之中。

如果传来的Cookie中已经存在PHPSESSID,我们期待输出其中的内容。

就算,

我指定的路径根本就没有全线放置Session。

PHP7.3

到了PHP7.3情况就发生了变化。

如果,我们对my_session_path有执行读写的权限,那一切OK,该生成新的就生成新的,改读取PHPSESSID就读取。

但是如果我们没有my_session_path的权限,那就麻烦大了。我们将获取不到session_id,获取的结果为空字符串。

后记

这个问题让我发现了长久以来其实后台的某个工具一直没有读写session path的权限。

Cannot Change Session when sesion is active

背景

最近把准备把项目直接从PHP5.6 升级到7.3。
升级过程中遇到个比较棘手的问题,那就是出现了大量Cannot change session name when session is active的Warnning。

剖析

我们通常使用session_name($name)来改变session的名称(默认PHPSESSIONID)。而这个warning出现在我们在session_start()之后调用session_name

1
2
3
4
5
6
7
8
9
10
11
12
<?php

session_start();
session_name("hoge");

//echo session_name()."<br/>";
//echo session_id()."<br/>";

session_regenerate_id(true);

echo session_name()."<br/>";
echo session_id()."<br/>";

上面这段,如果使用PHP5.6,则会产生两个cookie。一个是PHPSESSIONID,另一个则是我们变更之后的hoge。echo输出的内容为hoge

1
2
d78ca52754275ef448ea8bff2ef4c051
hoge

环境切换到PHP7.3,我们发现出现了Warnning,然后查看Cookie,我们只看到了PHPSESSIONID

1
2
3
Warning: session_name(): Cannot change session name when session is active in /Users/ning.a.li/workplace/operations/tmp/test.php on line 4
PHPSESSID
tqa2u87v89ul1a7k7srrn8fhf5

原因我知道了,那么为什么会出现这种问题呢?

原来,现有的code在我们开始load framework之前有对session和cookie进行操作,而进入framework之后,由于framework并不知道我们已经建立了session,framework尝试根据config建立session的时候就会产生Warning。

那么如何解决这个warnning呢?

首先我们得考虑是否真正有必要变更session_name

通常,当我们有两个web app运行在同一个domain下面的时候,我们可以通过设置不同的session_name来区分两个不同app的用户。

但是这种情况往往会造成不必要的麻烦。

就目前项目的code来说,修正的方法很简单,我们在framework中对session_name相关的调用添加@即可。即类似@session_name(my_session)

后记

虽然网上多数人抱怨这个Warning设计的莫名其妙,多数情况下,相比还是现有的Code的设计更加不合理吧。

PHP

什么时候需要ProxyPreserveHost ?

背景

在apache httpd 中设置Reverse Proxy的时候通常我们有如下配置

1
2
3
# https://myfrontend/images => https://mybackend/images
ProxyPass /images https://mybackend/images
ProxyPassReverse /images https://mybackend/images

ProxyPass不多说了,ProxyPassReverse是把response中的Location, Content-Location, URI等header由mybackend替换成myfrontend
今天配置服务器的时候,发现如果以上配置情况下,如果还有ProxyPreserveHost on配置,则我们的https://myfrontend/images的response变成了403。这是为什么呢?

ProxyPreserveHost 的作用

ProxyPreserveHost 的作用如文档上写的,很简单,当我们设置ProxyPass /images https://mybackend/images的时候,通过myfrontend服务器发往mybackend的request的Host Header应该是Host: mybackend
而如果ProxyPreserveHost on,则我们的request的header则回变成Host: myfrontend

文档上说,我们大多数情况下不需要这个配置。除非在mybackend上配置了大量的vhost,我们需要通过识别原来request的Host header来判断request交给哪个vhost处理。

这种说法略抽象,让我们在稍微深入考虑一下。

当我们的mybackend的服务器上,配置有myfrontend的vhost的时候,使用ProxyPreserveHost on就可以把我们的request交给myfrontend的vhost来处理。

这个时候,这个配置在mybackend上的vhost myfrontend是无法外部的DNS来resolve的。外部的DNS把myfrontend已经resolve到了我们的Reverse Proxy服务器。

所以当ProxyPreserveHost on 的时候就会有如下流程。

1
https://myfrontend/images(user request) => myfrontend(Reverse proxy to mybackend server with Host: myfrontend ) => mybackend(match with vhost setting as myfrontend)

因为在大多数情况下,我们通常不会在mybackend的服务器上配置和Reverse Proxy一模一样的vhost,所以就如文档中所说,我们大多数情况下不需要此配置。

后话

在web的世界里,当我们的目标服务器上配置有DNS无法resolve的vhost的时候,通常我们可以用ip+port,设置Host header为该无法resolve的vhost的方法来访问。
这个容易忽视的web的实质面貌,在本次讨论的问题中体现的淋漓尽致。

给activeadmin的filter添加自定义搜索条件

背景

默认情况下,如果我们定义一个activeadmin的filter。

1
filter :school_name, as: :string, label: "学校名称"

我们就可以按照school_name进行搜索。相应的SQL的mapping就会变成

1
2
3
...
where school_name like "%school_name%"
...

然而,很多时候我们想输入多个空格隔开的关键词,比如学校名清华大学 北京大学这种来匹配任何学校名包含这些学校的记录。

那么这个filter如何自定义呢?

自定义filter的搜索条件

首先在model中新建一个自定义的scope。

1
2
3
4
5
6
7
8
9
10
scope :school_name_includes, ->(search) {
current_scope = self
query = ""
search.split.uniq.each do |word|
query += "school_name LIKE '%#{word}%' or "
end
query=query[0..-4]
current_scope = current_scope.where(query)
current_scope
}

search是我们获取到的输入的搜索条件。比如上面的清华 北大
我们首先将单词分开,之后逐个加入LIKE搜索条件,并用or隔开。
去掉末尾多余的or之后,加入当前的scope条件中去。
这样,当我们输入清华 北大的时候,对应的SQL就会变成

1
where school_naame like "%清华%" or school_name like "%北大%"

最后,为了使得activeadmin能找到这个filter,我们需要定义。

1
2
3
def self.ransackable_scopes(auth_object = nil)
[ :school_name_includes]
end

以上,我们就可以在DSL中使用

1
filter :school_name_includes, as: :string, label: "学校名称"

来匹配符合空格隔开的多个学校中的任意一个的记录了。

后话

一开始总想着往current_scope上加or条件,后来发现,其实query里面,我们自己嵌入就行了。
总而言之,了解自己能够更改的范围以及在这个范围内思考问题很是重要。问题卡顿的时候,可以再三阅读文档,了解接口的特征,反复思考需求。

给既存的WatchOS Extension添加Complication

前言

写下这个标题的时候我也是比较懵逼的,Complication到底中文叫什么的?日文叫什么呢?我也不知道。
众所周知,apple Watch的表盘上有着显示各种信息的小模块,这些模块的统称就是Complication。
从0创建Compliation并不难,在创建WatchOS Extension的时候,直接勾选同时创建Complication,然后勾选需要支持的Complication的模块类型就行了。
那么,问题是,当我们写好了整个Extenstion,才想到需要添加一个Complication作为表盘上的快捷方式,那该如何是好呢?

添加方法

首先在WatchOS的Extension下面创建一个新的Class,实现NSObject, CLKComplicationDataSource两个协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import UIKit
import ClockKit

class ComplicationController: NSObject, CLKComplicationDataSource {

func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
handler([.forward, .backward])
}
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
//todo
}
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
//todo
}
}

接下来,我们要在WatchOS Extension中添加使用Complication的申明和配置。

1
2
3
4
5
6
<key>CLKComplicationPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ComplicationController</string>
<key>CLKComplicationSupportedFamilies</key>
<array>
<string>CLKComplicationFamilyGraphicCircular</string>
</array>

这里的CLKComplicationFamilyGraphicCircular是可以选择的Complication模块的一种。WatchOS提供了十几种可供选择的模块来实现。
具体查看文档即可。

最后就是要实现CLKComplicationDataSource协议的方法。

1
2
3
4
5
6
7
8
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
let template = CLKComplicationTemplateGraphicCircularImage()
let image = UIImage(named: "modular_image")

template.imageProvider = CLKFullColorImageProvider(fullColorImage: image!)

handler(template)
}

这个方法实现的是,当我们自定义表盘,选择到这个Extension的时候,显示的内容。这里是一张名为modular_image的图片。

1
2
3
4
5
6
7
8
9
10
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
let template = CLKComplicationTemplateGraphicCircularImage()
let image = UIImage(named: "modular_image")

template.imageProvider = CLKFullColorImageProvider(fullColorImage: image!)
let entry = CLKComplicationTimelineEntry.init(date: Date(), complicationTemplate: template)

handler(entry)

}

同样,这个方法实现的是我们抬起表,手表亮起来的时候,在表盘上该Extension所需要显示的内容,这里我们不具体实现根据时刻变化显示不同内容,仅仅作为一个快捷方式,我们可是和上面一样,显示一张图片。

后记

上面的总结是在编写Gopro的手表遥控器的时候遇到的一些问题。
作为一个非IOS的程序员,通过查阅资料翻阅文档来解决自己的疑惑,再加之Xcode强大的补完和提示功能,其实使用Swift编写IOS程序也并不是什么难事。

Apache httpd ProxyPass 和 ProxyRemote

当我们的client无法直接access backend_server的时候,我们通常在webserver端有如下配置。

1
2
client->backend_server NG
client->webserver->backend_server OK
1
2
ProxyPass /example https://backend_server/example
ProxyPassReverse /example https://backend_server/example

这样,当client访问https://webserver/example的时候,我们获取到的其实就是backend_server/example的内容。

需要注意的是,从client的角度是无从得知这个内部的Forward的。因此用户访问https://webserver/example的时候,总是返回通常的200 response。

这点和用Redirect的Module是不一样的。

但是,如果当我们的webserver也无法直接访问backend_server的时候改怎么办呢?

答案是使用ProxyRemote

1
2
3
ProxyRemote https://backend_server/ https://proxy_server:port
ProxyPass /example https://backend_server/example
ProxyPassReverse /example https://backend_server/example

我们在设置ProxyPass之前事先指定哪些Forward的Request需要通过Proxy获取便可。

使用API调用TPLINK智能插座

最近智能电器的概念很火,但是很多都是傻瓜式的,通过手机应用实现开关,留给自己DIY的空间并不多。

为了实现更多便利的DIY,我们有必要实现通过发送API请求来实现智能电器的控制。

为了实现通过调用API来控制插座通电这个功能,我找到了TPPINK HS105 这个东西。

默认的操作方式是通过手机应用,将智能插座连上自己加的WIFI,注册TPLINK CLOUND账号之后,你的智能插座就可以通过TPLINK CLOUD远程控制了。

具体原理是,你的手机应用发送请求给TPLINK CLOUD,TPLINK CLOUD找到你注册在上面的智能插座,并发送通断命令,这个命令通过你家WIFI传递到你的智能插座上,智能插座作出相应通断。

而我们想要实现的功能是,在和智能插座相连的同一内网中,通过直接向智能插座的HOST发送API来实现智能插座的控制。

与上面的傻瓜方式不同,我的PC(或者树莓派)向智能插座发起控制请求,这个请求通过WIFI的内网直接到达智能插座,智能插座作出相应通断。

为了解决上述DIY需求,我们需要两个东西,一个是这个请求的具体格式,另一个是智能插座在我们内网上的IP地址。

发现智能插座的内网IP

发现内网IP方法有很多种,可以上WIFI的控制页面去查所有链接的设备。可惜我家的WIFI似乎并不提供这一信息,于是,暴力一点,我们向左右内网子网地址广播PING包来侦测链接的设备。

我的子网掩码是24位,所以广播地址是192.168.0.255

1
2
3
4
5
6
7
8
9
10
11
➜  tplink ping 192.168.0.255
PING 192.168.0.255 (192.168.0.255): 56 data bytes
64 bytes from 192.168.0.3: icmp_seq=0 ttl=64 time=0.082 ms
64 bytes from 192.168.0.1: icmp_seq=0 ttl=255 time=3.807 ms
64 bytes from 192.168.0.5: icmp_seq=0 ttl=64 time=6.477 ms
64 bytes from 192.168.0.2: icmp_seq=0 ttl=64 time=37.603 ms
64 bytes from 192.168.0.4: icmp_seq=0 ttl=64 time=38.634 ms
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.130 ms
64 bytes from 192.168.0.1: icmp_seq=1 ttl=255 time=4.424 ms
64 bytes from 192.168.0.5: icmp_seq=1 ttl=64 time=4.472 ms
64 bytes from 192.168.0.4: icmp_seq=1 ttl=64 time=58.922 ms

1是路由器,2是我的手机,3是我的PC,4是IPAD,那基本上5就是我们的智能家电了。

接下来考虑如何发起请求。

十分庆幸有人已经把所有API包装起来了。我们直接调用封装后的方法就行。

1
2
3
4
5
6
7
const { Client  } = require('tplink-smarthome-api');

const client = new Client();
const plug = client.getDevice({host: your_host_address}).then((device)=>{
device.getSysInfo().then(console.log);
device.setPowerState(true);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜  tplink node index.js
{ sw_ver: '1.5.7 Build 180202 Rel.075643',
hw_ver: '1.0',
type: 'IOT.SMARTPLUGSWITCH',
model: 'HS105(JP)',
mac: 'xxxxx',
dev_name: 'Smart Wi-Fi Plug Mini',
alias: '只是个插座',
relay_state: 1,
on_time: 0,
active_mode: 'none',
feature: 'TIM',
updating: 0,
icon_hash: '',
rssi: -47,
led_off: 0,
longitude_i: xxxxx,
latitude_i: xxxxx,
hwId: 'xxxxx',
fwId: '00000000000000000000000000000000',
deviceId: 'xxxx',
oemId: 'xxxx',
tid: '',
err_code: 0 }

后话

陆续等空气质量传感器和温湿度传感器到货之后,可以通过室内的空气质量来控制空气净化器的开关。

冰冷暴雨夜的山中湖Solo Camp

流水账

计划了一个星期的Solo Camp。终于还是盼到了周末。

这次的目的地是山中湖边的みさきキャンプ場。在ゆるキャン第六话中「野クル」除了撫子之外的部员camp过的地方。

带着半分圣地巡礼的心情,周六起个大早,因为是solo,所以不用因为集合之类的拖延时间,前夜已经塞好所有装备。

装备

路线大概是这样的,

从我家=>東名川崎インター=>(東名高速)=>御殿場=>(国道138)=>(東富士五湖道路)=>山中湖=>(マリモ通り)=>みさきキャンプ場

7:30 从家出发。

东名的堵车也是厉害。大和トンネル先端开始10公里。直接给去程增加了一个多小时。

堵车

去目的地途中得先去当地的超市和home center买东西。

第一站是コメリハード&グリーン 忍野店

这家店是个home center,和ゆるキャン没有一毛钱关系。之所以先得来这里是因为出发前夜发现买的印有しまりん的旗子需要特定的旗杆才行。

搜遍了整个谷歌发现这家店幸好还有8个库存。

憋着在高速公路上的7成尿意跑到店里,拿起旗杆结账。

顺便吐槽一下,这家店里的厕所水龙头不出水。。。

第二站是漫画里也出现的超市オギノ山中湖店。

超市里BBQ的东西也是很充实,就是价格略贵。比如一把烧火的柴居然要800日元。从良心上来讲,这钱在别的地方买的柴可以烧到天亮。

オギノ旁边的百元店东西很少,包括对面的药妆店,真的只是药妆店。一点BBQ相关的东西都没,本来想买便宜的着火剂和柴火,结果只好又跑回オギノ买了。

在オギノ买了一盘450g的烤肉拼盘,两个北极贝,四个蛤,两瓶水,两袋泡面,一包关东煮,一袋冰,一带零食,一捆柴,两块着火剂。

收银的妹子不错。不过下意识看到了无名指上的戒指233

买完东西沿着阴天的湖岸开了半圈就到了目的地みさきキャンプ場。

前前后后大概已经有十几个帐篷。单人帐篷+停车券 1700日元,游鱼券600日元。也许是觉得特么湖里根本没有鱼,作为最后的良心,游鱼券上印着附近温泉的100日元优惠券。

望了一圈,比较开阔的场地感觉已经没有靠近河边的位置了。把行李放上拖车,拖着走到林子里,找了个河边,开始驻扎。

完成的样子如下。

驻扎效果

插旗子的杆子就是刚才在home center买的。

以前觉得旗子直接有个pole一插就行了。后来看了一下,日本这种旗子,还得专门的旗杆。其特征是有个固定的横杆。这样旗子没有风的时候也是展开的。

这种样式其实也并不陌生,大街小巷的店门口,比如パチンコ之类的店,门外就是这种旗子。

午饭是关东煮。

关东煮

其实泡面也行,随便凑活一下。风略大,burner有点力不从心了。感觉得买个挡板。

吃完饭抄起鱼竿钓鱼哎。

湖钓ルアー釣り还是第一次。和海钓的区别是,鱼竿比较轻,毕竟要经常抛甩。鱼线直接挂上ルアー就行了。

换了几个地方,除了扎到几根水草就没有任何收获。

另外还有个看起来很专业的大叔在钓鱼。腰里别着两根鱼竿,直接穿着捕鱼那种可以下水的衣服走到湖里。

大概到了三点半。天突然开始下雨了。

天气预报真是垃圾。早上看的说今天也就是阴到多云,结果直接就是倾盆大雨,远处还有轰鸣的雷声。

大叔也上岸了。跑到我帐篷门口和我打个招呼。

大叔说能不能拍一下しまりん的旗子。我说好呀,大叔拍完,欲言又止,最后还是和我说。

小伙子你甩鱼竿动作太大了,这样侧着轻轻一甩就行了。另外不需要把线扯来扯去,比起乱暴的小鱼,鲈鱼还是比较喜欢攻击安静游动的小鱼。云云。。。

我问大叔有没有钓到,大叔说,这年头,其实大家都知道这湖里根本没鱼。自己也是抱着被剃光头的心态来的。。。

大叔走了之后,雨越下越大,タープ上的水滚下来就像瀑布一样。

天也开始冷起来。我决定去温泉暖一下身子。

紅富士の湯

也是漫画里出现的温泉。车程大概15分钟,这里吐槽一下BMW的Navi也是垃圾。目的地是设施的话不能直接自动定位成停车场,害得我在狭窄的上坡路上惊悚地调了次头。

虽然是雨天,停车场也是水泄不通。山中湖毕竟世界驰名,游客的身影也是层出不穷。

浴室的窗外就是富士山,不过下雨,烟雾笼罩啥都看不到。男汤里面有两个池子,外面走下台阶也有两个。

我本来也不是特别喜欢泡着,暖和一下身子就出来了。

也没休息个几分钟,赶紧在天黑之间回到了camp场。下雨天湖边的路乌漆麻黑,加上对向车道闪眼的车灯,只好稳重再稳重。

在タープ下面坐下,接下来就是要做最后幸福感的事情了。

在雨中烤肉和生火。

天色已经暗了下来。在生火的时候,有个小哥拿着零食跑过来说分给我的,雨太大了,自己的装备撑不住先回去了。看到しまりん的旗子感觉好棒哈哈。一路平安~

大鹅

有个大鹅游过来。叫了几声又游走了。到了晚上睡觉时候我才反应过来应该是我把它平时的地盘给占了。

烤肉1

老顾介绍的合成木炭质量真心好。所有烤完只用了6快碳。而且真是一点烟有没。

Weber 炭

软广告

烤肉2

北极贝放着烤的样子很棒,味道嘛,其实我也不是特别感冒。下次多买盒肉算了。

大概到8点左右雨也停了,开始生火。月亮也出来了。

生火

顺便用公司手机的流量补了两话棒子剧,扫了几眼纪录片。

尝了一口小哥的爆米花,特么已经湿了,吃了口薯片,一股柴火味。算了,扔了哎。

800块钱的柴火烧到11点多就没了。这次比较后悔的是没带大的烧烤炉,

因为是solo,所以用了uniflame的 ユニセラtg-iiiミニ这款。烤肉正好,不过要烧柴就有点力不从心了。

横着竖着总之还是会掉外面。

倒是消火罐看起来挺大,放两根柴进去也能烧得很壮观。

十一点半,睡觉。

躺下才感觉特么三季节用帐篷已经不抵寒意了。

室外温度大概3度,室内估计撑死5度。毕竟flyer和底子之间透气的空隙太大,冷风直接从下面钻进来的感觉。

最佳温度5度的睡袋,上半身还好,下半身就别说了,反正晚上是冻到脚抽筋了。

这次最后悔的就是没把电热毯带过来。

就这么挨过一夜。被尿憋醒。上完厕所之后索性就开始收拾东西了。

早饭是,泡面+咖啡。

收拾完东西稍微散步走去看富士山。

富士山

天真特么好。昨天的鬼天气像一个梦一样。

回来路上又去了一次温泉。

中饭在温泉的食堂里顺便解决了。

山梨县名物,ほうとう

ほうとう

不知道这个东西有没有中文译名,没有的话我来取一个。山梨刀削面。

也许是出发早的缘故,东名高速也没堵车,很顺畅得就滚回了家。

收拾完坐下,才下午三点半。

反省

  • 三季节用的帐篷已经不是时候了。该考虑入手冬天专用的保暖增强性帐篷

  • 用普通的锅子烧水简直要命,烧个半小时也不开,该买 Jet boiler了

  • 得买个solo用的火架或者暖炉

  • 钓鱼是个败笔,下次先调查一下有没有鱼再买券

Camp装备入门1

从家里走出来的阿宅

随着近期动画化的作品「ゆるキャン」(中文渣译成 摇曳露营) 的火爆,出现了原本一些已经根治无望的阿宅从家里出门,投入到户外活动的奇迹。

杨教授当年的电击疗法于此相比也只能是相形见绌。

当大量的阿宅将本来投入到塑料小人,光碟小卡片上的钱投入到露营装备中的时候,一时间整个岛国的Camp场地都有种水泄不通,一地难求的感觉。
趁着Camp的火热,我也来安利一波Camp大法。

奢侈的户外运动

camp是项很烧钱的户外运动。

买齐一套Camp的装备大概成本不下20万日元。

装备齐了就算了吗? 除非你是考虑走路去camp,或者坐电车去camp,亦或者是在自己家阳台上camp,否则有个车也基本是必须的。

这么一来直接就把camp的门槛提高了很多。

当然,上面这些复杂的开销有个前提,那就是你自己想组织camp,或者是自己想一个人去solo camp。

最低限度的装备

作为第一次尝试camp,跟着有装备的大佬就行啦。那么这个时候,作为最低限度的装备,都需要哪些呢?

帐篷

当然跟着大佬出去也可以几个人挤一个大帐篷。不过很多情况,比如男女之间不方便之类的原因,还是买一顶自己的帐篷比较好。

一般来说,第一次买帐篷的人,经常容易犯的错误就是买个大帐篷。在一些地方有限的camp场,大帐篷几乎没有施展能力的空间。更何况大帐篷意味着更大的体积和重量,出门携带也是很不方便的。

这里我推荐几款1-2个人用的帐篷。虽然说1-2人,但是实际上一个人用最舒适。

这个帐篷的set已经包含了垫在帐篷与地面之间起保护作用的Ground Sheet以及帐篷内部的垫子。另外送两面亮骚的小旗。Pole(杆子)一根就可以支撑起来。简单却不失趣味。价格也适中。

轻量以及低价是这个帐篷的亮点。当然我不建议买更便宜的一些帐篷。便宜货一般接缝的地方很粗糙,下雨天漏水是常有的事情。montbell作为日本大厂,质量还是值得信赖的。

睡袋(シュラフ)

睡袋的形状各种个样。有方方正正的信封型的。有木乃伊型的。

选择睡袋的主要标准还是两个,一个是使用温度,一个是打包后的体积。

春夏用的睡袋可以比较随意。

基本ドンキー卖1000块钱的睡袋可以适合5-10月上旬的绝大地方的camp。上面这款和ドンキー里面卖的睡袋的区别是,有名maker的睡袋,质量相对较好。

秋冬用的睡袋需要特别注意使用温度。另外有时候温度标记会有两种,一个叫最低使用温度,一个是舒适使用温度。

基本上我们得根据舒适使用温度来选择睡袋。

虽然说明上写了3 season用。但是,coleman是北美的maker。所谓的冬季那是北美的寒冬,和日本的冬季不是一个次元的。这款使用温度为-5度的睡袋足够适合秋末冬初的camp。

座椅

无论是像Fuji Rock 那种野外Live,还是Camp,有把轻便又舒适的椅子肯定是必不可少的。

大多数折叠椅子的价格和质量基本大相径庭,可以产生价格差距的关键因素是椅子的重量。

这把椅子中和了舒适度,价格,以及重量,性价比已经相当高。

这把椅子有点小贵。之所以贵的因素是,这把椅子的总重量不过500g。野外fes之类的活动太适合了。在Fuji Rock的时候实际使用的就是这把椅子。不过,好像涨价了呢。

睡垫&床

有了帐篷和睡袋,就可以直接钻进去睡觉了? 除非那是你家地板。

野外的地面比想象得差很多。特别是小石子,沙粒,高低不平,坑坑洼洼的地面,没有个垫子肯定是睡不安稳的。

一般睡袋和帐篷之间得铺一个垫子。或者,直接架在地面上的床。

垫子可以分充气的和不充气的。很显然,充气的携带方便,但是可能睡起来不自然,垫子的话虽然可以多重折叠,但是想要舒适平坦的话,还是得选择比较厚的垫子。

这款是折叠型的。看起来也比较厚实。

充气型的要比折叠型的贵一点。不过收纳起来很方便。夏天的话充气型的垫子也比较凉爽。

这是个可以架起来的小床。通过和地面隔离,可以起到夏天阴凉,冬天隔热保暖的作用。缺点嘛就是比较重。价格倒也还合适。

后话

入手了以上的装备,基本就可以愉快地参与Camp了。当然,上面的仅仅是解决了最基本的住的问题。所以离Solo Camp 出道还是很远。

至于其他一些装备以及进阶装备, 那就放到以后慢慢介绍吧。