最近处理知识产权相关的事宜,需要统计项目内的所有第三方依赖的license.

一行命令搞定

1
mvn license:aggregate-third-party-report

会在target目录下输出aggregate-third-party-report.html文件,直接浏览器打开查看就可以了。

更多细节参考官网:

http://www.mojohaus.org/license-maven-plugin/index.html

依赖树分析

1
mvn dependency:tree

这个命令会返回 maven 依赖树,对于 排除冲突的包,是一个很好的工具。会返回类似以下的内容:

1
2
3
4
5
6
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ common-sql ---
[INFO] +- org.springframework:spring-jdbc:jar:5.3.20:compile
[INFO] +- org.springframework:spring-beans:jar:5.3.20:compile
[INFO] +- org.springframework:spring-core:jar:5.3.20:compile
[INFO] \- org.springframework:spring-jcl:jar:5.3.20:compile
[INFO] \- org.springframework:spring-tx:jar:5.3.20:compile

习惯了springboot的把代码和依赖包打成一个jar包来执行。有时候我们有一些很简单的控制台程序,也想打包成一个jar包来运行。当然我们可以引入springboot框架来支持。但是springboot框架实在是太大了。有时候我并不期望生成一个10M左右的jar包。我只需要很少的第三方依赖。这个时候我们就可以直接用Apache Maven Shade Plugin 来解决这个问题。

下面是一个示例,我们先新建一个maven工程。为了方便演示,我引入了fastjson的依赖。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
<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
<artifactId>demo</artifactId>
<version>1.0</version>

<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.15</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.example.Demo</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

关键点就是 引入 maven-shade-plugin 这个插件。然后我创建一个java类 org.example.Demo ,来演示简单的输出

1
2
3
4
5
6
7
8
9
10
11
package org.example;

import com.alibaba.fastjson.JSONObject;

public class Demo {
public static void main(String[] args) {
JSONObject root = new JSONObject();
root.put("example", "001");
System.out.println(root.toJSONString());
}
}

然后,就可以运行 mvn package 打包了。打包出来后,我们查看target目录:

1
2
408,034 demo-1.0.jar
2,394 original-demo-1.0.jar

我们可以看到 original-demo-1.0.jar, 这个是没包括 第三方依赖包的。文件只有 2k左右。 然后 demo-1.0.jar 包含了引入的第三方包,400k左右。比springboot打出来的包会小很多。

我们运行 java -jar demo-1.0.jar . 就可以看到 一个简单的输出:

1
2
3
C:\console-demo\target>java -jar demo-1.0.jar

{"example":"001"}

最近用 click house时,出现了一个很奇怪的错误:

1
SQL 错误 [62]: ClickHouse exception, code: 62, host: 10.2.20.161, port: 18123; Code: 62, e.displayText() = DB::Exception: Cannot parse expression of type Nullable(String) here: '[{"nodeName" (version 21.3.4.25 (official build))

一开始以为是 字符串里包含什么特殊字符,把 长字符串里的特殊字符全去掉,也还是不行。 后来各种尝试,发现是有两个Datetime类型的字段有问题。 有问题的SQL:

1
INSERT INTO default.TABLE7 (ID,C01,CREATETIME,UPDATETIME,TITLE) VALUES ('2222','0','2021-06-17','2021-06-17','TEST')

修改方案一:在最后一个 datetime类型的值后面加一个换行,修改成:

1
2
INSERT INTO default.TABLE7 (ID,C01,CREATETIME,UPDATETIME,TITLE) VALUES ('2222','0','2021-06-17','2021-06-17'
,'TEST')

修改方案二:补全时分秒,修改成:

1
INSERT INTO default.TABLE7 (ID,C01,CREATETIME,UPDATETIME,TITLE) VALUES ('2222','0','2021-06-17 00:00:00','2021-06-17 00:00:00','TEST')

具体的业务,字段特别多,大约70个字段左右。我自己尝试建立一张简单的表,并不会出现这个问题。 所以应该是 clickhouse在 字段数量比较多,SQL较长,且包含时间字段时,才会出现这样的BUG。

经过前面的两篇文单的介绍,我们已经完成了开发环境的准备和基本工程的创建。在这个基础上我们来写一个HelloWorld 宏。

HelloWorld宏开发

步骤一,用Idea打开工程

