@@ -0,0 +1,8 @@ | |||||
20210710 | |||||
1. 定时器手动触发 (done) | |||||
2. 优化BeanReadUtils的TableName预先进行转换 | |||||
2. updateEntity支持悲观锁,并能抛出异常 | |||||
3. 缓存支持自定义 key | |||||
4. 序列号生成器 | |||||
5. redis配置database(0~15) (done) |
@@ -0,0 +1,37 @@ | |||||
package cc.smtweb.framework.core; | |||||
import cc.smtweb.framework.core.cache.CacheManager; | |||||
import cc.smtweb.framework.core.db.impl.DatabaseUtil; | |||||
import cc.smtweb.framework.core.mvc.controller.scan.ApplicationScanner; | |||||
import cc.smtweb.framework.core.systask.TaskStartEvent; | |||||
import cc.smtweb.framework.core.systask.WebStartedEvent; | |||||
import lombok.SneakyThrows; | |||||
import org.springframework.boot.context.event.ApplicationStartedEvent; | |||||
import org.springframework.context.ApplicationListener; | |||||
import org.springframework.context.ConfigurableApplicationContext; | |||||
import org.springframework.stereotype.Component; | |||||
/** | |||||
* 执行接口扫描任务 | |||||
*/ | |||||
@Component | |||||
public class CoreApplicationStartedListener implements ApplicationListener<ApplicationStartedEvent> { | |||||
@SneakyThrows | |||||
@Override | |||||
public void onApplicationEvent(ApplicationStartedEvent event) { | |||||
System.out.println("onApplicationEvent============="); | |||||
ConfigurableApplicationContext applicationContext = event.getApplicationContext(); | |||||
applicationContext.publishEvent(new TaskStartEvent()); | |||||
//包扫描 | |||||
ApplicationScanner.scan(applicationContext); | |||||
//初始化数据库 | |||||
new DatabaseUtil(true, false).checkDb(); | |||||
//初始化缓存 | |||||
CacheManager.getIntance().init(); | |||||
// 通知 controller 正式使用 | |||||
applicationContext.publishEvent(new WebStartedEvent()); | |||||
System.out.println("start end============="); | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
package cc.smtweb.framework.core; | |||||
import cc.smtweb.framework.core.db.jdbc.IdGenerator; | |||||
import cc.smtweb.framework.core.mvc.config.ControllerConfig; | |||||
import org.springframework.beans.factory.annotation.Value; | |||||
import org.springframework.context.annotation.Bean; | |||||
import org.springframework.context.annotation.ComponentScan; | |||||
import org.springframework.context.annotation.Configuration; | |||||
import org.springframework.scheduling.annotation.EnableScheduling; | |||||
/** | |||||
* @author kevin | |||||
*/ | |||||
@Configuration | |||||
@ComponentScan | |||||
@EnableScheduling | |||||
public class CoreAutoConfiguration { | |||||
/** | |||||
* ID生成器的分步式机器码(1-1023) | |||||
*/ | |||||
@Value("${smtweb.machine-id}") | |||||
private int machineId; | |||||
@Bean | |||||
public IdGenerator idGenerator() { | |||||
return new IdGenerator(machineId); | |||||
} | |||||
@Bean | |||||
public ControllerConfig coreControllerConfig() { | |||||
return new ControllerConfig("core", "cc.smtweb.framework.core"); | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
package cc.smtweb.framework.core.annotation; | |||||
import java.lang.annotation.ElementType; | |||||
import java.lang.annotation.Retention; | |||||
import java.lang.annotation.RetentionPolicy; | |||||
import java.lang.annotation.Target; | |||||
/** | |||||
* 配置在@SwService中的函数,对应API请求,默认公用函数不用配置拦截器的函数, | |||||
* 也可以作为拦截实现的基类 | |||||
* @author kevin | |||||
*/ | |||||
@Retention(RetentionPolicy.RUNTIME) | |||||
@Target({ElementType.METHOD}) | |||||
public @interface SwAction { | |||||
/** | |||||
* 重写API请求地址,不配置使用: 服务类地址 + “/” + 函数名 | |||||
* @return API请求地址 | |||||
*/ | |||||
String value() default ""; | |||||
} |
@@ -0,0 +1,26 @@ | |||||
package cc.smtweb.framework.core.annotation; | |||||
import java.lang.annotation.ElementType; | |||||
import java.lang.annotation.Retention; | |||||
import java.lang.annotation.RetentionPolicy; | |||||
import java.lang.annotation.Target; | |||||
/** | |||||
* 被该注释修饰的类提供缓存服务 | |||||
* | |||||
* @author kevin | |||||
*/ | |||||
@Retention(RetentionPolicy.RUNTIME) | |||||
@Target({ElementType.TYPE}) | |||||
public @interface SwCache { | |||||
//唯一标识 | |||||
String ident(); | |||||
//标题,展示用 | |||||
String title(); | |||||
//依赖的缓存ident,多个用英文逗号分隔 | |||||
String depends() default ""; | |||||
//是否懒加载 | |||||
boolean lazy() default false; | |||||
//失效时间,单位分钟 | |||||
long timeout() default 0; | |||||
} |
@@ -0,0 +1,21 @@ | |||||
package cc.smtweb.framework.core.annotation; | |||||
import java.lang.annotation.ElementType; | |||||
import java.lang.annotation.Retention; | |||||
import java.lang.annotation.RetentionPolicy; | |||||
import java.lang.annotation.Target; | |||||
/** | |||||
* 被该注释修饰的方法对应了外键表名和字段名,名称可选 | |||||
* @author kevin | |||||
*/ | |||||
@Retention(RetentionPolicy.RUNTIME) | |||||
@Target({ElementType.FIELD}) | |||||
public @interface SwColumnForeign { | |||||
// 外键表名 | |||||
String table() default ""; | |||||
// ID字段名 | |||||
String id() default ""; | |||||
// 唯一名称字段名 | |||||
String code() default ""; | |||||
} |
@@ -0,0 +1,16 @@ | |||||
package cc.smtweb.framework.core.annotation; | |||||
import java.lang.annotation.*; | |||||
/** | |||||
* 路径注解 | |||||
* @author kevin | |||||
* | |||||
*/ | |||||
@Target( { ElementType.PARAMETER, ElementType.FIELD}) | |||||
@Retention(RetentionPolicy.RUNTIME) | |||||
@Documented | |||||
public @interface SwHeaderParam { | |||||
String value() default ""; | |||||
} |
@@ -0,0 +1,16 @@ | |||||
package cc.smtweb.framework.core.annotation; | |||||
import java.lang.annotation.*; | |||||
/** | |||||
* 路径注解 | |||||
* @author kevin | |||||
* | |||||
*/ | |||||
@Target( { ElementType.PARAMETER, ElementType.FIELD}) | |||||
@Retention(RetentionPolicy.RUNTIME) | |||||
@Documented | |||||
public @interface SwPathParam { | |||||
// String value() default ""; | |||||
} |
@@ -0,0 +1,16 @@ | |||||
package cc.smtweb.framework.core.annotation; | |||||
import java.lang.annotation.ElementType; | |||||
import java.lang.annotation.Retention; | |||||
import java.lang.annotation.RetentionPolicy; | |||||
import java.lang.annotation.Target; | |||||
/** | |||||
* 被该注释修饰的类提供控制器服务 | |||||
* @author kevin | |||||
*/ | |||||
@Retention(RetentionPolicy.RUNTIME) | |||||
@Target({ElementType.TYPE}) | |||||
public @interface SwService { | |||||
String value() default ""; | |||||
} |
@@ -0,0 +1,17 @@ | |||||
package cc.smtweb.framework.core.annotation; | |||||
import java.lang.annotation.ElementType; | |||||
import java.lang.annotation.Retention; | |||||
import java.lang.annotation.RetentionPolicy; | |||||
import java.lang.annotation.Target; | |||||
/** | |||||
* 被该注释修饰的类对应了数据库表名(库+表的形式,如 sw_user.sys_user) | |||||
* @author kevin | |||||
*/ | |||||
@Retention(RetentionPolicy.RUNTIME) | |||||
@Target({ElementType.TYPE}) | |||||
public @interface SwTable { | |||||
/** 库名+表名 */ | |||||
String value() default ""; | |||||
} |
@@ -0,0 +1,54 @@ | |||||
package cc.smtweb.framework.core.cache; | |||||
import cc.smtweb.framework.core.common.SwConsts; | |||||
import cc.smtweb.framework.core.db.DbEngine; | |||||
import cc.smtweb.framework.core.db.EntityDao; | |||||
import cc.smtweb.framework.core.db.impl.DefaultEntity; | |||||
import java.util.List; | |||||
/** | |||||
* Created by Akmm at 2022/6/16 15:53 | |||||
*/ | |||||
public class AbstractEntityCache<T extends DefaultEntity> extends AbstractCache<T> { | |||||
@Override | |||||
protected String getId(T bean) { | |||||
return String.valueOf(bean.getEntityId()); | |||||
} | |||||
private String getCacheKey(T bean, String fields) { | |||||
String[] fs = fields.split(","); | |||||
String ret = ""; | |||||
for (String f: fs) { | |||||
ret += SwConsts.SPLIT_CHAR + bean.getStr(f); | |||||
} | |||||
return ret.substring(SwConsts.SPLIT_CHAR.length()); | |||||
} | |||||
/** | |||||
* 注册其他key的List缓存,如tree的children | |||||
* | |||||
* @param key | |||||
* @param fields | |||||
*/ | |||||
protected void regList(String key, String fields) { | |||||
regList(key, bean -> getCacheKey(bean, fields)); | |||||
} | |||||
/** | |||||
* 注册其他key的Map缓存,如按code缓存 | |||||
* | |||||
* @param key | |||||
* @param fields | |||||
*/ | |||||
protected void regMap(String key, String fields) { | |||||
regMap(key, bean -> getCacheKey(bean, fields)); | |||||
} | |||||
@Override | |||||
protected List<T> loadAll() { | |||||
EntityDao<T> dao = DbEngine.getInstance().findDao(pTypeClass); | |||||
return dao.query(); | |||||
} | |||||
} |
@@ -0,0 +1,51 @@ | |||||
package cc.smtweb.framework.core.cache; | |||||
import java.util.Map; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
/** | |||||
* Created by Akmm at 2022/3/14 10:42 | |||||
*/ | |||||
public class SessionCacheFactory { | |||||
private static SessionCacheFactory INSTANCE = null; | |||||
private Map<Long, SessionCache> buffer = new ConcurrentHashMap<>(); | |||||
private SessionCacheFactory() { | |||||
} | |||||
/*获取单例*/ | |||||
public static SessionCacheFactory getInstance() { | |||||
if (INSTANCE == null) { | |||||
synchronized (SessionCacheFactory.class) { | |||||
if (INSTANCE == null) { | |||||
INSTANCE = new SessionCacheFactory(); | |||||
} | |||||
} | |||||
} | |||||
return INSTANCE; | |||||
} | |||||
//得到用户缓存对象 | |||||
public SessionCache getUserCache(long userId) { | |||||
return getUserCache(userId, 0L); | |||||
} | |||||
public SessionCache getUserCache(long userId, long timeout) { | |||||
SessionCache cache; | |||||
cache = buffer.get(userId); | |||||
if (cache == null) { | |||||
cache = buffer.get(userId); | |||||
if (cache != null) return cache; | |||||
cache = new SessionCache(timeout); | |||||
buffer.put(userId, cache); | |||||
} | |||||
return cache; | |||||
} | |||||
//删除用户缓存 | |||||
public void remove(String userId) { | |||||
buffer.remove(userId); | |||||
} | |||||
} |
@@ -0,0 +1,300 @@ | |||||
package cc.smtweb.framework.core.cache.redis; | |||||
import cc.smtweb.framework.core.util.CommUtil; | |||||
import io.lettuce.core.KeyValue; | |||||
import io.lettuce.core.SetArgs; | |||||
import io.lettuce.core.api.sync.RedisCommands; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import java.io.Serializable; | |||||
import java.nio.charset.StandardCharsets; | |||||
import java.util.ArrayList; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
/** | |||||
* @author kevin | |||||
*/ | |||||
@Slf4j | |||||
public class RedisConnection { | |||||
public static final int DEL_COUNT = 200; | |||||
private RedisCommands<byte[], byte[]> redis; | |||||
public RedisConnection(RedisCommands<byte[], byte[]> redisCommands) { | |||||
this.redis = redisCommands; | |||||
} | |||||
/** | |||||
* 获取指定key对应的值,并更新超时时间 | |||||
* | |||||
* @param key 关键字KEY | |||||
* @return byte[] 对象 | |||||
*/ | |||||
public byte[] get(String key) { | |||||
byte[] binKey = getBytes(key); | |||||
return redis.get(binKey); | |||||
} | |||||
// public <T extends Serializable> T get(String key, Class<T> clazz) { | |||||
// byte[] b = get(key); | |||||
// if (b != null) { | |||||
// return this.readObject(b, clazz); | |||||
// } | |||||
// | |||||
// return null; | |||||
// } | |||||
public boolean expire(String key, int expireSec) { | |||||
byte[] bkey = getBytes(key); | |||||
return redis.expire(bkey, expireSec); | |||||
} | |||||
/** | |||||
* 设置指定key对应的值,并更新超时时间 | |||||
* | |||||
* @param key 关键字KEY | |||||
* @param value 值 | |||||
* @param seconds 超时删除时间(秒) | |||||
* @return boolean 是否设置成功 | |||||
*/ | |||||
public boolean set(byte[] key, byte[] value, int seconds) { | |||||
if (value != null) { | |||||
Boolean result = Boolean.FALSE; | |||||
String ret; | |||||
if (seconds > 0) { | |||||
ret = redis.setex(key, seconds, value); | |||||
} else { | |||||
ret = redis.set(key, value); | |||||
} | |||||
if ("OK".equals(ret)) { | |||||
result = Boolean.TRUE; | |||||
} | |||||
return result; | |||||
} else { | |||||
Long ret = redis.del(key); | |||||
return (ret != null && ret == 1L); | |||||
} | |||||
} | |||||
public boolean setnx(byte[] key, byte[] value, int seconds) { | |||||
if (value != null) { | |||||
Boolean result = Boolean.FALSE; | |||||
String ret; | |||||
if (seconds > 0) { | |||||
ret = redis.set(key, value, new SetArgs().ex(seconds).nx()); | |||||
if ("OK".equals(ret)) { | |||||
result = Boolean.TRUE; | |||||
} | |||||
} else { | |||||
result = redis.setnx(key, value); | |||||
} | |||||
return result; | |||||
} else { | |||||
Long ret = redis.del(key); | |||||
return (ret != null && ret == 1L); | |||||
} | |||||
} | |||||
public <T extends Serializable> boolean setnx(String key, T obj, int seconds) { | |||||
return setnx(getBytes(key), CommUtil.writeObject(obj), seconds); | |||||
} | |||||
private byte[] getBytes(String key) { | |||||
return key.getBytes(StandardCharsets.UTF_8); | |||||
} | |||||
/** | |||||
* 获取指定key对应的值,并更新超时时间 | |||||
* | |||||
* @param keys 关键字KEY | |||||
* @return byte[] 对象 | |||||
*/ | |||||
public List<KeyValue<byte[], byte[]>> mget(String[] keys, int expireSec) { | |||||
byte[][] bkeys = new byte[keys.length][]; | |||||
for (int i = 0; i < keys.length; i++) { | |||||
bkeys[i] = getBytes(keys[i]); | |||||
} | |||||
List<KeyValue<byte[], byte[]>> result = redis.mget(bkeys); | |||||
if (result != null && expireSec > 0) { | |||||
redis.expire(bkeys[0], expireSec); | |||||
} | |||||
return result; | |||||
} | |||||
/** | |||||
* 获取指定key里指定的域对应的值。 此时,key对应的存储对象是map。 | |||||
* | |||||
* @param key redis关键字KEY | |||||
* @param field map的KEY值 | |||||
* @return byte[] 对象 | |||||
*/ | |||||
public byte[] hGet(String key, String field) { | |||||
return redis.hget(getBytes(key), getBytes(field)); | |||||
} | |||||
public List<byte[]> hVals(String key) { | |||||
return redis.hvals(getBytes(key)); | |||||
} | |||||
public boolean exists(String key) { | |||||
return redis.exists(getBytes(key)) == 1; | |||||
} | |||||
static byte[] writeObject(Serializable obj) { | |||||
return CommUtil.writeObject(obj); | |||||
} | |||||
static <T extends Serializable> T readObject(byte[] value, Class<T> clazz) { | |||||
return CommUtil.readObject(value, clazz); | |||||
} | |||||
public <T extends Serializable> boolean set(String key, T obj, int seconds) { | |||||
return set(getBytes(key), CommUtil.writeObject(obj), seconds); | |||||
} | |||||
public boolean del(String key) { | |||||
Long ret = redis.del(getBytes(key)); | |||||
return (ret != null && ret > 0); | |||||
} | |||||
public Long delKeys(String... keys) { | |||||
// Long[] result = new Long[keys.length]; | |||||
byte[][] batchKey = new byte[keys.length][]; | |||||
for (int i = 0; i < keys.length; i++) { | |||||
batchKey[i] = getBytes(keys[i]); | |||||
} | |||||
return redis.del(batchKey); | |||||
} | |||||
public boolean hSet(String key, byte[] field, byte[] value) { | |||||
return redis.hset(getBytes(key), field, value); | |||||
} | |||||
private boolean lhmSet(String key, Map<byte[], byte[]> values) { | |||||
String ret = redis.hmset(getBytes(key), values); | |||||
return "OK".equals(ret); | |||||
} | |||||
public <T extends Serializable> boolean hmSet(String key, Map<String, T> values) { | |||||
boolean ret = true; | |||||
if (values == null || values.isEmpty()) { | |||||
return ret; | |||||
} | |||||
final int size = 200; | |||||
Map<byte[], byte[]> map = new HashMap<>(size); | |||||
int count = 0; | |||||
for (Map.Entry<String, T> e : values.entrySet()) { | |||||
map.put(getBytes(e.getKey()), CommUtil.writeObject(e.getValue())); | |||||
if (++count >= size) { | |||||
ret = ret && lhmSet(key, map); | |||||
count = 0; | |||||
map.clear(); | |||||
} | |||||
} | |||||
if (count > 0) { | |||||
ret = ret && lhmSet(key, map); | |||||
} | |||||
return ret; | |||||
} | |||||
/** | |||||
* 获取指定key里指定的域对应的值,并转换为指定对象 此时,key对应的存储对象是map。 | |||||
* | |||||
* @param key redis关键字KEY | |||||
* @param field map的KEY值 | |||||
* @param clazz 对象存储类型 | |||||
* @return T 对象 | |||||
*/ | |||||
public <T extends Serializable> T hGet(String key, String field, Class<T> clazz) { | |||||
return CommUtil.readObject(hGet(key, field), clazz); | |||||
} | |||||
public boolean hSet(String key, String field, Serializable value) { | |||||
return hSet(key, getBytes(field), CommUtil.writeObject(value)); | |||||
} | |||||
public long hdel(String key, String field) { | |||||
return redis.hdel(getBytes(key), getBytes(field)); | |||||
} | |||||
public Long ttl(String key) { | |||||
return redis.ttl(getBytes(key)); | |||||
} | |||||
public <T extends Serializable> T get(String key, Class<T> clazz) { | |||||
return CommUtil.readObject(get(key), clazz); | |||||
} | |||||
public Map<byte[], byte[]> hGetAll(String key) { | |||||
return redis.hgetall(getBytes(key)); | |||||
} | |||||
/** | |||||
* 模糊查询keys,影响性能,谨慎使用 | |||||
* | |||||
* @param keyLike 支持*表达模糊搜索 | |||||
* @return 查询到的列表 | |||||
*/ | |||||
public List<String> matchKeys(String keyLike) { | |||||
List<byte[]> list = redis.keys(getBytes(keyLike)); | |||||
if (list != null && !list.isEmpty()) { | |||||
List<String> result = new ArrayList<>(list.size()); | |||||
for (byte[] b : list) { | |||||
result.add(new String(b, StandardCharsets.UTF_8)); | |||||
} | |||||
return result; | |||||
} | |||||
return null; | |||||
} | |||||
/** | |||||
* 模糊删除keys,影响性能,谨慎使用 | |||||
* | |||||
* @param keyLike 支持*表达模糊搜索 | |||||
* @return 成功删除的条数 | |||||
*/ | |||||
public Long delMatchKeys(String keyLike) { | |||||
long result = 0; | |||||
List<byte[]> list = redis.keys(getBytes(keyLike)); | |||||
if (list != null) { | |||||
int size = list.size(); | |||||
for (int i = 0; i < size; i += DEL_COUNT) { | |||||
int len = DEL_COUNT; | |||||
if (i + len > size) { | |||||
len = size - i; | |||||
} | |||||
Long ret = delKeys(list, i, len); | |||||
if (ret != null) { | |||||
result += ret; | |||||
} | |||||
} | |||||
} | |||||
return result; | |||||
} | |||||
private Long delKeys(List<byte[]> keys, int start, int len) { | |||||
byte[][] batchKey = new byte[len][]; | |||||
for (int i = 0; i < len; i++) { | |||||
batchKey[i] = keys.get(i + start); | |||||
} | |||||
return redis.del(batchKey); | |||||
} | |||||
} |
@@ -0,0 +1,220 @@ | |||||
package cc.smtweb.framework.core.cache.redis; | |||||
import cc.smtweb.framework.core.exception.SwException; | |||||
import cc.smtweb.framework.core.systask.SysTaskManager; | |||||
import cc.smtweb.framework.core.util.JsonUtil; | |||||
import cc.smtweb.framework.core.util.SpringUtil; | |||||
import io.lettuce.core.RedisClient; | |||||
import io.lettuce.core.RedisURI; | |||||
import io.lettuce.core.api.StatefulRedisConnection; | |||||
import io.lettuce.core.api.sync.RedisCommands; | |||||
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; | |||||
import io.lettuce.core.pubsub.api.sync.RedisPubSubCommands; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.apache.commons.pool2.impl.GenericObjectPool; | |||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig; | |||||
import org.springframework.beans.factory.DisposableBean; | |||||
import org.springframework.context.ApplicationContext; | |||||
import javax.annotation.PreDestroy; | |||||
import java.io.Serializable; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.function.Function; | |||||
/** | |||||
* Redis管理器 | |||||
* | |||||
* @author kevin | |||||
*/ | |||||
@Slf4j | |||||
public class RedisManager implements DisposableBean { | |||||
// SESSION | |||||
public static final String PREFIX_SESSION = "SID"; | |||||
// 定时器锁 | |||||
public static final String PREFIX_TIMER = "TIM"; | |||||
/** | |||||
* UserSession 超时时间 | |||||
*/ | |||||
public static final int SESSION_EXPIRE_SEC = 30 * 60; | |||||
/** | |||||
* 订阅发布的主题 | |||||
*/ | |||||
private static final String SCRIBE_SYSTEM = "SW_SYSTEM_"; | |||||
// private static final String SCRIBE_CACHED = "SW_CACHED"; | |||||
private final RedisSysTask redisSysTask; | |||||
private RedisClient redisClient; | |||||
private GenericObjectPool<StatefulRedisConnection<byte[], byte[]>> pool; | |||||
// 定义通道名称 | |||||
private final String channel; | |||||
public static RedisManager getInstance() { | |||||
return SpringUtil.getBean(RedisManager.class); | |||||
} | |||||
/** | |||||
* 初始化Redis连接池 | |||||
*/ | |||||
public RedisManager(final ApplicationContext applicationContext, SysTaskManager sysTaskManager, RedisURI redisUri) { | |||||
// this.applicationContext = applicationContext; | |||||
redisClient = RedisClient.create(redisUri); | |||||
channel = SCRIBE_SYSTEM + redisUri.getDatabase() + "_" + redisUri.getClientName(); | |||||
GenericObjectPoolConfig config = new GenericObjectPoolConfig(); | |||||
config.setTestWhileIdle(true); | |||||
pool = new GenericObjectPool<>(new RedisPooledObjectFactory(redisClient), config); | |||||
// pool = ConnectionPoolSupport.createGenericObjectPool( | |||||
// () -> redisClient.connect(ByteArrayCodec.INSTANCE), | |||||
// new GenericObjectPoolConfig(), false); | |||||
redisSysTask = new RedisSysTask(applicationContext, redisClient, SCRIBE_SYSTEM + redisUri.getDatabase() + "_*", channel); | |||||
sysTaskManager.add(redisSysTask); | |||||
} | |||||
@PreDestroy | |||||
public void shutdown() { | |||||
pool.close(); | |||||
redisSysTask.close(); | |||||
redisClient.shutdown(); | |||||
} | |||||
/** | |||||
* 执行redis命令 | |||||
*/ | |||||
public <T> T command(Function<RedisConnection, T> handler) { | |||||
// StatefulRedisConnection<byte[], byte[]> connection = redisClient.connect(ByteArrayCodec.INSTANCE); | |||||
StatefulRedisConnection<byte[], byte[]> connection = null; | |||||
try { | |||||
connection = pool.borrowObject(); | |||||
} catch (Exception e) { | |||||
throw new SwException(e); | |||||
} | |||||
try { | |||||
RedisCommands<byte[], byte[]> redisCommands = connection.sync(); | |||||
RedisConnection redis = new RedisConnection(redisCommands); | |||||
return handler.apply(redis); | |||||
} finally { | |||||
pool.returnObject(connection); | |||||
} | |||||
} | |||||
public boolean expire(String key, int expireSec) { | |||||
return command(redis -> { | |||||
return redis.expire(key, expireSec); | |||||
}); | |||||
} | |||||
/** | |||||
* 设置指定key对应的值,并更新超时时间 | |||||
* | |||||
* @param key 关键字KEY | |||||
* @param value 值 | |||||
* @param seconds 超时删除时间(秒) | |||||
* @return boolean 是否设置成功 | |||||
*/ | |||||
public boolean set(byte[] key, byte[] value, int seconds) { | |||||
return command(redis -> redis.set(key, value, seconds)); | |||||
} | |||||
public boolean setnx(byte[] key, byte[] value, int seconds) { | |||||
return command(redis -> redis.setnx(key, value, seconds)); | |||||
} | |||||
public <T extends Serializable> boolean setnx(String key, T obj, int seconds) { | |||||
return command(redis -> redis.setnx(key, obj, seconds)); | |||||
} | |||||
public <T extends Serializable> boolean set(String key, T obj, int seconds) { | |||||
return command(redis -> redis.set(key, obj, seconds)); | |||||
} | |||||
public boolean del(String key) { | |||||
return command(redis -> redis.del(key)); | |||||
} | |||||
public Long delKeys(String... keys) { | |||||
return command(redis -> redis.delKeys(keys)); | |||||
} | |||||
public boolean hSet(String key, byte[] field, byte[] value) { | |||||
return command(redis -> redis.hSet(key, field, value)); | |||||
} | |||||
public <T extends Serializable> boolean hmSet(String key, Map<String, T> values) { | |||||
return command(redis -> redis.hmSet(key, values)); | |||||
} | |||||
/** | |||||
* 获取指定key里指定的域对应的值,并转换为指定对象 此时,key对应的存储对象是map。 | |||||
* | |||||
* @param key redis关键字KEY | |||||
* @param field map的KEY值 | |||||
* @param clazz 对象存储类型 | |||||
* @return T 对象 | |||||
*/ | |||||
public <T extends Serializable> T hGet(String key, String field, Class<T> clazz) { | |||||
return command(redis -> redis.hGet(key, field, clazz)); | |||||
} | |||||
public List<byte[]> hVals(String key) { | |||||
return command(redis -> redis.hVals(key)); | |||||
} | |||||
public boolean hSet(String key, String field, Serializable value) { | |||||
return command(redis -> redis.hSet(key, field, value)); | |||||
} | |||||
public long hdel(String key, String field) { | |||||
return command(redis -> redis.hdel(key, field)); | |||||
} | |||||
public Long ttl(String key) { | |||||
return command(redis -> redis.ttl(key)); | |||||
} | |||||
public boolean exists(String key) { | |||||
return command(redis -> redis.exists(key)); | |||||
} | |||||
// 发布系统消息 | |||||
public void publish(RedisBroadcastEvent message) { | |||||
try (StatefulRedisPubSubConnection<String, String> connection = redisClient.connectPubSub()) { | |||||
RedisPubSubCommands<String, String> sync = connection.sync(); | |||||
sync.publish(channel, JsonUtil.encodeString(message)); | |||||
} | |||||
} | |||||
public Map<byte[], byte[]> hGetAll(String key) { | |||||
return command(redis -> redis.hGetAll(key)); | |||||
} | |||||
public <T extends Serializable> T get(String key, Class<T> clazz) { | |||||
return command(redis -> redis.get(key, clazz)); | |||||
} | |||||
public List<String> matchKeys(String keyLike) { | |||||
return command(redis -> redis.matchKeys(keyLike)); | |||||
} | |||||
public Long delMatchKeys(String keyLike) { | |||||
return command(redis -> redis.delMatchKeys(keyLike)); | |||||
} | |||||
public byte[] writeObject(Serializable entity) { | |||||
return RedisConnection.writeObject(entity); | |||||
} | |||||
public <T extends Serializable> T readObject(byte[] value, Class<T> clazz) { | |||||
return RedisConnection.readObject(value, clazz); | |||||
} | |||||
@Override | |||||
public void destroy() throws Exception { | |||||
this.shutdown(); | |||||
} | |||||
} |
@@ -0,0 +1,64 @@ | |||||
package cc.smtweb.framework.core.cache.redis.config; | |||||
import cc.smtweb.framework.core.cache.redis.RedisManager; | |||||
import cc.smtweb.framework.core.systask.SysTaskManager; | |||||
import io.lettuce.core.RedisURI; | |||||
import org.apache.commons.lang3.StringUtils; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.beans.factory.annotation.Value; | |||||
import org.springframework.context.ApplicationContext; | |||||
import org.springframework.context.annotation.Bean; | |||||
import org.springframework.context.annotation.Configuration; | |||||
/** | |||||
* 〈redis配置〉 | |||||
* | |||||
* @author kevin | |||||
* @since 1.0.0 | |||||
*/ | |||||
@Configuration | |||||
public class RedisConfig { | |||||
@Value("${spring.redis.name:127.0.0.1}") | |||||
private String clientName; | |||||
@Value("${spring.redis.host:127.0.0.1}") | |||||
private String redisHost; | |||||
@Value("${spring.redis.port:6379}") | |||||
private int redisPort; | |||||
@Value("${spring.redis.username:}") | |||||
private String redisUserName; | |||||
@Value("${spring.redis.password:}") | |||||
private String redisPassword; | |||||
@Value("${spring.redis.database:}") | |||||
private String redisDb; | |||||
@Autowired | |||||
private ApplicationContext applicationContext; | |||||
@Autowired | |||||
private SysTaskManager sysTaskManager; | |||||
@Bean | |||||
public RedisManager redisManager() { | |||||
RedisURI redisUri = RedisURI.create (redisHost, redisPort); | |||||
if (StringUtils.isNotBlank(clientName)) { | |||||
redisUri.setClientName(clientName); | |||||
} | |||||
if (StringUtils.isNotBlank(redisDb)) { | |||||
redisUri.setDatabase(Integer.parseInt(redisDb)); | |||||
} | |||||
if (StringUtils.isNotBlank(redisUserName)) { | |||||
redisUri.setUsername(redisUserName.trim()); | |||||
} | |||||
if (StringUtils.isNotBlank(redisPassword)) { | |||||
redisUri.setPassword((CharSequence)redisPassword.trim()); | |||||
} | |||||
return new RedisManager(applicationContext, sysTaskManager, redisUri); | |||||
} | |||||
} |
@@ -0,0 +1,290 @@ | |||||
package cc.smtweb.framework.core.common; | |||||
import org.apache.commons.lang3.StringUtils; | |||||
import java.sql.Types; | |||||
/** | |||||
* Created by Akmm at 2022/3/23 9:39 | |||||
* 系统的一些枚举变量 | |||||
*/ | |||||
public interface SwEnum { | |||||
/** | |||||
* Created by Akmm at 2022/2/9 10:01 | |||||
* 字段业务类别 | |||||
*/ | |||||
class FieldTypeBean extends AbstractEnum.IntEnumBean { | |||||
public String dataType; | |||||
public int notNull; | |||||
public FieldTypeBean(Integer value, String name, String dataType) { | |||||
super(value, name); | |||||
this.dataType = dataType; | |||||
this.notNull = 0; | |||||
} | |||||
public FieldTypeBean(Integer value, String name, String dataType, int notNull) { | |||||
super(value, name); | |||||
this.dataType = dataType; | |||||
this.notNull = notNull; | |||||
} | |||||
} | |||||
class FieldType extends AbstractEnum<Integer, FieldTypeBean> { | |||||
public static FieldType instance = new FieldType(); | |||||
public static FieldTypeBean ID = instance.addEnum(1, "主键", DataType.ID.value,1); | |||||
public static FieldTypeBean CODE = instance.addEnum(2, "编码字段", DataType.CODE.value,1); | |||||
public static FieldTypeBean NAME = instance.addEnum(3, "名称字段", DataType.NAME.value,1); | |||||
public static FieldTypeBean PARENT_ID = instance.addEnum(4, "父ID", DataType.ID.value); | |||||
public static FieldTypeBean LEVEL_CODE = instance.addEnum(5, "级次码", DataType.CODE.value); | |||||
public static FieldTypeBean ORDER = instance.addEnum(6, "排序字段", ""); | |||||
public static FieldTypeBean CREATE_USER = instance.addEnum(7, "创建人", DataType.ID.value); | |||||
public static FieldTypeBean CREATE_TIME = instance.addEnum(8, "创建时间", DataType.DATETIME.value); | |||||
public static FieldTypeBean UPDATE_USER = instance.addEnum(9, "更新人", DataType.ID.value); | |||||
public static FieldTypeBean LAST_TIME = instance.addEnum(10, "更新时间", DataType.DATETIME.value); | |||||
@Override | |||||
protected FieldTypeBean buildBean(Integer value, String name) { | |||||
return new FieldTypeBean(value, name, ""); | |||||
} | |||||
public FieldTypeBean addEnum(Integer value, String name, String dataType) { | |||||
final FieldTypeBean bean = new FieldTypeBean(value, name, dataType); | |||||
mapAll.put(value, bean); | |||||
return bean; | |||||
} | |||||
public FieldTypeBean addEnum(Integer value, String name, String dataType, int notNull) { | |||||
final FieldTypeBean bean = new FieldTypeBean(value, name, dataType,notNull); | |||||
mapAll.put(value, bean); | |||||
return bean; | |||||
} | |||||
} | |||||
/** | |||||
* 字段编辑类型 | |||||
*/ | |||||
class EditorType extends StrEnum { | |||||
public static EditorType instance = new EditorType(); | |||||
public static StrEnumBean INPUT = instance.addEnum("text", "文本"); | |||||
public static StrEnumBean TEXT = instance.addEnum("textarea", "长文本"); | |||||
public static StrEnumBean NUMBER = instance.addEnum("number", "数字"); | |||||
public static StrEnumBean SWITCH = instance.addEnum("switch", "布尔型"); | |||||
public static StrEnumBean DATE = instance.addEnum("date", "日期"); | |||||
public static StrEnumBean TIME = instance.addEnum("time", "时间"); | |||||
public static StrEnumBean DATETIME = instance.addEnum("datetime", "日期时间"); | |||||
public static StrEnumBean COMBO = instance.addEnum("select", "下拉列表"); | |||||
public static StrEnumBean TREE = instance.addEnum("select-tree", "下拉树"); | |||||
} | |||||
/** | |||||
* 数据字段类型Bean | |||||
*/ | |||||
class DataTypeBean extends AbstractEnum.StrEnumBean { | |||||
public String sqlType; | |||||
public int dataLength; | |||||
public String javaType; | |||||
public String shortJavaType; | |||||
//java.sql.Types里的值 | |||||
public int type; | |||||
public String defaultValue; | |||||
public String editor; | |||||
public DataTypeBean(String value, String name, String sqlType, int dataLength, String javaType, String shortJavaType, int type, String defaultValue, String editor) { | |||||
super(value, name); | |||||
this.sqlType = sqlType; | |||||
this.dataLength = dataLength; | |||||
this.javaType = javaType; | |||||
this.shortJavaType = shortJavaType; | |||||
this.defaultValue = defaultValue; | |||||
this.editor = editor; | |||||
this.type = type; | |||||
} | |||||
public String getSqlTypeCreate() { | |||||
if (dataLength > 0) return sqlType + "(" + dataLength + ")"; | |||||
return sqlType; | |||||
} | |||||
} | |||||
/** | |||||
* 数据类型定义,参见design_db.yaml配置 | |||||
*/ | |||||
class DataType extends AbstractEnum<String, DataTypeBean> { | |||||
public final static String TYPE_BOOL = "bool"; | |||||
public final static String TYPE_DATE = "date"; | |||||
public final static String TYPE_DATETIME = "datetime"; | |||||
public static DataType instance = new DataType(); | |||||
public static DataTypeBean ID = instance.addEnum("id", "ID", "bigint", 0, "long", "Long", Types.BIGINT, "", EditorType.INPUT.value); | |||||
public static DataTypeBean CODE = instance.addEnum("code", "编码", "varchar", 32, "String", "Str", Types.VARCHAR, "", EditorType.INPUT.value); | |||||
public static DataTypeBean NAME = instance.addEnum("name", "名称", "varchar", 100, "String", "Str", Types.VARCHAR, "", EditorType.INPUT.value); | |||||
public static DataTypeBean REMARK = instance.addEnum("remark", "备注", "varchar", 255, "String", "Str", Types.VARCHAR, "", EditorType.INPUT.value); | |||||
public static DataTypeBean TEXT = instance.addEnum("text", "大文本", "text", 0, "String", "Str", Types.CLOB, "", EditorType.TEXT.value); | |||||
public static DataTypeBean LONG = instance.addEnum("long", "长整型", "bigint", 0, "long", "Long", Types.BIGINT, "0", EditorType.INPUT.value); | |||||
public static DataTypeBean INT = instance.addEnum("int", "整型", "int", 0, "int", "Int", Types.INTEGER, "0", EditorType.NUMBER.value); | |||||
public static DataTypeBean SHORT = instance.addEnum("short", "短整型", "smallint", 0, "int", "Int", Types.SMALLINT, "0", EditorType.NUMBER.value); | |||||
public static DataTypeBean BOOL = instance.addEnum("bool", "布尔型", "tinyint", 0, "boolean", "Bool", Types.TINYINT, "0", EditorType.SWITCH.value); | |||||
public static DataTypeBean CURRENCY = instance.addEnum("currency", "金额型", "bigint", 0, "long", "Long", Types.BIGINT, "0", EditorType.NUMBER.value); | |||||
public static DataTypeBean DATE = instance.addEnum("date", "日期型", "bigint", 0, "long","Long", Types.BIGINT, "0", EditorType.DATE.value); | |||||
public static DataTypeBean TIME = instance.addEnum("time", "时间型", "bigint", 0, "long", "Long", Types.BIGINT, "0", EditorType.TIME.value); | |||||
public static DataTypeBean DATETIME = instance.addEnum("datetime", "日期时间型", "bigint", 0, "long", "Long", Types.BIGINT, "0", EditorType.DATETIME.value); | |||||
@Override | |||||
protected DataTypeBean buildBean(String value, String name) { | |||||
return null; | |||||
} | |||||
public DataTypeBean addEnum(String value, String name, String sqlType, int dataLength, String javaType, String shortJavaType, int type, String defaultValue, String editor) { | |||||
final DataTypeBean bean = new DataTypeBean(value, name, sqlType, dataLength, javaType, shortJavaType, type, defaultValue, editor); | |||||
mapAll.put(value, bean); | |||||
return bean; | |||||
} | |||||
@Override | |||||
public DataTypeBean getByValue(String value) { | |||||
if (value == null) return null; | |||||
return super.getByValue(value.toLowerCase()); | |||||
} | |||||
//根据数据库查询的metadata适配类型 | |||||
public static DataTypeBean getBySqlType(int sqlType, int precision, int scale) { | |||||
for (DataTypeBean bean: instance.mapAll.values()) { | |||||
if (bean.type != sqlType) continue; | |||||
if (bean.dataLength == 0 || bean.dataLength == precision) return bean; | |||||
} | |||||
return REMARK; | |||||
} | |||||
} | |||||
/** | |||||
* 数据字段类型Bean | |||||
*/ | |||||
class IndexTypeBean extends AbstractEnum.StrEnumBean { | |||||
public String fullName; | |||||
public IndexTypeBean(String value, String name, String fullName) { | |||||
super(value, name); | |||||
this.fullName = fullName; | |||||
} | |||||
} | |||||
class IndexType extends AbstractEnum<String, IndexTypeBean> { | |||||
public static IndexType instance = new IndexType(); | |||||
public static IndexTypeBean PK = instance.addEnum("P", "主键", "prim-key"); | |||||
public static IndexTypeBean I = instance.addEnum("I", "一般索引", ""); | |||||
public static IndexTypeBean U = instance.addEnum("U", "唯一索引", "unique"); | |||||
@Override | |||||
protected IndexTypeBean buildBean(String value, String name) { | |||||
return null; | |||||
} | |||||
public IndexTypeBean addEnum(String value, String name, String fullName) { | |||||
final IndexTypeBean bean = new IndexTypeBean(value, name, fullName); | |||||
mapAll.put(value, bean); | |||||
return bean; | |||||
} | |||||
} | |||||
/** | |||||
* 表类型 | |||||
*/ | |||||
class TableType extends IntEnum { | |||||
public static TableType instance = new TableType(); | |||||
public static AbstractEnum.IntEnumBean TYPE_GENERAL = instance.addEnum(0, "普通表"); | |||||
public static AbstractEnum.IntEnumBean TYPE_TREE = instance.addEnum(1, "树型表"); | |||||
public static AbstractEnum.IntEnumBean TYPE_CODE = instance.addEnum(2, "编码表"); | |||||
public static AbstractEnum.IntEnumBean TYPE_ABSTRACT = instance.addEnum(3, "虚拟抽象表"); | |||||
public static AbstractEnum.IntEnumBean TYPE_VIEW = instance.addEnum(4, "视图"); | |||||
} | |||||
/** | |||||
* 界面定义类型:::控件/页面 | |||||
*/ | |||||
class FormType extends IntEnum { | |||||
public static FormType instance = new FormType(); | |||||
public static IntEnumBean PAGE = instance.addEnum(0, "页面"); | |||||
public static IntEnumBean WIDGET = instance.addEnum(1, "控件"); | |||||
} | |||||
/** | |||||
* 控件类型:::grid,tree,combotree,combogrid、custom | |||||
*/ | |||||
class WidgetType extends StrEnum { | |||||
public static WidgetType instance = new WidgetType(); | |||||
public static StrEnumBean GRID = instance.addEnum("grid", "表格"); | |||||
public static StrEnumBean TREE = instance.addEnum("tree", "树"); | |||||
public static StrEnumBean COMBOGRID = instance.addEnum("combogrid", "下拉表格"); | |||||
public static StrEnumBean COMBOTREE = instance.addEnum("combotree", "下拉树"); | |||||
public static StrEnumBean CUSTOM = instance.addEnum("custom", "自定义"); | |||||
} | |||||
/** | |||||
* 数据集类型:::list-列表;form-表单;editList-编辑列表;tree;enum | |||||
*/ | |||||
class DatasetType extends StrEnum { | |||||
public static DatasetType instance = new DatasetType(); | |||||
public static StrEnumBean LIST = instance.addEnum("list", "查询列表"); | |||||
public static StrEnumBean FORM = instance.addEnum("form", "表单"); | |||||
public static StrEnumBean ITEM = instance.addEnum("item", "子表编辑"); | |||||
public static StrEnumBean TREE = instance.addEnum("tree", "树"); | |||||
public static StrEnumBean ENUM = instance.addEnum("enum", "枚举"); | |||||
} | |||||
/** | |||||
* 过滤条件类型:::控件/参数/link/const | |||||
*/ | |||||
class FilterType extends StrEnum { | |||||
public static FilterType instance = new FilterType(); | |||||
public static StrEnumBean INPUT = instance.addEnum("input", "输入"); | |||||
public static StrEnumBean PARAM = instance.addEnum("param", "参数"); | |||||
public static StrEnumBean LINK = instance.addEnum("link", "关联"); | |||||
public static StrEnumBean CONST = instance.addEnum("const", "常量"); | |||||
} | |||||
/** | |||||
* 操作符类型:::and/or/=/>=/<=/like/p | |||||
*/ | |||||
class OptType extends StrEnum { | |||||
public static OptType instance = new OptType(); | |||||
public static StrEnumBean AND = instance.addEnum("and", "且"); | |||||
public static StrEnumBean OR = instance.addEnum("or", "或"); | |||||
public static StrEnumBean EQ = instance.addEnum("=", "等于"); | |||||
public static StrEnumBean NE = instance.addEnum("<>", "不等于"); | |||||
public static StrEnumBean GT = instance.addEnum(">", "大于"); | |||||
public static StrEnumBean GE = instance.addEnum(">=", "大于等于"); | |||||
public static StrEnumBean LT = instance.addEnum("<", "小于"); | |||||
public static StrEnumBean LE = instance.addEnum("<=", "小于等于"); | |||||
public static StrEnumBean BT = instance.addEnum("bt", "介于"); | |||||
public static StrEnumBean PLIKE = instance.addEnum("plike", "开始以"); | |||||
public static StrEnumBean LIKE = instance.addEnum("like", "包含"); | |||||
} | |||||
//合计栏类型 "summary": "COUNT/SUM/AVG/MAX/MIN/其他为文本" | |||||
class SummaryType extends StrEnum { | |||||
public static SummaryType instance = new SummaryType(); | |||||
public static StrEnumBean COUNT = instance.addEnum("count", "计数"); | |||||
public static StrEnumBean SUM = instance.addEnum("sum", "求和"); | |||||
public static StrEnumBean AVG = instance.addEnum("avg", "均值"); | |||||
public static StrEnumBean MAX = instance.addEnum("max", "最大值"); | |||||
public static StrEnumBean MIN = instance.addEnum("min", "最小值"); | |||||
//是字符串 | |||||
public boolean isText(String v) { | |||||
if (StringUtils.isEmpty(v)) return true; | |||||
return !mapAll.containsKey(v.toLowerCase()); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,123 @@ | |||||
package cc.smtweb.framework.core.db.cache; | |||||
import cc.smtweb.framework.core.annotation.SwCache; | |||||
import cc.smtweb.framework.core.cache.AbstractCache; | |||||
import cc.smtweb.framework.core.cache.CacheManager; | |||||
import cc.smtweb.framework.core.db.DbEngine; | |||||
import cc.smtweb.framework.core.db.vo.ModelField; | |||||
import cc.smtweb.framework.core.db.vo.ModelTable; | |||||
import org.springframework.dao.DataAccessException; | |||||
import org.springframework.jdbc.core.ResultSetExtractor; | |||||
import java.sql.ResultSet; | |||||
import java.sql.SQLException; | |||||
import java.util.*; | |||||
/** | |||||
* Created by Akmm at 2022/1/12 18:34 | |||||
*/ | |||||
@SwCache(ident = "ASP_MODEL_TABLE", title = "数据库表定义") | |||||
public class ModelTableCache extends AbstractCache<ModelTable> { | |||||
private final static String mk = "k"; | |||||
private final static String md = "d"; | |||||
private final static String mc = "c"; | |||||
private final static String mk_link = "l"; | |||||
public static ModelTableCache getInstance() { | |||||
return CacheManager.getIntance().getCache(ModelTableCache.class); | |||||
} | |||||
public ModelTableCache() { | |||||
regMap(mk, k-> k.getName().toUpperCase()); | |||||
regList(md, k-> String.valueOf(k.getDbId())); | |||||
regList(mc, k-> String.valueOf(k.getMcId())); | |||||
regListEx(mk_link, k-> { | |||||
Set<String> list = new HashSet<>(); | |||||
for (ModelField field: k.getFields()) { | |||||
if (field.getLink() > 0) list.add(String.valueOf(field.getLink())); | |||||
} | |||||
return list.toArray(new String[list.size()]); | |||||
}); | |||||
// regList(mf, k-> k.get); | |||||
} | |||||
@Override | |||||
protected String getId(ModelTable bean) { | |||||
return String.valueOf(bean.getId()); | |||||
} | |||||
@Override | |||||
protected List<ModelTable> loadAll() { | |||||
return DbEngine.getInstance().query("SELECT\n" + | |||||
"t.tb_id,\n" + | |||||
"t.tb_prj_id,\n" + | |||||
"t.tb_mc_id,\n" + | |||||
"t.tb_db_id,\n" + | |||||
"t.tb_extends,\n" + | |||||
"t.tb_name,\n" + | |||||
"t.tb_title,\n" + | |||||
"t.tb_abbr,\n" + | |||||
"t.tb_type,\n" + | |||||
"t.tb_need_cache,\n" + | |||||
"t.tb_content,\n" + | |||||
"t.tb_create_uid,\n" + | |||||
"t.tb_update_uid,\n" + | |||||
"t.tb_create_at,\n" + | |||||
"t.tb_update_at\n" + | |||||
"from asp_model_table t\n", new ResultSetExtractor<List<ModelTable>>() { | |||||
@Override | |||||
public List<ModelTable> extractData(ResultSet rs) throws SQLException, DataAccessException { | |||||
List<ModelTable> list = new ArrayList<>(); | |||||
while (rs.next()) { | |||||
ModelTable table = new ModelTable(); | |||||
list.add(table); | |||||
table.setId(rs.getLong("tb_id")); | |||||
table.setDbId(rs.getLong("tb_db_id")); | |||||
table.setPrjId(rs.getLong("tb_prj_id")); | |||||
table.setMcId(rs.getLong("tb_mc_id")); | |||||
table.setExtends(rs.getString("tb_extends")); | |||||
table.setName(rs.getString("tb_name").toUpperCase()); | |||||
table.setTitle(rs.getString("tb_title")); | |||||
table.setAbbr(rs.getString("tb_abbr")); | |||||
table.setType(rs.getInt("tb_type")); | |||||
table.setNeedCache(rs.getInt("tb_need_cache") == 1); | |||||
table.setCreateUid(rs.getLong("tb_create_uid")); | |||||
table.setUpdateUid(rs.getLong("tb_update_uid")); | |||||
table.setCreateAt(rs.getLong("tb_create_at")); | |||||
table.setUpdateAt(rs.getLong("tb_update_at")); | |||||
table.setContent(rs.getString("tb_content")); | |||||
} | |||||
return list; | |||||
} | |||||
}); | |||||
} | |||||
public final ModelTable getByName(String key) { | |||||
return getByKey(mk, key.toUpperCase()); | |||||
} | |||||
public final Set<ModelTable> getDbTables(long dbId) { | |||||
return getListByKey(md, String.valueOf(dbId)); | |||||
} | |||||
public final Set<ModelTable> getTablesByMc(long mcId) { | |||||
return getListByKey(mc, String.valueOf(mcId)); | |||||
} | |||||
public final List<ModelTable> getTablesByMc(long mcId, Comparator<ModelTable> comparator) { | |||||
Set<ModelTable> set = getListByKey(mc, String.valueOf(mcId)); | |||||
if (set == null || set.isEmpty()) return null; | |||||
List<ModelTable> list = new ArrayList<>(set); | |||||
list.sort(comparator); | |||||
return list; | |||||
} | |||||
public final String getTableName(long id) { | |||||
ModelTable bean = get(id); | |||||
return bean == null ? String.valueOf(id) : bean.getTitle(); | |||||
} | |||||
//根据外键 | |||||
public final Set<ModelTable> getByLink(long tableId) { | |||||
return getListByKey(mk_link, String.valueOf(tableId)); | |||||
} | |||||
} |
@@ -0,0 +1,66 @@ | |||||
package cc.smtweb.framework.core.db.dao; | |||||
import cc.smtweb.framework.core.db.impl.DefaultEntity; | |||||
import cc.smtweb.framework.core.db.vo.ModelField; | |||||
import cc.smtweb.framework.core.exception.DbException; | |||||
import lombok.Getter; | |||||
import java.lang.reflect.InvocationTargetException; | |||||
import java.lang.reflect.Method; | |||||
/** | |||||
* 值对象字段处理类 | |||||
* @author xkliu | |||||
*/ | |||||
@Getter | |||||
public class EntityColumn { | |||||
private ModelField field; | |||||
private final Method readMethod; | |||||
private final Method writeMethod; | |||||
/** | |||||
* 构建值对象字段 | |||||
* @param field 字段名 | |||||
* @param readMethod 读值方法 | |||||
* @param writeMethod 写值方法 | |||||
*/ | |||||
public EntityColumn(ModelField field, Method readMethod, Method writeMethod) { | |||||
this.field = field; | |||||
this.readMethod = readMethod; | |||||
this.writeMethod = writeMethod; | |||||
} | |||||
/** | |||||
* 从对象中读取字段对应的属性值 | |||||
* @param obj 值对象 | |||||
* @return 属性值 | |||||
*/ | |||||
public Object readValue(Object obj) { | |||||
if (readMethod != null) { | |||||
try { | |||||
return readMethod.invoke(obj); | |||||
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { | |||||
throw new DbException(e); | |||||
} | |||||
} else { | |||||
return ((DefaultEntity)obj).get(field.getName()); | |||||
} | |||||
} | |||||
/** | |||||
* 写入值到对象字段对象属性 | |||||
* @param obj 值对象 | |||||
* @param value 属性值 | |||||
*/ | |||||
public void writeValue(Object obj, Object value) { | |||||
if (readMethod != null) { | |||||
try { | |||||
writeMethod.invoke(obj, value); | |||||
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { | |||||
throw new DbException(e); | |||||
} | |||||
} else { | |||||
((DefaultEntity)obj).put(field.getName(), value); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,103 @@ | |||||
package cc.smtweb.framework.core.db.impl; | |||||
import cc.smtweb.framework.core.common.SwMap; | |||||
import cc.smtweb.framework.core.util.JsonUtil; | |||||
import cc.smtweb.framework.core.util.NumberUtil; | |||||
import cc.smtweb.framework.core.util.jackson.BaseBeanSerializer; | |||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize; | |||||
import java.io.Serializable; | |||||
import java.util.Map; | |||||
/** | |||||
* Created by Akmm at 2016-02-23 9:31 | |||||
* bean基类,基于Map, | |||||
*/ | |||||
@JsonSerialize(using = BaseBeanSerializer.class) | |||||
public class BaseBean implements Serializable { | |||||
protected SwMap data = new SwMap(); | |||||
public SwMap getData() { | |||||
return data; | |||||
} | |||||
public void setData(SwMap data) { | |||||
this.data = data; | |||||
} | |||||
@Override | |||||
public BaseBean clone() throws CloneNotSupportedException { | |||||
BaseBean bean = (BaseBean) super.clone(); | |||||
bean.data = new SwMap(); | |||||
bean.getData().putAll(this.data); | |||||
return bean; | |||||
} | |||||
public boolean isEmpty() { | |||||
return data.isEmpty(); | |||||
} | |||||
public String getStr(String fieldName) { | |||||
return getStr(fieldName, ""); | |||||
} | |||||
public String getStr(String fieldName, String defValue) { | |||||
Object o = data.get(fieldName); | |||||
return o != null ? o.toString() : defValue; | |||||
} | |||||
public double getDouble(String fieldName) { | |||||
return getDouble(fieldName, 0.0); | |||||
} | |||||
public double getDouble(String fieldName, double defValue) { | |||||
Object o = data.get(fieldName); | |||||
if (o == null) return defValue; | |||||
if (o instanceof Number) return ((Number) o).doubleValue(); | |||||
return NumberUtil.getDoubleIgnoreErr(o.toString()); | |||||
} | |||||
public int getInt(String fieldName) { | |||||
Object o = data.get(fieldName); | |||||
if (o == null) return 0; | |||||
if (o instanceof Number) return ((Number) o).intValue(); | |||||
return NumberUtil.getIntIgnoreErr(o.toString()); | |||||
} | |||||
public long getLong(String fieldName) { | |||||
Object o = data.get(fieldName); | |||||
if (o == null) return 0L; | |||||
if (o instanceof Number) return ((Number) o).longValue(); | |||||
return NumberUtil.getLongIgnoreErr(o.toString()); | |||||
} | |||||
public boolean getBool(String fieldName) { | |||||
Object o = data.get(fieldName); | |||||
if (o == null) return false; | |||||
String v = o.toString(); | |||||
return "1".equals(v) || "t".equalsIgnoreCase(v) || "true".equalsIgnoreCase(v); | |||||
} | |||||
public void put(String fieldName, Object value) { | |||||
this.data.put(fieldName, value); | |||||
} | |||||
public void setBool(String fieldName, boolean value) { | |||||
this.data.put(fieldName, value ? 1 : 0); | |||||
} | |||||
public Object get(String fieldName) { | |||||
return this.data.get(fieldName); | |||||
} | |||||
public void readFromJson(String json) { | |||||
Map map = JsonUtil.parseMap(json); | |||||
if (map != null) { | |||||
data.putAll(map); | |||||
} | |||||
} | |||||
public void readFromMap(SwMap map) { | |||||
data.putAll(map); | |||||
} | |||||
} |
@@ -0,0 +1,158 @@ | |||||
package cc.smtweb.framework.core.db.impl; | |||||
/** | |||||
* Created with IntelliJ IDEA. | |||||
* User: AKhh | |||||
* Date: 12-12-21 下午10:04 | |||||
* To change this template use File | Settings | File Templates. | |||||
*/ | |||||
public class UtilTime { | |||||
long realStartTime; | |||||
long startTime; | |||||
long lastMessageTime; | |||||
String lastMessage = null; | |||||
boolean log = false; | |||||
/** | |||||
* Default constructor. Starts the timer. | |||||
*/ | |||||
public UtilTime() { | |||||
lastMessageTime = realStartTime = startTime = System.currentTimeMillis(); | |||||
lastMessage = "Begin"; | |||||
} | |||||
/** | |||||
* Creates a string with information including the passed message, the last passed message and the time since the last call, and the time since the beginning | |||||
* | |||||
* @param message A message to put into the timer String | |||||
* @return A String with the timing information, the timer String | |||||
*/ | |||||
public String timerString(String message) { | |||||
return timerString(message, this.getClass().getName()); | |||||
} | |||||
/** | |||||
* Creates a string with information including the passed message, the last passed message and the time since the last call, and the time since the beginning | |||||
* | |||||
* @param message A message to put into the timer String | |||||
* @param module The debug/log module/thread to use, can be null for root module | |||||
* @return A String with the timing information, the timer String | |||||
*/ | |||||
public String timerString(String message, String module) { | |||||
// time this call to avoid it interfering with the main timer | |||||
long tsStart = System.currentTimeMillis(); | |||||
String retString = "[[" + message + "- total:" + secondsSinceStart() + | |||||
",since last(" + ((lastMessage.length() > 20) ? (lastMessage.substring(0, 17) + "..."): lastMessage) + "):" + | |||||
secondsSinceLast() + "]]"; | |||||
lastMessage = message; | |||||
if (log) { | |||||
System.out.println(retString); | |||||
} | |||||
// have lastMessageTime come as late as possible to just time what happens between calls | |||||
lastMessageTime = System.currentTimeMillis(); | |||||
// update startTime to disclude the time this call took | |||||
startTime += (lastMessageTime - tsStart); | |||||
return retString; | |||||
} | |||||
/** | |||||
* Returns the number of seconds since the timer started | |||||
* | |||||
* @return The number of seconds since the timer started | |||||
*/ | |||||
public double secondsSinceStart() { | |||||
return ((double) timeSinceStart()) / 1000.0; | |||||
} | |||||
/** | |||||
* Returns the number of seconds since the last time timerString was called | |||||
* | |||||
* @return The number of seconds since the last time timerString was called | |||||
*/ | |||||
public double secondsSinceLast() { | |||||
return ((double) timeSinceLast()) / 1000.0; | |||||
} | |||||
/** | |||||
* Returns the number of milliseconds since the timer started | |||||
* | |||||
* @return The number of milliseconds since the timer started | |||||
*/ | |||||
public long timeSinceStart() { | |||||
long currentTime = System.currentTimeMillis(); | |||||
return currentTime - startTime; | |||||
} | |||||
/** | |||||
* Returns the number of milliseconds since the last time timerString was called | |||||
* | |||||
* @return The number of milliseconds since the last time timerString was called | |||||
*/ | |||||
public long timeSinceLast() { | |||||
long currentTime = System.currentTimeMillis(); | |||||
return currentTime - lastMessageTime; | |||||
} | |||||
/** | |||||
* Sets the value of the log member, denoting whether log output is off or not | |||||
* | |||||
* @param log The new value of log | |||||
*/ | |||||
public void setLog(boolean log) { | |||||
this.log = log; | |||||
} | |||||
/** | |||||
* Gets the value of the log member, denoting whether log output is off or not | |||||
* | |||||
* @return The value of log | |||||
*/ | |||||
public boolean getLog() { | |||||
return log; | |||||
} | |||||
/** | |||||
* Creates a string with information including the passed message, the time since the last call, | |||||
* and the time since the beginning. This version allows an integer level to be specified to | |||||
* improve readability of the output. | |||||
* | |||||
* @param level Integer specifying how many levels to indent the timer string so the output can be more easily read through nested method calls. | |||||
* @param message A message to put into the timer String | |||||
* @return A String with the timing information, the timer String | |||||
*/ | |||||
public String timerString(int level, String message) { | |||||
StringBuffer retStringBuf = new StringBuffer(); | |||||
for (int i = 0; i < level; i++) { | |||||
retStringBuf.append("| "); | |||||
} | |||||
retStringBuf.append("("); | |||||
String timeSinceStartStr = String.valueOf(timeSinceStart()); | |||||
// int spacecount = 5 - timeSinceStartStr.length(); | |||||
// for (int i=0; i < spacecount; i++) { retStringBuf.append(' '); } | |||||
retStringBuf.append(timeSinceStartStr + ","); | |||||
String timeSinceLastStr = String.valueOf(timeSinceLast()); | |||||
// spacecount = 4 - timeSinceLastStr.length(); | |||||
// for (int i=0; i < spacecount; i++) { retStringBuf.append(' '); } | |||||
retStringBuf.append(timeSinceLastStr); | |||||
retStringBuf.append(")"); | |||||
int spacecount = 12 + (2 * level) - retStringBuf.length(); | |||||
for (int i = 0; i < spacecount; i++) { | |||||
retStringBuf.append(' '); | |||||
} | |||||
retStringBuf.append(message); | |||||
// lastMessageTime = (new Date()).getTime(); | |||||
lastMessageTime = System.currentTimeMillis(); | |||||
// lastMessage = message; | |||||
return retStringBuf.toString(); | |||||
} | |||||
} |
@@ -0,0 +1,40 @@ | |||||
package cc.smtweb.framework.core.db.jdbc; | |||||
import cc.smtweb.framework.core.db.impl.BaseBean; | |||||
import org.springframework.beans.BeanUtils; | |||||
import org.springframework.jdbc.core.RowMapper; | |||||
import java.sql.ResultSet; | |||||
import java.sql.ResultSetMetaData; | |||||
import java.sql.SQLException; | |||||
/** | |||||
* ORM映射处理器,实现spring jdbcTemplate的行集映射器 | |||||
* @author xkliu | |||||
*/ | |||||
public class BaseBeanPropertyRowMapper<T> implements RowMapper<T> { | |||||
private Class<T> mappedClass; | |||||
public BaseBeanPropertyRowMapper(Class<T> mappedClass) { | |||||
this.mappedClass = mappedClass; | |||||
} | |||||
@Override | |||||
public T mapRow(ResultSet resultSet, int i) throws SQLException { | |||||
T mappedObject = BeanUtils.instantiateClass(this.mappedClass); | |||||
BaseBean map = (BaseBean) mappedObject; | |||||
ResultSetMetaData rsmd = resultSet.getMetaData(); | |||||
int columnCount = rsmd.getColumnCount(); | |||||
for(int index = 1; index <= columnCount; ++index) { | |||||
Object value = resultSet.getObject(index); | |||||
if (value != null) { | |||||
map.put(rsmd.getColumnLabel(index), value); | |||||
} | |||||
} | |||||
return mappedObject; | |||||
} | |||||
} |
@@ -0,0 +1,134 @@ | |||||
package cc.smtweb.framework.core.db.jdbc; | |||||
/** | |||||
* 主键生成器。 | |||||
*/ | |||||
//public class IdGenerator { | |||||
// private AtomicLong key; | |||||
// | |||||
// public IdGenerator() { | |||||
// this(0); | |||||
// } | |||||
// | |||||
// /** | |||||
// * | |||||
// * @param seed 集群服务id,用于集群产生的数据不重复(0~255) | |||||
// */ | |||||
// public IdGenerator(int seed) { | |||||
// long now = System.currentTimeMillis(); | |||||
// | |||||
// if(seed > 0xFF){ | |||||
// seed = 0xFF; | |||||
// } | |||||
// | |||||
// if(seed <= 0){ | |||||
// seed = 1; | |||||
// } | |||||
// | |||||
// key = new AtomicLong(now | (((long) seed) << 48)); | |||||
// } | |||||
// | |||||
// public long nextCode() { | |||||
// return key.incrementAndGet(); | |||||
// } | |||||
//} | |||||
/** | |||||
* tweeter的snowflake | |||||
* time—42bits,精确到ms,那就意味着其可以表示长达(2^42-1)/(1000360024*365)=139.5年 | |||||
* (a) id构成: 42位的时间前缀 + 10位的节点标识 + 12位的sequence避免并发的数字(12位不够用时强制得到新的时间前缀) | |||||
* 注意这里进行了小改动: snowkflake是5位的datacenter加5位的机器id; 这里变成使用10位的机器id | |||||
* (b) 对系统时间的依赖性非常强,需关闭ntp的时间同步功能。当检测到ntp时间调整后,将会拒绝分配id | |||||
* @author xkliu | |||||
*/ | |||||
public class IdGenerator { | |||||
/** | |||||
* 起始的时间戳 epoch 2017-1-1 | |||||
*/ | |||||
private final static long EPOCH_STMP = 1483200000000L; | |||||
/** | |||||
* 每一部分占用的位数 | |||||
*/ | |||||
//序列号占用的位数 | |||||
private final static long SEQUENCE_BIT = 12; | |||||
//机器标识占用的位数 | |||||
private final static long MACHINE_BIT = 10; | |||||
/** | |||||
* 每一部分的最大值 | |||||
*/ | |||||
private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT); | |||||
private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT); | |||||
/** | |||||
* 每一部分向左的位移 | |||||
*/ | |||||
private final static long MACHINE_LEFT = SEQUENCE_BIT; | |||||
private final static long TIMESTMP_LEFT = SEQUENCE_BIT + MACHINE_BIT; | |||||
// private long datacenterId; //数据中心 | |||||
// 机器标识 | |||||
private long machineIdShift; | |||||
// 序列号 | |||||
private long sequence = 0L; | |||||
// 上一次时间戳 | |||||
private long lastStmp = -1L; | |||||
// 0 ~ 1023 | |||||
public IdGenerator(long machineId) { | |||||
if (machineId > MAX_MACHINE_NUM || machineId < 0) { | |||||
throw new IllegalArgumentException("machineId " + machineId + " can't be greater than " + MAX_MACHINE_NUM + " or less than 0"); | |||||
} | |||||
this.machineIdShift = machineId << MACHINE_LEFT; | |||||
} | |||||
/** | |||||
* 产生下一个ID | |||||
* | |||||
* @return ID值 | |||||
*/ | |||||
public synchronized long nextId() { | |||||
long currStmp = getNewStamp(); | |||||
if (currStmp < lastStmp) { | |||||
throw new RuntimeException("Clock moved backwards. Refusing to generate id"); | |||||
} | |||||
if (currStmp == lastStmp) { | |||||
//相同毫秒内,序列号自增 | |||||
sequence = (sequence + 1) & MAX_SEQUENCE; | |||||
//同一毫秒的序列数已经达到最大 | |||||
if (sequence == 0L) { | |||||
currStmp = getNextMill(); | |||||
} | |||||
} else { | |||||
//不同毫秒内,序列号置为0 | |||||
sequence = 0L; | |||||
} | |||||
lastStmp = currStmp; | |||||
// 时间戳部分 + 机器标识部分 + 序列号部分 | |||||
return (currStmp - EPOCH_STMP) << TIMESTMP_LEFT | |||||
| machineIdShift | |||||
| sequence; | |||||
} | |||||
private long getNextMill() { | |||||
long mill = getNewStamp(); | |||||
while (mill <= lastStmp) { | |||||
mill = getNewStamp(); | |||||
} | |||||
return mill; | |||||
} | |||||
private long getNewStamp() { | |||||
return System.currentTimeMillis(); | |||||
} | |||||
// 根据ID值反向计算时间戳 | |||||
public static long queryTimestamp(long id) { | |||||
return (id >>> TIMESTMP_LEFT) + EPOCH_STMP; | |||||
} | |||||
} |
@@ -0,0 +1,87 @@ | |||||
package cc.smtweb.framework.core.db.jdbc; | |||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager; | |||||
import org.springframework.transaction.TransactionDefinition; | |||||
import org.springframework.transaction.TransactionStatus; | |||||
import org.springframework.transaction.support.DefaultTransactionDefinition; | |||||
import java.util.concurrent.atomic.AtomicInteger; | |||||
/** | |||||
* JDBC事务处理类,可以在try中使用自动结束事务 | |||||
* | |||||
* @author xkliu | |||||
*/ | |||||
public class JdbcTrans implements AutoCloseable { | |||||
private Logger logger = LoggerFactory.getLogger("trans"); | |||||
protected long time = System.currentTimeMillis(); | |||||
protected String info = Thread.currentThread().getStackTrace()[6].getFileName() + "::::" + Thread.currentThread().getStackTrace()[6].getLineNumber(); | |||||
private AtomicInteger count = new AtomicInteger(1); | |||||
public int getCount() { | |||||
return count.get(); | |||||
} | |||||
public void inc() { | |||||
count.incrementAndGet(); | |||||
} | |||||
public boolean canCommit() { | |||||
return count.decrementAndGet() == 0; | |||||
} | |||||
public void doEnd(String ei) { | |||||
long t = System.currentTimeMillis() - time; | |||||
if (t > 10000L) { | |||||
logger.info(info + "::::::" + t + "::::::" + ei); | |||||
} | |||||
} | |||||
private DataSourceTransactionManager transactionManager; | |||||
private TransactionStatus status; | |||||
/** | |||||
* 构造事务执行 | |||||
* | |||||
* @param transactionManager spring事务管理对象 | |||||
*/ | |||||
public JdbcTrans(DataSourceTransactionManager transactionManager) { | |||||
this.transactionManager = transactionManager; | |||||
// 事务定义类 | |||||
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); | |||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); | |||||
// 返回事务对象 | |||||
this.status = transactionManager.getTransaction(def); | |||||
} | |||||
/** | |||||
* 回滚事务 | |||||
*/ | |||||
public void rollback() { | |||||
if (status != null) { | |||||
transactionManager.rollback(status); | |||||
status = null; | |||||
} | |||||
} | |||||
/** | |||||
* 提交事务 | |||||
*/ | |||||
public void commit() { | |||||
if (status != null) { | |||||
transactionManager.commit(status); | |||||
status = null; | |||||
} | |||||
} | |||||
/** | |||||
* 实现自动关闭,回滚方式结束事务 | |||||
*/ | |||||
@Override | |||||
public void close() { | |||||
this.rollback(); | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
package cc.smtweb.framework.core.db.sqlbuilder; | |||||
import cc.smtweb.framework.core.db.jdbc.JdbcEngine; | |||||
public class DeleteSqlBuilder extends AbstractUpdateSqlBuilder<DeleteSqlBuilder> { | |||||
private String tableName; | |||||
DeleteSqlBuilder(String tableName) { | |||||
this.tableName = tableName; | |||||
} | |||||
@Override | |||||
public int update(JdbcEngine dbEngine) { | |||||
int fieldSize = wheres.size(); | |||||
Object[] params = new Object[fieldSize]; | |||||
StringBuilder sb = new StringBuilder("delete from "); | |||||
sb.append(tableName).append(" where "); | |||||
int index = 0; | |||||
for (SqlWhereValue whereValue: wheres) { | |||||
if (index > 0){ | |||||
sb.append(" and "); | |||||
} | |||||
sb.append(whereValue.getName()).append("=?"); | |||||
params[index] = whereValue.getValue(); | |||||
index++; | |||||
} | |||||
return dbEngine.update(sb.toString(), params); | |||||
} | |||||
} |
@@ -0,0 +1,20 @@ | |||||
package cc.smtweb.framework.core.db.sqlbuilder; | |||||
import java.util.function.BiFunction; | |||||
public class DirectSelectSqlBuilder extends AbstractSelectSqlBuilder { | |||||
private String sql; | |||||
DirectSelectSqlBuilder(String sql) { | |||||
this.sql = sql; | |||||
} | |||||
@Override | |||||
public <T> T exec(BiFunction<String, Object[], T> execute) { | |||||
StringBuilder sb = new StringBuilder(sql); | |||||
Object[] params = makeParams(sb); | |||||
return execute.apply(sb.toString(), params); | |||||
} | |||||
} |
@@ -0,0 +1,84 @@ | |||||
package cc.smtweb.framework.core.db.sqlbuilder; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
/** | |||||
* SQL语句建造器 | |||||
* @author admin | |||||
* @param <T> 建造器类型 | |||||
*/ | |||||
public abstract class SqlBuilder<T extends SqlBuilder> { | |||||
protected List<SqlFieldValue> fields = new ArrayList<>(); | |||||
protected List<SqlWhereValue> wheres; | |||||
SqlBuilder() { | |||||
} | |||||
public static InsertSqlBuilder createInsert(String tableName) { | |||||
return new InsertSqlBuilder(tableName); | |||||
} | |||||
public static UpdateSqlBuilder createUpdate(String tableName) { | |||||
return new UpdateSqlBuilder(tableName); | |||||
} | |||||
public static SelectSqlBuilder createSelect(String tableName) { | |||||
return new SelectSqlBuilder(tableName); | |||||
} | |||||
public static DeleteSqlBuilder createDelete(String tableName) { | |||||
return new DeleteSqlBuilder(tableName); | |||||
} | |||||
public static InsertSqlBuilder createInsert(String dbName, String tableName) { | |||||
return new InsertSqlBuilder(dbName + "." + tableName); | |||||
} | |||||
public static UpdateSqlBuilder createUpdate(String dbName, String tableName) { | |||||
return new UpdateSqlBuilder(dbName + "." + tableName); | |||||
} | |||||
public static AbstractSelectSqlBuilder createSelect(String dbName, String tableName) { | |||||
return new SelectSqlBuilder(dbName + "." + tableName); | |||||
} | |||||
public static AbstractSelectSqlBuilder createDirectSelect(String sql) { | |||||
return new DirectSelectSqlBuilder(sql); | |||||
} | |||||
public static DeleteSqlBuilder createDelete(String dbName, String tableName) { | |||||
return new DeleteSqlBuilder(dbName + "." + tableName); | |||||
} | |||||
public T add(String fieldName) { | |||||
fields.add(new SqlFieldValue(fieldName, null)); | |||||
return (T)this; | |||||
} | |||||
public T add(String fieldName, Object fieldValue) { | |||||
fields.add(new SqlFieldValue(fieldName, fieldValue)); | |||||
return (T)this; | |||||
} | |||||
public T addWhere(String fieldName, Object fieldValue, String op) { | |||||
if (wheres == null) { | |||||
wheres = new ArrayList<>(); | |||||
} | |||||
wheres.add(new SqlWhereValue(fieldName, fieldValue, op)); | |||||
return (T)this; | |||||
} | |||||
public T addWhere(String fieldName, Object fieldValue) { | |||||
return addWhere(fieldName, fieldValue, "="); | |||||
} | |||||
public T addWhereOrBegin() { | |||||
return addWhere(null, "(", " or "); | |||||
} | |||||
public T addWhereOrEnd() { | |||||
return addWhere(null, ")", " and "); | |||||
} | |||||
public T addOrderBy(String orderBy) { | |||||
return (T)this; | |||||
} | |||||
} |
@@ -0,0 +1,55 @@ | |||||
package cc.smtweb.framework.core.db.sqlbuilder; | |||||
import cc.smtweb.framework.core.db.jdbc.JdbcEngine; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
public class UpdateSqlBuilder extends AbstractUpdateSqlBuilder<UpdateSqlBuilder> { | |||||
private String tableName; | |||||
UpdateSqlBuilder(String tableName) { | |||||
this.tableName = tableName; | |||||
} | |||||
@Override | |||||
public int update(JdbcEngine dbEngine) { | |||||
int fieldSize = fields.size() + wheres.size(); | |||||
List<Object> params = new ArrayList<>(fieldSize); | |||||
// Object[] params = new Object[fieldSize]; | |||||
StringBuilder sb = new StringBuilder("update "); | |||||
sb.append(tableName).append(" set "); | |||||
// int index = 0; | |||||
for (SqlFieldValue field: fields) { | |||||
if (field.getValue() == VALUE_INVALID) { | |||||
sb.append(field.getName()); | |||||
} else { | |||||
sb.append(field.getName()).append("=?,"); | |||||
params.add(field.getValue()); | |||||
} | |||||
// params[index] = field.getValue(); | |||||
// index++; | |||||
} | |||||
sb.setCharAt(sb.length() - 1, ' '); | |||||
sb.append("where "); | |||||
boolean first = true; | |||||
for (SqlWhereValue whereValue: wheres) { | |||||
if (first) { | |||||
first = false; | |||||
} else { | |||||
sb.append(" and "); | |||||
} | |||||
sb.append(whereValue.getName()).append("=?"); | |||||
params.add(whereValue.getValue()); | |||||
// params[index] | |||||
// index++; | |||||
} | |||||
return dbEngine.update(sb.toString(), params.toArray()); | |||||
} | |||||
} |
@@ -0,0 +1,28 @@ | |||||
package cc.smtweb.framework.core.db.vo; | |||||
import lombok.Data; | |||||
/** | |||||
* 表缓存信息定义:{"name":"tn","title":"按表名","fields":"table_name","type":"M"} | |||||
* Created by Akmm at 2022/2/21 16:22 | |||||
*/ | |||||
@Data | |||||
public class ModelCache { | |||||
//按map缓存 | |||||
public static String CACHE_TYPE_MAP = "M"; | |||||
//按list缓存 | |||||
public static String CACHE_TYPE_LIST = "L"; | |||||
//缓存名,根据此名称去获取缓存信息 getByKey的参数 | |||||
private String name; | |||||
//缓存中文名,给人看的 | |||||
private String title; | |||||
//字段,多个字段,键值以下划线分隔 | |||||
private String fields; | |||||
//缓存类别:list/map | |||||
private String type; | |||||
public boolean isMapType() { | |||||
return CACHE_TYPE_MAP.equalsIgnoreCase(type); | |||||
} | |||||
} |
@@ -0,0 +1,96 @@ | |||||
package cc.smtweb.framework.core.db.vo; | |||||
import cc.smtweb.framework.core.annotation.SwTable; | |||||
import cc.smtweb.framework.core.db.impl.DefaultEntity; | |||||
/** | |||||
* Created by Akmm at 2022/3/1 15:35 | |||||
*/ | |||||
@SwTable("ASP_MODEL_CATALOG") | |||||
public class ModelCatalog extends DefaultEntity { | |||||
public static final String ENTITY_NAME = "ASP_MODEL_CATALOG"; | |||||
public ModelCatalog() { | |||||
super(ENTITY_NAME); | |||||
} | |||||
public long getId() { | |||||
return getLong("mc_id"); | |||||
} | |||||
public void setId(long mcId) { | |||||
put("mc_id", mcId); | |||||
} | |||||
public long getParentId() { | |||||
return getLong("mc_parent_id"); | |||||
} | |||||
public void setParentId(long mcParentId) { | |||||
put("mc_parent_id", mcParentId); | |||||
} | |||||
public String getLevelCode() { | |||||
return getStr("mc_level_code"); | |||||
} | |||||
public void setLevelCode(String mcLevelCode) { | |||||
put("mc_level_code", mcLevelCode); | |||||
} | |||||
public String getCode() { | |||||
return getStr("mc_code"); | |||||
} | |||||
public void setCode(String mcCode) { | |||||
put("mc_code", mcCode); | |||||
} | |||||
public String getName() { | |||||
return getStr("mc_name"); | |||||
} | |||||
public void setName(String mcName) { | |||||
put("mc_name", mcName); | |||||
} | |||||
public long getPrjId() { | |||||
return getLong("mc_prj_id"); | |||||
} | |||||
public void setPrjId(long mcPrjId) { | |||||
put("mc_prj_id", mcPrjId); | |||||
} | |||||
public long getCreateUid() { | |||||
return getLong("mc_create_uid"); | |||||
} | |||||
public void setCreateUid(long mcCreateUid) { | |||||
put("mc_create_uid", mcCreateUid); | |||||
} | |||||
public long getUpdateUid() { | |||||
return getLong("mc_update_uid"); | |||||
} | |||||
public void setUpdateUid(long mcUpdateUid) { | |||||
put("mc_update_uid", mcUpdateUid); | |||||
} | |||||
public long getCreateAt() { | |||||
return getLong("mc_create_at"); | |||||
} | |||||
public void setCreateAt(long mcCreateAt) { | |||||
put("mc_create_at", mcCreateAt); | |||||
} | |||||
public long getUpdateAt() { | |||||
return getLong("mc_update_at"); | |||||
} | |||||
public void setUpdateAt(long mcUpdateAt) { | |||||
put("mc_update_at", mcUpdateAt); | |||||
} | |||||
} |
@@ -0,0 +1,41 @@ | |||||
package cc.smtweb.framework.core.db.vo; | |||||
import com.fasterxml.jackson.annotation.JsonIgnore; | |||||
import com.fasterxml.jackson.annotation.JsonProperty; | |||||
import lombok.Data; | |||||
/** | |||||
* 字段定义 | |||||
* {name:"字段名,如id",fieldType:"字段类型,如编码字段", dataType:"数据类型,如ID/CODE/NAME等", null:"0-空/1-非空", default: "默认值", title:"中文名",link:"外键关联表",editor:"控件类型:TEXT/TextArea/NUMBER/COMBO"} | |||||
*/ | |||||
@Data | |||||
public class ModelField { | |||||
private String name; | |||||
private String title; | |||||
private String remark; | |||||
//字段类型,如编码字段,参见FieldTypeDef | |||||
private int fieldType; | |||||
/** | |||||
* 数据类型,参见DataType | |||||
*/ | |||||
private String dataType; | |||||
/** | |||||
* '禁止为空' | |||||
*/ | |||||
@JsonProperty("null") | |||||
private int notNull; | |||||
/** | |||||
* '默认值' | |||||
*/ | |||||
@JsonProperty("default") | |||||
private String defaultValue; | |||||
//外键关联表 | |||||
private long link; | |||||
//控件类型:TEXT/TextArea/NUMBER/COMBO | |||||
private String editor; | |||||
@JsonIgnore | |||||
public boolean isNotNull() { | |||||
return notNull == 1; | |||||
} | |||||
} |
@@ -0,0 +1,96 @@ | |||||
package cc.smtweb.framework.core.db.vo; | |||||
import cc.smtweb.framework.core.annotation.SwTable; | |||||
import cc.smtweb.framework.core.db.impl.DefaultEntity; | |||||
/** | |||||
* Created by Akmm at 2022/3/1 15:35 | |||||
*/ | |||||
@SwTable("ASP_MODEL_PROJECT") | |||||
public class ModelProject extends DefaultEntity { | |||||
public static final String ENTITY_NAME = "ASP_MODEL_PROJECT"; | |||||
public ModelProject() { | |||||
super(ENTITY_NAME); | |||||
} | |||||
public long getId() { | |||||
return getLong("prj_id"); | |||||
} | |||||
public void setId(long prjId) { | |||||
put("prj_id", prjId); | |||||
} | |||||
public String getName() { | |||||
return getStr("prj_name"); | |||||
} | |||||
public void setName(String prjName) { | |||||
put("prj_name", prjName); | |||||
} | |||||
public String getModule() { | |||||
return getStr("prj_module"); | |||||
} | |||||
public void setModule(String prj_module) { | |||||
put("prj_module", prj_module); | |||||
} | |||||
public String getDepends() { | |||||
return getStr("prj_depends"); | |||||
} | |||||
public void setDepends(String prjDepends) { | |||||
put("prj_depends", prjDepends); | |||||
} | |||||
public String getDesc() { | |||||
return getStr("prj_desc"); | |||||
} | |||||
public void setDesc(String prjDesc) { | |||||
put("prj_desc", prjDesc); | |||||
} | |||||
public int getStatus() { | |||||
return getInt("prj_status"); | |||||
} | |||||
public void setStatus(int prjStatus) { | |||||
put("prj_status", prjStatus); | |||||
} | |||||
public long getCreateUid() { | |||||
return getLong("prj_create_uid"); | |||||
} | |||||
public void setCreateUid(long prjCreateUid) { | |||||
put("prj_create_uid", prjCreateUid); | |||||
} | |||||
public long getUpdateUid() { | |||||
return getLong("prj_update_uid"); | |||||
} | |||||
public void setUpdateUid(long prjUpdateUid) { | |||||
put("prj_update_uid", prjUpdateUid); | |||||
} | |||||
public long getCreateAt() { | |||||
return getLong("prj_create_at"); | |||||
} | |||||
public void setCreateAt(long prjCreateAt) { | |||||
put("prj_create_at", prjCreateAt); | |||||
} | |||||
public long getUpdateAt() { | |||||
return getLong("prj_update_at"); | |||||
} | |||||
public void setUpdateAt(long prjUpdateAt) { | |||||
put("prj_update_at", prjUpdateAt); | |||||
} | |||||
} |
@@ -0,0 +1,282 @@ | |||||
package cc.smtweb.framework.core.db.vo; | |||||
import cc.smtweb.framework.core.annotation.SwTable; | |||||
import cc.smtweb.framework.core.common.SwConsts; | |||||
import cc.smtweb.framework.core.common.SwEnum; | |||||
import cc.smtweb.framework.core.db.DbEngine; | |||||
import cc.smtweb.framework.core.db.cache.ModelDatabaseCache; | |||||
import cc.smtweb.framework.core.db.cache.ModelTableCache; | |||||
import cc.smtweb.framework.core.db.impl.DefaultEntity; | |||||
import cc.smtweb.framework.core.util.JsonUtil; | |||||
import org.apache.commons.lang3.StringUtils; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
@SwTable(value = "ASP_MODEL_TABLE") | |||||
public class ModelTable extends DefaultEntity { | |||||
public final static String ENTITY_NAME = "ASP_MODEL_TABLE"; | |||||
/*冗余*/ | |||||
private String idField; | |||||
private List<ModelField> fields = new ArrayList<>(); | |||||
private List<ModelIndex> indexes = new ArrayList<>(); | |||||
private List<ModelCache> caches = new ArrayList<>(); | |||||
public ModelTable() { | |||||
super(ENTITY_NAME); | |||||
} | |||||
public long getId() { | |||||
return getLong("tb_id"); | |||||
} | |||||
public void setId(long tbId) { | |||||
put("tb_id", tbId); | |||||
} | |||||
public long getPrjId() { | |||||
return getLong("tb_prj_id"); | |||||
} | |||||
public void setPrjId(long tbPrjId) { | |||||
put("tb_prj_id", tbPrjId); | |||||
} | |||||
public long getMcId() { | |||||
return getLong("tb_mc_id"); | |||||
} | |||||
public void setMcId(long tbMcId) { | |||||
put("tb_mc_id", tbMcId); | |||||
} | |||||
public long getDbId() { | |||||
return getLong("tb_db_id"); | |||||
} | |||||
public void setDbId(long tbDbId) { | |||||
put("tb_db_id", tbDbId); | |||||
} | |||||
public String getExtends() { | |||||
return getStr("tb_extends"); | |||||
} | |||||
public void setExtends(String tbExtends) { | |||||
put("tb_extends", tbExtends); | |||||
} | |||||
public String getName() { | |||||
return getStr("tb_name"); | |||||
} | |||||
public void setName(String tbName) { | |||||
put("tb_name", tbName); | |||||
} | |||||
public String getTitle() { | |||||
return getStr("tb_title"); | |||||
} | |||||
public void setTitle(String tbTitle) { | |||||
put("tb_title", tbTitle); | |||||
} | |||||
public String getAbbr() { | |||||
return getStr("tb_abbr"); | |||||
} | |||||
public void setAbbr(String tbAbbr) { | |||||
put("tb_abbr", tbAbbr); | |||||
} | |||||
public int getType() { | |||||
return getInt("tb_type"); | |||||
} | |||||
public void setType(int tbType) { | |||||
put("tb_type", tbType); | |||||
} | |||||
public boolean isNeedCache() { | |||||
return getBool("tb_need_cache"); | |||||
} | |||||
public void setNeedCache(boolean tbNeedCache) { | |||||
setBool("tb_need_cache", tbNeedCache); | |||||
} | |||||
public String getContent() { | |||||
return getStr("tb_content"); | |||||
} | |||||
public long getCreateUid() { | |||||
return getLong("tb_create_uid"); | |||||
} | |||||
public void setCreateUid(long tbCreateUid) { | |||||
put("tb_create_uid", tbCreateUid); | |||||
} | |||||
public long getUpdateUid() { | |||||
return getLong("tb_update_uid"); | |||||
} | |||||
public void setUpdateUid(long tbUpdateUid) { | |||||
put("tb_update_uid", tbUpdateUid); | |||||
} | |||||
public long getCreateAt() { | |||||
return getLong("tb_create_at"); | |||||
} | |||||
public void setCreateAt(long tbCreateAt) { | |||||
put("tb_create_at", tbCreateAt); | |||||
} | |||||
public long getUpdateAt() { | |||||
return getLong("tb_update_at"); | |||||
} | |||||
public void setUpdateAt(long tbUpdateAt) { | |||||
put("tb_update_at", tbUpdateAt); | |||||
} | |||||
public String getIdField() { | |||||
return idField; | |||||
} | |||||
public String getDbName() { | |||||
return ModelDatabaseCache.getInstance().getName(getDbId()); | |||||
} | |||||
public List<ModelField> getFields() { | |||||
return fields; | |||||
} | |||||
public List<ModelIndex> getIndexes() { | |||||
return indexes; | |||||
} | |||||
public List<ModelCache> getCaches() { | |||||
return caches; | |||||
} | |||||
public ModelField findField(String fieldName) { | |||||
if (fieldName != null && fields != null) { | |||||
for (ModelField modelField : fields) { | |||||
if (fieldName.equals(modelField.getName())) { | |||||
return modelField; | |||||
} | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
public String findFieldTitle(String fieldName) { | |||||
ModelField field = findField(fieldName); | |||||
return field != null ? field.getTitle() : fieldName; | |||||
} | |||||
public String fullName() { | |||||
return getDbName() + '.' + getName(); | |||||
} | |||||
public void addIndex(ModelIndex modelIndex) { | |||||
indexes.add(modelIndex); | |||||
} | |||||
public ModelIndex findIndexByName(String indexName) { | |||||
for (ModelIndex modelIndex : indexes) { | |||||
if (indexName.equals(modelIndex.getName())) { | |||||
return modelIndex; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
public ModelIndex findPrimaryIndex() { | |||||
for (ModelIndex modelIndex : indexes) { | |||||
if (ModelIndex.TYPE_PRIMARY.equalsIgnoreCase(modelIndex.getType())) { | |||||
return modelIndex; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
public ModelField findFieldByName(String name) { | |||||
if (name != null) { | |||||
for (ModelField value : fields) { | |||||
if (name.equalsIgnoreCase(value.getName())) { | |||||
return value; | |||||
} | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
public ModelField findFieldByType(int type) { | |||||
for (ModelField value : fields) { | |||||
if (type == value.getFieldType()) { | |||||
return value; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
public ModelField findIdField() { | |||||
ModelIndex index = findPrimaryIndex(); | |||||
if (index != null) { | |||||
return findField(index.getFields()); | |||||
} | |||||
return null; | |||||
} | |||||
public List<ModelLinkName> findLinkeNames() { | |||||
List<ModelLinkName> list = new ArrayList<>(); | |||||
for (ModelField field : fields) { | |||||
if (field.getLink()<=0) { | |||||
continue; | |||||
} | |||||
ModelTable linkTable = ModelTableCache.getInstance().get(field.getLink()); | |||||
if (linkTable == null) { | |||||
continue; | |||||
} | |||||
ModelField linkNameField = linkTable.findFieldByType(SwEnum.FieldType.NAME.value); | |||||
if (linkNameField != null) { | |||||
list.add(new ModelLinkName(field.getName(), linkTable, linkNameField.getName())); | |||||
} | |||||
} | |||||
return list; | |||||
} | |||||
public void setContent(String tableContent) { | |||||
put("tb_content", tableContent); | |||||
//读取表定义信息 | |||||
ModelTable bean = JsonUtil.parse(tableContent, ModelTable.class); | |||||
if (bean == null) { | |||||
return; | |||||
} | |||||
this.fields = bean.fields; | |||||
this.indexes = bean.indexes; | |||||
this.caches = bean.caches; | |||||
ModelIndex i = findPrimaryIndex(); | |||||
if (i != null) { | |||||
this.idField = i.getFields(); | |||||
} | |||||
} | |||||
public String getSchemaTableName() { | |||||
String dbName = getDbName(); | |||||
if (StringUtils.isEmpty(dbName) || dbName.equals(SwConsts.DEF_DB_NAME)) return getName(); | |||||
return DbEngine.getInstance().getDbSchema() + "_" + dbName + "." + getName(); | |||||
} | |||||
} |
@@ -0,0 +1,39 @@ | |||||
package cc.smtweb.framework.core.exception; | |||||
/** | |||||
* bean绑定错误 | |||||
* @author kevin | |||||
* @since 2010-9-14 上午11:17:43 | |||||
* | |||||
*/ | |||||
public class BindBeanException extends SwException { | |||||
/** | |||||
* | |||||
*/ | |||||
private static final long serialVersionUID = 1L; | |||||
private static String msg = "绑定bean异常:Context中已经存在这个bean: "; | |||||
public BindBeanException() { | |||||
super(); | |||||
// TODO Auto-generated constructor stub | |||||
} | |||||
public BindBeanException(String message, Throwable cause) { | |||||
super(message, cause); | |||||
// TODO Auto-generated constructor stub | |||||
} | |||||
public BindBeanException(String message) { | |||||
super(msg+message); | |||||
// TODO Auto-generated constructor stub | |||||
} | |||||
public BindBeanException(Throwable cause) { | |||||
super(cause); | |||||
// TODO Auto-generated constructor stub | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
package cc.smtweb.framework.core.exception; | |||||
/** | |||||
* bean绑定错误 | |||||
* @author kevin | |||||
* @since 2010-9-14 上午11:17:43 | |||||
* | |||||
*/ | |||||
public class DbException extends RuntimeException { | |||||
/** | |||||
* | |||||
*/ | |||||
private static final long serialVersionUID = 1L; | |||||
public DbException() { | |||||
super(); | |||||
} | |||||
public DbException(String message, Throwable cause) { | |||||
super(message, cause); | |||||
} | |||||
public DbException(String message) { | |||||
super(message); | |||||
} | |||||
public DbException(Throwable cause) { | |||||
super(cause.getMessage(), cause); | |||||
} | |||||
} |
@@ -0,0 +1,7 @@ | |||||
package cc.smtweb.framework.core.exception; | |||||
public class JsonParseException extends SwException { | |||||
public JsonParseException(String s, Exception e) { | |||||
super(s, e); | |||||
} | |||||
} |
@@ -0,0 +1,29 @@ | |||||
package cc.smtweb.framework.core.mvc; | |||||
import org.springframework.stereotype.Component; | |||||
/** | |||||
* 定时器服务 | |||||
* @author xkliu | |||||
*/ | |||||
@Component | |||||
public class SchedulerManager { | |||||
private ISchedulerWakeup schedulerWakeup; | |||||
public void install(ISchedulerWakeup schedulerWakeup) { | |||||
this.schedulerWakeup = schedulerWakeup; | |||||
} | |||||
/** | |||||
* 唤醒定时器立即执行 | |||||
* @param clazz 定时器类 | |||||
* @param methodName 有@SwScheduling注解的定时器方法名 | |||||
*/ | |||||
public boolean wakeup(Class<?> clazz, String methodName) { | |||||
if (this.schedulerWakeup != null) { | |||||
return schedulerWakeup.wakeup(clazz, methodName); | |||||
} | |||||
return false; | |||||
} | |||||
} |
@@ -0,0 +1,24 @@ | |||||
package cc.smtweb.framework.core.mvc.config; | |||||
import cc.smtweb.framework.core.mvc.controller.scan.IScanActionBuilder; | |||||
import lombok.Getter; | |||||
@Getter | |||||
public class ControllerConfig { | |||||
/** 模块名称 */ | |||||
private final String module; | |||||
/** 控制器的包路径 */ | |||||
private final String packagePath; | |||||
private IScanActionBuilder scanActionBuilder; | |||||
public ControllerConfig(String module, String packagePath) { | |||||
this(module, packagePath, null); | |||||
} | |||||
public ControllerConfig(String module, String packagePath, IScanActionBuilder scanActionBuilder) { | |||||
this.module = module; | |||||
this.packagePath = packagePath; | |||||
this.scanActionBuilder = scanActionBuilder; | |||||
} | |||||
} |
@@ -0,0 +1,58 @@ | |||||
package cc.smtweb.framework.core.mvc.config; | |||||
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; | |||||
import org.springframework.boot.SpringApplication; | |||||
import org.springframework.boot.env.EnvironmentPostProcessor; | |||||
import org.springframework.core.env.ConfigurableEnvironment; | |||||
import org.springframework.core.env.PropertiesPropertySource; | |||||
import org.springframework.core.io.FileSystemResource; | |||||
import java.io.File; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
/** | |||||
* 〈读取外部配置文件〉 | |||||
* | |||||
* @author kevin | |||||
* @since 1.0.0 | |||||
*/ | |||||
public class SettingsEnvironmentPostProcessor implements EnvironmentPostProcessor { | |||||
@Override | |||||
public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, | |||||
SpringApplication springApplication) { | |||||
List<File> files = this.getProperties(); | |||||
for (File file : files) { | |||||
if (file.exists()) { | |||||
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean(); | |||||
FileSystemResource resource = new FileSystemResource(file); | |||||
yaml.setResources(resource); | |||||
if (yaml.getObject() != null) { | |||||
configurableEnvironment.getPropertySources() | |||||
.addLast(new PropertiesPropertySource(file.getName(), yaml.getObject())); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* <>获取所有资源配置文件</> | |||||
*/ | |||||
private List<File> getProperties() { | |||||
List<File> result = new ArrayList<>(); | |||||
String tomcatConf = System.getProperty("catalina.base") + File.separator + "conf" + File.separator; | |||||
File[] listFiles = new File(tomcatConf).listFiles(); | |||||
if (listFiles != null && listFiles.length > 0) { | |||||
for (File lf : listFiles) { | |||||
if (lf.isFile()) { | |||||
if (lf.getName().endsWith(".yaml")) { | |||||
result.add(lf); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return result; | |||||
} | |||||
} |
@@ -0,0 +1,60 @@ | |||||
package cc.smtweb.framework.core.mvc.controller; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.http.MediaType; | |||||
import org.springframework.web.bind.annotation.*; | |||||
import javax.annotation.PreDestroy; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpServletResponse; | |||||
import java.util.Map; | |||||
/** | |||||
* API接口控制器,所有API的入口 | |||||
* @author xkliu | |||||
*/ | |||||
@RestController | |||||
public class ApiController { | |||||
@Autowired | |||||
private MethodAccessManager methodAccessManager; | |||||
/** | |||||
* 处理API的GET请求 | |||||
* @param params URL请求参数 | |||||
* @param request Http请求类 | |||||
* @param response Http应答类 | |||||
* @return 返回对象,一般在函数里面使用R | |||||
* @throws Exception API处理异常 | |||||
*/ | |||||
@GetMapping(value = "/api/**", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}) | |||||
public Object commonGet(@RequestParam Map<String, Object> params, | |||||
HttpServletRequest request, HttpServletResponse response) throws Exception { | |||||
String apiUrl = request.getRequestURI().substring(5); | |||||
return methodAccessManager.invoke(apiUrl, params, null, request, response); | |||||
} | |||||
/** | |||||
* 处理API的POST请求 | |||||
* @param params URL请求参数 | |||||
* @param body POST内容,JSON格式 | |||||
* @param request Http请求类 | |||||
* @param response Http应答类 | |||||
* @return 返回对象,一般在函数里面使用R | |||||
* @throws Exception API处理异常 | |||||
*/ | |||||
@PostMapping(value = "/api/**", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}) | |||||
public Object commonPost(@RequestParam Map<String, Object> params, @RequestBody(required = false) String body, | |||||
HttpServletRequest request, HttpServletResponse response) throws Exception { | |||||
String apiUrl = request.getRequestURI().substring(5); | |||||
// 保证body在post的清空下不为null,用于区分get时吧参数组合成body | |||||
if (body == null) { | |||||
body = ""; | |||||
} | |||||
return methodAccessManager.invoke(apiUrl, params, body, request, response); | |||||
} | |||||
@PreDestroy | |||||
public void fin() { | |||||
methodAccessManager.showdown(); | |||||
} | |||||
} |
@@ -0,0 +1,7 @@ | |||||
package cc.smtweb.framework.core.mvc.controller; | |||||
import cc.smtweb.framework.core.mvc.controller.access.IMethodAccess; | |||||
public interface IActionManager { | |||||
boolean api(String url, IMethodAccess methodAccess); | |||||
} |
@@ -0,0 +1,8 @@ | |||||
package cc.smtweb.framework.core.mvc.controller; | |||||
import org.springframework.beans.BeansException; | |||||
public interface IBeanContext { | |||||
<T> T getBean(String name, Class<T> type) throws BeansException; | |||||
<T> T getBean(Class<T> type) throws BeansException; | |||||
} |
@@ -0,0 +1,27 @@ | |||||
package cc.smtweb.framework.core.mvc.controller; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import java.util.Map; | |||||
/** | |||||
* API参数解析器接口 | |||||
* @author xkliu | |||||
*/ | |||||
public interface IEditor { | |||||
String USER_SESSION = "_SW_USER_SESSION"; | |||||
String USER_TOKEN = "_SW_TOKEN"; | |||||
String BODY_MAP = "_SW_BODY_MAP"; | |||||
// String BODY_BEAN = "_SW_BODY_BEAN"; | |||||
/** | |||||
* 根据请求参数转为函数参数定义的类型的值 | |||||
* @param paramName 参数名 | |||||
* @param paramType 参数类型 | |||||
* @param context 全局bean对象上下文 | |||||
* @param params URL请求参数(query) | |||||
* @param body POST内容 | |||||
* @param request http请求 | |||||
* @return 转化后的值 | |||||
*/ | |||||
Object getParamValue(String paramName, Class<?> paramType, IBeanContext context, Map<String, Object> params, String body, HttpServletRequest request); | |||||
} |
@@ -0,0 +1,105 @@ | |||||
package cc.smtweb.framework.core.mvc.controller; | |||||
import cc.smtweb.framework.core.cache.CacheManager; | |||||
import cc.smtweb.framework.core.cache.ISwCache; | |||||
import cc.smtweb.framework.core.cache.redis.RedisManager; | |||||
import cc.smtweb.framework.core.common.R; | |||||
import cc.smtweb.framework.core.mvc.SchedulerManager; | |||||
import cc.smtweb.framework.core.mvc.controller.access.IMethodAccess; | |||||
import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; | |||||
import cc.smtweb.framework.core.mvc.controller.scan.BeanManager; | |||||
import cc.smtweb.framework.core.mvc.realm.interceptor.PermInterceptor; | |||||
import cc.smtweb.framework.core.mvc.realm.service.PermChecker; | |||||
import cc.smtweb.framework.core.mvc.scheduler.SchedulerTaskManager; | |||||
import lombok.Getter; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpServletResponse; | |||||
import java.util.Map; | |||||
/** | |||||
* 服务方法调用管理,通过url进行API调用 | |||||
* @author xkliu | |||||
*/ | |||||
@Slf4j | |||||
public class MethodAccessManager { | |||||
private Map<String, IMethodAccess> controllers; | |||||
private IBeanContext beanContext; | |||||
private PermInterceptor permInterceptor; | |||||
private SchedulerTaskManager schedulerTaskManager; | |||||
private MethodAccess[] destroyMethods; | |||||
@Getter | |||||
private CacheManager cacheManager; | |||||
public MethodAccessManager(RedisManager redisManager, CacheManager cacheManager) { | |||||
permInterceptor = new PermInterceptor(redisManager); | |||||
this.cacheManager = cacheManager; | |||||
} | |||||
// 执行控制器方法 | |||||
Object invoke(String url, Map<String, Object> params, String body, HttpServletRequest request, HttpServletResponse response) throws Exception { | |||||
// 调用普通方法 | |||||
// String url = module + "/" + service + "/" + method; | |||||
IMethodAccess methodAccess = controllers.get(url); | |||||
if (methodAccess != null) { | |||||
permInterceptor.preHandle(request, methodAccess.getPerm()); | |||||
return methodAccess.invoke(beanContext, params, body, request); | |||||
} | |||||
// TODO 调用默认方法 | |||||
// url = module + "/" + service + "/"; | |||||
// | |||||
// methodAccess = controllers.get(url); | |||||
// | |||||
// if (methodAccess != null) { | |||||
// permInterceptor.preHandle(request, methodAccess.getPerm()); | |||||
// | |||||
// request.setAttribute(PathParamEditor.ATTR_KEY, method); | |||||
// return methodAccess.invoke(beanContext, params, body, request, response); | |||||
// } | |||||
return R.error(404, "not find api:" + url); | |||||
} | |||||
public void init(BeanManager beanManager, ISwCache<Long, PermChecker> cache) { | |||||
this.beanContext = beanManager.getBeanContext(); | |||||
this.controllers = beanManager.getControllers(); | |||||
this.destroyMethods = beanManager.loadDestroyMethods(); | |||||
this.permInterceptor.setCache(cache); | |||||
// 启动定时任务 | |||||
this.schedulerTaskManager = SchedulerTaskManager.build(beanContext, beanManager.getTasks()); | |||||
if (this.schedulerTaskManager != null) { | |||||
// 设置用于外部服务调用 | |||||
SchedulerManager schedulerManager = this.beanContext.getBean(SchedulerManager.class); | |||||
if (schedulerManager != null) { | |||||
schedulerManager.install(this.schedulerTaskManager); | |||||
} else { | |||||
log.error("not find spring bean schedulerManager"); | |||||
} | |||||
} | |||||
} | |||||
void showdown() { | |||||
// 强行关闭定时任务 | |||||
if (this.schedulerTaskManager != null) { | |||||
this.schedulerTaskManager.shutdown(); | |||||
} | |||||
if (destroyMethods != null) { | |||||
for (MethodAccess methodAccess : destroyMethods) { | |||||
try { | |||||
methodAccess.invoke(beanContext); | |||||
} catch (Exception e) { | |||||
log.error(methodAccess.fullName(), e); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.access; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import cc.smtweb.framework.core.mvc.controller.IEditor; | |||||
import java.lang.reflect.Field; | |||||
import java.util.Map; | |||||
public class BindFieldAccess extends FieldAccess { | |||||
private IEditor editor; | |||||
private final String beanName; | |||||
public BindFieldAccess(IEditor editor, Field field, String beanName) { | |||||
super(field); | |||||
this.editor = editor; | |||||
this.beanName = beanName; | |||||
} | |||||
@Override | |||||
protected Object getFieldValue(Object instance, IBeanContext context, Map<String, Object> params) { | |||||
if (editor != null) { | |||||
return editor.getParamValue(beanName, this.field.getType(), context, params, null, null); | |||||
} | |||||
return null; | |||||
} | |||||
} |
@@ -0,0 +1,39 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.access; | |||||
import cc.smtweb.framework.core.exception.BindBeanException; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import java.lang.reflect.Field; | |||||
import java.util.Map; | |||||
public abstract class FieldAccess { | |||||
protected final Field field; | |||||
public FieldAccess(Field field) { | |||||
this.field = field; | |||||
} | |||||
protected abstract Object getFieldValue(Object instance, IBeanContext context, Map<String, Object> params); | |||||
public boolean writeValue(Object instance, IBeanContext context, Map<String, Object> params) { | |||||
Object bean = getFieldValue(instance, context, params); | |||||
if (bean != null) { | |||||
field.setAccessible(true); | |||||
try { | |||||
field.set(instance, bean); | |||||
} catch (IllegalArgumentException | IllegalAccessException e) { | |||||
throw new BindBeanException("not set " + getFullName(instance) + " value", e); | |||||
} | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
public String getFullName(Object instance) { | |||||
return instance.getClass().getName() + "." + field.getName(); | |||||
} | |||||
} |
@@ -0,0 +1,5 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.access; | |||||
public interface IBeanAccess { | |||||
Object getSingletonInstance(); | |||||
} |
@@ -0,0 +1,42 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.access; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import cc.smtweb.framework.core.mvc.controller.IEditor; | |||||
import lombok.Getter; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import java.util.Map; | |||||
/** | |||||
* API方法参数访问器 | |||||
* @author xkliu | |||||
*/ | |||||
@Getter | |||||
public class MethodParamAccess { | |||||
private int bindType; | |||||
private final IEditor editor; | |||||
private final String paramName; | |||||
private final Class<?> collectionType; | |||||
private final Class<?> paramType; | |||||
public MethodParamAccess(IEditor editor, String paramName, Class<?> paramType, int bindType, Class<?> collectionType) { | |||||
this.editor = editor; | |||||
this.paramName = paramName; | |||||
this.paramType = paramType; | |||||
this.bindType = bindType; | |||||
this.collectionType = collectionType; | |||||
} | |||||
public Object getParamValue(IBeanContext context, Map<String, Object> params, String body, HttpServletRequest request) { | |||||
if (editor != null) { | |||||
return editor.getParamValue(paramName, paramType, context, params, body, request); | |||||
} | |||||
return null; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return paramName + ":" + paramType.getSimpleName() + "-" + editor.getClass().getSimpleName(); | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.access; | |||||
import cc.smtweb.framework.core.annotation.SwScheduling; | |||||
import com.serotonin.timer.CronTimerTrigger; | |||||
import com.serotonin.timer.TimerTrigger; | |||||
import lombok.Getter; | |||||
import java.text.ParseException; | |||||
@Getter | |||||
public class SchedulerMethodAccess { | |||||
private final MethodAccess methodAccess; | |||||
private final SwScheduling swScheduling; | |||||
private final TimerTrigger timerTrigger; | |||||
public SchedulerMethodAccess(MethodAccess methodAccess, SwScheduling swScheduling) throws ParseException { | |||||
this.timerTrigger = new CronTimerTrigger(swScheduling.value()); | |||||
this.methodAccess = methodAccess; | |||||
this.swScheduling = swScheduling; | |||||
} | |||||
} |
@@ -0,0 +1,20 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.access; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import java.lang.reflect.Field; | |||||
import java.util.Map; | |||||
public class SingletonFieldAccess extends FieldAccess { | |||||
private final Object fieldValue; | |||||
public SingletonFieldAccess(Object fieldValue, Field field) { | |||||
super(field); | |||||
this.fieldValue = fieldValue; | |||||
} | |||||
@Override | |||||
protected Object getFieldValue(Object instance, IBeanContext context, Map<String, Object> params) { | |||||
return fieldValue; | |||||
} | |||||
} |
@@ -0,0 +1,26 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder; | |||||
import cc.smtweb.framework.core.cache.CacheManager; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import cc.smtweb.framework.core.mvc.controller.IEditor; | |||||
import org.apache.commons.lang3.StringUtils; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import java.util.Map; | |||||
public class CacheEditor implements IEditor { | |||||
private CacheManager cacheManager; | |||||
public CacheEditor(CacheManager cacheManager) { | |||||
this.cacheManager = cacheManager; | |||||
} | |||||
@Override | |||||
public Object getParamValue(String paramName, Class<?> paramType, IBeanContext context, Map<String, Object> params, String body, HttpServletRequest request) { | |||||
if (StringUtils.isNotBlank(paramName)) { | |||||
return cacheManager.getCache(paramName); | |||||
} else { | |||||
return cacheManager.getCache(paramType.getSimpleName()); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,225 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder; | |||||
import cc.smtweb.framework.core.cache.AbstractCache; | |||||
import cc.smtweb.framework.core.common.SwIpAddr; | |||||
import cc.smtweb.framework.core.common.SwMap; | |||||
import cc.smtweb.framework.core.mvc.controller.IEditor; | |||||
import cc.smtweb.framework.core.mvc.controller.binder.attr.BeanAttrEditor; | |||||
import cc.smtweb.framework.core.mvc.controller.binder.bean.*; | |||||
import cc.smtweb.framework.core.mvc.controller.binder.body.BeanBodyEditor; | |||||
import cc.smtweb.framework.core.mvc.controller.binder.body.StringBodyEditor; | |||||
import cc.smtweb.framework.core.mvc.controller.binder.body.SwMapBodyEditor; | |||||
import cc.smtweb.framework.core.mvc.controller.binder.param.*; | |||||
import cc.smtweb.framework.core.mvc.controller.binder.path.PathParamEditor; | |||||
import cc.smtweb.framework.core.mvc.scheduler.SchedulerPoint; | |||||
import cc.smtweb.framework.core.session.UserSession; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import java.sql.Timestamp; | |||||
@Slf4j | |||||
public class WebDataBinder { | |||||
private ParamEditor paramEditor = new ParamEditor(new BeanEditor()); | |||||
private ParamEditor bodyEditor = new ParamEditor(new BeanBodyEditor()); | |||||
private ParamEditor pathEditor = new ParamEditor(new PathParamEditor()); | |||||
private ParamEditor headerEditor = new ParamEditor(new NullEditor()); | |||||
private ParamEditor attributeEditor = new ParamEditor(new BeanAttrEditor()); | |||||
private IEditor cacheEditor; | |||||
public WebDataBinder(IEditor cacheEditor) { | |||||
this.cacheEditor = cacheEditor; | |||||
initParamEditor(); | |||||
initBodyEditor(); | |||||
initHeaderEditor(); | |||||
} | |||||
// public <T> void addParamEditor(Class<T> type, IEditor editor) { | |||||
// paramEditor.add(String.class, editor); | |||||
// } | |||||
public IEditor findEditor(Class<?> paramType, int bindType) { | |||||
IEditor result; | |||||
switch (bindType) { | |||||
case ParamEditor.TYPE_UNKNOWN: | |||||
case ParamEditor.TYPE_PARAM: | |||||
case ParamEditor.TYPE_FIELD: | |||||
if (AbstractCache.class.isAssignableFrom(paramType)) { | |||||
result = cacheEditor; | |||||
} else { | |||||
result = paramEditor.find(paramType); | |||||
} | |||||
break; | |||||
case ParamEditor.TYPE_BODY: | |||||
// @SwBody | |||||
result = bodyEditor.find(paramType); | |||||
break; | |||||
case ParamEditor.TYPE_PATH: | |||||
result = pathEditor.find(paramType); | |||||
break; | |||||
case ParamEditor.TYPE_HEADER: | |||||
result = headerEditor.find(paramType); | |||||
break; | |||||
case ParamEditor.TYPE_ATTR: | |||||
result = attributeEditor.find(paramType); | |||||
break; | |||||
default: | |||||
throw new IllegalStateException("Unexpected type value: " + bindType); | |||||
} | |||||
return result; | |||||
} | |||||
private void initParamEditor() { | |||||
TimestampEditor timestampEditor = new TimestampEditor(ParamEditor.TYPE_PARAM); | |||||
paramEditor.add(String.class, new StringEditor(ParamEditor.TYPE_PARAM)); | |||||
paramEditor.add(Timestamp.class, timestampEditor); | |||||
paramEditor.add(java.util.Date.class, timestampEditor); | |||||
paramEditor.add(java.sql.Date.class, new DateEditor(ParamEditor.TYPE_PARAM)); | |||||
paramEditor.add(java.sql.Time.class, new TimeEditor(ParamEditor.TYPE_PARAM)); | |||||
paramEditor.add(Byte.TYPE, new ByteEditor(ParamEditor.TYPE_PARAM,true)); | |||||
paramEditor.add(Long.TYPE, new LongEditor(ParamEditor.TYPE_PARAM, true)); | |||||
paramEditor.add(Double.TYPE, new DoubleEditor(ParamEditor.TYPE_PARAM, true)); | |||||
paramEditor.add(Float.TYPE, new FloatEditor(ParamEditor.TYPE_PARAM, true)); | |||||
paramEditor.add(Short.TYPE, new ShortEditor(ParamEditor.TYPE_PARAM, true)); | |||||
paramEditor.add(Integer.TYPE, new IntegerEditor(ParamEditor.TYPE_PARAM, true)); | |||||
paramEditor.add(Boolean.TYPE, new BooleanEditor(ParamEditor.TYPE_PARAM, true)); | |||||
paramEditor.add(Character.TYPE,new CharEditor(ParamEditor.TYPE_PARAM, true)); | |||||
paramEditor.add(Byte.class, new ByteEditor(ParamEditor.TYPE_PARAM, false)); | |||||
paramEditor.add(Long.class, new LongEditor(ParamEditor.TYPE_PARAM, false)); | |||||
paramEditor.add(Double.class, new DoubleEditor(ParamEditor.TYPE_PARAM,false)); | |||||
paramEditor.add(Float.class, new FloatEditor(ParamEditor.TYPE_PARAM,false)); | |||||
paramEditor.add(Short.class, new ShortEditor(ParamEditor.TYPE_PARAM,false)); | |||||
paramEditor.add(Integer.class, new IntegerEditor(ParamEditor.TYPE_PARAM,false)); | |||||
paramEditor.add(Boolean.class, new BooleanEditor(ParamEditor.TYPE_PARAM,false)); | |||||
paramEditor.add(HttpServletRequest.class, new HttpServletRequestEditor()); | |||||
// paramEditor.add(HttpServletResponse.class, new HttpServletResponseEditor()); | |||||
paramEditor.add(UserSession.class, new UserSessionEditor()); | |||||
paramEditor.add(SwIpAddr.class, new SwIpAddrEditor()); | |||||
paramEditor.add(SchedulerPoint.class, new BeanTypeEditor()); | |||||
} | |||||
private void initBodyEditor() { | |||||
TimestampEditor timestampEditor = new TimestampEditor(ParamEditor.TYPE_BODY); | |||||
bodyEditor.add(String.class, new StringEditor(ParamEditor.TYPE_BODY)); | |||||
bodyEditor.add(Timestamp.class, timestampEditor); | |||||
bodyEditor.add(java.util.Date.class, timestampEditor); | |||||
bodyEditor.add(java.sql.Date.class, new DateEditor(ParamEditor.TYPE_BODY)); | |||||
bodyEditor.add(java.sql.Time.class, new TimeEditor(ParamEditor.TYPE_BODY)); | |||||
bodyEditor.add(Byte.TYPE, new ByteEditor(ParamEditor.TYPE_BODY,true)); | |||||
bodyEditor.add(Long.TYPE, new LongEditor(ParamEditor.TYPE_BODY, true)); | |||||
bodyEditor.add(Double.TYPE, new DoubleEditor(ParamEditor.TYPE_BODY, true)); | |||||
bodyEditor.add(Float.TYPE, new FloatEditor(ParamEditor.TYPE_BODY, true)); | |||||
bodyEditor.add(Short.TYPE, new ShortEditor(ParamEditor.TYPE_BODY, true)); | |||||
bodyEditor.add(Integer.TYPE, new IntegerEditor(ParamEditor.TYPE_BODY, true)); | |||||
bodyEditor.add(Boolean.TYPE, new BooleanEditor(ParamEditor.TYPE_BODY, true)); | |||||
bodyEditor.add(Character.TYPE,new CharEditor(ParamEditor.TYPE_BODY, true)); | |||||
bodyEditor.add(Byte.class, new ByteEditor(ParamEditor.TYPE_BODY, false)); | |||||
bodyEditor.add(Long.class, new LongEditor(ParamEditor.TYPE_BODY, false)); | |||||
bodyEditor.add(Double.class, new DoubleEditor(ParamEditor.TYPE_BODY,false)); | |||||
bodyEditor.add(Float.class, new FloatEditor(ParamEditor.TYPE_BODY,false)); | |||||
bodyEditor.add(Short.class, new ShortEditor(ParamEditor.TYPE_BODY,false)); | |||||
bodyEditor.add(Integer.class, new IntegerEditor(ParamEditor.TYPE_BODY,false)); | |||||
bodyEditor.add(Boolean.class, new BooleanEditor(ParamEditor.TYPE_BODY,false)); | |||||
bodyEditor.add(String.class, new StringBodyEditor()); | |||||
bodyEditor.add(SwMap.class, new SwMapBodyEditor()); | |||||
} | |||||
private void initHeaderEditor() { | |||||
TimestampEditor timestampEditor = new TimestampEditor(ParamEditor.TYPE_HEADER); | |||||
headerEditor.add(String.class, new StringEditor(ParamEditor.TYPE_HEADER)); | |||||
headerEditor.add(Timestamp.class, timestampEditor); | |||||
headerEditor.add(java.util.Date.class, timestampEditor); | |||||
headerEditor.add(java.sql.Date.class, new DateEditor(ParamEditor.TYPE_HEADER)); | |||||
headerEditor.add(java.sql.Time.class, new TimeEditor(ParamEditor.TYPE_HEADER)); | |||||
headerEditor.add(Byte.TYPE, new ByteEditor(ParamEditor.TYPE_HEADER,true)); | |||||
headerEditor.add(Long.TYPE, new LongEditor(ParamEditor.TYPE_HEADER, true)); | |||||
headerEditor.add(Double.TYPE, new DoubleEditor(ParamEditor.TYPE_HEADER, true)); | |||||
headerEditor.add(Float.TYPE, new FloatEditor(ParamEditor.TYPE_HEADER, true)); | |||||
headerEditor.add(Short.TYPE, new ShortEditor(ParamEditor.TYPE_HEADER, true)); | |||||
headerEditor.add(Integer.TYPE, new IntegerEditor(ParamEditor.TYPE_HEADER, true)); | |||||
headerEditor.add(Boolean.TYPE, new BooleanEditor(ParamEditor.TYPE_HEADER, true)); | |||||
headerEditor.add(Character.TYPE,new CharEditor(ParamEditor.TYPE_HEADER, true)); | |||||
headerEditor.add(Byte.class, new ByteEditor(ParamEditor.TYPE_HEADER, false)); | |||||
headerEditor.add(Long.class, new LongEditor(ParamEditor.TYPE_HEADER, false)); | |||||
headerEditor.add(Double.class, new DoubleEditor(ParamEditor.TYPE_HEADER,false)); | |||||
headerEditor.add(Float.class, new FloatEditor(ParamEditor.TYPE_HEADER,false)); | |||||
headerEditor.add(Short.class, new ShortEditor(ParamEditor.TYPE_HEADER,false)); | |||||
headerEditor.add(Integer.class, new IntegerEditor(ParamEditor.TYPE_HEADER,false)); | |||||
headerEditor.add(Boolean.class, new BooleanEditor(ParamEditor.TYPE_HEADER,false)); | |||||
} | |||||
public void bindParam(Class<?> type, IEditor editor) { | |||||
paramEditor.add(type, editor); | |||||
} | |||||
public void bindBody(Class<?> type, IEditor editor) { | |||||
bodyEditor.add(type, editor); | |||||
} | |||||
/** | |||||
* 绑定自定义参数读取器 | |||||
* @param bindType 绑定类型ParamEditor.TYPE_XXX | |||||
* @param type 参数类型 | |||||
* @param editor 参数读取器 | |||||
*/ | |||||
public void bind(int bindType, Class<?> type, IEditor editor) { | |||||
switch (bindType) { | |||||
case ParamEditor.TYPE_PARAM: | |||||
paramEditor.add(type, editor); | |||||
break; | |||||
case ParamEditor.TYPE_BODY: | |||||
// @SwBody | |||||
bodyEditor.add(type, editor); | |||||
break; | |||||
case ParamEditor.TYPE_PATH: | |||||
pathEditor.add(type, editor); | |||||
break; | |||||
case ParamEditor.TYPE_HEADER: | |||||
headerEditor.add(type, editor);; | |||||
break; | |||||
case ParamEditor.TYPE_ATTR: | |||||
attributeEditor.add(type, editor); | |||||
break; | |||||
default: | |||||
throw new IllegalStateException("Unexpected type value: " + bindType); | |||||
} | |||||
} | |||||
/** | |||||
* 绑定默认自定义参数读取器 | |||||
* @param bindType 绑定类型ParamEditor.TYPE_XXX | |||||
* @param editor 默认参数读取器 | |||||
*/ | |||||
public void bindDefault(int bindType, IEditor editor) { | |||||
switch (bindType) { | |||||
case ParamEditor.TYPE_PARAM: | |||||
paramEditor.setDefaultEditor(editor); | |||||
break; | |||||
case ParamEditor.TYPE_BODY: | |||||
// @SwBody | |||||
bodyEditor.setDefaultEditor(editor); | |||||
break; | |||||
case ParamEditor.TYPE_PATH: | |||||
pathEditor.setDefaultEditor(editor); | |||||
break; | |||||
case ParamEditor.TYPE_HEADER: | |||||
headerEditor.setDefaultEditor(editor);; | |||||
break; | |||||
case ParamEditor.TYPE_ATTR: | |||||
attributeEditor.setDefaultEditor(editor); | |||||
break; | |||||
default: | |||||
throw new IllegalStateException("Unexpected type value: " + bindType); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,20 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.attr; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import cc.smtweb.framework.core.mvc.controller.IEditor; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import java.util.Map; | |||||
/** | |||||
* API函数@SwBody参数的转换处理,POST请求直接转换Json内容为Bean对象,GET请求转换Map请求参数为Bean对 | |||||
* @author xkliu | |||||
*/ | |||||
public abstract class AbstractAttrEditor implements IEditor { | |||||
@Override | |||||
public Object getParamValue(String paramName, Class<?> paramType, IBeanContext context, Map<String, Object> params, String body, HttpServletRequest request) { | |||||
return getValue(paramName, paramType, request); | |||||
} | |||||
public abstract Object getValue(String paramName, Class<?> paramType, HttpServletRequest request); | |||||
} |
@@ -0,0 +1,14 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.attr; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
/** | |||||
* API函数@SwBody参数的转换处理,POST请求直接转换Json内容为Bean对象,GET请求转换Map请求参数为Bean对 | |||||
* @author xkliu | |||||
*/ | |||||
public class BeanAttrEditor extends AbstractAttrEditor { | |||||
@Override | |||||
public Object getValue(String paramName, Class<?> paramType, HttpServletRequest request) { | |||||
return request.getAttribute(paramName); | |||||
} | |||||
} |
@@ -0,0 +1,13 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.bean; | |||||
import cc.smtweb.framework.core.mvc.controller.binder.param.AbstractRequestEditor; | |||||
import cc.smtweb.framework.core.session.SessionUtil; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
public class UserSessionEditor extends AbstractRequestEditor { | |||||
@Override | |||||
public Object getValue(HttpServletRequest request) { | |||||
return SessionUtil.getSession(); | |||||
} | |||||
} |
@@ -0,0 +1,82 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.body; | |||||
import cc.smtweb.framework.core.common.SwMap; | |||||
import cc.smtweb.framework.core.exception.BindBeanException; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import cc.smtweb.framework.core.mvc.controller.IEditor; | |||||
import cc.smtweb.framework.core.util.JsonUtil; | |||||
import org.apache.commons.lang3.StringUtils; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import java.util.Map; | |||||
/** | |||||
* API函数@SwBody参数的转换处理,POST请求直接转换Json内容为Bean对象,GET请求转换Map请求参数为Bean对 | |||||
* @author xkliu | |||||
*/ | |||||
public class BeanBodyEditor implements IEditor { | |||||
@Override | |||||
public Object getParamValue(String paramName, Class<?> paramType, IBeanContext context, Map<String, Object> params, String body, HttpServletRequest request) { | |||||
if (paramName != null) { | |||||
Object map = request.getAttribute(BODY_MAP); | |||||
if (map != null) { | |||||
return getFieldValue((SwMap) map, paramName, paramType); | |||||
} | |||||
} | |||||
if (body != null) { | |||||
return getPostValue(paramName, paramType, body, request); | |||||
} else { | |||||
return getGetValue(paramName, paramType, params); | |||||
} | |||||
} | |||||
private Object getGetValue(String paramName, Class<?> paramType, Map<String, Object> params) { | |||||
if (paramName == null) { | |||||
return JsonUtil.parse(params, paramType); | |||||
} | |||||
// Json字符进行类型转化 | |||||
Object result = params.get(paramName); | |||||
if (result == null) { | |||||
return null; | |||||
} | |||||
if (paramType.isAssignableFrom(result.getClass())) { | |||||
return result; | |||||
} | |||||
throw new BindBeanException("传入的 @SwBody(" + paramName + ") " + paramType.getName() + "类型不一致! " + result.getClass().getName()); | |||||
} | |||||
private Object getPostValue(String paramName, Class<?> paramType, String body, HttpServletRequest request) { | |||||
if (body == null || StringUtils.isBlank(body)) { | |||||
return null; | |||||
} | |||||
if (paramName == null) { | |||||
return JsonUtil.parse(body, paramType); | |||||
} else { | |||||
SwMap map = JsonUtil.parse(body, SwMap.class); | |||||
if (map != null) { | |||||
request.setAttribute(BODY_MAP, map); | |||||
return getFieldValue(map, paramName, paramType); | |||||
} | |||||
return null; | |||||
} | |||||
} | |||||
private Object getFieldValue(SwMap result, String paramName, Class<?> paramType) { | |||||
Object value = result.get(paramName); | |||||
if (value == null) { | |||||
return null; | |||||
} | |||||
if (value instanceof Map) { | |||||
return BeanUtil.toBean((Map)value, paramType); | |||||
} | |||||
throw new BindBeanException("传入的 @SwBody(" + paramName + ") 值非Object类型! " + value.getClass().getName()); | |||||
} | |||||
} |
@@ -0,0 +1,160 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.body; | |||||
import cc.smtweb.framework.core.common.SwMap; | |||||
import org.springframework.beans.BeanUtils; | |||||
import org.springframework.beans.BeansException; | |||||
import org.springframework.beans.FatalBeanException; | |||||
import org.springframework.util.Assert; | |||||
import org.springframework.util.ClassUtils; | |||||
import java.beans.PropertyDescriptor; | |||||
import java.lang.reflect.Method; | |||||
import java.lang.reflect.Modifier; | |||||
import java.sql.Timestamp; | |||||
import java.util.HashSet; | |||||
import java.util.Map; | |||||
import java.util.Set; | |||||
public class BeanUtil { | |||||
private static final Set<Class<?>> simpleType = new HashSet<>(); | |||||
static { | |||||
simpleType.add(String.class); | |||||
simpleType.add(Timestamp.class); | |||||
simpleType.add(java.util.Date.class); | |||||
simpleType.add(java.sql.Date.class); | |||||
simpleType.add(java.sql.Time.class); | |||||
simpleType.add(Byte.TYPE); | |||||
simpleType.add(Long.TYPE); | |||||
simpleType.add(Double.TYPE); | |||||
simpleType.add(Float.TYPE); | |||||
simpleType.add(Short.TYPE); | |||||
simpleType.add(Integer.TYPE); | |||||
simpleType.add(Boolean.TYPE); | |||||
simpleType.add(Character.TYPE); | |||||
simpleType.add(Byte.class); | |||||
simpleType.add(Long.class); | |||||
simpleType.add(Double.class); | |||||
simpleType.add(Float.class); | |||||
simpleType.add(Short.class); | |||||
simpleType.add(Integer.class); | |||||
simpleType.add(Boolean.class); | |||||
} | |||||
private BeanUtil() {} | |||||
public static Object toBean(Map source, Class<?> clazz) { | |||||
Object target = null; | |||||
try { | |||||
target = clazz.newInstance(); | |||||
} catch (InstantiationException | IllegalAccessException e) { | |||||
throw new FatalBeanException( | |||||
"Could not copy property '" + clazz.getName() + "' from source to target", e); | |||||
} | |||||
mapToBean(source, target); | |||||
return target; | |||||
} | |||||
private static void mapToBean(Map source, Object target) { | |||||
mapToBean(source, target, true); | |||||
} | |||||
private static void mapToBean(Map source, Object target, boolean root) throws BeansException { | |||||
Assert.notNull(source, "Source must not be null"); | |||||
Assert.notNull(target, "Target must not be null"); | |||||
Class<?> actualEditable = target.getClass(); | |||||
// if (editable != null) { | |||||
// if (!editable.isInstance(target)) { | |||||
// throw new IllegalArgumentException("Target class [" + target.getClass().getName() + | |||||
// "] not assignable to Editable class [" + editable.getName() + "]"); | |||||
// } | |||||
// actualEditable = editable; | |||||
// } | |||||
PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable); | |||||
for (PropertyDescriptor targetPd : targetPds) { | |||||
Method writeMethod = targetPd.getWriteMethod(); | |||||
Object value = source.get(targetPd.getName()); | |||||
if (value == null) { | |||||
Class<?> parameterType = writeMethod.getParameterTypes()[0]; | |||||
if (ClassUtils.isAssignable(parameterType, value.getClass())) { | |||||
writeBeanValue(target, targetPd, writeMethod, value); | |||||
} else { | |||||
if (root && value instanceof Map) { | |||||
Object propValue; | |||||
try { | |||||
propValue = parameterType.newInstance(); | |||||
} catch (InstantiationException | IllegalAccessException e) { | |||||
throw new FatalBeanException( | |||||
"Could not copy property '" + targetPd.getName() + "' from source to target", e); | |||||
} | |||||
mapToBean((Map) value, propValue, false); | |||||
writeBeanValue(target, targetPd, writeMethod, propValue); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private static void writeBeanValue(Object target, PropertyDescriptor targetPd, Method writeMethod, Object value) { | |||||
try { | |||||
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { | |||||
writeMethod.setAccessible(true); | |||||
} | |||||
writeMethod.invoke(target, value); | |||||
} catch (Throwable ex) { | |||||
throw new FatalBeanException( | |||||
"Could not copy property '" + targetPd.getName() + "' from source to target", ex); | |||||
} | |||||
} | |||||
public static void beanToMap(Object source, SwMap target) throws BeansException { | |||||
beanToMap(source, target, true); | |||||
} | |||||
public static void beanToMap(Object source, SwMap target, boolean root) throws BeansException { | |||||
Assert.notNull(source, "Source must not be null"); | |||||
Assert.notNull(target, "Target must not be null"); | |||||
PropertyDescriptor[] sourcePds = BeanUtils.getPropertyDescriptors(source.getClass()); | |||||
for (PropertyDescriptor sourcePd: sourcePds) { | |||||
// PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getKey()); | |||||
// if (sourcePd != null) { | |||||
Method readMethod = sourcePd.getReadMethod(); | |||||
if (readMethod != null) { | |||||
try { | |||||
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { | |||||
readMethod.setAccessible(true); | |||||
} | |||||
Object value = readMethod.invoke(source); | |||||
target.put(sourcePd.getName(), root ? toMapValue(value) : value); | |||||
} | |||||
catch (Throwable ex) { | |||||
throw new FatalBeanException( | |||||
"Could not copy property '" + sourcePd.getName() + "' from source to target", ex); | |||||
} | |||||
} | |||||
// } | |||||
} | |||||
} | |||||
private static Object toMapValue(Object value) { | |||||
if (value != null && isBeanType(value.getClass())) { | |||||
SwMap mapValue = new SwMap(); | |||||
beanToMap(value, mapValue, false); | |||||
return mapValue; | |||||
} | |||||
return value; | |||||
} | |||||
private static boolean isBeanType(Class<?> type) { | |||||
return !simpleType.contains(type); | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.body; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import cc.smtweb.framework.core.mvc.controller.IEditor; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import java.util.Map; | |||||
/** | |||||
* API函数@SwBody参数的转换处理,直接获取POST请求文本内容 | |||||
* @author xkliu | |||||
*/ | |||||
public class StringBodyEditor implements IEditor { | |||||
@Override | |||||
public Object getParamValue(String paramName, Class<?> paramType, IBeanContext context, Map<String, Object> params, String body, HttpServletRequest request) { | |||||
return body; | |||||
} | |||||
} |
@@ -0,0 +1,14 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.param; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import cc.smtweb.framework.core.mvc.controller.IEditor; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import java.util.Map; | |||||
public class BeanTypeEditor implements IEditor { | |||||
@Override | |||||
public Object getParamValue(String paramName, Class<?> paramType, IBeanContext context, Map<String, Object> params, String body, HttpServletRequest request) { | |||||
return params.get(paramType.getName()); | |||||
} | |||||
} |
@@ -0,0 +1,53 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.param; | |||||
import cc.smtweb.framework.core.exception.BindParamException; | |||||
import org.apache.commons.lang3.StringUtils; | |||||
import java.text.ParseException; | |||||
import java.util.Date; | |||||
public class DateEditor extends AbstractParameterEditor { | |||||
public DateEditor(int header) { | |||||
super(header); | |||||
} | |||||
@Override | |||||
protected Object parseText(Object value) { | |||||
if (value == null) { | |||||
return null; | |||||
} | |||||
if (value instanceof Date) { | |||||
return new java.sql.Date(((Date) value).getTime()); | |||||
} | |||||
String text = value.toString(); | |||||
if (StringUtils.isBlank(text)) { | |||||
return null; | |||||
} | |||||
Object result = null; | |||||
if (StringUtils.isNotBlank(text)) { | |||||
text = text.trim(); | |||||
if ("null".equals(text)) { | |||||
return null; | |||||
} else { | |||||
int pos = text.indexOf(' '); | |||||
if (pos > 0) { | |||||
text = text.substring(0, pos); | |||||
} | |||||
try { | |||||
result = new java.sql.Date(parseDate(text).getTime()); | |||||
} catch (ParseException e) { | |||||
throw new BindParamException(e); | |||||
} | |||||
} | |||||
} | |||||
return result; | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.param; | |||||
public class FloatEditor extends AbstractNumberEditor { | |||||
private static final Float FLOAT_ZERO = 0.0f; | |||||
public FloatEditor(int header, boolean automicType) { | |||||
super(header, automicType); | |||||
} | |||||
@Override | |||||
protected Object convert(Number value) { | |||||
return value.floatValue(); | |||||
} | |||||
@Override | |||||
protected Object getValue(String text) { | |||||
return Float.valueOf(text); | |||||
} | |||||
@Override | |||||
protected Object defaultValue() { | |||||
return FLOAT_ZERO; | |||||
} | |||||
} |
@@ -0,0 +1,24 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.param; | |||||
public class IntegerEditor extends AbstractNumberEditor { | |||||
private static final Integer INT_ZERO = 0; | |||||
public IntegerEditor(int header, boolean automicType) { | |||||
super(header, automicType); | |||||
} | |||||
@Override | |||||
protected Object convert(Number value) { | |||||
return value.intValue(); | |||||
} | |||||
@Override | |||||
protected Object getValue(String text) { | |||||
return Integer.valueOf(text); | |||||
} | |||||
@Override | |||||
protected Object defaultValue() { | |||||
return INT_ZERO; | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.param; | |||||
public class ShortEditor extends AbstractNumberEditor { | |||||
private static final Short SHORT_ZERO = 0; | |||||
public ShortEditor(int header, boolean automicType) { | |||||
super(header, automicType); | |||||
} | |||||
@Override | |||||
protected Object convert(Number value) { | |||||
return value.shortValue(); | |||||
} | |||||
@Override | |||||
protected Object getValue(String text) { | |||||
return Short.valueOf(text); | |||||
} | |||||
@Override | |||||
protected Object defaultValue() { | |||||
return SHORT_ZERO; | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.binder.param; | |||||
public class StringEditor extends AbstractParameterEditor { | |||||
public StringEditor(int header) { | |||||
super(header); | |||||
} | |||||
@Override | |||||
protected Object parseText(Object text) { | |||||
if (text == null) { | |||||
return text; | |||||
} | |||||
return text.toString(); | |||||
} | |||||
} |
@@ -0,0 +1,73 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.scan; | |||||
import cc.smtweb.framework.core.cache.AbstractCache; | |||||
import cc.smtweb.framework.core.cache.CacheManager; | |||||
import cc.smtweb.framework.core.cache.ISwCache; | |||||
import cc.smtweb.framework.core.mvc.config.ControllerConfig; | |||||
import cc.smtweb.framework.core.mvc.controller.ApiConfigBean; | |||||
import cc.smtweb.framework.core.mvc.controller.MethodAccessManager; | |||||
import cc.smtweb.framework.core.mvc.controller.binder.CacheEditor; | |||||
import cc.smtweb.framework.core.mvc.controller.binder.WebDataBinder; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.context.ConfigurableApplicationContext; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
/** | |||||
* 微服务API扫描,扫描生成api接口实现放入methodAccessManager | |||||
* | |||||
* @author xkliu | |||||
*/ | |||||
@Slf4j | |||||
public class ApplicationScanner { | |||||
public static void scan(ConfigurableApplicationContext applicationContext) throws Exception { | |||||
MethodAccessManager methodAccessManager = applicationContext.getBean(MethodAccessManager.class); | |||||
BeanManager beanManager = BeanManager.getInstance(); | |||||
beanManager.install(applicationContext); | |||||
String[] names = applicationContext.getBeanNamesForType(ControllerConfig.class); | |||||
if (names != null) { | |||||
WebDataBinder webDataBinder = new WebDataBinder(new CacheEditor(methodAccessManager.getCacheManager())); | |||||
ApiConfigBean apiConfig = applicationContext.getBean(ApiConfigBean.class); | |||||
List<ControllerConfig> controllerConfigs = new ArrayList<>(); | |||||
ScanContext scanContext = new ScanContext(beanManager, webDataBinder); | |||||
for (String name : names) { | |||||
ControllerConfig controllerConfig = applicationContext.getBean(name, ControllerConfig.class); | |||||
controllerConfigs.add(controllerConfig); | |||||
// 先注册自定义注解解析器 | |||||
IScanActionBuilder scanActionBuilder = controllerConfig.getScanActionBuilder(); | |||||
if (scanActionBuilder != null) { | |||||
scanContext.addScanAction(scanActionBuilder.build(applicationContext, webDataBinder)); | |||||
} | |||||
} | |||||
for (ControllerConfig controllerConfig : controllerConfigs) { | |||||
scanContext.setModule(controllerConfig.getModule()); | |||||
log.info("[/api/{}/*]start scan {}", controllerConfig.getModule(), controllerConfig.getPackagePath()); | |||||
// 扫描 Service 类 | |||||
PackageScanner packageScanner = new PackageScanner( | |||||
new ClassParser(new UrlMaker(apiConfig, controllerConfig.getModule()), scanContext)); | |||||
packageScanner.scan(controllerConfig.getPackagePath()); | |||||
} | |||||
// 单例进行实例化,并设置实例的field(@SwParam),关联扩展的action(@SwAction)到方法执行器 | |||||
beanManager.init(); | |||||
log.info("[smt] scan ok."); | |||||
} | |||||
// 处理权限管理和启动定时任务 | |||||
AbstractCache realmCache = CacheManager.getIntance().getCache(ISwCache.REALM_CACHE); | |||||
if (realmCache == null) { | |||||
log.error("not find RealmCache"); | |||||
} | |||||
methodAccessManager.init(beanManager, realmCache); | |||||
} | |||||
} |
@@ -0,0 +1,95 @@ | |||||
package cc.smtweb.framework.core.mvc.controller.scan; | |||||
import cc.smtweb.framework.core.annotation.SwAction; | |||||
import cc.smtweb.framework.core.mvc.controller.ApiConfigBean; | |||||
import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; | |||||
import lombok.Getter; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.apache.commons.lang3.StringUtils; | |||||
import java.lang.reflect.Method; | |||||
/** | |||||
* URL构造器,用于API的访问路径构造 | |||||
* @author xkliu | |||||
*/ | |||||
@Slf4j | |||||
class UrlMaker { | |||||
private static final String SERVICE_SUFFIX = "Service"; | |||||
private final ApiConfigBean apiConfig; | |||||
@Getter | |||||
private final String module; | |||||
private String serviceUrl; | |||||
UrlMaker(ApiConfigBean apiConfig, String module) { | |||||
this.apiConfig = apiConfig; | |||||
this.module = module; | |||||
} | |||||
private String getServiceName(Class clazz) { | |||||
String simpleName = clazz.getSimpleName(); | |||||
if (simpleName.endsWith(SERVICE_SUFFIX)) { | |||||
simpleName = simpleName.substring(0, simpleName.length() - SERVICE_SUFFIX.length()); | |||||
} | |||||
return simpleName; | |||||
} | |||||
/** | |||||
* 添加API映射到BeanManager,API的url先取SwApi配置的值,然后取SwService配置的值+“/”+方法名,最后使用默认规则: “服务名” / "方法名" | |||||
* url都以模块名开头,避免应用模块服务名称重复 | |||||
* @param clazz 服务类 | |||||
* @param method 执行方法,用于映射API | |||||
* @param methodAccess 方法实际执行 | |||||
* @param controllers 控制器管理容器 | |||||
*/ | |||||
void addApi(Class<?> clazz, Method method, MethodAccess methodAccess, BeanManager controllers) { | |||||
String serviceName = getServiceName(clazz); | |||||
String methodName = method.getName(); | |||||
String urlPrefix = module + "/"; | |||||
SwAction swAction = method.getAnnotation(SwAction.class); | |||||
if (swAction != null && StringUtils.isNotBlank(swAction.value())) { | |||||
// SwApi设置了URL | |||||
mappingApi(methodAccess, controllers, urlPrefix + swAction.value()); | |||||
} else if (this.serviceUrl != null) { | |||||
// SwService设置了URL | |||||
mappingApi(methodAccess, controllers, urlPrefix + this.serviceUrl + "/" + methodName); | |||||
} else { | |||||
// 小驼峰路径 | |||||
String littleCamelCaseUrl = urlPrefix + toLittleCamelName(serviceName) + "/" + methodName; | |||||
if (mappingApi(methodAccess, controllers, littleCamelCaseUrl)) { | |||||
// 大驼峰路径,类原名 | |||||
if (apiConfig.isBigCameCaseUrl()) { | |||||
mappingApi(methodAccess, controllers, urlPrefix + serviceName + "/" + methodName); | |||||
} | |||||
// 小写服务名路径 | |||||
if (apiConfig.isLowerCaseUrl()) { | |||||
mappingApi(methodAccess, controllers, urlPrefix + serviceName.toLowerCase() + "/" + methodName); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private String toLittleCamelName(String serviceName) { | |||||
return Character.toLowerCase(serviceName.charAt(0)) | |||||
+ serviceName.substring(1); | |||||
} | |||||
private boolean mappingApi(MethodAccess methodAccess, BeanManager controllers, String url) { | |||||
if (controllers.putIfAbsent(url, methodAccess) != null) { | |||||
log.error("url is repeat: " + url + ", " + methodAccess.controllerFullName()); | |||||
return false; | |||||
} | |||||
log.debug("[smt]init: " + url + ", " + methodAccess.controllerFullName()); | |||||
return true; | |||||
} | |||||
void setServiceUrl(String value) { | |||||
this.serviceUrl = StringUtils.trimToNull(value); | |||||
} | |||||
} |
@@ -0,0 +1,7 @@ | |||||
package cc.smtweb.framework.core.mvc.realm.exception; | |||||
public class ForbiddenException extends AuthorizationException { | |||||
public ForbiddenException(String s) { | |||||
super(s); | |||||
} | |||||
} |
@@ -0,0 +1,7 @@ | |||||
package cc.smtweb.framework.core.mvc.realm.exception; | |||||
public class UnknownAccountException extends AuthorizationException { | |||||
public UnknownAccountException(String s) { | |||||
super(s); | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
package cc.smtweb.framework.core.mvc.realm.interceptor; | |||||
import cc.smtweb.framework.core.cache.redis.RedisManager; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
/** | |||||
* 权限拦截器,在API请求处理中一起完成 | |||||
* @author xkliu | |||||
*/ | |||||
public class PermInterceptor extends AbstractPermInterceptor { | |||||
public PermInterceptor(RedisManager redisManager) { | |||||
super(redisManager); | |||||
} | |||||
/** | |||||
* 校验用户是否有API权限 | |||||
* @param request http请求 | |||||
* @param permissionValue 权限值 | |||||
* @return 是否有权限 | |||||
*/ | |||||
public boolean preHandle(HttpServletRequest request, String permissionValue) { | |||||
return super.handle(request, permissionValue); | |||||
} | |||||
} |
@@ -0,0 +1,85 @@ | |||||
package cc.smtweb.framework.core.mvc.scheduler; | |||||
import cc.smtweb.framework.core.common.SwMap; | |||||
import cc.smtweb.framework.core.db.DbEngine; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import cc.smtweb.framework.core.util.DateUtil; | |||||
import cc.smtweb.framework.core.util.JsonUtil; | |||||
import lombok.Getter; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import java.util.concurrent.ExecutorService; | |||||
public abstract class AbstractJobExecutor { | |||||
@Getter | |||||
private IBeanContext beanContext; | |||||
private ExecutorService executor; | |||||
protected Long redisLockValue; | |||||
private DbEngine dbEngine; | |||||
protected final Map<String, AbstractJobQueue> jobMap = new HashMap<>(); | |||||
public AbstractJobExecutor(IBeanContext beanContext, ExecutorService executor, Long redisLockValue, DbEngine dbEngine) { | |||||
this.beanContext = beanContext; | |||||
this.executor = executor; | |||||
this.redisLockValue = redisLockValue; | |||||
this.dbEngine = dbEngine; | |||||
} | |||||
public abstract boolean tryLock(AbstractJob job); | |||||
public abstract void unlock(AbstractJob job); | |||||
// 执行任务 | |||||
public void execute(final AbstractJobQueue jobQueue, final AbstractJob job) { | |||||
this.executor.submit(() -> { | |||||
if (tryLock(job)) { | |||||
try { | |||||
jobQueue.execute(job); | |||||
} finally { | |||||
unlock(job); | |||||
} | |||||
} | |||||
}); | |||||
} | |||||
void saveBreakPoint(AbstractJob job, SchedulerPoint schedulerPoint) { | |||||
if (schedulerPoint != null && schedulerPoint.changed) { | |||||
String value = JsonUtil.encodeString(schedulerPoint.value); | |||||
long now = DateUtil.nowDateTimeLong(); | |||||
if (schedulerPoint.insert) { | |||||
dbEngine.update("insert into sw_user.sch_break_point(bp_id, bp_job_name, bp_machine_id, bp_value, bp_create_time, bp_last_time) values(?,?,?,?,?,?)", | |||||
dbEngine.nextId(), job.getName(), this.redisLockValue, value, now, now); | |||||
} else { | |||||
dbEngine.update("update sw_user.sch_break_point set bp_machine_id=?, bp_value=?, bp_last_time=? where bp_job_name=?", | |||||
this.redisLockValue, value, now, job.getName()); | |||||
} | |||||
} | |||||
} | |||||
SchedulerPoint loadBreakPoint(AbstractJob job) { | |||||
String value = dbEngine.queryString("select bp_value from sw_user.sch_break_point where bp_job_name=?", job.getName()); | |||||
SchedulerPoint result; | |||||
if (value != null) { | |||||
SwMap swMap = JsonUtil.parse(value, SwMap.class); | |||||
result = new SchedulerPoint(swMap); | |||||
} else { | |||||
result = new SchedulerPoint(); | |||||
} | |||||
return result; | |||||
} | |||||
AbstractJobQueue initJobQueue(AbstractJob job) { | |||||
String key = job.getKey(); | |||||
AbstractJobQueue jobQueue = jobMap.get(key); | |||||
if (jobQueue == null) { | |||||
jobQueue = job.createJobQueue(this); | |||||
jobMap.put(key, jobQueue); | |||||
} | |||||
return jobQueue; | |||||
} | |||||
} |
@@ -0,0 +1,30 @@ | |||||
package cc.smtweb.framework.core.mvc.scheduler; | |||||
import com.serotonin.timer.TimerTask; | |||||
import com.serotonin.timer.TimerTrigger; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
/** | |||||
* 包装定时器任务 | |||||
* | |||||
* @author xkliu | |||||
* | |||||
*/ | |||||
@Slf4j | |||||
public class CronTimerTask extends TimerTask { | |||||
private final AbstractJobQueue jobQueue; | |||||
private AbstractJob job; | |||||
CronTimerTask(TimerTrigger timerTrigger, AbstractJobQueue jobQueue, AbstractJob job) { | |||||
super(timerTrigger); | |||||
this.jobQueue = jobQueue; | |||||
this.job = job; | |||||
} | |||||
@Override | |||||
public void run(long time) { | |||||
// 开启线程执行 | |||||
jobQueue.getJobExecutor().execute(jobQueue, job); | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
package cc.smtweb.framework.core.mvc.scheduler.job; | |||||
import cc.smtweb.framework.core.cache.redis.RedisManager; | |||||
import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; | |||||
import cc.smtweb.framework.core.mvc.scheduler.AbstractJob; | |||||
import cc.smtweb.framework.core.mvc.scheduler.AbstractJobExecutor; | |||||
import cc.smtweb.framework.core.mvc.scheduler.AbstractJobQueue; | |||||
public class GroupJob extends AbstractJob { | |||||
@Override | |||||
public AbstractJobQueue createJobQueue(AbstractJobExecutor jobManager) { | |||||
return new GroupJobQueue(jobManager); | |||||
} | |||||
public GroupJob(String group, MethodAccess methodAccess) { | |||||
super(methodAccess); | |||||
initKey(RedisManager.PREFIX_TIMER + '.' + group); | |||||
} | |||||
} |
@@ -0,0 +1,23 @@ | |||||
package cc.smtweb.framework.core.mvc.scheduler.job; | |||||
import cc.smtweb.framework.core.db.DbEngine; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import cc.smtweb.framework.core.mvc.scheduler.AbstractJob; | |||||
import cc.smtweb.framework.core.mvc.scheduler.AbstractJobExecutor; | |||||
import java.util.concurrent.ExecutorService; | |||||
public class LocalJobExecutor extends AbstractJobExecutor { | |||||
public LocalJobExecutor(IBeanContext beanContext, ExecutorService executor, Long redisLockValue, DbEngine dbEngine) { | |||||
super(beanContext, executor, redisLockValue, dbEngine); | |||||
} | |||||
@Override | |||||
public boolean tryLock(AbstractJob job) { | |||||
return true; | |||||
} | |||||
@Override | |||||
public void unlock(AbstractJob job) { | |||||
} | |||||
} |
@@ -0,0 +1,70 @@ | |||||
package cc.smtweb.framework.core.mvc.scheduler.job; | |||||
import cc.smtweb.framework.core.cache.redis.RedisManager; | |||||
import cc.smtweb.framework.core.db.DbEngine; | |||||
import cc.smtweb.framework.core.mvc.controller.IBeanContext; | |||||
import cc.smtweb.framework.core.mvc.scheduler.AbstractJob; | |||||
import cc.smtweb.framework.core.mvc.scheduler.AbstractJobExecutor; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import java.util.concurrent.ExecutorService; | |||||
import java.util.concurrent.locks.ReentrantLock; | |||||
@Slf4j | |||||
public class RedisJobExecutor extends AbstractJobExecutor implements Runnable { | |||||
public static final int TIME_CHECK_SEC = 30; | |||||
public static final int TIME_LOOK_SEC = 3 * TIME_CHECK_SEC; | |||||
private ReentrantLock lock = new ReentrantLock(); | |||||
private RedisManager redisManager; | |||||
public RedisJobExecutor(IBeanContext beanContext, ExecutorService executor, Long redisLockValue, DbEngine dbEngine, RedisManager redisManager) { | |||||
super(beanContext, executor, redisLockValue, dbEngine); | |||||
this.redisManager = redisManager; | |||||
} | |||||
@Override | |||||
public boolean tryLock(AbstractJob job) { | |||||
// redis检查, 同group需要排队等待执行\ | |||||
String key = job.getKey(); | |||||
boolean redisLocked = redisManager.setnx(key, redisLockValue, TIME_LOOK_SEC); | |||||
if (!redisLocked) { | |||||
Long oldLockedValue = redisManager.get(key, Long.class); | |||||
if (!redisLockValue.equals(oldLockedValue)) { | |||||
return false; | |||||
} | |||||
} | |||||
return redisLocked; | |||||
} | |||||
@Override | |||||
public void unlock(AbstractJob job) { | |||||
redisManager.del(job.getKey()); | |||||
} | |||||
@Override | |||||
public void run() { | |||||
if (lock.tryLock()) { | |||||
try { | |||||
// 定时更新redis锁定状态 | |||||
long now = System.currentTimeMillis(); | |||||
jobMap.forEach((key, value) -> { | |||||
long lastTime = value.getLastTime(); | |||||
if (lastTime > 0 && now - lastTime >= TIME_CHECK_SEC) { | |||||
if (redisManager.set(key, redisLockValue, TIME_LOOK_SEC)) { | |||||
value.updateLastTime(); | |||||
} else { | |||||
log.error("update redis lock failed, key: " + key); | |||||
} | |||||
} | |||||
}); | |||||
} finally { | |||||
lock.unlock(); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,96 @@ | |||||
package cc.smtweb.framework.core.mvc.service; | |||||
import cc.smtweb.framework.core.annotation.SwBody; | |||||
import cc.smtweb.framework.core.common.R; | |||||
import cc.smtweb.framework.core.exception.BizException; | |||||
import cc.smtweb.framework.core.exception.SwException; | |||||
import cc.smtweb.framework.core.common.SwMap; | |||||
import cc.smtweb.framework.core.session.UserSession; | |||||
/** | |||||
* Created by Akmm at 2022/3/2 10:39 | |||||
* 通用业务mvc总调度 | |||||
*/ | |||||
public abstract class AbstractCompService { | |||||
public final static String TYPE_LIST = "list"; | |||||
public final static String TYPE_COMBO = "combo"; | |||||
public final static String TYPE_TREE = "tree"; | |||||
public final static String TYPE_LOAD = "load"; | |||||
public final static String TYPE_SAVE = "save"; | |||||
public final static String TYPE_DEL = "del"; | |||||
protected abstract AbstractHandler createHandler(String type); | |||||
protected AbstractHandler getHandler(SwMap params, UserSession us, String type) throws Exception { | |||||
AbstractHandler handler = createHandler(type); | |||||
if (handler == null) throw new BizException("暂不支持此类服务:" + type); | |||||
if (params == null) params = new SwMap(); | |||||
if (us == null) us = UserSession.createSys(); | |||||
handler.init(params, us); | |||||
return handler; | |||||
} | |||||
protected R pageHandler(SwMap params, UserSession us, String type, IWorker worker) { | |||||
try { | |||||
AbstractHandler handler = getHandler(params, us, type); | |||||
return worker.doWork(handler); | |||||
} catch (Exception e) { | |||||
return R.error("操作失败!", e); | |||||
} | |||||
} | |||||
//保存 | |||||
public R save(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_SAVE, handler -> ((AbstractSaveHandler)handler).save()); | |||||
} | |||||
//树,换爹 | |||||
public R trcp(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_SAVE, handler -> ((AbstractSaveHandler)handler).changeParent()); | |||||
} | |||||
//读取 | |||||
public R load(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_LOAD, handler -> ((AbstractLoadHandler)handler).load()); | |||||
} | |||||
//删除 | |||||
public R del(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_DEL, handler -> ((AbstractDelHandler)handler).del()); | |||||
} | |||||
//列表数据 | |||||
public R list(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_LIST, handler -> ((AbstractListHandler)handler).data()); | |||||
} | |||||
//列表总记录数及合计栏 | |||||
public R listTotal(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_LIST, handler -> ((AbstractListHandler)handler).getTotal()); | |||||
} | |||||
//combo数据 | |||||
public R combo(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_COMBO, handler -> ((DefaultComboHandler)handler).data()); | |||||
} | |||||
//combo总记录数及合计栏 | |||||
public R comboTotal(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_COMBO, handler -> ((DefaultComboHandler)handler).getTotal()); | |||||
} | |||||
//combo数据过滤 | |||||
public R comboFilter(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_COMBO, handler -> ((DefaultComboHandler)handler).filter()); | |||||
} | |||||
//树数据 | |||||
public R tree(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_TREE, handler -> ((AbstractTreeHandler<Object>)handler).data()); | |||||
} | |||||
//树过滤 | |||||
public R treeFilter(@SwBody SwMap params, UserSession us) { | |||||
return pageHandler(params, us, TYPE_TREE, handler -> ((AbstractTreeHandler<Object>)handler).filter()); | |||||
} | |||||
} |
@@ -0,0 +1,87 @@ | |||||
package cc.smtweb.framework.core.mvc.service; | |||||
import cc.smtweb.framework.core.common.R; | |||||
import cc.smtweb.framework.core.db.DbEngine; | |||||
import cc.smtweb.framework.core.db.jdbc.AbsDbWorker; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
/** | |||||
* Created by Akmm at 2022/3/2 19:44 | |||||
* 保存 | |||||
*/ | |||||
@Slf4j | |||||
public abstract class AbstractSaveHandler<T> extends AbstractHandler { | |||||
protected T bean; | |||||
protected boolean isNew; | |||||
public R save() { | |||||
long id = readId(); | |||||
isNew = id <= 0L; | |||||
if (isNew) { | |||||
bean = createComp(); | |||||
} else { | |||||
bean = loadComp(id); | |||||
} | |||||
readFromPage(); | |||||
if (isNew) { | |||||
setNewId(); | |||||
} | |||||
checkValid(); | |||||
DbEngine.getInstance().doTrans(new AbsDbWorker() { | |||||
@Override | |||||
public void work(){ | |||||
saveDb(); | |||||
} | |||||
@Override | |||||
public void doAfterDbCommit(){ | |||||
saveSuccess(); | |||||
} | |||||
@Override | |||||
public void doAfterDbRollback(){ | |||||
saveFailed(); | |||||
} | |||||
}); | |||||
return R.success(bean); | |||||
} | |||||
/** | |||||
* 读取页面传回来的id | |||||
* | |||||
* @return | |||||
*/ | |||||
protected long readId() { | |||||
return params.readLong("id", 0L); | |||||
} | |||||
protected abstract void setNewId(); | |||||
//从页面读取数据 | |||||
protected abstract void readFromPage(); | |||||
//保存前的校验 | |||||
protected abstract void checkValid(); | |||||
//保存到数据库 | |||||
protected abstract void saveDb(); | |||||
//保存成功之后 | |||||
protected void saveSuccess() { | |||||
} | |||||
//保存失败之后 | |||||
protected void saveFailed() { | |||||
} | |||||
//构建一个新对象 | |||||
protected abstract T createComp(); | |||||
//从数据库读取 | |||||
protected abstract T loadComp(long id); | |||||
//树,改变父亲 | |||||
public abstract R changeParent(); | |||||
} |
@@ -0,0 +1,67 @@ | |||||
package cc.smtweb.framework.core.mvc.service; | |||||
import cc.smtweb.framework.core.common.R; | |||||
import cc.smtweb.framework.core.common.SwMap; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
/** | |||||
* Created by Akmm at 2022/3/2 19:44 | |||||
* 列表服务 | |||||
*/ | |||||
@Slf4j | |||||
public abstract class AbstractTreeHandler<T> extends AbstractHandler { | |||||
//树过滤 | |||||
public R filter() { | |||||
List<T> rows = filterData(); | |||||
List<SwMap> listRet = buildNodes(rows, true); | |||||
return R.success(listRet); | |||||
} | |||||
public R data() { | |||||
List<T> rows = getChildren(params.readLong("parent_id")); | |||||
List<SwMap> listRet = buildNodes(rows, params.readBool("lazy")); | |||||
return R.success(listRet); | |||||
} | |||||
//搜索 | |||||
protected abstract List<T> filterData(); | |||||
//获取指定节点的下级节点 | |||||
protected abstract List<T> getChildren(long id); | |||||
protected List<T> getChildren(T bean) { | |||||
return getChildren(getId(bean)); | |||||
} | |||||
//根据bean,构建treenode | |||||
private List<SwMap> buildNodes(List<T> rows, boolean lazy) { | |||||
List<SwMap> listRet = new ArrayList<>(); | |||||
if (rows == null || rows.isEmpty()) { | |||||
return listRet; | |||||
} | |||||
for (T row : rows) { | |||||
SwMap node = new SwMap(); | |||||
node.put("id", getId(row)); | |||||
node.put("text", getText(row)); | |||||
List<T> children = getChildren(row); | |||||
node.put("leaf", children == null || children.isEmpty()); | |||||
node.put("bean", row); | |||||
buildNode(node, row); | |||||
listRet.add(node); | |||||
if (!lazy) { | |||||
List<SwMap> list = buildNodes(children, lazy); | |||||
node.put("children", list); | |||||
} | |||||
} | |||||
return listRet; | |||||
} | |||||
//根据bean,构建treenode | |||||
protected void buildNode(SwMap node, T bean) {} | |||||
protected abstract long getId(T bean); | |||||
protected abstract String getText(T bean); | |||||
} |
@@ -0,0 +1,46 @@ | |||||
package cc.smtweb.framework.core.mvc.service; | |||||
import cc.smtweb.framework.core.cache.AbstractCache; | |||||
import cc.smtweb.framework.core.cache.CacheManager; | |||||
import cc.smtweb.framework.core.db.DbEngine; | |||||
import cc.smtweb.framework.core.db.EntityDao; | |||||
import cc.smtweb.framework.core.db.EntityHelper; | |||||
import cc.smtweb.framework.core.db.cache.ModelTableCache; | |||||
import cc.smtweb.framework.core.db.impl.DefaultEntity; | |||||
import cc.smtweb.framework.core.db.vo.ModelTable; | |||||
/** | |||||
* Created by Akmm at 2022/3/2 19:52 | |||||
* 默认实体实现 | |||||
*/ | |||||
public class DefaultDelHandler<T extends DefaultEntity> extends AbstractDelHandler { | |||||
protected String tableName; | |||||
public DefaultDelHandler(String tableName) { | |||||
this.tableName = tableName; | |||||
} | |||||
@Override | |||||
protected void checkValid() { | |||||
EntityHelper.checkExists(tableName, id); | |||||
// ModelTable table = ModelTableCache.getInstance().getByName(tableName); | |||||
//todo 检查外键引用的使用情况 | |||||
} | |||||
@Override | |||||
protected void delDb() { | |||||
EntityDao dao = DbEngine.getInstance().findDao(tableName); | |||||
dao.deleteEntity(id); | |||||
} | |||||
@Override | |||||
protected void saveSuccess() { | |||||
super.saveSuccess(); | |||||
ModelTable table = ModelTableCache.getInstance().getByName(tableName); | |||||
if (table.isNeedCache()) { | |||||
AbstractCache<T> cache = CacheManager.getIntance().getCache(tableName); | |||||
cache.remove(id); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
package cc.smtweb.framework.core.mvc.service; | |||||
import cc.smtweb.framework.core.exception.BizException; | |||||
import cc.smtweb.framework.core.exception.SwException; | |||||
import cc.smtweb.framework.core.db.DbEngine; | |||||
import cc.smtweb.framework.core.db.EntityDao; | |||||
import cc.smtweb.framework.core.db.EntityHelper; | |||||
import cc.smtweb.framework.core.db.impl.DefaultEntity; | |||||
/** | |||||
* Created by Akmm at 2022/3/3 20:04 | |||||
*/ | |||||
public class DefaultProvider<T extends DefaultEntity> extends AbstractCompProvider { | |||||
public String tableName; | |||||
public DefaultProvider(String tableName) { | |||||
this.tableName = tableName; | |||||
} | |||||
public T getBean(long id) { | |||||
return doGetData(tableName + "." + id, () -> this.loadBean(id)); | |||||
} | |||||
private T loadBean(long id) { | |||||
EntityDao<T> bdao = (EntityDao<T>) DbEngine.getInstance().findDao(tableName); | |||||
T bean = bdao.queryEntity(id); | |||||
if (bean == null) throw new BizException("没有找到指定数据(id=" + id + ")"); | |||||
EntityHelper.loadBeanLink(bean.getTableName(), bean.getData(), null); | |||||
return bean; | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
package cc.smtweb.framework.core.session; | |||||
import lombok.Getter; | |||||
import lombok.Setter; | |||||
import java.io.Serializable; | |||||
/** | |||||
* 用户会话缓存,前端调用API时传输 Auto_Token 参数来查找用户会话 | |||||
* @author xkliu | |||||
*/ | |||||
@Getter @Setter | |||||
public class UserSession implements Serializable { | |||||
private static final long serialVersionUID = 3854315462714888716L; | |||||
// 用户ID | |||||
private long userId; | |||||
// 当前组织ID | |||||
private long companyId; | |||||
// 站点ID | |||||
private long siteId; | |||||
// 终端类型 | |||||
private byte terminalType; | |||||
public static UserSession createSys() { | |||||
UserSession us = new UserSession(); | |||||
us.userId = 1; | |||||
us.companyId = 1; | |||||
us.terminalType = 0; | |||||
return us; | |||||
} | |||||
} |
@@ -0,0 +1,8 @@ | |||||
package cc.smtweb.framework.core.systask; | |||||
/** | |||||
* 任务接口 | |||||
*/ | |||||
public interface ISysTask { | |||||
int run(); | |||||
} |
@@ -0,0 +1,4 @@ | |||||
package cc.smtweb.framework.core.systask; | |||||
public class TaskStartEvent { | |||||
} |
@@ -0,0 +1,319 @@ | |||||
package cc.smtweb.framework.core.util; | |||||
import org.apache.commons.lang3.time.DateUtils; | |||||
import java.text.DateFormat; | |||||
import java.text.SimpleDateFormat; | |||||
import java.util.Calendar; | |||||
import java.util.Date; | |||||
/** | |||||
* 日期工具类 | |||||
*/ | |||||
public class DateUtil { | |||||
// private static ThreadLocal<DateFormat> stdTimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm:ss")); | |||||
private static ThreadLocal<DateFormat> stdDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); | |||||
private static ThreadLocal<DateFormat> stdDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); | |||||
// private static ThreadLocal<DateFormat> stdLongDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")); | |||||
private static ThreadLocal<DateFormat> simpleWeekFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("EEEE")); | |||||
private static ThreadLocal<DateFormat> simpleTimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("HHmmss")); | |||||
private static ThreadLocal<DateFormat> simpleDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd")); | |||||
private static ThreadLocal<DateFormat> simpleDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmss")); | |||||
private static ThreadLocal<DateFormat> simpleLongDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmssSSS")); | |||||
private DateUtil(){} | |||||
/** | |||||
* 获取当前时间 | |||||
* @return | |||||
*/ | |||||
public static Date now() { | |||||
return new Date(System.currentTimeMillis()); | |||||
} | |||||
/** | |||||
* 按yyyy-MM-dd格式化 | |||||
* @param date | |||||
* @return | |||||
*/ | |||||
public static String formatDate(Date date) { | |||||
return stdDateFormat.get().format(date); | |||||
} | |||||
/** | |||||
* 按yyyy-MM-dd HH:mm:ss格式化 | |||||
* @param date | |||||
* @return | |||||
*/ | |||||
public static String formatDateTime(Date date) { | |||||
return stdDatetimeFormat.get().format(date); | |||||
} | |||||
/** | |||||
* 格式化 | |||||
* @param date YYYYMMDD格式 | |||||
* @return | |||||
*/ | |||||
public static String formatDate(long date) { | |||||
String d = String.valueOf(date); | |||||
if(d.length() < 8) return d; //就是2011-03-04 13:11:01这种形式 | |||||
return d.substring(0, 4) + "-" + d.substring(4, 6) + "-" + d.substring(6, 8); | |||||
} | |||||
public static String formatDateTime(long date) { | |||||
String d = String.valueOf(date); | |||||
if(d.length() < 8) return d; //就是2011-03-04 13:11:01这种形式 | |||||
if(d.length() < 14) return d.substring(0, 4) + "-" + d.substring(4, 6) + "-" + d.substring(6, 8); | |||||
return d.substring(0, 4) + "-" + d.substring(4, 6) + "-" + d.substring(6, 8) + " " + d.substring(8, 10) + ":" + d.substring(10, 12) + ":" + d.substring(12); | |||||
} | |||||
/** | |||||
* 按yyyyMMdd格式化 | |||||
* @param date | |||||
* @return | |||||
*/ | |||||
public static String formatSimpleDate(Date date) { | |||||
return simpleDateFormat.get().format(date); | |||||
} | |||||
/** | |||||
* 按yyyyMMddHHmmss格式化 | |||||
* @param date | |||||
* @return | |||||
*/ | |||||
public static String formatSimpleDateTime(Date date) { | |||||
return simpleDatetimeFormat.get().format(date); | |||||
} | |||||
/** | |||||
* 按yyyyMMddHHmmssSSS格式化 | |||||
* @param date | |||||
* @return | |||||
*/ | |||||
public static String formatSimpleDateTimeS(Date date) { | |||||
return simpleLongDatetimeFormat.get().format(date); | |||||
} | |||||
/** | |||||
* 按yyyyMMdd格式化 | |||||
* @param date | |||||
* @return | |||||
*/ | |||||
public static long date2Long(Date date) { | |||||
return Long.parseLong(formatSimpleDate(date)); | |||||
} | |||||
/** | |||||
* 按yyyyMMddHHmmss格式化 | |||||
* @param date | |||||
* @return | |||||
*/ | |||||
public static long dateTime2Long(Date date) { | |||||
return Long.parseLong(formatSimpleDateTime(date)); | |||||
} | |||||
public static String nowDate() { | |||||
return formatDate(now()); | |||||
} | |||||
public static String nowDateTime() { | |||||
return formatDateTime(now()); | |||||
} | |||||
public static long nowDateLong() { | |||||
return date2Long(now()); | |||||
} | |||||
public static long nowDateTimeLong() { | |||||
return dateTime2Long(now()); | |||||
} | |||||
// 获得当天0点时间 | |||||
public static long getTimesmorning() { | |||||
return getTimesmorning(System.currentTimeMillis()); | |||||
} | |||||
// 获得指定时间0点时间 | |||||
public static long getTimesmorning(long time) { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTimeInMillis(time); | |||||
cal.set(Calendar.HOUR_OF_DAY, 0); | |||||
cal.set(Calendar.SECOND, 0); | |||||
cal.set(Calendar.MINUTE, 0); | |||||
cal.set(Calendar.MILLISECOND, 0); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
// 获得昨天0点时间 | |||||
public static long getYesterdaymorning() { | |||||
return getTimesmorning() - DateUtils.MILLIS_PER_DAY; | |||||
} | |||||
// 获得当天近7天时间 | |||||
public static long getWeekFromNow() { | |||||
return getTimesmorning() - DateUtils.MILLIS_PER_DAY * 7; | |||||
} | |||||
// 获得当天24点时间 | |||||
public static long getTimesnight() { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.set(Calendar.HOUR_OF_DAY, 24); | |||||
cal.set(Calendar.SECOND, 0); | |||||
cal.set(Calendar.MINUTE, 0); | |||||
cal.set(Calendar.MILLISECOND, 0); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
public static long getTimesnight(long time) { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTimeInMillis(time); | |||||
cal.set(Calendar.HOUR_OF_DAY, 24); | |||||
cal.set(Calendar.SECOND, 0); | |||||
cal.set(Calendar.MINUTE, 0); | |||||
cal.set(Calendar.MILLISECOND, 0); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
// 获得本周一0点时间 | |||||
public static long getTimesWeekmorning() { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); | |||||
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
// 获得本周日24点时间 | |||||
public static long getTimesWeeknight() { | |||||
return getTimesWeekmorning() + DateUtils.MILLIS_PER_DAY * 7; | |||||
} | |||||
// 获得本月第一天0点时间 | |||||
public static long getTimesMonthMorning() { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTimeInMillis(System.currentTimeMillis()); | |||||
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); | |||||
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH)); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
// 获得本月最后一天24点时间 | |||||
public static long getTimesMontgHight() { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTimeInMillis(System.currentTimeMillis()); | |||||
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); | |||||
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); | |||||
cal.set(Calendar.HOUR_OF_DAY, 24); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
public static long getLastMonthStartMorning() { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTimeInMillis(getTimesMonthMorning()); | |||||
cal.add(Calendar.MONTH, -1); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
public static long getLastMonthStartMorning(long timeInMillis) { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTimeInMillis(timeInMillis); | |||||
cal.add(Calendar.MONTH, -1); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
public static Date getCurrentQuarterStartTime() { | |||||
Calendar c = Calendar.getInstance(); | |||||
c.setTimeInMillis(getTimesMonthMorning()); | |||||
int currentMonth = c.get(Calendar.MONTH) + 1; | |||||
SimpleDateFormat longSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | |||||
SimpleDateFormat shortSdf = new SimpleDateFormat("yyyy-MM-dd"); | |||||
Date now = null; | |||||
try { | |||||
if (currentMonth <= 3) { | |||||
c.set(Calendar.MONTH, 0); | |||||
} else if (currentMonth <= 6) { | |||||
c.set(Calendar.MONTH, 3); | |||||
} else if (currentMonth <= 9) { | |||||
c.set(Calendar.MONTH, 4); | |||||
} else if (currentMonth <= 12) { | |||||
c.set(Calendar.MONTH, 9); | |||||
} | |||||
c.set(Calendar.DATE, 1); | |||||
now = longSdf.parse(shortSdf.format(c.getTime()) + " 00:00:00"); | |||||
} catch (Exception e) { | |||||
e.printStackTrace(); | |||||
} | |||||
return now; | |||||
} | |||||
/** | |||||
* 当前季度的结束时间,即2012-03-31 23:59:59 | |||||
* | |||||
* @return | |||||
*/ | |||||
public static Date getCurrentQuarterEndTime() { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTime(getCurrentQuarterStartTime()); | |||||
cal.add(Calendar.MONTH, 3); | |||||
return cal.getTime(); | |||||
} | |||||
public static long getCurrentYearStartTime() { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTimeInMillis(getTimesMonthMorning()); | |||||
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); | |||||
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.YEAR)); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
public static long getCurrentYearEndTime() { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTimeInMillis(getCurrentYearStartTime()); | |||||
cal.add(Calendar.YEAR, 1); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
public static long getLastYearStartTime() { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTimeInMillis(getCurrentYearStartTime()); | |||||
cal.add(Calendar.YEAR, -1); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
public static long getLastYearStartTime(long timeInMillis) { | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.setTimeInMillis(timeInMillis); | |||||
cal.add(Calendar.YEAR, -1); | |||||
return cal.getTimeInMillis(); | |||||
} | |||||
public static String parseTimeTag(Date date){ | |||||
if(date == null){ | |||||
return ""; | |||||
} | |||||
int now = date2Day(new Date()); | |||||
int day = date2Day(date); | |||||
int deDay = now - day; | |||||
// int deMonth = now/100 - day/100; | |||||
int deYear = now/10000 - day/10000; | |||||
if(deYear < 1){ | |||||
switch(deDay){ | |||||
case 0: return new SimpleDateFormat("HH:mm").format(date); | |||||
case 1: return "昨天"; | |||||
default: | |||||
return new SimpleDateFormat("MM-dd").format(date); | |||||
} | |||||
} | |||||
return new SimpleDateFormat("yyyy-MM-dd").format(date); | |||||
} | |||||
private static int date2Day(Date date){ | |||||
return Integer.parseInt(new SimpleDateFormat("yyyyMMdd").format(date)); | |||||
} | |||||
public static void main(String[] args) { | |||||
System.out.println(getTimesmorning()); | |||||
} | |||||
} |
@@ -0,0 +1,49 @@ | |||||
package cc.smtweb.framework.core.util; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import java.io.FileInputStream; | |||||
import java.io.IOException; | |||||
import java.io.UnsupportedEncodingException; | |||||
import java.nio.charset.StandardCharsets; | |||||
/** | |||||
* Created by Akmm at 2022/7/4 21:37 | |||||
* 文件操作处理 | |||||
*/ | |||||
@Slf4j | |||||
public class FileUtil { | |||||
/** | |||||
* 从fn中读取所有内容 | |||||
* | |||||
* @param fn 源文件名 | |||||
* @return byte[] | |||||
*/ | |||||
public static byte[] readFileByte(String fn) { | |||||
try { | |||||
FileInputStream fis = new FileInputStream(fn); | |||||
byte[] data = new byte[fis.available()]; | |||||
fis.read(data, 0, data.length); | |||||
fis.close(); | |||||
return data; | |||||
} catch (IOException e) { | |||||
log.error(e.getMessage(), e); | |||||
return null; | |||||
} | |||||
} | |||||
public static String readFileStr(String fn) { | |||||
return readFileStr(fn, StandardCharsets.UTF_8.toString()); | |||||
} | |||||
public static String readFileStr(String fn, String encode) { | |||||
byte[] bytes = readFileByte(fn); | |||||
if (bytes == null) return null; | |||||
try { | |||||
return new String(bytes, encode); | |||||
} catch (UnsupportedEncodingException e) { | |||||
log.error(e.getMessage(), e); | |||||
return null; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,221 @@ | |||||
package cc.smtweb.framework.core.util; | |||||
import cc.smtweb.framework.core.common.SwMap; | |||||
import org.apache.commons.lang3.StringUtils; | |||||
import java.util.*; | |||||
/** | |||||
* map工具类 | |||||
*/ | |||||
public class MapUtil { | |||||
private MapUtil() {} | |||||
public static String readString(Map map, String name) { | |||||
return readString(map, name, ""); | |||||
} | |||||
public static String readString(Map map, String name, String defaultValue) { | |||||
Object s = map.get(name); | |||||
if (s != null) { | |||||
return s.toString(); | |||||
} | |||||
return defaultValue; | |||||
} | |||||
public static long readLong(Map map, String name) { | |||||
return readLong(map, name, 0L); | |||||
} | |||||
public static long readLong(Map map, String name, Long defaultValue) { | |||||
Object s = map.get(name); | |||||
if (s != null) { | |||||
if (s instanceof Number) { | |||||
return ((Number) s).longValue(); | |||||
} else { | |||||
String value = s.toString(); | |||||
if (StringUtils.isNotBlank(value)) { | |||||
return Long.parseLong(value); | |||||
} | |||||
} | |||||
} | |||||
return defaultValue; | |||||
} | |||||
public static Long[] readLongArray(Map map, String name) { | |||||
return readLongArray(map, name, null); | |||||
} | |||||
public static Long[] readLongArray(Map map, String name, Long[] defaultValue) { | |||||
Object value = map.get(name); | |||||
if (value != null) { | |||||
if (value instanceof Long[]) { | |||||
return (Long[]) value; | |||||
} else if (value instanceof Object[]) { | |||||
Object[] items = (Object[])value; | |||||
List<Long> result = new ArrayList<>(items.length); | |||||
for (Object item: items) { | |||||
if (item != null) { | |||||
String s = item.toString(); | |||||
if (StringUtils.isNotBlank(s)) { | |||||
result.add(Long.valueOf(s)); | |||||
} | |||||
} | |||||
} | |||||
if (result.size() > 0) { | |||||
return result.toArray(new Long[result.size()]); | |||||
} | |||||
} else if (value instanceof List) { | |||||
List items = (List)value; | |||||
List<Long> result = new ArrayList<>(items.size()); | |||||
for (Object item: items) { | |||||
if (item != null) { | |||||
String s = item.toString(); | |||||
if (StringUtils.isNotBlank(s)) { | |||||
result.add(Long.valueOf(s)); | |||||
} | |||||
} | |||||
} | |||||
if (result.size() > 0) { | |||||
return result.toArray(new Long[result.size()]); | |||||
} | |||||
} else if (value instanceof String) { | |||||
String[] ary = value.toString().split(","); | |||||
if (ary.length > 0) { | |||||
List<Long> result = new ArrayList<>(ary.length); | |||||
for (String item: ary) { | |||||
if (StringUtils.isNotBlank(item)) { | |||||
result.add(Long.valueOf(item)); | |||||
} | |||||
} | |||||
if (result.size() > 0) { | |||||
return result.toArray(new Long[result.size()]); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return defaultValue; | |||||
} | |||||
public static int readInt(Map map, String name) { | |||||
return readInt(map, name, 0); | |||||
} | |||||
public static int readInt(Map map, String name, int defaultValue) { | |||||
Object s = map.get(name); | |||||
if (s != null) { | |||||
if (s instanceof Number) { | |||||
return ((Number) s).intValue(); | |||||
} else { | |||||
String value = s.toString(); | |||||
if (StringUtils.isNotBlank(value)) { | |||||
return Integer.parseInt(value); | |||||
} | |||||
} | |||||
} | |||||
return defaultValue; | |||||
} | |||||
public static float readFloat(Map map, String name) { | |||||
return readFloat(map, name, 0.0F); | |||||
} | |||||
public static float readFloat(Map map, String name, float defaultValue) { | |||||
Object s = map.get(name); | |||||
if (s != null) { | |||||
if (s instanceof Number) { | |||||
return ((Number) s).floatValue(); | |||||
} else { | |||||
String value = s.toString(); | |||||
if (StringUtils.isNotBlank(value)) { | |||||
return Float.parseFloat(value); | |||||
} | |||||
} | |||||
} | |||||
return defaultValue; | |||||
} | |||||
public static double readDouble(Map map, String name) { | |||||
return readDouble(map, name, 0d); | |||||
} | |||||
public static double readDouble(Map map, String name, double defaultValue) { | |||||
Object s = map.get(name); | |||||
if (s != null) { | |||||
if (s instanceof Number) { | |||||
return ((Number) s).doubleValue(); | |||||
} else { | |||||
String value = s.toString(); | |||||
if (StringUtils.isNotBlank(value)) { | |||||
return Double.parseDouble(value); | |||||
} | |||||
} | |||||
} | |||||
return defaultValue; | |||||
} | |||||
public static boolean readBool(Map map, String name) { | |||||
return readBool(map, name, false); | |||||
} | |||||
public static boolean readBool(Map map, String name, boolean defaultValue) { | |||||
Object s = map.get(name); | |||||
if (s != null) { | |||||
if (s instanceof Boolean) { | |||||
return (Boolean) s; | |||||
} else { | |||||
String value = s.toString(); | |||||
if ("true".equalsIgnoreCase(value) || "1".equals(value) || "y".equalsIgnoreCase(value)) { | |||||
return Boolean.TRUE; | |||||
} | |||||
} | |||||
} | |||||
return defaultValue; | |||||
} | |||||
public static Set<Long> readLongSet(SwMap swMap, String name) { | |||||
Object value = swMap.get(name); | |||||
if (value != null) { | |||||
String[] ary = value.toString().split(","); | |||||
if (ary.length > 0) { | |||||
Set<Long> result = new HashSet<>(ary.length); | |||||
for (String item : ary) { | |||||
if (StringUtils.isNotBlank(item)) { | |||||
result.add(Long.valueOf(item)); | |||||
} | |||||
} | |||||
return result; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
public static List<Map<String, Object>> readListMap(Map map, String name) { | |||||
Object v = map.get(name); | |||||
if (v == null) return null; | |||||
return (List<Map<String, Object>>)v; | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
package cc.smtweb.framework.core.util; | |||||
/** | |||||
* Created by Akmm at 2022/5/20 16:57 | |||||
* sql工具类 | |||||
*/ | |||||
public class SqlUtil { | |||||
/** | |||||
* 将sql中的表名替换成schema.table的格式 | |||||
* @param sql | |||||
* @return | |||||
*/ | |||||
public static String replaceTable(String sql) { | |||||
return sql; | |||||
} | |||||
} |
@@ -0,0 +1,70 @@ | |||||
package cc.smtweb.framework.core.util.jackson; | |||||
import com.fasterxml.jackson.core.JsonGenerator; | |||||
import com.fasterxml.jackson.core.JsonProcessingException; | |||||
import com.fasterxml.jackson.databind.JsonSerializer; | |||||
import com.fasterxml.jackson.databind.SerializerProvider; | |||||
import java.io.IOException; | |||||
import java.util.Date; | |||||
public class DateSerializer extends JsonSerializer<Date> { | |||||
@Override | |||||
public void serialize(Date value, JsonGenerator gen, | |||||
SerializerProvider serializers) throws IOException, | |||||
JsonProcessingException { | |||||
if (value == null) { | |||||
gen.writeNull(); | |||||
} else { | |||||
gen.writeString(toString(value)); | |||||
// gen.writeNumber(value); | |||||
// gen.writeString("\""); | |||||
} | |||||
} | |||||
@SuppressWarnings("deprecation") | |||||
public String toString (Date value) { | |||||
int year = value.getYear() + 1900; | |||||
int month = value.getMonth() + 1; | |||||
int day = value.getDate(); | |||||
String yearString; | |||||
String monthString; | |||||
String dayString; | |||||
String yearZeros = "0000"; | |||||
StringBuffer timestampBuf; | |||||
if (year < 1000) { | |||||
// Add leading zeros | |||||
yearString = "" + year; | |||||
yearString = yearZeros.substring(0, (4-yearString.length())) + | |||||
yearString; | |||||
} else { | |||||
yearString = "" + year; | |||||
} | |||||
if (month < 10) { | |||||
monthString = "0" + month; | |||||
} else { | |||||
monthString = Integer.toString(month); | |||||
} | |||||
if (day < 10) { | |||||
dayString = "0" + day; | |||||
} else { | |||||
dayString = Integer.toString(day); | |||||
} | |||||
// do a string buffer here instead. | |||||
timestampBuf = new StringBuffer(10); | |||||
timestampBuf.append(yearString); | |||||
timestampBuf.append("-"); | |||||
timestampBuf.append(monthString); | |||||
timestampBuf.append("-"); | |||||
timestampBuf.append(dayString); | |||||
return (timestampBuf.toString()); | |||||
} | |||||
public static void main(String[] args) { | |||||
System.out.println(new DateSerializer().toString(new Date())); | |||||
} | |||||
} |
@@ -0,0 +1,61 @@ | |||||
package cc.smtweb.framework.core.util.jackson; | |||||
import com.fasterxml.jackson.core.JsonGenerator; | |||||
import com.fasterxml.jackson.core.JsonProcessingException; | |||||
import com.fasterxml.jackson.databind.JsonSerializer; | |||||
import com.fasterxml.jackson.databind.SerializerProvider; | |||||
import java.io.IOException; | |||||
import java.util.Date; | |||||
public class TimeSerializer extends JsonSerializer<Date> { | |||||
@Override | |||||
public void serialize(Date value, JsonGenerator gen, | |||||
SerializerProvider serializers) throws IOException, | |||||
JsonProcessingException { | |||||
if (value == null) { | |||||
gen.writeNull(); | |||||
} else { | |||||
gen.writeString(toString(value)); | |||||
// gen.writeNumber(value); | |||||
// gen.writeString("\""); | |||||
} | |||||
} | |||||
@SuppressWarnings("deprecation") | |||||
public String toString (Date value) { | |||||
int hour = value.getHours(); | |||||
int minute = value.getMinutes(); | |||||
int second = value.getSeconds(); | |||||
String hourString; | |||||
String minuteString; | |||||
String secondString; | |||||
if (hour < 10) { | |||||
hourString = "0" + hour; | |||||
} else { | |||||
hourString = Integer.toString(hour); | |||||
} | |||||
if (minute < 10) { | |||||
minuteString = "0" + minute; | |||||
} else { | |||||
minuteString = Integer.toString(minute); | |||||
} | |||||
if (second < 10) { | |||||
secondString = "0" + second; | |||||
} else { | |||||
secondString = Integer.toString(second); | |||||
} | |||||
// do a string buffer here instead. | |||||
StringBuffer timestampBuf = new StringBuffer(8); | |||||
timestampBuf.append(hourString); | |||||
timestampBuf.append(":"); | |||||
timestampBuf.append(minuteString); | |||||
timestampBuf.append(":"); | |||||
timestampBuf.append(secondString); | |||||
return (timestampBuf.toString()); | |||||
} | |||||
} |
@@ -0,0 +1,54 @@ | |||||
package com.serotonin.timer; | |||||
import org.apache.commons.lang3.StringUtils; | |||||
import java.util.List; | |||||
abstract public class AbstractTimer { | |||||
abstract public boolean isInitialized(); | |||||
abstract public long currentTimeMillis(); | |||||
abstract public void execute(Runnable command); | |||||
public void execute(Runnable command, String name) { | |||||
if (StringUtils.isBlank(name)) | |||||
execute(command); | |||||
else | |||||
execute(new NamedRunnable(command, name)); | |||||
} | |||||
abstract public void execute(ScheduledRunnable command, long fireTime); | |||||
public void execute(ScheduledRunnable command, long fireTime, String name) { | |||||
if (StringUtils.isBlank(name)) | |||||
execute(command, fireTime); | |||||
else | |||||
execute(new ScheduledNamedRunnable(command, name), fireTime); | |||||
} | |||||
final public TimerTask schedule(TimerTask task) { | |||||
if (task.getTimer() == this) | |||||
throw new IllegalStateException("Task already scheduled or cancelled"); | |||||
task.setTimer(this); | |||||
scheduleImpl(task); | |||||
return task; | |||||
} | |||||
public void scheduleAll(AbstractTimer that) { | |||||
for (TimerTask task : that.cancel()) | |||||
schedule(task); | |||||
} | |||||
abstract protected void scheduleImpl(TimerTask task); | |||||
abstract public List<TimerTask> cancel(); | |||||
abstract public int purge(); | |||||
abstract public int size(); | |||||
abstract public List<TimerTask> getTasks(); | |||||
} |
@@ -0,0 +1,34 @@ | |||||
package com.serotonin.timer; | |||||
import java.text.ParseException; | |||||
import java.util.Date; | |||||
public class CronTimerTrigger extends TimerTrigger { | |||||
private final CronExpression cronExpression; | |||||
private long mostRecent; | |||||
public CronTimerTrigger(String pattern) throws ParseException { | |||||
cronExpression = new CronExpression(pattern); | |||||
} | |||||
@Override | |||||
protected long calculateNextExecutionTimeImpl() { | |||||
mostRecent = nextExecutionTime; | |||||
return cronExpression.getNextValidTimeAfter(new Date(nextExecutionTime)).getTime(); | |||||
} | |||||
@Override | |||||
protected long calculateNextExecutionTimeImpl(long after) { | |||||
return cronExpression.getNextValidTimeAfter(new Date(after)).getTime(); | |||||
} | |||||
@Override | |||||
protected long getFirstExecutionTime() { | |||||
return cronExpression.getNextValidTimeAfter(new Date(timer.currentTimeMillis())).getTime(); | |||||
} | |||||
@Override | |||||
public long mostRecentExecutionTime() { | |||||
return mostRecent; | |||||
} | |||||
} |
@@ -0,0 +1,12 @@ | |||||
package com.serotonin.timer; | |||||
public class ExecutionRejectedException extends Exception { | |||||
/** | |||||
* | |||||
*/ | |||||
private static final long serialVersionUID = 1L; | |||||
public ExecutionRejectedException() { | |||||
super(); | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
package com.serotonin.timer; | |||||
import java.util.Date; | |||||
public class FixedDelayTrigger extends AbstractTimerTrigger { | |||||
private final long period; | |||||
public FixedDelayTrigger(long delay, long period) { | |||||
super(delay); | |||||
this.period = period; | |||||
} | |||||
public FixedDelayTrigger(Date start, long period) { | |||||
super(start); | |||||
this.period = period; | |||||
} | |||||
@Override | |||||
protected long calculateNextExecutionTimeImpl() { | |||||
return timer.currentTimeMillis() + period; | |||||
} | |||||
@Override | |||||
protected long calculateNextExecutionTimeImpl(long after) { | |||||
return after + period; | |||||
} | |||||
@Override | |||||
public long mostRecentExecutionTime() { | |||||
return nextExecutionTime - period; | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
package com.serotonin.timer; | |||||
/** | |||||
* A class that wraps a Runnable and sets the thread name to the given name. | |||||
* | |||||
* @author Matthew Lohbihler | |||||
*/ | |||||
public class NamedRunnable implements Runnable { | |||||
private final Runnable runnable; | |||||
private final String name; | |||||
public NamedRunnable(Runnable runnable, String name) { | |||||
this.runnable = runnable; | |||||
this.name = name; | |||||
} | |||||
public void run() { | |||||
String originalName = Thread.currentThread().getName(); | |||||
// Append the given name to the original name. | |||||
Thread.currentThread().setName(originalName + " --> " + name); | |||||
try { | |||||
// Ok, go ahead and run the thingy. | |||||
runnable.run(); | |||||
} | |||||
finally { | |||||
// Return the name to its original. | |||||
Thread.currentThread().setName(originalName); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
package com.serotonin.timer; | |||||
/** | |||||
* A class that wraps a Runnable and sets the thread name to the given name. | |||||
* | |||||
* @author Matthew Lohbihler | |||||
*/ | |||||
public class ScheduledNamedRunnable implements ScheduledRunnable { | |||||
private final ScheduledRunnable runnable; | |||||
private final String name; | |||||
public ScheduledNamedRunnable(ScheduledRunnable runnable, String name) { | |||||
this.runnable = runnable; | |||||
this.name = name; | |||||
} | |||||
@Override | |||||
public void run(long fireTime) { | |||||
String originalName = Thread.currentThread().getName(); | |||||
// Append the given name to the original name. | |||||
Thread.currentThread().setName(originalName + " --> " + name); | |||||
try { | |||||
// Ok, go ahead and run the thingy. | |||||
runnable.run(fireTime); | |||||
} | |||||
finally { | |||||
// Return the name to its original. | |||||
Thread.currentThread().setName(originalName); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,8 @@ | |||||
package com.serotonin.timer; | |||||
/** | |||||
* Same as {@link Runnable}, but has a fireTime (millis) parameter. | |||||
*/ | |||||
public interface ScheduledRunnable { | |||||
void run(long fireTime); | |||||
} |
@@ -0,0 +1,13 @@ | |||||
package com.serotonin.timer; | |||||
public class SimulationTimeSource implements TimeSource { | |||||
private long time; | |||||
public long currentTimeMillis() { | |||||
return time; | |||||
} | |||||
public void setTime(long time) { | |||||
this.time = time; | |||||
} | |||||
} |
@@ -0,0 +1,194 @@ | |||||
package com.serotonin.timer; | |||||
import java.util.ArrayList; | |||||
import java.util.Collections; | |||||
import java.util.Comparator; | |||||
import java.util.List; | |||||
/** | |||||
* The simulation timer is a single threaded timer under the temporal control of the next and fastForward methods. Tasks | |||||
* are run in the same thread as the timer, so they will seem to complete instantly. Running them in an executor has the | |||||
* opposite effect of making them appear to take an awful long time to complete. | |||||
* | |||||
* @author Matthew Lohbihler | |||||
*/ | |||||
public class SimulationTimer extends AbstractTimer { | |||||
private final List<TimerTask> queue = new ArrayList<TimerTask>(); | |||||
private boolean cancelled; | |||||
private long currentTime; | |||||
@Override | |||||
public boolean isInitialized() { | |||||
return true; | |||||
} | |||||
public void setStartTime(long startTime) { | |||||
currentTime = startTime; | |||||
} | |||||
public void next() { | |||||
fastForwardTo(currentTime + 1); | |||||
} | |||||
public void fastForwardTo(long time) { | |||||
while (!queue.isEmpty() && queue.get(0).trigger.nextExecutionTime <= time) { | |||||
TimerTask task = queue.get(0); | |||||
currentTime = task.trigger.nextExecutionTime; | |||||
if (task.state == TimerTask.CANCELLED) | |||||
queue.remove(0); | |||||
else { | |||||
long next = task.trigger.calculateNextExecutionTime(); | |||||
if (next <= 0) { // Non-repeating, remove | |||||
queue.remove(0); | |||||
task.state = TimerTask.EXECUTED; | |||||
} | |||||
else { | |||||
// Repeating task, reschedule | |||||
task.trigger.nextExecutionTime = next; | |||||
updateQueue(); | |||||
} | |||||
task.run(); | |||||
} | |||||
} | |||||
currentTime = time; | |||||
} | |||||
@Override | |||||
public void execute(Runnable command) { | |||||
command.run(); | |||||
} | |||||
@Override | |||||
public void execute(ScheduledRunnable command, long fireTime) { | |||||
command.run(fireTime); | |||||
} | |||||
@Override | |||||
protected void scheduleImpl(TimerTask task) { | |||||
if (cancelled) | |||||
throw new IllegalStateException("Timer already cancelled."); | |||||
if (task.state == TimerTask.CANCELLED || task.state == TimerTask.EXECUTED) | |||||
throw new IllegalStateException("Task already executed or cancelled"); | |||||
if (task.state == TimerTask.VIRGIN) { | |||||
long time = task.trigger.getFirstExecutionTime(); | |||||
if (time < 0) | |||||
throw new IllegalArgumentException("Illegal execution time."); | |||||
task.trigger.nextExecutionTime = time; | |||||
task.state = TimerTask.SCHEDULED; | |||||
} | |||||
queue.add(task); | |||||
updateQueue(); | |||||
} | |||||
private void updateQueue() { | |||||
Collections.sort(queue, new Comparator<TimerTask>() { | |||||
@Override | |||||
public int compare(TimerTask t1, TimerTask t2) { | |||||
long diff = t1.trigger.nextExecutionTime - t2.trigger.nextExecutionTime; | |||||
if (diff < 0) | |||||
return -1; | |||||
if (diff == 0) | |||||
return 0; | |||||
return 1; | |||||
} | |||||
}); | |||||
} | |||||
@Override | |||||
public List<TimerTask> cancel() { | |||||
cancelled = true; | |||||
List<TimerTask> tasks = getTasks(); | |||||
queue.clear(); | |||||
return tasks; | |||||
} | |||||
@Override | |||||
public int purge() { | |||||
int result = 0; | |||||
for (int i = queue.size(); i > 0; i--) { | |||||
if (queue.get(i).state == TimerTask.CANCELLED) { | |||||
queue.remove(i); | |||||
result++; | |||||
} | |||||
} | |||||
return result; | |||||
} | |||||
@Override | |||||
public int size() { | |||||
return queue.size(); | |||||
} | |||||
@Override | |||||
public List<TimerTask> getTasks() { | |||||
return new ArrayList<TimerTask>(queue); | |||||
} | |||||
@Override | |||||
public long currentTimeMillis() { | |||||
return currentTime; | |||||
} | |||||
// | |||||
// public static void main(String[] args) throws Exception { | |||||
// long startTime = System.currentTimeMillis() - 32000; | |||||
// SimulationTimer simTimer = new SimulationTimer(); | |||||
// simTimer.setStartTime(startTime); | |||||
// | |||||
// simTimer.schedule(new NamedTask("task 7", new OneTimeTrigger(25000))); | |||||
// simTimer.schedule(new NamedTask("task 1", new OneTimeTrigger(1000))); | |||||
// simTimer.schedule(new NamedTask("task 2", new OneTimeTrigger(2000))); | |||||
// simTimer.schedule(new NamedTask("task 4", new OneTimeTrigger(20000))); | |||||
// simTimer.schedule(new NamedTask("task 5", new OneTimeTrigger(21000))); | |||||
// simTimer.schedule(new NamedTask("task 3", new OneTimeTrigger(10000))); | |||||
// simTimer.schedule(new NamedTask("task 6", new OneTimeTrigger(22000))); | |||||
// simTimer.schedule(new NamedTask("rate", new FixedRateTrigger(5000, | |||||
// 1800))); | |||||
// simTimer.schedule(new NamedTask("delay", new FixedDelayTrigger(6000, | |||||
// 2100))); | |||||
// simTimer.schedule(new NamedTask("cron", new | |||||
// CronTimerTrigger("0/6 * * * * ?"))); | |||||
// | |||||
// simTimer.fastForwardTo(System.currentTimeMillis()); | |||||
// | |||||
// System.out.println("Rescheduling"); | |||||
// | |||||
// RealTimeTimer rtTimer = new RealTimeTimer(); | |||||
// ExecutorService executorService = Executors.newCachedThreadPool(); | |||||
// rtTimer.init(executorService); | |||||
// rtTimer.scheduleAll(simTimer); | |||||
// | |||||
// Thread.sleep(20000); | |||||
// | |||||
// rtTimer.cancel(); | |||||
// executorService.shutdown(); | |||||
// } | |||||
// | |||||
// static class NamedTask extends TimerTask { | |||||
// String name; | |||||
// | |||||
// NamedTask(String name, TimerTrigger trigger) { | |||||
// super(trigger); | |||||
// this.name = name; | |||||
// } | |||||
// | |||||
// @Override | |||||
// public String toString() { | |||||
// return "NamedTask(" + name + ")"; | |||||
// } | |||||
// | |||||
// @Override | |||||
// protected void run(long runtime) { | |||||
// System.out.println(name + " ran at " + runtime); | |||||
// } | |||||
// } | |||||
} |
@@ -0,0 +1,30 @@ | |||||
package com.serotonin.timer; | |||||
import java.util.Date; | |||||
/** | |||||
* A simple way of creating a timeout. | |||||
* | |||||
* @author Matthew | |||||
*/ | |||||
public class TimeoutTask extends TimerTask { | |||||
private final ScheduledRunnable client; | |||||
public TimeoutTask(long delay, ScheduledRunnable client) { | |||||
this(new OneTimeTrigger(delay), client); | |||||
} | |||||
public TimeoutTask(Date date, ScheduledRunnable client) { | |||||
this(new OneTimeTrigger(date), client); | |||||
} | |||||
public TimeoutTask(TimerTrigger trigger, ScheduledRunnable client) { | |||||
super(trigger); | |||||
this.client = client; | |||||
} | |||||
@Override | |||||
public void run(long runtime) { | |||||
client.run(runtime); | |||||
} | |||||
} |
@@ -0,0 +1,124 @@ | |||||
package com.serotonin.timer; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
import java.util.concurrent.ExecutorService; | |||||
import java.util.concurrent.RejectedExecutionException; | |||||
class TimerThread extends Thread { | |||||
private static final Log LOG = LogFactory.getLog(TimerThread.class); | |||||
/** | |||||
* This flag is set to false by the reaper to inform us that there are no more live references to our Timer object. | |||||
* Once this flag is true and there are no more tasks in our queue, there is no work left for us to do, so we | |||||
* terminate gracefully. Note that this field is protected by queue's monitor! | |||||
*/ | |||||
boolean newTasksMayBeScheduled = true; | |||||
/** | |||||
* Our Timer's queue. We store this reference in preference to a reference to the Timer so the reference graph | |||||
* remains acyclic. Otherwise, the Timer would never be garbage-collected and this thread would never go away. | |||||
*/ | |||||
private final TaskQueue queue; | |||||
private final ExecutorService executorService; | |||||
private final TimeSource timeSource; | |||||
TimerThread(TaskQueue queue, ExecutorService executorService, TimeSource timeSource) { | |||||
this.queue = queue; | |||||
this.executorService = executorService; | |||||
this.timeSource = timeSource; | |||||
} | |||||
@Override | |||||
public void run() { | |||||
try { | |||||
mainLoop(); | |||||
} | |||||
catch (Throwable t) { | |||||
LOG.fatal("TimerThread failed", t); | |||||
} | |||||
finally { | |||||
// Someone killed this Thread, behave as if Timer was cancelled | |||||
synchronized (queue) { | |||||
newTasksMayBeScheduled = false; | |||||
queue.clear(); // Eliminate obsolete references | |||||
} | |||||
} | |||||
} | |||||
void execute(Runnable command) { | |||||
executorService.execute(command); | |||||
} | |||||
void execute(final ScheduledRunnable command, final long fireTime) { | |||||
executorService.execute(new Runnable() { | |||||
@Override | |||||
public void run() { | |||||
command.run(fireTime); | |||||
} | |||||
}); | |||||
} | |||||
ExecutorService getExecutorService() { | |||||
return executorService; | |||||
} | |||||
/** | |||||
* The main timer loop. (See class comment.) | |||||
*/ | |||||
private void mainLoop() { | |||||
while (true) { | |||||
try { | |||||
TimerTask task; | |||||
boolean taskFired; | |||||
synchronized (queue) { | |||||
// Wait for queue to become non-empty | |||||
while (queue.isEmpty() && newTasksMayBeScheduled) | |||||
queue.wait(); | |||||
if (queue.isEmpty()) | |||||
break; // Queue is empty and will forever remain; die | |||||
// Queue nonempty; look at first evt and do the right thing | |||||
long executionTime; | |||||
task = queue.getMin(); | |||||
synchronized (task.lock) { | |||||
if (task.state == TimerTask.CANCELLED) { | |||||
queue.removeMin(); | |||||
continue; // No action required, poll queue again | |||||
} | |||||
executionTime = task.trigger.nextExecutionTime; | |||||
if (taskFired = (executionTime <= timeSource.currentTimeMillis())) { | |||||
long next = task.trigger.calculateNextExecutionTime(); | |||||
if (next <= 0) { // Non-repeating, remove | |||||
queue.removeMin(); | |||||
task.state = TimerTask.EXECUTED; | |||||
} | |||||
else | |||||
// Repeating task, reschedule | |||||
queue.rescheduleMin(next); | |||||
} | |||||
} | |||||
if (!taskFired) {// Task hasn't yet fired; wait | |||||
long wait = executionTime - timeSource.currentTimeMillis(); | |||||
if (wait > 0) | |||||
queue.wait(wait); | |||||
} | |||||
} | |||||
if (taskFired) { | |||||
// Task fired; run it, holding no locks | |||||
try { | |||||
executorService.execute(task); | |||||
} | |||||
catch (RejectedExecutionException e) { | |||||
LOG.warn("Rejected task: " + task, e); | |||||
} | |||||
} | |||||
} | |||||
catch (InterruptedException e) { | |||||
// no op | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,80 @@ | |||||
package com.serotonin.timer; | |||||
import java.util.Date; | |||||
abstract public class TimerTrigger { | |||||
// The maximum time that a task can run late. If the next run time is calculated to be more than this in the past | |||||
// it will be adjusted to be more current. | |||||
private static final int MAX_TARDINESS = 1000 * 60 * 10; // 10 minutes. | |||||
protected AbstractTimer timer; | |||||
void setTimer(AbstractTimer timer) { | |||||
this.timer = timer; | |||||
} | |||||
AbstractTimer getTimer() { | |||||
return timer; | |||||
} | |||||
/** | |||||
* Next execution time for this task in the format returned by System.currentTimeMillis, assuming this task is | |||||
* scheduled for execution. For repeating tasks, this field is updated prior to each task execution. | |||||
*/ | |||||
long nextExecutionTime; | |||||
/** | |||||
* Returns the <i>scheduled</i> execution time of the most recent <i>actual</i> execution of this task. (If this | |||||
* method is invoked while task execution is in progress, the return value is the scheduled execution time of the | |||||
* ongoing task execution.) | |||||
* | |||||
* <p> | |||||
* This method is typically invoked from within a task's run method, to determine whether the current execution of | |||||
* the task is sufficiently timely to warrant performing the scheduled activity: | |||||
* | |||||
* <pre> | |||||
* public void run() { | |||||
* if (System.currentTimeMillis() - scheduledExecutionTime() >= MAX_TARDINESS) | |||||
* return; // Too late; skip this execution. | |||||
* // Perform the task | |||||
* } | |||||
* </pre> | |||||
* | |||||
* This method is typically <i>not</i> used in conjunction with <i>fixed-delay execution</i> repeating tasks, as | |||||
* their scheduled execution times are allowed to drift over time, and so are not terribly significant. | |||||
* | |||||
* @return the time at which the most recent execution of this task was scheduled to occur, in the format returned | |||||
* by Date.getTime(). The return value is undefined if the task has yet to commence its first execution. | |||||
* @see Date#getTime() | |||||
*/ | |||||
abstract public long mostRecentExecutionTime(); | |||||
abstract protected long getFirstExecutionTime(); | |||||
public long getNextExecutionTime() { | |||||
return nextExecutionTime; | |||||
} | |||||
/** | |||||
* Return the time of the next execution, or -1 if there isn't one. | |||||
* | |||||
* @return | |||||
*/ | |||||
protected final long calculateNextExecutionTime() { | |||||
long next = calculateNextExecutionTimeImpl(); | |||||
// If the system time changes on the O/S (due to NTP, manual change, or some other reason) this calculation | |||||
// can cause a good amount of disturbance (either a schedule that doesn't run for a while, or one that runs | |||||
// repeatedly in order to catch up). We check here to assure that the next execution time is not entirely | |||||
// ridiculous, and adjust it if so. | |||||
long now = timer.currentTimeMillis(); | |||||
if (now - next >= MAX_TARDINESS) | |||||
next = calculateNextExecutionTimeImpl(now); | |||||
return next; | |||||
} | |||||
abstract protected long calculateNextExecutionTimeImpl(); | |||||
abstract protected long calculateNextExecutionTimeImpl(long after); | |||||
} |
@@ -0,0 +1,99 @@ | |||||
package com.serotonin.timer.sync; | |||||
import java.util.concurrent.ConcurrentLinkedQueue; | |||||
public class AsyncJobSink implements Runnable { | |||||
private final ConcurrentLinkedQueue<Event> inbox = new ConcurrentLinkedQueue<Event>(); | |||||
private Thread thread; | |||||
private volatile boolean running; | |||||
public synchronized boolean initialize() { | |||||
if (thread == null) { | |||||
running = true; | |||||
thread = new Thread(this); | |||||
thread.start(); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
public synchronized void terminate() { | |||||
if (thread != null) { | |||||
running = false; | |||||
try { | |||||
thread.join(); | |||||
} | |||||
catch (InterruptedException e) { | |||||
// no op | |||||
} | |||||
thread = null; | |||||
} | |||||
} | |||||
public boolean add(Event event) { | |||||
if (running) | |||||
return inbox.offer(event); | |||||
return false; | |||||
} | |||||
@Override | |||||
public void run() { | |||||
int processed = 0; | |||||
while (true) { | |||||
Event event = inbox.poll(); | |||||
if (event != null) { | |||||
System.out.println("Processed " + event.getId()); | |||||
processed++; | |||||
} | |||||
else if (!running) | |||||
break; | |||||
else { | |||||
System.out.println("null"); | |||||
try { | |||||
Thread.sleep(50); | |||||
} | |||||
catch (InterruptedException e) { | |||||
// no op | |||||
} | |||||
} | |||||
} | |||||
System.out.println("Exiting having processed " + processed); | |||||
} | |||||
static class Event { | |||||
static int nextId = 0; | |||||
private final String id; | |||||
public Event() { | |||||
id = Integer.toString(nextId++); | |||||
} | |||||
public String getId() { | |||||
return id; | |||||
} | |||||
} | |||||
public static void main(String[] args) throws Exception { | |||||
AsyncJobSink sink = new AsyncJobSink(); | |||||
sink.initialize(); | |||||
long start = System.currentTimeMillis(); | |||||
int failed = 0; | |||||
for (int i = 0; i < 100; i++) { | |||||
Event event = new Event(); | |||||
if (!sink.add(event)) | |||||
failed++; | |||||
} | |||||
Thread.sleep(10000); | |||||
sink.terminate(); | |||||
System.out.println("Failed to add " + failed); | |||||
System.out.println("Runtime: " + (System.currentTimeMillis() - start)); | |||||
} | |||||
} |