Browse Source

修改包名,注释头

zhangweixin 4 years ago
commit
0de28e197b
63 changed files with 6093 additions and 0 deletions
  1. 8 0
      .gitignore
  2. 54 0
      README.md
  3. 112 0
      build.gradle
  4. 79 0
      gradle.properties
  5. BIN
      gradle/wrapper/gradle-wrapper.jar
  6. 32 0
      gradle/wrapper/gradle-wrapper.properties
  7. 145 0
      persagy-service-base/build.gradle
  8. 719 0
      persagy-service-base/src/main/kotlin/com/persagy/service/SBaseService.kt
  9. 55 0
      persagy-service-base/src/main/kotlin/com/persagy/service/SGlobalDefaultExceptionHandler.kt
  10. 298 0
      persagy-service-base/src/main/kotlin/com/persagy/service/SObjectService.kt
  11. 258 0
      persagy-service-base/src/main/kotlin/com/persagy/service/SPageContext.kt
  12. 70 0
      persagy-service-base/src/main/kotlin/com/persagy/service/config/SAsyncConfig.kt
  13. 73 0
      persagy-service-base/src/main/kotlin/com/persagy/service/interceptors/SAccessControlAllowOriginInterceptor.kt
  14. 81 0
      persagy-service-base/src/main/kotlin/com/persagy/service/interceptors/SPageInterceptor.kt
  15. 92 0
      persagy-service-base/src/main/kotlin/com/persagy/service/json/SFastjsonAnnotationIntrospector.kt
  16. 78 0
      persagy-service-base/src/main/kotlin/com/persagy/service/json/SJsonHttpMessageConverter.kt
  17. 67 0
      persagy-service-base/src/main/kotlin/com/persagy/service/utils/SBeanUtil.kt
  18. 157 0
      persagy-service-base/src/main/kotlin/com/persagy/service/utils/SJsonUtil.kt
  19. 137 0
      persagy-service-base/src/main/kotlin/com/persagy/service/utils/SQrCodeUtil.kt
  20. 138 0
      persagy-service-base/src/main/kotlin/com/persagy/service/utils/SRandCodeUtil.kt
  21. 75 0
      persagy-service-base/src/main/kotlin/com/persagy/service/utils/SSpringContextUtil.kt
  22. 120 0
      persagy-service-base/src/main/kotlin/com/persagy/service/utils/crypt/SAESCoderUtil.kt
  23. 88 0
      persagy-service-base/src/main/kotlin/com/persagy/service/utils/crypt/SCoderUtil.kt
  24. 124 0
      persagy-service-base/src/main/kotlin/com/persagy/service/utils/crypt/SDESCoderUtil.kt
  25. 137 0
      persagy-service-base/src/main/kotlin/com/persagy/service/utils/crypt/SPBECoderUtil.kt
  26. 301 0
      persagy-service-base/src/main/kotlin/com/persagy/service/utils/crypt/SRSACoderUtil.kt
  27. 95 0
      persagy-service-base/src/main/kotlin/com/persagy/sso/controllers/SSsoUserController.kt
  28. 75 0
      persagy-service-base/src/main/kotlin/com/persagy/sso/models/entities/SSsoUser.kt
  29. 128 0
      persagy-service-kafka/build.gradle
  30. 34 0
      persagy-service-kafka/src/main/kotlin/com/persagy/service/kafka/SBaseNotice.kt
  31. 192 0
      persagy-service-kafka/src/main/kotlin/com/persagy/service/kafka/SKafkaService.kt
  32. 129 0
      persagy-service-models/build.gradle
  33. 53 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/SBaseEntity.kt
  34. 65 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/SCascadeQuery.kt
  35. 49 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/SCascadeUpdate.kt
  36. 44 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/SJoinQuery.kt
  37. 50 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/annotations/SCascade.kt
  38. 53 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/annotations/SCascadeCount.kt
  39. 39 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/enums/SCascadeUpdateMode.kt
  40. 30 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/enums/SRequestType.kt
  41. 39 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/enums/SResponseType.kt
  42. 42 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/requests/SCountRequest.kt
  43. 53 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/requests/SCreateRequest.kt
  44. 42 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/requests/SDeleteRequest.kt
  45. 94 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/requests/SQueryRequest.kt
  46. 53 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/requests/SUpdateRequest.kt
  47. 52 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/responses/SBaseResponse.kt
  48. 44 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/responses/SCountResponse.kt
  49. 55 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/responses/SCreateResponse.kt
  50. 41 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/responses/SDeleteResponse.kt
  51. 69 0
      persagy-service-models/src/main/kotlin/com/persagy/service/models/responses/SQueryResponse.kt
  52. 124 0
      persagy-service-mybatis/build.gradle
  53. 48 0
      persagy-service-mybatis/src/main/kotlin/com/persagy/mybatis/SDaoFactory.kt
  54. 65 0
      persagy-service-mybatis/src/main/kotlin/com/persagy/mybatis/SMybatisDao.kt
  55. 126 0
      persagy-service-mysql/build.gradle
  56. 88 0
      persagy-service-mysql/src/main/kotlin/com/persagy/service/postgresql/controllers/SDatabaseDocController.kt
  57. 82 0
      persagy-service-mysql/src/main/kotlin/com/persagy/service/postgresql/models/SColumn.kt
  58. 56 0
      persagy-service-mysql/src/main/kotlin/com/persagy/service/postgresql/models/STable.kt
  59. 126 0
      persagy-service-postgresql/build.gradle
  60. 88 0
      persagy-service-postgresql/src/main/kotlin/com/persagy/service/postgresql/controllers/SDatabaseDocController.kt
  61. 82 0
      persagy-service-postgresql/src/main/kotlin/com/persagy/service/postgresql/models/SColumn.kt
  62. 56 0
      persagy-service-postgresql/src/main/kotlin/com/persagy/service/postgresql/models/STable.kt
  63. 34 0
      settings.gradle

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+/.idea
+/.gradle
+/.build
+build
+out
+gradlew.bat
+gradlew
+persagy-service.iml

+ 54 - 0
README.md

@@ -0,0 +1,54 @@
+# saga-service
+
+# 标准接口
+## 查询接口
+### 过滤(filters)
+
+### 排序(orders)
+
+### 分页(pageNumber与pageSize)
+pageNumber: 第几页,从1开始。 
+pageSize: 每页记录数。**最大1000条记录。**
+
+### 映射()
+
+## 关系表达式
+关系|运算符|Web接口语法|后台编程语法
+:---:|:---:|---|---|
+等于    | =         | 属性名 = 值              | SFilter.eq("属性名", 值)
+不等于  | !=        | 属性名 != 值             | SFilter.ne("属性名", 值)
+大于    | >         | 属性名 > 值              | SFilter.gt("属性名", 值)
+大于等于| >=        | 属性名 >= 值             | SFilter.gte("属性名", 值)
+小于    | <         | 属性名 < 值              | SFilter.lt("属性名", 值)
+小于等于| <=        | 属性名 <= 值             | SFilter.lte("属性名", 值)
+包含    | contain   | 属性名 contain 值        | SFilter.contain("属性名", 值)
+前缀    | startwith | 属性名 startwith 值      | SFilter.startWith("属性名", 值)
+后缀    | endwith   | 属性名 endwith 值        | SFilter.endWith("属性名", 值)
+为空    | isnull    | 属性名 isnull            | SFilter.isNull("属性名", 值)
+在列表内| in        | 属性名 in [值1, 值2,...] | SFilter.inList("属性名", 值)
+
+## 关系表达式
+关系|运算符|Web接口语法|后台编程语法
+:---:|:---:|---|---|
+非       | not | not 表达式           | SFilter.not(表达式)
+与       | and | 表达式1 and 表达式2  | SFilter.and(表达式1, 表达式2)
+或       | or  | 表达式1 or 表达式2   | SFilter.or(表达式1, 表达式2)
+多表达式 | ;   | 表达式1;表达式2;...  |
+
+## 函数
+函数 | 说明
+---|---|
+SLeft(属性名, 长度) | 字符串左截取
+SLength(属性名) | 取字符串长度
+SLower(属性名) | 字符串转换为小写
+SLPad(属性名, 长度, 填充字符) | 左填充字符串
+SMid(属性名, 开始位置, 长度) | 从指定位置截取批暄长度字符串
+SReverse(属性名) | 字符串逆序
+SRight(属性名, 长度) | 字符串右截取
+SRPad(属性名, 长度, 填充字符) | 右填充字符串
+SSubstring(属性名, 开始位置, 长度) | 从指定位置截取批暄长度字符串
+SUpper(属性名) | 字符串转换为大写
+
+#### 软件架构
+软件架构说明
+

+ 112 - 0
build.gradle

