Compare commits

...

11 Commits

Author SHA1 Message Date
99877eddcc feat:1.7 重构 service query 的组织模式 2024-09-30 22:55:02 +08:00
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
82 changed files with 1146 additions and 781 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.7.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,27 +51,36 @@ 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 {
// repositories {
// maven {
// allowInsecureProtocol = true
// url = "$nexus_url"
// credentials {
// username = "$nexus_usr"
// password = "$nexus_pwd"
// }
// }
// }
repositories {
maven {
name = "Gitea"
url = uri("https://git.synebula.com/api/packages/alex/maven")
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = "token 1b9a13c1c75832c2f82beba8c8db340364eff7b1"
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
publications {
mavenJava(MavenPublication) {

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,11 +17,12 @@ 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>,
override var logger: ILogger,
override var query: IQuery,
override var clazz: Class<TView>,
override var logger: ILogger
) : ICommandApp<TCommand, ID>, IQueryApp<TView, ID> {
@Autowired

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,14 @@ 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 query: IQuery<TRoot, ID>,
override var service: IService<TRoot, ID>,
override var query: IQuery,
override var clazz: Class<TRoot>,
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
@@ -13,12 +13,14 @@ interface IQueryApp<TView, ID> : IApplication {
/**
* 查询服务
*/
var query: IQuery<TView, ID>
var query: IQuery
var clazz: Class<TView>
@Method("获取数据")
@GetMapping("/{id:.+}")
fun get(@PathVariable id: ID): HttpMessage {
val data = this.query.get(id)
val data = this.query.get(id, clazz)
val msg = this.httpMessageFactory.create()
msg.data = data
return msg
@@ -27,7 +29,7 @@ interface IQueryApp<TView, ID> : IApplication {
@Method("获取列表数据")
@GetMapping
fun list(@RequestParam params: LinkedHashMap<String, String>): HttpMessage {
val data = this.query.list(params)
val data = this.query.list(params, clazz)
return this.httpMessageFactory.create(data)
}
@@ -39,7 +41,7 @@ interface IQueryApp<TView, ID> : IApplication {
@RequestParam parameters: LinkedHashMap<String, String>
): HttpMessage {
val params = Params(page, size, parameters)
val data = this.query.paging(params)
val data = this.query.paging(params, clazz)
return this.httpMessageFactory.create(data)
}
}

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
/**
@@ -14,7 +14,8 @@ import org.springframework.beans.factory.annotation.Autowired
*/
open class QueryApp<TView, ID>(
override var name: String,
override var query: IQuery<TView, ID>,
override var query: IQuery,
override var clazz: Class<TView>,
override var logger: ILogger,
) : IQueryApp<TView, ID> {

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,20 +97,15 @@ 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出错")
this.logger.error(this, ex, "解析token出错")
throw ex
}
}
@@ -103,19 +117,14 @@ 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出错")
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,16 +37,18 @@ 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, _ ->
).exceptionHandling {
it.authenticationEntryPoint { _, response, _ ->
response.status = Status.Success
response.characterEncoding = "utf-8"
response.contentType = "text/javascript;charset=utf-8"
@@ -55,6 +58,7 @@ class WebSecurity {
)
)
}
}
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,20 +1,22 @@
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 com.synebula.gaea.jpa.proxy.method.resolver.PageMethodResolver
import jakarta.persistence.EntityManager
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.domain.Specification
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>
@Suppress("UNCHECKED_CAST")
class JpaQuery(protected var entityManager: EntityManager) : IQuery {
protected var repos = mutableMapOf<Class<*>, SimpleJpaRepository<*, *>>()
init {
repo = SimpleJpaRepository<TView, ID>(clazz, entityManager)
}
override operator fun get(id: ID): TView? {
val view = this.repo.findById(id)
override operator fun <TView, ID> get(id: ID, clazz: Class<TView>): TView? {
val repo = this.getJpaRepository(clazz)
val view = repo.findById(id!!)
return if (view.isPresent) view.get() else null
}
@@ -25,9 +27,10 @@ class JpaQuery<TView, ID>(override var clazz: Class<TView>, entityManager: Entit
* @param params 查询条件。
* @return 视图列表
*/
override fun list(params: Map<String, String>?): List<TView> {
// method proxy in JpaRepositoryProxy [SimpleJpaRepository]
return emptyList()
override fun <TView> list(params: Map<String, String>?, clazz: Class<TView>): List<TView> {
val repo = this.getJpaRepository(clazz)
val spec = params?.toSpecification(clazz) as Specification<TView>
return repo.findAll(spec)
}
/**
@@ -36,9 +39,10 @@ class JpaQuery<TView, ID>(override var clazz: Class<TView>, entityManager: Entit
* @param params 查询条件。
* @return 数量
*/
override fun count(params: Map<String, String>?): Int {
// method proxy in JpaRepositoryProxy [SimpleJpaRepository]
return -1
override fun <TView> count(params: Map<String, String>?, clazz: Class<TView>): Long {
val repo = this.getJpaRepository(clazz)
val spec = params?.toSpecification(clazz) as Specification<TView>
return repo.count(spec)
}
/**
@@ -47,9 +51,11 @@ class JpaQuery<TView, ID>(override var clazz: Class<TView>, entityManager: Entit
* @param params 分页条件
* @return 分页数据
*/
override fun paging(params: Params): Page<TView> {
// method proxy in JpaRepositoryProxy [SimpleJpaRepository]
return Page()
override fun <TView> paging(params: Params, clazz: Class<TView>): Page<TView> {
val repo = this.getJpaRepository(clazz)
val p = PageMethodResolver("findAll", clazz).mappingArguments(arrayOf(params))
val page = repo.findAll(p[0] as Specification<TView>, p[1] as Pageable)
return Page(page.number + 1, page.size, page.totalElements, page.content)
}
/**
@@ -59,9 +65,18 @@ class JpaQuery<TView, ID>(override var clazz: Class<TView>, entityManager: Entit
*
* @return 视图列表
*/
override fun range(field: String, params: List<Any>): List<TView> {
override fun <TView> range(field: String, params: List<Any>, clazz: Class<TView>): List<TView> {
// method proxy in JpaRepositoryProxy [SimpleJpaRepository]
return emptyList()
}
private fun <TView> getJpaRepository(clazz: Class<TView>): SimpleJpaRepository<TView, in Any> {
if (this.repos.isNotEmpty() && this.repos.containsKey(clazz)) {
return this.repos[clazz] as SimpleJpaRepository<TView, Any>
} else {
val r = SimpleJpaRepository<TView, Any>(clazz, entityManager)
this.repos[clazz] = r
return r
}
}
}

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

@@ -0,0 +1,17 @@
package com.synebula.gaea.jpa
import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.repository.IRepository
import com.synebula.gaea.domain.repository.IRepositoryFactory
import jakarta.persistence.EntityManager
class JpaRepositoryFactory(private var entityManager: EntityManager) : IRepositoryFactory {
override fun createRawRepository(clazz: Class<*>): IRepository<*, *> {
val constructor = JpaRepository::class.java.getConstructor(Class::class.java, EntityManager::class.java)
return constructor.newInstance(clazz, this.entityManager)
}
override fun <T : IAggregateRoot<I>, I> createRepository(clazz: Class<T>): IRepository<T, I> {
return JpaRepository(clazz, this.entityManager)
}
}

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,15 +66,16 @@ 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
val predicates = mutableListOf<Predicate>()
for (argumentName in this.keys) {
try {
var fieldName = argumentName
var operator: Operator
val fieldValue = this[argumentName]!!
var operator: Operator = Operator.Default
// 判断是否为range类型(范围内查询)
var start = true
@@ -85,63 +83,192 @@ fun Map<String, String>?.toSpecification(clazz: Class<*>): Specification<*> {
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
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 (clazz.getDeclaredField(fieldName).type != Date::class.java) {
if (field.type != Date::class.java) {
operator = if (start) Operator.Gte else Operator.Lte
}
}
var predicate: Predicate
var digitalValue: Double
try {
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, 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.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
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,4 +1,4 @@
package com.synebula.gaea.mongodb.query
package com.synebula.gaea.mongodb.db
import com.synebula.gaea.ext.firstCharLowerCase
@@ -6,10 +6,10 @@ 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.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
@@ -19,7 +19,7 @@ import org.springframework.data.mongodb.core.query.Query
* 实现IQuery的Mongodb查询类
* @param template MongodbRepo对象
*/
open class MongodbUniversalQuery(var template: MongoTemplate) : IUniversalQuery {
open class MongodbQuery(var template: MongoTemplate) : IQuery {
/**
* 使用View解析是collection时是否校验存在默认不校验
@@ -38,9 +38,9 @@ open class MongodbUniversalQuery(var template: MongoTemplate) : IUniversalQuery
return this.find(query, clazz)
}
override fun <TView> count(params: Map<String, String>?, clazz: Class<TView>): Int {
override fun <TView> count(params: Map<String, String>?, clazz: Class<TView>): Long {
val query = Query()
return this.template.count(query.where(params, clazz), this.collection(clazz)).toInt()
return this.template.count(query.where(params, clazz), this.collection(clazz))
}
override fun <TView> paging(params: Params, clazz: Class<TView>): Page<TView> {

View File

@@ -1,89 +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.IQuery
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 MongodbQuery<TView, ID>(override var clazz: Class<TView>, var template: MongoTemplate) :
IQuery<TView, ID> {
/**
* 使用View解析是collection时是否校验存在默认不校验
*/
var validViewCollection = false
override fun get(id: ID): TView? {
return this.template.findOne(whereId(id), clazz, this.collection(clazz))
}
override fun list(params: Map<String, String>?): List<TView> {
val fields = this.fields(clazz)
val query = Query()
query.where(params, clazz)
query.select(fields)
return this.find(query, clazz)
}
override fun count(params: Map<String, String>?): Int {
val query = Query()
return this.template.count(query.where(params, clazz), this.collection(clazz)).toInt()
}
override fun paging(params: Params): Page<TView> {
val query = Query()
val fields = this.fields(clazz)
val result = Page<TView>(params.page, params.size)
result.total = this.count(params.parameters)
//如果总数和索引相同,说明该页没有数据,直接跳到上一页
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 range(field: String, params: List<Any>): List<TView> {
return this.find(Query.query(Criteria.where(field).`in`(params)), clazz)
}
protected fun 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,23 +0,0 @@
package com.synebula.gaea.mongodb.query
import com.synebula.gaea.query.IQuery
import com.synebula.gaea.query.IQueryFactory
import org.springframework.data.mongodb.core.MongoTemplate
class MongodbQueryFactory(var template: MongoTemplate) : IQueryFactory {
/**
* 创建IQuery接口类型
*/
override fun createRawQuery(clazz: Class<*>): IQuery<*, *> {
val constructor = MongodbQuery::class.java.getConstructor(Class::class.java, MongoTemplate::class.java)
return constructor.newInstance(clazz, this.template)
}
/**
* 创建IQuery接口类型
*/
override fun <T, I> createQuery(clazz: Class<T>): IQuery<T, I> {
return MongodbQuery(clazz, template)
}
}

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

@@ -13,12 +13,3 @@ dependencies {
api("org.springframework.boot:spring-boot-starter-aop:$spring_version")
}
publishing {
publications {
publish(MavenPublication) {
from components.java
}
}
}

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,11 +1,11 @@
package com.synebula.gaea.query
package com.synebula.gaea.db.query
/**
* 查询基接口, 其中方法都指定了查询的视图类型
*
* @author alex
*/
interface IUniversalQuery {
interface IQuery {
/**
* 根据Key获取对象
*
@@ -28,7 +28,7 @@ interface IUniversalQuery {
* @param params 查询条件
* @return 数量
*/
fun <TView> count(params: Map<String, String>?, clazz: Class<TView>): Int
fun <TView> count(params: Map<String, String>?, clazz: Class<TView>): Long
/**
* 根据实体类条件查询所有符合条件记录分页查询

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
/**
* 分页数据
@@ -13,13 +13,13 @@ data class Page<T>(var page: Int = 0, var size: Int = 0) {
/**
* 总数据量
*/
var total: Int = 0
var total: Long = 0
/**
* 数据索引从0开始表示数据在总量的第几条index = (page - 1) * size
*/
val index: Int
get() = (page - 1) * size
val index: Long
get() = (page - 1) * size.toLong()
/**
* 结果数据
@@ -36,7 +36,7 @@ data class Page<T>(var page: Int = 0, var size: Int = 0) {
* @param data 结果数据
*/
constructor(page: Int, size: Int, total: Int, data: List<T>) : this(page, size) {
constructor(page: Int, size: Int, total: Long, data: List<T>) : this(page, size) {
this.page = page
this.size = size
this.total = total

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
/**
@@ -26,7 +25,7 @@ open class Service<TRoot : IAggregateRoot<ID>, ID>(
protected open var repository: IRepository<TRoot, ID>,
protected open var mapper: IObjectMapper,
) : IService<ID> {
@Resource
protected open var bus: IBus<Any>? = null
/**

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

View File

@@ -1,18 +0,0 @@
package com.synebula.gaea.query
/**
* Query 工厂接口。 定义了Query的创建方法。
*/
interface IQueryFactory {
/**
* 创建原始类型的IQuery接口类型
*/
fun createRawQuery(clazz: Class<*>): IQuery<*, *>
/**
* 创建指定类型的IQuery接口类型
*/
fun <T, I> createQuery(clazz: Class<T>): IQuery<T, I>
}

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