@@ -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)); | |||
} | |||
} |