JoyLau's Blog

JoyLau 的技术学习与思考

背景

有时我们在 gradle 里定义了一些属性, 想在 springboot 的 application 配置文件里使用, 这里介绍这种处理方式, 并且将配置应用于 springboot 的自定义 Banner 图中

步骤

  1. 配置 build.gradle

添加以下配置

1
2
3
4
5
processResources {
filesMatching('application.yml') {
expand(project.properties)
}
}

如果想将 gradle 的配置应用于所有 springboot 配置文件, 则直接使用

1
2
3
processResources {
expand(project.properties)
}
  1. 在 springboot 的配置文件里使用

比如我在 gradle.properties 里定义了如下配置:

1
2
3
4
5
6
7
author=JoyLau
email=2587038142.liu@gmail.com
projectArtifact=es-doc-office
projectGroup=cn.joylau.code
projectVersion=2.0.5
javaVersion=1.8

在 springboot 配置文件里应用:

1
2
3
4
5
6
info:
app:
name: ${projectArtifact}
author: ${author}
email: ${email}
version: ${projectVersion}

需要注意的是: ${} 是 springboot 里本身使用引入内置变量的方法, 如果使用上述方式, 则原来使用 springboot 内置的方式的话需要加上 \ 转义, 即使用 \${}

经过如上使用, springboot 应用会在编译期将配置文件转化并复制到了项目的 build/resources/main 目录下, 如果到该目录下查找配置文件, 则会发现文件里的属性已经被实际的值替换了

自定义属性配置到 Banner 图中

很多时候我们会自定义 banner 图, 使用 banner.txt 可以使用 springboot 的内置变量
结合上面的使用方法, 我给出我的使用示例
banner 的生成可以去这个在线网站: Online Spring Boot Banner Generator

SpringBoot-Gradle-Property-Expansion

1
2
3
4
5
6
7
8
9
10
11
,------.  ,---.           ,------.    ,-----.   ,-----.          ,-----.  ,------. ,------. ,--.  ,-----. ,------.
| .---' ' .-' | .-. \ ' .-. ' ' .--./ ' .-. ' | .---' | .---' | | ' .--./ | .---'
| `--, `. `-. | | \ : | | | | | | | | | | | `--, | `--, | | | | | `--,
| `---. .-' | | '--' / ' '-' ' ' '--'\ ' '-' ' | |` | |` | | ' '--'\ | `---.
`------' `-----' `-------' `-----' `-----' `-----' `--' `--' `--' `-----' `------'

Author :: ${info.app.author} (${info.app.email})
Boot Version :: ${spring-boot.version}
App Version :: ${info.app.version}


最终效果

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
,------.  ,---.           ,------.    ,-----.   ,-----.          ,-----.  ,------. ,------. ,--.  ,-----. ,------.
| .---' ' .-' | .-. \ ' .-. ' ' .--./ ' .-. ' | .---' | .---' | | ' .--./ | .---'
| `--, `. `-. | | \ : | | | | | | | | | | | `--, | `--, | | | | | `--,
| `---. .-' | | '--' / ' '-' ' ' '--'\ ' '-' ' | |` | |` | | ' '--'\ | `---.
`------' `-----' `-------' `-----' `-----' `-----' `--' `--' `--' `-----' `------'

Author :: JoyLau (2587038142.liu@gmail.com)
Boot Version :: 2.1.2.RELEASE
App Version :: 2.0.5

2020-09-01 16:15:25.967 INFO 51691 --- [ main] cn.joylau.code.EsDocOfficeApplication : Starting EsDocOfficeApplication on JoyLaudeMacBook-Pro.local with PID 51691 (/Users/joylau/dev/idea-project/dev-app/es-doc-office/es-doc-office-service/build/classes/java/main started by joylau in /Users/joylau/dev/idea-project/es-doc-office)
2020-09-01 16:15:25.969 INFO 51691 --- [ main] cn.joylau.code.EsDocOfficeApplication : The following profiles are active: db,dev
2020-09-01 16:15:27.130 INFO 51691 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2020-09-01 16:15:27.231 INFO 51691 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 95ms. Found 3 repository interfaces.
2020-09-01 16:15:27.643 INFO 51691 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$54a92264] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-01 16:15:27.922 WARN 51691 --- [ main] io.undertow.websockets.jsr : UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used
2020-09-01 16:15:27.958 INFO 51691 --- [ main] io.undertow.servlet : Initializing Spring embedded WebApplicationContext
2020-09-01 16:15:27.959 INFO 51691 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1933 ms
2020-09-01 16:15:28.242 INFO 51691 --- [ main] c.a.d.s.b.a.DruidDataSourceAutoConfigure : Init DruidDataSource
2020-09-01 16:15:28.381 INFO 51691 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2020-09-01 16:15:28.659 INFO 51691 --- [ main] o.elasticsearch.plugins.PluginsService : no modules loaded
2020-09-01 16:15:28.660 INFO 51691 --- [ main] o.elasticsearch.plugins.PluginsService : loaded plugin [org.elasticsearch.index.reindex.ReindexPlugin]
2020-09-01 16:15:28.660 INFO 51691 --- [ main] o.elasticsearch.plugins.PluginsService : loaded plugin [org.elasticsearch.join.ParentJoinPlugin]
2020-09-01 16:15:28.660 INFO 51691 --- [ main] o.elasticsearch.plugins.PluginsService : loaded plugin [org.elasticsearch.percolator.PercolatorPlugin]
2020-09-01 16:15:28.660 INFO 51691 --- [ main] o.elasticsearch.plugins.PluginsService : loaded plugin [org.elasticsearch.script.mustache.MustachePlugin]
2020-09-01 16:15:28.660 INFO 51691 --- [ main] o.elasticsearch.plugins.PluginsService : loaded plugin [org.elasticsearch.transport.Netty4Plugin]
..........



背景

继上一篇文章 【indicator-sysmonitor 状态栏监控工具开启对磁盘读写的监控】,这里我想让监控的数据放到状态栏的最左侧, 可发现事情并不简单。。。