@@ -0,0 +1,112 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+group 'com.persagy'
+version PERSAGY_SERVICE_VERSION
+
+apply plugin: 'org.hidetake.ssh'
+
+buildscript {
+    repositories {
+        maven{ url "http://maven.aliyun.com/nexus/content/groups/public/"}
+        mavenCentral()
+        jcenter()
+    }
+    dependencies {
+        classpath group: 'org.springframework.boot', name: 'spring-boot-gradle-plugin', version: SPRING_BOOT_VERSION
+        classpath group: 'org.jetbrains.kotlin', name: 'kotlin-gradle-plugin', version: KOTLIN_VERSION
+        classpath group: 'org.jetbrains.dokka', name: 'dokka-gradle-plugin', version: DOKKA_VERSION
+        classpath group: 'org.hidetake', name: 'gradle-ssh-plugin', version: GRADLE_SSH_VERSION
+    }
+}
+
+allprojects {
+    repositories {
+        maven{
+            url MAVEN_REPO_PUBLIC_URL
+            credentials {
+                username = NEXUS_USERNAME
+                password = NEXUS_PASSWORD
+            }
+        }
+        maven{ url "http://maven.aliyun.com/nexus/content/groups/public/"}
+        maven { url "https://repo.spring.io/milestone" }
+        mavenCentral()
+        jcenter()
+    }
+    tasks.withType(Javadoc) {
+        options{ encoding "UTF-8"
+            charSet "UTF-8"
+            links "http://docs.oracle.com/javase/8/docs/api"
+        }
+    }
+}
+
+task dokkaAll(type: org.jetbrains.dokka.gradle.DokkaTask) {
+    jdkVersion = 8
+    outputFormat = "javadoc"
+    outputDirectory = "$buildDir/javadoc/service"
+    sourceDirs = files(
+            "$projectDir/../persagy-kotlin/persagy-kotlin-base/src/main/kotlin",
+            "$projectDir/../persagy-kotlin/persagy-kotlin-database/src/main/kotlin",
+            "$projectDir/../persagy-kotlin/persagy-kotlin-mybatis/src/main/kotlin",
+            "$projectDir/../persagy-kotlin/saga-kotlin-mysql/src/main/kotlin",
+            "$projectDir/../persagy-kotlin/persagy-kotlin-postgresql/src/main/kotlin",
+            "persagy-service-base/src/main/kotlin",
+            "persagy-service-kafka/src/main/kotlin",
+            "persagy-service-models/src/main/kotlin",
+            "persagy-service-mybatis/src/main/kotlin",
+            "persagy-service-postgresql/src/main/kotlin"
+    )
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+//////////////////////////////////////////////////////
+// ssh 部署文档
+ssh.settings {
+    knownHosts = allowAnyHosts
+}
+
+remotes {
+    docserver {
+        host = "47.94.89.44"
+        port = 22
+        user = "user1"
+        password = "@)!^sagacloud"
+    }
+}
+
+task deployDoc {
+    ssh.run {
+        session(remotes.docserver) {
+            // Deploy doc
+//            put from: "$buildDir/javadoc/service", into: '/opt/tomcat9/webapps/api/server'
+        }
+    }
+}

+ 79 - 0
gradle.properties

@@ -0,0 +1,79 @@
+#
+# **********************************************************************************************************************
+#
+#          !!
+#        .F88X
+#        X8888Y
+#      .}888888N;
+#        i888888N;        .:!              .I$WI:
+#          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+#          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+#            +888888N;  .8888888Y                                  "&&8Y.}8,
+#            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+#              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+#              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+#            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+#            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+#          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+#        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+#      .:R888888I
+#      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+#        ~8888'
+#        .!88~                                                                     All rights reserved.
+#
+# **********************************************************************************************************************
+#
+
+PERSAGY_URL = http://www.persagy.com
+PERSAGY_KOTLIN_VERSION = 1.4.112
+PERSAGY_SERVICE_VERSION = 1.4.147
+
+MAVEN_REPO_PUBLIC_URL = http://dev.dp.sagacloud.cn:8082/repository/maven-public/
+MAVEN_REPO_RELEASE_URL = http://dev.dp.sagacloud.cn:8082/repository/maven-releases/
+MAVEN_REPO_SNAPSHOT_URL = http://dev.dp.sagacloud.cn:8082/repository/maven-snapshots/
+NEXUS_USERNAME = saga
+NEXUS_PASSWORD = saga
+
+LICENSE_NAME = The Apache Software License, Version 2.0
+LICENSE_URL = http://www.apache.org/licenses/LICENSE-2.0.txt
+
+DEVELOPER_ID = Andy.Sybotan
+DEVELOPER_NAME = Andy
+
+JVM_TARGET = 1.8
+JDK_VERSION = 8
+
+# https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
+COMMONS_LANG_VERSION = 3.9
+# https://mvnrepository.com/artifact/org.jetbrains.dokka/dokka-gradle-plugin
+DOKKA_VERSION = 0.9.16
+# https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib-jdk8
+KOTLIN_VERSION = 1.3.61
+# https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter
+SHIRO_VERSION = 1.4.2
+# https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web
+SPRING_BOOT_VERSION = 2.2.2.RELEASE
+# https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server
+SPRING_CLOUD_VERSION = 1.4.7.RELEASE
+# https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-config
+SPRING_CLOUD_CONFIG_VERSION = 2.2.0.RELEASE
+# https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter
+SPRING_DRUID_VERSION = 1.1.21
+# https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka
+SPRING_KAFKA_VERSION = 2.4.0.RELEASE
+# https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter
+SPRING_MYBATIS_VERSION = 2.1.1
+# https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui
+SWAGGER_VERSION = 2.9.2
+# https://mvnrepository.com/artifact/org.testng/testng
+TESTNG_VERSION = 7.0.0
+# https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
+VALIDATOR_VERSION = 6.1.0.Final
+# https://mvnrepository.com/artifact/com.google.zxing/core
+# ������
+ZXING_VERSION = 3.2.1
+
+# https://repo.spring.io/milestone
+SPRING_CLOUD_DEPENDENCIES = Finchley.RELEASE
+
+GRADLE_SSH_VERSION = 2.9.0

BIN
gradle/wrapper/gradle-wrapper.jar


+ 32 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,32 @@
+#
+# **********************************************************************************************************************
+#
+#          !!
+#        .F88X
+#        X8888Y
+#      .}888888N;
+#        i888888N;        .:!              .I$WI:
+#          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+#          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+#            +888888N;  .8888888Y                                  "&&8Y.}8,
+#            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+#              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+#              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+#            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+#            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+#          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+#        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+#      .:R888888I
+#      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+#        ~8888'
+#        .!88~                                                                     All rights reserved.
+#
+# **********************************************************************************************************************
+#
+
+#Tue Apr 21 09:55:18 CST 2020
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME

+ 145 - 0
persagy-service-base/build.gradle

@@ -0,0 +1,145 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+group rootProject.group
+version rootProject.version
+
+apply plugin: 'kotlin'
+apply plugin: 'org.jetbrains.dokka'
+apply plugin: 'maven'
+
+sourceCompatibility = JVM_TARGET
+
+compileKotlin {
+    kotlinOptions.jvmTarget = JVM_TARGET
+}
+compileTestKotlin {
+    kotlinOptions.jvmTarget = JVM_TARGET
+}
+
+dependencies {
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // kotlin 依赖
+    compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: KOTLIN_VERSION
+    compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: KOTLIN_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 字符串处理工具
+    // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
+    compile group: 'org.apache.commons', name: 'commons-lang3', version: COMMONS_LANG_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 校验器
+    // https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
+    compile group: 'org.hibernate.validator', name: 'hibernate-validator', version: VALIDATOR_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // spring boot 依赖
+    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: SPRING_BOOT_VERSION
+    compile group: 'org.springframework.boot', name: 'spring-boot-starter-logging', version: SPRING_BOOT_VERSION
+    compile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: SPRING_BOOT_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // RESTful API 文档生成工具
+    // https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui
+    compile group: 'io.springfox', name: 'springfox-swagger-ui', version: SWAGGER_VERSION
+    compile group: 'io.springfox', name: 'springfox-swagger2', version: SWAGGER_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 二维码与条形码
+    // https://mvnrepository.com/artifact/com.google.zxing/core
+    compile group: 'com.google.zxing', name: 'core', version: ZXING_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Sybotan 依赖
+    compile group: 'com.persagy', name: 'persagy-kotlin-base', version: PERSAGY_KOTLIN_VERSION
+    compile group: 'com.persagy', name: 'persagy-kotlin-database', version: PERSAGY_KOTLIN_VERSION
+    compile project(':persagy-service-models')
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 文档生成
+dokka {
+    jdkVersion = JDK_VERSION
+    outputFormat = 'javadoc'
+    outputDirectory = '$buildDir/javadoc'
+    //sourceDirs = files('src/main/kotlin')
+}
+
+// 上成上传的文档jar包
+task javadocJar(type: Jar, dependsOn: dokka) {
+    classifier = 'javadoc'
+    from dokka.outputDirectory
+}
+
+// 上成上传的文档jar包
+task sourcesJar(type: Jar) {
+    classifier = 'sources'
+    from sourceSets.main.allSource
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// maven 仓库管理
+
+// 设置上传的包,默认上传aar与pom
+artifacts {
+    //archives javadocJar
+    //archives sourcesJar
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            pom.groupId = project.group
+            pom.version = project.version
+            pom.artifactId = project.name
+            pom.project {
+                name project.name
+                packaging 'jar'
+                url PERSAGY_URL
+                description '斯伯坦机器人世界 网站项目核心支持库。'
+                licenses {
+                    license {
+                        name LICENSE_NAME
+                        url LICENSE_URL
+                    }
+                }
+                developers {
+                    developer {
+                        id DEVELOPER_ID
+                        name DEVELOPER_NAME
+                    }
+                }
+            }
+            snapshotRepository(url: MAVEN_REPO_SNAPSHOT_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+            repository(url: MAVEN_REPO_RELEASE_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+        }
+    }
+}

+ 719 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/SBaseService.kt

@@ -0,0 +1,719 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service
+
+import com.persagy.base.extensions.getAllDeclaredField
+import com.persagy.base.extensions.toJson
+import com.persagy.database.*
+import com.persagy.database.extensions.keyFieldList
+import com.persagy.database.extensions.keyValue
+import com.persagy.service.models.SCascadeQuery
+import com.persagy.service.models.annotations.SCascade
+import com.persagy.service.models.annotations.SCascadeCount
+import java.lang.reflect.Field
+import java.lang.reflect.ParameterizedType
+
+/**
+ * 服务类基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+open class SBaseService<ENTITY: Any> {
+
+    /** 数据访问对象 */
+    protected var baseDao: SAbstractDao<ENTITY>
+
+    /** 实体类类型 */
+    val entityClass: Class<ENTITY>
+        get() = baseDao.entityClass
+
+    /**
+     * 构造函数
+     *
+     * @param   entityClazz     实体类
+     */
+    constructor(entityClazz: Class<ENTITY>) {
+        baseDao = daoFactory(entityClazz)
+    } // Constructor()
+
+    /**
+     * 构造函数
+     *
+     * @param   daoObject       数据访问对象
+     */
+    constructor(daoObject: SAbstractDao<ENTITY>) {
+        baseDao = daoObject
+    } // Constructor()
+
+    /**
+     * Dao对象工厂类
+     *
+     * @param   entityClazz     实体类
+     * @return  访问实体的Dao对象
+     */
+    private fun daoFactory(entityClazz: Class<ENTITY>): SAbstractDao<ENTITY> {
+        @Suppress("UNCHECKED_CAST")
+        val factory = Class.forName("com.persagy.mybatis.SDaoFactory").newInstance() as SAbstractDaoFactory<ENTITY>
+        return factory.createDao(entityClazz)
+    } // Fun daoFactory
+
+    /**
+     * 在数据库中插入实体
+     *
+     * @param   entity      插入数据库的实体
+     * @return  插入是否成功
+     */
+    open fun insert(entity: ENTITY): Boolean {
+        return baseDao.insert(entity)
+    } // Fun insert()
+
+    /**
+     * 在数据库中插入实体
+     *
+     * @param   entity      插入数据库的实体
+     * @return  插入是否成功
+     */
+    open fun replace(entity: ENTITY): Boolean {
+        return baseDao.replace(entity)
+    } // Fun replace()
+
+    /**
+     * 删除实体
+     *
+     * @param   entity      被删除的实体
+     * @return  删除是否成功
+     */
+    open fun delete(entity: ENTITY): Boolean {
+        return baseDao.delete(entity)
+    } // Fun delete()
+
+    /**
+     * 根据条件删除
+     *
+     * @param   args        删除条件
+     * @return  删除是否成功
+     */
+    open fun delete(vararg args: Pair<String, Any>): Boolean {
+        return baseDao.delete(*args)
+    } // Fun delete()
+
+    /**
+     * 根据条件删除
+     *
+     * @param   args        删除条件
+     * @return  删除是否成功
+     */
+    open fun delete(vararg args: SFilter): Boolean {
+        return baseDao.delete(*args)
+    } // Fun delete()
+
+    /**
+     * 根据条件删除
+     *
+     * @param   args        删除条件
+     * @return  删除是否成功
+     */
+    open fun delete(args: List<SFilter>): Boolean {
+        return baseDao.delete(args)
+    } // Fun delete()
+
+    /**
+     * 根据条件删除
+     *
+     * @param   query        删除条件
+     * @return  删除是否成功
+     */
+    open fun delete(query: String): Boolean {
+        return baseDao.delete(query)
+    } // Fun delete()
+
+    /**
+     * 删除所有记录
+     *
+     * @return  删除是否成功
+     */
+    open fun deleteAll(): Boolean {
+        return baseDao.deleteAll()
+    } // Fun deleteAll()
+
+    /**
+     * 更新实体
+     *
+     * @param   entity      更新实体
+     * @param   projection  将被更新的字段列表(以逗号分隔),为空表示更新所有非空字段
+     * @return  更新是否成功
+     */
+    open fun update(entity: ENTITY, projection: ArrayList<String>? = null): Boolean {
+        return baseDao.update(entity, projection)
+    } // Fun update()
+
+    /**
+     * 根据条件更新
+     *
+     * @param   projection  将被更新的字段列表(以逗号分隔),为空表示更新所有非空字段
+     * @param   args        更新条件
+     * @return  更新是否成功
+     */
+    open fun update(entity: ENTITY, projection: ArrayList<String>?, vararg args: Pair<String, Any>): Boolean {
+        return baseDao.update(entity, projection, *args)
+    } // Fun update()
+
+    /**
+     * 根据条件更新
+     *
+     * @param   projection  将被更新的字段列表(以逗号分隔),为空表示更新所有非空字段
+     * @param   args        更新条件
+     * @return  更新是否成功
+     */
+    open fun update(entity: ENTITY, projection: ArrayList<String>?, vararg args: SFilter): Boolean {
+        return baseDao.update(entity, projection, *args)
+    } // Fun update()
+
+    /**
+     * 根据条件更新
+     *
+     * @param   projection  将被更新的字段列表(以逗号分隔),为空表示更新所有非空字段
+     * @param   args        更新条件
+     * @return  更新是否成功
+     */
+    open fun update(entity: ENTITY, projection: ArrayList<String>?, args: List<SFilter>): Boolean {
+        return baseDao.update(entity, projection, args)
+    } // Fun update()
+
+    /**
+     * 根据条件更新
+     *
+     * @param   projection  将被更新的字段列表(以逗号分隔),为空表示更新所有非空字段
+     * @param   query       更新条件
+     * @return  更新是否成功
+     */
+    open fun update(entity: ENTITY, projection: ArrayList<String>?, query: String): Boolean {
+        return baseDao.update(entity, projection, query)
+    } // Fun update()
+
+    /**
+     * 根据条件查询一条记录
+     *
+     * @param   args        查询条件
+     */
+    open fun select(vararg args: Pair<String, Any>): SQueryBuilder<ENTITY> {
+        return baseDao.select(*args)
+    } // Fun select()
+
+    /**
+     * 根据条件查询记录
+     *
+     * @param   args        查询条件
+     * @return  查询构造器
+     */
+    open fun select(vararg args: SFilter): SQueryBuilder<ENTITY> {
+        return baseDao.select(*args)
+    } // Fun select()
+
+    /**
+     * 根据条件查询记录
+     *
+     * @param   args        查询条件
+     * @return  查询构造器
+     */
+    open fun select(args: List<SFilter>): SQueryBuilder<ENTITY> {
+        return baseDao.select(args)
+    } // Fun select()
+
+    /**
+     * 根据条件查询记录
+     *
+     * @param   query        查询条件
+     * @return  查询构造器
+     */
+    open fun select(query: String?): SQueryBuilder<ENTITY> {
+        return baseDao.select(query)
+    } // Fun select()
+
+    /**
+     * 根据条件查询所有记录
+     *
+     * @return  查找的记录
+     */
+    open fun selectAll(): SQueryBuilder<ENTITY> {
+        var builder = baseDao.selectAll()
+
+        builder.limit(9999999)
+        return builder
+    } // Fun selectAll()
+
+    /**
+     * 执行查询操作
+     *
+     * @param   builder     查询查询构造器
+     * @return  查询到的记录
+     */
+    open fun execQuery(builder: SQueryBuilder<ENTITY>): ArrayList<ENTITY> {
+        return baseDao.execQuery(builder)
+    } // Fun execQuery()
+
+    /**
+     * 执行查询操作
+     *
+     * @param   builder     查询查询构造器
+     * @return  查询到的记录数
+     */
+    open fun execCount(builder: SQueryBuilder<ENTITY>): Long {
+        return baseDao.execCount(builder)
+    } // Fun execQuery()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 回调函数
+    /**
+     * 创建对象前回调函数
+     *
+     * @param   entity      创建前的回调函数
+     * @return  返回true,可以创建对象
+     */
+    protected open fun onCreateBefore(entity: ENTITY): Boolean  {
+        return true
+    } // Fun onCreateBefore()
+
+    /**
+     * 创建成功后回调函数
+     *
+     * @param   entityList      创建成功对象列表
+     */
+    protected open fun onCreateSuccess(entityList: ArrayList<ENTITY>) {
+    } // Fun onCreateSuccess()
+
+    /**
+     * 创建失败回调函数
+     *
+     * @param   entityList      创建失败对象列表
+     */
+    protected open fun onCreateFailure(entityList: ArrayList<ENTITY>) {
+    } // Fun onCreateFailure()
+
+    /**
+     * 删除前回调函数
+     *
+     * @param   entity      创建前的回调函数
+     * @return  返回true,可以删除对象
+     */
+    protected open fun onDeleteBefore(entity: ENTITY): Boolean {
+        return true
+    } // Fun onCreateEntity()
+
+    /**
+     * 删除成功回调函数
+     *
+     * @param   entityList      删除成功对象列表
+     */
+    protected open fun onDeleteSuccess(entityList: ArrayList<ENTITY>) {
+    } // Fun onDeleteSuccess()
+
+    /**
+     * 删除失败回调函数
+     *
+     * @param   entityList      删除失败对象列表
+     */
+    protected open fun onDeleteFailure(entityList: ArrayList<ENTITY>) {
+    } // Fun onDeleteFailure()
+
+    /**
+     * 更新前回调函数
+     *
+     * @param   entity      创建前的回调函数
+     */
+    protected open fun onUpdateBefore(entity: ENTITY): Boolean {
+        return true
+    } // Fun onCreateEntity()
+
+    /**
+     * 更新成功能回调函数
+     *
+     * @param   entityList      更新成功对象列表
+     */
+    protected open fun onUpdateSuccess(entityList: ArrayList<ENTITY>) {
+    } // Fun onUpdateSuccess()
+
+    /**
+     * 更新失败回调函数
+     *
+     * @param   entityList      更新失败对象列表
+     */
+    protected open fun onUpdateFailure(entityList: ArrayList<ENTITY>) {
+    } // Fun onUpdateFailure()
+
+    /**
+     * 查询前回调函数
+     *
+     * @param   queryBuilder    查询构造器
+     */
+    protected open fun onQueryBefore(queryBuilder: SQueryBuilder<ENTITY>) {
+    } // Fun onQueryBefore()
+
+    /**
+     * 查询成功回调函数
+     *
+     * @param   pageInfo   页面信息
+     */
+    protected open fun onQuerySuccess(pageInfo: SPageQueryInfo<ENTITY>) {
+    } // Fun onQuerySuccess()
+
+    /**
+     * 统计个数前回调函数
+     *
+     * @param   queryBuilder    查询构造器
+     */
+    protected open fun onCountBefore(queryBuilder: SQueryBuilder<ENTITY>) {
+    } // Fun onQueryBefore()
+
+    /**
+     * 统计个数成功回调函数
+     *
+     * @param   count   统计结果
+     */
+    protected open fun onCountSuccess(count: Long) {
+    } // Fun onCountSuccess()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // 级联操作相关
+    /**
+     * 进行级联查询处理
+     *
+     * @param   entityList      实体列表
+     * @param   cascadeList     级联请求列表
+     */
+    fun processCascadeQuery(entityList: ArrayList<Any>, cascadeList: ArrayList<SCascadeQuery>) {
+        val entityMap = entityListToMap(entityList)
+        for (cascade in cascadeList) {
+            if (cascade.name == null) {
+                continue
+            }
+
+            // 获得级联相关信息。如果信息不完整,则退出
+            val cascadeField = entityClass.getAllDeclaredField(cascade.name!!) ?: continue
+            val cascadeQueryAnno = cascadeField.getAnnotation(SCascade::class.java)
+            val cascadeCountAnno = cascadeField.getAnnotation(SCascadeCount::class.java)
+            if (cascadeQueryAnno != null) {
+                cascadeQuery(entityMap, cascadeQueryAnno, cascadeField, cascade)
+            } else if (cascadeCountAnno != null) {
+                cascadeCount(entityMap, cascadeCountAnno, cascadeField, cascade)
+            }
+        }
+        return
+    } // Fun cascadeQuery()
+
+    /**
+     * 进行级联查询
+     *
+     * @param   entityMap       实体Map
+     * @param   cascadeAnno     级联查询注解
+     * @param   cascadeField    级联查询对应的属性
+     * @param   cascade         级联请求
+     */
+    private fun cascadeQuery(entityMap: HashMap<Any, Any>, cascadeAnno: SCascade, cascadeField: Field, cascade: SCascadeQuery) {
+        val cascadeClass = if (List::class.java.isAssignableFrom(cascadeField.type)) {
+            // 如果是1对多,或多对多。取列表的泛型
+            (cascadeField.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
+        } else {
+            // 如果为1对1,直接取对象类型
+            cascadeField.type
+        }
+
+        val idList = entityIdList(entityMap)
+        val relationList = baseDao.relationQuery(cascadeAnno.table, cascadeAnno.idColumn, cascadeAnno.childIdColumn, cascadeAnno.self, cascadeAnno.filter, idList)
+        // 如果未取到关联数据,则开始取下一个关联对象
+        if (relationList.size <= 0) {
+            return
+        }
+
+        val relationMap = relationListToMap(relationList)
+
+        /////////////////////////////////
+        // 开始构造级联查询
+        val service = SBaseService(cascadeClass)
+        val sqlBuilder = service.select(cascade.filters)
+        sqlBuilder.filter(SFilter.inList(cascadeClass.keyFieldList()[0].name, relationMap.keys.toList()))
+
+        /** 排序条件 */
+        if (null != cascade.orders) {
+            sqlBuilder.order(cascade.orders!!)
+        }
+
+        /** 去重 */
+        if (null != cascade.distinct && cascade.distinct!!) {
+            sqlBuilder.distinct()
+        }
+
+        /** 映射标志 */
+        if (null != cascade.projection) {
+            sqlBuilder.projection(cascade.projection!!)
+        }
+
+        /** 分组统计 */
+        if (null != cascade.group && !cascade.group!!.nameList.isNullOrEmpty()) {
+            // 分组属性名列表
+            sqlBuilder.group(*cascade.group!!.nameList.toArray(emptyArray<String>()))
+            // 是否统计个数
+            sqlBuilder.count(cascade.group!!.count ?: false)
+            // 求和
+            if (!cascade.group!!.sumList.isNullOrEmpty()) {
+                sqlBuilder.sum(*cascade.group!!.sumList.toArray(emptyArray<String>()))
+            }
+            // 求平均值
+            if (!cascade.group!!.avgList.isNullOrEmpty()) {
+                sqlBuilder.avg(*cascade.group!!.avgList.toArray(emptyArray<String>()))
+            }
+            // 取最大值
+            if (!cascade.group!!.maxList.isNullOrEmpty()) {
+                sqlBuilder.max(*cascade.group!!.maxList.toArray(emptyArray<String>()))
+            }
+            // 取最小值
+            if (!cascade.group!!.minList.isNullOrEmpty()) {
+                sqlBuilder.min(*cascade.group!!.minList.toArray(emptyArray<String>()))
+            }
+        }
+
+        val childrenList = sqlBuilder.exec()
+        for (child in childrenList) {
+            val childKey = child::class.java.keyFieldList()[0].name
+            val id2 = child.keyValue(childKey)
+            @Suppress("UNCHECKED_CAST")
+            val id1List = relationMap[id2] as ArrayList<Any?>?
+            id1List ?: continue
+            for (id1 in id1List) {
+                if (entityMap[id1] != null) {
+                    fillCascadeObject(entityMap[id1]!!, child, cascadeField)
+                }
+            }
+        }
+
+        /** 如果需要进行级联查询 */
+        if (cascade.cascade != null && cascade.cascade!!.size > 0 &&
+                childrenList.size > 0) {
+            @Suppress("UNCHECKED_CAST")
+            service.processCascadeQuery(childrenList as ArrayList<Any>, cascade.cascade!!)
+        }
+        return
+    } // Fun cascadeQuery()
+
+    /**
+     * 进行级联查询
+     *
+     * @param   entityMap       实体Map
+     * @param   cascadeAnno     级联查询注解
+     * @param   cascadeField    级联查询对应的属性
+     * @param   cascade         级联请求
+     */
+    private fun cascadeCount(entityMap: HashMap<Any, Any>, cascadeAnno: SCascadeCount, cascadeField: Field, cascade: SCascadeQuery) {
+        val idList = entityIdList(entityMap)
+        val relationList = baseDao.relationQuery(cascadeAnno.table, cascadeAnno.idColumn, cascadeAnno.childIdColumn, cascadeAnno.self, cascadeAnno.filter, idList)
+        // 如果未取到关联数据,则开始取下一个关联对象
+        if (relationList.size <= 0) {
+            return
+        }
+
+        val relationMap = relationListToMap(relationList)
+
+        /////////////////////////////////
+        // 开始构造级联查询
+        val service = SBaseService(cascadeAnno.childClass.java)
+        val sqlBuilder = service.select(cascade.filters)
+        sqlBuilder.filter(SFilter.inList(cascadeAnno.childClass.java.keyFieldList()[0].name, relationMap.keys.toList()))
+
+        /** 排序条件 */
+        if (null != cascade.orders) {
+            sqlBuilder.order(cascade.orders!!)
+        }
+
+        /** 去重 */
+        if (null != cascade.distinct && cascade.distinct!!) {
+            sqlBuilder.distinct()
+        }
+
+        /** 映射标志 */
+        if (null != cascade.projection) {
+            sqlBuilder.projection(cascade.projection!!)
+        }
+
+        /** 分组统计 */
+        if (null != cascade.group && !cascade.group!!.nameList.isNullOrEmpty()) {
+            // 分组属性名列表
+            sqlBuilder.group(*cascade.group!!.nameList.toArray(emptyArray<String>()))
+            // 是否统计个数
+            sqlBuilder.count(cascade.group!!.count ?: false)
+            // 求和
+            if (!cascade.group!!.sumList.isNullOrEmpty()) {
+                sqlBuilder.sum(*cascade.group!!.sumList.toArray(emptyArray<String>()))
+            }
+            // 求平均值
+            if (!cascade.group!!.avgList.isNullOrEmpty()) {
+                sqlBuilder.avg(*cascade.group!!.avgList.toArray(emptyArray<String>()))
+            }
+            // 取最大值
+            if (!cascade.group!!.maxList.isNullOrEmpty()) {
+                sqlBuilder.max(*cascade.group!!.maxList.toArray(emptyArray<String>()))
+            }
+            // 取最小值
+            if (!cascade.group!!.minList.isNullOrEmpty()) {
+                sqlBuilder.min(*cascade.group!!.minList.toArray(emptyArray<String>()))
+            }
+        }
+
+        val childrenList = sqlBuilder.exec()
+        for (child in childrenList) {
+            val childKey = child::class.java.keyFieldList()[0].name
+            val id2 = child.keyValue(childKey)
+            @Suppress("UNCHECKED_CAST")
+            val id1List = relationMap[id2] as ArrayList<Any?>?
+            id1List ?: continue
+            for (id1 in id1List) {
+                if (entityMap[id1] != null) {
+                    fillCascadeCount(entityMap[id1]!!, child, cascadeField)
+                }
+            }
+        }
+
+        return
+    } // Fun cascadeCount()
+
+    /**
+     * 为加速填充级联对象,将实体列表转换为map。
+     *
+     * @param   entityList      实体列表
+     * @return  以Map形式存储的实体
+     */
+    private fun entityListToMap(entityList: ArrayList<Any>): HashMap<Any, Any> {
+        val map = HashMap<Any, Any>()
+        val key = entityClass.keyFieldList()[0]
+        for (entity in entityList) {
+            map[entity.keyValue(key.name)!!] = entity
+        }
+        return map
+    } // Fun entityMap()
+
+    /**
+     * 获得实体的Id列表
+     *
+     * @param   entityMap   实体Map
+     * @return  ID列表
+     */
+    private fun entityIdList(entityMap: HashMap<Any, Any>): ArrayList<Any> {
+        val idList = ArrayList<Any>()
+        for (entity in entityMap) {
+            val keyField = entity.value.javaClass.keyFieldList()[0]
+            val keyValue = entity.value.keyValue(keyField.name)!!
+            println("keyField=${keyField.toJson()}")
+            println("keyValue=${keyValue.toJson()}")
+            idList.add(keyValue)
+        }
+        return idList
+    } // Fun entityIdList()
+
+    /**
+     * 将关系列表转换为Map
+     *
+     * @param   cascadeList     被转换的列表
+     * @return  转换后的Map
+     */
+    private fun relationListToMap(cascadeList: List<HashMap<String, Any>>): HashMap<Any, ArrayList<Any>> {
+        val map = HashMap<Any, ArrayList<Any>>()
+        for (item in cascadeList) {
+            item["id2"] ?: continue
+
+            var list = map[item["id2"]!!]
+            if (list == null) {
+                list = ArrayList()
+            }
+            list.add(item["id1"]!!)
+            map[item["id2"]!!] = list
+        }
+        return map
+    } // Fun cascadeToMap()
+
+    /**
+     * 填级联对象
+     *
+     * @param   entity      实体
+     * @param   child       填入的对象
+     * @param   field       填入的对象对应的实体类属性
+     */
+    private fun fillCascadeObject(entity: Any, child: Any?, field: Field) {
+        // 如果child为空,则返回
+        child ?: return
+
+        // 保存访问权限
+        val accessible = field.isAccessible
+        field.isAccessible = true
+
+        // 如果field是列表
+        if (List::class.java.isAssignableFrom(field.type)) {
+            @Suppress("UNCHECKED_CAST")
+            var children = field.get(entity) as ArrayList<Any>?
+            if (children == null) {
+                children = ArrayList()
+            }
+            children.add(child)
+            field.set(entity, children)
+        } else {
+            // 如果field是对象
+            if (field.get(entity) == null) {
+                field.set(entity, child)
+            }
+        }
+
+        // 恢复访问权限
+        field.isAccessible = accessible
+        return
+    } // Fun fillCascadeObject()
+
+    /**
+     * 填级联对象
+     *
+     * @param   entity      实体
+     * @param   child       填入的对象
+     * @param   field       填入的对象对应的实体类属性
+     */
+    private fun fillCascadeCount(entity: Any, child: Any?, field: Field) {
+        // 如果child为空,则返回
+        child ?: return
+
+        // 保存访问权限
+        val accessible = field.isAccessible
+        field.isAccessible = true
+
+        @Suppress("UNCHECKED_CAST")
+        var count = field.get(entity) as? Int
+        if (count == null) {
+            count = 0
+        }
+        count++
+        field.set(entity, count)
+
+        // 恢复访问权限
+        field.isAccessible = accessible
+        return
+    } // Fun fillCascadeCount()
+} // Class SBaseService

+ 55 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/SGlobalDefaultExceptionHandler.kt

@@ -0,0 +1,55 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service
+
+import org.springframework.web.bind.annotation.ControllerAdvice
+import org.springframework.web.bind.annotation.ExceptionHandler
+import org.springframework.web.bind.annotation.ResponseBody
+import javax.servlet.http.HttpServletRequest
+
+/**
+ * 全局默认异常处理句柄
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+// @ControllerAdvice
+class SGlobalDefaultExceptionHandler {
+    /**
+     * 默认异常处理句柄
+     * 如果返回的是View -- 方法的返回值是ModelAndView
+     * 如果返回的是String或者是Json数据,那么需要在方法上添加@ResponseBody注解
+     *
+     * @param   request     Http请求
+     * @param   e           异常
+     * @return  数据
+     */
+    @ExceptionHandler(Exception::class)
+    @ResponseBody
+    fun defaultExceptionHandler(request: HttpServletRequest, e: Exception): String {
+        return "对不起,服务器繁忙,请稍后再试!"
+    } // Fun defaultExceptionHandler()
+} // Class GlobalDefaultExceptionHandler

+ 298 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/SObjectService.kt

@@ -0,0 +1,298 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service
+
+import com.persagy.base.extensions.rootCause
+import com.persagy.base.extensions.toJson
+import com.persagy.database.SAbstractDao
+import com.persagy.database.SFilter
+import com.persagy.database.extensions.keyMap
+import com.persagy.service.models.enums.SResponseType
+import com.persagy.service.models.requests.*
+import com.persagy.service.models.responses.*
+import org.slf4j.LoggerFactory
+import org.springframework.web.bind.annotation.RequestBody
+import kotlin.collections.ArrayList
+
+/**
+ * 对象访问服务类基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+open class SObjectService<ENTITY: Any>
+    : SBaseService<ENTITY> {
+    /** 伴生对象 */
+    companion object {
+        /** 日志记录器 */
+        private val logger = LoggerFactory.getLogger(SObjectService::class.java)
+    } // Companion object
+
+    /**
+     * 构造函数
+     *
+     * @param   entityClazz     实体类
+     */
+    constructor(entityClazz: Class<ENTITY>): super(entityClazz)
+
+    /**
+     * 构造函数
+     *
+     * @param   daoObject       数据访问对象
+     */
+    constructor(daoObject: SAbstractDao<ENTITY>): super(daoObject)
+
+    /**
+     * 创建对象
+     *
+     * @param   request         请求对象
+     * @return  创建结果应答信息
+     */
+    fun createList(@RequestBody request: SCreateRequest<ENTITY>): SCreateResponse<ENTITY> {
+        return try {
+            if (request.isMerge) {
+                for (entity in request.content) {
+                    onCreateBefore(entity)
+                    replace(entity)
+                }
+            } else {
+                for (entity in request.content) {
+                    onCreateBefore(entity)
+                    insert(entity)
+                }
+            }
+            onCreateSuccess(request.content)
+            SCreateResponse(request.content)
+        } catch (e: Exception) {
+            onCreateFailure(request.content)
+            e.printStackTrace()
+            SCreateResponse(SResponseType.failure, e.message!!)
+        }
+    } // Fun createList()
+
+    /**
+     * 删除对象
+     *
+     * @param   entityList       对象ID列表
+     * @param   withFilterList   公共过滤条件
+     * @return  删除结果应答信息
+     */
+    fun deleteByKeysList(@RequestBody entityList: ArrayList<ENTITY>, withFilterList: ArrayList<SFilter>? = null): SBaseResponse {
+        return try {
+            for (entity in entityList) {
+                val filterList = ArrayList<SFilter>()
+                entity.keyMap().forEach {
+                    filterList.add(SFilter.eq(it.key, it.value))
+                }
+
+                if (withFilterList != null) {
+                    filterList.addAll(withFilterList)
+                }
+
+                onDeleteBefore(entity)
+
+                if (!delete(filterList)) {
+                    onDeleteFailure(entityList)
+                    return SBaseResponse(SResponseType.failure, "删除失败!")
+                }
+            }
+            onDeleteSuccess(entityList)
+            SBaseResponse(SResponseType.success)
+        } catch (e: Exception) {
+            e.printStackTrace()
+            SBaseResponse(SResponseType.failure, e.message!!)
+        }
+    } // Fun deleteByKeysList()
+
+    /**
+     * 根据条件删除对象
+     *
+     * @param   filterList  删除条件
+     * @return  删除结果应答信息
+     */
+    fun batchDelete(@RequestBody filterList: ArrayList<SFilter>): SBaseResponse {
+        return if (delete(filterList)) {
+            SBaseResponse(SResponseType.success)
+        } else {
+            SBaseResponse(SResponseType.failure, "删除失败!")
+        }
+    } // Fun batchDelete()
+
+    /**
+     * 更新对象
+     *
+     * @param   request         更新请求
+     * @param   withFilterList  公共过滤条件
+     * @return  更新结果应答信息
+     */
+    fun updateList(@RequestBody request: SUpdateRequest<ENTITY>, withFilterList: ArrayList<SFilter>? = null): SBaseResponse {
+        val entityList = request.content
+        return try {
+            for (entity in entityList!!) {
+                val filterList = ArrayList<SFilter>()
+                entity.keyMap().forEach {
+                    filterList.add(SFilter.eq(it.key, it.value))
+                }
+
+                if (withFilterList != null) {
+                    filterList.addAll(withFilterList)
+                }
+
+                onUpdateBefore(entity)
+                if (!update(entity, request.projection, filterList)) {
+                    onUpdateFailure(entityList)
+                    return SBaseResponse(SResponseType.failure, "更新失败!")
+                }
+            }
+
+            onUpdateSuccess(entityList)
+            SBaseResponse(SResponseType.success)
+        } catch (e: Exception){
+            e.printStackTrace()
+            SBaseResponse(SResponseType.failure, e.message!!)
+        }
+    } // Fun update()
+
+//    /**
+//     * 批量更新对象
+//     *
+//     * @param   request         批量更新请求
+//     * @param   withFilterList  公共过滤条件
+//     * @return  更新结果应答信息
+//     */
+//    fun batchUpdate(@RequestBody request: SBatchUpdateRequest<ENTITY>, withFilterList: ArrayList<SFilter>? = null): SBaseResponse {
+//        val filterList = ArrayList<SFilter>()
+//        if (withFilterList != null) {
+//            filterList.addAll(withFilterList)
+//        }
+//
+//        if (request.filters != null) {
+//            filterList.addAll(request.filters!!)
+//        }
+//
+//        return if (update(request.content!!, request.projection!!, filterList)) {
+//            SBaseResponse(SResponseType.success)
+//        } else {
+//            SBaseResponse(SResponseType.failure, "更新失败!")
+//        }
+//    } // Fun batchUpdate()
+
+    /**
+     * 根据条件分页查询
+     *
+     * @param   request         查询请求
+     * @param   withFilterList  公共过滤条件
+     * @return  查询结果应答信息
+     */
+    fun pageQuery(@RequestBody request: SQueryRequest, withFilterList: ArrayList<SFilter>? = null): SQueryResponse<ENTITY> {
+        return try {
+            val queryBuilder = select(request.filters)
+            if (null != withFilterList) {
+                queryBuilder.filterList.addAll(withFilterList)
+                logger.debug("filterList = ${queryBuilder.filterList.toJson()}")
+            }
+
+            /** 排序条件 */
+            if (null != request.orders) {
+                queryBuilder.order(request.orders!!)
+            }
+
+            /** 去重 */
+            if (null != request.distinct && request.distinct!!) {
+                queryBuilder.distinct()
+            }
+
+            /** 映射标志 */
+            if (null != request.projection) {
+                queryBuilder.projection(request.projection!!)
+            }
+
+            /** 分组统计 */
+            if (null != request.group && !request.group!!.nameList.isNullOrEmpty()) {
+                // 分组属性名列表
+                queryBuilder.group(*request.group!!.nameList.toArray(emptyArray<String>()))
+                // 是否统计个数
+                queryBuilder.count(request.group!!.count ?: false)
+                // 求和
+                if (!request.group!!.sumList.isNullOrEmpty()) {
+                    queryBuilder.sum(*request.group!!.sumList.toArray(emptyArray<String>()))
+                }
+                // 求平均值
+                if (!request.group!!.avgList.isNullOrEmpty()) {
+                    queryBuilder.avg(*request.group!!.avgList.toArray(emptyArray<String>()))
+                }
+                // 取最大值
+                if (!request.group!!.maxList.isNullOrEmpty()) {
+                    queryBuilder.max(*request.group!!.maxList.toArray(emptyArray<String>()))
+                }
+                // 取最小值
+                if (!request.group!!.minList.isNullOrEmpty()) {
+                    queryBuilder.min(*request.group!!.minList.toArray(emptyArray<String>()))
+                }
+            }
+
+            onQueryBefore(queryBuilder)
+            val pageInfo = queryBuilder.pageQuery(request.pageNumber,request.pageSize)
+            /** 如果需要进行级联查询 */
+            if (request.cascade != null && request.cascade!!.size > 0 &&
+                    pageInfo.content != null && pageInfo.content!!.size > 0) {
+                @Suppress("UNCHECKED_CAST")
+                processCascadeQuery(pageInfo.content!! as ArrayList<Any>, request.cascade!!)
+            }
+
+            onQuerySuccess(pageInfo)
+            SQueryResponse(pageInfo)
+        } catch (e : Exception){
+            e.printStackTrace()
+            SQueryResponse(SResponseType.failure, e.rootCause()?.message ?: e.message ?: "未知异常!")
+        }
+    } // Fun pageQuery()
+
+    /**
+     * 根据条件统计
+     *
+     * @param   request         查询条件请求
+     * @param   withFilterList  公共过滤条件
+     * @return  查询结果应答信息
+     */
+    fun count(@RequestBody request: SCountRequest, withFilterList: ArrayList<SFilter>? = null): SCountResponse {
+        return try {
+            val queryBuilder = select(request.filters)
+
+            if (null != withFilterList) {
+                queryBuilder.filterList.addAll(withFilterList)
+            }
+
+            onCountBefore(queryBuilder)
+            val count = queryBuilder.execCount()
+            onCountSuccess(count)
+            SCountResponse(count)
+        } catch (e : Exception){
+            e.printStackTrace()
+            SCountResponse(0, SResponseType.failure, e.rootCause()?.message ?: e.message ?: "未知异常!")
+        }
+    } // Fun count()
+} // Class SObjectService

+ 258 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/SPageContext.kt

@@ -0,0 +1,258 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service
+
+import org.slf4j.LoggerFactory
+import javax.servlet.http.Cookie
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+import javax.servlet.http.HttpSession
+
+/**
+ * 页面上下文
+ *
+ * @author 庞利祥 <sybotan@126.com>
+ */
+object SPageContext {
+    // 日志记录器
+    private val logger = LoggerFactory.getLogger(SPageContext::class.java)
+    // 客户端请求对象")
+    private val request = ThreadLocal<HttpServletRequest>()
+    // 服务器应答对象")
+    private val response = ThreadLocal<HttpServletResponse>()
+
+    var domain = "sybotan"
+
+    /**
+     * 获得客户端请求对象
+     *
+     * @return 返回客户端请求对象
+     */
+    fun getRequest(): HttpServletRequest {
+        return request.get()
+    } // Fun getRequest()
+
+    /**
+     * 设置客户端请求对象
+     *
+     * @param req 客户端请求对象
+     */
+    fun setRequest(req: HttpServletRequest) {
+        request.set(req)
+        return
+    } // Fun setRequest()
+
+    /**
+     * 释放保存客户端请求对象的变量
+     */
+    fun removeRequest() {
+        request.remove()
+        return
+    } // Fun removeRequest()
+
+    /**
+     * 获得服务器应答对象
+     *
+     * @return 服务器应答对象
+     */
+    fun getResponse(): HttpServletResponse {
+        return response.get()
+    } // Fun getRequest()
+
+    /**
+     * 设置服务器应答对象
+     *
+     * @param resp 服务器应答对象
+     */
+    fun setResponse(resp: HttpServletResponse) {
+        response.set(resp)
+        return
+    } // Fun setRequest()
+
+    /**
+     * 释放保存服务器应答对象的变量
+     */
+    fun removeResponse() {
+        response.remove()
+        return
+    } // Fun removeRequest()
+
+    /**
+     * 获得客户端会话对象
+     *
+     * @return 返回客户端会话对象
+     */
+    fun getSession(): HttpSession? {
+        val req = request.get()
+        return req?.session
+    } // Fun getSession()
+
+    /**
+     * 获得保存在客户端会话中的指定属性
+     *
+     * @param name   属性名
+     * @return 返回保存在客户端会话中的指定属性
+     */
+    fun getAttribute(name: String): Any? {
+        val session = getSession()
+        return session?.getAttribute(name)
+    } // Fun getAttribute()
+
+    /**
+     * 设置保存在客户端会话中的指定属性
+     *
+     * @param name   属性名
+     * @param object 值
+     */
+    fun setAttribute(name: String, `object`: Any) {
+        val session = getSession()
+        session?.setAttribute(name, `object`)
+        return
+    } // Fun setAttribute()
+
+    /**
+     * 移除保存在客户端会话中的指定属性
+     *
+     * @param name 属性名
+     */
+    fun removeAttribute(name: String) {
+        val session = getSession()
+        session?.removeAttribute(name)
+        return
+    } // Fun removeAttribute()
+
+    /**
+     * 移除保存在客户端会话中的所有属性
+     */
+    fun removeAllAttribute() {
+        val session = getSession() ?: return
+
+        val nameList = session.attributeNames
+        while (nameList.hasMoreElements()) {
+            val name = nameList.nextElement()
+            session.removeAttribute(name)
+        }
+        return
+    } // Fun removeAllAttribute()
+
+    /**
+     * 获得cookie
+     *
+     * @param name cookie名
+     * @return cookie
+     */
+    fun getCookie(name: String): Cookie? {
+        val request = getRequest()
+        val cookieList = request.cookies
+
+        if (null != cookieList) {
+            for (cookie in cookieList) {
+                if (name.compareTo(cookie.name) == 0) {
+                    return cookie
+                }
+            }
+        }
+
+        return null
+    } // Fun getCookie()
+
+    /**
+     * 添加cookie
+     *
+     * @param name cookie名称
+     * @param value cookie值
+     */
+    fun setCookie(name: String, value: String) {
+        val response = getResponse()
+        val cookie = Cookie(name, value)
+
+        cookie.domain = domain
+        cookie.path = "/"
+        cookie.maxAge = 365 * 24 * 3600     // cookie最大生命周期1年
+
+        response.addCookie(cookie)
+        return
+    } // Fun setCookie()
+
+    /**
+     * 删除cookie
+     *
+     * @param name cookie名称
+     */
+    fun removeCookie(name: String) {
+        val response = getResponse()
+        val cookie = Cookie(name, null)
+
+        cookie.domain = domain
+        cookie.path = "/"
+        cookie.maxAge = 0
+        response.addCookie(cookie)
+
+        return
+    } // Fun addCookie()
+
+    /**
+     * 获得请求头
+     *
+     * @param   name    请求头名
+     * @return  请求头内容
+     */
+    fun getHeader(name: String): String? {
+        val request = getRequest()
+        return request.getHeader(name)
+    } // Fun getHeader()
+
+    /**
+     * 获得客户端的IP地址
+     *
+     * @return 客户端的IP地址
+     */
+    fun getRequestIp(): String {
+        val request = getRequest()
+
+        var ip: String? = request.getHeader("x-forwarded-for")
+        if (null == ip || ip.isEmpty() || ip.equals("unknown", ignoreCase = true)) {
+            ip = request.getHeader("Proxy-Client-IP")
+        }
+        if (null == ip || ip.isEmpty() || ip.equals("unknown", ignoreCase = true)) {
+            ip = request.getHeader("WL-Proxy-Client-IP")
+        }
+        if (null == ip || ip.isEmpty() || ip.equals("unknown", ignoreCase = true)) {
+            ip = request.getHeader("HTTP_CLIENT_IP")
+        }
+        if (null == ip || ip.isEmpty() || ip.equals("unknown", ignoreCase = true)) {
+            ip = request.getHeader("HTTP_X_FORWARDED_FOR")
+        }
+        if (null == ip || ip.isEmpty() || ip.equals("unknown", ignoreCase = true)) {
+            ip = request.remoteAddr
+            if (ip!!.contains(":")) {
+                ip = "127.0.0.1"
+            }
+        }
+        return ip
+    } // Fun getRequestIp()
+} // Object SPageContext

+ 70 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/config/SAsyncConfig.kt

@@ -0,0 +1,70 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.config
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.PropertySource
+import org.springframework.scheduling.annotation.EnableAsync
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
+import java.util.concurrent.Executor
+
+/**
+ * 异步线程池配置文件
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@Configuration  //表明该类是一个配置类
+@EnableAsync    //开启异步事件的支持
+@PropertySource(value = ["classpath:application.properties"], ignoreResourceNotFound = true)
+open class SAsyncConfig {
+
+    /** 线程池维护线程的最少数量 */
+    // @Value("${corePoolSize}")
+    private var corePoolSize = 10
+
+    /** 线程池维护线程的最大数量 */
+    // @Value("${maxPoolSize}")
+    private var maxPoolSize = 50
+
+    /** 缓存队列 */
+    // @Value("${queueCapacity}")
+    private var queueCapacity = 10
+
+    /**
+     * 任务执行器
+     */
+    @Bean
+    open fun taskExecutor(): Executor {
+        val executor = ThreadPoolTaskExecutor()
+        executor.corePoolSize = corePoolSize
+        executor.maxPoolSize = maxPoolSize
+        executor.setQueueCapacity(queueCapacity)
+        executor.initialize();
+        return executor
+    } // Function
+} // Class SAsyncConfig

