Compare commits

..
Author SHA1 Message Date
LamGC 27ede0fe92 [Change] 将RankingMode和RankingContentType从PixivURL分离, 并增加Parser, 顺便整理单行过长的代码;
[Add] RankingMode, RankingModeParser 从PixivURL分离并增加对应Parser;
[Add] RankingContentType, RankingContentTypeParser 从PixivURL分离并增加对应Parser;
[Delete] PixivURL 分离RankingMode和RankingContentType两个枚举类;
[Add] BotEventHandler 注册两个新的Parser;
[Change] BotCommandProcess 适配更改, 调整`ranking`命令的"type"和"mode"参数数据类型;
[Change] DateParser, PagesQualityParser 调整包路径;
[Change] BotAdminCommandProcess, CacheStoreCentral, Main, PixivDownload, PixivDownloadTest, RandomRankingArtworksSender, RankingUpdateTimer 适配更改;
2020-08-12 14:46:05 +08:00
LamGC 7bd9e1c410 Closing #23 - 为部分命令【增加/优化】翻页功能 (#27)
* [Add] 为Ranking命令增加翻页功能;

[Change] BotCommandProcess 为`ranking`方法增加`pageIndex`参数, 增加对起始排名的计算, 移除起始排名的固定值, 改按pageIndex计算;
[Fix] CacheStoreCentral 修复`getRankingInfoByCache`方法中潜在的排行榜返回错误的问题;
[Delete] PixivDownload 移除无用的两个方法;
[Change] PixivDownload 调整`@SuppressWarnings`注解的范围;

* [Change] 调整搜索接口返回数据中Attribute的Enum存放位置;

[Change] PixivSearchLinkBuilder 调整SuppressWarnings注解;
[Deprecated] PixivSearchLinkBuilder.SearchArea.jsonKey 将属性标记deprecated;
[Add] PixivSearchAttribute 增加搜索结果属性枚举类, 用于代替`PixivSearchLinkBuilder.SearchArea.jsonKey`;

* [Add] 增加PixivSearchLinkBuilder中内部属性的getter, 增加Pixiv工具类;

[Update] PixivSearchAttribute 补充Javadoc;
[Add] PixivSearchLinkBuilder 增加大部分属性的getter方法;
[Add] PixivUtils 增加Pixiv工具类, 并添加通过字符串参数构造PixivSearchLinkBuilder的方法;

* [Fix] 修复搜索命令中area参数区分大小写的问题;

[Fix] PixivUtils 修复area参数解析时区分大小写的问题;

* [Fix] 修复搜索参数Area不在缓存标识范围内的问题;

[Fix] CacheStoreCentral 调整搜索功能的缓存Key形式, 移除Url不变部分, 增加Area部分到缓存标识中;

* [Add] 为搜索命令优化翻页功能;

[Change] BotCommandProcess 重制search命令, 增加用于获取范围相关搜索结果页面的`getSearchResult`方法, 调整消息格式;

* [Update] 优化搜索命令中的去重方法;

[Update] BotCommandProcess 调整搜索命令, 使用HashSet对artworkList进行去重;

* [Delete] 移除无用的`getSearchBody(String, ...)`方法;

