MySQL 中默认是没有开启日志记录的,所以需要手动修改配置文件开启日志。而在 MySQL 中我们需要关心的有三类日志:
The error log
错误日志,这一类日志包括了服务器运行时,包括启动和停止时的错误等信息The General Query Log
查询日志,通常是 mysqld 进行连接,断开连接,查询等等操作的日志The Slow Query Log
慢查询日志,包含了慢查询的 SQL 语句在配置中没有开启任何日志记录时,MySQL 相关的日志在 /var/log/syslog
中。之所以关注到 MySQL 的日志是因为发现服务器上 MySQL 间隔一定时间就自动重启,想要找出原因,开启日志之后观察了一段时间,初步排查可能是 clamav 执行时占用内存导致 MySQL 内存不足挂掉。现在把所有的日志都开启观察一段时间再看看。
修改 /etc/mysql/my.cnf
中的配置,开启 MySQL 服务器日志,不同发行版的配置地址可能不相同:
[mysqld_safe]
log_error=/var/log/mysql/error.log
[mysqld]
log_error=/var/log/mysql/error.log
开启查询日志
general_log_file = /var/log/mysql/mysql.log
general_log = 1
开启慢查询日志
log_slow_queries = /var/log/mysql/mysql-slow.log
long_query_time = 2
log-queries-not-using-indexes
修改保存配置之后需要重新启动 mysql
sudo service mysql restart
如果不想重启服务器,可以在运行时,通过 mysql client 登录之后 mysql -u root -p
然后执行命令来开启:
SET GLOBAL general_log = 'ON';
SET GLOBAL slow_query_log = 'ON';
ThreadLocal 线程本地变量,变量为线程独有,每个线程保存变量的副本,对副本的改动,对其他的线程而言是透明的。
一个典型的例子就是用 ThreadLocal 来保存一次请求的 Session 数据,程序的不同地方可能需要读取 Session 的内容,也要往 Session 中写入数据。
Create instance with new:
ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
Or using the withInitial()
static method:
ThreadLocal<Integer> threadLocal = Threadlocal.withInitial(() -> 1);
// 设置当前线程的线程局部变量的值
void set(Object value);
// 该方法返回当前线程所对应的线程局部变量
public Object get();
// 将当前线程局部变量的值删除
public void remove();
ThreadLocal 类允许我们创建只能被同一个线程读写的变量,通常的用法是当有一些 Object 不是线程安全,但是又想避免使用同步 访问机制时。比如 SimpleDateFormat,因此可以使用 ThreadLocal 来给每一个线程提供一个线程自己的对象。
public class Foo
{
// SimpleDateFormat is not thread-safe, so give one to each thread
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public String formatIt(Date date)
{
return formatter.get().format(date);
}
}
ThreadLocal 对象是给定线程中对象的引用,因此在服务端使用线程池时极有可能造成 classloading leaks 内存泄露 ,在使用时需要特别注意清理。ThreadLocal 持有的任何对象空间在 Java 永久堆上,即使重新部署 webapp 也不回收这部分内存,可能造成 java.lang.OutOfMemoryError 异常。
利用 HashMap,在 map 中保存了 Thread 名 -> 线程变量的关系,因此在多线程之间是隔离的,但同时也耗费了内存空间。
WizNote 已经用了好几年,虽然也一直在续费,但总感觉将死不死,基于整理这几年近 4000 条的笔记的目的,也一方面为迁移出 WizNote 的目的,研究一下 WizNote 笔记导出和备份的方法。
文中有些具体分析,基于 WizNote for Linux Version 2.5.8 版本,不同版本之间可能有些差异,务必要注意。
在 Linux 下 WizNote 笔记本地缓存放在~/.wiznote/{your-account-email-addr}/data/notes
目录下面,都是{GUID}
的方式存放,这些文件都是 zip 文件,每个文件里面包含 html , 图片以及元数据。元数据meta.xml
包含了每个 note 的相关信息,比如标题描述等等
如果需要更详细的信息,可以通过读 SQLite 的工具打开 ~/.wiznote/{your-account-email-addr}/data/index.db
文件
在导出数据之前有一些准备工作,先同步所有数据,在 Preference 中,Sync 同步选项下
否则可能导致本地缓存不是全部的笔记而造成一定程度数据丢失。
在 index.db 数据库中有两张很重要的表,WIZ_DOCUMENT
其中包括了所有笔记的信息,包括笔记的 GUID,标题等等信息,具体的表结构可以查看后文附录中内容。另外一张很重要的表是 WIZ_DOCUMENT_ATTACHMENT
其中存储了笔记附件信息。
表中重要的几列
DOCUMENT_GUID
看名字就能够猜出来这是笔记的全局唯一 ID,对应着 data 目录中存储的笔记 IDDOCUMENT_TITLE
,DOCUMENT_LOCATION
等等顾名思义就不多说DOCUMENT_GUID
形成关联,一个笔记可能会对应一个或者多个附件,这些信息都包含在附件表中所以对应的解决方案就是中 db 中读取笔记的 meta 信息,从磁盘 data 目录中找到对应的笔记,解压缩,然后将对应的附件拷贝到对应的笔记目录。
源码地址:https://github.com/einverne/ExptWizNote
表结构 WIZ_DOCUMENT
create table WIZ_DOCUMENT
(
DOCUMENT_GUID char(36) not null
primary key,
DOCUMENT_TITLE varchar(768) not null,
DOCUMENT_LOCATION varchar(768),
DOCUMENT_NAME varchar(300),
DOCUMENT_SEO varchar(300),
DOCUMENT_URL varchar(2048),
DOCUMENT_AUTHOR varchar(150),
DOCUMENT_KEYWORDS varchar(300),
DOCUMENT_TYPE varchar(20),
DOCUMENT_OWNER varchar(150),
DOCUMENT_FILE_TYPE varchar(20),
STYLE_GUID char(38),
DT_CREATED char(19),
DT_MODIFIED char(19),
DT_ACCESSED char(19),
DOCUMENT_ICON_INDEX int,
DOCUMENT_SYNC int,
DOCUMENT_PROTECT int,
DOCUMENT_READ_COUNT int,
DOCUMENT_ATTACHEMENT_COUNT int,
DOCUMENT_INDEXED int,
DT_INFO_MODIFIED char(19),
DOCUMENT_INFO_MD5 char(32),
DT_DATA_MODIFIED char(19),
DOCUMENT_DATA_MD5 char(32),
DT_PARAM_MODIFIED char(19),
DOCUMENT_PARAM_MD5 char(32),
WIZ_VERSION int64,
INFO_CHANGED int default 1,
DATA_CHANGED int default 1
);
表 WIZ_DOCUMENT_ATTACHMENT
结构
create table WIZ_DOCUMENT_ATTACHMENT
(
ATTACHMENT_GUID char(36) not null
primary key,
DOCUMENT_GUID varchar(36) not null,
ATTACHMENT_NAME varchar(768) not null,
ATTACHMENT_URL varchar(2048),
ATTACHMENT_DESCRIPTION varchar(600),
DT_INFO_MODIFIED char(19),
ATTACHMENT_INFO_MD5 char(32),
DT_DATA_MODIFIED char(19),
ATTACHMENT_DATA_MD5 char(32),
WIZ_VERSION int64
);
之前的关于 Nginx Config 的文章是当时看 Nginx 书记录下来的笔记,很大略,没有实际操作,等终究用到 location 的时候发现还是有很多需要注意的问题,比如匹配的优先顺序,比如 root 和 alias 的区别等等,所以单独拿一篇文章来记录一下目前遇到的问题,并来解决一下。
之前的文章 也简单的提到了 Nginx 配置中 location 块,这个配置能够是的针对 URL 中不同的路径分别可以配置不同的处理路径。
我当前遇到的问题就是提供 API 接口的项目和静态文件的项目是两个单独的项目,我需要 /
处理 proxy_pass
到本地一个端口,而 /resources
到本地另外一个静态资源文件的路径。
location 的语法在很多的文档教程中都被描述为:
location [ = | ~ | ~* | ^~ ] uri { ... }
=
用于非正则精确匹配 uri ,要求字符串与 uri 严格匹配,如果匹配成功,则停止向下搜索,并立即处理此请求^~
用于非正则 uri 前,Nginx 服务器找到标示 uri 和请求字符串匹配程度最高的 location 后立即使用该 location 处理请求,不再匹配 location 块的正则 url~
表示该 uri 包含正则,并且区分大小写~*
表示 uri 包含正则,不区分大小写从四个类别中就能看出来,location 使用两种表示方法,一种为不带 ~
的前缀字符,一种是带有 ~
的正则。
需要注意的是:
=
一个具体的请求 path 过来之后,Nginx 的具体匹配过程可以分为这么几步:
=
的 location,结束查找,只用该配置那么针对特定的问题:
location ^~ /resources {
alias /home/einverne/project/static/;
# autoindex on;
}
location ~ / {
proxy_pass http://localhost:9000;
}
首先对于静态文件,我们要让匹配到的第一时间就命中,所以使用了 ^~
在 location 后面接的表达式中的 slash 斜杠,可有可无,并没有影响。而对于 URL 中的尾部 /
则是,当有 /
时表示目录,没有时表示文件。当有 /
是服务器会自动去对应目录下找默认文件,而如果没有/
则会优先去匹配文件,如果找不到文件才会重定向到目录,查默认文件。
在 Location 或者其他 Nginx 配置中会经常看到 root
和 alias
,开始我以为这两者是能够混用的,但其实两者有着很大的区别。root
指令会将 location 中的部分附加到 root 定义的末尾形成一个完整的路径;而 alias
则不会包含 location 中定义的部分。
比如:
location /static {
root /var/www/app/static/;
autoindex off;
}
那么当 Nginx 寻找路径时会是:
/var/www/app/static/static/
如果这个在 static 目录的 static
目录不存在则显而易见会产生 404 错误。这是因为 location 中的 static
部分被附加到了 root 指定的路径后面,因此正确的做法是:
location /static {
root /var/www/app/;
autoindex off;
}
而对于 alias
正确的做法则是:
location /static {
alias /var/www/app/static/;
autoindex off;
}
Koa 是一个背靠 Express 的团队设计的全新的 Web 框架,旨在使之成为一个更轻量,更丰富,更加 robust 的基础框架。通过促进异步方法的使用,Koa 允许使用者抛弃 callback 调用,并且大大简化了错误处理。Koa 并没有将中间件绑定到核心代码,而是提供了一组优雅的方法让编写服务更加快速,通过很多第三方的扩展给 Koa 提供服务,从而实现更加丰富完整的 HTTP server。
Koa is a new web framework designed by the team behind Express, which aims to be a smaller, more expressive, and more robust foundation for web applications and APIs. By leveraging async functions, Koa allows you to ditch callbacks and greatly increase error-handling. Koa does not bundle any middleware within its core, and it provides an elegant suite of methods that make writing servers fast and enjoyable.
Koa 需要 node v7.6.0 及以上版本
nvm install 7
npm i koa
node my-koa-app.js
先从简单的例子说起,实现一个简单的 HTTP 服务:
const Koa = require('koa');
const app = new Koa();
const main = ctx => {
ctx.response.body = 'Hello World';
};
app.use(main);
app.listen(3000);
Koa 有一个 Context 对象,表示一次请求上下文,通过对该对象的访问来控制返回给客户端的内容。
Koa 的原生路由就需要使用者自己通过字符串匹配来维护复杂的路由,通过扩展 koa-route 可以实现更加丰富的路由选择
const route = require('koa-route');
const about = ctx => {
ctx.response.type = 'html';
ctx.response.body = '<a href="/">About</a>';
};
const main = ctx => {
ctx.response.body = 'Hello World';
};
app.use(route.get('/', main));
app.use(route.get('/about', about));
那么这样之后通过 localhost:3000/
和 localhost:3000/about
就可以访问不同内容。
对于静态资源可以使用 koa-static
const path = require('path');
const serve = require('koa-static');
const main = serve(path.join(__dirname));
app.use(main);
更多的内容可以参考文末链接。
Gulp 是基于 Node.js 的前端构建工具,可以通过 Gulp 实现前端代码编译,压缩,测试,图片压缩,浏览器自动刷新,等等,Gulp 提供了很多插件。
大概可以理解成 makefile 之于 C++, Maven 之于 Java 吧,通过定义任务简化前端代码构建过程中繁琐的过程。
全局安装
npm i gulp -g
然后在项目根目录安装一遍
npm i gulp --save-dev
一般在根目录创建 gulpfile.js
文件,用来编写 gulp task。
以压缩图片举例:
gulp.task('images', function() {
return gulp.src('src/images/**/*')
.pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))
.pipe(gulp.dest('dist/assets/img'))
.pipe(notify({ message: 'Images task complete' }));
});
再运行 gulp images
就可以将 src/images 文件夹及子文件夹图片压缩,最后存放到 dist 目录。
关于压缩图片其实有很多选择:
今天在用 mdx-server 将 mdx 文件导出 HTTP 接口时发现 mdx-server 项目并不支持类似于 GoldenDict Morphology 构词法一样的规则,所以只能够在 mdx-server 外自行处理英语单词的词形变化,搜索一圈之后发现了 NLTK。
英语中词形还原叫做 lemmatization,是将一个任何形式的单词还原为一般形式的意思。另外一个相关的概念是 stemming 也就是词干提取,抽取单词的词干或者词根。这两种方法在自然语言处理中都有大量的使用。这两种方式既有联系也有很大差异。
词干提取采用缩减方法,将词转变为词干,cats 变为 cat,将 effective 处理成 effect,而词性还原采用转变的方法,将词还原为一般形态,将 drove 变为 drive,将 driving 变为 drive
In linguistic morphology and information retrieval, stemming is the process for reducing inflected (or sometimes derived) words to their stem, base or root form—generally a written word form. The stem need not be identical to the morphological root of the word; it is usually sufficient that related words map to the same stem, even if this stem is not in itself a valid root. Algorithms for stemming have been studied in computer science since the 1960s. Many search engines treat words with the same stem as synonyms as a kind of query expansion, a process called conflation.
Stemming programs are commonly referred to as stemming algorithms or stemmers.
Lemmatisation (or lemmatization) in linguistics, is the process of grouping together the different inflected forms of a word so they can be analysed as a single item.
In computational linguistics, lemmatisation is the algorithmic process of determining the lemma for a given word. Since the process may involve complex tasks such as understanding context and determining the part of speech of a word in a sentence (requiring, for example, knowledge of the grammar of a language) it can be a hard task to implement a lemmatiser for a new language.
In many languages, words appear in several inflected forms. For example, in English, the verb ‘to walk’ may appear as ‘walk’, ‘walked’, ‘walks’, ‘walking’. The base form, ‘walk’, that one might look up in a dictionary, is called the lemma for the word. The combination of the base form with the part of speech is often called the lexeme of the word.
Lemmatisation is closely related to stemming. The difference is that a stemmer operates on a single word without knowledge of the context, and therefore cannot discriminate between words which have different meanings depending on part of speech. However, stemmers are typically easier to implement and run faster, and the reduced accuracy may not matter for some applications.
The NLTK Lemmatization 方法基于 WordNet 内置的 morphy function.
>>> from nltk.stem import WordNetLemmatizer
>>> wordnet_lemmatizer = WordNetLemmatizer()
>>> wordnet_lemmatizer.lemmatize(‘dogs’)
u’dog’
>>> wordnet_lemmatizer.lemmatize(‘churches’)
u’church’
>>> wordnet_lemmatizer.lemmatize(‘aardwolves’)
u’aardwolf’
>>> wordnet_lemmatizer.lemmatize(‘abaci’)
u’abacus’
>>> wordnet_lemmatizer.lemmatize(‘hardrock’)
‘hardrock’
>>> wordnet_lemmatizer.lemmatize(‘are’)
‘are’
>>> wordnet_lemmatizer.lemmatize(‘is’)
‘is’
lemmatize() 方法有第二个 pos 参数,可以传入 n
表示 noun,或者 v
表示 verb,或者其他的形容词等等,提高准确度。
更多的 doc 可以参考 API。
如果要说 AngularJS 是什么,那么用这些关键词就能够定义,单页面,适合编写大量 CRUD 操作,MVC
AngularJS 有如下特性:
安装 AngularJS 之前需要确保 Node.js 和 npm 安装。AngularJS 需要 node.js 的 8.x 或者 10.x 版本。
以前不熟悉 nodejs 的时候为了简单的使用 npm 所以找了 apt 方式安装的方法,这里如果要学习推荐通过 nvm 来安装,可以类似于 pyenv 一样来安装多个版本的 nodejs,并且可以非常方便的管理不同的环境。安装过程比较简单,直接去官方 repo 即可。
简单使用
nvm install node # "node" 是最新版本的别名,所以这行命令是安装最新的 node
nvm install v10.13.0
如果要查看可用版本可以使用
nvm ls-remote
启用并使用最新版本
nvm use v10.13.0
这时在查看 npm 的位置 whereis npm
就会发现在 ~/.nvm/versions
目录下了。
Angular CLI 用来创建项目,创建应用和库代码,并可以执行多种开发任务,测试,打包,发布等等
npm install -g @angular/cli
在创建开发环境时还会选择一些特外的特性
ng new angularjs-demo
Angular 自带一个开发服务器,可以在本地轻松构建和调试,进入工作空间 (angularjs-demo)
cd angularjs-demo
ng serve --open
更加详细的可以参考官网 quickstart
在学完官网的 Hero demo 之后对 AngularJS 有了一个基本印象,对于基本的 MVC,在 AngularJS 中通过学习 Java 中,定义好 Service 通过依赖注入到模板和 Component 中。
组件和模板定义 Angular 的视图,然后在视图中注入 Service 提供服务。
模块称为 NgModule,存放一些内聚的代码和模板,每个 Angular 都至少有一个 NgModule 类,根模板,习惯上命名为 AppModule,位于 app.module.ts
。
在 1.x 时代,可以使用如下代码定义模块
angular.module('myApp', []);
组件控制屏幕上一小片区域,在类中定义组件的逻辑,为视图提供支持。@Component 装饰器会指出紧随其后的那个类是个组件类,并为其指定元数据。
每一个 Component 由以下部分组成:
AngularJS 有一套自己的模板语法,这个需要熟悉一下。
AngularJS 支持双向数据绑定,大致语法如下:
从 Component 到 DOM
[property]="value"
从 DOM 到 Component
(event) = "handler"
[(ng-model)] = "property"
Angular 将组件和服务区分,提高模块性和复用性,服务应该提供某一类具体的功能。Angular 通过依赖注入来将逻辑和组件分离。服务可以被多个 Component 共用。
在 Angular 1.x 时代,Controller 也是很重要的一个部分,一个 Controller 应该是最简单,并且只对一个 view 负责的角色。如果要在 Controller 之间共享信息那么可以使用上面提及的 Service。
Directive 一般被叫做指令,Angular 中有三种类型的指令:
Angular2 中,属性指令至少需要一个带有 @Directive
装饰器修饰的控制器类,官网有一个很好的 highlight.directive.ts
例子。
数据模型对象 $scope
是一个简单的 Javascript 对象,其属性可以被视图,或者 Controller 访问。双向数据绑定意味着如果视图中数值发生变化,数据 Model 会根据脏检查意识到该变化,而数据 Model 发生变化,视图也会依据变化重新渲染。
简单的数据绑定
<input ng-model="person.name" type="text" placeholder="Yourname">
<h1>Hello\{\{ person.name \}\}</h1>
Angular 有一套自己的 HTML 标记语法,比如在 app.component.ts
中定义
title = '这是一个 AngularJS-demo 演示';
那就可以通过类似于模板的语法来访问该变量:
Welcome to !
又比如条件语句 ngIf
,后面的 isLogin 是在 class 中定义好的 boolean 变量:
<div *ngIf="isLogin">Hi </div>
或者循环 ngFor
,for 后面接一个表达式
*ngFor = "let variable of variablelist"
比如:
<a *ngFor="let nav of navs"></a>
本文 demo 源码: https://gitlab.com/einverne/angularjs-demo
Aviator 是一个轻量级、高性能的 Java 表达式执行引擎,它动态地将表达式编译成字节码并运行。
特性:
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>{version}</version>
</dependency>
最简单直观的使用:
import com.googlecode.aviator.AviatorEvaluator;
public class TestAviator {
public static void main(String[] args) {
Long result = (Long) AviatorEvaluator.execute("1+2+3");
System.out.println(result);
}
}
更加复杂的使用方式可以参考 wiki,文档已经足够详细,不再重复。
主要接口
AviatorEvaluator 最重要的方法:
execute(String expression)
execute(String expression, Map<String,Object> env)
execute(String expression, Map<String,Object> env, boolean cached)
这些方法用来执行表达式,并获取结果。围绕这个方法也有可以传入变量的 exec
方法
exec(String expression, Object... values)
自定义方法
主要可以分为以下几大类,包括数学计算相关,字符串处理相关
数学计算
MathAbsFunction
MathCosFunction
MathLog10Function
MathLogFunction
MathPowFunction
MathRoundFunction
MathSinFunction
MathSqrtFunction
MathTanFunction
字符串相关
StringContainsFunction
StringEndsWithFunction
StringIndexOfFunction
StringJoinFunction
StringLengthFunction
StringReplaceAllFunction
StringReplaceFirstFunction
StringSplitFunction
StringStartsWithFunction
StringSubStringFunction
序列相关方法
SeqCompsitePredFunFunction
SeqCountFunction # count(list) 长度
SeqFilterFunction # 过滤
SeqIncludeFunction # 是否在序列中
SeqMakePredicateFunFunction
SeqMapFunction # 遍历序列
SeqPredicateFunction
SeqReduceFunction # 求和
SeqSortFunction
SeqEveryFunction # 每个都满足
SeqNotAnyFunction # 不在
SeqSomeFunction # 序列中一个元素满足
额外的方法
BinaryFunction
BooleanFunction
Date2StringFunction
DateFormatCache
DoubleFunction
LongFunction
NowFunction
PrintFunction
PrintlnFunction
RandomFunction
StrFunction
String2DateFunction
SysDateFunction
FakeCodeGenerator
演示将中缀表达式转换为后缀表达式
用了近两年 iOS,中途也因为学习需要下载了很多的字典,但是没想到的是 iOS 竟然内置有版权的字典。
之前在下拉搜索框 (Spotlight) 中输入单词偶然会见到单词释义,但是也没有多想,可没想到原来长按选中之后的 “Look up” 竟然有查词的功能。后来查了一下原来 iOS 和 Mac 自带 dictionary 的应用。而 iOS 从 iOS 9 开始就已经有了这功能,iOS 9 中是长按高亮之后在弹出的菜单中选择 Define,而更新到 iOS 10 以后有了一些变化。
字典在 “Setting -> General -> Dictionary” 菜单中,然后选择适当的词典下载到设备中就能够使用。iOS 和 Mac 为不同国家不同语言用户提供了非常多的版权字典,虽然有些词典有些瑕疵但是完全不影响使用。系统自带的词典见附录。
查词有两种,第一种比较方便,在选中单词后在弹出的上下文菜单中选择“Look Up”,系统会弹出查词结果。
第二中就是在 HOME 下拉然后在搜索框中输入想要查找的单词,在下面的结果中会有字典的结果。
当然如果想要有自定义更好的字典那就要使用之前提到的 Goldendict 了。