+ 73 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/interceptors/SAccessControlAllowOriginInterceptor.kt

@@ -0,0 +1,73 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.interceptors
+
+import org.slf4j.LoggerFactory
+import org.springframework.web.servlet.ModelAndView
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter
+import java.lang.Exception
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+/**
+ * 允许跨域访问拦截器
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+class SAccessControlAllowOriginInterceptor : HandlerInterceptorAdapter() {
+    // 定义类对象
+    companion object {
+        // 日志
+        private val logger = LoggerFactory.getLogger(SAccessControlAllowOriginInterceptor::class.java)
+    } // companion object
+
+    /**
+     *
+     */
+    override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any?): Boolean {
+        response.addHeader("Access-Control-Allow-Origin", "*")
+        response.addHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE")
+        response.addHeader("Access-Control-Allow-Headers", "cache-control,content-type,hash-referer,x-requested-with")
+
+        return true
+    } // Fun preHandle
+
+    /**
+     *
+     */
+    override fun postHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any?, modelAndView: ModelAndView?) {
+        return
+    } // Fun postHandle()
+
+    /**
+     *
+     */
+    override fun afterCompletion(request: HttpServletRequest?, response: HttpServletResponse?, handler: Any?, ex: Exception?) {
+        return
+    } // Fun afterCompletion()
+
+} // Class SPageInterceptor

