1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.stream.Stream;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int n =4;
Scanner in = new Scanner(System.in);
while(in.hasNext()) {
n = in.nextInt();
kStep(n);
}
}
public static void kStep(int n) {
Long aLong = Stream.iterate(new long[]{1, 1, 2}, t -> new long[]{t[1], t[2], t[0] + t[2]})
.limit(n).skip(n - 1).findFirst().get()[0];
System.out.println(aLong);
}
}

花了大约 3 周的零散时间,吾辈终于写完了 Joplin 的 VSCode 插件。

joplin, joplin-vscode-plugin

起因

为什么吾辈使用 Joplin

吾辈曾经使用过一些笔记工具,包括 印象、OneNote、Notion 这些,但最终都放弃了。
关键还是在于吾辈核心的一些需求未能得到满足:

  1. 搜索要快
  2. 编辑器体验要好
  3. 数据应该能够全部导出
  4. 基于标准的 md,可以直接复制到其他平台
  5. 可以基于它进行二次开发
  6. 还没死

下面让我们来细品以上工具的功能

印象笔记

好吧,印象笔记有国际版/国内版,但国内版本很明显属于收钱不做正事的典型,很长时间内都没有任何变化了。而且,markdown 支持并非官方自带,而是需要使用第三方插件才行(18 年底更新之后貌似支持了)。而且最近 印象笔记又抢注国内的 notion 的图片商标,真可谓是国内独树一帜的奇观 了。

OneNote

老实说,如果习惯使用 Office 全家桶整理文档的话,OneNote 还是很香的,编辑体验和 Word 保持一致,搜索极快。但很遗憾的是,吾辈是坚定的 万物基于 markdown 人士,所以不喜欢 OneNote。

Notion

是目前遇到的一个比较满意的笔记工具,但主要有 3 点不太满意。

  1. 编辑器比较卡
  2. 搜索非常慢
  3. 无法导出全部数据

具体参考:Notion 使用体验

而 Joplin,则是吾辈能够解决 notion 的以上几个问题的笔记工具,同时开源免费,允许吾辈参与其中。

主要优点:

  1. 搜索很快,非常快
  2. 可以使用外部编辑器打开
  3. 数据都在自己手里,提供一次性导出全部的功能
  4. 使用标准的 md,可以直接复制到其他平台
  5. 可以基于它进行二次开发

主要缺点:

  1. ui/ux 有点简陋
  2. 没有 vsc 插件导致使用外部编辑器也并不是非常方便

为什么吾辈要写 vscode 的这个插件

  1. 作为专业的编辑器在编辑功能上 vscode 是笔记工具无法比拟的。例如快捷键支持
  2. vscode 不仅仅是一个编辑器,更有着非常庞大的插件生态圈,所以在 markdown 格式化、linter 校验、pdf 导出等功能上早已实现,不需要在笔记工具里重复造轮子 – 还可能是方轮子
  3. 事实上,我一直在使用 vscode 在进行 markdown 文档编辑工作,用 git + vscode 存储公司相关的文档。同时也在使用 joplin 存储个人的笔记资料,但目前经过一段时间发现我需要的是 vscode 的编辑 + joplin 的同步/搜索功能。

所以我编写了这个插件,用以给与我有相同需求的人使用。

插件介绍

在 VSCode 中集成 joplin,目前允许直接对目录、笔记进行操作,同时支持搜索功能。

预览图

功能

  • 在 VSCode 中有一个选项卡可以展示目录树
  • 在 VSCode 中创建/更新/删除目录/笔记
  • 在 VSCode 中点击即直接编辑
  • 在 VSCode 中搜索所有笔记

要求

插件设置

  • joplin.token: joplin web 服务的 token
  • joplin.port: joplin web 服务的端口,默认为 41184

已知问题

  • 缺少快捷键支持

发布说明

0.1.0

  • 创建笔记后直接打开
  • 关闭笔记后关闭同步
  • 在 Joplin 中对目录/笔记做操作时 VSCode 中的目录树定时自动更新
  • 添加开发环境变量
  • 支持国际化
  • 将 icon 替换为 joplin 的

