diff --git a/smtweb-framework/core/doc/TODO.mk b/smtweb-framework/core/doc/TODO.mk new file mode 100644 index 0000000..4f5bf71 --- /dev/null +++ b/smtweb-framework/core/doc/TODO.mk @@ -0,0 +1,8 @@ +20210710 + +1. 定时器手动触发 (done) +2. 优化BeanReadUtils的TableName预先进行转换 +2. updateEntity支持悲观锁,并能抛出异常 +3. 缓存支持自定义 key +4. 序列号生成器 +5. redis配置database(0~15) (done) diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/CoreApplicationStartedListener.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/CoreApplicationStartedListener.java new file mode 100644 index 0000000..e53645a --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/CoreApplicationStartedListener.java @@ -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 { + @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============="); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/CoreAutoConfiguration.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/CoreAutoConfiguration.java new file mode 100644 index 0000000..f576544 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/CoreAutoConfiguration.java @@ -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"); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwAction.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwAction.java new file mode 100644 index 0000000..112ed75 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwAction.java @@ -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 ""; +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwCache.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwCache.java new file mode 100644 index 0000000..b6dbbd4 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwCache.java @@ -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; +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwColumnForeign.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwColumnForeign.java new file mode 100644 index 0000000..fa1ada1 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwColumnForeign.java @@ -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 ""; +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwHeaderParam.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwHeaderParam.java new file mode 100644 index 0000000..69af9d4 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwHeaderParam.java @@ -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 ""; +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwPathParam.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwPathParam.java new file mode 100644 index 0000000..c4ced93 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwPathParam.java @@ -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 ""; +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwService.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwService.java new file mode 100644 index 0000000..ba25e6c --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwService.java @@ -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 ""; +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwTable.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwTable.java new file mode 100644 index 0000000..464f322 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/annotation/SwTable.java @@ -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 ""; +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/AbstractEntityCache.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/AbstractEntityCache.java new file mode 100644 index 0000000..744b0a1 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/AbstractEntityCache.java @@ -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 extends AbstractCache { + @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 loadAll() { + EntityDao dao = DbEngine.getInstance().findDao(pTypeClass); + return dao.query(); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/SessionCacheFactory.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/SessionCacheFactory.java new file mode 100644 index 0000000..8ad6fdc --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/SessionCacheFactory.java @@ -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 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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/redis/RedisConnection.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/redis/RedisConnection.java new file mode 100644 index 0000000..25a4701 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/redis/RedisConnection.java @@ -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 redis; + + public RedisConnection(RedisCommands 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 get(String key, Class 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 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> 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> 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 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 readObject(byte[] value, Class clazz) { + return CommUtil.readObject(value, clazz); + } + + public 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 values) { + String ret = redis.hmset(getBytes(key), values); + return "OK".equals(ret); + } + + public boolean hmSet(String key, Map values) { + boolean ret = true; + if (values == null || values.isEmpty()) { + return ret; + } + final int size = 200; + Map map = new HashMap<>(size); + int count = 0; + for (Map.Entry 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 hGet(String key, String field, Class 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 get(String key, Class clazz) { + return CommUtil.readObject(get(key), clazz); + } + + public Map hGetAll(String key) { + return redis.hgetall(getBytes(key)); + } + + /** + * 模糊查询keys,影响性能,谨慎使用 + * + * @param keyLike 支持*表达模糊搜索 + * @return 查询到的列表 + */ + public List matchKeys(String keyLike) { + List list = redis.keys(getBytes(keyLike)); + if (list != null && !list.isEmpty()) { + List 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 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 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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/redis/RedisManager.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/redis/RedisManager.java new file mode 100644 index 0000000..faf0b59 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/redis/RedisManager.java @@ -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> 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 command(Function handler) { +// StatefulRedisConnection connection = redisClient.connect(ByteArrayCodec.INSTANCE); + + StatefulRedisConnection connection = null; + try { + connection = pool.borrowObject(); + } catch (Exception e) { + throw new SwException(e); + } + + try { + RedisCommands 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 boolean setnx(String key, T obj, int seconds) { + return command(redis -> redis.setnx(key, obj, seconds)); + } + + public 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 boolean hmSet(String key, Map 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 hGet(String key, String field, Class clazz) { + return command(redis -> redis.hGet(key, field, clazz)); + } + + public List 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 connection = redisClient.connectPubSub()) { + RedisPubSubCommands sync = connection.sync(); + sync.publish(channel, JsonUtil.encodeString(message)); + } + } + + public Map hGetAll(String key) { + return command(redis -> redis.hGetAll(key)); + } + + public T get(String key, Class clazz) { + return command(redis -> redis.get(key, clazz)); + } + + public List 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 readObject(byte[] value, Class clazz) { + return RedisConnection.readObject(value, clazz); + } + + @Override + public void destroy() throws Exception { + this.shutdown(); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/redis/config/RedisConfig.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/redis/config/RedisConfig.java new file mode 100644 index 0000000..83367e3 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/cache/redis/config/RedisConfig.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/common/SwEnum.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/common/SwEnum.java new file mode 100644 index 0000000..d859d04 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/common/SwEnum.java @@ -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 { + 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 { + 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 { + 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()); + } + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/cache/ModelTableCache.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/cache/ModelTableCache.java new file mode 100644 index 0000000..703e606 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/cache/ModelTableCache.java @@ -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 { + 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 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 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>() { + @Override + public List extractData(ResultSet rs) throws SQLException, DataAccessException { + List 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 getDbTables(long dbId) { + return getListByKey(md, String.valueOf(dbId)); + } + + public final Set getTablesByMc(long mcId) { + return getListByKey(mc, String.valueOf(mcId)); + } + + public final List getTablesByMc(long mcId, Comparator comparator) { + Set set = getListByKey(mc, String.valueOf(mcId)); + if (set == null || set.isEmpty()) return null; + List 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 getByLink(long tableId) { + return getListByKey(mk_link, String.valueOf(tableId)); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/dao/EntityColumn.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/dao/EntityColumn.java new file mode 100644 index 0000000..3bc20ea --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/dao/EntityColumn.java @@ -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); + } + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/impl/BaseBean.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/impl/BaseBean.java new file mode 100644 index 0000000..b4de094 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/impl/BaseBean.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/impl/UtilTime.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/impl/UtilTime.java new file mode 100644 index 0000000..7863faf --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/impl/UtilTime.java @@ -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(); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/jdbc/BaseBeanPropertyRowMapper.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/jdbc/BaseBeanPropertyRowMapper.java new file mode 100644 index 0000000..8c4607a --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/jdbc/BaseBeanPropertyRowMapper.java @@ -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 implements RowMapper { + private Class mappedClass; + + public BaseBeanPropertyRowMapper(Class 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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/jdbc/IdGenerator.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/jdbc/IdGenerator.java new file mode 100644 index 0000000..4be311a --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/jdbc/IdGenerator.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/jdbc/JdbcTrans.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/jdbc/JdbcTrans.java new file mode 100644 index 0000000..31a6c33 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/jdbc/JdbcTrans.java @@ -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(); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DeleteSqlBuilder.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DeleteSqlBuilder.java new file mode 100644 index 0000000..4fdcc78 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DeleteSqlBuilder.java @@ -0,0 +1,33 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; + +public class DeleteSqlBuilder extends AbstractUpdateSqlBuilder { + 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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DirectSelectSqlBuilder.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DirectSelectSqlBuilder.java new file mode 100644 index 0000000..6986db6 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DirectSelectSqlBuilder.java @@ -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 exec(BiFunction execute) { + StringBuilder sb = new StringBuilder(sql); + + Object[] params = makeParams(sb); + + return execute.apply(sb.toString(), params); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlBuilder.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlBuilder.java new file mode 100644 index 0000000..c4ef731 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlBuilder.java @@ -0,0 +1,84 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import java.util.ArrayList; +import java.util.List; + +/** + * SQL语句建造器 + * @author admin + * @param 建造器类型 + */ +public abstract class SqlBuilder { + protected List fields = new ArrayList<>(); + protected List 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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/UpdateSqlBuilder.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/UpdateSqlBuilder.java new file mode 100644 index 0000000..e7c638a --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/UpdateSqlBuilder.java @@ -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 { + private String tableName; + + UpdateSqlBuilder(String tableName) { + this.tableName = tableName; + } + + @Override + public int update(JdbcEngine dbEngine) { + int fieldSize = fields.size() + wheres.size(); + List 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()); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCache.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCache.java new file mode 100644 index 0000000..edd4d1b --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCache.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCatalog.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCatalog.java new file mode 100644 index 0000000..b46e984 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCatalog.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelField.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelField.java new file mode 100644 index 0000000..0263245 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelField.java @@ -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; + } + +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelProject.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelProject.java new file mode 100644 index 0000000..33925e8 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelProject.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelTable.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelTable.java new file mode 100644 index 0000000..8ffeea1 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/db/vo/ModelTable.java @@ -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 fields = new ArrayList<>(); + private List indexes = new ArrayList<>(); + private List 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 getFields() { + return fields; + } + + public List getIndexes() { + return indexes; + } + + public List 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 findLinkeNames() { + List 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(); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/exception/BindBeanException.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/exception/BindBeanException.java new file mode 100644 index 0000000..0168e3a --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/exception/BindBeanException.java @@ -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 + } + +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/exception/DbException.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/exception/DbException.java new file mode 100644 index 0000000..5ca0635 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/exception/DbException.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/exception/JsonParseException.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/exception/JsonParseException.java new file mode 100644 index 0000000..22fc5bf --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/exception/JsonParseException.java @@ -0,0 +1,7 @@ +package cc.smtweb.framework.core.exception; + +public class JsonParseException extends SwException { + public JsonParseException(String s, Exception e) { + super(s, e); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/SchedulerManager.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/SchedulerManager.java new file mode 100644 index 0000000..5baf5aa --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/SchedulerManager.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/config/ControllerConfig.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/config/ControllerConfig.java new file mode 100644 index 0000000..a36f7bd --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/config/ControllerConfig.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/config/SettingsEnvironmentPostProcessor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/config/SettingsEnvironmentPostProcessor.java new file mode 100644 index 0000000..e31d3a9 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/config/SettingsEnvironmentPostProcessor.java @@ -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 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 getProperties() { + List 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; + } + +} \ No newline at end of file diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/ApiController.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/ApiController.java new file mode 100644 index 0000000..4ee218c --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/ApiController.java @@ -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 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 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(); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/IActionManager.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/IActionManager.java new file mode 100644 index 0000000..63ca1ca --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/IActionManager.java @@ -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); +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/IBeanContext.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/IBeanContext.java new file mode 100644 index 0000000..9746ee7 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/IBeanContext.java @@ -0,0 +1,8 @@ +package cc.smtweb.framework.core.mvc.controller; + +import org.springframework.beans.BeansException; + +public interface IBeanContext { + T getBean(String name, Class type) throws BeansException; + T getBean(Class type) throws BeansException; +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/IEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/IEditor.java new file mode 100644 index 0000000..8eff0d7 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/IEditor.java @@ -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 params, String body, HttpServletRequest request); +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/MethodAccessManager.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/MethodAccessManager.java new file mode 100644 index 0000000..67e99d1 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/MethodAccessManager.java @@ -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 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 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 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); + } + } + } + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/BindFieldAccess.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/BindFieldAccess.java new file mode 100644 index 0000000..482b0df --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/BindFieldAccess.java @@ -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 params) { + if (editor != null) { + return editor.getParamValue(beanName, this.field.getType(), context, params, null, null); + } + + return null; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/FieldAccess.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/FieldAccess.java new file mode 100644 index 0000000..392ec6e --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/FieldAccess.java @@ -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 params); + + public boolean writeValue(Object instance, IBeanContext context, Map 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(); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/IBeanAccess.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/IBeanAccess.java new file mode 100644 index 0000000..17c8fc3 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/IBeanAccess.java @@ -0,0 +1,5 @@ +package cc.smtweb.framework.core.mvc.controller.access; + +public interface IBeanAccess { + Object getSingletonInstance(); +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/MethodParamAccess.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/MethodParamAccess.java new file mode 100644 index 0000000..800c24a --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/MethodParamAccess.java @@ -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 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(); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SchedulerMethodAccess.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SchedulerMethodAccess.java new file mode 100644 index 0000000..6104a6a --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SchedulerMethodAccess.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SingletonFieldAccess.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SingletonFieldAccess.java new file mode 100644 index 0000000..88a5d7f --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SingletonFieldAccess.java @@ -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 params) { + return fieldValue; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/CacheEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/CacheEditor.java new file mode 100644 index 0000000..cc67d88 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/CacheEditor.java @@ -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 params, String body, HttpServletRequest request) { + if (StringUtils.isNotBlank(paramName)) { + return cacheManager.getCache(paramName); + } else { + return cacheManager.getCache(paramType.getSimpleName()); + } + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/WebDataBinder.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/WebDataBinder.java new file mode 100644 index 0000000..d11759e --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/WebDataBinder.java @@ -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 void addParamEditor(Class 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); + } + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/AbstractAttrEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/AbstractAttrEditor.java new file mode 100644 index 0000000..e28d213 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/AbstractAttrEditor.java @@ -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 params, String body, HttpServletRequest request) { + return getValue(paramName, paramType, request); + } + + public abstract Object getValue(String paramName, Class paramType, HttpServletRequest request); +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/BeanAttrEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/BeanAttrEditor.java new file mode 100644 index 0000000..fc6058f --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/BeanAttrEditor.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/UserSessionEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/UserSessionEditor.java new file mode 100644 index 0000000..3478ebb --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/UserSessionEditor.java @@ -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(); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanBodyEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanBodyEditor.java new file mode 100644 index 0000000..e950924 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanBodyEditor.java @@ -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 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 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()); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanUtil.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanUtil.java new file mode 100644 index 0000000..641492a --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanUtil.java @@ -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> 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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/StringBodyEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/StringBodyEditor.java new file mode 100644 index 0000000..9de8a32 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/StringBodyEditor.java @@ -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 params, String body, HttpServletRequest request) { + return body; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/BeanTypeEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/BeanTypeEditor.java new file mode 100644 index 0000000..b96da02 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/BeanTypeEditor.java @@ -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 params, String body, HttpServletRequest request) { + return params.get(paramType.getName()); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/DateEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/DateEditor.java new file mode 100644 index 0000000..8f49da3 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/DateEditor.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/FloatEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/FloatEditor.java new file mode 100644 index 0000000..c6cfe12 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/FloatEditor.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/IntegerEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/IntegerEditor.java new file mode 100644 index 0000000..e3666e2 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/IntegerEditor.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/ShortEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/ShortEditor.java new file mode 100644 index 0000000..5587c17 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/ShortEditor.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/StringEditor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/StringEditor.java new file mode 100644 index 0000000..c6f7e69 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/StringEditor.java @@ -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(); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ApplicationScanner.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ApplicationScanner.java new file mode 100644 index 0000000..34ac3e3 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ApplicationScanner.java @@ -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 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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/UrlMaker.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/UrlMaker.java new file mode 100644 index 0000000..7aa6aa9 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/UrlMaker.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/ForbiddenException.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/ForbiddenException.java new file mode 100644 index 0000000..2aa0cdd --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package cc.smtweb.framework.core.mvc.realm.exception; + +public class ForbiddenException extends AuthorizationException { + public ForbiddenException(String s) { + super(s); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/UnknownAccountException.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/UnknownAccountException.java new file mode 100644 index 0000000..3ac1681 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/UnknownAccountException.java @@ -0,0 +1,7 @@ +package cc.smtweb.framework.core.mvc.realm.exception; + +public class UnknownAccountException extends AuthorizationException { + public UnknownAccountException(String s) { + super(s); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/PermInterceptor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/PermInterceptor.java new file mode 100644 index 0000000..dcf9184 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/PermInterceptor.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJobExecutor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJobExecutor.java new file mode 100644 index 0000000..57a0fe4 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJobExecutor.java @@ -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 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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/CronTimerTask.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/CronTimerTask.java new file mode 100644 index 0000000..4184df9 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/CronTimerTask.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/GroupJob.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/GroupJob.java new file mode 100644 index 0000000..0fc49c5 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/GroupJob.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/LocalJobExecutor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/LocalJobExecutor.java new file mode 100644 index 0000000..d085307 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/LocalJobExecutor.java @@ -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) { + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/RedisJobExecutor.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/RedisJobExecutor.java new file mode 100644 index 0000000..643d67a --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/RedisJobExecutor.java @@ -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(); + } + } + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/AbstractCompService.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/AbstractCompService.java new file mode 100644 index 0000000..1352112 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/AbstractCompService.java @@ -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)handler).data()); + } + + //树过滤 + public R treeFilter(@SwBody SwMap params, UserSession us) { + return pageHandler(params, us, TYPE_TREE, handler -> ((AbstractTreeHandler)handler).filter()); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/AbstractSaveHandler.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/AbstractSaveHandler.java new file mode 100644 index 0000000..26b4b16 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/AbstractSaveHandler.java @@ -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 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(); +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/AbstractTreeHandler.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/AbstractTreeHandler.java new file mode 100644 index 0000000..0bd7382 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/AbstractTreeHandler.java @@ -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 extends AbstractHandler { + //树过滤 + public R filter() { + List rows = filterData(); + List listRet = buildNodes(rows, true); + return R.success(listRet); + } + + public R data() { + List rows = getChildren(params.readLong("parent_id")); + List listRet = buildNodes(rows, params.readBool("lazy")); + + return R.success(listRet); + } + + //搜索 + protected abstract List filterData(); + //获取指定节点的下级节点 + protected abstract List getChildren(long id); + protected List getChildren(T bean) { + return getChildren(getId(bean)); + } + + //根据bean,构建treenode + private List buildNodes(List rows, boolean lazy) { + List 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 children = getChildren(row); + node.put("leaf", children == null || children.isEmpty()); + node.put("bean", row); + buildNode(node, row); + listRet.add(node); + if (!lazy) { + List 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); +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/DefaultDelHandler.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/DefaultDelHandler.java new file mode 100644 index 0000000..de73fc3 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/DefaultDelHandler.java @@ -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 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 cache = CacheManager.getIntance().getCache(tableName); + cache.remove(id); + } + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/DefaultProvider.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/DefaultProvider.java new file mode 100644 index 0000000..3f15f88 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/mvc/service/DefaultProvider.java @@ -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 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 bdao = (EntityDao) 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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/session/UserSession.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/session/UserSession.java new file mode 100644 index 0000000..492c6cf --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/session/UserSession.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/systask/ISysTask.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/systask/ISysTask.java new file mode 100644 index 0000000..cb5d8ec --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/systask/ISysTask.java @@ -0,0 +1,8 @@ +package cc.smtweb.framework.core.systask; + +/** + * 任务接口 + */ +public interface ISysTask { + int run(); +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/systask/TaskStartEvent.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/systask/TaskStartEvent.java new file mode 100644 index 0000000..35b8bf0 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/systask/TaskStartEvent.java @@ -0,0 +1,4 @@ +package cc.smtweb.framework.core.systask; + +public class TaskStartEvent { +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/DateUtil.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/DateUtil.java new file mode 100644 index 0000000..6c55dd3 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/DateUtil.java @@ -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 stdTimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm:ss")); + private static ThreadLocal stdDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); + private static ThreadLocal stdDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); +// private static ThreadLocal stdLongDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")); + + private static ThreadLocal simpleWeekFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("EEEE")); + private static ThreadLocal simpleTimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("HHmmss")); + private static ThreadLocal simpleDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd")); + private static ThreadLocal simpleDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmss")); + private static ThreadLocal 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()); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/FileUtil.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/FileUtil.java new file mode 100644 index 0000000..fecc188 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/FileUtil.java @@ -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; + } + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/MapUtil.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/MapUtil.java new file mode 100644 index 0000000..9b38819 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/MapUtil.java @@ -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 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 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 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 readLongSet(SwMap swMap, String name) { + Object value = swMap.get(name); + if (value != null) { + String[] ary = value.toString().split(","); + if (ary.length > 0) { + Set 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> readListMap(Map map, String name) { + Object v = map.get(name); + if (v == null) return null; + return (List>)v; + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/SqlUtil.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/SqlUtil.java new file mode 100644 index 0000000..f67d777 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/SqlUtil.java @@ -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; + } + + +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/jackson/DateSerializer.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/jackson/DateSerializer.java new file mode 100644 index 0000000..c0e3961 --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/jackson/DateSerializer.java @@ -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 { + + @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())); + } +} diff --git a/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/jackson/TimeSerializer.java b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/jackson/TimeSerializer.java new file mode 100644 index 0000000..ffc457b --- /dev/null +++ b/smtweb-framework/core/src/main/java/cc/smtweb/framework/core/util/jackson/TimeSerializer.java @@ -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 { + + @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()); + } + +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/AbstractTimer.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/AbstractTimer.java new file mode 100644 index 0000000..c2713d0 --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/AbstractTimer.java @@ -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 cancel(); + + abstract public int purge(); + + abstract public int size(); + + abstract public List getTasks(); +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/CronTimerTrigger.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/CronTimerTrigger.java new file mode 100644 index 0000000..3c55a1b --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/CronTimerTrigger.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/ExecutionRejectedException.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/ExecutionRejectedException.java new file mode 100644 index 0000000..abf9455 --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/ExecutionRejectedException.java @@ -0,0 +1,12 @@ +package com.serotonin.timer; + +public class ExecutionRejectedException extends Exception { + /** + * + */ + private static final long serialVersionUID = 1L; + + public ExecutionRejectedException() { + super(); + } +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/FixedDelayTrigger.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/FixedDelayTrigger.java new file mode 100644 index 0000000..9f7e141 --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/FixedDelayTrigger.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/NamedRunnable.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/NamedRunnable.java new file mode 100644 index 0000000..6d4b116 --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/NamedRunnable.java @@ -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); + } + } +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/ScheduledNamedRunnable.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/ScheduledNamedRunnable.java new file mode 100644 index 0000000..0f75b9c --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/ScheduledNamedRunnable.java @@ -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); + } + } +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/ScheduledRunnable.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/ScheduledRunnable.java new file mode 100644 index 0000000..e16ef8c --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/ScheduledRunnable.java @@ -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); +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/SimulationTimeSource.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/SimulationTimeSource.java new file mode 100644 index 0000000..a2b1e69 --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/SimulationTimeSource.java @@ -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; + } +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/SimulationTimer.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/SimulationTimer.java new file mode 100644 index 0000000..542691c --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/SimulationTimer.java @@ -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 queue = new ArrayList(); + 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() { + @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 cancel() { + cancelled = true; + List 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 getTasks() { + return new ArrayList(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); + // } + // } +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/TimeoutTask.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/TimeoutTask.java new file mode 100644 index 0000000..eb6d679 --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/TimeoutTask.java @@ -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); + } +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/TimerThread.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/TimerThread.java new file mode 100644 index 0000000..5c52599 --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/TimerThread.java @@ -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 + } + } + } +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/TimerTrigger.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/TimerTrigger.java new file mode 100644 index 0000000..be2e734 --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/TimerTrigger.java @@ -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 scheduled execution time of the most recent actual 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.) + * + *

+ * 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: + * + *

+     * public void run() {
+     *     if (System.currentTimeMillis() - scheduledExecutionTime() >= MAX_TARDINESS)
+     *         return; // Too late; skip this execution.
+     *     // Perform the task
+     * }
+     * 
+ * + * This method is typically not used in conjunction with fixed-delay execution 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); +} diff --git a/smtweb-framework/core/src/main/java/com/serotonin/timer/sync/AsyncJobSink.java b/smtweb-framework/core/src/main/java/com/serotonin/timer/sync/AsyncJobSink.java new file mode 100644 index 0000000..b506920 --- /dev/null +++ b/smtweb-framework/core/src/main/java/com/serotonin/timer/sync/AsyncJobSink.java @@ -0,0 +1,99 @@ +package com.serotonin.timer.sync; + +import java.util.concurrent.ConcurrentLinkedQueue; + +public class AsyncJobSink implements Runnable { + private final ConcurrentLinkedQueue inbox = new ConcurrentLinkedQueue(); + + 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)); + } +} diff --git a/smtweb-framework/core/src/main/resources/config/application.yaml b/smtweb-framework/core/src/main/resources/config/application.yaml new file mode 100644 index 0000000..caf4dfc --- /dev/null +++ b/smtweb-framework/core/src/main/resources/config/application.yaml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev \ No newline at end of file diff --git a/smtweb-framework/core/src/main/resources/demo.json b/smtweb-framework/core/src/main/resources/demo.json new file mode 100644 index 0000000..2cff18c --- /dev/null +++ b/smtweb-framework/core/src/main/resources/demo.json @@ -0,0 +1,237 @@ +{ + "form": [ + { + "page": { + "id": "p721060295212011520", + "type": "fx-page", + "props": { + "title": "区划列表", + "key": "721060295216205824" + } + }, + "graph": { + "shape": "panel", + "id": "root_panel", + "type": "fx-split-panel", + "props": { + "horizontal": false, + "shadow": "never" + }, + "children": [ + { + "id": "721060295295897600", + "type": "fx-form-panel", + "shape": "panel", + "props": { + "paddingY": 5, + "paddingX": 10, + "size": "35", + "colNum": 2 + }, + "children": [ + { + "id": "id721060295295897601", + "type": "fx-", + "props": { + "label": "名称", + "type": "text", + "maxlength": 100, + "placeholder": "请输入查询内容", + "labelWidth": 100, + "dataset": "ds_1815c554ef8", + "field": "id_1815c554f1b", + "name": "ar_code" + }, + "events": {} + }, + { + "id": "id721060295321063424", + "type": "fx-button-group", + "props": { + "menus": [] + }, + "slots": { + "default": [ + { + "type": "fx-button", + "props": { + "label": "查询", + "leftIcon": "history-query", + "type": "primary", + "action": "button:search", + "dataset": "ds_1815c554ef8" + }, + "id": "id721060295321063425" + }, + { + "type": "fx-button", + "props": { + "label": "重置", + "type": "danger", + "action": "button:reset", + "leftIcon": "figma-reset-instance" + }, + "id": "id721060295321063426" + }, + { + "id": "id721060295321063427", + "type": "fx-button", + "props": { + "label": "新增", + "type": "success", + "leftIcon": "shield-add", + "action": "button:add", + "dataset": "ds_1815c554ef8" + } + } + ] + } + } + ] + },{ + "shape": "panel", + "id": "form_panel", + "type": "fx-form-panel", + "props": { + "colNum": 0, + "paddingX": 5, + "paddingY": 5, + "align": "full" + }, + "children": [ + { + "id": "id721060295329452032", + "type": "fx-table", + "props": { + "label": "表格", + "border": true, + "stripe": true, + "showHeader": true, + "fit": true, + "dataset": "ds_1815c554ef8", + "actionWidth": 120 + }, + "slots": { + "default": [ + { + "id": "id721060295329452033", + "type": "fx-table-column", + "props": { + "field": "ar_code", + "label": "编码" + } + }, + { + "id": "id721060295337840640", + "type": "fx-table-column", + "props": { + "field": "ar_name", + "label": "名称" + } + }, + { + "id": "id721060295337840641", + "type": "fx-table-column", + "props": { + "field": "ar_full_name", + "label": "全称" + } + }, + { + "id": "id721060295337840642", + "type": "fx-table-column", + "props": { + "field": "ar_seq", + "label": "排序码" + } + }, + { + "id": "id721060295337840643", + "type": "fx-table-column", + "props": { + "field": "ar_parent_id", + "label": "父ID" + } + }, + { + "id": "id721060295337840644", + "type": "fx-table-column", + "props": { + "field": "ar_remark", + "label": "备注" + } + } + ], + "button": [ + { + "type": "fx-button", + "props": { + "label": "编", + "type": "text", + "leftIcon": "edit", + "action": "button:edit", + "linkType": "dialog", + "dataset": "ds_1815c554ef8" + }, + "id": "id1813718bf36" + }, + { + "type": "fx-button", + "props": { + "label": "删", + "type": "text", + "leftIcon": "delete", + "action": "button:del", + "dataset": "ds_1815c554ef8" + }, + "id": "id721060295337840645" + } + ] + }, + "events": {} + } + ] + } + ] + } + } + ], + "model": [ + { + "dataset": "ds_1815c554ef8", + "label": "主数据集", + "fields": [ + { + "id": "id_1815c554efc", + "field": "ar_code" }, + { + "id": "id_1815c554efd", + "field": "ar_name" }, + { + "id": "id_1815c554eff", + "field": "ar_full_name" }, + { + "id": "id_1815c554f01", + "field": "ar_seq" }, + { + "id": "id_1815c554efe", + "field": "ar_parent_id" }, + { + "id": "id_1815c554f00", + "field": "ar_remark" } + ], + "filters": [ + { + "id": "id_1815c554f1b", + "field": "ar_name", + "required": false, + "type": "input" + } + ] + } + ], + "option": { + "widgetRef": [], + "vars": [] + } +} \ No newline at end of file diff --git a/smtweb-framework/core/src/main/resources/流程定义.json b/smtweb-framework/core/src/main/resources/流程定义.json new file mode 100644 index 0000000..4c359e2 --- /dev/null +++ b/smtweb-framework/core/src/main/resources/流程定义.json @@ -0,0 +1,84 @@ +{ + "form": [ + { + "page": { + "id": "id18117e2fce8", + "type": "fx-page", + "props": { + "title": "项目", + "key": "716232842824126464" + } + }, + "graph": { + "shape": "panel", + "id": "root_panel", + "type": "fx-split-panel", + "props": { + "horizontal": true, + "shadow": "never" + }, + "children": [ + { + "shape": "panel", + "id": "form_panel", + "type": "fx-form-panel", + "props": { + "colNum": 3, + "paddingX": 5, + "paddingY": 5 + }, + "children": [ + { + "id": "id18117e2fcf0", + "type": "fx-text", + "props": { + "label": "名称", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 100, + "dataset": "ds_18117e2fce9", + "field": "id_18117e2fced", + "name": "prj_name" + }, + "events": {} + } + ] + } + ] + } + } + ], + "model": [ + { + "dataset": "ds_18117e2fce9", + "label": "主数据集", + "fields": [ + { + "id": "id_18117e2fceb", + "field": "prj_id" + }, + { + "id": "id_18117e2fcec", + "field": "prj_depends" + }, + { + "id": "id_18117e2fced", + "field": "prj_name" + }, + { + "id": "id_18117e2fcee", + "field": "prj_module" + }, + { + "id": "id_18117e2fcef", + "field": "prj_desc" + } + ] + } + ], + "option": { + "widgetRef": [], + "vars": [] + } +} \ No newline at end of file diff --git a/smtweb-framework/core/src/test/java/cc/smtweb/framework/test/JsonTest.java b/smtweb-framework/core/src/test/java/cc/smtweb/framework/test/JsonTest.java new file mode 100644 index 0000000..b2de949 --- /dev/null +++ b/smtweb-framework/core/src/test/java/cc/smtweb/framework/test/JsonTest.java @@ -0,0 +1,33 @@ +package cc.smtweb.framework.test; + +import cc.smtweb.framework.core.util.JsonUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.omg.PortableInterceptor.SYSTEM_EXCEPTION; + +import java.util.ArrayList; +import java.util.List; + +public class JsonTest { + @Test + public void testList() throws JsonProcessingException { + { + List list = JsonUtil.OBJECT_MAPPER.readValue("[{ \"value\": 1 }, { \"value\": 2 }, { \"value\": 3 }]", ArrayList.class); + + System.out.println(list.get(0).getClass()); + } + + { + List list = JsonUtil.parseList("[{ \"value\": 1 }, { \"value\": 2 }, { \"value\": 3 }]", JsonItem.class); + + System.out.println(list.get(0).getClass()); + } + } +} + +@Data +class JsonItem { + private Integer value; +} diff --git a/smtweb-framework/core/src/test/java/cc/smtweb/framework/test/TestMain.java b/smtweb-framework/core/src/test/java/cc/smtweb/framework/test/TestMain.java new file mode 100644 index 0000000..748ed58 --- /dev/null +++ b/smtweb-framework/core/src/test/java/cc/smtweb/framework/test/TestMain.java @@ -0,0 +1,23 @@ +package cc.smtweb.framework.test; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.impl.DefaultEntity; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.framework.core.util.JsonUtil; + +import java.util.List; +import java.util.Map; + +/** + * Created by Akmm at 2021/12/25 22:21 + */ +public class TestMain { + + public static void main(String[] args) { + List list = DbEngine.getInstance().query("select * from asp_model_project", DefaultEntity.class); + System.out.println(list.size()); + /*ModelTable table = new ModelTable(); + table.setId(1); + table.setContent(s);*/ + } +}