小不的笔记

时间之外的往事

dubbo 接口调试直连服务提供者

dubbo 直连提供者

在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直联方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。 dubbo direct

通过 XML 配置

如果是线上需求需要点对点,可在 中配置 url 指向提供者,将绕过注册中心,多个地址用分号隔开,配置如下 [1]:

1
<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />

通过 -D 参数指定

在 JVM 启动参数中加入-D参数映射服务地址 [2],如: java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890

通过文件映射

如果服务比较多,也可以用文件映射,用 -Ddubbo.resolve.file 指定映射文件路径,此配置优先级高于 中的配置 [3],如: java -Ddubbo.resolve.file=xxx.properties 然后在映射文件 xxx.properties 中加入配置,其中 key 为服务名,value 为服务提供者 URL: com.alibaba.xxx.XxxService=dubbo://localhost:20890 注意 为了避免复杂化线上环境,不要在线上使用这个功能,只应在测试阶段使用。


1.0.6 及以上版本支持 key 为服务名,value 为服务提供者 url,此配置优先级最高,1.0.15 及以上版本支持 1.0.15 及以上版本支持,2.0 以上版本自动加载 ${user.home}/dubbo-resolve.properties文件,不需要配置 原始链接: http://dubbo.apache.org/zh-cn/docs/user/demos/explicit-target.html

git clone error

要在 github 上下载一个朋友做的项目,试了几次都报如下错误,但是在海外的 VPS 上秒 clone 完成,所以猜测是原始 resposity 过大,网络又不好导致的。

1
2
3
4
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed

解决方案:

节流:

只 clone 每个文件最新的一个提交,然后再 fetch 完整的提交记录。

1
2
3
$ git clone http://github.com/large-repository --depth 1
$ cd large-repository
$ git fetch --unshallow

开源:

如果有梯子,为 git 配置加速的梯子也能解决问题。

1
git config --global http.proxy http://127.0.0.1:4411

或者

1
2
3
export http_proxy=http://127.0.0.1:4411 # 配置http访问的
export https_proxy=http://127.0.0.1:4411 # 配置https
export all_proxy=http://127.0.0.1:4411 # 配置http和https访问

其它方式

  1. 使用 SSH 的方式 Use SSH 使用该方式,需要在 github settings -> SSH and GPG keys 里上传一个 SSH 公钥。 上传 SSH 公钥
1
$ git clone git@github.com:large-repository
  1. 下载 zip 包 Download Zip 通过该方式能下载master 分支下的完整源码,但是没有任何 git 提交记录。 其它下载方式

完成无用的方案:

该设置只影响到 push,对 clone 毫无帮助.

1
git config –-global http.postBuffer 524288000

Tomcat与UTF-8,告别中文乱码

简介

UTF-8是网页应用中最常用的字符编码。它支持世界上正在使用的所有语言,包括中日韩。 本文我们会展示所有的配置以确保在Tomcat中使用 UTF-8。

连接器(Connector) 配置

一个连接器在指定的端口上监听连接。我们需要确保我们所有的连接器都使用UTF-8来编码请求。 给TOMCAT_ROOT/conf/server.xml里的所有的连接器添加一个参数 URIEncoding=”UTF-8″ 。

1
2
3
4
<Connector URIEncoding="UTF-8"  port="8080"  redirectPort="8443"  connectionTimeout="20000"  protocol="HTTP/1.1"/>

<Connector
URIEncoding="UTF-8" port="8009" redirectPort="8443" protocol="AJP/1.3"/>

字符集过滤器

配置过连接器之后, 我们该强制网页程序使用UTF-8去处理所有的请求与响应。 如果我们在使用Spring,直接注册CharacterEncodingFilter到web.xml中, 同时要确保该过滤器是web.xml中的第一个过滤器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

不然的话就需要自己定义一个CharacterSetFilter的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CharacterSetFilter implements Filter {
// ...
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain next) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
response.setCharacterEncoding("UTF-8");
next.doFilter(request, response);
}
// ...
}

然后我们把这个过滤器添加到web.xml里:

1
2
3
4
5
6
7
8
9
<filter>
<filter-name>CharacterSetFilter</filter-name>
<filter-class>com.baeldung.CharacterSetFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>CharacterSetFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Servlet 页编码

我们的网页程序另一部分需要配置的是Servlet 页。 确保Servlet使用UTF-8的最好方法是添加这个标签到每个JSP页的最头部:

1
<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>

HTML页编码

Servlet页编码是告诉JVM如何处理字符,HTML页编码是告诉浏览器如何处理字符。 我们应该添加这个标签 到所有HTML的 head 区域。

1
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />

参考链接: Making Tomcat UTF-8-Ready How to use UTF-8 everywhere

如何清空 DNS 缓存

Windows

1
ipconfig /flushdns

Linux (根据实际运行的服务执行相应的命令)

1
2
/etc/init.d/named restart
/etc/init.d/nscd restart

macOS

1
sudo killall -HUP mDNSResponder

例外:

OSX Yosemite 10.10.0 – 10.10.3

1
sudo discoveryutil mdnsflushcache

OSX Leopard Snow Leopard 10.5 – 10.6

1
sudo dscacheutil -flushcache

Maven 最佳实践

添加依赖

如果需要给项目添加依赖,只需要在pom.xml的<dependencies>节点下添加相关依赖就可以。

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.24.0-GA</version>
</dependency>
</dependencies>

