Compare commits

...

10 Commits

Author SHA1 Message Date
de8fc0256f 增加query factory 2024-07-03 16:08:22 +08:00
48a24b99e1 添加文件 2024-06-29 20:18:33 +08:00
54b7cedac6 升级JDK/KOTLIN/SPRING等依赖信息 2024-06-26 17:31:44 +08:00
75743c6ef6 fix gradle config file: java & kotlin compile target jvm 2023-04-20 23:06:33 +08:00
86a4bc8b0a update package version 2023-04-18 11:22:23 +08:00
eff39eb7f8 fix spring jpa proxy bug 2023-04-18 10:50:47 +08:00
230ceea0fa update latest gradle & spring;
fix new spring package dependencies
2023-04-18 09:58:52 +08:00
b099d42883 修改jpa引用类型 2022-12-02 14:54:11 +08:00
aadf880052 调整app包结构 2022-12-01 11:17:35 +08:00
ad3cfef96f fix token 获取 bug 2022-11-23 15:41:17 +08:00
80 changed files with 1066 additions and 693 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,4 @@
.*
gradlew*
build
!.gitignore

View File

@@ -1,12 +1,17 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
buildscript {
ext {
kotlin_version = '1.6.10'
spring_version = "2.7.0"
jvm_version = '21'
kotlin_version = '2.0.0'
spring_version = '3.3.0'
}
repositories {
mavenLocal()
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/repository/public' }
mavenCentral()
}
@@ -17,19 +22,21 @@ buildscript {
subprojects {
group 'com.synebula'
version '1.5.0'
version '1.6.0'
buildscript {
repositories {
mavenLocal()
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/repository/public' }
mavenCentral()
}
}
repositories {
mavenLocal()
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/repository/public' }
mavenCentral()
}
@@ -44,14 +51,18 @@ subprojects {
testApi group: 'junit', name: 'junit', version: '4.12'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
/*** 指定 Java & Kotlin 语言编译目标JVM ***/
sourceCompatibility = "$jvm_version"
targetCompatibility = "$jvm_version"
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
compilerOptions {
jvmTarget = JvmTarget.valueOf("JVM_$jvm_version")
}
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
compilerOptions {
jvmTarget = JvmTarget.valueOf("JVM_$jvm_version")
}
}
publishing {

View File

@@ -1,6 +1,6 @@
#Mon May 18 17:21:26 CST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
#Thu Jun 06 15:31:38 CST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored Normal file
View File

@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

90
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,90 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,8 @@
package com.synebula.gaea.app.autoconfig
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
@Configuration
@ComponentScan(basePackages = ["com.synebula.gaea.app.bus", "com.synebula.gaea.app.component", "com.synebula.gaea.app.security"])
class AppAutoConfiguration

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.app.component.bus
package com.synebula.gaea.app.bus
import com.synebula.gaea.bus.Bus
import org.springframework.stereotype.Component

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.app.component.bus
package com.synebula.gaea.app.bus
import com.synebula.gaea.bus.DomainSubscribe
import com.synebula.gaea.bus.IBus

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.app.component.cache
package com.synebula.gaea.app.cache
import com.google.common.cache.Cache
import com.google.common.cache.CacheBuilder
@@ -24,7 +24,7 @@ open class Cache<K, V>(expire: Int) : ICache<K, V> {
}
override fun add(key: K, value: V) {
this.guavaCache.put(key, value)
this.guavaCache.put(key!!, value!!)
}
/**

View File

@@ -1,6 +1,5 @@
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
@@ -40,7 +39,7 @@ object Excel {
val titleFont = wb.createFont()
titleFont.bold = true
titleStyle.setFont(titleFont)
setBorderStyle(titleStyle, BorderStyle.THIN)
setBorderStyle(titleStyle)
//声明列对象
// 第三步在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制
@@ -62,7 +61,7 @@ object Excel {
val contentStyle = wb.createCellStyle()
contentStyle.alignment = HorizontalAlignment.LEFT// 创建一个修改居左格式
contentStyle.verticalAlignment = VerticalAlignment.CENTER
setBorderStyle(contentStyle, BorderStyle.THIN)
setBorderStyle(contentStyle)
//创建内容
var col = 0
@@ -146,6 +145,7 @@ object Excel {
this.getCellValue(cell, evaluator).toString().toIntOrNull()
}
}
Double::class.java.name -> {
if (cell.cellType == CellType.NUMERIC) {
cell.numericCellValue
@@ -153,6 +153,7 @@ object Excel {
this.getCellValue(cell, evaluator).toString().toDoubleOrNull()
}
}
Float::class.java.name -> {
if (cell.cellType == CellType.NUMERIC) {
cell.numericCellValue.toFloat()
@@ -160,6 +161,7 @@ object Excel {
this.getCellValue(cell, evaluator).toString().toFloatOrNull()
}
}
Decimal::class.java.name -> {
if (cell.cellType == CellType.NUMERIC) {
cell.numericCellValue.toBigDecimal()
@@ -167,6 +169,7 @@ object Excel {
this.getCellValue(cell, evaluator).toString().toBigDecimalOrNull()
}
}
Boolean::class.java.name -> {
if (cell.cellType == CellType.BOOLEAN) {
cell.booleanCellValue
@@ -174,11 +177,13 @@ object Excel {
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
@@ -278,7 +283,7 @@ object Excel {
/**
* 设置cell style的边框
*/
private fun setBorderStyle(style: HSSFCellStyle, borderStyle: BorderStyle) {
private fun setBorderStyle(style: HSSFCellStyle, borderStyle: BorderStyle = BorderStyle.THIN) {
style.borderTop = borderStyle
style.borderRight = borderStyle
style.borderBottom = borderStyle
@@ -297,6 +302,7 @@ object Excel {
numericCellValue
}
}
CellType.STRING -> cell.richStringCellValue.string
CellType.BLANK -> ""
CellType.FORMULA -> evaluator.evaluate(cell).toString()

View File

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

View File

@@ -1,12 +1,12 @@
package com.synebula.gaea.app
package com.synebula.gaea.app.controller
import com.synebula.gaea.app.cmd.ICommandApp
import com.synebula.gaea.app.query.IQueryApp
import com.synebula.gaea.app.controller.cmd.ICommandApp
import com.synebula.gaea.app.controller.query.IQueryApp
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.db.query.IQuery
import com.synebula.gaea.domain.service.ICommand
import com.synebula.gaea.domain.service.IService
import com.synebula.gaea.log.ILogger
import com.synebula.gaea.query.IQuery
import org.springframework.beans.factory.annotation.Autowired
/**
@@ -17,7 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired
* @param query 业务查询服务
* @param logger 日志组件
*/
open class Application<TCommand : ICommand, TView, ID>(
open class DomainApplication<TCommand : ICommand, TView, ID>(
override var name: String,
override var service: IService<ID>,
override var query: IQuery<TView, ID>,

View File

@@ -1,6 +1,6 @@
package com.synebula.gaea.app
package com.synebula.gaea.app.controller
import com.google.gson.Gson
import com.synebula.gaea.app.security.session.UserSession
import com.synebula.gaea.data.message.HttpMessage
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.data.message.Status
@@ -58,14 +58,12 @@ interface IApplication {
/**
* 获取用户信息
* @param clazz 用户信息结构类
*/
fun <T> userSession(clazz: Class<T>): T? {
fun userSession(): UserSession? {
try {
val authentication = SecurityContextHolder.getContext().authentication.principal.toString()
val authentication = SecurityContextHolder.getContext().authentication.principal
try {
val gson = Gson()
return gson.fromJson(authentication, clazz)
return authentication as UserSession
} catch (ex: Exception) {
logger.error(this, ex, "[$name]解析用户信息异常!用户信息:$authentication: ${ex.message}")
}

View File

@@ -1,12 +1,12 @@
package com.synebula.gaea.app
package com.synebula.gaea.app.controller
import com.synebula.gaea.app.cmd.ISimpleCommandApp
import com.synebula.gaea.app.query.IQueryApp
import com.synebula.gaea.app.controller.cmd.ISimpleCommandApp
import com.synebula.gaea.app.controller.query.IQueryApp
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.db.query.IQuery
import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.service.ISimpleService
import com.synebula.gaea.log.ILogger
import com.synebula.gaea.query.IQuery
import com.synebula.gaea.record.service.IService
import org.springframework.beans.factory.annotation.Autowired
/**
@@ -17,14 +17,13 @@ import org.springframework.beans.factory.annotation.Autowired
* @param query 业务查询服务
* @param logger 日志组件
*/
open class SimpleApplication<TRoot : IAggregateRoot<ID>, ID>(
open class RecordApplication<TRoot : IAggregateRoot<ID>, ID>(
override var name: String,
override var service: ISimpleService<TRoot, ID>,
override var service: IService<TRoot, ID>,
override var query: IQuery<TRoot, ID>,
override var logger: ILogger,
) : ISimpleCommandApp<TRoot, ID>, IQueryApp<TRoot, ID> {
@Autowired
override lateinit var httpMessageFactory: HttpMessageFactory
}
}

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.app.cmd
package com.synebula.gaea.app.controller.cmd
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.domain.service.ICommand

View File

@@ -1,6 +1,6 @@
package com.synebula.gaea.app.cmd
package com.synebula.gaea.app.controller.cmd
import com.synebula.gaea.app.IApplication
import com.synebula.gaea.app.controller.IApplication
import com.synebula.gaea.data.message.HttpMessage
import com.synebula.gaea.data.message.Status
import com.synebula.gaea.domain.service.ICommand

View File

@@ -1,10 +1,10 @@
package com.synebula.gaea.app.cmd
package com.synebula.gaea.app.controller.cmd
import com.synebula.gaea.app.IApplication
import com.synebula.gaea.app.controller.IApplication
import com.synebula.gaea.data.message.HttpMessage
import com.synebula.gaea.data.message.Status
import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.service.ISimpleService
import com.synebula.gaea.record.service.IService
import com.synebula.gaea.spring.aop.annotation.Method
import org.springframework.web.bind.annotation.*
@@ -16,12 +16,13 @@ import org.springframework.web.bind.annotation.*
* @since 2020-05-15
*/
interface ISimpleCommandApp<TRoot : IAggregateRoot<ID>, ID> : IApplication {
var service: ISimpleService<TRoot, ID>
var service: IService<TRoot, ID>
@PostMapping
@Method("添加")
fun add(@RequestBody entity: TRoot): HttpMessage {
return this.httpMessageFactory.create(service.add(entity))
val id = service.add(entity)
return this.httpMessageFactory.create(id!!)
}
@PutMapping("/{id:.+}")

View File

@@ -1,8 +1,8 @@
package com.synebula.gaea.app.cmd
package com.synebula.gaea.app.controller.cmd
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.service.ISimpleService
import com.synebula.gaea.record.service.IService
import com.synebula.gaea.log.ILogger
import org.springframework.beans.factory.annotation.Autowired
@@ -15,7 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired
*/
open class SimpleCommandApp<TRoot : IAggregateRoot<ID>, ID>(
override var name: String,
override var service: ISimpleService<TRoot, ID>,
override var service: IService<TRoot, ID>,
override var logger: ILogger,
) : ISimpleCommandApp<TRoot, ID> {
@Autowired

View File

@@ -1,9 +1,9 @@
package com.synebula.gaea.app.query
package com.synebula.gaea.app.controller.query
import com.synebula.gaea.app.IApplication
import com.synebula.gaea.app.controller.IApplication
import com.synebula.gaea.data.message.HttpMessage
import com.synebula.gaea.query.IQuery
import com.synebula.gaea.query.Params
import com.synebula.gaea.db.query.IQuery
import com.synebula.gaea.db.query.Params
import com.synebula.gaea.spring.aop.annotation.Method
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable

View File

@@ -1,8 +1,8 @@
package com.synebula.gaea.app.query
package com.synebula.gaea.app.controller.query
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.db.query.IQuery
import com.synebula.gaea.log.ILogger
import com.synebula.gaea.query.IQuery
import org.springframework.beans.factory.annotation.Autowired
/**

View File

@@ -1,9 +1,9 @@
package com.synebula.gaea.app.component.security
package com.synebula.gaea.app.security
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.exceptions.TokenExpiredException
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
@@ -71,6 +71,25 @@ class TokenManager {
.sign(algorithm)
}
/**
* 校验token是否正确
*
* @param token 密钥
* @param func 自定义验证方法
*/
fun verifyTime(token: String, func: (total: Long, remain: Long) -> Unit) {
try {
val now = Date()
val jwt = JWT.decode(token)
val total = jwt.expiresAt.time - jwt.issuedAt.time //总时间
val remain = jwt.expiresAt.time - now.time //剩余的时间
func(total, remain)
} catch (ex: Exception) {
this.logger.error(this, ex, "解析token出错")
throw ex
}
}
/**
* 校验token是否正确
*
@@ -78,21 +97,16 @@ class TokenManager {
* @return 是否正确
*/
fun <T> verify(token: String, clazz: Class<T>): T? {
try {
val now = Date()
return try {
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)
gson.fromJson(json, clazz)
} catch (ex: TokenExpiredException) {
null
} catch (ex: Exception) {
this.logger.debug(this, ex, "解析token出错")
throw ex
this.logger.error(this, ex, "解析token出错")
throw ex
}
}
@@ -103,20 +117,15 @@ class TokenManager {
* @return 是否正确
*/
fun verify(token: String): String {
try {
val now = Date()
return try {
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()
result.getClaim(this.payload).asString()
} catch (ex: TokenExpiredException) {
""
} catch (ex: Exception) {
this.logger.debug(this, ex, "解析token出错")
throw ex
this.logger.error(this, ex, "解析token出错")
throw ex
}
}

View File

@@ -1,16 +1,16 @@
package com.synebula.gaea.app.component.security
package com.synebula.gaea.app.security
import com.synebula.gaea.app.component.security.session.UserSessionManager
import com.synebula.gaea.app.security.session.UserSessionManager
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.data.message.Status
import jakarta.servlet.FilterChain
import jakarta.servlet.ServletException
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.filter.OncePerRequestFilter
import java.io.IOException
import javax.servlet.FilterChain
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
/**
@@ -40,7 +40,9 @@ class WebAuthorization(
val identity = if (this.storage == "cookie") {
request.cookies.find { it.name == tokenKey }?.value ?: ""
} else {
request.getHeader(tokenKey) ?: ""
var token = request.getHeader(tokenKey) ?: ""
if (token.isEmpty()) token = request.getParameter(tokenKey) ?: ""
token
}
val user = this.userSessionManager.userSession(identity)
if (user != null) {
@@ -62,4 +64,5 @@ class WebAuthorization(
response.flushBuffer()
}
}
}

View File

@@ -1,12 +1,13 @@
package com.synebula.gaea.app.component.security
package com.synebula.gaea.app.security
import com.synebula.gaea.app.component.security.session.UserSessionManager
import com.synebula.gaea.app.security.session.UserSessionManager
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.data.message.Status
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer
@@ -36,24 +37,27 @@ class WebSecurity {
@Bean
@Throws(Exception::class)
fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
httpSecurity.cors().and().csrf().disable() // 跨域伪造请求限制无效
httpSecurity.cors(Customizer.withDefaults())
.csrf { it.disable() } // 跨域伪造请求限制无效
// 设置Session的创建策略为Spring Security永不创建HttpSession 不使用HttpSession来获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
// 除了登录接口其他资源都必须登录访问
.and().authorizeRequests().antMatchers(this.signInPath).permitAll().anyRequest().authenticated()
.authorizeHttpRequests { it.requestMatchers(this.signInPath).permitAll().anyRequest().authenticated() }
// 添加鉴权拦截器
.and().addFilterBefore(
.addFilterBefore(
WebAuthorization(httpMessageFactory, userSessionManager),
UsernamePasswordAuthenticationFilter::class.java
).exceptionHandling().authenticationEntryPoint { _, response, _ ->
response.status = Status.Success
response.characterEncoding = "utf-8"
response.contentType = "text/javascript;charset=utf-8"
response.writer.print(
this.httpMessageFactory.create(
Status.Unauthorized, "用户未登录,请重新登录后尝试!"
).exceptionHandling {
it.authenticationEntryPoint { _, response, _ ->
response.status = Status.Success
response.characterEncoding = "utf-8"
response.contentType = "text/javascript;charset=utf-8"
response.writer.print(
this.httpMessageFactory.create(
Status.Unauthorized, "用户未登录,请重新登录后尝试!"
)
)
)
}
}
return httpSecurity.build()
@@ -62,7 +66,7 @@ class WebSecurity {
@Bean
@Throws(Exception::class)
fun ignoringCustomizer(): WebSecurityCustomizer {
return WebSecurityCustomizer { web -> web.ignoring().antMatchers(this.signInPath) }
return WebSecurityCustomizer { web -> web.ignoring().requestMatchers(this.signInPath) }
}
/**

View File

@@ -0,0 +1,16 @@
package com.synebula.gaea.app.security.session
import com.synebula.gaea.data.permission.AuthorityType
import com.synebula.gaea.data.permission.PermissionType
open class User {
/**
* 权限类型, 根据类型来动态应用[permissions]中的权限信息. 通常配置在角色中
*/
var permissionType = PermissionType.Minimum
/**
* 用户的权限信息. 通常通过角色配置
*/
var permissions = mapOf<String, AuthorityType>()
}

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.app.component.security.session
package com.synebula.gaea.app.security.session
/**
* 登陆用户会话信息
@@ -27,7 +27,7 @@ class UserSession(var uid: String, var user: Any) {
* 获取指定类型的用户信息
*/
@Suppress("UNCHECKED_CAST")
fun <T> user(): T {
fun <T : User> user(): T {
return user as T
}
}

View File

@@ -1,6 +1,6 @@
package com.synebula.gaea.app.component.security.session
package com.synebula.gaea.app.security.session
import com.synebula.gaea.app.component.cache.Cache
import com.synebula.gaea.app.cache.Cache
import com.synebula.gaea.ext.toMd5
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

View File

@@ -1,5 +0,0 @@
package com.synebula.gaea.app.struct
class ExcelData(var title: String = "",
var columnNames: List<String> = listOf(),
var data: List<List<String>> = listOf())

View File

@@ -1,5 +0,0 @@
package com.synebula.gaea.app.struct.exception
import com.auth0.jwt.exceptions.TokenExpiredException
class TokenCloseExpireException(msg: String, var payload: String) : TokenExpiredException(msg)

View File

@@ -0,0 +1 @@
com.synebula.gaea.app.autoconfig.AppAutoConfiguration

View File

@@ -5,7 +5,7 @@ ext {
dependencies {
api project(":src:gaea")
implementation("org.springframework.boot:spring-boot-starter-data-jpa:$spring_version")
api("org.springframework.boot:spring-boot-starter-data-jpa:$spring_version")
implementation("org.javassist:javassist:$jassist_version")
}

View File

@@ -1,10 +1,10 @@
package com.synebula.gaea.jpa
import com.synebula.gaea.query.IQuery
import com.synebula.gaea.query.Page
import com.synebula.gaea.query.Params
import com.synebula.gaea.db.query.IQuery
import com.synebula.gaea.db.query.Page
import com.synebula.gaea.db.query.Params
import jakarta.persistence.EntityManager
import org.springframework.data.jpa.repository.support.SimpleJpaRepository
import javax.persistence.EntityManager
class JpaQuery<TView, ID>(override var clazz: Class<TView>, entityManager: EntityManager) : IQuery<TView, ID> {
protected var repo: SimpleJpaRepository<TView, ID>
@@ -14,7 +14,7 @@ class JpaQuery<TView, ID>(override var clazz: Class<TView>, entityManager: Entit
}
override operator fun get(id: ID): TView? {
val view = this.repo.findById(id)
val view = this.repo.findById(id!!)
return if (view.isPresent) view.get() else null
}

View File

@@ -2,13 +2,13 @@ package com.synebula.gaea.jpa
import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.repository.IRepository
import jakarta.persistence.EntityManager
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.support.SimpleJpaRepository
import javax.persistence.EntityManager
class JpaRepository<TAggregateRoot : IAggregateRoot<ID>, ID>(
override var clazz: Class<TAggregateRoot>,
open class JpaRepository<TAggregateRoot : IAggregateRoot<ID>, ID>(
final override var clazz: Class<TAggregateRoot>,
entityManager: EntityManager
) : IRepository<TAggregateRoot, ID> {
protected var repo: JpaRepository<TAggregateRoot, ID>? = null

View File

@@ -1,15 +1,12 @@
package com.synebula.gaea.jpa
import com.synebula.gaea.data.date.DateTime
import com.synebula.gaea.query.Operator
import com.synebula.gaea.query.Where
import com.synebula.gaea.db.query.Operator
import com.synebula.gaea.db.query.Where
import jakarta.persistence.criteria.*
import org.springframework.data.jpa.domain.Specification
import java.lang.reflect.Field
import java.util.*
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.CriteriaQuery
import javax.persistence.criteria.Predicate
import javax.persistence.criteria.Root
/**
@@ -69,79 +66,209 @@ fun String.tryToDigital(field: Field): Double {
* @param clazz 类
* @return Specification
*/
fun Map<String, String>?.toSpecification(clazz: Class<*>): Specification<*> {
fun Map<String, String>.toSpecification(clazz: Class<*>): Specification<*> {
val rangeStartSuffix = "[0]" //范围查询开始后缀
val rangeEndSuffix = "[1]" //范围查询结束后缀
return Specification<Any?> { root: Root<Any?>, _: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
val predicates: MutableList<Predicate> = ArrayList()
for (argumentName in this!!.keys) {
if (this[argumentName] == null) continue
var fieldName = argumentName
var operator: Operator
// 判断是否为range类型(范围内查询)
var start = true
if (fieldName.endsWith(rangeStartSuffix) || fieldName.endsWith(rangeEndSuffix)) {
fieldName = fieldName.substring(fieldName.length - 3)
if (fieldName.endsWith(rangeEndSuffix)) start = false
}
val field = clazz.getDeclaredField(fieldName)
val where: Where = field.getDeclaredAnnotation(Where::class.java)
operator = where.operator
// 如果是范围内容, 判断是数值类型还是时间类型
if (operator === Operator.Range) {
if (clazz.getDeclaredField(fieldName).type != Date::class.java) {
operator = if (start) Operator.Gte else Operator.Lte
}
}
var predicate: Predicate
var digitalValue: Double
val predicates = mutableListOf<Predicate>()
for (argumentName in this.keys) {
try {
var fieldName = argumentName
val fieldValue = this[argumentName]!!
var operator: Operator = Operator.Default
// 判断是否为range类型(范围内查询)
var start = true
if (fieldName.endsWith(rangeStartSuffix) || fieldName.endsWith(rangeEndSuffix)) {
fieldName = fieldName.substring(fieldName.length - 3)
if (fieldName.endsWith(rangeEndSuffix)) start = false
}
val fieldTree = fieldName.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
//查找是否是嵌入字段, 找到最深的类型
var field: Field
if (fieldTree.isNotEmpty()) {
var hostClass = clazz //需要查找字段所在的class
var i = 0
do {
field = hostClass.getDeclaredField(fieldTree[i])
hostClass = field.type
i++
} while (i < fieldTree.size)
} else {
field = clazz.getDeclaredField(fieldName)
}
val where = field.getDeclaredAnnotation(Where::class.java)
if (where != null) operator = where.operator
// 如果是范围内容, 判断是数值类型还是时间类型
if (operator === Operator.Range) {
if (field.type != Date::class.java) {
operator = if (start) Operator.Gte else Operator.Lte
}
}
var predicate: Predicate
var digitalValue: Double
when (operator) {
Operator.Ne -> predicate =
criteriaBuilder.notEqual(root.get<Any>(fieldName), this[fieldName]!!.toFieldType(field))
Operator.Ne -> predicate = criteriaBuilder.notEqual(
getFieldPath<Any>(root, fieldName),
typeConvert(field, fieldValue)
)
Operator.Lt -> {
digitalValue = this[fieldName]!!.tryToDigital(field)
predicate = criteriaBuilder.lessThan(root.get(fieldName), digitalValue)
Operator.Lt -> try {
digitalValue = parseDigital(field, fieldValue)
predicate = criteriaBuilder.lessThan(getFieldPath(root, fieldName), digitalValue)
} catch (e: Exception) {
throw RuntimeException(
String.format(
"class [%s] field [%s] can not use annotation Where(Operator.lt)",
field.declaringClass.name,
field.name
), e
)
}
Operator.Gt -> {
digitalValue = this[fieldName]!!.tryToDigital(field)
predicate = criteriaBuilder.greaterThan(root.get(fieldName), digitalValue)
Operator.Gt -> try {
digitalValue = parseDigital(field, fieldValue)
predicate = criteriaBuilder.greaterThan(
getFieldPath(
root,
fieldName
), digitalValue
)
} catch (e: Exception) {
throw RuntimeException(
String.format(
"class [%s] field [%s] can not use annotation Where(Operator.gt)",
field.declaringClass.name,
field.name
), e
)
}
Operator.Lte -> {
digitalValue = this[fieldName]!!.tryToDigital(field)
predicate = criteriaBuilder.lessThanOrEqualTo(root.get(fieldName), digitalValue)
Operator.Lte -> try {
digitalValue = parseDigital(field, fieldValue)
predicate = criteriaBuilder.lessThanOrEqualTo(
getFieldPath(
root,
fieldName
), digitalValue
)
} catch (e: Exception) {
throw RuntimeException(
String.format(
"class [%s] field [%s] can not use annotation Where(Operator.lte)",
field.declaringClass.name,
field.name
), e
)
}
Operator.Gte -> {
digitalValue = this[fieldName]!!.tryToDigital(field)
predicate = criteriaBuilder.greaterThanOrEqualTo(root.get(fieldName), digitalValue)
Operator.Gte -> try {
digitalValue = parseDigital(field, fieldValue)
predicate = criteriaBuilder.greaterThanOrEqualTo(
getFieldPath(
root,
fieldName
), digitalValue
)
} catch (e: Exception) {
throw RuntimeException(
String.format(
"class [%s] field [%s] can not use annotation Where(Operator.gte)",
field.declaringClass.name,
field.name
), e
)
}
Operator.Like -> predicate = criteriaBuilder.like(root.get(fieldName), "%${this[fieldName]}%")
Operator.Range -> {
predicate = if (start) {
criteriaBuilder.greaterThanOrEqualTo(root.get(fieldName), this[argumentName]!!)
} else {
criteriaBuilder.lessThanOrEqualTo(root.get(fieldName), this[argumentName]!!)
}
}
Operator.Like -> predicate = criteriaBuilder.like(
getFieldPath(root, fieldName),
String.format("%%%s%%", fieldValue)
)
else -> predicate =
criteriaBuilder.equal(root.get<Any>(fieldName), this[fieldName]!!.toFieldType(field))
Operator.Range -> predicate = if (start) criteriaBuilder.greaterThanOrEqualTo(
getFieldPath(root, fieldName), this[argumentName]!!
) else criteriaBuilder.lessThanOrEqualTo(
getFieldPath(root, fieldName), this[argumentName]!!
)
else -> predicate = criteriaBuilder.equal(
getFieldPath<Any>(root, fieldName),
typeConvert(field, fieldValue)
)
}
predicates.add(predicate)
} catch (e: NoSuchFieldException) {
throw Error(
"class [${field.declaringClass.name}] field [${field.name}] can't annotation [@Where(${operator.declaringClass.simpleName}.${operator.name})]",
e
)
throw RuntimeException(e)
}
}
criteriaBuilder.and(*predicates.toTypedArray())
}
}
}
/**
* 获取字段在
*/
fun <Y> getFieldPath(root: Root<Any?>, field: String): Path<Y>? {
val fieldTree = field.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var path: Path<Y>
if (fieldTree.isNotEmpty()) {
path = root.get(fieldTree[0])
for (i in 1 until fieldTree.size) {
path = path.get(fieldTree[i])
}
return path
}
return root.get(field)
}
/**
* 类型转换
*
* @param field 字段对象
* @param value 值
* @return object
*/
fun typeConvert(field: Field, value: String): Any {
var result: Any = value
val fieldType = field.type
if (fieldType != value.javaClass) {
if (Int::class.java == fieldType || Int::class.javaPrimitiveType == fieldType) {
result = value.toInt()
}
if (Double::class.java == fieldType || Double::class.javaPrimitiveType == fieldType) {
result = value.toDouble()
}
if (Float::class.java == fieldType || Float::class.javaPrimitiveType == fieldType) {
result = value.toFloat()
}
if (Date::class.java == fieldType) {
result = DateTime(value, "yyyy-MM-dd HH:mm:ss").date
}
}
return result
}
/**
* 格式化数值类型
*
* @param field 字段对象
* @param value 值
* @return double
*/
fun parseDigital(field: Field, value: String): Double {
val result: Double
val fieldType = field.type
result =
if (Int::class.java == fieldType || Int::class.javaPrimitiveType == fieldType || Double::class.java == fieldType || Double::class.javaPrimitiveType == fieldType || Float::class.java == fieldType || Float::class.javaPrimitiveType == fieldType) {
value.toDouble()
} else throw java.lang.RuntimeException(
String.format(
"class [%s] field [%s] is not digital",
field.declaringClass.name,
field.name
)
)
return result
}

View File

@@ -5,13 +5,13 @@ import org.springframework.beans.factory.FactoryBean
import org.springframework.cglib.proxy.Enhancer
import org.springframework.data.repository.Repository
class JpaRepositoryFactory(
class JpaDbContextFactory(
private val beanFactory: BeanFactory,
private val interfaceType: Class<*>,
private val implBeanNames: List<String>
) : FactoryBean<Any> {
override fun getObject(): Any {
val handler: JpaRepositoryProxy<*, *, *> = JpaRepositoryProxy<Repository<Any, Any>, Any, Any>(
val handler: JpaDbContextProxy<*, *, *> = JpaDbContextProxy<Repository<Any, Any>, Any, Any>(
beanFactory,
interfaceType, implBeanNames
)

View File

@@ -1,6 +1,7 @@
package com.synebula.gaea.jpa.proxy
import com.synebula.gaea.jpa.proxy.method.JpaMethodProxy
import jakarta.persistence.EntityManager
import javassist.*
import javassist.bytecode.AnnotationsAttribute
import javassist.bytecode.MethodInfo
@@ -24,9 +25,8 @@ import org.springframework.data.repository.Repository
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import javax.persistence.EntityManager
class JpaRepositoryProxy<T : Repository<S, ID>?, S, ID>(
class JpaDbContextProxy<T : Repository<S, ID>?, S, ID>(
beanFactory: BeanFactory,
interfaceType: Class<*>,
implementBeanNames: List<String>?

View File

@@ -8,5 +8,5 @@ import kotlin.reflect.KClass
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Inherited
@Import(JpaRepositoryRegister::class)
annotation class JpaRepositoryProxyScan(val basePackages: Array<String> = [], val scanInterfaces: Array<KClass<*>> = [])
@Import(JpaDbContextRegister::class)
annotation class JpaDbContextProxyScan(val basePackages: Array<String> = [], val scanInterfaces: Array<KClass<*>> = [])

View File

@@ -24,17 +24,20 @@ import org.springframework.util.ClassUtils
import java.util.*
import java.util.stream.Collectors
class JpaRepositoryRegister : ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware,
class JpaDbContextRegister : ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware,
EnvironmentAware,
BeanFactoryAware {
private lateinit var environment: Environment
private lateinit var resourceLoader: ResourceLoader
private var classLoader: ClassLoader? = null
private var beanFactory: BeanFactory? = null
override fun registerBeanDefinitions(metadata: AnnotationMetadata, registry: BeanDefinitionRegistry) {
val attributes = AnnotationAttributes(
metadata.getAnnotationAttributes(
JpaRepositoryProxyScan::class.java.name
JpaDbContextProxyScan::class.java.name
) ?: mapOf()
)
val basePackages = attributes.getStringArray("basePackages")
@@ -55,7 +58,7 @@ class JpaRepositoryRegister : ImportBeanDefinitionRegistrar, ResourceLoaderAware
val implClazzDefinitions = scan(basePackages, arrayOf(beanClazzTypeFilter))
for (definition in implClazzDefinitions) {
definition.isAutowireCandidate = false
registry.registerBeanDefinition(Objects.requireNonNull(definition.beanClassName), definition)
registry.registerBeanDefinition(Objects.requireNonNull(definition.beanClassName!!), definition)
}
// 构建bean定义
// 1 bean参数
@@ -66,7 +69,7 @@ class JpaRepositoryRegister : ImportBeanDefinitionRegistrar, ResourceLoaderAware
builder.addConstructorArgValue(beanClazz)
builder.addConstructorArgValue(implBeanNames)
val definition = builder.rawBeanDefinition as GenericBeanDefinition
definition.beanClass = JpaRepositoryFactory::class.java
definition.beanClass = JpaDbContextFactory::class.java
definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE
registry.registerBeanDefinition(beanClazz.name, definition)
}

View File

@@ -5,7 +5,7 @@ import com.synebula.gaea.jpa.proxy.method.resolver.AbstractMethodResolver
import com.synebula.gaea.jpa.proxy.method.resolver.DefaultMethodResolver
import com.synebula.gaea.jpa.proxy.method.resolver.FindMethodResolver
import com.synebula.gaea.jpa.proxy.method.resolver.PageMethodResolver
import com.synebula.gaea.query.Params
import com.synebula.gaea.db.query.Params
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.domain.Specification
import java.lang.reflect.InvocationTargetException

View File

@@ -10,8 +10,8 @@ class FindMethodResolver(targetMethodName: String, clazz: Class<*>) : AbstractMe
@Suppress("UNCHECKED_CAST")
override fun mappingArguments(args: Array<Any>): Array<Any> {
val params = args[0] as Map<String, String>?
val specification = params.toSpecification(entityClazz)
return arrayOf(specification)
val specification = params?.toSpecification(entityClazz)
return if (specification != null) arrayOf(specification) else arrayOf()
}
override fun mappingResult(result: Any): Any {

View File

@@ -1,15 +1,16 @@
package com.synebula.gaea.jpa.proxy.method.resolver
import com.synebula.gaea.jpa.toSpecification
import com.synebula.gaea.query.Order
import com.synebula.gaea.query.Params
import com.synebula.gaea.db.query.Order
import com.synebula.gaea.db.query.Params
import jakarta.persistence.EmbeddedId
import jakarta.persistence.Id
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import java.util.*
import javax.persistence.EmbeddedId
import javax.persistence.Id
import com.synebula.gaea.db.query.Page as QueryPage
/**
* 分页方法参数映射
@@ -29,8 +30,7 @@ class PageMethodResolver(targetMethodName: String, clazz: Class<*>) : AbstractMe
val fields = entityClazz.declaredFields
for (field in fields) {
val isId = Arrays.stream(field.declaredAnnotations).anyMatch { annotation: Annotation ->
(annotation.annotationClass.java == Id::class.java
|| annotation.annotationClass.java == EmbeddedId::class.java)
(annotation.annotationClass.java == Id::class.java || annotation.annotationClass.java == EmbeddedId::class.java)
}
if (isId) {
sort = Sort.by(Sort.Direction.ASC, field.name)
@@ -50,7 +50,7 @@ class PageMethodResolver(targetMethodName: String, clazz: Class<*>) : AbstractMe
override fun mappingResult(result: Any): Any {
val page = result as Page<*>
// Page 页面从0开始
return com.synebula.gaea.query.Page(page.number + 1, page.size, page.totalElements.toInt(), page.content)
// Page 页面从0开始 [com.synebula.gaea.query.Page as QueryPage]
return QueryPage(page.number + 1, page.size, page.totalElements.toInt(), page.content)
}
}

View File

@@ -1,9 +1,9 @@
package com.synebula.gaea.mongodb
import com.synebula.gaea.data.date.DateTime
import com.synebula.gaea.query.Operator
import com.synebula.gaea.query.Order
import com.synebula.gaea.query.Where
import com.synebula.gaea.db.query.Operator
import com.synebula.gaea.db.query.Order
import com.synebula.gaea.db.query.Where
import org.springframework.data.domain.Sort
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Query

View File

@@ -4,11 +4,11 @@ import com.synebula.gaea.spring.autoconfig.Factory
import com.synebula.gaea.spring.autoconfig.Proxy
import org.springframework.beans.factory.BeanFactory
class MongodbRepositoryFactory(
class MongoDbContextFactory(
supertype: Class<*>,
var beanFactory: BeanFactory,
) : Factory(supertype) {
override fun createProxy(): Proxy {
return MongodbRepositoryProxy(supertype, this.beanFactory)
return MongoDbContextProxy(supertype, this.beanFactory)
}
}

View File

@@ -0,0 +1,69 @@
package com.synebula.gaea.mongodb.autoconfig
import com.synebula.gaea.db.context.IDbContext
import com.synebula.gaea.domain.repository.IRepository
import com.synebula.gaea.mongodb.db.query.MongodbQuery
import com.synebula.gaea.mongodb.repository.MongodbRepository
import com.synebula.gaea.db.query.IQuery
import com.synebula.gaea.reflect.getGenericInterface
import com.synebula.gaea.spring.autoconfig.Proxy
import org.springframework.beans.factory.BeanFactory
import org.springframework.data.mongodb.core.MongoTemplate
import java.lang.reflect.Method
class MongoDbContextProxy(
private var supertype: Class<*>, private var beanFactory: BeanFactory
) : Proxy() {
/**
* 实际的执行类
*/
private var context: Any?
init {
if (this.supertype.interfaces.any { it == IDbContext::class.java }) {
this.context = beanFactory.getBean(IDbContext::class.java)
if (context == null) {
val constructor = IDbContext::class.java.getConstructor(Class::class.java, MongoTemplate::class.java)
this.context = constructor.newInstance(this.beanFactory.getBean(MongoTemplate::class.java))
}
} else {
// 判断接口类型
val clazz: Class<*> // 代理服务类型
val interfaceClazz: Class<*> // 代理服务接口
if (this.supertype.interfaces.any { it == IQuery::class.java }) {
clazz = MongodbQuery::class.java
interfaceClazz = IQuery::class.java
} else {
clazz = MongodbRepository::class.java
interfaceClazz = IRepository::class.java
}
val constructor = clazz.getConstructor(Class::class.java, MongoTemplate::class.java)
this.context = constructor.newInstance(
this.supertype.getGenericInterface(interfaceClazz)!!.actualTypeArguments[0],
this.beanFactory.getBean(MongoTemplate::class.java)
)
}
}
/**
* 执行代理方法
*
* @param proxy 代理对象
* @param method 需要执行的方法
* @param args 参数列表
* @return 方法执行结果
*/
override fun exec(proxy: Any, method: Method, args: Array<Any>): Any? {
try {
val proxyMethod: Method = this.context!!.javaClass.getMethod(method.name, *method.parameterTypes)
return proxyMethod.invoke(this.context, *args)
} catch (ex: NoSuchMethodException) {
throw NoSuchMethodException("method [${method.toGenericString()}] not implements in class [${this.context!!.javaClass}], you must implements interface [${this.supertype.name}] ")
}
}
}

View File

@@ -1,7 +1,8 @@
package com.synebula.gaea.mongodb.autoconfig
import com.synebula.gaea.db.context.IDbContext
import com.synebula.gaea.db.query.IQuery
import com.synebula.gaea.domain.repository.IRepository
import com.synebula.gaea.query.IQuery
import com.synebula.gaea.spring.autoconfig.Register
import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
@@ -9,20 +10,20 @@ import org.springframework.beans.factory.support.GenericBeanDefinition
import org.springframework.core.annotation.AnnotationAttributes
import org.springframework.core.type.AnnotationMetadata
class MongodbRepositoryRegister : Register() {
class MongoDbContextRegister : Register() {
override fun scan(metadata: AnnotationMetadata): Map<String, BeanDefinition> {
val result = mutableMapOf<String, BeanDefinition>()
// 获取注解参数信息:basePackages
val attributes = AnnotationAttributes(
metadata.getAnnotationAttributes(
MongodbRepositoryScan::class.java.name
MongoDbRepositoryScan::class.java.name
) ?: mapOf()
)
val basePackages = attributes.getStringArray("basePackages")
val beanDefinitions = this.doScan(
basePackages,
arrayOf(this.interfaceFilter(arrayOf(IRepository::class.java, IQuery::class.java)))
arrayOf(this.interfaceFilter(arrayOf(IDbContext::class.java, IQuery::class.java, IRepository::class.java)))
)
beanDefinitions.forEach { beanDefinition ->
// 获取实际的bean类型
@@ -44,7 +45,7 @@ class MongodbRepositoryRegister : Register() {
builder.addConstructorArgValue(beanClazz)
builder.addConstructorArgValue(this._beanFactory)
val definition = builder.rawBeanDefinition as GenericBeanDefinition
definition.beanClass = MongodbRepositoryFactory::class.java
definition.beanClass = MongoDbContextFactory::class.java
definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE
result[beanClazz.name] = definition
}
@@ -59,7 +60,7 @@ class MongodbRepositoryRegister : Register() {
builder.addConstructorArgValue(this._beanFactory)
builder.addConstructorArgValue(emptyArray<String>())
val definition = builder.rawBeanDefinition as GenericBeanDefinition
definition.beanClass = MongodbRepositoryFactory::class.java
definition.beanClass = MongoDbContextFactory::class.java
definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE
result[IRepository::class.java.name] = definition
}

View File

@@ -8,5 +8,5 @@ import java.lang.annotation.Inherited
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Inherited
@Import(MongodbRepositoryRegister::class)
annotation class MongodbRepositoryScan(val basePackages: Array<String> = [])
@Import(MongoDbContextRegister::class)
annotation class MongoDbRepositoryScan(val basePackages: Array<String> = [])

View File

@@ -1,55 +0,0 @@
package com.synebula.gaea.mongodb.autoconfig
import com.synebula.gaea.domain.repository.IRepository
import com.synebula.gaea.mongodb.query.MongodbQuery
import com.synebula.gaea.mongodb.repository.MongodbRepository
import com.synebula.gaea.query.IQuery
import com.synebula.gaea.reflect.getGenericInterface
import com.synebula.gaea.spring.autoconfig.Proxy
import org.springframework.beans.factory.BeanFactory
import org.springframework.data.mongodb.core.MongoTemplate
import java.lang.reflect.Method
class MongodbRepositoryProxy(
private var supertype: Class<*>, private var beanFactory: BeanFactory
) : Proxy() {
private var mongodbRepo: Any
init {
// 判断接口类型
val clazz: Class<*> // 代理服务类型
val interfaceClazz: Class<*> // 代理服务接口
if (this.supertype.interfaces.any { it == IRepository::class.java }) {
clazz = MongodbRepository::class.java
interfaceClazz = IRepository::class.java
} else {
clazz = MongodbQuery::class.java
interfaceClazz = IQuery::class.java
}
val constructor = clazz.getConstructor(Class::class.java, MongoTemplate::class.java)
this.mongodbRepo = constructor.newInstance(
this.supertype.getGenericInterface(interfaceClazz)!!.actualTypeArguments[0],
this.beanFactory.getBean(MongoTemplate::class.java)
)
}
/**
* 执行代理方法
*
* @param proxy 代理对象
* @param method 需要执行的方法
* @param args 参数列表
* @return 方法执行结果
*/
override fun exec(proxy: Any, method: Method, args: Array<Any>): Any? {
try {
val proxyMethod: Method = this.mongodbRepo.javaClass.getMethod(method.name, *method.parameterTypes)
return proxyMethod.invoke(this.mongodbRepo, *args)
} catch (ex: NoSuchMethodException) {
throw NoSuchMethodException("method [${method.toGenericString()}] not implements in class [${this.mongodbRepo.javaClass}], you must implements interface [${this.supertype.name}] ")
}
}
}

View File

@@ -0,0 +1,50 @@
package com.synebula.gaea.mongodb.db.context
import com.synebula.gaea.db.IEntity
import com.synebula.gaea.db.context.IDbContext
import com.synebula.gaea.mongodb.where
import com.synebula.gaea.mongodb.whereId
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.query.Query
class MongodbContext(
protected var template: MongoTemplate
) : IDbContext {
override fun <TEntity : IEntity<ID>, ID> add(entity: TEntity, clazz: Class<TEntity>) {
this.template.save(entity)
}
override fun <TEntity : IEntity<ID>, ID> add(entities: List<TEntity>, clazz: Class<TEntity>) {
this.template.insert(entities, clazz)
}
override fun <TEntity : IEntity<ID>, ID> remove(id: ID, clazz: Class<TEntity>) {
this.template.remove(whereId(id), clazz)
}
override fun <TEntity : IEntity<ID>, ID> get(id: ID, clazz: Class<TEntity>): TEntity? {
return this.template.findOne(whereId(id), clazz)
}
override fun <TEntity : IEntity<ID>, ID> update(entity: TEntity, clazz: Class<TEntity>) {
this.template.save(entity)
}
override fun <TEntity : IEntity<ID>, ID> update(entities: List<TEntity>, clazz: Class<TEntity>) {
this.template.save(entities)
}
override fun <TEntity : IEntity<ID>, ID> count(params: Map<String, String>?, clazz: Class<TEntity>): Int {
val query = Query()
return this.template.count(query.where(params, clazz), clazz).toInt()
}
override val isCommitted=true
override fun commit() {
}
override fun rollback() {
}
}

View File

@@ -1,24 +1,20 @@
package com.synebula.gaea.mongodb.query
package com.synebula.gaea.mongodb.db.query
import com.synebula.gaea.ext.firstCharLowerCase
import com.synebula.gaea.mongodb.order
import com.synebula.gaea.mongodb.select
import com.synebula.gaea.mongodb.where
import com.synebula.gaea.mongodb.whereId
import com.synebula.gaea.query.IQuery
import com.synebula.gaea.query.Page
import com.synebula.gaea.query.Params
import com.synebula.gaea.query.Table
import com.synebula.gaea.db.query.IQuery
import com.synebula.gaea.db.query.Page
import com.synebula.gaea.db.query.Params
import com.synebula.gaea.db.query.Table
import com.synebula.gaea.reflect.fieldNames
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Query
/**
* 实现IQuery的Mongodb查询类
* @param template MongodbRepo对象
*/
open class MongodbQuery<TView, ID>(override var clazz: Class<TView>, var template: MongoTemplate) :
IQuery<TView, ID> {

View File

@@ -1,7 +1,7 @@
package com.synebula.gaea.mongodb.query
package com.synebula.gaea.mongodb.db.query
import com.synebula.gaea.query.IQuery
import com.synebula.gaea.query.IQueryFactory
import com.synebula.gaea.db.query.IQuery
import com.synebula.gaea.db.query.IQueryFactory
import org.springframework.data.mongodb.core.MongoTemplate
class MongodbQueryFactory(var template: MongoTemplate) : IQueryFactory {

View File

@@ -1,88 +0,0 @@
package com.synebula.gaea.mongodb.query
import com.synebula.gaea.ext.firstCharLowerCase
import com.synebula.gaea.mongodb.order
import com.synebula.gaea.mongodb.select
import com.synebula.gaea.mongodb.where
import com.synebula.gaea.mongodb.whereId
import com.synebula.gaea.query.IUniversalQuery
import com.synebula.gaea.query.Page
import com.synebula.gaea.query.Params
import com.synebula.gaea.query.Table
import com.synebula.gaea.reflect.fieldNames
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Query
/**
* 实现IQuery的Mongodb查询类
* @param template MongodbRepo对象
*/
open class MongodbUniversalQuery(var template: MongoTemplate) : IUniversalQuery {
/**
* 使用View解析是collection时是否校验存在默认不校验
*/
var validViewCollection = false
override fun <TView, ID> get(id: ID, clazz: Class<TView>): TView? {
return this.template.findOne(whereId(id), clazz, this.collection(clazz))
}
override fun <TView> list(params: Map<String, String>?, clazz: Class<TView>): List<TView> {
val fields = this.fields(clazz)
val query = Query()
query.where(params, clazz)
query.select(fields)
return this.find(query, clazz)
}
override fun <TView> count(params: Map<String, String>?, clazz: Class<TView>): Int {
val query = Query()
return this.template.count(query.where(params, clazz), this.collection(clazz)).toInt()
}
override fun <TView> paging(params: Params, clazz: Class<TView>): Page<TView> {
val query = Query()
val fields = this.fields(clazz)
val result = Page<TView>(params.page, params.size)
result.total = this.count(params.parameters, clazz)
//如果总数和索引相同,说明该页没有数据,直接跳到上一页
if (result.total == result.index) {
params.page -= 1
result.page -= 1
}
query.select(fields)
query.where(params.parameters, clazz)
query.with(order(params.orders))
query.skip(params.index).limit(params.size)
result.data = this.find(query, clazz)
return result
}
override fun <TView> range(field: String, params: List<Any>, clazz: Class<TView>): List<TView> {
return this.find(Query.query(Criteria.where(field).`in`(params)), clazz)
}
protected fun <TView> find(query: Query, clazz: Class<TView>): List<TView> {
return this.template.find(query, clazz, this.collection(clazz))
}
fun <TView> fields(clazz: Class<TView>): Array<String> {
return clazz.fieldNames().toTypedArray()
}
/**
* 获取collection
*/
fun <TView> collection(clazz: Class<TView>): String {
val table = clazz.getDeclaredAnnotation(Table::class.java)
return if (table != null) table.name
else {
val name = clazz.simpleName.removeSuffix("View").firstCharLowerCase()
if (!validViewCollection || this.template.collectionExists(name)) name
else throw RuntimeException("找不到名为[${clazz.name}]的集合")
}
}
}

View File

@@ -1,61 +0,0 @@
package com.synebula.gaea.mongodb.repository
import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.repository.IUniversalRepository
import com.synebula.gaea.mongodb.where
import com.synebula.gaea.mongodb.whereId
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.query.Query
/**
* 实现ITypedRepository的Mongodb仓储类
* @param repo MongodbRepo对象
*/
open class MongodbUniversalRepository(private var repo: MongoTemplate) : IUniversalRepository {
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> remove(id: ID, clazz: Class<TAggregateRoot>) {
this.repo.remove(whereId(id), clazz)
}
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> get(
id: ID,
clazz: Class<TAggregateRoot>,
): TAggregateRoot? {
return this.repo.findOne(whereId(id), clazz)
}
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> update(
root: TAggregateRoot,
clazz: Class<TAggregateRoot>,
) {
this.repo.save(root)
}
/**
* 更新多个个对象。
*
* @param roots 需要更新的对象。
*/
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> update(
roots: List<TAggregateRoot>,
clazz: Class<TAggregateRoot>
) {
this.repo.save(roots)
}
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> add(root: TAggregateRoot, clazz: Class<TAggregateRoot>) {
this.repo.save(root)
}
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> add(
roots: List<TAggregateRoot>,
clazz: Class<TAggregateRoot>,
) {
this.repo.insert(roots, clazz)
}
override fun <TAggregateRoot> count(params: Map<String, String>?, clazz: Class<TAggregateRoot>): Int {
val query = Query()
return this.repo.count(query.where(params, clazz), clazz).toInt()
}
}

View File

@@ -5,7 +5,6 @@ import com.synebula.gaea.data.serialization.json.IJsonSerializer
class HttpMessage(private var serializer: IJsonSerializer) : DataMessage<Any>() {
constructor(data: Any, serializer: IJsonSerializer) : this(serializer) {
this.data = data
}

View File

@@ -0,0 +1,10 @@
package com.synebula.gaea.data.permission
/**
* 元素授权类型
*/
enum class AuthorityType {
Default,
Deny,
Allow
}

View File

@@ -0,0 +1,28 @@
package com.synebula.gaea.data.permission
/**
* 角色权限类型
*/
enum class PermissionType {
/**
* 拥有所有权限
*/
All,
/**
* 拥有最大权限
* 不配置无权则有权限
*/
Maximum,
/**
* 最小权限
* 不配置有权则无权限
*/
Minimum,
/**
* 没有任何权限
*/
None
}

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.domain.model
package com.synebula.gaea.db
/**
* 继承本接口说明对象为实体类型

View File

@@ -0,0 +1,66 @@
package com.synebula.gaea.db.context
import com.synebula.gaea.db.IEntity
/**
* 继承自IUnitOfWork表示实现了工作单元模式的上下文接口。
*
* @author alex
*/
interface IDbContext : IUnitOfWork {
/**
* 插入单个对象。
*
* @param entity 需要插入的对象。
* @return 返回原对象如果对象ID为自增则补充自增ID。
*/
fun <TEntity : IEntity<ID>, ID> add(entity: TEntity, clazz: Class<TEntity>)
/**
* 插入多个个对象。
*
* @param entities 需要插入的对象。
*/
fun <TEntity : IEntity<ID>, ID> add(entities: List<TEntity>, clazz: Class<TEntity>)
/**
* 更新对象。
*
* @param entity 需要更新的对象。
* @return
*/
fun <TEntity : IEntity<ID>, ID> update(entity: TEntity, clazz: Class<TEntity>)
/**
* 更新多个个对象。
*
* @param entities 需要更新的对象。
*/
fun <TEntity : IEntity<ID>, ID> update(entities: List<TEntity>, clazz: Class<TEntity>)
/**
* 通过id删除该条数据
*
* @param id id
* @param clazz 操作数据的类型
*/
fun <TEntity : IEntity<ID>, ID> remove(id: ID, clazz: Class<TEntity>)
/**
* 根据ID获取对象。
*
* @param id id
* @param clazz 操作数据的类型
* @return 聚合根
*/
fun <TEntity : IEntity<ID>, ID> get(id: ID, clazz: Class<TEntity>): TEntity?
/**
* 根据条件查询符合条件记录的数量
*
* @param params 查询条件。
* @return int
*/
fun <TEntity : IEntity<ID>, ID> count(params: Map<String, String>?, clazz: Class<TEntity>): Int
}

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.domain.repository.context
package com.synebula.gaea.db.context
/**
* 表示所有继承于该接口的类型都是Unit Of Work的一种实现

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.query
package com.synebula.gaea.db.query
/**
* 查询基接口, 其中方法都指定了查询的视图类型

View File

@@ -1,5 +1,4 @@
package com.synebula.gaea.query
package com.synebula.gaea.db.query
/**
* Query 工厂接口 定义了Query的创建方法

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.query
package com.synebula.gaea.db.query
enum class Operator {
/**

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.query
package com.synebula.gaea.db.query
/**
* class OrderType

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.query
package com.synebula.gaea.db.query
/**
* 分页数据

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.query
package com.synebula.gaea.db.query
/**
* class 分页参数信息

View File

@@ -1,3 +1,3 @@
package com.synebula.gaea.query
package com.synebula.gaea.db.query
annotation class Table(val name: String = "")

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.query
package com.synebula.gaea.db.query
/**
* 字段注解规定字段的查询方式

View File

@@ -1,6 +1,6 @@
package com.synebula.gaea.domain.event
import com.synebula.gaea.data.message.IEvent
import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.db.IEntity
class AfterRemoveEvent<T : IAggregateRoot<I>, I>(var id: I? = null) : IEvent
class AfterRemoveEvent<T : IEntity<I>, I>(var id: I? = null) : IEvent

View File

@@ -1,6 +1,7 @@
package com.synebula.gaea.domain.event
import com.synebula.gaea.data.message.IEvent
import com.synebula.gaea.db.IEntity
import com.synebula.gaea.domain.model.IAggregateRoot
class BeforeRemoveEvent<T : IAggregateRoot<I>, I>(var id: I? = null) : IEvent
class BeforeRemoveEvent<T : IEntity<I>, I>(var id: I? = null) : IEvent

View File

@@ -1,5 +1,5 @@
package com.synebula.gaea.domain.model
abstract class AggregateRoot<ID> : Entity<ID>(), IAggregateRoot<ID> {
override var alive: Boolean = true
override var avalible: Boolean = true
}

View File

@@ -1,3 +1,5 @@
package com.synebula.gaea.domain.model
import com.synebula.gaea.db.IEntity
abstract class Entity<ID> : IEntity<ID>

View File

@@ -1,5 +1,7 @@
package com.synebula.gaea.domain.model
import com.synebula.gaea.db.IEntity
/**
* 继承本接口,说明对象为聚合根。
*
@@ -10,5 +12,5 @@ interface IAggregateRoot<ID> : IEntity<ID> {
/**
* 实体对象是否有效。
*/
var alive: Boolean
var avalible: Boolean
}

View File

@@ -1,7 +0,0 @@
package com.synebula.gaea.domain.model
/**
* 继承本接口,说明对象为值类型。
* @author alex
*/
interface IValue

View File

@@ -1,65 +0,0 @@
package com.synebula.gaea.domain.repository
import com.synebula.gaea.domain.model.IAggregateRoot
/**
* 定义了提供增删改的仓储接口。
* 本接口泛型放置到方法上并需要显式提供聚合根的class对象
*/
interface IUniversalRepository {
/**
* 插入单个对象。
*
* @param root 需要插入的对象。
* @return 返回原对象如果对象ID为自增则补充自增ID。
*/
fun <TAggregateRoot : IAggregateRoot<ID>, ID> add(root: TAggregateRoot, clazz: Class<TAggregateRoot>)
/**
* 插入多个个对象。
*
* @param roots 需要插入的对象。
*/
fun <TAggregateRoot : IAggregateRoot<ID>, ID> add(roots: List<TAggregateRoot>, clazz: Class<TAggregateRoot>)
/**
* 更新对象。
*
* @param root 需要更新的对象。
* @return
*/
fun <TAggregateRoot : IAggregateRoot<ID>, ID> update(root: TAggregateRoot, clazz: Class<TAggregateRoot>)
/**
* 更新多个个对象。
*
* @param roots 需要更新的对象。
*/
fun <TAggregateRoot : IAggregateRoot<ID>, ID> update(roots: List<TAggregateRoot>, clazz: Class<TAggregateRoot>)
/**
* 通过id删除该条数据
*
* @param id id
* @param clazz 操作数据的类型
*/
fun <TAggregateRoot : IAggregateRoot<ID>, ID> remove(id: ID, clazz: Class<TAggregateRoot>)
/**
* 根据ID获取对象。
*
* @param id id
* @param clazz 操作数据的类型
* @return 聚合根
*/
fun <TAggregateRoot : IAggregateRoot<ID>, ID> get(id: ID, clazz: Class<TAggregateRoot>): TAggregateRoot?
/**
* 根据条件查询符合条件记录的数量
*
* @param params 查询条件。
* @return int
*/
fun <TAggregateRoot> count(params: Map<String, String>?, clazz: Class<TAggregateRoot>): Int
}

View File

@@ -1,30 +0,0 @@
package com.synebula.gaea.domain.repository.context
import com.synebula.gaea.domain.model.IAggregateRoot
/**
* 继承自IUnitOfWork表示实现了工作单元模式的上下文接口。
*
* @author alex
*/
interface IContext : IUnitOfWork {
/**
* 将指定的聚合根标注为“新建”状态。
* @param obj 聚合根
*/
fun <T : IAggregateRoot<ID>, ID> add(obj: T)
/**
* 将指定的聚合根标注为“更改”状态。
*
* @param obj 聚合根
*/
fun <T : IAggregateRoot<ID>, ID> update(obj: T)
/**
* 将指定的聚合根标注为“删除”状态。
*
* @param obj 聚合根
*/
fun <T : IAggregateRoot<ID>, ID> remove(obj: T)
}

View File

@@ -7,7 +7,6 @@ import com.synebula.gaea.domain.event.AfterRemoveEvent
import com.synebula.gaea.domain.event.BeforeRemoveEvent
import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.repository.IRepository
import javax.annotation.Resource
/**
@@ -25,9 +24,8 @@ open class Service<TRoot : IAggregateRoot<ID>, ID>(
protected open var clazz: Class<TRoot>,
protected open var repository: IRepository<TRoot, ID>,
protected open var mapper: IObjectMapper,
) : IService<ID> {
@Resource
protected open var bus: IBus<Any>? = null
) : IService<ID> {
/**
* 增加对象

View File

@@ -1,69 +0,0 @@
package com.synebula.gaea.domain.service
import com.synebula.gaea.bus.IBus
import com.synebula.gaea.data.message.DataMessage
import com.synebula.gaea.domain.event.AfterRemoveEvent
import com.synebula.gaea.domain.event.BeforeRemoveEvent
import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.repository.IRepository
import com.synebula.gaea.log.ILogger
import javax.annotation.Resource
/**
* 依赖了IRepository仓储借口的服务实现类 GenericsService
* 该类依赖仓储接口 @see IGenericsRepository, 需要显式提供聚合根的class对象
*
* @param repository 仓储对象
* @param clazz 聚合根类对象
* @param logger 日志组件
* @author alex
* @version 0.1
* @since 2020-05-17
*/
open class SimpleService<TRoot : IAggregateRoot<ID>, ID>(
protected open var clazz: Class<TRoot>,
protected open var repository: IRepository<TRoot, ID>,
override var logger: ILogger,
) : ISimpleService<TRoot, ID> {
@Resource
protected open var bus: IBus<Any>? = null
override fun add(root: TRoot): DataMessage<ID> {
val msg = DataMessage<ID>()
this.repository.add(root)
msg.data = root.id
return msg
}
override fun update(id: ID, root: TRoot) {
root.id = id
this.repository.update(root)
}
override fun remove(id: ID) {
val beforeRemoveEvent = BeforeRemoveEvent<TRoot, ID>(id)
this.bus?.publish(beforeRemoveEvent.topic(this.clazz), beforeRemoveEvent)
this.repository.remove(id)
val afterRemoveEvent = AfterRemoveEvent<TRoot, ID>(id)
this.bus?.publish(afterRemoveEvent.topic(this.clazz), afterRemoveEvent)
}
/**
* 增加对象
*
* @param roots 增加对象命令列表
*/
override fun add(roots: List<TRoot>) {
this.repository.add(roots)
}
/**
* 批量更新对象
*
* @param roots 更新对象命令列表
*/
override fun update(roots: List<TRoot>) {
this.repository.update(roots)
}
}

View File

@@ -1,49 +0,0 @@
package com.synebula.gaea.query
/**
* 查询基接口, 其中方法都指定了查询的视图类型。
*
* @author alex
*/
interface IUniversalQuery {
/**
* 根据Key获取对象。
*
* @param id 对象Key。
* @return 视图结果
*/
fun <TView, ID> get(id: ID, clazz: Class<TView>): TView?
/**
* 根据实体类条件查询所有符合条件记录
*
* @param params 查询条件。
* @return 视图列表
*/
fun <TView> list(params: Map<String, String>?, clazz: Class<TView>): List<TView>
/**
* 根据条件查询符合条件记录的数量
*
* @param params 查询条件。
* @return 数量
*/
fun <TView> count(params: Map<String, String>?, clazz: Class<TView>): Int
/**
* 根据实体类条件查询所有符合条件记录(分页查询)
*
* @param params 分页条件
* @return 分页数据
*/
fun <TView> paging(params: Params, clazz: Class<TView>): Page<TView>
/**
* 查询条件范围内数据。
* @param field 查询字段
* @param params 查询条件
*
* @return 视图列表
*/
fun <TView> range(field: String, params: List<Any>, clazz: Class<TView>): List<TView>
}

View File

@@ -0,0 +1,5 @@
package com.synebula.gaea.record.model
import com.synebula.gaea.db.IEntity
interface IRecord<ID> : IEntity<ID>

View File

@@ -1,4 +1,4 @@
package com.synebula.gaea.domain.record
package com.synebula.gaea.record.model
import java.util.*

View File

@@ -1,7 +1,7 @@
package com.synebula.gaea.domain.service
package com.synebula.gaea.record.service
import com.synebula.gaea.data.message.DataMessage
import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.db.IEntity
import com.synebula.gaea.log.ILogger
@@ -11,7 +11,7 @@ import com.synebula.gaea.log.ILogger
* @version 0.0.1
* @since 2016年9月18日 下午2:23:15
*/
interface ISimpleService<TAggregateRoot : IAggregateRoot<ID>, ID> {
interface IService<Entity : IEntity<ID>, ID> {
/**
* 日志组件
*/
@@ -20,31 +20,31 @@ interface ISimpleService<TAggregateRoot : IAggregateRoot<ID>, ID> {
/**
* 增加对象
*
* @param root 增加对象命令
* @param entity 增加对象命令
*/
fun add(root: TAggregateRoot): DataMessage<ID>
fun add(entity: Entity): ID?
/**
* 增加对象
*
* @param roots 增加对象命令列表
* @param entities 增加对象命令列表
*/
fun add(roots: List<TAggregateRoot>)
fun add(entities: List<Entity>)
/**
* 更新对象
*
* @param id 对象ID
* @param root 更新对象命令
* @param entity 更新对象命令
*/
fun update(id: ID, root: TAggregateRoot)
fun update(id: ID, entity: Entity)
/**
* 批量更新对象
*
* @param roots 更新对象命令列表
* @param entities 更新对象命令列表
*/
fun update(roots: List<TAggregateRoot>)
fun update(entities: List<Entity>)
/**
* 增加对象

View File

@@ -0,0 +1,65 @@
package com.synebula.gaea.record.service
import com.synebula.gaea.bus.IBus
import com.synebula.gaea.data.message.DataMessage
import com.synebula.gaea.db.context.IDbContext
import com.synebula.gaea.domain.event.AfterRemoveEvent
import com.synebula.gaea.domain.event.BeforeRemoveEvent
import com.synebula.gaea.log.ILogger
import com.synebula.gaea.record.model.IRecord
/**
* 依赖了IRepository仓储借口的服务实现类 GenericsService
* 该类依赖仓储接口 @see IGenericsRepository, 需要显式提供聚合根的class对象
*
* @param context 仓储对象
* @param clazz 聚合根类对象
* @param logger 日志组件
* @author alex
* @version 0.1
* @since 2020-05-17
*/
open class Service<TEntity : IRecord<ID>, ID>(
protected open var clazz: Class<TEntity>,
protected open var context: IDbContext,
protected open var bus: IBus<Any>? = null,
override var logger: ILogger
) : IService<TEntity, ID> {
override fun add(entity: TEntity): ID? {
this.context.add(entity, clazz)
return entity.id
}
override fun update(id: ID, entity: TEntity) {
entity.id = id
this.context.update(entity, clazz)
}
override fun remove(id: ID) {
val beforeRemoveEvent = BeforeRemoveEvent<TEntity, ID>(id)
this.bus?.publish(beforeRemoveEvent.topic(this.clazz), beforeRemoveEvent)
this.context.remove(id, clazz)
val afterRemoveEvent = AfterRemoveEvent<TEntity, ID>(id)
this.bus?.publish(afterRemoveEvent.topic(this.clazz), afterRemoveEvent)
}
/**
* 增加对象
*
* @param entitys 增加对象命令列表
*/
override fun add(entitys: List<TEntity>) {
this.context.add(entitys, clazz)
}
/**
* 批量更新对象
*
* @param entitys 更新对象命令列表
*/
override fun update(entitys: List<TEntity>) {
this.context.update(entitys, clazz)
}
}