diff --git a/build.gradle b/build.gradle index 8e5492c..4355845 100644 --- a/build.gradle +++ b/build.gradle @@ -21,14 +21,14 @@ allprojects { subprojects { ext { - version '0.6.1' + version '0.13.1' spring_version = "2.3.0.RELEASE" } buildscript { repositories { mavenLocal() - maven { url 'http://maven.aliyuMongoRepositoryn.com/nexus/content/groups/public/' } + maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } mavenCentral() } } diff --git a/src/gaea.app/build.gradle b/src/gaea.app/build.gradle index fce0f14..9333a61 100644 --- a/src/gaea.app/build.gradle +++ b/src/gaea.app/build.gradle @@ -1,9 +1,22 @@ +buildscript { + dependencies { + classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version") + } +} + +apply plugin: 'kotlin-spring' + dependencies { compile project(":src:gaea") compile("org.springframework.boot:spring-boot-starter-web:$spring_version") + compile("org.springframework.boot:spring-boot-starter-aop:$spring_version") compile("org.springframework.boot:spring-boot-starter-mail:$spring_version") + compile("org.springframework.boot:spring-boot-starter-security:$spring_version") compile group: 'net.sf.dozer', name: 'dozer', version: '5.5.1' - compile group: 'org.apache.poi', name: 'poi', version: '4.1.2' + compile group: 'org.apache.poi', name: 'poi-ooxml', version: '5.0.0' + compile group: 'com.google.code.gson', name: 'gson', version: '2.8.6' + compile group: 'com.google.guava', name: 'guava', version: '30.1.1-jre' + compile group: 'com.auth0', name: 'java-jwt', version: '3.14.0' } publishing { diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/Application.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/Application.kt index 329a7df..fdcbd6d 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/Application.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/Application.kt @@ -20,8 +20,8 @@ import javax.annotation.Resource open class Application( override var name: String, override var clazz: Class, - override var service: IService?, - override var query: IQuery?, + override var service: IService, + override var query: IQuery, override var logger: ILogger? ) : ICommandApp, IQueryApp { diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/IApplication.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/IApplication.kt index 767ffa9..56bf9fa 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/IApplication.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/IApplication.kt @@ -1,8 +1,10 @@ package com.synebula.gaea.app -import com.synebula.gaea.app.component.HttpMessage +import com.google.gson.Gson +import com.synebula.gaea.app.struct.HttpMessage import com.synebula.gaea.data.message.Status import com.synebula.gaea.log.ILogger +import org.springframework.security.core.context.SecurityContextHolder interface IApplication { @@ -21,7 +23,7 @@ interface IApplication { * 安全执行 */ fun safeExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage { - val msg = HttpMessage(Status.Success) + val msg = HttpMessage() try { process(msg) logger?.debug(this, "$name business execute success") @@ -37,7 +39,7 @@ interface IApplication { * 可抛出自定义异常信息的安全controller实现了异常捕获和消息组成。 */ fun throwExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage { - val msg = HttpMessage(Status.Success) + val msg = HttpMessage() try { process(msg) logger?.debug(this, "$name business execute success") @@ -47,4 +49,23 @@ interface IApplication { } return msg } + + /** + * 获取用户信息 + * @param clazz 用户信息结构类 + */ + fun sessionUser(clazz: Class): T? { + try { + val authentication = SecurityContextHolder.getContext().authentication.principal.toString() + try { + val gson = Gson() + return gson.fromJson(authentication, clazz) + } catch (ex: Exception) { + logger?.error(this, ex, "[$name]解析用户信息异常!用户信息:$authentication: ${ex.message}") + } + } catch (ex: Exception) { + logger?.error(this, ex, "[$name]获取用户信息异常!${ex.message}") + } + return null + } } \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/LazyApplication.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/LazyApplication.kt new file mode 100644 index 0000000..628a9e9 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/LazyApplication.kt @@ -0,0 +1,30 @@ +package com.synebula.gaea.app + +import com.synebula.gaea.app.cmd.ILazyCommandApp +import com.synebula.gaea.app.query.IQueryApp +import com.synebula.gaea.data.serialization.json.IJsonSerializer +import com.synebula.gaea.domain.model.IAggregateRoot +import com.synebula.gaea.domain.service.ILazyService +import com.synebula.gaea.log.ILogger +import com.synebula.gaea.query.IQuery +import javax.annotation.Resource + +/** + * 联合服务,同时实现了ILazyCommandApp和IQueryApp接口 + * + * @param name 业务名称 + * @param service 业务domain服务 + * @param query 业务查询服务 + * @param logger 日志组件 + */ +open class LazyApplication, TKey>( + override var name: String, + override var clazz: Class, //view class type + override var service: ILazyService, + override var query: IQuery, + override var logger: ILogger? +) : ILazyCommandApp, IQueryApp { + + @Resource + override var jsonSerializer: IJsonSerializer? = null +} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/CommandApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/CommandApp.kt index 94c82c7..78860e9 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/CommandApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/CommandApp.kt @@ -14,9 +14,10 @@ import javax.annotation.Resource * @param logger 日志组件 */ open class CommandApp( - override var name: String, - override var service: IService?, - override var logger: ILogger?) : ICommandApp { + override var name: String, + override var service: IService, + override var logger: ILogger? +) : ICommandApp { @Resource override var jsonSerializer: IJsonSerializer? = null } \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ICommandApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ICommandApp.kt index d888115..9976caf 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ICommandApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ICommandApp.kt @@ -1,7 +1,8 @@ package com.synebula.gaea.app.cmd import com.synebula.gaea.app.IApplication -import com.synebula.gaea.app.component.HttpMessage +import com.synebula.gaea.app.component.aop.annotation.MethodName +import com.synebula.gaea.app.struct.HttpMessage import com.synebula.gaea.data.message.Status import com.synebula.gaea.data.serialization.json.IJsonSerializer import com.synebula.gaea.domain.service.ICommand @@ -18,42 +19,32 @@ import org.springframework.web.bind.annotation.* interface ICommandApp : IApplication { var jsonSerializer: IJsonSerializer? - var service: IService? + var service: IService @PostMapping + @MethodName("添加") fun add(@RequestBody command: TCommand): HttpMessage { - return this.safeExecute("添加${this.name}数据失败 - ${if (jsonSerializer != null) jsonSerializer?.serialize(command) else ""}") { - if (this.service != null) { - val msg = this.service!!.add(command) - it.load(msg) - } else { - it.status = Status.Error - it.message = "没有对应服务,无法执行该操作" - } - } - } - - @DeleteMapping("/{id:.+}") - fun remove(@PathVariable id: TKey): HttpMessage { - return this.safeExecute("删除${this.name}[id: $id]失败") { - if (this.service != null) - it.data = this.service!!.remove(id) - else { - it.status = Status.Error - it.message = "没有对应服务,无法执行该操作" - } - } + return HttpMessage(service.add(command)) } @PutMapping("/{id:.+}") + @MethodName("更新") fun update(@PathVariable id: TKey, @RequestBody command: TCommand): HttpMessage { - return this.safeExecute("更新${this.name}失败 - ${if (jsonSerializer != null) jsonSerializer?.serialize(command) else ""}") { - if (this.service != null) - this.service!!.update(id, command) - else { - it.status = Status.Error - it.message = "没有对应服务,无法执行该操作" - } + this.service.update(id, command) + return HttpMessage() + } + + @DeleteMapping("/{id:.+}") + @MethodName("删除") + fun remove(@PathVariable id: TKey): HttpMessage { + val msg = HttpMessage() + try { + msg.data = this.service.remove(id) + } catch (ex: IllegalStateException) { + msg.status = Status.Error + msg.message = ex.message ?: "" } + + return msg } } diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ILazyCommandApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ILazyCommandApp.kt new file mode 100644 index 0000000..b3e1aec --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ILazyCommandApp.kt @@ -0,0 +1,50 @@ +package com.synebula.gaea.app.cmd + +import com.synebula.gaea.app.IApplication +import com.synebula.gaea.app.component.aop.annotation.MethodName +import com.synebula.gaea.app.struct.HttpMessage +import com.synebula.gaea.data.message.Status +import com.synebula.gaea.data.serialization.json.IJsonSerializer +import com.synebula.gaea.domain.model.IAggregateRoot +import com.synebula.gaea.domain.service.ILazyService +import org.springframework.web.bind.annotation.* + +/** + * 直接使用实体对象提供服务的api + * + * @author alex + * @version 0.1 + * @since 2020-05-15 + */ +interface ILazyCommandApp, TKey> : IApplication { + var jsonSerializer: IJsonSerializer? + + var service: ILazyService + + @PostMapping + @MethodName("添加") + fun add(@RequestBody entity: TRoot): HttpMessage { + return HttpMessage(service.add(entity)) + } + + @PutMapping("/{id:.+}") + @MethodName("更新") + fun update(@PathVariable id: TKey, @RequestBody entity: TRoot): HttpMessage { + this.service.update(id, entity) + return HttpMessage() + } + + @DeleteMapping("/{id:.+}") + @MethodName("删除") + fun remove(@PathVariable id: TKey): HttpMessage { + val msg = HttpMessage() + try { + msg.data = this.service.remove(id) + } catch (ex: IllegalStateException) { + msg.status = Status.Error + msg.message = ex.message ?: "" + } + + return msg + } +} diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/LazyCommandApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/LazyCommandApp.kt new file mode 100644 index 0000000..46abef3 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/LazyCommandApp.kt @@ -0,0 +1,25 @@ +package com.synebula.gaea.app.cmd + +import com.synebula.gaea.data.serialization.json.IJsonSerializer +import com.synebula.gaea.domain.model.IAggregateRoot +import com.synebula.gaea.domain.service.ICommand +import com.synebula.gaea.domain.service.ILazyService +import com.synebula.gaea.domain.service.IService +import com.synebula.gaea.log.ILogger +import javax.annotation.Resource + +/** + * 指令服务,同时实现ICommandApp + * + * @param name 业务名称 + * @param service 业务domain服务 + * @param logger 日志组件 + */ +open class LazyCommandApp, TKey>( + override var name: String, + override var service: ILazyService, + override var logger: ILogger? +) : ILazyCommandApp { + @Resource + override var jsonSerializer: IJsonSerializer? = null +} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EmailMessenger.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EmailMessenger.kt index 28a5dca..8bfbe91 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EmailMessenger.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EmailMessenger.kt @@ -11,24 +11,21 @@ import org.springframework.stereotype.Component import java.io.File @Component -class EmailMessenger : IEmailMessenger { +class EmailMessenger(var mailSender: JavaMailSender?) : IEmailMessenger { - @Autowired - private lateinit var mailSender: JavaMailSender - - @Value("\${spring.mail.host}") + @Value("\${spring.mail.host:}") var host = "" - @Value("\${spring.mail.port}") + @Value("\${spring.mail.port:}") var port = "" - @Value("\${spring.mail.sender}") + @Value("\${spring.mail.sender:}") var sender = "" - @Value("\${spring.mail.username}") + @Value("\${spring.mail.username:}") var username = "" - @Value("\${spring.mail.password}") + @Value("\${spring.mail.password:}") var password = "" @Autowired @@ -42,13 +39,20 @@ class EmailMessenger : IEmailMessenger { * @param receivers 邮件接受者 * @param files 附件 */ - override fun sendMessage(subject: String, content: String, receivers: List, files: Map): Map { + override fun sendMessage( + subject: String, + content: String, + receivers: List, + files: Map + ): Map { val result = mutableMapOf() this.check() receivers.forEach { receiver -> result[receiver] = true + if (this.mailSender == null) + throw Exception("没有配置JavaMailSender的实现实例,请重新配置") try { - val mail = mailSender.createMimeMessage() + val mail = this.mailSender!!.createMimeMessage() val mimeMessageHelper = MimeMessageHelper(mail, true, "utf-8") mimeMessageHelper.setFrom(sender) mimeMessageHelper.setTo(receiver) @@ -59,7 +63,7 @@ class EmailMessenger : IEmailMessenger { val file = FileSystemResource(File(path)) mimeMessageHelper.addAttachment(name, file) } - mailSender.send(mail) //发送 + this.mailSender!!.send(mail) //发送 } catch (e: Exception) { logger.error(e, "发送邮件[$subject]至地址[$receiver]失败") result[receiver] = false diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBus.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBus.kt new file mode 100644 index 0000000..8448054 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBus.kt @@ -0,0 +1,42 @@ +package com.synebula.gaea.app.component + +import com.google.common.eventbus.AsyncEventBus +import com.google.common.eventbus.EventBus +import com.synebula.gaea.event.IEvent +import com.synebula.gaea.event.IEventBus +import org.springframework.stereotype.Component +import java.util.concurrent.Executors + +@Component +class EventBus : IEventBus { + + /** + * 同步事件总线 + */ + var eventBus = EventBus() + + /** + * 异步事件总线 + */ + var asyncEventBus = AsyncEventBus(Executors.newFixedThreadPool(2)) + + override fun register(obj: Any) { + eventBus.register(obj) + asyncEventBus.register(obj) + } + + override fun unregister(obj: Any) { + eventBus.unregister(obj) + asyncEventBus.unregister(obj) + } + + override fun publish(event: IEvent) { + eventBus.post(event) + } + + override fun publishAsync(event: IEvent) { + asyncEventBus.post(event) + } + +} + diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBusSubscriberProcessor.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBusSubscriberProcessor.kt new file mode 100644 index 0000000..98226c4 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBusSubscriberProcessor.kt @@ -0,0 +1,45 @@ +package com.synebula.gaea.app.component + +import com.google.common.eventbus.Subscribe +import com.synebula.gaea.event.IEventBus +import org.springframework.beans.BeansException +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.config.BeanPostProcessor +import org.springframework.stereotype.Component +import java.lang.reflect.Method + + +@Component +class EventBusSubscriberProcessor : BeanPostProcessor { + + // 事件总线bean由Spring IoC容器负责创建,这里只需要通过@Autowired注解注入该bean即可使用事件总线 + @Autowired + var eventBus: IEventBus? = null + + @Throws(BeansException::class) + override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any { + return bean + } + + //对于每个容器执行了初始化的 bean,如果这个 bean 的某个方法注解了@Subscribe,则将该 bean 注册到事件总线 + @Throws(BeansException::class) + override fun postProcessAfterInitialization(bean: Any, beanName: String): Any { + // for each method in the bean + val methods: Array = bean.javaClass.methods + for (method in methods) { + // check the annotations on that method + val annotations: Array = method.getAnnotations() + for (annotation in annotations) { + // if it contains the Subscribe annotation + if (annotation.annotationClass == Subscribe::class) { + // 如果这是一个Guava @Subscribe注解的事件监听器方法,说明所在bean实例 + // 对应一个Guava事件监听器类,将该bean实例注册到Guava事件总线 + eventBus?.register(bean) + return bean + } + } + } + return bean + } + +} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/Logger.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/Logger.kt index 24b042d..ec4150d 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/Logger.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/Logger.kt @@ -48,7 +48,7 @@ class Logger : ILogger { override fun trace(format: String, vararg args: Any) { if (this.logger.isTraceEnabled) { - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) this.logger.trace(message) } } @@ -56,7 +56,7 @@ class Logger : ILogger { override fun trace(t: Throwable, format: String, vararg args: Any) { if (this.logger.isTraceEnabled) { - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) this.logger.trace(message, t) } } @@ -64,7 +64,7 @@ class Logger : ILogger { override fun trace(obj: Any, format: String, vararg args: Any) { if (this.logger.isTraceEnabled) { val real = this.getLogger(obj) - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) real.trace(message) } } @@ -72,7 +72,7 @@ class Logger : ILogger { override fun trace(obj: Any, t: Throwable?, format: String, vararg args: Any) { if (this.logger.isTraceEnabled) { val real = this.getLogger(obj) - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) real.trace(message, t) } } @@ -95,7 +95,7 @@ class Logger : ILogger { override fun debug(format: String, vararg args: Any) { if (this.logger.isDebugEnabled) { - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) this.logger.debug(message) } } @@ -103,7 +103,7 @@ class Logger : ILogger { override fun debug(t: Throwable, format: String, vararg args: Any) { if (this.logger.isDebugEnabled) { - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) this.logger.debug(message, t) } } @@ -111,7 +111,7 @@ class Logger : ILogger { override fun debug(obj: Any, format: String, vararg args: Any) { if (this.logger.isDebugEnabled) { val real = this.getLogger(obj) - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) real.debug(message) } } @@ -119,7 +119,7 @@ class Logger : ILogger { override fun debug(obj: Any, t: Throwable?, format: String, vararg args: Any) { if (this.logger.isDebugEnabled) { val real = this.getLogger(obj) - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) real.debug(message, t) } } @@ -140,7 +140,7 @@ class Logger : ILogger { override fun info(format: String, vararg args: Any) { if (this.logger.isInfoEnabled) { - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) this.logger.info(message) } } @@ -148,7 +148,7 @@ class Logger : ILogger { override fun info(t: Throwable, format: String, vararg args: Any) { if (this.logger.isInfoEnabled) { - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) this.logger.info(message, t) } } @@ -156,7 +156,7 @@ class Logger : ILogger { override fun info(obj: Any, format: String, vararg args: Any) { if (this.logger.isInfoEnabled) { val real = this.getLogger(obj) - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) real.info(message) } } @@ -164,7 +164,7 @@ class Logger : ILogger { override fun info(obj: Any, t: Throwable?, format: String, vararg args: Any) { if (this.logger.isInfoEnabled) { val real = this.getLogger(obj) - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) real.info(message, t) } } @@ -185,7 +185,7 @@ class Logger : ILogger { override fun warn(format: String, vararg args: Any) { if (this.logger.isWarnEnabled) { - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) this.logger.warn(message) } } @@ -193,7 +193,7 @@ class Logger : ILogger { override fun warn(t: Throwable, format: String, vararg args: Any) { if (this.logger.isWarnEnabled) { - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) this.logger.warn(message, t) } } @@ -201,7 +201,7 @@ class Logger : ILogger { override fun warn(obj: Any, format: String, vararg args: Any) { if (this.logger.isWarnEnabled) { val real = this.getLogger(obj) - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) real.warn(message) } } @@ -209,7 +209,7 @@ class Logger : ILogger { override fun warn(obj: Any, t: Throwable?, format: String, vararg args: Any) { if (this.logger.isWarnEnabled) { val real = this.getLogger(obj) - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) real.warn(message, t) } } @@ -232,7 +232,7 @@ class Logger : ILogger { override fun error(format: String, vararg args: Any) { if (this.logger.isErrorEnabled) { - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) this.logger.error(message) } } @@ -240,7 +240,7 @@ class Logger : ILogger { override fun error(t: Throwable, format: String, vararg args: Any) { if (this.logger.isErrorEnabled) { - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) this.logger.error(message, t) } } @@ -248,7 +248,7 @@ class Logger : ILogger { override fun error(obj: Any, format: String, vararg args: Any) { if (this.logger.isErrorEnabled) { val real = this.getLogger(obj) - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) real.error(message) } } @@ -256,7 +256,7 @@ class Logger : ILogger { override fun error(obj: Any, t: Throwable?, format: String, vararg args: Any) { if (this.logger.isErrorEnabled) { val real = this.getLogger(obj) - val message = String.format(format, *args) + val message = if (args.isEmpty()) format else String.format(format, *args) real.error(message, t) } } diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/AppAspect.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/AppAspect.kt new file mode 100644 index 0000000..ed07e8b --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/AppAspect.kt @@ -0,0 +1,92 @@ +package com.synebula.gaea.app.component.aop + +import com.google.gson.Gson +import com.synebula.gaea.app.IApplication +import com.synebula.gaea.app.component.aop.annotation.Handler +import com.synebula.gaea.app.component.aop.annotation.MethodName +import com.synebula.gaea.app.component.aop.annotation.ModuleName +import com.synebula.gaea.app.struct.HttpMessage +import com.synebula.gaea.data.message.Status +import com.synebula.gaea.exception.NoticeUserException +import com.synebula.gaea.log.ILogger +import org.aspectj.lang.ProceedingJoinPoint +import org.aspectj.lang.annotation.Around +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.ApplicationContext +import org.springframework.core.DefaultParameterNameDiscoverer + +abstract class AppAspect { + private var paramDiscover = DefaultParameterNameDiscoverer() + + private val gson = Gson() + + @Autowired + lateinit var logger: ILogger + + @Autowired + lateinit var applicationContext: ApplicationContext + + /** + * 定义切面的方法 + */ + abstract fun func() + + + /** + * 环绕通知,环绕增强,相当于MethodInterceptor + */ + @Around("func()") + fun around(point: ProceedingJoinPoint): Any? { + val clazz = point.`this`.javaClass //获取实际对象的类型避免获取到父类 + val func = point.signature.declaringType.methods.find { + it.name == point.signature.name + }!!//获取声明类型中的方法信息 + val funcAnnotations = func.annotations ?: arrayOf() + + var funcName = func.name + //遍历方法注解 + for (funcAnnotation in funcAnnotations) { + val annotations = funcAnnotation.annotationClass.annotations + + //尝试寻找方法注解的处理类 + val handler = annotations.find { it is Handler } + if (handler != null && handler is Handler) { + val handleClazz = applicationContext.getBean(handler.value.java) + handleClazz.handle(clazz, func, point.args) + } + if (funcAnnotation is MethodName) + funcName = funcAnnotation.name + } + + return try { + point.proceed() + } catch (ex: Throwable) { + //找到类的模块名称,否则使用类名 + var moduleName = clazz.name + if (IApplication::class.java.isAssignableFrom(clazz)) { + moduleName = (point.`this` as IApplication).name + } else { + val name = clazz.annotations.find { it is ModuleName } + if (name != null && name is ModuleName) { + moduleName = name.value + } + } + var message = "$moduleName - $funcName 异常" + if (ex is NoticeUserException) { + message = "$message: ${ex.message}" + } else { + message = "$message。" + + } + logger.error( + ex, + "$message。Method args ${ + paramDiscover.getParameterNames(func)?.contentToString() + } values is ${ + gson.toJson(point.args) + }" + ) + return HttpMessage(Status.Error, message) + } + } +} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/AccessLog.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/AccessLog.kt new file mode 100644 index 0000000..28015c6 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/AccessLog.kt @@ -0,0 +1,8 @@ +package com.synebula.gaea.app.component.aop.annotation + +import com.synebula.gaea.app.component.aop.handler.AccessLogHandler + +@Target(AnnotationTarget.FUNCTION) +@Handler(AccessLogHandler::class) +@Retention(AnnotationRetention.RUNTIME) +annotation class AccessLog \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/Handler.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/Handler.kt new file mode 100644 index 0000000..73b5b47 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/Handler.kt @@ -0,0 +1,8 @@ +package com.synebula.gaea.app.component.aop.annotation + +import com.synebula.gaea.app.component.aop.handler.AnnotationHandler +import kotlin.reflect.KClass + +@Target(AnnotationTarget.ANNOTATION_CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class Handler(val value: KClass) \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/MethodName.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/MethodName.kt new file mode 100644 index 0000000..3f09685 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/MethodName.kt @@ -0,0 +1,13 @@ +package com.synebula.gaea.app.component.aop.annotation + +import java.lang.annotation.Inherited + +/** + * 标记方法名称,由AOP负责记录异常时使用该名称 + * + * @param name 异常消息 + */ +@Inherited +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class MethodName(val name: String) diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/ModuleName.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/ModuleName.kt new file mode 100644 index 0000000..3ab6e15 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/ModuleName.kt @@ -0,0 +1,8 @@ +package com.synebula.gaea.app.component.aop.annotation + +/** + * 模块的业务名称 + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class ModuleName(val value: String) diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AccessLogHandler.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AccessLogHandler.kt new file mode 100644 index 0000000..1898eb3 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AccessLogHandler.kt @@ -0,0 +1,29 @@ +package com.synebula.gaea.app.component.aop.handler + +import com.fasterxml.jackson.databind.ObjectMapper +import com.synebula.gaea.log.ILogger +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component +import org.springframework.web.context.request.RequestContextHolder +import org.springframework.web.context.request.ServletRequestAttributes +import java.lang.reflect.Method + +@Component +class AccessLogHandler : AnnotationHandler { + private val mapper = ObjectMapper() + + @Autowired + lateinit var logger: ILogger + + override fun handle(clazz: Class, func: Method, args: Array, exception: Exception?) { + val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes + val request = attributes.request + logger.info( + "${request.method} ${request.requestURL} from ${request.remoteAddr}, call function ${clazz.name}.${func.name}, args: ${ + mapper.writeValueAsString( + args + ) + }" + ) + } +} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AnnotationHandler.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AnnotationHandler.kt new file mode 100644 index 0000000..fbd2a37 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AnnotationHandler.kt @@ -0,0 +1,7 @@ +package com.synebula.gaea.app.component.aop.handler + +import java.lang.reflect.Method + +interface AnnotationHandler { + fun handle(clazz: Class, func: Method, args: Array, exception: Exception? = null) +} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/Excel.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/Excel.kt new file mode 100644 index 0000000..b8e2626 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/Excel.kt @@ -0,0 +1,306 @@ +package com.synebula.gaea.app.component.poi + +import com.synebula.gaea.app.struct.ExcelData +import com.synebula.gaea.data.date.DateTime +import com.synebula.gaea.exception.NoticeUserException +import org.apache.poi.hpsf.Decimal +import org.apache.poi.hssf.usermodel.HSSFCell +import org.apache.poi.hssf.usermodel.HSSFCellStyle +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator +import org.apache.poi.hssf.usermodel.HSSFWorkbook +import org.apache.poi.ss.formula.BaseFormulaEvaluator +import org.apache.poi.ss.usermodel.* +import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import org.springframework.web.multipart.MultipartFile +import java.util.* + + +/** + * Excel操作对象 + */ +object Excel { + + /** + * 导出excel + * + * @param data 需要导出的数据 + * @return excel表格数据 + */ + fun export(data: ExcelData, writeTo: (wb: HSSFWorkbook) -> Unit) { + // 第一步,创建一个HSSFWorkbook,对应一个Excel文件 + val wb = HSSFWorkbook() + // 第二步,在workbook中添加一个sheet,对应Excel文件中的sheet,并设置默认样式 + val sheet = wb.createSheet(data.title) + + // 第四步,创建单元格,并设置值表头 设置表头居中 + val titleStyle = wb.createCellStyle() + titleStyle.alignment = HorizontalAlignment.CENTER// 创建一个居中格式 + titleStyle.verticalAlignment = VerticalAlignment.CENTER + val titleFont = wb.createFont() + titleFont.bold = true + titleStyle.setFont(titleFont) + setBorderStyle(titleStyle, BorderStyle.THIN) + + //声明列对象 + // 第三步,在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制 + var row = sheet.createRow(0) + row.height = 25 * 20 + var cell: HSSFCell + //创建标题 + for (col in data.columnNames.indices) { + try { + cell = row.createCell(col) + cell.setCellStyle(titleStyle) + cell.setCellValue(data.columnNames[col]) + setColumnWidth(data.columnNames[col], col, sheet) + } catch (ex: RuntimeException) { + throw Exception("创建索引${col}列[${data.columnNames[col]}]时出现异常", ex) + } + } + + val contentStyle = wb.createCellStyle() + contentStyle.alignment = HorizontalAlignment.LEFT// 创建一个修改居左格式 + contentStyle.verticalAlignment = VerticalAlignment.CENTER + setBorderStyle(contentStyle, BorderStyle.THIN) + + //创建内容 + var col = 0 + for (i in data.data.indices) { + try { + row = sheet.createRow(i + 1) + row.height = 20 * 20 + col = 0 + while (col < data.data[i].size) { + cell = row.createCell(col) + cell.setCellStyle(contentStyle) + cell.setCellValue(data.data[i][col]) + setColumnWidth(data.data[i][col], col, sheet) + + col++ + } + } catch (ex: RuntimeException) { + throw Exception("创建索引${i}行[${data.columnNames[col]}]时出现异常", ex) + } + } + writeTo(wb) + } + + /** + * 导入文件 + * + * @param file 上传文件流 + * @param columns 文件列名称、类型定义 + * @param rowStart 数据起始行,默认0 + * @param columnStart 数据起始列,默认0 + * + * @return ExcelData + */ + fun import( + file: MultipartFile, + columns: List>, + rowStart: Int = 0, + columnStart: Int = 0, + emptyRowFilter: (row: Row) -> Boolean = + { row -> + row.getCell( + 0, + Row.MissingCellPolicy.RETURN_NULL_AND_BLANK + ) == null + } + ): List> { + if (file.originalFilename?.endsWith(".xls") != true && file.originalFilename?.endsWith(".xlsx") != true) + throw RuntimeException("无法识别的文件格式[${file.originalFilename}]") + + val evaluator: BaseFormulaEvaluator + val workbook = if (file.originalFilename?.endsWith(".xls") == true) { + val wb = HSSFWorkbook(file.inputStream) + evaluator = HSSFFormulaEvaluator(wb) + wb + } else { + val wb = XSSFWorkbook(file.inputStream) + evaluator = XSSFFormulaEvaluator(wb) + wb + } + + val sheet = workbook.getSheetAt(0) + + val data = mutableListOf>() + for (r in rowStart..sheet.lastRowNum) { + val row = sheet.getRow(r) ?: continue + if (emptyRowFilter(row)) continue //空行不处理 + val rowData = mutableMapOf() + for (c in columnStart until columns.size + columnStart) { + try { + val column = columns[c] + val cell = row.getCell(c, Row.MissingCellPolicy.RETURN_BLANK_AS_NULL) + if (cell == null) { + rowData[columns[c].first] = "" + continue + } + val value: Any? = when (column.second) { + Int::class.java.name -> { + if (cell.cellType == CellType.NUMERIC) { + cell.numericCellValue.toInt() + } else { + this.getCellValue(cell, evaluator).toString().toIntOrNull() + } + } + Double::class.java.name -> { + if (cell.cellType == CellType.NUMERIC) { + cell.numericCellValue + } else { + this.getCellValue(cell, evaluator).toString().toDoubleOrNull() + } + } + Float::class.java.name -> { + if (cell.cellType == CellType.NUMERIC) { + cell.numericCellValue.toFloat() + } else { + this.getCellValue(cell, evaluator).toString().toFloatOrNull() + } + } + Decimal::class.java.name -> { + if (cell.cellType == CellType.NUMERIC) { + cell.numericCellValue.toBigDecimal() + } else { + this.getCellValue(cell, evaluator).toString().toBigDecimalOrNull() + } + } + Boolean::class.java.name -> { + if (cell.cellType == CellType.BOOLEAN) { + cell.booleanCellValue + } else { + this.getCellValue(cell, evaluator).toString().toBoolean() + } + } + Date::class.java.name -> try { + cell.dateCellValue + } catch (ignored: Exception) { + DateTime(cell.stringCellValue).date + } + else -> cell.stringCellValue + } + rowData[columns[c].first] = value + } catch (ex: Exception) { + throw RuntimeException("解析EXCEL文件${file.originalFilename}第${c + 1}行第${c + 1}列出错", ex) + } + } + data.add(rowData) + } + workbook.close() + file.inputStream.close() + return data + } + + /** + * 导入文件 + * + * @param file 上传文件流 + * @param rowStartIndex 数据起始行,默认0 + * @param columnStartIndex 数据起始列,默认0 + * + * @return ExcelData + */ + fun import( + file: MultipartFile, + columnSize: Int = 0, + rowStartIndex: Int = 0, + columnStartIndex: Int = 0, + emptyRowFilter: (row: Row) -> Boolean = { row -> + row.getCell( + 0, + Row.MissingCellPolicy.RETURN_NULL_AND_BLANK + ) == null + } + ): List> { + if (file.originalFilename?.endsWith(".xls") != true && file.originalFilename?.endsWith(".xlsx") != true) + throw NoticeUserException("无法识别的文件格式[${file.originalFilename}]") + val evaluator: BaseFormulaEvaluator + val workbook = if (file.originalFilename?.endsWith(".xls") == true) { + val wb = HSSFWorkbook(file.inputStream) + evaluator = HSSFFormulaEvaluator(wb) + wb + } else { + val wb = XSSFWorkbook(file.inputStream) + evaluator = XSSFFormulaEvaluator(wb) + wb + } + val sheet = workbook.getSheetAt(0) + + val titles = mutableListOf() + val titleRow = sheet.getRow(rowStartIndex) + val size = if (columnSize != 0) columnSize else titleRow.physicalNumberOfCells //列数 + for (i in columnStartIndex until size) { + titles.add(titleRow.getCell(i).stringCellValue) + } + + val data = mutableListOf>() + for (r in (rowStartIndex + 1)..sheet.lastRowNum) { + val row = sheet.getRow(r) ?: continue + if (emptyRowFilter(row)) continue //空行不处理 + val rowData = mutableMapOf() + for (c in columnStartIndex until size + columnStartIndex) { + try { + val title = titles[c] + val cell = row.getCell(c, Row.MissingCellPolicy.RETURN_BLANK_AS_NULL) + if (cell == null) { + rowData[title] = "" + continue + } + val value = getCellValue(cell, evaluator) + rowData[title] = value.toString() + } catch (ex: Exception) { + throw NoticeUserException("解析EXCEL文件${file.originalFilename}第${r + 1}行第${c + 1}列出错", ex) + } + } + data.add(rowData) + } + workbook.close() + file.inputStream.close() + return data + } + + /** + * 设置列宽 + * + * @param content 列内容 + * @param col 列索引 + * @param sheet 需要设置的表格 + */ + private fun setColumnWidth(content: String, col: Int, sheet: Sheet) { + //设置列宽 + val width = (content.length * 1.5 * 256).toInt() + if (width > sheet.getColumnWidth(col)) + sheet.setColumnWidth(col, width) + } + + /** + * 设置cell style的边框 + */ + private fun setBorderStyle(style: HSSFCellStyle, borderStyle: BorderStyle) { + style.borderTop = borderStyle + style.borderRight = borderStyle + style.borderBottom = borderStyle + style.borderLeft = borderStyle + } + + private fun getCellValue(cell: Cell, evaluator: BaseFormulaEvaluator): Any { + return when (cell.cellType) { + CellType.BOOLEAN -> cell.booleanCellValue + CellType.ERROR -> cell.errorCellValue + CellType.NUMERIC -> { + val numericCellValue: Double = cell.numericCellValue + if (DateUtil.isCellDateFormatted(cell)) { + DateUtil.getLocalDateTime(numericCellValue) + } else { + numericCellValue + } + } + CellType.STRING -> cell.richStringCellValue.string + CellType.BLANK -> "" + CellType.FORMULA -> evaluator.evaluate(cell).toString() + else -> throw Exception("匹配类型错误") + } + } +} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/excel/Excel.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/excel/Excel.kt deleted file mode 100644 index 3d0d179..0000000 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/excel/Excel.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.synebula.gaea.app.component.poi.excel - -import org.apache.poi.hssf.usermodel.HSSFCell -import org.apache.poi.hssf.usermodel.HSSFCellStyle -import org.apache.poi.hssf.usermodel.HSSFWorkbook -import org.apache.poi.ss.usermodel.BorderStyle -import org.apache.poi.ss.usermodel.HorizontalAlignment -import org.apache.poi.ss.usermodel.Sheet -import org.apache.poi.ss.usermodel.VerticalAlignment -import java.lang.Exception -import java.lang.RuntimeException -import kotlin.math.ceil - -/** - * Excel操作对象 - */ -object Excel { - - /** - * 导出excel - * - * @param data 需要导出的数据 - * @return excel表格数据 - */ - fun export(data: ExcelData, writeTo: (wb: HSSFWorkbook) -> Unit) { - // 第一步,创建一个HSSFWorkbook,对应一个Excel文件 - val wb = HSSFWorkbook() - // 第二步,在workbook中添加一个sheet,对应Excel文件中的sheet,并设置默认样式 - val sheet = wb.createSheet(data.title) - - // 第四步,创建单元格,并设置值表头 设置表头居中 - val titleStyle = wb.createCellStyle() - titleStyle.alignment = HorizontalAlignment.CENTER// 创建一个居中格式 - titleStyle.verticalAlignment = VerticalAlignment.CENTER - val titleFont = wb.createFont() - titleFont.bold = true - titleStyle.setFont(titleFont) - this.setBorderStyle(titleStyle, BorderStyle.THIN) - - //声明列对象 - // 第三步,在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制 - var row = sheet.createRow(0) - row.height = 25 * 20 - var cell: HSSFCell - //创建标题 - for (col in data.columnNames.indices) { - try { - cell = row.createCell(col) - cell.setCellStyle(titleStyle) - cell.setCellValue(data.columnNames[col]) - this.setColumnWidth(data.columnNames[col], col, sheet) - } catch (ex: RuntimeException) { - throw Exception("创建索引${col}列[${data.columnNames[col]}]时出现异常", ex) - } - } - - val contentStyle = wb.createCellStyle() - contentStyle.alignment = HorizontalAlignment.LEFT// 创建一个修改居左格式 - contentStyle.verticalAlignment = VerticalAlignment.CENTER - this.setBorderStyle(contentStyle, BorderStyle.THIN) - - //创建内容 - var col = 0 - for (i in data.data.indices) { - try { - row = sheet.createRow(i + 1) - row.height = 20 * 20 - col = 0 - while (col < data.data[i].size) { - cell = row.createCell(col) - cell.setCellStyle(contentStyle) - cell.setCellValue(data.data[i][col]) - this.setColumnWidth(data.data[i][col], col, sheet) - - col++ - } - } catch (ex: RuntimeException) { - throw Exception("创建索引${i}行[${data.columnNames[col]}]时出现异常", ex) - } - } - writeTo(wb) - } - - /** - * 设置列宽 - * - * @param content 列内容 - * @param col 列索引 - * @param sheet 需要设置的表格 - */ - private fun setColumnWidth(content: String, col: Int, sheet: Sheet) { - //设置列宽 - val width = (content.length * 1.5 * 256).toInt() - if (width > sheet.getColumnWidth(col)) - sheet.setColumnWidth(col, width) - } - - /** - * 设置cell style的边框 - */ - private fun setBorderStyle(style: HSSFCellStyle, borderStyle: BorderStyle) { - style.borderTop = borderStyle - style.borderRight = borderStyle - style.borderBottom = borderStyle - style.borderLeft = borderStyle - } -} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/TokenManager.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/TokenManager.kt new file mode 100644 index 0000000..a9a4c7d --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/TokenManager.kt @@ -0,0 +1,131 @@ +package com.synebula.gaea.app.component.security + +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm +import com.google.gson.Gson +import com.synebula.gaea.app.struct.exception.TokenCloseExpireException +import com.synebula.gaea.log.ILogger +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component +import java.util.* + +@Component +class TokenManager { + @Autowired + private lateinit var logger: ILogger + + @Autowired + private lateinit var gson: Gson + + @Value("\${jwt.secret:}") + val secret: String = "" + + /** + * 短期有效期, 默认一天。单位分钟 + */ + @Value("\${jwt.expire.normal:${24 * 60}}") + private val normalExpire = "" + + /** + * 长期有效期, 默认一年。单位分钟 + */ + @Value("\${jwt.expire.remember:${365 * 24 * 60}}") + private val rememberExpire = "" + + /** + * 放到header的名称 + */ + val header = "token" + + /** + * 业务载荷名称 + */ + val payload = "user" + + /** + * 生成签名,5min后过期 + * + * @param user 用户 + * @return 加密的token + */ + fun sign(user: Any, remember: Boolean = false): String { + return this.sign(gson.toJson(user), remember) + } + + /** + * 生成签名,5min后过期 + * + * @param userJson 用户 Json + * @return 加密的token + */ + fun sign(userJson: String, remember: Boolean = false): String { + val milli = this.expireMilliseconds(if (remember) rememberExpire.toLong() else normalExpire.toLong()) + val date = Date(System.currentTimeMillis() + milli) + val algorithm: Algorithm = Algorithm.HMAC256(secret) + // 附带username信息 + return JWT.create() + .withClaim(this.payload, userJson) + .withIssuedAt(Date()) + .withExpiresAt(date) + .sign(algorithm) + } + + /** + * 校验token是否正确 + * + * @param token 密钥 + * @return 是否正确 + */ + fun verify(token: String, clazz: Class): T? { + try { + val now = Date() + val algorithm = Algorithm.HMAC256(secret) + val jwt = JWT.decode(token) + val remain = jwt.expiresAt.time - now.time //剩余的时间 + val total = jwt.expiresAt.time - jwt.issuedAt.time //总时间 + if (remain > 0 && 1.0 * remain / total <= 0.3) //存活时间少于总时间的1/3重新下发 + throw TokenCloseExpireException("", JWT.decode(token).getClaim("user").asString()) + + val result = JWT.require(algorithm).build().verify(token) + val json = result.getClaim(this.payload).asString() + return gson.fromJson(json, clazz) + } catch (ex: Exception) { + this.logger.debug(this, ex, "解析token出错") + throw ex + } + } + + /** + * 校验token是否正确 + * + * @param token 密钥 + * @return 是否正确 + */ + fun verify(token: String): String { + try { + val now = Date() + val algorithm = Algorithm.HMAC256(secret) + val jwt = JWT.decode(token) + val remain = jwt.expiresAt.time - now.time //剩余的时间 + val total = jwt.expiresAt.time - jwt.issuedAt.time //总时间 + if (remain > 0 && 1.0 * remain / total <= 0.3) //存活时间少于总时间的1/3重新下发 + throw TokenCloseExpireException("", JWT.decode(token).getClaim("user").asString()) + + val result = JWT.require(algorithm).build().verify(token) + return result.getClaim(payload).asString() + } catch (ex: Exception) { + this.logger.debug(this, ex, "解析token出错") + throw ex + } + } + + /** + * 获取超时毫秒 + * @param minutes 分钟数 + */ + private fun expireMilliseconds(minutes: Long): Long { + return minutes * 60 * 1000 + } +} + diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebAuthorization.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebAuthorization.kt new file mode 100644 index 0000000..1d451d4 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebAuthorization.kt @@ -0,0 +1,43 @@ +package com.synebula.gaea.app.component.security + +import com.synebula.gaea.app.struct.HttpMessage +import com.synebula.gaea.app.struct.exception.TokenCloseExpireException +import com.synebula.gaea.data.message.Status +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter +import java.io.IOException +import javax.servlet.FilterChain +import javax.servlet.ServletException +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + + +/** + * 登录成功后 走此类进行鉴权操作 + */ +class WebAuthorization(authenticationManager: AuthenticationManager, var tokenManager: TokenManager) : + BasicAuthenticationFilter(authenticationManager) { + + /** + * 在过滤之前和之后执行的事件 + */ + @Throws(IOException::class, ServletException::class) + override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) { + val token = request.getHeader(tokenManager.header) + try { + val user = tokenManager.verify(token) + val authentication = + UsernamePasswordAuthenticationToken(user, null, null) + SecurityContextHolder.getContext().authentication = authentication + super.doFilterInternal(request, response, chain) + } catch (ex: TokenCloseExpireException) { + response.status = Status.Success + response.characterEncoding = "utf-8" + response.contentType = "text/javascript;charset=utf-8" + response.writer.print(HttpMessage(Status.Reauthorize, tokenManager.sign(ex.payload), "重新下发认证消息")) + response.flushBuffer() + } + } +} diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebSecurity.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebSecurity.kt new file mode 100644 index 0000000..c0f6f3c --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebSecurity.kt @@ -0,0 +1,70 @@ +package com.synebula.gaea.app.component.security + +import com.synebula.gaea.app.struct.HttpMessage +import com.synebula.gaea.data.message.Status +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.builders.WebSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter +import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.stereotype.Component +import org.springframework.web.cors.CorsConfiguration +import org.springframework.web.cors.CorsConfigurationSource +import org.springframework.web.cors.UrlBasedCorsConfigurationSource + + +@Component +@EnableWebSecurity +class WebSecurity : WebSecurityConfigurerAdapter() { + @Autowired + lateinit var tokenManager: TokenManager + + /** + * 安全配置 + */ + @Throws(Exception::class) + override fun configure(http: HttpSecurity) { + // 跨域共享 + http.cors() + .and().csrf().disable() // 跨域伪造请求限制无效 + .authorizeRequests() + .anyRequest().authenticated()// 资源任何人都可访问 + .and() + .addFilter(WebAuthorization(authenticationManager(), tokenManager))// 添加JWT鉴权拦截器 + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置Session的创建策略为:Spring Security永不创建HttpSession 不使用HttpSession来获取SecurityContext + .and() + .exceptionHandling() + .authenticationEntryPoint { _, response, _ -> + response.status = Status.Success + response.characterEncoding = "utf-8" + response.contentType = "text/javascript;charset=utf-8" + response.writer.print(HttpMessage(Status.Unauthorized, "用户未登录,请重新登录后尝试!")) + } + } + + @Throws(Exception::class) + override fun configure(web: WebSecurity) { + web.ignoring().antMatchers("/sign/**") + } + + /** + * 跨域配置 + * @return 基于URL的跨域配置信息 + */ + @Bean + fun corsConfigurationSource(): CorsConfigurationSource { + val configuration = CorsConfiguration() + configuration.allowedOrigins = listOf("*") + configuration.allowedMethods = listOf("*") + configuration.allowedHeaders = listOf("*") + // 如果所有的属性不全部配置,一定要执行该方法 + configuration.applyPermitDefaultValues() + val source = UrlBasedCorsConfigurationSource() + // 注册跨域配置 + source.registerCorsConfiguration("/**", configuration) + return source + } +} diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt index 608d2aa..ff5ded0 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt @@ -1,8 +1,8 @@ package com.synebula.gaea.app.query import com.synebula.gaea.app.IApplication -import com.synebula.gaea.app.component.HttpMessage -import com.synebula.gaea.data.message.Status +import com.synebula.gaea.app.component.aop.annotation.MethodName +import com.synebula.gaea.app.struct.HttpMessage import com.synebula.gaea.query.IQuery import com.synebula.gaea.query.Params import org.springframework.web.bind.annotation.GetMapping @@ -13,54 +13,38 @@ interface IQueryApp : IApplication { /** * 查询服务 */ - var query: IQuery? + var query: IQuery /** * 查询的View类型 */ var clazz: Class + @MethodName("获取数据") @GetMapping("/{id:.+}") fun get(@PathVariable id: TKey): HttpMessage { - return this.doQuery("获取${this.name}数据失败") { - this.query!!.get(id, clazz) - } + val data = this.query.get(id, clazz) + val msg = HttpMessage() + msg.data = data + return msg } + @MethodName("获取列表数据") @GetMapping fun list(@RequestParam params: LinkedHashMap): HttpMessage { - return this.doQuery("获取${this.name}列表数据失败") { - this.query!!.list(params, clazz) - } + val data = this.query.list(params, clazz) + return HttpMessage(data) } + @MethodName("获取分页数据") @GetMapping("/segments/{size}/pages/{page}") fun paging( @PathVariable size: Int, @PathVariable page: Int, @RequestParam parameters: LinkedHashMap ): HttpMessage { - return this.doQuery("获取${this.name}分页数据[条数:$size,页码:$page]失败") { - val data = Params(page, size, parameters) - this.query!!.paging(data, clazz) - } - } - - - /** - * 抽取查询业务判断功能 - * - * @param error 错误消息 - * @param biz 业务执行逻辑 - */ - fun doQuery(error: String, biz: (() -> Any?)): HttpMessage { - return this.safeExecute(error) { - if (this.query != null) { - it.data = biz() - } else { - it.status = Status.Error - it.message = "没有对应服务,无法执行该操作" - } - } + val params = Params(page, size, parameters) + val data = this.query.paging(params, clazz) + return HttpMessage(data) } } \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/QueryApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/QueryApp.kt index d8760fc..7b1a295 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/QueryApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/QueryApp.kt @@ -13,6 +13,6 @@ import com.synebula.gaea.query.IQuery open class QueryApp( override var name: String, override var clazz: Class, - override var query: IQuery?, + override var query: IQuery, override var logger: ILogger? ) : IQueryApp \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/excel/ExcelData.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/ExcelData.kt similarity index 75% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/excel/ExcelData.kt rename to src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/ExcelData.kt index e00e96f..ba4cf67 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/excel/ExcelData.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/ExcelData.kt @@ -1,4 +1,4 @@ -package com.synebula.gaea.app.component.poi.excel +package com.synebula.gaea.app.struct class ExcelData(var title: String = "", var columnNames: List = listOf(), diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/HttpMessage.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/HttpMessage.kt similarity index 78% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/HttpMessage.kt rename to src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/HttpMessage.kt index c463c1d..43e8236 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/HttpMessage.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/HttpMessage.kt @@ -1,5 +1,6 @@ -package com.synebula.gaea.app.component +package com.synebula.gaea.app.struct +import com.google.gson.Gson import com.synebula.gaea.data.message.DataMessage class HttpMessage() : DataMessage() { @@ -22,4 +23,8 @@ class HttpMessage() : DataMessage() { this.message = msg.message this.data = msg.data } + + override fun toString(): String { + return Gson().toJson(this) + } } \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/exception/TokenCloseExpireException.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/exception/TokenCloseExpireException.kt new file mode 100644 index 0000000..ffe8a26 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/exception/TokenCloseExpireException.kt @@ -0,0 +1,5 @@ +package com.synebula.gaea.app.struct.exception + +import com.auth0.jwt.exceptions.TokenExpiredException + +class TokenCloseExpireException(msg: String, var payload: String) : TokenExpiredException(msg) diff --git a/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/MongoExt.kt b/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/MongoExt.kt index 4980f82..1c1349f 100644 --- a/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/MongoExt.kt +++ b/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/MongoExt.kt @@ -42,8 +42,12 @@ fun Query.where( //日期类型特殊处理为String类型 val fieldType = onFieldType(key) - if (fieldType != null && value.javaClass != fieldType && fieldType == Date::class.java) { - value = DateTime(value.toString(), "yyyy-MM-dd HH:mm:ss").date + if (fieldType != null && value.javaClass != fieldType) { + when (fieldType) { + Date::class.java -> value = DateTime(value.toString(), "yyyy-MM-ddTHH:mm:ss").date + Int::class.java -> value = value.toString().toInt() + Integer::class.java -> value = value.toString().toInt() + } } val where = onWhere(key) diff --git a/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/repository/MongoRepository.kt b/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/repository/MongoRepository.kt index 93d035b..75c7869 100644 --- a/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/repository/MongoRepository.kt +++ b/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/repository/MongoRepository.kt @@ -20,8 +20,8 @@ open class MongoRepository(private var repo: MongoTemplate) : IRepository { override fun , TKey> get( id: TKey, clazz: Class - ): TAggregateRoot { - return this.repo.findOne(whereId(id), clazz) as TAggregateRoot + ): TAggregateRoot? { + return this.repo.findOne(whereId(id), clazz) } override fun , TKey> update( @@ -35,6 +35,9 @@ open class MongoRepository(private var repo: MongoTemplate) : IRepository { this.repo.save(obj) } + override fun , TKey> add(obj: List, clazz: Class) { + this.repo.insert(obj, clazz) + } override fun count(params: Map?, clazz: Class): Int { val query = Query() diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/cache/CacheEntity.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/cache/CacheEntity.kt index 7d30454..3272bbf 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/cache/CacheEntity.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/cache/CacheEntity.kt @@ -1,6 +1,6 @@ package com.synebula.gaea.data.cache -import java.util.Date +import java.util.* /** * diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/DateCode.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/DateCode.kt index 7d603a1..4afe5e4 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/DateCode.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/DateCode.kt @@ -1,7 +1,7 @@ package com.synebula.gaea.data.code import java.text.SimpleDateFormat -import java.util.Date +import java.util.* /** * @author alex diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/FixedRandomCode.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/FixedRandomCode.kt index 49a5562..001657b 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/FixedRandomCode.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/FixedRandomCode.kt @@ -1,6 +1,6 @@ package com.synebula.gaea.data.code -import java.util.Random +import java.util.* /** * 固定长度随机编号生成。 diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/GUIDCode.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/GUIDCode.kt index a24406f..9aad658 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/GUIDCode.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/GUIDCode.kt @@ -1,6 +1,6 @@ package com.synebula.gaea.data.code -import java.util.UUID +import java.util.* /** * 全球唯一编号生成。 diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/AlignTime.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/AlignTime.kt index 0b10fe4..76b4a1f 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/AlignTime.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/AlignTime.kt @@ -1,6 +1,8 @@ package com.synebula.gaea.data.date import java.util.* +import kotlin.math.ceil +import kotlin.math.floor /** * 校准时间。 @@ -61,9 +63,9 @@ class AlignTime { */ fun ceilingTime(lastTime: DateTime, intervalSeconds: Int): DateTime { val span = lastTime - this.baseTime - val count = Math.ceil(span.totalSeconds / intervalSeconds).toInt() + val count = ceil(span.totalSeconds / intervalSeconds).toInt() val newTime = DateTime(this.baseTime.date) - newTime.addSeconds(count * intervalSeconds * 1L) + newTime.addSecond(count * intervalSeconds) return newTime } @@ -97,9 +99,9 @@ class AlignTime { */ fun floorTime(lastTime: DateTime, intervalSeconds: Int): DateTime { val span = lastTime - this.baseTime - val count = Math.floor(span.totalSeconds / intervalSeconds).toInt() + val count = floor(span.totalSeconds / intervalSeconds).toInt() val newTime = DateTime(this.baseTime.date) - newTime.addSeconds(count * intervalSeconds * 1L) + newTime.addSecond(count * intervalSeconds) return newTime } } \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/DateTime.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/DateTime.kt index 8348d94..053b4a9 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/DateTime.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/DateTime.kt @@ -7,7 +7,7 @@ import java.util.* /** * 时间格式,方便用于获取Date格式的多种形式 */ -class DateTime : Comparable { +class DateTime() : Comparable { // 内部存储日历格式方便操作 /** @@ -25,7 +25,15 @@ class DateTime : Comparable { /** * 列出时间的级别数组 */ - private val calendarLevel = intArrayOf(Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH, Calendar.MONTH, Calendar.YEAR) + private val calendarLevel = intArrayOf( + Calendar.MILLISECOND, + Calendar.SECOND, + Calendar.MINUTE, + Calendar.HOUR_OF_DAY, + Calendar.DAY_OF_MONTH, + Calendar.MONTH, + Calendar.YEAR + ) val date: Date get() = calendar.time @@ -43,60 +51,169 @@ class DateTime : Comparable { return instance.time } + /** + * 当前时间年 + */ var year: Int get() = calendar.get(Calendar.YEAR) set(value) = calendar.set(Calendar.YEAR, value) + /** + * 当前时间月 + */ var month: Int get() = calendar.get(Calendar.MONTH) set(value) = calendar.set(Calendar.MONTH, value) + /** + * 当前时间天 + */ var day: Int get() = calendar.get(Calendar.DAY_OF_MONTH) set(value) = calendar.set(Calendar.DAY_OF_MONTH, value) + /** + * 获取时间 + */ val time: Time get() = Time(calendar.time) - val firstDay: DateTime + /** + * 获取当月天数 + */ + val days: Int + get() { + return this.calendar.getActualMaximum(Calendar.DAY_OF_MONTH) + } + + /** + * 当前月第一天 + */ + val firstDayOfMonth: DateTime get() { val instance = calendar.clone() as Calendar instance.set(Calendar.DAY_OF_MONTH, 1) return DateTime(instance) } - val lastDay: DateTime + /** + * 当前月最后一天 + */ + val lastDayOfMonth: DateTime get() { val instance = calendar.clone() as Calendar instance.set(Calendar.DAY_OF_MONTH, instance.getActualMaximum(Calendar.DAY_OF_MONTH)) return DateTime(instance) } + /** + * 当前周第一天 + */ + val firstDayOfWeek: DateTime + get() { + val instance = calendar.clone() as Calendar + instance.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY) + instance.set(Calendar.HOUR_OF_DAY, 0) + instance.set(Calendar.MINUTE, 0) + instance.set(Calendar.SECOND, 0) + instance.set(Calendar.MILLISECOND, 0) + return DateTime(instance) + } + + /** + * 当前周最后一天 + */ + val lastDayOfWeek: DateTime + get() { + val instance = this.firstDayOfWeek.calendar.clone() as Calendar + instance.add(Calendar.DAY_OF_WEEK, 6)//当前周第一天加6天 + instance.set(Calendar.HOUR_OF_DAY, 23) + instance.set(Calendar.MINUTE, 59) + instance.set(Calendar.SECOND, 59) + instance.set(Calendar.MILLISECOND, 0) + return DateTime(instance) + } + + /** + * 当前天最早时间 + */ + val earliestTime: DateTime + get() { + val instance = this.calendar.clone() as Calendar + instance.set(Calendar.HOUR_OF_DAY, 0) + instance.set(Calendar.MINUTE, 0) + instance.set(Calendar.SECOND, 0) + instance.set(Calendar.MILLISECOND, 0) + return DateTime(instance) + } + + /** + * 当前天最晚时间 + */ + val latestTime: DateTime + get() { + val instance = this.calendar.clone() as Calendar + instance.set(Calendar.HOUR_OF_DAY, 23) + instance.set(Calendar.MINUTE, 59) + instance.set(Calendar.SECOND, 59) + instance.set(Calendar.MILLISECOND, 0) + return DateTime(instance) + } + + /** + * 当前时间最上一月 + */ + val previousMonth: DateTime + get() { + return this.addMonth(-1) + } + + /** + * 当前时间下一月 + */ + val nextMonth: DateTime + get() { + return this.addMonth(1) + } + + /** + * 当前时间最上一天 + */ + val previousDay: DateTime + get() { + return this.addDay(-1) + } + + /** + * 当前时间下一天 + */ + val nextDay: DateTime + get() { + return this.addDay(1) + } + /** * 从Date格式转化 */ - constructor(date: Date) { + constructor(date: Date) : this() { this.calendar.time = date - this.calendar.set(Calendar.MILLISECOND, 0) } /** * 从Calendar格式转化 */ - constructor(calendar: Calendar) { + constructor(calendar: Calendar) : this() { this.calendar = calendar - this.calendar.set(Calendar.MILLISECOND, 0) } /** * 从Date格式转化 */ - constructor(date: String, format: String) { + constructor(date: String, format: String = "yyyy-MM-dd HH:mm:ss") : this() { val formatter = SimpleDateFormat(format) try { val value = formatter.parse(date) this.calendar.time = value - this.calendar.set(Calendar.MILLISECOND, 0) } catch (e: ParseException) { throw RuntimeException("date string can't format to date", e) } @@ -105,9 +222,8 @@ class DateTime : Comparable { /** * 从Date格式转化。需要注意的是月0代表是一月,以此类推。 */ - constructor(year: Int, month: Int, day: Int = 0, hour: Int = 0, minute: Int = 0, second: Int = 0) { + constructor(year: Int, month: Int, day: Int = 0, hour: Int = 0, minute: Int = 0, second: Int = 0) : this() { this.calendar.set(year, month, day, hour, minute, second) - this.calendar.set(Calendar.MILLISECOND, 0) } /** @@ -156,10 +272,11 @@ class DateTime : Comparable { * 比较当前时间是否在目标时间范围内。 * @param start 目标开始时间。 * @param end 目标结束时间。 - * @return 是否。 + * @return true or false */ - fun between(start: DateTime, end: DateTime): Boolean { - return this in start..end + fun isBetween(start: DateTime, end: DateTime): Boolean { + //return this in start..end + return start.dateNoTime.compareTo(this.dateNoTime) * this.dateNoTime.compareTo(end.dateNoTime) >= 0 } /** @@ -167,27 +284,234 @@ class DateTime : Comparable { * @param start 目标开始时间。 * @param end 目标结束时间。 * @param level 比较时间的最小级别。 - * @return 是否。 + * @return true or false */ - fun between(start: DateTime, end: DateTime, level: TimeUnit): Boolean { + fun isBetween(start: DateTime, end: DateTime, level: TimeUnit): Boolean { return this.compareTo(start, level) >= 0 && this.compareTo(end, level) <= 0 } + /** - * 增加秒 + * 判断当前时间是否在某时间后 + * + * @param other 另一时间 + * @return true or false */ - fun addSeconds(seconds: Long) { - if (seconds <= Int.MAX_VALUE) - this.calendar.add(Calendar.SECOND, seconds.toInt()) - else { - val span = TimeSpan(seconds * 1000) - this.calendar.add(Calendar.DAY_OF_MONTH, span.day) - this.calendar.add(Calendar.HOUR_OF_DAY, span.hour) - this.calendar.add(Calendar.MINUTE, span.minute) - this.calendar.add(Calendar.SECOND, span.second) - } + fun isAfter(other: DateTime): Boolean { + return this.date.after(other.date) } + /** + * 判断当前时间是否在某时间前 + * + * @param other 另一时间 + * @return true or false + */ + fun isBefore(other: DateTime): Boolean { + return this.date.before(other.date) + } + + /** + * 判断当前时间是否同一天 + * + * @param other 另一时间 + * @return true or false + */ + fun isSameDay(other: DateTime): Boolean { + return this.calendar.get(Calendar.ERA) == other.calendar.get(Calendar.ERA) + && this.calendar.get(Calendar.YEAR) == other.calendar.get(Calendar.YEAR) + && this.calendar.get(Calendar.DAY_OF_YEAR) == other.calendar.get(Calendar.DAY_OF_YEAR) + } + + + /** + * 加减年。 + * + * @param year 月份。可以为负数,即为减。 + * @return this + */ + fun addYear(year: Int): DateTime { + this.calendar.add(Calendar.YEAR, year) + return this + } + + /** + * 加减月份。 + * + * @param month 月份。可以为负数,即为减。 + * @return this + */ + fun addMonth(month: Int): DateTime { + this.calendar.add(Calendar.MONTH, month) + return this + } + + + /** + * 加减日期。 + * + * @param day 天数。可以为负数,即为减。 + * @return this + */ + fun addDay(day: Int): DateTime { + this.calendar.add(Calendar.DAY_OF_MONTH, day) + return this + } + + + /** + * 加减小时。 + * + * @param hour 小时。可以为负数,即为减。 + * @return this + */ + fun addHour(hour: Int): DateTime { + this.calendar.add(Calendar.HOUR_OF_DAY, hour) + return this + } + + /** + * 加减分钟。 + * + * @param minute 分钟。可以为负数,即为减。 + * @return this + */ + fun addMinute(minute: Int): DateTime { + this.calendar.add(Calendar.MINUTE, minute) + return this + } + + /** + * 加减秒 + * @param second 秒数。可以为负数,即为减。 + * @return this + */ + fun addSecond(second: Int): DateTime { + this.calendar.add(Calendar.SECOND, second) + return this + } + + /** + * 加减毫秒 + * @param millisecond 毫秒数。可以为负数,即为减。 + * @return this + */ + fun addMillisecond(millisecond: Int): DateTime { + this.calendar.add(Calendar.MILLISECOND, millisecond) + return this + } + + /** + * 设置年。 + * + * @param year 月份。 + * @return this + */ + fun setYear(year: Int): DateTime { + this.calendar.set(Calendar.YEAR, year) + return this + } + + /** + * 设置月份。 + * + * @param month 月份。 + * @return this + */ + fun setMonth(month: Int): DateTime { + this.calendar.set(Calendar.MONTH, month) + return this + } + + + /** + * 设置日期。 + * + * @param day 天数。 + * @return this + */ + fun setDay(day: Int): DateTime { + this.calendar.set(Calendar.DAY_OF_MONTH, day) + return this + } + + + /** + * 设置小时。 + * + * @param hour 小时。 + * @return this + */ + fun setHour(hour: Int): DateTime { + this.calendar.set(Calendar.HOUR_OF_DAY, hour) + return this + } + + /** + * 设置分钟。 + * + * @param minute 分钟。 + * @return this + */ + fun setMinute(minute: Int): DateTime { + this.calendar.set(Calendar.MINUTE, minute) + return this + } + + /** + * 设置秒 + * @param second 总秒数。 + * @return this + */ + fun setSecond(second: Int): DateTime { + this.calendar.set(Calendar.SECOND, second) + return this + } + + /** + * 设置毫秒。 + * @param millisecond 毫秒数。 + * @return this + */ + fun setMillisecond(millisecond: Int): DateTime { + this.calendar.set(Calendar.MILLISECOND, millisecond) + return this + } + + /** + * 当前天是否工作日 + */ + fun isWorkday(): Boolean { + return !this.isWeekend() + } + + /** + * 当前天是否周末 + */ + fun isWeekend(): Boolean { + val dayOfWeek = this.calendar.get(Calendar.DAY_OF_WEEK) + return (dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY) + } + + /** + * 清空毫秒,避免误差 + */ + fun clearMillisecond() { + this.calendar.set(Calendar.MILLISECOND, 0) + } + + /** + * 克隆当前对象 + * + * @return 返回新的DateTime对象 + */ + fun clone(): DateTime { + return DateTime(this.calendar.clone() as Calendar) + } + + /** + * 时间相见 + */ operator fun minus(other: DateTime): TimeSpan { return TimeSpan(this.milliseconds - other.milliseconds) } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/Time.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/Time.kt index 73a3543..5f676f7 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/Time.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/Time.kt @@ -1,10 +1,8 @@ package com.synebula.gaea.data.date -import java.math.BigDecimal import java.text.ParseException import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Date +import java.util.* /** diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/TimeSpan.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/TimeSpan.kt index bc2c2fe..6d8505b 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/TimeSpan.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/TimeSpan.kt @@ -6,9 +6,6 @@ */ package com.synebula.gaea.data.date -import java.math.BigDecimal -import java.util.* - /** * * @author whj diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Message.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Message.kt index c946c9e..c003dca 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Message.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Message.kt @@ -9,9 +9,7 @@ open class Message { /** * 获取状态是否成功 */ - val success: Boolean - get() = this.status == Status.Success - + fun success(): Boolean = this.status == Status.Success /** * 附带提示消息 diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Status.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Status.kt index 179549e..c8baa33 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Status.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Status.kt @@ -6,11 +6,21 @@ object Status { */ const val Success = 200 + /** + * 重新授权 + */ + const val Reauthorize = 205 + /** * 失败 */ const val Failure = 400 + /** + * 未授权 + */ + const val Unauthorized = 401 + /** * 错误 */ diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt index 2d6e582..8c653ef 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt @@ -15,6 +15,14 @@ interface IRepository { */ fun , TKey> add(obj: TAggregateRoot, clazz: Class) + /** + * 插入多个个对象。 + * + * @param obj 需要插入的对象。 + * @return 返回原对象,如果对象ID为自增,则补充自增ID。 + */ + fun , TKey> add(obj: List, clazz: Class) + /** * 更新对象。 * @@ -38,7 +46,7 @@ interface IRepository { * @param clazz 操作数据的类型 * @return 聚合根 */ - fun , TKey> get(id: TKey, clazz: Class): TAggregateRoot + fun , TKey> get(id: TKey, clazz: Class): TAggregateRoot? /** diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Command.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Command.kt index b0f3e75..2c07161 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Command.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Command.kt @@ -1,7 +1,5 @@ package com.synebula.gaea.domain.service -import java.util.* - /** * 命令基础实现类,发给service的命令。 * diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ILazyService.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ILazyService.kt new file mode 100644 index 0000000..2d2efec --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ILazyService.kt @@ -0,0 +1,39 @@ +package com.synebula.gaea.domain.service + +import com.synebula.gaea.data.message.DataMessage +import com.synebula.gaea.data.message.Message +import com.synebula.gaea.domain.model.IAggregateRoot +import com.synebula.gaea.log.ILogger + + +/** + * 继承该接口,表明对象为领域服务。 + * @author alex + * @version 0.0.1 + * @since 2016年9月18日 下午2:23:15 + */ +interface ILazyService, TKey> { + /** + * 日志组件。 + */ + var logger: ILogger + + fun add(root: TAggregateRoot): DataMessage + + fun update(id: TKey, root: TAggregateRoot) + + fun remove(id: TKey) + + /** + * 添加一个删除对象前执行监听器。 + * @param key 监听器标志。 + * @param func 监听方法。 + */ + fun addBeforeRemoveListener(key: String, func: (id: TKey) -> Message) + + /** + * 移除一个删除对象前执行监听器。 + * @param key 监听器标志。 + */ + fun removeBeforeRemoveListener(key: String) +} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/LazyService.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/LazyService.kt new file mode 100644 index 0000000..a0f6f4b --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/LazyService.kt @@ -0,0 +1,73 @@ +package com.synebula.gaea.domain.service + +import com.synebula.gaea.data.IObjectConverter +import com.synebula.gaea.data.message.DataMessage +import com.synebula.gaea.data.message.Message +import com.synebula.gaea.domain.model.IAggregateRoot +import com.synebula.gaea.domain.repository.IRepository +import com.synebula.gaea.log.ILogger + + +/** + * 依赖了IRepository仓储借口的服务实现类 Service + * 该类依赖仓储接口 @see IRepository, 需要显式提供聚合根的class对象 + * + * @param repository 仓储对象 + * @param clazz 聚合根类对象 + * @param logger 日志组件 + * @author alex + * @version 0.1 + * @since 2020-05-17 + */ +open class LazyService, TKey>( + protected open var clazz: Class, + protected open var repository: IRepository, + override var logger: ILogger +) : ILazyService { + + /** + * 删除对象前执行监听器。 + */ + protected val beforeRemoveListeners = mutableMapOf Message>() + + /** + * 添加一个删除对象前执行监听器。 + * @param key 监听器标志。 + * @param func 监听方法。 + */ + override fun addBeforeRemoveListener(key: String, func: (id: TKey) -> Message) { + this.beforeRemoveListeners[key] = func + } + + /** + * 移除一个删除对象前执行监听器。 + * @param key 监听器标志。 + */ + override fun removeBeforeRemoveListener(key: String) { + this.beforeRemoveListeners.remove(key) + } + + override fun add(root: TAggregateRoot): DataMessage { + val msg = DataMessage() + this.repository.add(root, this.clazz) + msg.data = root.id + return msg + } + + override fun update(id: TKey, root: TAggregateRoot) { + root.id = id + this.repository.update(root, this.clazz) + } + + override fun remove(id: TKey) { + val functions = this.beforeRemoveListeners.values + var msg: Message + for (func in functions) { + msg = func(id) + if (!msg.success()) { + throw IllegalStateException(msg.message) + } + } + this.repository.remove(id, this.clazz) + } +} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Service.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Service.kt index 1c5f23f..c68a1f1 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Service.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Service.kt @@ -68,8 +68,8 @@ open class Service, TKey>( var msg: Message for (func in functions) { msg = func(id) - if (!msg.success) { - throw java.lang.RuntimeException(msg.message) + if (!msg.success()) { + throw IllegalStateException(msg.message) } } this.repository.remove(id, this.clazz) diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEvent.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEvent.kt new file mode 100644 index 0000000..59132cb --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEvent.kt @@ -0,0 +1,4 @@ +package com.synebula.gaea.event + +interface IEvent { +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEventBus.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEventBus.kt new file mode 100644 index 0000000..c0b6850 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEventBus.kt @@ -0,0 +1,28 @@ +package com.synebula.gaea.event + +interface IEventBus { + + /** + * 注册事件Listener + * @param obj Listener所在类 + */ + fun register(obj: Any) + + /** + * 取消注册事件Listener + * @param obj Listener所在类 + */ + fun unregister(obj: Any) + + /** + * 同步发布事件 + * @param event 事件 + */ + fun publish(event: IEvent) + + /** + * 异步发布事件 + * @param event 事件 + */ + fun publishAsync(event: IEvent) +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/exception/NoticeUserException.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/exception/NoticeUserException.kt new file mode 100644 index 0000000..fff4d1a --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/exception/NoticeUserException.kt @@ -0,0 +1,6 @@ +package com.synebula.gaea.exception + +/** + * 需要通知给用户的异常 + */ +class NoticeUserException(message: String, cause: Exception? = null) : Exception(message, cause) \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassPath.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassPath.kt index a62caba..7aa60b5 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassPath.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassPath.kt @@ -2,8 +2,7 @@ package com.synebula.gaea.io.scan import java.io.IOException import java.net.URL -import java.util.Enumeration -import java.util.HashSet +import java.util.* /** * diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassScanner.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassScanner.kt index fb1b904..3b03ef0 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassScanner.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassScanner.kt @@ -5,9 +5,7 @@ import java.io.FileFilter import java.io.UnsupportedEncodingException import java.net.URLDecoder import java.nio.charset.Charset -import java.util.Collections -import java.util.HashSet -import java.util.LinkedList +import java.util.* import java.util.jar.JarFile /** diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt index a4c5090..0362126 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt @@ -11,8 +11,8 @@ import com.synebula.gaea.query.type.Order */ data class Params(var page: Int = 1, var size: Int = 10) { - private var _parameters = mutableMapOf() - private var _orders = mutableMapOf() + private var _parameters = linkedMapOf() + private var _orders = linkedMapOf() /** * 数据索引,从0开始。表示数据在总量的第几条。(index = (page - 1) * size) @@ -24,13 +24,13 @@ data class Params(var page: Int = 1, var size: Int = 10) { /** * 排序条件。 */ - var orders: Map + var orders: LinkedHashMap set(value) { - this._orders = value.toMutableMap() + this._orders = value } get() { if (this._parameters.keys.count { it.startsWith("@") } > 0) { - val params = mutableMapOf() + val params = linkedMapOf() this._parameters.forEach { if (it.key.startsWith("@")) { this._orders[it.key.removePrefix("@")] = Order.valueOf(it.value.toString()) @@ -45,13 +45,13 @@ data class Params(var page: Int = 1, var size: Int = 10) { /** * 查询条件。 */ - var parameters: Map + var parameters: LinkedHashMap set(value) { - this._parameters = value.toMutableMap() + this._parameters = value } get() { if (this._parameters.keys.count { it.startsWith("@") } > 0) { - val params = mutableMapOf() + val params = linkedMapOf() this._parameters.forEach { if (it.key.startsWith("@")) { this._orders[it.key.removePrefix("@")] = Order.valueOf(it.value.toString())