+ 81 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/interceptors/SPageInterceptor.kt

@@ -0,0 +1,81 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.interceptors
+
+import com.persagy.service.SPageContext
+import org.slf4j.LoggerFactory
+import org.springframework.web.servlet.HandlerInterceptor
+import org.springframework.web.servlet.ModelAndView
+import java.lang.Exception
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+/**
+ * 页面拦截器
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+class SPageInterceptor(val accessControlAllowOrigin: Boolean = true) : HandlerInterceptor {
+    // 定义类对象
+    companion object {
+        // 日志
+        private val logger = LoggerFactory.getLogger(SPageInterceptor::class.java)
+    } // companion object
+
+    /**
+     *
+     */
+    override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any?): Boolean {
+        SPageContext.setRequest(request)
+        SPageContext.setResponse(response)
+
+        // 如果允许跨域访问
+        if (accessControlAllowOrigin) {
+            response.addHeader("Access-Control-Allow-Origin", "*")
+            response.addHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE")
+            response.addHeader("Access-Control-Allow-Headers", "cache-control,content-type,hash-referer,x-requested-with")
+        }
+
+        return true
+    } // Fun preHandle
+
+    /**
+     */
+    override fun postHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any?, modelAndView: ModelAndView?) {
+        return
+    } // Fun postHandle()
+
+    /**
+     *
+     */
+    override fun afterCompletion(request: HttpServletRequest?, response: HttpServletResponse?, handler: Any?, ex: Exception?) {
+        SPageContext.removeRequest()
+        SPageContext.removeResponse()
+        return
+    } // Fun afterCompletion()
+
+} // Class SPageInterceptor

+ 92 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/json/SFastjsonAnnotationIntrospector.kt

@@ -0,0 +1,92 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.json
+
+import com.alibaba.fastjson.annotation.JSONField
+import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector
+import com.fasterxml.jackson.databind.PropertyName
+import com.fasterxml.jackson.databind.introspect.Annotated
+import com.persagy.base.extensions.toJson
+
+/**
+ * swagger使用的json注解拦截器
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+class SFastjsonAnnotationIntrospector : JacksonAnnotationIntrospector() {
+
+    /**
+     * 判断注解是否为JSONField类型
+     *
+     * @param   ann         注解
+     * @return  如果有JSONField注解,则返回true;否则false.
+     */
+    override fun isAnnotationBundle(ann: Annotation): Boolean {
+        println("ann = ${ann.toJson()}")
+        return if (ann.annotationClass == JSONField::class.java) {
+            println("ann = ======")
+            true
+        } else {
+            println("ann = -------")
+            super.isAnnotationBundle(ann)
+        }
+    } // Fun isAnnotationBundle()
+
+    /**
+     * 为序列为操作查找属性名
+     *
+     * @param   a           注解
+     * @return  属性名
+     */
+    override fun findNameForSerialization(a: Annotated): PropertyName? {
+        val nameForSerialization = super.findNameForSerialization(a)
+        if (nameForSerialization == null || nameForSerialization === PropertyName.USE_DEFAULT) {
+            val jsonField = _findAnnotation(a, JSONField::class.java)
+            if (jsonField != null) {
+                return PropertyName.construct(jsonField.name)
+            }
+        }
+        return nameForSerialization
+    } // Fun findNameForSerialization()
+
+    /**
+     * 为反序列为操作查找属性名
+     *
+     * @param   a           注解
+     * @return  属性名
+     */
+    override fun findNameForDeserialization(a: Annotated): PropertyName? {
+        val nameForDeserialization = super.findNameForDeserialization(a)
+        if (nameForDeserialization == null || nameForDeserialization === PropertyName.USE_DEFAULT) {
+            val jsonField = _findAnnotation(a, JSONField::class.java)
+            if (jsonField != null) {
+                return PropertyName.construct(jsonField.name)
+            }
+        }
+        return nameForDeserialization
+    } // Fun findNameForDeserialization()
+} // Class SFastjsonAnnotationIntrospector

+ 78 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/json/SJsonHttpMessageConverter.kt

@@ -0,0 +1,78 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.json
+
+import com.alibaba.fastjson.JSON
+import com.alibaba.fastjson.PropertyNamingStrategy
+import com.alibaba.fastjson.serializer.NameFilter
+import com.alibaba.fastjson.serializer.SerializeConfig
+import com.alibaba.fastjson.serializer.SerializerFeature
+import com.alibaba.fastjson.support.config.FastJsonConfig
+import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter
+import com.persagy.base.utils.SJsonUtil
+import org.springframework.http.MediaType
+import springfox.documentation.swagger.web.SwaggerResource
+import springfox.documentation.swagger.web.UiConfiguration
+
+/**
+ *
+ */
+open class SJsonHttpMessageConverter : FastJsonHttpMessageConverter() {
+
+    /**
+     * 属性名命名规则
+     */
+    var propertyNamingStrategy : PropertyNamingStrategy
+        get() = SJsonUtil.serializeConfig.propertyNamingStrategy
+        set(value) {
+            SJsonUtil.serializeConfig.propertyNamingStrategy = value
+        } // Set propertyNamingStrategy
+
+    init {
+        val fastJsonConfig = FastJsonConfig()
+        fastJsonConfig.dateFormat = JSON.DEFFAULT_DATE_FORMAT
+        fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect)
+        this.fastJsonConfig = fastJsonConfig;
+
+        val nameFilter = NameFilter({ obj, name, value -> name })
+        //val nameFilter = { `object`, name, value -> name }
+        SJsonUtil.serializeConfig.addFilter(UiConfiguration::class.java, nameFilter)
+        SJsonUtil.serializeConfig.addFilter(SwaggerResource::class.java, nameFilter)
+//        serializeConfig.addFilter(MetricsEndpoint.MetricResponse::class.java, nameFilter)
+//        serializeConfig.addFilter(EnvironmentEndpoint.EnvironmentDescriptor::class.java, nameFilter)
+        fastJsonConfig.serializeConfig = SJsonUtil.serializeConfig
+
+        val fastMediaTypes = ArrayList<MediaType>()
+        fastMediaTypes.add(MediaType.APPLICATION_JSON)
+        fastMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED)
+        fastMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM)
+        fastMediaTypes.add(MediaType.TEXT_HTML)
+        fastMediaTypes.add(MediaType("application", "vnd.spring-boot.actuator.v2+json"))
+        this.supportedMediaTypes = fastMediaTypes;
+    } // Init
+
+} // Class SJsonHttpMessageConverter

+ 67 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/utils/SBeanUtil.kt

@@ -0,0 +1,67 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.utils
+
+import org.springframework.beans.factory.support.BeanDefinitionBuilder
+import org.springframework.beans.factory.support.DefaultListableBeanFactory
+import org.springframework.context.ApplicationContext
+
+/**
+ * Bean工具类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+object SBeanUtil {
+
+    /**
+     * 动态注册Bean
+     *
+     * @param   ctx     应用上下文
+     * @param   clz     Bean对应的类
+     * @param   name    Bean名称
+     */
+    fun registBeanDefinition(ctx: ApplicationContext, clz: Class<*>, name: String) {
+        val defaultBeanFactory = ctx.autowireCapableBeanFactory as (DefaultListableBeanFactory)
+        val beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clz)
+        //beanDefinitionBuilder.addPropertyValue("name", name)
+        defaultBeanFactory.registerBeanDefinition(name, beanDefinitionBuilder.beanDefinition)
+        defaultBeanFactory.removeBeanDefinition(name)
+        return
+    } // Fun registBeanDefinition()
+
+    /**
+     * 动态删除注册的Bean
+     *
+     * @param   ctx     应用上下文
+     * @param   name    Bean名称
+     */
+    fun removeBeanDefinition(ctx: ApplicationContext, name: String) {
+        val defaultBeanFactory = ctx.autowireCapableBeanFactory as (DefaultListableBeanFactory)
+        defaultBeanFactory.removeBeanDefinition(name)
+        return
+    } // Fun removeBeanDefinition()
+} // Class SBeanUtil()

+ 157 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/utils/SJsonUtil.kt

@@ -0,0 +1,157 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.utils
+
+import com.alibaba.fastjson.JSON
+import com.alibaba.fastjson.PropertyNamingStrategy
+import com.alibaba.fastjson.serializer.SerializeConfig
+import com.alibaba.fastjson.serializer.SerializerFeature
+import java.io.InputStream
+import java.lang.reflect.Type
+
+object SJsonUtil {
+    /** 序列化配置 */
+    val serializeConfig = SerializeConfig()
+
+    /**
+     * 转换对象到json格式字符串
+     *
+     * @param   obj                     被转换对象
+     * @param   namingStrategy          命名规则
+     * @return  json格式字符串
+     */
+    fun toJson(obj: Any, namingStrategy: PropertyNamingStrategy? = null): String {
+        return if (null == namingStrategy) {
+            JSON.toJSONString(obj, serializeConfig, SerializerFeature.DisableCircularReferenceDetect)
+        } else {
+            val config = SerializeConfig()
+            config.propertyNamingStrategy = namingStrategy
+            JSON.toJSONString(obj, config, SerializerFeature.DisableCircularReferenceDetect)
+        }
+    } // Fun toJson()
+
+    /**
+     * 转换对象到json格式字符串,包含空字符串
+     *
+     * @param   obj                     被转换对象
+     * @param   namingStrategy          首字母是否大写
+     * @return  json格式字符串
+     */
+    fun toJsonAll(obj: Any, namingStrategy: PropertyNamingStrategy? = null): String {
+        return if (null == namingStrategy) {
+            JSON.toJSONString(obj,
+                    serializeConfig,
+                    SerializerFeature.WriteMapNullValue,
+                    SerializerFeature.WriteNullListAsEmpty,
+                    SerializerFeature.DisableCircularReferenceDetect)
+        } else {
+            val config = SerializeConfig()
+            config.propertyNamingStrategy = namingStrategy
+            JSON.toJSONString(obj,
+                    config,
+                    SerializerFeature.WriteMapNullValue,
+                    SerializerFeature.WriteNullListAsEmpty,
+                    SerializerFeature.DisableCircularReferenceDetect)
+        }
+    } // Fun toJsonAll()
+
+    /**
+     * 将Json数据解析成相应的映射对象
+     *
+     * @param   jsonData                要解析的JSON数据
+     * @param   type                    Java类型
+     * @return  解析结果
+     */
+    @Throws(Exception::class)
+    fun <T> fromJson(jsonData: String, type: Class<T>): T? {
+        return JSON.parseObject(jsonData, type)
+    } // Fun fromJson()
+
+    /**
+     * 将Json数据解析成相应的映射对象
+     *
+     * @param   jsonData                要解析的JSON数据
+     * @param   type                    Java类型
+     * @return  解析结果
+     */
+    @Throws(Exception::class)
+    fun fromJson(jsonData: String, type: Type): Any? {
+        return JSON.parseObject(jsonData, type)
+    } // Fun fromJson()
+
+    /**
+     * 将Json数据解析成相应的映射对象
+     *
+     * @param   input                   要解析的JSON数据输入流
+     * @param   type                    Java类型
+     * @param   namingStrategy          命名规则
+     * @return  解析结果
+     */
+    @Throws(Exception::class)
+    fun <T> fromJson(input: InputStream, type: Class<T>): T? {
+        val count = input.available()
+        val buf = ByteArray(count)
+        var readCount = 0
+        while (readCount < count) {
+            readCount += input.read(buf, readCount, count - readCount)
+        }
+
+        return fromJson(String(buf), type)
+    } // Fun fromJson()
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    /**
+     * 将Json数据解析成相应的映射对象
+     *
+     * @param   jsonData                要解析的JSON数据
+     * @param   namingStrategy          命名规则
+     * @return  解析结果
+     */
+    @Throws(Exception::class)
+    inline fun <reified T> fromJson(jsonData: String, namingStrategy: PropertyNamingStrategy? = null): T {
+        return JSON.parseObject(jsonData, T::class.java)
+    } // Fun parseJson()
+
+    /**
+     * 将Json数据解析成相应的映射对象
+     *
+     * @param   input                   要解析的JSON数据输入流
+     * @param   namingStrategy          命名规则
+     * @return  解析结果
+     */
+    @Throws(Exception::class)
+    inline fun <reified T> fromJson(input: InputStream, namingStrategy: PropertyNamingStrategy? = null): T {
+        val count = input.available()
+        val buf = ByteArray(count)
+        var readCount = 0
+        while (readCount < count) {
+            readCount += input.read(buf, readCount, count - readCount)
+        }
+
+        return fromJson(String(buf), namingStrategy)
+    } // Fun fromJson()
+} // Object SJsonUtil

+ 137 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/utils/SQrCodeUtil.kt

@@ -0,0 +1,137 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+package com.persagy.service.utils
+
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.EncodeHintType
+import com.google.zxing.WriterException
+import com.google.zxing.qrcode.QRCodeWriter
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
+import java.awt.image.BufferedImage
+import java.io.IOException
+import java.util.*
+import javax.imageio.ImageIO
+import javax.servlet.ServletOutputStream
+import javax.servlet.http.HttpServletResponse
+
+/**
+ * 二维码生成工具
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+object SQrCodeUtil {
+
+    var bgColor = 0xFFFFFF
+    var pixelColor = 0x0
+
+    /**
+     * 生成并返回二维码到 Web 客户端
+     *
+     * @param   response        web 请求应答体
+     * @param   contents        二维码存储的内容
+     * @param   size            二维码大小
+     * @param   logo            二维码中间的logo图像
+     * @return  二维码图像
+     */
+    fun outQrCode(response: HttpServletResponse, contents: String, size: Int, logo: BufferedImage? = null) {
+        val buffImg = createQrCode(contents, size, logo)
+
+        // 禁止图像缓存
+        response.setHeader("Pragma", "no-cache")
+        response.setHeader("Cache-Control", "no-cache")
+        response.setDateHeader("Expires", 0)
+        response.contentType = "image/jpeg"
+
+        // 将图像输出到Servlet输出流中
+        val sos: ServletOutputStream
+        try {
+            sos = response.outputStream
+            ImageIO.write(buffImg, "jpeg", sos)
+            sos.close()
+        } catch (e: IOException) {
+            e.printStackTrace()
+        }
+
+        return
+    } // Fun outQrCode()
+
+    /**
+     * 创建二维码图像
+     *
+     * @param   contents        二维码存储的内容
+     * @param   size            二维码大小
+     * @return  二维码图像
+     */
+    fun createQrCode(contents: String, size: Int, logo: BufferedImage? = null): BufferedImage? {
+        try {
+            val hints = Hashtable<EncodeHintType, Any>()
+            hints[EncodeHintType.CHARACTER_SET] = "utf-8"
+            hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.L
+
+            val matrix = QRCodeWriter().encode(contents, BarcodeFormat.QR_CODE, size, size, hints)
+            var image = BufferedImage(size, size, BufferedImage.TYPE_INT_RGB)
+            for (y in 0 until size) {
+                for (x in 0 until size) {
+                    if (matrix.get(x, y)) {
+                        image.setRGB(x, y, pixelColor)
+                    } else {
+                        image.setRGB(x, y, bgColor)
+                    }
+                }
+            }
+
+            if (null != logo) {
+                image = addLogo(image, logo)
+            }
+
+            return image
+        } catch (e: WriterException) {
+            e.printStackTrace()
+        }
+        return null
+    } // Fun createQrCode()
+
+    /**
+     * 为二维码图片增加Logo
+     *
+     * @param   src         二维码图片
+     * @param   logo        Logol图片
+     * @return  添加过Logo的二维码图片
+     */
+    private fun addLogo(src: BufferedImage, logo: BufferedImage): BufferedImage {
+        val srcWidth = src.width
+        val srcHeight = src.height
+        val logoWidth = logo.width
+        val logoHeight = logo.height
+
+        val image = BufferedImage(srcWidth, srcHeight, BufferedImage.TYPE_4BYTE_ABGR)
+        val gd = image.createGraphics()
+        gd.drawImage(src,0,0, null)
+        gd.drawImage(logo,srcWidth * 2 / 5, srcHeight * 2 / 5, srcWidth * 3 / 5, srcHeight / 5, null)
+
+        return image
+    } // Fun addLogo()
+} // Object SQrCodeUtil

