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


import java.util.Scanner;
import java.util.function.Supplier;
import java.util.stream.Stream;

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

/**
*/
public class Main {
public static void main(String args[]){
Scanner scanner = new Scanner(System.in);
int row = scanner.nextInt();
int column = scanner.nextInt();
System.out.println(new Box(row,column));
}
static class Box{
private final int row;
private final int column;
public Box(int row, int column) {
this.row = row;
this.column = column;
}

public Supplier<String> createLine(){
return ()-> Stream.generate(()->"---+").limit(column).collect(joining("","+","\n"));
}
public Supplier<String> createLine2(){
return ()-> Stream.generate(()->" |").limit(column).collect(joining("","|","\n"));
}
public Supplier<String> createNext(){
return ()-> Stream.generate(()->createLine2().get()+createLine().get()).limit(row).collect(joining());
}
@Override
public String toString() {
return createLine().get()+createNext().get();
}
}

}


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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175


import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.util.ObjectUtils;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.NumberFormat;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
public class TestCrudLine {

@org.junit.Test
public void Test1() throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(8);
Stream<Path> pathStream = Files.find(Paths.get("D:", "dev", "xxx"), Integer.MAX_VALUE,
(path, attr) -> path.getFileName().toString().endsWith(".xml")
&&!path.toString().contains("target")
&&!path.toString().contains(".idea")
);
List<List<CrudVo>> sum = Lists.newArrayList();
pathStream.forEach(e->{
Future<List<CrudVo>> submit = executorService.submit(new GetCrudLines(e.toString()));
try {
Optional.of(submit.get()).ifPresent(sum::add);
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (ExecutionException e1) {
e1.printStackTrace();
}
});
List<CrudVo> res = sum.parallelStream().flatMap(Collection::stream).collect(Collectors.toList());
System.out.println("汇总的sql对象:"+res);
int size = res.size();
Map<String, LongSummaryStatistics> collect1 = res.stream().collect(Collectors.groupingBy(CrudVo::getType, Collectors.summarizingLong(CrudVo::getLines)));
for(Map.Entry<String, LongSummaryStatistics> entries : collect1.entrySet()){
LongSummaryStatistics value = entries.getValue();
System.out.println();
System.out.println("SQL类型:"+entries.getKey());
System.out.println("求和:"+value.getSum());
System.out.println("求平均值:"+value.getAverage());
System.out.println("求最大值:"+value.getMax());
System.out.println("求最小值:"+value.getMin());
System.out.println("求总数:"+value.getCount());
}
NumberFormat instance = NumberFormat.getInstance();
instance.setMaximumFractionDigits(2);
Map<String, LongSummaryStatistics> collect2 = res.stream().collect(Collectors.groupingBy(CrudVo::getComplexity, Collectors.summarizingLong(CrudVo::getLines)));
for(Map.Entry<String, LongSummaryStatistics> entries : collect2.entrySet()){
LongSummaryStatistics value = entries.getValue();
System.out.println();
System.out.println("复杂度:"+entries.getKey());
System.out.println("求总数:"+value.getCount());
System.out.println("占比:"+instance.format((float)value.getCount()/(float)size*100)+"%");
}

}




class GetCrudLines implements Callable<List<CrudVo>>{
String path;
public GetCrudLines(String xmlPath) {
this.path = xmlPath;
}
@Override
public List<CrudVo> call() throws Exception {
return getCrudLinesDo(this.path);
}
public String getId(String key,String s){
try {
int i = s.indexOf(key + "=\"") + key.length() + 2;
String substring = s.substring(i);
int i1 = substring.indexOf("\"");
String id = substring.substring(0, i1);
return id;
}catch (Exception e){
return "";
}
}

private List<CrudVo> getCrudLinesDo(String path) throws IOException {
List<CrudVo> res = Lists.newArrayList();
BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
String str = null,sqlIdStr=null;
int lineStart = 0,sqlIdStart=0;
List<String> filterStr = Arrays.asList("insert", "delete", "update", "select");
Map<String,Integer> sqlFeild = Maps.newHashMap();
CrudVo crudVo=null;
while((str = bufferedReader.readLine()) != null){
String s = Strings.trimToNull(str);
if(ObjectUtils.isEmpty(s)){continue;}
if(s.startsWith("<sql")){
sqlIdStr =getId("id",s);
sqlFeild.put(sqlIdStr,0);
sqlIdStart=0;
}
sqlIdStart++;
if(s.endsWith("</sql>")){
sqlFeild.put(sqlIdStr,sqlIdStart-2);
}
//开始标签
String s1 = filterStr.stream().filter(e -> s.contains("<" + e)).findAny().orElse("");
if(!ObjectUtils.isEmpty(s1)){
crudVo = new CrudVo();
crudVo.setType(s1);
crudVo.setId(getId("id",s));
lineStart = 0;
}
lineStart++;
if(s.startsWith("<include")){
try {
lineStart += sqlFeild.get(getId("refid", s));
}catch (Exception e){

}
}
//结束标签
String s2 = filterStr.stream().filter(e -> s.contains("</" + e)).findAny().orElse("");
if(!ObjectUtils.isEmpty(s2)){
int lines = lineStart - 2;
crudVo.setLines(lines);
if(lines<30){
crudVo.setComplexity("简单");
}else if(lines<60&&lines>=30){
crudVo.setComplexity("一般");
}else if(lines<100&&lines>=60){
crudVo.setComplexity("较难");
}else{
crudVo.setComplexity("难");
}
res.add(crudVo);
}
}
// System.out.println(res);
return res;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class CrudVo{
String id;
String type;
int lines;
String complexity;

@Override
public String toString() {
return "{" +
"'id':'" + id + '\'' +
", 'type':'" + type + '\'' +
", 'lines':" + lines +
", 'complexity':" + complexity +
'}';
}
}

}

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173


import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.NumberFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static com.sun.xml.internal.xsom.impl.UName.comparator;
import static java.util.stream.Collectors.*;

@Slf4j
public class CheckTableRows {
private static BiFunction<String,Integer,CheckTable> checkTable = CheckTable::new;
//确认已经移植成功的表,mysql的条目数
private final static List<CheckTable> markDoneTables = Arrays.asList(
checkTable.apply("xxx_FD_MESSAGE_T",5951)
,checkTable.apply("xxx_LOOKUP_CLASSIFY_T",2631)
,checkTable.apply("xxx_PERSONALIZED_SETTING_T",623)
);
//临时表,不涉及
private final static List<String> tempTables =Arrays.asList("temp_test","temp_test1");

@org.junit.Test
public void Test1() throws Exception {
Path pathOracle = Paths.get("C:\\Users\\git\\SaveTheWorld\\soup\\src\\test\\java\\com\\cbg\\tools\\demo\\sql\\move", "all_tables_oracle_new.txt");
Path pathMysql = Paths.get("C:\\Users\\git\\SaveTheWorld\\soup\\src\\test\\java\\com\\cbg\\tools\\demo\\sql\\move", "all_tables_mysql.txt");
Stream<String> linesOracle = Files.lines(pathOracle);
Stream<String> linesMysql = Files.lines(pathMysql);
//源数据
List<CheckTable> collectOracle = getCheckTables(linesOracle);
List<CheckTable> collectMysql = getCheckTables(linesMysql);

collectOracle.removeIf(e->{
String tableName = e.getTableName();
return markDoneTables.stream().filter(m->{
return (m.getTableName()).equalsIgnoreCase(tableName);
}).findAny().isPresent()||tempTables.contains(tableName);
});
collectOracle.addAll(markDoneTables);
Map<Boolean, List<CheckTable>> collect1 = collectOracle.parallelStream().collect(partitioningBy(collectMysql::contains));
List<CheckTable> collectDone = collect1.get(true);
List<CheckTable> collectNotDone = collect1.get(false);
System.out.println("已完成:"+collectDone);
System.out.println("总共:"+collectOracle.size()+",已完成:"+collectDone.size()+"条"+",未完成:"+collectNotDone.size()+"条");
NumberFormat instance = NumberFormat.getInstance();
instance.setMaximumFractionDigits(2);
System.out.println("完成进度:"+instance.format((float)collectDone.size()/(float)collectOracle.size()*100)+"%");
System.out.println("未完成:"+collectNotDone);
List<String> collectNotDone_noTable = collectNotDone.parallelStream().map(CheckTable::getTableName).filter(e -> {
return !collectMysql.stream().map(CheckTable::getTableName).collect(toList()).contains(e);
}).collect(toList());
System.out.println("未完成(无表):"+collectNotDone_noTable);
System.out.println("未完成(无表):"+collectNotDone_noTable.size()+"条");
List<CheckTable> collectNotDone_hasTable_WrongRows = collectNotDone.parallelStream().filter(e -> {
return !collectNotDone_noTable.contains(e.getTableName());
}).collect(toList());
List<List<CheckTable>> collect = collectNotDone_hasTable_WrongRows.parallelStream().map(e -> {
List<CheckTable> objects = Lists.newArrayList();
CheckTable checkTable = collectMysql.stream().filter(m -> m.getTableName().equalsIgnoreCase(e.getTableName())).findFirst().get();
objects.add(e);
objects.add(checkTable);
return objects;
}).collect(toList());
System.out.println("未完成(oracle)(表相同,数据量不一致):"+collectNotDone_hasTable_WrongRows);
System.out.println("未完成(oracle)(表相同,数据量不一致):"+collectNotDone_hasTable_WrongRows.size()+"条");
System.out.println("未完成(oracle,mysql)(表相同,数据量不一致):"+collect);
System.out.println("未完成(oracle,mysql)(表相同,数据量不一致):"+collect.size()+"条");

}

public List<CheckTable> getCheckTables(Stream<String> linesOracle) {
return linesOracle.parallel().map(org.apache.logging.log4j.util.Strings::trimToNull).filter(
((Predicate<String>)ObjectUtils::isEmpty).negate()
).map(e -> {
CheckTable checkTable = new CheckTable();
Splitter.on("&").trimResults()
.withKeyValueSeparator(";").split(e)
.forEach((k, v) -> {
checkTable.setTableName(k.toLowerCase());
checkTable.setRows(Integer.parseInt(v));
});
return checkTable;
}).collect(toList());
}
@org.junit.Test
public void getOracleInsertData() throws Exception {
Path pathOracle = Paths.get("D:\\sqlExport\\");
Stream<Path> list = Files.list(pathOracle);
CopyOnWriteArrayList co = new CopyOnWriteArrayList();
List<CompletableFuture> objects = Lists.newArrayList();
list.forEach(e->{
CompletableFuture<String> voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
try {
Stream<String> linesOracle = Files.lines(e);
String name = e.getFileName().toString();
long count = linesOracle.filter(lo -> lo.contains("INSERT INTO")).count();
return name.replace(".sql","") + ";" + count;
} catch (Exception e1) {
String name = e.getFileName().toString();
// System.out.println("错误:"+name);
// e1.printStackTrace();
return name.replace(".sql","") + ";" + "Exception";
}
}, Executors.newFixedThreadPool(100));
voidCompletableFuture.thenAccept(co::add);
objects.add(voidCompletableFuture);
});
CompletableFuture.allOf((CompletableFuture[])objects.stream().toArray(size->new CompletableFuture[size])).join();
co.removeIf(ObjectUtils::isEmpty);
co.stream().sorted().forEach(System.out::println);

}

@org.junit.Test
public void Test2() throws Exception {
CheckTable checkTable1 = new CheckTable();
CheckTable checkTable2 = new CheckTable();
checkTable1.setTableName("gg");
checkTable2.setTableName("gg");
checkTable1.setRows(0);
checkTable2.setRows(0);
System.out.println(checkTable1 == checkTable2);
String s = org.apache.logging.log4j.util.Strings.trimToNull(" ");
System.out.println(ObjectUtils.isEmpty(s));
HashMap<Object, Object> objectObjectHashMap = Maps.newHashMap();
}
@org.junit.Test
public void TestRepeat() throws Exception {
}
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class CheckTable {
private String tableName;

private Integer rows;

public void setTableName(String tableName) {
this.tableName = org.apache.logging.log4j.util.Strings.trimToNull(tableName);
}

@Override
public String toString() {
return "{" +
"tableName:'" + tableName + '\'' +
", rows:" + rows +
'}';
}

}


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
oracle过程:
CREATE OR REPLACE PROCEDURE "FUT"."GETALLTABLEROWS" is --仅供参考
allTables varchar(32);
count1 number(11);
selectCountSql VARCHAR2(500):='';
v_date VARCHAR2(50);


cursor cur is --游标 给查询赋值
select a.table_name allTables from user_tables a ORDER BY table_name ;
BEGIN
execute immediate 'drop table temp_test';
execute immediate 'CREATE TABLE temp_test (
allTables VARCHAR(200)
)';
execute immediate 'drop table temp_test1';
execute immediate 'CREATE TABLE temp_test1 (
rows1 number(11)
)';

