Browse Source

update

main
wangshaoping 7 months ago
parent
commit
64a7011441
  1. 9
      app.platform/build.gradle
  2. 14
      app.platform/src/main/java/app/platform/Application.java
  3. 7
      build-version.gradle
  4. 19
      default-authorizationserver/build-common.gradle
  5. 15
      default-authorizationserver/build-jetty.gradle
  6. 7
      default-authorizationserver/build-tomcat.gradle
  7. 15
      default-authorizationserver/build-undertow.gradle
  8. 21
      default-authorizationserver/build.gradle
  9. 0
      default-authorizationserver/gradle.properties
  10. 32
      default-authorizationserver/src/main/java/sample/DefaultAuthorizationServerApplication.java
  11. 147
      default-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java
  12. 60
      default-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java
  13. 74
      default-authorizationserver/src/main/java/sample/jose/Jwks.java
  14. 85
      default-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java
  15. 10
      default-authorizationserver/src/main/resources/application.yml
  16. 137
      default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerApplicationTests.java
  17. 126
      default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerConsentTests.java
  18. 2
      erm.frontend/package.json
  19. 25
      erm.frontend/public/index.html
  20. 2
      gradle.properties
  21. 2
      io.sc.engine.mv.frontend/package.json
  22. 25
      io.sc.engine.mv.frontend/public/index.html
  23. 2
      io.sc.engine.mv/src/main/resources/META-INF/platform/plugins/liquibase.json
  24. 14
      io.sc.engine.rule.core/src/main/java/io/sc/engine/rule/core/code/impl/support/ResourceWrapper.java
  25. 67
      io.sc.engine.rule.core/src/main/java/io/sc/engine/rule/core/po/function/Function.java
  26. 6
      io.sc.engine.rule.core/src/main/resources/io/sc/engine/rule/core/code/template/groovy/functions.ftl
  27. 2
      io.sc.engine.rule.core/src/main/resources/io/sc/engine/rule/core/code/template/groovy/groovy.ftl
  28. 2
      io.sc.engine.rule.frontend/package.json
  29. 25
      io.sc.engine.rule.frontend/public/index.html
  30. 8
      io.sc.engine.rule.frontend/src/components/index.ts
  31. 8
      io.sc.engine.rule.frontend/src/i18n/messages.json
  32. 8
      io.sc.engine.rule.frontend/src/i18n/messages_tw_CN.json
  33. 8
      io.sc.engine.rule.frontend/src/i18n/messages_zh_CN.json
  34. 3
      io.sc.engine.rule.frontend/src/menus/menus.json
  35. 10
      io.sc.engine.rule.frontend/src/routes/routes.json
  36. 148
      io.sc.engine.rule.frontend/src/views/function/Function.vue
  37. 72
      io.sc.engine.rule.frontend/src/views/function/ImportDialog.vue
  38. 4
      io.sc.engine.rule.frontend/src/views/functions/Functions.vue
  39. 2
      io.sc.engine.rule.frontend/src/views/lib/ImportDialog.vue
  40. 4
      io.sc.engine.rule.frontend/src/views/lib/IndicatorGrid.vue
  41. 6
      io.sc.engine.rule.frontend/src/views/lib/Lib.vue
  42. 28
      io.sc.engine.rule.frontend/src/views/lib/ProcessorGrid.vue
  43. 8
      io.sc.engine.rule.frontend/src/views/resources/designer/DesignerDialog.vue
  44. 28
      io.sc.engine.rule.frontend/src/views/resources/designer/Processor.vue
  45. 1
      io.sc.engine.rule.server/build.gradle
  46. 69
      io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/controller/FunctionWebController.java
  47. 84
      io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/converter/FunctionEntityConverter.java
  48. 163
      io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/entity/FunctionEntity.java
  49. 10
      io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/repository/FunctionRepository.java
  50. 18
      io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/service/FunctionService.java
  51. 37
      io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/service/impl/FunctionServiceImpl.java
  52. 69
      io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/vo/FunctionVo.java
  53. 12
      io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/plugins/item/ResourceExampleItem.java
  54. 25
      io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/resource/service/impl/ResourceServiceImpl.java
  55. 44
      io.sc.engine.rule.server/src/main/resources/META-INF/platform/plugins/components.json
  56. 3
      io.sc.engine.rule.server/src/main/resources/META-INF/platform/plugins/repositories.json
  57. 3
      io.sc.engine.rule.server/src/main/resources/META-INF/platform/plugins/rule-engine-sample-resource.json
  58. 9
      io.sc.engine.rule.server/src/main/resources/io/sc/engine/rule/server/sample/用户自定义函数.json
  59. 31
      io.sc.engine.rule.server/src/main/resources/liquibase/RE_1.0.0_20220515__Rule Engine Database Schema DDL.xml
  60. 2
      io.sc.engine.st.frontend/package.json
  61. 25
      io.sc.engine.st.frontend/public/index.html
  62. 4
      io.sc.engine.st/src/main/resources/META-INF/platform/plugins/liquibase.json
  63. 2
      io.sc.platform.attachment/src/main/resources/META-INF/platform/plugins/liquibase.json
  64. 2
      io.sc.platform.core.frontend/package.json
  65. 25
      io.sc.platform.core.frontend/public/index.html
  66. 2
      io.sc.platform.core.frontend/src/components/index.ts
  67. 9
      io.sc.platform.core.frontend/src/menus/menus.json
  68. 16
      io.sc.platform.core.frontend/src/platform/components/form/elements/WCodeMirror.vue
  69. 9
      io.sc.platform.core.frontend/src/platform/components/math/WMath.vue
  70. 59
      io.sc.platform.core.frontend/src/platform/components/math/toolbar/Toolbar.vue
  71. 2
      io.sc.platform.core.frontend/src/platform/css/quasar.sass
  72. 2
      io.sc.platform.core.frontend/src/platform/i18n/messages.json
  73. 2
      io.sc.platform.core.frontend/src/platform/i18n/messages_tw_CN.json
  74. 4
      io.sc.platform.core.frontend/src/platform/i18n/messages_zh_CN.json
  75. 13
      io.sc.platform.core.frontend/src/routes/routes.json
  76. 9
      io.sc.platform.core.frontend/src/views/testcase/code-mirror/code-mirror.vue
  77. 7
      io.sc.platform.core.frontend/src/views/testcase/math/MathEditor.vue
  78. 51
      io.sc.platform.core.frontend/src/views/testcase/maxgraph/maxgraph.vue
  79. 4
      io.sc.platform.core.frontend/template-project/package.json
  80. 25
      io.sc.platform.core.frontend/template-project/public/index.html
  81. 9
      io.sc.platform.core.frontend/template-project/src/views/testcase/code-mirror/code-mirror.vue
  82. 7
      io.sc.platform.core.frontend/template-project/src/views/testcase/math/MathEditor.vue
  83. 36
      io.sc.platform.core/src/main/java/io/sc/platform/core/plugins/PluginManager.java
  84. 131
      io.sc.platform.core/src/main/java/io/sc/platform/core/plugins/item/ExportableResource.java
  85. 66
      io.sc.platform.core/src/main/java/io/sc/platform/core/springboot/AfterEnvironmentProcessor.java
  86. 7
      io.sc.platform.core/src/main/java/io/sc/platform/core/springboot/BeforeEnvironmentProcessor.java
  87. 2
      io.sc.platform.core/src/main/resources/META-INF/platform/plugins/directories.json
  88. 9
      io.sc.platform.core/src/main/resources/META-INF/platform/plugins/exportable-resources.json
  89. 2
      io.sc.platform.developer.doc/asciidoc/9999-appendix/appendix.adoc
  90. 62
      io.sc.platform.developer.doc/asciidoc/9999-appendix/environment/environment.adoc
  91. 1
      io.sc.platform.developer.doc/asciidoc/9999-appendix/oauth2/oauth2.adoc
  92. 52
      io.sc.platform.developer.doc/asciidoc/市场风险/市场风险.adoc
  93. 2
      io.sc.platform.developer.frontend/package.json
  94. 25
      io.sc.platform.developer.frontend/public/index.html
  95. 3
      io.sc.platform.flowable/src/main/java/io/sc/platform/flowable/service/impl/ProcessOperationServiceImpl.java
  96. 6
      io.sc.platform.flowable/src/main/resources/META-INF/platform/plugins/liquibase.json
  97. 2
      io.sc.platform.gradle/templates/pgp/app/build-common.gradle
  98. 2
      io.sc.platform.gradle/templates/pgp/setup/gradle.properties
  99. 4
      io.sc.platform.jdbc.driver.dm/src/main/resources/META-INF/platform/plugins/jdbc-connection-template.json
  100. 2
      io.sc.platform.lcdp.frontend/package.json

9
app.platform/build.gradle

@ -13,10 +13,11 @@ dependencies {
implementation (
project(":io.sc.platform.app"),
project(":io.sc.platform.developer"),
//project(":io.sc.platform.security.oauth2.server.authorization"),
// project(":io.sc.platform.security.oauth2.server.resource"),
project(":io.sc.platform.security.loginform"),
project(":io.sc.platform.security.oauth2.server.authorization"),
//project(":io.sc.platform.security.oauth2.server.resource"),
//project(":io.sc.platform.security.loginform"),
/*
project(":io.sc.platform.scheduler.manager"),
project(":io.sc.platform.scheduler.executor"),
@ -37,6 +38,7 @@ dependencies {
// project(":erm.frontend"),
project(":io.sc.standard"),
*/
)
}
@ -77,7 +79,6 @@ bootJar{
}
}
jib {
outputPaths {
tar = "build/libs/${project.name}-${project.version}-image.tar"

14
app.platform/src/main/java/app/platform/Application.java

@ -2,16 +2,9 @@ package app.platform;
import io.sc.platform.core.ApplicationLauncher;
import io.sc.platform.core.PlatformSpringBootServletInitializer;
import io.sc.platform.orm.entity.BaseEntity;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.web.WebApplicationInitializer;
import java.util.Locale;
import java.util.Set;
/**
* 应用程序入口
*/
@ -19,12 +12,5 @@ import java.util.Set;
public class Application extends PlatformSpringBootServletInitializer implements WebApplicationInitializer {
public static void main(String[] args) throws Exception {
ApplicationLauncher.run(Application.class,args);
// ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// provider.addIncludeFilter(new AssignableTypeFilter(BaseEntity.class));
// Set<BeanDefinition> components = provider.findCandidateComponents("io.sc");
// for (BeanDefinition component : components)
// {
// System.out.println(component.getBeanClassName());
// }
}
}

7
build-version.gradle

@ -7,6 +7,13 @@ ext['PlatformDependencyVersions'] =[
"org.checkerframework:checker-qual" : "${checker_version}",
"com.google.guava:guava" : "${guava_version}",
"com.nimbusds:nimbus-jose-jwt" : "9.22",
"org.codehaus.groovy:groovy" : "3.0.19",
"org.codehaus.groovy:groovy-jsr223" : "3.0.19",
"org.codehaus.groovy:groovy-datetime" : "3.0.19",
"org.codehaus.groovy:groovy-dateutil" : "3.0.19",
"org.codehaus.groovy:groovy-json" : "3.0.19",
"org.codehaus.groovy:groovy-sql" : "3.0.19",
"org.codehaus.groovy:groovy-xml" : "3.0.19",
];
/***********************************************************************

19
default-authorizationserver/build-common.gradle

@ -0,0 +1,19 @@
/**
* (target)
* : -D
* :
* 1. gradle bootwar # , target=tomcat
* 2. gradle bootwar -Dtarget=undertow # undertow, target=undertow
* 3. gradle bootwar -Dtarget=jetty # jetty, target=jetty
*/
def target =System.getProperty("target") ?: "tomcat";
System.setProperty('target',target);
// targetRuntime build.gradle
apply from: "build-${target}.gradle"
//
publishPublicationPublicationToMavenRepository.enabled=false
// docker
jibBuildTar.enabled =true

15
default-authorizationserver/build-jetty.gradle

@ -0,0 +1,15 @@
println "[Jetty] 环境 ......"
configurations {
all*.exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-core"
all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-websocket"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-jetty")
providedRuntime(
"org.springframework.boot:spring-boot-starter-jetty",
)
}

7
default-authorizationserver/build-tomcat.gradle

@ -0,0 +1,7 @@
println "[Tomcat] 环境 ......"
dependencies {
providedRuntime(
"org.springframework.boot:spring-boot-starter-tomcat",
)
}

15
default-authorizationserver/build-undertow.gradle

@ -0,0 +1,15 @@
println "[Undertow] 环境 ......"
configurations {
all*.exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-core"
all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-websocket"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-undertow")
providedRuntime(
"org.springframework.boot:spring-boot-starter-undertow",
)
}

21
default-authorizationserver/build.gradle

@ -0,0 +1,21 @@
apply plugin: 'war'
apply plugin: 'com.google.cloud.tools.jib'
apply from: "build-common.gradle"
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web"){
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
}
dependencies {
implementation(
"jakarta.servlet:jakarta.servlet-api",
"org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-security",
"org.springframework.boot:spring-boot-starter-jdbc",
"org.springframework.security:spring-security-oauth2-authorization-server:0.4.5",
"com.h2database:h2"
)
}

0
default-authorizationserver/gradle.properties

32
default-authorizationserver/src/main/java/sample/DefaultAuthorizationServerApplication.java

@ -0,0 +1,32 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Joe Grandja
* @since 0.0.1
*/
@SpringBootApplication
public class DefaultAuthorizationServerApplication {
public static void main(String[] args) {
SpringApplication.run(DefaultAuthorizationServerApplication.class, args);
}
}

147
default-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

@ -0,0 +1,147 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.config;
import java.util.UUID;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import sample.jose.Jwks;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
/**
* @author Joe Grandja
* @since 0.0.1
*/
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
// @formatter:off
http
.exceptionHandling(exceptions ->
exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// @formatter:on
return http.build();
}
// @formatter:off
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
// Save registered client in db as if in-memory
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
registeredClientRepository.save(registeredClient);
return registeredClientRepository;
}
// @formatter:on
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = Jwks.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
@Bean
public EmbeddedDatabase embeddedDatabase() {
// @formatter:off
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.H2)
.setScriptEncoding("UTF-8")
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
.addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
.build();
// @formatter:on
}
}

60
default-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java

@ -0,0 +1,60 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* @author Joe Grandja
* @since 0.1.0
*/
@EnableWebSecurity
public class DefaultSecurityConfig {
// @formatter:off
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
// @formatter:on
// @formatter:off
@Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user1")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
// @formatter:on
}

74
default-authorizationserver/src/main/java/sample/jose/Jwks.java

