Appearance
一、框架使用
运行环境:
JDK21 及以上
MySQL8 及以上
代码结构:
bash
{{PROJECT_NAME}}/
├─ pom.xml # maven配置
├─ src/main/java/com/yimiyisu/{{PROJECT_NAME}}
│ ├─ Application.java # 启动入口
│ ├─ controller/ # 接口层(参数校验、权限、路由)
│ ├─ service/ # 业务层(核心流程编排)
│ │ ├─ domain/ # 业务实体/DTO/枚举
│ │ └─ xxxService.java # 领域服务
│ ├─ events/ # 事件层(异步任务、定时任务)
│ │ ├─ model/ # 事件模型
│ │ ├─ stat/ # 统计相关事件
│ │ └─ xxxEvent.java/ # 事件
│ ├─ hooks/ # 数据钩子(前后置逻辑)
│ ├─ kit/ # 工具
│ └─ domain/ # 跨层通用模型
└─ src/main/resources/
├─ app.properties # 环境配置
└─ static/ # 静态资源1. pom.xml 初始模版
xml
XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>{{PROJECT_NAME}}</artifactId>
<packaging>jar</packaging>
<name>{{PROJECT_NAME}}</name>
<groupId>com.yimiyisu</groupId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 框架依赖 -->
<dependency>
<groupId>me.zeto</groupId>
<artifactId>core</artifactId>
<version>0.1.5-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>package.xml</descriptor>
</descriptors>
<outputDirectory>${project.build.directory}/dist/</outputDirectory>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>me.zeto</groupId>
<artifactId>plugin</artifactId>
<version>0.1.6</version>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>ZenOptimizer</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
<exclude>META-INF/LICENSE</exclude>
<exclude>META-INF/NOTICE</exclude>
<exclude>META-INF/maven/**</exclude>
<exclude>**/test/**</exclude>
<exclude>**/about.html</exclude>
<exclude>**/*.lombok</exclude>
<exclude>lombok/**</exclude>
<exclude>junit/**</exclude>
<exclude>com/mongodb/**</exclude>
<exclude>org/mongodb/**</exclude>
</excludes>
</filter>
<filter>
<artifact>com.mysql:mysql-connector-j</artifact>
<includes>
<include>com/mysql/cj/**</include>
</includes>
</filter>
</filters>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.yimiyisu.{{PROJECT_NAME}}.Application</mainClass>
</transformer>
</transformers>
<finalName>${project.name}</finalName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>注意:<minimizeJar>true</minimizeJar>开启了包压缩,打包后可能会导致某些依赖丢失,需要手动排出,如:
xml
<filter>
<artifact>com.mysql:mysql-connector-j</artifact>
<includes>
<include>com/mysql/cj/**</include>
</includes>
</filter>2. Application.java
初始模版:
java
public class Application extends ZenApp {
public static void main(String[] args) {
int listenPort = 7058;
String appName = "interact";
ZenApp app = new Application();
app.start(args, appName, listenPort);
}
}2.1 必须 extends ZenApp
2.2 listenPort 为端口号
2.3 appName为应用名
2.4 若为多店铺系统的店铺端,需要切换店铺功能,需添加
java
app.multiTenant();配合 ZenTenantHook 一起使用
2.5 若需要操作日志,需添加
java
app.openBizLoger(new OperationLogService());参数为自己实现的service,service示例:
typescript
public class OperationLogService implements IBizLogger {
@Override
public void put(BizLogDO bizLogDO) {
AliLogService.setLog(bizLogDO);
}
}必须 implements IBizLogger,此时当数据库配置了启用日志时,对该数据库的操作会被该方法拦截
3. controller 类
例:
java
@Slf4j
@AccessRole(ZenRole.ANONYMITY)
public class Home extends ZenController {
@Inject
private ZenEngine zenEngine;
@Inject
private WxAuthService wxAuthService;
}2.1 @AccessRole(ZenRole.ANONYMITY):访问权限控制,必须有
ZenRole枚举:
| ANONYMITY | 匿名用户 |
|---|---|
| SIGNATURE | C 端登陆用户 |
| ADMIN | C 端管理员 |
| CONSOLE | 中后台登录用户 |
| SUPPER | 中后台管理员用户 |
| DEVELOPER | 研发用户 |
2.2 extends ZenController:必须继承ZenController
2.3 @Inject:注入 Bean 对象
4. controller 中方法
例:
java
@MethodType(ZenMethod.ALL)
@Tracker(1)
public ZenResult test(ZenData data) {
MessageService messageService = ConfigKit.getBean(MessageService.class);
System.out.println(messageService);
try (ExceptionTracker tracker = new ExceptionTracker("test")) {
tracker.setTitle("好像有异常");
return ZenResult.success();
}
}3.1 @MethodType(ZenMethod.ALL):声明访问类型,默认 POST,可填 GET,ALL,尽量都标明
3.2 @Tracker(1):跟踪接口执行时间,上下文参数,计时器默认值 200ms,接口执行超过这个时间会在日志中打印出来
3.3 ZenResult:返回值,固定
3.3.1 属性说明
| success | 请求接口是否成功 |
|---|---|
| message | 响应的用户消息 |
| action | 原始的请求对象 |
| data | 响应的数据 |
3.3.2 常用方法
| ZenResult success() | 返回成功结果 |
|---|---|
| ZenResult success(String message) | 返回成功结果,带输出消息 |
| ZenResult success(String message, ZenAction action) | 返回成功结果,带消息和前端指令 |
| ZenResult fail(String message) | 返回失败结果,带异常消息 |
| ZenResult fail(Throwable throwable) | 返回失败结果,带异常盏 |
| ZenResult redirect(String uri) | 服务器端二次跳转 |
| ZenResult parse(String content) | 将字符串结果集,转成对象 |
| boolean isEmpty() | 判断接口返回结果是否为空 |
| ZenResult refresh() | 通知前端刷新页面 |
| ZenResult setData(Object data) | 设置结果数据 |
| boolean exist(String key) | 判断返回对象是否包指定字段 |
| String get(String key) | 获取返回对象指定字段值 |
| T asEntity(Class<T> clazz) | 单条数据接口返回实例化对象 |
| PageEntity<T> asPageEntity(Class<T> clazz) | 分页接口返回实例化的分页对象 |
| List<T> asList(Class<T> clazz) | 列表接口返回实例化的列表对象 |
| List<String> getAsStringList(String key) | 获取对象指定字段的列表字符串 |
| List<Map<String, Object>> asListMap() | 获取 list 的 map 对象 |
| T get(String key, Class<T> clazz) | 指定字段转成实例化对象 |
| String[] getList(String name) | 获取字符串列表 |
| int getInt() | count 接口返回整形对象 |
| int getLong() | sum 接口返回长整形对象 |
ZenAction 输出行为对象
| SUCCESS() | 请求成功 |
|---|---|
| REBACK() | 返回到上一页 |
| JUMP() | 客户端跳转,data 为目标地址 |
| REFRESH() | 刷新客户端网页 |
| GOLOGIN() | 跳转到登录页面 |
| E404() | 跳转到 404 页面 |
| END() | 程序结束,中断后续逻辑,hook 拦截时使用 |
| ORIGINAL() | 输出原生字符串 |
| NOPERMISSION() | 无访问权限 |
| FAIL() | 请求失败 |
| REDIRECT() | 页面302重定向 |
使用示例
java
return ZenResult.success().setAction(ZenAction.REDIRECT).setData(authorizeUrl);3.4 ZenData:接口请求的入参对象,固定 待补充
常用方法
| ZenData create() | 静态函数构造 ZenData |
|---|---|
| ZenData create(ZenData data) | 静态函数构造 ZenData,继承 data 的上下文信息 |
| boolean isEmpty() | 判断字段是否为空 |
| boolean isEmpty(String key) | 判断字段 key 是否为空 |
| String get(String name) | 获取 key 字段数据 |
| String query(String name) | 获取 url 中的 queryString 参数值 |
| String getHeader(String headerName) | 获取用户请求的 header 值 |
| String getIP() | 获取用户请求的真实 IP |
| T get(String key, Class<T> clazz) | 将 key 字段数据转成 clazz 型对象 |
| List<Map<String, Object>> getAsListMap(String key) | 将 key 字段数据转成 List 的 map 对象 |
| List<T> getAsList(String key, Class<T> clazz) | 将 key 字段数据转成 clazz 的列表对象 |
| String getTenant() | 获取租户 ID |
| Session session() | 获取用户的 Session 对象 |
| ZenData put(String key, Object value) | 增加新数据 |
| ZenData batch(JsonElement element) | 批量插入数据 |
| T parse(Class<T> clazz) | 转成业务对象 |
| int getPageNumber() | 获取分页的页号,默认值 0 |
| int getPageSize() | 获取分页的 size, 默认值 20,最大 100 |
| String[] getKeys() | 获取参数的所有 key |
| String getAccount() | 获取主账户 |
| ZenUser getUser() | 获取user |
| String getUid() | 获取uid |
5. service 类
用@Component注册,用@Inject引入其他service
6. domain 实体类
系统已经集成lombok,@Data尽量所有实体类都加上
7. ZenEngine 数据操作引擎
用来进行所有数据库操作
6.1 使用方法
java
@Inject
private ZenEngine zenEngine;6.2 常用方法
示例:
java
ZenResult groupResult = zenEngine.execute("get/white_group", ZenData.create("id", integralWhite.getGroup()));| ZenResult execute(String api, ZenData zenData) | 执行自动化接口 |
|---|---|
| void inc(String table, String id, String column, int count) | 单字段数据自增,自减操作 |
| List<JsonObject> selectById(String tableName, List<String> ids) | 手工批量查询 |
6.3 跨应用数据库操作
表名格式为数据库实际表名,格式:appName_tableName,zen 前缀的系统表禁止处理
| JsonObject get(String tableName, String id) | 单条数据查询 |
|---|---|
| JsonObject get(String tableName, Map<String, String> condition) | 多条件单条数据查询 |
| void update(String tableName, String id, Map<String, Object> data) | 更新单条数据 |
| void update(String tableName, Map<String, Object> data, Map<String, String> condition) | 多条件更新单条数据 |
| void delete(String tableName, String id) | 删除单条数据 |
8. event 事件
7.1 先定义model
命名格式为 xxxEventModel,定时任务为xxxLoopEventModel
示例:
java
@Data
public class RecordExportEventModel {
private String encryptKey; //加密密码
private int encryptType; //加密类型 0 不加密 1 自动加密 2 人工加密
}7.2 编写event类
命名格式对应model名,为xxxEvent,定时任务为xxxLoopEventModel,xxx和model保持一致
示例:
java
TypeScript
public class RecordExportEvent implements IEvent<RecordExportEventModel> {
@Inject
private RecordExportService recordExportService;
@Inject
private ZenEngine zenEngine;
@Override
@Subscribe
public void execute(RecordExportEventModel recordExportEventModel) {
if (CacheKit.has("recordExportEventModel:"+recordExportEventModel.getExportTaskId())) return;
CacheKit.set("recordExportEventModel:"+recordExportEventModel.getExportTaskId(), 1, 1);
String cdnPath = recordExportService.RecordExport(recordExportEventModel);
String exportTaskId = recordExportEventModel.getExportTaskId(); // 导出任务id
if (!cdnPath.isEmpty()){
// 回填path并修改状态为成功
zenEngine.execute("patch/export_task", ZenData.create().put("id", exportTaskId).put("status", 2).put("path", cdnPath));
}else{
// 更新状态为:导出失败
zenEngine.execute("patch/export_task", ZenData.create().put("id", exportTaskId).put("status", 3));
}
}
}必须实现IEvent<对应的model类名>
重写execute方法,必须添加@Override @Subscribe 两个注解,方法的参数为对应的model类
- 定时任务需要在类上添加@Crontab()注解,里面写时间表达式,如"0 */1 * * * ?",需要查数据时,必须使用游标模式,通过id作为游标,对应的查询接口要以id升序排序,条件要有id > 传入的id这个条件
示例:
java
Java
/**
* 定时补发 每两分钟执行一次
*/
@Crontab("0 */1 * * * ?")
@Slf4j
public class ReissueLoopEvent implements IEvent<ReissueLoopEventModel> {
@Inject
private ZenEngine zenEngine;
private static final String RECORD_CURSOR = "reissue_loop_cursor";
@Override
@Subscribe
public void execute(ReissueLoopEventModel reissueLoopEventModel) {
// 从缓存获取游标
String cursor = CacheKit.get(RECORD_CURSOR); // 上次执行到的中奖记录id
long now = DateKit.now();
// 60分钟以上 白天:6h之内 晚上:30天之内
long begin = DateKit.isNight() ? now - 30 * 24 * 3600 : now - 6 * 3600;
ZenData param = ZenData.create().put("status", 1).put("pageSize", 100).put("id", cursor).put("createGmt", now)
.put("updateGmt", DateKit.now() - 60 * 60);
ZenResult recordResult = zenEngine.execute("list/activity_record_cursor_send", param);
if (recordResult.isEmpty()) {
// 延时重置游标
EventKit.trigger(cursor, 20, (item) -> {
String id = String.valueOf(item);
if (StringKit.isNotEmpty(id) && id.equals(CacheKit.get(RECORD_CURSOR))) {
// System.out.println("重置发放中游标" + id);
CacheKit.set(RECORD_CURSOR, StringKit.objectId(begin), 5);
}
});
return;
}
List<SendRewardEventModel> recordResultList = recordResult.asList(SendRewardEventModel.class);
// 将最后一条记录id存入缓存
CacheKit.set(RECORD_CURSOR, recordResultList.getLast().getId(), 5);
// 执行发送 - 补发
for (SendRewardEventModel record : recordResultList) {
// 中奖记录大于30天的跳过
if (DateKit.now() - record.getCreateGmt() > 3600 * 24 * 30)
continue;
// 补发更新成待发放
zenEngine.execute("patch/activity_record_status", ZenData.create("id", record.getId()).put("status1", "1")
.put("status2", "2").put("createGmt", DateKit.now()));
EventKit.trigger(record);
}
}
}7.3 事件触发
定时任务无需手动触发
异步任务需要手动触发,通过EventKit
EventKit 常用方法
| trigger(Object eventModel) | 触发对应 model 的异步事件 |
|---|---|
| trigger(Object eventModel, int seconds) | 延时 seconds 秒后触发对应 model 的异步事件 |
| trigger(Object data, int seconds, Consumer<Object> callback) | 延时 seconds 秒后触发函数调用 |
| off(Object eventModel) | 卸载异步事件 |
示例:
java
Java
// 异步扣除积分明细
PointsExpenseEventModel pointsExpenseEventModel = new PointsExpenseEventModel();
pointsExpenseEventModel.setId(resultPut.get("id"));
pointsExpenseEventModel.setPointsId(pointsId);
pointsExpenseEventModel.setTotal(num);
pointsExpenseEventModel.setPurpose(purpose);
pointsExpenseEventModel.setExtra(extra);
EventKit.trigger(pointsExpenseEventModel);事件中execute方法的参数即为触发时传入的参数
9. hooks
可以拦截/api接口和/do接口,在接口执行前或后做一些操作,尽量不使用
示例:
java
Java
@ZenHook({"patch/templateWithDefault"})
public class PathTypeHook implements IHook {
@Inject
private ZenEngine zenEngine;
@Override
public ZenResult before(ZenData data) {
IHook.super.before(data);
return zenEngine.execute("patch/templateNoDefault", ZenData.create());
}
}必须添加ZenHook注解,接口可以写多个
必须 implements IHook
- 重写接口中方法,共有两个
before:在方法执行前拦截,返回 ZenResult 对象,可以通过 ZenAction.End 终止方法继续执行
入参:ZenData 出参:ZenResult
after:在方法执行后,调用的钩子,可用于异步调用,MQ 消息发送等
入参:ZenData ZenResult 出参:无
- 若为多租户系统后台,需要切换租户时,必须添加 ZenTenantHook,返回当前用户有哪些租户
示例:
java
TypeScript
@ZenHook("zenTenant") // 固定
public class ZenTenantHook implements IHook {
@Inject
private ZenEngine zenEngine;
@Override
public ZenResult before(ZenData data) {
if(StringKit.isEmpty(data.getAccount())) return ZenResult.success().setAction(ZenAction.END);
ZenResult tenantResult = zenEngine.execute("list/stall_employ", data);
if (tenantResult.isEmpty()) {
return ZenResult.fail("找不到有效门店");
}
List<String> list = tenantResult.asList().stream().
map((element) -> element.get("stall").getAsString()).toList();
return ZenResult.success().
put("list", list).
put("depend", "stall").
setAction(ZenAction.END);
}
}10. app.properties 配置文件
9.1 运行环境
java
env=LOCAL_DEV配置名 env
值 LOCAL_DEV:本地 daily:日常 demo:演示 online:线上
9.2 数据库配置
java
dbHost=_$$UdCez9vGORbBEKP1zpArpnJo7YyRtdLSMbk7cOwnX90k5b5D8owOVh1lNv/xPhYF
dbUser=_$$egPUAfl+Hf7P6uyCLpHyzg==
dbPasswd=_$$NymQvkQqV0S3FrBEIwByjkzd+azx0+z73VwFajUZhmo=
如果zen_user等系统表和业务表不在一个数据时,需单独配置
zenDbPasswd=
zenDbUser=
zenDbHost=9.3 redis配置
java
redisAddress=redis://:74edsCG9bWpmSBOn@db.zeto.me:6021/109.4 自定义配置
自定义的配置,程序中通过ConfigKit获取
示例:ConfigKit.get("wxMiniAppId")
常用方法
| String get(String name) | 获取指定配置 |
|---|---|
| JsonObject selfWidthTenant(String tenantId, String name) | 获取租户的自定义配置 |
| String get(String name, String defaultValue) | 获取配置,设置默认值 |
| JsonObject self(String name) | 获取自定义配置 |
| JsonObject self(String name) | 获取自定义配置 |
| JsonArray selfAsArray(String name) | 获取数组类型配置 |
| void self(String name, String data) | 设置自定义配置 |
| void selfWidthTenant(String tenantId, String name, String data) | 设置租户级别的自定义配置 |
| String getPath() | 当前环境物理路径 |
| BeeDataSource getDatasource() | 获取数据库的数据源,用于自定义 mybatis 配置等 |
| boolean isDev() | 是否开发环境 |
| boolean isDaily() | 是否日常环境 |
| boolean isOnline() | 是否线上环境 |
| void setTenantExpire(String tenantId, boolean status) | 设置租户是否过期,true 过期,false 未过期 |
11. 广播 Broadcast
10.1 配置文件配置
broadRedis : 默认广播服务器与缓存服务相同,可以通过该变量指定服务
boradTenant : 指定监听的租户对象
10.2 对象属性
| String target | 广播目标对象,一般取对象 Id |
|---|---|
| String type | 广播的业务类型 |
| String tenantId | 租户ID,非必填 |
10.3 常用方法
| Broadcast(String target, String type) | 构造函数 |
|---|---|
| Broadcast(String target, String type, String tenantId) | 构造函数 |
| Broadcast every() | 启用多实例接收,默认单实例接收 |
| void send(String app) | 输入目标应用,发送广播 |
| void send(String app, Object data) | 输入目标应用和广播的数据,发送广播 |
| String getData() | 获取广播数据,只能获取一次 |
10.4 使用示例
使用前要确保两个应用广播服务器相同
营销平台通知商城发积分
营销平台写法:
java
Broadcast broadcast = new Broadcast(record.getId(), "mall_points", expand.getShopId());
broadcast.send("mall_client");因为商城有多个环境,所以要指定租户,因为每个商城的店铺id不同,所以本例使用店铺id,对应商城应用配置文件要配置boradTenant=店铺id
商城接收广播写法:
先在event目录下创建broadcast目录,在目录中创建BroadcastEvent.java,以上名称固定
typescript
// 监听广播
public class BroadcastEvent implements IEvent<Broadcast> {
@Inject
private PointsSendService pointsSendService;
@Override
@Subscribe
public void execute(Broadcast broadcast) {
System.out.println("收到广播"+broadcast.getType()+broadcast.getTarget());
switch (broadcast.getType()){
case "mall_points":
pointsSendService.send(broadcast.getTarget());
return;
}
}
}IEvent<Broadcast> 固定,如需接收其他广播,添加对应的case即可
12. 大屏/统计数据 DataV
DataV 对象属性:
| String id | 统计编号 |
|---|---|
| String targetId | 统计对象,非必填 |
| boolean refreshing | 是否刷新中 |
DataV 对象方法:
| static DataV get(String datavId) | 获取统计对象 |
|---|---|
| static DataV get(String targetId, String datavId) | 获取统计对象 |
| void setCacheTime(int time=600) | 数据缓存时间,默认 600 秒 |
| DataV put(String key, Object value) | 添加统计属性 |
| void refresh() | 主动刷新统计 |
| void save() | 保存统计结果 |
使用示例:
- 前端使用方法,调用对应的获取接口,如果需要刷新,就延迟重新执行
javascript
async getData() {
this.data = await $.post({
url: "/api/member/homeStat",
});
if (this.data.isRefreshing) {
// 延迟2秒后重新执行
await new Promise((resolve) => setTimeout(resolve, 2000));
return await this.getData();
}
return this.data;
},- Controller 写法
java
/**
* 获取大屏数据
*/
public ZenResult homeStat(ZenData data) {
String account = data.getAccount();
// 首页账户数据
DataV accountData = DataV.get(data.getAccount(), AccountHomeStat.ID);
// 首页门店数据
DataV stallData = DataV.get(data.getTenant(), StallHomeStat.ID);
boolean isRefreshing = accountData.isRefreshing() || stallData.isRefreshing();
return ZenResult.success().put("accountData",accountData.getResult()).put("stallData",stallData.getResult()).put("isRefreshing",isRefreshing);
}- 在events目录下创建stat目录,编写统计类
例:
java
// erp首页账户数据
@Component
public class AccountHomeStat implements IStat {
public final static String ID = "erp:accountHomeStat";
@Inject
private SettingsStat settingsStat;
@Inject
private StockStat stockStat;
@Override
public void execute(DataV dataV) {
// 基于账户统计
String accountId = dataV.getTargetId();
//获取客户总数
long customerNumber = settingsStat.getCustomerNumber(accountId);
//获取供应商总数
long supplierNumber = settingsStat.getSupplierNumber(accountId);
//获取库存总数
long stockNumber = stockStat.getStockNumber(accountId);
//获取库存总额
long stockAmount = stockStat.getStockAmount(accountId);
//获取库存预警数
long stockWarning = stockStat.getStockWarning(accountId);
//获取客户欠款
long customerDebt = settingsStat.getCustomerDebt(accountId);
//获取供应商欠款
long supplierDebt = settingsStat.getSupplierDebt(accountId);
//获取过期商品数量
long expiredNumber = stockStat.getExpiredNumber(accountId);
dataV.put("customerNumber", customerNumber);
dataV.put("supplierNumber", supplierNumber);
dataV.put("stockNumber", stockNumber);
dataV.put("stockAmount", stockAmount);
dataV.put("stockWarning", stockWarning);
dataV.put("customerDebt", customerDebt);
dataV.put("supplierDebt", supplierDebt);
dataV.put("expiredNumber", expiredNumber);
dataV.setCacheTime(600);
dataV.save();
}
}- 在stat目录下创建tools目录,放settingsStat、stockStat等java类
例:
typescript
@Component
public class SettingsStat {
@Inject
private ZenEngine zenEngine;
//获取客户总数
public long getCustomerNumber(String account) {
return zenEngine.execute("count/customer_total", ZenData.create("account", account)).getLong();
}
//获取供应商总数
public long getSupplierNumber(String account) {
return zenEngine.execute("count/supplier_total", ZenData.create("account", account)).getLong();
}
//获取客户欠款
public long getCustomerDebt(String account) {
return zenEngine.execute("sum/customer", ZenData.create("account", account)).getInt("receiveDebt");
}
//获取供应商欠款
public long getSupplierDebt(String account) {
return zenEngine.execute("sum/supplier", ZenData.create("account", account)).getInt("payDebt");
}
//获取欠款客户数
public long getDebtCustomerNumber(String account) {
return zenEngine.execute("count/customer", ZenData.create("account", account)).getLong();
}
//获取欠款供应商数
public long getDebtSupplierNumber(String account) {
return zenEngine.execute("count/supplier", ZenData.create("account", account)).getLong();
}
}13. 内置Kit
1. StringKit 字符串工具类
| String objectId() | 生成一个对象的标准雪花 ID |
|---|---|
| String shortId() | 生成一个对象的短雪花 ID |
| String objectId(long time) | 指定时间生成雪花 ID |
| String shortId(long time) | 知道时间生成短雪花 ID |
| boolean isEmpty(String str) | 是否空字符串 |
| boolean isEmpty(Object str) | 是否空对象 |
| boolean isNotEmpty(String... str) | 非空字符串 |
| boolean isNumber(String value) | 是否是数字 |
| String get(String name) | 获取指定配置 |
| String fileExt(String fname) | 获取文件后缀 |
| int rand(int min, int max) | 生成一个随机整数 |
| String rand(int size) | 生成长度为 size 的随机数字字符串 |
| String md5(String words) | 生成 MD5 的密纹字符串,不能用于密码 |
| String SHA1(String words) | 生成 SHA1 的密纹 |
| String SHA512(String words) | 生成 SHA512 的密纹 |
| String SHA256(String words) | 生成 SHA256 的密纹 |
| String urlEncode(String content) | 用 utf-8 字符集,进行 URL 编码 |
| String encrypt(String content) | 基于当前时间戳(秒)生成密文 |
| DecryptResult decrypt(content content) | 将时间戳密文解密为明文和原时间 |
| String encrypt(String content, String ukey) | AES 加密后转为 Base64 编码 |
| String decrypt(String content, String ukey) | Base64 解码后 AES 解密 |
13.2 CacheKit 缓存工具类
- 缓存默认时长 180 分钟,所有计数器类缓存为持久缓存无时长限制
| String get(String cacheId) | 获取字符串型缓存 |
|---|---|
| T get(String cacheId, Class<T> clazz) | 获取指定类型的对象 |
| String getWithClean(String cacheId) | 获取缓存后删除缓存,常见:验证码场景 |
| void set(String cacheId, Object data) | 设置缓存,有效时长 180 分钟 |
| void set(String cacheId, Object data, Integer minutes) | 指定时长,设置缓存,0 表示永不过期 |
| bool has(String cacheId) | 判断缓存是否存在 |
| void remove(String cacheId) | 删除缓存 |
| int hGet(String cacheId, String prop) | 获取哈希对象计数器值 |
| String hGetObject(String cacheId, String prop) | 获取 hash 缓存的对象值 |
| void hSet(String cacheId,String prop, Object value) | 对象类型 hash 缓存,永久有效 |
| void hSet(String cacheId, Map<String, Integer> data) | 批量计数器的值同步,注意不是增减,是覆盖式原数据 |
| void hSet(String cacheId, String prop, int number) | 单条计数器的值同步,会覆盖原数据 |
| long hIncrBy(String cacheId,String prop, int number) | 增减原子计数器,number 正值为增加,负值为减少 |
| Set<String> hKeys(String cacheId) | 获取 hash 缓存的所有 key |
| void hDel(String cacheId,String prop) | 根据 hash 的 key,删除对应缓存 |
13.3 ConfigKit 配置中心工具类
| String get(String name) | 获取指定配置 |
|---|---|
| JsonObject selfWidthTenant(String tenantId, String name) | 获取租户的自定义配置 |
| String get(String name, String defaultValue) | 获取配置,设置默认值 |
| JsonObject self(String name) | 获取自定义配置 |
| JsonObject self(String name) | 获取自定义配置 |
| JsonArray selfAsArray(String name) | 获取数组类型配置 |
| void self(String name, String data) | 设置自定义配置 |
| void selfWidthTenant(String tenantId, String name, String data) | 设置租户级别的自定义配置 |
| String getPath() | 当前环境物理路径 |
| BeeDataSource getDatasource() | 获取数据库的数据源,用于自定义 mybatis 配置等 |
| boolean isDev() | 是否开发环境 |
| boolean isDaily() | 是否日常环境 |
| boolean isOnline() | 是否线上环境 |
| void setTenantExpire(String tenantId, boolean status) | 设置租户是否过期,true 过期,false 未过期 |
13.4 DateKit 日期工具类
| long now() | 当前 unix 时间戳 |
|---|---|
| boolean isNight() | 当前是否处于夜间23点至第二天6点以前 |
| long today() | 今天 0 点的时间戳 |
| long today(int span) | 以今天 0 点时间戳为基点,获取 span 天后的 0 点时间戳 |
| long month() | 当前月的 0 点时间戳 |
| long month(int month) | 指定月份的 0 点时间戳 |
| long week() | 当前周的 0 点时间戳 |
| Calendar getCalendar() | 获取当前 0 点的日期对象 |
| boolean isInCronBetween(long time,String cronStart,String cronEnd) | 判断指定时间是否在,两个时间表达式之间 |
| String toString(long date) | unix 时间戳转长格式的日期字符串 |
| String toShortString(long date) | unix 时间戳转短格式的日期字符串 |
| String toString(long unixTime, String pattern) | 指定格式,把 unix 时间戳转格式化后的日期字符串 |
| String toString(Date date) | 指定时间转长格式的日期字符串 |
| String toString(Date date, String pattern) | 指定时间转指定格式的日期字符串 |
| long toUnix(String time, String pattern) | 指定时间转指定格式的日期字符串 |
| long toUnix(String time) | 时间字符串转 unix 时间戳 |
| String gmtDate() | 当前时间 GMT 格式的字符串 |
| String gmtDate(LocalDateTime localDateTime) | 指定时间 GMT 格式的字符串 |
| String gmtDate(Date date) | 指定时间 GMT 格式的字符串 |
13.5 EventKit 异步事件工具类
| trigger(Object eventModel) | 触发对应 model 的异步事件 |
|---|---|
| trigger(Object eventModel, int seconds) | 延时 seconds 秒后触发对应 model 的异步事件 |
| trigger(Object data, int seconds, Consumer<Object> callback) | 延时 seconds 秒后触发函数调用 |
| off(Object eventModel) | 卸载异步事件 |
13.6 HttpKit 网络请求工具类
- 尽量多使用异步调用外部接口,如果上游服务限流了,可通过轮询事件控制请求频率
| String get(String url) | 请求 Get 接口,返回文本内容 |
|---|---|
| String get(String url, Consumer<String> callback) | 异步请求 Get 接口,返回文本内容 |
| T get(String url, Class<T> clazz) | 请求 Get 接口,返回指定类型对象 |
| String get(String url, Map<String, Object> params) | 带参数请求 Get 接口,返回文本内容 |
| void get(String url, Map<String, Object> params, Consumer<String> callback) | 异步带参数请求 Get 接口,返回文本内容 |
| T get(String url, Map<String, Object> params, Class<T> clazz) | 带参数请求 Get 接口,返回指定类型对象 |
| Map<String, Object> getAsMap(String url) | 请求 Get 接口,返回 Map 对象 |
| Map<String, Object> getAsMap(String url, Map<String, Object> params) | 带参数请求 Get 接口,返回 Map 对象 |
| String post(String url, Map<String, Object> data) | 请求 Post 接口,返回文本内容 |
| void post(String url, Map<String, Object> data, Consumer<String> callback) | 异步请求 Post 接口,返回文本内容 |
| T post(String url, Map<String, Object> params, Class<T> clazz) | 请求 Post 接口,返回指定类型对象 |
| Map<String, Object> postAsMap(String url, Map<String, Object> data) | 请求 Post 接口,返回 Map 对象 |
| String postAsForm(String url, Map<String, Object> data) | 传统 Form 表单形式,发送 Post 请求 |
| void postAsForm(String url, Map<String, Object> data, Consumer<String> callback) | 异步传统 Form 表单形式,发送 Post 请求 |
| void postFile(String url,String fileName, String filePath, Map<String, String> data, Map<String, String> headers, Consumer<String> callback) | 异步传统 Form 表单形式,上传文件 Post 请求 |
| InputStream download(String url) | 发起 Get 请求,下载文件流 |
13.7 JsonKit Json工具类
| T parse(String json, Class<T> clazz) | 字符串转指定类型对象 |
|---|---|
| String stringify(Object value) | 对象序列化为字符串 |
| T parse(Object json, Class<T> clazz) | 转指定类型对象 |
| Map<String, Object> parseAsMap(String json) | 转成 map 对象 |
| List<Map<String, Object>> parseAsListMap(String json) | 转成 list 集合的 map 对象 |
| Map<String, String> parseAsStringMap(String json) | 转成字符串型 map 对象 |
| List<String> parseAsStringList(String json) | 转成字符串的 list 集合 |
| List<T> parseAsList(String json, Class<T> clazz) | 转成指定类型的 list 集合 |
| JsonObject parse(String json) | 字符串转 jsonObject |
| JsonObject parse(Object json) | 对象转 jsonObject |
13.8 UploadKit 上传工具类
| String image(String url, String path) | 将网络图片,上传到服务器,并返回新路径 |
|---|---|
| void put(String path, String content) | 将文本内容推送到云存储上 |
| void put(String path, InputStream inputStream) | 将文本推送到私有云存储 |
| String get(String path) | 获取文件内容 |
| String getURL(String path) | 将附件路径,转换成临时授权后的可访问路径,有效期 10 分钟 |
| void remove(String path) | 删除指定文件 |
| void refresh(String path) | 刷新 CDN 文件缓存,由于 CDN 原理,不同节点间有 3-5 分钟延迟 |
13.9 UserKit 用户工具类
| ZenUser get(String uid) | 通过 ID 查询用户 |
|---|---|
| List<ZenUser> selectByUids(List<String> ids) | 通过 uid 数组批量查询用户 |
| void pause(String uid) | 暂停用户登录系统 |
| String insert(ZenUser user) | 系统中插入用户,返回新用户登录凭证 Token |
| void update(ZenUser user) | 更新用户头像,昵称,邮箱等信息 |
| void setMeta(String uid, String name, String value) | 添加标记信息,标记信息是用户额外的缓存信息,不能存储业务数据 |
| void setOpenId(String uid, String openId) | 设置用户 openId,如果已存在则不允许修改 |
| void removeMeta(String uid, String name) | 删除标记信息 |
| void update(ZenUser user) | 更新用户头像,昵称,邮箱等信息 |
| boolean updateMobile(String uid, String mobile, UserTag tag) | 更新手机号,已存在的手机号不能更新 |
| void updateUionId(String uionId, String uid) | 更新用户 uionId |
| void updateTag(ZenUser user) | 更新用户标记 |
| void removeToken(String token) | 删除指定 Token |
| void cleanToken(String uid) | 清除用户所有 token |
| void cleanToken(int days) | 清除 N 天以前登陆的 token |
| String createToken(String uid) | 生成用户新 token |
| ZenUser getByToken(String token) | 根据 token 返回用户 |
| String createToken(String uid, String qrCodeId) | 根据二维码 ID,跨端生成用户新 token |
| void changePassword(String uid, String pwd) | 修改用户密码 |
| List<ZenUser> search(String keyword) | 根据关键字搜索用户 |
| boolean exist(String username) | 判断用户名是否存在 |
| boolean getByOpenId(String openId, String app) | 根据 openId 和应用名查询用户 |
| ZenUser getByOpenId(String openId, UserTag tag) | 通过 openId 查询用户 |
| getByName(String username) | 通过用户名查询用户 |
| ZenUser getByMobile(String phone, ZenRole tag) | 手机号查询用户 |
| ZenUser getByMobile(String phone, String app) | 手机号,所属应用查询用户 |
| ZenUser getByMobile(String phone, int tag, String app) | 手机号,自定义标,所属应用查询用户 |
13.10 IOKit 文件工具类
| String readToString(String file) | 读取文件文本内容 |
|---|---|
| String readToString(BufferedReader bufferedReader) | 读取文件文本内容 |
| String readToString(Path path) | 读取文件文本内容 |
| String readToString(InputStream input) | 读取文件文本内容 |
| void copyFile(File source, File dest) | 本地复制文件 |
| void compressGZIP(File input, File output) | gzip 压缩文件,输出到指定路径 |
| byte[] compressGZIP(byte[] data) | gzip 压缩字节码,返回新字节码 |
| byte[] compressGZIP(String content) | gzip 压缩文本为字节码,默认 utf-8 编码 |
| byte[] compressGZIP(String content, Charset charset) | gzip 指定编码压缩文本 |
13.11 TagKit 标签工具类
| long getValue(int position) | 获取标记值 |
|---|---|
| long remove(long value, int position) | 删除标记 |
| boolean contain(long value, int position) | 是否包含标记 |
| long add(long value, int position) | 添加标记 |
| boolean isHas(long value, long data) | 是否包含指定值的标记 |
13.12 PatternKit 正则表达式工具类
| boolean isStrongPassword(String password) | 是否是强类型密码 |
|---|---|
| boolean isSnowId(String str) | 是否是对象雪花 ID |
| boolean isEmail(String email) | 是否邮箱 |
| boolean isIdCard18(String idCard) | 是否 18 位身份证 |
| boolean isIdCard15(String idCard) | 是否 15 位身份证 |
| boolean isImage(String suffix) | 是否图片 |
| boolean isMobile(String mobile) | 是否手机号 |
| boolean isPhone(String phone) | 是否电话号码 |
| boolean isDigit(String digit) | 是否数字字符串 |
| boolean isDecimals(String decimals) | 是否浮点字符串 |
| boolean isBlankSpace(String blankSpace) | 是否空格 |
| boolean isChinese(String chinese) | 是否中文 |
| boolean isNumber(String str) | 是否数字 |
| boolean isNumberOrChar(String str) | 是否数字或英文字符串 |
13.13 ExceptionTracker 异常跟踪类,自动发送异常消息
同类型异常,3 分钟内,发生 10 次,自动触发消息通知
消息模板名在配置文件中设置,配置名称 exception
消息声明需要在 kooteam 中设置
确定性异常:
java
ExceptionTracker.report("test", 10, 180, "系统发生异常了");未知异常:
java
public ZenResult test(ZenData data) {
try (ExceptionTracker tracker = new ExceptionTracker("test",10,180)) {
tracker.setMessage("系统发生异常了");
return ZenResult.success();
}
}13.14 TimeTracker 程序运行计时器
程序运行超过指定时间,自动输出运行时长日志
时间单位毫秒
示例:
java
try (TimeTracker timeTracker = new TimeTracker(url, 300)) {
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() == 200) return response.body();
else System.out.printf("%s 调用异常 %n响应内容: %s", url, response.body());
} catch (IOException | InterruptedException e) {
System.out.printf("%s 调用异常 %n响应内容: %s", url, e.getMessage());
}