合并dev

This commit is contained in:
2022-08-22 15:19:57 +08:00
53 changed files with 1594 additions and 281 deletions

View File

@@ -21,14 +21,14 @@ allprojects {
subprojects { subprojects {
ext { ext {
version '0.6.1' version '0.13.1'
spring_version = "2.3.0.RELEASE" spring_version = "2.3.0.RELEASE"
} }
buildscript { buildscript {
repositories { repositories {
mavenLocal() mavenLocal()
maven { url 'http://maven.aliyuMongoRepositoryn.com/nexus/content/groups/public/' } maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
mavenCentral() mavenCentral()
} }
} }

View File

@@ -1,9 +1,22 @@
buildscript {
dependencies {
classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version")
}
}
apply plugin: 'kotlin-spring'
dependencies { dependencies {
compile project(":src:gaea") compile project(":src:gaea")
compile("org.springframework.boot:spring-boot-starter-web:$spring_version") 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-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: '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 { publishing {

View File

@@ -20,8 +20,8 @@ import javax.annotation.Resource
open class Application<TCommand : ICommand, TView, TKey>( open class Application<TCommand : ICommand, TView, TKey>(
override var name: String, override var name: String,
override var clazz: Class<TView>, override var clazz: Class<TView>,
override var service: IService<TKey>?, override var service: IService<TKey>,
override var query: IQuery?, override var query: IQuery,
override var logger: ILogger? override var logger: ILogger?
) : ICommandApp<TCommand, TKey>, IQueryApp<TView, TKey> { ) : ICommandApp<TCommand, TKey>, IQueryApp<TView, TKey> {

View File

@@ -1,8 +1,10 @@
package com.synebula.gaea.app 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.data.message.Status
import com.synebula.gaea.log.ILogger import com.synebula.gaea.log.ILogger
import org.springframework.security.core.context.SecurityContextHolder
interface IApplication { interface IApplication {
@@ -21,7 +23,7 @@ interface IApplication {
* 安全执行 * 安全执行
*/ */
fun safeExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage { fun safeExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage {
val msg = HttpMessage(Status.Success) val msg = HttpMessage()
try { try {
process(msg) process(msg)
logger?.debug(this, "$name business execute success") logger?.debug(this, "$name business execute success")
@@ -37,7 +39,7 @@ interface IApplication {
* 可抛出自定义异常信息的安全controller实现了异常捕获和消息组成。 * 可抛出自定义异常信息的安全controller实现了异常捕获和消息组成。
*/ */
fun throwExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage { fun throwExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage {
val msg = HttpMessage(Status.Success) val msg = HttpMessage()
try { try {
process(msg) process(msg)
logger?.debug(this, "$name business execute success") logger?.debug(this, "$name business execute success")
@@ -47,4 +49,23 @@ interface IApplication {
} }
return msg return msg
} }
/**
* 获取用户信息
* @param clazz 用户信息结构类
*/
fun <T> sessionUser(clazz: Class<T>): T? {
try {
val authentication = SecurityContextHolder.getContext().authentication.principal.toString()
try {
val gson = Gson()
return gson.fromJson<T>(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
}
} }

View File

@@ -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<TRoot : IAggregateRoot<TKey>, TKey>(
override var name: String,
override var clazz: Class<TRoot>, //view class type
override var service: ILazyService<TRoot, TKey>,
override var query: IQuery,
override var logger: ILogger?
) : ILazyCommandApp<TRoot, TKey>, IQueryApp<TRoot, TKey> {
@Resource
override var jsonSerializer: IJsonSerializer? = null
}

View File

@@ -14,9 +14,10 @@ import javax.annotation.Resource
* @param logger 日志组件 * @param logger 日志组件
*/ */
open class CommandApp<TCommand : ICommand, TKey>( open class CommandApp<TCommand : ICommand, TKey>(
override var name: String, override var name: String,
override var service: IService<TKey>?, override var service: IService<TKey>,
override var logger: ILogger?) : ICommandApp<TCommand, TKey> { override var logger: ILogger?
) : ICommandApp<TCommand, TKey> {
@Resource @Resource
override var jsonSerializer: IJsonSerializer? = null override var jsonSerializer: IJsonSerializer? = null
} }

View File

@@ -1,7 +1,8 @@
package com.synebula.gaea.app.cmd package com.synebula.gaea.app.cmd
import com.synebula.gaea.app.IApplication 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.message.Status
import com.synebula.gaea.data.serialization.json.IJsonSerializer import com.synebula.gaea.data.serialization.json.IJsonSerializer
import com.synebula.gaea.domain.service.ICommand import com.synebula.gaea.domain.service.ICommand
@@ -18,42 +19,32 @@ import org.springframework.web.bind.annotation.*
interface ICommandApp<TCommand : ICommand, TKey> : IApplication { interface ICommandApp<TCommand : ICommand, TKey> : IApplication {
var jsonSerializer: IJsonSerializer? var jsonSerializer: IJsonSerializer?
var service: IService<TKey>? var service: IService<TKey>
@PostMapping @PostMapping
@MethodName("添加")
fun add(@RequestBody command: TCommand): HttpMessage { fun add(@RequestBody command: TCommand): HttpMessage {
return this.safeExecute("添加${this.name}数据失败 - ${if (jsonSerializer != null) jsonSerializer?.serialize(command) else ""}") { return HttpMessage(service.add(command))
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 = "没有对应服务,无法执行该操作"
}
}
} }
@PutMapping("/{id:.+}") @PutMapping("/{id:.+}")
@MethodName("更新")
fun update(@PathVariable id: TKey, @RequestBody command: TCommand): HttpMessage { fun update(@PathVariable id: TKey, @RequestBody command: TCommand): HttpMessage {
return this.safeExecute("更新${this.name}失败 - ${if (jsonSerializer != null) jsonSerializer?.serialize(command) else ""}") { this.service.update(id, command)
if (this.service != null) return HttpMessage()
this.service!!.update(id, command) }
else {
it.status = Status.Error @DeleteMapping("/{id:.+}")
it.message = "没有对应服务,无法执行该操作" @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
} }
} }

View File

@@ -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<TRoot : IAggregateRoot<TKey>, TKey> : IApplication {
var jsonSerializer: IJsonSerializer?
var service: ILazyService<TRoot, TKey>
@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
}
}

View File

@@ -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<TRoot : IAggregateRoot<TKey>, TKey>(
override var name: String,
override var service: ILazyService<TRoot, TKey>,
override var logger: ILogger?
) : ILazyCommandApp<TRoot, TKey> {
@Resource
override var jsonSerializer: IJsonSerializer? = null
}

View File

@@ -11,24 +11,21 @@ import org.springframework.stereotype.Component
import java.io.File import java.io.File
@Component @Component
class EmailMessenger : IEmailMessenger { class EmailMessenger(var mailSender: JavaMailSender?) : IEmailMessenger {
@Autowired @Value("\${spring.mail.host:}")
private lateinit var mailSender: JavaMailSender
@Value("\${spring.mail.host}")
var host = "" var host = ""
@Value("\${spring.mail.port}") @Value("\${spring.mail.port:}")
var port = "" var port = ""
@Value("\${spring.mail.sender}") @Value("\${spring.mail.sender:}")
var sender = "" var sender = ""
@Value("\${spring.mail.username}") @Value("\${spring.mail.username:}")
var username = "" var username = ""
@Value("\${spring.mail.password}") @Value("\${spring.mail.password:}")
var password = "" var password = ""
@Autowired @Autowired
@@ -42,13 +39,20 @@ class EmailMessenger : IEmailMessenger {
* @param receivers 邮件接受者 * @param receivers 邮件接受者
* @param files 附件 * @param files 附件
*/ */
override fun sendMessage(subject: String, content: String, receivers: List<String>, files: Map<String, String>): Map<String, Boolean> { override fun sendMessage(
subject: String,
content: String,
receivers: List<String>,
files: Map<String, String>
): Map<String, Boolean> {
val result = mutableMapOf<String, Boolean>() val result = mutableMapOf<String, Boolean>()
this.check() this.check()
receivers.forEach { receiver -> receivers.forEach { receiver ->
result[receiver] = true result[receiver] = true
if (this.mailSender == null)
throw Exception("没有配置JavaMailSender的实现实例请重新配置")
try { try {
val mail = mailSender.createMimeMessage() val mail = this.mailSender!!.createMimeMessage()
val mimeMessageHelper = MimeMessageHelper(mail, true, "utf-8") val mimeMessageHelper = MimeMessageHelper(mail, true, "utf-8")
mimeMessageHelper.setFrom(sender) mimeMessageHelper.setFrom(sender)
mimeMessageHelper.setTo(receiver) mimeMessageHelper.setTo(receiver)
@@ -59,7 +63,7 @@ class EmailMessenger : IEmailMessenger {
val file = FileSystemResource(File(path)) val file = FileSystemResource(File(path))
mimeMessageHelper.addAttachment(name, file) mimeMessageHelper.addAttachment(name, file)
} }
mailSender.send(mail) //发送 this.mailSender!!.send(mail) //发送
} catch (e: Exception) { } catch (e: Exception) {
logger.error(e, "发送邮件[$subject]至地址[$receiver]失败") logger.error(e, "发送邮件[$subject]至地址[$receiver]失败")
result[receiver] = false result[receiver] = false

View File

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

View File

@@ -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<Method> = bean.javaClass.methods
for (method in methods) {
// check the annotations on that method
val annotations: Array<Annotation> = 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
}
}

View File

@@ -48,7 +48,7 @@ class Logger : ILogger {
override fun trace(format: String, vararg args: Any) { override fun trace(format: String, vararg args: Any) {
if (this.logger.isTraceEnabled) { 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) this.logger.trace(message)
} }
} }
@@ -56,7 +56,7 @@ class Logger : ILogger {
override fun trace(t: Throwable, format: String, vararg args: Any) { override fun trace(t: Throwable, format: String, vararg args: Any) {
if (this.logger.isTraceEnabled) { 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) this.logger.trace(message, t)
} }
} }
@@ -64,7 +64,7 @@ class Logger : ILogger {
override fun trace(obj: Any, format: String, vararg args: Any) { override fun trace(obj: Any, format: String, vararg args: Any) {
if (this.logger.isTraceEnabled) { if (this.logger.isTraceEnabled) {
val real = this.getLogger(obj) 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) real.trace(message)
} }
} }
@@ -72,7 +72,7 @@ class Logger : ILogger {
override fun trace(obj: Any, t: Throwable?, format: String, vararg args: Any) { override fun trace(obj: Any, t: Throwable?, format: String, vararg args: Any) {
if (this.logger.isTraceEnabled) { if (this.logger.isTraceEnabled) {
val real = this.getLogger(obj) 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) real.trace(message, t)
} }
} }
@@ -95,7 +95,7 @@ class Logger : ILogger {
override fun debug(format: String, vararg args: Any) { override fun debug(format: String, vararg args: Any) {
if (this.logger.isDebugEnabled) { 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) this.logger.debug(message)
} }
} }
@@ -103,7 +103,7 @@ class Logger : ILogger {
override fun debug(t: Throwable, format: String, vararg args: Any) { override fun debug(t: Throwable, format: String, vararg args: Any) {
if (this.logger.isDebugEnabled) { 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) this.logger.debug(message, t)
} }
} }
@@ -111,7 +111,7 @@ class Logger : ILogger {
override fun debug(obj: Any, format: String, vararg args: Any) { override fun debug(obj: Any, format: String, vararg args: Any) {
if (this.logger.isDebugEnabled) { if (this.logger.isDebugEnabled) {
val real = this.getLogger(obj) 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) real.debug(message)
} }
} }
@@ -119,7 +119,7 @@ class Logger : ILogger {
override fun debug(obj: Any, t: Throwable?, format: String, vararg args: Any) { override fun debug(obj: Any, t: Throwable?, format: String, vararg args: Any) {
if (this.logger.isDebugEnabled) { if (this.logger.isDebugEnabled) {
val real = this.getLogger(obj) 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) real.debug(message, t)
} }
} }
@@ -140,7 +140,7 @@ class Logger : ILogger {
override fun info(format: String, vararg args: Any) { override fun info(format: String, vararg args: Any) {
if (this.logger.isInfoEnabled) { 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) this.logger.info(message)
} }
} }
@@ -148,7 +148,7 @@ class Logger : ILogger {
override fun info(t: Throwable, format: String, vararg args: Any) { override fun info(t: Throwable, format: String, vararg args: Any) {
if (this.logger.isInfoEnabled) { 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) this.logger.info(message, t)
} }
} }
@@ -156,7 +156,7 @@ class Logger : ILogger {
override fun info(obj: Any, format: String, vararg args: Any) { override fun info(obj: Any, format: String, vararg args: Any) {
if (this.logger.isInfoEnabled) { if (this.logger.isInfoEnabled) {
val real = this.getLogger(obj) 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) real.info(message)
} }
} }
@@ -164,7 +164,7 @@ class Logger : ILogger {
override fun info(obj: Any, t: Throwable?, format: String, vararg args: Any) { override fun info(obj: Any, t: Throwable?, format: String, vararg args: Any) {
if (this.logger.isInfoEnabled) { if (this.logger.isInfoEnabled) {
val real = this.getLogger(obj) 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) real.info(message, t)
} }
} }
@@ -185,7 +185,7 @@ class Logger : ILogger {
override fun warn(format: String, vararg args: Any) { override fun warn(format: String, vararg args: Any) {
if (this.logger.isWarnEnabled) { 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) this.logger.warn(message)
} }
} }
@@ -193,7 +193,7 @@ class Logger : ILogger {
override fun warn(t: Throwable, format: String, vararg args: Any) { override fun warn(t: Throwable, format: String, vararg args: Any) {
if (this.logger.isWarnEnabled) { 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) this.logger.warn(message, t)
} }
} }
@@ -201,7 +201,7 @@ class Logger : ILogger {
override fun warn(obj: Any, format: String, vararg args: Any) { override fun warn(obj: Any, format: String, vararg args: Any) {
if (this.logger.isWarnEnabled) { if (this.logger.isWarnEnabled) {
val real = this.getLogger(obj) 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) real.warn(message)
} }
} }
@@ -209,7 +209,7 @@ class Logger : ILogger {
override fun warn(obj: Any, t: Throwable?, format: String, vararg args: Any) { override fun warn(obj: Any, t: Throwable?, format: String, vararg args: Any) {
if (this.logger.isWarnEnabled) { if (this.logger.isWarnEnabled) {
val real = this.getLogger(obj) 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) real.warn(message, t)
} }
} }
@@ -232,7 +232,7 @@ class Logger : ILogger {
override fun error(format: String, vararg args: Any) { override fun error(format: String, vararg args: Any) {
if (this.logger.isErrorEnabled) { 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) this.logger.error(message)
} }
} }
@@ -240,7 +240,7 @@ class Logger : ILogger {
override fun error(t: Throwable, format: String, vararg args: Any) { override fun error(t: Throwable, format: String, vararg args: Any) {
if (this.logger.isErrorEnabled) { 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) this.logger.error(message, t)
} }
} }
@@ -248,7 +248,7 @@ class Logger : ILogger {
override fun error(obj: Any, format: String, vararg args: Any) { override fun error(obj: Any, format: String, vararg args: Any) {
if (this.logger.isErrorEnabled) { if (this.logger.isErrorEnabled) {
val real = this.getLogger(obj) 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) real.error(message)
} }
} }
@@ -256,7 +256,7 @@ class Logger : ILogger {
override fun error(obj: Any, t: Throwable?, format: String, vararg args: Any) { override fun error(obj: Any, t: Throwable?, format: String, vararg args: Any) {
if (this.logger.isErrorEnabled) { if (this.logger.isErrorEnabled) {
val real = this.getLogger(obj) 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) real.error(message, t)
} }
} }

View File

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

View File

@@ -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

View File

@@ -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<out AnnotationHandler>)

View File

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

View File

@@ -0,0 +1,8 @@
package com.synebula.gaea.app.component.aop.annotation
/**
* 模块的业务名称
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ModuleName(val value: String)

View File

@@ -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<Any>, func: Method, args: Array<Any>, 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
)
}"
)
}
}

View File

@@ -0,0 +1,7 @@
package com.synebula.gaea.app.component.aop.handler
import java.lang.reflect.Method
interface AnnotationHandler {
fun handle(clazz: Class<Any>, func: Method, args: Array<Any>, exception: Exception? = null)
}

View File

@@ -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<Pair<String, String>>,
rowStart: Int = 0,
columnStart: Int = 0,
emptyRowFilter: (row: Row) -> Boolean =
{ row ->
row.getCell(
0,
Row.MissingCellPolicy.RETURN_NULL_AND_BLANK
) == null
}
): List<Map<String, Any?>> {
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<Map<String, Any?>>()
for (r in rowStart..sheet.lastRowNum) {
val row = sheet.getRow(r) ?: continue
if (emptyRowFilter(row)) continue //空行不处理
val rowData = mutableMapOf<String, Any?>()
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<Map<String, String>> {
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<String>()
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<Map<String, String>>()
for (r in (rowStartIndex + 1)..sheet.lastRowNum) {
val row = sheet.getRow(r) ?: continue
if (emptyRowFilter(row)) continue //空行不处理
val rowData = mutableMapOf<String, String>()
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("匹配类型错误")
}
}
}

View File

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

View File

@@ -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 <T> verify(token: String, clazz: Class<T>): 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
}
}

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
package com.synebula.gaea.app.query package com.synebula.gaea.app.query
import com.synebula.gaea.app.IApplication 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.data.message.Status import com.synebula.gaea.app.struct.HttpMessage
import com.synebula.gaea.query.IQuery import com.synebula.gaea.query.IQuery
import com.synebula.gaea.query.Params import com.synebula.gaea.query.Params
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
@@ -13,54 +13,38 @@ interface IQueryApp<TView, TKey> : IApplication {
/** /**
* 查询服务 * 查询服务
*/ */
var query: IQuery? var query: IQuery
/** /**
* 查询的View类型 * 查询的View类型
*/ */
var clazz: Class<TView> var clazz: Class<TView>
@MethodName("获取数据")
@GetMapping("/{id:.+}") @GetMapping("/{id:.+}")
fun get(@PathVariable id: TKey): HttpMessage { fun get(@PathVariable id: TKey): HttpMessage {
return this.doQuery("获取${this.name}数据失败") { val data = this.query.get(id, clazz)
this.query!!.get(id, clazz) val msg = HttpMessage()
} msg.data = data
return msg
} }
@MethodName("获取列表数据")
@GetMapping @GetMapping
fun list(@RequestParam params: LinkedHashMap<String, Any>): HttpMessage { fun list(@RequestParam params: LinkedHashMap<String, Any>): HttpMessage {
return this.doQuery("获取${this.name}列表数据失败") { val data = this.query.list(params, clazz)
this.query!!.list(params, clazz) return HttpMessage(data)
}
} }
@MethodName("获取分页数据")
@GetMapping("/segments/{size}/pages/{page}") @GetMapping("/segments/{size}/pages/{page}")
fun paging( fun paging(
@PathVariable size: Int, @PathVariable size: Int,
@PathVariable page: Int, @PathVariable page: Int,
@RequestParam parameters: LinkedHashMap<String, Any> @RequestParam parameters: LinkedHashMap<String, Any>
): HttpMessage { ): HttpMessage {
return this.doQuery("获取${this.name}分页数据[条数:$size,页码:$page]失败") { val params = Params(page, size, parameters)
val data = Params(page, size, parameters) val data = this.query.paging(params, clazz)
this.query!!.paging(data, clazz) return HttpMessage(data)
}
}
/**
* 抽取查询业务判断功能
*
* @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 = "没有对应服务,无法执行该操作"
}
}
} }
} }

View File

@@ -13,6 +13,6 @@ import com.synebula.gaea.query.IQuery
open class QueryApp<TView, TKey>( open class QueryApp<TView, TKey>(
override var name: String, override var name: String,
override var clazz: Class<TView>, override var clazz: Class<TView>,
override var query: IQuery?, override var query: IQuery,
override var logger: ILogger? override var logger: ILogger?
) : IQueryApp<TView, TKey> ) : IQueryApp<TView, TKey>

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.app.component.poi.excel package com.synebula.gaea.app.struct
class ExcelData(var title: String = "", class ExcelData(var title: String = "",
var columnNames: List<String> = listOf(), var columnNames: List<String> = listOf(),

View File

@@ -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 import com.synebula.gaea.data.message.DataMessage
class HttpMessage() : DataMessage<Any>() { class HttpMessage() : DataMessage<Any>() {
@@ -22,4 +23,8 @@ class HttpMessage() : DataMessage<Any>() {
this.message = msg.message this.message = msg.message
this.data = msg.data this.data = msg.data
} }
override fun toString(): String {
return Gson().toJson(this)
}
} }

View File

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

View File

@@ -42,8 +42,12 @@ fun Query.where(
//日期类型特殊处理为String类型 //日期类型特殊处理为String类型
val fieldType = onFieldType(key) val fieldType = onFieldType(key)
if (fieldType != null && value.javaClass != fieldType && fieldType == Date::class.java) { if (fieldType != null && value.javaClass != fieldType) {
value = DateTime(value.toString(), "yyyy-MM-dd HH:mm:ss").date 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) val where = onWhere(key)

View File

@@ -20,8 +20,8 @@ open class MongoRepository(private var repo: MongoTemplate) : IRepository {
override fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> get( override fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> get(
id: TKey, id: TKey,
clazz: Class<TAggregateRoot> clazz: Class<TAggregateRoot>
): TAggregateRoot { ): TAggregateRoot? {
return this.repo.findOne(whereId(id), clazz) as TAggregateRoot return this.repo.findOne(whereId(id), clazz)
} }
override fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> update( override fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> update(
@@ -35,6 +35,9 @@ open class MongoRepository(private var repo: MongoTemplate) : IRepository {
this.repo.save(obj) this.repo.save(obj)
} }
override fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> add(obj: List<TAggregateRoot>, clazz: Class<TAggregateRoot>) {
this.repo.insert(obj, clazz)
}
override fun <TAggregateRoot> count(params: Map<String, Any>?, clazz: Class<TAggregateRoot>): Int { override fun <TAggregateRoot> count(params: Map<String, Any>?, clazz: Class<TAggregateRoot>): Int {
val query = Query() val query = Query()

View File

@@ -1,6 +1,6 @@
package com.synebula.gaea.data.cache package com.synebula.gaea.data.cache
import java.util.Date import java.util.*
/** /**
* *

View File

@@ -1,7 +1,7 @@
package com.synebula.gaea.data.code package com.synebula.gaea.data.code
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.*
/** /**
* @author alex * @author alex

View File

@@ -1,6 +1,6 @@
package com.synebula.gaea.data.code package com.synebula.gaea.data.code
import java.util.Random import java.util.*
/** /**
* 固定长度随机编号生成。 * 固定长度随机编号生成。

View File

@@ -1,6 +1,6 @@
package com.synebula.gaea.data.code package com.synebula.gaea.data.code
import java.util.UUID import java.util.*
/** /**
* 全球唯一编号生成。 * 全球唯一编号生成。

View File

@@ -1,6 +1,8 @@
package com.synebula.gaea.data.date package com.synebula.gaea.data.date
import java.util.* import java.util.*
import kotlin.math.ceil
import kotlin.math.floor
/** /**
* 校准时间。 * 校准时间。
@@ -61,9 +63,9 @@ class AlignTime {
*/ */
fun ceilingTime(lastTime: DateTime, intervalSeconds: Int): DateTime { fun ceilingTime(lastTime: DateTime, intervalSeconds: Int): DateTime {
val span = lastTime - this.baseTime 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) val newTime = DateTime(this.baseTime.date)
newTime.addSeconds(count * intervalSeconds * 1L) newTime.addSecond(count * intervalSeconds)
return newTime return newTime
} }
@@ -97,9 +99,9 @@ class AlignTime {
*/ */
fun floorTime(lastTime: DateTime, intervalSeconds: Int): DateTime { fun floorTime(lastTime: DateTime, intervalSeconds: Int): DateTime {
val span = lastTime - this.baseTime 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) val newTime = DateTime(this.baseTime.date)
newTime.addSeconds(count * intervalSeconds * 1L) newTime.addSecond(count * intervalSeconds)
return newTime return newTime
} }
} }

View File

@@ -7,7 +7,7 @@ import java.util.*
/** /**
* 时间格式方便用于获取Date格式的多种形式 * 时间格式方便用于获取Date格式的多种形式
*/ */
class DateTime : Comparable<DateTime> { class DateTime() : Comparable<DateTime> {
// 内部存储日历格式方便操作 // 内部存储日历格式方便操作
/** /**
@@ -25,7 +25,15 @@ class DateTime : Comparable<DateTime> {
/** /**
* 列出时间的级别数组 * 列出时间的级别数组
*/ */
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 val date: Date
get() = calendar.time get() = calendar.time
@@ -43,60 +51,169 @@ class DateTime : Comparable<DateTime> {
return instance.time return instance.time
} }
/**
* 当前时间年
*/
var year: Int var year: Int
get() = calendar.get(Calendar.YEAR) get() = calendar.get(Calendar.YEAR)
set(value) = calendar.set(Calendar.YEAR, value) set(value) = calendar.set(Calendar.YEAR, value)
/**
* 当前时间月
*/
var month: Int var month: Int
get() = calendar.get(Calendar.MONTH) get() = calendar.get(Calendar.MONTH)
set(value) = calendar.set(Calendar.MONTH, value) set(value) = calendar.set(Calendar.MONTH, value)
/**
* 当前时间天
*/
var day: Int var day: Int
get() = calendar.get(Calendar.DAY_OF_MONTH) get() = calendar.get(Calendar.DAY_OF_MONTH)
set(value) = calendar.set(Calendar.DAY_OF_MONTH, value) set(value) = calendar.set(Calendar.DAY_OF_MONTH, value)
/**
* 获取时间
*/
val time: Time val time: Time
get() = Time(calendar.time) get() = Time(calendar.time)
val firstDay: DateTime /**
* 获取当月天数
*/
val days: Int
get() {
return this.calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
}
/**
* 当前月第一天
*/
val firstDayOfMonth: DateTime
get() { get() {
val instance = calendar.clone() as Calendar val instance = calendar.clone() as Calendar
instance.set(Calendar.DAY_OF_MONTH, 1) instance.set(Calendar.DAY_OF_MONTH, 1)
return DateTime(instance) return DateTime(instance)
} }
val lastDay: DateTime /**
* 当前月最后一天
*/
val lastDayOfMonth: DateTime
get() { get() {
val instance = calendar.clone() as Calendar val instance = calendar.clone() as Calendar
instance.set(Calendar.DAY_OF_MONTH, instance.getActualMaximum(Calendar.DAY_OF_MONTH)) instance.set(Calendar.DAY_OF_MONTH, instance.getActualMaximum(Calendar.DAY_OF_MONTH))
return DateTime(instance) 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)
}
/** /**
* 从ate格式转化 * 从ate格式转化
*/ */
constructor(date: Date) { constructor(date: Date) : this() {
this.calendar.time = date this.calendar.time = date
this.calendar.set(Calendar.MILLISECOND, 0)
} }
/** /**
* 从Calendar格式转化 * 从Calendar格式转化
*/ */
constructor(calendar: Calendar) { constructor(calendar: Calendar) : this() {
this.calendar = calendar this.calendar = calendar
this.calendar.set(Calendar.MILLISECOND, 0)
} }
/** /**
* 从ate格式转化 * 从ate格式转化
*/ */
constructor(date: String, format: String) { constructor(date: String, format: String = "yyyy-MM-dd HH:mm:ss") : this() {
val formatter = SimpleDateFormat(format) val formatter = SimpleDateFormat(format)
try { try {
val value = formatter.parse(date) val value = formatter.parse(date)
this.calendar.time = value this.calendar.time = value
this.calendar.set(Calendar.MILLISECOND, 0)
} catch (e: ParseException) { } catch (e: ParseException) {
throw RuntimeException("date string can't format to date", e) throw RuntimeException("date string can't format to date", e)
} }
@@ -105,9 +222,8 @@ class DateTime : Comparable<DateTime> {
/** /**
* 从ate格式转化。需要注意的是月0代表是一月以此类推。 * 从ate格式转化。需要注意的是月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(year, month, day, hour, minute, second)
this.calendar.set(Calendar.MILLISECOND, 0)
} }
/** /**
@@ -156,10 +272,11 @@ class DateTime : Comparable<DateTime> {
* 比较当前时间是否在目标时间范围内。 * 比较当前时间是否在目标时间范围内。
* @param start 目标开始时间。 * @param start 目标开始时间。
* @param end 目标结束时间。 * @param end 目标结束时间。
* @return 是否。 * @return true or false
*/ */
fun between(start: DateTime, end: DateTime): Boolean { fun isBetween(start: DateTime, end: DateTime): Boolean {
return this in start..end //return this in start..end
return start.dateNoTime.compareTo(this.dateNoTime) * this.dateNoTime.compareTo(end.dateNoTime) >= 0
} }
/** /**
@@ -167,27 +284,234 @@ class DateTime : Comparable<DateTime> {
* @param start 目标开始时间。 * @param start 目标开始时间。
* @param end 目标结束时间。 * @param end 目标结束时间。
* @param level 比较时间的最小级别。 * @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 return this.compareTo(start, level) >= 0 && this.compareTo(end, level) <= 0
} }
/** /**
* 增加秒 * 判断当前时间是否在某时间后
*
* @param other 另一时间
* @return true or false
*/ */
fun addSeconds(seconds: Long) { fun isAfter(other: DateTime): Boolean {
if (seconds <= Int.MAX_VALUE) return this.date.after(other.date)
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)
}
} }
/**
* 判断当前时间是否在某时间前
*
* @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 { operator fun minus(other: DateTime): TimeSpan {
return TimeSpan(this.milliseconds - other.milliseconds) return TimeSpan(this.milliseconds - other.milliseconds)
} }

View File

@@ -1,10 +1,8 @@
package com.synebula.gaea.data.date package com.synebula.gaea.data.date
import java.math.BigDecimal
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.*
import java.util.Date
/** /**

View File

@@ -6,9 +6,6 @@
*/ */
package com.synebula.gaea.data.date package com.synebula.gaea.data.date
import java.math.BigDecimal
import java.util.*
/** /**
* *
* @author whj * @author whj

View File

@@ -9,9 +9,7 @@ open class Message {
/** /**
* 获取状态是否成功 * 获取状态是否成功
*/ */
val success: Boolean fun success(): Boolean = this.status == Status.Success
get() = this.status == Status.Success
/** /**
* 附带提示消息 * 附带提示消息

View File

@@ -6,11 +6,21 @@ object Status {
*/ */
const val Success = 200 const val Success = 200
/**
* 重新授权
*/
const val Reauthorize = 205
/** /**
* 失败 * 失败
*/ */
const val Failure = 400 const val Failure = 400
/**
* 未授权
*/
const val Unauthorized = 401
/** /**
* 错误 * 错误
*/ */

View File

@@ -15,6 +15,14 @@ interface IRepository {
*/ */
fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> add(obj: TAggregateRoot, clazz: Class<TAggregateRoot>) fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> add(obj: TAggregateRoot, clazz: Class<TAggregateRoot>)
/**
* 插入多个个对象。
*
* @param obj 需要插入的对象。
* @return 返回原对象如果对象ID为自增则补充自增ID。
*/
fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> add(obj: List<TAggregateRoot>, clazz: Class<TAggregateRoot>)
/** /**
* 更新对象。 * 更新对象。
* *
@@ -38,7 +46,7 @@ interface IRepository {
* @param clazz 操作数据的类型 * @param clazz 操作数据的类型
* @return 聚合根 * @return 聚合根
*/ */
fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> get(id: TKey, clazz: Class<TAggregateRoot>): TAggregateRoot fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> get(id: TKey, clazz: Class<TAggregateRoot>): TAggregateRoot?
/** /**

View File

@@ -1,7 +1,5 @@
package com.synebula.gaea.domain.service package com.synebula.gaea.domain.service
import java.util.*
/** /**
* 命令基础实现类发给service的命令。 * 命令基础实现类发给service的命令。
* *

View File

@@ -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<TAggregateRoot : IAggregateRoot<TKey>, TKey> {
/**
* 日志组件。
*/
var logger: ILogger
fun add(root: TAggregateRoot): DataMessage<TKey>
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)
}

View File

@@ -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<TAggregateRoot : IAggregateRoot<TKey>, TKey>(
protected open var clazz: Class<TAggregateRoot>,
protected open var repository: IRepository,
override var logger: ILogger
) : ILazyService<TAggregateRoot, TKey> {
/**
* 删除对象前执行监听器。
*/
protected val beforeRemoveListeners = mutableMapOf<String, (id: TKey) -> 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<TKey> {
val msg = DataMessage<TKey>()
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)
}
}

View File

@@ -68,8 +68,8 @@ open class Service<TAggregateRoot : IAggregateRoot<TKey>, TKey>(
var msg: Message var msg: Message
for (func in functions) { for (func in functions) {
msg = func(id) msg = func(id)
if (!msg.success) { if (!msg.success()) {
throw java.lang.RuntimeException(msg.message) throw IllegalStateException(msg.message)
} }
} }
this.repository.remove(id, this.clazz) this.repository.remove(id, this.clazz)

View File

@@ -0,0 +1,4 @@
package com.synebula.gaea.event
interface IEvent {
}

View File

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

View File

@@ -0,0 +1,6 @@
package com.synebula.gaea.exception
/**
* 需要通知给用户的异常
*/
class NoticeUserException(message: String, cause: Exception? = null) : Exception(message, cause)

View File

@@ -2,8 +2,7 @@ package com.synebula.gaea.io.scan
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
import java.util.Enumeration import java.util.*
import java.util.HashSet
/** /**
* *

View File

@@ -5,9 +5,7 @@ import java.io.FileFilter
import java.io.UnsupportedEncodingException import java.io.UnsupportedEncodingException
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.charset.Charset import java.nio.charset.Charset
import java.util.Collections import java.util.*
import java.util.HashSet
import java.util.LinkedList
import java.util.jar.JarFile import java.util.jar.JarFile
/** /**

View File

@@ -11,8 +11,8 @@ import com.synebula.gaea.query.type.Order
*/ */
data class Params(var page: Int = 1, var size: Int = 10) { data class Params(var page: Int = 1, var size: Int = 10) {
private var _parameters = mutableMapOf<String, Any>() private var _parameters = linkedMapOf<String, Any>()
private var _orders = mutableMapOf<String, Order>() private var _orders = linkedMapOf<String, Order>()
/** /**
* 数据索引从0开始。表示数据在总量的第几条。index = (page - 1) * size * 数据索引从0开始。表示数据在总量的第几条。index = (page - 1) * size
@@ -24,13 +24,13 @@ data class Params(var page: Int = 1, var size: Int = 10) {
/** /**
* 排序条件。 * 排序条件。
*/ */
var orders: Map<String, Order> var orders: LinkedHashMap<String, Order>
set(value) { set(value) {
this._orders = value.toMutableMap() this._orders = value
} }
get() { get() {
if (this._parameters.keys.count { it.startsWith("@") } > 0) { if (this._parameters.keys.count { it.startsWith("@") } > 0) {
val params = mutableMapOf<String, Any>() val params = linkedMapOf<String, Any>()
this._parameters.forEach { this._parameters.forEach {
if (it.key.startsWith("@")) { if (it.key.startsWith("@")) {
this._orders[it.key.removePrefix("@")] = Order.valueOf(it.value.toString()) 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<String, Any> var parameters: LinkedHashMap<String, Any>
set(value) { set(value) {
this._parameters = value.toMutableMap() this._parameters = value
} }
get() { get() {
if (this._parameters.keys.count { it.startsWith("@") } > 0) { if (this._parameters.keys.count { it.startsWith("@") } > 0) {
val params = mutableMapOf<String, Any>() val params = linkedMapOf<String, Any>()
this._parameters.forEach { this._parameters.forEach {
if (it.key.startsWith("@")) { if (it.key.startsWith("@")) {
this._orders[it.key.removePrefix("@")] = Order.valueOf(it.value.toString()) this._orders[it.key.removePrefix("@")] = Order.valueOf(it.value.toString())