@ -0,0 +1,74 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jose;
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import javax.crypto.SecretKey;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.RSAKey;
/**
* @author Joe Grandja
* @since 0.1.0
*/
public final class Jwks {
private Jwks() {
}
public static RSAKey generateRsa() {
KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// @formatter:off
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
}
public static ECKey generateEc() {
KeyPair keyPair = KeyGeneratorUtils.generateEcKey();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
// @formatter:off
return new ECKey.Builder(curve, publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
}
public static OctetSequenceKey generateSecret() {
SecretKey secretKey = KeyGeneratorUtils.generateSecretKey();
// @formatter:off
return new OctetSequenceKey.Builder(secretKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
}
}

85
default-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java

@ -0,0 +1,85 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jose;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
/**
* @author Joe Grandja
* @since 0.1.0
*/
final class KeyGeneratorUtils {
private KeyGeneratorUtils() {
}
static SecretKey generateSecretKey() {
SecretKey hmacKey;
try {
hmacKey = KeyGenerator.getInstance("HmacSha256").generateKey();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return hmacKey;
}
static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
static KeyPair generateEcKey() {
EllipticCurve ellipticCurve = new EllipticCurve(
new ECFieldFp(
new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951")),
new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"));
ECPoint ecPoint = new ECPoint(
new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"));
ECParameterSpec ecParameterSpec = new ECParameterSpec(
ellipticCurve,
ecPoint,
new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"),
1);
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
keyPairGenerator.initialize(ecParameterSpec);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
}

10
default-authorizationserver/src/main/resources/application.yml

@ -0,0 +1,10 @@
server:
port: 9000
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
org.springframework.security.oauth2: INFO
# org.springframework.boot.autoconfigure: DEBUG

137
default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerApplicationTests.java

@ -0,0 +1,137 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import java.io.IOException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for the sample Authorization Server.
*
* @author Daniel Garnier-Moiroux
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class DefaultAuthorizationServerApplicationTests {
private static final String REDIRECT_URI = "http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc";
private static final String AUTHORIZATION_REQUEST = UriComponentsBuilder
.fromPath("/oauth2/authorize")
.queryParam("response_type", "code")
.queryParam("client_id", "messaging-client")
.queryParam("scope", "openid")
.queryParam("state", "some-state")
.queryParam("redirect_uri", REDIRECT_URI)
.toUriString();
@Autowired
private WebClient webClient;
@BeforeEach
public void setUp() {
this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(true);
this.webClient.getOptions().setRedirectEnabled(true);
this.webClient.getCookieManager().clearCookies(); // log out
}
@Test
public void whenLoginSuccessfulThenDisplayNotFoundError() throws IOException {
HtmlPage page = this.webClient.getPage("/");
assertLoginPage(page);
this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
WebResponse signInResponse = signIn(page, "user1", "password").getWebResponse();
assertThat(signInResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND.value()); // there is no "default" index page
}
@Test
public void whenLoginFailsThenDisplayBadCredentials() throws IOException {
HtmlPage page = this.webClient.getPage("/");
HtmlPage loginErrorPage = signIn(page, "user1", "wrong-password");
HtmlElement alert = loginErrorPage.querySelector("div[role=\"alert\"]");
assertThat(alert).isNotNull();
assertThat(alert.getTextContent()).isEqualTo("Bad credentials");
}
@Test
public void whenNotLoggedInAndRequestingTokenThenRedirectsToLogin() throws IOException {
HtmlPage page = this.webClient.getPage(AUTHORIZATION_REQUEST);
assertLoginPage(page);
}
@Test
public void whenLoggingInAndRequestingTokenThenRedirectsToClientApplication() throws IOException {
// Log in
this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
this.webClient.getOptions().setRedirectEnabled(false);
signIn(this.webClient.getPage("/login"), "user1", "password");
// Request token
WebResponse response = this.webClient.getPage(AUTHORIZATION_REQUEST).getWebResponse();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value());
String location = response.getResponseHeaderValue("location");
assertThat(location).startsWith(REDIRECT_URI);
assertThat(location).contains("code=");
}
private static <P extends Page> P signIn(HtmlPage page, String username, String password) throws IOException {
HtmlInput usernameInput = page.querySelector("input[name=\"username\"]");
HtmlInput passwordInput = page.querySelector("input[name=\"password\"]");
HtmlButton signInButton = page.querySelector("button");
usernameInput.type(username);
passwordInput.type(password);
return signInButton.click();
}
private static void assertLoginPage(HtmlPage page) {
assertThat(page.getUrl().toString()).endsWith("/login");
HtmlInput usernameInput = page.querySelector("input[name=\"username\"]");
HtmlInput passwordInput = page.querySelector("input[name=\"password\"]");
HtmlButton signInButton = page.querySelector("button");
assertThat(usernameInput).isNotNull();
assertThat(passwordInput).isNotNull();
assertThat(signInButton.getTextContent()).isEqualTo("Sign in");
}
}

126
default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerConsentTests.java

@ -0,0 +1,126 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
/**
* Consent screen integration tests for the sample Authorization Server.
*
* @author Dmitriy Dubson
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class DefaultAuthorizationServerConsentTests {
@Autowired
private WebClient webClient;
@MockBean
private OAuth2AuthorizationConsentService authorizationConsentService;
private final String redirectUri = "http://127.0.0.1/login/oauth2/code/messaging-client-oidc";
private final String authorizationRequestUri = UriComponentsBuilder
.fromPath("/oauth2/authorize")
.queryParam("response_type", "code")
.queryParam("client_id", "messaging-client")
.queryParam("scope", "openid message.read message.write")
.queryParam("state", "state")
.queryParam("redirect_uri", this.redirectUri)
.toUriString();
@BeforeEach
public void setUp() {
this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
this.webClient.getOptions().setRedirectEnabled(true);
this.webClient.getCookieManager().clearCookies();
when(this.authorizationConsentService.findById(any(), any())).thenReturn(null);
}
@Test
@WithMockUser("user1")
public void whenUserConsentsToAllScopesThenReturnAuthorizationCode() throws IOException {
final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri);
assertThat(consentPage.getTitleText()).isEqualTo("Consent required");
List<HtmlCheckBoxInput> scopes = new ArrayList<>();
consentPage.querySelectorAll("input[name='scope']").forEach(scope ->
scopes.add((HtmlCheckBoxInput) scope));
for (HtmlCheckBoxInput scope : scopes) {
scope.click();
}
List<String> scopeIds = new ArrayList<>();
scopes.forEach(scope -> {
assertThat(scope.isChecked()).isTrue();
scopeIds.add(scope.getId());
});
assertThat(scopeIds).containsExactlyInAnyOrder("message.read", "message.write");
DomElement submitConsentButton = consentPage.querySelector("button[id='submit-consent']");
this.webClient.getOptions().setRedirectEnabled(false);
WebResponse approveConsentResponse = submitConsentButton.click().getWebResponse();
assertThat(approveConsentResponse.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value());
String location = approveConsentResponse.getResponseHeaderValue("location");
assertThat(location).startsWith(this.redirectUri);
assertThat(location).contains("code=");
}
@Test
@WithMockUser("user1")
public void whenUserCancelsConsentThenReturnAccessDeniedError() throws IOException {
final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri);
assertThat(consentPage.getTitleText()).isEqualTo("Consent required");
DomElement cancelConsentButton = consentPage.querySelector("button[id='cancel-consent']");
this.webClient.getOptions().setRedirectEnabled(false);
WebResponse cancelConsentResponse = cancelConsentButton.click().getWebResponse();
assertThat(cancelConsentResponse.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value());
String location = cancelConsentResponse.getResponseHeaderValue("location");
assertThat(location).startsWith(this.redirectUri);
assertThat(location).contains("error=access_denied");
}
}

2
erm.frontend/package.json

@ -92,7 +92,7 @@
"luckyexcel": "1.0.1",
"mockjs": "1.1.0",
"pinia": "2.1.7",
"platform-core": "8.1.279",
"platform-core": "8.1.287",
"quasar": "2.15.4",
"tailwindcss": "3.4.4",
"vue": "3.4.31",

25
erm.frontend/public/index.html

@ -26,6 +26,31 @@
<script src="/webjars/tailwindcss/3.3.5/tailwindcss.js" th:src="@{/webjars/tailwindcss/3.3.5/tailwindcss.js}"></script>
<script src="/configure.js" th:src="@{/configure.js}"></script>
<script>
(function () {
"use strict";
window.addEventListener("load", function () {
var box, div, link, namespaceURI;
// First check whether the page contains any <math> element.
namespaceURI = "http://www.w3.org/1998/Math/MathML";
if (document.body.getElementsByTagNameNS(namespaceURI, "math")[0]) {
// Create a div to test mspace, using Kuma's "offscreen" CSS
document.body.insertAdjacentHTML("afterbegin", "<div style='border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px;'><math xmlns='" + namespaceURI + "'><mspace height='23px' width='77px'></mspace></math></div>");
div = document.body.firstChild;
box = div.firstChild.firstChild.getBoundingClientRect();
document.body.removeChild(div);
if (Math.abs(box.height - 23) > 1 || Math.abs(box.width - 77) > 1) {
// Insert the mathml.css stylesheet.
link = document.createElement("link");
link.href = '[(@{/webjars/mathfonts/1.0.0/mathml.css})]'.startsWith('[')? 'http://localhost:8080/webjars/mathfonts/1.0.0/mathml.css' : '[(@{/webjars/mathfonts/1.0.0/mathml.css})]';
link.rel = "stylesheet";
document.head.appendChild(link);
}
}
});
}());
</script>
</head>
<body>
<noscript>

2
gradle.properties

@ -38,7 +38,7 @@ application_version=1.0.0
platform_group=io.sc
platform_version=8.1.44
platform_plugin_version=8.1.44
platform_core_frontend_version=8.1.281
platform_core_frontend_version=8.1.287
###########################################################
# dependencies version

2
io.sc.engine.mv.frontend/package.json

@ -92,7 +92,7 @@
"luckyexcel": "1.0.1",
"mockjs": "1.1.0",
"pinia": "2.1.7",
"platform-core": "8.1.279",
"platform-core": "8.1.287",
"quasar": "2.15.4",
"tailwindcss": "3.4.4",
"vue": "3.4.31",

25
io.sc.engine.mv.frontend/public/index.html

@ -26,6 +26,31 @@
<script src="/webjars/tailwindcss/3.3.5/tailwindcss.js" th:src="@{/webjars/tailwindcss/3.3.5/tailwindcss.js}"></script>
<script src="/configure.js" th:src="@{/configure.js}"></script>
<script>
(function () {
"use strict";
window.addEventListener("load", function () {
var box, div, link, namespaceURI;
// First check whether the page contains any <math> element.
namespaceURI = "http://www.w3.org/1998/Math/MathML";
if (document.body.getElementsByTagNameNS(namespaceURI, "math")[0]) {
// Create a div to test mspace, using Kuma's "offscreen" CSS
document.body.insertAdjacentHTML("afterbegin", "<div style='border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px;'><math xmlns='" + namespaceURI + "'><mspace height='23px' width='77px'></mspace></math></div>");
div = document.body.firstChild;
box = div.firstChild.firstChild.getBoundingClientRect();
document.body.removeChild(div);
if (Math.abs(box.height - 23) > 1 || Math.abs(box.width - 77) > 1) {
// Insert the mathml.css stylesheet.
link = document.createElement("link");
link.href = '[(@{/webjars/mathfonts/1.0.0/mathml.css})]'.startsWith('[')? 'http://localhost:8080/webjars/mathfonts/1.0.0/mathml.css' : '[(@{/webjars/mathfonts/1.0.0/mathml.css})]';
link.rel = "stylesheet";
document.head.appendChild(link);
}
}
});
}());
</script>
</head>
<body>
<noscript>

2
io.sc.engine.mv/src/main/resources/META-INF/platform/plugins/liquibase.json

@ -16,7 +16,7 @@
"order" : 12000,
"description":"模型验证工具",
"locations":[
"liquibase/io.sc.engine.mv_8.0.0_20221020__Model Validate Database Schema DDL.xml"
"classpath:/liquibase/io.sc.engine.mv_8.0.0_20221020__Model Validate Database Schema DDL.xml"
]
}
]

14
io.sc.engine.rule.core/src/main/java/io/sc/engine/rule/core/code/impl/support/ResourceWrapper.java

@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.sc.engine.rule.core.po.function.Function;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown=true)
@ -14,17 +17,24 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
})
public abstract class ResourceWrapper {
protected boolean isExecuteTestCase;
protected List<Function> functions;
public boolean getIsExecuteTestCase() {
return isExecuteTestCase;
}
public void setIsExecuteTestCase(boolean isExecuteTestCase) {
this.isExecuteTestCase = isExecuteTestCase;
}
public List<Function> getFunctions() {
return functions;
}
public void setFunctions(List<Function> functions) {
this.functions = functions;
}
@JsonIgnore
public abstract String getType();
}

67
io.sc.engine.rule.core/src/main/java/io/sc/engine/rule/core/po/function/Function.java

@ -0,0 +1,67 @@
package io.sc.engine.rule.core.po.function;
public class Function {
protected String id;
protected String name;
protected String signature;
protected String description;
private Boolean enable;
private String mathXml;
private String body;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
public String getMathXml() {
return mathXml;
}
public void setMathXml(String mathXml) {
this.mathXml = mathXml;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}

6
io.sc.engine.rule.core/src/main/resources/io/sc/engine/rule/core/code/template/groovy/functions.ftl

@ -0,0 +1,6 @@
<#assign functions=$wrapper.functions!>
<#list functions! as function>
<#if (function.enable)!>
${function.body}
</#if>
</#list>

2
io.sc.engine.rule.core/src/main/resources/io/sc/engine/rule/core/code/template/groovy/groovy.ftl

@ -106,6 +106,8 @@ import static io.sc.engine.rule.core.util.NumberFormater.percent;
import io.sc.engine.rule.core.function.JpmmlEvaluator;
<#include "/functions.ftl"/>
<#if $wrapper.type=="RESOURCE">
<#include "/resource_groovy.ftl"/>
<#elseif $wrapper.type=="LIB">

2
io.sc.engine.rule.frontend/package.json

@ -92,7 +92,7 @@
"luckyexcel": "1.0.1",
"mockjs": "1.1.0",
"pinia": "2.1.7",
"platform-core": "8.1.279",
"platform-core": "8.1.287",
"quasar": "2.15.4",
"tailwindcss": "3.4.4",
"vue": "3.4.31",

25
io.sc.engine.rule.frontend/public/index.html

@ -26,6 +26,31 @@
<script src="/webjars/tailwindcss/3.3.5/tailwindcss.js" th:src="@{/webjars/tailwindcss/3.3.5/tailwindcss.js}"></script>
<script src="/configure.js" th:src="@{/configure.js}"></script>
<script>
(function () {
"use strict";
window.addEventListener("load", function () {
var box, div, link, namespaceURI;
// First check whether the page contains any <math> element.
namespaceURI = "http://www.w3.org/1998/Math/MathML";
if (document.body.getElementsByTagNameNS(namespaceURI, "math")[0]) {
// Create a div to test mspace, using Kuma's "offscreen" CSS
document.body.insertAdjacentHTML("afterbegin", "<div style='border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px;'><math xmlns='" + namespaceURI + "'><mspace height='23px' width='77px'></mspace></math></div>");
div = document.body.firstChild;
box = div.firstChild.firstChild.getBoundingClientRect();
document.body.removeChild(div);
if (Math.abs(box.height - 23) > 1 || Math.abs(box.width - 77) > 1) {
// Insert the mathml.css stylesheet.
link = document.createElement("link");
link.href = '[(@{/webjars/mathfonts/1.0.0/mathml.css})]'.startsWith('[')? 'http://localhost:8080/webjars/mathfonts/1.0.0/mathml.css' : '[(@{/webjars/mathfonts/1.0.0/mathml.css})]';
link.rel = "stylesheet";
document.head.appendChild(link);
}
}
});
}());
</script>
</head>
<body>
<noscript>

8
io.sc.engine.rule.frontend/src/components/index.ts

@ -8,9 +8,11 @@ import component_engine_rule_authorization from '@/views/authorization/Authoriza
import component_engine_rule_workflow from '@/views/workflow/Workflow.vue';
import component_engine_rule_dictionary from '@/views/dictionary/Dictionary.vue';
import component_engine_rule_lib from '@/views/lib/Lib.vue';
import component_engine_rule_functions from '@/views/functions/Functions.vue';
import component_engine_rule_function from '@/views/function/Function.vue';
import component_engine_rule_testcase from '@/views/testcase/Testcase.vue';
import component_engine_rule_migration from '@/views/migration/Migration.vue';
import component_engine_rule_report from '@/views/report/tzblwdxfxbb.vue';
import component_engine_rule_report2 from '@/views/report/tzblwoeivbb.vue';
const localComponents = {
'component.engine.rule.resources': component_engine_rule_resources,
@ -19,9 +21,11 @@ const localComponents = {
'component.engine.rule.workflow': component_engine_rule_workflow,
'component.engine.rule.dictionary': component_engine_rule_dictionary,
'component.engine.rule.lib': component_engine_rule_lib,
'component.engine.rule.functions': component_engine_rule_functions,
'component.engine.rule.function': component_engine_rule_function,
'component.engine.rule.testcase': component_engine_rule_testcase,
'component.engine.rule.migration': component_engine_rule_migration,
'component.engine.rule.report': component_engine_rule_report,
'component.engine.rule.report2': component_engine_rule_report2,
};
export default localComponents;

8
io.sc.engine.rule.frontend/src/i18n/messages.json

@ -5,7 +5,7 @@
"menu.engine.rule.workflow": "Workflow",
"menu.engine.rule.dictionary": "Meta Data Manager",
"menu.engine.rule.lib": "Feature Library Manager",
"menu.engine.rule.functions": "Function Library",
"menu.engine.rule.function": "User Defined Function Library",
"menu.engine.rule.testcase": "Test Case Manager",
"menu.engine.rule.migration": "Data Back and Migration",
@ -170,6 +170,12 @@
"re.dictionary.importSample.grid.toolbar.import": "Import Example",
"re.dictionary.importSample.grid.toolbar.import.tip": "Are you sure to import the meta data?",
"re.function.grid.title":"Functions",
"re.function.grid.entity.signature":"Signature",
"re.function.grid.entity.mathXml":"Math ML",
"re.function.grid.entity.body":"Body",
"re.function.import.dialog.title": "Import User Defined Functions",
"re.lib.grid.title": "Feature Library Tree",
"re.lib.grid.toolbar.addGroup": "Add New",
"re.lib.grid.toolbar.addTop": "Top Folder",

8
io.sc.engine.rule.frontend/src/i18n/messages_tw_CN.json

@ -5,7 +5,7 @@
"menu.engine.rule.workflow": "流程審批",
"menu.engine.rule.dictionary": "元數據管理",
"menu.engine.rule.lib": "特征庫管理",
"menu.engine.rule.functions": "函數庫",
"menu.engine.rule.function": "自定義函數庫",
"menu.engine.rule.testcase": "試算用例",
"menu.engine.rule.migration": "數據備份和遷移",
@ -170,6 +170,12 @@
"re.dictionary.importSample.grid.toolbar.import": "導入示例",
"re.dictionary.importSample.grid.toolbar.import.tip": "您確定要導入示例元數據嗎?",
"re.function.grid.title":"自定義函數",
"re.function.grid.entity.signature":"函數簽名",
"re.function.grid.entity.mathXml":"數學符號",
"re.function.grid.entity.body":"函數體",
"re.function.import.dialog.title": "導入自定義函數",
"re.lib.grid.title": "特征庫",
"re.lib.grid.toolbar.addGroup": "新增",
"re.lib.grid.toolbar.addTop": "頂級文件夾",

8
io.sc.engine.rule.frontend/src/i18n/messages_zh_CN.json

@ -5,7 +5,7 @@
"menu.engine.rule.workflow": "流程审批",
"menu.engine.rule.dictionary": "元数据管理",
"menu.engine.rule.lib": "特征库管理",
"menu.engine.rule.functions": "函数库",
"menu.engine.rule.function": "自定义函数库",
"menu.engine.rule.testcase": "试算用例管理",
"menu.engine.rule.migration": "数据备份和迁移",
@ -169,6 +169,12 @@
"re.dictionary.importSample.grid.title": "示例元数据列表",
"re.dictionary.importSample.grid.toolbar.import": "导入示例",
"re.dictionary.importSample.grid.toolbar.import.tip": "您确定要导入示例元数据吗?",
"re.function.grid.title":"自定义函数",
"re.function.grid.entity.signature":"函数签名",
"re.function.grid.entity.mathXml":"数学符号",
"re.function.grid.entity.body":"函数体",
"re.function.import.dialog.title": "导入自定义函数",
"re.lib.grid.title": "特征库",
"re.lib.grid.toolbar.addGroup": "新增",

3
io.sc.engine.rule.frontend/src/menus/menus.json

@ -30,10 +30,9 @@
/*/*/
{"type":"ROUTE", "order":500, "parentId":"menu.engine.rule", "id":"menu.engine.rule.lib", "titleI18nKey":"menu.engine.rule.lib", "icon":"bi-folder2", "routeName":"route.engine.rule.lib"},
/*/*/
{"type":"ROUTE", "order":600, "parentId":"menu.engine.rule", "id":"menu.engine.rule.functions", "titleI18nKey":"menu.engine.rule.functions", "icon":"bi-folder2", "routeName":"route.engine.rule.functions"},
{"type":"ROUTE", "order":600, "parentId":"menu.engine.rule", "id":"menu.engine.rule.function", "titleI18nKey":"menu.engine.rule.function", "icon":"bi-folder2", "routeName":"route.engine.rule.function"},
/*/*/
{"type":"ROUTE", "order":700, "parentId":"menu.engine.rule", "id":"menu.engine.rule.testcase", "titleI18nKey":"menu.engine.rule.testcase", "icon":"bi-suitcase-lg", "routeName":"route.engine.rule.testcase"},
/*/*/
{"type":"ROUTE", "order":800, "parentId":"menu.engine.rule", "id":"menu.engine.rule.migration", "titleI18nKey":"menu.engine.rule.migration", "icon":"bi-share", "routeName":"route.engine.rule.migration"}
]

10
io.sc.engine.rule.frontend/src/routes/routes.json

@ -91,17 +91,17 @@
}
},
{
"name": "route.engine.rule.functions",
"path": "re/functions",
"name": "route.engine.rule.function",
"path": "re/function",
"parent": "/",
"priority": 0,
"module": "io.sc.engine.rule.frontend",
"component": "component.engine.rule.functions",
"componentPath": "@/views/functions/Functions.vue",
"component": "component.engine.rule.function",
"componentPath": "@/views/function/Function.vue",
"redirect": null,
"meta": {
"permissions": [
"/re/functions/**/*"
"/re/function/**/*"
]
}
},

148
io.sc.engine.rule.frontend/src/views/function/Function.vue

@ -0,0 +1,148 @@
<template>
<div style="height: 100%">
<w-grid
ref="gridRef"
:title="$t('re.function.grid.title')"
:config-button="true"
db-click-operation="edit"
selection="multiple"
:checkbox-selection="false"
:data-url="Environment.apiContextPath('/api/re/function')"
:pageable="false"
:sort-by="['-lastModifyDate']"
:toolbar-configure="{ noIcon: false }"
:toolbar-actions="[
'refresh',
'separator',
'add',
'edit',
'remove',
'separator',
'view',
'separator',
{
name: 'import',
label: $t('import'),
icon: 'file_upload',
click: () => {
importDialogRef.open();
},
},
{
extend: 'export',
click: (arg) => {
Downloader.get(Environment.apiContextPath('/api/re/function/export'));
},
},
]"
:columns="[
{ width: 70, name: 'enable', label: $t('enable'), format: Formater.enableTag(), align: 'center', sortable: false },
{ width: 120, name: 'name', label: $t('name') },
{ width: 150, name: 'signature', label: $t('re.function.grid.entity.signature') },
{ width: 200, name: 'description', label: $t('description') },
{
width: 230,
name: 'mathXml',
label: $t('re.function.grid.entity.mathXml'),
format: (value) => {
return {
componentType: 'w-math',
attrs: {
modelValue: value,
readOnly: true,
zoom: 2,
},
};
},
},
{
width: '100%',
name: 'body',
label: $t('re.function.grid.entity.body'),
format: (value) => {
return {
componentType: 'w-code-mirror',
attrs: {
modelValue: value,
readOnly: true,
toolbar: false,
rows: 3,
lang: 'java',
},
};
},
},
]"
:editor="{
dialog: {
width: '900px',
},
form: {
colsNum: 1,
fields: [
{ name: 'name', label: $t('name'), type: 'text', required: true },
{ name: 'signature', label: $t('re.function.grid.entity.signature'), type: 'text', required: true },
{ name: 'description', label: $t('description'), type: 'text' },
{
name: 'mathXml',
label: $t('re.function.grid.entity.mathXml'),
type: 'code-mirror',
rows: 5,
lang: 'xml',
toolbar: false,
defaultValue: `<mrow>
<mo>函数名</mo>
<mi>(</mi>
<mspace></mspace>
<mi>参数1</mi>
<mspace></mspace>
<mo>,</mo>
<mspace></mspace>
<mi>参数2</mi>
<mspace></mspace>
<mi>)</mi>
</mrow>`,
},
{ name: 'body', label: $t('re.function.grid.entity.body'), type: 'code-mirror', rows: 10, lang: 'java', toolbar: false },
{ name: 'enable', label: $t('enable'), type: 'checkbox', defaultValue: true },
],
},
}"
:viewer="{
panel: {
columnNum: 1,
fields: [
{ name: 'id', label: $t('id') },
{ name: 'name', label: $t('name') },
{ name: 'signature', label: $t('re.function.grid.entity.signature') },
{ name: 'description', label: $t('description') },
{ name: 'mathXml', label: $t('re.function.grid.entity.mathXml') },
{ name: 'body', label: $t('re.function.grid.entity.body'), format: Formater.none() },
{ name: 'enable', label: $t('enable'), format: Formater.none() },
{ name: 'dataComeFrom', label: $t('dataComeFrom') },
{ name: 'creator', label: $t('creator') },
{ name: 'createDate', label: $t('createDate') },
{ name: 'lastModifier', label: $t('lastModifier') },
{ name: 'lastModifyDate', label: $t('lastModifyDate'), format: Formater.none() },
],
},
}"
></w-grid>
<ImportDialog
ref="importDialogRef"
@after-imported="
() => {
gridRef.refresh();
}
"
></ImportDialog>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Environment, Formater, Downloader } from 'platform-core';
import ImportDialog from './ImportDialog.vue';
const gridRef = ref();
const importDialogRef = ref();
</script>