因为 Ubuntu 下并不像 Mac 下按住 option 键可随意拖动

解决方式

1
sudo vim /usr/share/indicator-application/ordering-override.keyfile

修改顺序, 数字越小越靠左

修改完毕使用 restart unity-panel-service 重启生效

但发现修改完后顺序并没有改变

这个时候需要结合状态栏实际已有的托盘图标来操作顺序

获取状态栏图标的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh

dbus-send --type=method_call --print-reply --dest=com.canonical.indicator.application /com/canonical/indicator/application/service com.canonical.indicator.application.service.GetApplications | grep "string" > /tmp/indicators.txt

c=$(wc -l < /tmp/indicators.txt)
i=$((c / 8))
s=6

while [ "$i" != "0" ]; do
echo $(awk -v n=$s '/string/ && !--n {getline; print; exit}' /tmp/indicators.txt)
s=$(( $s + 8 ))
i=$(( $i - 1 ))
done

执行这个脚本获取图标的程序名称, 再修改 ordering-override.keyfile 的顺序, restart unity-panel-service 重启生效

缺点

按照上述方式操作后, 顺序得以改变, 但是如果后续打开了新的程序有托盘图标, 则新程序的图标会在最左边

背景

indicator-sysmonitor 默认的模式可以监控 CPU 使用率, 内存使用, 网络 I/O 等, 但是却缺少了很关键的对当前磁盘 I/O 的监控,于是我就想着把他给加上去

解决方式

indicator-sysmonitor 可以新建传感器,可以自定义命令来显示输出, 于是我想着使用 shell 命令获取当前磁盘的 I/O 在输出即可

dstat 方式

  1. 使用 dstat 命令, 需要机器上事先安装 dstat
1
dstat  --disk

该命令可以监控磁盘使用情况

我在稍对结果做下过滤的优化, 使用下面的命令

1
dstat  --disk 1 1 | sed -n '4p' | awk '{printf "r: "}{printf $1}{printf "   w: "}{printf $2}'

上述的命令的解释为 1s 输出一次, 一次输出一行, 取第四行, 取第一列和第二列,在加上读写的标识 r:w: 的前缀

输出的结果为:

1
r: 0   w: 6244kj

在 indicator-sysmonitor 里新建一项, 复制上述命令,效果如下

disk-dstat

iotop

  1. 使用 iotop 命令, 需要机器上事先安装 iotop
1
sudo iotop

在美化下输出结果:

1
sudo iotop -o -b -n 1 | sed -n '2p' | awk '{printf "r: "}{printf  $4 $5}{printf "  w: "}{printf $10 $11}'

命令的意思同上

输出的结果为:

1
2
r: 0.00B/s  w: 0.00B/s

同上操作, 命令更换下,效果如下:

disk-iotop

对比

  1. 输出单位不一样,第一种方式单位只有 k,m 这样的, 第二种是 B/s, KB/s, MB/s 这样的, 不过第一种方式的单位也可以手动给补全上
  2. 第一种方式 dstat 命令不需要 root 权限即可执行, 第二种方式 iotop 命令需要 root 权限即需要加 sudo

使用 sudo 的常用方式为:

1
echo "你的 root 的密码" | sudo iotop ....

但是这样的方式在终端执行可以输出结果, 在 indicator-sysmonitor 执行却不能输出结果。。。。。

于是需要解决这个问题, 即使用普通用户执行 iotop 命令时不需要输入密码

这里我的解决方案如下:

1
2
3
4
5
6
7
8
sudo visudo

## 添加下面几行
User_Alias NET_USERS = joylau

Cmnd_Alias SYS_STATUS = /usr/sbin/iotop # 多个命令逗号隔开

NET_USERS ALL=(root) NOPASSWD:SYS_STATUS

Ctrl + O 保存后,普通用户 joylau 使用 sudo iotop 就不需要输入密码了, 也就实现了第二种方式的效果了

  1. 性能对比: 实测第一种方式的性能(CPU使用平均在 2%)要稍好于第二种(CPU使用平均在 5%)

问题描述

我的 MacBook Pro 使用的是绿联的外接扩展坞, 其中有一个网口
在京东购买的: https://item.jd.com/4445121.html

型号为: CM179
网卡芯片为: RTL8153B

最近发现只要我的 MacBook Pro 关闭了盖子, 会导致接在同一交换机下的路由器就无法上网
打开盖子后,网络又恢复正常了

解决

搜索了一番没找到解决方式

于是就找到当时购买的这款产品的京东介绍页面看了看

想着去绿联的官网找找驱动试试: https://www.lulian.cn/download/list-34-cn.html

找到对应的型号和网卡芯片: https://www.lulian.cn/download/38-cn.html

下载并解压驱动压缩包,双击“RTUNICv1.0.20.pkg”文件,一直点击继续,安装完成后重启电脑即可。

重启后又试了下关闭盖子, 问题解决了!!!

说明

Awtrix App 开发入门之开发一款显示个人博客访问人数的 App

  1. 具备 Awtrix 硬件设备
  2. 博客的计数工具是不蒜子

环境准备

  1. JDK 8 的环境
  2. 开发工具: B4J

效果图

gif
app
config

开发准备

  1. 模板文件

AWTRIX.bas: 这个文件的内容不需要改动直接复制即可

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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=7.31
@EndOfDesignText@
'This Class takes control of the Interface to AWTRIX, the Icon Renderer
'and some useful functions to make the development more easier.
'Usually you dont need to modify this Class!

#Event: Started
#Event: controllerButton(button as int,dir as boolean)
#Event: controllerAxis(axis as int, dir as float)
#Event: Exited
#Event: iconRequest
#Event: settingsChanged
#Event: startDownload(jobNr As Int) As String
#Event: evalJobResponse(Resp As JobResponse)