for x in cur loop--游标取值 循环
allTables :=x.allTables;
insert into temp_test (allTables) values(allTables) ;
selectCountSql := 'insert into temp_test1 select count(1) from '||allTables||'';
-- dbms_output.put_line(selectCountSql);
execute immediate selectCountSql;


--自己的语句
-- select i_jdid,i_ryid;

end loop;

--这可以给记录表插数据
end ;
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
mysql:
CREATE DEFINER=`root`@`%` PROCEDURE `getAllTableRows`(OUT `test` text)
BEGIN
DECLARE allTables VARCHAR(200);
DECLARE tui VARCHAR(200);
DECLARE count int;
DECLARE i int default 0;
declare report1 cursor for select a.allTables from temp_test a;

declare continue handler for not found set i =1;



drop table if EXISTS temp_test;
CREATE TABLE `temp_test` (
`allTables` VARCHAR(200),
`rows1` int(0)
);
insert into temp_test(allTables)
select table_name allTables from information_schema.`TABLES` where table_schema="fut_back_test" and table_type ="BASE TABLE" ORDER BY table_name;



open report1;

fetch report1 into tui;


while i<>1 do
set @sql=concat('select count(*) into @count from ', tui);
prepare stmt from @sql;
execute stmt;
DEALLOCATE PREPARE stmt;
set count =@count;

