0.2.1 增加gaea.app gaea.mongo模块功能
This commit is contained in:
30
build.gradle
30
build.gradle
@@ -15,31 +15,31 @@ buildscript {
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group 'com.synebula'
|
||||
version version
|
||||
}
|
||||
|
||||
subprojects {
|
||||
ext {
|
||||
version '0.2.0'
|
||||
version '0.2.1'
|
||||
spring_version = "2.0.0.RELEASE"
|
||||
}
|
||||
|
||||
group 'com.synebula'
|
||||
version project.version
|
||||
|
||||
apply plugin: 'idea'
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven { url 'http://maven.aliyuMongoRepositoryn.com/nexus/content/groups/public/' }
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'maven'
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
rootProject.name = 'gaea.project'
|
||||
include 'src:gaea'
|
||||
findProject(':src:gaea')?.name = 'gaea'
|
||||
include 'src:gaea.domain'
|
||||
findProject(':src:gaea.domain')?.name = 'gaea.domain'
|
||||
include 'src:gaea.app'
|
||||
findProject(':src:gaea.app')?.name = 'gaea.app'
|
||||
include 'src:gaea.mongo'
|
||||
findProject(':src:gaea.mongo')?.name = 'gaea.mongo'
|
||||
|
||||
|
||||
18
src/gaea.app/build.gradle
Normal file
18
src/gaea.app/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
dependencies {
|
||||
compile project(":src:gaea")
|
||||
compile("org.springframework.boot:spring-boot-starter-web:$spring_version")
|
||||
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
|
||||
compile group: 'net.sf.dozer', name: 'dozer', version: '5.5.1'
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
group 'com.synebula'
|
||||
artifactId 'gaea.app'
|
||||
version "$version"
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.synebula.gaea.app
|
||||
|
||||
import com.synebula.gaea.app.components.HttpMessage
|
||||
import com.synebula.gaea.data.message.Status
|
||||
import com.synebula.gaea.log.ILogger
|
||||
|
||||
interface IApplication {
|
||||
|
||||
/**
|
||||
* 业务名称
|
||||
*/
|
||||
var name: String
|
||||
|
||||
/**
|
||||
* 日志组件
|
||||
*/
|
||||
var logger: ILogger
|
||||
|
||||
|
||||
/**
|
||||
* 安全执行
|
||||
*/
|
||||
fun safeExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage {
|
||||
val msg = HttpMessage(Status.Success)
|
||||
try {
|
||||
process(msg)
|
||||
logger.debug("$name business execute success")
|
||||
} catch (ex: Exception) {
|
||||
msg.status = Status.Error
|
||||
msg.message = if (error.isEmpty()) ex.message ?: "" else error
|
||||
msg.data = ex.message
|
||||
logger.error(ex, "$error: ${ex.message}")
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
/**
|
||||
* 可抛出自定义异常信息的安全controller实现了异常捕获和消息组成。
|
||||
*/
|
||||
fun throwExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage {
|
||||
val msg = HttpMessage(Status.Success)
|
||||
try {
|
||||
process(msg)
|
||||
logger.debug("$name business execute success")
|
||||
} catch (ex: Exception) {
|
||||
logger.error(ex, "$error。异常消息将抛出!: ${ex.message}")
|
||||
throw RuntimeException(error, ex)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.synebula.gaea.app
|
||||
|
||||
import com.synebula.gaea.app.components.HttpMessage
|
||||
import com.synebula.gaea.data.message.Status
|
||||
import com.synebula.gaea.domain.service.ICommand
|
||||
import com.synebula.gaea.domain.service.IService
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
/**
|
||||
* 应用类接口,提供向Command服务的接口
|
||||
*
|
||||
* @author alex
|
||||
* @version 0.1
|
||||
* @since 2018 18-2-8
|
||||
*/
|
||||
interface ICommandApp<TCommand : ICommand, TKey> : IApplication {
|
||||
|
||||
var service: IService<TKey>?
|
||||
|
||||
@PostMapping
|
||||
fun add(@RequestBody command: TCommand): HttpMessage {
|
||||
return this.throwExecute("${this.name}添加失败") {
|
||||
if (this.service != null) {
|
||||
val msg = this.service!!.add(command)
|
||||
it.load(msg)
|
||||
} else {
|
||||
it.status = Status.Error
|
||||
it.message = "没有对应服务,无法执行该操作"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/{key:.+}")
|
||||
fun remove(@PathVariable key: TKey): HttpMessage {
|
||||
return this.throwExecute("${this.name}删除失败") {
|
||||
if (this.service != null)
|
||||
it.data = this.service!!.remove(key)
|
||||
else {
|
||||
it.status = Status.Error
|
||||
it.message = "没有对应服务,无法执行该操作"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/{key:.+}")
|
||||
fun update(@PathVariable key: TKey, @RequestBody command: TCommand): HttpMessage {
|
||||
return this.throwExecute("${this.name}更新失败") {
|
||||
if (this.service != null)
|
||||
this.service!!.update(key, command)
|
||||
else {
|
||||
it.status = Status.Error
|
||||
it.message = "没有对应服务,无法执行该操作"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/gaea.app/src/main/kotlin/com/synebula/gaea/app/ILogin.kt
Normal file
15
src/gaea.app/src/main/kotlin/com/synebula/gaea/app/ILogin.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.synebula.gaea.app
|
||||
|
||||
import com.synebula.gaea.data.message.Message
|
||||
|
||||
|
||||
interface ILogin {
|
||||
/**
|
||||
* 定义登录方法。
|
||||
*
|
||||
* @param name 登录名
|
||||
* @param password 登录密码
|
||||
* @return StatusMessage, data 内容为 map 其中 key account中存储用户账户名称
|
||||
*/
|
||||
fun login(name: String, password: String): Message<Any>
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.synebula.gaea.app
|
||||
|
||||
import com.synebula.gaea.app.components.HttpMessage
|
||||
import com.synebula.gaea.data.message.Status
|
||||
import com.synebula.gaea.query.IQuery
|
||||
import com.synebula.gaea.query.PagingParam
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
|
||||
/**
|
||||
* 应用类接口,提供向Query服务的接口
|
||||
*
|
||||
* @author alex
|
||||
* @version 0.1
|
||||
* @since 2018 18-2-8
|
||||
*/
|
||||
interface IQueryApp<TView, TKey> : IApplication {
|
||||
/**
|
||||
* 查询服务
|
||||
*/
|
||||
var query: IQuery<TView, TKey>?
|
||||
|
||||
@GetMapping("/{key:.+}")
|
||||
fun get(@PathVariable key: TKey): HttpMessage {
|
||||
return this.safeExecute("${this.name}获取数据失败") {
|
||||
if (this.query != null)
|
||||
it.data = this.query!!.get(key)
|
||||
else {
|
||||
it.status = Status.Error
|
||||
it.message = "没有对应服务,无法执行该操作"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
fun list(@RequestParam parameters: MutableMap<String, Any>): HttpMessage {
|
||||
return this.safeExecute("${this.name}获取数据失败") {
|
||||
if (this.query != null)
|
||||
it.data = this.query!!.list(parameters)
|
||||
else {
|
||||
it.status = Status.Error
|
||||
it.message = "没有对应服务,无法执行该操作"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/split/{size}/pages/{page}")
|
||||
fun paging(@PathVariable page: Int, @PathVariable size: Int, @RequestParam parameters: MutableMap<String, Any>): HttpMessage {
|
||||
return this.safeExecute("${this.name}获取分页数据失败") {
|
||||
if (this.query != null) {
|
||||
val params = PagingParam(page, size)
|
||||
params.parameters = parameters
|
||||
it.data = this.query!!.paging(params)
|
||||
} else {
|
||||
it.status = Status.Error
|
||||
it.message = "没有对应服务,无法执行该操作"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.synebula.gaea.app.components
|
||||
|
||||
import com.synebula.gaea.data.message.Status
|
||||
import com.synebula.gaea.data.message.Message
|
||||
|
||||
class HttpMessage(status: Int = Status.Success) : Message<Any>(status) {
|
||||
fun load(msg: Message<*>) {
|
||||
this.status = msg.status
|
||||
this.message = msg.message
|
||||
this.data = msg.data
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.synebula.gaea.domain.repository.specifications
|
||||
|
||||
/**
|
||||
* 逆反规约。
|
||||
*
|
||||
* @author alex
|
||||
*
|
||||
* @param <T>
|
||||
* 规约对象的类型。
|
||||
*/
|
||||
class NotSpecification<T>
|
||||
/**
|
||||
* 构造一个新的逆反规约对象。
|
||||
*
|
||||
* @param specification
|
||||
* 需要逆反的规约对象。
|
||||
*/
|
||||
(private val spec: ISpecification<T>) : Specification<T>() {
|
||||
|
||||
override fun isSatisfiedBy(obj: T): Boolean {
|
||||
return !spec.isSatisfiedBy(obj)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
dependencies {
|
||||
compile project(":src:gaea")
|
||||
compile("org.springframework.boot:spring-boot-starter-data-mongodb:$spring_version")
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
group 'com.synebula'
|
||||
artifactId 'gaea.domain'
|
||||
version project.version
|
||||
artifactId 'gaea.mongo'
|
||||
version "$version"
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.synebula.gaea.query
|
||||
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.data.mongodb.core.MongoTemplate
|
||||
import org.springframework.data.mongodb.core.query.Criteria
|
||||
import org.springframework.data.mongodb.core.query.Query
|
||||
import org.springframework.data.mongodb.core.query.isEqualTo
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.ParameterizedType
|
||||
|
||||
|
||||
open class MongoQuery<TView>(var collection: String, var repo: MongoTemplate) : IQuery<TView, String> {
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
protected val viewClass: Class<TView> = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<TView>
|
||||
|
||||
private val _systemClass = arrayOf(
|
||||
"String",
|
||||
"Date",
|
||||
"Int",
|
||||
"Double",
|
||||
"Float",
|
||||
"BigDecimal",
|
||||
"Decimal")
|
||||
|
||||
|
||||
override fun list(params: Map<String, Any>?): List<TView> {
|
||||
val viewFields = this.viewFields()
|
||||
val query = Query()
|
||||
this.where(query, params)
|
||||
this.select(query, viewFields.toTypedArray())
|
||||
return this.repo.find(query, this.viewClass, this.collection)
|
||||
}
|
||||
|
||||
override fun count(params: Map<String, Any>?): Int {
|
||||
val query = Query()
|
||||
return this.repo.count(where(query, params), this.collection).toInt()
|
||||
}
|
||||
|
||||
override fun paging(params: PagingParam): PagingData<TView> {
|
||||
val viewFields = this.viewFields()
|
||||
val result = PagingData<TView>(1, 10)
|
||||
result.size = params.size
|
||||
result.page = params.page
|
||||
val query = Query()
|
||||
this.where(query, params.parameters)
|
||||
result.total = this.repo.count(query, this.collection).toInt()
|
||||
this.select(query, viewFields.toTypedArray())
|
||||
query.with(order(params.orderBy))
|
||||
query.skip(params.index).limit(params.size)
|
||||
result.data = this.repo.find(query, this.viewClass, this.collection)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun get(key: String): TView? {
|
||||
return this.repo.findOne(Query.query(Criteria.where("_id").isEqualTo(key))
|
||||
, this.viewClass, this.collection)
|
||||
}
|
||||
|
||||
protected fun viewFields(): List<String> {
|
||||
return traversalFields(viewClass.declaredFields)
|
||||
}
|
||||
|
||||
private fun traversalFields(fields: Array<Field>): List<String> {
|
||||
val names = mutableListOf<String>()
|
||||
fields.forEach { field ->
|
||||
names.add(field.name)
|
||||
if (!field.type.isPrimitive
|
||||
&& !field.type.isArray
|
||||
&& !this._systemClass.contains(field.type.simpleName))
|
||||
names.addAll(this.traversalFields(field.type.declaredFields).map { "${field.name}.$it" })
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
protected open fun where(query: Query, params: Map<String, Any>?): Query {
|
||||
val criteria = Criteria()
|
||||
if (params != null) {
|
||||
for (param in params) {
|
||||
val value = this.convertFieldValueType(param.key, param.value)
|
||||
criteria.and(param.key).isEqualTo(value)
|
||||
}
|
||||
}
|
||||
return query.addCriteria(criteria)
|
||||
}
|
||||
|
||||
protected fun convertFieldValueType(key: String, value: Any): Any? {
|
||||
val getter = this.viewClass.getMethod("get${key.substring(0, 1).toUpperCase()}${key.substring(1)}")
|
||||
return this.convertClass(getter.returnType, value.toString())
|
||||
}
|
||||
|
||||
protected open fun select(query: Query, fields: Array<String>): Query {
|
||||
fields.forEach {
|
||||
query.fields().include(it)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
protected open fun order(orders: MutableMap<String, OrderType>?): Sort {
|
||||
val orderList = mutableListOf<Sort.Order>()
|
||||
orders?.forEach() {
|
||||
orderList.add(Sort.Order(Sort.Direction.valueOf(it.value.name), it.key))
|
||||
}
|
||||
return if (orderList.size == 0)
|
||||
Sort.by("_id")
|
||||
else
|
||||
Sort.by(orderList)
|
||||
}
|
||||
|
||||
private fun convertClass(type: Class<*>, value: String): Any? {
|
||||
|
||||
if (!type.isPrimitive) { // 判断基本类型
|
||||
if (type == String::class.java) { // 如果是string则直接返回
|
||||
return value
|
||||
}
|
||||
// 如果不为null 则通过反射实例一个对象返回
|
||||
return if ("" == value) null else type.getConstructor(String::class.java).newInstance(value)
|
||||
}
|
||||
|
||||
// 下面处理基本类型,返回包装类
|
||||
return when (type.name) {
|
||||
"int" -> Integer.parseInt(value)
|
||||
"byte" -> java.lang.Byte.parseByte(value)
|
||||
"boolean" -> java.lang.Boolean.parseBoolean(value)
|
||||
"double" -> java.lang.Double.parseDouble(value)
|
||||
"float" -> java.lang.Float.parseFloat(value)
|
||||
"long" -> java.lang.Long.parseLong(value)
|
||||
"short" -> java.lang.Short.parseShort(value)
|
||||
else -> value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.synebula.gaea.repository
|
||||
|
||||
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||
import com.synebula.gaea.domain.repository.ITypedRepository
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.data.mongodb.core.MongoTemplate
|
||||
import org.springframework.data.mongodb.core.query.Criteria
|
||||
import org.springframework.data.mongodb.core.query.Query
|
||||
import org.springframework.data.mongodb.core.query.isEqualTo
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
abstract class MongoRepository<TAggregateRoot : IAggregateRoot<String>> : ITypedRepository<TAggregateRoot, String> {
|
||||
|
||||
@Autowired
|
||||
private lateinit var repo: MongoTemplate
|
||||
|
||||
override fun remove(id: String, clazz: Class<TAggregateRoot>) {
|
||||
this.repo.remove(queryId(id), clazz)
|
||||
}
|
||||
|
||||
override fun get(id: String, clazz: Class<TAggregateRoot>): TAggregateRoot {
|
||||
return this.repo.findOne(queryId(id), clazz) as TAggregateRoot
|
||||
}
|
||||
|
||||
override fun update(obj: TAggregateRoot) {
|
||||
this.repo.save(obj)
|
||||
}
|
||||
|
||||
override fun add(obj: TAggregateRoot) {
|
||||
this.repo.save(obj)
|
||||
}
|
||||
|
||||
protected fun queryId(id: String): Query = Query(Criteria("_id").isEqualTo(id))
|
||||
}
|
||||
@@ -2,9 +2,9 @@ publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
group 'com.synebula'
|
||||
artifactId 'gaea.data'
|
||||
version project.version
|
||||
artifactId 'gaea'
|
||||
version "$version"
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,47 @@
|
||||
package com.synebula.gaea.data.message
|
||||
|
||||
import java.util.Date
|
||||
import java.util.*
|
||||
|
||||
open class Message<T> {
|
||||
/**
|
||||
*
|
||||
* 用来统一Http返回消息类型,通常使用json格式传递
|
||||
*
|
||||
* @param status http编码。200成功,400错误,500异常
|
||||
* @tparam T 消息数据类型
|
||||
*/
|
||||
open class Message<T>(var status: Int = Status.Success) {
|
||||
/**
|
||||
* 传递的业务数据
|
||||
*/
|
||||
var data: T? = null
|
||||
|
||||
/**
|
||||
* 附带提示消息
|
||||
*/
|
||||
var message = ""
|
||||
|
||||
/**
|
||||
* 消息时间戳
|
||||
*/
|
||||
val timestamp: Long = Date().time
|
||||
|
||||
constructor(data: T) : this(Status.Success) {
|
||||
this.data = data
|
||||
}
|
||||
|
||||
constructor(status: Int, message: String) : this(status) {
|
||||
this.message = message
|
||||
}
|
||||
|
||||
constructor(status: Int, data: T, message: String) : this(status) {
|
||||
this.data = data
|
||||
this.message = message
|
||||
}
|
||||
|
||||
open fun from(other: Message<T>) {
|
||||
this.status = other.status
|
||||
this.data = other.data
|
||||
this.message = other.message
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.synebula.gaea.data.message
|
||||
|
||||
object Status {
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
val Success = 200
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*/
|
||||
val Failure = 400
|
||||
|
||||
/**
|
||||
* 错误
|
||||
*/
|
||||
val Error = 500
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.synebula.gaea.data.message.http
|
||||
|
||||
import com.synebula.gaea.data.message.Message
|
||||
|
||||
/**
|
||||
*
|
||||
* 用来统一Http返回消息类型,通常使用json格式传递
|
||||
*
|
||||
* @param status http编码。200成功,400错误,500异常
|
||||
* @tparam T 消息数据类型
|
||||
*/
|
||||
class HttpMessage<T>(var status: Int = HttpStatus.Success.code) : Message<T>() {
|
||||
/**
|
||||
* 附带提示消息
|
||||
*/
|
||||
var message = ""
|
||||
|
||||
constructor(data: T) : this(HttpStatus.Success.code) {
|
||||
this.data = data
|
||||
}
|
||||
|
||||
constructor(status: Int, message: String) : this(status) {
|
||||
this.message = message
|
||||
}
|
||||
|
||||
constructor(status: Int, data: T, message: String) : this(status) {
|
||||
this.data = data
|
||||
this.message = message
|
||||
}
|
||||
|
||||
fun from(other: HttpMessage<T>) {
|
||||
this.status = other.status
|
||||
this.data = other.data
|
||||
this.message = message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.synebula.gaea.data.message.http
|
||||
|
||||
/**
|
||||
* 状态类型。
|
||||
*
|
||||
* @author alex
|
||||
* @version 0.0.1
|
||||
* @since 2016年9月6日 下午3:27:55
|
||||
*/
|
||||
enum class HttpStatus(val code: Int) {
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
Success(200),
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*/
|
||||
Failure(400),
|
||||
|
||||
/**
|
||||
* 错误
|
||||
*/
|
||||
Error(500);
|
||||
|
||||
companion object {
|
||||
fun valueOf(code: Int): HttpStatus {
|
||||
return when (code) {
|
||||
200 -> HttpStatus.Success
|
||||
400 -> HttpStatus.Failure
|
||||
else -> HttpStatus.Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@ interface ITypedRepository<TAggregateRoot : IAggregateRoot<TKey>, TKey> {
|
||||
* @param obj 需要插入的对象。
|
||||
* @return 返回原对象,如果对象ID为自增,则补充自增ID。
|
||||
*/
|
||||
fun add(obj: TAggregateRoot): Unit
|
||||
fun add(obj: TAggregateRoot)
|
||||
|
||||
/**
|
||||
* 更新对象。
|
||||
@@ -17,7 +17,7 @@ interface ITypedRepository<TAggregateRoot : IAggregateRoot<TKey>, TKey> {
|
||||
* @param obj 需要更新的对象。
|
||||
* @return
|
||||
*/
|
||||
fun update(obj: TAggregateRoot): Unit
|
||||
fun update(obj: TAggregateRoot)
|
||||
|
||||
/**
|
||||
* 通过id删除该条数据
|
||||
@@ -25,7 +25,7 @@ interface ITypedRepository<TAggregateRoot : IAggregateRoot<TKey>, TKey> {
|
||||
* @param id id
|
||||
* @param clazz 操作数据的类型
|
||||
*/
|
||||
fun remove(id: TKey, clazz: Class<TAggregateRoot>): Unit
|
||||
fun remove(id: TKey, clazz: Class<TAggregateRoot>)
|
||||
|
||||
/**
|
||||
* 根据ID获取对象。
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.synebula.gaea.domain.repository.specifications
|
||||
|
||||
/**
|
||||
* 逆反规约。
|
||||
*
|
||||
* @author alex
|
||||
*
|
||||
* @param <T> 规约对象的类型。
|
||||
*
|
||||
* @param spec 需要逆反的规约对象。
|
||||
*
|
||||
*/
|
||||
class NotSpecification<T>(private val spec: ISpecification<T>) : Specification<T>() {
|
||||
|
||||
override fun isSatisfiedBy(obj: T): Boolean {
|
||||
return !spec.isSatisfiedBy(obj)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.synebula.gaea.domain.service
|
||||
|
||||
import com.synebula.gaea.data.IObjectConverter
|
||||
import com.synebula.gaea.data.message.http.HttpMessage
|
||||
import com.synebula.gaea.data.message.Message
|
||||
import com.synebula.gaea.domain.model.complex.IComplexAggregateRoot
|
||||
import com.synebula.gaea.domain.repository.IComplexRepository
|
||||
import com.synebula.gaea.log.ILogger
|
||||
@@ -18,8 +18,8 @@ open class ComplexService<TAggregateRoot : IComplexAggregateRoot<TKey, TSecond>,
|
||||
protected var converter: IObjectConverter, protected var aggregateRootClass: Class<TAggregateRoot>)
|
||||
: IComplexService<TKey, TSecond> {
|
||||
|
||||
override fun add(command: ICommand): HttpMessage<Pair<TKey, TSecond>> {
|
||||
val msg = HttpMessage<Pair<TKey, TSecond>>()
|
||||
override fun add(command: ICommand): Message<Pair<TKey, TSecond>> {
|
||||
val msg = Message<Pair<TKey, TSecond>>()
|
||||
val root = this.convert(command)
|
||||
repository.add(root)
|
||||
msg.data = Pair<TKey, TSecond>(root.id, root.secondary)
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.synebula.gaea.domain.service
|
||||
|
||||
import com.synebula.gaea.data.message.http.HttpMessage
|
||||
import com.synebula.gaea.data.message.Message
|
||||
|
||||
/**
|
||||
* class IFlatService
|
||||
@@ -11,7 +11,7 @@ import com.synebula.gaea.data.message.http.HttpMessage
|
||||
*/
|
||||
interface IComplexService<TKey, TSecond> {
|
||||
|
||||
fun add(command: ICommand): HttpMessage<Pair<TKey, TSecond>>
|
||||
fun add(command: ICommand): Message<Pair<TKey, TSecond>>
|
||||
|
||||
fun update(key: TKey, secondary: TSecond, command: ICommand)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.synebula.gaea.domain.service
|
||||
|
||||
import com.synebula.gaea.data.message.http.HttpMessage
|
||||
import com.synebula.gaea.data.message.Message
|
||||
import com.synebula.gaea.log.ILogger
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ interface IService<TKey> {
|
||||
*/
|
||||
var logger: ILogger
|
||||
|
||||
fun add(command: ICommand): HttpMessage<TKey>
|
||||
fun add(command: ICommand): Message<TKey>
|
||||
|
||||
fun update(key: TKey, command: ICommand)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.synebula.gaea.domain.service
|
||||
|
||||
import com.synebula.gaea.data.IObjectConverter
|
||||
import com.synebula.gaea.data.message.http.HttpMessage
|
||||
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
|
||||
@@ -20,8 +20,8 @@ open class Service<TAggregateRoot : IAggregateRoot<TKey>, TKey>
|
||||
protected var converter: IObjectConverter,
|
||||
protected var aggregateRootClass: Class<TAggregateRoot>) : IService<TKey> {
|
||||
|
||||
override fun add(command: ICommand): HttpMessage<TKey> {
|
||||
val msg = HttpMessage<TKey>()
|
||||
override fun add(command: ICommand): Message<TKey> {
|
||||
val msg = Message<TKey>()
|
||||
val root = this.convert(command)
|
||||
repository.add(root)
|
||||
msg.data = root.id
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.synebula.gaea.io.scan
|
||||
|
||||
/**
|
||||
*
|
||||
* @author alex
|
||||
* @version 0.0.1
|
||||
* @since 2016年9月20日 上午10:50:06
|
||||
*/
|
||||
object ClassLoaderContext {
|
||||
/**
|
||||
* 获得class loader<br></br>
|
||||
* 若当前线程class loader不存在,取当前类的class loader
|
||||
*
|
||||
* @return 类加载器
|
||||
*/
|
||||
fun get(): ClassLoader {
|
||||
var classLoader: ClassLoader? = Thread.currentThread().contextClassLoader
|
||||
if (classLoader == null) {
|
||||
classLoader = ClassLoaderContext::class.java.classLoader
|
||||
}
|
||||
return classLoader!!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.synebula.gaea.io.scan
|
||||
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.util.Enumeration
|
||||
import java.util.HashSet
|
||||
|
||||
/**
|
||||
*
|
||||
* @author alex
|
||||
* @version 0.0.1
|
||||
* @since 2016年9月20日 下午2:59:47
|
||||
*/
|
||||
object ClassPath {
|
||||
|
||||
/**
|
||||
* 获得Java ClassPath路径
|
||||
* @return 获得Java ClassPath路径,不包括 jre
|
||||
*/
|
||||
fun get(): Array<String> {
|
||||
val paths = System.getProperty("java.class.path")
|
||||
val separator = System.getProperty("path.separator")
|
||||
return paths.split(separator.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得ClassPath
|
||||
*
|
||||
* @param packageName 包名称
|
||||
* @return ClassPath路径字符串集合
|
||||
*/
|
||||
fun get(packageName: String): Array<String> {
|
||||
val paths = HashSet<String>()
|
||||
var path = packageName.replace(".", "/")
|
||||
// 判断路径最后一个字符是不是"/",如果不是加上
|
||||
path = if (path.lastIndexOf("/") != path.length - 1) "$path/" else path
|
||||
|
||||
var resources: Enumeration<URL>? = null
|
||||
try {
|
||||
resources = ClassLoaderContext.get().getResources(path)
|
||||
} catch (e: IOException) {
|
||||
}
|
||||
|
||||
while (resources!!.hasMoreElements()) {
|
||||
paths.add(resources.nextElement().path)
|
||||
}
|
||||
return paths.toTypedArray()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package com.synebula.gaea.io.scan
|
||||
|
||||
import java.io.File
|
||||
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.jar.JarFile
|
||||
|
||||
/**
|
||||
* 包扫描组件
|
||||
* @author alex
|
||||
* @version 0.0.1
|
||||
* @since 2016年9月20日 下午2:56:50
|
||||
*
|
||||
* @param packageName 扫描的包名
|
||||
*/
|
||||
class ClassScanner(private var packageName: String) {
|
||||
/**
|
||||
* 类过滤器列表
|
||||
*/
|
||||
private var classFilters: Array<out IClassFilter>? = null
|
||||
|
||||
/**
|
||||
* 扫描的类
|
||||
*/
|
||||
private val classes: MutableSet<Class<*>> = HashSet()
|
||||
|
||||
/**
|
||||
* 文件过滤器
|
||||
*/
|
||||
private val fileFilter = FileFilter { file -> file.isDirectory || file.name.endsWith(".class") || file.name.endsWith(".jar") }
|
||||
|
||||
|
||||
/**
|
||||
* 构造方法。
|
||||
*
|
||||
* @param packageName 需要扫描的包名。
|
||||
* @param classFilter
|
||||
*/
|
||||
constructor(packageName: String, vararg classFilter: IClassFilter) : this(packageName) {
|
||||
this.classFilters = classFilter
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始扫描。
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
fun scan(): Collection<Class<*>> {
|
||||
// 如果类集合不为空,先清空。
|
||||
if (classes.isNotEmpty())
|
||||
classes.clear()
|
||||
|
||||
var paths = ClassPath.get(packageName)// 先扫描当前类路径下文件
|
||||
this.scanPath(paths)
|
||||
if (classes.isEmpty()) {// 若当前类路径下文件没有,扫描java路径
|
||||
paths = ClassPath.get()
|
||||
this.scanPath(paths)
|
||||
}
|
||||
return classes
|
||||
}
|
||||
|
||||
/*
|
||||
* Private
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 填充满足条件的class 填充到 classes<br></br>
|
||||
* 同时会判断给定的路径是否为Jar包内的路径,如果是,则扫描此Jar包
|
||||
*
|
||||
* @param paths Class文件路径或者所在目录Jar包路径
|
||||
*/
|
||||
private fun scanPath(paths: Array<String>) {
|
||||
for (path in paths) {
|
||||
try {
|
||||
// 路径编码,防止由于路径中空格和中文导致的Jar找不到
|
||||
val realPath = URLDecoder.decode(path, Charset.defaultCharset().name())
|
||||
val files = scanDirectory(realPath)
|
||||
for (file in files) {
|
||||
val fileName = file.toString()
|
||||
if (fileName.contains(".jar") && !fileName.endsWith(".class"))
|
||||
scanJar(file)
|
||||
else if (fileName.endsWith(".class"))
|
||||
scanClass(realPath, file)
|
||||
}
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描文件,过滤添加类文件。
|
||||
*
|
||||
* @param path 扫描的磁盘路径
|
||||
*/
|
||||
private fun scanDirectory(path: String): Collection<File> {
|
||||
var realPath = path
|
||||
val files = LinkedList<File>()
|
||||
val index = realPath.lastIndexOf(".jar")
|
||||
if (index != -1) {// 首先判path是不是jar包下,如果是说明当前path是文件
|
||||
// jar文件
|
||||
realPath = realPath.substring(0, index + 4)// 截取jar路径 [xxx/xxx.jar]
|
||||
if (realPath.startsWith("file:"))
|
||||
realPath = realPath.substring(5) // 去掉文件前缀[file:]
|
||||
files.add(File(realPath))
|
||||
} else {// 否则扫描文件夹下文件
|
||||
val directory = LinkedList<File>()
|
||||
directory.add(File(realPath))
|
||||
var list: Array<File>?
|
||||
var file: File
|
||||
while (directory.size != 0) {
|
||||
file = directory.poll()
|
||||
list = file.listFiles(fileFilter)
|
||||
if (list!!.isNotEmpty()) {
|
||||
for (item in list) {
|
||||
if (item.isDirectory)
|
||||
directory.add(item)
|
||||
else
|
||||
files.add(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描jar文件,过滤添加类文件。
|
||||
*
|
||||
* @param file 扫描的jar文件
|
||||
*/
|
||||
private fun scanJar(file: File) {
|
||||
try {
|
||||
val jar = JarFile(file)
|
||||
val entries = jar.entries()
|
||||
val list = Collections.list(entries)
|
||||
for (entry in list) {
|
||||
if (entry.name.endsWith(".class")) {
|
||||
val className = entry.name.replace("/", ".").replace(".class", "")
|
||||
filterClass(className)
|
||||
}
|
||||
}
|
||||
jar.close()
|
||||
} catch (ex: Throwable) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描.class类文件,过滤添加类文件。
|
||||
*
|
||||
* @param path 路径
|
||||
* @param file 文件
|
||||
*/
|
||||
private fun scanClass(path: String, file: File) {
|
||||
var realPath = path
|
||||
if (!realPath.endsWith(File.separator)) {
|
||||
realPath += File.separator
|
||||
}
|
||||
var absolutePath = file.absolutePath
|
||||
if (packageName.isEmpty())
|
||||
absolutePath = absolutePath.substring(realPath.length)
|
||||
|
||||
val filePathWithDot = absolutePath.replace(File.separator, ".")
|
||||
val subIndex = filePathWithDot.indexOf(packageName)
|
||||
if (subIndex != -1) {
|
||||
val endIndex = filePathWithDot.lastIndexOf(".class")
|
||||
val className = filePathWithDot.substring(subIndex, endIndex)
|
||||
filterClass(className)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤符合要求的类,添加到结果列表中。
|
||||
*
|
||||
* @param className 类名
|
||||
*/
|
||||
private fun filterClass(className: String) {
|
||||
if (className.startsWith(packageName)) {
|
||||
try {
|
||||
val clazz = Class.forName(className, false, ClassLoaderContext.get())
|
||||
if (classFilters == null)
|
||||
classes.add(clazz)
|
||||
else {
|
||||
for (iClassFilter in classFilters!!) {
|
||||
if (iClassFilter.accept(clazz))
|
||||
classes.add(clazz)
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.synebula.gaea.io.scan
|
||||
|
||||
/**
|
||||
* 类过滤器,用于过滤不需要加载的类<br></br>
|
||||
* @author alex
|
||||
* @version 0.0.1
|
||||
* @since 2016年9月18日 下午4:41:29
|
||||
*/
|
||||
interface IClassFilter {
|
||||
fun accept(clazz: Class<*>): Boolean
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.synebula.gaea.query
|
||||
|
||||
/**
|
||||
* 查询基接口。
|
||||
*
|
||||
* @author wxf
|
||||
*/
|
||||
interface IComplexQuery<TView, TKey, TSecond> {
|
||||
|
||||
|
||||
/**
|
||||
* 根据Key获取对象。
|
||||
*
|
||||
* @param key 对象Key。
|
||||
* @return
|
||||
*/
|
||||
fun get(key: TKey, secondary: TSecond): TView
|
||||
|
||||
/**
|
||||
* 根据实体类条件查询所有符合条件记录
|
||||
*
|
||||
* @param parameters 查询条件。
|
||||
* @return list
|
||||
*/
|
||||
fun list(parameters: Map<String, Any>): List<TView>
|
||||
|
||||
/**
|
||||
* 根据条件查询符合条件记录的数量
|
||||
*
|
||||
* @param parameters 查询条件。
|
||||
* @return int
|
||||
*/
|
||||
fun count(parameters: Map<String, Any>): Int
|
||||
|
||||
/**
|
||||
* 根据实体类条件查询所有符合条件记录(分页查询)
|
||||
*
|
||||
* @param param 分页条件
|
||||
* @return
|
||||
*/
|
||||
fun paging(param: PagingParam): PagingData<TView>
|
||||
}
|
||||
42
src/gaea/src/main/kotlin/com/synebula/gaea/query/IQuery.kt
Normal file
42
src/gaea/src/main/kotlin/com/synebula/gaea/query/IQuery.kt
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.synebula.gaea.query
|
||||
|
||||
/**
|
||||
* 查询基接口。
|
||||
*
|
||||
* @author alex
|
||||
*/
|
||||
interface IQuery<TView, TKey> {
|
||||
|
||||
|
||||
/**
|
||||
* 根据Key获取对象。
|
||||
*
|
||||
* @param key 对象Key。
|
||||
* @return
|
||||
*/
|
||||
fun get(key: TKey): TView?
|
||||
|
||||
/**
|
||||
* 根据实体类条件查询所有符合条件记录
|
||||
*
|
||||
* @param params 查询条件。
|
||||
* @return list
|
||||
*/
|
||||
fun list(params: Map<String, Any>?): List<TView>
|
||||
|
||||
/**
|
||||
* 根据条件查询符合条件记录的数量
|
||||
*
|
||||
* @param params 查询条件。
|
||||
* @return int
|
||||
*/
|
||||
fun count(params: Map<String, Any>?): Int
|
||||
|
||||
/**
|
||||
* 根据实体类条件查询所有符合条件记录(分页查询)
|
||||
*
|
||||
* @param params 分页条件
|
||||
* @return 分页数据
|
||||
*/
|
||||
fun paging(params: PagingParam): PagingData<TView>
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.synebula.gaea.query
|
||||
|
||||
/**
|
||||
* class OrderType
|
||||
*
|
||||
* @author alex
|
||||
* @version 0.1
|
||||
* @since 2018 18-2-6
|
||||
*/
|
||||
enum class OrderType {
|
||||
/**
|
||||
* 升序排列
|
||||
*/
|
||||
ASC,
|
||||
|
||||
/**
|
||||
* 降序排序
|
||||
*/
|
||||
DESC
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.synebula.gaea.query
|
||||
|
||||
/**
|
||||
* 分页数据。
|
||||
* 真实数据行无后台遍历需求,直接使用object类型表示即可.
|
||||
*
|
||||
* @author alex
|
||||
*/
|
||||
class PagingData<T> {
|
||||
/**
|
||||
* 页码,从1开始。
|
||||
*/
|
||||
var page: Int = 0
|
||||
|
||||
/**
|
||||
* 每页数据量。
|
||||
*/
|
||||
|
||||
var size: Int = 0
|
||||
|
||||
/**
|
||||
* 总数据量。
|
||||
*/
|
||||
|
||||
var total: Int = 0
|
||||
|
||||
/**
|
||||
* 数据索引,从0开始。表示数据在总量的第几条。(index = (page - 1) * size )
|
||||
*/
|
||||
var index: Int
|
||||
get() = (page - 1) * size
|
||||
|
||||
/**
|
||||
* 结果数据。
|
||||
*/
|
||||
var data = listOf<T>()
|
||||
|
||||
|
||||
/**
|
||||
* 数据构造。
|
||||
*
|
||||
* @param page 页码,从1开始。
|
||||
* @param size 每页数据量。
|
||||
*/
|
||||
constructor(page: Int, size: Int) : super() {
|
||||
this.page = page
|
||||
this.size = size
|
||||
this.index = (page - 1) * size + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据构造。
|
||||
*
|
||||
* @param page 页码,从1开始。
|
||||
* @param size 每页数据量。
|
||||
* @param total 总数据量。
|
||||
* @param data 结果数据。
|
||||
*/
|
||||
|
||||
constructor(page: Int, size: Int, total: Int, data: List<T>) : super() {
|
||||
this.page = page
|
||||
this.size = size
|
||||
this.total = total
|
||||
this.index = (page - 1) * size + 1
|
||||
this.data = data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.synebula.gaea.query
|
||||
|
||||
/**
|
||||
* class PagingParam
|
||||
*
|
||||
* @author alex
|
||||
* @version 0.1
|
||||
* @since 2018 18-2-5
|
||||
*/
|
||||
data class PagingParam(var page: Int = 1, var size: Int = 10) {
|
||||
|
||||
/**
|
||||
* 数据索引,从0开始。表示数据在总量的第几条。(index = (page - 1) * size)
|
||||
*/
|
||||
var index: Long = 0
|
||||
get() = (page.toLong() - 1) * size.toLong()
|
||||
private set
|
||||
|
||||
/**
|
||||
* 排序条件。
|
||||
*/
|
||||
var orderBy: MutableMap<String, OrderType> = hashMapOf()
|
||||
|
||||
/**
|
||||
* 查询条件。
|
||||
*/
|
||||
var parameters: MutableMap<String, Any> = hashMapOf()
|
||||
|
||||
/**
|
||||
* 添加查询条件
|
||||
*/
|
||||
fun addParameter(field: String, value: Any): PagingParam {
|
||||
parameters[field] = value
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加排序条件
|
||||
*/
|
||||
fun addOrderBy(field: String, type: OrderType = OrderType.ASC): PagingParam {
|
||||
orderBy[field] = type
|
||||
return this
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user