0.0.1

  • 在 VSCode 中有一个选项卡可以展示目录树
  • 在 VSCode 中创建/更新/删除目录/笔记
  • 在 VSCode 中点击即直接编辑
  • 在 VSCode 中搜索所有笔记

使用 Markdown

Note: You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:

  • Split the editor (Cmd+\ on macOS or Ctrl+\ on Windows and Linux)
  • Toggle preview (Shift+CMD+V on macOS or Shift+Ctrl+V on Windows and Linux)
  • Press Ctrl+Space (Windows, Linux) or Cmd+Space (macOS) to see a list of Markdown snippets

For more information

Enjoy!

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

import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
* 全排列
*/
public class Main46 {
public static void main(String[] args) {
int n=5;
Scanner s = new Scanner(System.in);
n = s.nextInt();
List<String> list = IntStream.rangeClosed(1, n).mapToObj(Integer::toString).collect(Collectors.toList());
Stream<String> stream = list.stream();
for(int i=1;i<n;i++){
stream=stream.flatMap(
str->list.stream()
.filter(temp->!str.contains(temp))
.map(e->str.concat(" "+e))
);
}
String collect = stream.sorted().collect(Collectors.joining("\n", "", ""));
System.out.println(collect);

}

}

场景

GitHub 源码

在后台项目中,即便使用了 antd,仍然存在太多太多的列表页面。这些列表页面大多数又是非常相似的,所以吾辈需要解决重复的简单列表的编写,避免每次都手动控制过滤器/分页之类的东西,将之抽象成配置项,然后通过配置生成列表页面。或许已经有很多人做过了这件事情,但于吾辈而言,这仍然是全新的体验,所以也便于此记录,并供之以他人参考。

理念

使用逐级递进的方式进行封装,使用者可以根据需求停留在一个合适的封装层次,使用不同封装层次的组件。

  • BasicList:高层列表封装组件
    • ListHeader:列表页顶部工具栏组件
      • CommonHeader: 通用的顶部工具栏组件
    • ListFilter:过滤器组件
      • FilterSelect:单选过滤器
      • FilterTimeRange:时间区间过滤器
      • FilterSlot:自定义过滤器
    • ListTable:列表封装组件

BasicList 使用步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
graph TD;
A[添加 api class 实现 BasicListApi] --> B[添加配置项]
B --> C{是否需要动态修改};
C --> |是| C1[使用 useMemo 声明]
C --> |否| C2[将之抽离到组件外部声明为常量]
C1 --> D
C2 --> D
D{是否有非通用 Filter}
D --> |是| D1[自定义 slot filter]
D --> |否| D2[使用 select/time filter]
D1 --> E
D2 --> E
E{是否需要自定义表格相关功能}
E --> |是| E1[使用 filterOperate 配置]
E --> |否| E2[不声明 filter 配置]
E1 --> F
E2 --> F
F[列表渲染]

BasicList 渲染流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
graph TB
A[从 props 拿到配置项列表] --> B{header 是否为 ReactElement}
B --> |是| B1[直接渲染]
B --> |否| B2[使用 ListHeader 渲染 header 配置项]
B1 --> C
B2 --> C
C{filters 是否存在}
C --> |是| C1{filters 是否为 ReactElement}
C1 --> |是| C1A[直接渲染]
C1 --> |否| C1B[使用 ListFilter 渲染 filters 配置项] --> C1C[监听 initialParams 变化, 以随时修改 form]
C --> |否| C2[不渲染]
C1A --> D
C1B --> D
C2 --> D
D[渲染列表]

使用示例

使用基本 API

GitHub 代码示例

如下所示,我们想要构造下面这样一个简单的列表页面,包含一个面包屑导航列表、搜索框、过滤条件选择器和一个表格。

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
import * as React from 'react'
import { Button } from 'antd'
import { Link } from 'react-router-dom'
import { Moment } from 'moment'
import { LabeledValue } from 'antd/es/select'
import { userApi } from './api/UserApi'
import {
BasicList,
BasicListPropsType,
FilterFieldTypeEnum,
} from '../../components/list'