insert into temp_test(allTables,rows1) VALUES(tui,count) ;
fetch report1 into tui;

end while;
close report1;

delete from temp_test where rows1 is null;

END

场景

最近吾辈写了一个 chrome 插件,之前也有写过许多 user.js 脚本,所以便想在此谈一下它们的区别,以及如何选择。

简介

user.js wiki, chrome plugin dev doc

user.js 是一个开放的的标准,最早由 Firefox 的一个插件提出,后来在 chrome 得到原生支持。实际上,user.js 只是在用户的浏览器上访问一些网站时,向网站注入一些 JavaScript 脚本,除了顶部的声明信息以及一些 GM_* 的特殊 api 之外,它与普通的 JavaScript 没有什么不同。同时,目前支持 user.js 的浏览器插件很多,最流行的大概就是 TamperMonkey,同时支持非常多的浏览器,在不同浏览器之间实现了 跨平台。当然,为网页注入一段 JavaScript 脚本可以改变网页本身,但普通 JavaScript 做不到的事情,user.js 也基本没法实现。例如 获取所有的浏览器标签页 这种涉及到浏览器本身的操作。

而插件则不同,它是浏览器提供的一种附加功能,借用官方介绍:扩展程序是可定制浏览体验的小型软件程序。它们使用户可以根据个人需要或偏好来定制 Chrome 功能和行为。它们基于 Web 技术(例如 HTML,JavaScript 和 CSS)构建。,它们能访问浏览器众多的扩展 API,实现对浏览器级别的功能定制 – 而非仅限于网站。例如上面 user.js 做不到的事情 获取所有的浏览器标签页 浏览器插件可以轻易实现。