使用Idea的时候有一个注意事项,就是要修改maven的 setting.xml文件。因为confluence使用的自己的服务器maven.atlassian.com来下载maven依赖。

  1. 点击Idea左上角的File菜单
  2. 打开Settings…
  3. 找到 Build, Execution, Deployment, 点开 Build Tools, 选择 Maven
  4. 修改 User settings file的配置,点击右边的 override, 就可以写入值了。 可以通过 atlas-version命令来查看SDK的安装目录,我们使用SDK目录下的 apache-maven-3.5.4\conf\setting.xml 配置文件
  5. 在Idea里更新maven的配置,查看是否maven依赖都能正常加载。第一次加载maven依赖的时候会比较耗时间。

步骤二,修改配置文件

  1. 打开 src\main\resources\atlassian-plugin.xml 文件

  2. 在 后面加入,以下代码:

    1
    2
    3
    4
    5
    <xhtml-macro name="helloworld" class="com.example.Helloworld" key='helloworld-macro'>
    <description key="helloworld.macro.desc"/>
    <category name="formatting"/>
    <parameters/>
    </xhtml-macro>
    • 注意,xhtml-macro 和 web-resource 是同级节点。
  3. 打开 src\main\resources\myConfluenceMacro.properties 文件,加入helloworld.macro.desc 配置项

    1
    helloworld.macro.desc=Hello World

步骤三,插入Java代码

  1. 创建Helloworld类,写入以下代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.example;
    import com.atlassian.confluence.content.render.xhtml.ConversionContext;
    import com.atlassian.confluence.macro.Macro;
    import com.atlassian.confluence.macro.MacroExecutionException;
    import java.util.Map;
    public class Helloworld implements Macro {
    @Override
    public String execute(Map<String, String> map, String s, ConversionContext conversionContext)
    throws MacroExecutionException {
    return "<h1>Hello World</h1>";
    }
    @Override
    public BodyType getBodyType() {
    return BodyType.NONE;
    }
    @Override
    public OutputType getOutputType() {
    return OutputType.BLOCK;
    }
    }

步骤四,编译及效果查看

  1. 在命令行里运行 atlas-mvn package 命令,重新打包

    1
    atlas-mvn package
  2. 打包成功后,会输出类似以下的信息:

    1
    2
    3
    4
    5
    6
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 11.916 s
    [INFO] Finished at: 2021-03-01T18:20:54+08:00
    [INFO] ------------------------------------------------------------------------
  3. 切换到原来运行atlas-run的命令行窗口,你可以看到 类似以下的日志输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
[INFO] [talledLocalContainer] 2021-03-01 18:21:00,379 INFO [QuickReload - Plugin Installer] [atlassian.plugin.manager.DefaultPluginManager] updatePlugin Updating plugin 'com.example.myConfluenceMacro-tests' from version '1.0.0-SNAPSHOT' to version '1.0.0-SNAPSHOT'
[INFO] [talledLocalContainer] 2021-03-01 18:21:00,379 INFO [QuickReload - Plugin Installer] [atlassian.plugin.manager.DefaultPluginManager] broadcastPluginDisabling Disabling com.example.myConfluenceMacro-tests
[INFO] [talledLocalContainer] 2021-03-01 18:21:01,629 INFO [QuickReload - Plugin Installer] [plugins.quickreload.install.PluginInstallerMechanic] installPluginImmediately
[INFO] [talledLocalContainer] ^
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer] If you can type on a Dvorak keyboard can you automatically speak Esperanto and program in Lisp?
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer] Quick Reload Finished (2979 ms) - 'myConfluenceMacro-1.0.0-SNAPSHOT-tests.jar'
  1. 在浏览器里创建一个页面,插入一个宏,在选择宏的弹窗里,你就可以看到 Helloworld这个宏了。 选择宏图示
  2. 插入Helloworld宏后,发布,就可以看到发布后的效果 Helloworld宏图示

参考资料

https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-confluence-hello-world-macro/

有这么一个问题,我们在一个后台界面维护一个关键词的数据,期望当关键词发生变化时,能立即加载到java内存当中。如果变化过于频繁,则没有必要每次变更都 重新加载。一秒内最多加载一次即可。 这个工具类就是为了解决这个问题。

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

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class SpanRunner {

private final long span = 1000;
Map<Runnable, Long> timeMap = new ConcurrentHashMap<>();
Map<Runnable, Long> scheduleMap = new ConcurrentHashMap<>();

ScheduledExecutorService schedule = new ScheduledThreadPoolExecutor(1);

public void runSpan(Runnable runnable) {
if (!timeMap.containsKey(runnable)) {
timeMap.put(runnable, System.currentTimeMillis());
runnable.run();
} else {
if (!scheduleMap.containsKey(runnable)) {
long lastRun = timeMap.get(runnable);
if (System.currentTimeMillis() - lastRun > span) {

timeMap.put(runnable, System.currentTimeMillis());
runnable.run();
} else {
schedule.schedule(new Runnable() {
@Override public void run() {
timeMap.put(runnable, System.currentTimeMillis());

try {
runnable.run();
}catch (Exception e){
e.printStackTrace();
}finally {
scheduleMap.remove(runnable);
}
}
}, span, TimeUnit.MILLISECONDS);
scheduleMap.put(runnable, 1L);
}
}
}
}

}