72
io.sc.engine.rule.frontend/src/views/function/ImportDialog.vue

@ -0,0 +1,72 @@
<template>
<w-dialog ref="dialogRef" :title="$t('re.function.import.dialog.title')" width="600px" :can-maximize="false">
<q-form action="post">
<div class="row py-1">
<div class="col-1"></div>
<div class="col-10">
<q-file ref="fileRef" v-model="modelValue.file" :label="$t('file.single.tip')" dense outlined clearable counter accept=".json">
<template #prepend>
<q-icon name="cloud_upload" />
</template>
</q-file>
</div>
<div class="col-1"></div>
</div>
<div class="row py-1">
<div class="col-1"></div>
<div class="col-10 row justify-center q-gutter-md py-2">
<q-btn icon="bi-database-up" :label="$t('import')" color="primary" @click="importData"></q-btn>
</div>
<div class="col-1"></div>
</div>
</q-form>
</w-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { axios, Environment } from 'platform-core';
const emit = defineEmits<{
(e: 'afterImported', evt: Event): void;
}>();
const dialogRef = ref();
const modelValue = reactive({
file: undefined,
});
const fileRef = ref();
const importData = () => {
axios
.post(
Environment.apiContextPath('/api/re/function/import'),
{
file: fileRef.value.nativeEl.files[0],
},
{
loading: true,
headers: {
'Content-Type': 'multipart/form-data',
},
},
)
.then(() => {
close();
emit('afterImported');
});
};
const open = () => {
modelValue.file = undefined;
dialogRef.value.show();
};
const close = () => {
dialogRef.value.hide();
};
defineExpose({
open,
close,
});
</script>

4
io.sc.engine.rule.frontend/src/views/functions/Functions.vue

@ -1,4 +0,0 @@
<template>
<div>functions</div>
</template>
<script setup lang="ts"></script>

2
io.sc.engine.rule.frontend/src/views/lib/ImportDialog.vue