private Sub Class_Globals
Private Appduration As Int
Private mscrollposition As Int
Private show As Boolean = True
Private forceDown As Boolean
Private LockApp As Boolean = False
Private Icon As List
Private appName As String
Private AppVersion As String
Private TickInterval As Int
Private NeedDownloads As Int = 0
Private UpdateInterval As Int = 0
Private AppDescription As String
Private AppAuthor As String
Private SetupInfos As String
Private MatrixInfo As Map
Private appSettings As Map = CreateMap()
Private ServerVersion As String
Private DisplayTime As Int
Private MatrixWidth As Int = 32
Private MatrixHeight As Int = 8
Private DownloadHeader As Map
Private pluginversion As Int = 1
Private Tag As List = Array As String()
Private playdescription As String
Private Cover As Int
Private Game As Boolean
Private startTimestamp As Long
Private icoMap As Map
Private RenderedIcons As Map
Private animCounter As Map
Private iconList As List'ignore
Private timermap As Map
Private set As Map 'ignore
Private Target As Object
Private commandList As List
Private colorCounter As Int
Private startTime As String ="0"
Private endtime As String = "0"
Private CharMap As Map
Private TextBuffer As String
Private TextLength As Int
Private UppercaseLetters As Boolean
Private SystemColor() As Int
Private event As String
Private Enabled As Boolean = True
Private noIcon() As Short = Array As Short(0, 0, 0, 63488, 63488, 0, 0, 0, 0, 0, 63488, 0, 0, 63488, 0, 0, 0, 0, 0, 0, 0, 63488, 0, 0, 0, 0, 0, 0, 63488, 0, 0, 0, 0, 0, 0, 63488, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63488, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
Private isRunning As Boolean
Private Menu As Map
Private MenuList As List
Private bc As B4XSerializator
Private noIconMessage As Boolean
Private verboseLog As Boolean
Private finishApp As Boolean
Type JobResponse (jobNr As Int,Success As Boolean,ResponseString As String,Stream As InputStream)
Private httpMap As Map
Private OAuthToken As String
Private OAuth As Boolean
Private oauthmap As Map
Private mContentType As String

Private poll As Map = CreateMap("enable":False,"sub":"")
Private mHidden As Boolean
End Sub

'Initializes the Helperclass.
Public Sub Initialize(class As Object, Eventname As String)

oauthmap.Initialize
Tag.Initialize
httpMap.Initialize
DownloadHeader.Initialize
event=Eventname
iconList.Initialize
Icon.Initialize
commandList.Initialize
RenderedIcons.Initialize
icoMap.Initialize
animCounter.Initialize
timermap.Initialize
set.Initialize
Menu.Initialize
MatrixInfo.Initialize
MenuList.Initialize
Target=class
End Sub

'Checks if the app should shown
Private Sub timesComparative As Boolean
Try
If startTime = endtime Then Return True
Dim startT() As String=Regex.Split(":",startTime)
Dim EndT() As String=Regex.Split(":",endtime)
Dim hour As Int=DateTime.GetHour(DateTime.Now)
Dim minute As Int=DateTime.GetMinute(DateTime.Now)
Dim second As Int=DateTime.GetSecond(DateTime.Now)
Dim now, start, stop As Int
now = ((hour * 3600) + (minute * 60) + second)
start = (startT(0) * 3600) + (startT(1) * 60)
stop = ( EndT(0)* 3600) + (EndT(1) * 60)
If (start < stop) Then
Return (now >= start And now <= stop )
Else
Return (now >= start Or now <= stop)
End If
Catch
Log("Got Error from " & appName)
Log("Error in TimesComparative:")
Log(LastException)
Return True
End Try
End Sub

#Region IconRenderer
Private Sub startIconRenderer
isRunning=True
FirstTick
For Each k As Timer In timermap.Keys
k.Enabled=True
Sleep(1)
Next
End Sub

Private Sub stopIconRenderer
isRunning=False
For Each k As Timer In timermap.Keys
k.Enabled=False
Sleep(1)
Next
End Sub

Private Sub FirstTick
For Each IconID As Int In icoMap.Keys
Try
If icoMap.ContainsKey(IconID) Then
Dim ico As List=icoMap.get(IconID)
Dim parse As JSONParser
If animCounter.Get(IconID)>ico.Size-1 Then animCounter.put(IconID,0)
parse.Initialize(ico.Get(animCounter.Get(IconID)))
Dim bmproot As List = parse.NextArray
Dim bmp(bmproot.Size) As Short
For i=0 To bmproot.Size-1
bmp(i)=bmproot.Get(i)
Next
RenderedIcons.Put(IconID,bmp)
animCounter.put(IconID,animCounter.Get(IconID)+1)
Else
Log("IconID" & IconID & "doesnt exists")
End If
Catch
Log("Got Error from " & appName)
Log("Error in IconPreloader:")
Log("IconID:" & IconID)
Log(LastException)
End Try
Next
End Sub

Private Sub Timer_Tick
Try
Dim iconid As Int=timermap.Get(Sender)
If icoMap.ContainsKey(iconid) Then
Dim ico As List= icoMap.get(iconid)
Dim parse As JSONParser
If animCounter.Get(iconid)>ico.Size-1 Then animCounter.put(iconid,0)
parse.Initialize(ico.Get(animCounter.Get(iconid)))
Dim bmproot As List = parse.NextArray
Dim bpm(bmproot.Size) As Short
For i=0 To bmproot.Size-1
bpm(i)=bmproot.Get(i)
Next
RenderedIcons.Put(iconid,bpm)
animCounter.put(iconid,animCounter.Get(iconid)+1)
Else
Logger("IconID" & iconid & "doesnt exists")
End If
Catch
Log("Got Error from " & appName)
Log("Error in IconRenderer:")
Log(LastException)
stopIconRenderer
End Try
End Sub

Private Sub addToIconRenderer(iconMap As Map)
Try
If iconMap.Size=0 Then Return
Dim runMarker As Boolean
If isRunning Then
stopIconRenderer
runMarker=True
End If
timermap.Clear
icoMap.Clear
animCounter.Clear
RenderedIcons.Clear
For Each ico As Int In iconMap.Keys
Dim ico1 As Map = iconMap.get(ico)
If ico1.ContainsKey("tick") Then
icoMap.Put(ico,ico1.Get("data"))
animCounter.Put(ico,0)
Dim timer As Timer
timer.Initialize("Timer",ico1.Get("tick"))
Dim icoExists As Boolean=False
For Each timerico As Int In timermap.Values
If timerico=ico Then icoExists=True
Next
If Not(icoExists) Then timermap.Put(timer,ico)
Else
RenderedIcons.Put(ico,ico1.Get("data"))
End If
Next
If runMarker Then
startIconRenderer
End If
Catch
Log("Got Error from " & appName)
Log("Error in IconAdder:")
Log(LastException)
End Try
End Sub

'returns the rendered Icon
Public Sub getIcon(ID As Int) As Short()
If RenderedIcons.ContainsKey(ID) Then
Return RenderedIcons.Get(ID)
Else
If noIconMessage = False Then
Logger("Icon " & ID & " not found")
noIconMessage=True
End If

Return noIcon
End If
End Sub
#End Region

'This is the interface between AWTRIX and the App
Public Sub interface(function As String, Params As Map) As Object
Select Case function
Case "start"
mscrollposition=MatrixWidth
If SubExists(Target,event&"_Started") Then
CallSub(Target,event&"_Started")
End If
Try
Appduration = Params.Get("AppDuration")
If DisplayTime>0 Then
Appduration=DisplayTime
End If
verboseLog =Params.Get("verboseLog")
ServerVersion = Params.Get("ServerVersion")
MatrixWidth = Params.Get("MatrixWidth")
MatrixHeight = Params.Get("MatrixHeight")
UppercaseLetters = Params.Get("UppercaseLetters")
CharMap = Params.Get("CharMap")
SystemColor = Params.Get("SystemColor")
MatrixInfo=Params.Get("MatrixInfo")
set.Put("interval",TickInterval)
set.Put("needDownload",NeedDownloads)
set.Put("DisplayTime", DisplayTime)
set.Put("forceDownload", forceDown)
Catch
Log("Got Error from " & appName)
Log("Error in start procedure")
Log(LastException)
End Try
startTimestamp=DateTime.now
noIconMessage=False
If show Then
set.Put("show",timesComparative)
Else
set.Put("show",show)
End If

set.Put("isGame",Game)
set.Put("hold",LockApp)
set.Put("iconList",Icon)
Return set
Case "downloadCount"
Return NeedDownloads
Case "startDownload"
httpMap.Initialize
DownloadHeader.Initialize
mContentType=""
If SubExists(Target,event&"_startDownload") Then
CallSub2(Target,event&"_startDownload",Params.Get("jobNr"))
End If
If DownloadHeader.Size>0 Then
httpMap.Put("Header",DownloadHeader)
End If
If mContentType.Length>0 Then
httpMap.Put("ContentType",mContentType)
End If
Return httpMap
Case "httpResponse"
Dim res As JobResponse
res.Initialize
res.jobNr=Params.Get("jobNr")
res.Success=Params.Get("success")
res.ResponseString=Params.Get("response")
res.Stream=Params.Get("InputStream")
If SubExists(Target,event&"_evalJobResponse") Then
CallSub2(Target,event&"_evalJobResponse",res)
End If
Return True
Case "running"
startIconRenderer
Case "tick"
commandList.Clear
If finishApp Then
finishApp=False
commandList.Add(CreateMap("type":"finish"))
Else
If SubExists(Target,event&"_genFrame") Then
CallSub(Target,event&"_genFrame")'ignore
End If
End If

Return commandList
Case "infos"
Dim infos As Map
infos.Initialize
Dim isconfigured As Boolean = True
If File.Exists(File.Combine(File.DirApp,"Apps"),appName&".ax") Then
Dim m As Map = bc.ConvertBytesToObject(File.ReadBytes(File.Combine(File.DirApp,"Apps"),appName&".ax"))
For Each v As Object In m.Values
If v="null" Or v="" Then
isconfigured=False
End If
Next
If OAuth And OAuthToken.Length=0 Then isconfigured=False
End If
infos.Put("isconfigured",isconfigured)
infos.Put("AppVersion",AppVersion)
infos.Put("tags",Tag)
infos.Put("poll",poll)
infos.Put("oauth",OAuth)
infos.Put("oauthmap",oauthmap)
infos.Put("isGame",Game)
infos.Put("CoverIcon",Cover)
infos.Put("pluginversion",pluginversion)
infos.Put("author",AppAuthor)
infos.Put("howToPLay",playdescription)
infos.Put("description",AppDescription)
infos.Put("setupInfos",SetupInfos)
infos.Put("hidden",mHidden)
Return infos
Case "setSettings"
makeSettings
Return True
Case "getUpdateInterval"
Return UpdateInterval
Case "setEnabled"
saveSingleSetting("Enabled",Params.Get("Enabled"))
makeSettings
Case "getEnable"
Return Enabled
Case "stop"
If Game Then
finishApp=False
show=False
End If
stopIconRenderer
If SubExists(Target,event&"_Exited") Then
CallSub(Target,event&"_Exited")
End If
Case "getIcon"
If SubExists(Target,event&"_iconRequest") Then
CallSub(Target,event&"_iconRequest")
End If
Return CreateMap("iconList":Icon)
Case "iconList"
addToIconRenderer(Params)
Case "externalCommand"
externalCommand(Params)
Case "controller"
Control(Params)
Case "getMenu"
Menu.Initialize
Menu.Put("Version","1.6")
Menu.Put("Theme","Light Theme")
Menu.Put("Items",MenuList)
Return Menu
Case "setToken"
OAuthToken=Params.Get("token")
Case "isReady"
If SubExists(Target,event&"_isReady") Then
Return CallSub(Target,event&"_isReady")
Else
Return True
End If

Case "shouldShow"
Return show
Case "poll"
Dim s As String=Params.Get("sub")
If SubExists(Target,event & "_" & s) Then
CallSub(Target,event & "_" & s)
End If
End Select
Return True
End Sub

'This function calculates the ammount of pixels wich a text needs
Public Sub calcTextLength(text As String) As Int
If UppercaseLetters Then text = text.ToUpperCase
If TextBuffer<>text Then
Dim Length As Int
For i=0 To text.Length-1
If CharMap.ContainsKey(Asc(text.CharAt(i))) Then
Length=Length+(CharMap.Get(Asc(text.CharAt(i))))
Else
Length=Length+4
End If
Next
TextBuffer=text
TextLength=Length
Return Length
End If
Return TextLength
End Sub

'This Helper automaticly display a text in a default app style
'If the text is longer than the Matrixwitdh it will scroll the text
'otherwise it will center the text. Call drawText to handle it manually.
'
'Text - the text to be displayed
'IconOffset - wether you need an offset if you place an icon on the left side.
'yPostition
'Color - custom text color. Pass Null to use the Global textcolor (recommended).
'
'<code>App.genText("Hello World",True,Array as int(255,0,0),false)</code>
Public Sub genText(Text As String,IconOffset As Boolean,yPostition As Int,Color() As Int,callFinish As Boolean)
If Text.Length=0 Then
finish
Return
End If
calcTextLength(Text)
Dim offset As Int
If IconOffset Then offset = 24 Else offset = 32
If TextLength>offset Then
drawText(Text,mscrollposition,yPostition,Color)
mscrollposition=mscrollposition-1
If mscrollposition< 0-TextLength Then
If LockApp And callFinish Then
finish
Return
Else
mscrollposition=MatrixWidth
End If
End If
Else
Dim x As Int
If TextLength<offset+1 Then
If IconOffset Then
x=((MatrixWidth/2)-TextLength/2)+4
Else
x=(MatrixWidth/2)-TextLength/2
End If
End If
drawText(Text,x,yPostition,Color)
End If
End Sub

'This functions build and savee the settings. You dont need to call this manually
Public Sub makeSettings
If Game Then show=False
If File.Exists(File.Combine(File.DirApp,"Apps"),appName&".ax") Then
Dim data() As Byte = File.ReadBytes(File.Combine(File.DirApp,"Apps"),appName&".ax")
Dim m As Map = bc.ConvertBytesToObject(data)
For Each k As String In appSettings.Keys
If Not(m.ContainsKey(k)) Then
m.Put(k,appSettings.Get(k))
Else
appSettings.Put(k,m.Get(k))
End If
Next
For Counter = m.Size -1 To 0 Step -1
Dim SettingsKey As String = m.GetKeyAt(Counter)
If Not(SettingsKey="UpdateInterval" Or SettingsKey="StartTime" Or SettingsKey="EndTime" Or SettingsKey="DisplayTime" Or SettingsKey="Enabled") Then
If Not(appSettings.ContainsKey(SettingsKey)) Then m.Remove(SettingsKey)
End If
Next
Try
Enabled=m.Get("Enabled")
startTime=m.Get("StartTime")
endtime=m.Get("EndTime")
UpdateInterval=m.Get("UpdateInterval")
DisplayTime=m.Get("DisplayTime")
File.WriteBytes(File.Combine(File.DirApp,"Apps"),appName&".ax",bc.ConvertObjectToBytes(m))
If SubExists(Target,event&"_settingsChanged") Then
CallSub(Target,event&"_settingsChanged")'ignore
End If
Catch
Log("Got Error from " & appName)
Log("Error while saving settings")
Log(LastException)
End Try
Else
Dim m As Map
m.Initialize
m.Put("UpdateInterval",UpdateInterval)
m.Put("StartTime","00:00")
m.Put("EndTime","00:00")
m.Put("DisplayTime","0")
m.Put("Enabled",True)
For Each k As String In appSettings.Keys
m.Put(k,appSettings.Get(k))
Next
File.WriteBytes(File.Combine(File.DirApp,"Apps"),appName&".ax",bc.ConvertObjectToBytes(m))
End If
End Sub

'Returns the value of a Settingskey
public Sub get(SettingsKey As String) As Object
If appSettings.ContainsKey(SettingsKey) Then
Return appSettings.Get(SettingsKey)
Else
Log(SettingsKey & " not found")
Return ""
End If
End Sub

Public Sub saveSingleSetting(key As String, value As Object)
If File.Exists(File.Combine(File.DirApp,"Apps"),appName&".ax") Then
Dim data() As Byte = File.ReadBytes(File.Combine(File.DirApp,"Apps"),appName&".ax")
Dim m As Map = bc.ConvertBytesToObject(data)
m.Put(key,value)
File.WriteBytes(File.Combine(File.DirApp,"Apps"),appName&".ax",bc.ConvertObjectToBytes(m))
End If
End Sub


'Draws a Bitmap
Public Sub drawBMP(x As Int,y As Int,bmp() As Short,width As Int, height As Int)
commandList.Add(CreateMap("type":"bmp","x":x,"y":y,"bmp":bmp,"width":width,"height":height))
End Sub

'Draws a Text
Public Sub drawText(text As String,x As Int, y As Int,Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"text","text":text,"x":x,"y":y))
Else
commandList.Add(CreateMap("type":"text","text":text,"x":x,"y":y,"color":Color))
End If
End Sub