能力/API

相关 API 链接如下,此处就不贴了(太多了)

工程化

在工程化上,user.js 几乎没有工程化的最佳实践,直到目前为止,仍然没有简单可用的打包工具对 user.js 进行专门支持,例如通过 react 编写一些 UI 相关的内容并最终打包成一个 *.user.js 脚本文件是比较困难的。
看看目前的主流打包工具吧

  • rollup: JavaScript SDK 打包工具
  • Webpack: 最强大的打包工具
  • parcel: 开箱可用的打包工具

是的,打包工具有不少,但这三者都对 user.js 没有太好的支持,主要有两点

  1. 保证打包后顶部信息说明注释仍然在最顶部
  2. 支持多入口打包
  3. 支持 TypeScript
  4. 支持 vue/react 现代前端框架

目前最合适的是 parcel,但仍然没有默认解决第一个问题。

与之相比,chrome 插件的工程化就要好上不少,parcel 甚至官方支持了 WebManifest 类型的资源,这对浏览器插件的开发有着极大的方便 – 可以使用现代前端的一切!

发布

user.js 可以发布在任何静态托管服务里,一般可以放在 GreasyFork 上,供需要的用户通过脚本管理其安装。GreasyFork 甚至可以直接导入 GitHub 上的源码和 README 发布一个脚本,同时在后续过程中自动更新。
而 chrome 插件则只能通过 chrome web store 进行分发,而且每次更新或上架都需要被审核,这其实是比较花时间的,并非是即时性的。同时 web store 也有注册费用,必须使用 Visa 等国外信用卡支付 $5,而这对于国内开发者而言是一件比较麻烦的事情。

