在拦截器里存储接口访问次数:
(两个小问题:1. 统计计数的并行化方案:线程池选择/其他并行化方法;2. 没有统计接口的响应时间,在现有方法下如何直接添加,必须在额外添加一个线程安全的Map进行计算吗?)
package com.highmall.core.interceptor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.util.concurrent.AtomicLongMap;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.plugin.activerecord.Db;
/**
*
* @author qiww
*
*/
public class ModuleStatisticsInterceptor implements Interceptor {
ExecutorService saveAPICountsToDB = Executors.newFixedThreadPool(100);//newSingleThreadExecutor();
public static AtomicLongMap<String> apiCallCountsAtomicLongMap = AtomicLongMap.create();
/**
* 统计接口调用次数
*/
@Override
public void intercept(Invocation inv) {
//lambda表达式
saveAPICountsToDB.execute(() -> {
String controllerPath = inv.getControllerKey();
String key = controllerPath.substring(1, controllerPath.length()) + ":" + inv.getMethodName();
apiCallCountsAtomicLongMap.incrementAndGet(key);
});
inv.invoke();
}
public static void saveAPICallCountsToDB(){
for (String key : apiCallCountsAtomicLongMap.asMap().keySet()) {
if (apiCallCountsAtomicLongMap.get(key) <= 0) {
continue;
}
String controllerName = key.split(":")[0];
String methodName = key.split(":")[1];
int n = Db.update("UPDATE yw_module_statistics SET count = count + ? WHERE controllerName = ? AND methodName = ? LIMIT 1",
apiCallCountsAtomicLongMap.getAndAdd(key, -apiCallCountsAtomicLongMap.get(key)),
controllerName,
methodName);
if (n == 0) {
//只有首次访问命中
Db.update("INSERT INTO yw_module_statistics(controllerName, methodName, count, state) values(?,?, 1, 1)");
}
}
}
}周期性的存储次数到数据库中:
package com.highmall.core.quartz;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import com.highmall.core.interceptor.ModuleStatisticsInterceptor;
import com.jfinal.kit.LogKit;
/**
* 将接口访问次数写入数据库
* @author qiww
*
*/
public class Task_SaveAPICallCountsToDB implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
LogKit.info("将接口访问次数写入数据库");
ModuleStatisticsInterceptor.saveAPICallCountsToDB();
}
}数据库存储样例:

有几个小建议:
1:saveAPICountsToDB.execute 中使用 lambda 会更简洁,省好几行代码
2:saveAPICallCountsToDB 中的 findFirst 方法中使用 sql 要添加一个 limit 1 性能才更好
3:saveAPICallCountsToDB 中的逻辑可以改进一下,性能会更高:
int n = Db.update("UPDATE yw_module_statistics SET count = count + ? WHERE controllerName = ? AND methodName = ? LIMIT 1", ...)
if (n == 0) {
Db.update("INSERT INTO....);
}
上面的逻辑是,默认一上来就执行 update ,得到的返回值 n 如果是 0,证明记录不存在,这个时间再执行 insert into
这样做的原因是,绝大部分情况下都会命中前一个 update,只有 action 在第一次被请求时才会命中后面的 insert into,避免掉了每次的 findFirts 判断是非常划算的