'Draws a Circle
Public Sub drawCircle(X As Int, Y As Int, Radius As Int, Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"circle","x":x,"y":y,"r":Radius,"color":SystemColor))
Else
commandList.Add(CreateMap("type":"circle","x":x,"y":y,"r":Radius,"color":Color))
End If
End Sub

'Draws a filled Circle
Public Sub fillCircle(X As Int, Y As Int, Radius As Int, Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"fillCircle","x":x,"y":y,"r":Radius,"color":SystemColor))
Else
commandList.Add(CreateMap("type":"fillCircle","x":x,"y":y,"r":Radius,"color":Color))
End If
End Sub

'Draws a single Pixel
Public Sub drawPixel(X As Int,Y As Int,Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"pixel","x":x,"y":y,"color":SystemColor))
Else
commandList.Add(CreateMap("type":"pixel","x":x,"y":y,"color":Color))
End If
End Sub

'Draws a Rectangle
Public Sub drawRect(X As Int,Y As Int,Width As Int,Height As Int,Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"rect","x":x,"y":y,"w":Width,"h":Height,"color":SystemColor))
Else
commandList.Add(CreateMap("type":"rect","x":x,"y":y,"w":Width,"h":Height,"color":Color))
End If
End Sub

'Draws a Line
Public Sub drawLine(X0 As Int,Y0 As Int,X1 As Int,Y1 As Int,Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"line","x0":X0,"y0":Y0,"x1":X1,"y1":Y1,"color":SystemColor))
Else
commandList.Add(CreateMap("type":"line","x0":X0,"y0":Y0,"x1":X1,"y1":Y1,"color":Color))
End If
End Sub

