格物、致知、诚意、正心

0%

简单介绍

beancount 是一种复式记账的语言,可以将交易记录在文本文件中,可以做很多的脚本处理,还有 Web 界面可以查看账目。

复式记账

基于会计恒等式产生的记账形式,每笔交易的结果至少被记录在一个借方和一个贷方的账户,且该笔交易的借贷双方总额相等,即 ”有借必有贷,借贷必相等“ 。

资产 = 负债 + 所有者权益

资产 = 负债 + 所有者权益 + (收入 - 费用)

复式记账可以做到对资金的强掌控,每一笔资金的来源和去向一清二楚。

本地安装

1
2
3
4
5
6
7
8
# 本文所有 python 环境都是 python3
pip3 install virtualenv

# 进入自己想放置环境配置的地方
python -m venv BEANCOUNT
source BEANCOUNT/bin/active
pip install beancount
pip install fava

安装完成即可开始记账,如何记账可以参考文章最下面的几篇博客。

网页访问

我是用 Docker 部署 fava 来使用的,一般小笔的交易直接在 fava 的编辑器中打开账本进行记账。

建立部署文件夹

1
2
# 进入账本文件夹中执行
mkdir deploy && cd deploy

记录依赖

1
2
3
4
# 进入 venv
source BEANCOUNT/bin/active
# 将正确的依赖配置记录下来
pip freeze > requirements.txt

Dockerfile

创建 Dockerfile 文件,然后输入以下内容

1
2
3
4
5
6
7
8
9
FROM python

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

ENV FAVA_HOST=0.0.0.0
VOLUME /data

CMD ["fava", "/data/main.bean"]

Docker Compose

创建 docker-compose.yml 文件,输入以下内容(具体配置可按自己的需求改变)

1
2
3
4
5
6
7
8
9
version: "3.3"
services:

fava:
build: .
ports:
- "127.0.0.1:8888:5000"
volumes:
- ..:/data

注意:

  • 挂载的 volumes 中的 .. 是因为上面这些文件都在账本目录下的 deploy 文件夹中,.. 正好是账本文件夹。
  • 主账本名称应该为 main.bean

部署网站

1
docker-compose up -d --buil

之后可以访问: http://localhost:8888/ 查看账本是否部署成功

参考

基础软件

Brew

1
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Iterm2

1
brew cask install iterm2

Oh My Zsh

1
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

应用程序

Chrome

1
brew cask install google-chrome

Dropbox

1
brew cask install dropbox

Alfred4

1
brew cask install alfred

1Password

1
brew cask install 1password

Joplin

1
brew cask install joplin

Typora

1
brew cask install typora

Telegram

1
brew cask install telegram-desktop

通用开发环境

VSCode

1
brew cask install visual-studio-code

JetBrains

1
brew cask install jetbrains-toolbox

Docker

1
brew cask install docker

Anisble

1
brew install ansible

Git Flow

1
brew install git-flow-avh

前端开发环境

Node

1
2
3
brew install nvm

nvm install node

博客环境

Hexo

1
npm install -g hexo-cli

NFS

服务端

ubuntu 直接使用包管理器安装,其他平台类似。

1
apt install nfs-kernel-server

创建专用目录。

1
2
3
mkdir -p /data/nas-root
chown nobody:nogroup /data/nas-root
chmod 777 /data/nas-root

导出文件配置

1
2
3
vim /etc/exports

/data/nas-root 10.10.10.0/24(rw,sync,no_subtree_check)

使配置生效

1
2
exportfs -a
systemctl restart nfs-kernel-server

客户端

查看服务器共享文件状态

1
2
3
4
showmount -e 10.10.10.100

Exports list on 10.10.10.100:
/data/nas-root 10.10.10.0/24

挂载

我这边是在 Mac 下挂载,Linux 平台只需要改变路径就好。

1
sudo mount -o resvport -t nfs 10.10.10.100:/data/nas-root /Volumes/NAS

SAMBA

服务端

Ubuntu 使用包管理器安装

1
apt install samba

增加 SAMBA 账户

1
smbpasswd -a etby

编辑配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
vi /etc/samba/smb.conf

[NAS]
comment = NAS Directory
browseable = yes
path = /data/nas-root
create mask = 0777
directory mask = 0777
valid users = etby
force user = nobody
force group = nogroup
public = yes
available = yes
writable = yes

前言