@ -39,7 +39,7 @@ const fileRef = ref();
const importData = () => {
axios
.post(
Environment.apiContextPath('/api/re/lib/import'),
Environment.apiContextPath('/api/re/function/import'),
{
file: fileRef.value.nativeEl.files[0],
},

4
io.sc.engine.rule.frontend/src/views/lib/IndicatorGrid.vue

@ -124,8 +124,8 @@
return result;
},
},
{ width: 80, name: 'valueTypeIsList', label: $t('re.resources.designer.parameter.grid.entity.valueTypeIsList'), format: Formater.checkTag() },
{ width: '100%', name: 'defaultValue', label: $t('defaultValue') },
{ width: 90, name: 'valueTypeIsList', label: $t('re.resources.designer.parameter.grid.entity.valueTypeIsList'), format: Formater.checkTag() },
{ width: 100, name: 'defaultValue', label: $t('defaultValue') },
]"
:editor="{
dialog: {

6
io.sc.engine.rule.frontend/src/views/lib/Lib.vue

@ -32,7 +32,7 @@
</q-tabs>
<q-tab-panels v-model="currentSelectedIndicatorOrTestcaseTabNameRef" animated style="height: calc(100% - 48px)">
<q-tab-panel name="indicator" class="px-0 py-0" style="height: 100%; padding-left: 0px; padding-right: 0px; padding-bottom: 0px">
<q-splitter :model-value="indicatorSplitterRef" unit="px" separator-style="width: 3px;" horizontal class="w-full" style="height: 100%">
<q-splitter :model-value="indicatorSplitterRef" unit="%" separator-style="height: 3px;" horizontal style="height: 100%">
<template #before>
<IndicatorGrid
ref="indicatorGridRef"
@ -80,7 +80,7 @@
</q-splitter>
</q-tab-panel>
<q-tab-panel name="testcase" class="px-0 pb-0" style="height: 100%; padding-left: 0px; padding-right: 0px; padding-bottom: 0px">
<q-splitter :model-value="testcaseSplitterRef" unit="px" separator-style="width: 3px;" horizontal class="w-full" style="height: 100%">
<q-splitter :model-value="testcaseSplitterRef" unit="px" separator-style="height: 3px;" horizontal style="height: 100%">
<template #before>
<TestCase
ref="testcaseGridRef"
@ -118,7 +118,7 @@ import ProcessorGrid from './ProcessorGrid.vue';
import TestCase from './TestCase.vue';
import TestCaseParameter from './TestCaseParameter.vue';
const indicatorSplitterRef = ref(300); //
const indicatorSplitterRef = ref(60); //
const testcaseSplitterRef = ref(300); //
const libTreeGridRef = ref(); //
const indicatorGridRef = ref(); //

28
io.sc.engine.rule.frontend/src/views/lib/ProcessorGrid.vue

@ -214,6 +214,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
},
};
//return PlaceHolder.replace(row.arithmetic);
@ -316,6 +317,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'OBJECT_PROPERTIES' === arg.form.getFieldValue('type');
},
@ -436,6 +438,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
},
],
},
@ -470,6 +473,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'ARITHMETIC' === arg.form.getFieldValue('type');
},
@ -485,6 +489,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'TERNARY' === arg.form.getFieldValue('type');
},
@ -500,6 +505,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'TERNARY' === arg.form.getFieldValue('type');
},
@ -515,6 +521,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'TERNARY' === arg.form.getFieldValue('type');
},
@ -530,6 +537,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'WHEN_THEN' === arg.form.getFieldValue('type');
},
@ -545,6 +553,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'WHEN_THEN' === arg.form.getFieldValue('type');
},
@ -572,6 +581,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
},
{
colSpan: 5,
@ -764,6 +774,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
},
{
name: 'value',
@ -775,6 +786,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
},
],
},
@ -803,6 +815,7 @@
lineBreak: true,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'GROOVY_SCRIPT' === arg.form.getFieldValue('type');
},
@ -925,6 +938,7 @@
rows: 1,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
},
{
name: 'value',
@ -1281,6 +1295,7 @@ const props = defineProps({
const gridRef = ref();
const objectPropertiesMatcherDialogRef = ref();
const dsOptionsRef = ref([]);
const userDefinedFunctionsRef = ref();
const sqlQueryResultFieldsRef = ref([]);
const indicatorOptionsRef = ref();
const autoCompletionManager = new AutoCompletionManager();
@ -1310,4 +1325,17 @@ defineExpose({
});
const Enums = await EnumTools.fetch(['io.sc.engine.rule.core.enums.ProcessorType']);
axios.get(Environment.apiContextPath('/api/re/function?pageable=false')).then((response) => {
const options = [];
const items = response.data?.content;
if (items && items.length > 0) {
for (const item of items) {
if (item.enable) {
options.push(item);
}
}
}
userDefinedFunctionsRef.value = options;
});
</script>

8
io.sc.engine.rule.frontend/src/views/resources/designer/DesignerDialog.vue

@ -56,7 +56,7 @@
</template>
<template #after>
<q-splitter v-model="horizontalSplitterRef" unit="px" separator-style="height: 3px" horizontal style="height: 100%">
<q-splitter v-model="horizontalSplitterRef" unit="%" separator-style="height: 3px" horizontal style="height: 100%">
<template #before>
<q-tabs
v-model="statusReactive.parameterAndTestcaseTab"
@ -66,9 +66,9 @@
@update:model-value="
(value) => {
if (value === 'testcase') {
horizontalSplitterRef = 300;
horizontalSplitterRef = 40;
} else {
horizontalSplitterRef = 500;
horizontalSplitterRef = 60;
}
}
"
@ -196,7 +196,7 @@ const props = defineProps({
});
const dialogRef = ref();
const horizontalSplitterRef = ref(400);
const horizontalSplitterRef = ref(60);
const verticalSplitterRef = ref(400);
const readOnlyRef = ref(false);

28
io.sc.engine.rule.frontend/src/views/resources/designer/Processor.vue

@ -449,6 +449,7 @@
lineWrap: true,
lineBreak: false,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'OBJECT_PROPERTIES' === arg.form.getFieldValue('type');
},
@ -597,6 +598,7 @@
label: $t('re.resources.designer.processor.grid.entity.mathFormula'),
type: 'math',
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'MATH_FORMULA' === arg.form.getFieldValue('type');
},
@ -612,6 +614,7 @@
lineWrap: true,
lineBreak: false,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'ARITHMETIC' === arg.form.getFieldValue('type');
},
@ -627,6 +630,7 @@
lineWrap: true,
lineBreak: false,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'TERNARY' === arg.form.getFieldValue('type');
},
@ -642,6 +646,7 @@
lineWrap: true,
lineBreak: false,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'TERNARY' === arg.form.getFieldValue('type');
},
@ -657,6 +662,7 @@
lineWrap: true,
lineBreak: false,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'TERNARY' === arg.form.getFieldValue('type');
},
@ -672,6 +678,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'WHEN_THEN' === arg.form.getFieldValue('type');
},
@ -687,6 +694,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'WHEN_THEN' === arg.form.getFieldValue('type');
},
@ -714,6 +722,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
},
{
colSpan: 5,
@ -909,6 +918,7 @@
lineWrap: true,
lineBreak: false,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
},
{
name: 'value',
@ -920,6 +930,7 @@
lineBreak: false,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
},
],
},
@ -1006,6 +1017,7 @@
lineBreak: true,
placeholder: true,
autoCompletion: autoCompletion,
userDefinedFunctions: userDefinedFunctionsRef,
showIf: (arg) => {
return 'GROOVY_SCRIPT' === arg.form.getFieldValue('type');
},
@ -1456,7 +1468,7 @@
} else if ('SQL' === type || 'OBJECT_PROPERTIES' === type) {
editorDialogWidthRef = '80%';
} else {
editorDialogWidthRef = '60%';
editorDialogWidthRef = '70%';
}
if ('OBJECT_PROPERTIES' === type) {
@ -1537,6 +1549,7 @@ const optionOptionsRef = ref([]);
const dsOptionsRef = ref([]);
const sqlQueryResultFieldsRef = ref([]);
const parameterOptionsRef = ref();
const userDefinedFunctionsRef = ref();
const editorDialogWidthRef = ref('50%');
const decisionTreeDialogRef = ref();
const executionFlowDialogRef = ref();
@ -1578,4 +1591,17 @@ axios.get(Environment.apiContextPath('/api/system/datasource')).then((response)
dsOptionsRef.value = options;
}
});
axios.get(Environment.apiContextPath('/api/re/function?pageable=false')).then((response) => {
const options = [];
const items = response.data?.content;
if (items && items.length > 0) {
for (const item of items) {
if (item.enable) {
options.push(item);
}
}
}
userDefinedFunctionsRef.value = options;
});
</script>

1
io.sc.engine.rule.server/build.gradle

@ -17,6 +17,7 @@ dependencies {
"org.webjars:org.webjars.mxgraph:3.9.12",
"org.webjars:org.webjars.jquery:1.12.4",
"org.webjars:org.webjars.mathfonts:1.0.0",
"org.webjars:org.webjars.mathcss:1.0.0",
)
}

69
io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/controller/FunctionWebController.java

@ -0,0 +1,69 @@
package io.sc.engine.rule.server.function.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import io.sc.engine.rule.core.po.function.Function;
import io.sc.engine.rule.server.function.converter.FunctionEntityConverter;
import io.sc.engine.rule.server.function.entity.FunctionEntity;
import io.sc.engine.rule.server.function.repository.FunctionRepository;
import io.sc.engine.rule.server.function.service.FunctionService;
import io.sc.engine.rule.server.function.vo.FunctionVo;
import io.sc.engine.rule.server.lib.converter.LibEntityConverter;
import io.sc.platform.core.util.ObjectMapper4Json;
import io.sc.platform.mvc.controller.support.RestCrudController;
import io.sc.platform.mvc.support.FileDownloader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
/**
* 自定义函数管理器 Controller
*
*/
@RestController("io.sc.engine.rule.server.function.controller.FunctionWebController")
@RequestMapping("/api/re/function")
public class FunctionWebController extends RestCrudController<FunctionVo, FunctionEntity,String, FunctionRepository, FunctionService> {
private static final Logger log = LoggerFactory.getLogger(FunctionWebController.class);
@RequestMapping(value="import",method= {RequestMethod.POST})
@ResponseBody
public void imports(@RequestParam(name="file",required=false) MultipartFile multipartFile, Locale locale) throws Exception{
if(multipartFile!=null && !multipartFile.isEmpty()) {
List<Function> functions =null;
//解析文件
try {
functions = ObjectMapper4Json.getMapper().readValue(multipartFile.getInputStream(), new TypeReference<List<Function>>(){});
} catch (Exception e) {
log.error("",e);
throw e;
}
//导入
try {
service.imports(FunctionEntityConverter.fromPo(functions));
}catch (Exception e) {
log.error("",e);
throw e;
}
}
}
@GetMapping(value="export")
@ResponseBody
public void exports(HttpServletRequest request, HttpServletResponse response) throws Exception{
List<Function> pos =service.exports();
if(pos!=null) {
String json = ObjectMapper4Json.getMapper().writeValueAsString(pos);
InputStream in =new ByteArrayInputStream(json.getBytes("UTF-8"));
FileDownloader.download(request, response, "UserDefinedFunctions.json", in);
}
}
}

84
io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/converter/FunctionEntityConverter.java

@ -0,0 +1,84 @@
package io.sc.engine.rule.server.function.converter;
import io.sc.engine.rule.core.po.function.Function;
import io.sc.engine.rule.server.function.entity.FunctionEntity;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义函数实体转换器用于实现持久化实体和PO对象之间的互相转换
*/
public class FunctionEntityConverter {
/**
* 将实体转换成 PO 对象
* @param entity 实体对象
* @return PO 对象
*/
public static Function toPo(FunctionEntity entity) {
if(entity!=null) {
Function po =new Function();
po.setId(entity.getId());
po.setName(entity.getName());
po.setSignature(entity.getSignature());
po.setDescription(entity.getDescription());
po.setEnable(entity.getEnable());
po.setMathXml(entity.getMathXml());
po.setBody(entity.getBody());
return po;
}
return null;
}
/**
* 将实体集合转换成 PO 对象集合
* @param entities 实体集合
* @return PO 对象集合
*/
public static List<Function> toPo(List<? extends FunctionEntity> entities){
if(entities!=null && entities.size()>0) {
List<Function> pos =new ArrayList<>(entities.size());
for(FunctionEntity entity : entities) {
pos.add(toPo(entity));
}
return pos;
}
return null;
}
/**
* PO 对象转换成实体对象
* @param po PO 对象
* @return 实体对象
*/
public static FunctionEntity fromPo(Function po) {
if(po!=null) {
FunctionEntity entity =new FunctionEntity();
entity.setId(po.getId());
entity.setName(po.getName());
entity.setSignature(po.getSignature());
entity.setDescription(po.getDescription());
entity.setEnable(po.getEnable());
entity.setMathXml(po.getMathXml());
entity.setBody(po.getBody());
return entity;
}
return null;
}
/**
* PO 对象集合转换成实体对象集合
* @param pos PO 对象集合
* @return 实体对象集合
*/
public static List<FunctionEntity> fromPo(List<Function> pos){
if(pos!=null && pos.size()>0) {
List<FunctionEntity> entities =new ArrayList<>(pos.size());
for(Function po : pos) {
entities.add(fromPo(po));
}
return entities;
}
return null;
}
}

163
io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/entity/FunctionEntity.java