+ 138 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/utils/SRandCodeUtil.kt

@@ -0,0 +1,138 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.utils
+
+import org.slf4j.LoggerFactory
+import javax.imageio.ImageIO
+import javax.servlet.ServletOutputStream
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+import java.awt.*
+import java.awt.image.BufferedImage
+import java.io.IOException
+import java.util.Random
+
+/**
+ * 验证码生成器
+ *
+ * @author 庞利祥 <sybotan@126.com>
+ */
+object SRandCodeUtil {
+    // 日志
+    private val logger = LoggerFactory.getLogger(SRandCodeUtil::class.java)
+
+    /**
+     * 生成验证码
+     *
+     * @param request  客户端请求对象
+     * @param response 服务器应答对象
+     */
+    fun checkCode(request: HttpServletRequest, response: HttpServletResponse) {
+        val width = 195// 验证码图片的宽度
+        val height = 26// 验证码图片的高度
+        val codeCount = 4// 验证码字符个
+        val xx: Int
+        val fontHeight: Int// 字体高度
+        val codeY: Int
+        val codeSequence = charArrayOf('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9')
+        xx = width / (codeCount + 1)
+        fontHeight = height - 2
+        codeY = height - 5
+
+        // 定义图像buffer
+        val buffImg = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
+        val gd = buffImg.createGraphics()
+
+        // 创建随机数生成器
+        val random = Random()
+
+        // 将图像填充为白色
+        gd.color = Color.WHITE
+        gd.fillRect(0, 0, width, height)
+
+        // 创建字体,字体的大小应该根据图片的高度来定
+        // 字体
+        val fontTypes = arrayOf("tahoma", "Atlantic Inline", "fantasy", "Times New Roman", "Georgia", "Arial", "Helvetica", "sans-serif", "System")
+        val fontTypesLength = fontTypes.size
+        val font = Font(fontTypes[random.nextInt(fontTypesLength)], Font.BOLD, fontHeight)
+        // 设置字体
+        gd.font = font
+
+        gd.color = Color.red
+        for (i in 0..99) {
+            val x = random.nextInt(width)
+            val y = random.nextInt(height)
+            gd.drawOval(x, y, 0, 0)
+        }
+
+        // randomCode用于保存随机产生的验证码,以便用户登录后进行验证
+        val randomCode = StringBuffer()
+        var red: Int
+        var green: Int
+        var blue: Int
+
+        // 随机产生codeCount数字的验证码
+        for (i in 0 until codeCount) {
+            // 得到随机产生的验证码数字
+            val strRand = codeSequence[random.nextInt(34)].toString()
+            // 产生随机的颜色分量来构建颜色值,这样输出的每位数字的颜色值都将不同
+            red = random.nextInt(200)
+            green = random.nextInt(200)
+            blue = random.nextInt(200)
+
+            // 用随机产生的颜色将验证码绘制到图像中
+            gd.color = Color(red, green, blue)
+            gd.drawString(strRand, (i + 1) * xx, codeY)
+
+            // 将产生的四个随机数组合在
+            randomCode.append(strRand)
+        }
+
+        // 将四位数字的验证码保存到Session中
+        logger.debug("verifyCode = $randomCode")
+        val session = request.session
+        session.setAttribute("verifyCode", randomCode.toString())
+
+        // 禁止图像缓存
+        response.setHeader("Pragma", "no-cache")
+        response.setHeader("Cache-Control", "no-cache")
+        response.setDateHeader("Expires", 0)
+        response.contentType = "image/jpeg"
+
+        // 将图像输出到Servlet输出流中
+        val sos: ServletOutputStream
+        try {
+            sos = response.outputStream
+            ImageIO.write(buffImg, "jpeg", sos)
+            sos.close()
+        } catch (e: IOException) {
+            e.printStackTrace()
+        }
+        return
+    } // Fun checkCode()
+
+} // Class RandCOdeUtil

+ 75 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/utils/SSpringContextUtil.kt

@@ -0,0 +1,75 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.utils
+
+import org.springframework.context.ApplicationContext
+import org.springframework.context.ApplicationContextAware
+import org.springframework.stereotype.Component
+
+/**
+ * Spring上下文环境工具
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@Component
+class SSpringContextUtil : ApplicationContextAware{
+    companion object {
+        /** 应用上下文对象 */
+        lateinit var applicationContext: ApplicationContext
+
+        /**
+         * 根据名称获得Bean
+         *
+         * @param   name    bean名称
+         * @return  对应的bean对象
+         */
+        fun getBean(name: String) : Any? {
+            return applicationContext.getBean(name)
+        } // Fun getBean()
+
+        /**
+         * 根据类型获得Bean
+         *
+         * @param   requiredType    类型
+         * @return  对应的bean对象
+         */
+        fun getBean(requiredType: Class<*>) : Any? {
+            return applicationContext.getBean(requiredType)
+        } // Fun getBean()
+    }
+
+    /**
+     * 实现ApplicationContextAware接口的回调方法。设置上下文环境
+     *
+     * @param   context     应用上下文对象
+     */
+    override fun setApplicationContext(context: ApplicationContext) {
+        applicationContext = context
+        return
+    } // Fun setApplicationContext()
+
+} // SSpringContextUtil

+ 120 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/utils/crypt/SAESCoderUtil.kt

@@ -0,0 +1,120 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.utils.crypt
+
+import javax.crypto.Cipher
+import javax.crypto.KeyGenerator
+import javax.crypto.spec.SecretKeySpec
+import java.io.IOException
+import java.security.Key
+import java.security.NoSuchAlgorithmException
+import java.security.SecureRandom
+import java.util.*
+
+/**
+ * AES安全编码组件
+ *
+ * @author Andy
+ */
+abstract class SAESCoderUtil : SCoderUtil() {
+    companion object {
+        const val ALGORITHM = "AES"
+
+        /**
+         * 转换密钥
+         *
+         * @param key
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        private fun toKey(key: ByteArray): Key {
+            return SecretKeySpec(key, ALGORITHM)
+        }
+
+        /**
+         * 解密
+         *
+         * @param data
+         * @param key
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        fun decrypt(data: ByteArray, key: String): ByteArray {
+            val k = toKey(Base64.getDecoder().decode(key))
+
+            val cipher = Cipher.getInstance(ALGORITHM)
+            cipher.init(Cipher.DECRYPT_MODE, k)
+
+            return cipher.doFinal(data)
+        } // Fun decrypt()
+
+        /**
+         * 加密
+         *
+         * @param data
+         * @param key
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        fun encrypt(data: ByteArray, key: String): ByteArray {
+            val k = toKey(Base64.getDecoder().decode(key))
+            val cipher = Cipher.getInstance(ALGORITHM)
+            cipher.init(Cipher.ENCRYPT_MODE, k)
+
+            return cipher.doFinal(data)
+        } // Fun encrypt()
+
+        /**
+         * 生成密钥
+         *
+         * @param seed
+         * @return
+         * @throws IOException
+         * @throws NoSuchAlgorithmException
+         */
+        @Throws(IOException::class, NoSuchAlgorithmException::class)
+        @JvmOverloads
+        fun initKey(seed: String? = null): String? {
+            val secureRandom = if (seed != null) {
+                SecureRandom(Base64.getDecoder().decode(seed))
+            } else {
+                SecureRandom()
+            }
+
+            val kg = KeyGenerator.getInstance(ALGORITHM)
+            kg.init(secureRandom)
+
+            val secretKey = kg.generateKey()
+
+            return Base64.getEncoder().encodeToString(secretKey.encoded)
+        } // Fun initKey()
+    } // companion object
+} // Class SAESCoderUtil
+

+ 88 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/utils/crypt/SCoderUtil.kt

@@ -0,0 +1,88 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.utils.crypt
+
+import java.security.MessageDigest
+import java.security.NoSuchAlgorithmException
+
+/**
+ * 基础加密组件
+ *
+ * @author Andy
+ */
+abstract class SCoderUtil {
+    companion object {
+        /** SHA算法 */
+        const val KEY_SHA = "SHA"
+        /** MD5算法 */
+        const val KEY_MD5 = "MD5"
+        /**
+         * MAC算法可选以下多种算法
+         *
+         *
+         * <pre>
+         * HmacMD5
+         * HmacSHA1
+         * HmacSHA256
+         * HmacSHA384
+         * HmacSHA512
+        </pre> *
+         */
+        const val KEY_MAC = "HmacMD5"
+
+        /**
+         * MD5加密
+         *
+         * @param data
+         * @return
+         * @throws NoSuchAlgorithmException
+         * @throws Exception
+         */
+        @Throws(NoSuchAlgorithmException::class)
+        fun encryptMD5(data: ByteArray): ByteArray {
+            val md5 = MessageDigest.getInstance(KEY_MD5)
+            md5.update(data)
+            return md5.digest()
+        } // Fun encryptMD5()
+
+        /**
+         * SHA加密
+         *
+         * @param data
+         * @return
+         * @throws NoSuchAlgorithmException
+         * @throws Exception
+         */
+        @Throws(NoSuchAlgorithmException::class)
+        fun encryptSHA(data: ByteArray): ByteArray {
+            val sha = MessageDigest.getInstance(KEY_SHA)
+            sha.update(data)
+            return sha.digest()
+        } // Fun encryptSHA()
+    }
+
+} // Class SCoderUtil

+ 124 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/utils/crypt/SDESCoderUtil.kt

@@ -0,0 +1,124 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.utils.crypt
+
+import javax.crypto.Cipher
+import javax.crypto.KeyGenerator
+import javax.crypto.SecretKeyFactory
+import javax.crypto.spec.DESKeySpec
+import java.io.IOException
+import java.security.Key
+import java.security.NoSuchAlgorithmException
+import java.security.SecureRandom
+import java.util.*
+
+/**
+ * DES安全编码组件
+ *
+ * @author Andy
+ */
+abstract class SDESCoderUtil : SCoderUtil() {
+    companion object {
+        const val ALGORITHM = "DES"
+
+        /**
+         * 转换密钥
+         *
+         * @param key
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        private fun toKey(key: ByteArray?): Key {
+            val dks = DESKeySpec(key!!)
+            val keyFactory = SecretKeyFactory.getInstance(ALGORITHM)
+
+            return keyFactory.generateSecret(dks)
+        }
+
+        /**
+         * 解密
+         *
+         * @param data
+         * @param key
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        fun decrypt(data: ByteArray, key: String): ByteArray {
+            val k = toKey(Base64.getDecoder().decode(key))
+
+            val cipher = Cipher.getInstance(ALGORITHM)
+            cipher.init(Cipher.DECRYPT_MODE, k)
+
+            return cipher.doFinal(data)
+        } // Fun decrypt()
+
+        /**
+         * 加密
+         *
+         * @param data
+         * @param key
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        fun encrypt(data: ByteArray, key: String): ByteArray {
+            val k = toKey(Base64.getDecoder().decode(key))
+            val cipher = Cipher.getInstance(ALGORITHM)
+            cipher.init(Cipher.ENCRYPT_MODE, k)
+
+            return cipher.doFinal(data)
+        } // Fun encrypt()
+
+        /**
+         * 生成密钥
+         *
+         * @param seed
+         * @return
+         * @throws IOException
+         * @throws NoSuchAlgorithmException
+         */
+        @Throws(IOException::class, NoSuchAlgorithmException::class)
+        @JvmOverloads
+        fun initKey(seed: String? = null): String? {
+            val secureRandom = if (seed != null) {
+                SecureRandom(Base64.getDecoder().decode(seed))
+            } else {
+                SecureRandom()
+            }
+
+            val kg = KeyGenerator.getInstance(ALGORITHM)
+            kg.init(secureRandom)
+
+            val secretKey = kg.generateKey()
+
+            return Base64.getEncoder().encodeToString(secretKey.encoded)
+        } // Fun initKey()
+    } // companion object
+} // Class SDESCoderUtil
+

+ 137 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/utils/crypt/SPBECoderUtil.kt

@@ -0,0 +1,137 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.utils.crypt
+
+import javax.crypto.Cipher
+import javax.crypto.SecretKeyFactory
+import javax.crypto.spec.PBEKeySpec
+import javax.crypto.spec.PBEParameterSpec
+import java.security.Key
+
+abstract class SPBECoderUtil : SCoderUtil() {
+    companion object {
+        const val ALGORITHM = "PBEWITHMD5andDES"
+
+        // 初始化盐为20090421
+        private val salt = "20090421".toByteArray()
+
+        /**
+         * 转换密钥<br></br>
+         *
+         * @param password
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        private fun toKey(password: String): Key {
+            val keySpec = PBEKeySpec(password.toCharArray())
+            val keyFactory = SecretKeyFactory.getInstance(ALGORITHM)
+
+            return keyFactory.generateSecret(keySpec)
+        } // Fun toKey()
+
+        /**
+         * 加密
+         *
+         * @param data     数据
+         * @param password 密码
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        fun encrypt(data: ByteArray, password: String): ByteArray {
+
+            val key = toKey(password)
+
+            val paramSpec = PBEParameterSpec(salt, 100)
+            val cipher = Cipher.getInstance(ALGORITHM)
+            cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec)
+
+            return cipher.doFinal(data)
+        } // Fun encrypt()
+
+        /**
+         * 加密
+         *
+         * @param data     数据
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        fun encrypt(data: ByteArray): ByteArray {
+
+            val key = toKey("Sybot-20090421")
+
+            val paramSpec = PBEParameterSpec(salt, 100)
+            val cipher = Cipher.getInstance(ALGORITHM)
+            cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec)
+
+            return cipher.doFinal(data)
+        } // Fun encrypt()
+
+        /**
+         * 解密
+         *
+         * @param data     数据
+         * @param password 密码
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        fun decrypt(data: ByteArray, password: String): ByteArray {
+
+            val key = toKey(password)
+
+            val paramSpec = PBEParameterSpec(salt, 100)
+            val cipher = Cipher.getInstance(ALGORITHM)
+            cipher.init(Cipher.DECRYPT_MODE, key, paramSpec)
+
+            return cipher.doFinal(data)
+
+        } // Fun decrypt()
+
+        /**
+         * 解密
+         *
+         * @param data     数据
+         * @return
+         * @throws Exception
+         */
+        @Throws(Exception::class)
+        fun decrypt(data: ByteArray): ByteArray {
+
+            val key = toKey("Sybot-20090421")
+
+            val paramSpec = PBEParameterSpec(salt, 100)
+            val cipher = Cipher.getInstance(ALGORITHM)
+            cipher.init(Cipher.DECRYPT_MODE, key, paramSpec)
+
+            return cipher.doFinal(data)
+
+        } // Fun decrypt()
+    }
+} // Class PBECoder

+ 301 - 0
persagy-service-base/src/main/kotlin/com/persagy/service/utils/crypt/SRSACoderUtil.kt