每次Linux初始化配置都比较麻烦,所以在此记录一下,以后方便配置。我主要是用 Ubuntu ,所以以下是基于它的,其他系统类似。

配置

SUDO 免密码

1
2
3
sudo vi /etc/sudoers

etby ALL=(ALL) NOPASSWD:ALL

卸载 SNAP

针对主要用来做母鸡的服务器

1
sudo apt autoremove --purge snapd

安装软件

OH MY ZSH

OH MY ZSH 以及它的工具链用的很熟练了,感觉非常舒服。

安装

1
2
3
4
5
6
7
# 安装本体
sudo apt-get install zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

# 安装插件
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
plugins=(
# Common
git
zsh-syntax-highlighting
zsh-autosuggestions

# Develop
git-flow-avh # 需要安装 git-flow-avh # sudo apt-get install git-flow

# Server
docker
docker-compose
kubectl
)

显卡驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 添加驱动仓库
sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt-get update

# 安装 Ubuntu 驱动工具
sudo apt-get install ubuntu-drivers-common
# 查看设备以及推荐
sudo ubuntu-drivers devices
# 按推荐的显卡驱动安装
sudo apt-get install nvidia-driver-418

# 重启
sudo reboot
# 查看安装情况
nvidia-smi

前言

最近,正在做一个新需求:需要在已有的页面中新增一种与当前数据结构完全不同的数据展示,页面展示基本不变,但后端返回数据结构与这个数据代表的意义完全不同。在产品与UI眼中,这或许并不是什么大的变动,但对程序来说却比较复杂,所有牵扯到的逻辑都需要梳理。(这或许也是产品和研发打架的原因之一)

问题分析

原因

一般小型项目都是 接口 -> 数据库 -> 界面展示 一把梭,只用一个类来定义对象,中间不会经过任何转化和分层。一般来说数据结构很少变,而且多数据类型同一个页面展示的情况大多比较稀少,所以一般都是如此处理的。

解决方法

我这边一般想到几个方法:

直接修改逻辑,增加处理逻辑

这样一般改变逻辑较多,牵扯到逻辑判断的地方都要增加新数据结构的处理。

在已有数据对象中包含新对象

在已有对象中创建一个字段,将新对象注入,改写 get 类型方法,返回新对象的数据,展示到页面上。

这样改动比较简单,但容易发生数据访问问题,需要多测试才行。而且侵入性太强,需要更改已有数据结构,导致逻辑混乱。

复制页面,不同的数据结构访问不同的页面

这种方法只适用于整体数据结构改变的方式,如果是局部异同,则无法解决。而且会增加界面,增加逻辑复杂度,如果界面逻辑更改,则需要两处都进行更改。

提取公共字段,增加抽象层,增加专用展示对象

具体操作其实和直接更改类似,都是需要同样的工作量。而且由于增加了许多兼容性的处理,会导致工作量更大。

好处是,兼容性足够强,再次增加逻辑方便,而且抽象提好了数据层次,对数据结构侵入性没有。

坏处是,之前所有逻辑都需要更改数据结构,添加此层次。

将新对象转化为旧对象

这种方法一般是最容易实现的,但是如果需要进行空字段的处理,同样会增加工作量,比较麻烦。在整体逻辑需要用到某些数据的时候,如果没有此数据,则需要增加专用的处理逻辑。如果需要更改的地方比较少,工作量不太的话,此种方法则是一般情况下大多数人会选择的,但是会导致一些认知混乱,当然如果逻辑本身不复杂的话,一般不会出问题。

DataCache

https://github.com/etby/DataCache

这是一个对象缓存框架,按理说应该是一个对象池。

http://blog.etby.org/2018/03/27/datacache-library/

这是我在写发布这个库的第一个版本之后,写的一篇简单介绍的博客。

应用场景

在做一个应用的时候,经常会遇到各种页面之间数据同步的问题。这个基本是每个APP开发工程中都会遇到的问题,解决方式也是各式各样。

在这里说一下我的方法:

  1. 使用 DataBinding
  2. 数据对象全局唯一 ( 使用DataCache库 )
  3. 在所有外部入口,比如 API、DB 都会设计一层拦截层,在这次获取字段改变,然后直接使用全局唯一对象的set 方法进行设置,UI 就会自动更新

以上是核心逻辑,在整个应用中保持单个数据只使用一个Java对象,然后使用DataBinding框架触发界面更新。( 现在则可以增加LiveData,使整个过程更流畅)