前提说明

  1. 你需要先安装好confluence开发用的SDK,请参考 confluence宏插件开发01-搭建开发环境

HelloWorld插件工程

步骤一,创建工程

Confluence SDK提供了atlas-create-confluence-plugin 命令,来创建一个插件的工程。

  1. 打开一个命令行窗口,切换到你想要创建工程的目录

  2. 执行 atlas-create-confluence-plugin 命令,填写必要的参数,你会看到类似以下的输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Define value for groupId: : com.example
    Define value for artifactId: : myConfluenceMacro
    Define value for version: 1.0.0-SNAPSHOT: :
    Define value for package: com.example: :
    Use OSGi Java Config: (Y/N/y/n) N: :
    Confirm properties configuration:
    groupId: com.example
    artifactId: myConfluenceMacro
    version: 1.0.0-SNAPSHOT
    package: com.example
    use OSGi Java Config: N
    Y: :
  3. 填写完毕后,工具会自动下载依赖的文件,需要保证网络是通的。下载创建完毕后,你会看到类似这样的输出:

    1
    2
    3
    4
    5
    6
    7
    [INFO] Project created from Archetype in dir: C:\work\demo\confluence-demo\myConfluenceMacro
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 07:15 min
    [INFO] Finished at: 2021-03-01T12:40:43+08:00
    [INFO] ------------------------------------------------------------------------
  4. 现在你可以打开 myConfluenceMacro 文件夹,查看刚刚创建好的文件了。

步骤二,运行工程

  1. 确保命令行目录在 myConfluenceMacro 目录下,执行 atlas-run 命令,这个命令是把 一个本地的 confluence开发环境运行起来,第一次运行的时候会需要好长一段时间,需要下载大量的依赖,这个时候你可以先去干点别的,但请保证系统不会自动进入睡眠状态。

    1
    2
    cd myConfluenceMacro
    atlas-run
  2. 如果你看到命令行里的输出 慢下来了,你可以试着用浏览器访问 http://localhost:1990/confluence ,打开网页后使用 默认的用户名admin,密码admin登录。

步骤三,确认插件已安装

  1. 浏览器里 点击右上角的小齿轮,点击下拉菜单里的“管理应用”
  2. 打开的界面里搜索 myConfluenceMacro 插件,下拉的分类里选择 系统,如下图: 应用管理图示

下一步

到这里,插件的安装及运行就已经完成了,但似乎这个插件啥也干不了,接下来我们在这个基础上开发一个自定义宏

参考资料

https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-confluence-hello-world-macro/

最近在研究Confluence宏的开发,记录和整理一下开发过程中遇到的问题。

前提说明

  1. 你需要掌握基本的java知识。Conflunce是用Java开发的wiki, 所以如果你想开发Confluence插件,基本的java语法还是要掌握的

Windows安装

步骤一,检查JAVA_HOME环境变量

  1. 如果你还没安装jdk,需要先安装JDK 1.8或更新的版本。如果你已经安装好了jdk,并且配置好了JAVA_HOME,请直接到步骤二
  2. 依次打开 win10 开始菜单》windows系统》控制面板》系统和安全 》系统》高级系统设置》环境变量
  3. 添加系统变量JAVA_HOME,对应的值是你的JDK安装的目录,比如:C:\Program Files\Java\jdk1.8.0

步骤二,下载及安装SDK

  1. 下载最新版本的SDK
  2. 双击打开下载好的SDK安装文件
  3. 跟随安装界面一步步安装

步骤三,检查安装结果

打开windows的命令行工具。输入以下命令

1
atlas-version

如果安装成功,你会看到类似以下的返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
ATLAS Version:    8.2.6
ATLAS Home: C:\Applications\Atlassian\atlassian-plugin-sdk-8.2.6
ATLAS Scripts: C:\Applications\Atlassian\atlassian-plugin-sdk-8.2.6\bin
ATLAS Maven Home: C:\Applications\Atlassian\atlassian-plugin-sdk-8.2.6\apache-maven-3.5.4
AMPS Version: 8.1.0
--------
Executing: "C:\Applications\Atlassian\atlassian-plugin-sdk-8.2.6\apache-maven-3.5.4\bin\mvn.cmd" --version -gs C:\Applications\Atlassian\atlassian-plugin-sdk-8.2.6\apache-maven-3.5.4/conf/settings.xml
Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-18T02:33:14+08:00)
Maven home: C:\Applications\Atlassian\atlassian-plugin-sdk-8.2.6\apache-maven-3.5.4\bin\..
Java version: 1.8.0_91, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk1.8.0\jre
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