type PropsType = {}

type Config = Omit<BasicListPropsType, 'params'> & {
params?: {
keyword?: string
test?: number
birthdayTimeRange?: [Moment, Moment]
}
}
const testOptionList: LabeledValue[] = [
{ value: 0, label: '测试 0' },
{ value: 1, label: '测试 1' },
{ value: 2, label: '测试 2' },
]

//列表配置项
const config: Config = {
header: {
placeholder: '用户名/住址',
list: ['用户', '列表'],
},
filters: [
{
type: FilterFieldTypeEnum.Select,
label: '测试字段',
field: 'test',
options: testOptionList,
},
{
type: FilterFieldTypeEnum.TimeRange,
label: '生日',
field: 'birthdayTimeRange',
},
],
columns: [
{ field: 'id', title: 'ID' },
{ field: 'name', title: '姓名' },
{ field: 'birthday', title: '生日' },
{
field: 'operate',
title: '操作',
slot: (param) => <Link to={`/system/user/${param.record.id}`}>详情</Link>,
},
],
api: userApi,
}

/**
* 一个基本的列表页面
* @constructor
*/
const BasicListExample: React.FC<PropsType> = () => {
return <BasicList {...config} />
}

export default BasicListExample

使用自定义过滤器组件

GitHub 代码示例

事实上,总有各种奇怪的过滤器无法满足,这时候就需要添加一个自定义的过滤器了。
例如下面这个过滤器,包含了年龄的值和单位,是不是感觉很奇怪

关键代码配置如下

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
const config: Config = {
filters: [
{
type: FilterFieldTypeEnum.Slot,
label: '年龄',
field: 'age',
children: (
<Input.Group compact>
<Form.Item name={['age', 'value']} noStyle>
<InputNumber style={{ width: 'calc(100% - 64px)' }} />
</Form.Item>
<Form.Item name={['age', 'unit']} noStyle>
<Select style={{ width: 64 }} options={ageUnitOptionList} />
</Form.Item>
</Input.Group>
),
},
],
// 此处是为了添加过滤器的默认值
params: {
age: {
unit: 0,
},
},
}

添加表格的额外操作

GitHub 代码示例

有时候,我们需要添加一个额外的表格操作,例如导出/导入数据/删除选中数据。

关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function handleBatchDelete({
selectedRowKeys,
setSelectedRowKeys,
searchPage,
}: ListTableOperateParam) {
if (selectedRowKeys.length === 0) {
return
}
await userApi.batchDelete(selectedRowKeys)
setSelectedRowKeys([])
await searchPage()
}

const config = useMemo<Config>(
() => ({
tableOperate: (params) => (
<Button onClick={() => handleBatchDelete(params)}>删除选中</Button>
),
}),
[],
)

过滤器的下拉框数据来源是异步的

GitHub 代码示例

很多时候,我们的数据来源并不是由前端写死,而是从后台获取的,这就要求我们传入的值是 react 的一个 State 而非一个固定值。

关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const testOptionList = useAsyncMemo([], dictApi.list)
const config = useMemo<Config>(
() => ({
filters: [
{
type: FilterFieldTypeEnum.Select,
label: '测试字段',
field: 'test',
options: testOptionList,
},
],
}),
[testOptionList],
)

组件 API

BasicList

参考 src/components/common/table/js/BasicListOptions.d.ts

prop 类型 说明
header BasicList.Header 标题栏相关配置
[filters] BaseFilterField[] 过滤器列表
((params: any, onChange: (params: any) => void) => ReactElement)
[params] Params 查询参数
columns TableColumn[] 列字段列表
api BaseListApi api 对象
[tableOptions] TableOptions 一些其他选项
[tableOperate] ListTableOperate 一些其他操作

ListFilter