scope 值有 compile、provided、runtime、system、test、import。默认为compile。

provided

对于开始时需要的依赖,而在运行时运行环境已经提供了的依赖,就需要额外的指定scope为provided。这样在打包时就不会把依赖打包在内。

1
2
3
4
5
6
 <dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>

runtime

在运行的时候才会依赖,在编译的时候不会依赖。比较典型的场景是JDBC driver 和 DataSource 连接池。开发的时候没办法直接调用相关的类但是运行的时候又存在。

1
2
3
4
5
6
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
<scope>runtime</scope>
</dependency>

system

有些jar包,nexus 公共库没有,而我们由于各种原因不想使用私服,我们可以指定scope为system, 然后配合systemPath指定jar位置。

1
2
3
4
5
6
7
<dependency>
<groupId>org.xobo.local</groupId>
<artifactId>myartifact</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/lib/myartifact_1.0.0.jar</systemPath>
</dependency>

同时需要配置includeSystemScope属性

1
2
3
4
5
6
7
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>

pom.basedir表示项目根目录,即包含pom.xml文件的目录。 还有一个更老的写法basedir、project.basedir 已经被标记为弃用了。

Maven Model Builder – Introduction https://maven.apache.org/ref/3-LATEST/maven-model-builder/index.html

test

在测试范围有效,在编译与打包的时候都不会使用这个依赖。

1
2
3
4
5
6
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>

管理版本依赖

使用 dependencyManagement 管理Jar包版本。dependencyManagement 只是一个针对依赖的声明,并不真正的添加依赖。等真正添加依赖时,可以为这些依赖的某些属性提供默认值,比如版本号。 Java项目中三方Jar包非常的丰富,丰富的同时带来了依赖的混乱,通过 dependencyManagement 可以快速的统一 Jar 包版本。 以 javassist 为例,项目中依赖多个 javassist 版本。 在 pom.xml 增加以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.24.0-GA</version>
</dependency>
</dependencies>
</dependencyManagement>

这个时候 javassist 就会被统一成 3.24.0-GA 版本了,但是还有一个漏网之鱼。 这是因为 javassist 从 3.13.x 开始变了 groupId。 我们需要单独排除这个依赖。 这样 javassist 的版本就彻底的统一了。

生成 source 和 doc

1
mvn source:jar

如果希望在 package install 或 deploy时自动生成.需要给 pom.xml 的 repositories 节点添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>

本地 jar 包

0x01. 上传至 Maven 私服(nexus)。 0x02. 创建项目内 Maven 库。 1 给 pom.xml 的 repositories 节点添加如下配置:

1
2
3
4
<repository>
<id>project</id>
<url>file://${project.basedir}/repo</url>
</repository>

2 执行如下命令

1
2
3
mvn install:install-file -DlocalRepositoryPath=repo -DcreateChecksum=true \
-Dpackaging=jar -Dfile=[your-jar] -DgroupId=[...] \
-DartifactId=[...] -Dversion=[...]

也可以使用该脚本自动解析 jar 包名称并安装到项目 repo install-to-project-repo

指定 JDK 版本

可以通过属性配置

1
2
3
4
5
<properties>  
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>

也可以给 pom.xml 的 build 节点添加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
    <version>3.7.0</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>

最终版pom.xml

pom 的依赖是一棵树,单纯看一个pom.xml什么也看不出来这个时候我们就需要打印出依赖树:

1
mvn dependency:tree

pom.xml不仅仅是依赖,还是涉及到各个配置之间的继承覆盖,这个时候就需要查看最终的pom.xml, 在pom.xml所在的目录执行命令:

1
mvn help:effective-pom

这样就可以输出一个完整的 pom.xml。 如果希望能输出到一个xml文件里:

1
mvn help:effective-pom -Doutput=all.xml

pom.xml 片段

My pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">


<modelVersion>4.0.0</modelVersion>
<!-- groupId 统一为域名倒序, 可以加子域但不要自创域名。 -->
<groupId>org.xobo</groupId>
<artifactId>maven-pom-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>

<name>maven-pom-sample</name>
<url>http://www.ezhiyang.com</url>

<properties>
<!-- 统一属性, 可根据实际情况修改 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>

<!-- jar包版本属性, 属性值建议命名为 artifactId.version -->
<commons-dubbo-api.version>0.0.11-SNAPSHOT</commons-dubbo-api.version>
<sendcloud-sdk.version>1.0.0</sendcloud-sdk.version>
</properties>

<dependencies>

<dependency>
<groupId>org.xobo.3rd</groupId>
<artifactId>sendcloud-sdk</artifactId>
</dependency>

<dependency>
<groupId>org.xobo</groupId>
<artifactId>commons-dubbo-api</artifactId>
</dependency>
</dependencies>

<!-- 使用 dependencyManagement 统一项目 jar 包版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.xobo</groupId>
<artifactId>commons-dubbo-api</artifactId>
<version>${commons-dubbo-api.version}</version>
</dependency>