这个方式并不优美,但是挺好用的。对于掌控整个数据流来说,只需要掌控入口,工作量会小很多。

一些问题

  • 由于数据对象有可能循环引用,所以对直接传递对象和序列化有影响,处理起来比较麻烦
  • 外部数据更新比较麻烦,对于API,我这边是修改了Gson源码来做到自动解析的。
  • 性能问题,虽然只用一个对象能够减少内存使用,但更新的时候通知的地方也比较多,有可能会导致性能问题。

DDD 与 DataCache

领域驱动设计虽然之前自己查了很多资料,也看了一些书,但始终不得要领,而且无法进行实际的运用。毕竟增加了许多工作量,我却无法灵活使用,不熟的东西还是不适合使用。

处理上面提到的增加页面数据的问题时,我想到了 DataCache 库,想到了它的优缺点和我现在的窘境,之后想到的 DDD 的分层。对数据结构的分层:VO/PO/DTO 等等。

DDD 的优缺点

  • 分层、解耦
  • 对逻辑更改的容忍度比较好 ( 只需要在其中一些层进行更改, 这也是所有分层设计都具备的优点 )
  • 对数据影响力的限制 ( 应用=数据+算法, 但如果数据的实用性和扩散度太高, 会导致逻辑(算法)被牵制 )
  • 穿透性有影响,会影响一些代码的通用性,增加工作量
  • 学习和理解起来比较困难

使用 DDD 的一部分

虽然 DDD 比较庞大,但其实我们已经有意无意的使用了类似的思想。比如上面页面问题提出公共数据对象的方式,或者聚合数据对象的方式,都是表现层分层的一种思想,那些对象已经类似VO了。

我们则可以做的更好,将VO完全独立出来,然后使用DataCacheVO单例化。这样就可以解决多页面的数据同步问题,在逻辑需要用到数据的时候则可以使用真实数据对象来进行操作。这样就可以避开单对象的循环引用的问题,序列化和传递对象则不必太拘束。

逻辑层和展现层使用完全不同的数据结构,当逻辑改变时,则就不需要进行大量的更改,只需要在层与层之间进行一些转化。或许这样做之后,增加的学习成本和工作量则不再是阻碍了。

这两天由于公司移动方向的开发资源不足,所以决定接触一下一些跨平台的方案。由于Flutter暂时还没有 Release ,所以主要还是先依靠 React Native 来做一些页面。虽然 React Native 已经是非常成熟的技术了,但我第一次用还是遇到了问题,在此记录一下。

需求

因为 React Native 并不是完全脱离本地代码,而且也是集成进现有的项目,所以调用本地代码的需求比较大。需要在 iOSAndroid 两方代码中都建立通道,才能在 React Native 中统一调用。

添加模块

https://reactnative.cn/docs/native-modules-ios/

https://reactnative.cn/docs/native-modules-android/

上面是 React Native 中文网的两个文档,iOS 端直接按照文档步骤添加完成就好,Android 则与文档之中的不同。

Android 添加自定义 Toast 模块

定义模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ReactModule(name = "RNToast")
public class ToastModule extends ReactContextBaseJavaModule {

public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
}

@Override
public String getName() {
return "RNToast";
}

@ReactMethod
public void toast(final String msg) {
ToastUtils.showCenterToast(msg, getReactApplicationContext());
}

}

与文档中不同的是增加了ReactModule的注解,主要参考了已有的 ToastAndroid 模块

定义 Package

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@ReactModuleList(nativeModules = {
ToastModule.class
})
public class ReactNativePackage extends LazyReactPackage {

@Override
public List<ModuleSpec> getNativeModules(ReactApplicationContext reactContext) {
return Arrays.asList(
ModuleSpec.nativeModuleSpec(
ToastModule.class,
() -> new ToastModule(reactContext) // 此处是 Provider 接口实现
)
);
}

@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return LazyReactPackage.getReactModuleInfoProviderViaReflection(this);
}

}

同样增加了 ReactModuleList 注解,以及使用了 LazyReactPackage 构建懒加载的 Package,主要参考了 MainReactPackage

添加 Package 到 RN 环境

1
2
3
4
5
6
7
8
9
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackage(new MainReactPackage())
.addPackage(new ReactNativePackage()) // 此处是新增代码
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();

这块是初始化 ReactRootView 时的参数设置,只需要增加自定义的 Package。预先提供的内置 Module 都是这样提供的,必要时可以通过 MainPeactPackage 进入之后查看源码