[Delete] CacheStoreCentral 移除无用的方法;
2020-07-29 03:21:38 +08:00
LamGC cc4eeac728 [Fix] 修复Mirai框架在首次设置被禁言状态后依然执行命令的漏洞;
[Fix] MiraiMain 修复Mirai框架在首次设置被禁言状态后依然执行命令的漏洞;
2020-07-28 20:49:52 +08:00
LamGC 6b47e19569 [Update] 完善排行榜和搜索接口的文档;
[Change] Pixiv排行榜接口.md 整理文档格式;
[Add] Pixiv搜索接口.md 添加Url参数"Type"与返回数据中类型存在与否的关系表;
[Add] Pixiv搜索接口.md 补充了其他属性的信息;
2020-07-18 14:52:52 +08:00
LamGC 24ff4b214e [Change] 调整并规范化README.md内容
[Change] README.md 补充少量内容并按照 sparanoid/chinese-copywriting-guidelines 指南项目调整内容格式;
2020-07-17 21:44:30 +08:00
LamGC bf8de1ac1e [Fix] 修复潜在的时区错误问题;
[Fix] Dockerfile.sample 将时区配置从`Asia/Shanghai`改成`GMT+8`;
2020-07-15 14:09:47 +08:00
LamGC a7c434da61 [Change] 调整Readme内容;
[Fix] README.md 补充管理员命令内容;
2020-07-15 10:35:31 +08:00
LamGC 575dc0c7fb [Fix] Dockerfile.sample 修复了容器无法正常启动的问题; 2020-07-14 11:01:21 +08:00
LamGC 56ef463c63 [Change] MessageEvent 调整toString输出中hashCode的格式; 2020-07-13 20:35:39 +08:00
LamGC 4387da37f5 [Change] 调整群禁言管理职责;
[Delete] BotEventHandler 移除禁言状态管理相关功能;
[Add] GroupMuteManager, GroupMuteManagerTest 增加群禁言状态管理类;
[Change] MiraiMain 将禁言状态对接由使用BotEventHandler改为独立管理;
2020-07-13 09:47:36 +08:00
LamGC 0fc3e3ab48 [Change] 调整应用内事件线程池参数;
[Change] BotEventHandler 调整线程池参数;
2020-07-13 09:47:31 +08:00
LamGC a606ec0423 [Fix] IssueTemplates 修复模板错误的问题; 2020-07-12 11:18:29 +08:00
LamGC 210aa84ed5 [Update] IssueTemplates 增加功能模板并更新Bug模板; 2020-07-12 11:12:21 +08:00
LamGC 6fbbe522db [Fix #22] PixivDownload 修复因InputStream意外关闭导致的异常误判; 2020-07-12 01:12:58 +08:00
LamGC 6d55325fc7 [Fix] PixivUgoiraBuilder 修复ZipInputStream在读取完第一帧图像后被意外关闭的问题; 2020-07-10 09:53:41 +08:00
LamGC d1c7f6f973 [Version] 更新版本(2.5.2-20200630.2-SNAPSHOT -> 2.5.2-20200709.1-SNAPSHOT); 2020-07-09 22:45:36 +08:00
LamGC 0727ef4f93 [Clear] CQPluginMain 整理代码; 2020-07-09 22:19:35 +08:00
LamGC 73a1caaf46 [Fix #21] 修复了RandomIntervalSendTimer在高版本Java中因非法反射导致应用异常终止的问题; 2020-07-09 22:17:09 +08:00
LamGC 4784f8773b [Change] Pixiv搜索推荐候选接口.md 更新文档内容; 2020-07-03 10:09:21 +08:00
LamGC f80b6e72e0 [Fix] CQPluginMain 通过延迟加载来修复ApplicationBoot初始化失败的问题; 2020-07-03 09:11:27 +08:00
LamGC 05e933838e Merge branch 'update-SpringCQ' 2020-07-03 08:20:18 +08:00
LamGC 3045b571a8 [Change] Main, SpringCQApplication 移除仅与SpringCQ相关的Spring相关代码, 转移到SpringCQApplication;
[Delete] CQPluginMain 移除注释信息;
2020-07-03 07:52:48 +08:00
LamGC 553212e556 [Change] SpringCQApplication, Main 将SpringCQ启动部分迁移到SpringCQApplication内; 2020-07-03 00:23:56 +08:00
LamGC 5383de7450 Merge remote-tracking branch 'origin/master' 2020-07-02 23:33:36 +08:00
LamGC 9964205dc2 [Add] Pixiv搜索推荐候选接口.md 添加可用于优化搜索的接口文档; 2020-07-02 23:21:28 +08:00
LamGC 394c3940e4 Merge remote-tracking branch 'origin/master' 2020-07-02 14:03:59 +08:00
LamGC d3d6f151d4 [Delete] application.properties 移除Properties版Application配置文件;
[Delete] CQConfig 移除不兼容的类;
[Add] application.yml 添加yml格式的Application配置文件;
[Change] Main 移除CQConfig类引用;
[Update] net.lz1998:spring-cq 升级依赖项版本(4.14.0.6 -> 4.15.0.1);
[Change] SpringCQMessageSenderFactory, CQPluginMain 使用较为妥协的方法实现SenderFactory;
2020-07-02 14:02:01 +08:00
LamGC 62e3affef1 [Change] LocalHashCacheStore 调整内部类CacheObject的访问权; 2020-07-02 13:46:46 +08:00
LamGC 51efc95c1c [Update] net.mamoe:mirai-core, net.mamoe:mirai-core-qqandroid 更新依赖库版本(1.0.2 -> 1.0.4); 2020-07-02 13:41:34 +08:00
LamGC d5c3a438b0 [Change] Pixiv预加载数据.md 更改文档标题; 2020-07-02 09:25:01 +08:00
LamGC 792bfcb1bd [Add] Pixiv预加载数据.md 增加新的Pixiv接口文档; 2020-07-02 09:10:01 +08:00
LamGC cbd7db3570 Merge branch 'master' of github.com:LamGC/ContentGrabbingJi 2020-06-30 22:43:22 +08:00
LamGC 467e7065fa [Update] net.lamgc:java-utils 更新依赖项版本(1.2.0_20200517.1-SNAPSHOT -> 1.3.1); 2020-06-30 22:40:39 +08:00
LamGC d2240e56fd [Change] README.md 调整格式; 2020-06-30 10:40:34 +08:00
LamGC eef9f285ca [Update] README.md 更新一份更规范的Readme; 2020-06-30 08:58:08 +08:00
LamGC f63d52df6d [Version] 更新版本(2.5.2-20200630.1-SNAPSHOT -> 2.5.2-20200630.2-SNAPSHOT); 2020-06-30 08:05:18 +08:00
LamGC 50a638e97d [Clear] CacheStoreCentral 清理测试代码; 2020-06-30 08:01:08 +08:00
LamGC 968a616595 [Version] 更新版本(2.5.2-20200618.1-SNAPSHOT -> 2.5.2-20200630.1-SNAPSHOT); 2020-06-30 01:23:04 +08:00
LamGC 6443ba68ab [Fix] AutoCleanTimer 修复自动清除定时器没有重复工作;
[Fix] LocalHashCacheStore 修复clean方法抛出'ConcurrentModificationException'异常的问题;
[Change] CacheStoreCentral 调整PreLoadData的热点缓存时间;
[Change] HotDataCacheStore 将最近获取的Key重置其过期时间, 调整clean过程;
2020-06-30 01:21:17 +08:00
LamGC cbe0a38f59 Merge branch 'master' of github.com:LamGC/ContentGrabbingJi 2020-06-29 23:33:41 +08:00
LamGC eadfacb7d0 [Clear] PixivSearchLinkBuilder 清理代码; 2020-06-23 13:53:54 +08:00
LamGC 572189d906 [Change] Main 增加'buildPassword'命令用于构造加密后的密码, 移除对PixivLoginProxyServer的嵌入支持;
[Delete] pom.xml 移除依赖项'net.lamgc:PixivLoginProxyServer';
2020-06-23 10:02:47 +08:00
LamGC 811694587d [Version] 更新版本(2.5.2-20200617.1-SNAPSHOT -> 2.5.2-20200618.1-SNAPSHOT); 2020-06-18 19:27:11 +08:00
LamGC 419f2de055 [Add] BotEventHandler 设计在消息正在发送,被时限线程池中断时, 重新投递事件以重新发送消息; 2020-06-18 10:11:08 +08:00
LamGC 368c78e171 [Add] CacheStoreCentral, BotCommandProcess Search命令增加对pageIndex参数使用(之前竟然是个摆设!?); 2020-06-18 10:05:34 +08:00
LamGC bccf47db6e [Change] MessageEvent toString方法增加hashCode输出; 2020-06-18 09:35:37 +08:00
LamGC cf351074cc [Delete] BufferMessageEvent 移除缓冲消息事件;
[Add] BufferedMessageSender 增加缓冲消息发送器, 用于替代BufferMessageEvent;
[Change] BotCommandProcess 将Random命令中所使用的BufferMessageEvent替换成BufferedMessageSender;
2020-06-18 09:35:25 +08:00
LamGC c4ce18d37a [Fix] log4j2.xml 修复HttpClient日志输出过多的问题;
[Change] PixivURL 将PixivURL设计为不可继承且不可实例化;
2020-06-18 09:01:16 +08:00
LamGC 26fd18917d [Delete] PixivAccessProxyServer 移除弃用的类;
[Change] Locker 删除不必要的代码;
[Change] PixivDownload 删除多余的Javadoc内容;
[Change] TimeLimitThreadPoolExecutorTest 调整日志输出方向, 补充测试细节;
2020-06-17 19:49:36 +08:00
LamGC acbd990181 [Version] 更新版本(2.5.2-20200611.1-SNAPSHOT -> 2.5.2-20200617.1-SNAPSHOT); 2020-06-17 16:48:47 +08:00
LamGC 6db9cda08a Merge branch 'optimize-memory-cache' 2020-06-17 16:47:08 +08:00
LamGC 1c742bfb6f [Fix] TimeLimitThreadPoolExecutor 修复了超时提醒遭到时限线程池中断的问题, 调整线程池仅发起一次中断; 2020-06-17 16:14:32 +08:00
LamGC 2f30fe1696 [Change] CacheStoreCentral 调整PreLoadData的本地缓存时间, 添加注意事项; 2020-06-17 10:51:06 +08:00
LamGC 32db952e63 Merge branch 'master' into optimize-memory-cache 2020-06-15 17:42:51 +08:00
LamGC bd6b825704 [Fix] pom.xml 排除slf4j-log4j12依赖项以修复package后应用启动报Slf4j实现冲突的问题; 2020-06-15 17:42:31 +08:00
LamGC 3943963505 [Fix] BotEventHandler 修复线程池最大线程数与最低线程数错误, 导致无法启动应用的问题; 2020-06-15 17:40:23 +08:00
LamGC 08822f68eb [Change] Locker 增加清理日志;
[Add] LockerMapTest 增加单元测试;
2020-06-15 17:18:28 +08:00
LamGC 2f647ee9fa [Add] LockerMap, Locker 增加Locker锁对象和LockerMap锁对象存储;
[Change] CacheStoreCentral 将synchronized所使用的的锁对象由String(常量池)转换成Locker<K>以尝试减少内存占用;
2020-06-15 16:22:37 +08:00
LamGC 44a7f49510 [Change] BotGlobal 补充IllegalStateException错误信息; 2020-06-15 15:48:07 +08:00
LamGC 85088e1b2c [Change] RandomIntervalSendTimer 调整日志输出内容; 2020-06-13 23:10:01 +08:00
LamGC 8dfb858b9f [Change] RedisPoolCacheStore 优化内部方法实现; 2020-06-13 17:24:06 +08:00
LamGC 0e76cebc31 [Fix] PreLoadDataAttributeComparator 修复获取PreLoadData抛出的异常信息未记录到日志的问题; 2020-06-13 16:51:15 +08:00
LamGC c64320ad78 [Change] HttpRequestException 统一接口异常, 调整类所在包路径; 2020-06-13 16:44:52 +08:00
LamGC 91e065f657 [Change] ImageCacheStore 优化中断处理, 自动取消任务, 补充TaskState设置; 2020-06-13 12:54:17 +08:00
LamGC 91f8b0070f [Change] PreLoadDataAttributeComparator 更改类名; 2020-06-13 00:28:52 +08:00
LamGC ad54dbfbf3 [Change] PreLoadDataComparator, PreLoadDataAttribute 将PreLoadData的属性Enum迁移到单独的类;
[Change] BotCommandProcess 适配更改;
2020-06-12 22:28:14 +08:00
LamGC ebb3dea99e [Change] ConsoleMessageEvent, ConsoleMessageSender 将消息传递给Sender发送, 设置消息前缀格式;
[Change] ConsoleMain 调整私聊模式下的前缀;
[Change] MessageSource 规范化命名;
2020-06-12 20:13:36 +08:00
LamGC abcd26f21b [Fix] BotEventHandler 修复事件处理线程非预期设置的问题;
[Change] BotEventHandler 设置线程超时时间;
[Fix] CacheStoreCentral 整理'InterruptedException'在'getImageById'的传递路径;
2020-06-12 20:00:37 +08:00
LamGC d9b08f8ad9 [Change] BotCommandProcess, CacheStoreCentral 调整个别日志的输出级别; 2020-06-12 19:28:59 +08:00
LamGC 5e030c12b2 [Change] BotCommandProcess 将各命令中'page'参数更名为'p'以简化命令;
[Change] PixivSearchLinkBuilder 更改类名(PixivSearchBuilder -> PixivSearchLinkBuilder);
[Add] PixivURL 增加接口常量;
[Change] Main, CacheStoreCentral, PixivSearchLinkBuilderTest 适配PixivSearchLinkBuilder的更改;
2020-06-12 17:06:04 +08:00
LamGC 62eabce8f6 [Clear] PixivDownload, RandomIntervalSendTimer 整理代码; 2020-06-12 10:31:16 +08:00
LamGC 64bca3c8f7 [Change] RandomIntervalSendTimer 更改Timer线程名; 2020-06-12 10:20:56 +08:00
LamGC 3b3f97e638 [Fix] CacheStoreCentral 修复Search命令中'option'参数区分大小写的问题;
[Fix] BotCommandProcess 修复Search命令中遇到不存在作品时会中断处理的问题;
[Change] BotCommandProcess 调整Ranking命令的'type'参数默认值(ILLUST -> ALL);
2020-06-12 10:12:29 +08:00
LamGC 951824cbe2 [Version] 更新版本(2.5.2-20200610.4-SNAPSHOT -> 2.5.2-20200611.1-SNAPSHOT); 2020-06-11 16:47:35 +08:00
LamGC e104abedeb [Add] MiraiMain 增加原始消息事件日志信息; 2020-06-11 16:46:32 +08:00
LamGC c3967d214d [Change] Dockerfile.sample 提升镜像使用的Jdk版本(8-jre -> 14 jdk), 增加Arthas诊断工具, 优化构建指令顺序; 2020-06-11 10:57:40 +08:00
LamGC 26e377a2c7 [Change] log4j2.xml 调整日志输出到文件的限制;
[Change] 将内容较长的日志设为TRACE级别, 以减少日志占用;
[CLear] 整理代码;
2020-06-11 09:49:19 +08:00
LamGC 87f2535b48 [Change] BotCommandProcess 优化Tag过滤表达式;
[Change] ImageCacheHandler 调整日志输出级别;
2020-06-11 09:08:58 +08:00
LamGC 97d06c4fc3 [Change] CacheStoreCentral 调整代码以为后续更改做准备;
[Change] BotCommandProcess, ImageCacheHandler, PreLoadDataComparator, RandomRankingArtworksSender 适配CacheStoreCentral的更改;
2020-06-10 20:21:23 +08:00
LamGC bfe25c2012 [Version] 更新版本(2.5.2-20200610.3-SNAPSHOT -> 2.5.2-20200610.4-SNAPSHOT); 2020-06-10 16:39:01 +08:00
LamGC 036f3eaf4a [Delete] simple.properties 删除弃用的Properties模板文件; 2020-06-10 16:27:13 +08:00
LamGC f07c8d0b76 [Add] MiraiMain 增加Bot网络重连相关设置; 2020-06-10 16:26:20 +08:00
LamGC d0cb4417b1 [Version] 更新版本(2.5.2-20200610.2-SNAPSHOT -> 2.5.2-20200610.3-SNAPSHOT); 2020-06-10 15:47:30 +08:00
LamGC 3f256c5a0a [Fix] log4j2.xml 修复日志配置错误的问题;
[Change] BotGlobal, Main 调整日志输出内容和级别;
2020-06-10 15:46:33 +08:00
LamGC b9180b9651 [Change] MiraiMain 显性设置DeviceInfo为'randomDeviceInfo'; 2020-06-10 15:26:26 +08:00
LamGC 7ccb306ca1 Merge pull request #15 from LamGC/change-license-AGPLv3
更改开源许可证(LGPLv3 -> AGPLv3)
2020-06-10 14:57:05 +08:00
LamGC 5dd13ce088 [Change] pom.xml 增加'licenses'; 2020-06-10 14:49:40 +08:00
LamGC e9b33938fa [Add] README.md 增加'LICENSE'部分; 2020-06-10 14:41:12 +08:00
LamGC cc040e6ec9 [Change] 更改开源许可证; 2020-06-10 10:44:53 +08:00
LamGC 04724168aa [Version] 更新版本(2.5.2-20200610.1-SNAPSHOT -> 2.5.2-20200610.2-SNAPSHOT); 2020-06-10 10:20:20 +08:00
LamGC 89ded96336 [Change] 更改部分组件所使用的的工具类; 2020-06-10 10:17:05 +08:00
LamGC af0346e952 [Delete] net.sourceforge.htmlunit:htmlunit 删除未使用的依赖项;
[Delete] com.github.rholder:guava-retrying 删除未使用的依赖项;
[Delete] mysql:mysql-connector-java 删除未使用的依赖项;
2020-06-10 09:39:10 +08:00
LamGC 4ccf2fafbc [Version] 更新版本(2.5.2-20200609.2-SNAPSHOT -> 2.5.2-20200610.1-SNAPSHOT); 2020-06-10 09:21:27 +08:00
LamGC f1e58d72ac [Change] log4j2.xml 调整mirai日志在STANDARD_STD*的输出级别; 2020-06-10 09:20:35 +08:00
LamGC 9242a1d474 [Change] MiraiToSlf4jLoggerAdapter 调整类命名(MiraiToSlf4jLogger -> MiraiToSlf4jLoggerAdapter);
[Change] MiraiMain 适配调整;
2020-06-10 09:13:12 +08:00
LamGC 065d21c4e4 [Update] MiraiToSlf4jLogger 补充类Javadoc; 2020-06-10 09:10:17 +08:00
LamGC 5eab94c429 [Change] Console* 调整包路径; 2020-06-10 09:09:54 +08:00
LamGC 2dd62bb6c8 [Version] 更新版本(2.5.2-20200609.1-SNAPSHOT -> 2.5.2-20200609.2-SNAPSHOT); 2020-06-09 16:02:03 +08:00
LamGC 21613fe3c0 [Update] net.mamoe:mirai-core 更新依赖项版本(1.0-RC2-1 -> 1.0.2);
[Update] net.mamoe:mirai-core-qqandroid 更新依赖项版本(1.0-RC2-1 -> 1.0.2);
2020-06-09 15:51:33 +08:00
LamGC ca56b2c9ba [Change] MiraiToSlf4jLogger, log4j2.xml, log4j2-test.xml, MiraiMain 将Mirai框架的日志接入Slf4j(Log4j2);
[Change] BotEventHandler 调整日志使用;
2020-06-09 15:35:43 +08:00
LamGC e0f773639f [Change] ConsoleMain 调整Terminal获取方式; 2020-06-09 14:16:26 +08:00
LamGC 75aa78a3d7 [Version] 更新版本(2.5.2-20200606.1-SNAPSHOT -> 2.5.2-20200609.1-SNAPSHOT); 2020-06-09 10:12:02 +08:00
LamGC 18a8ad95a1 [Change] PixivUgoiraBuilderTest 调整测试细节; 2020-06-09 10:07:54 +08:00
LamGC 438d0a95d3 [Add] pom.xml 增加JLine库;
[Update] ConsoleMain 增强Cli使用体验;
[Add] BotEventHandler 增加同步执行命令的方法'executeMessageEvent(MessageEvent, boolean)';
[Change] Main 适配ConsoleMain的更改;
2020-06-09 09:50:16 +08:00
LamGC 6789b5b7c5 [Change] CacheStoreCentral 调整'getImageById'方法中对'pageIndex'的参数值检查时机;
[Change] MiraiMessageSender 增加警告忽略注释;
2020-06-08 19:13:51 +08:00
LamGC ad289f952f [Fix] log4j2.xml 修复SYSTEM_OUT级别限定错误的问题; 2020-06-08 18:47:53 +08:00
LamGC 3ae0e4cd8d [Change] HotDataCacheStore 调整日志输出级别(DEBUG -> TRACE); 2020-06-08 16:08:13 +08:00
LamGC 5550c7aef1 [Clear] CacheStoreCentral 删除无用的运行器代码; 2020-06-08 15:57:38 +08:00
LamGC d4d3432c76 [Add] PixivUgoiraBuilder 增加方法'buildUgoira(OutputStream, boolean)'以提供输出流给Builder输出已构建的动图数据;
[Add] PixivUgoiraBuilder 增加方法'getUgoiraMeta():JsonObject'方法, 可通过该方法获取动图元数据;
2020-06-08 15:30:22 +08:00
LamGC d1aeda012e [Fix] BotCommandProcess 修复因'getImageById'迁移导致'image'命令不可用的问题;
[Clear] CacheStoreCentral 清除无用代码;
2020-06-08 09:37:06 +08:00
LamGC 683a38bc17 [Add] BotGlobal BotGlobal在初始化时将检查Redis连通性;
[Change] RandomRankingArtworksSender 支持外部设置groupId, 以使用群组配置;
[Change] BotCommandProcess, BotAdminCommandProcess 适配RandomRankingArtworksSender更改;
2020-06-08 09:24:17 +08:00
LamGC 188309509b [Change] BotCommandProcess 简化Search参数'ContentOption'的名称('contentOption' -> 'option'); 2020-06-06 19:27:55 +08:00
LamGC 3915712337 [Delete] search.txt 删除旧文档; 2020-06-06 17:57:24 +08:00
LamGC 1e88ba70dd [Version] 更新版本(2.5.2-20200604.3-SNAPSHOT -> 2.5.2-20200606.1-SNAPSHOT); 2020-06-06 17:54:09 +08:00
LamGC e6b2544998 [Update] 更新接口文档; 2020-06-06 17:53:37 +08:00
LamGC a2f6f1d140 [Clear] BotCommandProcess 整理代码; 2020-06-06 17:07:40 +08:00
LamGC f54ed35a09 [Change] SettingProperties 增加群号检查, 防止出现非法群号; 2020-06-06 15:42:35 +08:00
LamGC a426f80ec5 [Fix] BotCommandProcess 修复Search命令对作品限制的管理不受'image.allowR18'选项控制的问题; 2020-06-06 11:50:29 +08:00
LamGC c1a21d1065 [Change] ConsoleMain, ConsoleMessageEvent 支持启动时设置会话群组Id和QQId; 2020-06-06 10:52:12 +08:00
LamGC e570ddbb53 [Change] PreLoadDataComparator 增加对JsonElement是否为JsonObject的检查; 2020-06-06 10:51:24 +08:00
LamGC 223d78dbd6 [Change #5] 优化'getImageById'从文件读入缓存时, 检查图片完整性的速度; 2020-06-05 17:03:41 +08:00
LamGC ef5651be47 [Fix] CacheStore 修正Javadoc中的错误描述; 2020-06-05 16:29:16 +08:00
LamGC 9a8aac1960 [Fix] BotGlobal 修复BotGlobal初始化失败的问题;
[Fix] BotCommandProcess 修复Search命令未找到相关作品时提示语无法触发的问题;
[Change] BotCommandProcess 调整'isNoSafe(int, Properties, boolean)'方法中对于作品限制的判断方式;
2020-06-05 16:23:13 +08:00
LamGC 4bbed5fd55 [Change] BotCommandProcess, CacheStoreCentral 将ImageFile缓存管理转移到CacheStoreCentral;
[Change] RandomRankingArtworksSender 适配修改;
[Change] BotCommandProcess, BotGlobal 将'imageStoreDir'由BotCommandProcess转移到BotGlobal;
2020-06-05 10:07:13 +08:00
LamGC bcc21149b9 [Change #10] 将缓存存取部分从BotCommandProcess分离;
[Change] 整理代码;
2020-06-05 09:53:30 +08:00
LamGC e93c322c02 [Change] BotGlobal, BotCommandProcess 调整PixivDownload的初始化过程;
[Change] BotCommandProcess 将'Search'所属缓存部分抽出到单独的方法('getSearchBody');
2020-06-04 20:14:57 +08:00
LamGC feb51b8534 [Change] BotGlobal, BotCommandProcess 将'Gson'和'PixivDownload'对象纳入BotGlobal; 2020-06-04 19:47:20 +08:00
LamGC 273dbd45b0 [Version] 更新版本(2.5.2-20200604.2-SNAPSHOT -> 2.5.2-20200604.3-SNAPSHOT); 2020-06-04 16:16:53 +08:00
LamGC b39a82b936 [Fix] BotGlobal 修复BotGlobal初始化失败的问题; 2020-06-04 16:15:27 +08:00
LamGC 4589624b9b [Version] 更新版本(2.5.2-20200604.1-SNAPSHOT -> 2.5.2-20200604.2-SNAPSHOT); 2020-06-04 15:58:56 +08:00
LamGC 033fd0188b [Change] 调整Logger创建方式(getLogger(String) -> getLogger(Class)); 2020-06-04 15:58:10 +08:00
LamGC 4318869846 [Change] 将'Proxy', 'CookieStore'转移到BotGlobal类; 2020-06-04 15:54:52 +08:00
LamGC 749b89e668 [Version] 更新版本(2.5.2-20200522.1-SNAPSHOT -> 2.5.2-20200604.1-SNAPSHOT); 2020-06-04 11:26:26 +08:00
LamGC 9ce18469e6 [Add] 增加控制台框架以进行本地测试, 或非聊天机器人使用; 2020-06-04 11:25:08 +08:00
LamGC e803d2161b [Add] PropertiesUtils 增加一个工具类; 2020-06-04 11:24:40 +08:00
LamGC 3fbf80f233 Merge remote-tracking branch 'origin/master' 2020-06-04 11:20:22 +08:00
LamGC 8b8ef7e744 [Change] Issue #12 调整框架启动机器人应用的方式, 增加一个用于机器人应用内部共享数据的类;
[Fix] BotEventHandler, ImageCacheStore 增加ShutdownHook用于关闭线程池, 解决线程池阻塞关闭过程的问题;
[Change] BotEventHandler 调整'match(String)'方法;
[Change] BotAdminCommandProcess 调整'savePushList()'方法对文件创建失败的行为;
2020-06-04 11:19:18 +08:00
LamGC 7fbdf28ec8 [Update] Bug_Report.md 增加默认标签; 2020-06-04 10:50:37 +08:00
LamGC b29482927c [Add] Bug_Report.md 添加Bug反馈模板; 2020-06-04 10:47:28 +08:00
LamGC 69da2b02ac [Fix #11] 修复在图片缓存失效的情况下, 'getImageToBotCode'依然会尝试从缓存获取图片File对象导致NPE;
[Change] BotCommandProcess 调整字符串拼接形式, 统一错误提示语的格式;
2020-06-04 09:29:53 +08:00
LamGC eb2de09859 [Change] RandomIntervalSendTimer 将随机间隔发送器的Timer设为daemon thread; 2020-06-03 19:37:45 +08:00
LamGC ef70ac77cb [Update] HotDataCacheStore, Cleanable 更新Javadoc; 2020-06-03 17:03:54 +08:00
LamGC b4e9fdab7d [Change] TimeLimitThreadPoolExecutor 整理代码, 清除无用参数;
[Update] Pixiv排行榜接口.md 更新文档内容, 补充返回数据信息;
2020-06-03 17:02:46 +08:00
LamGC 5c5827123d [Update] net.lamgc:java-utils 更新依赖项版本(1.2.0_20200514.1-SNAPSHOT -> 1.2.0_20200517.1-SNAPSHOT), 以修复作品无效检测不生效的问题; 2020-06-03 16:51:26 +08:00
LamGC 2bfb78304c [Change] HotDataCacheStore, RandomIntervalSendTimer 支持浮动时间参数为0; 2020-06-03 14:50:45 +08:00
LamGC 637ea46b9a [Fix] MiraiMessageEvent 修复群消息事件错误设置私聊Sender的问题; 2020-06-01 21:02:46 +08:00
LamGC f5140a7a1e [Change] SpringCQMessageSenderFactory 尝试通过ThreadLocal获取当前处理的CoolQ对象, 目前EventExecutor暂不支持, 仅预留;
[Change] CQPluginMain 调整'processMessage'方法修饰符, 补充Javadoc;
2020-06-01 20:15:59 +08:00
LamGC 0fafddc715 [Change] AdultContentDetector, PixivURL 整理代码, 补充Javadoc; 2020-06-01 16:12:33 +08:00
LamGC 4c03a0f7d9 [Change] HotDataCacheStore 调整'supportedList'的条件, HotDataCacheStore将支持使用ListCacheStore;
[Change] AutoSender 将AutoSender的messageSender增加final属性;
2020-06-01 16:01:17 +08:00
LamGC ee02072b2d [Deprecated] PixivURL 将旧版Search接口设为弃用; 2020-06-01 15:58:54 +08:00
LamGC 3f6c87da3c [Add] MiraiMain 增加对临时消息的支持;
[Change] MiraiMessageEvent 调整消息对象转换的方式, 通过静态方法转换Mirai的MessageEvent;
2020-06-01 15:55:31 +08:00
LamGC d45cd8aab5 [Update] 更新文档, 增加排行榜接口文档, 待补充返回数据; 2020-05-28 16:20:16 +08:00
LamGC 598d6ef205 [Fix] README.md 修复格式错误;
[Update] README.md 补充参数细节;
2020-05-27 19:30:04 +08:00
LamGC 2190ef1b59 [Change] README.md 调整Readme内容以减少对用户选择带来的影响; 2020-05-27 19:27:29 +08:00
LamGC fcc522c057 Merge branch 'master' of github.com:LamGC/ContentGrabbingJi 2020-05-26 14:45:01 +08:00
LamGC 5a81604d94 [Change] BotCommandProcess 调整'色图'命令处理方法的命名; 2020-05-26 14:31:12 +08:00
LamGC 376306e404 [Update] README.md 更新文档说明, 提供详细命令说明和注意事项; 2020-05-26 10:37:40 +08:00
LamGC 0fe69253ed [Change] .gitignore 将Idea项目文件由指定文件名改为匹配后缀名; 2020-05-26 10:28:46 +08:00
LamGC fe8bf16d3a [Clean] RandomRankingArtworksSender 清理无用导入;
[Change] AutoCleanTimer 统一LoggerName名称;
2020-05-25 20:23:14 +08:00
LamGC 99c66babec [Add] BotEventHandler 增加对'Sender.sendMessage(String)'返回值的处理; 2020-05-25 09:45:09 +08:00
LamGC cb8b01fd74 Merge branch 'master' of github.com:LamGC/ContentGrabbingJi 2020-05-25 09:25:49 +08:00
LamGC 1f48b3ffdc [Change] MessageSender, MessageEvent 允许'senderMessage(String)'抛出异常;
[Change] RandomRankingArtworksSender 调整异常捕获日志输出;
[Change] MiraiMessageEvent, MiraiMessageSender, SpringCQMessageEvent 适配更改;
2020-05-25 09:21:46 +08:00
LamGC 68feed8d3c Merge remote-tracking branch 'origin/master' 2020-05-22 23:09:39 +08:00
LamGC d89711b0b9 [Version] 更新版本(2.5.2-20200520.1-SNAPSHOT -> 2.5.2-20200522.1-SNAPSHOT); 2020-05-22 20:42:29 +08:00
LamGC a3376e96ee [Add] BotCommandProcess, BotEventHandler, PixivDownload 对作品Id不存在的情况做出反应(而不是作为内部异常反馈); 2020-05-22 20:39:49 +08:00
LamGC 33d18cef6b Merge remote-tracking branch 'origin/master' 2020-05-22 20:34:32 +08:00
LamGC 7aa00ff98b [Fix] BotCommandProcess 修复ImageStore不允许使用软链接的问题; 2020-05-22 20:34:15 +08:00
LamGC e956e36584 [Fix] RankingUpdateTimer 修复首次执行时间计算错误的问题; 2020-05-22 20:23:46 +08:00
LamGC 8e27221457 [Change] BufferMessageEvent 调整成员变量修饰符, 补充新的构造方法'<init>(int, int, String)'; 2020-05-22 08:49:48 +08:00
LamGC a5fca68ef5 [Change] MessageEventExcutionDebugger 在配置文件无法转换的情况下不再忽略'NumberFormatException'异常; 2020-05-20 15:31:43 +08:00
LamGC 8edb728fe9 [Update] README.md 补充Readme内容 2020-05-20 14:34:55 +08:00
LamGC cc05af8a24 Create README.md 2020-05-20 11:00:25 +08:00
LamGC 60fa641962 [Version] 更新版本(2.5.2-20200517.1-SNAPSHOT -> 2.5.2-20200520.1-SNAPSHOT); 2020-05-20 10:36:30 +08:00
LamGC 3ab373cc70 [Update] .gitignore 补充VSCode忽略项; 2020-05-20 10:34:55 +08:00
LamGC 461cd246d8 [Add] BotCommandProcess 新增'色图'功能以随机从排行榜中获取一张图片;
[Add] BufferMessageEvent 增加用于获取Sender发送内容的Event实现;
[Change] BotEventHandler Handler现在允许命令处理方法不返回消息(返回'null');
2020-05-20 09:55:50 +08:00
LamGC f040f130d7 [Update] Main, AutoArtworksSender, ImageCacheStore, PixivAccessProxyServer, PagesQualityParser 整理代码问题, 删除无用类; 2020-05-20 09:05:50 +08:00
LamGC 1f402fbbac [Change] LocalHashCacheStore 实现'Cleanable'接口, 增加'<init>(int, int, boolean)'构造函数以允许自动清理
[Update] LocalHashCacheStore 补充Javadoc内容;
[Update] .gitignore 补充并整理忽略项;
2020-05-20 08:56:27 +08:00
LamGC f7f3c3beaf [Fix] BotCommandProcess 修复help命令中帮助信息的错误; 2020-05-17 19:38:17 +08:00
LamGC 04c1753c22 [Version] 更新版本(2.5.1 -> 2.5.2-20200517.1-SNAPSHOT); 2020-05-17 17:45:06 +08:00
LamGC d993e9d719 [Add] MiraiMessageSender 增加对默认表情的支持; 2020-05-12 15:25:29 +08:00
LamGC 60e91987d1 Merge branch 'mirai' 2020-05-12 15:14:58 +08:00
LamGC 65392fc2fe [Fix] net.mamoe:mirai-core, net.mamoe:mirai-core-qqandroid 更新Mirai-Core版本号以修复VerifyError问题(1.0-RC2 -> 1.0-RC2-1);
[Fix] MiraiMessageSender 调整日志输出, 以修复日志打印处理后消息顺序混乱的问题;
2020-05-12 15:14:28 +08:00
LamGC d38934a0f4 [Change] RankingUpdateTimer 调整时间补齐的阀值(12:00 -> 11:30);
[Add] documents/interfaces/* 整理部分Pixiv接口的文档;
2020-05-12 15:03:08 +08:00
LamGC 7943357d96 [Fix] MiraiMain 修复未登录时异常退出, 抛出NPE的问题; 2020-05-11 12:16:35 +08:00
LamGC a170ff040b [Update] net.mamoe:mirai-core, net.mamoe:mirai-core-qqandroid 更新依赖项版本(0.39.4 -> 1.0-RC2);
[Update] MiraiMain, MiraiMessageEvent 适配mirai新版本;
[Change] MiraiMessageSenderFactory 增加参数检查并优化过程;
[Change] MiraiMain 显性指定Mirai所用协议为'BotConfiguration.MiraiProtocol.ANDROID_PAD';
2020-05-11 11:36:37 +08:00
LamGC cb2ebfdb73 [Change] MessageEvent 对消息内容删除首尾空格以提高用户体验性; 2020-05-09 23:13:53 +08:00
LamGC 66b22c543a [Fix] SettingProperties 修复了配置文件无法创建, 导致无法保存配置的问题;
[Fix] Main 修复了潜在的文件创建失败的问题;
2020-05-09 22:54:32 +08:00
LamGC 6bace4b048 [Version] 更新版本(2.5.0 -> 2.5.1); 2020-05-09 22:35:46 +08:00
LamGC 597aac4e95 [Fix] BotCommandProcess 修复部分指令无法使用群组配置的问题; 2020-05-09 22:35:21 +08:00
LamGC 181d60285b [Version] 更新版本(2.5.0-20200509.1-SNAPSHOT -> 2.5.0); 2020-05-09 22:20:07 +08:00
LamGC c06b8717c3 [Add] CacheStoreUtils 增加用于缓存库的工具类;
[Update] BotCommandProcess 补充注释, 优化"info"命令输出内容的格式;
2020-05-09 22:19:29 +08:00
LamGC b4a28b6735 [Version] 更新版本(2.5.0-20200506.1-SNAPSHOT -> 2.5.0-20200509.1-SNAPSHOT); 2020-05-09 18:16:54 +08:00
LamGC 40057c3683 [Fix] Issue #1 修复了ImageCache对重复缓存请求的策略(忽略并返回)导致的后续处理发生NPE的问题;
[Update] ImageCache 更新了ImageCache, 将该部分单独分离到 ImageCacheStore;
[Update] BotCommandProcess 适配新的ImageCache, 调整内容检查的时机以减少不必要的API访问;
2020-05-09 18:16:16 +08:00
LamGC 4beb4d78fb [Fix] BotCommandProcess 修复Search命令格式不正确的问题; 2020-05-09 12:52:15 +08:00
LamGC d9edaa681f [Add] BotAdminCommandProcess 为 addPushGroup 增加参数检查; 2020-05-09 10:10:48 +08:00
LamGC 7b52abde60 [Change] BotCommandProcess 增加Search命令的反馈内容(可能必须要QQ支持长文本才能使用); 2020-05-08 23:31:24 +08:00
LamGC 2cb4fe1dbc [Fix] SettingProperties 修复无法设置全局配置项的问题; 2020-05-08 12:23:43 +08:00
LamGC 5830327dad [Add] BotEventHandler 增加禁言记录, 防止应用因过多的在禁言状态反馈导致被封号;
[Update] MiraiMain 适配禁言记录功能;
2020-05-07 19:46:54 +08:00
LamGC cbd10ff281 [Fix] Issue #4 修复因Mirai API变动导致应用无法从消息获取ImageId的问题; 2020-05-07 18:24:09 +08:00
LamGC 84b8006f89 [Change] RankingUpdateTimer 将定时更新时间调整为"+8 11:30", 并增加日期指定; 2020-05-07 15:21:05 +08:00
LamGC d32d891ad7 [Update] BotCommandProcess 补充Javadoc内容;
[Add] BotCommandProcess 在Ranking命令增加force参数以允许强制查询预计不在排行榜范围的日期;
2020-05-07 11:29:31 +08:00
LamGC aabd35ce71 [Add] AutoCleanTimer, Cleanable 增加缓存库自动清理;
[Update] HotDataCacheStore 增加对自动清理的支持;
[Change] RedisPoolCacheStore 将Redis缓存库抽象类设为default(原public);
[Change] MiraiMessageSender, BotCommandProcess 适配HotDataCacheStore的修改;
[Change] BotCommandProcess 调整ranking命令的输出格式;
2020-05-07 10:26:11 +08:00
LamGC a36eb713d7 [Version] 更新版本(2.5.0-20200505.1-SNAPSHOT -> 2.5.0-20200506.1-SNAPSHOT); 2020-05-06 11:09:18 +08:00
LamGC f452c1c128 Merge branch 'add-MultiProperties' 2020-05-06 11:08:16 +08:00
LamGC cb42aadb15 [Fix] SettingProperties 修复在初次设置群组配置时未创建该群组所属Properties导致NPE的问题;
[Update] BotAdminCommandProcess 更新日志内容及命令返回信息;
[Change] BotCommandProcess 调整配置项key格式;
2020-05-06 10:55:32 +08:00
LamGC b969bb29b2 Merge branch 'master' into add-MultiProperties 2020-05-06 10:18:00 +08:00
LamGC 0d74007a98 [Change] search.txt 整理接口相关文档的存储目录; 2020-05-06 09:42:34 +08:00
LamGC edade24883 [Change] Issue #2 调整Logger Name以统一Logger Name格式; 2020-05-06 09:34:28 +08:00
LamGC 34f57404ca [Update] 重构Setting Properties, 使其支持分群配置; 2020-05-06 00:57:48 +08:00
LamGC 99b6e14ff7 [Change] RandomIntervalSendTimer 调整日志输出内容; 2020-05-05 17:09:45 +08:00
LamGC c8fe2c3fdd [Version] 更新版本(2.5.0-20200504.2-SNAPSHOT -> 2.5.0-20200505.1-SNAPSHOT);
[Update] net.lamgc:java-utils 更新依赖版本(1.1.0 -> 1.2.0_20200505.1-SNAPSHOT);
2020-05-05 01:29:11 +08:00
LamGC 49a33d4078 [Add] MessageEventExecutionDebugger 添加对消息处理的调试器Enum;
[Add] BotEventHandler 添加对 MessageEventExecutionDebugger 的支持;
[Add] VirtualLoadMessageEvent 增加 toVirtualLoadMessageEvent(MessageEvent) 方法;
2020-05-05 01:27:10 +08:00
LamGC cd1d2316ee [Add] BotEventHandler 添加executeMessageEvent(MessageEvent)方法;
[Change] BotEventHandler 将BotEventHandler.executor设为private;
[Change] MiraiMain, CQPluginMain, RankingUpdateTimer 适配BotEventHandler的调整
2020-05-04 23:06:07 +08:00
LamGC cf08353ed9 [Change] BotEventHandler 调整事件处理线程池的线程数(min: 4, Max: 32); 2020-05-04 22:52:47 +08:00
LamGC bef6a684b9 [Change] BotEventHandler 调整事件处理线程池的线程数; 2020-05-04 22:46:06 +08:00
LamGC 04960889b4 [Version] 更新版本(2.5.0-20200504.01-SNAPSHOT -> 2.5.0-20200504.2-SNAPSHOT);
[Update] net.lamgc:java-utils 更新依赖版本(1.1.0_5-SNAPSHOT -> 1.1.0);
[Update] net.lamgc:PixivLoginProxyServer 更新依赖版本(1.1.0 -> 1.1.1);
2020-05-04 19:44:11 +08:00
LamGC 0dc8fc78b4 [Version] 更新版本(2.4.0 -> 2.5.0-20200504.01-SNAPSHOT); 2020-05-04 17:07:47 +08:00
LamGC 40c5284be2 [Change] Dockerfile.sample 调整构建指令顺序以充分利用构建缓存;
[Fix] Main 修复SystemProperties设置null抛出NPE的问题;
2020-05-04 17:07:39 +08:00
LamGC 46cb47c7d1 [Add] LICENSE 添加开源许可证(LGPLv3); 2020-05-04 16:55:16 +08:00
LamGC fe213deecb [Change] Main 调整参数的接收形式;
[Fix] Dockerfile.sample 修复Java应用无法接收stop命令发送信号的问题;
[Change] RankingUpdateTimer 调整更新投递形式, 将同步更新调整为异步更新;
[Change] BotEventHandler 实装TimeLimitThreadPoolExecutor;
2020-05-04 02:07:46 +08:00
LamGC f279d99fda [Add] TimeLimitThreadPoolExecutor 增加一个带有执行时间限制的线程池及对应单元测试类; 2020-05-04 02:04:59 +08:00
LamGC a09aef5be2 [Delete] 删除已完成或已经没有存在意义的TODO; 2020-05-02 10:40:11 +08:00
LamGC 766411fa09 [Fix] Dockerfile.sample 修复应用无法接收容器退出信号的问题(现在应用将可以接受信号主动退出, 即使ExitCode不为0); 2020-05-02 00:56:05 +08:00
LamGC 84b544cea9 [Change] Main 调整日志输出级别, 补充日志输出内容;
[Change] Dockerfile.sample 调整镜像内应用运行路径;
2020-05-01 22:28:15 +08:00
LamGC 2f492f5b03 [Add] log4j2.xml, log4j2-test.xml 添加允许指定日志存储目录的功能(sys:cgj.logsPath); 2020-05-01 13:19:08 +08:00
LamGC 15af939c3f [Fix] PixivDownload 修复因排行榜总量的不确定导致排行榜获取异常的问题;
[Change] BotEventHandler 调整事件处理完成后的日志输出形式;
2020-04-30 19:08:34 +08:00
LamGC a28cb142b4 [Change] 调整数据存储的路径设置及存储目录参数名; 2020-04-30 17:51:57 +08:00
LamGC e8fda3214b [Version] 更新版本(2.3.0 -> 2.4.0); 2020-04-30 02:24:21 +08:00
LamGC fba6d3532c [Delete] 删除不再使用的类和方法;
[Update] 优化Javadoc并设置编译注解, 优化代码;
[Change] 调整部分日志的输出级别;
[Change] RedisPoolCacheStore clear方法将根据返回信息确定是否清空成功;
[Change] Dockerfile.sample 调整镜像构建步骤;
2020-04-30 02:23:31 +08:00
LamGC 0075446412 [Fix] MiraiMessageSender 修复因BotCode-Image中flashImage参数不存在导致NPE的问题; 2020-04-30 01:54:27 +08:00
LamGC 2388cd419e [Add] Dockerfile.sample 增加一个用于构建CGJ-BotMode镜像的Dockerfile模板; 2020-04-30 00:45:41 +08:00
LamGC 7f83b16118 [Add] Main 添加日志输出内容; 2020-04-29 23:44:38 +08:00
LamGC 10cad32efa [Version] 更新版本(2.2.1 -> 2.3.0); 2020-04-28 23:16:10 +08:00
LamGC 22a113ef38 [Change] 调整CGJ包结构, 将机器人平台相关类单独存放在framework包内; 2020-04-28 23:15:55 +08:00
LamGC 6824b12e8f [Add] MiraiMessageSender 增加对闪照的支持; 2020-04-28 10:57:17 +08:00
LamGC 1c556c5b94 [Update] MiraiMessageSender 对已上传的图片所属ImageId设置10天有效期; 2020-04-28 00:32:40 +08:00
LamGC dbc9f4c90b [Fix] BotCommandProcess 修复了通过缓存获取排行榜时排行榜从第2名开始读取的bug;
[Update] MiraiMain, MiraiMessageSender, MiraiMessageEvent 更新Mirai框架(0.32.0 -> 0.39.4)并适配框架新版本的变动;
2020-04-28 00:19:24 +08:00
LamGC d3c1975722 [Fix] PixivURL 修复由于智障修复导致的bug; 2020-04-27 23:26:22 +08:00
LamGC c09b750fe6 [Version] 更新版本(2.2.0 -> 2.2.1); 2020-04-27 14:13:51 +08:00
LamGC ca479ef1af [Change] log4j2.xml 调整SYSTEM_OUT控制台输出配置; 2020-04-27 14:06:33 +08:00
LamGC c2c49d2355 [Add] PixivDownload 增加对排行榜获取失败抛出异常的信息;
[Fix] PixivURL.RankingContentType 修复isSupportedMode判断错误的问题;
[Fix] RankingUpdateTimer 调整每日更新时间(12:10 -> 12:30)以尝试修复排行榜更新时机过早的问题;
2020-04-27 12:32:25 +08:00
LamGC 22e74e8cd5 [Add] PixivIllustType 增加了对IllustInfo的解析; 2020-04-26 15:53:39 +08:00
LamGC d549c5674d [Update] BotCommandProcess 补充Javadoc;
[Change] PixivUgoiraBuilder 调整日志输出级别;
2020-04-26 15:45:23 +08:00
LamGC c6952de84c [Add] log4j2-test.xml 增加测试运行时的日志配置文件;
[Add] pom.xml 设置package goal的文件排除项, 将测试运行所使用的日志配置文件(log4j2-test.xml)排除;
2020-04-25 17:07:13 +08:00
LamGC 410d6c0828 [Add] BotAdminCommandProcess, PixivDownload 增加日志输出内容;
[Change] RandomIntervalSendTimer 对AutoSender增加异常捕获以减少对其他Timer执行的影响;
2020-04-25 17:02:01 +08:00
LamGC aaa1bc932b [Add] RandomRankingArtworksSender 增加参数;
[Add] BotAdminCommandProcess 增加更多可自定义的参数项, 增加json配置项常量;
2020-04-25 02:29:38 +08:00
LamGC a1e54e70d9 [Change] PixivURL 调整RankingContentType.ALL名称(ALL -> TYPE_ALL);
[Add] PixivDownload 增加调试日志信息;
[Fix] RandomIntervalSendTimer 修复cancel方法无法停止定时器的问题;
2020-04-24 23:54:26 +08:00
LamGC 16522155e1 [Add] 通过增加参数信息来为命令方法提供来源群和来源QQ; 2020-04-24 23:52:05 +08:00
LamGC 5f796f7da0 [Update] BotCommandProcess 更新帮助信息, 补充Javadoc, 调整命令名;
[Change] RandomIntervalSendTimer 将下一执行时间从分钟调整为具体时间;
[Change] MessageEvent toString方法调整为输出具体实现类名而不是"MessageEvent";
2020-04-24 10:45:03 +08:00
LamGC 5a52dd9208 [Add] 初步添加成人内容检测器, 尚未使用; 2020-04-24 01:42:47 +08:00
LamGC b53aafa81b [Fix] RandomRankingArtworksSender 修复排行榜选取错误的问题;
[Add] RandomRankingArtworksSender 增加对rankingStart, rankingStop的范围选取检查;
[Update] RandomRankingArtworksSender 更新Javadoc;
2020-04-24 01:41:53 +08:00
LamGC 28aa086f15 [Version] 更新版本(2.1.0 -> 2.2.0);
[Change] groupId纳入net.lamgc;
2020-04-24 01:28:06 +08:00
LamGC 0eadefa74f [Add] BotAdminCommandProcess 完善作品报告管理功能;
[Add] BotCommandProcess 对Report增加报告时间;
2020-04-24 01:24:03 +08:00
LamGC 4afa414725 [Add] CacheStore 增加keys, remove方法;
[Update] HotDataCacheStore, LocalHashCacheStore, RedisPoolCacheStore 适配CacheStore的更改;
2020-04-24 01:23:11 +08:00
LamGC 0f202cb076 [Update] RedisPoolCacheStore 优化代码; 2020-04-24 00:42:41 +08:00
LamGC 1f3d99ac10 [Add] BotCommandProcess 增加作品报告功能, 以允许用户主动报告不当作品;
[Update] 适配报告功能;
2020-04-24 00:34:50 +08:00
LamGC d33f4028d1 [Fix] BotEventHandler 修复无命令帮助信息无法发送的问题(前缀识别失效); 2020-04-24 00:33:51 +08:00
LamGC 4020bbfea8 [Change] BotAdminCommandProcess 调整方法名, 增加提示信息; 2020-04-23 23:18:41 +08:00
LamGC 19605a9401 [Add] BotEventHandler 增加启动预处理方法;
[Add] BotAdminCommandProcess 增加推送功能相关管理命令;
[Change] RandomIntervalSendTimer 调整Timer管理过程;
[Change] BotCommandProcess ranking方法对参数错误的处理调整为返回错误信息;
[Fix] RankingUpdateTimer 修复参数错误的问题;
2020-04-23 16:23:06 +08:00
LamGC 21466a49f9 [Update] pom.xml 补充插件版本;
[Delete] pom.xml 删除kotlin编译插件;
2020-04-23 09:23:26 +08:00
LamGC 73ae9a268b [Add] MessageSenderBuilder, MessageSenderFactory 增加用于创建MessageSender而又与平台无关的Builder, 初步增加对应Factory; 2020-04-21 22:43:24 +08:00
LamGC 620c3785ad [Change] MessageSender 调整包路径; 2020-04-21 22:33:20 +08:00
LamGC 2df5513727 [Change] BotEventHandler 修改命令前缀以防止错误的触发; 2020-04-20 01:35:02 +08:00
LamGC 8798633c2c [Fix] BotCommandProcess 修复排行榜数据选取错误的问题; 2020-04-20 01:33:30 +08:00
LamGC 9dfc20a525 [Add] AutoSender 增加自动发送器接口;
[Add] RandomIntervalSendTimer 增加随机延迟自动发送器;
[Add] RecommendArtworksSender 增加随机排行榜作品发送器;
[Change] PixivDownload 重新开发 getRanking 方法;
2020-04-20 01:32:11 +08:00
LamGC dd88f2acab [Update] MiraiMessageSender 增加新构造器, 补充Javadoc; 2020-04-19 23:58:33 +08:00
LamGC 36460e4c34 [Add] MiraiMain 增加正常关闭过程; 2020-04-19 13:03:10 +08:00
LamGC 084be3970a [Change] 将各MessageEvent的消息发送部分分离成单独的MessageSender, 将MessageEvent依赖于对应的MessageSender; 2020-04-19 12:23:02 +08:00
LamGC 11005b0f6c [Add] BotCode 增加 getCodePattern 方法以允许其他类获取BotCode所使用的正则匹配表达式; 2020-04-19 12:20:58 +08:00
LamGC 970be847a0 [Change] RankingUpdateTimer 调整更新时间(12:30 -> 12:10);
[Change] RankingUpdateTimer 调整日志级别, 补充日志输出信息;
2020-04-19 12:16:03 +08:00
LamGC 09fa1bd2e8 [Add] MessageSource 将MiraiMessageEvent的MessageSource分离成独立的类;
[Change] SpringCQMessageEvent, MiraiMessageEvent 适配新的更改;
2020-04-19 01:34:45 +08:00
LamGC ae27141fea [Add] MessageSender 增加消息发送器接口;
[Change] MessageEvent 实现 MessageSender 接口;
2020-04-19 01:00:52 +08:00
LamGC b328def8f9 [Change] BotCommandProcess 将isNoSafe方法公开;
[Change] MiraiMessageEvent 将UploadImage方法公开;
[Add] MiraiMessageEvent 增加MessageSource枚举类;
2020-04-19 00:31:24 +08:00
LamGC dbfed874c0 [Add] VirtualLoadMessageEvent 增加假负载消息事件, 该事件在消息发送上将没有操作, 纯属执行命令;
[Change] RankingUpdateTimer 调整排行榜更新方式, 利用假负载消息触发一次完全执行;
2020-04-18 01:15:04 +08:00
LamGC f02b0e9e98 [Update] 补充Javadoc, 设置@SuppressWarning注解; 2020-04-18 01:13:01 +08:00
LamGC 260cfe3dd0 [Delete] MessageEvent 删除可能会导致兼容性降低的未使用方法;
[Delete] MiraiMessageEvent, SpringCQMessageEvent 适配 MessageEvent 的更改;
2020-04-17 18:29:59 +08:00
LamGC c1427379c6 [Add] StringListRedisCacheStore 增加类型为String的RedisListCacheStore实现;
[Change] BotCommandProcess 更换pagesCache的缓存组件(RedisPoolCacheStore<List<String>> -> StringListRedisCacheStore);
2020-04-17 18:25:48 +08:00
LamGC f844d150e8 [Change] 更改artifactId(CGJ_2 -> ContentGrabbingJi); 2020-04-17 17:46:42 +08:00
LamGC 2ec696b3cd [Change] PreLoadDataComparator Attribute类属性attrName访问权修改(default -> public); 2020-04-17 17:46:01 +08:00
LamGC c8c6dbe4fd [Update] 优化Logger名称;
[Update] 为 processMessage(MessageEvent) 方法增加 @SuppressWarnings("unused") 注解并补充Javadoc;
2020-04-17 17:20:37 +08:00
LamGC 4dff477ded [Change] log4j2.xml 调整Root Logger最低日志级别(Debug -> Info); 2020-04-17 15:54:13 +08:00
LamGC 0ab5634ce3 [Update] MiraiMain 将登录密码形式由明文文本更改为Md5以加强机器人在服务端的安全性;
[Update] pom.xml 版本号升级(1.0-SNAPSHOT -> 2.1.0);
2020-04-17 15:52:58 +08:00
LamGC 3e4b7f1145 [Update] BotCommandProcess 对IllustPreLoadData更换缓存组件(JsonRedisCacheStore -> HotDataCacheStore);
[Update] BotCommandProcess 补充Javadoc;
2020-04-17 15:19:09 +08:00
LamGC ac38e1b5cf [Change] CacheObject, LocalHashCacheStore 合并类; 2020-04-16 23:48:59 +08:00
LamGC d8d4784c0f [Update] HotDataCacheStore 补充Javadoc细节; 2020-04-16 22:49:15 +08:00
LamGC 99cdfb7598 [Change] SpringCQMessageEvent 更新 getImageUrl 具体实现, 首选BotCode中url地址;
[Change] BotEventHandler 调整initial代码;
2020-04-16 20:38:04 +08:00
LamGC 9f58e186d5 [Update] SpringCQMessageEvent 适配 getImageUrl 方法; 2020-04-16 20:20:29 +08:00
LamGC 0e182be31c [Add] MessageEvent 增加 getImageUrl 抽象方法, 增加对 toString 方法的重写;
[Update] MessageEvent 补充Javadoc;
[Update] MiraiMessageEvent 适配 getImageUrl 方法;
[Change] MiraiMessageEvent 优化缓存存取过程;
2020-04-16 19:55:48 +08:00
LamGC 006352679a [Change] ImageCacheHandler 将缓存键名从downloadLink更改为fileName;
[Change] PixivUgoiraBuilderTest 暂时设置单元测试忽略以关闭耗时的测试, 加快构建速度;
2020-04-16 17:12:24 +08:00
LamGC 4304cfcef2 [Add] URLs 增加一个工具类; 2020-04-16 17:10:12 +08:00
LamGC 5ad2a56ce2 [Add] BotCommandProcess 将 getImageById 中用于构造BotCode的代码分离到 getImageToBotCode 方法;
[Add] PreLoadDataComparator 增加新的比较器, 主要用于对Pixiv搜索结果的排序;
[Change] BotCommandProcess 重写 getImageById 方法以提高处理效率;
[Change] BotCommandProcess 将 search 方法对搜索结果的排序抽出到新的Comparator实现类(PreLoadDataComparator);
2020-04-16 16:51:25 +08:00
LamGC 2af3c20a21 [Change] PixivDownload 调整日志输出内容和级别;
[Add] PixivDownload 增加 PixivDownload#getRanking(List<JsonObject>, int, int);
2020-04-16 11:01:52 +08:00
LamGC 7468777b1d [Change] Main, PixivUgoiraBuilder 调整日志输出; 2020-04-16 10:59:53 +08:00
LamGC b720f657d0 [Change] 将部分CacheStore对象的类型调整为CacheStore<>以保证DIP, 增加灵活性;
[Change] 将Ranking缓存调整为JsonObjectRedisListCacheStore, 减少不必要的缓存数据查询;
2020-04-16 10:59:18 +08:00
LamGC c0c9a2d299 [Add] 增加Redis List的支持, 可通过List存储缓存项;
[Add] 增加支持JsonObject类型的RedisListCacheStore;
2020-04-16 10:53:57 +08:00
LamGC 66d3d0aa31 [Change] 适配PixivURL的修改(#ad1922bc);
[Add] 增加用于通过接口返回数据判别作品类型的PixivIllustType Enum类;
2020-04-15 21:54:30 +08:00
LamGC 450705b540 [Add] BotEventHandler 增加对消息的日志记录;
[Delete] BotEventHandler 移除无用代码;
2020-04-15 15:13:00 +08:00
LamGC a26762d96b [Change] 完成包路径调整;
[Change] 调整部分日志输出级别;
[Add] ranking 方法增加对过早日期的过滤, 以拦截非法请求;
[Change] globalProp 从 CQPluginMain 迁移到 BotCommandProcess;
[Update] 优化缓存失效时的数据存取过程;
[Fix] 修复 search 方法对结果进行likeCount排序遇到特殊JsonObject时抛出异常的问题;
[Update] getRankingInfoByCache 对因接口返回无效导致的空缓存增加浮动有效时间;
[Delete] getRankingInfoByCache 删除无用代码;
2020-04-15 15:09:14 +08:00
LamGC 46b2c1078b [Add] 增加com.squareup:gifencoder依赖项以用于处理Pixiv动图;
[Update] 整理文件内的注释内容;
2020-04-15 14:58:43 +08:00
LamGC ad1922bc94 [Change] 优化 PixivURL#getPixivIllustInfoAPI 方法的参数形式;
[Add] 增加用于获取动图元数据的接口地址;
[Add] 将"ajax/user/{userId}/profile/all"接口加入到PixivURL;
[Add] 重命名"ajax/user/{userId}/profile/top"接口常量名(PIXIV_USER_ILLUST_LIST_URL -> PIXIV_USER_TOP_ILLUST_LIST_URL);
2020-04-15 14:57:11 +08:00
LamGC 428bb1ae16 [Add] 增加Pixiv动图下载构建工具类 PixivUgoiraBuilder;
[Add] 增加针对 PixivUgoiraBuilder 的单元测试类;
2020-04-15 14:52:46 +08:00
LamGC 6570889c1e [Add] 增加对 BotCode 的单元测试类; 2020-04-15 14:51:46 +08:00
LamGC 0d1c651ff4 [Add] 增加测试下的日志配置文件; 2020-04-15 14:51:13 +08:00
LamGC 8a3c3f10c5 [Change] PixivURL 调整常量名; 2020-04-14 11:08:05 +08:00
LamGC b06702f367 [Add] PixivSearchBuilder 增加equals, hashCode, toString方法的覆写;
[Update] PixivSearchBuilderTest 补充测试项;
2020-04-14 11:05:53 +08:00
LamGC ee1f066ee4 [Add] 增加PixivLoginProxyServer的支持; 2020-04-14 11:02:49 +08:00
LamGC 579049b943 [Update] HotDataCacheStore 增加对CacheStore新增加方法的兼容; 2020-04-14 10:41:13 +08:00
LamGC e6c0633a5e [Add] 增加toString方法的覆写, 以提供快速输出对象详情的功能; 2020-04-14 10:39:17 +08:00
LamGC 5a50977be5 [Delete] 移除PixivLoginProxyServer类;
[Add] 导入PixivLoginProxyServer依赖库;
2020-04-14 10:38:32 +08:00
LamGC c9c8db7de9 [Add] CacheStore 增加对列表缓存的部分支持;
[Update] LocalHashCacheStore, RedisPoolCacheStore增加对CacheStore新增加方法的兼容;
[Add] RedisPoolCacheStore 增加executeJedisCommand方法以向子类提供覆盖重写重点方法的能力;
2020-04-14 10:37:07 +08:00
LamGC e20f9d34c9 [Change] 调整包路径, 增加 loadGlobalProperties 命令以提供重载方式; 2020-04-11 12:50:27 +08:00
LamGC 841a368767 [Change] PixivDownload 因日志系统升级, 重新调整日志等级; 2020-04-11 12:28:36 +08:00
LamGC bc6aaa49a0 [Add] BotCode 增加用于操作机器人功能代码的工具类;
[Add] BotEventHandler, MessageEvent 将CQPluginMain消息处理部分迁移至BotEventHandler;
[Add] SpringCQMessageEvent, MiraiMessageEvent 增加两个消息事件实现;
[Add] MiraiMain 增加对 Mirai 框架的支持;
[Add] Main 增加 Mirai 框架的支持;
2020-04-11 12:27:24 +08:00
LamGC 7d843c3f77 [Change] 调整包路径;
[Change] 修改 CQPluginMain 功能, 将命令处理的具体细节迁移;
2020-04-11 12:18:43 +08:00
LamGC a0efc3c0af [Add] 增加新的缓存库类(HotDataCacheStore, StringRedisCacheStore);
[Add] CacheStore 增加update(String, T, long)方法, 该方法将以Unix时间戳设置过期时间;
[Update] LocalHashCacheStore, RedisPoolCacheStore 适配CacheStore新方法;
[Delete] 删除 RedisCacheStore 类;
2020-04-11 12:13:55 +08:00
LamGC 8375b81b17 [Change] 调整包路径;
[Add] RedisPoolCacheStore, JsonRedisCacheStore 增加可提供JedisPool对象的构造方法, 以实现Redis连接池共用;
2020-04-10 22:00:38 +08:00
LamGC 9bf59da444 [Add] 增加 Mirai 框架依赖项, 升级日志系统(Log4j -> Log4j2);
[Update] 将Log4j配置文件替换为Log4j2配置文件;
2020-04-10 21:58:56 +08:00
LamGC 900a4e14ec [Delete] 删除目前暂不使用的类; 2020-04-10 21:57:19 +08:00
LamGC ce93c7372e [Change] 调整 runUpdateTimer 命令为 runUpdateTask 并将命令移到 CQBotAdminProcess 中;
[Fix] 修复 RankingUpdateTimer 时间错误的问题;
2020-04-02 00:33:22 +08:00
LamGC 9a2a69ea41 [Add] RankingMode 增加 MODE_ORIGINAL (原创)选项;
[Fix] RankingContentType 修复部分RankingMode并不支持的问题;
2020-04-01 23:52:05 +08:00
LamGC afb5c56e52 [Add] 对ranking和search增加作品页数的显示; 2020-04-01 22:03:50 +08:00
LamGC ae3f52532c [Add] PixivURL 为RankingContentType增加isSupportedMode以检查指定的RankingMode是否支持;
[Update] 对CQProcess, PixivDownload, RankingUpdateTimer添加对RankingContentType.isSupportedMode的支持以消除无效请求带来的资源浪费;
2020-04-01 09:50:45 +08:00
LamGC 3e4874b67c [Change] CQPluginMain 将两个ArgumentsRunner设计为多次使用以提高性能; 2020-04-01 09:48:32 +08:00
LamGC 2174fb96ef [Add] 增加排行榜缓存更新的定时任务; 2020-03-31 13:02:14 +08:00
LamGC 64303dc6b6 [Fix] 修复引号包括的参数值尾部多余的引号没有过滤的问题; 2020-03-31 11:17:13 +08:00
LamGC 3333f4c5c5 [Add] 增加redisAddr启动参数以通过参数指定redis缓存库地址;
[Update] 补充Javadoc;
[Change] 根据性能分析结果调整imageCacheExecutor线程池参数;
[Change] 调整CQGlobal内线程池参数;
[Change] 将illustPages缓存纳入Redis缓存库;
[Change] 根据性能分析结果将SpringBoot的HttpThreads降至1;
2020-03-31 11:04:08 +08:00
LamGC e4e7771d4d [Fix] 修复使用双引号引用的参数值没有过滤双引号的bug; 2020-03-31 10:56:08 +08:00
LamGC fd8b38d6fd [Fix] 修复缓存项因key不符导致无法设置过期时间的bug; 2020-03-30 21:41:22 +08:00
LamGC 1653372d3e [Change] 增加对无前缀情况下的处理; 2020-03-30 12:31:31 +08:00
LamGC d4266aaa57 [Change] 重命名类名; 2020-03-30 12:12:53 +08:00
LamGC 5d595d50a1 [Add] 增加Ranking带图作品限制的配置项;
[Add] 增加image.downloadAllPages配置项以指定下载作品中某一Pages的时候是否下载其他Pages;
2020-03-30 12:07:29 +08:00
LamGC ddf9473326 [Fix] 修复Prefix不会自动补充前缀分隔符的问题; 2020-03-30 11:41:58 +08:00
LamGC 2d8652c8bc [Change] 将请求签名作为线程锁对象以提高处理能力;
[Add] 对Search和Ranking增加消息结果数配置项;
2020-03-30 11:36:44 +08:00
LamGC ce55adfc40 [Change] 对两个getRanking方法设置日志标志; 2020-03-30 11:34:39 +08:00
LamGC 18d96c73d6 [Add] CacheStore 增加supportedPersistence以让使用方知晓实现是否支持持久化;
[Change] CQProcess 使用RedisPoolCacheStore;
2020-03-30 10:10:53 +08:00
LamGC 056d9740b9 [Add] 调整缓存相关类的结构, 增加CacheStore接口; 2020-03-30 02:17:25 +08:00
LamGC a16238d9e7 [Change] 调整File实例化参数;
[Update] 调整帮助信息;
2020-03-28 21:18:12 +08:00
LamGC ad6e0b1c34 尝试使用Integer代替String以减少内存占用;
调整输出等级
2020-03-27 14:55:52 +08:00
LamGC 078ab1caf0 增加解析方法; 2020-03-27 09:16:07 +08:00
LamGC 8f09dc47b4 缓存优化3.0 2020-03-27 09:15:28 +08:00
LamGC 532d7371d8 initial commit 2020-03-26 14:39:59 +08:00
292 changed files with 10170 additions and 14506 deletions
-103
View File
@@ -1,103 +0,0 @@
# 贡献指南 #
> 警告:该指南尚未完成,所以在遇到与贡献有关问题时应该及时联系项目管理者以确定有关贡献事项准则,
如该指南未提及有关事项时,你不应该擅作主张地决定事项准则,擅作主张将会在 Review 阶段造成不必要的麻烦(甚至会被拒绝)。
!!!这条消息将在贡献指南完善的差不多后去除。!!!
欢迎你为本项目作出贡献!本项目的发展离不开所有贡献者(不仅仅只是提交了相关代码,帮助编写文档,还是提出问题或想法也算是贡献者之一!)的贡献。
在为本项目作出任何贡献之前,请先阅读[贡献者准则](https://github.com/LamGC/ContentGrabbingJi/blob/3.0.0/CODE_OF_CONDUCT.md)
一旦你尝试(或正在)为本项目贡献(贡献中,或者贡献过),即代表你**认同并接受**贡献者准则,**清楚明白且接受**因**违反贡献者准则**所造成的后果。
## 代码规范 ##
本项目代码规范大部分遵循 《Alibaba Java Coding Guidelines》,
详细内容可前往 https://github.com/alibaba/p3c 查阅。
如果你的 IDE 为 Eclipse 或 Idea,则推荐你为 IDE 添加阿里巴巴根据该规范开发的规范检查插件,该插件有助于你保持代码规范。
## Commit Message ##
提交信息需要遵循以下格式:
```
[TAG] ModuleName 对提交的说明;
[TAG] FileName/ClassName 对该文件的说明;
...
```
#### 标点符号的使用 ####
在提交信息中,符号使用**英文符号**。
#### 文件名或类名的说明 ####
对于类代码文件而言,FileName 只需要填写类名,对其他文件则需要包括后缀名。
FileName 和 ModuleName 可按需要使用`, `追加,注意与说明空一格,
例如: `[Add][Change] SimpleClass, MainClass 添加 SimpleClass 并调整 MainClass 的日志格式;`
ModuleName 根据修改的相关子模块名填写即可,对项目而非子模块的更改,则 ModuleName 为 `Project`,一个例子:
```
[Change][Update][Optimize] Project, SubModule01, WebFrontend 更新项目中的Readme, 修改 SubModule01 中鉴权的实现细节, 优化Web前端模块的启动速度;
[Update] Readme.md 调整文档格式;
[Change] AuthorizationChecker 调整鉴权过程以避免潜在的错误;
[Optimize] MainClass 优化初始化速度, 移除不必要的检查步骤;
```
#### 标签的使用 ####
目前支持以下Tag:
- `Add` 文件添加
- `Change` 文件更改
- `Update` 更新文件
- `Delete` 文件删除
- `Document` 文档相关
- `Fix` 问题修复
- `Optimize` 优化相关(或者代码整理啥的)
- `Move` 文件移动(例如包更改)
- `Rename` 更改名称(类名, 文件名等)
- `Issue:Id` 指定 Commit 所关联的 Github Issue(如果与其他标签有关,可直接加到指定标签后方,该标签可不用加)
- `PR:ID` 指定 Commit 所关联的 Pull Request(可选的,可直接使用 Github 的默认 Commit Message
> 注意:后续如有需要,会追加一些标签。
对大部分文件而言,FileName 按照以上规则填写即可,但对于依赖项而言,则要按照 GAVGroupId:ArtifactId:Version)坐标去除 **Version** 项后填写,
例如:`[Add] junit:junit 添加 Junit 单元测试依赖项;`
或者更新依赖项需要添加对依赖项版本号的变更:`[Update] junit:junit 更新 Junit 以修复潜在的漏洞('4.13' -> '4.13.1');`
#### 引用 Github 中的相关 Issue 或者 PR ####
如需引用相关 Issue 或者 PRPull Request),可在相关 Tag 中引用,或者另起一个 Tag 并声明指定的 ID,例如:
```
[Fix #11] Module 修复了部分功能异常情况;
...
```
或者是:
```
[Change][Issue #15] Module 增加了一些功能;
...
```
对于需要修复的相关 Issue,既可以采用第一种形式,也可以用第二种形式,单独添加一个 Issue 标签标明关联的 Issue 即可,比如这样:
```
[Fix][Issue #17] Module 修复了返回异常的问题;
...
```
如确定修复完成,也可以直接使用 Github 会对其作出相关操作的词(?),比如:
```
[Fix close#20] Module 修复响应码判断错误的问题;
...
```
或者是
```
[Fix][Issue fixed#28] Module 修复模板编译错误的问题;
...
```
## 代码质量 ##
如果你想添加新的功能,请务必对新功能相关代码编写好覆盖全,情况完善的单元测试(尽可能的覆盖所有新增功能,确保对大部分情况都有足够的测试)!
覆盖全、多角度、高度完善的单元测试是保证代码、项目质量必不可少的!
每次提交后,请检查 Action,查看 Github 中的代码是否能通过所有的单元测试。
## 依赖管理 ##
> 请**尽可能避免引入**新依赖项,如果某个依赖项的引入,仅仅是用于某些计算(或某个小功能,比如仅使用 Apache Commons 中的 `RandomUtils` 类获取 long 类型的随机数),
则这个依赖项的引入是完全没有必要的。
对于因极少需要而引入某些依赖项时,请考虑了解依赖项内的*实现细节*,并复现在项目中,以避免引入依赖项。
(当然,这是有例外的,如果某个功能较为复杂,且在该依赖项拥有很好的质量保证时,可考虑引入,具体细节可发起**issue**与我们进行讨论。)
**另外!**引入依赖项请注意**License**,确保依赖项可以被本项目使用。
+35
View File
@@ -0,0 +1,35 @@
---
name: Bug 反馈
about: 使用这个模板反馈应用问题。
title: ''
labels: bug
assignees: ''
---
## 环境信息 ##
系统(例如: Windows 10 1909): ``
Java版本(例如: Oracle Jdk 8.242): ``
<!-- 如果直接使用发布版,那么就填写发布版版本号(v开头) -->
<!-- 如果你通过编译运行的方式运行开发版,请你填写运行所使用的Commit Id -->
发生问题所在的版本: ``
## 问题描述 ##
<!-- 尽可能的清晰描述问题的信息 -->
## 预期行为 ##
<!-- 你觉得正常情况下应该会发生什么? -->
## 实际行为 ##
<!-- 实际上这个功能做了什么? -->
## 复现步骤 ##
<!-- 将复现步骤详细的写出,如果是偶发问题,可以写已知的,触发几率高的方法 -->
1.
## 相关信息 ##
### 日志 ###
<!-- 如果日志涉及了问题,请务必将日志一同提交,这对排查问题非常有用 -->
```
```
+11
View File
@@ -0,0 +1,11 @@
---
name: 功能/想法 提议
about: 使用这个模板将你对应用的想法提出来,或许我们会采纳!
title: ''
labels: function, question
assignees: ''
---
<!-- 如果可以,尽可能的清晰、详细的表达你的想法 -->
<!-- 没关系的,我们会进一步向你交流以尝试了解你的想法! -->
-39
View File
@@ -1,39 +0,0 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Unit test confirmation of commit
on:
push:
branches: [ 3.0.0 ]
pull_request:
branches: [ 3.0.0 ]
jobs:
buildAndTest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Set up Redis Server
# You may pin to the exact commit or the version.
# uses: supercharge/redis-github-action@8dd3c86cd02fabe1bc459d55ba892a9ce91e23c6
uses: supercharge/[email protected]
with:
# Redis version to use
redis-version: latest
- name: Build with Maven
run: mvn -B test --file pom.xml
# - name: Upload Coverage Report to Codecov
# uses: codecov/codecov-action@v1
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# file: .*?/target/jacoco.exec$
# flags: Unit tests
# name: Jacoco-Converage-Report
# fail_ci_if_error: true
# verbose: true
+11 -31
View File
@@ -1,36 +1,16 @@
# Compiled class file
*.class
# Log file
# Ignore test date folder
/pluginData/
/logs/
*/logs/
*.log
/cookies.store
/target/
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# IDEA ide files
# Ignore Idea files
/.idea/
*.iml
# maven build directory
target/
# Test run directory
/test/
*/test/
# Ignore Visual Studio Code files
.classpath
.factorypath
.project
/.settings/
/.vscode/
-76
View File
@@ -1,76 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project, and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [[email protected]](mailto:[email protected]). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
@@ -1,16 +0,0 @@
# CacheStore-API #
如需开发更多缓存组件,至少需要实现以下接口:
- CacheStore / CollectionCacheStore
- `CacheStore` 是所有缓存容器的父接口。
- 这两个类为抽象接口,定义了部分具体接口的公共方法。
- 不一定要**单独**实现该接口,可以**直接实现**具体的接口(比如 `SingleCacheStore`
- CacheStoreFactory
- 你还需要为其添加 `@Factory` 注解,否则不会生效。
- SingleCacheStore / MapCacheStore / SetCacheStore / ListCacheStore
- `MapCacheStore``SetCacheStore``ListCacheStore``CollectionCacheStore` 的子类。
- 至少需要提供其中一种实现才能算是一个有效的缓存组件。尚未实现的部分将会由其他组件代替。
完成缓存组件的开发后,应按照 SPI 机制的要求,设置所属 CacheStoreFactory 为 Service。
正常情况下,该模块不需要进行更改,即使需要更改,也需要保证向后兼容性。
-32
View File
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-CacheStore-api</artifactId>
</project>
@@ -1,114 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.Arrays;
import java.util.Objects;
/**
* 缓存键.
* 可根据不同缓存实现的需要调整名称.
* @author LamGC
*/
public final class CacheKey {
/**
* 默认分隔符.
*/
public final static String DEFAULT_SEPARATOR = ".";
private final String[] keys;
/**
* 创建一个缓存键名.
* @param first 组成键名的第一个部分.
* @param keyStrings 键名的其余组成部分.
* @throws NullPointerException 当 keyStrings 为 null 时抛出.
*/
public CacheKey(String first, String... keyStrings) {
Objects.requireNonNull(first);
if (keyStrings == null || keyStrings.length == 0) {
this.keys = new String[] {first};
} else {
checkKeyStrings(keyStrings);
this.keys = new String[keyStrings.length + 1];
this.keys[0] = first;
System.arraycopy(keyStrings, 0, this.keys, 1, keyStrings.length);
}
}
/**
* 提供一组字符串用于组成缓存键.
* @param keyStrings 键名组成数组.
*/
public CacheKey(String[] keyStrings) {
Objects.requireNonNull(keyStrings);
checkKeyStrings(keyStrings);
if (keyStrings.length == 0) {
throw new IllegalArgumentException("Provide at least one element that makes up the key");
}
this.keys = keyStrings;
}
private void checkKeyStrings(String[] keyStrings) {
for (String keyString : keyStrings) {
if (keyString == null) {
throw new NullPointerException("KeyStrings contains null");
}
}
}
/**
* 获取组成 Key 的字符串数组.
* @return 返回用于组成 Key 的字符串数组.
*/
public String[] getKeyArray() {
return keys;
}
/**
* 使用指定分隔符组成完整 Key.
* @param separator 分隔符.
* @return 返回组装后的完整 Key.
*/
public String join(String separator) {
return String.join(separator, keys);
}
@Override
public String toString() {
return join(DEFAULT_SEPARATOR);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CacheKey cacheKey = (CacheKey) o;
return Arrays.equals(keys, cacheKey.keys);
}
@Override
public int hashCode() {
return Arrays.hashCode(keys);
}
}
@@ -1,92 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.Set;
/**
* 缓存存储容器.
* 缓存库之间不应该出现干扰的情况, 即使允许在实现上共用一个存储.
* @author LamGC
*/
public interface CacheStore<V> {
/**
* 设置指定缓存的生存时间.
*
* <p>当该缓存项的 TTL 为 0 时, 该缓存项将会失效, 并被删除.
* 关于删除失效缓存项的时机在此并不特别规定, 依照各实现自行处理.
* @param key 欲设置过期时间的缓存项键名.
* @param ttl 有效期时间, 如果设为 -1 则代表清除缓存项的 TTL, 缓存项不会因为 TTL 为 0 而失效. 单位"毫秒(ms)".
* @return 如果设置成功, 返回 true, 如果设置失败, 或缓存项不存在, 返回 false.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean setTimeToLive(CacheKey key, long ttl);
/**
* 查询指定缓存项的 TTL.
* @param key 欲查询 TTL 的缓存项键名.
* @return 如果缓存项存在且已设置 TTL, 则返回当前剩余 TTL, 如果缓存项不存在或未设置 TTL, 返回 -1.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
long getTimeToLive(CacheKey key);
/**
* 获取当前缓存项数量.
* @return 返回缓存项数量.
*/
long size();
/**
* 清空缓存存储容器.
* @return 操作成功返回 true.
*/
boolean clear();
/**
* 检查指定缓存项是否存在.
*
* <p>如果缓存项不存在或失效(比如因 TTL 为 0 而失效), 则会判定为不存在.
* 缓存项失效不代表不存在(因为根据实现的不同, 可能还没来得及清理失效缓存项),
* 但缓存项不存在一定是失效的(即便如此, 缓存项一旦失效, 便不可获取).
*
* <p>故后续除特殊情况使用"缓存项 失效"描述, 文档将以"缓存项不存在"描述缓存项失效或不存在的情况.
* @param key 缓存项键名
* @return 如果存在, 返回 true, 如果不存在或失效, 返回 false.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean exists(CacheKey key);
/**
* 删除指定缓存.
* @param key 欲删除的缓存项键名.
* @return 如果存在并删除成功, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean remove(CacheKey key);
// 文档没有硬性要求"Set 中不能存在失效缓存项"的原因是因为: 即便确保了当时获取到的 Set 没有失效缓存项,
// 但是如果在获取后立刻出现失效缓存项了呢? 这个情况下依然不能保证没有失效的缓存项, 故不在文档中做出该硬性要求.
// 虽然是没说吧, 但是在返回时依然需要确保不会有过多失效缓存项的情况, 且获取时务必检查缓存项是否存在, 不要认为 Set 中的缓存项必然存在!
/**
* 获取缓存存储容器中的所有缓存项键名.
* @return 返回存储了所有缓存项键名的 Set 对象.
*/
Set<String> keySet();
}
@@ -1,94 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
/**
* 缓存存储容器构造工厂.
*
* <p>可支持不同实现缓存存储容器.
* @author LamGC
*/
public interface CacheStoreFactory {
/**
* Factory 初始化方法.
* <p> 当 Factory 被加载且验证正确后, 将会在第一次获取 CacheStore 前被调用一次(也可能在验证完成后执行).
* <p> 该方法仅调用一次, 之后将不再调用.
* @param dataDirectory 数据存储目录, 可在该目录内存储配置, 或持久化缓存.
* @throws Exception 上层管理类允许捕获任何抛出的异常, 如果 Factory 抛出异常, 将视为失效, 不被使用.
*/
void initial(File dataDirectory) throws Exception;
/**
* 获取一个新的 CacheStore 对象.
* @param identify 缓存标识.
* @param converter 类型的转换器.
* @return 返回 CacheStore 对象.
* @throws GetCacheStoreException 当 Factory 无法返回 CacheStore 对象时抛出, 需说明失败原因.
*/
<V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException;
/**
* 获取一个新的有序列表缓存存储容器.
* @param identify 缓存标识.
* @param converter 元素类型与 String 的转换器.
* @param <E> 元素类型.
* @return 返回新的有序列表缓存存储容器.
* @throws GetCacheStoreException 当 Factory 无法返回 CacheStore 对象时抛出, 需说明失败原因.
*/
<E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException;
/**
* 获取一个新的无序集合缓存存储容器.
* @param identify 缓存标识.
* @param converter 元素类型与 String 的转换器.
* @param <E> 元素类型.
* @return 返回新的无序集合缓存存储容器.
* @throws GetCacheStoreException 当 Factory 无法返回 CacheStore 对象时抛出, 需说明失败原因.
*/
<E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException;
/**
* 获取一个新的映射表缓存存储容器.
* @param identify 缓存标识
* @param converter 字段值类型与 String 的转换器.
* @param <V> 字段值类型.
* @return 返回新的映射表缓存存储容器.
* @throws GetCacheStoreException 当 Factory 无法返回 CacheStore 对象时抛出, 需说明失败原因.
*/
<V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException;
/**
* 当前是否可以创建 {@link CacheStore}
*
* <p> 如果返回 true, 将会使用该 Factory.
* 如果返回 false 或抛出异常, 将不会通过该 Factory 创建 CacheStore,
*
* <p> 除非模块能保证 Factory 正常情况下一定能提供 CacheStore 对象,
* 否则请不要尝试永远返回 true 来向应用保证 Factory 一定能创建 CacheStore, 保持 Factory 的有效性,
* 一旦后续创建 CacheStore 时发生异常, 将视为无法创建.
* @return 如果可以, 返回 true.
*/
boolean canGetCacheStore();
}
@@ -1,37 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
/**
* 缓存容器类型.
* @author LamGC
*/
public enum CacheStoreSource {
/**
* 内存存储(速度最快).
*/
MEMORY,
/**
* 本地存储(单机存储).
*/
LOCAL,
/**
* 远端存储(例如网络, 可多机读写且与单机无关).
*/
REMOTE
}
@@ -1,94 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.Collection;
/**
* 集合型缓存存储容器.
* @param <E> 元素类型.
* @param <C> 集合类型.
* @see Collection
* @author LamGC
*/
public interface CollectionCacheStore<E, C extends Collection<E>> extends CacheStore<C> {
/**
* 为缓存项添加一个元素.
* 当缓存项不存在时, 将会创建一个新的缓存项.
* @param key 缓存项键名.
* @param element 待添加的元素.
* @return 如果成功返回 true.
* @throws NullPointerException 当 key 或 element 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
boolean addElement(CacheKey key, E element);
/**
* 为缓存项添加一组元素.
* 当缓存项不存在时, 将会创建一个新的缓存项.
* @param key 缓存项键名.
* @param elements 欲添加的元素集合.
* @return 如果成功添加, 返回 true, 如果无法添加(例如缓存项 List/Set 长度限制), 返回 false.
* @throws NullPointerException 当 key 或 value 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
boolean addElements(CacheKey key, Collection<E> elements);
/**
* 检查指定元素是否包含在指定缓存项中.
* @param key 待检查的缓存项键名.
* @param element 待查找的缓存值.
* @return 如果存在, 返回 true, 如果元素不存在, 或缓存项不存在, 返回 false.
* @throws NullPointerException 当 key 或 element 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
boolean containsElement(CacheKey key, E element);
/**
* 检查指定缓存项是否为空.
* @param key 待检查的缓存项键名.
* @return 如果缓存项无元素, 返回 true, 等效于 {@code elementsLength(key) == 0}
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean isEmpty(CacheKey key);
/**
* 获取指定缓存项中的元素数量.
* @param key 待获取元素的缓存项键名.
* @return 返回指定缓存项中的元素数量, 如果缓存项不存在, 返回 -1.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
int elementsLength(CacheKey key);
/**
* 清空集合中的所有元素.
* @param key 欲清空集合的缓存项键名.
* @return 操作成功返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean clearCollection(CacheKey key);
/**
* 删除缓存项中指定的元素.
* <p>该方法与 {@link CacheStore#remove(CacheKey)} 不同, 该方法仅删除缓存项中的指定元素, 即使删除后缓存项中没有元素, 也不会删除缓存项.
* @param key 待操作的缓存项键名.
* @param element 欲删除的元素.
* @return 如果元素存在且删除成功, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean removeElement(CacheKey key, E element);
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author LamGC
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Factory {
/**
* Cache 模块名称.
* @return 返回实现模块名称.
*/
String name();
/**
* CacheStore 优先级.
* <p>最终所使用的 CacheStoreFactory 将会根据其优先级进行选择.
* 当优先级高的 Factory 表示无法创建 CacheStore 时, 将会寻找比该 Factory 优先级较低的下一个 Factory 并尝试获取,
* 重复该过程直到找到能使用的 Factory, 或者使用缺省的 CacheStore-local.
*
* <p>注意: 即使优先级超过 {@linkplain FactoryPriority#PRIORITY_HIGHEST 10} 也会被视为 10,
* 同样的, 即使优先级低于 {@linkplain FactoryPriority#PRIORITY_LOWEST 0} 也会被视为 0.
* @return 返回优先级, 最低优先级为 0, 优先级越高, 越会优先选择, 除非无法使用.
*/
int priority() default FactoryPriority.PRIORITY_NORMAL;
/**
* CacheStore 存储源类型.
* <p> 可帮助 Core 根据需要选择对应的类型(比如部分低TTL临时缓存追求速度不强求持久化, 建立热点数据高频短时缓存等).
* @return 返回存储源类型.
*/
CacheStoreSource source() default CacheStoreSource.LOCAL;
}
@@ -1,61 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
/**
* Factory 优先级常量.
* @author LamGC
*/
public final class FactoryPriority {
/**
* 最高优先级
* <p> 优先级数值: 10
*/
public final static int PRIORITY_HIGHEST = 10;
/**
* 较高优先级
* <p> 优先级数值: 8
*/
public final static int PRIORITY_HIGHER = 8;
/**
* 普通优先级
* <p> 优先级数值: 5
*/
public final static int PRIORITY_NORMAL = 5;
/**
* 较低优先级
* <p> 优先级数值: 3
*/
public final static int PRIORITY_LOWER = 3;
/**
* 最高优先级
* <p> 优先级数值: 0
*/
public final static int PRIORITY_LOWEST = 0;
/**
* 该类不允许实例化.
*/
private FactoryPriority() {}
}
@@ -1,79 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.List;
/**
* List 类型的缓存存储容器.
* <p>该存储容器内的 List 与 {@link List} 概念相同, 即"有序列表".
* @param <E> 值类型.
* @author LamGC
*/
public interface ListCacheStore<E> extends CollectionCacheStore<E, List<E>> {
/**
* 获取缓存项中的指定元素.
* @param key 欲取值的缓存项键名.
* @param index 元素索引, 从 0 开始.
* @return 如果缓存项存在, 且指定索引存在元素, 则返回元素,
* 如果缓存项不存在, 或索引超出范围(超出长度或低于 0)等导致获取失败, 返回 null.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
E getElement(CacheKey key, int index);
/**
* 根据返回获取部分元素.
* @param key 欲取值的缓存项键值.
* @param index 起始元素索引, 从 0 开始.
* @param length 获取长度.
* @return 如果成功, 返回成功获取到的元素数量, 失败返回 null.
* <p>对于起始元素索引超出范围, 或获取长度与实际能获取的长度不同的情况, 不允许返回 null,
* 相反, 返回的 List 中的元素数量应反映实际所可获取的元素数量, 例如:
* <ul>
* <li>因 index 超出范围(包括 index 低于 0, 也是如此)而无法获取元素, 则返回无元素 List 对象;
* <li>如果从起始元素开始获取无法获取 length 数量的元素, 则直接返回包含已成功获取元素的 List 对象.
* </ul>
* 但需要注意的是: 获取异常不包括在上述范围, 因此如果在获取中出现错误导致获取失败, 应以失败返回 null.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
List<E> getElementsByRange(CacheKey key, int index, int length);
/**
* 删除指定索引的元素.
*
* <p>该方法与 {@link CacheStore#remove(CacheKey)} 不同, 该方法仅删除缓存项中的指定元素, 即使删除后缓存项中没有元素, 也不会删除缓存项.
* @param key 待操作的缓存项键名.
* @param index 欲删除元素的索引, 从 0 开始.
* @return 如果元素存在且删除成功, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean removeElement(CacheKey key, int index);
/**
* 删除缓存项中指定的元素.
* <p>当 List 存在多个该元素时, 删除第一个匹配到的元素.
* <p>该方法与 {@link CacheStore#remove(CacheKey)} 不同, 该方法仅删除缓存项中的指定元素, 即使删除后缓存项中没有元素, 也不会删除缓存项.
* @param key 待操作的缓存项键名.
* @param element 欲删除的元素.
* @return 如果元素存在且删除成功, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
@Override
boolean removeElement(CacheKey key, E element);
}
@@ -1,129 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.Map;
import java.util.Set;
/**
* Map 缓存存储容器.
* @author LamGC
*/
public interface MapCacheStore<V> extends CacheStore<Map<String, V>> {
/**
* 获取 Map 字段数量.
* @param key Map 缓存项的键名.
* @return 返回 Map 字段数量, 如果缓存项不存在或获取失败, 返回 -1.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
int mapSize(CacheKey key);
/**
* 获取 Map 字段集合.
* @param key 待查询的 Map 缓存项的键名.
* @return 返回 Map 字段集合, 如果缓存项不存在, 返回 null.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
Set<String> mapFieldSet(CacheKey key);
/**
* 获取 Map 字段值集合.
* @param key 待查询的 Map 缓存项的键名.
* @return 返回 Map 字段值集合, 如果缓存项不存在, 返回 null.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
Set<V> mapValueSet(CacheKey key);
/**
* 将指定的值与此映射中的指定字段关联.
* @param key Map 缓存项的键名.
* @param field 字段名.
* @param value 字段值.
* @return 如果成功返回 true.
* @throws NullPointerException 当 key/field/value 为 null 时抛出, 缓存存储容器不允许出现 null 值.
*/
boolean put(CacheKey key, String field, V value);
/**
* 添加一组字段.
* @param key Map 缓存项的键名.
* @param map 待添加的 Map.
* @return 如果成功返回 true.
* @throws NullPointerException 当 key/map 为 null 时抛出, 缓存存储容器不允许出现 null 值.
*/
boolean putAll(CacheKey key, Map<? extends String, ? extends V> map);
/**
* 如果字段不存在, 则会将指定的值与此映射中的指定字段关联.
*
* <p>该方法与 {@link #put(CacheKey, String, Object)} 类似, 但如果字段存在, 将不会执行任何操作并以失败返回.
* @param key Map 缓存项的键名.
* @param field 字段名.
* @param value 字段值.
* @return 如果字段不存在且设置成功, 返回 true, 否则返回 false.
* @throws NullPointerException 当 key/field/value 为 null 时抛出, 缓存存储容器不允许出现 null 值.
*/
boolean putIfNotExist(CacheKey key, String field, V value);
/**
* 获取指定字段的字段值.
* @param key Map 缓存项的键名.
* @param field 字段名.
* @return 如果 Map 缓存项存在且字段存在, 返回字段的对应值.
* @throws NullPointerException 当 key/field 为 null 时抛出.
*/
V get(CacheKey key, String field);
/**
* 删除 Map 中的指定字段.
* @param key Map 缓存项的键名.
* @param field 待删除的字段名.
* @return 如果 Map 缓存项存在, 字段存在并且删除成功, 返回 true.
* @throws NullPointerException 当 key/field 为 null 时抛出.
*/
boolean removeField(CacheKey key, String field);
/**
* 检查 Map 中是否有指定字段.
* @param key Map 缓存项的键名.
* @param field 待检查的字段名.
* @return 如果 Map 缓存项存在且字段存在, 返回 true.
* @throws NullPointerException 当 key/field 为 null 时抛出.
*/
boolean containsField(CacheKey key, String field);
/**
* 检查 Map 是否为空(没有任何字段).
*
* <p>该方法等价于 {@code mapSize(key) == 0}
* @param key Map 缓存项的键名.
* @return 如果 Map 缓存项存在且为空, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean mapIsEmpty(CacheKey key);
/**
* 清空 Map 中的所有字段(并不会删除 Map 缓存项).
* @param key 待清空的 Map 缓存项键名.
* @return 如果存在且清空成功, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean clearMap(CacheKey key);
}
@@ -1,37 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.Set;
/**
* 无序集合的缓存存储容器.
* <p>其中, 元素是唯一的, 不会出现重复情况.
* @param <E> 值类型.
* @author LamGC
*/
public interface SetCacheStore<E> extends CollectionCacheStore<E, Set<E>> {
/*
* 说实话, SetCacheStore 的存在有点...奇怪, 或者可能就没有用,
* 因为根据 2 代对缓存存储的使用情况来看, 用到 Set 的地方根本就没有,
* 而且可能在某些方面实现起来也不是件容易事情.
* 所以 SetCacheStore 可能会废弃掉(最晚也会在正式版出来前),
* 或者有用的话才会为其设计相关方法.
* (比如偏向 Redis 的那套进行设计, 但是我希望不要出现偏向性, 为所有实现提供一个平衡的实现复杂度)
*/
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
/**
* 单项存取的缓存存储容器.
* <p>该缓存存储容器在存储上, 一个键对应一个值, 不存在一对多的情况.
* @param <V> 值类型.
* @author LamGC
*/
public interface SingleCacheStore<V> extends CacheStore<V> {
/**
* 设置指定键为指定值.
* 如果缓存项不存在, 则新建缓存并存储, 如果存在, 则覆盖原缓存;
* <p>覆盖缓存相当于是"删除"该缓存并重新创建, "删除"意味着原缓存项的相关设置将会丢失(例如"过期时间").
* @param key 缓存项键名.
* @param value 缓存值.
* @return 如果成功返回 true.
* @throws NullPointerException 当 key 或 value 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
boolean set(CacheKey key, V value);
/**
* 设置指定键为指定值.
* <p>该方法与 {@link #set(CacheKey, Object)} 类似, 但如果该 key 已经存在缓存, 则不执行 set 操作并返回 false.
* @param key 缓存项键名.
* @param value 缓存值.
* @return 如果成功返回 true, 当 key 已存在, 或设置失败时返回 false.
* @throws NullPointerException 当 key 或 value 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
boolean setIfNotExist(CacheKey key, V value);
/**
* 获取缓存项值.
* @param key 欲取值的缓存项键名.
* @return 如果缓存项存在, 返回缓存项的值, 否则返回 null.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
V get(CacheKey key);
}
@@ -1,42 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.convert;
/**
* 转换器接口
* @param <S> 源类型
* @param <T> 目标类型
* @author LamGC
*/
public interface Converter<S, T> {
/**
* 将源类型对象转换成目标类型.
* @param source 源类型对象.
* @return 对应的目标类型对象.
*/
T to(S source);
/**
* 从目标类型对象转换回源类型.
* @param target 目标类型对象.
* @return 对应的源类型对象.
*/
S from(T target);
}
@@ -1,27 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.convert;
/**
* 某类型到字符串的转换器接口.
* @param <T> 源类型.
* @author LamGC
*/
public interface StringConverter<T> extends Converter<T, String> {
}
@@ -1,35 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.convert;
/**
* 字符串原样转换的转换器.
* 不进行任何处理.
* @author LamGC
*/
public class StringToStringConverter implements StringConverter<String> {
@Override
public String to(String source) {
return source;
}
@Override
public String from(String target) {
return target;
}
}
@@ -1,34 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.exception;
/**
* 获取 CacheStore 异常.
* <p>当无法获取 CacheStore 时抛出.
* @author LamGC
*/
public class GetCacheStoreException extends RuntimeException {
public GetCacheStoreException(String message) {
super(message);
}
public GetCacheStoreException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,63 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import org.junit.Assert;
import org.junit.Test;
/**
* @see CacheKey
*/
public class CacheKeyTest {
@Test
public void hashCodeAndEqualsTest() {
CacheKey key = new CacheKey("test");
Assert.assertEquals(key, key);
Assert.assertNotEquals(key, null);
Assert.assertNotEquals(key, new Object());
Assert.assertEquals(new CacheKey("test", "key01"), new CacheKey("test", "key01"));
Assert.assertEquals(new CacheKey("test", "key01").hashCode(), new CacheKey("test", "key01").hashCode());
Assert.assertNotEquals(new CacheKey("test", "key01"), new CacheKey("test", "key00"));
Assert.assertNotEquals(new CacheKey("test", "key01").hashCode(), new CacheKey("test", "key00").hashCode());
Assert.assertEquals(new CacheKey("test", "key01").toString(), "test.key01");
}
@Test
public void buildTest() {
Assert.assertThrows(NullPointerException.class, () -> new CacheKey(null));
Assert.assertThrows(NullPointerException.class, () -> new CacheKey(null, new String[0]));
Assert.assertThrows(IllegalArgumentException.class, () -> new CacheKey(new String[0]));
final String[] keys = new String[] {"test", "key01"};
Assert.assertArrayEquals(new CacheKey(keys).getKeyArray(), keys);
}
@Test
public void joinTest() {
Assert.assertEquals("test.key01", new CacheKey("test", "key01").join("."));
Assert.assertEquals("test:key01", new CacheKey("test", "key01").join(":"));
}
@Test
public void nullValueCheckTest() {
CacheKey test = new CacheKey("test", (String[]) null);
Assert.assertEquals("test", test.toString());
Assert.assertThrows(NullPointerException.class, () -> new CacheKey("test", (String) null));
}
}
@@ -1,38 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.convert;
import org.junit.Assert;
import org.junit.Test;
/**
* @see StringToStringConverter
*/
public class StringToStringConverterTest {
private final Converter<String, String> stringConverter = new StringToStringConverter();
private final static String TEST_CONTENT = "test";
@Test
public void convertTest() {
Assert.assertEquals(TEST_CONTENT, stringConverter.to(TEST_CONTENT));
Assert.assertEquals(TEST_CONTENT, stringConverter.from(TEST_CONTENT));
}
}
@@ -1,39 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.exception;
import org.junit.Assert;
import org.junit.Test;
public class GetCacheStoreExceptionTest {
@Test
public void messageCheck() {
final String message = "uncaught exception";
Assert.assertEquals(message, new GetCacheStoreException(message).getMessage());
}
@Test
public void causeCheck() {
final String message = "uncaught exception";
final Throwable cause = new IllegalStateException();
Assert.assertEquals(cause, new GetCacheStoreException(message, cause).getCause());
}
}
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-CacheStore-local</artifactId>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-CacheStore-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
@@ -1,122 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* 定时清理机制.
* <p>定时通知已实现 {@link Cleanable} 接口的对象进行清理.
* @see Cleanable
* @author LamGC
*/
public class AutoCleanTimer implements Runnable {
private final static Set<WeakReference<Cleanable>> CLEANABLE_STORE_SET = new CopyOnWriteArraySet<>();
private final static ScheduledExecutorService SCHEDULED_EXECUTOR =
new ScheduledThreadPoolExecutor(1,
new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("Thread-AutoClean-%d")
.build()
);
private final static Logger log = LoggerFactory.getLogger(AutoCleanTimer.class);
private final static AtomicReference<ReferenceQueue<Cleanable>> REFERENCE_QUEUE = new AtomicReference<>(null);
static {
SCHEDULED_EXECUTOR.scheduleAtFixedRate(new AutoCleanTimer(), 100L, 100L, TimeUnit.MILLISECONDS);
Runtime.getRuntime().addShutdownHook(new Thread(SCHEDULED_EXECUTOR::shutdownNow, "ShutdownThread-AutoClean"));
}
/**
* 增加需要定时执行清理的缓存库.
* @param store 已实现Cleanable的对象
*/
public static void add(Cleanable store) {
CLEANABLE_STORE_SET.add(new WeakReference<>(store, REFERENCE_QUEUE.get()));
}
/**
* 移除对指定 Cleanable 的轮询.
* @param store 欲停止轮询的 Cleanable 对象.
*/
public static void remove(final Cleanable store) {
CLEANABLE_STORE_SET.removeIf(cleanableReference -> cleanableReference.get() == store);
}
/**
* 获取当前被轮询的 Cleanable 数量.
* @return 返回轮询的 Cleanable 数量.
*/
public static int size() {
return CLEANABLE_STORE_SET.size();
}
/**
* 设置虚引用回收队列, 以检查虚引用对象回收状况.
* <p> 本方法用于诊断 AutoCleanTimer 对虚引用对象的处理情况, 一般情况下无需使用.
* @param queue 引用队列.
*/
public static void setWeakReferenceQueue(ReferenceQueue<Cleanable> queue) {
REFERENCE_QUEUE.set(queue);
}
private AutoCleanTimer() {}
private final Set<WeakReference<Cleanable>> toBeCleanReference = new HashSet<>();
@Override
public void run() {
if (CLEANABLE_STORE_SET.size() == 0) {
return;
}
for (WeakReference<Cleanable> reference : CLEANABLE_STORE_SET) {
Cleanable store = reference.get();
if (reference.isEnqueued() || store == null) {
// 由于 COW ArraySet 的 Iterator 不支持 remove 操作,
// 所以先收集起来, 等完成所有清理工作后统一删除引用.
toBeCleanReference.add(reference);
continue;
}
try {
store.clean();
} catch (Exception e) {
log.error("{} 执行清理动作时发生异常:\n{}", store.toString(), Throwables.getStackTraceAsString(e));
}
}
if (toBeCleanReference.size() != 0) {
CLEANABLE_STORE_SET.removeAll(toBeCleanReference);
toBeCleanReference.clear();
}
}
}
@@ -1,33 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
/**
* 可清理接口, 实现该接口代表该类具有清理动作.
* @author LamGC
*/
public interface Cleanable {
/**
* 该方法需要CacheStore完成对过期Entry的清除.
* @return 返回已清理数量.
* @throws Exception 即使该方法抛出异常, 也不会影响后续情况.
*/
long clean() throws Exception;
}
@@ -1,95 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.ListCacheStore;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 基于 {@link CopyOnWriteArrayList} 的有序列表缓存存储容器.
* @param <E> 元素类型.
* @author LamGC
*/
public class CopyOnWriteArrayListCacheStore<E>
extends LocalCollectionCacheStore<E, List<E>>
implements ListCacheStore<E>
{
@Override
public E getElement(CacheKey key, int index) {
List<E> itemCollection = getCacheItemCollection(key, false);
try {
return itemCollection == null ? null : itemCollection.get(index);
} catch (IndexOutOfBoundsException e) {
return null;
}
}
@Override
public List<E> getElementsByRange(CacheKey key, int index, int length) {
int listLength = elementsLength(key);
if (listLength == -1) {
return null;
}
List<E> itemCollection = getCacheItemCollection(key, false);
List<E> result = new ArrayList<>();
try {
ListIterator<E> iterator = itemCollection.listIterator(index);
for (int i = 0; i < length && iterator.hasNext(); i++) {
result.add(iterator.next());
}
} catch (IndexOutOfBoundsException ignored) {
// 正常情况来讲, 该 try-catch 块只有 listIterator 会抛出 IndexOutOfBoundsException,
// 而一旦抛出 IndexOutOfBoundsException, 就代表 index 溢出了, try 块后面代码没有继续执行,
// 既然抛出异常时, result 并没有添加任何元素, 为何要再 new 一个 List 浪费内存呢? :D
}
return result;
}
@Override
public boolean removeElement(CacheKey key, int index) {
List<E> itemCollection = getCacheItemCollection(key, false);
if (itemCollection != null) {
try {
itemCollection.remove(index);
return true;
} catch (ArrayIndexOutOfBoundsException e) {
return false;
}
}
return false;
}
@Override
protected List<E> getCacheItemCollection(CacheKey key, boolean create) {
Objects.requireNonNull(key);
Map<String, CacheItem<List<E>>> cacheMap = getCacheMap();
if (!cacheMap.containsKey(key.toString())) {
if (create) {
cacheMap.put(key.toString(), new CacheItem<>(new CopyOnWriteArrayList<>()));
} else {
return null;
}
}
return cacheMap.get(key.toString()).getValue();
}
}
@@ -1,163 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.CacheStore;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* 基于 {@link Hashtable} 的缓存存储容器.
* @param <V> 值类型.
* @author LamGC
* @see net.lamgc.cgj.bot.cache.CacheStore
* @see Hashtable
*/
public abstract class HashCacheStore<V> implements CacheStore<V>, Cleanable {
private final Map<String, CacheItem<V>> cacheMap = new Hashtable<>();
/**
* 获取内部 Map 对象.
* 仅供其他子类使用.
* @return 返回存储缓存项的 Map.
*/
protected Map<String, CacheItem<V>> getCacheMap() {
return cacheMap;
}
@Override
public boolean setTimeToLive(CacheKey key, long ttl) {
if (!exists(key)) {
return false;
}
CacheItem<V> item = cacheMap.get(key.toString());
item.setExpireDate(ttl < 0 ? null : new Date(System.currentTimeMillis() + ttl));
return true;
}
@Override
public long getTimeToLive(CacheKey key) {
if (!exists(key)) {
return -1;
}
CacheItem<V> item = cacheMap.get(key.toString());
Date expireDate = item.getExpireDate();
if (expireDate != null) {
return expireDate.getTime() - System.currentTimeMillis();
}
return -1;
}
@Override
public long size() {
return cacheMap.size();
}
@Override
public boolean clear() {
cacheMap.clear();
return true;
}
@Override
public boolean exists(CacheKey key) {
if (!cacheMap.containsKey(key.toString())) {
return false;
}
CacheItem<V> item = cacheMap.get(key.toString());
// 在检查其过期情况后根据情况进行清理, 减轻主动清理机制的负担.
if (item.isExpire(new Date())) {
remove(key);
return false;
}
return true;
}
@Override
public boolean remove(CacheKey key) {
// 根据 Collection 说明, 删除时 key 存在映射就会返回, 只要返回 null 就代表没有.
return cacheMap.remove(key.toString()) != null;
}
@Override
public Set<String> keySet() {
return Collections.unmodifiableSet(cacheMap.keySet());
}
@Override
public long clean() {
Map<String, CacheItem<V>> cacheMap = getCacheMap();
Date currentDate = new Date();
AtomicLong cleanCount = new AtomicLong(0);
cacheMap.keySet().removeIf(key -> {
CacheItem<V> item = cacheMap.get(key);
if (item.isExpire(currentDate)) {
cleanCount.incrementAndGet();
return true;
}
return false;
});
return cleanCount.get();
}
/**
* 缓存项.
* @author LamGC
*/
protected final static class CacheItem<V> {
private final V value;
private Date expireDate;
CacheItem(V value) {
this(value, null);
}
CacheItem(V value, Date expireDate) {
this.value = value;
this.expireDate = expireDate;
}
public V getValue() {
return value;
}
public void setExpireDate(Date expireDate) {
this.expireDate = expireDate;
}
public Date getExpireDate() {
return expireDate;
}
/**
* 检查缓存项是否过期.
* @param date 当前时间.
* @return 如果已设置过期时间且早于提供的Date, 则该缓存项过期, 返回 true.
* @throws NullPointerException 当 date 传入 null 时抛出.
*/
public boolean isExpire(Date date) {
Date expireDate = getExpireDate();
return expireDate != null && expireDate.before(Objects.requireNonNull(date));
}
}
}
@@ -1,119 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import java.util.*;
import java.util.function.Function;
/**
*
* @param <V>
* @see net.lamgc.cgj.bot.cache.CacheStore
* @see net.lamgc.cgj.bot.cache.MapCacheStore
* @author LamGC
*/
public class HashMapCacheStore<V> extends HashCacheStore<Map<String, V>> implements MapCacheStore<V> {
@Override
public int mapSize(CacheKey key) {
return getMap(key, false, Map::size, -1);
}
@Override
public Set<String> mapFieldSet(CacheKey key) {
return getMap(key, false, map -> Collections.unmodifiableSet(map.keySet()), null);
}
@Override
public Set<V> mapValueSet(CacheKey key) {
return getMap(key, false, map -> new HashSet<>(map.values()), null);
}
@Override
public boolean put(CacheKey key, String field, V value) {
return getMap(key, true, map -> {
map.put(Objects.requireNonNull(field), Objects.requireNonNull(value));
return true;
}, false);
}
@Override
public boolean putAll(CacheKey key, Map<? extends String, ? extends V> map) {
return getMap(key, true, keyMap -> {
keyMap.putAll(Objects.requireNonNull(map));
return true;
}, false);
}
@Override
public boolean putIfNotExist(CacheKey key, String field, V value) {
return getMap(key, true, map -> {
if (map.containsKey(Objects.requireNonNull(field))) {
return false;
}
map.put(Objects.requireNonNull(field), Objects.requireNonNull(value));
return true;
}, false);
}
@Override
public V get(CacheKey key, String field) {
return getMap(key, false, map -> map.get(Objects.requireNonNull(field)), null);
}
@Override
public boolean removeField(CacheKey key, String field) {
return getMap(key, false, map -> map.remove(Objects.requireNonNull(field)) != null, false);
}
@Override
public boolean containsField(CacheKey key, String field) {
return getMap(key, false, map -> map.containsKey(Objects.requireNonNull(field)), false);
}
@Override
public boolean mapIsEmpty(CacheKey key) {
return getMap(key, false, Map::isEmpty, false);
}
@Override
public boolean clearMap(CacheKey key) {
return getMap(key, false, map -> {
map.clear();
return true;
}, false);
}
private <R> R getMap(CacheKey key, boolean create, Function<Map<String, V>, R> notNull, R isNull) {
Objects.requireNonNull(key);
String keyString = key.toString();
Map<String, CacheItem<Map<String, V>>> cacheMap = getCacheMap();
if (!cacheMap.containsKey(keyString)) {
if (create) {
cacheMap.put(keyString, new CacheItem<>(new Hashtable<>()));
} else {
return isNull;
}
}
return notNull.apply(cacheMap.get(keyString).getValue());
}
}
@@ -1,47 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SetCacheStore;
import java.util.*;
/**
*
* @param <E> 元素类型.
* @author LamGC
*/
public class HashSetCacheStore<E> extends LocalCollectionCacheStore<E, Set<E>> implements SetCacheStore<E> {
@Override
protected Set<E> getCacheItemCollection(CacheKey key, boolean create) {
Objects.requireNonNull(key);
String keyString = key.toString();
Map<String, CacheItem<Set<E>>> cacheMap = getCacheMap();
if (!cacheMap.containsKey(keyString)) {
if (create) {
cacheMap.put(keyString, new CacheItem<>(new HashSet<>()));
} else {
return null;
}
}
return cacheMap.get(keyString).getValue();
}
}
@@ -1,54 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import java.util.Objects;
/**
* 基于 {@link java.util.Hashtable} 的 Map 缓存存储容器.
* @param <V> 值类型.
* @author LamGC
*/
public class HashSingleCacheStore<V> extends HashCacheStore<V> implements SingleCacheStore<V> {
@Override
public boolean set(CacheKey key, V value) {
getCacheMap().put(Objects.requireNonNull(key).toString(), new CacheItem<>(Objects.requireNonNull(value)));
return true;
}
@Override
public boolean setIfNotExist(CacheKey key, V value) {
if (exists(key)) {
return false;
}
return set(key, value);
}
@Override
public V get(CacheKey key) {
if (!exists(key)) {
return null;
}
return getCacheMap().get(key.toString()).getValue();
}
}
@@ -1,70 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import java.io.File;
/**
* 本地缓存存储容器工厂.
* 最快速但又是最占内存的方法, 适用于远端缓存失效, 或无远端缓存的情况下使用.
* 最简单的缓存实现, 无持久化功能.
* @author LamGC
*/
@Factory(name = "Local-Memory", priority = FactoryPriority.PRIORITY_LOWEST, source = CacheStoreSource.MEMORY)
public class LocalCacheStoreFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
// 不需要做任何事情, 除非需要做持久化.
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
return new HashSingleCacheStore<>();
}
@Override
public <V> ListCacheStore<V> newListCacheStore(String identify, StringConverter<V> converter) {
return new CopyOnWriteArrayListCacheStore<>();
}
@Override
public <V> SetCacheStore<V> newSetCacheStore(String identify, StringConverter<V> converter) {
return new HashSetCacheStore<>();
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
return new HashMapCacheStore<>();
}
/**
* 内存使用阀值.
* <p>当内存使用到了指定百分比时, 将禁止创建 CacheStore.
*/
private final static double MEMORY_USAGE_THRESHOLD = 85;
@Override
public boolean canGetCacheStore() {
Runtime runtime = Runtime.getRuntime();
double memoryUsedPercentage = (double) runtime.totalMemory() / runtime.maxMemory();
return memoryUsedPercentage < MEMORY_USAGE_THRESHOLD;
}
}
@@ -1,111 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.CollectionCacheStore;
import java.util.Collection;
import java.util.Objects;
/**
* 本地集合缓存存储容器.
* @param <E> 元素类型
* @author LamGC
* @see net.lamgc.cgj.bot.cache.CacheStore
* @see net.lamgc.cgj.bot.cache.CollectionCacheStore
*/
public abstract class LocalCollectionCacheStore<E, C extends Collection<E>>
extends HashCacheStore<C>
implements CollectionCacheStore<E, C> {
/**
* 获取缓存项集合对象.
* @param key 缓存项键名
* @param create 如果不存在, 是否创建.
* @return 如果不存在且 create 为 false, 或添加失败, 返回 false, 添加成功返回 true.
*/
protected abstract C getCacheItemCollection(CacheKey key, boolean create);
@Override
public boolean addElement(CacheKey key, E element) {
Objects.requireNonNull(key);
Objects.requireNonNull(element);
Collection<E> itemCollection = getCacheItemCollection(key, true);
return itemCollection.add(element);
}
@Override
public boolean addElements(CacheKey key, Collection<E> elements) {
Objects.requireNonNull(key);
Objects.requireNonNull(elements);
Collection<E> itemCollection = getCacheItemCollection(key, true);
return itemCollection.addAll(elements);
}
@Override
public boolean containsElement(CacheKey key, E value) {
Objects.requireNonNull(key);
Objects.requireNonNull(value);
Collection<E> itemCollection = getCacheItemCollection(key, false);
if (itemCollection == null) {
return false;
}
return itemCollection.contains(value);
}
@Override
public boolean isEmpty(CacheKey key) {
Collection<E> itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false);
if (itemCollection == null) {
return false;
}
return itemCollection.isEmpty();
}
@Override
public int elementsLength(CacheKey key) {
Collection<E> itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false);
if (itemCollection == null) {
return -1;
}
return itemCollection.size();
}
@Override
public boolean clearCollection(CacheKey key) {
Collection<E> itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false);
if (itemCollection == null) {
return false;
}
itemCollection.clear();
return true;
}
@Override
public boolean removeElement(CacheKey key, E element) {
Objects.requireNonNull(key);
Objects.requireNonNull(element);
Collection<E> itemCollection = getCacheItemCollection(key, false);
if (itemCollection == null) {
return false;
}
return itemCollection.remove(element);
}
}
@@ -1,35 +0,0 @@
#
# Copyright (C) 2021 LamGC
#
# ContentGrabbingJi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License.
#
# ContentGrabbingJi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
#
# Copyright (C) 2020 LamGC
#
# ContentGrabbingJi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# ContentGrabbingJi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
net.lamgc.cgj.bot.cache.local.LocalCacheStoreFactory
@@ -1,91 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.ref.ReferenceQueue;
/**
* @see AutoCleanTimer
*/
public class AutoCleanTimerTest {
@BeforeClass
public static void before() throws ClassNotFoundException, InterruptedException {
Class.forName(AutoCleanTimer.class.getName(), true, ClassLoader.getSystemClassLoader());
Thread.sleep(150L);
}
@Test
public void addTest() throws InterruptedException {
HashSingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
AutoCleanTimer.add(cacheStore);
Thread.sleep(300L);
final CacheKey persistenceKey = new CacheKey("persistenceKey");
final CacheKey expireKey = new CacheKey("expireKey");
final String value = "testValue";
// 过期键与持久键
cacheStore.set(persistenceKey, value);
cacheStore.set(expireKey, value);
cacheStore.setTimeToLive(expireKey, 50);
Thread.sleep(1000L);
Assert.assertTrue(cacheStore.exists(persistenceKey));
Assert.assertFalse(cacheStore.exists(expireKey));
}
@Test
public void weakReferenceCleanTest() throws InterruptedException {
ReferenceQueue<Cleanable> referenceQueue = new ReferenceQueue<>();
AutoCleanTimer.setWeakReferenceQueue(referenceQueue);
AutoCleanTimer.add(new HashSingleCacheStore<>());
System.gc();
Assert.assertNotNull(referenceQueue.remove(100L));
System.gc();
Thread.sleep(300L);
Assert.assertEquals(0, AutoCleanTimer.size());
}
@Test
public void methodExceptionThrowTest() throws InterruptedException {
class ThrowExceptionCleanable implements Cleanable {
private boolean throed;
@Override
public long clean() throws Exception {
if (!throed) {
throed = true;
throw new Exception();
}
return 0;
}
}
ThrowExceptionCleanable cleanable = new ThrowExceptionCleanable();
AutoCleanTimer.add(cleanable);
Thread.sleep(300L);
AutoCleanTimer.remove(cleanable);
}
}
@@ -1,139 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import com.google.common.collect.Lists;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.ListCacheStore;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* @see CopyOnWriteArrayListCacheStore
* @see LocalCollectionCacheStore
*/
public class CopyOnWriteArrayListCacheStoreTest {
@Test
public void nullThrowTest() {
final ListCacheStore<String> cacheStore = new CopyOnWriteArrayListCacheStore<>();
final CacheKey key = new CacheKey("testKey");
// LocalCollectionCacheStore
Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElement(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElement(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElements(null, new ArrayList<>()));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElements(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.elementsLength(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.containsElement(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.containsElement(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.isEmpty(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.clearCollection(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeElement(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeElement(key, null));
// CopyOnWriteArrayListCacheStore
Assert.assertThrows(NullPointerException.class, () -> cacheStore.getElement(null, 0));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.getElementsByRange(null, 0, 0));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeElement(null, 0));
}
@Test
public void notExistCacheTest() {
final ListCacheStore<String> cacheStore = new CopyOnWriteArrayListCacheStore<>();
final CacheKey key = new CacheKey("testKey");
Assert.assertFalse(cacheStore.clearCollection(key));
Assert.assertFalse(cacheStore.isEmpty(key));
Assert.assertEquals(-1, cacheStore.elementsLength(key));
Assert.assertFalse(cacheStore.containsElement(key, "testValue"));
Assert.assertFalse(cacheStore.removeElement(key, "testValue"));
}
@Test
public void addAndGetTest() {
final ListCacheStore<Integer> cacheStore = new CopyOnWriteArrayListCacheStore<>();
final CacheKey key = new CacheKey("testKey");
List<Integer> numbers = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// getElement/getElementsByRange Cache不存在测试
Assert.assertNull(cacheStore.getElement(key, 0));
Assert.assertNull(cacheStore.getElementsByRange(key, 0, 1));
// addElement/getElement 正常情况测试
Assert.assertTrue("addElement operation failed!", cacheStore.addElement(key, 0));
Assert.assertEquals(new Integer(0), cacheStore.getElement(key, 0));
// 超出范围的 null 测试
Assert.assertNull(cacheStore.getElement(key, cacheStore.elementsLength(key)));
// addElements/getElementsByRange 正常情况测试
Assert.assertTrue("addElements operation failed!", cacheStore.addElements(key, numbers));
Assert.assertEquals(Lists.newArrayList(0, 1, 2), cacheStore.getElementsByRange(key, 0, 3));
// 不足长度的 getElementsByRange
Assert.assertEquals(Lists.newArrayList(7, 8, 9), cacheStore.getElementsByRange(key, 7, 8));
// 超出索引的 getElementsByRange
List<Integer> result = cacheStore.getElementsByRange(key, cacheStore.elementsLength(key) + 1, 8);
Assert.assertNotNull("getElementsByRange returned null if index is out of range", result);
Assert.assertEquals("getElementsByRange returned a non empty list when the index was out of range",
0, result.size());
// 不足长度的 getElementsByRange
Assert.assertEquals(Lists.newArrayList(), cacheStore.getElementsByRange(key, cacheStore.elementsLength(key), 0));
}
@Test
public void removeElementTest() {
// removeElement(String, E) / removeElement(String, int)
final ListCacheStore<String> cacheStore = new CopyOnWriteArrayListCacheStore<>();
final CacheKey key = new CacheKey("testKey");
Random random = new Random();
List<String> numbers = Lists.newArrayList("1", "2", "3", "4", "5", "6", "7", "8", "9");
// 删除不存在 Cache 返回 false
Assert.assertFalse(cacheStore.removeElement(key, 0));
Assert.assertTrue("addElements operation failed!", cacheStore.addElements(key, numbers));
int removeIndex = random.nextInt(cacheStore.elementsLength(key));
numbers.remove(removeIndex);
Assert.assertTrue("removeElement operation failed!", cacheStore.removeElement(key, removeIndex));
Assert.assertEquals(numbers, cacheStore.getElementsByRange(key, 0, cacheStore.elementsLength(key)));
String removeTarget = cacheStore.getElement(key, random.nextInt(cacheStore.elementsLength(key)));
Assert.assertNotNull(removeTarget);
Assert.assertTrue(cacheStore.containsElement(key, removeTarget));
numbers.remove(removeTarget);
Assert.assertTrue("removeElement operation failed!", cacheStore.removeElement(key, removeTarget));
Assert.assertEquals(numbers, cacheStore.getElementsByRange(key, 0, cacheStore.elementsLength(key)));
Assert.assertTrue("clearCollection operation failed!", cacheStore.clearCollection(key));
Assert.assertTrue(cacheStore.exists(key));
Assert.assertEquals(0, cacheStore.elementsLength(key));
Assert.assertTrue(cacheStore.isEmpty(key));
// 删除不存在元素返回 false
Assert.assertFalse(cacheStore.removeElement(key, cacheStore.elementsLength(key)));
}
}
@@ -1,132 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
/**
* @see HashMapCacheStore
*/
public class HashMapCacheStoreTest {
@Test
public void nullThrowTest() {
final MapCacheStore<String> cacheStore = new HashMapCacheStore<>();
final CacheKey key = new CacheKey("testKey");
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapSize(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapFieldSet(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapValueSet(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(null, "field", "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, null, "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, "field", null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(null, new HashMap<>()));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(null, "field", "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, null, "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, "field", null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(null, "field"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(null, "field"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapIsEmpty(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.clearMap(null));
}
@Test
public void keyNotExistTest() {
final MapCacheStore<String> cacheStore = new HashMapCacheStore<>();
final CacheKey key = new CacheKey("testKey");
Assert.assertEquals(-1, cacheStore.mapSize(key));
Assert.assertFalse(cacheStore.mapIsEmpty(key));
Assert.assertFalse(cacheStore.clearMap(key));
Assert.assertFalse(cacheStore.containsField(key, "Field"));
Assert.assertFalse(cacheStore.removeField(key, "Field"));
Assert.assertNull(cacheStore.get(key, "Field"));
Assert.assertTrue(cacheStore.put(key, "Field", "value"));
Assert.assertTrue("clearMap operation failed!", cacheStore.remove(key));
Assert.assertTrue(cacheStore.putAll(key, new HashMap<>()));
Assert.assertTrue("clearMap operation failed!", cacheStore.remove(key));
Assert.assertTrue(cacheStore.putIfNotExist(key, "Field", "value"));
}
@Test
public void putAndGetTest() {
final MapCacheStore<String> cacheStore = new HashMapCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final Map<String, String> expectedMap = new HashMap<>();
expectedMap.put("test01", "testValue01");
expectedMap.put("test02", "testValue02");
expectedMap.put("test03", "testValue03");
expectedMap.put("test04", "testValue04");
expectedMap.put("test05", "testValue05");
expectedMap.put("test06", "testValue06");
// put/get, mapIsEmpty, containsField
Assert.assertTrue("put operation failed!", cacheStore.put(key, "test00", "testValue00"));
Assert.assertTrue(cacheStore.containsField(key, "test00"));
Assert.assertEquals("testValue00", cacheStore.get(key, "test00"));
Assert.assertTrue("removeField operation failed!", cacheStore.removeField(key, "test00"));
// putIfNotExist
Assert.assertTrue(cacheStore.putIfNotExist(key, "test00", "testValue00"));
Assert.assertFalse(cacheStore.putIfNotExist(key, "test00", "testValue00"));
Assert.assertTrue("clearMap operation failed!", cacheStore.clearMap(key));
// putAll
Assert.assertTrue(cacheStore.putAll(key, expectedMap));
Assert.assertTrue(expectedMap.keySet().containsAll(cacheStore.mapFieldSet(key)));
Assert.assertTrue(expectedMap.values().containsAll(cacheStore.mapValueSet(key)));
}
@Test
public void fieldChangeTest() {
final MapCacheStore<String> cacheStore = new HashMapCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final Map<String, String> expectedMap = new HashMap<>();
expectedMap.put("test01", "testValue01");
expectedMap.put("test02", "testValue02");
expectedMap.put("test03", "testValue03");
expectedMap.put("test04", "testValue04");
expectedMap.put("test05", "testValue05");
expectedMap.put("test06", "testValue06");
// mapSize, clearMap, mapIsEmpty 测试
Assert.assertTrue("putAll operation failed!", cacheStore.putAll(key, expectedMap));
Assert.assertEquals(expectedMap.size(), cacheStore.mapSize(key));
Assert.assertTrue(cacheStore.clearMap(key));
Assert.assertTrue(cacheStore.mapIsEmpty(key));
// removeField 多分支测试
Assert.assertTrue("put operation failed!", cacheStore.put(key, "test00", "testValue00"));
Assert.assertTrue(cacheStore.containsField(key, "test00"));
Assert.assertEquals("testValue00", cacheStore.get(key, "test00"));
Assert.assertTrue("removeField operation failed!", cacheStore.removeField(key, "test00"));
Assert.assertFalse(cacheStore.removeField(key, "test00"));
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.*;
/**
* @see HashSetCacheStore
*/
public class HashSetCacheStoreTest {
@Test
public void getCacheItemCollection() {
HashSetCacheStore<String> store = new HashSetCacheStore<>();
final CacheKey key = new CacheKey("test");
Assert.assertNull(store.getCacheItemCollection(key, false));
Set<String> collection = store.getCacheItemCollection(key, true);
Assert.assertNotNull(collection);
Assert.assertEquals(collection, store.getCacheItemCollection(key, false));
}
}
@@ -1,176 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import com.google.common.base.Throwables;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
/**
* @see HashSingleCacheStore
* @see HashCacheStore
*/
public class HashSingleCacheStoreTest {
@Test
public void nullThrowTest() {
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
// HashSingleCacheStore
Assert.assertThrows(NullPointerException.class, () -> cacheStore.set(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.set(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.setIfNotExist(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.setIfNotExist(key, null));
// HashCacheStore
Assert.assertThrows(NullPointerException.class, () -> cacheStore.exists(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.getTimeToLive(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.setTimeToLive(null, 0));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.remove(null));
}
@Test
public void setAndGetTest() {
SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertEquals(value, cacheStore.get(key));
Assert.assertTrue("Remove operation failed!", cacheStore.remove(key));
Assert.assertNull("Set operation failed!", cacheStore.get(key));
}
@Test
public void setIfNotExistTest() {
SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
final String value2 = "testValue02";
Assert.assertFalse(cacheStore.exists(key));
Assert.assertTrue("The key does not exist but an unexpected result was returned!",
cacheStore.setIfNotExist(key, value));
Assert.assertFalse(cacheStore.setIfNotExist(key, value2));
Assert.assertEquals(value, cacheStore.get(key));
}
@Test
public void expireTest() throws InterruptedException {
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
// Cache
Assert.assertFalse(cacheStore.setTimeToLive(key, 300));
Assert.assertEquals(-1, cacheStore.getTimeToLive(key));
// TTL 到期被动检查测试: 使用 exists 经 expire 检查失败后返回 false.
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, 200));
Assert.assertNotEquals(-1, cacheStore.getTimeToLive(key));
Thread.sleep(300);
Assert.assertFalse(cacheStore.exists(key));
// 取消 TTL 测试
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, 200));
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, -1));
Thread.sleep(300);
Assert.assertTrue(cacheStore.exists(key));
Assert.assertEquals(-1, cacheStore.getTimeToLive(key));
}
@Test
public void removeTest() {
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
// 删除不存在Cache测试
Assert.assertFalse(cacheStore.remove(key));
// 删除存在的Cache测试
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue(cacheStore.remove(key));
}
@Test
public void clearTest() {
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue(cacheStore.exists(key));
Assert.assertTrue("Clear operation failed!", cacheStore.clear());
Assert.assertFalse(cacheStore.exists(key));
}
@Test
public void sizeAndKeySetTest() {
Map<String, String> expectedMap = new HashMap<>();
expectedMap.put("test01", "testValue01");
expectedMap.put("test02", "testValue02");
expectedMap.put("test03", "testValue03");
expectedMap.put("test04", "testValue04");
expectedMap.put("test05", "testValue05");
expectedMap.put("test06", "testValue06");
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
expectedMap.forEach((key, value) -> cacheStore.set(new CacheKey(key), value));
Assert.assertEquals(expectedMap.size(), cacheStore.size());
Assert.assertTrue(expectedMap.keySet().containsAll(cacheStore.keySet()));
}
@Test
public void cleanTest() throws InterruptedException {
SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey persistenceKey = new CacheKey("persistenceKey");
final CacheKey expireKey = new CacheKey("expireKey");
final String value = "testValue";
// 过期键与持久键
cacheStore.set(persistenceKey, value);
cacheStore.set(expireKey, value);
cacheStore.setTimeToLive(expireKey, 50);
Thread.sleep(100L);
Cleanable cleanableStore = (Cleanable) cacheStore;
try {
cleanableStore.clean();
} catch (Exception e) {
Assert.fail("Cleaning up expired keys is an exception\n" +
Throwables.getStackTraceAsString(e));
return;
}
Assert.assertTrue(cacheStore.exists(persistenceKey));
Assert.assertFalse(cacheStore.exists(expireKey));
}
}
@@ -1,93 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.ListCacheStore;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import net.lamgc.cgj.bot.cache.SetCacheStore;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
/**
* @see LocalCacheStoreFactory
*/
public class LocalCacheStoreFactoryTest {
private final static TemporaryFolder tempFolder = TemporaryFolder.builder()
.build();
private final static String IDENTIFY = "test";
private final static StringConverter<String> CONVERTER = new StringToStringConverter();
private static File storeFolder;
@BeforeClass
public static void beforeProcess() throws IOException {
tempFolder.create();
storeFolder = tempFolder.newFolder("cache", "Local-Memory");
Assert.assertNotNull(createLocalCacheStoreFactory());
}
private static LocalCacheStoreFactory createLocalCacheStoreFactory() {
LocalCacheStoreFactory factory = new LocalCacheStoreFactory();
Assert.assertNotNull(storeFolder);
factory.initial(storeFolder);
return factory;
}
@Test
public void initial() {
Assert.assertNotNull(createLocalCacheStoreFactory());
}
@Test
public void newSingleCacheStore() {
SingleCacheStore<String> cacheStore = createLocalCacheStoreFactory().newSingleCacheStore(IDENTIFY, CONVERTER);
Assert.assertEquals(HashSingleCacheStore.class, cacheStore.getClass());
}
@Test
public void newListCacheStore() {
ListCacheStore<String> cacheStore = createLocalCacheStoreFactory().newListCacheStore(IDENTIFY, CONVERTER);
Assert.assertEquals(CopyOnWriteArrayListCacheStore.class, cacheStore.getClass());
}
@Test
public void newSetCacheStore() {
SetCacheStore<String> cacheStore = createLocalCacheStoreFactory().newSetCacheStore(IDENTIFY, CONVERTER);
Assert.assertEquals(HashSetCacheStore.class, cacheStore.getClass());
}
@Test
public void newMapCacheStore() {
MapCacheStore<String> cacheStore = createLocalCacheStoreFactory().newMapCacheStore(IDENTIFY, CONVERTER);
Assert.assertEquals(HashMapCacheStore.class, cacheStore.getClass());
}
@Test
public void canGetCacheStore() {
Assert.assertTrue(createLocalCacheStoreFactory().canGetCacheStore());
}
}
@@ -1,63 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-CacheStore-redis</artifactId>
<build>
<resources>
<resource>
<directory>${project.basedir}/src/main/lua/</directory>
<includes>
<include>*.lua</include>
</includes>
<targetPath>${project.build.outputDirectory}/lua/</targetPath>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-CacheStore-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
</dependencies>
</project>
@@ -1,50 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
/**
* @author LamGC
*/
public enum LuaScript {
/**
* [List] 检查元素是否存在.
*/
LIST_CHECK_ELEMENT_CONTAINS("CheckElementContains"),
/**
* [List] 删除指定索引的元素.
*/
LIST_REMOVE_ELEMENT_BY_INDEX("RemoveElementByIndex"),
/**
* [All] 删除所有前缀为指定字符串的键.
*/
STORE_REMOVE_KEYS_BY_PREFIX("RemoveKeysByPrefix")
;
public final static String PACKAGE_PATH = "lua/";
private final String scriptName;
LuaScript(String scriptName) {
this.scriptName = scriptName;
}
public String getScriptName() {
return scriptName;
}
}
@@ -1,110 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.CacheStore;
import java.util.*;
/**
*
* @author LamGC
*/
public abstract class RedisCacheStore<V> implements CacheStore<V> {
private final RedisConnectionPool connectionPool;
protected RedisCacheStore(RedisConnectionPool connectionPool) {
this.connectionPool = connectionPool;
}
/**
* 获取 Key 前缀.
* <p>key = getKeyPrefix() + key
* @param cacheKey CacheKey 对象.
* @return 返回 Key 前缀.
*/
protected String getKeyString(CacheKey cacheKey) {
return RedisUtils.toRedisCacheKey(getKeyPrefix(), Objects.requireNonNull(cacheKey));
}
/**
* 获取 Key 的完整前缀.
* @return 返回完整前缀.
*/
protected abstract String getKeyPrefix();
@Override
public boolean setTimeToLive(CacheKey key, long ttl) {
String keyString = getKeyString(key);
return connectionPool.executeRedis(jedis -> {
Long result;
if (ttl >= 0) {
result = jedis.pexpire(keyString, ttl);
} else {
result = jedis.persist(keyString);
}
return result.intValue() == RedisUtils.RETURN_CODE_OK;
});
}
@Override
public long getTimeToLive(CacheKey key) {
return connectionPool.executeRedis(jedis -> {
Long ttl = jedis.pttl(getKeyString(key));
return ttl < 0 ? -1 : ttl;
});
}
@Override
public long size() {
return (long) connectionPool.executeRedis(jedis -> jedis.keys(getKeyString(RedisUtils.CACHE_KEY_ALL)).size());
}
@Override
public boolean clear() {
List<String> keys = new ArrayList<>(1);
keys.add(getKeyString(RedisUtils.CACHE_KEY_ALL));
connectionPool.executeScript(LuaScript.STORE_REMOVE_KEYS_BY_PREFIX, keys, null);
return true;
}
@Override
public boolean exists(CacheKey key) {
return connectionPool.executeRedis(jedis -> jedis.exists(getKeyString(key)));
}
@Override
public boolean remove(CacheKey key) {
return connectionPool.executeRedis(jedis -> jedis.del(getKeyString(key)) == RedisUtils.RETURN_CODE_OK);
}
@Override
public Set<String> keySet() {
Set<String> keys = connectionPool.executeRedis(jedis ->
jedis.keys(getKeyString(RedisUtils.CACHE_KEY_ALL)));
final int prefixLength = getKeyPrefix().length();
Set<String> newKeys = new HashSet<>();
for (String key : keys) {
newKeys.add(key.substring(prefixLength + 1));
}
return newKeys;
}
}
@@ -1,86 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import com.google.gson.Gson;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
*
* @author LamGC
*/
@Factory(name = "Redis", priority = FactoryPriority.PRIORITY_HIGHER, source = CacheStoreSource.REMOTE)
public class RedisCacheStoreFactory implements CacheStoreFactory {
private final static Logger log = LoggerFactory.getLogger(RedisCacheStoreFactory.class);
private final RedisConnectionPool connectionPool = new RedisConnectionPool();
@Override
public void initial(File dataDirectory) {
final File propertiesFile = new File(dataDirectory, RedisUtils.PROPERTIES_FILE_NAME);
if (!propertiesFile.exists()) {
log.warn("未找到 Redis 配置文件, 使用默认配置.");
return;
} else if (!propertiesFile.isFile()) {
log.warn("Redis 配置文件不是一个文件, 使用默认配置.");
return;
}
try (Reader propertiesReader = new BufferedReader(
new InputStreamReader(new FileInputStream(propertiesFile), StandardCharsets.UTF_8))) {
RedisConnectionProperties properties = new Gson()
.fromJson(propertiesReader, RedisConnectionProperties.class);
connectionPool.setConnectionProperties(properties);
log.debug("Redis 配置文件已成功读取: {}", properties);
} catch (IOException e) {
log.error("读取 Redis 配置文件时发生异常, 将使用默认配置连接 Redis.", e);
}
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
return new RedisSingleCacheStore<>(connectionPool, identify, converter);
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) {
return new RedisListCacheStore<>(connectionPool, identify, converter);
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) {
throw new GetCacheStoreException("No corresponding implementation");
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
return new RedisMapCacheStore<>(connectionPool, identify, converter);
}
@Override
public boolean canGetCacheStore() {
return connectionPool.available();
}
}
@@ -1,232 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
/**
* 统一的 Redis 连接池.
* @author LamGC
*/
class RedisConnectionPool {
private final static Logger log = LoggerFactory.getLogger(RedisConnectionPool.class);
private final AtomicReference<JedisPool> POOL = new AtomicReference<>();
private final AtomicReference<RedisConnectionProperties> connectionProperties = new AtomicReference<>();
private final Map<LuaScript, String> scriptMap = new HashMap<>();
public RedisConnectionPool() {
Runtime.getRuntime().addShutdownHook(new Thread(this::close,
"Shutdown-RedisConnectionPool@" + Integer.toHexString(this.hashCode())));
}
public synchronized void setConnectionProperties(RedisConnectionProperties properties) {
if(connectionProperties.get() != null) {
connectionProperties.set(properties);
}
}
public synchronized void reconnectRedis() {
JedisPool jedisPool = POOL.get();
if (jedisPool != null && !jedisPool.isClosed()) {
return;
}
JedisPoolConfig config = new JedisPoolConfig();
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
RedisConnectionProperties properties = connectionProperties.get();
if (properties == null) {
jedisPool = new JedisPool(config);
} else {
String userName = properties.getUserName();
if (Strings.isNullOrEmpty(userName)) {
jedisPool = new JedisPool(config,
properties.getHost(),
properties.getPort(),
properties.getConnectionTimeout(),
properties.getSocketTimeout(),
properties.getPassword(),
properties.getDatabaseId(),
properties.getClientName(),
properties.enableSsl()
);
} else {
jedisPool = new JedisPool(config,
properties.getHost(),
properties.getPort(),
properties.getConnectionTimeout(),
properties.getSocketTimeout(),
userName,
properties.getPassword(),
properties.getDatabaseId(),
properties.getClientName(),
properties.enableSsl()
);
}
}
POOL.set(jedisPool);
loadScript();
}
/**
* 获取一个 Jedis 对象.
* <p>注意, 需回收 Jedis 对象, 否则可能会耗尽连接池导致后续操作受到影响.
* @return 返回可用的 Jedis 连接.
*/
public Jedis getConnection() {
JedisPool pool = POOL.get();
if (pool == null || pool.isClosed()) {
reconnectRedis();
pool = POOL.get();
if (pool == null) {
throw new IllegalStateException("Redis connection lost");
}
}
return pool.getResource();
}
/**
* 执行 Redis 操作并返回相关值.
* <p>本方法会自动回收 Jedis.
* @param function 待运行的操作.
* @param <R> 返回值类型.
* @return 返回 function 返回的内容.
*/
public <R> R executeRedis(Function<Jedis, R> function) {
try (Jedis jedis = getConnection()) {
return function.apply(jedis);
}
}
/**
* 检查 Redis 连接池是否有可用的资源.
* @return 如果连接池依然活跃, 返回 true.
*/
public boolean available() {
JedisPool jedisPool = POOL.get();
if (jedisPool == null || jedisPool.isClosed()) {
reconnectRedis();
jedisPool = POOL.get();
if (jedisPool == null || jedisPool.isClosed()) {
return false;
}
}
if (jedisPool.getNumIdle() == 0) {
try (Jedis jedis = jedisPool.getResource()) {
return "pong".equalsIgnoreCase(jedis.ping());
} catch (Exception e) {
log.error("Redis 连接测试时发生异常", e);
return false;
}
}
return true;
}
/**
* 获取指定脚本的 Sha.
* @param script 脚本.
* @return 如果存在, 返回 Sha, 否则返回 null.
*/
public String getScriptSha(LuaScript script) {
return scriptMap.get(script);
}
/**
* 加载脚本.
*/
private void loadScript() {
for (LuaScript script : LuaScript.values()) {
InputStream scriptStream = this.getClass().
getResourceAsStream("/" + LuaScript.PACKAGE_PATH + script.getScriptName() + ".lua");
if (scriptStream == null) {
log.warn("脚本 {} 获取失败, 相关操作将无法使用, 请检查缓存组件是否损坏.", script.getScriptName());
continue;
}
String scriptContent;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(scriptStream, StandardCharsets.UTF_8))) {
String line;
StringBuilder builder = new StringBuilder();
while((line = reader.readLine()) != null) {
builder.append(line).append('\n');
}
scriptContent = builder.toString();
} catch (IOException e) {
log.error("读取脚本文件时发生异常.(Script: " + script.getScriptName() + ")", e);
continue;
}
try {
String scriptSha = executeRedis(jedis -> jedis.scriptLoad(scriptContent));
if (scriptSha != null) {
scriptMap.put(script, scriptSha);
log.debug("脚本 {} 已成功加载.(Sha: {})", script, scriptSha);
}
} catch (Exception e) {
log.error("加载脚本时发生异常.(Script: " + script.getScriptName() + ")", e);
}
}
}
/**
* 执行脚本.
* @param script Lua 脚本.
* @param keys 待传入脚本的键列表.
* @param args 待传入脚本的参数列表.
* @return 如果成功, 返回脚本所返回的数据, 需根据脚本实际返回转换对象.
* @throws NullPointerException 当 script 为 {@code null} 时抛出.
*/
public Object executeScript(final LuaScript script, final List<String> keys, final List<String> args) {
String scriptSha = this.getScriptSha(Objects.requireNonNull(script));
if (scriptSha == null) {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
log.warn("脚本未加载, 方法 {}() 无法执行(方法存在于Class {}:{}). (所需脚本: {})",
stackTraceElements[2].getMethodName(),
stackTraceElements[2].getClassName(),
stackTraceElements[2].getLineNumber(),
script.getScriptName());
return false;
}
return executeRedis(jedis -> {
List<String> keysList = (keys == null) ? Collections.emptyList() : keys;
List<String> argsList = (args == null) ? Collections.emptyList() : args;
return jedis.evalsha(scriptSha, keysList, argsList);
});
}
public void close() {
JedisPool jedisPool = POOL.get();
if (jedisPool != null && !jedisPool.isClosed()) {
jedisPool.close();
}
}
}
@@ -1,128 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import com.google.common.base.Strings;
import redis.clients.jedis.Protocol;
/**
* Redis 连接配置对象.
* @author LamGC
*/
@SuppressWarnings({"FieldCanBeLocal", "unused"})
public class RedisConnectionProperties {
private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
private int socketTimeout = Protocol.DEFAULT_TIMEOUT;
private String host = Protocol.DEFAULT_HOST;
private int port = Protocol.DEFAULT_PORT;
private boolean ssl = false;
private String userName = null;
private String password = null;
private int databaseId = Protocol.DEFAULT_DATABASE;
private String clientName = null;
@Override
public String toString() {
int showPasswordLength = password.length() / 4;
return "RedisConnectionProperties{" +
"connectionTimeout=" + connectionTimeout +
", socketTimeout=" + socketTimeout +
", host='" + host + '\'' +
", port=" + port +
", ssl=" + ssl +
", userName='" + userName + '\'' +
", password='" + password.substring(0, showPasswordLength) +
Strings.repeat("*", password.length() - showPasswordLength) + '\'' +
", databaseId=" + databaseId +
", clientName='" + clientName + '\'' +
'}';
}
public int getConnectionTimeout() {
return connectionTimeout;
}
public int getSocketTimeout() {
return socketTimeout;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public boolean enableSsl() {
return ssl;
}
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
public int getDatabaseId() {
return databaseId;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public void setDatabaseId(int databaseId) {
this.databaseId = databaseId;
}
public void setHost(String host) {
this.host = host;
}
public void setPassword(String password) {
this.password = password;
}
public void setPort(int port) {
this.port = port;
}
public void setSocketTimeout(int socketTimeout) {
this.socketTimeout = socketTimeout;
}
public void setEnableSsl(boolean ssl) {
this.ssl = ssl;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
@@ -1,158 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.ListCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* Redis 列表缓存存储容器.
* @param <E>
* @author LamGC
*/
public class RedisListCacheStore<E> extends RedisCacheStore<List<E>> implements ListCacheStore<E> {
private final String keyPrefix;
private final StringConverter<E> converter;
private final RedisConnectionPool connectionPool;
public RedisListCacheStore(RedisConnectionPool connectionPool, String keyPrefix, StringConverter<E> converter) {
super(connectionPool);
this.connectionPool = connectionPool;
keyPrefix = Objects.requireNonNull(keyPrefix).trim();
if (keyPrefix.isEmpty()) {
throw new IllegalArgumentException("Key prefix cannot be empty.");
}
if (keyPrefix.endsWith(RedisUtils.KEY_SEPARATOR)) {
this.keyPrefix = keyPrefix;
} else {
this.keyPrefix = keyPrefix + RedisUtils.KEY_SEPARATOR;
}
this.converter = Objects.requireNonNull(converter);
}
@Override
public E getElement(CacheKey key, int index) {
return connectionPool.executeRedis(jedis -> converter.from(jedis.lindex(getKeyString(key), index)));
}
@Override
public List<E> getElementsByRange(CacheKey key, int index, int length) {
List<String> strings = connectionPool.executeRedis(jedis ->
// stop = start + length - 1
jedis.lrange(getKeyString(key), index, index + length - 1));
List<E> result = new ArrayList<>(strings.size());
strings.forEach(element -> result.add(converter.from(element)));
return result;
}
@Override
public boolean removeElement(CacheKey key, int index) {
List<String> keys = new ArrayList<>(1);
List<String> args = new ArrayList<>(1);
keys.add(getKeyString(key));
args.add(String.valueOf(index));
Number result = (Number) connectionPool.executeScript(LuaScript.LIST_REMOVE_ELEMENT_BY_INDEX, keys, args);
return result.intValue() == 1;
}
@Override
public boolean removeElement(CacheKey key, E element) {
return connectionPool.executeRedis(jedis ->
jedis.lrem(getKeyString(key), 1, converter.to(element)) != RedisUtils.RETURN_CODE_FAILED);
}
@Override
public boolean addElement(CacheKey key, E element) {
Objects.requireNonNull(element);
connectionPool.executeRedis(jedis ->
jedis.lpush(getKeyString(key), converter.to(element)) != RedisUtils.RETURN_CODE_FAILED);
return true;
}
@Override
public boolean addElements(CacheKey key, Collection<E> elements) {
Objects.requireNonNull(elements);
if (elements.size() == 0) {
return exists(key);
}
List<E> values = new ArrayList<>(elements);
String[] valueStrings = new String[values.size()];
for (int i = 0; i < valueStrings.length; i++) {
valueStrings[i] = converter.to(values.get(i));
}
connectionPool.executeRedis(jedis ->
jedis.lpush(getKeyString(key), valueStrings) != RedisUtils.RETURN_CODE_FAILED);
return true;
}
/**
* {@inheritDoc}
*
* <p> 注意: 在 Redis 实现中, 该功能通过一段 Lua 脚本实现,
* 由于 Redis 并没有原生支持该功能, 所以只能用脚本遍历查找.
* 如果 List 元素过多, 可能会导致执行缓慢且影响后续操作, 谨慎使用.
* @param key 待检查的缓存项键名.
* @param element 待查找的缓存值.
* @return 如果存在, 返回 true, 如果元素不存在, 或缓存项不存在, 返回 false.
* @throws NullPointerException 当 key 或 element 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
@Override
public boolean containsElement(CacheKey key, E element) {
List<String> keys = new ArrayList<>(1);
List<String> args = new ArrayList<>(1);
keys.add(getKeyString(key));
args.add(converter.to(element));
Number result = (Number) connectionPool.executeScript(LuaScript.LIST_CHECK_ELEMENT_CONTAINS, keys, args);
return result.intValue() != -1;
}
@Override
public boolean isEmpty(CacheKey key) {
return elementsLength(key) == -1;
}
@Override
public int elementsLength(CacheKey key) {
long result = connectionPool.executeRedis(jedis -> jedis.llen(getKeyString(key)));
if (result == 0) {
return -1;
} else {
return (int) result;
}
}
@Override
public boolean clearCollection(CacheKey key) {
return remove(key);
}
@Override
protected String getKeyPrefix() {
return this.keyPrefix;
}
}
@@ -1,171 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import java.util.*;
/**
* Redis Map缓存存储容器.
* @param <V> 值类型.
* @author LamGC
*/
public class RedisMapCacheStore<V> extends RedisCacheStore<Map<String, V>> implements MapCacheStore<V> {
private final String keyPrefix;
private final StringConverter<V> converter;
private final RedisConnectionPool connectionPool;
public RedisMapCacheStore(RedisConnectionPool connectionPool, String keyPrefix, StringConverter<V> converter) {
super(connectionPool);
this.connectionPool = connectionPool;
keyPrefix = Objects.requireNonNull(keyPrefix).trim();
if (keyPrefix.isEmpty()) {
throw new IllegalArgumentException("Key prefix cannot be empty.");
}
if (keyPrefix.endsWith(RedisUtils.KEY_SEPARATOR)) {
this.keyPrefix = keyPrefix;
} else {
this.keyPrefix = keyPrefix + RedisUtils.KEY_SEPARATOR;
}
this.converter = Objects.requireNonNull(converter);
}
@Override
protected String getKeyPrefix() {
return this.keyPrefix;
}
@Override
public int mapSize(CacheKey key) {
return connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
if (jedis.exists(keyString)) {
return jedis.hlen(keyString).intValue();
}
return 0;
});
}
@Override
public Set<String> mapFieldSet(CacheKey key) {
return connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
if (jedis.exists(keyString)) {
return jedis.hkeys(keyString);
}
return null;
});
}
@Override
public Set<V> mapValueSet(CacheKey key) {
List<String> rawValueSet = connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
if (jedis.exists(keyString)) {
return jedis.hvals(keyString);
}
return null;
});
if (rawValueSet == null) {
return null;
}
Set<V> result = new HashSet<>();
for (String rawValue : rawValueSet) {
result.add(converter.from(rawValue));
}
return result;
}
@Override
public boolean put(CacheKey key, String field, V value) {
Objects.requireNonNull(field);
Objects.requireNonNull(value);
connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
return jedis.hset(keyString, field, converter.to(value));
});
return true;
}
@Override
public boolean putAll(CacheKey key, Map<? extends String, ? extends V> map) {
Objects.requireNonNull(key);
Objects.requireNonNull(map);
if (map.size() == 0) {
return exists(key);
}
final Map<String, String> targetMap = new HashMap<>(map.size());
map.forEach((k, v) -> targetMap.put(k, converter.to(v)));
return connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
return RedisUtils.isOk(jedis.hmset(keyString, targetMap));
});
}
@Override
public boolean putIfNotExist(CacheKey key, String field, V value) {
Objects.requireNonNull(field);
Objects.requireNonNull(value);
return connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
return jedis.hsetnx(keyString, field, converter.to(value)) == RedisUtils.RETURN_CODE_OK;
});
}
@Override
public V get(CacheKey key, String field) {
Objects.requireNonNull(field);
String value = connectionPool.executeRedis(jedis -> jedis.hget(getKeyString(key), field));
if (value == null) {
return null;
}
return converter.from(value);
}
@Override
public boolean removeField(CacheKey key, String field) {
Objects.requireNonNull(field);
return connectionPool.executeRedis(jedis ->
jedis.hdel(getKeyString(key), field) == RedisUtils.RETURN_CODE_OK);
}
@Override
public boolean containsField(CacheKey key, String field) {
Objects.requireNonNull(field);
return connectionPool.executeRedis(jedis -> jedis.hexists(getKeyString(key), field));
}
@Override
public boolean mapIsEmpty(CacheKey key) {
return mapSize(key) == 0;
}
@Override
public boolean clearMap(CacheKey key) {
return remove(key);
}
}
@@ -1,82 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import java.util.Objects;
/**
* Redis 单项缓存存储容器.
* @param <V> 值类型.
* @see net.lamgc.cgj.bot.cache.CacheStore
* @see net.lamgc.cgj.bot.cache.SingleCacheStore
* @see net.lamgc.cgj.bot.cache.redis.RedisCacheStore
* @author LamGC
*/
public class RedisSingleCacheStore<V> extends RedisCacheStore<V> implements SingleCacheStore<V> {
private final String keyPrefix;
private final StringConverter<V> converter;
private final RedisConnectionPool connectionPool;
public RedisSingleCacheStore(RedisConnectionPool connectionPool, String keyPrefix, StringConverter<V> converter) {
super(connectionPool);
this.connectionPool = connectionPool;
keyPrefix = Objects.requireNonNull(keyPrefix).trim();
if (keyPrefix.isEmpty()) {
throw new IllegalArgumentException("Key prefix cannot be empty.");
}
if (keyPrefix.endsWith(RedisUtils.KEY_SEPARATOR)) {
this.keyPrefix = keyPrefix;
} else {
this.keyPrefix = keyPrefix + RedisUtils.KEY_SEPARATOR;
}
this.converter = Objects.requireNonNull(converter);
}
@Override
public boolean set(CacheKey key, V value) {
return connectionPool.executeRedis(jedis ->
RedisUtils.isOk(jedis.set(getKeyString(key), converter.to(Objects.requireNonNull(value)))));
}
@Override
public boolean setIfNotExist(CacheKey key, V value) {
return connectionPool.executeRedis(jedis ->
jedis.setnx(getKeyString(key), converter.to(Objects.requireNonNull(value)))
== RedisUtils.RETURN_CODE_OK);
}
@Override
public V get(CacheKey key) {
String value = connectionPool.executeRedis(jedis -> jedis.get(getKeyString(key)));
if (value == null) {
return null;
}
return converter.from(value);
}
@Override
protected String getKeyPrefix() {
return this.keyPrefix;
}
}
@@ -1,77 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
/**
* @author LamGC
*/
public class RedisUtils {
/**
* 返回码 - 成功
*/
public final static int RETURN_CODE_OK = 1;
/**
* 返回码 - 失败
*/
public final static int RETURN_CODE_FAILED = 0;
/**
* Key 匹配规则 - 所有 Key
*/
public final static String KEY_PATTERN_ALL = "*";
/**
* 特殊缓存键 - 所有 Key.
*/
public final static CacheKey CACHE_KEY_ALL = new CacheKey(KEY_PATTERN_ALL);
/**
* Key 分隔符
*/
public final static String KEY_SEPARATOR = ":";
/**
* Redis 组件配置文件名.
*/
public final static String PROPERTIES_FILE_NAME = "redis.properties.json";
/**
* 检查字符串返回结果是否为操作成功.
* @param result 字符串返回结果.
* @return 如果为操作成功, 返回 true.
*/
public static boolean isOk(String result) {
return "OK".equalsIgnoreCase(result);
}
/**
* 将 {@link CacheKey} 转换为 Redis 的标准 key 格式.
* @param keyPrefix Key 前缀.
* @param cacheKey 缓存键对象.
* @return 返回格式化后的 Key.
*/
public static String toRedisCacheKey(String keyPrefix, CacheKey cacheKey) {
return (keyPrefix.endsWith(KEY_SEPARATOR) ? keyPrefix : (keyPrefix + KEY_SEPARATOR)) +
cacheKey.join(RedisUtils.KEY_SEPARATOR);
}
}
@@ -1,25 +0,0 @@
local key = tostring(KEYS[1])
local value = ARGV[1]
local step = 175
if (key == nil) or (value == nil) then
return -1
end
if not (ARGV[2] == nil) then
step = tonumber(ARGV[2])
end
-- 步进循环搜索, 找到了立刻返回
local listLength = redis.call('llen', key);
for i = 0, listLength, step do
local storeValue = redis.call("lrange", key, i, i + step)
for listIndex, v in ipairs(storeValue) do
if value == v then
return i + listIndex - 1;
end
end
end
-- 如果没找到索引, 返回 0
return -1
@@ -1,31 +0,0 @@
local function getRandom(n)
local t = {
"0","1","2","3","4","5","6","7","8","9",
"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
}
local s = ""
for _ = 1, n do
s = s .. t[math.random(#t)]
end;
return s
end
local key = tostring(KEYS[1])
local index = tonumber(ARGV[1])
if (key == nil) or (index == nil) or (redis.call("exists", key) == false) then
return 0
end
local listLength = redis.call("llen", key)
if listLength == 0 then
return 0
end
if (index < 0) or (index >= listLength) or redis.call("lIndex", key, index) == nil then
return 0
else
local flag = getRandom(24)
redis.call("lSet", key, index, flag);
return redis.call("lRem", key, 0, flag);
end
@@ -1,18 +0,0 @@
local keyPrefix = tostring(KEYS[1])
if (keyPrefix == nil) then
return false
end
if (string.sub(keyPrefix, string.len(keyPrefix)) ~= "*") then
keyPrefix = keyPrefix .. "*"
end
local keys = redis.call("KEYS", keyPrefix)
local count = 0;
for _, v in ipairs(keys) do
count = count + redis.call("DEL", v)
end
return count
@@ -1,35 +0,0 @@
#
# Copyright (C) 2021 LamGC
#
# ContentGrabbingJi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License.
#
# ContentGrabbingJi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
#
# Copyright (C) 2020 LamGC
#
# ContentGrabbingJi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# ContentGrabbingJi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
net.lamgc.cgj.bot.cache.redis.RedisCacheStoreFactory
@@ -1,211 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import org.junit.*;
import redis.clients.jedis.Jedis;
import java.util.HashSet;
import java.util.Set;
import static net.lamgc.cgj.bot.cache.redis.util.RedisTestUtils.assertDeleteIfExist;
import static net.lamgc.cgj.bot.cache.redis.util.RedisTestUtils.randomString;
/**
* @see RedisCacheStore
*/
public class RedisCacheStoreTest {
private final static String KEY_PREFIX = "test:store";
private static RedisConnectionPool connectionPool;
private static RedisCacheStore<String> cacheStore;
private static Jedis jedis;
@BeforeClass
public static void beforeAllTest() {
jedis = new Jedis();
connectionPool = new RedisConnectionPool();
cacheStore = new SimpleRedisCacheStore(connectionPool, KEY_PREFIX);
}
@AfterClass
public static void afterAllTest() {
if (jedis != null && jedis.isConnected()) {
jedis.close();
}
connectionPool.close();
}
@Before
public void beforeTest() {
clearAllKey();
}
@After
public void afterTest() {
clearAllKey();
}
private void clearAllKey() {
Set<String> keys = jedis.keys(RedisUtils.toRedisCacheKey(KEY_PREFIX, RedisUtils.CACHE_KEY_ALL));
if (keys.isEmpty()) {
return;
}
String[] keyArray = new String[keys.size()];
keys.toArray(keyArray);
Assert.assertEquals("Failed to delete test key.",
keyArray.length, jedis.del(keyArray).intValue());
}
@Test
public void setTimeToLiveTest() {
final CacheKey key = new CacheKey("test_set_time_to_live");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final long timeToLive = 500;
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("TTL set successfully for nonexistent key.", cacheStore.setTimeToLive(key, timeToLive));
Assert.assertTrue("Failed to set key value for test.", RedisUtils.isOk(jedis.set(keyString, "value")));
Assert.assertTrue("Failed to set TTL on key value.", cacheStore.setTimeToLive(key, timeToLive));
long currentTTL = jedis.ttl(keyString);
Assert.assertTrue("TTL was not set successfully.", currentTTL >= 0);
Assert.assertTrue("The actual set TTL is greater than expected.",
currentTTL < timeToLive);
Assert.assertTrue("Failed to clear TTL on key value.", cacheStore.setTimeToLive(key, -1));
currentTTL = jedis.ttl(keyString);
Assert.assertTrue("TTL was not clear successfully.", currentTTL < 0);
}
@Test
public void getTimeToLiveTest() {
final CacheKey key = new CacheKey("test_get_time_to_live");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final long timeToLive = 500;
assertDeleteIfExist(jedis, keyString);
Assert.assertEquals("The getTimeToLive() method returned a positive integer for a nonexistent key.",
-1, cacheStore.getTimeToLive(key));
Assert.assertTrue("Failed to set key value for test.", RedisUtils.isOk(jedis.set(keyString, "value")));
Assert.assertEquals("Failed to set TTL on key value.", 1, jedis.pexpire(keyString, timeToLive).intValue());
long currentTTL = cacheStore.getTimeToLive(key);
Assert.assertTrue("TTL failed to get.", currentTTL > 0);
Assert.assertTrue("The actual set TTL is greater than expected.",
currentTTL <= timeToLive);
Assert.assertTrue("The actual set TTL is greater than expected.",
currentTTL >= (timeToLive - 5));
Assert.assertEquals("Failed to clear TTL on key value.",
1, jedis.persist(keyString).intValue());
currentTTL = cacheStore.getTimeToLive(key);
Assert.assertTrue("TTL was not clear successfully.", currentTTL < 0);
}
@Test
public void sizeTest() {
final String keyPrefix =
RedisUtils.toRedisCacheKey(KEY_PREFIX, new CacheKey("test_size")) + ':';
final String allKey = RedisUtils.toRedisCacheKey(keyPrefix, RedisUtils.CACHE_KEY_ALL);
for (int i = 0; i < 50; i++) {
Assert.assertTrue("Failed to add key.", RedisUtils.isOk(
jedis.set(
RedisUtils.toRedisCacheKey(keyPrefix, new CacheKey(randomString(8))),
randomString(10)
)));
Assert.assertEquals("Inconsistent number of keys.", jedis.keys(allKey).size(), cacheStore.size());
}
}
@Test
public void existsTest() {
final CacheKey key = new CacheKey("test_exists");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("The exists() method returned 'true' for a nonexistent key.", cacheStore.exists(key));
Assert.assertTrue("Failed to set key value for test.", RedisUtils.isOk(jedis.set(keyString, "value")));
Assert.assertTrue("The exists() method returned 'false' for the existing key.", cacheStore.exists(key));
}
@Test
public void removeTest() {
final CacheKey key = new CacheKey("test_remove");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("Attempt to delete nonexistent key succeeded.", cacheStore.remove(key));
Assert.assertTrue("Failed to set key value for test.", RedisUtils.isOk(jedis.set(keyString, "value")));
Assert.assertTrue("Key deletion failed.", cacheStore.remove(key));
}
@Test
public void clearTest() {
final CacheKey key = new CacheKey("test_clear");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String externalKey = "test:external";
Assert.assertTrue("Failed to set external key.", RedisUtils.isOk(jedis.set(externalKey, "externalValue")));
Assert.assertTrue("Failed to set test key.", RedisUtils.isOk(jedis.set(keyString, "testValue")));
Assert.assertNotEquals("The cache store is empty.",
0, jedis.keys(RedisUtils.toRedisCacheKey(KEY_PREFIX, RedisUtils.CACHE_KEY_ALL)).size());
Assert.assertTrue(cacheStore.clear());
Assert.assertEquals("The cache store is not empty.",
0, jedis.keys(RedisUtils.toRedisCacheKey(KEY_PREFIX, RedisUtils.CACHE_KEY_ALL)).size());
Assert.assertTrue("External key removed.", jedis.exists(externalKey));
}
@Test
public void keySetTest() {
clearAllKey();
Set<String> actualKeys = cacheStore.keySet();
Assert.assertNotNull("keySet() returned 'null'.", actualKeys);
Assert.assertEquals("CacheStore is empty, but keySet() is not empty.", 0, actualKeys.size());
Set<String> expectKeys = new HashSet<>();
for (int i = 0; i < 50; i++) {
String nextKey = randomString(10);
expectKeys.add(nextKey);
Assert.assertTrue("Failed to set key value for test.",
RedisUtils.isOk(jedis.set(KEY_PREFIX + RedisUtils.KEY_SEPARATOR + nextKey, nextKey)));
}
actualKeys = cacheStore.keySet();
Assert.assertNotNull("keySet() returned 'null'.", actualKeys);
Assert.assertEquals("Number of keys not as expected.", expectKeys.size(), actualKeys.size());
for (String actualKey : actualKeys) {
Assert.assertTrue(String.format("Key '%s' does not belong to the expected key.", actualKey),
expectKeys.contains(actualKey));
}
}
}
@@ -1,379 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.ListCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import org.junit.*;
import redis.clients.jedis.Jedis;
import java.lang.reflect.Field;
import java.util.*;
/**
* @see RedisListCacheStore
*/
public class RedisListCacheStoreTest {
private static Jedis jedis;
private final static StringConverter<String> CONVERTER = new StringToStringConverter();
private final static String IDENTIFY = "test:list";
private static RedisConnectionPool connectionPool;
@BeforeClass
public static void beforeAllTest() {
jedis = new Jedis();
connectionPool = new RedisConnectionPool();
connectionPool.reconnectRedis();
Assert.assertTrue("Redis is not connected.", connectionPool.available());
}
@AfterClass
public static void afterAllTest() {
if (jedis != null) {
jedis.close();
}
}
private ListCacheStore<String> newListCacheStore() {
return new RedisListCacheStore<>(connectionPool, IDENTIFY, CONVERTER);
}
private Set<String> getListElements(String key) {
Set<String> actualElements = new HashSet<>();
for (long i = 0; i < jedis.llen(key); i++) {
actualElements.add(jedis.lindex(key, i));
}
return actualElements;
}
@Before
public void beforeTest() {
Set<String> keys = jedis.keys(RedisUtils.toRedisCacheKey(IDENTIFY, RedisUtils.CACHE_KEY_ALL));
for (String key : keys) {
jedis.del(key);
}
}
@Test
public void prefixCheck() throws NoSuchFieldException, IllegalAccessException {
final Field prefixField = RedisListCacheStore.class.getDeclaredField("keyPrefix");
prefixField.setAccessible(true);
String prefix = (String) prefixField.get(new RedisListCacheStore<>(connectionPool, IDENTIFY, CONVERTER));
Assert.assertTrue("The prefix does not contain a separator at the end.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefix = (String) prefixField.get(new RedisListCacheStore<>(connectionPool,
IDENTIFY + RedisUtils.KEY_SEPARATOR, CONVERTER));
Assert.assertTrue("The separator at the end of the prefix is missing.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefixField.setAccessible(false);
}
@Test(expected = IllegalArgumentException.class)
public void emptyPrefixTest() {
new RedisListCacheStore<>(connectionPool, "", CONVERTER);
}
@Test(expected = NullPointerException.class)
public void nullPrefixTest() {
new RedisListCacheStore<>(connectionPool, null, CONVERTER);
}
@Test
public void addElementTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_add_element");
final String element = "test";
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final int lastCount = jedis.llen(listKeyStr).intValue();
Assert.assertTrue("The operation to be tested failed.",
listCacheStore.addElement(listKey, element));
Assert.assertEquals("The key value has no change to the quantity.",
lastCount + 1, jedis.llen(listKeyStr).intValue());
Assert.assertEquals("The value of pop is different from that of push.",
element, jedis.lpop(listKeyStr));
}
@Test
public void addElementsTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_add_elements");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final Set<String> expectedElements = new HashSet<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
Assert.assertEquals("The key does not exist, but the empty collection was added successfully.",
jedis.exists(listKeyStr),
listCacheStore.addElements(listKey, Collections.emptyList()));
Assert.assertTrue("The operation to be tested failed.",
listCacheStore.addElements(listKey, expectedElements));
Assert.assertEquals("The quantity is not as expected.",
expectedElements.size(), jedis.llen(listKeyStr).intValue());
Set<String> actualElements = getListElements(listKeyStr);
Assert.assertTrue("The added value is not as expected.",
actualElements.containsAll(expectedElements));
Assert.assertEquals("Key does not exist, but adding empty collection failed.",
jedis.exists(listKeyStr),
listCacheStore.addElements(listKey, Collections.emptySet()));
}
@Test
public void removeElementByElementTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_remove_element_by_element");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final List<String> expectedElements = new ArrayList<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
jedis.del(listKeyStr);
Assert.assertFalse("The operation to be tested failed.",
listCacheStore.removeElement(listKey, "NoExistElement"));
Assert.assertNotEquals("The expected create operation failed.",
RedisUtils.RETURN_CODE_FAILED, jedis.lpush(listKeyStr, expectedElementsArr).intValue());
Random random = new Random();
final int deletedIndex = random.nextInt(expectedElements.size());
Assert.assertTrue("The operation to be tested failed.",
listCacheStore.removeElement(listKey, expectedElements.get(deletedIndex)));
expectedElements.remove(deletedIndex);
Assert.assertEquals("The quantity is not as expected.",
expectedElements.size(), jedis.llen(listKeyStr).intValue());
Set<String> actualElements = getListElements(listKeyStr);
Assert.assertTrue("The added value is not as expected.",
actualElements.containsAll(expectedElements));
}
@Test
public void removeElementByIndexTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_remove_element_by_index");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final Set<String> expectedElements = new HashSet<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
jedis.del(listKeyStr);
// 尝试删除不存在的 Key
Assert.assertFalse("The attempt to delete a value that does not exist succeeded.",
listCacheStore.removeElement(listKey, 0));
Assert.assertNotEquals("The expected create operation failed.",
RedisUtils.RETURN_CODE_FAILED, jedis.lpush(listKeyStr, expectedElementsArr).intValue());
Assert.assertFalse("The attempt to delete the value of the illegal index succeeded.",
listCacheStore.removeElement(listKey, jedis.llen(listKeyStr).intValue()));
Random random = new Random();
final int deletedIndex = random.nextInt(expectedElements.size());
String deletedElement = jedis.lindex(listKeyStr, deletedIndex);
Assert.assertTrue("The operation to be tested failed.",
listCacheStore.removeElement(listKey, deletedIndex));
expectedElements.remove(deletedElement);
Assert.assertEquals("The quantity is not as expected.",
expectedElements.size(), jedis.llen(listKeyStr).intValue());
Set<String> actualElements = getListElements(listKeyStr);
Assert.assertTrue("The added value is not as expected.",
actualElements.containsAll(expectedElements));
}
@Test
public void containsElementTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_contains_element");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final Set<String> expectedElements = new HashSet<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
jedis.lpush(listKeyStr, expectedElementsArr);
Set<String> actualElements = getListElements(listKeyStr);
expectedElements.add("f");
expectedElements.add("g");
expectedElements.add("h");
expectedElements.add("i");
for (String expectedElement : expectedElements) {
Assert.assertEquals(String.format("Make a difference: '%s'.", expectedElement),
actualElements.contains(expectedElement),
listCacheStore.containsElement(listKey, expectedElement));
}
}
@Test
public void isEmptyTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_is_empty");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
Assert.assertEquals("Key does not exist but returns 'true'",
!jedis.exists(listKeyStr), listCacheStore.isEmpty(listKey));
jedis.lpush(listKeyStr, "test");
Assert.assertEquals("Key does exist but returns 'false'.",
jedis.exists(listKeyStr), !listCacheStore.isEmpty(listKey));
}
@Test
public void elementsLengthTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_elements_length");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final Set<String> expectedElements = new HashSet<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
long beforeLength = jedis.llen(listKeyStr);
if (jedis.llen(listKeyStr) == 0) {
Assert.assertEquals("The list was empty but returned a value other than '-1' or '0'.",
-1, listCacheStore.elementsLength(listKey));
} else {
Assert.assertEquals("The length of the list is incorrect.",
beforeLength, listCacheStore.elementsLength(listKey));
}
jedis.del(listKeyStr);
jedis.lpush(listKeyStr, expectedElementsArr);
Assert.assertEquals("The length of the list is incorrect.",
jedis.llen(listKeyStr).intValue(), listCacheStore.elementsLength(listKey));
}
@Test
public void clearCollectionTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_clear_collection");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final Set<String> expectedElements = new HashSet<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
Assert.assertEquals("The list does not exist but returns 'true'.",
jedis.exists(listKeyStr), listCacheStore.clearCollection(listKey));
jedis.lpush(listKeyStr, expectedElementsArr);
Assert.assertTrue("The operation to be tested failed.",
listCacheStore.clearCollection(listKey));
Assert.assertEquals("The list is not empty after the clear operation",
0, jedis.llen(listKeyStr).intValue());
Assert.assertFalse("The list is still exist", jedis.exists(listKeyStr));
}
@Test
public void getElementTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_get_element");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final List<String> expectedElements = new ArrayList<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
jedis.lpush(listKeyStr, expectedElementsArr);
Collections.reverse(expectedElements);
for (int i = 0; i < expectedElements.size(); i++) {
Assert.assertEquals("Wrong value, index: " + i, expectedElements.get(i),
listCacheStore.getElement(listKey, i));
}
}
@Test
public void getElementsByRangeTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_get_elements_by_range");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final List<String> expectedElements = new ArrayList<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
expectedElements.add("f");
expectedElements.add("g");
expectedElements.add("h");
expectedElements.add("i");
expectedElements.add("j");
expectedElements.add("k");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
jedis.lpush(listKeyStr, expectedElementsArr);
Collections.reverse(expectedElements);
final int start = 2;
final int length = 4;
List<String> actualElements = listCacheStore.getElementsByRange(listKey, start, length);
for (int i = 0; i < length; i++) {
Assert.assertEquals("Wrong value, index: " + i,
expectedElements.get(start + i), actualElements.get(i));
}
}
}
@@ -1,414 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import com.google.common.base.Throwables;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import redis.clients.jedis.Jedis;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static net.lamgc.cgj.bot.cache.redis.util.RedisTestUtils.assertDeleteIfExist;
import static net.lamgc.cgj.bot.cache.redis.util.RedisTestUtils.randomString;
/**
* @see RedisMapCacheStore
*/
public class RedisMapCacheStoreTest {
private final static TemporaryFolder tempFolder = TemporaryFolder.builder().build();
private final static String KEY_PREFIX = "test:map";
private final static StringConverter<String> CONVERTER = new StringToStringConverter();
private final static RedisCacheStoreFactory factory = new RedisCacheStoreFactory();
private static MapCacheStore<String> cacheStore;
private static Jedis jedis;
@BeforeClass
public static void beforeAll() {
jedis = new Jedis();
try {
tempFolder.create();
} catch (IOException e) {
Assert.fail(Throwables.getStackTraceAsString(e));
return;
}
try {
factory.initial(tempFolder.newFolder("Redis"));
cacheStore =
factory.newMapCacheStore(KEY_PREFIX, CONVERTER);
} catch (IOException e) {
Assert.fail(Throwables.getStackTraceAsString(e));
}
}
@AfterClass
public static void afterAll() {
if (jedis != null) {
jedis.close();
}
}
@Before
public void before() {
Assert.assertTrue("Clear execution failed before the test started.", cacheStore.clear());
}
@After
public void after() {
Assert.assertTrue("After the test, clear execution failed", cacheStore.clear());
}
@Test(expected = IllegalArgumentException.class)
public void emptyPrefixTest() {
factory.newMapCacheStore("", CONVERTER);
}
@Test(expected = NullPointerException.class)
public void nullPrefixTest() {
factory.newMapCacheStore(null, CONVERTER);
}
@Test
public void prefixCheck() throws NoSuchFieldException, IllegalAccessException {
final Field prefixField = RedisMapCacheStore.class.getDeclaredField("keyPrefix");
prefixField.setAccessible(true);
String prefix = (String) prefixField.get(factory.newMapCacheStore(KEY_PREFIX, CONVERTER));
Assert.assertTrue("The prefix does not contain a separator at the end.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefix = (String) prefixField.get(factory.newMapCacheStore(
KEY_PREFIX + RedisUtils.KEY_SEPARATOR, CONVERTER));
Assert.assertTrue("The separator at the end of the prefix is missing.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefixField.setAccessible(false);
}
@Test
public void nullThrowTest() {
final CacheKey key = new CacheKey("test_null_throw");
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapSize(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapFieldSet(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapValueSet(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(null, "field", "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, null, "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, "field", null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(null, new HashMap<>()));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(null, "field", "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, null, "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, "field", null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(null, "field"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(null, "field"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapIsEmpty(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.clearMap(null));
}
@Test
public void putTest() {
final CacheKey key = new CacheKey("test_put");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field01";
final String value = "value01";
if (jedis.hexists(keyString, field)) {
Assert.assertEquals("The field used by the test is occupied and deletion failed.",
1, jedis.hdel(keyString, field).intValue());
}
Assert.assertTrue("The operation to be tested failed.", cacheStore.put(key, field, value));
Assert.assertTrue("The field does not exist after put.", jedis.hexists(keyString, field));
Assert.assertEquals("The value of the field changes after put.", value, jedis.hget(keyString, field));
}
@Test
public void getTest() {
final CacheKey key = new CacheKey("test_get");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field01";
final String value = "value01";
assertDeleteIfExist(jedis, keyString);
Assert.assertNull("Method returned a non null value for a field that does not exist.",
cacheStore.get(key, field));
Assert.assertTrue("Failed to set the field used by the test.",
jedis.hset(keyString, field, value).intValue() >= 0);
Assert.assertTrue("The field used by the test does not exist.",
jedis.hexists(keyString, field));
Assert.assertEquals("The obtained field value does not match the expected value.",
value, cacheStore.get(key, field));
}
@Test
public void putAllTest() {
final CacheKey key = new CacheKey("test_put_all");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertEquals("The key does not exist, but the empty collection was added successfully.",
jedis.exists(keyString),
cacheStore.putAll(key, Collections.emptyMap()));
Assert.assertTrue("The operation to be tested failed.", cacheStore.putAll(key, expectMap));
Map<String, String> actualMap = jedis.hgetAll(keyString);
for (String actualKey : actualMap.keySet()) {
Assert.assertTrue("Field does not exist in the expected.", expectMap.containsKey(actualKey));
Assert.assertEquals("The value of field " + actualKey + " does not match the expected value.",
expectMap.get(actualKey), actualMap.get(actualKey));
}
Assert.assertEquals("Key does not exist, but adding empty collection failed.",
jedis.exists(keyString),
cacheStore.putAll(key, Collections.emptyMap()));
}
@Test
public void putIfNotExistTest() {
final CacheKey key = new CacheKey("test_put_if_not_exist");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field01";
final String value = "value01";
if (jedis.hexists(keyString, field)) {
Assert.assertEquals("The field used by the test is occupied and deletion failed.",
1, jedis.hdel(keyString, field).intValue());
}
Assert.assertTrue("Field does not exist but put failed.", cacheStore.putIfNotExist(key, field, value));
Assert.assertFalse("Field does exist but put successful.", cacheStore.putIfNotExist(key, field, value));
}
@Test
public void mapSizeTest() {
final CacheKey key = new CacheKey("test_map_size");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertEquals("Non 0 returned when map does not exist.", 0, cacheStore.mapSize(key));
Assert.assertEquals("The number of test fields prepared is inconsistent.",
expectMap.size(), jedis.hset(keyString, expectMap).intValue());
Assert.assertEquals("The number of fields obtained does not match the actual number.",
expectMap.size(), cacheStore.mapSize(key));
}
@Test
public void mapIsEmptyTest() {
final CacheKey key = new CacheKey("test_put_if_not_exist");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field01";
final String value = "value01";
if (jedis.hlen(keyString) != 0) {
Assert.assertFalse("Map exists but returns 'true'.", cacheStore.mapIsEmpty(key));
Assert.assertEquals("Failed to delete map.", 1, jedis.del(keyString).intValue());
Assert.assertTrue("Map not exists but returns 'false'.", cacheStore.mapIsEmpty(key));
} else {
Assert.assertTrue("Map not exists but returns 'false'.", cacheStore.mapIsEmpty(key));
Assert.assertEquals("Failed to set field value for test.",
1, jedis.hset(keyString, field, value).intValue());
Assert.assertFalse("Map exists but returns 'true'.", cacheStore.mapIsEmpty(key));
}
}
@Test
public void mapFieldSetTest() {
final CacheKey key = new CacheKey("test_map_field_set");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertNull("The mapFieldSet() method returned a non null value for a nonexistent map." +
"(If the map does not exist, null should be returned instead of an empty set)",
cacheStore.mapFieldSet(key));
Assert.assertEquals("The number of test fields prepared is inconsistent.",
expectMap.size(), jedis.hset(keyString, expectMap).intValue());
Set<String> fieldSet = cacheStore.mapFieldSet(key);
Assert.assertNotNull("Method returns 'null' for the existing map.", fieldSet);
Assert.assertTrue("The actual set is different from the expectation.",
fieldSet.containsAll(expectMap.keySet()));
Assert.assertTrue("The actual set is different from the expectation.",
expectMap.keySet().containsAll(fieldSet));
}
@Test
public void mapValueSetTest() {
final CacheKey key = new CacheKey("test_map_value_set");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertNull("The mapValueSet() method returned a non null value for a nonexistent map." +
"(If the map does not exist, null should be returned instead of an empty set)",
cacheStore.mapValueSet(key));
Assert.assertEquals("The number of test fields prepared is inconsistent.",
expectMap.size(), jedis.hset(keyString, expectMap).intValue());
Set<String> valueSet = cacheStore.mapValueSet(key);
Assert.assertNotNull("Method returns 'null' for the existing map.", valueSet);
Assert.assertTrue("The actual set is different from the expectation.",
valueSet.containsAll(expectMap.values()));
Assert.assertTrue("The actual set is different from the expectation.",
expectMap.values().containsAll(valueSet));
}
@Test
public void removeFieldTest() {
final CacheKey key = new CacheKey("test_remove_field");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field";
final String value = "value";
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("Returns 'true' when trying to delete a field in a nonexistent map.",
cacheStore.removeField(key, "field"));
Assert.assertEquals("Failed to add field for test.",
1, jedis.hset(keyString, field, value).intValue());
Assert.assertFalse("Returns 'true' when trying to delete a field that does not exist in the map.",
cacheStore.removeField(key, randomString(10)));
assertDeleteIfExist(jedis, keyString);
Assert.assertEquals("Failed to add field for test.",
expectMap.size(), jedis.hset(keyString, expectMap).intValue());
for (String expectField : expectMap.keySet()) {
Assert.assertTrue("The attempt to delete an existing field failed: '" + expectField + "'.",
cacheStore.removeField(key, expectField));
}
}
@Test
public void containsFieldTest() {
final CacheKey key = new CacheKey("test_contains_field");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field";
final String value = "value";
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("Returns 'true' when trying to check for a map that does not exist.",
cacheStore.containsField(key, "field"));
Assert.assertEquals("Failed to add field for test.",
1, jedis.hset(keyString, field, value).intValue());
Assert.assertFalse("Returns 'true' when trying to check for a field that does not exist.",
cacheStore.removeField(key, randomString(10)));
Assert.assertTrue("An attempt to check for existing fields returned 'false'.",
cacheStore.containsField(key, field));
}
@Test
public void clearMapTest() {
final CacheKey key = new CacheKey("test_remove_field");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("Attempt to empty nonexistent map succeeded.", cacheStore.clearMap(key));
Assert.assertEquals("Failed to add field for test.",
expectMap.size(), jedis.hset(keyString, expectMap).intValue());
Assert.assertTrue("The operation to be tested failed.", cacheStore.clearMap(key));
Assert.assertEquals("After the clear operation, the map still contains fields.",
0, jedis.hlen(keyString).intValue());
}
}
@@ -1,142 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import org.junit.*;
import redis.clients.jedis.Jedis;
import java.lang.reflect.Field;
import static net.lamgc.cgj.bot.cache.redis.util.RedisTestUtils.*;
/**
* @see RedisCacheStore
* @see RedisSingleCacheStore
*/
public class RedisSingleCacheStoreTest {
private final static String KEY_PREFIX = "test:single";
private final static StringConverter<String> CONVERTER = new StringToStringConverter();
private static Jedis jedis;
private final RedisConnectionPool connectionPool = new RedisConnectionPool();
private final SingleCacheStore<String> cacheStore =
new RedisSingleCacheStore<>(connectionPool, KEY_PREFIX, CONVERTER);
@BeforeClass
public static void beforeAllTest() {
jedis = new Jedis();
}
@AfterClass
public static void afterAllTest() {
if (jedis != null && jedis.isConnected()) {
jedis.close();
}
}
@Before
public void before() {
Assert.assertTrue("Clear execution failed before the test started.", cacheStore.clear());
}
@After
public void after() {
Assert.assertTrue("After the test, clear execution failed", cacheStore.clear());
}
@Test(expected = IllegalArgumentException.class)
public void emptyPrefixTest() {
new RedisSingleCacheStore<>(connectionPool, "", CONVERTER);
}
@Test(expected = NullPointerException.class)
public void nullPrefixTest() {
new RedisSingleCacheStore<>(connectionPool, null, CONVERTER);
}
@Test
public void prefixCheck() throws NoSuchFieldException, IllegalAccessException {
final Field prefixField = RedisSingleCacheStore.class.getDeclaredField("keyPrefix");
prefixField.setAccessible(true);
String prefix = (String) prefixField.get(new RedisSingleCacheStore<>(connectionPool, KEY_PREFIX, CONVERTER));
Assert.assertTrue("The prefix does not contain a separator at the end.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefix = (String) prefixField.get(new RedisSingleCacheStore<>(connectionPool,
KEY_PREFIX + RedisUtils.KEY_SEPARATOR, CONVERTER));
Assert.assertTrue("The separator at the end of the prefix is missing.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefixField.setAccessible(false);
}
@Test
public void setTest() {
final CacheKey key = new CacheKey("test_set");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
String value = randomString(10);
assertDeleteIfExist(jedis, keyString);
Assert.assertTrue("Failed to set value on specified key.", cacheStore.set(key, value));
Assert.assertEquals("The value set does not match the expected value.",
value, jedis.get(keyString));
value = randomString(12);
Assert.assertTrue("Cannot rewritten the value of an existing key.", cacheStore.set(key, value));
Assert.assertEquals("The rewritten value does not match the expected value.",
value, jedis.get(keyString));
}
@Test
public void setIfNotExistTest() {
final CacheKey key = new CacheKey("test_set_if_not_exist");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
String value = randomString(10);
assertDeleteIfExist(jedis, keyString);
Assert.assertTrue("Failed to set value on specified key.", cacheStore.setIfNotExist(key, value));
Assert.assertEquals("The value set does not match the expected value.",
value, jedis.get(keyString));
value = randomString(12);
Assert.assertFalse("Write value to existing key succeeded.", cacheStore.setIfNotExist(key, value));
Assert.assertNotEquals("The key value is modified and the method setIfNotExist() returns 'false'.",
value, jedis.get(keyString));
}
@Test
public void getTest() {
final CacheKey key = new CacheKey("test_get");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
String value = randomString(10);
assertDeleteIfExist(jedis, keyString);
Assert.assertNull("The get() method returned a non null value for a nonexistent key.",
cacheStore.get(key));
Assert.assertTrue("Failed to set test key.", RedisUtils.isOk(jedis.set(keyString, value)));
Assert.assertEquals("The value obtained does not match the expected value.",
value, cacheStore.get(key));
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import org.junit.Assert;
import org.junit.Test;
/**
* @see RedisUtils
*/
public class RedisUtilsTest {
@Test
public void isOkTest() {
Assert.assertTrue(RedisUtils.isOk("OK"));
Assert.assertTrue(RedisUtils.isOk("ok"));
Assert.assertFalse(RedisUtils.isOk("Failed"));
}
@Test
public void toRedisCacheKey() {
final CacheKey key = new CacheKey("test");
final String prefix = "prefix";
Assert.assertEquals("prefix:test", RedisUtils.toRedisCacheKey(prefix, key));
Assert.assertEquals("prefix:test", RedisUtils.toRedisCacheKey(prefix + RedisUtils.KEY_SEPARATOR, key));
}
}
@@ -1,34 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
public class SimpleRedisCacheStore extends RedisCacheStore<String> {
private final String keyPrefix;
protected SimpleRedisCacheStore(RedisConnectionPool connectionPool, String keyPrefix) {
super(connectionPool);
this.keyPrefix = keyPrefix;
}
@Override
protected String getKeyPrefix() {
return keyPrefix;
}
}
@@ -1,95 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.lamgc.cgj.bot.cache.redis.RedisConnectionProperties;
import net.lamgc.cgj.bot.cache.redis.RedisUtils;
import org.junit.Assert;
import redis.clients.jedis.Jedis;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.Random;
/**
*
* @author LamGC
*/
public final class RedisTestUtils {
private RedisTestUtils() {}
private final static Gson GSON_INSTANCE = new GsonBuilder()
.serializeNulls()
.setPrettyPrinting()
.create();
/**
* 创建测试用配置文件.
* @param testDirectory 组件文件夹.
* @param properties 配置对象.
* @throws IOException 当配置文件写入失败时抛出.
*/
public static void createConnectionProperties(File testDirectory, RedisConnectionProperties properties)
throws IOException {
File propertiesFile = new File(testDirectory, "Redis/" + RedisUtils.PROPERTIES_FILE_NAME);
Files.write(propertiesFile.toPath(),
GSON_INSTANCE.toJson(properties).getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE);
}
/**
* 如果存在键, 则删除, 如果失败则判定断言失败.
* @param jedis Jedis 对象.
* @param keyString 待删除的键名.
*/
public static void assertDeleteIfExist(Jedis jedis, String keyString) {
if (jedis.exists(keyString)) {
Assert.assertEquals("The key used by the test is occupied and deletion failed.",
1, jedis.del(keyString).intValue());
}
}
/**
* 随机字符串.
* @param length 长度.
* @return 返回指定长度的随机字符串.
*/
public static String randomString(int length) {
final char[] chars = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
};
StringBuilder buffer = new StringBuilder(length);
Random random = new Random();
for (int i = 0; i < length; i++) {
buffer.append(chars[random.nextInt(chars.length)]);
}
return buffer.toString();
}
}
-40
View File
@@ -1,40 +0,0 @@
# Event 模块 #
Event 模块用于定义 ContentGrabbingJi 所使用事件系统的规范。
通过定义事件系统的规范接口,可增加项目模块配置的灵活性。
虽说如此,但 Event 模块并未计划允许用户任意更换,仅允许于开发阶段更换。
## 组件 ##
### EventExecutor 事件执行器
事件执行器用于接收任意事件,并将事件交给可处理该事件的对象方法。
根据实现不同,事件执行器可能是同步的,也可能是异步的,需检查 `isAsync` 方法返回值以了解相关信息。
### EventObject 事件对象
当某一对象实现 `EventObject` 时,对象即为事件对象,
事件对象可交由事件执行器分发到处理方法中。
### HandlerRegistry 事件处理注册器
用于注册并存储所有 EventHandler 内符合条件的处理方法。
在 EventExecutor 需要时将会请求可处理事件的所有处理方法。
### EventHandler 事件处理器
当某一对象实现 `EventHandler` 时,则表明该对象包含用于处理指定事件的处理方法。
HandlerRegistry 将会扫描内部方法,并将符合要求的方法注册为处理方法。
### EventObject 事件对象
事件对象接口,实现了该接口的对象将视为一种事件对象。
事件对象可通过 EventExecutor 投递到任何可处理该事件的处理方法进行处理。
### AbstractEventObject 抽象事件对象
实现了 EventObject 中 `getEventId` 方法的抽象对象。
### Cancelable 可取消事件
当事件实现了该接口时,代表事件可以被取消。
当事件被取消后,EventExecutor 将会终止处理该事件,同时事件将通知所有已注册的 Observer。
### SupportedCancel 支持取消接口
当 EventExecutor 实现该接口时,表明 EventExecutor 拥有在事件取消时终止事件的能力,
需要注意的是,即使 EventExecutor 未实现该接口,也不一定代表 EventExecutor 不支持终止事件的处理。
## 相关实现 ##
目前已有默认的实现存在于 Core 模块中,目前暂不需要开发其他实现。
-32
View File
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-Event-api</artifactId>
</project>
@@ -1,36 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.util.UUID;
/**
* 抽象事件对象.
* <p> 已完成对 {@link EventObject#getEventId()} 的实现.
* @author LamGC
*/
public abstract class AbstractEventObject implements EventObject {
private final UUID eventId = UUID.randomUUID();
@Override
public UUID getEventId() {
return eventId;
}
}
@@ -1,66 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.util.Observer;
/**
* 可取消接口.
* <p> 实现了该接口的事件可对其处理进行取消.
* <p> 注意: 取消操作是不可逆的!
* @author LamGC
*/
public interface Cancelable {
/**
* 取消该事件.
* <p> 该操作不可逆.
* <p> 调用本方法后, 将会触发所有已通过 {@link #registerCancelObserver(Observer)} 注册的 {@link Observer} 事件取消的状态变更.
* @see #registerCancelObserver(Observer)
*/
void cancel();
/**
* 检查事件是否已被取消.
* <p> 注意: 本方法不同于 {@link Thread#interrupted()} 方法, 即使调用本方法, 也不会清除取消状态(取消操作是不可逆的).
* @return 如果事件已被取消, 返回 true.
*/
boolean canceled();
/**
* 注册事件取消监听器.
* <p> 注意: 如果发生取消事件, 无论 {@link java.util.Observable} 是否为事件对象本身, 参数 {@code arg} 都必须传递事件对象自身!
* @param cancelObserver 观察者对象.
* @throws UnsupportedOperationException 当该可取消对象不支持 {@link java.util.Observable} 时抛出,
* 既然不支持观察取消事件, 那么 {@link #observableCancel()} 应当返回 {@code false}, 否则该方法不允许抛出该异常.
*/
default void registerCancelObserver(Observer cancelObserver) throws UnsupportedOperationException {
throw new UnsupportedOperationException("This operation is not supported by this event.");
}
/**
* 是否可观察取消事件.
* <p> 如果本方法返回 {@code true},
* 那么 {@link #registerCancelObserver(Observer)} 不允许抛出 {@link UnsupportedOperationException} 异常.
* @return 如果可以, 返回 true.
*/
default boolean observableCancel() {
return false;
}
}
@@ -1,38 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
/**
* 事件执行器.
* @author LamGC
*/
public interface EventExecutor {
/**
* 执行事件.
* @param event 事件对象.
*/
void execute(EventObject event);
/**
* 是否为异步事件执行器.
* @return 如果为异步事件执行器, 返回 {@code true}, 如果为同步事件执行器, 返回 {@code false}.
*/
boolean isAsync();
}
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 事件处理器注解.
* <p> 标记了该注解的方法, 如符合处理方法条件, 则会被 {@link HandlerRegistry} 注册为事件处理方法.
* @author LamGC
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventHandler {
/**
* 是否接受事件对象的子类.
* <p> 当该选项为 {@code true} 时, 子类事件也会传递到该方法进行处理.
* 例如事件 Parent, 和它的子类 Child, 处理方法: {@code handler(Parent event)};
* 如果该选项为 {@code true}, 那么 Child 也会传递到该方法进行处理, 反之, 如果该选项为 {@code false},
* 那么该方法只会接收 Parent 事件, 而不会接收它的子类 Child.
* <p> 默认值: {@code false}
* @return 返回该方法是否支持继承性.
*/
boolean inheritable() default false;
}
@@ -1,35 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.util.UUID;
/**
* 事件对象.
* @author LamGC
*/
public interface EventObject {
/**
* 获取事件 UUID.
* <p> 需保证 UUID 对应了唯一的事件对象, 即使内容重复.
* @return 返回事件Id.
*/
UUID getEventId();
}
@@ -1,62 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 事件处理工具类.
* @author LamGC
*/
public final class EventUtils {
private EventUtils() {}
/**
* 检查 {@link EventExecutor} 是否支持取消事件.
* @param executor 事件执行器.
* @return 如果支持, 返回 {@code true}
*/
public static boolean isSupportedCancel(EventExecutor executor) {
return executor instanceof SupportedCancel;
}
/**
* 检查方法是否符合事件处理方法条件.
* @param method 待检查的方法.
* @return 如果符合, 返回 true.
*/
public static boolean checkEventHandlerMethod(Method method) {
int modifiers = method.getModifiers();
// 新版事件系统将不再允许静态方法作为事件处理方法, 以降低管理难度.
if (!Modifier.isPublic(modifiers) || Modifier.isAbstract(modifiers) || Modifier.isStatic(modifiers)) {
return false;
}
if (!method.isAnnotationPresent(EventHandler.class) || method.getParameterCount() != 1) {
return false;
}
Class<?> param = method.getParameterTypes()[0];
if (!EventObject.class.isAssignableFrom(param)) {
return false;
}
return method.getReturnType().equals(Void.TYPE);
}
}
@@ -1,44 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 事件处理注册器.
* <p> 实现了该接口的类将允许注册事件处理方法.
* @author LamGC
*/
public interface HandlerRegistry {
/**
* 注册对象中的事件处理方法.
* @param handlerObject 包含事件处理方法的对象.\
* @return 返回已成功添加的方法数.
*/
int registerHandler(Object handlerObject);
/**
* 获取能处理指定事件的所有方法.
* @param event 待匹配的事件对象.
* @return 返回一个集合, 集合存储了可处理该事件的所有方法和它所属类的对象.
*/
Map<Method, Object> getMatchedHandlerMethod(EventObject event);
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.util.NoSuchElementException;
import java.util.UUID;
/**
* 支持取消接口.
* <p> 实现了该接口的 {@link EventExecutor} 可对已投递执行的事件进行取消, 以阻止事件被执行.
* @author LamGC
*/
public interface SupportedCancel {
/**
* 通过 EventId 取消事件的处理.
* @param eventId 事件Id.
* @return 如果成功, 返回 true, 如果事件已执行完成, 返回 false.
* @throws UnsupportedOperationException 当事件未实现 {@link Cancelable} 接口时抛出.
* @throws NoSuchElementException 当 EventId 所属事件在 {@link SupportedCancel} 中无法找到时抛出.
*/
boolean cancelEvent(UUID eventId) throws UnsupportedOperationException, NoSuchElementException;
/**
* 通过 Event 对象取消事件的处理.
* @param event 事件对象.
* @return 如果成功, 返回 true, 如果事件已执行完成, 返回 false.
* @throws UnsupportedOperationException 当事件未实现 {@link Cancelable} 接口时抛出.
* @throws NoSuchElementException 当事件在 {@link SupportedCancel} 中无法找到时抛出.
*/
boolean cancelEvent(EventObject event) throws UnsupportedOperationException, NoSuchElementException;
/**
* 对可取消对象执行取消处理操作.
* @param cancelableEvent 可取消对象事件.
* @return 如果成功, 返回 true, 如果事件已执行完成, 返回 false.
* @throws NoSuchElementException 当事件在 {@link SupportedCancel} 中无法找到时抛出.
* @throws IllegalArgumentException 当 Cancelable 对象未实现 {@link EventObject} (即不是一个事件对象) 时抛出.
*/
boolean cancelEvent(Cancelable cancelableEvent) throws NoSuchElementException, IllegalArgumentException;
}
@@ -1,93 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import net.lamgc.cgj.bot.event.handler.IllegalHandler;
import net.lamgc.cgj.bot.event.handler.StandardHandler;
import net.lamgc.cgj.bot.event.object.TestEvent;
import org.junit.Assert;
import org.junit.Test;
import java.lang.reflect.Method;
import java.util.NoSuchElementException;
import java.util.UUID;
/**
* @see EventUtils
*/
public class EventUtilsTest {
@Test
public void supportedCancelCheckTest() {
class NonSupportedCancelClass implements EventExecutor {
@Override
public void execute(EventObject event) {
}
@Override
public boolean isAsync() {
return false;
}
}
class SupportedCancelClass implements SupportedCancel, EventExecutor {
@Override
public boolean cancelEvent(UUID eventId) throws UnsupportedOperationException, NoSuchElementException {
return false;
}
@Override
public boolean cancelEvent(EventObject event) throws UnsupportedOperationException, NoSuchElementException {
return false;
}
@Override
public boolean cancelEvent(Cancelable cancelableEvent) throws NoSuchElementException {
return false;
}
@Override
public void execute(EventObject event) {
}
@Override
public boolean isAsync() {
return false;
}
}
Assert.assertTrue(EventUtils.isSupportedCancel(new SupportedCancelClass()));
Assert.assertFalse(EventUtils.isSupportedCancel(new NonSupportedCancelClass()));
}
@Test
public void standardHandlerMethodCheckTest() throws NoSuchMethodException {
Method targetMethod = StandardHandler.class.getMethod("standardHandle", TestEvent.class);
Assert.assertTrue(EventUtils.checkEventHandlerMethod(targetMethod));
}
@Test
public void invalidHandlerMethodCheckTest() {
for (Method method : IllegalHandler.class.getDeclaredMethods()) {
Assert.assertFalse(EventUtils.checkEventHandlerMethod(method));
}
}
}
@@ -1,62 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event.handler;
import net.lamgc.cgj.bot.event.EventHandler;
import net.lamgc.cgj.bot.event.object.TestEvent;
public abstract class IllegalHandler {
@EventHandler
public void nonArgumentHandleMethod() {
}
@EventHandler
public abstract void abstractHandleMethod(TestEvent event);
@EventHandler
public static void staticHandleMethod(TestEvent event) {
}
@EventHandler
private void privateHandleMethod(TestEvent event) {
}
public void nonAnnotationHandle() {
}
@EventHandler
public void multiArgumentsHandleMethod(TestEvent event, Object object) {
}
@EventHandler
public void invalidArgumentMethod(Object object) {
}
@EventHandler
public Object invalidReturnTypeMethod(TestEvent event) {
return null;
}
}
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event.handler;
import net.lamgc.cgj.bot.event.EventHandler;
import net.lamgc.cgj.bot.event.object.TestEvent;
public class StandardHandler {
@EventHandler
public void standardHandle(TestEvent event) {
}
}
@@ -1,33 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event.object;
import net.lamgc.cgj.bot.event.AbstractEventObject;
public class TestEvent extends AbstractEventObject {
private final String content;
public TestEvent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
-32
View File
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-Function-api</artifactId>
</project>
-45
View File
@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-Function-pixiv</artifactId>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-Function-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-pixiv</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-Template-Velocity</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-Template-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
</project>
-46
View File
@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-Template-api</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-framework-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
-39
View File
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-common</artifactId>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-CacheStore-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
@@ -1,362 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import com.google.common.base.Throwables;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
/**
* CacheStore 构造器.
*
* <p>这只是个门面类, 最终调用 {@link CacheStoreFactory} 的具体实现类获取 {@link CacheStore} 对象.
* <p> CacheStoreBuilder 确保了返回不为 null,
* 即使所有 Factory 无法返回合适的 CacheStore 实例, 也只会抛出 {@link NoSuchFactoryException} 异常.
* @see CacheStoreFactory
* @author LamGC
*/
public final class CacheStoreBuilder {
private final static Logger log = LoggerFactory.getLogger(CacheStoreBuilder.class);
private volatile List<CacheStoreFactory> factoryList;
private volatile Map<CacheStoreFactory, FactoryInfo> factoryInfoMap;
private final ServiceLoader<CacheStoreFactory> factoryLoader = ServiceLoader.load(CacheStoreFactory.class);
private final File dataDirectory;
private final Object getFactoryLock = new Object();
/**
* 获取 CacheStoreBuilder 实例.
* <p> 该方法仅提供给 ContentGrabbingJiBot 调用, 请勿通过该方法私自创建 CacheStoreBuilder.
* @param cacheDataDirectory 缓存组件数据目录的根目录(例如提供 File 为 {@code /data/cache},
* 缓存组件名为 {@code example}, 则缓存组件的缓存路径为 {@code /data/cache/example}).
* @return 返回新的 CacheStoreBuilder 实例, 各实例之间是没有任何关系的(包括创建的 CacheStore, 除非缓存组件设计错误).
* @throws IOException 当路径检查失败时抛出.
*/
public static CacheStoreBuilder getInstance(File cacheDataDirectory) throws IOException {
Objects.requireNonNull(cacheDataDirectory, "Cache component data directory is null");
if (!cacheDataDirectory.exists() && !cacheDataDirectory.mkdirs()) {
throw new IOException("Data directory creation failed: " + cacheDataDirectory.getAbsolutePath());
} else if (!cacheDataDirectory.isDirectory()) {
throw new IOException("The specified data store path is not a directory: " +
cacheDataDirectory.getAbsolutePath());
} else if (!cacheDataDirectory.canRead() || !cacheDataDirectory.canWrite()) {
throw new IOException("The specified data store directory cannot be read or written.");
}
return new CacheStoreBuilder(cacheDataDirectory);
}
private CacheStoreBuilder(File dataDirectory) {
this.dataDirectory = dataDirectory;
loadFactory();
}
/**
* 使用 SPI 机制加载所有缓存组件.
*
* <p>第一次执行时加载, 由 {@link #getFactory(CacheStoreSource, Function)} 调用.
* <p>由于 ServiceLoader 线程不安全, 所以通过 synchronized 保证其安全性.
* 不通过 块进行初始化的原因是因为担心发生异常导致无法继续执行
* (除非必要, 否则不要使用 执行可能会发生异常的代码.).
*/
private synchronized void loadFactory() {
factoryLoader.reload();
List<CacheStoreFactory> newFactoryList = new ArrayList<>();
Map<CacheStoreFactory, FactoryInfo> newFactoryInfoMap = new HashMap<>(8);
try {
for (CacheStoreFactory factory : factoryLoader) {
FactoryInfo info;
try {
info = new FactoryInfo(factory.getClass());
if (newFactoryInfoMap.containsValue(info)) {
log.warn("发现 Name 重复的 Factory, 已跳过. (被拒绝的实现: {})", factory.getClass().getName());
continue;
}
newFactoryInfoMap.put(factory, info);
} catch (IllegalArgumentException e) {
log.warn("Factory {} 加载失败: {}", factory.getClass().getName(), e.getMessage());
continue;
}
if (!initialFactory(factory, info)) {
log.warn("Factory {} 初始化失败.", info.getFactoryName());
continue;
}
newFactoryList.add(factory);
log.info("Factory {} 已加载(优先级: {}, 实现类: {}).",
info.getFactoryName(),
info.getFactoryPriority(),
factory.getClass().getName());
}
newFactoryList.sort(new PriorityComparator(newFactoryInfoMap));
synchronized (getFactoryLock) {
this.factoryList = newFactoryList;
this.factoryInfoMap = newFactoryInfoMap;
}
} catch (Error error) {
// 防止发生 Error 又不输出到日志导致玄学问题难以排查.
log.error("加载 CacheStoreFactory 时发生严重错误.", error);
throw error;
}
}
/**
* 初始化 Factory.
* @param factory Factory 对象.
* @param info Factory Info.
* @return 初始化成功则返回 {@code true}, 否则返回 {@code false}.
*/
private boolean initialFactory(CacheStoreFactory factory, FactoryInfo info) {
File factoryDataDirectory = new File(dataDirectory, info.getFactoryName());
if (!factoryDataDirectory.exists() && !factoryDataDirectory.mkdirs()) {
log.warn("Factory {} 数据存储目录创建失败, 可能会影响后续操作. (Path: {})",
info.getFactoryName(),
factoryDataDirectory.getAbsolutePath());
}
try {
factory.initial(factoryDataDirectory);
return true;
} catch (Exception e) {
log.error("Factory {} 初始化失败.\n{}", info.getFactoryName(), Throwables.getStackTraceAsString(e));
return false;
}
}
/**
* 优先级排序器.
*/
private static final class PriorityComparator implements Comparator<CacheStoreFactory> {
private final Map<CacheStoreFactory, FactoryInfo> factoryInfoMap;
private PriorityComparator(Map<CacheStoreFactory, FactoryInfo> factoryInfoMap) {
this.factoryInfoMap = factoryInfoMap;
}
@Override
public int compare(CacheStoreFactory o1, CacheStoreFactory o2) {
FactoryInfo info1 = Objects.requireNonNull(factoryInfoMap.get(o1));
FactoryInfo info2 = Objects.requireNonNull(factoryInfoMap.get(o2));
return info2.getFactoryPriority() - info1.getFactoryPriority();
}
}
/**
* 获取一个当前可用的高优先级 Factory 对象.
* @return 返回可用的高优先级 Factory 对象.
*/
private <R extends CacheStore<?>> R getFactory(CacheStoreSource storeSource,
Function<CacheStoreFactory, R> function)
throws NoSuchFactoryException {
Map<CacheStoreFactory, FactoryInfo> localFactoryInfoMap;
Iterator<CacheStoreFactory> iterator;
synchronized (getFactoryLock) {
localFactoryInfoMap = this.factoryInfoMap;
iterator = factoryList.iterator();
}
while (iterator.hasNext()) {
CacheStoreFactory factory = iterator.next();
FactoryInfo info = localFactoryInfoMap.get(factory);
if (storeSource != null && info.getStoreSource() != storeSource) {
continue;
}
try {
if (factory.canGetCacheStore()) {
log.debug("CacheStoreFactory {} 可用(优先级: {}).", info.getFactoryName(), info.getFactoryPriority());
} else {
continue;
}
} catch (Exception e) {
log.error("CacheStoreFactory " + info.getFactoryName() +
" (" + factory.getClass().getName() + ") 检查可用性时发生异常", e);
continue;
}
try {
R result = function.apply(factory);
log.trace("已通过 Factory '{}' 获取 CacheStore '{}'. (Factory实现类: {}).",
info.getFactoryName(),
result.getClass().getName(),
factory.getClass().getName());
return result;
} catch (Exception e) {
if (!iterator.hasNext()) {
throw new NoSuchFactoryException(new GetCacheStoreException("CacheStoreFactory " +
info.getFactoryName() + " (" + factory.getClass().getName() +
") 创建 CacheStore 时发生异常.", e));
} else {
if (e instanceof GetCacheStoreException) {
log.warn("CacheStoreFactory '{} ({})' 无法提供相应 CacheStore. 原因: {}",
info.getFactoryName(), factory.getClass().getName(), e.getMessage());
} else {
log.warn("CacheStoreFactory '" + info.getFactoryName() +
" (" + factory.getClass().getName() + ")' 创建 CacheStore 时发生异常.", e);
}
}
}
}
throw new NoSuchFactoryException();
}
/**
* 检查是否为 {@code null}.
* @param cacheStore 缓存库.
* @param factory 工厂对象.
* @param <V> 缓存库类型.
* @return 如果不为 null, 则正常返回.
* @throws GetCacheStoreException 当 cacheStore 为 {@code null} 时抛出.
*/
private <V extends CacheStore<?>> V returnRequireNonNull(V cacheStore, CacheStoreFactory factory) {
if (cacheStore == null) {
throw new GetCacheStoreException("Factory '" + factory.getClass().getName() + "' returned null");
}
return cacheStore;
}
/**
* 获取单项缓存存储容器.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <V> 值类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
return newSingleCacheStore(null, identify, converter);
}
/**
* 获取单项缓存存储容器.
* @param storeSource 存储类型.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <V> 值类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <V> SingleCacheStore<V> newSingleCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<V> converter) {
try {
return getFactory(storeSource, factory ->
returnRequireNonNull(factory.newSingleCacheStore(identify, converter), factory));
} catch (NoSuchFactoryException e) {
throw new GetCacheStoreException("无可用的 Factory.", e);
}
}
/**
* 获取列表缓存存储容器.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <E> 元素类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) {
return newListCacheStore(null, identify, converter);
}
/**
* 获取列表缓存存储容器.
* @param storeSource 存储类型.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <E> 元素类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <E> ListCacheStore<E> newListCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<E> converter) {
try {
return getFactory(storeSource, factory ->
returnRequireNonNull(factory.newListCacheStore(identify, converter), factory));
} catch (NoSuchFactoryException e) {
throw new GetCacheStoreException("无可用的 Factory.", e);
}
}
/**
* 获取集合缓存存储容器.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <E> 元素类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) {
return newSetCacheStore(null, identify, converter);
}
/**
* 获取集合缓存存储容器.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <E> 元素类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <E> SetCacheStore<E> newSetCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<E> converter) {
try {
return getFactory(storeSource, factory ->
returnRequireNonNull(factory.newSetCacheStore(identify, converter), factory));
} catch (NoSuchFactoryException e) {
throw new GetCacheStoreException("无可用的 Factory.", e);
}
}
/**
* 获取映射表缓存存储容器.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <V> 值类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
return newMapCacheStore(null, identify, converter);
}
/**
* 获取映射表缓存存储容器.
* @param storeSource 存储类型.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <V> 值类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <V> MapCacheStore<V> newMapCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<V> converter) {
try {
return getFactory(storeSource, factory ->
returnRequireNonNull(factory.newMapCacheStore(identify, converter), factory));
} catch (NoSuchFactoryException e) {
throw new GetCacheStoreException("无可用的 Factory.", e);
}
}
}
@@ -1,92 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import com.google.common.base.Strings;
import java.util.Objects;
/**
* CacheStoreFactory 的标识信息.
* @author LamGC
*/
public final class FactoryInfo {
private final String factoryName;
private final int factoryPriority;
private final CacheStoreSource storeSource;
public FactoryInfo(Class<? extends CacheStoreFactory> factoryClass) {
Factory factoryAnnotation = factoryClass.getAnnotation(Factory.class);
if (factoryAnnotation == null) {
throw new IllegalArgumentException("Annotation not found");
} else if (Strings.isNullOrEmpty(factoryAnnotation.name())) {
throw new IllegalArgumentException("Factory name is empty");
}
this.factoryName = factoryAnnotation.name();
this.storeSource = factoryAnnotation.source();
int factoryPriority = factoryAnnotation.priority();
if (factoryPriority > FactoryPriority.PRIORITY_HIGHEST) {
this.factoryPriority = FactoryPriority.PRIORITY_HIGHEST;
} else {
this.factoryPriority = Math.max(FactoryPriority.PRIORITY_LOWEST, factoryPriority);
}
}
/**
* 获取 Factory 声明的名称.
* @return 返回 Factory 名称.
*/
public String getFactoryName() {
return factoryName;
}
/**
* 获取 Factory 优先级.
* @return 返回 Factory 的优先级.
*/
public int getFactoryPriority() {
return factoryPriority;
}
/**
* 获取存储容器实现的存储源类型.
* @return 返回 Factory 所属实现组件的存储源类型.
*/
public CacheStoreSource getStoreSource() {
return storeSource;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FactoryInfo that = (FactoryInfo) o;
return factoryName.equals(that.factoryName) && storeSource == that.storeSource;
}
@Override
public int hashCode() {
return Objects.hash(factoryName, storeSource);
}
}
@@ -1,35 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
/**
* 找不到 Factory 异常.
* <p>当尝试获取 {@link CacheStoreFactory} 失败时抛出.
* @see CacheStoreFactory
* @author LamGC
*/
public class NoSuchFactoryException extends Exception {
public NoSuchFactoryException() {
super("Unable to get available factory");
}
public NoSuchFactoryException(Throwable cause) {
super("Unable to get available factory", cause);
}
}
@@ -1,130 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.framework;
import java.util.Objects;
/**
* 框架所属平台.
* <p> 用于标识一个平台, 每个平台有唯一的 {@link #platformIdentify PlatformIdentify}.
* <p> {@link #platformName PlatformName} 是允许不同的,
* 只要 {@link #platformIdentify PlatformIdentify} 唯一即可.
*
* <p> {@link #platformIdentify PlatformIdentify}
* 遵循 Camel-Case (骆峰命名法, 每个单词之间无空格, 单词首字母大写),
* 对于平台标识由少量字母组成的则全大写(例如腾讯QQ的平台标识则为 'QQ' 而不是 'qq' 或 'Qq').
*
* <p> 希望各框架组件开发者能够遵循以上规则, 如出现平台识别信息混乱的情况, 不排除会有将 Platform 枚举化(固定 Platform 类型)的情况.
*
* @author LamGC
*/
public final class Platform {
private final String platformName;
private final String platformIdentify;
/**
* 构造一个 Platform 对象.
* @param platformName 平台名
* @param platformIdentify 平台唯一标识名.
*/
public Platform(String platformName, String platformIdentify) {
this.platformName = Objects.requireNonNull(platformName, "PlatformName is null");
this.platformIdentify = Objects.requireNonNull(platformIdentify, "PlatformIdentify is null");
}
@Override
public String toString() {
return "Platform{" +
"platformName='" + platformName + '\'' +
", platformIdentify='" + platformIdentify + '\'' +
'}';
}
/**
* 获取平台名称.
* @return 返回平台名称.
*/
public String getPlatformName() {
return platformName;
}
/**
* 获取平台唯一标识.
* 注意, 该标识将应用于平台所属事件的处理相关.
* @return 返回平台标识.
*/
public String getPlatformIdentify() {
return platformIdentify;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Platform platform = (Platform) o;
return platformIdentify.equals(platform.platformIdentify);
}
@Override
public int hashCode() {
return Objects.hash(platformIdentify);
}
/**
* 标准化平台信息.
* <p> 暂不公开, 仅用于 Platform 实例唯一化.
*/
private enum Standard {
/**
* 腾讯 QQ
*/
Tencent_QQ(new Platform("Tencent QQ", "QQ")),
/**
* 腾讯微信
*/
Tencent_WeChat(new Platform("Tencent WeChat", "WeChat")),
/**
* Telegram(电报)
*/
Telegram(new Platform("Telegram", "Telegram")),
/**.
* Discord
*/
Discord(new Platform("Discord", "Discord")),
/**
* OneBot
*/
OneBot(new Platform("OneBot Http API", "OneBot"))
;
private final Platform platform;
Standard(Platform platform) {
this.platform = platform;
}
public Platform getPlatform() {
return platform;
}
}
}
@@ -1,41 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.framework.message;
/**
* 消息来源
* @author LamGC
*/
public enum MessageSource {
/**
* 好友/联系人 私聊消息
*/
PRIVATE,
/**
* 临时私聊事件
*/
TEMP,
/**
* 群组消息
*/
GROUP,
/**
* 讨论组消息
*/
DISCUSS
}
@@ -1,358 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import net.lamgc.cgj.bot.cache.factory.*;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @see CacheStoreBuilder
*/
public class CacheStoreBuilderTest {
private final static TemporaryFolder tempDirectory = TemporaryFolder.builder().build();
private final static Logger log = LoggerFactory.getLogger(CacheStoreBuilderTest.class);
@BeforeClass
public static void beforeAction() throws IOException {
tempDirectory.create();
}
@Test
public void getCacheStoreTest() throws IOException {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
SingleCacheStore<String> singleCacheStore = cacheStoreBuilder.newSingleCacheStore(CacheStoreSource.REMOTE, identify, converter);
Assert.assertNotNull(singleCacheStore);
Assert.assertEquals(RemoteCacheFactory.RemoteSingleCacheFactory.class, singleCacheStore.getClass());
ListCacheStore<String> listCacheStore = cacheStoreBuilder.newListCacheStore(CacheStoreSource.MEMORY, identify, converter);
Assert.assertNotNull(listCacheStore);
Assert.assertEquals(MemoryFactory.MemoryListCacheStore.class, listCacheStore.getClass());
MapCacheStore<String> mapCacheStore = cacheStoreBuilder.newMapCacheStore(CacheStoreSource.LOCAL, identify, converter);
Assert.assertNotNull(mapCacheStore);
Assert.assertEquals(LocalFactory.LocalMapCacheStore.class, mapCacheStore.getClass());
SetCacheStore<String> setCacheStore = cacheStoreBuilder.newSetCacheStore(identify, converter);
Assert.assertNotNull(setCacheStore);
Assert.assertEquals(SetCacheStoreFactory.OnlySetCacheStore.class, setCacheStore.getClass());
}
@Test
public void loadFailureTest() throws IllegalAccessException, NoSuchFieldException, IOException {
CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
Field factoryListField;
factoryListField = CacheStoreBuilder.class.getDeclaredField("factoryList");
factoryListField.setAccessible(true);
Object o = factoryListField.get(cacheStoreBuilder);
Assert.assertTrue(o instanceof List);
@SuppressWarnings("unchecked")
List<CacheStoreFactory> factoryList = (List<CacheStoreFactory>) o;
Set<Class<? extends CacheStoreFactory>> classSet = new HashSet<>();
factoryList.forEach(factory -> classSet.add(factory.getClass()));
// 重名检查
if (classSet.contains(DuplicateNameFactoryA.class) && classSet.contains(DuplicateNameFactoryB.class)) {
Assert.fail("There are different factories with the same name");
return;
}
if (classSet.contains(NoAnnotationFactory.class)) {
Assert.fail("Factory without @Factory added is loaded");
return;
}
if (classSet.contains(InitialFailureFactory.class)) {
Assert.fail("The factory that failed to initialize was loaded");
}
}
@Test
public void multiThreadReloadTest() throws IOException, NoSuchMethodException, InterruptedException {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
final CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
final AtomicBoolean uncaughtExceptionFlag = new AtomicBoolean();
final int totalCount = 100000;
final Method loadFactoryMethod = CacheStoreBuilder.class.getDeclaredMethod("loadFactory");
loadFactoryMethod.setAccessible(true);
Thread accessThreadA = new Thread(() -> {
for (int i = 0; i < totalCount; i++) {
SingleCacheStore<String> singleCacheStore = cacheStoreBuilder.newSingleCacheStore(CacheStoreSource.REMOTE, identify, converter);
Assert.assertNotNull(singleCacheStore);
Assert.assertEquals(RemoteCacheFactory.RemoteSingleCacheFactory.class, singleCacheStore.getClass());
ListCacheStore<String> listCacheStore = cacheStoreBuilder.newListCacheStore(CacheStoreSource.MEMORY, identify, converter);
Assert.assertNotNull(listCacheStore);
Assert.assertEquals(MemoryFactory.MemoryListCacheStore.class, listCacheStore.getClass());
MapCacheStore<String> mapCacheStore = cacheStoreBuilder.newMapCacheStore(CacheStoreSource.LOCAL, identify, converter);
Assert.assertNotNull(mapCacheStore);
Assert.assertEquals(LocalFactory.LocalMapCacheStore.class, mapCacheStore.getClass());
SetCacheStore<String> setCacheStore = cacheStoreBuilder.newSetCacheStore(identify, converter);
Assert.assertNotNull(setCacheStore);
Assert.assertEquals(SetCacheStoreFactory.OnlySetCacheStore.class, setCacheStore.getClass());
}
}, "Thread-AccessBuilderA");
Thread reloadThreadA = new Thread(() -> {
int count = 0;
final Random random = new Random();
while(count++ < totalCount) {
if (random.nextInt() % 2 == 0) {
try {
loadFactoryMethod.invoke(cacheStoreBuilder);
} catch (IllegalAccessException | InvocationTargetException e) {
log.error("重载 Builder 时发生异常.",
e instanceof InvocationTargetException ?
((InvocationTargetException) e).getTargetException() :
e);
}
}
}
}, "Thread-ReloadBuilderA");
Thread accessThreadB = new Thread(() -> {
for (int i = 0; i < totalCount; i++) {
SingleCacheStore<String> singleCacheStore = cacheStoreBuilder.newSingleCacheStore(CacheStoreSource.REMOTE, identify, converter);
Assert.assertNotNull(singleCacheStore);
Assert.assertEquals(RemoteCacheFactory.RemoteSingleCacheFactory.class, singleCacheStore.getClass());
ListCacheStore<String> listCacheStore = cacheStoreBuilder.newListCacheStore(CacheStoreSource.MEMORY, identify, converter);
Assert.assertNotNull(listCacheStore);
Assert.assertEquals(MemoryFactory.MemoryListCacheStore.class, listCacheStore.getClass());
MapCacheStore<String> mapCacheStore = cacheStoreBuilder.newMapCacheStore(CacheStoreSource.LOCAL, identify, converter);
Assert.assertNotNull(mapCacheStore);
Assert.assertEquals(LocalFactory.LocalMapCacheStore.class, mapCacheStore.getClass());
SetCacheStore<String> setCacheStore = cacheStoreBuilder.newSetCacheStore(identify, converter);
Assert.assertNotNull(setCacheStore);
Assert.assertEquals(SetCacheStoreFactory.OnlySetCacheStore.class, setCacheStore.getClass());
}
}, "Thread-AccessBuilderB");
Thread reloadThreadB = new Thread(() -> {
int count = 0;
final Random random = new Random();
while(count++ < totalCount) {
if (random.nextInt() % 2 == 0) {
try {
loadFactoryMethod.invoke(cacheStoreBuilder);
} catch (IllegalAccessException | InvocationTargetException e) {
log.error("重载 Builder 时发生异常.",
e instanceof InvocationTargetException ?
((InvocationTargetException) e).getTargetException() :
e);
}
}
}
}, "Thread-ReloadBuilderB");
class TestUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("An uncapped exception occurred in thread " + t.getName(), e);
uncaughtExceptionFlag.set(true);
}
}
accessThreadA.setUncaughtExceptionHandler(new TestUncaughtExceptionHandler());
reloadThreadA.setUncaughtExceptionHandler(new TestUncaughtExceptionHandler());
accessThreadB.setUncaughtExceptionHandler(new TestUncaughtExceptionHandler());
reloadThreadB.setUncaughtExceptionHandler(new TestUncaughtExceptionHandler());
accessThreadA.start();
reloadThreadA.start();
accessThreadB.start();
reloadThreadB.start();
accessThreadA.join();
reloadThreadA.join();
accessThreadB.join();
reloadThreadB.join();
if (uncaughtExceptionFlag.get()) {
Assert.fail("Exception occurred while multithreading reload");
}
}
@Test
public void noSpecifiedGetCacheStoreTest() throws IOException {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
SingleCacheStore<String> singleCacheStore = cacheStoreBuilder.newSingleCacheStore(identify, converter);
Assert.assertNotNull(singleCacheStore);
ListCacheStore<String> listCacheStore = cacheStoreBuilder.newListCacheStore(identify, converter);
Assert.assertNotNull(listCacheStore);
MapCacheStore<String> mapCacheStore = cacheStoreBuilder.newMapCacheStore(identify, converter);
Assert.assertNotNull(mapCacheStore);
SetCacheStore<String> setCacheStore = cacheStoreBuilder.newSetCacheStore(identify, converter);
Assert.assertNotNull(setCacheStore);
}
@Test
public void noSuchFactoryExceptionThrowTest() throws NoSuchFieldException, IllegalAccessException, IOException {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
Field factoryListField;
factoryListField = CacheStoreBuilder.class.getDeclaredField("factoryList");
factoryListField.setAccessible(true);
Object o = factoryListField.get(cacheStoreBuilder);
Assert.assertTrue(o instanceof List);
@SuppressWarnings("unchecked")
List<CacheStoreFactory> factoryList = (List<CacheStoreFactory>) o;
factoryList.clear();
try {
cacheStoreBuilder.newSingleCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newMapCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newListCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newSetCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
}
@Test
public void getInstanceFailureTest() throws IOException {
Assert.assertThrows(IOException.class, () ->
CacheStoreBuilder.getInstance(tempDirectory.newFile("invalid file.bin")));
File onlyReadableDirectory = tempDirectory.newFile("onlyReadable");
// Assert.assertTrue(onlyReadableDirectory.setWritable(false));
Assert.assertThrows(IOException.class, () ->
CacheStoreBuilder.getInstance(new File(onlyReadableDirectory, "cache")));
Assert.assertNotNull(
CacheStoreBuilder.getInstance(new File(tempDirectory.newFolder("valid directory"), "cache")));
}
@Test
public void lastFactoryThrowExceptionTest() throws NoSuchFieldException, IllegalAccessException, IOException {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
Field factoryListField;
factoryListField = CacheStoreBuilder.class.getDeclaredField("factoryList");
factoryListField.setAccessible(true);
Object o = factoryListField.get(cacheStoreBuilder);
Assert.assertTrue(o instanceof List);
@SuppressWarnings("unchecked")
List<CacheStoreFactory> factoryList = (List<CacheStoreFactory>) o;
factoryList.removeIf(factory -> !(factory instanceof GetCacheStoreExceptionFactory));
try {
cacheStoreBuilder.newSingleCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newMapCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newListCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newSetCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
}
}
@@ -1,88 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import net.lamgc.cgj.bot.cache.factory.*;
import org.junit.Assert;
import org.junit.Test;
/**
* @see FactoryInfo
*/
public class FactoryInfoTest {
@Test
public void analyticTest() {
FactoryInfo infoA = new FactoryInfo(FactoryInfoTestFactory.class);
Assert.assertEquals("test-factoryInfo", infoA.getFactoryName());
Assert.assertEquals(6, infoA.getFactoryPriority());
Assert.assertEquals(CacheStoreSource.MEMORY, infoA.getStoreSource());
FactoryInfo infoB = new FactoryInfo(FactoryInfoTestFactory.class);
Assert.assertEquals(infoA, infoB);
Assert.assertEquals(infoA.hashCode(), infoB.hashCode());
}
@Test
@SuppressWarnings({"SimplifiableAssertion", "EqualsWithItself", "ConstantConditions"})
public void equalsTest() {
FactoryInfo infoA = new FactoryInfo(FactoryInfoTestFactory.class);
FactoryInfo infoB = new FactoryInfo(FactoryInfoTestFactory.class);
Assert.assertTrue(infoA.equals(infoA));
Assert.assertTrue(infoA.equals(infoB));
Assert.assertFalse(infoA.equals(null));
Assert.assertFalse(infoA.equals(new Object()));
Assert.assertFalse(
new FactoryInfo(NameNoEqualFactoryA.class)
.equals(new FactoryInfo(NameNoEqualFactoryB.class)));
Assert.assertFalse(
new FactoryInfo(NameNoEqualFactoryA.class)
.equals(new FactoryInfo(CacheStoreSourceNoEqualFactory.class)));
}
@Test(expected = IllegalArgumentException.class)
public void noAnnotationTest() {
new FactoryInfo(NoAnnotationFactory.class);
}
@Test(expected = IllegalArgumentException.class)
public void unnamedTest() {
new FactoryInfo(UnnamedFactory.class);
}
@Test
public void priorityCheckTest() {
FactoryInfo toHighPriorityFactoryInfo = new FactoryInfo(TooHighPriorityFactory.class);
Assert.assertEquals(FactoryPriority.PRIORITY_HIGHEST,
toHighPriorityFactoryInfo.getFactoryPriority());
FactoryInfo toLowPriorityFactoryInfo = new FactoryInfo(TooLowPriorityFactory.class);
Assert.assertEquals(FactoryPriority.PRIORITY_LOWEST,
toLowPriorityFactoryInfo.getFactoryPriority());
Assert.assertNotEquals(toHighPriorityFactoryInfo, toLowPriorityFactoryInfo);
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-throwException")
public class AvailabilityCheckExceptionThrowFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
throw new IllegalStateException();
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-NameNoEqualA", source = CacheStoreSource.REMOTE)
public class CacheStoreSourceNoEqualFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "duplicate")
public class DuplicateNameFactoryA implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "duplicate", priority = FactoryPriority.PRIORITY_HIGHEST)
public class DuplicateNameFactoryB implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-factoryInfo", source = CacheStoreSource.MEMORY, priority = 6)
public class FactoryInfoTestFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-getCacheStoreException", priority = FactoryPriority.PRIORITY_HIGHEST)
public class GetCacheStoreExceptionFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
throw new IllegalStateException();
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
throw new IllegalStateException();
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
throw new IllegalStateException();
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
throw new IllegalStateException();
}
@Override
public boolean canGetCacheStore() {
return true;
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "initialFailure")
public class InitialFailureFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
throw new IllegalStateException();
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -1,153 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import org.junit.Assert;
import java.io.File;
import java.util.Map;
import java.util.Set;
@Factory(name = "test-local", source = CacheStoreSource.LOCAL)
public class LocalFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
Assert.assertNotNull(dataDirectory);
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return new LocalMapCacheStore<>();
}
@Override
public boolean canGetCacheStore() {
return true;
}
public static class LocalMapCacheStore<V> implements MapCacheStore<V> {
@Override
public int mapSize(CacheKey key) {
return 0;
}
@Override
public Set<String> mapFieldSet(CacheKey key) {
return null;
}
@Override
public Set<V> mapValueSet(CacheKey key) {
return null;
}
@Override
public boolean put(CacheKey key, String field, V value) {
return false;
}
@Override
public boolean putAll(CacheKey key, Map<? extends String, ? extends V> map) {
return false;
}
@Override
public boolean putIfNotExist(CacheKey key, String field, V value) {
return false;
}
@Override
public V get(CacheKey key, String field) {
return null;
}
@Override
public boolean removeField(CacheKey key, String field) {
return false;
}
@Override
public boolean containsField(CacheKey key, String field) {
return false;
}
@Override
public boolean mapIsEmpty(CacheKey key) {
return false;
}
@Override
public boolean clearMap(CacheKey key) {
return false;
}
@Override
public boolean setTimeToLive(CacheKey key, long ttl) {
return false;
}
@Override
public long getTimeToLive(CacheKey key) {
return 0;
}
@Override
public long size() {
return 0;
}
@Override
public boolean clear() {
return false;
}
@Override
public boolean exists(CacheKey key) {
return false;
}
@Override
public boolean remove(CacheKey key) {
return false;
}
@Override
public Set<String> keySet() {
return null;
}
}
}
@@ -1,149 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import org.junit.Assert;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@Factory(name = "test-memory", source = CacheStoreSource.MEMORY)
public class MemoryFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
Assert.assertNotNull(dataDirectory);
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return new MemoryListCacheStore<>();
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return true;
}
public static class MemoryListCacheStore<E> implements ListCacheStore<E> {
@Override
public E getElement(CacheKey key, int index) {
return null;
}
@Override
public List<E> getElementsByRange(CacheKey key, int index, int length) {
return null;
}
@Override
public boolean removeElement(CacheKey key, int index) {
return false;
}
@Override
public boolean addElement(CacheKey key, E element) {
return false;
}
@Override
public boolean addElements(CacheKey key, Collection<E> elements) {
return false;
}
@Override
public boolean containsElement(CacheKey key, E element) {
return false;
}
@Override
public boolean isEmpty(CacheKey key) {
return false;
}
@Override
public int elementsLength(CacheKey key) {
return 0;
}
@Override
public boolean clearCollection(CacheKey key) {
return false;
}
@Override
public boolean removeElement(CacheKey key, E element) {
return false;
}
@Override
public boolean setTimeToLive(CacheKey key, long ttl) {
return false;
}
@Override
public long getTimeToLive(CacheKey key) {
return 0;
}
@Override
public long size() {
return 0;
}
@Override
public boolean clear() {
return false;
}
@Override
public boolean exists(CacheKey key) {
return false;
}
@Override
public boolean remove(CacheKey key) {
return false;
}
@Override
public Set<String> keySet() {
return null;
}
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-NameNoEqualA")
public class NameNoEqualFactoryA implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}

Some files were not shown because too many files have changed in this diff Show More