<!-- 手动上传至 nexus 的 jar 包,groupId 固定为 org.xobo.3rd -->
<dependency>
<groupId>org.xobo.3rd</groupId>
<artifactId>sendcloud-sdk</artifactId>
<version>${sendcloud-sdk.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<resources>
<!-- 打包的时候把 xml 等文件打入 jar 包, 可根据实际项目需要增删条目 -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.html</include>
<include>**/*.xsd</include>
<include>**/*.schemas</include>
<include>**/*.handlers</include>
<include>**/*.properties</include>
<include>**/*.png</include>
<include>**/*.jpg</include>
<include>**/*.gif</include>
<include>**/*.css</include>
<include>**/*.js</include>
<include>**/*.bpmn</include>
<include>**/*.bpmn2</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.html</include>
<include>**/*.xsd</include>
<include>**/*.schemas</include>
<include>**/*.handlers</include>
<include>**/*.properties</include>
<include>**/*.png</include>
<include>**/*.jpg</include>
<include>**/*.gif</include>
<include>**/*.css</include>
<include>**/*.js</include>
<include>**/*.bpmn</include>
<include>**/*.bpmn2</include>
</includes>
</resource>
</resources>
</build>


<!-- 使用私有 nexus 服务器, 根据实际情况修改或删除 -->
<repositories>
<repository>
<id>ezhiyang</id>
<url>http://localhost:8081/nexus/content/groups/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>ezhiyang</id>
<url>http://localhost:8081/nexus/content/groups/public/</url>
</pluginRepository>
</pluginRepositories>
<distributionManagement>
<repository>
<id>ezhiyang-deployment</id>
<url>http://localhost:8081/nexus/content/repositories/releases</url>
</repository>
<snapshotRepository>
<id>ezhiyang-deployment</id>
<url>http://localhost:8081/nexus/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
</project>

BDF2’s dependencyManagement

MySQL CheatSheet

连接

1
2
3
4
5
6
mysql -h <host> -u <user> -p<passwd>
mysql -h <host> -u <user> -p
Enter password: ********
mysql -u user -p
mysql
mysql -h <host> -u <user> -p <Database>

查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT * FROM table
SELECT * FROM table1, table2, ...
SELECT field1, field2, ... FROM table1, table2, ...
SELECT ... FROM ... WHERE condition
SELECT ... FROM ... WHERE condition GROUP BY field
SELECT ... FROM ... WHERE condition GROUP BY field HAVING condition2
SELECT ... FROM ... WHERE condition ORDER BY field1, field2
SELECT ... FROM ... WHERE condition ORDER BY field1, field2 DESC
SELECT ... FROM ... WHERE condition LIMIT 10
SELECT DISTINCT field1 FROM ...
SELECT DISTINCT field1, field2 FROM ...

SELECT ... FROM t1 JOIN t2 ON t1.id1 = t2.id2 WHERE condition
SELECT ... FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 WHERE condition
SELECT ... FROM t1 JOIN (t2 JOIN t3 ON ...) ON ...
SELECT ... FROM t1 JOIN t2 USING(id) WHERE condition

条件

1
2
3
4
5
6
7
8
9
field1 = value1
field1 <> value1
field1 LIKE 'value _ %'
field1 IS NULL
field1 IS NOT NULL
field1 IN (value1, value2)
field1 NOT IN (value1, value2)
condition1 AND condition2
condition1 OR condition2

数据处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
INSERT INTO table1 (field1, field2, ...) VALUES (value1, value2, ...)
INSERT table1 SET field1=value_1, field2=value_2 ...

LOAD DATA INFILE '/tmp/mydata.txt' INTO TABLE table1
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' ESCAPED BY '\\'

DELETE FROM table1 / TRUNCATE table1
DELETE FROM table1 WHERE condition
-- join:
DELETE FROM table1, table2 WHERE table1.id1 = table2.id2 AND condition

UPDATE table1 SET field1=new_value1 WHERE condition
-- join:
UPDATE table1, table2 SET field1=new_value1, field2=new_value2, ...
WHERE table1.id1 = table2.id2 AND condition

浏览

1
2
3
4
5
6
7
8
9
10
11
SHOW DATABASES
SHOW TABLES
SHOW FIELDS FROM table / SHOW COLUMNS FROM table / DESCRIBE table / DESC table / EXPLAIN table
SHOW CREATE TABLE table
SHOW CREATE TRIGGER trigger
SHOW TRIGGERS LIKE '%update%'
SHOW PROCESSLIST
KILL process_number
SELECT table_name, table_rows FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '**yourdbname**';
$ mysqlshow
$ mysqlshow database

数据库 CRUD

1
2
3
4
5
6
CREATE DATABASE [IF NOT EXISTS] mabase [CHARACTER SET charset] [COLLATE collation]
CREATE DATABASE mabase CHARACTER SET utf8
DROP DATABASE mabase
USE mabase

ALTER DATABASE mabase CHARACTER SET utf8

表 CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
CREATE TABLE table (field1 type1, field2 type2, ...)
CREATE TABLE table (field1 type1 unsigned not null auto_increment, field2 type2, ...)
CREATE TABLE table (field1 type1, field2 type2, ..., INDEX (field))
CREATE TABLE table (field1 type1, field2 type2, ..., PRIMARY KEY (field1))
CREATE TABLE table (field1 type1, field2 type2, ..., PRIMARY KEY (field1, field2))
CREATE TABLE table1 (fk_field1 type1, field2 type2, ...,
FOREIGN KEY (fk_field1) REFERENCES table2 (t2_fieldA)
[ON UPDATE] [CASCADESET NULLRESTRICT]
[ON DELETE] [CASCADESET NULLRESTRICT])
CREATE TABLE table1 (fk_field1 type1, fk_field2 type2, ...,
FOREIGN KEY (fk_field1, fk_field2) REFERENCES table2 (t2_fieldA, t2_fieldB))
CREATE TABLE table IF NOT EXISTS (...)

CREATE TABLE new_tbl_name LIKE tbl_name
[SELECT ... FROM tbl_name ...]

CREATE TEMPORARY TABLE table (...)

CREATE table new_table_name as SELECT [ *column1, column2 ] FROM table_name

DROP TABLE table
DROP TABLE IF EXISTS table
DROP TABLE table1, table2, ...
DROP TEMPORARY TABLE table

ALTER TABLE table MODIFY field1 type1
ALTER TABLE table MODIFY field1 type1 NOT NULL ...
ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1
ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1 NOT NULL ...
ALTER TABLE table ALTER field1 SET DEFAULT ...
ALTER TABLE table ALTER field1 DROP DEFAULT
ALTER TABLE table ADD new_name_field1 type1
ALTER TABLE table ADD new_name_field1 type1 FIRST
ALTER TABLE table ADD new_name_field1 type1 AFTER another_field
ALTER TABLE table DROP field1
ALTER TABLE table ADD INDEX (field);
ALTER TABLE table ADD PRIMARY KEY (field);

-- Change field order:
ALTER TABLE table MODIFY field1 type1 FIRST
ALTER TABLE table MODIFY field1 type1 AFTER another_field
ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1 FIRST
ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1 AFTER another_field

ALTER TABLE old_name RENAME new_name;

主键

1
2
3
4
5
6
 CREATE TABLE table (..., PRIMARY KEY (field1, field2))
CREATE TABLE table (..., FOREIGN KEY (field1, field2) REFERENCES table2 (t2_field1, t2_field2))
ALTER TABLE table ADD PRIMARY KEY (field);
ALTER TABLE table ADD CONSTRAINT constraint_name PRIMARY KEY (field, field2);
### create/modify/drop view
CREATE VIEW view AS SELECT ... FROM table WHERE ...

事件调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
-- 事件调度状态
SHOW VARIABLES LIKE 'event_scheduler'
-- 查看事件任务
SHOW EVENTS ;
-- 开启事件调度
SET GLOBAL event_scheduler = ON;
-- 关闭事件调度
SET GLOBAL event_scheduler = OFF;
-- 禁用指定事件
ALTER EVENT myevent DISABLE;
-- 启用指定事件
ALTER EVENT myevent ENABLE;
-- 事件重命名
ALTER EVENT myevent RENAME TO yourevent;
-- 创建事件
CREATE EVENT myevent ON SCHEDULE EVERY 1 MINUTE
DO
INSERT INTO messages(message,created_at)
VALUES('Test ALTER EVENT statement',NOW());

-- 修改事件调度
ALTER EVENT myevent ON SCHEDULE EVERY 2 MINUTE;

-- 修改事件体
ALTER EVENT myevent
DO
INSERT INTO messages(message,created_at)
VALUES('Message from event',NOW());

-- 创建事件(多行)
DELIMITER $$

CREATE EVENT myevent
ON SCHEDULE
EVERY 1 MINUTE
DO
BEGIN
INSERT INTO messages(message,created_at)
VALUES('Test ALTER EVENT statement',NOW());
END $$

DELIMITER ;

-- 删除事件
DROP EVENT IF EXISTS myevent;

权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 CREATE USER 'user'@'localhost' IDENTIFIED BY 'password';

GRANT ALL PRIVILEGES ON base.* TO 'user'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, DELETE ON base.* TO 'user'@'localhost' IDENTIFIED BY 'password';
REVOKE ALL PRIVILEGES ON base.* FROM 'user'@'host'; -- one permission only
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'user'@'host'; -- all permissions

SET PASSWORD = PASSWORD('new_pass')
SET PASSWORD FOR 'user'@'host' = PASSWORD('new_pass')
SET PASSWORD = OLD_PASSWORD('new_pass')

DROP USER 'user'@'host'

-- MySQL 分配权限时, 只支持库名的模糊匹配不支持表名的模糊匹配,可以用下面的 SQL 来生成批量的授权语句
SELECT CONCAT('GRANT update, insert ON myDB.', TABLE_NAME, ' to ''user'';') grantSQL
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'myDB' and TABLE_NAME like 'MyTable%';

忘记 root 密码

1
2
3
4
5
6
$ /etc/init.d/mysql stop
$ mysqld_safe --skip-grant-tables &
$ mysql # on another terminal
mysql> UPDATE mysql.user SET password=PASSWORD('nouveau') WHERE user='root';
## Kill mysqld_safe from the terminal, using Control + \
$ /etc/init.d/mysql start

异常关闭后,表修复

1
2
mysqlcheck --all-databases
mysqlcheck --all-databases --fast

加载数据

1
2
3
mysql> SOURCE input_file
$ mysql database < filename-20120201.sql
$ cat filename-20120201.sql mysql database

参考链接

https://en.wikibooks.org/wiki/MySQL/CheatSheet

High Sierra升级后记 eclipse 菜单变灰

eclipse菜单都变成灰色的

我用英文的系统语言没问题,同事使用中文系统语言出现 eclipse 菜单全是灰色的,没办法使用的问题。

方法一:

修改 Eclipse.app/Contents/Info.plist 文件
1. 把 CFBundleDevelopmentRegion 对应的 value 值由 English 改为 en。
2. 把 CFBundleLocalizations 对应的 array 里元素除了 en 其它都删掉。

详情参考 eclipse.org

方法二:

在eclipse.ini文件中的-product后加入如下内容(注意要换行)如:

1
2
3
4
org.eclipse.epp.package.jee.product
-nl
en_US
--launcher.defaultAction


configuration\config.ini文件添加:osgi.nl=en_US
详情参考 max.cn

telnet 被移除

使用 brew 安装 telnet.

1
2
brew tap theeternalsw0rd/telnet                                                                                                                                                                                                                   
brew install telnet

dubbo 接口调试

公司内部有多个项目中并使用 dubbo 相互间提供服务,每次相关接口调试与联调都是一种折磨,在有多个服务提供方时,不能确定这次调用由哪个提供方处理。在长期的摸索中找到如下调试方法:

1. 封装为 HTTP 接口

把接口封装为 HTTP 服务,就变成了 HTTP 接口的调试; 优点: 该方法实现相对简单,也是我调试自己写的接口最常用的方法。 缺点: 1. 每个接口都要实现一个 HTTP 接口(可以写个工具自动把 dubbo 接口转化为 HTTP 接口的工具); 2. HTTP 接口不能完整的模拟 dubbo 接口的调用环境。做为服务提供方,代码在执行时是没有 HttpRequest/HttpResponse 的。

2. 构建相对独立 dubbo 的环境

dubbo 联调时,最头疼的是经常会有别的服务提供方把调用处理掉了,所以要构建一个可以指定服务提供方的环境。 可以新启一个注册中心(zookeeper),跟小伙伴都连上这个注册中心,在这个二人世界里开心的联调。 dubbo 的开发者也考虑到调试的问题提供了相应的配置

2.1 文件映射

推荐使用文件映射的方式.

2.2 修改注册方式

节点

属性

类型

描述

\dubbo:dubbo:registry

register

boolean

是否向此注册中心注册服务,如果设为false,将只订阅,不注册。开发环境建议设置为 false

\dubbo:reference

url

string

点对点直连服务提供者地址,将绕过注册中心。

示例

1
2
3
4
5
<!-- 不提供服务 -->
<dubbo:registry address="zookeeper://${zookeeper}" register="false"/>

<!-- 直连配置的提供者 -->
<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />

3. 使用 telnet 调试接口

偶然间发现 dubbo 可以通过 telnet 调试,感觉圣光在照耀着我。 invoke XxxService.xxxMethod({"prop": "value"})

1
2
> telnet localhost 20880
dubbo> invoke org.xobo.say("xobo", {"welcome": "hello world!"})

4. 中文乱码

通过 telnet 会发现 dubbo 序列化数据默认编码是 GBK。 可以通过设置或节点的charset属性来修改默认编码。 macOS 自带的 telnet 对中文的输入和输出都不友好,又写了一个简单的 Python 脚本来代替默认的 telnet 客户端。

5. No such method

参数是对象的时候,会报居然报错:No such method xx in xxx, 需要添加一个class属性来指定参数类型。

1
invoke org.xobo.say({"class":"org.xobo.HelloDTO", "welcome": "hello world!"})

简易 Telnet 客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/local/bin/python3
"""用于替代 macOS 下的 telnet 以方便调试 dubbo 接口
Example:
./telent_dubbo.py localhost 20880

"""

import sys
import telnetlib
import platform
if platform.system() != "Windows":
try:
import readline
except ImportError:
print("May need install package readline")

host = sys.argv[1]
port = sys.argv[2]
encoding = "GBK"

if len(sys.argv)==4:
encoding = sys.argv[3]


tn = telnetlib.Telnet(host, port)
cmd = None
def remove_last_line_from_string(s):
return s[:s.rfind('\n')]
while cmd != "exit":
cmd = input("> ").strip()
tn.write(cmd.encode(encoding) + b"\n")
if cmd != "exit":
result = tn.read_until(b"dubbo>").decode(encoding)
result = remove_last_line_from_string(result);
print(result)

Telnet命令参考手册

(+) (#) Dubbo2.0.5以上版本服务提供端口支持telnet命令, 使用如: telnet localhost 20880 或者: echo status nc -i 1 localhost 20880 telnet命令可以扩展,参见:扩展参考手册第6条。 status命令所检查的资源也可以扩展,参见:扩展参考手册第5条。

ls

(list services and methods) ls 显示服务列表。 ls -l 显示服务详细信息列表。 ls XxxService 显示服务的方法列表。 ls -l XxxService 显示服务的方法详细信息列表。

ps

(print server ports and connections) ps 显示服务端口列表。 ps -l 显示服务地址列表。 ps 20880 显示端口上的连接信息。 ps -l 20880 显示端口上的连接详细信息。

cd

(change default service) cd XxxService 改变缺省服务,当设置了缺省服务,凡是需要输入服务名作为参数的命令,都可以省略服务参数。 cd / 取消缺省服务。

pwd

(print working default service) pwd 显示当前缺省服务。

trace

trace XxxService 跟踪1次服务任意方法的调用情况。 trace XxxService 10 跟踪10次服务任意方法的调用情况。 trace XxxService xxxMethod 跟踪1次服务方法的调用情况 trace XxxService xxxMethod 10 跟踪10次服务方法的调用情况。

count

count XxxService 统计1次服务任意方法的调用情况。 count XxxService 10 统计10次服务任意方法的调用情况。 count XxxService xxxMethod 统计1次服务方法的调用情况。 count XxxService xxxMethod 10 统计10次服务方法的调用情况。

invoke

invoke XxxService.xxxMethod({"prop": "value"}) 调用服务的方法。 invoke xxxMethod({"prop": "value"}) 调用服务的方法(自动查找包含此方法的服务)。

status

status 显示汇总状态,该状态将汇总所有资源的状态,当全部OK时则显示OK,只要有一个ERROR则显示ERROR,只要有一个WARN则显示WARN。 status -l 显示状态列表。

log

2.0.6以上版本支持 log debug 修改dubbo logger的日志级别 log 100 查看file logger的最后100字符的日志

help

help 显示telnet命帮助信息。 help xxx 显示xxx命令的详细帮助信息。

clear

clear 清除屏幕上的内容。 clear 100 清除屏幕上的指定行数的内容。

exit

exit 退出当前telnet命令行。

dorado & BDF常见问题

部分记录了我在 使用 dorado 开发当中遇到的问题,该问题列表会不断补充,也同时欢迎在评论里补充新问题。

相关网站

dorado nexus 私服 [http://nexus.bsdn.org](http://nexus.bsdn.org “dorado nexus 私服” target=”_blank”)
dorado client api [http://dorado7.bsdn.org/jsdoc/](http://dorado7.bsdn.org/jsdoc/ “dorado client api” target=”_blank”)
wiki [http://wiki.bsdn.org/](http://wiki.bsdn.org/ “wiki” target=”_blank”)
BDF2项目创建向导 [http://bsdn.org/projects/bdf/deploy/bdf2-new-project-wizard/view.Wizard.d](http://bsdn.org/projects/bdf/deploy/bdf2-new-project-wizard/view.Wizard.d “BDF2项目创建向导” target=”_blank”)
dorado eclipse plugin
百度网盘: [https://pan.baidu.com/s/1chalW5ebFOC3cKkYJLvkig](https://pan.baidu.com/s/1chalW5ebFOC3cKkYJLvkig  “百度网盘” target=”_blank”) 提取码:xobo

Dorado

Q: 想在 Spring Boot 环境下使用 dorado

A: BDF3 就是基于 Spring Boot2 的,可以在 BDF3 的基础之上开发或学习其如何把 dorado 和 Spring Boot2 集成在一起的。 https://github.com/muxiangqiu/bdf3.git

Dorado IDE

Q: 打开view 文件时”Loading Model…遇到问题”。

A: 需要更新 dorado 规则, 更新 dorado IDE 规则 具体可以参考dorado配置规则。建议使用在线方式更新。 在线模式下 ServerName对应着域名或ip, Port就是端口号, App Name就是contextpath,如果为空可以不填。图片中是示例访问地址为: http://localhost:8080/waterdrop 。如果 项目访问地址是: http://localhost:8080

Server Name: localhost Port: 8080 App Name:

Widget

AjaxAction

Q: AjaxAction如何根据后台执行的或失败执行不同的Javascript代码?

A: ajaxAction 的对应的后台 @Expose 方法,返回一个字符串。如果返回空就是执行成功,错误就把错误信息返回。然后在回调函数中根据返回值,进行不同的判断。

1
2
3
4
5
6
7
ajaxAction.execute(function(msg){
if(!msg) {
// do ok
} else {
// do error
}
});

View

Q: 如何切换皮肤?

A:

1
WebConfigure.set(DoradoContext.SESSION, "view.skin", skinName);

Q: 为什么我的控件显示是英文

A: dorado 默认是支持多语言的,dorado 的默认控件会根据HTTP Header中 accept-language 的值来选择显示语言。如果没有多语言需求的话可以把语言固定下来。在dorado home目录下的context.xml里添加如下配置:

1
2
3
4
5
6
7
8
9
<bean id="dorado.localeResolver"
class="com.bstek.dorado.view.resource.SpringLocaleResolverAdapter">
<property name="springLocaleResolver">
<bean
class="org.springframework.web.servlet.i18n.FixedLocaleResolver">
<constructor-arg index="0" value="zh_CN" />
</bean>
</property>
</bean>

Q: 如何在外部获取 dorado 控件?

A: 在 dorado 控件的事件内,可以通过 view.id/get 等方法来获取其它 dorado 控件。对于事件以外的地方作用域里没有 view 对象,这个时候我们可以使用 viewMain 或 $id来获取。 viewMain 其实就是 view 但是在dorado7 低版本中不支持; $id 只能通过控件 id 来选取控件,而且其返回的是dorado.ObjectGroup,我们需要通过.objects0来获取具体的控件。 $id(“widgetId”).objects[0]

DataSet

Q: 为什么DataProvider的代码里加载出数据,而的dataSet 中没有呢?

A: 如果开启分页,即 dataSet 中 pageSize 值大于 0, dataSet 会从 page.entities 内取值。

Q: 设置的DataProvider明明存在,为什么会提示无法在”XXXXX”类中查找到唯一匹配的”XXXX”方法?

**A:**如果开启分页,@DataProvider 标记的方法必须有 Page page 属性,其中T 为相应的 POJO 类。

Q: 为什么 DataSet刷新后中有数据,用调试器也能取出来为什么JS代码取不出来呢?

A: DataSet 的刷新有两个方法,同步与异步 flush, flushAsync。 最直白的理解使用 flush 刷新数据时,代码会等数据加载完成之后再执行后续代码;使用flushAsync刷新数据时,先处理后续代码,最后处理数据加载。 所以遇到该情况,多是使用了 flushAsync 刷新 DataSet 然后直接在下一行代码里就从 DataSet 中取数据。建议使用异步加回调的方式。

1
2
3
dataSet.flushAsync(function(){
//get data from dataSet
});

AutoForm

Q: 为什么AutoForm 的 entity 有时候是 dorado.Entity 有时候是 JavaScript Object?

A: AutoForm 绑定 dataSet 时返回的是 entity,如果没有返回的是 JavaScript Object。aufoform 一直是与 dataSet 同时出现的,如果确实不需要 dataSet,可以将 AutoForm 的 createPrivateDataSet 属性设为 true,它会自行创建一个自有 DataSet。

Q: 如何在一组输入框内,实现选择日期区间选择,如下图所示。

日期区间选择 A: 详见DateRangeDemo.view.xml附件。 DateRangeDemo.view.xml

Q: 如何限制文件框只能输入两个字符

A: 可以利用editor的onKeyPress事件

1
2
3
4
5
6
7
view.get("#autoFormContractMain.#amount.editor").bind("onKeyPress", function(self, arg) {
var orignText = self.doGetText();
var keyCode = arg.keyCode;
if (orignText && orignText.length>1) {
arg.returnValue = false;
}
})

DataGrid

Q: 重新加载数据后,dataGrid 启用 client filter 时,dataGrid 数据为什么不更新?

A: 这个可以确认为 dorado7 的 Bug, 目前通过以下代码手动刷新 dataGrid。

1
2
3
4
5
6
7
8
9
10
function resetFilteredDataGrid(dataGrid){
var filterEntity = dataGrid.get("filterEntity");
var lastCriteria = filterEntity.toJSON();
filterEntity.fromJSON({});
dataGrid.filter();
setTimeout(function(){
filterEntity.fromJSON(lastCriteria);
dataGrid.filter();
}, 100);

Q: DataGrid 如何设置某些数据不能选中

在 RowSelectorColumn 的 onRenderCell 事件里,修改默认渲染动作。

1
2
3
4
5
6
7
8
9
10
var selectable = function(){
// 计算是否可选
return ;
};
if (selectable()) {
arg.processDefault = true;
} else {
jQuery(arg.dom).empty();
arg.processDefault = false;
}

Q: 如何在 DataGrid 的 Column上显示按钮?

A: 可以在 DataColumn 的 onRenderCell 事件里重写渲染方法。

1
2
3
4
5
6
7
8
9
10
11
12
jQuery(arg.dom).empty().xCreate({
tagName: "button",
content: "查看",
style: { // 定义按钮的style
color: "#1b8ce0",
"font-weight": "bold"
},
onclick: function(){ // 定义onclick事件
view.id("dialog").show();
}
});
arg.processDefault = false; // 禁止默认渲染

Q: 如何在 DataGrid 的 Column上显示链接?

A: 可以在 DataColumn 的 onRenderCell 事件里重写渲染方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xobo.renderClickLink(arg, '详情', function(){
dialog.show();
});

$namespace("xobo")
xobo.renderClickLink = function(arg, content, callback, config){
if (!config) {
config = {};
}
jQuery(arg.dom).empty().xCreate({
tagName: "a",
content: content "",
style: config.style
{
"color": "#1b8ce0",
"font-weight": "bold",
}
});
jQuery(arg.dom).css("cursor", config.cursor "pointer");
if (click) {
jQuery(arg.dom).click(callback);
}
arg.processDefault = false;
}

DownloadAction

下载控件,我用过的最难用的 dorado 控件之一,极不推荐使用。该 Action 提交是采用表单提交的方式,一旦在该 action 的后台处理上遇到异常直接就把用户页面转到 5xx 的错误页面去了。

Q: 为什么通过 DownloadAction 的方式传递到后台的代码有乱码?

A: 在web.xml中配置编解码Filter(org.springframework.web.filter.CharacterEncodingFilter). 一定要做为第一个 fitler才行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Q: 为什么通过DownloadAction 传递到后台的参数都变成 String ?

A: 对于 dorado 的绝大多数 action 控件来说,能准确的把前台赋值给 parameter 属性的值在后台转换为相应的 Java 值。但是 downloadAction 会把 parameter 的值都转换为字符串。已经习惯了把 dorado 对象直接丢到 parameter 里传到后台的,在这里肯定要踩坑。兼容方案就是在前台把要传递的值先转化为 JSON 字符串,然后把这个字符串传到后台,在后台再把字符串转回 Map。dorado 提供了工具类 dorado.JSON.stringify 来把对象转化为 JSON 字符串。

UploadAction

Q: 如何给uploadAction添加阻塞的任务指示器?

A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
xobo.maskUpload = function(uploadAction, msg) {
if (!uploadAction) {
return;
}
uploadAction.bind("onError", function(self, arg) {
dorado.util.TaskIndicator.hideTaskIndicator(self.taskId);
});
uploadAction.bind("beforeFileUpload", function(self, arg) {
self.taskId = dorado.util.TaskIndicator.showTaskIndicator(msg "上传中...", "main");
});

uploadAction.bind("onFileUploaded", function(self, arg) {
dorado.util.TaskIndicator.hideTaskIndicator(self.taskId);
});
}

xobo.maskUpload('uploadActionId');

Q: DataSetDropDown 添加自定义列之后,如何显示过滤框?

A: 在 DataSetDropDown 的 onOpen 事件里,添加如下代码:

1
2
3
setTimeout(function(){
self.get("box.control").set("showFilterBar", true)
}, 200)

日期选择器

Q: 如何把日期选择器的默认日期改为输入框的值?

A: dorado 的日期选择框默认打开的时候都是当前时间。创建一个新的DateDropDown控件,然后在其 onCreate 事件中插入如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
self.initDropDownBox = function(box, editor){
var dropDown = this, datePicker = dropDown.get("box.control");
if (datePicker) {
var date = editor.get("value");
var year, month;
if (typeof date == "string") {
date = new Date(date);
} else {
if (!(date instanceof Date)) {
date = new Date();
}
}
datePicker.set("date", date);
}
};

杂项

dorado滚动条

dorado滚动条默认是窄窄的一条,只有鼠标指上去之后才会变宽。但是对于部分客户这个设定是不可以接受的。又由于 dorado 布局相关的原因,没有计划去支持实体的滚动条。只能通过调整滚动条的宽度来照顾该部分客户。 我们需要配置如下两个属性: widget.scrollerSize是默认滚动条宽度; widget.scrollerExpandedSize是展开后滚动条宽度,建议设置为 10.

dorado.Setting[“widget.scrollerSize”]=10;
dorado.Setting[“widget.scrollerExpandedSize”]=10;

文件下载

触发文件下载有两种方式

  1. 使用 window.location.href = “downloadUrl”;
  2. 通过 iframe.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function downloadFile(url){
    var iframeId = "iframeDownloadFile";
    var iframe = document.getElementById(iframeId);
    if (!iframe) {
    iframe = document.createElement('iframe');
    iframe.style.display = "none";
    iframe.id = iframeId;
    document.body.appendChild(iframe);
    }
    iframe.src = url;
    }

文件下载Java端代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void download(String filename, InputStream inputStream, HttpServletResponse response)
throws IOException {
response.setContentType("application/octet-stream");

String encodFilename = URLEncoder.encode(filename, "utf-8");
response.setHeader("Content-Disposition",
String.format("attachment; filename=\\"%1$s\\"; filename\*=utf-8''%1$s", encodFilename));

OutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
IOUtils.copy(inputStream, outputStream);
} catch (FileNotFoundException fne) {
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
}

父子页面交互

在一些业务场景下,我们会需要在子页面来操作父页面的元素,或者反过来。  

1
2
3
4
5
6
7
8
9
// sub page
top.doSth = function(params){
//do what you want
}

// parent page
if (jQuery.isFunction(top.doSth)){
top.doSth(datas);
}

性能

Q: 在前台大批量操作数据,页面很卡怎么办?

A: 在批量操作时禁止dataSet通知观察者(dataSet.disableObservers()),等操作完之后启用并通知出来。

1
2
3
4
5
6
7
8
9
10
// 禁止DataSet将消息发送给其观察者。
dataSet.disableObservers();

// 做批量操作
doBatchOperations();

// 允许DataSet将消息发送给其观察者。
dataSet.enableObservers();
// 通知DataSet的所有观察者刷新数据。
dataSet.notifyObservers();

BDF2

Maven 依赖

Q: BDF2 突然不能正常启动,报NoSuchMethodError异常:

Initialization of bean failed; nested exception is java.lang.NoSuchMethodError: com.bstek.bdf2.core.context.ContextHolder.getApplicationContext()Lorg/springframework/web/context/WebApplicationContext;

A: bdf2-orm(v2.1.0)为了兼容 Spring Boot 把ContextHolder.getApplicationContext()的返回值由WebApplicationContext改为ApplicationContext, 而 bdf2 的其它模块基本上都依赖于最新的 bdf2-orm, 导致 bdf2 的maven 项目出现不兼容的新老模块混用情况以致抛出方法未找到异常。 解决办法就是统一 bdf2 的 jar 包依赖: 1:升级其它 BDF2 模块到 2.1.0 及其以后版本; 2: 降级bdf2-orm。 Maven 项目可以通过排除依赖的bdf2-orm,然后指定bdf2-orm的版本为 2.0.7。 添加依赖:

com.bstek.bdf2
bdf2-orm-hibernate3
2.0.7

Home

Q: BDF2 如何通过JavaScript在首页中打开新的Tab页: A: 在子页面调用 top.openUrlInFrameTab 方法。

1
2
3
window.openUrlInFrameTab(url, name, icon);
// example
window.openUrlInFrameTab("Test.d", "测试页面", "");

eclipse

xsd

因为各种原因, xsd 文件加载缓慢或者网站升级文件会找不到,导致xml文件校验慢甚至失败。可以在eclipse指定xsd。

![[image-20240408142608724.png]]

bdf2.0.xsd下载

解决Mail中发完邮件,会在草稿里留一份

使用macOS自带的Mail写邮件时,总会有一份草稿保留到草稿箱,就算邮件发送成功它依旧在那里。当然我们可以手动删掉,但是有一个更好的解决方法。 这个问题是由于IMAP账号导致的,所以要对所有的IMAP做相同的操作。 打开Mail的偏好设置(⌘ + ,)切换至”Accounts”->”Mailbox Behaviors”。 OS 10.12 Sierra 修改Drafts Mailbox为”On My Mac” -> “Drafts”. 其它版本 取消在服务器端保存草稿 参考链接: http://royalwise.com/do-you-have-drafts-left-in-your-folder-even-after-sending/