'Sends a custom or undocumented command
Public Sub customCommand(cmd As Map)
commandList.Add(cmd)
End Sub

'Fills the screen with a color
Public Sub fill(Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"fill","color":SystemColor))
Else
commandList.Add(CreateMap("type":"fill","color":Color))
End If
End Sub

'Exits the app and force AWTRIX to switch to the next App
'only needed if you have set LockApp to true
Public Sub finish
finishApp=True
End Sub

'Returns a rainbowcolor wich is fading each tick
Public Sub rainbow As Int()
colorCounter=colorCounter+1
If colorCounter>255 Then colorCounter=0
Return(wheel(colorCounter))
End Sub

Private Sub wheel(Wheelpos As Int) As Int() 'ignore
If(Wheelpos < 85) Then
Return Array As Int(Wheelpos * 3, 255 - Wheelpos * 3, 0)
else if(Wheelpos < 170) Then
Wheelpos =Wheelpos- 85
Return Array As Int(255 - Wheelpos * 3, 0, Wheelpos * 3)
Else
Wheelpos =Wheelpos- 170
Return Array As Int(0, Wheelpos * 3, 255 - Wheelpos * 3)
End If
End Sub

Public Sub Logger(msg As String)
If verboseLog Then
DateTime.DateFormat=DateTime.DeviceDefaultTimeFormat
Log(DateTime.Date(DateTime.Now) &" " & appName & ":" & CRLF & msg)
End If
End Sub