prop 类型 说明
[initialValue] any 查询参数
filters `(FilterSelectType FilterTimeRangeType
onChange (value: T) => void 当过滤器的参数发生改变时

ListTable

prop 类型 说明
columns TableColumn[] 列字段列表
api BaseListApi api 对象
params Params 查询参数
[tableOptions] TableOptions 一些其他选项
[tableOperate] ListTableOperate 一些其他选项

其他类型定义

下面是类型定义,所有的类型定义都有对应的 .d.ts 文件,请使用 C-N 搜索 class

HeaderNavItem 类型 说明
string 导航的名字
name string 导航的名字
[link] string 如果是 route 的话必须有值
Header 类型 说明
list HeaderNavItem[] 导航元素列表
placeholder string 搜索框的提示文本

过滤器相关

FilterFieldTypeEnum 类型 说明
Slot 1 自定义 slot
Select 2 普通选择框
TimeRange 3 日期区间选择器
FilterFieldBase 类型 说明
type FilterFieldTypeEnum 过滤器元素类型
label string 显示的标题
FilterSelectType 类型 说明
extends FilterFieldBase 继承基本过滤器配置
field string 字段名
options LabeledValue[] 值列表
FilterTimeRangeType 类型 说明
extends FilterFieldBase 继承基本过滤器配置
field string 字段名
FilterSlotType 类型 说明
extends FilterFieldBase 继承基本过滤器配置
field string 字段名
children ReactElement Form.Item 的子元素
[computed] (res: Record<string, any>, value: any) => Record<string, any> 自定义计算方法
Params 类型 说明
keyword string 查询关键字
...args any[] 其他查询参数

表格相关

TableColumn 类型 说明
field string 在数据项中对应的字段名
title string 列标题
[formatter] (v: any, record: any) => any 自定义字段格式化函数
[slot] (param: { text: string; record: any; i: number }) => ReactNode 自定义 slot
BaseListApi 类型 说明
pageList (params: any) => Promise<PageRes<any>> 所有 ListTable 中的 api 对象必须实现该类型
ListTableOperateParam 类型 说明
searchPage (page?: PageParam) => Promise<void> 导航元素列表
selectedRowKeys string[] 当前选中行的主键
setSelectedRowKeys (selectedRowKeys: string[]) => void 设置当前选中行的主键
page PageData<any> 分页数据信息
params Params 过滤器及搜索参数
TableOptions 类型 说明
[isSelect] boolean 是否可选,默认为 false
[rowKey] string 行的唯一键,默认为 id

总结

相比于之前吾辈 vue 的初版 List 封装,嗯,差距非常明显!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
[remote "origin"]
url = http://xxx@xxx.com/a/xxx/xxx/xxx
fetch = +refs/heads/*:refs/remotes/origin/*
push = refs/heads/xxxx:refs/for/xxxx
[branch "xxxx"]
remote = origin
merge = refs/heads/xxxx

端口配置

1
2
3
4
5
6
81:  8005,81,8443,8009,8443
82: 8008,82,8446,8012,8446
8080:8006,8080,8444,8010,8444
8081:8007,8081,8445,8011,8445
80:8013,80,8447,8014,8447
83: 8015,83,8448,8016,8448

权限配置

1
2
3
4
5
6
7
8
9
10
11
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="admin-gui"/>
<role rolename="manager-gui"/>
<role rolename="manager-jmx"/>
<role rolename="manager-script"/>
<role rolename="manager-status"/>
<user username="tomcat" password="" roles="admin-gui,manager-gui,manager-jmx,
manager-script,manager-status"/>
</tomcat-users>

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
private Map<String, String> getCookies() throws Exception {
//获取 Cookie
Connection.Response res = Jsoup.connect(baseLoginUrl)
.method(Method.GET)
.execute();
Map<String, String> sessionId = res.cookies();
return sessionId;
}
private void postOK(String url,String param,Map<String, String> cookies) throws Exception {
OkHttpClient client = new OkHttpClient();
String cookiesString = getCookiesString(cookies);
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, param);
Request request = new Request.Builder()
.url(url)
.post(body)
.addHeader("content-type", "application/json")
.addHeader("cache-control", "no-cache")
.addHeader("Cookie", cookiesString)
.build();
com.squareup.okhttp.Response response = client.newCall(request).execute();
System.out.println(response.body().string());
}
Map<String, String> sessionId = getCookies();
//get
Response execute1 = Jsoup.connect(liulanliang.get(0))
.header("X-Requested-With","XMLHttpRequest")
.ignoreContentType(true).cookies(sessionId).execute();
String url = "";
String param = "{}";
postOK(url, param, sessionId);

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

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Selectable;

import java.io.File;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.toList;

public class W3PdfProcessor implements PageProcessor {
public static final String D_DEV_SOUP_SRC_MAIN_RESOURCES_W3_PDF = "D:\\dev\\soup\\src\\main\\resources\\w3Pdf\\";
private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);

static Pdf pdf = new Pdf("上****娃)",316,"******/4ff5fff9005d59bbf09fd0/"
,"******************");


@Override
public void process(Page page) {
byte[] bytes = page.getBytes();
Selectable url = page.getUrl();
List<String> strings = Arrays.asList(url.get().split("/"));
Collections.reverse(strings);
try {
saveFile(pdf.getName()+"\\"+strings.get(0)+"."+pdf.getName()+".png",bytes);
} catch (Exception e) {
e.printStackTrace();
}


}

@Override
public Site getSite() {
return site;
}

public static void main(String[] args) {
new File(D_DEV_SOUP_SRC_MAIN_RESOURCES_W3_PDF+pdf.getName()).mkdir();
List<String> collect = IntStream.rangeClosed(1, pdf.getPage())
.mapToObj(e -> pdf.getPreUrl() + e).collect(toList());
Spider
.create(new W3PdfProcessor())
.addUrl(collect.toArray(new String[0]))
.thread(10)
.run();
}

/**
* 将字节流转换成文件
* @param filename
* @param data
* @throws Exception
*/
public static void saveFile(String filename,byte [] data)throws Exception{
if(data != null){
String filepath = D_DEV_SOUP_SRC_MAIN_RESOURCES_W3_PDF + filename;
File file = new File(filepath);
if(file.exists()){
file.delete();
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(data,0,data.length);
fos.flush();
fos.close();
}
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Pdf{
String name;
int page;
String preUrl;
String pdfOnlineUrl;
}

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


import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
*/
public class Main {
public static boolean isZhishu(int n){
int sqrt = (int)Math.sqrt((double) n);
return IntStream.rangeClosed(2, sqrt).noneMatch(i -> n % i == 0);
}
private static Function<String,BigInteger> bigInteger = BigInteger::new;


public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while(in.hasNext()){
int x=in.nextInt();
System.out.println(gongbeishu(x));
}
}
static BiFunction<BigInteger,BigInteger,BigInteger> multiply2_a=
(e, n)-> Stream.generate(()->e).limit(n.longValue()).reduce(bigInteger.apply("1"), getBigIntegerBinaryOperator());

private static BinaryOperator<BigInteger> getBigIntegerBinaryOperator() {
return (a, b)->bigInteger.apply(a.toString()).multiply(bigInteger.apply(b.toString()));
}

public static BigInteger gongbeishu(int n){
Map<Integer, Integer> objectObjectConcurrentHashMap = new HashMap<>();
IntStream.rangeClosed(2, n). forEach(
fang->{
IntStream.rangeClosed(2, n).filter(Main::isZhishu)
.filter(
e->multiply2_a.apply(bigInteger.apply(e+""),bigInteger.apply(fang+"")).compareTo(bigInteger.apply(n+""))==-1
)
.forEach(a->{
objectObjectConcurrentHashMap.put(a,fang);
})
;
}
);
return IntStream.rangeClosed(2, n).filter(Main::isZhishu).mapToObj(e->{
return multiply2_a.apply(bigInteger.apply(e+""),bigInteger.apply(Optional.ofNullable(objectObjectConcurrentHashMap.get(e)).orElse(1)+""));
})
.reduce(bigInteger.apply("1"), getBigIntegerBinaryOperator());

}
}