vonzhou / blog Goto Github PK
View Code? Open in Web Editor NEWMy Blog
My Blog
在开发过程中,把对象转换为字符串的场景很常见,但是直接调用对象的toString方法,是可能会抛出NPE的,所以会有很多类似下面的模板代码。
String str = a == null ? "" : a.toString();
其实有很多常用的工具包中都提供了null-safe的工具方法,这里列举下。
其实最简单也最容易被忽略的是 String.valueOf() 方法。
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
JDK 1.7之后引入的Objects工具中也有:
public static String toString(Object o) {
return String.valueOf(o);
}
public static String toString(Object o, String nullDefault) {
return (o != null) ? o.toString() : nullDefault;
}
public static String toString(Object obj) {
return obj == null ? "" : obj.toString();
}
public static String toString(Object obj, String nullStr) {
return obj == null ? nullStr : obj.toString();
}
最近在使用Arrays.asList
时又犯了一个低级的错误导致了运行时异常,所以有必要把这个点好好理解下。从踩过的2个坑开始分析。
Arrays.asList
列表转数组问题详细可以看这篇文章,toArray 方法返回的依然是 Object[],但是与 java.util.ArrayList 不同的是这里底层存储是泛型类型的数组 privatefinalE[]a,所以保留了实际的类型。
Arrays.asList
列表不能新增元素Arrays.asList
构造的是一个固定大小的列表,底层存储是泛型数组,不能新增元素,原因在后面源码分析部分会看到。
Arrays.asList
列表源码分析Arrays.asList
返回的是一个Arrays
的内部类ArrayList
,很小巧。
其实这个内部类和java.util.ArrayList
的外貌(类层次图)是一样的,只是实现不同而已。
接下来我们分析内部类ArrayList
源码。
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
@Override
public int size() {
return a.length;
}
@Override
public Object[] toArray() {
return a.clone();
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size)
return Arrays.copyOf(this.a, size,
(Class<? extends T[]>) a.getClass());
System.arraycopy(this.a, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
@Override
public E get(int index) {
return a[index];
}
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
@Override
public int indexOf(Object o) {
E[] a = this.a;
if (o == null) {// !! 支持null
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
E[] a = this.a;
for (int i = 0; i < a.length; i++) {
a[i] = operator.apply(a[i]);
}
}
@Override
public void sort(Comparator<? super E> c) {
Arrays.sort(a, c);
}
}
使用泛型数组,并且原始数组不能为Null。
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
直接对数组a进行clone,所以保留了原始对象类型。
public Object[] toArray() {
return a.clone();
}
和java.util.ArrayList
一样,如果使用带指定参数类型的toArray就不存在返回类型不匹配的问题。
该实现没有覆写add方法,而自AbstractList继承而来的add方法默认会抛出UnsupportedOperationException
.
public boolean add(E e) {
add(size(), e);
return true;
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
一个服务内部依赖外部服务的时候一定要提供注入点(构造器或者setter方法),这样可以方便用例中进行mock
每个接口各种场景都要测试到位,至少本系统内部逻辑是符合预期的
TDD,提测之前要有测试用例,因为事后补的话会发现有些接口设计不合理,或者缺少一些注入点。
➜ Workspace tar -zxf zookeeper-3.4.6.tar.gz
➜ Workspace mkdir -p data/zookeeper
配置文件:
➜ zookeeper-3.4.6 cat conf/zoo.cfg
tickTime=2000
dataDir=/Users/vonzhou/Workspace/data/zookeeper
clientPort=2181
启动:
➜ zookeeper-3.4.6 bin/zkServer.sh start
JMX enabled by default
Using config: /Users/vonzhou/Workspace/zookeeper-3.4.6/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
验证:
➜ zookeeper-3.4.6 telnet 127.0.0.1 2181
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
srvr
Zookeeper version: 3.4.6-1569965, built on 02/20/2014 09:09 GMT
Latency min/avg/max: 0/0/0
Received: 1
Sent: 0
Connections: 1
Outstanding: 0
Zxid: 0x0
Mode: standalone
Node count: 4
Connection closed by foreign host.
➜ Workspace tar -zxf kafka_2.11-0.9.0.1.tgz
➜ kafka_2.11-0.9.0.1 bin/kafka-server-start.sh -daemon config/server.properties
创建一个topic:
➜ kafka_2.11-0.9.0.1 bin/kafka-topics.sh --create --zookeeper 127.0.0.1:2181 --replication-factor 1 --partitions 1 --topic test
Created topic "test".
向这个topic发送消息:
➜ kafka_2.11-0.9.0.1 bin/kafka-console-producer.sh --broker-list 127.0.0.1:9092 --topic test
Test msg 0
Test msg 1
从这个topic订阅消息:
➜ kafka_2.11-0.9.0.1 bin/kafka-console-consumer.sh --zookeeper 127.0.0.1:2181 --topic test --from-beginning
Test msg 0
Test msg 1
^CProcessed a total of 2 messages
有一个这样的场景,项目中有一个module专门处理同一类事情,比如调用外部的RPC、DB操作等,这个module里面的类命名都遵守统一的规范,比如xxx或者xxx。我们需要在每次RPC前后打印出RPC的耗时以及输入和外部返回的结果,如果在每个类里面都做这个事情显得特别冗余,都知道可以使用AOP,那么针对这种情况,哪种方式更好了, BeanNameAutoProxyCreator,之前没有使用过,所以这里做个梳理。
针对这种bean name符合一定特征的bean做拦截的话,就是需要配置特定的BeanNameAutoProxyCreator bean,指定bean name的模式和拦截器的名称。
<bean id="fooLogInterceptor" class="com.vonzhou.interceptor.FooLogInterceptor"/>
<bean id="fooLogProxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*RPC</value>
</list>
</property>
<property name="interceptorNames">
<value>fooLogInterceptor</value>
</property>
</bean>
拦截器的实现:
public class FooLogInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object result = null;
try {
result = invocation.proceed();
return result;
} catch (Exception e) {
throw e;
} finally {
try {
// do log
} catch (Exception e) {
// todo
}
}
}
}
本质上,BeanNameAutoProxyCreator也是一个BeanPostProcessor,在Spring bean初始化后进行了代理拦截。
Cron语法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year
特殊字符含义是:
(1):表示匹配该域的任意值,假如在Minutes域使用, 即表示每分钟都会触发事件。
(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
(3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
(4)/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.
(5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
(6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
(7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份
(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
其实也可以看qurtz中的封装org.quartz.CronExpression
。
static {
monthMap.put("JAN", Integer.valueOf(0));
monthMap.put("FEB", Integer.valueOf(1));
monthMap.put("MAR", Integer.valueOf(2));
monthMap.put("APR", Integer.valueOf(3));
monthMap.put("MAY", Integer.valueOf(4));
monthMap.put("JUN", Integer.valueOf(5));
monthMap.put("JUL", Integer.valueOf(6));
monthMap.put("AUG", Integer.valueOf(7));
monthMap.put("SEP", Integer.valueOf(8));
monthMap.put("OCT", Integer.valueOf(9));
monthMap.put("NOV", Integer.valueOf(10));
monthMap.put("DEC", Integer.valueOf(11));
dayMap.put("SUN", Integer.valueOf(1));
dayMap.put("MON", Integer.valueOf(2));
dayMap.put("TUE", Integer.valueOf(3));
dayMap.put("WED", Integer.valueOf(4));
dayMap.put("THU", Integer.valueOf(5));
dayMap.put("FRI", Integer.valueOf(6));
dayMap.put("SAT", Integer.valueOf(7));
}
protected transient TreeSet<Integer> seconds;
protected transient TreeSet<Integer> minutes;
protected transient TreeSet<Integer> hours;
protected transient TreeSet<Integer> daysOfMonth;
protected transient TreeSet<Integer> months;
protected transient TreeSet<Integer> daysOfWeek;
protected transient TreeSet<Integer> years;
每天的11点30分执行 * 30 11 ? * * *
John Carmack这个知名程序员开始阅读并参与OpenBSD代码,通过邮件列表可以看到本次应该是增加注释和改善可读性。
我最开始知道约翰·卡马克是因为2015年阅读的《Doom启示录》一书,两个约翰对技术的着迷让人激动不已。
开源的贡献并不一定是要引入什么复杂的功能或者解决了很大的问题,哪怕是写文档,完善注释都是有价值的。
https://news.ycombinator.com/item?id=23224584
点击“阅读原文”,可以找到引用的原文。
一切的一切都是复杂性
Complexity: 软件系统的结构导致很难去理解或修改系统。
复杂性的表现:
造成复杂性的原因:
模块的2方面:接口,实现
模块的使用者(依赖者)只需要关心接口提供的抽象;强大复杂的功能隐藏在后面。
示例:Java中的对象反序列化 vs Unix IO接口。
接下来会讲到实现 deep module的技术:信息隐藏,
系统实现中经常需要能够感知配置文件的变化,然后及时更新上下文。
WatchService用来监控一个目录是否发生改变,但是可以通过 WatchEvent 上下文定位具体文件的变化。具体使用过程中要注意以下两点:
// 监控文件的变化,重新加载
executor.submit(new Runnable() {
@Override
public void run() {
try {
final Path path = FileSystems.getDefault().getPath(getMonitorDir());
System.out.println(path);
final WatchService watchService = FileSystems.getDefault().newWatchService();
final WatchKey watchKey = path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
final WatchKey wk = watchService.take();
for (WatchEvent<?> event : wk.pollEvents()) {
final Path changed = (Path) event.context();
Path absolute = path.resolve(changed);
File configFile = absolute.toFile();
long lastModified = configFile.lastModified();
logger.info(lastModified + "----------------");
// 利用文件时间戳,防止触发两次
if (changed.endsWith(getLicenseName()) && lastModified != LAST_MOD && configFile.length > 0) {
logger.info("----------------- reloading -----------------");
LAST_MOD = lastModified; // 保存上一次时间戳
UPDATED = true; // 设置标志位
}
}
if (LICENSE_UPDATED) {
reloadFile(); // 重新加载
}
// reset the key
boolean valid = wk.reset();
if (!valid) {
logger.error("watch key invalid!");
}
}
} catch (Exception e) {
logger.error("");
}
}
});
Watching a Directory for Changes
add 2017.9.5
金融市场的功能是将资金从资金盈余的主体引导到资金短缺的主体手中,有助于资本的合理配置,从而对增加生产和提高效果做出贡献。
不同的分类方法可以反映金融市场的特征。
金融中介机构的功能是间接融资,为何重要?
我国的监管体系是一行三会:
谈谈对TDD的感受。
维基百科定义:
Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: requirements are turned into very specific test cases, then the software is improved so that the tests pass. This is opposed to software development that allows software to be added that is not proven to meet requirements.
TDD由kent beck于2003年提出,kent出了好几本好书。
工作好几年了,从最开始的不写测试用例,到后来的推荐写用例,再到现在的强制用例覆盖,自己对测试用例的认知也在慢慢改变。
之前的做法是先实现功能,然后本地或者部署到dev环境,用postman接口测试,有问题修改后再部署,如此往复,有一种很混沌的感觉,而且每次部署都比较慢。
TDD可以让自己更有信心, 测试用例能通过,基本逻辑就不会有大的问题,而且各种边界情况都可以覆盖到,代码修改或重构后,如果有测试用例就会实现小步快跑。
现在的做法是从入口处,先把核心的接口定下来,然后写测试用例,外部系统交互采用Mock的方式,然后填充接口的实现直到测试用例通过。为了解决本地测试用例连接dev数据慢的问题,可以集成测试环境使用内存数据库,在支持依赖注入的框架下很容易。
最近在项目中遇到了一个问题:当[条件A]很大的时候数据查询特别慢,A>1000的时候就需要3秒左右,这是不能忍受的。经过排查发现代码中有一个根据A串行查询redis的操作,如下:
if (CollectionUtils.isNotEmpty(list)) {
for (String key : list) {
data = redisService.getDataToday(key)
}
}
对这种情况的测试效果:
A 时间
100 249.78ms
500 1.17s
1000 2.4s
其实可以看到时间消耗在了每次查询的网络开销上。接下来进行了2中不同方式的优化。
上面的场景其实就是把今天的数据放在redis中,如果只是查询历史数据,就可以避免查询redis的开销,如果对精确性没有那么高。
优化后(A=1000)的时间262.93ms。
既然知道性能的瓶颈在网络开销上,就可以采用批量操作的方式来改进,pipeline,但是要在这里可以使用mget或者pipeline的方式,注意考虑失败的情况。
public List<Object> getValueByKeys(final List<String> keys) {
if (CollectionUtils.isEmpty(keys))
return null;
return redisTemplate.executePipelined(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
for (String key : keys) {
stringRedisConn.get(key);
}
return null;
}
});
}
优化后(A=1000)的时间304.76ms,的确有所改善。
《货币金融学》这本书断断续续的看,没有实质性的收获,接下来开始精读,不懂的就去查,同时尽量和现在的业务结合起来。
在金融市场(Financial markets)中,资金从拥有闲置货币的人(没有生产用途)手中转移到资金短缺的人(有生产用途)手中,从而提高了经济效率。
证券(security),又称金融工具,是对发行人未来收入与资产的索取权。
债券(bond)是债务证券,它承诺在一个特定的时间段中进行定期支付。
利率(interest rate)是借款的成本,利率不仅影响消费支出与储蓄的意愿,还影响企业的投资决策,因为利率对整个经济的运行有重要的意义。
股票(stock)代表持有者对公司的所有权,股票是对公司收益和资产的索取权。股票市场是最受关注的金融市场。
银行和其他金融机构将资金从无生产用途的人手中转移到有生产用途的人手中。
货币和货币政策对于通货膨胀(inflation)、经济周期(business cycles)和利率有重要的影响。
诺贝尔经济学得主米尔顿·佛利里德曼:“无论何时何地,通货膨胀都是货币现象”。
全球范围内金融市场一体化的程度在不断增强。
外汇市场(foreign exchange market)是货币兑换的场所,是资金跨国转移的中介市场,是汇率(foreign exchange rate)的决定场所。
由于工作变动的原因,最近3个月说实话在技术上有点落后了,没有之前那样每天都会持续学习新技术,或者至少在关注,看github,medium,看源码。
这3个月更多的是业务上的学习,完全切换了自己的工作背景,context,从0开始学习,会计知识,银行业务,流动性管理,支付清算,路漫漫,很多目前还是停留在表面,未能产出自己的想法。
另一个自己的弱项就是表述能力,或者说是沟通能力,走出去的能力,并不是写好代码就行了,业务的复杂性迫使自己去打开自己,找人学习,自己蒙头琢磨,可能别人一句话就道破自己的疑问。
业务飞速发展,都是使用成熟的轮子在造车,作为一个技术人,如果自己不去抽时间学习,久而久之真的就被麻痹了,离开了这些成熟的轮子,就废人一个。
领域建模,始终是一项核心能力。
要有核心能力。
赶上大趋势。
control + J javadoc预览
状态机有很多的应用场景,表征的是一个事物的生命周期或者状态变迁。
如何实现一个状态机呢?
第一种方法是使用乐观锁,只有当原始状态符合我们的预期时才更新为下一个状态。
public void stateTransfer1(Long id, String prevStatus, String status) {
fooDAO.updateStatusTo(id, prevStatus, status);
}
另一种方式是,先对行加锁,然后在应用层的一个事务里判断记录目前的状态是否可以转换为下一个状态,这里一个好的实际是用一个Map维护状态转移之间的映射关系,方便维护。
private static final Map<String, List<String>> PREV_STATUS = new HashMap<>();
static {
PREV_STATUS.put("SUCCESS", Arrays.asList("AAA", "BBB"));
PREV_STATUS.put("FAIL", Arrays.asList("CCC"));
}
public void stateTransfer2(Long id, String status) {
// do in transaction
Foo foo1 = fooDAO.lockById(id);
if (PREV_STATUS.get(status).contains(foo1.getStatus())) {
fooDAO.updateStatus(id, status);
}
}
我们都害怕重复,重复是繁琐的,重复是低效的。凡是可以简化重复操作的方式,都会成倍节约我们的时间,甚至降低日后的运维成本。
.
命令(dot command)的效果是普通模式下重复上一次执行的操作, 这个看似简单的命令,实则会大大提升我们的效率。
重复上次修改
(repeat last change)这个修改可能是删除一个字符、插入特定文本、做了格式调整等等。
接下来看几个例子,每次操作完成, 使用u
将文本恢复到初始状态。
x
命令删除单个字符,然后不断的.
VIM就会删除光标下的字符。
dd
命令删除整行,然后不断的.
VIM就会删除光标所在的行,先往下,让后往上,直到删掉所有。
5.
输入可以同时删除5行。
yy
命令复制整行,然后移动到合适的位置,使用p
进行复制到当前行的下一行,或者P
复制到当前行的上一行,后续移到新位置,可以使用.
进行上述复制。
>G
命令会增加从当前行到文末的缩进层次,如果.
就会不断缩进。
从进入插入模式,到<Esc>
返回到普通模式,期间进行进行的编辑,也可以使用.
进行重复。
可以使用daw
命令(delete a word)来删除整个单词,aw是一种文本对象(text object)。
daw
之后不断的.
就可以删除光标所在的单词,节省了大量输入。
当经济学家使用利率一词时,指的就是到期收益率。
现值(present value,或称现期贴现值,present discounted value),就是把未来的收益换算成现在的价值。
PV = CF / (1+i)^n
PV 现值,CF未来的现金流量(cash flow),i利率,n年限
虽然多个金融工具偿付的时间安排不一样,但是可以借助于现值的概念对他们进行比较。
到期收益率(yield to maturity):使债务工具所有的未来回报的现值与其今天的价值相等的利率。
上面4种类型的信用市场工具到期收益率计算如下:
LV贷款金额,FP每年固定偿付金额,n年限,i就是要求的到期收益率
P息票债券的现价,C每年的息票利息,n年限,F面值,i就是要求的到期收益率
债券价格和到期收益率之间负相关,可以这么理解,利率越高,未来的息票利息与最终偿付的款项所折现的价值就越少,因此,债券价格必然更低。
息票债券有一个特例,统一公债(consol)或永续债券(perpetuity):没有到期日,不必偿付本金,永远只需要支付固定的息票利息。如果息票利率相等,长期息票债券的价值就与永续债券十分相近。
2年前学过一段时间的Go语言,看了几本书,但是后来不用就又生疏了,本次打算从头学习,never late to learn。最主要的原因是当前云原生技术 Docker,k8s都是Go实现的。在接下来的“Go入门”系列中主要跟着《The way to Go》践行。
Go 语言起源 2007 年,于 2009 年正式对外发布。三位作者都是大牛:Robert Griesemer参与开发 Java HotSpot 虚拟机;Rob Pike,Go 语言项目总负责人,贝尔实验室 Unix 团队成员;Ken Thompson,贝尔实验室 Unix 团队成员,C 语言、Unix 的创始人之一。
➜ the-way-to-go git:(master) ✗ go version
go version go1.10.3 darwin/amd64
➜ the-way-to-go git:(master) ✗ cat hello_world.go
package main
func main() {
println("Hello", "world")
}
➜ the-way-to-go git:(master) ✗ go run hello_world.go
Hello world
如果学了不用的话,可能仍然会再次遗忘,还是要有目的的学习,和现有的知识网络产生交集。平时哪些地方可以把Go用起来?
好的模型也是不断迭代出来的
Valuable models do not emerge immediately; they require a deep understanding of the domain. That understanding comes from diving in, implementing an initial design based on a probably naive model, and then transforming it again and again. Each time the team gains insight, the model is transformed to reveal that richer knowledge, and the code is refactored to reflect the deeper model and make its potential available to the application. Then, once in a while, this onion peeling leads to an opportunity to break through to a much deeper model, attended by a rush of profound design changes.
mvn -pl moduleABC am install
CTRL + A: 移动到命令起始处
CTRL + E: 移动到命令结尾处
学习目标:
- 货币的定义
- 货币的计量,即M0、M1
货币(经济学家称货币供给,money supply)指的是在产品和服务支付及债务偿还中被普遍接受的东西。当大部分人谈到货币时,往往指的是通货(currency,纸币和硬币)。
相关的两个词:
物物交换的时候,必须实现需求的双重吻合,交易成本很高。运用货币作为交易媒介,节省了产品和服务的交易时间,促进了经济效率。
另一方面,货币会鼓励专业化和劳动分工。
统一了经济社会中价值衡量的手段,不需要多种价格(以电影表示经济学课程的价格、以套子表示电影的价格)。
跨越时间购买力的储藏,货币的价值储藏功能并非独有的,其他资产都有,可能更具有优势,但是货币的流动性(liquidity,资产转换为交易媒介的程度和速度)是最好的。
货币计量对于政策制定者和经济学家十分重要。
M0 = 流通中现金
= 货币发行量 - 商业银行库存现金
M1 = M0 + 单位活期存款
M2 = M1 + 个人存款 + 单位定期存款 + 单位其他存款
返回当前的日期和时间
返回当前的日期
返回当前的时间
给日期添加指定的时间间隔
从日期减去指定的时间间隔
语法:
DATE_SUB(date,INTERVAL expr type)
date 参数是合法的日期表达式。expr 参数是您希望添加的时间间隔。
Type 值:
MICROSECOND
SECOND
MINUTE
HOUR
DAY
WEEK
MONTH
QUARTER
YEAR
SECOND_MICROSECOND
MINUTE_MICROSECOND
MINUTE_SECOND
HOUR_MICROSECOND
HOUR_SECOND
HOUR_MINUTE
DAY_MICROSECOND
DAY_SECOND
DAY_MINUTE
DAY_HOUR
YEAR_MONTH
返回两个日期之间的天数
用不同的格式显示日期/时间
格式 | 描述 |
---|---|
%a | 缩写星期名 |
%b | 缩写月名 |
%c | 月,数值 |
%D | 带有英文前缀的月中的天 |
%d | 月的天,数值(00-31) |
%e | 月的天,数值(0-31) |
%f | 微秒 |
%H | 小时 (00-23) |
%h | 小时 (01-12) |
%I | 小时 (01-12) |
%i | 分钟,数值(00-59) |
%j | 年的天 (001-366) |
%k | 小时 (0-23) |
%l | 小时 (1-12) |
%M | 月名 |
%m | 月,数值(00-12) |
%p | AM 或 PM |
%r | 时间,12-小时(hh:mm:ss AM 或 PM) |
%S | 秒(00-59) |
%s | 秒(00-59) |
%T | 时间, 24-小时 (hh:mm:ss) |
%U | 周 (00-53) 星期日是一周的第一天 |
%u | 周 (00-53) 星期一是一周的第一天 |
%V | 周 (01-53) 星期日是一周的第一天,与 %X 使用 |
%v | 周 (01-53) 星期一是一周的第一天,与 %x 使用 |
%W | 星期名 |
%w | 周的天 (0=星期日, 6=星期六) |
%X | 年,其中的星期日是周的第一天,4 位,与 %V 使用 |
%x | 年,其中的星期一是周的第一天,4 位,与 %v 使用 |
%Y | 年,4 位 |
%y | 年,2 位 |
年末将至,这里总结下2019阅读的书籍。
国父对世界格局的洞察。
软件设计哲学,如何解决软件复杂性的问题。
入门读物。
生活和工作的原则, 做到极度透明和开放,从失败中学习反思,从历史经验中学习总结原则,要有机器的系统思维,出问题后追究背后的原理,控制自己的非理性部分。
一个人穿越羌塘的游记。
精髓就是驱动经济周期的五大要素,及央行的调控手段和面临的十大挑战。MV=PQ
那个黑色的年代,读来令人唏嘘不已。
经济学入门。
很好的会计入门书,财务三大报表:资产负债表、利润表、现金流量表。
看了2,3,4章,基本能理解Spark的架构,单机模式运行了Scala的几个例子。后续技术选型的时候再深入。
经典的小书。
对RocketMQ的脉络做了一个大概的说明吧,深入细节的东西还是需要自己看代码
整本书读下来,总的来说,除了老生常谈的几大经典设计模式外,重点就是划分好系统的边界,组织好依赖关系,区分哪些属于高层的业务层,哪些是外层的细节层。
末代皇帝溥仪老师庄士敦对清末自己经历的事情的讲述。
Scala将OOP和函数式编程有机结合,动静兼备。前面的基本招式通读后,基本上可以理解。但是后面的类型系统,协变型变,定界延续目前还搞不懂,暂时略过。
过去好几天了,但是觉得还是有必要分享下这个**的错误。
五一之后的第一天。
系统遗留的一个后门接口威力无比,可以执行任何sql,包括删除所有记录,更新所有记录。
有一天,我掉进了这个坑,一切发生的毫无征兆,意识到的那一刻头脑一片空白。
为了头方便省事,为了修复一个问题,想直接走后门修改掉,so easy。
复制-粘贴-执行,操作行云流水,不假思索。然后点击相关页面,一片空白,才意识到刚才执行的sql超出了预期,紧接着是用户的反馈。。。。OMG
**!执行了一个没有加where条件的update语句。。。
第一反应,联系DBA,看能不能全表恢复,才知道数据恢复并没有想象中的那么快速,这种方式恢复可能需要XXX天!!
幸运的是,这张DB表对应的有离线表,才迅速通过其他方式把数据恢复了,业务不可用时间1-2小时。
吃一堑长一智,我觉得在这个过程中暴露了很多的问题。
这两天花了很多时间在折腾使用AOP对Spring MVC Controller进行拦截,但是没有效果。然后尝试了下Spring的HandlerInterceptor,使用起来比较简单,**也容易理解。下面是Spring Doc对HandlerInterceptor接口及相关方法的说明。
HandlerInterceptor 接口:
Workflow interface that allows for customized handler execution chains. Applications can register any number of existing or custom interceptors for certain groups of handlers, to add common pre-processing behavior without needing to modify each handler implementation.
A HandlerInterceptor gets called before the appropriate HandlerAdapter triggers the execution of the handler itself. This mechanism can be used for a large field of preprocessing aspects, e.g. for authorization checks, or common handler behavior like locale or theme changes. Its main purpose is to permit the factoring out of otherwise repetitive handler code.
Typically an interceptor chain is defined per HandlerMapping bean, sharing its granularity. To be able to apply a certain interceptor chain to a group of handlers, one needs to map the desired handlers via one HandlerMapping bean. The interceptors themselves are defined as beans in the application context, referenced by the mapping bean definition via its “interceptors” property (in XML: a of elements).
A HandlerInterceptor is basically similar to a Servlet Filter, but in contrast to the latter it allows custom pre-processing with the option to prohibit the execution of the handler itself, and custom post-processing. Filters are more powerful; for example they allow for exchanging the request and response objects that are handed down the chain. Note that a filter gets configured in web.xml, a HandlerInterceptor in the application context.
As a basic guideline, fine-grained handler-related pre-processing tasks are candidates for HandlerInterceptor implementations, especially factored-out common handler code and authorization checks. On the other hand, a Filter is well-suited for request content and view content handling, like multipart forms and GZIP compression. This typically shows when one needs to map the filter to certain content types (e.g. images), or to all requests.
preHandle 方法:
Intercept the execution of a handler. Called after HandlerMapping determined an appropriate handler object, but before HandlerAdapter invokes the handler.
DispatcherServlet processes a handler in an execution chain, consisting of any number of interceptors, with the handler itself at the end. With this method, each interceptor can decide to abort the execution chain, typically sending a HTTP error or writing a custom response.
postHandle 方法:
Intercept the execution of a handler. Called after HandlerAdapter actually invoked the handler, but before the DispatcherServlet renders the view. Can expose additional model objects to the view via the given ModelAndView.
DispatcherServlet processes a handler in an execution chain, consisting of any number of interceptors, with the handler itself at the end. With this method, each interceptor can post-process an execution, getting applied in inverse order of the execution chain.
afterCompletion 方法:
Callback after completion of request processing, that is, after rendering the view. Will be called on any outcome of handler execution, thus allows for proper resource cleanup.
Note: Will only be called if this interceptor’s preHandle method has successfully completed and returned true!
As with the postHandle method, the method will be invoked on each interceptor in the chain in reverse order, so the first interceptor will be the last to be invoked.
接下来是一个具体实例,完整代码:
public class LogInterceptor extends HandlerInterceptorAdapter {
private static Logger logger = Logger.getLogger(LogInterceptor.class);
// Intercept the execution of a handler.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if(!isValidUser()){
logger.info("Invalid user from " + request.getRemoteAddr());
response.sendRedirect("http://localhost:8888/user/error");
return false;
}
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
logger.info("Will call " + handler.toString());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("before return view page, i can get model = " + modelAndView.getModelMap().get("hello"));
}
// Callback after completion of request processing, that is, after rendering the view.
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// Our Log Func
long curTime = System.currentTimeMillis();
long startTime = (Long)request.getAttribute("startTime");
long cost = curTime - startTime;
logger.info("Call cost time " + cost + " mills!");
}
// FAKE
private boolean isValidUser(){
return true;
}
}
application context中对拦截器的配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/admin/**"/>
<bean class="com.vonzhou.learning.interceptor.LogInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
所以当我们访问对应的URL时就会触发拦截器的执行,如下:
➜ ~ curl http://localhost:8888/user/service
You are our user, Welcome!
server端的日志输出:
2016-07-23 10:25:37,374 INFO LogInterceptor:32 - Will call public java.lang.String com.vonzhou.learning.controller.UserController.serveUser(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Locale,org.springframework.ui.ModelMap)
2016-07-23 10:25:38,379 INFO LogInterceptor:38 - before return view page, i can get model = Some Data
2016-07-23 10:25:38,381 INFO LogInterceptor:50 - Call cost time 1007 mills!
我们只看该逻辑相关的代码,其他的可以忽略掉,做到有目的的阅读源码。
Service层处理HTTP请求的入口是service()方法(更深层次的需要看Tomcat的原理),分别处理HTTP不同的方法,这里我们看GET方法的逻辑。
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req, resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req, resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
进入到Spring的框架逻辑中,调用doService,然后发布一个事件,这里重点看处理请求的doService方法,FrameworkServlet中该方法为abstract,具体在DispatcherServlet中实现。
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// Expose current LocaleResolver and request as LocaleContext.
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
// Expose current RequestAttributes to current thread.
RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = null;
if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
try {
doService(request, response);
} catch (ServletException ex) {
failureCause = ex;
throw ex;
} catch (IOException ex) {
failureCause = ex;
throw ex;
} catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
} finally {
// Clear request attributes and reset thread-bound context.
LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
requestAttributes.requestCompleted();
}
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
} else {
this.logger.debug("Successfully completed request");
}
if (this.publishEvents) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause));
}
}
}
doDispatch中具体处理每个请求,包括Interceptor的拦截逻辑和Controller handler的业务逻辑。
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +
" request for [" + requestUri + "]");
}
/**
* Make framework objects available to handlers and view objects.
* 为了处理器对象,视图对象可以访问某些某些全局的对象,设置到HTTP request的属性中
* */
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
try {
doDispatch(request, response);
} finally {
}
}
/**
* 具体分发请求到handler
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
*
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
try {
ModelAndView mv;
boolean errorView = false;
try {
processedRequest = checkMultipart(request);
/**
* 获取该请求对应的 handler和interceptor(定位到了某个类)
* Determine handler for the current request.
* */
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
/**
* 获得该请求对应的更具体的handler(适配器模式,包含更多的策略和配置),如AnnotationMethodHandlerAdapter
* Determine handler adapter for the current request.
* */
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
/**
* 依次执行我们的Interceptor
* Apply preHandle methods of registered interceptors.
* */
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
/**
* 如果该preHandle方法返回了false,那么就倒序依次执行前面的Interceptor的 afterCompletion 方法,然后返回
* 所以不会执行后面的 Controller的handler方法,以及Interceptor的postHandle方法
* */
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}
/**
* 真正进入 Controller handler 进行请求处理
* Actually invoke the handler.
* */
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
//
/**
* 然后逆序依次执行我们的Interceptor的 postHandle 方法
* Apply postHandle methods of registered interceptors.
* */
if (interceptors != null) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
} catch (ModelAndViewDefiningException ex) {
logger.debug("ModelAndViewDefiningException encountered", ex);
mv = ex.getModelAndView();
} catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
/** 此时所有的Interceptor都顺利完成了(preHandle -> handler -> postHandle),
* 此时倒序依次执行所有的Interceptor的 afterCompletion方法
* Trigger after-completion for successful outcome.
* */
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
} catch (Exception ex) {
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
} catch (Error err) {
ServletException ex = new NestedServletException("Handler processing failed", err);
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
} finally {
// Clean up any resources used by a multipart request.
if (processedRequest != request) {
cleanupMultipart(processedRequest);
}
}
}
private void triggerAfterCompletion(HandlerExecutionChain mappedHandler,
int interceptorIndex,
HttpServletRequest request,
HttpServletResponse response,
Exception ex) throws Exception {
// Apply afterCompletion methods of registered interceptors.
if (mappedHandler != null) {
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);
} catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
}
反映货币供应量的指标
萨伊定律:让·巴蒂斯特·萨伊1803书《政治经济学概论》中提出的,在商业活动中最容易的就是生产商品,而比较困难的是销售产品,为什么我们不能创造一个总能卖出所有东西的社会呢?当太多的生产手段被用来生产某一种产品,而其他产品的生产却不足,此时就会发生供给过剩的问题。
1862年克莱门特·朱格拉发表《论德、英、美三国经济危机及其发展周期》,他写道:“萧条的唯一原因就是繁荣。”
边际分析法:把追加的支出和追加的收入相比较,二者相等时为临界点,也就是投入的资金所得到的利益与输出损失相等时的点。如果组织的目标是取得最大利润,那么当追加的收入和追加的支出相等时,这一目标就能达到。
1885年,西蒙·纽科姆出版了一本《政治经济学原理》,提出了交易方程,该方程后来被欧文·费雪作为货币数量论重新介绍。MV=PQ,其中
M=货币供给
V=货币流通速度
P=商品与服务的价格
Q=商品与服务的数量
经济周期4个分类维度:
术语解释
出清:出清意味着供求均衡。如果市场没有出清,那么一定存在着短缺或者过剩。经济学术语:当价格确实能使需求等于供给,以至于任何人可以在那个价格上买到他所要买的东西,或者卖掉他所要卖的东西。这时的市场就是出清的。
一般均衡理论:
一般均衡理论(General Equilibrium Theory)是理论性的微观经济学的一个分支,寻求在整体经济的框架内解释生产、消费和价格。一般均衡是指经济中存在着这样一套价格系统,它能够使
每个消费者都能在给定价格下提供自己所拥有的生产要素,并在各自的预算限制下购买产品来达到自己的消费效用极大化
每个企业都会在给定的价格下决定其产量和对生产要素的需求,来达到其利润的极大化
每个市场(产品市场和要素市场)都会在这套价格体系下达到总供给与总需求的相等(均衡)
当经济具备上述这样的条件时,就是达到一般均衡,这时的价格就是一般均衡价格。
如何描述,类比共振问题 :+1
亚当·斯密:我认为人类在探究经济周期方面已经走得很远了,但是我们还要认识到,即使最优秀的专家对处于周期性背景下的经济运行提出合理建议时,他们也从来不会有什么绝对的把握。因此,研究经济周期这个现象总会具有挑战性,无论你是一位商业经理、金融投资者、企业家或者是财政大臣。
术语解释
工资指数化:是指工人的货币工资随物价指数浮动,按照价格指数自动调节收入的一种工资制度。
消费不足论:认为经济危机的根源在于人们对消费品的需求跟不上对消费品的生产。与可以投资的数量相比,富人或节俭的人得到过多的收入,因而造成整个社会消费不足,不能结清市场上的商品。
转移支付:是指政府或企业的一种不以购买本年的商品和劳务而作的支付,即政府或企业无偿地支付给个人或下级政府,以增加其收入和购买力的费用,它是一种收入再分配的形式。在萧条时期,就业减少,个人收入减少,符合接受福利支出的人增加了,从而作为转移支付之一的福利支出增加,抑制了个人消费的减少。在膨胀时期,就业增加,个人收入增加,符合接受福利支出的人减少了,从而作为转移支付之一的福利支出减少,抑制了个人消费的增加。
把全球经济看作是一台巨大的运作的机器,其中5个巨大的活塞上下运动的速度不通,5个活塞分别是 货币,资产,房地产建设,资本性支出,存货。
货币方面的周期驱动力 | 经济方面的周期驱动力 |
---|---|
利息支付 | 资产价格,房地产价格,资本性支出,存货 |
术语解释
“财富效应”理论:股价上升会刺激消费,过度的消费反过来又会提高公司利润和就业,然后造成通货膨胀。利润的提高使股价继续上升,收入的增加又刺激着对股票的需求。然后股价的上升继续刺激消费,于是在理论的永动机上,整个循环将重新开始。
10个挑战:
1992索罗斯投机的过程:英镑在1990.10加入了ERM,英国经济处于严重的衰退期,通货膨胀率高,因为汇率水平限制,买进法国德国的债券,自然就赚了。
术语解释
空头:投机者认为某种商品、股票、债券等的价格将要下跌,于是卖出期货,希望在跌价后再买回或补进,获取差额利益。投机者在卖出后至未买回或补进前,手中并无实物,故称“空头”。
欧洲汇率机制:The European exchange rate mechanism,1979年创立,旨在限制欧盟成员国货币汇率波动的制度。 ERM为成员国货币的汇率设定固定的中心汇率,允许汇率在中心汇率上下一定的幅度内波动。
昨天遇到了一个奇怪的问题,一条在Sublime里面看着十分正常的SQL语句,执行了也是正常的,导出的Insert语句也是看着没毛病的,但是在查询出来,传递到下游的时候,说多了一个乱码。WTF,然后就开始了排查, 既然SQL语句没有问题,工程里面用到了mybatis mapper,然后就在查询出来的地方打印日志,发现的确查询出来有空格,此时甚至怀疑DB的问题(多么naive)。然后我就想为啥偏偏这个column有问题? 把这个column缓存其他的数字,是OK的,那么原本的数字有何问题呢?因为是集成测试环境无法执行SQL,每次都是工单执行的,这一次为了复现直接登mysql客户端,贴入SQL语句,在terminal上看的很清晰就是在这个数字后面多了个不可见字符。在sublime里面试了很多方法看不出来是啥,最后用vim打开,一目了然。
这个不可见字符就是 <202c>。
经验总结:
VIM可以同时打开多个文件,缓冲区(Buffer)列表记录了本次编辑会话中打开的所有文件,”缓冲区“意味着我们编辑的是文件在内存中的映像。
➜ ~ vim a.txt b.txt c.txt d.txt
此时VIM会打个多个文件(缓冲区),窗口显示的是第一个文件。
: ls
命令会列出此次打开的缓冲区列表。
1 # "a.txt" line 1
2 %a "b.txt" line 1
3 "c.txt" line 0
4 "d.txt" line 0
%
符号表名当前窗口展示的文件, #
表示上次从这个文件轮换过来。
: bnext
会切换到下一个缓冲区。
Ctrl + ^
可以在当前文件和轮换文件(#
指示的)快速切换。
:bprev
bnext
在列表中正向、反向移动一项。
:first
:blast
分别跳到列表的第一个、最后一个文件。
:buffer N
直接根据编号跳到对应的文件。
一般使用的比较少。
:bdelete N
删除对应编号的缓冲区。
POST JSON数据
curl -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' http://xxxxxx:8004/xxxx
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.