Private Sub Control(controller As Map)
If controller.ContainsKey("GameStart") And Game Then
Dim state As Boolean = controller.Get("GameStart")
If state Then
show=True
Else
finishApp=True
show=False
End If
Return
End If

If controller.ContainsKey("button") Then
Dim buttonNR As Int = controller.Get("button")
Dim buttonDIR As Boolean = controller.Get("dir")
If SubExists(Target,event&"_controllerButton") Then
CallSub3(Target,event&"_controllerButton",buttonNR,buttonDIR)
End If
If verboseLog Then
If buttonDIR Then Logger($"Button ${buttonNR} down"$) Else Logger($"Button ${buttonNR} up"$)
End If
Return
End If

If controller.ContainsKey("axis") Then
Dim AxisNR As Int = controller.Get("axis")
Dim val As Float = controller.Get("dir")
If SubExists(Target,event&"_controllerAxis") Then
CallSub3(Target,event&"_controllerAxis",AxisNR,val)
End If
Return
End If
End Sub

Private Sub externalCommand(cmd As Map)
If SubExists(Target,event&"_externalCommand") Then
CallSub2(Target,event&"_externalCommand",cmd)
End If
End Sub

Public Sub throwError(message As String)
Logger(message)
End Sub

'Returns the timestamp when the app was started.
Sub getstartedAt As Long
Return startTimestamp
End Sub

'Gets or sets the app tags
Sub gettags As List
Return Tag
End Sub

Sub settags(Tags As List)
Tag=Tags
End Sub

'Returns the runtime of the app
Sub getduration As Int
Return Appduration
End Sub

'If set to true, awtrix will skip this app
Sub setshouldShow(shouldShow As Boolean)
show=shouldShow
End Sub

'If set to true, AWTRIX will download new data before each start.
Sub setforceDownload(forceDownload As Boolean)
forceDown=forceDownload
End Sub

'If set to true AWTRIX will wait for the "finish" command before switch to the next app.
Sub setlock(lock As Boolean)
LockApp=lock
End Sub

'IconIDs from AWTRIXER. You can add multiple if you need more
Sub seticons(icons As List)
Icon=icons
End Sub

'Sets or gets the appname
Sub getname As String
Return appName
End Sub

Sub setname(name As String)
appName=name
End Sub

'Sets or gets the app description
Sub getdescription As String
Return AppDescription
End Sub

Sub setdescription(description As String)
AppDescription=description
End Sub

'The developer if this App
Sub getauthor As String
Return AppAuthor
End Sub

Sub setauthor(author As String)
AppAuthor=author
End Sub

'Sets or gets the appversion
Sub getversion As String
Return AppVersion
End Sub

Sub setversion(version As String)
AppVersion=version
End Sub

'Sets or gets the tickinterval
Sub gettick As String
Return TickInterval
End Sub

Sub settick(tick As String)
TickInterval=tick
End Sub

'How many downloadhandlers should be generated
Sub setdownloads(downloads As Int)
NeedDownloads=downloads
End Sub

'Setup Instructions. You can use HTML to format it
Sub setsetupDescription(setupDescription As String)
SetupInfos=setupDescription
End Sub

'gets all informations from the matrix as a map
Sub getmatrix As Map
Return MatrixInfo
End Sub

'needed Settings for this App (wich can be configurate from user via webinterface)
Sub setsettings(settings As Map)
appSettings=settings
End Sub

'returns the version of the serever
Sub getserver As String
Return ServerVersion
End Sub

'returns the size of the Matrix as an array (height,width)
Sub getmatrixSize As Int()
Dim size() As Int = Array As Int(MatrixHeight,MatrixWidth)
Return size
End Sub

'if this is a game you can set your play instructions here
Sub sethowToPlay(howToPlay As String)
playdescription=howToPlay
End Sub

'Icon (ID) to be displayed in the Appstore and MyApps
Sub setcoverIcon(coverIcon As Int)
Cover=coverIcon
End Sub