@@ -0,0 +1,301 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.utils.crypt
+
+import javax.crypto.BadPaddingException
+import javax.crypto.Cipher
+import javax.crypto.IllegalBlockSizeException
+import javax.crypto.NoSuchPaddingException
+import java.io.IOException
+import java.security.*
+import java.security.interfaces.RSAPrivateKey
+import java.security.interfaces.RSAPublicKey
+import java.security.spec.InvalidKeySpecException
+import java.security.spec.PKCS8EncodedKeySpec
+import java.security.spec.X509EncodedKeySpec
+import java.util.*
+
+/**
+ * RSA安全编码组件
+ *
+ * @author Andy
+ */
+abstract class SRSACoderUtil : SCoderUtil() {
+    companion object {
+        const val KEY_ALGORITHM = "RSA"
+        const val SIGNATURE_ALGORITHM = "MD5withRSA"
+
+        private const val PUBLIC_KEY = "RSAPublicKey"
+        private const val PRIVATE_KEY = "RSAPrivateKey"
+
+        /**
+         * 用私钥对信息生成数字签名
+         *
+         * @param data       加密数据
+         * @param privateKey 私钥
+         * @return
+         * @throws IOException
+         * @throws NoSuchAlgorithmException
+         * @throws InvalidKeySpecException
+         * @throws InvalidKeyException
+         * @throws SignatureException
+         */
+        @Throws(IOException::class, NoSuchAlgorithmException::class, InvalidKeySpecException::class, InvalidKeyException::class, SignatureException::class)
+        fun sign(data: ByteArray, privateKey: String): String? {
+            // 解密由base64编码的私钥
+            val keyBytes = Base64.getDecoder().decode(privateKey)
+
+            // 构造PKCS8EncodedKeySpec对象
+            val pkcs8KeySpec = PKCS8EncodedKeySpec(keyBytes!!)
+
+            // KEY_ALGORITHM 指定的加密算法
+            val keyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
+
+            // 取私钥匙对象
+            val priKey = keyFactory.generatePrivate(pkcs8KeySpec)
+
+            // 用私钥对信息生成数字签名
+            val signature = Signature.getInstance(SIGNATURE_ALGORITHM)
+            signature.initSign(priKey)
+            signature.update(data)
+
+            return Base64.getEncoder().encodeToString(signature.sign())
+        } // Fun sign()
+
+        /**
+         * 校验数字签名
+         *
+         * @param data      加密数据
+         * @param publicKey 公钥
+         * @param sign      数字签名
+         * @return 校验成功返回true 失败返回false
+         * @throws IOException
+         * @throws NoSuchAlgorithmException
+         * @throws InvalidKeySpecException
+         * @throws InvalidKeyException
+         * @throws SignatureException
+         */
+        @Throws(IOException::class, NoSuchAlgorithmException::class, InvalidKeySpecException::class, InvalidKeyException::class, SignatureException::class)
+        fun verify(data: ByteArray, publicKey: String, sign: String): Boolean {
+
+            // 解密由base64编码的公钥
+            val keyBytes = Base64.getDecoder().decode(publicKey)
+
+            // 构造X509EncodedKeySpec对象
+            val keySpec = X509EncodedKeySpec(keyBytes!!)
+
+            // KEY_ALGORITHM 指定的加密算法
+            val keyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
+
+            // 取公钥匙对象
+            val pubKey = keyFactory.generatePublic(keySpec)
+
+            val signature = Signature.getInstance(SIGNATURE_ALGORITHM)
+            signature.initVerify(pubKey)
+            signature.update(data)
+
+            // 验证签名是否正常
+            return signature.verify(Base64.getDecoder().decode(sign))
+        } // Fun verify()
+
+        /**
+         * 解密<br></br>
+         * 用私钥解密
+         *
+         * @param data
+         * @param key
+         * @return
+         * @throws IOException
+         * @throws NoSuchAlgorithmException
+         * @throws InvalidKeySpecException
+         * @throws NoSuchPaddingException
+         * @throws InvalidKeyException
+         * @throws BadPaddingException
+         * @throws IllegalBlockSizeException
+         */
+        @Throws(IOException::class, NoSuchAlgorithmException::class, InvalidKeySpecException::class, NoSuchPaddingException::class, InvalidKeyException::class, IllegalBlockSizeException::class, BadPaddingException::class)
+        fun decryptByPrivateKey(data: ByteArray, key: String): ByteArray {
+            // 对密钥解密
+            val keyBytes = Base64.getDecoder().decode(key)
+
+            // 取得私钥
+            val pkcs8KeySpec = PKCS8EncodedKeySpec(keyBytes!!)
+            val keyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
+            val privateKey = keyFactory.generatePrivate(pkcs8KeySpec)
+
+            // 对数据解密
+            val cipher = Cipher.getInstance(keyFactory.algorithm)
+            cipher.init(Cipher.DECRYPT_MODE, privateKey)
+
+            return cipher.doFinal(data)
+        } // Fun decryptByPrivateKey()
+
+        /**
+         * 解密<br></br>
+         * 用公钥解密
+         *
+         * @param data
+         * @param key
+         * @return
+         * @throws IOException
+         * @throws NoSuchAlgorithmException
+         * @throws InvalidKeySpecException
+         * @throws NoSuchPaddingException
+         * @throws InvalidKeyException
+         * @throws BadPaddingException
+         * @throws IllegalBlockSizeException
+         */
+        @Throws(IOException::class, NoSuchAlgorithmException::class, InvalidKeySpecException::class, NoSuchPaddingException::class, InvalidKeyException::class, IllegalBlockSizeException::class, BadPaddingException::class)
+        fun decryptByPublicKey(data: ByteArray, key: String): ByteArray {
+            // 对密钥解密
+            val keyBytes = Base64.getDecoder().decode(key)
+
+            // 取得公钥
+            val x509KeySpec = X509EncodedKeySpec(keyBytes!!)
+            val keyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
+            val publicKey = keyFactory.generatePublic(x509KeySpec)
+
+            // 对数据解密
+            val cipher = Cipher.getInstance(keyFactory.algorithm)
+            cipher.init(Cipher.DECRYPT_MODE, publicKey)
+
+            return cipher.doFinal(data)
+        } // Fun decryptByPublicKey()
+
+        /**
+         * 用公钥加密
+         *
+         * @param data
+         * @param key
+         * @return
+         * @throws IOException
+         * @throws NoSuchAlgorithmException
+         * @throws InvalidKeySpecException
+         * @throws NoSuchPaddingException
+         * @throws InvalidKeyException
+         * @throws BadPaddingException
+         * @throws IllegalBlockSizeException
+         */
+        @Throws(IOException::class, NoSuchAlgorithmException::class, InvalidKeySpecException::class, NoSuchPaddingException::class, InvalidKeyException::class, IllegalBlockSizeException::class, BadPaddingException::class)
+        fun encryptByPublicKey(data: ByteArray, key: String): ByteArray {
+            // 对公钥解密
+            val keyBytes = Base64.getDecoder().decode(key)
+
+            // 取得公钥
+            val x509KeySpec = X509EncodedKeySpec(keyBytes!!)
+            val keyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
+            val publicKey = keyFactory.generatePublic(x509KeySpec)
+
+            // 对数据加密
+            val cipher = Cipher.getInstance(keyFactory.algorithm)
+            cipher.init(Cipher.ENCRYPT_MODE, publicKey)
+
+            return cipher.doFinal(data)
+        } // Fun encryptByPublicKey()
+
+        /**
+         * 用私钥加密
+         *
+         * @param data
+         * @param key
+         * @return
+         * @throws IOException
+         * @throws NoSuchAlgorithmException
+         * @throws InvalidKeySpecException
+         * @throws NoSuchPaddingException
+         * @throws InvalidKeyException
+         * @throws BadPaddingException
+         * @throws IllegalBlockSizeException
+         */
+        @Throws(IOException::class, NoSuchAlgorithmException::class, InvalidKeySpecException::class, NoSuchPaddingException::class, InvalidKeyException::class, IllegalBlockSizeException::class, BadPaddingException::class)
+        fun encryptByPrivateKey(data: ByteArray, key: String): ByteArray {
+            // 对密钥解密
+            val keyBytes = Base64.getDecoder().decode(key)
+
+            // 取得私钥
+            val pkcs8KeySpec = PKCS8EncodedKeySpec(keyBytes!!)
+            val keyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
+            val privateKey = keyFactory.generatePrivate(pkcs8KeySpec)
+
+            // 对数据加密
+            val cipher = Cipher.getInstance(keyFactory.algorithm)
+            cipher.init(Cipher.ENCRYPT_MODE, privateKey)
+
+            return cipher.doFinal(data)
+        } // Fun encryptByPrivateKey()
+
+        /**
+         * 取得私钥
+         *
+         * @param keyMap
+         * @return
+         */
+        fun getPrivateKey(keyMap: Map<String, Any>): String? {
+            val key = keyMap[PRIVATE_KEY] as Key
+
+            return Base64.getEncoder().encodeToString(key.encoded)
+        } // Fun getPrivateKey()
+
+        /**
+         * 取得公钥
+         *
+         * @param keyMap
+         * @return
+         */
+        fun getPublicKey(keyMap: Map<String, Any>): String? {
+            val key = keyMap[PUBLIC_KEY] as Key
+
+            return Base64.getEncoder().encodeToString(key.encoded)
+        } // Fun getPublicKey()
+
+        /**
+         * 初始化密钥
+         *
+         * @return
+         * @throws NoSuchAlgorithmException
+         */
+        @Throws(NoSuchAlgorithmException::class)
+        fun initKey(): Map<String, Any> {
+            val keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM)
+            keyPairGen.initialize(1024)
+
+            val keyPair = keyPairGen.generateKeyPair()
+
+            // 公钥
+            val publicKey = keyPair.public as RSAPublicKey
+
+            // 私钥
+            val privateKey = keyPair.private as RSAPrivateKey
+
+            val keyMap = HashMap<String, Any>(2)
+
+            keyMap[PUBLIC_KEY] = publicKey
+            keyMap[PRIVATE_KEY] = privateKey
+            return keyMap
+        } // Fun initKey()
+    }
+} // Class SRSACoderUtil

+ 95 - 0
persagy-service-base/src/main/kotlin/com/persagy/sso/controllers/SSsoUserController.kt

@@ -0,0 +1,95 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.sso.controllers
+
+import com.persagy.base.extensions.toJson
+import com.persagy.service.SObjectService
+import com.persagy.service.models.requests.SCreateRequest
+import com.persagy.service.models.requests.SQueryRequest
+import com.persagy.service.models.requests.SUpdateRequest
+import com.persagy.service.models.responses.SBaseResponse
+import com.persagy.service.models.responses.SCreateResponse
+import com.persagy.service.models.responses.SQueryResponse
+import com.persagy.sso.models.entities.SSsoUser
+import io.swagger.annotations.ApiOperation
+import org.slf4j.LoggerFactory
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+
+/**
+ * 单点登录用户接口
+ *
+ * @author 庞利祥 <sybotan@126.com>
+ */
+open class SSsoUserController<USER: SSsoUser> {
+    companion object {
+        // 日志
+        private val logger = LoggerFactory.getLogger(SSsoUserController::class.java)
+    } // Companion object
+
+    protected lateinit var userService: SObjectService<USER> // = SDataService(SMybatisDao(SSsoUser::class.java))
+
+    /**
+     * 创建用户
+     */
+    @ApiOperation(value = "创建单点登录用户", notes = "")
+    @PostMapping(value = ["/create"])
+    fun create(@RequestBody request: SCreateRequest<USER>): SCreateResponse<USER> {
+        logger.debug("user =${request.toJson()}")
+        return userService.createList(request)
+    } // Fun create()
+
+    /**
+     * 更新用户
+     */
+    @ApiOperation(value = "更新单点登录用户", notes = "")
+    @PostMapping(value = ["/update"])
+    fun updateList(@RequestBody request: SUpdateRequest<USER>): SBaseResponse {
+        return userService.updateList(request)
+    } // Fun update()
+
+    /**
+     * 删除用户
+     */
+    @ApiOperation(value = "删除单点登录用户", notes = "")
+    @PostMapping(value = ["/delete"])
+    fun delete(@RequestBody entityList: ArrayList<USER>): SBaseResponse {
+        return userService.deleteByKeysList(entityList)
+    } // Fun delete()
+
+    /**
+     * 查询单点登录用户
+     *
+     * @param   request     查询单点登录用户
+     * @return  查询结果应答信息
+     */
+    @ApiOperation(value="查询单点登录用户", notes="")
+    @PostMapping(value = ["/query"])
+    fun queryPage(@RequestBody request: SQueryRequest): SQueryResponse<USER> {
+        return userService.pageQuery(request)
+    } // Fun queryPage()
+} // Class SSsoUserController

+ 75 - 0
persagy-service-base/src/main/kotlin/com/persagy/sso/models/entities/SSsoUser.kt

@@ -0,0 +1,75 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.sso.models.entities
+
+import com.persagy.service.models.SBaseEntity
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+import javax.persistence.Column
+import javax.persistence.Id
+import javax.persistence.Table
+
+/**
+ * 单点登录用户实体类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "单点登录用户")
+@Table(name = "user")
+open class SSsoUser: SBaseEntity() {
+    /** 用户id */
+    @ApiModelProperty(value = "用户id")
+    @Id
+    @Column(name = "id", updatable = false)
+    var id: String? = null
+
+    /** 用户名 */
+    @ApiModelProperty(value = "用户名", required = true)
+    @Column(name = "user_name")
+    var userName: String? = null
+
+    /** 密码 */
+    @ApiModelProperty(value = "密码", required = true)
+    //@Column(name = "password", updatable = false)
+    var password: String? = null
+
+    /** 真实姓名 */
+    @ApiModelProperty(value = "真实姓名")
+    var realName: String? = null
+
+    /** 手机号码 */
+    @ApiModelProperty(value = "手机号码,全局唯一")
+    var phone: String? = null
+
+    /** 邮箱 */
+    @ApiModelProperty(value = "邮箱,全局唯一")
+    var mail: String? = null
+
+    /** 身份证号码 */
+    @ApiModelProperty(value = "身份证号码,全局唯一")
+    var certno: String? = null
+} // Class SSsoUser

+ 128 - 0
persagy-service-kafka/build.gradle

@@ -0,0 +1,128 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+group rootProject.group
+version rootProject.version
+
+apply plugin: "kotlin"
+apply plugin: "org.jetbrains.dokka"
+apply plugin: "maven"
+
+sourceCompatibility = JVM_TARGET
+
+compileKotlin {
+    kotlinOptions.jvmTarget = "1.8"
+}
+compileTestKotlin {
+    kotlinOptions.jvmTarget = "1.8"
+}
+
+dependencies {
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // kafka 支持
+    // https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka
+    compile group: "org.springframework.kafka",         name: "spring-kafka",                   version: SPRING_KAFKA_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Sybotan 依赖
+    compile group: 'com.persagy', name: 'persagy-kotlin-base', version: PERSAGY_KOTLIN_VERSION
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 文档生成
+dokka {
+    jdkVersion = JDK_VERSION
+    outputFormat = "javadoc"
+    outputDirectory = "$buildDir/javadoc"
+    //sourceDirs = files("src/main/kotlin")
+}
+
+// 上成上传的文档jar包
+task javadocJar(type: Jar, dependsOn: dokka) {
+    classifier = "javadoc"
+    from dokka.outputDirectory
+}
+
+// 上成上传的文档jar包
+task sourcesJar(type: Jar) {
+    classifier = 'sources'
+    from sourceSets.main.allSource
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// maven 仓库管理
+
+// 设置上传的包,默认上传aar与pom
+artifacts {
+    //archives javadocJar
+    archives sourcesJar
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            pom.groupId = project.group
+            pom.version = project.version
+            pom.artifactId = project.name
+            pom.project {
+                name project.name
+                packaging "jar"
+                url PERSAGY_URL
+                description "斯伯坦机器人 kafka支持库。"
+                licenses {
+                    license {
+                        name LICENSE_NAME
+                        url LICENSE_URL
+                    }
+                }
+                developers {
+                    developer {
+                        id DEVELOPER_ID
+                        name DEVELOPER_NAME
+                    }
+                }
+            }
+            snapshotRepository(url: MAVEN_REPO_SNAPSHOT_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+            repository(url: MAVEN_REPO_RELEASE_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+        }
+    }
+}
+buildscript {
+    ext.kotlin_version = '1.3.61'
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+repositories {
+    mavenCentral()
+}

+ 34 - 0
persagy-service-kafka/src/main/kotlin/com/persagy/service/kafka/SBaseNotice.kt

@@ -0,0 +1,34 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.kafka
+
+/**
+ * 通知基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+open class SBaseNotice(val type: String, val ops: String)

+ 192 - 0
persagy-service-kafka/src/main/kotlin/com/persagy/service/kafka/SKafkaService.kt

@@ -0,0 +1,192 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.kafka
+
+import com.persagy.base.extensions.toJson
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.kafka.support.SendResult
+import org.springframework.util.concurrent.ListenableFuture
+import org.springframework.util.concurrent.ListenableFutureCallback
+import java.util.*
+
+/**
+ * Kafka 服务。用于连接 kafka, 收发 kafka 消息
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+open class SKafkaService {
+    /** 延迟发送时间 */
+    var delaySendTime = 3000L
+    /** Kafka 访问模板 */
+    @Suppress("UNCHECKED_CAST")
+    lateinit var kafkaTemplate: KafkaTemplate<Int, String>
+    /** 实现迟延相关数据 */
+    private val delayMap = HashMap<String, SDelayData>()
+
+    /**
+     * 发送通知
+     *
+     * @param   topic       发送标题
+     * @param   notice      通知
+     * @param   delaySend   是否启动延迟发送,默认延迟发送
+     */
+    fun sendNotice(topic: String, notice: SBaseNotice, delaySend: Boolean = true) {
+        val delayData = delayMap[topic] ?: SDelayData()
+        if (!delayMap.containsKey(topic)) {
+            delayMap[topic] = delayData
+        }
+
+        // 合并请求
+        if (!mergeNotice(topic, notice)) {
+            if (delayData.lastNotice != null) {
+                sendTopic(topic, delayData.lastNotice!!.toJson())
+            }
+            delayData.lastNotice = notice
+        }
+
+        // 是否延迟发送
+        if (delaySend) {
+            delaySend(topic)
+        } else {
+            flush(topic)
+        }
+
+        return
+    } // Fun sendNotice()
+
+    /**
+     * 发送 topic 消息
+     *
+     * @param   topic       发送标题
+     * @param   data        消息数据
+     */
+    fun sendTopic(topic: String, data: String) {
+        val future: ListenableFuture<SendResult<Int, String>> = kafkaTemplate.send(topic, data).also {
+            it.addCallback(object : ListenableFutureCallback<SendResult<Int, String>> {
+                /**
+                 * 发送失败回调
+                 */
+                override fun onFailure(ex: Throwable) {
+                } // Fun onFailure
+                /**
+                 * 发送成功回调
+                 */
+                override fun onSuccess(result: SendResult<Int, String>) {
+                } // Fun onSuccess()
+            })
+        }
+
+        kafkaTemplate.flush()
+    } // Fun sendTopic()
+
+    /**
+     * 合并通知
+     *
+     * @param   lastNotice          缓存的通知
+     * @param   notice              新的通知
+     * @return  成功返回true,否则返回false
+     */
+    open fun mergeNotice(lastNotice: SBaseNotice, notice: SBaseNotice): Boolean {
+        return false
+    } // Function mergeNotice()
+
+    /**
+     * 延迟发送缓存的消息
+     *
+     * @param   topic       发送标题
+     */
+    private fun delaySend(topic: String) {
+        val delayData = delayMap[topic] ?: return
+
+        // 如果延迟任务不为空,则取消之前的延迟任务。保证延迟任务在最后一次发送后执行。
+        if (delayData.delayTask != null) {
+            delayData.delayTask!!.cancel()
+        }
+
+        // 创建延迟任务
+        delayData.delayTask = object : TimerTask() {
+            override fun run() {
+                // 发送缓存的任务
+                flush(topic)
+            }
+        }
+
+        // 启动延迟发送任务
+        delayData.delayTimer.schedule(delayData.delayTask, delaySendTime)
+
+        return
+    } // Fun delaySend()
+
+    /**
+     * 发送缓存的消息
+     *
+     * @param   topic       发送标题
+     */
+    fun flush(topic: String) {
+        val delayData = delayMap[topic] ?: return
+        // 如果没有缓存通知
+        if (delayData.lastNotice == null) {
+            return
+        }
+
+        sendTopic(topic, delayData.lastNotice!!.toJson())
+        delayData.lastNotice = null
+
+        return
+    } // Fun flush()
+
+    /**
+     * 合并通知
+     *
+     * @param   topic       发送标题
+     * @param   notice      要合并的通知
+     * @return  成功返回true,否则返回false
+     */
+    private fun mergeNotice(topic: String, notice: SBaseNotice): Boolean {
+        val delayData = delayMap[topic] ?: return false
+
+        // 如果最后发送的通知为空
+        if (delayData.lastNotice == null) {
+            delayData.lastNotice = notice
+            return true
+        }
+
+        return mergeNotice(delayData.lastNotice!!, notice)
+    } // Fun mergeNotice()
+
+    /**
+     * 延迟处理数据
+     */
+    inner class SDelayData {
+        /** 最后发送的通知 */
+        var lastNotice: SBaseNotice? = null
+        /** 延迟发送通知定时器 */
+        val delayTimer = Timer()
+        /** 延迟发送通知定时任务 */
+        var delayTask: TimerTask? = null
+    } // Class SDelayData
+} // Class SKafkaService

+ 129 - 0
persagy-service-models/build.gradle

@@ -0,0 +1,129 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+group rootProject.group
+version rootProject.version
+
+apply plugin: "kotlin"
+apply plugin: "org.jetbrains.dokka"
+apply plugin: "maven"
+
+sourceCompatibility = JVM_TARGET
+
+compileKotlin {
+    kotlinOptions.jvmTarget = JVM_TARGET
+}
+compileTestKotlin {
+    kotlinOptions.jvmTarget = JVM_TARGET
+}
+
+dependencies {
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // kotlin 依赖
+    compile group: "org.jetbrains.kotlin", name: "kotlin-stdlib-jdk8", version: KOTLIN_VERSION
+    compile group: "org.jetbrains.kotlin", name: "kotlin-reflect", version: KOTLIN_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // spring boot 依赖
+    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: SPRING_BOOT_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//    compile group: "javax.persistence", name: "persistence-api", version: PERSISTENCE_VERSION
+
+    // https://mvnrepository.com/artifact/io.swagger/swagger-annotations
+    compile group: 'io.swagger', name: 'swagger-annotations', version: '1.5.22'
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Sybotan依赖
+    compile group: "com.persagy", name: "persagy-kotlin-base", version: PERSAGY_KOTLIN_VERSION
+    compile group: "com.persagy", name: "persagy-kotlin-database", version: PERSAGY_KOTLIN_VERSION
+
+    // compile project(':sybotan-service-mybatis')
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 文档生成
+dokka {
+    jdkVersion = JDK_VERSION
+    outputFormat = "javadoc"
+    outputDirectory = "$buildDir/javadoc"
+    //sourceDirs = files("src/main/kotlin")
+}
+
+// 上成上传的文档jar包
+task javadocJar(type: Jar, dependsOn: dokka) {
+    classifier = "javadoc"
+    from dokka.outputDirectory
+}
+
+// 上成上传的文档jar包
+task sourcesJar(type: Jar) {
+    classifier = 'sources'
+    from sourceSets.main.allSource
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// maven 仓库管理
+
+// 设置上传的包,默认上传aar与pom
+artifacts {
+    //archives javadocJar
+    archives sourcesJar
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            pom.groupId = project.group
+            pom.version = project.version
+            pom.artifactId = project.name
+            pom.project {
+                name project.name
+                packaging "jar"
+                url PERSAGY_URL
+                description "斯伯坦机器人 mybatis支持库。"
+                licenses {
+                    license {
+                        name LICENSE_NAME
+                        url LICENSE_URL
+                    }
+                }
+                developers {
+                    developer {
+                        id DEVELOPER_ID
+                        name DEVELOPER_NAME
+                    }
+                }
+            }
+            snapshotRepository(url: MAVEN_REPO_SNAPSHOT_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+            repository(url: MAVEN_REPO_RELEASE_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+        }
+    }
+}

+ 53 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/SBaseEntity.kt

@@ -0,0 +1,53 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models
+
+import io.swagger.annotations.ApiModelProperty
+import java.util.*
+import javax.persistence.Column
+import kotlin.collections.HashMap
+
+/**
+ * 实体类基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+open class SBaseEntity {
+    /** 统计信息 */
+    @ApiModelProperty(value = "统计信息")
+    var statistics: HashMap<String, Any?>? = null
+
+    /** 最后更新日期 */
+    @ApiModelProperty(value = "最后更新日期", example = "2009-04-21 13:16:00")
+    @Column(name = "last_update", updatable = false, insertable = false)
+    var lastUpdate: Date? = null
+
+    /** 创建时间 */
+    @ApiModelProperty(value = "创建时间", example = "2017-10-15 08:30:00")
+    @Column(name = "create_time", updatable = false, insertable = false)
+    var createTime: Date? = null
+} // Class SBaseEntity

+ 65 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/SCascadeQuery.kt

@@ -0,0 +1,65 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models
+
+import com.persagy.database.SGroup
+import io.swagger.annotations.ApiModelProperty
+
+/**
+ * 级联查询对象
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+class SCascadeQuery {
+    /** 级联对象属性名 */
+    @ApiModelProperty(value = "级联对象属性名", required = true)
+    var name: String? = null
+
+    /** 查询条件 */
+    @ApiModelProperty(value = "查询条件")
+    var filters: String? = null
+
+    /** 排序条件 */
+    @ApiModelProperty(value = "排序条件", example = "name asc, createTime desc")
+    var orders: String? = null
+
+    /** 字段影射,空为查询所有字段 */
+    @ApiModelProperty(value = "字段影射,空为更新所有字段")
+    var projection: ArrayList<String>? = null
+
+    /** 去除重复记录 */
+    @ApiModelProperty(value = "去除重复记录")
+    var distinct: Boolean? = null
+
+    /** 级联对象列表 */
+    @ApiModelProperty(value = "级联对象列表")
+    var cascade: ArrayList<SCascadeQuery>? = null
+
+    /** 分组统计 */
+    @ApiModelProperty(value = "分组")
+    var group: SGroup? = null
+} // Class SCascadeQuery

+ 49 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/SCascadeUpdate.kt

@@ -0,0 +1,49 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models
+
+import com.persagy.service.models.enums.SCascadeUpdateMode
+import io.swagger.annotations.ApiModelProperty
+
+/**
+ * 级联更新对象
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+class SCascadeUpdate {
+    /** 级联对象属性名 */
+    @ApiModelProperty(value = "级联对象属性名", required = true)
+    var name: String? = null
+
+    /** 字段影射,空为查询所有字段 */
+    @ApiModelProperty(value = "字段影射,空为更新所有字段")
+    var projection: ArrayList<String>? = null
+
+    /** 更新模式 */
+    @ApiModelProperty(value = "更新模式")
+    var mode: SCascadeUpdateMode = SCascadeUpdateMode.Override
+} // Class SCascadeQuery

+ 44 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/SJoinQuery.kt

@@ -0,0 +1,44 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models
+
+import io.swagger.annotations.ApiModelProperty
+
+/**
+ * Join查询对象
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+class SJoinQuery {
+    /** 级联对象属性名 */
+    @ApiModelProperty(value = "Join对象属性名", required = true)
+    var name: String? = null
+
+    /** 字段影射,空为查询所有字段 */
+    @ApiModelProperty(value = "字段影射,空为查询所有字段")
+    var projection = ArrayList<String>()
+} // Class SJoinQuery