@ -0,0 +1,163 @@
package io.sc.engine.rule.server.function.entity;
import io.sc.engine.rule.server.function.vo.FunctionVo;
import io.sc.platform.orm.DeepClone;
import io.sc.platform.orm.IdClearable;
import io.sc.platform.orm.converter.NumericBooleanConverter;
import io.sc.platform.orm.entity.AuditorEntity;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.beans.BeanUtils;
import javax.persistence.*;
import javax.validation.constraints.Size;
/**
* 用户自定义函数实体类
*/
@Entity(name="io.sc.engine.rule.server.function.entity.FunctionEntity")
@Table(name="RE_FUNCTION")
public class FunctionEntity extends AuditorEntity<FunctionVo> implements DeepClone, IdClearable {
//ID,主键
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid2")
@Column(name="ID_", length=36)
@Size(max=36)
protected String id;
//名称
@Column(name="NAME_", length=255)
@Size(max=255)
protected String name;
//描述
@Column(name="DESCRIPTION_", length=255)
@Size(max=255)
protected String description;
//是否可用
@Column(name="ENABLE_")
@Convert(converter=NumericBooleanConverter.class)
private Boolean enable;
//函数签名
@Column(name="SIGNATURE_", length=255)
@Size(max=255)
private String signature;
//math xml
@Column(name="MATH_XML_", length=255)
@Size(max=255)
private String mathXml;
// 函数体
@Column(name="BODY_")
private String body;
@Override
public FunctionVo toVo() {
FunctionVo vo =new FunctionVo();
super.toVo(vo);
vo.setId(this.getId());
vo.setName(this.getName());
vo.setSignature(this.getSignature());
vo.setDescription(this.getDescription());
vo.setEnable(this.getEnable());
vo.setMathXml(this.getMathXml());
vo.setBody(this.getBody());
return vo;
}
public @Size(max = 36) String getId() {
return id;
}
public void setId(@Size(max = 36) String id) {
this.id = id;
}
public @Size(max = 255) String getName() {
return name;
}
public void setName(@Size(max = 255) String name) {
this.name = name;
}
public @Size(max = 255) String getSignature() {
return signature;
}
public void setSignature(@Size(max = 255) String signature) {
this.signature = signature;
}
public @Size(max = 255) String getDescription() {
return description;
}
public void setDescription(@Size(max = 255) String description) {
this.description = description;
}
public Boolean getEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
public @Size(max = 255) String getMathXml() {
return mathXml;
}
public void setMathXml(@Size(max = 255) String mathXml) {
this.mathXml = mathXml;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
@Override
public Object deepClone() throws Exception {
FunctionEntity entity =new FunctionEntity();
BeanUtils.copyProperties(this, entity);
return entity;
}
@Override
public void clearId() {
this.setId(null);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
FunctionEntity other = (FunctionEntity) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}

10
io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/repository/FunctionRepository.java

@ -0,0 +1,10 @@
package io.sc.engine.rule.server.function.repository;
import io.sc.engine.rule.server.function.entity.FunctionEntity;
import io.sc.platform.orm.repository.DaoRepository;
import org.springframework.stereotype.Service;
@Service("io.sc.engine.rule.server.function.repository.FunctionRepository")
public interface FunctionRepository extends DaoRepository<FunctionEntity,String>{
}

18
io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/service/FunctionService.java

@ -0,0 +1,18 @@
package io.sc.engine.rule.server.function.service;
import io.sc.engine.rule.core.po.function.Function;
import io.sc.engine.rule.core.po.lib.Lib;
import io.sc.engine.rule.server.function.entity.FunctionEntity;
import io.sc.engine.rule.server.function.repository.FunctionRepository;
import io.sc.engine.rule.server.lib.entity.LibEntity;
import io.sc.platform.orm.service.DaoService;
import java.util.List;
/**
* 自定义函数服务接口
*/
public interface FunctionService extends DaoService<FunctionEntity, String, FunctionRepository>{
public void imports(List<FunctionEntity> entities) throws Exception;
public List<Function> exports() throws Exception;
}

37
io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/service/impl/FunctionServiceImpl.java

@ -0,0 +1,37 @@
package io.sc.engine.rule.server.function.service.impl;
import io.sc.engine.rule.core.po.function.Function;
import io.sc.engine.rule.server.function.converter.FunctionEntityConverter;
import io.sc.engine.rule.server.function.entity.FunctionEntity;
import io.sc.engine.rule.server.function.repository.FunctionRepository;
import io.sc.engine.rule.server.function.service.FunctionService;
import io.sc.platform.orm.service.impl.DaoServiceImpl;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.Collections;
import java.util.List;
@Service("io.sc.engine.rule.server.function.service.impl.FunctionServiceImpl")
public class FunctionServiceImpl extends DaoServiceImpl<FunctionEntity, String, FunctionRepository> implements FunctionService {
@Override
@Transactional
public void imports(List<FunctionEntity> entities) throws Exception {
if(entities==null || entities.isEmpty()){
return;
}
for(FunctionEntity entity : entities){
entity.clearId();
repository.save(entity);
}
}
@Override
public List<Function> exports() throws Exception {
List<FunctionEntity> entities =repository.findAll();
if(entities==null || entities.isEmpty()) {
return Collections.emptyList();
}
return FunctionEntityConverter.toPo(entities);
}
}

69
io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/vo/FunctionVo.java

@ -0,0 +1,69 @@
package io.sc.engine.rule.server.function.vo;
import io.sc.platform.orm.api.vo.AuditorVo;
public class FunctionVo extends AuditorVo {
protected String id;
protected String name;
protected String signature;
protected String description;
private Boolean enable;
private String mathXml;
private String body;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
public String getMathXml() {
return mathXml;
}
public void setMathXml(String mathXml) {
this.mathXml = mathXml;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}

12
io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/plugins/item/ResourceExampleItem.java

@ -10,6 +10,7 @@ public class ResourceExampleItem {
protected String file; //资源示例文件URL
protected String dictionary; //所需数据字典示例文件URL
protected String lib; //所需指标库示例文件URL
protected String function; //所需自定义函数示例文件URL
//附加属性
private String configurationFileUrl; //贡献项配置文件位置URL
@ -110,6 +111,14 @@ public class ResourceExampleItem {
this.lib = lib;
}
public String getFunction() {
return function;
}
public void setFunction(String function) {
this.function = function;
}
/**
* 获取贡献项配置文件位置URL
* @return 贡献项配置文件位置URL
@ -133,6 +142,9 @@ public class ResourceExampleItem {
+ ", parentId=" + parentId
+ ", order=" + order
+ ", file=" + file
+ ", dictionary=" + dictionary
+ ", lib=" + lib
+ ", function=" + function
+ ", configurationFileUrl=" + configurationFileUrl
+ "]";
}

25
io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/resource/service/impl/ResourceServiceImpl.java

@ -1,5 +1,6 @@
package io.sc.engine.rule.server.resource.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import io.sc.engine.rule.core.classes.ResourceAbstract;
import io.sc.engine.rule.core.code.impl.support.ResourceWrapper;
import io.sc.engine.rule.core.code.impl.support.ResourceWrapper4Lib;
@ -8,12 +9,16 @@ import io.sc.engine.rule.core.enums.DeployStatus;
import io.sc.engine.rule.core.enums.ExecuteMode;
import io.sc.engine.rule.core.enums.ModelCategory;
import io.sc.engine.rule.core.po.dictionary.Dictionary;
import io.sc.engine.rule.core.po.function.Function;
import io.sc.engine.rule.core.po.lib.Lib;
import io.sc.engine.rule.core.po.resource.Resource;
import io.sc.engine.rule.core.util.Strings;
import io.sc.engine.rule.server.dictionary.converter.DictionaryEntityConverter;
import io.sc.engine.rule.server.dictionary.entity.ReleasableDictionaryEntity;
import io.sc.engine.rule.server.dictionary.service.DictionaryService;
import io.sc.engine.rule.server.function.entity.FunctionEntity;
import io.sc.engine.rule.server.function.service.FunctionService;
import io.sc.engine.rule.server.function.converter.FunctionEntityConverter;
import io.sc.engine.rule.server.lib.converter.LibEntityConverter;
import io.sc.engine.rule.server.lib.entity.LibEntity;
import io.sc.engine.rule.server.lib.entity.ReleasableLibEntity;
@ -44,13 +49,11 @@ import io.sc.platform.mvc.service.SystemParameterService;
import io.sc.platform.orm.entity.support.EntityChangedEventType;
import io.sc.platform.orm.service.impl.DaoServiceImpl;
import io.sc.platform.orm.service.support.QueryParameter;
import io.sc.platform.orm.service.support.QueryResult;
import io.sc.platform.orm.service.support.criteria.impl.InSet;
import io.sc.platform.orm.util.EntityVoUtil;
import io.sc.platform.security.util.SecurityUtil;
import io.sc.platform.system.role.jpa.entity.RoleEntity;
import io.sc.platform.system.role.service.RoleService;
import io.sc.platform.system.user.jpa.entity.UserEntity;
import org.flowable.engine.ProcessEngine;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
@ -78,6 +81,8 @@ public class ResourceServiceImpl extends DaoServiceImpl<ResourceEntity, String,
@Autowired
private DictionaryService dictionaryService;
@Autowired
private FunctionService functionService;
@Autowired
private LibService libService;
@Autowired
private ResourceQueryService resourceQueryService;
@ -340,17 +345,20 @@ public class ResourceServiceImpl extends DaoServiceImpl<ResourceEntity, String,
@Override
public ResourceWrapper getDefineById(String resourceId) throws Exception {
if(StringUtils.hasText(resourceId)) {
//获取自定义函数
List<FunctionEntity> functionEntities =functionService.getRepository().findAll();
//先找资源
ResourceEntity resourceEntity =this.findById(resourceId);
if(resourceEntity!=null) {
List<ReleasableLibEntity> libEntities =libService.findReleasableLibEntitiesByResourceId(resourceEntity.getId());
List<ReleasableDictionaryEntity> dictionaryEntities =dictionaryService.findReleasableDictionaryEntitiesByResourceId(resourceEntity.getId());
ResourceWrapper4Resource wrapper =new ResourceWrapper4Resource();
wrapper.setResource(ResourceEntityConverter.toPo(resourceEntity,false));
wrapper.getResource().setTestCases(null);//在获取定义时,不返回测试用例
wrapper.setDictionaries(DictionaryEntityConverter.toPo(dictionaryEntities));
wrapper.setLibs(LibEntityConverter.toPo(libEntities));
wrapper.setFunctions(FunctionEntityConverter.toPo(functionEntities));
return wrapper;
}
//再找指标库
@ -361,6 +369,7 @@ public class ResourceServiceImpl extends DaoServiceImpl<ResourceEntity, String,
wrapper.setDictionaries(DictionaryEntityConverter.toPo(dictionaryEntities));
wrapper.setLib(LibEntityConverter.toPo(libEntity));
wrapper.getLib().setTestCases(null);
wrapper.setFunctions(FunctionEntityConverter.toPo(functionEntities));
return wrapper;
}
}
@ -529,6 +538,16 @@ public class ResourceServiceImpl extends DaoServiceImpl<ResourceEntity, String,
libService.imports(LibEntityConverter.fromPo(po));
}
}
//导入所需的用户自定义函数
String function =item.getFunction();
if(StringUtils.hasText(function)) {
org.springframework.core.io.Resource rs =new DefaultResourceLoader().getResource(function);
if(rs!=null && rs.exists()) {
List<Function> functions = ObjectMapper4Json.getMapper().readValue(rs.getInputStream(), new TypeReference<List<Function>>(){});
functionService.imports(FunctionEntityConverter.fromPo(functions));
}
}
}
}
}

44
io.sc.engine.rule.server/src/main/resources/META-INF/platform/plugins/components.json

@ -9,35 +9,41 @@
{
"includes":[
"io.sc.engine.rule.server.jpa.listener",
"io.sc.engine.rule.server.jpa.listener.handler",
"io.sc.engine.rule.server.service.impl",
"io.sc.engine.rule.server.validator.service",
"io.sc.engine.rule.server.common.bean",
"io.sc.engine.rule.server.common.controller",
"io.sc.engine.rule.server.common.initializer",
"io.sc.engine.rule.server.common.service.impl",
"io.sc.engine.rule.server.asciidoc.service.impl",
"io.sc.engine.rule.server.dictionary.controller",
"io.sc.engine.rule.server.lib.controller",
"io.sc.engine.rule.server.model.controller",
"io.sc.engine.rule.server.resource.controller",
"io.sc.engine.rule.server.scorecard.controller",
"io.sc.engine.rule.server.testcase.controller",
"io.sc.engine.rule.server.migration.controller",
"io.sc.engine.rule.server.dictionary.service.impl",
"io.sc.engine.rule.server.function.controller",
"io.sc.engine.rule.server.function.service.impl",
"io.sc.engine.rule.server.lib.controller",
"io.sc.engine.rule.server.lib.service.impl",
"io.sc.engine.rule.server.migration.service.impl",
"io.sc.engine.rule.server.model.controller",
"io.sc.engine.rule.server.model.service.impl",
"io.sc.engine.rule.server.resource.controller",
"io.sc.engine.rule.server.resource.service.impl",
"io.sc.engine.rule.server.scorecard.controller",
"io.sc.engine.rule.server.scorecard.service.impl",
"io.sc.engine.rule.server.service.impl",
"io.sc.engine.rule.server.testcase.controller",
"io.sc.engine.rule.server.testcase.service.impl",
"io.sc.engine.rule.server.validator.service",
"io.sc.engine.rule.server.common.bean",
"io.sc.engine.rule.server.common.initializer",
"io.sc.engine.rule.server.testcase.bean",
"io.sc.engine.rule.server.jpa.listener",
"io.sc.engine.rule.server.jpa.listener.handler",
"io.sc.engine.rule.server.migration.controller",
"io.sc.engine.rule.server.migration.service.impl",
"io.sc.engine.rule.server.workflow.controller",
"io.sc.engine.rule.server.workflow.listener",
"io.sc.engine.rule.server.workflow.service.impl"

3
io.sc.engine.rule.server/src/main/resources/META-INF/platform/plugins/repositories.json

@ -12,6 +12,9 @@
"io.sc.engine.rule.server.dictionary.entity",
"io.sc.engine.rule.server.dictionary.repository",
"io.sc.engine.rule.server.function.entity",
"io.sc.engine.rule.server.function.repository",
"io.sc.engine.rule.server.lib.entity",
"io.sc.engine.rule.server.lib.entity.processor",
"io.sc.engine.rule.server.lib.repository",

3
io.sc.engine.rule.server/src/main/resources/META-INF/platform/plugins/rule-engine-sample-resource.json

@ -7,6 +7,7 @@
"parentId" :"re.engine.sample.resource.engine",
"id" :"re.engine.sample.resource.engine.base",
"file" :"classpath:/io/sc/engine/rule/server/sample/引擎示例.json",
"dictionary":"classpath:/io/sc/engine/rule/server/sample/引擎内置示例(数据字典).json"
"dictionary":"classpath:/io/sc/engine/rule/server/sample/引擎内置示例(数据字典).json",
"function" :"classpath:/io/sc/engine/rule/server/sample/用户自定义函数.json"
}
]

9
io.sc.engine.rule.server/src/main/resources/io/sc/engine/rule/server/sample/用户自定义函数.json

@ -0,0 +1,9 @@
[ {
"id" : "a5602f84-60b6-4bc0-bede-0984a0fa23ba",
"name" : "DIV",
"signature" : "DIV(a,b)",
"description" : "特殊除法, a 除以 b, 用于处理分母为零",
"enable" : false,
"mathXml" : "<mspace></mspace>\n<mrow>\n <mo>DIV</mo>\n <mi>(</mi>\n <mspace></mspace>\n <mi>a</mi>\n <mspace></mspace>\n <mo>,</mo>\n <mspace></mspace>\n <mi>b</mi>\n <mspace></mspace>\n <mi>)</mi>\n</mrow>\n<mspace></mspace>",
"body" : "def DIV(Double a, Double b){\n if(b==0){\n if(a>0){\n return 999;\n }else if(a<0){\n return -999;\n }else{\n return 0;\n }\n }\n}"
} ]

31
io.sc.engine.rule.server/src/main/resources/liquibase/RE_1.0.0_20220515__Rule Engine Database Schema DDL.xml

@ -657,5 +657,36 @@
baseColumnNames="TEST_CASE_ID_"
referencedTableName="RE_TEST_CASE"
referencedColumnNames="ID_" onDelete="CASCADE"/>
<!-- 自定义函数表 -->
<createTable tableName="RE_FUNCTION" remarks="自定义函数表">
<column name="ID_" type="NVARCHAR(36)" remarks="ID_">
<constraints primaryKey="true"/>
</column>
<column name="NAME_" type="NVARCHAR(255)" remarks="名称"/>
<column name="SIGNATURE_" type="NVARCHAR(255)" remarks="函数签名"/>
<column name="DESCRIPTION_" type="NVARCHAR(255)" remarks="描述"/>
<column name="ENABLE_" type="SMALLINT" remarks="是否可用"/>
<column name="MATH_XML_" type="NVARCHAR(512)" remarks="数学公式的 matchml"/>
<column name="BODY_" type="CLOB" remarks="函数体"/>
<!-- 审计字段 -->
<column name="JPA_VERSION_" type="INTEGER" remarks="JPA乐观锁版本"/>
<column name="DATA_COME_FROM_" type="NVARCHAR(10)" remarks="数据来源(INPUT:手工录入,IMPORT:系统自动导入)"/>
<column name="CREATOR_" type="NVARCHAR(255)" remarks="创建人"/>
<column name="CREATE_DATE_" type="DATETIME" remarks="创建日期"/>
<column name="LAST_MODIFIER_" type="NVARCHAR(255)" remarks="最后修改人"/>
<column name="LAST_MODIFYDATE_" type="DATETIME" remarks="最后修改日期"/>
<column name="CORP_CODE_" type="NVARCHAR(255)" remarks="所属法人代码"/>
</createTable>
<addUniqueConstraint tableName="RE_FUNCTION" columnNames="NAME_,CORP_CODE_"></addUniqueConstraint>
<addNotNullConstraint columnName="NAME_" columnDataType="NVARCHAR(255)" tableName="RE_FUNCTION" constraintName="CONST_RE_FUNCTION_NAME"/>
<addNotNullConstraint columnName="SIGNATURE_" columnDataType="NVARCHAR(255)" tableName="RE_FUNCTION" constraintName="CONST_RE_FUNCTION_SIGNATURE"/>
<addNotNullConstraint columnName="ENABLE_" columnDataType="SMALLINT" tableName="RE_FUNCTION" constraintName="CONST_RE_FUNCTION_ENABLE"/>
<addNotNullConstraint columnName="DATA_COME_FROM_" columnDataType="NVARCHAR(10)" tableName="RE_FUNCTION" constraintName="CONST_RE_FUNCTION_DCF"/>
<addNotNullConstraint columnName="CORP_CODE_" columnDataType="NVARCHAR(255)" tableName="RE_FUNCTION" constraintName="CONST_RE_FUNCTION_CORP"/>
<addDefaultValue columnName="ENABLE_" columnDataType="SMALLINT" tableName="RE_FUNCTION" defaultValueNumeric="1"/>
<addDefaultValue columnName="DATA_COME_FROM_" columnDataType="NVARCHAR(10)" tableName="RE_FUNCTION" defaultValue="INPUT"/>
<addDefaultValue columnName="CORP_CODE_" columnDataType="NVARCHAR(255)" tableName="RE_FUNCTION" defaultValue="_PRIMARY_"/>
</changeSet>
</databaseChangeLog>

2
io.sc.engine.st.frontend/package.json

@ -92,7 +92,7 @@
"luckyexcel": "1.0.1",
"mockjs": "1.1.0",
"pinia": "2.1.7",
"platform-core": "8.1.279",
"platform-core": "8.1.287",
"quasar": "2.15.4",
"tailwindcss": "3.4.4",
"vue": "3.4.31",

25
io.sc.engine.st.frontend/public/index.html

@ -26,6 +26,31 @@
<script src="/webjars/tailwindcss/3.3.5/tailwindcss.js" th:src="@{/webjars/tailwindcss/3.3.5/tailwindcss.js}"></script>
<script src="/configure.js" th:src="@{/configure.js}"></script>
<script>
(function () {
"use strict";
window.addEventListener("load", function () {
var box, div, link, namespaceURI;
// First check whether the page contains any <math> element.
namespaceURI = "http://www.w3.org/1998/Math/MathML";
if (document.body.getElementsByTagNameNS(namespaceURI, "math")[0]) {
// Create a div to test mspace, using Kuma's "offscreen" CSS
document.body.insertAdjacentHTML("afterbegin", "<div style='border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px;'><math xmlns='" + namespaceURI + "'><mspace height='23px' width='77px'></mspace></math></div>");
div = document.body.firstChild;
box = div.firstChild.firstChild.getBoundingClientRect();
document.body.removeChild(div);
if (Math.abs(box.height - 23) > 1 || Math.abs(box.width - 77) > 1) {
// Insert the mathml.css stylesheet.
link = document.createElement("link");
link.href = '[(@{/webjars/mathfonts/1.0.0/mathml.css})]'.startsWith('[')? 'http://localhost:8080/webjars/mathfonts/1.0.0/mathml.css' : '[(@{/webjars/mathfonts/1.0.0/mathml.css})]';
link.rel = "stylesheet";
document.head.appendChild(link);
}
}
});
}());
</script>
</head>
<body>
<noscript>

4
io.sc.engine.st/src/main/resources/META-INF/platform/plugins/liquibase.json

@ -16,8 +16,8 @@
"order" : 12000,
"description":"压力测试",
"locations":[
"liquibase/io.sc.engine.st_8.0.0_20221020__Stress Test Database Schema DDL.xml",
"liquibase/io.sc.engine.st_1.0.0_20221020__ST Data.xml"
"classpath:/liquibase/io.sc.engine.st_8.0.0_20221020__Stress Test Database Schema DDL.xml",
"classpath:/liquibase/io.sc.engine.st_1.0.0_20221020__ST Data.xml"
]
}
]

2
io.sc.platform.attachment/src/main/resources/META-INF/platform/plugins/liquibase.json

@ -4,7 +4,7 @@
"order" : 200,
"description":"附件管理",
"locations":[
"liquibase/io.sc.platform.attachment_8.0.0_20220606__Attachment Database Schema DDL.xml"
"classpath:/liquibase/io.sc.platform.attachment_8.0.0_20220606__Attachment Database Schema DDL.xml"
]
}
]

2
io.sc.platform.core.frontend/package.json

@ -1,6 +1,6 @@
{
"name": "platform-core",
"version": "8.1.281",
"version": "8.1.287",
"description": "前端核心包,用于快速构建前端的脚手架",
"//main": "库的主文件",
"main": "dist/platform-core.js",

25
io.sc.platform.core.frontend/public/index.html

@ -26,6 +26,31 @@
<script src="/webjars/tailwindcss/3.3.5/tailwindcss.js" th:src="@{/webjars/tailwindcss/3.3.5/tailwindcss.js}"></script>
<script src="/configure.js" th:src="@{/configure.js}"></script>
<script>
(function () {
"use strict";
window.addEventListener("load", function () {
var box, div, link, namespaceURI;
// First check whether the page contains any <math> element.
namespaceURI = "http://www.w3.org/1998/Math/MathML";
if (document.body.getElementsByTagNameNS(namespaceURI, "math")[0]) {
// Create a div to test mspace, using Kuma's "offscreen" CSS
document.body.insertAdjacentHTML("afterbegin", "<div style='border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px;'><math xmlns='" + namespaceURI + "'><mspace height='23px' width='77px'></mspace></math></div>");
div = document.body.firstChild;
box = div.firstChild.firstChild.getBoundingClientRect();
document.body.removeChild(div);
if (Math.abs(box.height - 23) > 1 || Math.abs(box.width - 77) > 1) {
// Insert the mathml.css stylesheet.
link = document.createElement("link");
link.href = '[(@{/webjars/mathfonts/1.0.0/mathml.css})]'.startsWith('[')? 'http://localhost:8080/webjars/mathfonts/1.0.0/mathml.css' : '[(@{/webjars/mathfonts/1.0.0/mathml.css})]';
link.rel = "stylesheet";
document.head.appendChild(link);
}
}
});
}());
</script>
</head>
<body>
<noscript>

2
io.sc.platform.core.frontend/src/components/index.ts

@ -11,6 +11,7 @@ import component_testcase_codemirror from '@/views/testcase/code-mirror/code-mir
import component_testcase_loading from '@/views/testcase/loading/loading.vue';
import component_testcase_excel from '@/views/testcase/excel/Excel.vue';
import component_testcase_word from '@/views/testcase/word/Word.vue';
import component_testcase_maxgraph from '@/views/testcase/maxgraph/Maxgraph.vue';
import component_testcase_likmDialog from '@/views/likm/Dialog.vue';
import component_testcase_likmDrawer from '@/views/likm/Drawer.vue';
import component_testcase_likmForm from '@/views/likm/Form.vue';
@ -31,6 +32,7 @@ const localComponents = {
'component.testcase.loading': component_testcase_loading,
'component.testcase.excel': component_testcase_excel,
'component.testcase.word': component_testcase_word,
'component.testcase.maxgraph': component_testcase_maxgraph,
'component.testcase.likmDialog': component_testcase_likmDialog,
'component.testcase.likmDrawer': component_testcase_likmDrawer,
'component.testcase.likmForm': component_testcase_likmForm,

9
io.sc.platform.core.frontend/src/menus/menus.json

@ -81,6 +81,15 @@
"icon": "bi-palette",
"routeName": "route.testcase.excel"
},
{
"type": "ROUTE",
"order": 500,
"parentId": "menu.testcase",
"id": "menu.testcase.maxgraph",
"titleI18nKey": "menu.testcase.maxgraph",
"icon": "bi-palette",
"routeName": "route.testcase.maxgraph"
},
{
"type": "ROUTE",
"order": 500,

16
io.sc.platform.core.frontend/src/platform/components/form/elements/WCodeMirror.vue

@ -18,7 +18,7 @@
<template #label> <span v-if="requiredIfComputed" style="color: red">*</span> {{ attrs.label }}</template>
<template #control>
<div v-if="editable && toolbar" class="w-full pt-2 border-b border-b-gray-200">
<Toolbar :source-code-editor="editorView"></Toolbar>
<Toolbar :source-code-editor="editorView" :user-defined-functions="userDefinedFunctions"></Toolbar>
</div>
<div
ref="codemirrorRef"
@ -108,19 +108,7 @@ const props = defineProps({
type: Function,
default: undefined,
},
activateOnCompletion: {
type: Function,
default: undefined,
},
//
autoCompletionOptions: {
type: Array,
default: () => {
return [];
},
},
//
extAutoCompletionOptions: {
userDefinedFunctions: {
type: Array,
default: () => {
return [];

9
io.sc.platform.core.frontend/src/platform/components/math/WMath.vue

@ -1,7 +1,7 @@
<template>
<div v-show="showIfComputed" :class="readOnly ? '' : 'border border-gray-200'" style="min-width: 600px">
<div v-if="!readOnly" class="row py-1 border-b border-b-gray-200">
<Toolbar v-model="modelValueRef" v-model:zoom="zoomModelValueRef"></Toolbar>
<Toolbar v-model="modelValueRef" v-model:zoom="zoomModelValueRef" :user-defined-functions="userDefinedFunctions"></Toolbar>
</div>
<div style="overflow: auto">
<math
@ -67,6 +67,12 @@ const props = defineProps({
type: Function,
default: undefined,
},
userDefinedFunctions: {
type: Array,
default: () => {
return [];
},
},
});
const emit = defineEmits(['update:modelValue', 'change']);
@ -190,7 +196,6 @@ const dragleave = (event) => {
event.preventDefault();
const tagName = event.target.tagName.toLowerCase();
if (tagName === 'mspace') {
console.log('dragleave');
event.target.className = mathMsSpaceClass;
}
};

59
io.sc.platform.core.frontend/src/platform/components/math/toolbar/Toolbar.vue

@ -94,6 +94,48 @@
</q-list>
</q-menu>
</q-btn>
<!-- 用户自定义函数 -->
<q-btn
v-if="userDefinedFunctions && userDefinedFunctions.length > 0"
:label="$t('math.toolbar.userDefinedFunction')"
stretch
flat
dense
no-caps
padding="0px 10px"
icon-right="arrow_drop_down"
>
<q-menu>
<q-list>
<q-item
v-for="userDefinedFunction in userDefinedFunctions"
:key="userDefinedFunction.name"
v-close-popup
clickable
:title="userDefinedFunction.description"
draggable="true"
@dragstart="
(e) => {
dragstart(e, userDefinedFunction);
}
"
@click="
(e) => {
append(e, userDefinedFunction);
}
"
>
<template v-if="userDefinedFunction.mathXml && userDefinedFunction.mathXml.trim() !== ''">
<q-item-section v-dompurify-html="'<math>' + userDefinedFunction.mathXml + '</math>'"></q-item-section>
</template>
<template v-else>
<q-item-section>{{ userDefinedFunction.signature }}</q-item-section>
</template>
</q-item>
</q-list>
</q-menu>
</q-btn>
</q-toolbar>
</template>
<script setup lang="ts">
@ -143,11 +185,26 @@ const zoomModelValueRef = defineModel('zoom', { type: Number, default: ZoomLevel
const props = defineProps({
sourceCodeEditor: { type: Object, default: undefined },
type: { type: String, default: 'math' },
userDefinedFunctions: {
type: Array,
default: () => {
return [];
},
},
});
const undoManager = new UndoManager();
const dragstart = (event, userDefinedFunction) => {
event.dataTransfer.setData('math', userDefinedFunction.mathXml);
event.dataTransfer.setDragImage(event.srcElement, 0, 0);
};
const append = (event, userDefinedFunction) => {
props.sourceCodeEditor?.dispatch(props.sourceCodeEditor?.state?.replaceSelection(userDefinedFunction.signature));
modelValueRef.value = modelValueRef.value + userDefinedFunction.mathXml.replace('<mspace></mspace>', '');
};
onMounted(() => {
watch(
() => modelValueRef.value,

2
io.sc.platform.core.frontend/src/platform/css/quasar.sass

@ -0,0 +1,2 @@
@import 'quasar/src/css/index.sass'
@import 'quasar/src/css/variables.sass'

2
io.sc.platform.core.frontend/src/platform/i18n/messages.json

@ -263,7 +263,7 @@
"math.toolbar.functions.formater.comma": "Thousandth place, leave the y digits after the x decimal point",
"math.toolbar.functions.formater.percent": "percentage, leave the y digits after the x decimal point",
"math.tools": "Tools",
"math.toolbar.userDefinedFunction":"User Defined Function",
"math.contextMenu.miTomn": "Variable -> Const",
"math.contextMenu.mnTomi": "Const -> Variable",

2
io.sc.platform.core.frontend/src/platform/i18n/messages_tw_CN.json

@ -263,7 +263,7 @@
"math.toolbar.functions.formater.comma": "千分位, 保留 x 小數點後 y 位",
"math.toolbar.functions.formater.percent": "百分數, 保留 x 小數點後 y 位",
"math.tools": "工具",
"math.toolbar.userDefinedFunction":"自定義函數",
"math.contextMenu.miTomn": "變量 -> 常量",
"math.contextMenu.mnTomi": "常量 -> 變量",

4
io.sc.platform.core.frontend/src/platform/i18n/messages_zh_CN.json

@ -263,10 +263,12 @@
"math.toolbar.functions.formater.comma": "千分位, 保留 x 小数点后 y 位",
"math.toolbar.functions.formater.percent": "百分数, 保留 x 小数点后 y 位",
"math.tools": "工具",
"math.toolbar.userDefinedFunction":"自定义函数",
"math.contextMenu.miTomn": "变量 -> 常量",
"math.contextMenu.mnTomi": "常量 -> 变量",
"math.contextMenu.mnTo0": "设置为 0",
"math.contextMenu.mnTo1": "设置为 1"
}

13
io.sc.platform.core.frontend/src/routes/routes.json

@ -117,6 +117,19 @@
}
},
{
"name": "route.testcase.maxgraph",
"path": "testcase/maxgraph",
"parent": "/",
"priority": 0,
"component": "component.testcase.maxgraph",
"componentPath": "@/views/testcase/maxgraph/Maxgraph.vue",
"redirect": null,
"meta": {
"permissions": ["/testcase/maxgraph/**/*"]
}
},
{
"name": "route.testcase.likm.dialog",
"path": "testcase/likm/dialog",

9
io.sc.platform.core.frontend/src/views/testcase/code-mirror/code-mirror.vue

@ -4,11 +4,13 @@
:rows="20"
:placeholder="true"
:auto-completion="autoCompletion"
:activate-on-completion="activateOnCompletion"
:editable="true"
:user-defined-functions="userDefinedFunctionsRef"
user-defined-functions-ref
></w-code-mirror>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { axios, Environment, Tools } from '@/platform';
import { AutoCompletionManager } from './AutoCompletionManager';
import { AutoCompletionManager2 } from './AutoCompletionManager2';
@ -26,6 +28,11 @@ const activateOnCompletion = (completion) => {
console.log(completion);
};
const userDefinedFunctionsRef = ref();
axios.get(Environment.apiContextPath('/api/re/function?pageable=false')).then((response) => {
userDefinedFunctionsRef.value = response.data.content;
});
const reg = /\$\{(.+?)\}\.$/g;
//const regReverse = /(\.\}(.+?)\{\$)+/g;
let str = ' ${对象}.${属性}+${对象2}.';

7
io.sc.platform.core.frontend/src/views/testcase/math/MathEditor.vue

@ -1,6 +1,6 @@
<template>
<div>
<w-math v-model="formulaRef" v-model:zoom="zoomRef" :auto-completion="autoCompletion"></w-math>
<w-math v-model="formulaRef" v-model:zoom="zoomRef" :auto-completion="autoCompletion" :user-defined-functions="userDefinedFunctionsRef"></w-math>
</div>
</template>
<script setup lang="ts">
@ -9,6 +9,7 @@ import { axios, Environment } from '@/platform';
import { AutoCompletionManager } from './AutoCompletionManager';
const autoCompletionManager = new AutoCompletionManager();
const userDefinedFunctionsRef = ref();
const autoCompletion = (context) => {
return autoCompletionManager.autoCompletion(context);
@ -20,6 +21,10 @@ axios.get(Environment.apiContextPath('/api/re/common/listParameterAndValueTypeBy
autoCompletionManager.setValueTypes(response.data.valueTypes);
});
axios.get(Environment.apiContextPath('/api/re/function?pageable=false')).then((response) => {
userDefinedFunctionsRef.value = response.data.content;
});
const zoomRef = ref(10);
const formula = '';
const formulaRef = ref(`

51
io.sc.platform.core.frontend/src/views/testcase/maxgraph/maxgraph.vue

@ -0,0 +1,51 @@
<template>
<div id="graph-container"></div>
</template>
<script setup lang="ts">
import { onMounted, vue } from 'vue';
import { type CellStyle, Graph, InternalEvent } from '@maxgraph/core';
onMounted(() => {
const container = <HTMLElement>document.getElementById('graph-container');
// Disables the built-in context menu
InternalEvent.disableContextMenu(container);
const graph = new Graph(container);
graph.setPanning(true); // Use mouse right button for panning
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
const vertex01 = graph.insertVertex({
parent,
position: [10, 10],
size: [100, 100],
value: 'rectangle',
});
const vertex02 = graph.insertVertex({
parent,
position: [350, 90],
size: [50, 50],
style: {
fillColor: 'orange',
shape: 'ellipse',
verticalAlign: 'top',
verticalLabelPosition: 'bottom',
},
value: 'ellipse',
});
graph.insertEdge({
parent,
source: vertex01,
target: vertex02,
value: 'edge',
style: {
edgeStyle: 'orthogonalEdgeStyle',
rounded: true,
},
});
});
});
</script>

4
io.sc.platform.core.frontend/template-project/package.json

@ -1,6 +1,6 @@
{
"name": "platform-core",
"version": "8.1.281",
"version": "8.1.287",
"description": "前端核心包,用于快速构建前端的脚手架",
"private": false,
"keywords": [],
@ -104,7 +104,7 @@
"luckyexcel": "1.0.1",
"mockjs": "1.1.0",
"pinia": "2.1.7",
"platform-core": "8.1.281",
"platform-core": "8.1.287",
"quasar": "2.15.4",
"tailwindcss": "3.4.4",
"vue": "3.4.31",

25
io.sc.platform.core.frontend/template-project/public/index.html

@ -26,6 +26,31 @@
<script src="/webjars/tailwindcss/3.3.5/tailwindcss.js" th:src="@{/webjars/tailwindcss/3.3.5/tailwindcss.js}"></script>
<script src="/configure.js" th:src="@{/configure.js}"></script>
<script>
(function () {
"use strict";
window.addEventListener("load", function () {
var box, div, link, namespaceURI;
// First check whether the page contains any <math> element.
namespaceURI = "http://www.w3.org/1998/Math/MathML";
if (document.body.getElementsByTagNameNS(namespaceURI, "math")[0]) {
// Create a div to test mspace, using Kuma's "offscreen" CSS
document.body.insertAdjacentHTML("afterbegin", "<div style='border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px;'><math xmlns='" + namespaceURI + "'><mspace height='23px' width='77px'></mspace></math></div>");
div = document.body.firstChild;
box = div.firstChild.firstChild.getBoundingClientRect();
document.body.removeChild(div);
if (Math.abs(box.height - 23) > 1 || Math.abs(box.width - 77) > 1) {
// Insert the mathml.css stylesheet.
link = document.createElement("link");
link.href = '[(@{/webjars/mathfonts/1.0.0/mathml.css})]'.startsWith('[')? 'http://localhost:8080/webjars/mathfonts/1.0.0/mathml.css' : '[(@{/webjars/mathfonts/1.0.0/mathml.css})]';
link.rel = "stylesheet";
document.head.appendChild(link);
}
}
});
}());
</script>
</head>
<body>
<noscript>

9
io.sc.platform.core.frontend/template-project/src/views/testcase/code-mirror/code-mirror.vue

@ -4,11 +4,13 @@
:rows="20"
:placeholder="true"
:auto-completion="autoCompletion"
:activate-on-completion="activateOnCompletion"
:editable="true"
:user-defined-functions="userDefinedFunctionsRef"
user-defined-functions-ref
></w-code-mirror>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { axios, Environment, Tools } from '@/platform';
import { AutoCompletionManager } from './AutoCompletionManager';
import { AutoCompletionManager2 } from './AutoCompletionManager2';
@ -26,6 +28,11 @@ const activateOnCompletion = (completion) => {
console.log(completion);
};
const userDefinedFunctionsRef = ref();
axios.get(Environment.apiContextPath('/api/re/function?pageable=false')).then((response) => {
userDefinedFunctionsRef.value = response.data.content;
});
const reg = /\$\{(.+?)\}\.$/g;
//const regReverse = /(\.\}(.+?)\{\$)+/g;
let str = ' ${对象}.${属性}+${对象2}.';

7
io.sc.platform.core.frontend/template-project/src/views/testcase/math/MathEditor.vue

@ -1,6 +1,6 @@
<template>
<div>
<w-math v-model="formulaRef" v-model:zoom="zoomRef" :auto-completion="autoCompletion"></w-math>
<w-math v-model="formulaRef" v-model:zoom="zoomRef" :auto-completion="autoCompletion" :user-defined-functions="userDefinedFunctionsRef"></w-math>
</div>
</template>
<script setup lang="ts">
@ -9,6 +9,7 @@ import { axios, Environment } from '@/platform';
import { AutoCompletionManager } from './AutoCompletionManager';
const autoCompletionManager = new AutoCompletionManager();
const userDefinedFunctionsRef = ref();
const autoCompletion = (context) => {
return autoCompletionManager.autoCompletion(context);
@ -20,6 +21,10 @@ axios.get(Environment.apiContextPath('/api/re/common/listParameterAndValueTypeBy
autoCompletionManager.setValueTypes(response.data.valueTypes);
});
axios.get(Environment.apiContextPath('/api/re/function?pageable=false')).then((response) => {
userDefinedFunctionsRef.value = response.data.content;
});
const zoomRef = ref(10);
const formula = '';
const formulaRef = ref(`

36
io.sc.platform.core/src/main/java/io/sc/platform/core/plugins/PluginManager.java

@ -19,6 +19,7 @@ public class PluginManager {
private static final String MESSAGES_LOCATION ="META-INF/platform/plugins/messages.json";
private static final String JSON_SERIALIZER_LOCATION ="META-INF/platform/plugins/json-serializers.json";
private static final String RESTART_PROPERTIES_LOCATION ="META-INF/platform/plugins/restart-properties.json";
private static final String EXPORTABLE_RESOURCES_LOCATION ="META-INF/platform/plugins/exportable-resources.json";
private List<Plugin<Map<String,String>>> systemPropertyPlugins;
private Map<String,String> systemProperties;
@ -41,6 +42,9 @@ public class PluginManager {
private List<Plugin<RestartProperties>> restartPropertiesPlugins;
private List<RestartProperties> restartProperties;
private List<Plugin<List<ExportableResource>>> exportableResourcePlugins;
private List<ExportableResource> exportableResources;
public List<Plugin<Map<String, String>>> getSystemPropertyPlugins() {
return systemPropertyPlugins;
}
@ -153,6 +157,22 @@ public class PluginManager {
this.restartProperties = restartProperties;
}
public List<Plugin<List<ExportableResource>>> getExportableResourcePlugins() {
return exportableResourcePlugins;
}
public void setExportableResourcePlugins(List<Plugin<List<ExportableResource>>> exportableResourcePlugins) {
this.exportableResourcePlugins = exportableResourcePlugins;
}
public List<ExportableResource> getExportableResources() {
return exportableResources;
}
public void setExportableResources(List<ExportableResource> exportableResources) {
this.exportableResources = exportableResources;
}
public static PluginManager getInstance(){
return PluginManagerHolder.instance;
}
@ -170,6 +190,7 @@ public class PluginManager {
loadMessagePlugins(parser);
loadJsonSerializerPlugins(parser);
loadRestartPropertiesPlugins(parser);
loadExportableResourcesPlugins(parser);
}
private void loadSystemPropertyPlugins(PluginParser parser){
@ -266,4 +287,19 @@ public class PluginManager {
restartProperties =result;
}
}
private void loadExportableResourcesPlugins(PluginParser parser){
exportableResourcePlugins =parser.parse(EXPORTABLE_RESOURCES_LOCATION,new TypeReference<List<ExportableResource>>(){});
if(exportableResourcePlugins!=null && !exportableResourcePlugins.isEmpty()){
List<ExportableResource> result =new ArrayList<>();
exportableResourcePlugins.forEach( plugin -> {
List<ExportableResource> exportableResources =plugin.getValue();
for(ExportableResource exportableResource : exportableResources){
exportableResource.setConfigurationFileUrl(plugin.getFileUrl());
}
result.addAll(exportableResources);
});
exportableResources =result;
}
}
}

131
io.sc.platform.core/src/main/java/io/sc/platform/core/plugins/item/ExportableResource.java

@ -0,0 +1,131 @@
package io.sc.platform.core.plugins.item;
import java.util.Arrays;
/**
* 可导出资源插件
*/
public class ExportableResource{
private String type;
private String name;
private String description;
private String[] sources;
private String selectedSource;
private String target;
private String configurationFileUrl;
/**
* 获取可导出文件类型支持的类型包括:
* file: 单个文件
* dir: 目录
* @return 可导出文件类型
*/
public String getType() {
return type;
}
/**
* 设置可导出文件类型支持的类型包括:
* file: 单个文件
* dir: 目录
* @param type 可导出文件类型
*/
public void setType(String type) {
this.type = type;
}
/**
* 获取可导出文件名
* @return 可导出文件名
*/
public String getName() {
return name;
}
/**
* 设置可导出文件名
* @param name 可导出文件名
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取可导出文件描述
* @return 可导出文件描述
*/
public String getDescription() {
return description;
}
/**
* 设置可导出文件描述
* @param description 可导出文件描述
*/
public void setDescription(String description) {
this.description = description;
}
/**
* 获取可导出文件可选来源列表
* @return 可导出文件可选来源列表
*/
public String[] getSources() {
return sources;
}
/**
* 设置可导出文件可选来源列表
* @param sources 可导出文件可选来源列表
*/
public void setSources(String[] sources) {
this.sources = sources;
}
/**
* 获取被选择的可导出文件来源
* @return 被选择的可导出文件来源
*/
public String getSelectedSource() {
return selectedSource;
}
/**
* 设置被选择的可导出文件来源
* @param selectedSource 被选择的可导出文件来源
*/
public void setSelectedSource(String selectedSource) {
this.selectedSource = selectedSource;
}
/**
* 获取可导出文件的目标文件
* @return 可导出文件的目标文件
*/
public String getTarget() {
return target;
}
/**
* 设置可导出文件的目标文件
* @param target 可导出文件的目标文件
*/
public void setTarget(String target) {
this.target = target;
}
public String getConfigurationFileUrl() {
return configurationFileUrl;
}
public void setConfigurationFileUrl(String configurationFileUrl) {
this.configurationFileUrl = configurationFileUrl;
}
@Override
public String toString() {
return "ExportableResource [type=" + type + ", name=" + name + ", description=" + description
+ ", sources=" + Arrays.toString(sources) + ", selectedSource=" + selectedSource + ", target=" + target
+ "]";
}
}

66
io.sc.platform.core/src/main/java/io/sc/platform/core/springboot/AfterEnvironmentProcessor.java

@ -1,10 +1,22 @@
package io.sc.platform.core.springboot;
import io.sc.platform.core.Environment;
import io.sc.platform.core.plugins.PluginManager;
import io.sc.platform.core.plugins.item.ExportableResource;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
/**
* Spring Boot 环境准备好后
@ -17,6 +29,58 @@ public class AfterEnvironmentProcessor implements EnvironmentPostProcessor, Orde
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Environment.getInstance().setMultiCorportationMode(environment.getProperty(Environment.KEY_IS_MULTI_CORPORATION_MODE,Boolean.class,false));
exportExportableResources(environment);
}
private void exportExportableResources(ConfigurableEnvironment environment){
try {
List<ExportableResource> exportableResources = PluginManager.getInstance().getExportableResources();
setSelectedReSourceAndTarget(exportableResources,environment);
exportResources(exportableResources);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void setSelectedReSourceAndTarget(List<ExportableResource> exportableResources,ConfigurableEnvironment environment){
if(exportableResources!=null && exportableResources.size()>0 && environment!=null){
ResourceLoader resourceLoader =new DefaultResourceLoader();
for(ExportableResource exportableResource : exportableResources){
if(exportableResource.getSources()!=null && exportableResource.getSources().length>0){
for(String file : exportableResource.getSources()){
Resource resource =resourceLoader.getResource(file);
if(resource!=null && resource.exists()){
exportableResource.setSelectedSource(file);
break;
}
}
exportableResource.setTarget(environment.resolvePlaceholders(exportableResource.getTarget()));
}
}
}
}
private void exportResources(List<ExportableResource> exportableResources) throws IOException {
if(exportableResources!=null && exportableResources.size()>0){
ResourceLoader resourceLoader =new DefaultResourceLoader();
for(ExportableResource exportableResource : exportableResources){
File target =new File(exportableResource.getTarget());
boolean needExportResource =true;
if(target.isFile() && target.exists()){
if(!Environment.getInstance().isRunningInDevelopment()){
needExportResource =false;
}
}
if(needExportResource){
String selectedSource =exportableResource.getSelectedSource();
if(StringUtils.hasText(selectedSource)){
Resource source =resourceLoader.getResource(selectedSource);
if(source!=null && source.exists()){
IOUtils.copy(source.getInputStream(), new FileOutputStream(target));
}
}
}
}
}
}
}

7
io.sc.platform.core/src/main/java/io/sc/platform/core/springboot/BeforeEnvironmentProcessor.java

@ -39,10 +39,13 @@ public class BeforeEnvironmentProcessor implements EnvironmentPostProcessor, Ord
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Environment.getInstance().setMultiCorportationMode(environment.getProperty(Environment.KEY_IS_MULTI_CORPORATION_MODE,Boolean.class,false));
// 将属性添加到 spring boot ConfigurableEnvironment 最后,拥有最低优先级
platformProperties.put(Environment.KEY_APPLICATION_NAME,Environment.getInstance().getApplicationName());
platformProperties.put(Environment.KEY_IS_RUNNING_IN_WEB_CONTAINER,Environment.getInstance().isRunningInWebContainer());
platformProperties.put(Environment.KEY_IS_RUNNING_IN_DEVELOPMENT,Environment.getInstance().isRunningInDevelopment());
platformProperties.put(Environment.KEY_IS_MULTI_CORPORATION_MODE,Environment.getInstance().isMultiCorportationMode());
environment.getPropertySources().addLast(new PropertiesPropertySource(APPLICATION_EXT_PROPERTIES,platformProperties));
// 设置应用的 Home 目录
@ -168,9 +171,9 @@ public class BeforeEnvironmentProcessor implements EnvironmentPostProcessor, Ord
String homeDir = DirectoryManager.getInstance().getHomeDir();
String content = getContent();
if(StringUtils.hasText(content)) {
content = environment.resolvePlaceholders(content);
//content = environment.resolvePlaceholders(content);
FileUtil.writeString(homeDir + "/config/application.properties", content, false);
}
FileUtil.writeString(homeDir + "/config/application.properties", content, false);
}
private String getContent(){

2
io.sc.platform.core/src/main/resources/META-INF/platform/plugins/directories.json

@ -1,5 +1,7 @@
[
{"name":"dir.config" ,"path":"/config" ,"autoCreate":true},
{"name":"dir.config.https" ,"path":"/config/https" ,"autoCreate":true},
{"name":"dir.config.oauth2" ,"path":"/config/oauth2" ,"autoCreate":true},
{"name":"dir.log" ,"path":"/logs" ,"autoCreate":true},
{"name":"dir.work" ,"path":"/work" ,"autoCreate":true}
]

9
io.sc.platform.core/src/main/resources/META-INF/platform/plugins/exportable-resources.json

@ -0,0 +1,9 @@
[
{
"type" :"file",
"name" :"keystore.p12",
"description" :"keystore.p12",
"sources" :["classpath:/keystore/keystore.p12"],
"target" :"${dir.config.https}/keystore.p12"
}
]

2
io.sc.platform.developer.doc/asciidoc/9999-appendix/appendix.adoc

@ -1,5 +1,5 @@
include::software/software.adoc[]
#include::environment/environment.adoc[]
include::environment/environment.adoc[]
include::platform/platform.adoc[]
include::docker-install/docker-install.adoc[]
include::docker-command/docker-command.adoc[]

62
io.sc.platform.developer.doc/asciidoc/9999-appendix/environment/environment.adoc

@ -1,57 +1,35 @@
[appendix]
= 环境信息
== 腾讯云外网服务器
== 公司服务器
=== proxmox
[options=header,cols="1,2"]
|===
| 配置项 | 值
| 申请日期 | 2023-6-27,有效期 1 年
| IP地址 | 124.222.99.204
| 操作系统 | proxmox
| IP地址 | 172.16.70.99
| UI 服务端口 | 8006
| 端口 | 22
| administrator | Wspsc@139@Wspsc
| root | wspsc@139
| wangshaoping | wspsc@139
| root | wspsc
|===
=== 连接方法
[source,bash]
----
# ssh 连接
ssh root@124.222.99.204
# scp
scp 本地文件 root@124.222.99.204:远程文件夹
----
=== 部署的应用
|===
| 应用名称 | 端口 | 用户名 | 密码 | 访问地址
| nexus | 8000 | admin | Yusys@fxyw888 | http://124.222.99.204:8000
| gitlib | 9000 | wangshaoping | wspsc@139 | http://124.222.99.204:9000
|===
== 公司金融云外网服务器
=== 100
[options=header,cols="1,2"]
|===
| 配置项 | 值
| IP地址 | 210.12.198.142
| 端口 | 8000
| root | Yusys@fxyw65
| wangshaoping | wspsc@139
| 操作系统 | Ubuntu 24.04 LTS
| IP地址 | 172.16.70.100
| 端口 | 22
| wsp | Yusystem@fxyw@888
| root | Yusystem@fxyw@888
|===
=== 连接方法
[source,bash]
----
# ssh 连接
ssh root@210.12.198.142 -p 8000
# scp
scp -P 8000 本地文件 root@210.12.198.142:远程文件夹
----
=== 部署的应用
=== 101
[options=header,cols="1,2"]
|===
| 应用名称 | 端口 | 用户名 | 密码 | 访问地址
| gitlab | 9000 | admin | Yusys@fxyw888 | http://210.12.198.142:9000
| zentao | 80->7000 | admin | Yusys@fxyw888 | http://210.12.198.142/zentao
| 配置项 | 值
| 操作系统 | Ubuntu 24.04 LTS
| IP地址 | 172.16.70.101
| 端口 | 22
| wsp | admin123
| root | admin123
|===

1
io.sc.platform.developer.doc/asciidoc/9999-appendix/oauth2/oauth2.adoc

@ -169,6 +169,7 @@ private_key_jwt 和 client_secret_jwt 唯一的区别就是生成 JWT 的方式
=== /oauth2/authorize
实现类: org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter
|===
| 参数名 | 参数类型 | 参数值 | 说明
| client_id | 请求 | |

52
io.sc.platform.developer.doc/asciidoc/市场风险/市场风险.adoc

@ -0,0 +1,52 @@
= 市场风险
== 市场风险计量方法
. 缺口分析
. 久期分析
. 外汇敞口分析
. 敏感性分析
. 风险价值(VaR)
=== 缺口分析
缺口分析用来衡量利率变动对银行当期收益的影响。具体而言,就是将银行的所有生息资产和付息负债按照重新定价的期限划分到不同的时间段(如一个月以内,
1至3个月,3个月至1年,1至5年,5年以上等)。在每个时间段内,将利率敏感性资产减去利率敏感性负债,再加上表外业务头寸,就得到该时段内的重新定价“缺口”。
以该缺口乘以假定的利率变动,即得出这一利率变动对净利息收入变动的大致影响。
资产(包括表外业务头寸)> 负债时,就产生了正缺口,即资产敏感型缺口,此时,市场利率下降会导致银行的净利息收入下降。
相反,当某一时段内的负债 > 资产(包括表外业务头寸)时,就产生了负缺口,即负债敏感型缺口,此时,市场利率上升会导致银行的净利息收入下降。
=== 久期分析
久期分析也称为持续期分析或期限弹性分析,也是对银行资产负债利率敏感度进行分析的重要方法,主要用于衡量利率变动对银行整体经济价值的影响。
具体而言,就是对个时段的缺口赋予响应的敏感性权重,得到加权缺口,然后对所有时段的加权缺口进行汇总,以此估算某一给定的小幅(通常小于1%)
利率变动可能会对银行经济价值产生的影响。
久期(也称持续期),也叫麦考利久期,是使用加权平均数的形式计算固定收益产品的平均到期时间。用于对固定收益产品的利率敏感程度或利率弹性的衡量
(固定收益产品的价值与市场利率成反比,久期就是用来描述这个固定收益产品价值与市场利率变化的敏感性)。
当市场利率发生变化时,固定收益产品(贷款、证券)的价格将发生反比例的变动,其变动程度取决于久期的长度,久期越长,其变动幅度也就越大。

2
io.sc.platform.developer.frontend/package.json

@ -92,7 +92,7 @@
"luckyexcel": "1.0.1",
"mockjs": "1.1.0",
"pinia": "2.1.7",
"platform-core": "8.1.279",
"platform-core": "8.1.287",
"quasar": "2.15.4",
"tailwindcss": "3.4.4",
"vue": "3.4.31",

25
io.sc.platform.developer.frontend/public/index.html

@ -26,6 +26,31 @@
<script src="/webjars/tailwindcss/3.3.5/tailwindcss.js" th:src="@{/webjars/tailwindcss/3.3.5/tailwindcss.js}"></script>
<script src="/configure.js" th:src="@{/configure.js}"></script>
<script>
(function () {
"use strict";
window.addEventListener("load", function () {
var box, div, link, namespaceURI;
// First check whether the page contains any <math> element.
namespaceURI = "http://www.w3.org/1998/Math/MathML";
if (document.body.getElementsByTagNameNS(namespaceURI, "math")[0]) {
// Create a div to test mspace, using Kuma's "offscreen" CSS
document.body.insertAdjacentHTML("afterbegin", "<div style='border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px;'><math xmlns='" + namespaceURI + "'><mspace height='23px' width='77px'></mspace></math></div>");
div = document.body.firstChild;
box = div.firstChild.firstChild.getBoundingClientRect();
document.body.removeChild(div);
if (Math.abs(box.height - 23) > 1 || Math.abs(box.width - 77) > 1) {
// Insert the mathml.css stylesheet.
link = document.createElement("link");
link.href = '[(@{/webjars/mathfonts/1.0.0/mathml.css})]'.startsWith('[')? 'http://localhost:8080/webjars/mathfonts/1.0.0/mathml.css' : '[(@{/webjars/mathfonts/1.0.0/mathml.css})]';
link.rel = "stylesheet";
document.head.appendChild(link);
}
}
});
}());
</script>
</head>
<body>
<noscript>

3
io.sc.platform.flowable/src/main/java/io/sc/platform/flowable/service/impl/ProcessOperationServiceImpl.java

@ -428,6 +428,7 @@ public class ProcessOperationServiceImpl implements ProcessOperationService {
if(ASSIGNMENT_STRATEGY_DATA_OBJECT_ID.equalsIgnoreCase(id)) {
String value =(String)data.getValue();
if(StringUtils.hasText(value)) {
value =value.trim();
try {
Map<String,String> map =jsonMapper.readValue(value,new TypeReference<Map<String,String>>(){});
String taskDefineId =taskDefine.getId();
@ -445,7 +446,7 @@ public class ProcessOperationServiceImpl implements ProcessOperationService {
}
}
} catch (Exception e) {
log.warn(ASSIGNMENT_STRATEGY_DATA_OBJECT_ID + "property's value is NOT a validated Json String! " + value);
log.error(ASSIGNMENT_STRATEGY_DATA_OBJECT_ID + "property's value is NOT a validated Json String! " + value,e);
}
}
}

6
io.sc.platform.flowable/src/main/resources/META-INF/platform/plugins/liquibase.json

@ -4,9 +4,9 @@
"order" : 400,
"description":"工作流",
"locations":[
"liquibase/io.sc.platform.flowable_8.0.0_20220606__Flowable(6.8.0) Database Schema DDL.xml",
"liquibase/io.sc.platform.flowable_8.0.0_20220606__Process Manager Database Schema DDL.xml",
"liquibase/io.sc.platform.flowable_8.0.0_20220606__Process Manager Data.xml"
"classpath:/liquibase/io.sc.platform.flowable_8.0.0_20220606__Flowable(6.8.0) Database Schema DDL.xml",
"classpath:/liquibase/io.sc.platform.flowable_8.0.0_20220606__Process Manager Database Schema DDL.xml",
"classpath:/liquibase/io.sc.platform.flowable_8.0.0_20220606__Process Manager Data.xml"
]
}
]

2
io.sc.platform.gradle/templates/pgp/app/build-common.gradle

@ -6,7 +6,7 @@
* 2. gradle bootwar -Dtarget=undertow # undertow, target=undertow
* 3. gradle bootwar -Dtarget=jetty # jetty, target=jetty
*/
def target =System.getProperty("target") ?: "undertow";
def target =System.getProperty("target") ?: "tomcat";
System.setProperty('target',target);
// targetRuntime build.gradle

2
io.sc.platform.gradle/templates/pgp/setup/gradle.properties

@ -38,7 +38,7 @@ application_version=1.0.0
platform_group=io.sc
platform_version=8.1.44
platform_plugin_version=8.1.44
platform_core_frontend_version=8.1.279
platform_core_frontend_version=8.1.287
###########################################################
# dependencies version

4
io.sc.platform.jdbc.driver.dm/src/main/resources/META-INF/platform/plugins/jdbc-connection-template.json

@ -3,8 +3,8 @@
"type" : "DM DBMS",
"version" : "8.1.2",
"driver" : "dm.jdbc.driver.DmDriver",
"url" : "jdbc:dm://${host}:${port}/",
"urlSample" : "jdbc:dm://localhost:5236/",
"url" : "jdbc:dm://${host}:${port}",
"urlSample" : "jdbc:dm://localhost:5236?compatibleMode=oracle",
"hibernateDialect" : "org.hibernate.dialect.DmDialect",
"sqlDialect" : "io.sc.platform.jdbc.sql.dialect.impl.OracleDialect",
"validationQuery" : "select id_code"

2
io.sc.platform.lcdp.frontend/package.json

@ -92,7 +92,7 @@
"luckyexcel": "1.0.1",
"mockjs": "1.1.0",
"pinia": "2.1.7",
"platform-core": "8.1.279",
"platform-core": "8.1.287",
"quasar": "2.15.4",
"tailwindcss": "3.4.4",
"vue": "3.4.31",

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save