'set this to true if this is a game.
Sub setisGame(isGame As Boolean)
Game=isGame
End Sub

public Sub InitializeOAuth (AuthorizeURL As String, TokenURL As String, ClientId As String, ClientSecret As String, Scope As String)
OAuth=True
oauthmap=CreateMap("AuthorizeURL":AuthorizeURL,"TokenURL":TokenURL,"ClientId":ClientId,"ClientSecret":ClientSecret,"Scope":Scope)
End Sub

Sub getToken As String
Return OAuthToken
End Sub

Sub getScrollposition As Int
Return mscrollposition
End Sub

'Sends a POST request with the given data as the post data.
Public Sub PostString(Link As String, Text As String)
httpMap=CreateMap("type":"PostString","Link":Link,"Text":Text)
End Sub

'Sends a POST request with the given string as the post data
Public Sub PostBytes(Link As String, Data() As Byte)
httpMap=CreateMap("type":"PostBytes","Link":Link,"Data":Data)
End Sub

'Sends a PUT request with the given data as the post data.
Public Sub PutString(Link As String, Text As String)
httpMap=CreateMap("type":"PutString","Link":Link,"Text":Text)
End Sub

'Sends a PUT request with the given string as the post data
Public Sub PutBytes(Link As String, Data() As Byte)
httpMap=CreateMap("type":"PutBytes","Link":Link,"Data":Data)
End Sub

'Sends a PATCH request with the given string as the request payload.
Public Sub PatchString(Link As String, Text As String)
httpMap=CreateMap("type":"PatchString","Link":Link,"Text":Text)
End Sub

'Sends a PATCH request with the given data as the request payload.
Public Sub PatchBytes(Link As String, Data() As Byte)
httpMap=CreateMap("type":"PatchBytes","Link":Link,"Data":Data)
End Sub

'Sends a HEAD request.
Public Sub Head(Link As String)
httpMap=CreateMap("type":"Head","Link":Link)
End Sub

'Sends a multipart POST request.
'NameValues - A map with the keys and values. Pass Null if not needed.
'Files - List of MultipartFileData items. Pass Null if not needed.
Public Sub PostMultipart(Link As String, NameValues As Map, Files As List)
httpMap=CreateMap("type":"PostMultipart","Link":Link,"NameValues":NameValues,"Files":Files)
End Sub

'Sends a POST request with the given file as the post data.
'This method doesn't work with assets files.
Public Sub PostFile(Link As String, Dir As String, FileName As String)
httpMap=CreateMap("type":"PostFile","Link":Link,"Dir":Dir,"FileName":FileName)
End Sub

'Submits a HTTP GET request.
'Consider using Download2 if the parameters should be escaped.
Public Sub Download(Link As String)
httpMap=CreateMap("type":"Download","Link":Link)
End Sub

'Submits a HTTP GET request.
'Encodes illegal parameter characters.
'<code>Example:
'job.Download2("http://www.example.com", _
' Array As String("key1", "value1", "key2", "value2"))</code>
Public Sub Download2(Link As String, Parameters() As String)
httpMap=CreateMap("type":"Download2","Link":Link,"Parameters":Parameters)
End Sub

'Sets the header for the request as an map
'(Headername,Headervalue)
Public Sub setHeader(header As Map)
DownloadHeader=header
End Sub

'Sets the Mime header of the request.
'This method should only be used with requests that have a payload.
Public Sub SetContentType(ContentType As String)
mContentType=ContentType
End Sub

'enables pollingmode
'pass the subname wich should be called every 5s. e.g for App_mySub :
'<code>app.pollig("mySub"):</code>
'if you pass a empty String ("") AWTRIX will start the download
Public Sub polling(enable As Boolean,subname As String)
poll=CreateMap("enable":enable,"sub":subname)
End Sub


'hide this app from apploop
Sub setHidden(hide As Boolean)
mHidden=hide
End Sub





  1. BlogViews.b4j: b4j 工程配置文件
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
AppType=StandardJava
Build1=Default,b4j.example
Group=Default Group
Library1=jcore
Library2=json
Library3=jrandomaccessfile
Module1=AWTRIX
Module2=BlogViews
NumberOfFiles=0
NumberOfLibraries=3
NumberOfModules=2
Version=8.5
@EndOfDesignText@
'Draws a Rectangle'Non-UI application (console / server application)
#Region Project Attributes
#LibraryName:BlogViews
#End Region

Sub Process_Globals

End Sub

Sub AppStart (Args() As String)

End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
Return True
End Sub

其中修改:

  • Module2
  • LibraryName
  1. BlogViews.bas: 程序主文件
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
B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=4.2
@EndOfDesignText@

Sub Class_Globals
Dim App As AWTRIX

'Declare your variables here
Dim followers As Int = 0
Dim iconId As Int = 8
End Sub

' ignore
public Sub GetNiceName() As String
Return App.Name
End Sub

' ignore
public Sub Run(Tag As String, Params As Map) As Object
Return App.interface(Tag,Params)
End Sub

' Config your App
Public Sub Initialize() As String

App.Initialize(Me,"App")

'App name (must be unique, avoid spaces)
App.Name="BlogViews"

'Version of the App
App.Version="1.0"

'Description of the App. You can use HTML to format it
App.Description=$"Shows your website unique visitor on <b>busuanzi</b> statistical tools"$

App.Author="JoyLau"

App.CoverIcon=iconId

'SetupInstructions. You can use HTML to format it
App.setupDescription= $"
<b>Website:</b> Your Website Address<br/>
<b>IconID:</b> Icon id<br/>
"$

'How many downloadhandlers should be generated
App.Downloads=1

'IconIDs from AWTRIXER. You can add multiple if you want to display them at the same time
App.Icons=Array As Int(iconId)

'Tickinterval in ms (should be 65 by default, for smooth scrolling))p://
App.Tick=65

'needed Settings for this App (Wich can be configurate from user via webinterface)
App.settings=CreateMap("Website":"http://blog.joylau.cn","IconID":iconId)

App.MakeSettings
Return "AWTRIX20"
End Sub