+ 50 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/annotations/SCascade.kt

@@ -0,0 +1,50 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.annotations
+
+import java.lang.annotation.Inherited
+
+/**
+ * 数据库访问框架级联注解
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@Target(AnnotationTarget.FIELD)
+@MustBeDocumented
+@Inherited
+annotation class SCascade (
+        /** 关系表 */
+        val table: String,
+        /** 主对象ID名称 */
+        val idColumn: String,
+        /** 子对象ID名称 */
+        val childIdColumn: String,
+        /** 关联自身(比如楼层连通关系,两个ID都是楼层) */
+        val self: Boolean = false,
+        /** 过滤条件 */
+        val filter: String = ""
+) // Annotation SCascade

+ 53 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/annotations/SCascadeCount.kt

@@ -0,0 +1,53 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.annotations
+
+import java.lang.annotation.Inherited
+import kotlin.reflect.KClass
+
+/**
+ * 数据库访问框架级联注解
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@Target(AnnotationTarget.FIELD)
+@MustBeDocumented
+@Inherited
+annotation class SCascadeCount (
+        /** 子对象类型 */
+        val childClass: KClass<*>,
+        /** 关系表 */
+        val table: String,
+        /** 主对象ID名称 */
+        val idColumn: String,
+        /** 子对象ID名称 */
+        val childIdColumn: String,
+        /** 关联自身(比如楼层连通关系,两个ID都是楼层) */
+        val self: Boolean = false,
+        /** 过滤条件 */
+        val filter: String = ""
+) // Annotation SCascade

+ 39 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/enums/SCascadeUpdateMode.kt

@@ -0,0 +1,39 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.enums
+
+/**
+ * 更新模式
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+enum class SCascadeUpdateMode {
+    /** 将原来的级联数据删除,更新新的级联数据 */
+    Override,
+    /** 不删除原来的级联数据,将新的级联数据合并 */
+    Merge
+} // Enum SCascadeUpdateMode

+ 30 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/enums/SRequestType.kt

@@ -0,0 +1,30 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.enums
+
+enum class SRequestType {
+}

+ 39 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/enums/SResponseType.kt

@@ -0,0 +1,39 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.enums
+
+/**
+ * 应答结果类型基类
+ *
+ * @author 庞利祥 <sybotan@126.com>
+ */
+enum class SResponseType {
+    success,            // 成功
+    failure,            // 失败
+    exception,          // 异常
+    notFound            // 未找到
+} // Enum Class SResponseType

+ 42 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/requests/SCountRequest.kt

@@ -0,0 +1,42 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.requests
+
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+
+/**
+ * 统计请求对象基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "统计请求对象基类")
+open class SCountRequest {
+    /** 查询条件 */
+    @ApiModelProperty(value = "查询条件")
+    var filters: String? = null
+} // Class SCountRequest

+ 53 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/requests/SCreateRequest.kt

@@ -0,0 +1,53 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.requests
+
+import com.persagy.service.models.SCascadeUpdate
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+import javax.validation.Valid
+
+/**
+ * 创建请求对象基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "创建请求对象基类")
+open class SCreateRequest<ENTITY: Any> {
+    /** 如果对象在库中已存在,是否执行合并操作 */
+    @ApiModelProperty(value = "如果对象在库中已存在,是否执行合并操作")
+    var isMerge = false
+
+    /** 更新内容 */
+    @ApiModelProperty(value = "更新内容", required = true)
+    @Valid
+    lateinit var content: ArrayList<ENTITY>
+
+    /** 级联对象列表 */
+    @ApiModelProperty(value = "级联对象列表")
+    var cascade: ArrayList<SCascadeUpdate>? = null
+} // Class SCreateRequest

+ 42 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/requests/SDeleteRequest.kt

@@ -0,0 +1,42 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.requests
+
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+
+/**
+ * 删除请求对象基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "删除请求对象基类")
+open class SDeleteRequest<KEY: Any> {
+    /** 删除内容 */
+    @ApiModelProperty(value = "删除内容", required = true)
+    lateinit var keyList: ArrayList<KEY>
+}  // Class SDeleteRequest

+ 94 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/requests/SQueryRequest.kt

@@ -0,0 +1,94 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.requests
+
+import com.persagy.database.SGroup
+import com.persagy.service.models.SCascadeQuery
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+
+/**
+ * 查询请求对象基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "查询请求对象基类")
+open class SQueryRequest {
+    companion object {
+        /** 设置接口一次可取得的最大记录数 */
+        var maxRow = 1000
+    } // Companion object
+
+    /** 第几页 */
+    @ApiModelProperty(value = "第几页(从1开始)", example = "1")
+    var pageNumber: Int = 1
+        set(value) {            // 限定pageNumber不能小于1
+            field = value
+            if (value < 1) {
+                field = 1
+            }
+        }
+
+    /** 每页记录数 */
+    @ApiModelProperty(value = "每页记录数(0 < 记录数 < 1000)", example = "50")
+    var pageSize: Int = 50
+        set(value) {            // 限定pageSize不能小于1且不能大于maxRow
+            field = value
+            if (value < 1 ) {
+                field = 1
+            }
+            if (value > maxRow) {
+                field = maxRow
+            }
+            // maxRow一次设置有效,之后将自动设备为1000
+            maxRow = 1000
+        }
+
+    /** 查询条件 */
+    @ApiModelProperty(value = "查询条件")
+    var filters: String? = null
+
+    /** 排序条件 */
+    @ApiModelProperty(value = "排序条件", example = "name asc, createTime desc")
+    var orders: String? = null
+
+    /** 去除重复记录 */
+    @ApiModelProperty(value = "去除重复记录")
+    var distinct: Boolean? = null
+
+    /** 字段影射,空为查询所有字段 */
+    @ApiModelProperty(value = "字段影射,空为查询所有字段")
+    var projection: ArrayList<String>? = null
+
+    /** 级联对象列表 */
+    @ApiModelProperty(value = "级联对象列表")
+    var cascade: ArrayList<SCascadeQuery>? = null
+
+    /** 分组统计 */
+    @ApiModelProperty(value = "分组")
+    var group: SGroup? = null
+} // Class SQueryRequest

+ 53 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/requests/SUpdateRequest.kt

@@ -0,0 +1,53 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.requests
+
+import com.persagy.service.models.SCascadeUpdate
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+import javax.validation.Valid
+
+/**
+ * 更新请求对象基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "更新请求对象基类")
+open class SUpdateRequest<ENTITY: Any> {
+    /** 级联对象列表 */
+    @ApiModelProperty(value = "级联对象列表")
+    var cascade: ArrayList<SCascadeUpdate>? = null
+
+    /** 更新内容 */
+    @ApiModelProperty(value = "更新内容", required = true)
+    @Valid
+    var content: ArrayList<ENTITY>? = null
+
+    /** 字段影射,空为查询所有字段 */
+    @ApiModelProperty(value = "字段影射,空为更新所有字段")
+    var projection: ArrayList<String>? = null
+} // Class SUpdateRequest

+ 52 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/responses/SBaseResponse.kt

@@ -0,0 +1,52 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.responses
+
+import com.persagy.service.models.enums.SResponseType
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+
+/**
+ * 应答对象基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "应答对象基类")
+open class SBaseResponse(result: SResponseType = SResponseType.failure, message: String = "") {
+    /** 结果 */
+    @ApiModelProperty(value = "结果")
+    var result: SResponseType = SResponseType.failure
+    /** 消息 */
+    @ApiModelProperty(value = "消息")
+    var message: String = ""
+
+    /** 初始化 */
+    init {
+        this.result = result
+        this.message = message
+    } // init()
+} // Class SBaseResponse

+ 44 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/responses/SCountResponse.kt

@@ -0,0 +1,44 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.responses
+
+import com.persagy.service.models.enums.SResponseType
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+
+/**
+ * 统计请求应答对象基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "统计请求应答对象基类")
+open class SCountResponse(
+        /** 符合条件的记录数 */
+        @ApiModelProperty(value = "符合条件的记录数")
+        val count: Long = 0,
+        result: SResponseType = SResponseType.success, message: String = ""
+) : SBaseResponse(result, message)  // Class SCountResponse

+ 55 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/responses/SCreateResponse.kt

@@ -0,0 +1,55 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.responses
+
+import com.persagy.service.models.enums.SResponseType
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+
+/**
+ * 创建对象请求应答对象基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "查询请求应答对象基类")
+open class SCreateResponse<ENTITY: Any>(result: SResponseType = SResponseType.failure, message: String = "")
+    : SBaseResponse(result, message) {
+
+    /** 对象列表 */
+    @ApiModelProperty(value = "创建成功的对象列表")
+    var entityList: ArrayList<ENTITY>? = null
+
+    /**
+     * 子构造函数
+     *
+     * @param   entity      实体对象
+     */
+    constructor(entity: ArrayList<ENTITY>) : this(SResponseType.success, "") {
+        this.entityList = entity
+    } // Fun constructor()
+
+} // Class SCreateResponse

+ 41 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/responses/SDeleteResponse.kt

@@ -0,0 +1,41 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.responses
+
+import com.persagy.service.models.enums.SResponseType
+import io.swagger.annotations.ApiModel
+
+/**
+ * 统计请求应答对象基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "删除请求应答对象基类")
+open class SDeleteResponse(
+        result: SResponseType = SResponseType.failure,
+        message: String = ""
+) : SBaseResponse(result, message) // Class SDeleteResponse

+ 69 - 0
persagy-service-models/src/main/kotlin/com/persagy/service/models/responses/SQueryResponse.kt

@@ -0,0 +1,69 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.models.responses
+
+import com.persagy.database.SPageQueryInfo
+import com.persagy.service.models.enums.SResponseType
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+
+/**
+ * 查询请求应答对象基类
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "查询请求应答对象基类")
+open class SQueryResponse<ENTITY: Any>(result: SResponseType = SResponseType.success, message: String = "")
+    : SBaseResponse(result, message) {
+    /** 符合条件的总记录数 */
+    @ApiModelProperty(value = "符合条件的总记录数")
+    var total: Long? = null
+
+    /** 第几页 */
+    @ApiModelProperty(value = "第几页")
+    var pageNumber: Int? = null
+
+    /** 每页记录数 */
+    @ApiModelProperty(value = "每页记录数")
+    var pageSize: Int? = null
+
+    /** 查询到的数据列表 */
+    @ApiModelProperty(value = "查询到的数据列表")
+    var content: List<ENTITY>? = null
+
+    /**
+     * 子构造函数
+     *
+     * @param   info    分页查询信息
+     */
+    constructor(info: SPageQueryInfo<ENTITY>): this(SResponseType.success, "") {
+        this.total = info.total
+        this.pageNumber = info.pageNumber
+        this.pageSize = info.pageSize
+        this.content = info.content
+    } // Fun constructor()
+} // Class SQueryResponse

+ 124 - 0
persagy-service-mybatis/build.gradle