与文档中不同的地方

文档写了需要放在指定的包名的 MainApplication 中,这个并不需要。只需要在 ReactInstanceManager 构建时增加自定义的 Package 就好。

这个 package 需要在MainApplication.java文件的getPackages方法中提供。这个文件位于你的 react-native 应用文件夹的 android 目录中。具体路径是: android/app/src/main/java/com/your-app-name/MainApplication.java

问题

最近在用Flutter开发一个小一点的应用,有一些需要用到第三方框架的地方。但是当我按照文档接入之后,却出现了不该出现的问题。

在使用Fluro框架和Flutter_Redux框架的时候,存储在组件列表中的数据存储对象却无法找到。但是,越过App层的Fluro直接使用redux却可用。

在这还得吐槽一下Flutter,由于基础设施不完善,DEBUG起来非常麻烦,我也只能去求助大牛了。

解决过程

初步怀疑是flutter_redux框架的bug,然后就提了一个issue。具体:

https://github.com/brianegan/flutter_redux/issues/41

之后大牛回复我希望能提供一个demo,我就建了一个项目,按照错误代码去重建。结果无论如何都无法重现,但是我又不能将旧代码逻辑转移过来,这样也许会再次产生,比较产生bug的因素目前还不明确。

我就只能将私有仓库公开,之后提交给大牛,让他帮我看看了。

没想到大牛直接给我提了一个 pull request,感谢大牛。

解决方案

大牛给我的回答如下:

Hey there! The problem you’re experiencing is a confusing issue with how Dart handles imports. By changing the imports to package-relative paths, I was able to get the app working.

General rule: always use import ‘package:bungami_list:path/to/file rather than import ‘path/to/file.dart

This is definitely a confusing part of Dart, and it might be helpful to read this thread on more info: dart-lang/sdk#32042 (comment)

大体是说,是因为import包的问题:dart针对于URI不同的库就算代码相同,也不会认为是同一个库。所有尽量使用import全路径的方式来进行导包,就算是在同一个项目中。更多的信息可以看这里:https://github.com/dart-lang/sdk/issues/32042#issuecomment-363107221

最后

经过这次的问题解决,我也获得了很多经验:

  • 感谢开源社区
  • 要多参与开源
  • 当你有一些问题怀疑是由于开源库引起的,可以尝试去阅读源代码解决。如果源代码无法解决你的问题,可以直接提Issue给具体的项目,求职大牛帮忙。
  • 开源的项目并不只需要解决代码方面的问题,使用方面的问题也是需要解决的。最好的方式是完整优秀的文档,对于无法文档化的问题,则集合大家的力量去解决。

简介

这个库会针对某一个特定的数据在内存中只保存一个对象,有数据更新的时候只会更新其中的字段,对象的引用并不会发生变化。

搭配数据绑定框架之后,可以保证所有页面的数据和状态同步。

为什么写这个库

数据同步其实一直是一个比较棘手的问题:当你在一个子页面点赞或者评论之后返回,父页面并没有发生变化,就比较尴尬了。如果嵌套更深的话,直接路由到首页也会出现这个问题。

这个在网页到还不是什么大问题,因为是不同的页面。但是在客户端的话,用户只会看到你这出问题了。

当然,解决方案也是有很多:

  • 数据库同步
  • 发消息同步
  • 让用户自己刷新

因为数据绑定我用的非常的多,而且同事们大家都觉得数据绑定框架不错,所以准备找一个依据此框的解决方案。而且我们一致认为不应该引入重量级的数据库来做这个事情,就只能在内存中做了。而且又因为状态改变之后需要同步到View,而数据绑定到布局的对象并不方便随便更改,所有就想到了在内存中维护一个唯一对象的方式。

最早虽然是唯一对象,但是并没有使用注解处理器生成代码,而是手动根据需要更新的字段进行配置。然后手动在API层做一些标记,但是这样比较麻烦,而且数据结构改变之后并不能及时同步,容易出现问题。

然后我就使用了注解处理器,根据API返回的字段进行标记。对内存中的原始对象只更新有用的字段,而且由于字段自动化了,工作量大大降低了。

注解处理器其实和代码并没有什么不同,不过就是在解决某些问题上效率有区别,在合适的场景选择合适的工具。