吾辈知道可以使用开发者模式打开解压缩的插件,但这终究不是一个好的分发策略,不能要求每个用户都执行这种麻烦的操作,同时,每次重新打开浏览器都会进行提示也不厌其烦。

对于使用者

  • 安装难度:chrome 插件安装事实上只能从 chrome store 就意味着国内用户几乎用不了,嗯,这点是一个相当的减分项。而油猴脚本则不太相同,只要支持 user.js 的插件即可添加任意多的 user.js 脚本,事实上,插件只是一个管理工具,chrome 原生支持它!
  • 需求: 如果只是为网站诸如一段脚本修改一些网站的内容,则优先考虑使用 user.js,如果涉及到操作浏览器相关功能,则只能选择插件,本质上 user.js 在插件中就是 content script 功能。
  • 性能:对于一般用户而言,安装一个 chrome 插件会一直在后台开启一个线程,而脚本不会 – 只会在需要生效的网站上应用。

博客已使用 service-worker 技术对网站添加离线支持功能,具体实现不过是使用了 hexo-offline 插件,之后所有第一次访问过的页面之后便离线可用了,同时即便在线浏览仍然能更快的展示页面。
想要使用它的主要原因是吾辈的博客是静态的,意味着几乎所有的资源都可以通过 service-worker 缓存,以此提高网站的可用性。

注: Vue 官网 使用 hexo 构建,同时使用了该插件实现离线支持。

ServiceWorker

场景

吾辈最近开发了一个 标签页快速切换插件,原本准备在 Chrome Store 先行上架的,不过由于 Chrome 的 开发者注册 需要 Visa 信用卡付费,所以不得已暂缓了下来。
于是,去看了一下 Firefox 的插件开发者中心,发现其并不需要收费,所以便想要兼容一下 Firefox,但吾辈却由此发现了奇怪的事情。

首先是 firefox 报的一个错误

1
安全错误:位于 moz-extension://b0d38f9f-eca1-4163-870e-64aedabf5f20/popup.html 的内容不可以载入或者链接至 chrome://mozapps/skin/extensions/extension.svg。

当然,吾辈看到这个错误的第一时间是很懵逼的,因为吾辈未曾引用过 chrome://mozapps/skin/extensions/extension.svg 这个 svg 图片啊,为什么会提示不可以载入呢?

原因

于是吾辈去看了 browser.tabs.query 所查询到的数据,结果。。。

1
2
3
4
arr = await browser.tabs.query({})
arr.find(
(tab) => (tab.favIconUrl = 'chrome://mozapps/skin/extensions/extension.svg'),
)

查询到的 tab

为什么 Firefox 的附加组件管理器标签页会使用 Chrome 的图标啊?难道是 Firefox 的开发团队复制 Chrome 源码的时候忘记替换了么?
抱着好玩的想法,吾辈将这个错误分享到了某个 Telegram 前端群中,有人提出了完全不同的解释:这是 Firefox 历史的遗产 – 遠古的 XUL 留下來的 scheme,甚至在 Chrome 浏览器出现之前就已经存在了。

参考:

复现步骤

如果有人想要复现这个问题玩一下的话,具体流程如下:

1
2
graph TB;
打开附加组件管理器 --> 调试附加组件 --> 选择一个扩展点击检查按钮 --> 输入上面的代码

流程示意图

总结

真相只有一个:Firefox 58 之后虽然号称重构了这个浏览器,但 UI 层并未抛弃掉老旧的 XUL,同时 Firefox 主要推动的 web extension api 则并未注意到这个问题(这里或许能从侧面说明 Firefox 的插件生态并不繁荣,这种问题都没有人遇到过。。。),导致了这种 自相冲突 的奇葩问题。