@@ -0,0 +1,124 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+group rootProject.group
+version rootProject.version
+
+apply plugin: 'kotlin'
+apply plugin: 'org.jetbrains.dokka'
+apply plugin: 'maven'
+
+sourceCompatibility = JVM_TARGET
+
+compileKotlin {
+    kotlinOptions.jvmTarget = JVM_TARGET
+}
+compileTestKotlin {
+    kotlinOptions.jvmTarget = JVM_TARGET
+}
+
+dependencies {
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // alibaba数据库连接池
+    // https://mvnrepository.com/artifact/com.alibaba/druid
+    //compile group: 'com.alibaba', name: 'druid', version: DRUID_VERSION
+    // https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter
+    compile group: 'com.alibaba', name: 'druid-spring-boot-starter', version: SPRING_DRUID_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // MyBatis支持
+    // https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter
+    compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: SPRING_MYBATIS_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Sybotan依赖
+    compile group: 'com.persagy', name: 'persagy-kotlin-mybatis', version: PERSAGY_KOTLIN_VERSION
+    compile project(':persagy-service-base')
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 文档生成
+dokka {
+    jdkVersion = JDK_VERSION
+    outputFormat = 'javadoc'
+    outputDirectory = "$buildDir/javadoc"
+    //sourceDirs = files('src/main/kotlin')
+}
+
+// 上成上传的文档jar包
+task javadocJar(type: Jar, dependsOn: dokka) {
+    classifier = 'javadoc'
+    from dokka.outputDirectory
+}
+
+// 上成上传的文档jar包
+task sourcesJar(type: Jar) {
+    classifier = 'sources'
+    from sourceSets.main.allSource
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// maven 仓库管理
+
+// 设置上传的包,默认上传aar与pom
+artifacts {
+    //archives javadocJar
+    archives sourcesJar
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            pom.groupId = project.group
+            pom.version = project.version
+            pom.artifactId = project.name
+            pom.project {
+                name project.name
+                packaging 'jar'
+                url PERSAGY_URL
+                description '斯伯坦机器人 mybatis支持库。'
+                licenses {
+                    license {
+                        name LICENSE_NAME
+                        url LICENSE_URL
+                    }
+                }
+                developers {
+                    developer {
+                        id DEVELOPER_ID
+                        name DEVELOPER_NAME
+                    }
+                }
+            }
+            snapshotRepository(url: MAVEN_REPO_SNAPSHOT_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+            repository(url: MAVEN_REPO_RELEASE_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+        }
+    }
+}

+ 48 - 0
persagy-service-mybatis/src/main/kotlin/com/persagy/mybatis/SDaoFactory.kt

@@ -0,0 +1,48 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.mybatis
+
+import com.persagy.database.SAbstractDao
+import com.persagy.database.SAbstractDaoFactory
+
+/**
+ * Dao工厂
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+class SDaoFactory<ENTITY: Any> : SAbstractDaoFactory<ENTITY>() {
+    /**
+     * 创建DAO对象
+     *
+     * @param   entityClazz     实体类
+     * @return  实体类的数据访问对象
+     */
+    override fun createDao(entityClazz: Class<ENTITY>): SAbstractDao<ENTITY> {
+        return SMybatisDao(entityClazz)
+    } // Fun createDao()
+
+} // Class SDaoFactory

+ 65 - 0
persagy-service-mybatis/src/main/kotlin/com/persagy/mybatis/SMybatisDao.kt

@@ -0,0 +1,65 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.mybatis
+
+import com.alibaba.druid.pool.DruidDataSource
+import com.persagy.service.utils.SSpringContextUtil
+import org.mybatis.spring.SqlSessionTemplate
+import javax.sql.DataSource
+
+/**
+ * Mybatis基础Dao操作
+ *
+ * @author 庞利祥 <sybotan@126.com>
+ */
+open class SMybatisDao<ENTITY: Any>(entityClazz: Class<ENTITY>, val mapperClass: Class<*>? = null) : SAbstractMybatisDao<ENTITY>(entityClazz) {
+    companion object {
+        /** sql会话 */
+        val sqlSession: SqlSessionTemplate by lazy {
+            val dataSource = SSpringContextUtil.getBean(DataSource::class.java) as DruidDataSource
+            SSqlProvider.driver = when (dataSource.driverClassName) {
+                "org.postgresql.Driver" ->
+                    Class.forName("com.persagy.postgresql.SPostgreSqlDriver").newInstance() as SAbstractDriver
+                "com.mysql.cj.jdbc.Driver" ->
+                    Class.forName("com.persagy.mysql.SMySqlDriver").newInstance() as SAbstractDriver
+                "org.mariadb.jdbc.Driver" ->
+                    Class.forName("com.persagy.mysql.SMySqlDriver").newInstance() as SAbstractDriver
+                else ->
+                    Class.forName("com.persagy.postgresql.SPostgreSqlDriver").newInstance() as SAbstractDriver
+            }
+            SSpringContextUtil.getBean(SqlSessionTemplate::class.java) as SqlSessionTemplate
+        }
+    }
+
+    /** sql映射对象*/
+    override  val mapper: IBaseMapper<ENTITY> by lazy {
+        val clz = mapperClass ?: IMybatisMapper::class.java
+        @Suppress("UNCHECKED_CAST")
+        val map = sqlSession.getMapper(clz as Class<IBaseMapper<ENTITY>>)
+        map
+    }
+} // Class SMybatisDao

+ 126 - 0
persagy-service-mysql/build.gradle

@@ -0,0 +1,126 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+group rootProject.group
+version rootProject.version
+
+apply plugin: 'kotlin'
+apply plugin: 'org.jetbrains.dokka'
+apply plugin: 'maven'
+
+sourceCompatibility = JVM_TARGET
+
+compileKotlin {
+    kotlinOptions.jvmTarget = JVM_TARGET
+}
+compileTestKotlin {
+    kotlinOptions.jvmTarget = JVM_TARGET
+}
+
+dependencies {
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // alibaba数据库连接池
+    // https://mvnrepository.com/artifact/com.alibaba/druid
+    //compile group: 'com.alibaba', name: 'druid', version: DRUID_VERSION
+    // https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter
+    compile group: 'com.alibaba', name: 'druid-spring-boot-starter', version: SPRING_DRUID_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // MyBatis支持
+    // https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter
+    compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: SPRING_MYBATIS_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Sybotan依赖
+    compile group: 'com.persagy', name: 'persagy-kotlin-mybatis', version: PERSAGY_KOTLIN_VERSION
+    compile group: 'com.persagy', name: 'persagy-kotlin-postgresql', version: PERSAGY_KOTLIN_VERSION
+    compile project(':persagy-service-base')
+    compile project(':persagy-service-mybatis')
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 文档生成
+dokka {
+    jdkVersion = JDK_VERSION
+    outputFormat = 'javadoc'
+    outputDirectory = "$buildDir/javadoc"
+    //sourceDirs = files('src/main/kotlin')
+}
+
+// 上成上传的文档jar包
+task javadocJar(type: Jar, dependsOn: dokka) {
+    classifier = 'javadoc'
+    from dokka.outputDirectory
+}
+
+// 上成上传的文档jar包
+task sourcesJar(type: Jar) {
+    classifier = 'sources'
+    from sourceSets.main.allSource
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// maven 仓库管理
+
+// 设置上传的包,默认上传aar与pom
+artifacts {
+    //archives javadocJar
+    archives sourcesJar
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            pom.groupId = project.group
+            pom.version = project.version
+            pom.artifactId = project.name
+            pom.project {
+                name project.name
+                packaging 'jar'
+                url PERSAGY_URL
+                description '斯伯坦机器人 mybatis支持库。'
+                licenses {
+                    license {
+                        name LICENSE_NAME
+                        url LICENSE_URL
+                    }
+                }
+                developers {
+                    developer {
+                        id DEVELOPER_ID
+                        name DEVELOPER_NAME
+                    }
+                }
+            }
+            snapshotRepository(url: MAVEN_REPO_SNAPSHOT_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+            repository(url: MAVEN_REPO_RELEASE_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+        }
+    }
+}

+ 88 - 0
persagy-service-mysql/src/main/kotlin/com/persagy/service/postgresql/controllers/SDatabaseDocController.kt

@@ -0,0 +1,88 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.postgresql.controllers
+
+import com.persagy.service.SObjectService
+import com.persagy.service.models.requests.SQueryRequest
+import com.persagy.service.models.responses.SQueryResponse
+import com.persagy.service.postgresql.models.SColumn
+import com.persagy.service.postgresql.models.STable
+import io.swagger.annotations.Api
+import io.swagger.annotations.ApiImplicitParam
+import io.swagger.annotations.ApiOperation
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.web.bind.annotation.*
+
+/**
+ * 数据库设计接口
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+@Api(tags= ["数据库设计接口"])
+@RestController
+@RequestMapping("/--database-doc--/")
+class SDatabaseDocController {
+    /** 表服务对象 */
+    private val tableService = SObjectService(STable::class.java)
+
+    /** 字段服务对象 */
+    private val columnService = SObjectService(SColumn::class.java)
+
+    /**
+     * 查询表信息
+     *
+     * @param   schema      数据库
+     * @return  字段列表
+     */
+    @ApiOperation(value="查询表信息", notes="")
+    @RequestMapping(value = ["/table-list"], method = [RequestMethod.GET, RequestMethod.POST])
+    fun tableList(schema: String): List<Any> {
+        return tableService.select("schemaName" to schema).exec()
+    } // Function tableList()
+
+    /**
+     * 查询字段信息
+     *
+     * @param   schema      数据库
+     * @param   table       表名
+     * @return  字段列表
+     */
+    @ApiOperation(value="查询字段信息", notes="")
+    @RequestMapping(value = ["/column-list"], method = [RequestMethod.GET, RequestMethod.POST])
+    fun columnList(schema: String, table: String): List<Any> {
+        val builder = columnService.select("schemaName" to schema, "tableName" to table)
+//        builder.tableName =
+//                "(SELECT *,\n" +
+//                "    (SELECT \"description\" FROM \"pg_description\"\n" +
+//                "    JOIN \"pg_class\" ON \"pg_description\".\"objoid\" = \"pg_class\".\"oid\"\n" +
+//                "    JOIN \"pg_namespace\" ON \"pg_class\".\"relnamespace\" = \"pg_namespace\".\"oid\"\n" +
+//                "WHERE \"relname\" = \"t\".\"table_name\" and \"nspname\"= \"t\".\"table_schema\" and \"objsubid\" = \"t\".\"ordinal_position\")\n" +
+//                "FROM \"information_schema\".\"columns\" t) ct"
+        return builder.exec()
+        //return mapper.columnList(schema, table)
+    } // Function columnList()
+} // Class SDatabaseDocController

+ 82 - 0
persagy-service-mysql/src/main/kotlin/com/persagy/service/postgresql/models/SColumn.kt

@@ -0,0 +1,82 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.postgresql.models
+
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+import javax.persistence.Column
+import javax.persistence.Table
+
+/**
+ * 数据库设计-表结构
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "数据库设计-表结构")
+@Table(name = "information_schema.columns")
+open class SColumn {
+    /** 模式 */
+    @ApiModelProperty(value = "模式")
+    @Column(name = "TABLE_SCHEMA")
+    var schemaName: String? = null
+
+    /** 表名 */
+    @ApiModelProperty(value = "表名")
+    @Column(name = "TABLE_NAME")
+    var tableName: String? = null
+
+    /** 字段名 */
+    @ApiModelProperty(value = "字段名")
+    @Column(name = "COLUMN_NAME")
+    var columnName: String? = null
+
+    /** 类型 */
+    @Column(name = "DATA_TYPE")
+    @ApiModelProperty(value = "类型")
+    var columnType: String? = null
+
+    /** 字符最大长度 */
+    @ApiModelProperty(value = "字符最大长度")
+    @Column(name = "CHARACTER_MAXIMUM_LENGTH")
+    var charMaxLength: Int? = null
+
+    /** 可为空 */
+    @ApiModelProperty(value = "可为空")
+    @Column(name = "IS_NULLABLE")
+    var nullable: String? = null
+
+    /** 默认值 */
+    @ApiModelProperty(value = "默认值")
+    @Column(name = "COLUMN_DEFAULT")
+    var default: String? = null
+
+    /** 注释 */
+    @ApiModelProperty(value = "注释")
+    @Column(name = "COLUMN_COMMENT")
+    var description: String? = null
+
+} // Class SColumn

+ 56 - 0
persagy-service-mysql/src/main/kotlin/com/persagy/service/postgresql/models/STable.kt

@@ -0,0 +1,56 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.postgresql.models
+
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+import javax.persistence.Column
+import javax.persistence.Table
+
+/**
+ * 数据库设计-表结构
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "数据库设计-表结构")
+@Table(name = "information_schema.tables")
+open class STable {
+    /** 模式 */
+    @ApiModelProperty(value = "模式")
+    @Column(name = "TABLE_SCHEMA")
+    var schemaName: String? = null
+
+    /** 表名 */
+    @ApiModelProperty(value = "表名")
+    @Column(name = "TABLE_NAME")
+    var tableName: String? = null
+
+    /** 注释 */
+    @ApiModelProperty(value = "注释")
+    @Column(name = "TABLE_COMMENT")
+    var description: String? = null
+} // Class STable

+ 126 - 0
persagy-service-postgresql/build.gradle

@@ -0,0 +1,126 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+group rootProject.group
+version rootProject.version
+
+apply plugin: 'kotlin'
+apply plugin: 'org.jetbrains.dokka'
+apply plugin: 'maven'
+
+sourceCompatibility = JVM_TARGET
+
+compileKotlin {
+    kotlinOptions.jvmTarget = JVM_TARGET
+}
+compileTestKotlin {
+    kotlinOptions.jvmTarget = JVM_TARGET
+}
+
+dependencies {
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // alibaba数据库连接池
+    // https://mvnrepository.com/artifact/com.alibaba/druid
+    //compile group: 'com.alibaba', name: 'druid', version: DRUID_VERSION
+    // https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter
+    compile group: 'com.alibaba', name: 'druid-spring-boot-starter', version: SPRING_DRUID_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // MyBatis支持
+    // https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter
+    compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: SPRING_MYBATIS_VERSION
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Sybotan依赖
+    compile group: 'com.persagy', name: 'persagy-kotlin-mybatis', version: PERSAGY_KOTLIN_VERSION
+    compile group: 'com.persagy', name: 'persagy-kotlin-postgresql', version: PERSAGY_KOTLIN_VERSION
+    compile project(':persagy-service-base')
+    compile project(':persagy-service-mybatis')
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 文档生成
+dokka {
+    jdkVersion = JDK_VERSION
+    outputFormat = 'javadoc'
+    outputDirectory = "$buildDir/javadoc"
+    //sourceDirs = files('src/main/kotlin')
+}
+
+// 上成上传的文档jar包
+task javadocJar(type: Jar, dependsOn: dokka) {
+    classifier = 'javadoc'
+    from dokka.outputDirectory
+}
+
+// 上成上传的文档jar包
+task sourcesJar(type: Jar) {
+    classifier = 'sources'
+    from sourceSets.main.allSource
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// maven 仓库管理
+
+// 设置上传的包,默认上传aar与pom
+artifacts {
+    //archives javadocJar
+    archives sourcesJar
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            pom.groupId = project.group
+            pom.version = project.version
+            pom.artifactId = project.name
+            pom.project {
+                name project.name
+                packaging 'jar'
+                url PERSAGY_URL
+                description '斯伯坦机器人 mybatis支持库。'
+                licenses {
+                    license {
+                        name LICENSE_NAME
+                        url LICENSE_URL
+                    }
+                }
+                developers {
+                    developer {
+                        id DEVELOPER_ID
+                        name DEVELOPER_NAME
+                    }
+                }
+            }
+            snapshotRepository(url: MAVEN_REPO_SNAPSHOT_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+            repository(url: MAVEN_REPO_RELEASE_URL) {
+                authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
+            }
+        }
+    }
+}

+ 88 - 0
persagy-service-postgresql/src/main/kotlin/com/persagy/service/postgresql/controllers/SDatabaseDocController.kt

@@ -0,0 +1,88 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.postgresql.controllers
+
+import com.persagy.service.SObjectService
+import com.persagy.service.models.requests.SQueryRequest
+import com.persagy.service.models.responses.SQueryResponse
+import com.persagy.service.postgresql.models.SColumn
+import com.persagy.service.postgresql.models.STable
+import io.swagger.annotations.Api
+import io.swagger.annotations.ApiImplicitParam
+import io.swagger.annotations.ApiOperation
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.web.bind.annotation.*
+
+/**
+ * 数据库设计接口
+ *
+ * @author  庞利祥(sybotan@126.com)
+ */
+@Api(tags= ["数据库设计接口"])
+@RestController
+@RequestMapping("/--database-doc--/")
+class SDatabaseDocController {
+    /** 表服务对象 */
+    private val tableService = SObjectService(STable::class.java)
+
+    /** 字段服务对象 */
+    private val columnService = SObjectService(SColumn::class.java)
+
+    /**
+     * 查询表信息
+     *
+     * @param   schema      模式
+     * @return  字段列表
+     */
+    @ApiOperation(value="查询表信息", notes="")
+    @RequestMapping(value = ["/table-list"], method = [RequestMethod.GET, RequestMethod.POST])
+    fun tableList(schema: String): List<Any> {
+        return tableService.select("schemaName" to schema).exec()
+    } // Function tableList()
+
+    /**
+     * 查询字段信息
+     *
+     * @param   schema      模式
+     * @param   table       表名
+     * @return  字段列表
+     */
+    @ApiOperation(value="查询字段信息", notes="")
+    @RequestMapping(value = ["/column-list"], method = [RequestMethod.GET, RequestMethod.POST])
+    fun columnList(schema: String, table: String): List<Any> {
+        val builder = columnService.select("schemaName" to schema, "tableName" to table)
+        builder.tableName =
+                "(SELECT *,\n" +
+                "    (SELECT \"description\" FROM \"pg_description\"\n" +
+                "    JOIN \"pg_class\" ON \"pg_description\".\"objoid\" = \"pg_class\".\"oid\"\n" +
+                "    JOIN \"pg_namespace\" ON \"pg_class\".\"relnamespace\" = \"pg_namespace\".\"oid\"\n" +
+                "WHERE \"relname\" = \"t\".\"table_name\" and \"nspname\"= \"t\".\"table_schema\" and \"objsubid\" = \"t\".\"ordinal_position\")\n" +
+                "FROM \"information_schema\".\"columns\" t) ct"
+        return builder.exec()
+        //return mapper.columnList(schema, table)
+    } // Function columnList()
+} // Class SDatabaseDocController

+ 82 - 0
persagy-service-postgresql/src/main/kotlin/com/persagy/service/postgresql/models/SColumn.kt

@@ -0,0 +1,82 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.postgresql.models
+
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+import javax.persistence.Column
+import javax.persistence.Table
+
+/**
+ * 数据库设计-表结构
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "数据库设计-表结构")
+@Table(name = "information_schema.columns")
+open class SColumn {
+    /** 模式 */
+    @ApiModelProperty(value = "模式")
+    @Column(name = "table_schema")
+    var schemaName: String? = null
+
+    /** 表名 */
+    @ApiModelProperty(value = "表名")
+    @Column(name = "table_name")
+    var tableName: String? = null
+
+    /** 字段名 */
+    @ApiModelProperty(value = "字段名")
+    @Column(name = "column_name")
+    var columnName: String? = null
+
+    /** 类型 */
+    @Column(name = "udt_name")
+    @ApiModelProperty(value = "类型")
+    var columnType: String? = null
+
+    /** 字符最大长度 */
+    @ApiModelProperty(value = "字符最大长度")
+    @Column(name = "character_maximum_length")
+    var charMaxLength: Int? = null
+
+    /** 可为空 */
+    @ApiModelProperty(value = "可为空")
+    @Column(name = "is_nullable")
+    var nullable: String? = null
+
+    /** 默认值 */
+    @ApiModelProperty(value = "默认值")
+    @Column(name = "column_default")
+    var default: String? = null
+
+    /** 注释 */
+    @ApiModelProperty(value = "注释")
+    @Column(name = "description")
+    var description: String? = null
+
+} // Class SColumn

+ 56 - 0
persagy-service-postgresql/src/main/kotlin/com/persagy/service/postgresql/models/STable.kt

@@ -0,0 +1,56 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+package com.persagy.service.postgresql.models
+
+import io.swagger.annotations.ApiModel
+import io.swagger.annotations.ApiModelProperty
+import javax.persistence.Column
+import javax.persistence.Table
+
+/**
+ * 数据库设计-表结构
+ *
+ * @author  庞利祥 <sybotan@126.com>
+ */
+@ApiModel(description = "数据库设计-表结构")
+@Table(name = "information_schema.tables")
+open class STable {
+    /** 模式 */
+    @ApiModelProperty(value = "模式")
+    @Column(name = "table_schema")
+    var schemaName: String? = null
+
+    /** 表名 */
+    @ApiModelProperty(value = "表名")
+    @Column(name = "table_name")
+    var tableName: String? = null
+
+    /** 注释 */
+    @ApiModelProperty(value = "注释")
+    @Column(name = "description")
+    var description: String? = null
+} // Class STable

+ 34 - 0
settings.gradle

@@ -0,0 +1,34 @@
+/*
+ * *********************************************************************************************************************
+ *
+ *          !!
+ *        .F88X
+ *        X8888Y
+ *      .}888888N;
+ *        i888888N;        .:!              .I$WI:
+ *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
+ *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
+ *            +888888N;  .8888888Y                                  "&&8Y.}8,
+ *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
+ *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
+ *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
+ *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
+ *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
+ *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
+ *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
+ *      .:R888888I
+ *      .&888888I                                          Copyright (c) 2016-2020.  博锐尚格科技股份有限公司
+ *        ~8888'
+ *        .!88~                                                                     All rights reserved.
+ *
+ * *********************************************************************************************************************
+ */
+
+rootProject.name = 'persagy-service'
+
+include 'persagy-service-models'
+include 'persagy-service-base'
+include 'persagy-service-mybatis'
+include 'persagy-service-postgresql'
+include 'persagy-service-kafka'
+include 'persagy-service-mysql'