当然,要根据API返回数据进行标记的话,需要在JSON层面进行处理,所以就只能魔改Gson了。然后这两个库搭配起来,就做到了新流入的数据都是有标记的,对象中的哪些字段是有用的,都可以区分。

之后在数据层的接口处进行过滤,将新的解析出来的对象替换为原始对象,然后将需要更新的字段进行更新。这样,之前绑定的UI就会自动更新了。所有页面的数据永远都是最新的,永远都是相同的展示。

优缺点

有什么优点

  • 减少相同数据对象冗余
  • 全局展示数据和状态同步
  • 内存原始对象字段完整度高 (API如果在某些情况只返回几个字段,那么解析出来的对象中的其他字段都是没有用的)
  • 逻辑层更改状态并不需要接触UI,也不会间接接触UI(监听器模式,当然只在我们自己写的代码中,数据绑定生成的观察者也是有引用的)

有什么缺点

  • 不能保证100%靠谱(靠谱程序和后台API接口的优雅程度正相关)
  • 不同API返回数据有差异 (后台缓存或者同步问题,会导致不同接口返回数据不一致,会导致数据变化)
  • API返回字段无效或者错误 (会导致整个应用内部数据都出错,当然有些API是客户端控制返回字段的,然而这并没有什么*用)
  • 不靠谱 ~

总结

只建议没有能力或者没有资源做到完美的团队使用。

对于对数据流控制完美,逻辑架构优秀,开发资源充沛,排期并不紧张,测试严禁充分的优秀团队来说:这并不算是一个比较好的解决方案。

更新计划

应该就到此为止了,整体设计思路已经完成,没有必要再增加新的功能,估计后续只是优化细节。

如果有一些优化或者扩展的想法,请联系我。

最后

这算是我第一个正式的开源库了,很有纪念意义。

当然肯定不会是最后一个。我要渐渐成长,然后为开源做出更大的贡献~

环境

服务器在阿里云香港, 避免不必要的网络问题. 使用Ubuntu系统, 因为包管理比较方便. 系统版本是Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-49-generic x86_64)

安装

安装Docker

下面是一段在Ubuntu中安装Docker的脚步, 虽然我只在17.04版本上测试过, 但是其他版本应该也可以用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash

sudo apt-get update

sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common -y

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo apt-key fingerprint 0EBFCD88

sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"

sudo apt-get update

sudo apt-get install docker-ce -y

如果不能使用, 请按照官网步骤进行. 如果是其他系统, 请参考官网其他系统的文档
https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/

安装&启动 Nexus3

如果Docker已经安装成功, 则只需要一行命令即可

docker run -d -p 127.0.0.1:8081:8081 --name nexus sonatype/nexus3

我这边需要使用Nginx反向代理, 所以直接本地访问, 如果想通过IP访问, 去掉IP配置即可

安装完成之后docker ps查看一下, 应该会是下面这样

1
2
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                      NAMES
64c907fbac26 sonatype/nexus3 "bin/nexus run" 25 hours ago Up 25 hours 127.0.0.1:8081->8081/tcp nexus

这样就已经启动了, 之后再用Nginx配置域名之后反向代理就可以通过域名访问了.

代理JCenter等仓库

以上这个是我的jcenter的配置, 其他仓库配置都是一样, 只需要替换名称和远程连接就行

这是目前的配置, 代理了jcenter和mavenCentral, 还有google和jitpack.
然后创建了一个组, etby_works. 这样我在studio客户端只需要配置一个就好, 在需要的时候可以直接在服务器上新增远程仓库. 新项目可以继续创建新的组, 也可以使用一个大组来负责所有的远程仓库.

权限配置

关闭匿名访问

Security -> Anonymous -> 取消勾选 Allow anonymous users to access the server 之后确认

添加只读权限用户

创建用户时, 配置Save 按钮上方的 Grantednx-anonymous, 此权限默认只读.

客户端配置

1
2
3
4
5
6
7
8
9
10
11
12
allprojects {
repositories {
maven {
url 'http://maven.etby.studio/repository/etby_works/'
credentials {
username 'username'
password 'password'
}
}

}
}

在Android Studio中配置成这样基本就没问题了, url和账号密码都改成自己的, 上面的也不是我的账号密码 =-=

其他

目前暂时没有仓库上传需求, 所以没有写上传的一些东西, 如果以后有机会再补全吧~

如果有不详细的地方, 可以直接Google, 也可以联系我.

纪念第一篇原创文章