参考资料

https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-windows-system/

https://my.oschina.net/u/1864677/blog/4333480 https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-confluence-hello-world-macro/

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
package com.example.plugin;

import com.atlassian.confluence.content.render.xhtml.ConversionContext;
import com.atlassian.confluence.macro.Macro;
import com.atlassian.confluence.macro.MacroExecutionException;
import com.atlassian.plugin.spring.scanner.annotation.component.ConfluenceComponent;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.webresource.api.assembler.PageBuilderService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Map;

@ConfluenceComponent
public class Helloworld implements Macro {
private PageBuilderService pageBuilderService;

@Autowired
public Helloworld(@ComponentImport PageBuilderService pageBuilderService) {
this.pageBuilderService = pageBuilderService;
}

@Override public String execute(Map<String, String> map, String s, ConversionContext conversionContext)
throws MacroExecutionException {
pageBuilderService.assembler().resources().requireWebResource("com.example.plugin.hello-demo:hello-demo-resources");

String output = "<div class =\"helloworld\">";
output = output + "<div class = \"" + map.get("Color") + "\">";
if (map.get("Name") != null) {
output = output + ("<h1>Hello " + map.get("Name") + "!</h1>");
} else {
output = output + "<h1>Hello World!<h1>";
}
output = output + "</div>" + "</div>";

if(s!=null && s.length()>0){
output = "<div id='myCanvas'></div>";
output +="<script type=\"text/javascript\">";
output +="var c=document.getElementById('myCanvas');";
output +="var umlText = \"" + parse(s) + "\";";
output +="\nvar uml = new NutUml(c);";
output +="uml.drawUml(umlText);";
output +="</script>";
}

return output;

}

private String parse(String s) {
s= StringUtils.replace(s,"\"","\\\"");
s= StringUtils.replace(s,"\r","\\r");
s= StringUtils.replace(s,"\n","\\n");
return s;
}

@Override public BodyType getBodyType() {
return BodyType.PLAIN_TEXT;
}

@Override public OutputType getOutputType() {
return OutputType.BLOCK;
}
}

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
<atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2">
<plugin-info>
<description>${project.description}</description>
<version>${project.version}</version>
<vendor name="${project.organization.name}" url="${project.organization.url}" />
<param name="plugin-icon">images/pluginIcon.png</param>
<param name="plugin-logo">images/pluginLogo.png</param>
</plugin-info>

<!-- add our i18n resource -->
<resource type="i18n" name="i18n" location="hello-demo"/>

<!-- add our web resources -->
<web-resource key="hello-demo-resources" name="hello-demo Web Resources">
<dependency>com.atlassian.auiplugin:ajs</dependency>

<resource type="download" name="hello-demo.css" location="/css/hello-demo.css"/>
<resource type="download" name="hello-demo.js" location="/js/hello-demo.js"/>
<resource type="download" name="images/" location="/images"/>

<context>hello-demo</context>
</web-resource>
<xhtml-macro name="helloworld" class="com.example.plugin.Helloworld" key='helloworld-macro'>
<description key="helloworld.macro.desc"/>
<category name="formatting"/>
<parameters>
<parameter name="Name" type="string" />
<parameter name="Color" type="enum">
<value name="red"/>
<value name="green"/>
<value name="blue"/>
</parameter>
</parameters>
</xhtml-macro>
</atlassian-plugin>

代码

1
2
3
4
5
6
7
public static void main(String[] args){
AntPathMatcher matcher = new AntPathMatcher();
Map<String,String> map = matcher.extractUriTemplateVariables("/dev/{id}","/dev/123");
map.entrySet().forEach(k->{
System.out.println(k.getKey() + ":" + k.getValue());
});
}

输出结果

1
id:123

AntPathMatcher 是 Spring core 提供的一个工具类。使用这个工具类我们可以很容易的实现URL里变量的提取。 我们在spring里常见的代码是:

1
2
@RequestMapping(value = "/id/{fileId}", method = RequestMethod.GET)
public String getFileById(@PathVariable(value = "fileId", required = true) String fileId)

通过AntPathMatcher 就可以自己实现URL里的变量提取了。