'this sub is called right before AWTRIX will display your App
Sub App_Started

End Sub

'Called with every update from Awtrix
'return one URL for each downloadhandler
Sub App_startDownload(jobNr As Int)
Select jobNr
Case 1
App.Download("http://busuanzi.ibruce.info/busuanzi?jsonpCallback=callback")
App.Header = CreateMap("Referer":App.Get("Website"),"Cookie":"busuanziId=D58737A150864C68B83F962028616CD6")
End Select
End Sub

'process the response from each download handler
'if youre working with JSONs you can use this online parser
'to generate the code automaticly
'https://json.blueforcer.de/
Sub App_evalJobResponse(Resp As JobResponse)
Try
If Resp.success Then
Select Resp.jobNr
Case 1
Dim parser As JSONParser
parser.Initialize(Resp.ResponseString.replace("try{callback(","").replace(");}catch(e){}",""))
Dim root As Map = parser.NextObject
followers = root.Get("site_uv")
End Select
End If
Catch
Log("Error in: "& App.Name & CRLF & LastException)
Log("API response: " & CRLF & Resp.ResponseString)
End Try
End Sub

'this sub is called right before AWTRIX will display your App
Sub App_iconRequest
App.Icons=Array As Int(App.Get("IconID"))
End Sub

'With this sub you build your frame.
Sub App_genFrame
App.genText(followers,True,1,Null,True)
App.drawBMP(0,0,App.getIcon(App.Get("IconID")),8,8)
End Sub

配置项解释:

  1. App.name: 应用程序的名称。在Appstore,MyApps和文件名中使用
  2. App.version: 应用程序的版本。版本必须是数字,并且最多可以包含2个小数位(例如1.25)
  3. App.description: 应用程序的描述,简要地描述应用。可以选择将文本格式设置为HTML
  4. App.author: 应用程序的创建者。
  5. App.coverIcon:应用程序的图标。数据库中的 IconID。也可以在 Web 页面里创建并上传自己的图标
  6. App.settings: 应用程序的设置。生成一个由键和值组成的映射。例如```CreateMap(“ Key”:“ Value”)``可以输入缺省值,以便应用可以立即启动,也可以将值保留为空(“”),以便 AWTRIX 通知用户需要调整。在设置之前,AWTRIX 将不会加载该应用程序!
  7. App.setupDescription: 简要说明如何设置应用程序。可以将文本格式设置为HTML
  8. App.downloads: 指定您的应用需要下载多少次。如果一个下载依赖于另一下载,则需要多次下载。
  9. App.icons: 指定应用程序需要的图标。在启动应用程序之前,这些也将由 AWTRIX 下载。
  10. App.tick: 指定应用程序应运行的速度。对于简单的文本,65(ms)是最适合的。

程序解释:

  1. 设置默认的访问量 0,默认使用的图标 id: 1230
  2. App 运行时请求接口: http://busuanzi.ibruce.info/busuanzi?jsonpCallback=callback, 需要带上头信息 Referer,和 Cookie

Referer: 模拟浏览器请求,否则的话不蒜子的接口将不可用
Cookie: 带上 Cookie 的话相当于用户一直在刷新网页的效果,此时独立访客数不会 +1 的, 不带上的话每次访问接口都是导致数目 +1 ,造成数量不准确

  1. 解析返回的回调信息字符串结果, 获取想要的数据 site_uv

编译

  1. 将以上项目导入 B4J
  2. 点击 Tools -> Configure Paths , 设置 javac 的目录和 jar 包导出目录(Additional Libraries)
  3. 编译 jar 包: 点击 Project -> Compile to Library

手动安装

  1. 将已编译的 jar 包复制到服务端的 Apps 文件夹中
  2. 在 Awtrix Web 界面重新启动 AWTRIX
1
reload apps

报错信息

1
2
You have JVM property https.proxyHost set to '...'.
This may lead to incorrect behaviour. Proxy should be set in Settings | Proxy

这是由于本地开启了科学上网代理服务造成的

解决方式

select Help -> Edit Custom VM Options add below:

-Dhttp.proxyHost
-Dhttp.proxyPort
-Dhttps.proxyHost
-Dhttps.proxyPort
-DsocksProxyHost
-DsocksProxyPort

有时我们希望在后台实时生成文件并下载到客户端

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
@GetMapping(value = "download")
public void download(HttpServletResponse response) {
try(OutputStream outputStream = response.getOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8)
) {
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=" + new String("压缩文件.zip".getBytes("UTF-8"), "ISO-8859-1"));

File[] files = new File("").listFiles();
for (File file : files) {
// 单个文件压缩
compress(zipOutputStream, new FileInputStream(file), file.getName());
}
zipOutputStream.flush();
} catch (IOException e) {
}

}



/**
* 单个文件压缩
*
* @param zipOutputStream
* @param inputStream
* @param fileName
* @throws IOException
*/
private static void compress(ZipOutputStream zipOutputStream, InputStream inputStream, String fileName) throws IOException {
if (inputStream == null) return;
zipOutputStream.putNextEntry(new ZipEntry(fileName));
int bytesRead;
byte[] buffer = new byte[FileUtil.BUFFER_SIZE];
while ((bytesRead = inputStream.read(buffer)) != -1) {
zipOutputStream.write(buffer, 0, bytesRead);
}
zipOutputStream.closeEntry();
inputStream.close();
}

错误信息

1
2
3
4
5
 The module '/node_modules/better-sqlite3/build/better_sqlite3.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 57. This version of Node.js requires
NODE_MODULE_VERSION 64. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).

解决方法

npm install --save-dev electron-rebuild
使用electron-rebuild进行重新编译:

node_modules/.bin/electron-rebuild -f -w better-sqlite3

如果没有编译成功, 则查看是否安装了, node-gyp
因为在 electron-rebuild 项目的 README 里,
看到这句话:

1
If you have a good node-gyp config but you see an error about a missing element on Windows like `Could not load the Visual C++ component "VCBuild.exe"`, try to launch electron-rebuild in an npm script:
0%