diff --git a/smtweb-framework/.gitignore b/smtweb-framework/.gitignore new file mode 100644 index 0000000..59bb4c9 --- /dev/null +++ b/smtweb-framework/.gitignore @@ -0,0 +1,27 @@ +# ---> Java +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.idea +*.iml diff --git a/smtweb-framework/pom.xml b/smtweb-framework/pom.xml new file mode 100644 index 0000000..f4a9585 --- /dev/null +++ b/smtweb-framework/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + cc.smtweb + smtweb-framework + pom + 2.2.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.3.1.RELEASE + + + + UTF-8 + UTF-8 + 1.8 + 1.8 + 1.8 + true + + + + sw-framework-core + sw-framework-auth + sw-framework-file + sw-framework-web + ../smtweb-system/sw-system-bpm + + diff --git a/smtweb-framework/sw-framework-auth/pom.xml b/smtweb-framework/sw-framework-auth/pom.xml new file mode 100644 index 0000000..e376dcd --- /dev/null +++ b/smtweb-framework/sw-framework-auth/pom.xml @@ -0,0 +1,132 @@ + + + cc.smtweb + sw-framework-auth + 2.2.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.5.6 + + + + 4.0.0 + + + + org.springframework.boot + spring-boot-starter-web + + + cc.smtweb + sw-framework-core + 2.2.0-SNAPSHOT + + + org.springframework.boot + spring-boot-test + test + + + + org.junit.platform + junit-platform-launcher + 1.6.2 + test + + + + org.junit.jupiter + junit-jupiter-api + 5.6.2 + test + + + + org.junit.jupiter + junit-jupiter-engine + 5.6.2 + test + + + org.junit.vintage + junit-vintage-engine + 5.6.2 + test + + + org.junit.jupiter + junit-jupiter-params + 5.6.2 + test + + + org.springframework + spring-test + 5.2.7.RELEASE + test + + + + org.mockito + mockito-all + 1.10.19 + test + + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + config/*.yaml + + + + + + + + + + + + + + + + + + nexus-releases + Nexus Release Repository + http://47.92.149.153:7000/repository/maven-releases/ + + + nexus-snapshots + Nexus Snapshot Repository + http://47.92.149.153:7000/repository/maven-snapshots/ + + + diff --git a/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/captcha/ImageMaker.java b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/captcha/ImageMaker.java new file mode 100644 index 0000000..5cd1f42 --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/captcha/ImageMaker.java @@ -0,0 +1,66 @@ +package cc.smtweb.framework.auth.captcha; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class ImageMaker { + private static final String signChar = "()+-*/="; + + // 生成png图片 + public void make() throws IOException { + int fontHeight = 34; + int width = 19; + int height = fontHeight * (62 + signChar.length()); + // 创建BufferedImage对象 + + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + // ---------- 增加下面的代码使得背景透明 ----------------- + Graphics2D g2d = image.createGraphics(); + image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT); + g2d.dispose(); + + // 画图 + g2d = image.createGraphics(); + g2d.setColor(new Color(0,0,0)); + + // 画图 + Font font = new Font("Courier New", Font.BOLD, 30); + g2d.setFont(font); + + System.out.println(font.getMaxCharBounds(g2d.getFontRenderContext())); + + int top = 25; + for (int i = 0; i < 10; i++) { + g2d.drawString(i + "", 0, top); + top += fontHeight; + } + + for (int i = 0; i < 26; i++) { + g2d.drawString((char)(i + (int)'a') + "", 0, top); + top += fontHeight; + } + + for (int i = 0; i < 26; i++) { + g2d.drawString((char)(i + (int)'A') + "", 0, top); + top += fontHeight; + } + + for (int i = 0; i < signChar.length(); i++) { + g2d.drawString(signChar.charAt(i) + "", 0, top); + top += fontHeight; + } + + // 释放对象 + g2d.dispose(); + + // 保存文件 + ImageIO.write(image, "png", new File("/var/tmp/test.png")); + } + + public static void main(String[] args) throws IOException { + new ImageMaker().make(); + } +} diff --git a/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/spring/AuthAutoConfiguration.java b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/spring/AuthAutoConfiguration.java new file mode 100644 index 0000000..aec848f --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/spring/AuthAutoConfiguration.java @@ -0,0 +1,21 @@ +package cc.smtweb.framework.auth.spring; + +import cc.smtweb.framework.core.mvc.config.ControllerConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * @author kevin + */ +@Configuration +@ComponentScan +public class AuthAutoConfiguration { + + /** 配置自定义service扫描路径 {module}/{service}/{method} */ + @Bean + public ControllerConfig authControllerConfig() { + return new ControllerConfig("auth", "cc.smtweb.framework.auth.web"); + } + +} diff --git a/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/spring/cache/RealmCache.java b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/spring/cache/RealmCache.java new file mode 100644 index 0000000..313377a --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/spring/cache/RealmCache.java @@ -0,0 +1,60 @@ +package cc.smtweb.framework.auth.spring.cache; + +import cc.smtweb.framework.core.annotation.SwCache; +import cc.smtweb.framework.core.cache.AbstractCache; +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; +import cc.smtweb.framework.core.mvc.realm.service.PermChecker; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.*; + +/** + * 用户权限集合缓存器,@todo 权限部分需要重写 + * @author xkliu + */ +@Log4j2 +@SwCache(ident = "PERM_CHECKER", title = "用户权限") +public class RealmCache extends AbstractCache { + @Autowired + private JdbcEngine dbEngine; + + private static final long ENTERPRISE_ADMIN_ID = 1; + + @Override + protected String getId(PermChecker bean) { + return "1";//todo + } + + @Override + protected List loadAll() { + return null; + } + + + protected PermChecker load(Long key) { + // admin + if (key == ENTERPRISE_ADMIN_ID) { + return PermChecker.build(new HashSet<>(Collections.singletonList("*"))); + } + + // TODO: 合并相同角色,自己到缓存里面获取 + Set permissions = new HashSet<>(); + + List permList = dbEngine.queryStringList("SELECT menu_api_perm FROM sys_menu WHERE menu_id in\n" + + "(SELECT rmp_menu_id from sys_role_menu_privilege WHERE rmp_role_id in\n" + + "(SELECT role_id FROM sw_user.sys_role WHERE role_id IN" + + "(SELECT ur_role_id FROM sw_user.sys_user_role WHERE ur_user_id=?)))", key); + + if (permList != null) { + for (String perm: permList) { + for (String item: perm.split(",")) { + permissions.add(item.trim()); + } + } + } + + return PermChecker.build(permissions); + } + +} diff --git a/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/LoginAckVO.java b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/LoginAckVO.java new file mode 100644 index 0000000..d0d6a33 --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/LoginAckVO.java @@ -0,0 +1,11 @@ +package cc.smtweb.framework.auth.web.entity; + +import lombok.Data; + +@Data +public class LoginAckVO { + private Long userId; + private String userAvatar; + private String userName; + private String token; +} diff --git a/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/LoginVO.java b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/LoginVO.java new file mode 100644 index 0000000..f71673a --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/LoginVO.java @@ -0,0 +1,12 @@ +package cc.smtweb.framework.auth.web.entity; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class LoginVO implements Serializable { + private String username; + + private String password; +} diff --git a/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/MenuPO.java b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/MenuPO.java new file mode 100644 index 0000000..9016cb5 --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/MenuPO.java @@ -0,0 +1,29 @@ +package cc.smtweb.framework.auth.web.entity; + +import cc.smtweb.framework.core.annotation.SwColumn; +import cc.smtweb.framework.core.annotation.SwTable; +import lombok.Data; + +import java.io.Serializable; + +@Data +@SwTable("sw_user.sys_menu") +public class MenuPO implements Serializable { + @SwColumn(type={SwColumn.Type.ID}) + private Long menuId; + + @SwColumn(type={SwColumn.Type.PARENT_ID}) + private Long menuParentId; + + private String menuName; + + private Long menuSiteId; + + + private String menuPermiss; + + private String menuUrl; + + @SwColumn(type={SwColumn.Type.ORDER}) + private Integer menuSort; +} diff --git a/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/UserPO.java b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/UserPO.java new file mode 100644 index 0000000..e73b405 --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/entity/UserPO.java @@ -0,0 +1,28 @@ +package cc.smtweb.framework.auth.web.entity; + +import cc.smtweb.framework.core.annotation.SwColumn; +import cc.smtweb.framework.core.annotation.SwTable; +import lombok.Data; + +import java.io.Serializable; + +@Data +@SwTable("sw_user.sys_user") +public class UserPO implements Serializable { + @SwColumn(type={SwColumn.Type.ID}) + private Long userId; + + private String userNickCode; + + private String userNickName; + + private Long userCreatePartyId; + + private String userPwd; + + private String userPhone; + + private Integer userStatus; + + private String userAvatar; +} diff --git a/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/service/AuthService.java b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/service/AuthService.java new file mode 100644 index 0000000..4fbb2db --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/java/cc/smtweb/framework/auth/web/service/AuthService.java @@ -0,0 +1,89 @@ +package cc.smtweb.framework.auth.web.service; + +import cc.smtweb.framework.auth.web.entity.LoginAckVO; +import cc.smtweb.framework.auth.web.entity.LoginVO; +import cc.smtweb.framework.auth.web.entity.UserPO; +import cc.smtweb.framework.core.*; +import cc.smtweb.framework.core.annotation.*; +import cc.smtweb.framework.core.session.SessionManager; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.core.db.DbEngine; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; + +@Slf4j +@SwService +public class AuthService { + @SwParam + private DbEngine dbEngine; + + @SwParam + private SessionManager sessionManager; + + @SwPerm(SwPerm.NONE) + public R login(@SwBody LoginVO loginPO) { + if (StringUtils.isBlank(loginPO.getUsername())) { + return R.error("账号不能为空"); + } + + if (StringUtils.isBlank(loginPO.getPassword())) { + return R.error("密码不能为空"); + } + + UserPO user = dbEngine.queryEntity("select user_id,user_nick_name,user_nick_code,user_pwd,user_create_party_id from sw_user.sys_user where user_nick_code=?", UserPO.class, loginPO.getUsername()); + + if (user == null) { + return R.error("账号不存在"); + } + + // digest:md5("goodpj" + user.userId + password) + String pass = DigestUtils.md5Hex("goodpj" + user.getUserId() + loginPO.getPassword()); + + if (!pass.equals(user.getUserPwd())) { + return R.error("账号或者密码出错"); + } + + UserSession userSession = new UserSession(); + userSession.setUserId(user.getUserId()); + userSession.setSiteId(user.getUserCreatePartyId()); + + String token = sessionManager.login(userSession); + + LoginAckVO data = new LoginAckVO(); + + data.setUserId(user.getUserId()); + data.setUserName(user.getUserNickName()); + data.setUserAvatar(user.getUserAvatar()); + data.setToken(token); + + return R.success(data); + } + + @SwPerm() + public R ping(@SwParam("msg") String msg) { + return R.success(msg); + } + + @SwPerm("user:edit") + public R config(@SwParam("username") String username) { + return R.success("config: " + username); + } + + + /** + * 退出登录 + * @return code + */ + public R logout() { + sessionManager.logout(); + return R.success(); + } + + // defaultRun 命名的函数是默认函数 +// @SwPerm(SwPerm.NONE) +// public R defaultRun(@SwPathParam String path) { +// return R.success(path).put("dao", authDao); +// } + +} diff --git a/smtweb-framework/sw-framework-auth/src/main/resources/META-INF/spring.factories b/smtweb-framework/sw-framework-auth/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..82bd9bb --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cc.smtweb.framework.auth.spring.AuthAutoConfiguration diff --git a/smtweb-framework/sw-framework-auth/src/main/resources/auth/courier_new_34_19.png b/smtweb-framework/sw-framework-auth/src/main/resources/auth/courier_new_34_19.png new file mode 100644 index 0000000..44eada2 Binary files /dev/null and b/smtweb-framework/sw-framework-auth/src/main/resources/auth/courier_new_34_19.png differ diff --git a/smtweb-framework/sw-framework-auth/src/main/resources/config/application-dev.yaml b/smtweb-framework/sw-framework-auth/src/main/resources/config/application-dev.yaml new file mode 100644 index 0000000..2422f31 --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/resources/config/application-dev.yaml @@ -0,0 +1,64 @@ +smtweb: + machine-id: 1 + file: + local-path: /data/files/ + host: http://127.0.0.1 + url: http://127.0.0.1:8888/files/ + db: + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +feign: + hystrix: + enabled: false +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + # 设置服务名 + application: + name: smtweb_user + main: + allow-bean-definition-overriding: true + banner-mode: console + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + password: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: root + password: 1681860 + # 连接池配置 + hikari: + pool-name: HikariPool + minimum-idle: 10 + maximum-pool-size: 100 + connection-timeout: 60000 + idle-timeout: 600000 + max-lifetime: 1800000 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + profiles: + include: role + cache: + type: caffeine + cache-names: + - core + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h + + + + diff --git a/smtweb-framework/sw-framework-auth/src/main/resources/config/application-prod.yaml b/smtweb-framework/sw-framework-auth/src/main/resources/config/application-prod.yaml new file mode 100644 index 0000000..d6f1168 --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/resources/config/application-prod.yaml @@ -0,0 +1,35 @@ +sme: + machine-id: 1 + file-local-path: /data/files/smart/ + file-host: http://auth.smtweb.cc + file-url: ${sme.file-host}:${server.port}${server.servlet.context-path}/${sme.file-local-path} +server: + port: 10001 + servlet: + context-path: / +logging: + level: + smtweb: DEBUG + +spring: + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + datasource: + user: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: smt + password: smt_123456 + + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + + + diff --git a/smtweb-framework/sw-framework-auth/src/main/resources/config/application.yaml b/smtweb-framework/sw-framework-auth/src/main/resources/config/application.yaml new file mode 100644 index 0000000..caf4dfc --- /dev/null +++ b/smtweb-framework/sw-framework-auth/src/main/resources/config/application.yaml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev \ No newline at end of file diff --git a/smtweb-framework/sw-framework-auth/target/classes/META-INF/spring.factories b/smtweb-framework/sw-framework-auth/target/classes/META-INF/spring.factories new file mode 100644 index 0000000..82bd9bb --- /dev/null +++ b/smtweb-framework/sw-framework-auth/target/classes/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cc.smtweb.framework.auth.spring.AuthAutoConfiguration diff --git a/smtweb-framework/sw-framework-auth/target/classes/auth/courier_new_34_19.png b/smtweb-framework/sw-framework-auth/target/classes/auth/courier_new_34_19.png new file mode 100644 index 0000000..44eada2 Binary files /dev/null and b/smtweb-framework/sw-framework-auth/target/classes/auth/courier_new_34_19.png differ diff --git a/smtweb-framework/sw-framework-auth/target/classes/config/application-dev.yaml b/smtweb-framework/sw-framework-auth/target/classes/config/application-dev.yaml new file mode 100644 index 0000000..2422f31 --- /dev/null +++ b/smtweb-framework/sw-framework-auth/target/classes/config/application-dev.yaml @@ -0,0 +1,64 @@ +smtweb: + machine-id: 1 + file: + local-path: /data/files/ + host: http://127.0.0.1 + url: http://127.0.0.1:8888/files/ + db: + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +feign: + hystrix: + enabled: false +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + # 设置服务名 + application: + name: smtweb_user + main: + allow-bean-definition-overriding: true + banner-mode: console + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + password: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: root + password: 1681860 + # 连接池配置 + hikari: + pool-name: HikariPool + minimum-idle: 10 + maximum-pool-size: 100 + connection-timeout: 60000 + idle-timeout: 600000 + max-lifetime: 1800000 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + profiles: + include: role + cache: + type: caffeine + cache-names: + - core + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h + + + + diff --git a/smtweb-framework/sw-framework-auth/target/classes/config/application-prod.yaml b/smtweb-framework/sw-framework-auth/target/classes/config/application-prod.yaml new file mode 100644 index 0000000..d6f1168 --- /dev/null +++ b/smtweb-framework/sw-framework-auth/target/classes/config/application-prod.yaml @@ -0,0 +1,35 @@ +sme: + machine-id: 1 + file-local-path: /data/files/smart/ + file-host: http://auth.smtweb.cc + file-url: ${sme.file-host}:${server.port}${server.servlet.context-path}/${sme.file-local-path} +server: + port: 10001 + servlet: + context-path: / +logging: + level: + smtweb: DEBUG + +spring: + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + datasource: + user: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: smt + password: smt_123456 + + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + + + diff --git a/smtweb-framework/sw-framework-auth/target/classes/config/application.yaml b/smtweb-framework/sw-framework-auth/target/classes/config/application.yaml new file mode 100644 index 0000000..caf4dfc --- /dev/null +++ b/smtweb-framework/sw-framework-auth/target/classes/config/application.yaml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev \ No newline at end of file diff --git a/smtweb-framework/sw-framework-auth/target/maven-archiver/pom.properties b/smtweb-framework/sw-framework-auth/target/maven-archiver/pom.properties new file mode 100644 index 0000000..55808f3 --- /dev/null +++ b/smtweb-framework/sw-framework-auth/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Apache Maven +#Tue Nov 02 19:04:59 CST 2021 +version=2.1.0-SNAPSHOT +groupId=cc.smtweb +artifactId=sw-framework-auth diff --git a/smtweb-framework/sw-framework-auth/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/smtweb-framework/sw-framework-auth/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/smtweb-framework/sw-framework-auth/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/smtweb-framework/sw-framework-auth/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..d6ab0c6 --- /dev/null +++ b/smtweb-framework/sw-framework-auth/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,9 @@ +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-auth\src\main\java\cc\smtweb\framework\auth\web\entity\LoginAckVO.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-auth\src\main\java\cc\smtweb\framework\auth\web\entity\UserPO.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-auth\src\main\java\cc\smtweb\framework\auth\spring\cache\RealmCache.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-auth\src\main\java\cc\smtweb\framework\auth\AuthApplication.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-auth\src\main\java\cc\smtweb\framework\auth\web\entity\MenuPO.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-auth\src\main\java\cc\smtweb\framework\auth\web\entity\LoginVO.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-auth\src\main\java\cc\smtweb\framework\auth\captcha\ImageMaker.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-auth\src\main\java\cc\smtweb\framework\auth\spring\AuthAutoConfiguration.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-auth\src\main\java\cc\smtweb\framework\auth\web\service\AuthService.java diff --git a/smtweb-framework/sw-framework-core/doc/TODO.mk b/smtweb-framework/sw-framework-core/doc/TODO.mk new file mode 100644 index 0000000..4f5bf71 --- /dev/null +++ b/smtweb-framework/sw-framework-core/doc/TODO.mk @@ -0,0 +1,8 @@ +20210710 + +1. 定时器手动触发 (done) +2. 优化BeanReadUtils的TableName预先进行转换 +2. updateEntity支持悲观锁,并能抛出异常 +3. 缓存支持自定义 key +4. 序列号生成器 +5. redis配置database(0~15) (done) diff --git a/smtweb-framework/sw-framework-core/pom.xml b/smtweb-framework/sw-framework-core/pom.xml new file mode 100644 index 0000000..1aadd07 --- /dev/null +++ b/smtweb-framework/sw-framework-core/pom.xml @@ -0,0 +1,202 @@ + + + cc.smtweb + sw-framework-core + 2.2.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.5.6 + + + + 4.0.0 + + + + UTF-8 + UTF-8 + 1.8 + 1.8 + 1.8 + true + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + + com.fasterxml.jackson.core + jackson-core + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + + io.lettuce + lettuce-core + 6.1.5.RELEASE + + + org.apache.commons + commons-pool2 + 2.11.1 + + + + commons-codec + commons-codec + 1.15 + + + + + com.esotericsoftware + kryo + 4.0.0 + + + org.projectlombok + lombok + 1.18.22 + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + org.apache.commons + commons-jexl3 + 3.2.1 + + + + org.apache.tika + tika-core + 2.1.0 + + + + com.github.ben-manes.caffeine + caffeine + + + + mysql + mysql-connector-java + 8.0.27 + runtime + + + commons-collections + commons-collections + 3.2.2 + compile + + + + org.junit.platform + junit-platform-launcher + 1.6.2 + test + + + + org.junit.jupiter + junit-jupiter-api + 5.6.2 + test + + + + org.junit.jupiter + junit-jupiter-engine + 5.6.2 + test + + + org.junit.vintage + junit-vintage-engine + 5.6.2 + test + + + org.junit.jupiter + junit-jupiter-params + 5.6.2 + test + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + config/*.yaml + + + + + + + + + + + + + + + + + + + nexus-releases + Nexus Release Repository + http://47.92.149.153:7000/repository/maven-releases/ + + + nexus-snapshots + Nexus Snapshot Repository + http://47.92.149.153:7000/repository/maven-snapshots/ + + + diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/CoreApplication.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/CoreApplication.java new file mode 100644 index 0000000..5c73f81 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/CoreApplication.java @@ -0,0 +1,11 @@ +package cc.smtweb.framework.core; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CoreApplication { + public static void main(String[] args) { + SpringApplication.run(CoreApplication.class, args); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/CoreApplicationStartedListener.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/CoreApplicationStartedListener.java new file mode 100644 index 0000000..c369fcc --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/CoreApplicationStartedListener.java @@ -0,0 +1,33 @@ +package cc.smtweb.framework.core; + +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.mvc.controller.scan.ApplicationScanner; +import cc.smtweb.framework.core.systask.TaskStartEvent; +import cc.smtweb.framework.core.systask.WebStartedEvent; +import lombok.SneakyThrows; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.stereotype.Component; + +/** + * 执行接口扫描任务 + */ +@Component +public class CoreApplicationStartedListener implements ApplicationListener { + @SneakyThrows + @Override + public void onApplicationEvent(ApplicationStartedEvent event) { + System.out.println("onApplicationEvent============="); + ConfigurableApplicationContext applicationContext = event.getApplicationContext(); + + applicationContext.publishEvent(new TaskStartEvent()); + //包扫描 + ApplicationScanner.scan(applicationContext); + //初始化缓存 + CacheManager.getIntance().init(); + + // 通知 controller 正式使用 + applicationContext.publishEvent(new WebStartedEvent()); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/CoreAutoConfiguration.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/CoreAutoConfiguration.java new file mode 100644 index 0000000..f576544 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/CoreAutoConfiguration.java @@ -0,0 +1,33 @@ +package cc.smtweb.framework.core; + +import cc.smtweb.framework.core.db.jdbc.IdGenerator; +import cc.smtweb.framework.core.mvc.config.ControllerConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * @author kevin + */ +@Configuration +@ComponentScan +@EnableScheduling +public class CoreAutoConfiguration { + /** + * ID生成器的分步式机器码(1-1023) + */ + @Value("${smtweb.machine-id}") + private int machineId; + + @Bean + public IdGenerator idGenerator() { + return new IdGenerator(machineId); + } + + @Bean + public ControllerConfig coreControllerConfig() { + return new ControllerConfig("core", "cc.smtweb.framework.core"); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/R.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/R.java new file mode 100644 index 0000000..26a302d --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/R.java @@ -0,0 +1,117 @@ +package cc.smtweb.framework.core; + +import cc.smtweb.framework.core.exception.ExceptionMessage; + +import java.util.Date; + +/** + * 〈返回结果集〉 + * + * @author kevin + * @since 1.0.0 + */ +public class R extends SwMap { + protected R() { + this(0); + } + + protected R(int code) { + put("now", new Date()); + put("code", code); + } + + public static R success() { + return new R(); + } + + public static R success(Object data) { + return new R().put("data", data); + } + + public static R success(String name, Object data) { + return new R().put(name, data); + } + + public static R error() { + return error(ExceptionMessage.INNER_ERROR.getMsg()); + } + + public static R error(String msg) { + R r = new R(ExceptionMessage.INNER_ERROR.getCode()); + r.put("msg", msg); + return r; + } + + public static R error(int code, String msg) { + R r = new R(code); + r.put("msg", msg); + return r; + } + + public static R errorf(String format, Object...args) { + R r = new R(ExceptionMessage.INNER_ERROR.getCode()); + r.put("msg", String.format(format, args)); + return r; + } + +// public static R error(ExceptionMessage cm) { +// R r = new R(cm.getCode()); +// r.put("msg", cm.getMsg()); +// return r; +// } + +// public static R error(ExceptionMessage cm, Throwable ex) { +// R r = new R(cm.getCode()); +// r.put("msg", cm.getMsg()); +// r.put("exception", ex.getMessage()); +// return r; +// } + + public static R error(String msg, Throwable ex) { + R r = new R(ExceptionMessage.INNER_ERROR.getCode()); + r.put("msg", msg); + r.put("exception", ex.getMessage()); + return r; + } + + public static R error(ExceptionMessage cm, String msg) { + R r = new R(cm.getCode()); + r.put("msg", msg); + return r; + } + + @Override + public R put(String key, Object value) { + super.put(key, value); + return this; + } + + public R putNotNull(String key, Object value) { + if (value != null) { + super.put(key, value); + } + + return this; + } + + public void setMessage(ExceptionMessage cm) { + this.put("code", cm.getCode()); + this.put("msg", cm.getMsg()); + } + + public void setData(Object data) { + this.put("data", data); + } + + public int readCode() { + return this.readInt("code"); + } + + public String readMsg() { + return this.readString("msg"); + } + + public boolean readSuccess() { + return this.readInt("code", -1) == 0; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/SwException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/SwException.java new file mode 100644 index 0000000..387315c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/SwException.java @@ -0,0 +1,53 @@ +package cc.smtweb.framework.core; + +import cc.smtweb.framework.core.exception.ExceptionMessage; + +/** + * 〈全局异常〉 + * + * @author kevin + * @since 1.0.0 + */ +public class SwException extends RuntimeException { + + private static final long serialVersionUID = 8096609992852791423L; + +// private ExceptionMessage cm; + + public SwException() { + } + + public SwException(String msg) { + super(msg); + } + + public SwException(int code, String msg) { + super(msg); + } + + public SwException(String msg, Throwable e) { + super(msg, e); + } + + public SwException(Throwable e) { + super(e.getMessage(), e); + } + + public SwException(ExceptionMessage cm) { + super(cm.getMsg()); + } + + public SwException(ExceptionMessage cm, Throwable e) { + super(cm.getMsg(), e); + } + + public SwException(ExceptionMessage cm, String msg) { + super(msg); + } + + +// public ExceptionMessage getCm() { +// return cm; +// } + +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/SwIpAddr.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/SwIpAddr.java new file mode 100644 index 0000000..8a55568 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/SwIpAddr.java @@ -0,0 +1,9 @@ +package cc.smtweb.framework.core; + +import lombok.Data; + +@Data +public class SwIpAddr { + private String ip; + private int port; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/SwMap.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/SwMap.java new file mode 100644 index 0000000..6f61eb7 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/SwMap.java @@ -0,0 +1,93 @@ +package cc.smtweb.framework.core; + +import cc.smtweb.framework.core.util.MapUtil; + +import java.util.HashMap; +import java.util.Set; + +/** + * 通用map对象,用于无具体类型的传值 + * @author kevin + */ +public class SwMap extends HashMap { + public SwMap() {} + + public SwMap(int initialCapacity) { + super(initialCapacity); + } + + public String readString(String name) { + return MapUtil.readString(this, name, null); + } + + public String readString(String name, String defaultValue) { + return MapUtil.readString(this, name, defaultValue); + } + + public Long readLong(String name) { + return MapUtil.readLong(this, name, null); + } + + public Long readLong(String name, Long defaultValue) { + return MapUtil.readLong(this, name, defaultValue); + } + + public Long[] readLongArray(String name) { + return MapUtil.readLongArray(this, name, null); + } + + public Long[] readLongArray(String name, Long[] defaultValue) { + return MapUtil.readLongArray(this, name, defaultValue); + } + + public Set readLongSet(String name) { + return MapUtil.readLongSet(this, name); + } + + public Integer readInt(String name) { + return MapUtil.readInt(this, name, null); + } + + public Integer readInt(String name, Integer defaultValue) { + return MapUtil.readInt(this, name, defaultValue); + } + + public Float readFloat(String name) { + return MapUtil.readFloat(this, name, null); + } + + public Float readFloat(String name, Float defaultValue) { + return MapUtil.readFloat(this, name, defaultValue); + } + + public Double readDouble(String name) { + return MapUtil.readDouble(this, name, null); + } + + public Double readDouble(String name, Double defaultValue) { + return MapUtil.readDouble(this, name, defaultValue); + } + + public Boolean readBool(String name) { + return MapUtil.readBool(this, name, null); + } + + public Boolean readBool(String name, Boolean defaultValue) { + return MapUtil.readBool(this, name, defaultValue); + } + + @Override + public SwMap put(String name, Object value) { + if (value != null) { + super.put(name, value); + } else { + super.remove(name); + } + + return this; + } + + public static SwMap of(String name, Object value) { + return new SwMap().put(name, value); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwAction.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwAction.java new file mode 100644 index 0000000..112ed75 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwAction.java @@ -0,0 +1,21 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 配置在@SwService中的函数,对应API请求,默认公用函数不用配置拦截器的函数, + * 也可以作为拦截实现的基类 + * @author kevin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface SwAction { + /** + * 重写API请求地址,不配置使用: 服务类地址 + “/” + 函数名 + * @return API请求地址 + */ + String value() default ""; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwAttr.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwAttr.java new file mode 100644 index 0000000..e9c3675 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwAttr.java @@ -0,0 +1,16 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.*; + +/** + * 参数注解,request定制的上下文内容,定制使用 + * @author kevin + * + */ + +@Target( { ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SwAttr { + String value() default ""; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwBean.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwBean.java new file mode 100644 index 0000000..31b4376 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwBean.java @@ -0,0 +1,22 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 被该注释修饰的类提供服务,如Dao,Service等 + * @author kevin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface SwBean { + String value() default ""; + + /** + * 是否强行生成对象,不检查单例 + * @return 是否强行生成对象 + */ + boolean alwaysCreate() default false; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwBody.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwBody.java new file mode 100644 index 0000000..50ff91b --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwBody.java @@ -0,0 +1,15 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.*; + +/** + * 参数注解,当编译器无法找到参数名称时,需要用该注解标明参数名称,当然你也可以用该参数标注参数别名与表单域字段名同意 + * @author kevin + * + */ +@Target( { ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SwBody { + String value() default ""; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwCache.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwCache.java new file mode 100644 index 0000000..b6dbbd4 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwCache.java @@ -0,0 +1,26 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 被该注释修饰的类提供缓存服务 + * + * @author kevin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface SwCache { + //唯一标识 + String ident(); + //标题,展示用 + String title(); + //依赖的缓存ident,多个用英文逗号分隔 + String depends() default ""; + //是否懒加载 + boolean lazy() default false; + //失效时间,单位分钟 + long timeout() default 0; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwColumn.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwColumn.java new file mode 100644 index 0000000..c4d88ed --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwColumn.java @@ -0,0 +1,38 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 被该注释修饰的方法对应了字段名和类型 + * @author kevin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface SwColumn { + String value() default ""; + + /** 字段作用,前端用RefType表示 */ + Type[] type() default {}; + + public static enum Type { + // 主键 + ID, + // 上级ID,树结构需要 + PARENT_ID, + // 排序字段,树结构需要 + ORDER, + // 编码字段 + CODE, + // 名词字段 + NAME, + // 主表ID,MapToOne + MASTER_ID, + // 创建时间 + CREATE_TIME, + // 更新时间 + LAST_TIME, + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwColumnForeign.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwColumnForeign.java new file mode 100644 index 0000000..fa1ada1 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwColumnForeign.java @@ -0,0 +1,21 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 被该注释修饰的方法对应了外键表名和字段名,名称可选 + * @author kevin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface SwColumnForeign { + // 外键表名 + String table() default ""; + // ID字段名 + String id() default ""; + // 唯一名称字段名 + String code() default ""; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwConstruct.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwConstruct.java new file mode 100644 index 0000000..ed9eef5 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwConstruct.java @@ -0,0 +1,20 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 通过对@SwService/@SwBean类中的函数注解,服务启动时会调用一次被注解的函数 + * @author kevin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface SwConstruct { + /** + * 销毁顺序,值越小越先创建 + * @return 销毁顺序值 + */ + int order() default 0; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwDestroy.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwDestroy.java new file mode 100644 index 0000000..da89811 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwDestroy.java @@ -0,0 +1,20 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 通过对@SwService/@SwBean类中的函数注解,服务停止时会调用一次被注解的函数 + * @author kevin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface SwDestroy { + /** + * 销毁顺序,值越大越先销毁 + * @return 销毁顺序值 + */ + int order() default 0; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwHeaderParam.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwHeaderParam.java new file mode 100644 index 0000000..69af9d4 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwHeaderParam.java @@ -0,0 +1,16 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.*; + + +/** + * 路径注解 + * @author kevin + * + */ +@Target( { ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SwHeaderParam { + String value() default ""; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwParam.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwParam.java new file mode 100644 index 0000000..033d671 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwParam.java @@ -0,0 +1,18 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.*; + + +/** + * 参数注解,当编译器无法找到参数名称时,需要用该注解标明参数名称,当然你也可以用该参数标注参数别名与表单域字段名同意 + * @author kevin + * + */ +@Target( { ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SwParam { + String value() default ""; + /** 注入类型,有多个类实现/子类时使用 */ + Class type() default Object.class; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwPathParam.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwPathParam.java new file mode 100644 index 0000000..c4ced93 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwPathParam.java @@ -0,0 +1,16 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.*; + + +/** + * 路径注解 + * @author kevin + * + */ +@Target( { ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SwPathParam { +// String value() default ""; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwPerm.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwPerm.java new file mode 100644 index 0000000..0ef2c48 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwPerm.java @@ -0,0 +1,21 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 被该注释修饰的方法都会经过切面拦截校验权限,默认是需要已登录权限 + * @author kevin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface SwPerm { + /** 无权限控制的值,在函数上注解@SwPerm(SwPerm.NONE) */ + static final String NONE = "*"; + static final String SESSION = ""; + + /** 权限定义值 */ + String value() default SESSION; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwScheduling.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwScheduling.java new file mode 100644 index 0000000..261f1f0 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwScheduling.java @@ -0,0 +1,30 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 通过对@SwService/@SwBean类中的函数注解,定时任务方法注解 + * + * @author xkliu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value={ElementType.METHOD}) +public @interface SwScheduling { + /** + * 时间触发条件,如果值是单数字就是用秒定时,否则就是采用linux/unux crondtab 样式 + */ + String value(); + + /** + * 定时任务分组条件, 有值时同一分组不允许并发执行 + */ + String group() default ""; + + /** + * 是否支持多服务同时运行 + */ + boolean multiServer() default false; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwService.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwService.java new file mode 100644 index 0000000..ba25e6c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwService.java @@ -0,0 +1,16 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 被该注释修饰的类提供控制器服务 + * @author kevin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface SwService { + String value() default ""; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwTable.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwTable.java new file mode 100644 index 0000000..464f322 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/annotation/SwTable.java @@ -0,0 +1,17 @@ +package cc.smtweb.framework.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 被该注释修饰的类对应了数据库表名(库+表的形式,如 sw_user.sys_user) + * @author kevin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface SwTable { + /** 库名+表名 */ + String value() default ""; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/cache/AbstractCache.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/cache/AbstractCache.java new file mode 100644 index 0000000..f7b5262 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/cache/AbstractCache.java @@ -0,0 +1,493 @@ +package cc.smtweb.framework.core.cache; + +import cc.smtweb.framework.core.annotation.SwCache; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.cache.ISwCache; +import cc.smtweb.framework.core.redis.RedisBroadcastEvent; +import cc.smtweb.framework.core.redis.RedisManager; +import cc.smtweb.framework.core.util.CommUtil; +import cc.smtweb.framework.core.util.kryo.KryoTool; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.github.benmanes.caffeine.cache.RemovalCause; +import com.github.benmanes.caffeine.cache.Scheduler; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.Serializable; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * 封装caffeine的抽象Cache类,实现内存缓存,并支持用redis广播进行数据缓存同步 + * + * @param <> 缓存的值类 + * @author zhenggm + */ +@Slf4j +public abstract class AbstractCache implements ISwCache { + protected final static int LS_NONE = 0; + protected final static int LS_LOADING = 1; + protected final static int LS_LOADED = 2; + + private final static String split_char = "-"; + //唯一标识 + protected String ident; + //展示名称 + protected String title; + //是否懒加载,懒加载时,将不从库loadall + protected boolean lazy; + //依赖 + protected String[] depends; + + //数据加载状态 + private short loadStatu = LS_NONE; + private Class pTypeClass = null; + + + private LoadingCache cache; + private Map cacheOrg = new ConcurrentHashMap<>(); + //本地缓存对象,按List缓存,减轻redis压力 + protected Map> mapListLocal = new ConcurrentHashMap<>(); + //本地按其他键值缓存的对象 + protected Map> mapMapLocal = new ConcurrentHashMap<>(); + + /*注册要按list缓存的信息,key=list的类别,按什么字段来缓存。IGetBeanKey为根据bean获取此bean的key + 如资金科目明细项,需按资金科目缓存List。key=sc,value=class_id + */ + protected Map> mapListReg = new HashMap<>(); + protected Map> mapMapReg = new HashMap<>(); + + public AbstractCache() { + pTypeClass = CommUtil.getParameterizedType(getClass()); + } + + //注册 + protected void install(ScheduledExecutorService executorService) { + SwCache swCache = this.getClass().getAnnotation(SwCache.class); + if (swCache != null) { + ident = swCache.ident(); + title = swCache.title(); + lazy = swCache.lazy(); + depends = StringUtils.split(swCache.depends(), ","); + } + final @NonNull Caffeine kvCaffeine = Caffeine.newBuilder() + .scheduler(Scheduler.forScheduledExecutorService(executorService)); + if (swCache.timeout() > 0) { + kvCaffeine.expireAfterAccess(swCache.timeout(), TimeUnit.MINUTES); + } + this.cache = kvCaffeine.build(this::onLoad); + } + + public boolean isNotInited() { + return loadStatu == LS_NONE; + } + + /** + * 注册标识,用于同步数据事件通知 + */ + public String getIdent() { + return ident; + } + + public String getTitle() { + return title; + } + + public boolean isLazy() { + return lazy; + } + + public String[] getDepends() { + return depends; + } + + /** + * 初始化 + * redis有,则从redis下载; + * 否则,非lazy,从数据库加载,并更新redis + */ + protected void init() { + if (RedisManager.getInstance().exists(getIdent())) { + //从缓存服务器下载到本地 + syncCache(); + } else if (!lazy) { + refresh(); + } + } + + /** + * 注册其他key的List缓存,如tree的children + * + * @param key + * @param iGetBeanKey + */ + protected void regList(String key, IGetBeanKey iGetBeanKey) { + mapListReg.put(key, iGetBeanKey); + } + + /** + * 注册其他key的Map缓存,如按code缓存 + * + * @param key + * @param iGetBeanKey + */ + + protected void regMap(String key, IGetBeanKey iGetBeanKey) { + mapMapReg.put(key, iGetBeanKey); + } + + //获取bean的id + protected abstract String getId(T bean); + + /** + * 从redis获取全部缓存 + * redis上只保留,拉下来后,自行组织其他格式缓存 + */ + public void syncCache() { + loadStatu = LS_LOADING; + + cache.invalidateAll(); + cacheOrg.clear(); + mapListLocal.clear(); + mapMapLocal.clear(); + + List vals = RedisManager.getInstance().hVals(getIdent()); + for (byte[] v : vals) { + T bean = CommUtil.readObject(v, pTypeClass); + doUpdate(bean); + } + loadStatu = LS_LOADED; + } + + /** + * 监听redis同步事件 + * + * @param event + */ + public void syncCache(RedisBroadcastEvent event) { + //加载中,忽略 + if (loadStatu == LS_LOADING) { + log.info("加载中,忽略本次同步通知:" + event.toString()); + return; + } + switch (event.getAction()) { + case RedisBroadcastEvent.CODE_REMOVE: + doRemove(event.getKey()); + break; + case RedisBroadcastEvent.CODE_CLEAR: + doClear(); + break; + case RedisBroadcastEvent.CODE_CACHE_UPDATE: + T bean = RedisManager.getInstance().hGet(getIdent(), event.getKey(), pTypeClass); + if (bean != null) { + doRemove(getId(bean)); + doUpdate(bean); + } + break; + case RedisBroadcastEvent.CODE_CACHE_REFRESH: + syncCache(); + break; + default: + throw new IllegalStateException("Unexpected value: " + event.getAction()); + } + } + + + /** + * 通过指定的key获取缓存对象值 + * + * @param key 缓存key值 + * @return 缓存对象值 + */ + protected T load(String key) { + return null; + } + + /** + * 从数据库加载全部,lazy模式可以不用实现 + */ + protected List loadAll() { + return null; + } + + /** + * @param key + * @return + */ + private T onLoad(String key) { + if (lazy) {//非懒加载,不管 + T result = load(key); + + if (result != null) { + this.put(result); + } + + return result; + } + return null; + } + + /** + * 删除时,需要拿原来的对象;对象被取出后,可能被修改 + * + * @param key + * @return + */ + protected T getForRemove(String key) { + return cacheOrg.get(key); + } + + /** + * 本地删除缓存 + * + * @param key + */ + protected void doRemove(String key) { + T bean = getForRemove(key); + if (bean != null) { + for (Map.Entry> entry : mapListReg.entrySet()) { + doRemoveList(entry.getKey(), getBeanKey(entry.getValue(), bean), bean); + } + + for (Map.Entry> entry : mapMapReg.entrySet()) { + doRemoveMap(entry.getKey(), getBeanKey(entry.getValue(), bean)); + } + } + cache.invalidate(key); + cacheOrg.remove(key); + } + + /** + * 插入新对象 + */ + protected void doUpdate(T value) { + final String key = getId(value); + cache.put(key, value); + cacheOrg.put(key, CommUtil.cloneObj(value, pTypeClass)); + + for (Map.Entry> entry : mapListReg.entrySet()) { + doUpdateList(entry.getKey(), getBeanKey(entry.getValue(), value), value); + } + + for (Map.Entry> entry : mapMapReg.entrySet()) { + doUpdateMap(entry.getKey(), getBeanKey(entry.getValue(), value), value); + } + } + + //本地调用,删除一个对象时,更新列表缓存 + private void doRemoveList(String regionKey, String key, T value) { + if (StringUtils.isEmpty(key)) return; + Set list = mapListLocal.get(regionKey + split_char + key); + if (list == null) { + return; + } + list.remove(value); + } + + //本地调用,删除对象时,更新map缓存 + private void doRemoveMap(String regionKey, String key) { + if (StringUtils.isEmpty(key)) return; + Map map = mapMapLocal.get(regionKey); + if (map != null) { + map.remove(key); + } + } + + //本地调用,更新列表缓存 + private void doUpdateList(String regionKey, String key, T value) { + if (StringUtils.isEmpty(key)) return; + Set list = mapListLocal.computeIfAbsent(regionKey + split_char + key, k -> new LinkedHashSet<>()); + list.add(value); + } + + //本地调用,更新对象时,更新map缓存 + private void doUpdateMap(String regionKey, String key, T value) { + if (StringUtils.isEmpty(key)) return; + Map map = mapMapLocal.computeIfAbsent(regionKey, k -> new HashMap<>()); + map.put(key, value); + } + + /** + * 外部调用,更新缓存:先删除,再更新本地缓存,更新并通知redis + * + * @param bean + */ + public final void put(T bean) { + doRemove(getId(bean)); + doUpdate(bean); + if (loadStatu != LS_LOADING) { + RedisManager.getInstance().hSet(getIdent(), getId(bean), bean); + //通知redis + publishUpdate(getId(bean)); + } + } + + /** + * 将对象重置为修改前的值,用于取出修改后,事务提交失败 + * + * @param bean + */ + public final void reset(T bean) { + final String id = getId(bean); + T b = getForRemove(id); + if (b != null) { + cache.put(id, b); + } + } + + @Override + public final T get(String key) { + return cache.get(key); + } + + public final T get(long key) { + return cache.get(String.valueOf(key)); + } + + /** + * 按其他key取bean + * + * @param rk 注册类型 + * @param key + * @return + */ + protected final T localGetByKey(String rk, String key) { + Map map = mapMapLocal.get(rk); + if (map != null) { + return map.get(key); + } + return null; + } + + public final Set localGetListByKey(String rk, String key) { + return mapListLocal.get(rk + split_char + key); + } + + /** + * 通知redis更新:更新单个bean + * + * @param key + */ + protected final void publishUpdate(String key) { + RedisBroadcastEvent message = new RedisBroadcastEvent(); + message.setIdent(getIdent()); + message.setKey(key); + message.setAction(RedisBroadcastEvent.CODE_CACHE_UPDATE); + RedisManager.getInstance().publish(message); + } + + /** + * 通知redis更新:删除单个bean + * + * @param key + */ + @Override + public final void publishRemove(String key) { + RedisBroadcastEvent message = new RedisBroadcastEvent(); + message.setIdent(getIdent()); + message.setKey(key); + message.setAction(RedisBroadcastEvent.CODE_REMOVE); + RedisManager.getInstance().publish(message); + } + + /** + * 通知redis更新:清空缓存 + */ + @Override + public final void publishClear() { + RedisBroadcastEvent message = new RedisBroadcastEvent(); + message.setIdent(getIdent()); + message.setAction(RedisBroadcastEvent.CODE_CLEAR); + RedisManager.getInstance().publish(message); + } + + /** + * 通知redis更新:刷新缓存 * + */ + public final void publishRefresh() { + RedisBroadcastEvent message = new RedisBroadcastEvent(); + message.setIdent(getIdent()); + message.setAction(RedisBroadcastEvent.CODE_CACHE_REFRESH); + RedisManager.getInstance().publish(message); + } + + /** + * 外部调用,删除单个对象 + * + * @param key + */ + public void remove(String key) { + this.doRemove(key); + RedisManager.getInstance().hdel(getIdent(), key); + publishRemove(key); + } + + public void remove(long key) { + remove(String.valueOf(key)); + } + + /** + * 本地调用,清空缓存 + */ + protected void doClear() { + cache.cleanUp(); + cacheOrg.clear(); + mapListLocal.clear(); + mapMapLocal.clear(); + } + + /** + * 外部调用,清空缓存:清空本地缓存,并更新redis,通知其他服务 + */ + public void clear() { + doClear(); + RedisManager.getInstance().del(getIdent()); + publishClear(); + } + + /** + * 重载缓存:先清空,再重载 + */ + public void refresh() { + loadStatu = LS_LOADING; + doClear(); + List list = loadAll(); + if (list != null) { + for (T bean : list) { + doUpdate(bean); + } + } + loadStatu = LS_LOADED; + RedisManager.getInstance().hmSet(getIdent(), cacheOrg); + publishRefresh(); + } + + /** + * 根据接口,得到bean的缓存key + * + * @param iGetBeanKey + * @param bean + * @return + */ + protected String getBeanKey(IGetBeanKey iGetBeanKey, T bean) { + return iGetBeanKey.getKey(bean); + } + + public Collection getAll() { + return cacheOrg.values(); + } + + /** + * 获取bean缓存key的接口类,用于按非id的缓存,如code等 + * + * @param + */ + public interface IGetBeanKey { + String getKey(T bean); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/cache/CacheManager.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/cache/CacheManager.java new file mode 100644 index 0000000..6d44d4e --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/cache/CacheManager.java @@ -0,0 +1,109 @@ +package cc.smtweb.framework.core.cache; + +import cc.smtweb.framework.core.annotation.SwTable; +import cc.smtweb.framework.core.db.cache.EntityCache; +import cc.smtweb.framework.core.db.cache.ModelTableCache; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.framework.core.redis.RedisBroadcastEvent; +import cc.smtweb.framework.core.util.SpringUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +/** + * 内存缓存管理器,管理lazy加载的缓存,长时间不使用会失效 + */ +@Slf4j +public class CacheManager { + //所有缓存对象 + private final Map cacheMap = new HashMap<>(); + private final Map cacheMapCls = new HashMap<>(); + //记录顺序用 + private List listCache = new ArrayList<>(); + //缓存清理线程池 + private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2); + + public AbstractCache getCache(String ident) { + final AbstractCache cache = cacheMap.get(ident); + if (cache != null && cache.isNotInited()) { + cache.init(); + } + return cache; + } + + public T getCache(Class clazz) { + final T cache = (T) cacheMapCls.get(clazz); + if (cache != null && cache.isNotInited()) { + cache.init(); + } + return cache; + } + + public static CacheManager getIntance() { + return SpringUtil.getBean(CacheManager.class); + } + + // 初始化cache + public boolean install(AbstractCache cache) { + cache.install(executorService); + if (cacheMap.putIfAbsent(cache.getIdent(), cache) == null) { + cacheMapCls.put(cache.getClass(), cache); + listCache.add(cache); + return true; + } + + return false; + } + + /** + * 启动时的初始化及预加载 + * 1、按依赖关系排序 + * 2、预加载(lazy模式不从库加载) + */ + public void init() { + listCache.sort((o1, o2) -> { + //o1依赖o2 + if (isContains(o1.getDepends(), o2.getIdent())) return 1; + //o2依赖o1 + if (isContains(o2.getDepends(), o1.getIdent())) return -1; + + return 0;//getPluginIndex(o1) - getPluginIndex(o2); + }); + for (AbstractCache cache : listCache) { + cache.init(); + } + //按表加载并初始化 + for (ModelTable table : ModelTableCache.getInstance().getAll()) { + if (table.isNeedCache() && !cacheMap.containsKey(table.getName())) { + EntityCache cache = new EntityCache(table.getName()); + cache.install(executorService); + cacheMapCls.put(cache.getClass(), cache); + cacheMap.put(table.getName(), cache); + } + } + } + + private boolean isContains(String[] src, String dest) { + if (src == null || src.length == 0) return false; + for (String s : src) { + if (s.trim().equals(dest)) return true; + } + return false; + } + + @EventListener + public void onRedisBroadcastEvent(RedisBroadcastEvent event) { + AbstractCache cache = getCache(event.getIdent()); + if (cache == null) { + log.info("接收到不存在的缓存更新!(" + event.toString() + ")"); + return; + } + cache.syncCache(event); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/cache/ISwCache.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/cache/ISwCache.java new file mode 100644 index 0000000..59d3c8a --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/cache/ISwCache.java @@ -0,0 +1,24 @@ +package cc.smtweb.framework.core.cache; + +public interface ISwCache { + /** 权限缓存名称 */ + String REALM_CACHE = "RealmCache"; + + /** + * 根据键值获取缓存对象 + * @param key 缓存唯一键值 + * @return 缓存对象 + */ + V get(K key); + + /** + * 给redis发送广播消息,多服务同步删除相同key值的缓存对象 + * @param key 缓存键值 + */ + void publishRemove(K key); + + /** + * 给redis发送广播消息,多服务同步清空相同类型的缓存对象 + */ + void publishClear(); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/DbEngine.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/DbEngine.java new file mode 100644 index 0000000..b0ae535 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/DbEngine.java @@ -0,0 +1,271 @@ +package cc.smtweb.framework.core.db; + +import cc.smtweb.framework.core.db.impl.DefaultEntity; +import cc.smtweb.framework.core.db.jdbc.IdGenerator; +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; +import cc.smtweb.framework.core.util.SpringUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 提供数据库基本操作和代理EntityDao操作,在DbEngineConfiguration中初始化 + * + * @author xkliu + */ +@Slf4j +public class DbEngine extends JdbcEngine { + private String dbSchema; + private final Map, EntityDao> daoMap = new ConcurrentHashMap<>(); + private final Map> tableDaoMap = new ConcurrentHashMap<>(); + + public static DbEngine getInstance() { + return SpringUtil.getBean(DbEngine.class); + } + + public DbEngine(JdbcTemplate jdbcTemplate, IdGenerator idGenerator, String type) { + super(jdbcTemplate, idGenerator, type); + } + + /** + * bean为Data格式,调用此方法 + * @param type bean类 + * @param + * @return + */ + public EntityDao findDao(Class type) { + EntityDao handler = (EntityDao) daoMap.get(type); + + if (handler == null) { + synchronized (daoMap) { + handler = new EntityDao<>(type, this); + daoMap.put(type, handler); + } + } + + return handler; + } + + public EntityDao findDao(String tableName) { + EntityDao handler = tableDaoMap.get(type); + + if (handler == null) { + synchronized (tableDaoMap) { + handler = new EntityDao<>(tableName, this); + tableDaoMap.put(tableName, handler); + } + } + + return handler; + } + + public String getDbSchema() { + if (dbSchema == null) { + synchronized (DbEngine.class) { + if (dbSchema == null) { + try { + final ResultSetExtractor rse = new ResultSetExtractor() { + @Override + public String extractData(ResultSet rs) throws SQLException { + if (rs.next()) return rs.getString(1); + throw new SQLException("未指定当前数据库,请检查您的链接!"); + } + }; + + dbSchema = query("SELECT database()", rse); + + } catch (Exception e) { + log.error("获取mysql的数据库失败", e); + return null; + } + } + } + } + return dbSchema; + } + + /** + * 根据PO对象的ID值更新其余所有字段 + * + * @param entity PO对象 + * @param PO对象类型 + * @return 更新数量 + */ + public int updateEntity(T entity) { + return updateEntity(entity, null, null); + } + + /** + * 使用ID字段更新单行数据 + * + * @param entity PO值对象,对象属性是需要更新的值 + * @param fields 需要更新额字段列表,逗号分隔 + * @param PO值对象类型 + * @return 更新数量 + */ + public int updateEntity(T entity, String fields) { + return updateEntity(entity, fields, null); + } + + /** + * 指定自定义条件更新对象 + * + * @param entity PO值对象,对象属性是需要更新的值和更新条件值 + * @param fields 需要更新额字段列表,逗号分隔 + * @param whereFields 更新条件字段列表,逗号分隔 + * @param PO值对象类型 + * @return 更新数量 + */ + public int updateEntity(T entity, String fields, String whereFields) { + EntityDao dao = findDao((Class) entity.getClass()); + return dao.updateEntity(entity, fields, whereFields); + } + + /** + * 用PO对象所有字段入单行数据 + * + * @param entity PO对象 + * @param PO对象类型 + * @return 更新数量 + */ + public int insertEntity(T entity) { + return insertEntity(entity, null); + } + + /** + * 插入单行数据 + * + * @param entity PO对象 + * @param fields 逗号分隔的字段列表 + * @param PO对象类型 + * @return 更新数量 + */ + public int insertEntity(T entity, String fields) { + EntityDao dao = findDao((Class) entity.getClass()); + return dao.insertEntity(entity, fields); + } + + /** + * 根据ID值删除单行数据 + * + * @param entity PO对象 + * @param PO对象类型 + * @return 删除数量 + */ + public int deleteEntity(T entity) { + EntityDao dao = findDao((Class) entity.getClass()); + return dao.deleteEntity(entity); + } + + /** + * 根据ID值删除单行数据 + * + * @param entityType PO对象类型 + * @param id 记录主建值 + * @param PO对象 + * @return 删除数量 + */ + public int deleteEntity(Class entityType, Long id) { + EntityDao dao = findDao(entityType); + return dao.deleteEntity(id); + } + + /** + * 根据ID值删除单行数据 + * + * @param entityType PO对象类型 + * @param whereSql Where条件SQL语句,以where开头 + * @param params 条件的值,可以多个 + * @param PO对象 + * @return 删除数量 + */ + public int deleteEntity(Class entityType, String whereSql, Object... params) { + EntityDao dao = findDao(entityType); + return dao.deleteEntity(whereSql, params); + } + + /** + * 读取实体对象ID值 + * + * @param entity + * @param + * @return + */ + public Long readEntityId(T entity) { + EntityDao dao = findDao((Class) entity.getClass()); + return dao.readId(entity); + } + + /** + * 查询单行数据,返回bean + */ + public T queryEntity(Class type, Long id) { + return queryEntity(type, id, null); + } + + public T queryEntity(Class type, Long id, String fields) { + return findDao(type).queryEntity(id, fields); + } + + /** + * 查询对象所有数据,返回列表 + */ + public List query(Class type) { + return query(type, null); + } + + /** + * 查询对象所有数据,返回列表 + */ + public List query(Class type, String fields) { + return findDao(type).query(fields); + } + + /** + * 传入where条件查询实体类别 + * + * @param type 实体类型类 + * @param sqlWhere sql的where语句部分,不包含from + * @param params 条件参数值 + * @param 实体类型 + * @return + */ + public List queryWhere(Class type, String sqlWhere, Object... params) { + return findDao(type).queryWhere(sqlWhere, params); + } + + /** + * 批量插入单行数据 + * + * @param entities PO对象列表 + * @param 实体类型 + * @return 更新数量 + */ + public int[] batchInsertEntity(List entities) { + if (entities == null || entities.isEmpty()) { + return null; + } + return findDao((Class) entities.get(0).getClass()).batchInsertEntity(entities, null); + } + + /** + * 批量插入单行数据 + * + * @param entities PO对象列表 + * @param fields 逗号分隔的字段列表 + * @param 实体类型 + * @return 更新数量 + */ + public int[] batchInsertEntity(List entities, String fields) { + if (entities == null || entities.isEmpty()) { + return null; + } + return findDao((Class) entities.get(0).getClass()).batchInsertEntity(entities, fields); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/EntityDao.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/EntityDao.java new file mode 100644 index 0000000..8731a5f --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/EntityDao.java @@ -0,0 +1,281 @@ +package cc.smtweb.framework.core.db; + +import cc.smtweb.framework.core.db.dao.AbstractEntityDao; +import cc.smtweb.framework.core.db.dao.EntityColumn; +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; +import cc.smtweb.framework.core.db.vo.def.FieldType; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 提供数据对象Dao操作 + * + * @author xkliu + */ +public class EntityDao extends AbstractEntityDao { + @Getter + protected JdbcEngine jdbcEngine; + + public EntityDao(Class type, JdbcEngine jdbcEngine) { + super(type); + this.jdbcEngine = jdbcEngine; + } + + public EntityDao(String tableName, JdbcEngine jdbcEngine) { + super(tableName); + this.jdbcEngine = jdbcEngine; + } + + /** + * 获取数据库唯一id + * + * @return 返回ID值 + */ + public long nextId() { + return this.jdbcEngine.nextId(); + } + + /** + * 根据PO对象的ID值更新其余所有字段 + * + * @param entity PO对象 + * @return 更新数量 + */ + public int updateEntity(T entity) { + return updateEntity(entity, null, null); + } + + /** + * 使用ID字段更新单行数据 + * + * @param entity PO值对象,对象属性是需要更新的值 + * @param fields 需要更新额字段列表,逗号分隔 + * @return 更新数量 + */ + public int updateEntity(T entity, String fields) { + return updateEntity(entity, fields, null); + } + + /** + * 指定自定义条件更新对象 + * + * @param entity PO值对象,对象属性是需要更新的值和更新条件值 + * @param fields 需要更新额字段列表,逗号分隔 + * @param whereFields 更新条件字段列表,逗号分隔 + * @return 更新数量 + */ + public int updateEntity(T entity, String fields, String whereFields) { + StringBuilder sb = new StringBuilder(); + Object[] params = this.handleUpdate(entity, sb, fields, whereFields); + + return jdbcEngine.update(sb.toString(), params); + } + + /** + * 用PO对象所有字段入单行数据 + * + * @param type PO对象字段范围的类,是entity的父类 + * @param entity PO对象 + * @return 更新数量 + */ + public int insertEntity(Class type, T entity) { + return insertEntity(type, entity, null); + } + + /** + * 用PO对象所有字段入单行数据 + * + * @param entity PO对象 + * @return 更新数量 + */ + public int insertEntity(T entity) { + return insertEntity(entity.getClass(), entity, null); + } + + /** + * 插入单行数据 + * + * @param entity PO对象 + * @param fields 逗号分隔的字段列表 + * @return 更新数量 + */ + public int insertEntity(T entity, String fields) { + return insertEntity(entity.getClass(), entity, fields); + } + + private int insertEntity(Class type, T entity, String fields) { + StringBuilder sb = new StringBuilder(); + + Object[] params = handleInsert(entity, sb, fields); + + return jdbcEngine.update(sb.toString(), params); + } + + /** + * 批量插入单行数据 + * + * @param entities PO对象列表 + * @return 更新数量 + */ + public int[] batchInsertEntity(List entities) { + return batchInsertEntity(entities, null); + } + + /** + * 批量插入单行数据 + * + * @param entities PO对象列表 + * @param fields 逗号分隔的字段列表 + * @return 更新数量 + */ + public int[] batchInsertEntity(List entities, String fields) { + StringBuilder sql = new StringBuilder(); + + sql.append("insert into ").append(tableName).append("("); + + List listFields = adjustFields(fields, FieldType.CREATE_TIME, FieldType.LAST_TIME); + List insertColumns = new ArrayList<>(this.columns.size()); + + if (listFields == null) { + for (EntityColumn column : this.columns.values()) { + sql.append(column.getField().getName()).append(","); + insertColumns.add(column); + } + } else { + + for (String name : listFields) { + EntityColumn column = this.columns.get(name.trim()); + sql.append(column.getField().getName()).append(","); + insertColumns.add(column); + } + } + + sql.setCharAt(sql.length() - 1, ')'); + + // values(?,?) + sql.append(" values("); + for (int i = insertColumns.size(); i > 0; i--) { + sql.append("?,"); + } + sql.setCharAt(sql.length() - 1, ')'); + + // 参数列表 + List listParams = new ArrayList<>(entities.size()); + for (T obj : entities) { + List params = new ArrayList<>(this.columns.size()); + + for (EntityColumn column : this.columns.values()) { + params.add(column.readValue(obj)); + } + + listParams.add(params.toArray()); + } + + return jdbcEngine.batchUpdate(sql.toString(), listParams); + } + + + /** + * 根据ID值删除单行数据 + * + * @param entity PO对象 + * @return 删除数量 + */ + public int deleteEntity(T entity) { + StringBuilder sb = new StringBuilder(); + + Object[] params = handleDelete(entity, sb); + + return jdbcEngine.update(sb.toString(), params); + } + + /** + * 根据ID值删除单行数据 + * + * @param id 记录主建值 + * @return 删除数量 + */ + public int deleteEntity(Long id) { + StringBuilder sb = new StringBuilder(); + handleDelete(sb); + + return jdbcEngine.update(sb.toString(), id); + } + + /** + * 根据ID值删除单行数据 + * + * @param whereSql Where条件SQL语句,以where开头 + * @param params 条件的值,可以多个 + * @return 删除数量 + */ + public int deleteEntity(String whereSql, Object... params) { + StringBuilder sb = new StringBuilder("DELETE FROM "); + sb.append(getTableName()).append(" ").append(whereSql); + + return jdbcEngine.update(sb.toString(), params); + } + + /** + * 读取实体对象ID值 + * + * @param entity + * @return + */ + public Long readEntityId(T entity) { + return (Long) readId(entity); + } + + /** + * 查询单行数据,返回bean + */ + public T queryEntity(Long id) { + return queryEntity(id, null); + } + + public T queryEntity(Long id, String fields) { + StringBuilder sb = new StringBuilder(); + handleSelectOne(sb, fields); + + List list = jdbcEngine.query(sb.toString(), type, id); + + if (list != null && !list.isEmpty()) { + return list.get(0); + } + + return null; + } + + /** + * 查询对象所有数据,返回列表 + */ + public List query() { + return query(null); + } + + /** + * 查询对象所有数据,返回列表 + */ + public List query(String fields) { + StringBuilder sb = new StringBuilder(); + handleSelect(sb, fields); + + return jdbcEngine.query(sb.toString(), type); + } + + /** + * 查询对象所有数据,返回列表 + */ + public List queryWhere(String sqlWhere, Object... params) { + StringBuilder sb = new StringBuilder(); + handleSelect(sb, null); + if (StringUtils.isNotEmpty(sqlWhere)) { + sb.append(" where ").append(sqlWhere); + } + + return jdbcEngine.query(sb.toString(), type, params); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/cache/EntityCache.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/cache/EntityCache.java new file mode 100644 index 0000000..898f660 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/cache/EntityCache.java @@ -0,0 +1,64 @@ +package cc.smtweb.framework.core.db.cache; + +import cc.smtweb.framework.core.annotation.SwCache; +import cc.smtweb.framework.core.cache.AbstractCache; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.EntityDao; +import cc.smtweb.framework.core.db.impl.DefaultEntity; +import cc.smtweb.framework.core.db.vo.ModelCache; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.framework.core.util.CommUtil; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.Scheduler; +import org.apache.commons.lang3.StringUtils; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Created by Akmm at 2022/2/19 17:19 + */ +public class EntityCache extends AbstractCache { + private String tableName; + + public EntityCache(String tableName) { + this.tableName = tableName; + ModelTable table = ModelTableCache.getInstance().getByName(tableName); + + ident = tableName; + title = table.getTitle(); + lazy = false; + + if (!table.isNeedCache() || CommUtil.isEmpty(table.getCaches())) return; + + for (ModelCache cache : table.getCaches()) { + final IGetBeanKey key = bean -> { + String ret = ""; + for (String s : StringUtils.split(cache.getFields(), ",")) { + if (StringUtils.isNotEmpty(s)) { + ret += "_" + bean.getStr(s); + } + } + return ret; + }; + if (cache.isMapType()) { + regMap(cache.getName(), key); + } else { + regList(cache.getName(), key); + } + } + } + + @Override + protected String getId(DefaultEntity bean) { + return String.valueOf(bean.getEntityId()); + } + + @Override + protected List loadAll() { + EntityDao dao = DbEngine.getInstance().findDao(tableName); + return dao.query(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/cache/ModelDatabaseCache.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/cache/ModelDatabaseCache.java new file mode 100644 index 0000000..b19c1fa --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/cache/ModelDatabaseCache.java @@ -0,0 +1,45 @@ +package cc.smtweb.framework.core.db.cache; + +import cc.smtweb.framework.core.annotation.SwCache; +import cc.smtweb.framework.core.cache.AbstractCache; +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.EntityDao; +import cc.smtweb.framework.core.db.vo.ModelDatabase; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + * Created by Akmm at 2022/1/12 18:34 + */ +@SwCache(ident = "ASP_MODEL_DATABASE", title = "数据库") +public class ModelDatabaseCache extends AbstractCache { + private final static String mk = "k"; + + public static ModelDatabaseCache getInstance() { + return CacheManager.getIntance().getCache(ModelDatabaseCache.class); + } + + public ModelDatabaseCache() { + regMap(mk, ModelDatabase::getName); + } + + @Override + protected String getId(ModelDatabase bean) { + return String.valueOf(bean.getId()); + } + + @Override + protected List loadAll() { + EntityDao dao = DbEngine.getInstance().findDao(ModelDatabase.class); + return dao.query(); + } + + public final ModelDatabase getByName(String key) { + return localGetByKey(mk, key); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/cache/ModelTableCache.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/cache/ModelTableCache.java new file mode 100644 index 0000000..bd068b6 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/cache/ModelTableCache.java @@ -0,0 +1,90 @@ +package cc.smtweb.framework.core.db.cache; + +import cc.smtweb.framework.core.annotation.SwCache; +import cc.smtweb.framework.core.cache.AbstractCache; +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.vo.ModelTable; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ResultSetExtractor; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Created by Akmm at 2022/1/12 18:34 + */ +@SwCache(ident = "ASP_MODEL_TABLE", title = "数据库表定义") +public class ModelTableCache extends AbstractCache { + private final static String mk = "k"; + private final static String md = "d"; + + public static ModelTableCache getInstance() { + return CacheManager.getIntance().getCache(ModelTableCache.class); + } + + public ModelTableCache() { + regMap(mk, k-> k.getName().toUpperCase()); + regList(md, k-> String.valueOf(k.getDatabaseId())); + } + + @Override + protected String getId(ModelTable bean) { + return String.valueOf(bean.getId()); + } + + @Override + protected List loadAll() { + return DbEngine.getInstance().query("SELECT\n" + + "t.table_id,\n" + + "t.prjoect_id,\n" + + "t.catalog_id,\n" + + "t.database_id,\n" + + "t.table_extends,\n" + + "t.table_name,\n" + + "t.table_title,\n" + + "t.table_abbr,\n" + + "t.table_type,\n" + + "t.need_cache,\n" + + "t.table_content,\n" + + "t.table_create_uid,\n" + + "t.table_update_uid,\n" + + "t.table_create_at,\n" + + "t.table_update_at\n" + + "from asp_model_table t\n", new ResultSetExtractor>() { + @Override + public List extractData(ResultSet rs) throws SQLException, DataAccessException { + List list = new ArrayList<>(); + while (rs.next()) { + ModelTable table = new ModelTable(); + list.add(table); + table.setId(rs.getLong("table_id")); + table.setDatabaseId(rs.getLong("database_id")); + table.setPrjoectId(rs.getLong("prjoect_id")); + table.setCatalogId(rs.getLong("catalog_id")); + table.setTableExtends(rs.getString("table_extends")); + table.setName(rs.getString("table_name").toUpperCase()); + table.setTitle(rs.getString("table_title")); + table.setAbbr(rs.getString("table_abbr")); + table.setType(rs.getInt("table_type")); + table.setNeedCache(rs.getInt("need_cache") == 1); + table.setCreateUid(rs.getLong("table_create_uid")); + table.setCreateAt(rs.getLong("table_create_at")); + table.setUpdateAt(rs.getLong("table_update_at")); + table.setTableContent(rs.getString("table_content")); + } + return list; + } + }); + } + + public final ModelTable getByName(String key) { + return localGetByKey(mk, key.toUpperCase()); + } + public final Set getDbTables(long dbId) { + return localGetListByKey(md, String.valueOf(dbId)); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/config/DbEngineConfiguration.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/config/DbEngineConfiguration.java new file mode 100644 index 0000000..cbd33a6 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/config/DbEngineConfiguration.java @@ -0,0 +1,41 @@ +package cc.smtweb.framework.core.db.config; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.jdbc.IdGenerator; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * 默认数据源 + * @author xkliu + */ +@Configuration +public class DbEngineConfiguration { + @Value("${smtweb.db.type}") + private String dbType; +// @Bean +// public DataSource dataSource() { +// return DataSourceBuilder.create().build(); +// } +// +// @Bean +// public NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource) { +// return new NamedParameterJdbcTemplate(dataSource); +// } + + /** + * 产生数据库数据库访问对象 dbEngine + * @param jdbcTemplate Spring框架Jdbc,通过 spring.datasource 配置 + * @param idGenerator ID生成器对象,思想数据库ID生成 + * @return dbEngine对象 + */ + @Bean + @ConfigurationProperties(prefix = "smtweb.db.default") + public DbEngine dbEngine(JdbcTemplate jdbcTemplate, IdGenerator idGenerator) { + System.out.println("create dbEngine============="); + return new DbEngine(jdbcTemplate, idGenerator, dbType); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/config/DesignConfig.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/config/DesignConfig.java new file mode 100644 index 0000000..766e726 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/config/DesignConfig.java @@ -0,0 +1,43 @@ +package cc.smtweb.framework.core.db.config; + +import cc.smtweb.framework.core.db.vo.def.DataType; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +//通过@PropertySource注解指定要读取的yaml配置文件,默认读取src\main\resources\application.yml配置 +@PropertySource(value = "classpath:config/design_db.yaml", factory = YamlPropertyLoaderFactory.class) +@ConfigurationProperties(prefix = "design") +public class DesignConfig { + private List dataTypes; + + private Map mapType = null; + + private void adjustMap() { + if (mapType == null) { + synchronized (DesignConfig.class) { + if (mapType == null) { + Map map = new HashMap<>(); + for (DataType type : dataTypes) { + map.put(type.getType(), type); + } + mapType = map; + } + } + } + } + + public DataType getDataType(String type) { + adjustMap(); + return mapType.get(type); + } + + public List getDataTypes() { + return dataTypes; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/config/YamlPropertyLoaderFactory.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/config/YamlPropertyLoaderFactory.java new file mode 100644 index 0000000..24c0c05 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/config/YamlPropertyLoaderFactory.java @@ -0,0 +1,23 @@ +package cc.smtweb.framework.core.db.config; + +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.support.DefaultPropertySourceFactory; +import org.springframework.core.io.support.EncodedResource; + +import java.io.IOException; + +/** + * 实现yaml配置文件加载工厂,以使用@PropertySource注解加载指定yaml文件的配置 + */ +public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory { + @Override + public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { + + if (null == resource) { + super.createPropertySource(name, resource); + } + return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).get(0); + } + +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/dao/AbstractEntityDao.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/dao/AbstractEntityDao.java new file mode 100644 index 0000000..4492db7 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/dao/AbstractEntityDao.java @@ -0,0 +1,383 @@ +package cc.smtweb.framework.core.db.dao; + +import cc.smtweb.framework.core.annotation.SwTable; +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.db.cache.ModelTableCache; +import cc.smtweb.framework.core.db.impl.BaseBean; +import cc.smtweb.framework.core.db.impl.DefaultEntity; +import cc.smtweb.framework.core.db.vo.def.FieldType; +import cc.smtweb.framework.core.db.vo.ModelField; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.framework.core.exception.DbException; +import cc.smtweb.framework.core.util.DateUtil; +import cc.smtweb.framework.core.util.SpringUtil; +import cc.smtweb.framework.core.util.VariableUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.ClassUtils; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.*; + +/** + * 抽象的值对象数据库访问 + * + * @param 数据库值对象类型 + */ +public abstract class AbstractEntityDao { + protected ModelTable modelTable; + protected String tableName; + protected Map columns = new HashMap<>(); + protected Class type; + protected CacheManager cacheManager = SpringUtil.getBean(CacheManager.class); +// public AbstractEntityDao(String tableName) { +// this.tableName = tableName; +// } + + /** + * 通过值对象类型构造值对象数据库访问 + * + * @param type 值对象类型 + */ + public AbstractEntityDao(Class type) { + this.type = type; + // type.isAnnotationPresent(Table.class); + SwTable table = type.getAnnotation(SwTable.class); + Class superclass = type.getSuperclass(); + + if (table == null && superclass != null) { + table = superclass.getAnnotation(SwTable.class); + } + + if (table == null) { + throw new IllegalAccessError("not find annotation @SwTable"); + } + + tableName = table.value(); + modelTable = ModelTableCache.getInstance().getByName(tableName); + + for (ModelField field : modelTable.getFields()) { + String voFieldName = getVoFieldName(modelTable.getAbbr(), field.getName()); + // 处理get method + try { + PropertyDescriptor pd = new PropertyDescriptor(voFieldName, type); + // 获得get方法 + Method readMethod = pd.getReadMethod(); + Method writeMethod = pd.getWriteMethod(); + + if (readMethod != null && writeMethod != null) { + Class[] parameterTypes = writeMethod.getParameterTypes(); + if (parameterTypes.length == 1 && ClassUtils.isAssignable(parameterTypes[0], readMethod.getReturnType())) { + add(field, readMethod, writeMethod); + } + } + } catch (IntrospectionException ignore) { + } + } + } + + /** + * 通过值对象类型构造值对象数据库访问 + * + * @param tableName 表名 + */ + public AbstractEntityDao(String tableName) { + this.tableName = tableName; + this.type = (Class)DefaultEntity.class; + modelTable = cacheManager.getCache(ModelTableCache.class).getByName(tableName); + + for (ModelField field : modelTable.getFields()) { + add(field, null, null); + } + } + + /** + * 根据字段名,得到类中的字段属性名 + * @param tbAbbr 表缩写,一般是字段的前缀 + * @param fieldName 字段名 + * @return + */ + private String getVoFieldName(String tbAbbr, String fieldName) { + fieldName = fieldName.toLowerCase(); + tbAbbr = tbAbbr.toLowerCase() + "_"; + if (fieldName.startsWith(tbAbbr)) return VariableUtil.underlineToHump(fieldName.substring(tbAbbr.length())); + return VariableUtil.underlineToHump(fieldName); + } + + /** + * 添加值对象的属性访问方法何注解值 + * + * @param field 字段名 + * @param readMethod 读值对象属性方法 + * @param writeMethod 写值值对象属性方法 + */ + protected void add(ModelField field, Method readMethod, Method writeMethod) { + EntityColumn beanColumn = new EntityColumn(field, readMethod, writeMethod); + columns.put(field.getName(), beanColumn); + } + + protected void updateTime(T obj, FieldType type) { + ModelField field = modelTable.findFieldByType(type); + if (field == null) return; + EntityColumn col = columns.get(field.getName()); + if (col != null) { + col.writeValue(obj, DateUtil.nowDateTimeLong()); + } + } + + /** + * 校验传入的字段,如没有创建时间和最后更改时间,则加上 + * + * @param fields + * @return + */ + protected List adjustFields(String fields, FieldType... types) { + if (StringUtils.isEmpty(fields)) return null; + String[] fieldNames = fields.toLowerCase().split(","); + List listFields = new ArrayList<>(fieldNames.length + 2); + boolean[] includeTypes = new boolean[types.length]; + for (int i = 0, len = includeTypes.length; i < len; i++) { + includeTypes[i] = false; + } + + for (String name : fieldNames) { + EntityColumn column = this.columns.get(name.trim()); + for (int i = 0, len = types.length; i < len; i++) { + if (types[i].name().equalsIgnoreCase(column.getField().getFieldType())) { + includeTypes[i] = true; + } + } + listFields.add(name); + } + for (int i = 0, len = types.length; i < len; i++) { + if (!includeTypes[i]) { + ModelField field = modelTable.findFieldByType(types[i]); + if (field != null) listFields.add(field.getName()); + } + } + return listFields; + } + + /** + * 拼接插入SQL语句 + * + * @param obj 值对象 + * @param sql 记录sql的字符缓存 + * @param fields 逗号分割字段列表,设置为null表示使用所有字段 + * @return SQL参数列表 + */ + protected Object[] handleInsert(T obj, StringBuilder sql, String fields) { + List result; + List listFields = adjustFields(fields, FieldType.CREATE_TIME, FieldType.LAST_TIME); + sql.append("insert into ").append(tableName).append("("); + + updateTime(obj, FieldType.CREATE_TIME); + updateTime(obj, FieldType.LAST_TIME); + + if (listFields == null) { + result = new ArrayList<>(this.columns.size()); + + for (EntityColumn column : this.columns.values()) { + result.add(column.readValue(obj)); + sql.append(column.getField().getName()).append(","); + } + } else { + result = new ArrayList<>(listFields.size()); + + for (String name : listFields) { + EntityColumn column = this.columns.get(name.trim()); + result.add(column.readValue(obj)); + sql.append(column.getField().getName()).append(","); + } + } + + sql.setCharAt(sql.length() - 1, ')'); + + // values(?,?) + sql.append(" values("); + for (int i = result.size(); i > 0; i--) { + sql.append("?,"); + } + sql.setCharAt(sql.length() - 1, ')'); + + return result.toArray(); + } + + /** + * 拼接更新SQL语句 + * + * @param obj 值对象 + * @param sql 记录sql的字符缓存 + * @param fields 逗号分割更新字段列表,设置为null表示使用所有字段 + * @param whereFields 逗号分割查询字段列表,设置为null表示使用ID字段作为查询条件 + * @return SQL参数列表 + */ + protected Object[] handleUpdate(T obj, StringBuilder sql, String fields, String whereFields) { + EntityColumn idColumn = findIdColumn(); + + List listFields = adjustFields(fields, FieldType.LAST_TIME); + updateTime(obj, FieldType.LAST_TIME); + + sql.append("update ").append(tableName).append(" set "); + List result = new ArrayList<>(); + + if (listFields == null) { + for (EntityColumn column : this.columns.values()) { + if (idColumn != column) { + sql.append(column.getField().getName()).append("=?,"); + } + } + sql.setCharAt(sql.length() - 1, ' '); + + // 默认使用Id字段条件 + result.add(idColumn.readValue(obj)); + + sql.append("where ").append(idColumn.getField().getName()).append("=?"); + } else { + + for (String name : listFields) { + name = name.trim(); + EntityColumn beanColumn = getBeanColumn(name); + + sql.append(name).append("=?,"); + result.add(beanColumn.readValue(obj)); + + } + + sql.setCharAt(sql.length() - 1, ' '); + + if (StringUtils.isNotBlank(whereFields)) { + sql.append("where "); + boolean first = true; + for (String name : whereFields.split(",")) { + name = name.trim(); + if (first) { + first = false; + } else { + sql.append(" and "); + } + + sql.append(name).append("=?"); + result.add(readValue(obj, name)); + } + } else { + // 默认使用Id字段条件 + result.add(idColumn.readValue(obj)); + sql.append("where ").append(idColumn.getField().getName()).append("=?"); + } + } + return result.toArray(); + } + + private EntityColumn findIdColumn() { + ModelField field = modelTable.findIdField(); + EntityColumn idColumn = field != null ? columns.get(field.getName()) : null; + if (idColumn == null) { + throw new DbException(tableName + " not define id column"); + } + + return idColumn; + } + + /** + * 拼接删除值对象语句 + * + * @param obj 值对象 + * @param sql 记录sql的字符缓存 + * @return SQL参数列表 + */ + protected Object[] handleDelete(T obj, StringBuilder sql) { + EntityColumn idColumn = findIdColumn(); + + sql.append("DELETE FROM ").append(tableName).append(" WHERE ").append(idColumn.getField()).append("=?"); + + return new Object[]{idColumn.readValue(obj)}; + } + + /** + * 拼接删除值对象语句,条件由外部设置 + * + * @param sql 记录sql的字符缓存 + */ + protected void handleDelete(StringBuilder sql) { + EntityColumn idColumn = findIdColumn(); + sql.append("DELETE FROM ").append(tableName).append(" WHERE ").append(idColumn.getField().getName()).append("=?"); + } + + private Object readValue(T obj, String fieldName) { + EntityColumn beanColumn = getBeanColumn(fieldName); + + return beanColumn.readValue(obj); + } + + private EntityColumn getBeanColumn(String fieldName) { + EntityColumn beanColumn = this.columns.get(fieldName); + + if (beanColumn == null) { + throw new DbException("not define column:" + fieldName); + } + + return beanColumn; + } + + /** + * 获取表名 + * + * @return 表名 + */ + protected String getTableName() { + return tableName; + } + + /** + * 拼接查询SQL语句 + * + * @param sql SQL字符缓存 + * @param fields 逗号分割的查询字段列表,传入null表示全部值对象字段 + */ + public void handleSelect(StringBuilder sql, String fields) { + sql.append("select "); + + if (fields != null) { + sql.append(fields).append(' '); + } else { + for (String fieldName : columns.keySet()) { + sql.append(fieldName).append(','); + } + sql.setCharAt(sql.length() - 1, ' '); + } + + sql.append("from ").append(tableName); + } + + protected void handleSelectOne(StringBuilder sql, String fields) { + EntityColumn idColumn = findIdColumn(); + + sql.append("select "); + + if (fields != null) { + sql.append(fields).append(' '); + } else { + for (EntityColumn field : columns.values()) { + sql.append(field.getField().getName()).append(","); + } + sql.setCharAt(sql.length() - 1, ' '); + } + + sql.append("from ").append(tableName).append(" where ").append(idColumn.getField().getName()).append("=?"); + } + + /** + * 读取ID值 + * + * @param entity + * @return + */ + public Long readId(T entity) { + EntityColumn idColumn = findIdColumn(); + + return (Long) idColumn.readValue(entity); + } +} + diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/dao/EntityColumn.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/dao/EntityColumn.java new file mode 100644 index 0000000..3bc20ea --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/dao/EntityColumn.java @@ -0,0 +1,66 @@ +package cc.smtweb.framework.core.db.dao; + +import cc.smtweb.framework.core.db.impl.DefaultEntity; +import cc.smtweb.framework.core.db.vo.ModelField; +import cc.smtweb.framework.core.exception.DbException; +import lombok.Getter; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * 值对象字段处理类 + * @author xkliu + */ +@Getter +public class EntityColumn { + private ModelField field; + private final Method readMethod; + private final Method writeMethod; + + /** + * 构建值对象字段 + * @param field 字段名 + * @param readMethod 读值方法 + * @param writeMethod 写值方法 + */ + public EntityColumn(ModelField field, Method readMethod, Method writeMethod) { + this.field = field; + this.readMethod = readMethod; + this.writeMethod = writeMethod; + } + + /** + * 从对象中读取字段对应的属性值 + * @param obj 值对象 + * @return 属性值 + */ + public Object readValue(Object obj) { + if (readMethod != null) { + try { + return readMethod.invoke(obj); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + throw new DbException(e); + } + } else { + return ((DefaultEntity)obj).get(field.getName()); + } + } + + /** + * 写入值到对象字段对象属性 + * @param obj 值对象 + * @param value 属性值 + */ + public void writeValue(Object obj, Object value) { + if (readMethod != null) { + try { + writeMethod.invoke(obj, value); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + throw new DbException(e); + } + } else { + ((DefaultEntity)obj).put(field.getName(), value); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/dao/EntityColumnForeign.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/dao/EntityColumnForeign.java new file mode 100644 index 0000000..49c4d7d --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/dao/EntityColumnForeign.java @@ -0,0 +1,26 @@ +package cc.smtweb.framework.core.db.dao; + +import lombok.Getter; + +/** + * 字段外键属性 + * @author admin + */ +@Getter +public class EntityColumnForeign { + private final String table; + private final String id; + private final String name; + + /** + * 构造字段外键属性 + * @param table 外键表名 + * @param id 外键ID字段名 + * @param name 外键名称字段名 + */ + public EntityColumnForeign(String table, String id, String name) { + this.table = table; + this.id = id; + this.name = name; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/BaseBean.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/BaseBean.java new file mode 100644 index 0000000..3053b44 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/BaseBean.java @@ -0,0 +1,98 @@ +package cc.smtweb.framework.core.db.impl; + +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.framework.core.util.NumberUtil; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Created by Akmm at 2016-02-23 9:31 + * bean基类,基于Map, + */ +public class BaseBean implements Serializable { + protected Map data = new HashMap<>(); + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } + + @Override + public BaseBean clone() throws CloneNotSupportedException { + BaseBean bean = (BaseBean) super.clone(); + bean.data = new HashMap<>(); + bean.getData().putAll(this.data); + return bean; + } + + public boolean isEmpty() { + return data.isEmpty(); + } + + public String getStr(String fieldName) { + return getStr(fieldName, ""); + } + + public String getStr(String fieldName, String defValue) { + Object o = data.get(fieldName); + return o != null ? o.toString() : defValue; + } + + public double getDouble(String fieldName) { + return getDouble(fieldName, 0.0); + } + + public double getDouble(String fieldName, double defValue) { + Object o = data.get(fieldName); + if (o == null) return defValue; + if (o instanceof Number) return ((Number) o).doubleValue(); + return NumberUtil.getDoubleIgnoreErr(o.toString()); + } + + public int getInt(String fieldName) { + Object o = data.get(fieldName); + if (o == null) return 0; + if (o instanceof Number) return ((Number) o).intValue(); + return NumberUtil.getIntIgnoreErr(o.toString()); + } + + public long getLong(String fieldName) { + Object o = data.get(fieldName); + if (o == null) return 0L; + if (o instanceof Number) return ((Number) o).longValue(); + return NumberUtil.getLongIgnoreErr(o.toString()); + } + + public boolean getBool(String fieldName) { + Object o = data.get(fieldName); + if (o == null) return false; + String v = o.toString(); + return "1".equals(v) || "t".equalsIgnoreCase(v) || "true".equalsIgnoreCase(v); + } + + public void put(String fieldName, Object value) { + this.data.put(fieldName, value); + } + + public void setBool(String fieldName, boolean value) { + this.data.put(fieldName, value ? 1 : 0); + } + + public Object get(String fieldName) { + return this.data.get(fieldName); + } + + public void readFromJson(String json){ + Map map = JsonUtil.parseMap(json); + if (map != null) { + data.putAll(map); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/DefaultDatabaseInfoImpl.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/DefaultDatabaseInfoImpl.java new file mode 100644 index 0000000..b7283b4 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/DefaultDatabaseInfoImpl.java @@ -0,0 +1,362 @@ +package cc.smtweb.framework.core.db.impl; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.util.SpringUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.support.JdbcUtils; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.function.Function; + +/** + * Created with IntelliJ IDEA. + * User: AKhh + * Date: 12-12-21 下午10:14 + * 获取连接的数据库相关信息,from ofbiz + */ +@Slf4j +public class DefaultDatabaseInfoImpl implements IDatabaseInfo { + private static final String MODULE = DefaultDatabaseInfoImpl.class.getName(); + private DbEngine dbEngine = SpringUtil.getBean(DbEngine.class); + + @SuppressWarnings("unchecked") + public void printDatabaseInfo() { + dbEngine.doConn(connection -> { + try { + DatabaseMetaData dbData = connection.getMetaData(); + // 数据库信息 + try { + log.debug("Database Product Name is " + dbData.getDatabaseProductName(), MODULE); + log.debug("Database Product Version is " + dbData.getDatabaseProductVersion(), MODULE); + } catch (SQLException sqle) { + log.debug("Unable to get Database name & version information", MODULE); + } + // JDBC Driver Info + try { + log.debug("Database Driver Name is " + dbData.getDriverName(), MODULE); + log.debug("Database Driver Version is " + dbData.getDriverVersion(), MODULE); + } catch (SQLException sqle) { + log.debug("Unable to get Driver name & version information", MODULE); + } + // Db/Driver support settings + try { + log.debug("Database Setting/Support Information (those with a * should be true):", MODULE); + log.debug("- supports transactions [" + dbData.supportsTransactions() + "]*", MODULE); + log.debug("- isolation None [" + dbData.supportsTransactionIsolationLevel(Connection.TRANSACTION_NONE) + "]", MODULE); + log.debug("- isolation ReadCommitted [" + dbData.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED) + "]", MODULE); + log.debug("- isolation ReadUncommitted[" + dbData.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED) + "]", MODULE); + log.debug("- isolation RepeatableRead [" + dbData.supportsTransactionIsolationLevel(Connection.TRANSACTION_REPEATABLE_READ) + "]", MODULE); + log.debug("- isolation Serializable [" + dbData.supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE) + "]", MODULE); + log.debug("- is case sensitive [" + dbData.supportsMixedCaseIdentifiers() + "]", MODULE); + log.debug("- stores LowerCase [" + dbData.storesLowerCaseIdentifiers() + "]", MODULE); + log.debug("- stores MixedCase [" + dbData.storesMixedCaseIdentifiers() + "]", MODULE); + log.debug("- stores UpperCase [" + dbData.storesUpperCaseIdentifiers() + "]", MODULE); + log.debug("- max table name length [" + dbData.getMaxTableNameLength() + "]", MODULE); + log.debug("- max column name length [" + dbData.getMaxColumnNameLength() + "]", MODULE); + log.debug("- max schema name length [" + dbData.getMaxSchemaNameLength() + "]", MODULE); + log.debug("- concurrent connections [" + dbData.getMaxConnections() + "]", MODULE); + log.debug("- concurrent statements [" + dbData.getMaxStatements() + "]", MODULE); + log.debug("- ANSI SQL92 Entry [" + dbData.supportsANSI92EntryLevelSQL() + "]", MODULE); + log.debug("- ANSI SQL92 Itermediate [" + dbData.supportsANSI92IntermediateSQL() + "]", MODULE); + log.debug("- ANSI SQL92 Full [" + dbData.supportsANSI92FullSQL() + "]", MODULE); + log.debug("- ODBC SQL Grammar Core [" + dbData.supportsCoreSQLGrammar() + "]", MODULE); + log.debug("- ODBC SQL Grammar Extended[" + dbData.supportsExtendedSQLGrammar() + "]", MODULE); + log.debug("- ODBC SQL Grammar Minimum [" + dbData.supportsMinimumSQLGrammar() + "]", MODULE); + log.debug("- outer joins [" + dbData.supportsOuterJoins() + "]*", MODULE); + log.debug("- limited outer joins [" + dbData.supportsLimitedOuterJoins() + "]", MODULE); + log.debug("- full outer joins [" + dbData.supportsFullOuterJoins() + "]", MODULE); + log.debug("- group by [" + dbData.supportsGroupBy() + "]*", MODULE); + log.debug("- group by not in select [" + dbData.supportsGroupByUnrelated() + "]", MODULE); + log.debug("- column aliasing [" + dbData.supportsColumnAliasing() + "]", MODULE); + log.debug("- order by not in select [" + dbData.supportsOrderByUnrelated() + "]", MODULE); + // this doesn't work in HSQLDB, other databases? Debug.logInfo("- named parameters [" + dbData.supportsNamedParameters() + "]", MODULE); + log.debug("- alter table add column [" + dbData.supportsAlterTableWithAddColumn() + "]*", MODULE); + log.debug("- non-nullable column [" + dbData.supportsNonNullableColumns() + "]*", MODULE); + } catch (Exception e) { + log.error("Unable to get misc. support/setting information", e); + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return null; + }); + } + + //需要考虑分库的情况 + @SuppressWarnings("unchecked") + public Map getTables() { + final Map tables = new TreeMap<>(); + dbEngine.doConn(new Function() { + @Override + public Object apply(Connection connection) { + try { + DatabaseMetaData dbData = connection.getMetaData(); + if (dbData == null) { + return null; + } + System.out.println("获取数据库表信息..............................."); + boolean needsUpperCase = false; + try { + needsUpperCase = dbData.storesLowerCaseIdentifiers() || dbData.storesMixedCaseIdentifiers(); + } catch (SQLException sqle) { + String message = "Error getting identifier case information... Error was:" + sqle.toString(); + log.error(message, MODULE); + } + String lookupSchemaName = null; + if (dbData.supportsSchemasInTableDefinitions()) { + lookupSchemaName = dbData.getUserName(); + } + if (dbEngine.isMysql()) {//非oracle才需要 + String dbName = dbEngine.getDbSchema(); + /*Set list = .getInstance().getModelReader().getListSchema(); + if (list != null && !list.isEmpty()) { + for (String catalog : list) { + getTableSet(dbData, dbName + "_" + catalog, needsUpperCase, lookupSchemaName); + } + }*/ + //当前库都要去读 + getTableSet(dbData, dbName, needsUpperCase, lookupSchemaName); + } else { + //当前库都要去读 + getTableSet(dbData, null, needsUpperCase, lookupSchemaName); + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return null; + } + + private void getTableSet(DatabaseMetaData dbData, String catalog, boolean needsUpperCase, String lookupSchemaName) { + ResultSet tableSet = null; + try { + String[] types = {"TABLE"/*,"VIEW", "ALIAS", "SYNONYM"*/}; + tableSet = dbData.getTables(catalog, lookupSchemaName, null, types); + while (tableSet.next()) { + String tableName = tableSet.getString("TABLE_NAME"); + // for those databases which do not return the schema name with the table name (pgsql 7.3) + //boolean appendSchemaName = false; + if (needsUpperCase && tableName != null) { + tableName = tableName.toUpperCase(); + } + //if (appendSchemaName) { + // tableName = String.format("%s.%s", lookupSchemaName, tableName); + //} + String tableType = tableSet.getString("TABLE_TYPE"); + // only allow certain table types + if (tableType != null && + !"TABLE".equalsIgnoreCase(tableType) && + !"BASE TABLE".equalsIgnoreCase(tableType) && + !"VIEW".equalsIgnoreCase(tableType) && + !"ALIAS".equalsIgnoreCase(tableType) && + !"SYNONYM".equalsIgnoreCase(tableType)) { + continue; + } + try (ResultSet pkSet = dbData.getPrimaryKeys(catalog, lookupSchemaName, tableName)) { + String s = ""; + while (pkSet.next()) { + s += "," + pkSet.getString(4); + } + TableCheckInfo table = new TableCheckInfo(); + table.tableName = tableName; + table.catalog = catalog; + if (StringUtils.isNotEmpty(s)) { + table.pk = s.substring(1); + } + tables.put(tableName, table); + } + } + } catch (SQLException sqle) { + String message = "Error getting next table information... Error was:" + sqle.toString(); + log.error(message, MODULE); + } finally { + JdbcUtils.closeResultSet(tableSet); + } + } + }); + return tables; + } + + @SuppressWarnings("unchecked") + //获取列信息 + public Map> getColumnInfo(final Map dbTables) { + // if there are no tableNames, don't even try to get the columns + final Map> colInfo = new HashMap<>(); + if (dbTables.size() == 0) { + return colInfo; + } + dbEngine.doConn(new Function() { + @Override + public Object apply(Connection connection) { + try { + DatabaseMetaData dbData = connection.getMetaData(); + boolean needsUpperCase = false; + try { + needsUpperCase = dbData.storesLowerCaseIdentifiers() || dbData.storesMixedCaseIdentifiers(); + } catch (SQLException sqle) { + String message = "Error getting identifier case information... Error was:" + sqle.toString(); + log.error(message, MODULE); + } + + String lookupSchemaName = null; + if (dbData.supportsSchemasInTableDefinitions()) { + lookupSchemaName = dbData.getUserName(); + } + + if (dbEngine.isMysql()) {//非oracle才需要 + String dbName = dbEngine.getDbSchema(); + /*Set list = ModelConfigUtil.getInstance().getModelReader().getListSchema(); + if (list != null && !list.isEmpty()) { + for (String catalog : list) { + getColumnInfo(dbData, dbName + "_" + catalog, needsUpperCase, lookupSchemaName); + } + }*/ + } + + getColumnInfo(dbData, null, needsUpperCase, lookupSchemaName); + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return null; + } + + private void getColumnInfo(DatabaseMetaData dbData, String catalog, boolean needsUpperCase, String lookupSchemaName) { + ResultSet rsCols = null; + try { + rsCols = dbData.getColumns(catalog, lookupSchemaName, null, null); + while (rsCols.next()) { + ColumnCheckInfo ccInfo = new ColumnCheckInfo(); + + ccInfo.tableName = rsCols.getString("TABLE_NAME"); + // for those databases which do not return the schema name with the table name (pgsql 7.3) + //boolean appendSchemaName = false; + // AKzz : 不在表名前加用户名. + //if (ccInfo.tableName != null && lookupSchemaName != null && !ccInfo.tableName.startsWith(lookupSchemaName)) { + // appendSchemaName = true; + //} + if (needsUpperCase && ccInfo.tableName != null) { + ccInfo.tableName = ccInfo.tableName.toUpperCase(); + } + //if (appendSchemaName) { + // ccInfo.tableName = lookupSchemaName + "." + ccInfo.tableName; + //} + // ignore the column info if the table name is not in the list we are concerned with + if (!dbTables.containsKey(ccInfo.tableName)) { + continue; + } + ccInfo.columnName = rsCols.getString("COLUMN_NAME"); + if (needsUpperCase && ccInfo.columnName != null) { + ccInfo.columnName = ccInfo.columnName.toUpperCase(); + } + // NOTE: this may need a toUpperCase in some cases, keep an eye on it + ccInfo.typeName = rsCols.getString("TYPE_NAME"); + ccInfo.columnSize = rsCols.getInt("COLUMN_SIZE"); + ccInfo.decimalDigits = rsCols.getInt("DECIMAL_DIGITS"); + // NOTE: this may need a toUpperCase in some cases, keep an eye on it + ccInfo.isNullable = rsCols.getString("IS_NULLABLE"); + List tableColInfo = colInfo.get(ccInfo.tableName); + if (tableColInfo == null) { + tableColInfo = new ArrayList<>(); + colInfo.put(ccInfo.tableName, tableColInfo); + } + tableColInfo.add(ccInfo); + } + } catch (SQLException sqle) { + String message = "Error getting table fields information... Error was:" + sqle.toString(); + log.error(message, MODULE); + } finally { + JdbcUtils.closeResultSet(rsCols); + } + } + }); + return colInfo; + } + + @SuppressWarnings("unchecked") + //获取索引 + public Map>> getIndexInfo(final Collection tableNames, final boolean include_nounique) { + final Map>> indexInfo = new HashMap<>(); + if (!tableNames.isEmpty()) { + for (TableCheckInfo c : tableNames) { + indexInfo.put(c.tableName, new HashMap<>()); + } + dbEngine.doConn(connection -> { + try { + DatabaseMetaData dbData = connection.getMetaData(); + boolean needsUpperCase = false; + try { + needsUpperCase = dbData.storesLowerCaseIdentifiers() || dbData.storesMixedCaseIdentifiers(); + } catch (SQLException sqle) { + String message = "Error getting identifier case information... Error was:" + sqle.toString(); + log.error(message, MODULE); + } + +// int totalIndices = 0; + String lookupSchemaName = null; + if (dbData.supportsSchemasInTableDefinitions()) { + lookupSchemaName = dbData.getUserName(); + } + + for (TableCheckInfo curTableName : tableNames) { + ResultSet rsCols = null; + try { + // false for unique, we don't really use unique indexes + // true for approximate, don't really care if stats are up-to-date + rsCols = dbData.getIndexInfo(curTableName.catalog, lookupSchemaName, curTableName.tableName, !include_nounique, true); + while (rsCols != null && rsCols.next()) { + // NOTE: The code in this block may look funny, but it is designed so that the wrapping loop can be removed + // skip all index info for statistics + if (rsCols.getShort("TYPE") == DatabaseMetaData.tableIndexStatistic) continue; + //HACK: for now skip all "unique" indexes since our foreign key indices are not unique, but the primary key ones are + //原来的方法: 只取了NON_UNIQUE == true的索引. + //if (!rsCols.getBoolean ("NON_UNIQUE")) continue; + boolean unique = rsCols.getBoolean("NON_UNIQUE"); + + //for (int x = 1; x<=rsCols.getMetaData().getColumnCount();x++){ + // System.out.println(rsCols.getMetaData().getColumnName(x) + " = " + rsCols.getString(x)); + //} + + //System.out.println("INDEX_NAME=" + rsCols.getString("INDEX_NAME") + " --- "+ "COLUMN_NAME=" + rsCols.getString("COLUMN_NAME") ); + + //改动, 可以取所有的索引. + if (include_nounique) { + // 要取nouniqune的 + } else { + // 不取nouniqune的, + if (!unique) continue; + } + String tableName = rsCols.getString("TABLE_NAME"); + if (needsUpperCase && tableName != null) { + tableName = tableName.toUpperCase(); + } + + String indexName = rsCols.getString("INDEX_NAME"); + if (needsUpperCase && indexName != null) { + indexName = indexName.toUpperCase(); + } + Map> tableIndexList = indexInfo.computeIfAbsent(tableName, k -> new HashMap<>()); + Set set = tableIndexList.computeIfAbsent(indexName, k -> new LinkedHashSet<>()); + // totalIndices++; + set.add(rsCols.getString("COLUMN_NAME")); + } + } catch (Exception e) { + log.debug("Error getting index info using lookupSchemaName " + lookupSchemaName, e); + } finally { + JdbcUtils.closeResultSet(rsCols); + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return null; + }); + } + return indexInfo; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/DefaultEntity.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/DefaultEntity.java new file mode 100644 index 0000000..558618d --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/DefaultEntity.java @@ -0,0 +1,62 @@ +package cc.smtweb.framework.core.db.impl; + +import cc.smtweb.framework.core.db.cache.ModelTableCache; +import cc.smtweb.framework.core.db.vo.ModelField; +import cc.smtweb.framework.core.db.vo.ModelTable; +import org.apache.commons.lang3.StringUtils; + +import java.io.Serializable; + +/** + * Created by Akmm at 14-1-2 上午11:19 + * 实体bean抽象类 + */ +public class DefaultEntity extends BaseBean implements Serializable, Cloneable { + //表名 + private String tableName; + + public DefaultEntity(String tableName) { + this.tableName = tableName; + } + + public ModelTable getModelTable() { + return ModelTableCache.getInstance().getByName(tableName); + } + + //根据实体定义,设默认值 + public void init() { + ModelTable entity = getModelTable(); + if (entity == null) return; + String pkField = getPkFieldName(); + for (ModelField field : entity.getFields()) { + if (data.containsKey(field.getName())) continue;//有值了,不能动 + if (field.getName().equalsIgnoreCase(pkField)) continue;//是pk,不要初始化 + String s = field.getDefaultValue(); + if (StringUtils.isNotEmpty(s)) put(field.getName(), s); + } + } + + /** 主键字段 */ + public String getPkFieldName() { + return getModelTable().getIdField(); + } + + public long getEntityId() { + return getLong(getPkFieldName()); + } + + public void setEntityId(long id) { + data.put(getPkFieldName(), id); + } + + public boolean isNew() { + return getEntityId() <= 0L; + } + + @Override + public DefaultEntity clone() throws CloneNotSupportedException { + DefaultEntity bean = (DefaultEntity) super.clone(); + bean.getData().remove(getPkFieldName()); + return bean; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/IDatabaseInfo.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/IDatabaseInfo.java new file mode 100644 index 0000000..a206c3c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/impl/IDatabaseInfo.java @@ -0,0 +1,37 @@ +package cc.smtweb.framework.core.db.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Created with IntelliJ IDEA. + * User: AKhh + * Date: 12-12-21 下午10:13 + * To change this template use File | Settings | File Templates. + */ +public interface IDatabaseInfo { + void printDatabaseInfo(); + + Map getTables(); + + Map> getColumnInfo(Map dbTables); + + Map>> getIndexInfo(Collection tableNames, boolean include_nounique); + + public static class TableCheckInfo { + public String tableName; + public String catalog; + public String pk; + } + + public static class ColumnCheckInfo { + public String tableName; + public String columnName; + public String typeName; + public int columnSize; + public int decimalDigits; + public String isNullable; // Y|N or "" = ie nobody knows + } +} \ No newline at end of file diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/IdGenerator.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/IdGenerator.java new file mode 100644 index 0000000..4be311a --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/IdGenerator.java @@ -0,0 +1,134 @@ +package cc.smtweb.framework.core.db.jdbc; + +/** + * 主键生成器。 + */ +//public class IdGenerator { +// private AtomicLong key; +// +// public IdGenerator() { +// this(0); +// } +// +// /** +// * +// * @param seed 集群服务id,用于集群产生的数据不重复(0~255) +// */ +// public IdGenerator(int seed) { +// long now = System.currentTimeMillis(); +// +// if(seed > 0xFF){ +// seed = 0xFF; +// } +// +// if(seed <= 0){ +// seed = 1; +// } +// +// key = new AtomicLong(now | (((long) seed) << 48)); +// } +// +// public long nextCode() { +// return key.incrementAndGet(); +// } +//} + +/** + * tweeter的snowflake + * time—42bits,精确到ms,那就意味着其可以表示长达(2^42-1)/(1000360024*365)=139.5年 + * (a) id构成: 42位的时间前缀 + 10位的节点标识 + 12位的sequence避免并发的数字(12位不够用时强制得到新的时间前缀) + * 注意这里进行了小改动: snowkflake是5位的datacenter加5位的机器id; 这里变成使用10位的机器id + * (b) 对系统时间的依赖性非常强,需关闭ntp的时间同步功能。当检测到ntp时间调整后,将会拒绝分配id + * @author xkliu + */ +public class IdGenerator { + /** + * 起始的时间戳 epoch 2017-1-1 + */ + private final static long EPOCH_STMP = 1483200000000L; + + /** + * 每一部分占用的位数 + */ + //序列号占用的位数 + private final static long SEQUENCE_BIT = 12; + //机器标识占用的位数 + private final static long MACHINE_BIT = 10; + + /** + * 每一部分的最大值 + */ + private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT); + private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT); + + /** + * 每一部分向左的位移 + */ + private final static long MACHINE_LEFT = SEQUENCE_BIT; + private final static long TIMESTMP_LEFT = SEQUENCE_BIT + MACHINE_BIT; + + // private long datacenterId; //数据中心 + // 机器标识 + private long machineIdShift; + // 序列号 + private long sequence = 0L; + // 上一次时间戳 + private long lastStmp = -1L; + + // 0 ~ 1023 + public IdGenerator(long machineId) { + if (machineId > MAX_MACHINE_NUM || machineId < 0) { + throw new IllegalArgumentException("machineId " + machineId + " can't be greater than " + MAX_MACHINE_NUM + " or less than 0"); + } + + this.machineIdShift = machineId << MACHINE_LEFT; + } + + /** + * 产生下一个ID + * + * @return ID值 + */ + public synchronized long nextId() { + long currStmp = getNewStamp(); + if (currStmp < lastStmp) { + throw new RuntimeException("Clock moved backwards. Refusing to generate id"); + } + + if (currStmp == lastStmp) { + //相同毫秒内,序列号自增 + sequence = (sequence + 1) & MAX_SEQUENCE; + //同一毫秒的序列数已经达到最大 + if (sequence == 0L) { + currStmp = getNextMill(); + } + } else { + //不同毫秒内,序列号置为0 + sequence = 0L; + } + + lastStmp = currStmp; + + // 时间戳部分 + 机器标识部分 + 序列号部分 + return (currStmp - EPOCH_STMP) << TIMESTMP_LEFT + | machineIdShift + | sequence; + } + + private long getNextMill() { + long mill = getNewStamp(); + while (mill <= lastStmp) { + mill = getNewStamp(); + } + return mill; + } + + private long getNewStamp() { + return System.currentTimeMillis(); + } + + // 根据ID值反向计算时间戳 + public static long queryTimestamp(long id) { + return (id >>> TIMESTMP_LEFT) + EPOCH_STMP; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/JdbcEngine.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/JdbcEngine.java new file mode 100644 index 0000000..5016a50 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/JdbcEngine.java @@ -0,0 +1,411 @@ +package cc.smtweb.framework.core.db.jdbc; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.exception.DbException; +import cc.smtweb.framework.core.util.JsonUtil; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * JDBC访问类,包装了spring jdbcTemplate对象 + */ +public class JdbcEngine { + private final static String DB_TYPE_MYSQL = "mysql"; + + private JdbcTemplate jdbcTemplate; + private DataSourceTransactionManager dataSourceTransactionManager; + private IdGenerator idGenerator; + + protected String type; + + public JdbcEngine(JdbcTemplate jdbcTemplate, IdGenerator idGenerator, String type) { + this.jdbcTemplate = jdbcTemplate; + this.dataSourceTransactionManager = new DataSourceTransactionManager(jdbcTemplate.getDataSource()); + this.idGenerator = idGenerator; + this.type = type; + } + + public boolean isMysql() { + return DB_TYPE_MYSQL.equalsIgnoreCase(type); + } + + /** + * 获取数据库唯一id + * + * @return 返回ID值 + */ + public long nextId() { + return this.idGenerator.nextId(); + } + + /** + * 查询单行数据 + * + * @param sql 查询SQL + * @param rowHandler ResultSet处理器 + * @param 返回得对象类型 + * @return 返回单个对象数据 + */ + public T queryEntity(String sql, final ResultSetExtractor rowHandler) { + return jdbcTemplate.query(sql, rowHandler); + } + + /** + * 查询单行数据 + * + * @param sql 查询SQL + * @param rowHandler ResultSet处理器 + * @param params SQL参数 + * @param 返回得对象类型 + * @return 返回单个对象数据 + */ + public T queryEntity(String sql, final ResultSetExtractor rowHandler, Object... params) { + return jdbcTemplate.query(sql, rowHandler, params); + } + + /** + * 通过回调函数查询SQL,返回列表或者对象数据 + */ + public T query(String sql, ResultSetExtractor rse, Object... params) { + return jdbcTemplate.query(sql, rse, params); + } + + /** + * 查询SQL,返回列表数据 + */ + public List query(String sql, RowMapper rowMapper) { + return jdbcTemplate.query(sql, rowMapper); + } + + /** + * 查询SQL,返回列表数据 + */ + public List query(String sql, RowMapper rowMapper, Object... params) { + return jdbcTemplate.query(sql, rowMapper, params); + } + + /** + * 翻页查询 + */ + public List pagedQuery(String sql, RowMapper rowMapper, int start, int limit, Object... params) { + return jdbcTemplate.query(sql + " LIMIT " + start + "," + limit, rowMapper, params); + } + + /** + * 执行更新SQL + */ + public int update(String sql) { + return jdbcTemplate.update(sql); + } + + /** + * 执行更新SQL + */ + public int update(String sql, Object... params) { + return jdbcTemplate.update(sql, params); + } + + /** + * 执行更新语句直到无更新数据或者达到最大修改记录数 + * + * @param sql 需要执行的SQL语句 + * @param limit 每次更新的条数 + * @param count 最大更新次数 + * @param params 执行参数 + * @return 返回更新的数据数量 + */ + public int pagedUpdate(String sql, int limit, int count, Object... params) { + int result = 0; + for (int i = 0, ret = 1; i < count && ret > 0; i++) { + ret = jdbcTemplate.update(sql + " LIMIT " + limit, params); + if (ret > 0) { + result += ret; + } + } + + return result; + } + + /** + * 查询字符list + * + * @param sql 查询SQL + * @param params 查询SQL参数 + * @return 字符list + */ + public List queryStringList(String sql, Object... params) { + return jdbcTemplate.query(sql, (resultSet, i) -> resultSet.getString(1), params); + } + + /** + * 查询字符set + * + * @param sql 查询SQL + * @param params 查询SQL参数 + * @return 字符set + */ + public Set queryStringSet(String sql, Object... params) { + return jdbcTemplate.query(sql, (resultSet) -> { + Set result = new HashSet<>(); + while (resultSet.next()) { + String value = resultSet.getString(1); + if (value != null) { + result.add(value); + } + } + + return result; + }, params); + } + + /** + * 查询字符 + * + * @param sql 查询SQL + * @param params 查询SQL参数 + * @return 字符 + */ + public String queryString(String sql, Object... params) { + List list = jdbcTemplate.query(sql, (resultSet, i) -> resultSet.getString(1), params); + if (list != null && !list.isEmpty()) { + return list.get(0); + } + + return null; + } + + /** + * 查询值为JSON格式,转换为对象返回 + * + * @param sql 查询SQL + * @param clazz 对象类 + * @param params 查询SQL参数 + * @param 对象类型 + * @return JSON格式,转换为对象返回 + */ + public T queryJson(String sql, Class clazz, Object... params) { + List list = jdbcTemplate.query(sql, (resultSet, i) -> resultSet.getString(1), params); + if (list != null && !list.isEmpty()) { + String s = list.get(0); + if (StringUtils.isNotBlank(s)) { + return JsonUtil.parse(s, clazz); + } else { + try { + return clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new DbException(e); + } + } + } + + return null; + } + + /** + * 查询Long对象 + * + * @param sql 查询SQL + * @param params 查询SQL参数 + * @return Long对象 + */ + public Long queryLong(String sql, Object... params) { + List list = jdbcTemplate.query(sql, (resultSet, i) -> resultSet.getLong(1), params); + if (list != null && !list.isEmpty()) { + return list.get(0); + } + + return null; + } + + /** + * 查询List对象 + * + * @param sql 查询SQL + * @param params 查询SQL参数 + * @return List对象 + */ + public List queryLongList(String sql, Object... params) { + return jdbcTemplate.query(sql, (resultSet, i) -> resultSet.getLong(1), params); + } + + /** + * 查询Set对象 + * + * @param sql 查询SQL + * @param params 查询SQL参数 + * @return Set对象 + */ + public Set queryLongSet(String sql, Object... params) { + return jdbcTemplate.query(sql, (resultSet) -> { + Set result = new HashSet<>(); + while (resultSet.next()) { + long value = resultSet.getLong(1); + if (!resultSet.wasNull()) { + result.add(value); + } + } + + return result; + }, params); + } + + /** + * 查询Integer对象 + * + * @param sql 查询SQL + * @param params 查询SQL参数 + * @return Integer对象 + */ + public Integer queryInt(String sql, Object... params) { + List list = jdbcTemplate.query(sql, (resultSet, i) -> resultSet.getInt(1), params); + if (list != null && !list.isEmpty()) { + return list.get(0); + } + + return null; + } + + /** + * 执行批量更新语句 + * + * @param sql SQL语句 + * @param params 数组参数列表 + * @return 执行结果数组 + */ + public int[] batchUpdate(String sql, List params) { + return jdbcTemplate.batchUpdate(sql, params); + } + + /** + * 启动事务,需要用try(DbTrans dbTrans=dbEngine.openTrans())来处理异常后自动回滚事务,如果手工提交事务DbTaans.commit() + * + * @return 事务对象 + */ + public JdbcTrans openTrans() { + return new JdbcTrans(this.dataSourceTransactionManager); + } + + /** + * 在回调函数中执行事务,返回的true就提交事务,否则回滚事务 + * + * @param extractor 回调方法 + * @return 事务是否成功,如果异常会也会事务回滚后再抛出异常 + */ + public boolean doTrans(Supplier extractor) { + try (JdbcTrans jdbcTrans = openTrans()) { + boolean result = extractor.get(); + if (result) { + jdbcTrans.commit(); + return true; + } + } + + return false; + } + + /** + * 获取原始数据库连接执行命令 + * + * @return 数据库连接 + */ + public T doConn(Function extractor) { + DataSource ds = Objects.requireNonNull(this.jdbcTemplate.getDataSource()); + + try (Connection conn = ds.getConnection()) { + return extractor.apply(conn); + } catch (SQLException e) { + throw new DbException(e); + } + } + + /** + * 查询单行数据,返回bean + */ + public T queryEntity(String sql, Class type) { + List list = jdbcTemplate.query(sql, createRowMapper(type)); + + if (list != null && !list.isEmpty()) { + return list.get(0); + } + + return null; + } + + /** + * 查询单行数据,返回bean + */ + public T queryEntity(String sql, Class type, Object... params) { + List list = query(sql, createRowMapper(type), params); + + if (list != null && !list.isEmpty()) { + return list.get(0); + } + + return null; + } + + /** + * 通过回调函数查询SQL,返回列表或者对象数据 + */ + public T query(String sql, ResultSetExtractor rse) { + return jdbcTemplate.query(sql, rse); + } + + /** + * 查询SQL,返回列表数据 + */ + public List query(String sql, Class type) { + return jdbcTemplate.query(sql, createRowMapper(type)); + } + + /** + * 查询SQL,返回列表数据 + */ + public List query(String sql, Class type, Object... params) { + return jdbcTemplate.query(sql, createRowMapper(type), params); + } + + /** + * 翻页查询 + */ + public List pagedQuery(String sql, Class type, int start, int limit) { + return jdbcTemplate.query(sql + " LIMIT " + start + "," + limit, createRowMapper(type)); + } + + private RowMapper createRowMapper(Class type) { + RowMapper rowMapper; + if (java.util.Map.class.isAssignableFrom(type)) { + if (SwMap.class.equals(type)) { + rowMapper = new SwMapPropertyRowMapper<>(type); + } else { + rowMapper = new MapPropertyRowMapper<>(type); + } + } else { + rowMapper = new BeanPropertyRowMapper<>(type); + } + return rowMapper; + } + + /** + * 翻页查询 + */ + public List pagedQuery(String sql, Class type, int start, int limit, Object... params) { + return jdbcTemplate.query(sql + " LIMIT " + start + "," + limit, createRowMapper(type), params); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/JdbcTrans.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/JdbcTrans.java new file mode 100644 index 0000000..4639aa2 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/JdbcTrans.java @@ -0,0 +1,56 @@ +package cc.smtweb.framework.core.db.jdbc; + +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +/** + * JDBC事务处理类,可以在try中使用自动结束事务 + * @author xkliu + */ +public class JdbcTrans implements AutoCloseable { + private DataSourceTransactionManager transactionManager; + private TransactionStatus status; + + /** + * 构造事务执行 + * @param transactionManager spring事务管理对象 + */ + public JdbcTrans(DataSourceTransactionManager transactionManager) { + this.transactionManager = transactionManager; + // 事务定义类 + DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + // 返回事务对象 + this.status = transactionManager.getTransaction(def); + } + + /** + * 回滚事务 + */ + public void rollback() { + if (status != null) { + transactionManager.rollback(status); + status = null; + } + } + + /** + * 提交事务 + */ + public void commit() { + if (status != null) { + transactionManager.commit(status); + status = null; + } + } + + /** + * 实现自动关闭,回滚方式结束事务 + */ + @Override + public void close() { + this.rollback(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/MapPropertyRowMapper.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/MapPropertyRowMapper.java new file mode 100644 index 0000000..c8ebc51 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/MapPropertyRowMapper.java @@ -0,0 +1,39 @@ +package cc.smtweb.framework.core.db.jdbc; + +import org.springframework.beans.BeanUtils; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; + +/** + * ORM映射处理器,实现spring jdbcTemplate的行集映射器 + * @author xkliu + */ +public class MapPropertyRowMapper implements RowMapper { + private Class mappedClass; + + public MapPropertyRowMapper(Class mappedClass) { + this.mappedClass = mappedClass; + } + + @Override + public T mapRow(ResultSet resultSet, int i) throws SQLException { + T mappedObject = BeanUtils.instantiateClass(this.mappedClass); + java.util.Map map = (java.util.Map)mappedObject; + + ResultSetMetaData rsmd = resultSet.getMetaData(); + int columnCount = rsmd.getColumnCount(); + + for(int index = 1; index <= columnCount; ++index) { + Object value = resultSet.getObject(index); + + if (value != null) { + map.put(rsmd.getColumnLabel(index), value); + } + } + + return mappedObject; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/SwMapPropertyRowMapper.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/SwMapPropertyRowMapper.java new file mode 100644 index 0000000..1d3ca0c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/jdbc/SwMapPropertyRowMapper.java @@ -0,0 +1,59 @@ +package cc.smtweb.framework.core.db.jdbc; + +import cc.smtweb.framework.core.SwMap; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; + +/** + * ORM映射处理器,实现spring jdbcTemplate的行集映射器,对下划线进行小驼峰命名转化 + * @author xkliu + */ +public class SwMapPropertyRowMapper implements RowMapper { + public SwMapPropertyRowMapper(Class mappedClass) { + } + + @Override + public T mapRow(ResultSet resultSet, int i) throws SQLException { + SwMap map = new SwMap(); + + ResultSetMetaData rsmd = resultSet.getMetaData(); + int columnCount = rsmd.getColumnCount(); + + for(int index = 1; index <= columnCount; ++index) { + Object value = resultSet.getObject(index); + + if (value != null) { + String columnLabel = rsmd.getColumnLabel(index); + map.put(toCamelCase(columnLabel), value); + } + } + + return (T)map; + } + + private String toCamelCase(String columnLabel) { + int len = columnLabel.length(); + StringBuilder sb = new StringBuilder(len); + int lowCase = 1; + for (int i = 0; i < len; i++) { + char ch = columnLabel.charAt(i); + if (ch == '_') { + lowCase = 2; + } else { + if (lowCase == 1) { + ch = Character.toLowerCase(ch); + lowCase = 0; + } else if (lowCase == 2) { + ch = Character.toUpperCase(ch); + lowCase = 0; + } + sb.append(ch); + } + } + + return sb.toString(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/AbstractSelectSqlBuilder.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/AbstractSelectSqlBuilder.java new file mode 100644 index 0000000..9006790 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/AbstractSelectSqlBuilder.java @@ -0,0 +1,129 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +public class AbstractSelectSqlBuilder extends SqlBuilder { + private List orderBy; + + protected void makeFields(StringBuilder sb) { + } + + protected Object[] makeParams(StringBuilder sb) { + makeFields(sb); + + if (wheres != null) { + List params = new ArrayList<>(wheres.size()); + + if (wheres.size() > 0) { + String logicOp = " AND "; + boolean addLoginOp = false; + + for (int i = 0; i < wheres.size(); i++) { + SqlWhereValue whereValue = wheres.get(i); + + if (i == 0) { + sb.append(" WHERE "); + } + + if (whereValue.getName() == null) { + Object value = whereValue.getValue(); + + if ("(".equals(value)) { + if (i > 0) { + sb.append(logicOp); + } + addLoginOp = false; + } + + sb.append(value); + logicOp = whereValue.getOp(); + } else { + if (addLoginOp) { + sb.append(logicOp); + } else { + addLoginOp = true; + } + + sb.append(whereValue.getName()).append(whereValue.getOp()).append('?'); + params.add(whereValue.getValue()); + } + } + } + + if (orderBy != null) { + for (int i = 0; i < orderBy.size(); i++) { + if (i == 0) { + sb.append(" ORDER BY "); + } else { + sb.append(","); + } + + sb.append(orderBy.get(i)); + } + } + + return params.toArray(new Object[params.size()]); + } + + return null; + } + + @Override + public AbstractSelectSqlBuilder addOrderBy(String orderByField) { + if (this.orderBy == null) { + this.orderBy = new ArrayList<>(); + } + + this.orderBy.add(orderByField); + + return this; + } + + public List query(DbEngine dbEngine, Class clazz) { + return exec((sql, params) -> dbEngine.query(sql, clazz, params)); + } + + public T queryEntity(DbEngine dbEngine, Class clazz) { + return exec((sql, params) -> dbEngine.queryEntity(sql, clazz, params)); + +// StringBuilder sb = new StringBuilder("select "); +// +// Object[] params = makeParams(sb); +// +// return dbEngine.queryEntity(sb.toString(), clazz, params); + } + + public T exec(BiFunction execute) { + StringBuilder sb = new StringBuilder("select "); + + Object[] params = makeParams(sb); + + return execute.apply(sb.toString(), params); + } + + public List pagedQuery(DbEngine dbEngine, Class clazz, int start, int limit) { + return exec((sql, params) -> dbEngine.pagedQuery(sql, clazz, start, limit, params)); +// StringBuilder sb = new StringBuilder("select "); +// +// Object[] params = makeParams(sb); +// +// return dbEngine.pagedQuery(sb.toString(), clazz, start, limit, params); + } + + public int queryInt(JdbcEngine dbEngine) { + return exec(dbEngine::queryInt); + } + + public SqlJoinTable addJoinTable(String dbName, String tableName, String tableAlias) { + return null; + } + + public SqlJoinTable findJoinTable(String dbName, String tableName) { + return null; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/AbstractUpdateSqlBuilder.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/AbstractUpdateSqlBuilder.java new file mode 100644 index 0000000..ebb4e53 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/AbstractUpdateSqlBuilder.java @@ -0,0 +1,20 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; + +public abstract class AbstractUpdateSqlBuilder extends SqlBuilder { + // 无效的更新值,用以占位表示不组装值到params对象里面 + public static final Object VALUE_INVALID = new SqlFieldValue("", ""); + + public boolean isEmpty() { + return fields.isEmpty(); + } + + public abstract int update(JdbcEngine dbEngine); + + public void updateMap(java.util.Map map) { + for (SqlFieldValue field: fields) { + map.put(field.getName(), field.getValue()); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DeleteSqlBuilder.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DeleteSqlBuilder.java new file mode 100644 index 0000000..4fdcc78 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DeleteSqlBuilder.java @@ -0,0 +1,33 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; + +public class DeleteSqlBuilder extends AbstractUpdateSqlBuilder { + private String tableName; + + DeleteSqlBuilder(String tableName) { + this.tableName = tableName; + } + + @Override + public int update(JdbcEngine dbEngine) { + int fieldSize = wheres.size(); + Object[] params = new Object[fieldSize]; + + StringBuilder sb = new StringBuilder("delete from "); + + sb.append(tableName).append(" where "); + + int index = 0; + for (SqlWhereValue whereValue: wheres) { + if (index > 0){ + sb.append(" and "); + } + sb.append(whereValue.getName()).append("=?"); + params[index] = whereValue.getValue(); + index++; + } + + return dbEngine.update(sb.toString(), params); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DirectSelectSqlBuilder.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DirectSelectSqlBuilder.java new file mode 100644 index 0000000..6986db6 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/DirectSelectSqlBuilder.java @@ -0,0 +1,20 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import java.util.function.BiFunction; + +public class DirectSelectSqlBuilder extends AbstractSelectSqlBuilder { + private String sql; + + DirectSelectSqlBuilder(String sql) { + this.sql = sql; + } + + @Override + public T exec(BiFunction execute) { + StringBuilder sb = new StringBuilder(sql); + + Object[] params = makeParams(sb); + + return execute.apply(sb.toString(), params); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/InsertSqlBuilder.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/InsertSqlBuilder.java new file mode 100644 index 0000000..dfc1e3c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/InsertSqlBuilder.java @@ -0,0 +1,38 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; + +public class InsertSqlBuilder extends AbstractUpdateSqlBuilder { + private String tableName; + + InsertSqlBuilder(String tableName) { + this.tableName = tableName; + } + + @Override + public int update(JdbcEngine dbEngine) { + int fieldSize = fields.size(); + Object[] params = new Object[fieldSize]; + + StringBuilder sb = new StringBuilder("insert into "); + sb.append(tableName).append('('); + + for (int i = 0; i < fieldSize; i++) { + SqlFieldValue field = fields.get(i); + sb.append(field.getName()).append(','); + params[i] = field.getValue(); + } + + sb.setCharAt(sb.length() - 1, ')'); + + sb.append(" values("); + + for (int i = 0; i < fieldSize; i++) { + sb.append("?,"); + } + + sb.setCharAt(sb.length() - 1, ')'); + + return dbEngine.update(sb.toString(), params); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SelectSqlBuilder.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SelectSqlBuilder.java new file mode 100644 index 0000000..61b1433 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SelectSqlBuilder.java @@ -0,0 +1,98 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +public class SelectSqlBuilder extends AbstractSelectSqlBuilder { + private String tableName; + private List joinTables; + + SelectSqlBuilder(String tableName) { + this.tableName = tableName; + } + +// @Override +// public List query(DbEngine dbEngine, Class clazz) { +// StringBuilder sb = new StringBuilder("select "); +// +// Object[] params = makeParams(sb); +// +// if (params != null) { +// return dbEngine.query(sb.toString(), clazz, params); +// } else { +// return dbEngine.query(sb.toString(), clazz); +// } +// } + + @Override + protected void makeFields(StringBuilder sb) { + for (SqlFieldValue field: fields) { + sb.append(field.getName()).append(","); + } + + sb.setCharAt(sb.length() - 1, ' '); + sb.append("FROM ").append(tableName); + + if (joinTables != null) { + for (SqlJoinTable joinTable: joinTables) { + sb.append(joinTable.joinSql()).append(joinTable.getDbName()).append('.').append(joinTable.getTableName()) + .append(' ').append(joinTable.getTableAlias()).append(" ON("); + + boolean first = true; + for (SqlJoinField joinField: joinTable.getFields()) { + if (first) { + first = false; + } else { + sb.append(" AND "); + } + + sb.append(joinField.getKeyField()).append("=").append(joinField.getValueField()); + } + + sb.append(')'); + } + } + } + + @Override + public T exec(BiFunction execute) { + StringBuilder sb = new StringBuilder("select "); + + Object[] params = makeParams(sb); + + return execute.apply(sb.toString(), params); + } + + @Override + public SqlJoinTable addJoinTable(String dbName, String tableName, String tableAlias) { + if (joinTables == null) { + joinTables = new ArrayList<>(); + } + + SqlJoinTable joinTable = new SqlJoinTable(); + joinTable.setDbName(dbName); + joinTable.setTableName(tableName); + joinTable.setTableAlias(tableAlias); + + joinTables.add(joinTable); + + return joinTable; + } + + @Override + public SqlJoinTable findJoinTable(String dbName, String tableName) { + if (joinTables != null) { + for (SqlJoinTable table: joinTables) { + if (dbName.equals(table.getDbName()) && tableName.equals(table.getTableName())) { + return table; + } + } + } + + return null; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlBuilder.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlBuilder.java new file mode 100644 index 0000000..c4ef731 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlBuilder.java @@ -0,0 +1,84 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import java.util.ArrayList; +import java.util.List; + +/** + * SQL语句建造器 + * @author admin + * @param 建造器类型 + */ +public abstract class SqlBuilder { + protected List fields = new ArrayList<>(); + protected List wheres; + + SqlBuilder() { + } + + public static InsertSqlBuilder createInsert(String tableName) { + return new InsertSqlBuilder(tableName); + } + public static UpdateSqlBuilder createUpdate(String tableName) { + return new UpdateSqlBuilder(tableName); + } + public static SelectSqlBuilder createSelect(String tableName) { + return new SelectSqlBuilder(tableName); + } + public static DeleteSqlBuilder createDelete(String tableName) { + return new DeleteSqlBuilder(tableName); + } + + public static InsertSqlBuilder createInsert(String dbName, String tableName) { + return new InsertSqlBuilder(dbName + "." + tableName); + } + + public static UpdateSqlBuilder createUpdate(String dbName, String tableName) { + return new UpdateSqlBuilder(dbName + "." + tableName); + } + + public static AbstractSelectSqlBuilder createSelect(String dbName, String tableName) { + return new SelectSqlBuilder(dbName + "." + tableName); + } + + public static AbstractSelectSqlBuilder createDirectSelect(String sql) { + return new DirectSelectSqlBuilder(sql); + } + + public static DeleteSqlBuilder createDelete(String dbName, String tableName) { + return new DeleteSqlBuilder(dbName + "." + tableName); + } + + public T add(String fieldName) { + fields.add(new SqlFieldValue(fieldName, null)); + return (T)this; + } + + public T add(String fieldName, Object fieldValue) { + fields.add(new SqlFieldValue(fieldName, fieldValue)); + return (T)this; + } + + public T addWhere(String fieldName, Object fieldValue, String op) { + if (wheres == null) { + wheres = new ArrayList<>(); + } + wheres.add(new SqlWhereValue(fieldName, fieldValue, op)); + return (T)this; + } + + public T addWhere(String fieldName, Object fieldValue) { + return addWhere(fieldName, fieldValue, "="); + } + + public T addWhereOrBegin() { + return addWhere(null, "(", " or "); + } + + public T addWhereOrEnd() { + return addWhere(null, ")", " and "); + } + + public T addOrderBy(String orderBy) { + return (T)this; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlFieldValue.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlFieldValue.java new file mode 100644 index 0000000..50ee584 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlFieldValue.java @@ -0,0 +1,14 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import lombok.Getter; + +@Getter +class SqlFieldValue { + private final String name; + private final Object value; + + public SqlFieldValue(String name, Object value) { + this.name = name; + this.value = value; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlJoinField.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlJoinField.java new file mode 100644 index 0000000..be4f2df --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlJoinField.java @@ -0,0 +1,15 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import lombok.Data; +import lombok.Getter; + +@Getter +public class SqlJoinField { + private String keyField; + private String valueField; + + public SqlJoinField(String keyField, String valueField) { + this.keyField = keyField; + this.valueField = valueField; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlJoinTable.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlJoinTable.java new file mode 100644 index 0000000..a5bc154 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlJoinTable.java @@ -0,0 +1,39 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * SelectSqlBuilder使用的查询关联表 + * @author xkliu + */ +@Data +public class SqlJoinTable { + public static final int LEFT_JOIN = 1; + public static final int RIGHT_JOIN = 2; + public static final int INNER_JOIN = 3; + public static final int FULL_JOIN = 4; + + private int joinType = LEFT_JOIN; + private String dbName; + private String tableName; + private String tableAlias; + + private List fields = new ArrayList<>(); + + public void add(String keyField, String valueField) { + fields.add(new SqlJoinField(keyField, valueField)); + } + + public String joinSql() { + switch (joinType) { + default: + case LEFT_JOIN: return " LEFT JOIN "; + case RIGHT_JOIN: return " RIGHT JOIN "; + case INNER_JOIN: return " INNER JOIN "; + case FULL_JOIN: return " FULL JOIN "; + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlWhereValue.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlWhereValue.java new file mode 100644 index 0000000..d3a9360 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/SqlWhereValue.java @@ -0,0 +1,13 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import lombok.Getter; + +class SqlWhereValue extends SqlFieldValue { + @Getter + private String op; + + public SqlWhereValue(String name, Object value, String op) { + super(name, value); + this.op = op; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/UpdateSqlBuilder.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/UpdateSqlBuilder.java new file mode 100644 index 0000000..e7c638a --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/sqlbuilder/UpdateSqlBuilder.java @@ -0,0 +1,55 @@ +package cc.smtweb.framework.core.db.sqlbuilder; + +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; + +import java.util.ArrayList; +import java.util.List; + +public class UpdateSqlBuilder extends AbstractUpdateSqlBuilder { + private String tableName; + + UpdateSqlBuilder(String tableName) { + this.tableName = tableName; + } + + @Override + public int update(JdbcEngine dbEngine) { + int fieldSize = fields.size() + wheres.size(); + List params = new ArrayList<>(fieldSize); +// Object[] params = new Object[fieldSize]; + + StringBuilder sb = new StringBuilder("update "); + + sb.append(tableName).append(" set "); + +// int index = 0; + for (SqlFieldValue field: fields) { + if (field.getValue() == VALUE_INVALID) { + sb.append(field.getName()); + } else { + sb.append(field.getName()).append("=?,"); + params.add(field.getValue()); + } +// params[index] = field.getValue(); +// index++; + } + + sb.setCharAt(sb.length() - 1, ' '); + sb.append("where "); + + boolean first = true; + for (SqlWhereValue whereValue: wheres) { + if (first) { + first = false; + } else { + sb.append(" and "); + } + sb.append(whereValue.getName()).append("=?"); + params.add(whereValue.getValue()); +// params[index] +// index++; + } + + return dbEngine.update(sb.toString(), params.toArray()); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/KeyValueVO.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/KeyValueVO.java new file mode 100644 index 0000000..fc5b6c5 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/KeyValueVO.java @@ -0,0 +1,9 @@ +package cc.smtweb.framework.core.db.vo; + +import lombok.Data; + +@Data +public class KeyValueVO { + private String key; + private String value; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCache.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCache.java new file mode 100644 index 0000000..6ff3a11 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCache.java @@ -0,0 +1,30 @@ +package cc.smtweb.framework.core.db.vo; + +import lombok.Data; + +import java.util.List; + +/** + * 表缓存信息定义:{"name":"tn","title":"按表名","fields":"table_name","type":"M"} + * Created by Akmm at 2022/2/21 16:22 + */ +@Data +public class ModelCache { + //按map缓存 + public static String CACHE_TYPE_MAP = "M"; + //按list缓存 + public static String CACHE_TYPE_LIST = "L"; + + //缓存名,根据此名称去获取缓存信息 getByKey的参数 + private String name; + //缓存中文名,给人看的 + private String title; + //字段,多个字段,键值以下划线分隔 + private String fields; + //缓存类别:list/map + private String type; + + public boolean isMapType() { + return CACHE_TYPE_MAP.equalsIgnoreCase(type); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCatalog.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCatalog.java new file mode 100644 index 0000000..c9ea0de --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelCatalog.java @@ -0,0 +1,27 @@ +package cc.smtweb.framework.core.db.vo; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 目录 + */ +@Data +public class ModelCatalog implements Serializable { + private long id; + private long parentId; + //项目id + private long prjId; + //目录编码及名称 + private String code; + private String name; + //创建者 + private Long createUid; + //创建时间 + private Long createTime; + //最后更新人 + private Long updateUid; + //更新时间 + private Long updateTime; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelDatabase.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelDatabase.java new file mode 100644 index 0000000..f3dc64d --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelDatabase.java @@ -0,0 +1,31 @@ +package cc.smtweb.framework.core.db.vo; + +import cc.smtweb.framework.core.annotation.SwTable; +import lombok.Data; + +import java.io.Serializable; +import java.util.Map; + +@Data +@SwTable(value = "ASP_MODEL_DATABASE") +public class ModelDatabase implements Serializable { + private long id; + //项目id + private long prjId; + // 库名 + private String name; + //中文标题 + private String title; + //状态:0-启用 1-停用 + private int status; + //版本 + private int version; + //创建者 + private Long createUid; + //创建时间 + private Long createTime; + //最后更新人 + private Long updateUid; + //更新时间 + private Long updateTime; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelField.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelField.java new file mode 100644 index 0000000..d36ba95 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelField.java @@ -0,0 +1,38 @@ +package cc.smtweb.framework.core.db.vo; + +import cc.smtweb.framework.core.db.vo.def.DataType; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 字段定义 + * {name:"字段名,如id",fieldType:"字段类型,如编码字段", dataType:"数据类型,如ID/CODE/NAME等", null:"0-空/1-非空", default: "默认值", title:"中文名",link:"外键关联表",editor:"控件类型:TEXT/TextArea/NUMBER/COMBO"} + */ +@Data +public class ModelField { + private String name; + private String title; + private String remark; + //字段类型,如编码字段,参见FieldTypeDef + private String fieldType; + /** + * 数据类型,参见DataType + */ + private String dataType; + /** + * '禁止为空' + */ + @JsonProperty("null") + private int notNull; + /** + * '默认值' + */ + @JsonProperty("default") + private String defaultValue; + //外键关联表 + private String link; + //控件类型:TEXT/TextArea/NUMBER/COMBO + private String editor; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelIndex.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelIndex.java new file mode 100644 index 0000000..f75cdbd --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelIndex.java @@ -0,0 +1,20 @@ +package cc.smtweb.framework.core.db.vo; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 索引定义 {name:"索引名称,如idx_t1", fields:"字段,如f1,f2", type="索引类别:P-主键 I-一般索引 U-唯一索引"} + */ +@Data +public class ModelIndex { + public static final String TYPE_PRIMARY = "P"; + public static final String TYPE_INDEX = "I"; + public static final String TYPE_UNIQUE = "U"; + + private String type; + private String name; + private String fields; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelProject.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelProject.java new file mode 100644 index 0000000..6c6555a --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelProject.java @@ -0,0 +1,29 @@ +package cc.smtweb.framework.core.db.vo; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 项目 + */ +@Data +public class ModelProject implements Serializable { + private long id; + // 项目名称 + private String name; + //依赖项目 + private String depends; + //状态:0-启用 1-停用 + private int status; + //备注 + private String desc; + //创建者 + private Long createUid; + //创建时间 + private Long createTime; + //最后更新人 + private Long updateUid; + //更新时间 + private Long updateTime; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelTable.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelTable.java new file mode 100644 index 0000000..f936ebc --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/ModelTable.java @@ -0,0 +1,142 @@ +package cc.smtweb.framework.core.db.vo; + +import cc.smtweb.framework.core.annotation.SwTable; +import cc.smtweb.framework.core.db.vo.def.FieldType; +import cc.smtweb.framework.core.util.JsonUtil; +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Data +@SwTable(value="ASP_MODEL_TABLE") +public class ModelTable implements Serializable { + private long id; + //所属数据库id及名称 + private long databaseId; + //项目id + private long prjoectId; + //目录id + private long catalogId; + //库表名 + private String name; + //中文名 + private String title; + //缩写 + private String abbr; + //继承的表 + private String tableExtends; + //类型 0-普通表,1 树型表 2 编码表 9-虚拟抽象表 11 视图' + private int type; + //表定义 + private String tableContent; + //是否需要缓存 + private boolean needCache; + //创建者 + private Long createUid; + //创建时间 + private Long createAt; + //最后更新人 + private Long updateUid; + //更新时间 + private Long updateAt; + + /*冗余*/ + private String idField; + private String dbName; + + private List fields = new ArrayList<>(); + private List indexes = new ArrayList<>(); + private List caches = new ArrayList<>(); + + public ModelField findField(String fieldName) { + if (fieldName != null && fields != null) { + for (ModelField modelField : fields) { + if (fieldName.equals(modelField.getName())) { + return modelField; + } + } + } + + return null; + } + + public String fullName() { + return dbName + '.' + name; + } + + public void addIndex(ModelIndex modelIndex) { + indexes.add(modelIndex); + } + + public ModelIndex findIndexByName(String indexName) { + for (ModelIndex modelIndex : indexes) { + if (indexName.equals(modelIndex.getName())) { + return modelIndex; + } + } + + return null; + } + + public ModelIndex findPrimaryIndex() { + for (ModelIndex modelIndex : indexes) { + if (ModelIndex.TYPE_PRIMARY.equalsIgnoreCase(modelIndex.getType())) { + return modelIndex; + } + } + + return null; + } + + public ModelField findFieldByName(String name) { + if (name != null) { + for (ModelField value : fields) { + if (name.equalsIgnoreCase(value.getName())) { + return value; + } + } + } + + return null; + } + + public ModelField findFieldByType(FieldType type) { + if (type != null) { + for (ModelField value : fields) { + if (type.name().equalsIgnoreCase(value.getFieldType())) { + return value; + } + } + } + + return null; + } + + public ModelField findIdField() { + ModelIndex index = findPrimaryIndex(); + if (index != null) { + return findField(index.getFields()); + } + + return null; + } + + public void setTableContent(String tableContent) { + this.tableContent = tableContent; + //读取表定义信息 + ModelTable bean = JsonUtil.parse(tableContent, ModelTable.class); + if (bean == null) { + return; + } + this.fields = bean.fields; + this.indexes = bean.indexes; + this.caches = bean.caches; + + ModelIndex i = findPrimaryIndex(); + if (i != null) { + this.idField = i.getFields(); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/def/DataType.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/def/DataType.java new file mode 100644 index 0000000..a80061e --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/def/DataType.java @@ -0,0 +1,33 @@ +package cc.smtweb.framework.core.db.vo.def; + +import lombok.Data; + +/** + * 数据类型定义,参见design_db.yaml配置 + */ +@Data +public class DataType { + /*{type: "id", name: "ID", sql-type: "bigint", java-type: "long", widget: "fz-field-long", defaultValue: "0"} + - {type: "code", name: "编码", sql-type: "varchar", java-type: "string", data-length: 32, widget: "fz-field-string", defaultValue: ""} + - {type: "name", name: "名字", sql-type: "varchar", java-type: "string", data-length: 100, widget: "fz-field-string", defaultValue: ""} + - {type: "remark", name: "备注", sql-type: "varchar", data-length: 255, java-type: "string", widget: "fz-field-string", defaultValue: ""} + - {type: "text", name: "大文本", sql-type: "text", java-type: "string", widget: "fz-field-string", defaultValue: ""} + - {type: "currency", name: "货币", sql-type: "bigint", java-type: "long", widget: "fz-field-long", defaultValue: "0"} + - {type: "datetime", name: "时间日期", sql-type: "bigint", java-type: "long", widget: "fz-field-datetime", defaultValue: "0"} + - {type: "date", name: "日期", sql-type: "int", java-type: "int", widget: "fz-field-date", defaultValue: "0"} + - {type: "time", name: "时间", sql-type: "int", java-type: "int", widget: "fz-field-time", defaultValue: "0"} + - {type: "int", name: "整型", sql-type: "int", java-type: "int", widget: "fz-field-int", defaultValue: "0"} + - {type: "smallint", name: "短整型", sql-type: "smallint", java-type: "short", widget: "fz-field-int", defaultValue: "0"} + - {type: "bool", name: "布尔型", sql-type: "tinyint", java-type: "boolean", widget: "fz-field-bool", defaultValue: "0"}*/ + public static final String TYPE_STR = "varchar"; + public static final String TYPE_BOOL = "bool"; + public static final String TYPE_DATETIME = "datetime"; + public static final String TYPE_DATE = "date"; + + private String type; + private String name; + private String sqlType; + private int dataLength; + private String javaType; + private String defaultValue; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/def/FieldType.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/def/FieldType.java new file mode 100644 index 0000000..c88ebff --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/db/vo/def/FieldType.java @@ -0,0 +1,22 @@ +package cc.smtweb.framework.core.db.vo.def; + +/** + * Created by Akmm at 2022/2/9 10:01 + * 字段业务类别 + */ +public enum FieldType { + // 主键 + ID, + // 上级ID,树结构需要 + PARENT_ID, + // 排序字段,树结构需要 + ORDER, + // 编码字段 + CODE, + // 名词字段 + NAME, + // 创建时间 + CREATE_TIME, + // 更新时间 + LAST_TIME +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/BindBeanException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/BindBeanException.java new file mode 100644 index 0000000..df2c05c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/BindBeanException.java @@ -0,0 +1,41 @@ +package cc.smtweb.framework.core.exception; + +import cc.smtweb.framework.core.SwException; + +/** + * bean绑定错误 + * @author kevin + * @since 2010-9-14 上午11:17:43 + * + */ +public class BindBeanException extends SwException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private static String msg = "绑定bean异常:Context中已经存在这个bean: "; + + public BindBeanException() { + super(); + // TODO Auto-generated constructor stub + } + + public BindBeanException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public BindBeanException(String message) { + + super(msg+message); + // TODO Auto-generated constructor stub + } + + public BindBeanException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/BindParamException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/BindParamException.java new file mode 100644 index 0000000..4eea3c2 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/BindParamException.java @@ -0,0 +1,34 @@ +package cc.smtweb.framework.core.exception; + +import cc.smtweb.framework.core.SwException; + +import java.text.ParseException; + +public class BindParamException extends SwException { + /** + * + */ + private static final long serialVersionUID = 1L; + private String paramName; + + public String getParamName() { + return paramName; + } + + public BindParamException(String message, String paramName) { + this(message, null, paramName); + } + + public BindParamException(Exception e, String paramName) { + this(e.getMessage(), e, paramName); + } + + public BindParamException(String message, Exception e, String paramName) { + super("[" + paramName + "]" + message, e); + this.paramName = paramName; + } + + public BindParamException(ParseException e) { + super(e); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/BizException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/BizException.java new file mode 100644 index 0000000..0fbb71a --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/BizException.java @@ -0,0 +1,29 @@ +package cc.smtweb.framework.core.exception; + +/** + * 业务异常 + * @author kevin + * + */ +public class BizException extends RuntimeException { + /** + * + */ + private static final long serialVersionUID = 1L; + + public BizException() { + super(); + } + + public BizException(String message, Throwable cause) { + super(message, cause); + } + + public BizException(String message) { + super(message); + } + + public BizException(Throwable cause) { + super(cause.getMessage(), cause); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/CacheException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/CacheException.java new file mode 100644 index 0000000..9bc0056 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/CacheException.java @@ -0,0 +1,13 @@ +package cc.smtweb.framework.core.exception; + +import cc.smtweb.framework.core.SwException; + +public class CacheException extends SwException { + public CacheException(String message) { + super(message); + } + + public CacheException(Throwable e) { + super(e); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/DbException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/DbException.java new file mode 100644 index 0000000..583b001 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/DbException.java @@ -0,0 +1,33 @@ +package cc.smtweb.framework.core.exception; + +import cc.smtweb.framework.core.SwException; + +/** + * bean绑定错误 + * @author kevin + * @since 2010-9-14 上午11:17:43 + * + */ +public class DbException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public DbException() { + super(); + } + + public DbException(String message, Throwable cause) { + super(message, cause); + } + + public DbException(String message) { + super(message); + } + + public DbException(Throwable cause) { + super(cause.getMessage(), cause); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/ExceptionMessage.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/ExceptionMessage.java new file mode 100644 index 0000000..d4d739a --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/ExceptionMessage.java @@ -0,0 +1,105 @@ +package cc.smtweb.framework.core.exception; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.apache.commons.lang3.StringUtils; + +import java.util.Set; + +/** + * 错误码 JDK version used: + * + * @author kevin + * @version v1.0 + */ +@Setter +@Getter +@ToString +public class ExceptionMessage { + private int code; + + private String msg; + + private ExceptionMessage(int code, String msg) { + this.code = code; + this.msg = msg; + } + + // 401 + public static ExceptionMessage NO_AUTH_ERROR = new ExceptionMessage(401, "登录失效!"); + + // 403 + public static ExceptionMessage FORBIDDEN_ERROR = new ExceptionMessage(403, "没有访问权限!"); + + // 404 + public static ExceptionMessage NOT_FOUND_ERROR = new ExceptionMessage(404, "没有找到需要资源!"); + + // 通用异常 + public static ExceptionMessage INNER_ERROR = new ExceptionMessage(100100, "内部服务错误,请稍后再试!"); + + // 参数错误 + public static ExceptionMessage PARAM_ERROR = new ExceptionMessage(100101, "参数校验失败!"); + + // 数据库访问异常 + public static ExceptionMessage DB_ERROR = new ExceptionMessage(100102, "数据库访问失败!"); + + // 外部网络访问异常 + public static ExceptionMessage NETWORK_ERROR = new ExceptionMessage(100103, "外部网络访问失败!"); + + // 字符转换异常 + public static ExceptionMessage UNSUPPORTED_ENCODING_ERROR = new ExceptionMessage(100104, "字符编码失败!"); + + // 没有登录 + public static ExceptionMessage NO_LOGIN_ERROR = new ExceptionMessage(100105, "用户没有登录!"); + + // 没有登录 + public static ExceptionMessage CAPTCHA_ERROR = new ExceptionMessage(100106, "验证码错误!"); + + // 用户不存在 + public static ExceptionMessage USER_ERROR = new ExceptionMessage(100107, "手机号或密码错误,请重新输入!"); + + // 用户被禁 + public static ExceptionMessage USER_FORBIDED = new ExceptionMessage(100108, "用户被禁!"); + + // 密码错误 + public static ExceptionMessage PASSWORD_ERROR = new ExceptionMessage(100109, "密码错误,请重新输入!"); + + // 文件上传失败 + public static ExceptionMessage FILE_UPLOAD_ERROR = new ExceptionMessage(100110, "文件上传失败!"); + + // 文件不存在 + public static ExceptionMessage FILE_NOT_EXIST = new ExceptionMessage(100111, "文件不存在!"); + + // 树形层级只有6级 + public static ExceptionMessage TREE_HIERARCHY_ERROR = new ExceptionMessage(100112, "树形层级过多!"); + + // 手机号被使用了 + public static ExceptionMessage PHONE_ERROR = new ExceptionMessage(100113, "手机号被使用了!"); + + // 手机号不存在 + public static ExceptionMessage PHONE_EXISTS_ERROR = new ExceptionMessage(100114, "手机号不存在!"); + + // 消息发送要延迟 + public static ExceptionMessage SM_SEND_NEED_DELAY = new ExceptionMessage(100115, "验证码有效时间"); + + // 身份不能确认 + public static ExceptionMessage IDENTITY_ERROR = new ExceptionMessage(100116, "请选择登录身份!"); + + // 身份不能确认 + public static ExceptionMessage USER_PERMISSION_CHANGE = new ExceptionMessage(100117, "权限变更,请选择退出重新登录!"); + + // 数据发生变更 + public static ExceptionMessage DATA_CHANGE_ERROR = new ExceptionMessage(100118, "数据发生变更,请刷新后重试!"); + + // 数据发生变更 + public static ExceptionMessage SM_SEND_ERROR = new ExceptionMessage(100119, "验证码发生失败!"); + + // 敏感词 + public static ExceptionMessage XSS_ERROR = new ExceptionMessage(100120, "含有敏感词,请检查。"); + + // 树形层级数量太多 + public static ExceptionMessage TREE_NUMBER_ERROR = new ExceptionMessage(100121, "树形该层节点过多!"); + + public static ExceptionMessage APP_ID_NOT_EXISTS = new ExceptionMessage(100122, "不存在的应用ID"); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/JsonParseException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/JsonParseException.java new file mode 100644 index 0000000..4ed1977 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/exception/JsonParseException.java @@ -0,0 +1,9 @@ +package cc.smtweb.framework.core.exception; + +import cc.smtweb.framework.core.SwException; + +public class JsonParseException extends SwException { + public JsonParseException(String s, Exception e) { + super(s, e); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/ISchedulerWakeup.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/ISchedulerWakeup.java new file mode 100644 index 0000000..0cfd153 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/ISchedulerWakeup.java @@ -0,0 +1,15 @@ +package cc.smtweb.framework.core.mvc; + +/** + * 定时任务唤醒服务接口 + * @author xkliu + */ +public interface ISchedulerWakeup { + /** + * 唤醒定时任务立即执行 + * @param clazz 包含定时任务方法的类名 + * @param methodName 有定时任务注解的方法名 + * @return 是否唤醒请求成功 + */ + boolean wakeup(Class clazz, String methodName); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/SchedulerManager.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/SchedulerManager.java new file mode 100644 index 0000000..5baf5aa --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/SchedulerManager.java @@ -0,0 +1,29 @@ +package cc.smtweb.framework.core.mvc; + +import org.springframework.stereotype.Component; + +/** + * 定时器服务 + * @author xkliu + */ +@Component +public class SchedulerManager { + private ISchedulerWakeup schedulerWakeup; + + public void install(ISchedulerWakeup schedulerWakeup) { + this.schedulerWakeup = schedulerWakeup; + } + + /** + * 唤醒定时器立即执行 + * @param clazz 定时器类 + * @param methodName 有@SwScheduling注解的定时器方法名 + */ + public boolean wakeup(Class clazz, String methodName) { + if (this.schedulerWakeup != null) { + return schedulerWakeup.wakeup(clazz, methodName); + } + + return false; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/ControllerConfig.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/ControllerConfig.java new file mode 100644 index 0000000..1dd91ff --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/ControllerConfig.java @@ -0,0 +1,25 @@ +package cc.smtweb.framework.core.mvc.config; + +import cc.smtweb.framework.core.mvc.controller.scan.IScanAction; +import cc.smtweb.framework.core.mvc.controller.scan.IScanActionBuilder; +import lombok.Getter; + +@Getter +public class ControllerConfig { + /** 模块名称 */ + private final String module; + /** 控制器的包路径 */ + private final String packagePath; + + private IScanActionBuilder scanActionBuilder; + + public ControllerConfig(String module, String packagePath) { + this(module, packagePath, null); + } + + public ControllerConfig(String module, String packagePath, IScanActionBuilder scanActionBuilder) { + this.module = module; + this.packagePath = packagePath; + this.scanActionBuilder = scanActionBuilder; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/GlobalExceptionHandler.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..8f8d360 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/GlobalExceptionHandler.java @@ -0,0 +1,88 @@ +package cc.smtweb.framework.core.mvc.config; + +import cc.smtweb.framework.core.mvc.realm.exception.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import cc.smtweb.framework.core.R; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 〈异常处理〉 + * + * @author kevin + * @since 1.0.0 + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { +// private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class); +// +// /** +// * 〈校验的异常处理〉 +// */ +// @ExceptionHandler(value = {BindException.class}) +// @ResponseBody +// public Result handleBindException(BindException be) { +// return super.handleException(be); +// } +// + + @ExceptionHandler(AuthenticationException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public R handleAuthenticationException(AuthenticationException e) { + return R.error("认证失败", e); + } + + @ExceptionHandler(UnauthenticatedException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public R handleUnauthenticatedException(UnauthenticatedException e) { + return R.error("认证失败", e); + } + + @ExceptionHandler(UnknownAccountException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public R handleUnknownAccountException(UnknownAccountException e) { + return R.error("用户名或密码错误", e); + } + + @ExceptionHandler(AuthorizationException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public R handleAuthorizationException(AuthorizationException e) { + log.error("No permissions:" + e.getMessage()); + return R.error("没有权限", e); + } + + @ExceptionHandler(ForbiddenException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public R handleAuthorizationException(ForbiddenException e) { + log.error("No permissions:" + e.getMessage()); + return R.error("没有权限", e); + } + + @ExceptionHandler(value = {Exception.class}) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public R handleException(Exception ex) { + Throwable e = ex; + if (ex instanceof java.lang.reflect.InvocationTargetException) { + e = ex.getCause(); + } + + log.error(e.getMessage(), e); + return R.error(e.getMessage(), e); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public R handleException(MethodArgumentNotValidException ex) { + List errors = ex.getBindingResult().getAllErrors(); + List validationMsg = errors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList()); + // 不满足需求 一个一个提示 + return R.error(validationMsg.get(0)); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/SettingsEnvironmentPostProcessor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/SettingsEnvironmentPostProcessor.java new file mode 100644 index 0000000..6d0fa03 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/SettingsEnvironmentPostProcessor.java @@ -0,0 +1,57 @@ +package cc.smtweb.framework.core.mvc.config; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.io.FileSystemResource; + +/** + * 〈读取外部配置文件〉 + * + * @author kevin + * @since 1.0.0 + */ +public class SettingsEnvironmentPostProcessor implements EnvironmentPostProcessor { + + @Override + public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, + SpringApplication springApplication) { + List files = this.getProperties(); + for (File file : files) { + if (file.exists()) { + YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean(); + FileSystemResource resource = new FileSystemResource(file); + yaml.setResources(resource); + if (yaml.getObject() != null) { + configurableEnvironment.getPropertySources() + .addLast(new PropertiesPropertySource(file.getName(), yaml.getObject())); + } + } + } + } + + /** + * <>获取所有资源配置文件 + */ + private List getProperties() { + List result = new ArrayList<>(); + String tomcatConf = System.getProperty("catalina.base") + File.separator + "conf" + File.separator; + File[] listFiles = new File(tomcatConf).listFiles(); + if (listFiles != null && listFiles.length > 0) { + for (File lf : listFiles) { + if (lf.isFile()) { + if (lf.getName().endsWith(".yaml")) { + result.add(lf); + } + } + } + } + return result; + } + +} \ No newline at end of file diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/WebMvcConfig.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/WebMvcConfig.java new file mode 100644 index 0000000..c1b8101 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/config/WebMvcConfig.java @@ -0,0 +1,124 @@ +package cc.smtweb.framework.core.mvc.config; + +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.db.jdbc.IdGenerator; +import cc.smtweb.framework.core.mvc.controller.MethodAccessManager; +import cc.smtweb.framework.core.mvc.controller.ApiConfigBean; +import cc.smtweb.framework.core.redis.RedisManager; +import cc.smtweb.framework.core.session.SessionManager; +import cc.smtweb.framework.core.session.UserSessionArgumentResolver; +import cc.smtweb.framework.core.util.JsonUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +/** + * @author kevin + */ +@Configuration +@Slf4j +public class WebMvcConfig implements WebMvcConfigurer { + private static final String PATTERN_FILES = "/files/**"; + private static final String PATTERN_STATIC = "/static/**"; + + @Value("${smtweb.file.local-path}") + private String fileLocalPath; + + @Value("${smtweb.static.local-path:}") + private String staticLocalPath; + +// @Value("${smtweb.api.json-full:}") +// private String apiJsonFull; + + @Autowired + private ApiConfigBean apiConfigBean; + + private static final String TRUE_VALUE = "true"; + + @Autowired + private RedisManager redisManager; + + @Bean + public SessionManager sessionManager(RedisManager redisManager, IdGenerator idGenerator) { + return new SessionManager(redisManager, idGenerator); + } + + @Bean + public CacheManager cacheManager() { + return new CacheManager(); + } + + @Bean + public MethodAccessManager methodAccessManager(CacheManager cacheManager) { + return new MethodAccessManager(redisManager, cacheManager); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + // 2.4 以前使用 .allowedOrigins("*") + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedMethods("*") + .maxAge(3600) + .allowCredentials(true); + } + + @Bean + @Primary + public HttpMessageConverters customConverters() { + MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); + if (apiConfigBean.isJsonFull()) { + // 输出所有空属性 + messageConverter.setObjectMapper(JsonUtil.API_OBJECT_MAPPER); + } else { + messageConverter.setObjectMapper(JsonUtil.OBJECT_MAPPER); + } + + // 将转换器添加到converters中 + return new HttpMessageConverters(messageConverter); + } + + @Override + public void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.add(new UserSessionArgumentResolver()); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // 静态资源文件 + if (!registry.hasMappingForPattern(PATTERN_STATIC)) { + if (StringUtils.isBlank(staticLocalPath)) { + registry.addResourceHandler(PATTERN_STATIC).addResourceLocations("classpath:/static/"); + } else if (staticLocalPath.endsWith("/")) { + // staticLocalPath 必须 / 结束 + registry.addResourceHandler(PATTERN_STATIC).addResourceLocations("classpath:/static/", + "file:" + staticLocalPath); + log.info("'/static/**' => file:" + staticLocalPath); + } else { + log.error("'smtweb.static.local-path' 必须用'/'结尾: " + staticLocalPath); + } + } else { + log.error("'/static/**' 已经被注册"); + } + + // 动态用户文件 + if (!registry.hasMappingForPattern(PATTERN_FILES)) { + registry.addResourceHandler(PATTERN_FILES).addResourceLocations("file:" + fileLocalPath); + log.info("'/files/**' => file:" + fileLocalPath); + } else { + log.error("'/files/**' 已经被注册"); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/ApiConfigBean.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/ApiConfigBean.java new file mode 100644 index 0000000..2d69275 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/ApiConfigBean.java @@ -0,0 +1,21 @@ +package cc.smtweb.framework.core.mvc.controller; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * API相关配置参数 + * @author xkliu + */ +@Component +@ConfigurationProperties(prefix = "smtweb.api") +@Data +public class ApiConfigBean { + // JSON是否输出非空字段 + private boolean jsonFull; + // 是否支持大驼峰url匹配规则 + private boolean lowerCaseUrl; + // 是否支持大驼峰url匹配规则,默认都支持 little camel-case + private boolean bigCameCaseUrl; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/ApiController.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/ApiController.java new file mode 100644 index 0000000..6b4917c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/ApiController.java @@ -0,0 +1,59 @@ +package cc.smtweb.framework.core.mvc.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.PreDestroy; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +/** + * API接口控制器,所有API的入口 + * @author xkliu + */ +@RestController +public class ApiController { + @Autowired + private MethodAccessManager methodAccessManager; + + /** + * 处理API的GET请求 + * @param params URL请求参数 + * @param request Http请求类 + * @param response Http应答类 + * @return 返回对象,一般在函数里面使用R + * @throws Exception API处理异常 + */ + @GetMapping("/api/**") + public Object commonGet(@RequestParam Map params, + HttpServletRequest request, HttpServletResponse response) throws Exception { + String apiUrl = request.getRequestURI().substring(5); + return methodAccessManager.invoke(apiUrl, params, null, request, response); + } + + /** + * 处理API的POST请求 + * @param params URL请求参数 + * @param body POST内容,JSON格式 + * @param request Http请求类 + * @param response Http应答类 + * @return 返回对象,一般在函数里面使用R + * @throws Exception API处理异常 + */ + @PostMapping("/api/**") + public Object commonPost(@RequestParam Map params, @RequestBody(required = false) String body, + HttpServletRequest request, HttpServletResponse response) throws Exception { + String apiUrl = request.getRequestURI().substring(5); + // 保证body在post的清空下不为null,用于区分get时吧参数组合成body + if (body == null) { + body = ""; + } + return methodAccessManager.invoke(apiUrl, params, body, request, response); + } + + @PreDestroy + public void fin() { + methodAccessManager.showdown(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/DefaultPageController.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/DefaultPageController.java new file mode 100644 index 0000000..1f2fa7d --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/DefaultPageController.java @@ -0,0 +1,29 @@ +package cc.smtweb.framework.core.mvc.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * 默认静态页面处理 + * @author xkliu + */ +@Controller +public class DefaultPageController { + /** + * 重定向默认静态页面 + * @return 静态页面重定向路径 + */ + @GetMapping("/") + public String index() { + return "forward:/static/index.html"; + } + + /** + * 重定向默认网站图标 + * @return 网站图标重定向路径 + */ + @GetMapping(value = "/favicon.ico") + public String favicon() { + return "forward:/static/favicon.ico"; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/IActionManager.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/IActionManager.java new file mode 100644 index 0000000..63ca1ca --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/IActionManager.java @@ -0,0 +1,7 @@ +package cc.smtweb.framework.core.mvc.controller; + +import cc.smtweb.framework.core.mvc.controller.access.IMethodAccess; + +public interface IActionManager { + boolean api(String url, IMethodAccess methodAccess); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/IBeanContext.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/IBeanContext.java new file mode 100644 index 0000000..9746ee7 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/IBeanContext.java @@ -0,0 +1,8 @@ +package cc.smtweb.framework.core.mvc.controller; + +import org.springframework.beans.BeansException; + +public interface IBeanContext { + T getBean(String name, Class type) throws BeansException; + T getBean(Class type) throws BeansException; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/IEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/IEditor.java new file mode 100644 index 0000000..8eff0d7 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/IEditor.java @@ -0,0 +1,27 @@ +package cc.smtweb.framework.core.mvc.controller; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * API参数解析器接口 + * @author xkliu + */ +public interface IEditor { + String USER_SESSION = "_SW_USER_SESSION"; + String USER_TOKEN = "_SW_TOKEN"; + String BODY_MAP = "_SW_BODY_MAP"; +// String BODY_BEAN = "_SW_BODY_BEAN"; + + /** + * 根据请求参数转为函数参数定义的类型的值 + * @param paramName 参数名 + * @param paramType 参数类型 + * @param context 全局bean对象上下文 + * @param params URL请求参数(query) + * @param body POST内容 + * @param request http请求 + * @return 转化后的值 + */ + Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/MethodAccessManager.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/MethodAccessManager.java new file mode 100644 index 0000000..416dc07 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/MethodAccessManager.java @@ -0,0 +1,105 @@ +package cc.smtweb.framework.core.mvc.controller; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.cache.ISwCache; +import cc.smtweb.framework.core.mvc.SchedulerManager; +import cc.smtweb.framework.core.mvc.controller.access.IMethodAccess; +import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; +import cc.smtweb.framework.core.mvc.controller.scan.BeanManager; +import cc.smtweb.framework.core.mvc.realm.interceptor.PermInterceptor; +import cc.smtweb.framework.core.mvc.realm.service.PermChecker; +import cc.smtweb.framework.core.mvc.scheduler.SchedulerTaskManager; +import cc.smtweb.framework.core.redis.RedisManager; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +/** + * 服务方法调用管理,通过url进行API调用 + * @author xkliu + */ +@Slf4j +public class MethodAccessManager { + private Map controllers; + private IBeanContext beanContext; + private PermInterceptor permInterceptor; + private SchedulerTaskManager schedulerTaskManager; + private MethodAccess[] destroyMethods; + + @Getter + private CacheManager cacheManager; + + public MethodAccessManager(RedisManager redisManager, CacheManager cacheManager) { + permInterceptor = new PermInterceptor(redisManager); + this.cacheManager = cacheManager; + } + + // 执行控制器方法 + Object invoke(String url, Map params, String body, HttpServletRequest request, HttpServletResponse response) throws Exception { + // 调用普通方法 +// String url = module + "/" + service + "/" + method; + + IMethodAccess methodAccess = controllers.get(url); + + if (methodAccess != null) { + permInterceptor.preHandle(request, methodAccess.getPerm()); + + return methodAccess.invoke(beanContext, params, body, request); + } + + // TODO 调用默认方法 +// url = module + "/" + service + "/"; +// +// methodAccess = controllers.get(url); +// +// if (methodAccess != null) { +// permInterceptor.preHandle(request, methodAccess.getPerm()); +// +// request.setAttribute(PathParamEditor.ATTR_KEY, method); +// return methodAccess.invoke(beanContext, params, body, request, response); +// } + + return R.error(404, "not find api:" + url); + } + + public void init(BeanManager beanManager, ISwCache cache) { + this.beanContext = beanManager.getBeanContext(); + this.controllers = beanManager.getControllers(); + this.destroyMethods = beanManager.loadDestroyMethods(); + this.permInterceptor.setCache(cache); + + // 启动定时任务 + this.schedulerTaskManager = SchedulerTaskManager.build(beanContext, beanManager.getTasks()); + + if (this.schedulerTaskManager != null) { + // 设置用于外部服务调用 + SchedulerManager schedulerManager = this.beanContext.getBean(SchedulerManager.class); + if (schedulerManager != null) { + schedulerManager.install(this.schedulerTaskManager); + } else { + log.error("not find spring bean schedulerManager"); + } + } + } + + void showdown() { + // 强行关闭定时任务 + if (this.schedulerTaskManager != null) { + this.schedulerTaskManager.shutdown(); + } + + if (destroyMethods != null) { + for (MethodAccess methodAccess : destroyMethods) { + try { + methodAccess.invoke(beanContext); + } catch (Exception e) { + log.error(methodAccess.fullName(), e); + } + } + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/BindFieldAccess.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/BindFieldAccess.java new file mode 100644 index 0000000..a664270 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/BindFieldAccess.java @@ -0,0 +1,28 @@ +package cc.smtweb.framework.core.mvc.controller.access; + +import cc.smtweb.framework.core.exception.BindBeanException; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; + +import java.lang.reflect.Field; +import java.util.Map; + +public class BindFieldAccess extends FieldAccess { + private IEditor editor; + private final String beanName; + + public BindFieldAccess(IEditor editor, Field field, String beanName) { + super(field); + this.editor = editor; + this.beanName = beanName; + } + + @Override + protected Object getFieldValue(Object instance, IBeanContext context, Map params) { + if (editor != null) { + return editor.getParamValue(beanName, this.field.getType(), context, params, null, null); + } + + return null; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/ControllerAccess.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/ControllerAccess.java new file mode 100644 index 0000000..dc61fff --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/ControllerAccess.java @@ -0,0 +1,69 @@ +package cc.smtweb.framework.core.mvc.controller.access; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Map; + +/** 控制器访问类 */ +@Slf4j +public class ControllerAccess implements IBeanAccess { + private final Class clazz; + private final List fieldList; + @Getter + private final boolean singleton; + @Getter + private Object singletonInstance; + + public ControllerAccess(Class clazz, List fieldList, boolean singleton) { + this.clazz = clazz; + this.fieldList = fieldList; + this.singleton = singleton; + } + + public String getFullName() { + return clazz.getName(); + } + + public Object getOrNewInstance(IBeanContext context, Map params) throws IllegalAccessException, InstantiationException { + if (singletonInstance != null) { + return singletonInstance; + } else { + Object result = clazz.newInstance(); + + writeBeanFields(result, context, params); + + return result; + } + } + + private void writeBeanFields(Object bean, IBeanContext context, Map params) { + for (FieldAccess fieldAccess: fieldList) { + boolean ret = fieldAccess.writeValue(bean, context, params); + if (!ret) { + log.error("write bean " + fieldAccess.getFullName(bean) + + " failure."); + } + } + } + + public Object initSingletonFields(IBeanContext context, Map params) { + if (singletonInstance != null) { + writeBeanFields(singletonInstance, context, params); + } + + return singletonInstance; + } + + public Class getBeanType() { + return clazz; + } + + public void newSingletonInstance() throws IllegalAccessException, InstantiationException { + if (isSingleton()) { + this.singletonInstance = clazz.newInstance(); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/FieldAccess.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/FieldAccess.java new file mode 100644 index 0000000..3fb7791 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/FieldAccess.java @@ -0,0 +1,40 @@ +package cc.smtweb.framework.core.mvc.controller.access; + +import cc.smtweb.framework.core.exception.BindBeanException; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; + +import java.lang.reflect.Field; +import java.util.Map; + +public abstract class FieldAccess { + protected final Field field; + + public FieldAccess(Field field) { + this.field = field; + } + + protected abstract Object getFieldValue(Object instance, IBeanContext context, Map params); + + public boolean writeValue(Object instance, IBeanContext context, Map params) { + Object bean = getFieldValue(instance, context, params); + + if (bean != null) { + field.setAccessible(true); + + try { + field.set(instance, bean); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new BindBeanException("not set " + getFullName(instance) + " value", e); + } + + return true; + } + + return false; + } + + public String getFullName(Object instance) { + return instance.getClass().getName() + "." + field.getName(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/IBeanAccess.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/IBeanAccess.java new file mode 100644 index 0000000..17c8fc3 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/IBeanAccess.java @@ -0,0 +1,5 @@ +package cc.smtweb.framework.core.mvc.controller.access; + +public interface IBeanAccess { + Object getSingletonInstance(); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/IMethodAccess.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/IMethodAccess.java new file mode 100644 index 0000000..b070db5 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/IMethodAccess.java @@ -0,0 +1,50 @@ +package cc.smtweb.framework.core.mvc.controller.access; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * API方法访问接口 + * @author xkliu + */ +public interface IMethodAccess { + // 方法返回值Java类型 + Class getReturnType(); + + // @SwBody 注解Java类型 + Class getBodyType(); + + // @SwAttr 注解Java类型 + Class getAttrType(String paramName); + + /** + * 执行反射的API函数,一般时定时器等无参数环境调用 + * @param context 对象上下文 + * @return API函数返回值 + */ + Object invoke(IBeanContext context); + + /** + * 执行类方法,实现API调用 + * @param context 上下文对象,包括springboot服务类,@SwBean类 + * @param params url请求参数map + * @param body post请求的内容 + * @param request HTTP请求对象 + * @return 应答对象,一般是返回R转换为json返回 + */ + Object invoke(IBeanContext context, Map params, String body, HttpServletRequest request); + + /** + * 输出错误信息用,方法全名 + * @return 方法全名 + */ + String fullName(); + + /** + * API权限 + * @return 权限串 + */ + String getPerm(); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/MethodAccess.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/MethodAccess.java new file mode 100644 index 0000000..962dc7b --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/MethodAccess.java @@ -0,0 +1,112 @@ +package cc.smtweb.framework.core.mvc.controller.access; + +import cc.smtweb.framework.core.SwException; +import cc.smtweb.framework.core.exception.BindBeanException; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.binder.ParamEditor; +import lombok.Getter; +import org.apache.commons.lang3.ObjectUtils; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; + +/** + * API方法执行 + * @author xkliu + */ +public class MethodAccess implements IMethodAccess { + private final ControllerAccess controllerAccess; + private final Method method; + @Getter + private final String perm; + private final MethodParamAccess[] paramBinds; + + public MethodAccess(ControllerAccess controllerAccess, Method method, String perm, MethodParamAccess[] paramBinds) { + this.controllerAccess = controllerAccess; + this.method = method; + this.perm = perm; + this.paramBinds = paramBinds; + } + + @Override + public Class getReturnType() { + return this.method.getReturnType(); + } + + @Override + public Class getBodyType() { + for (MethodParamAccess methodParamAccess: paramBinds) { + if (methodParamAccess.getBindType() == ParamEditor.TYPE_BODY) { + return methodParamAccess.getParamType(); + } + } + + return null; + } + + @Override + public Class getAttrType(String paramName) { + for (MethodParamAccess methodParamAccess: paramBinds) { + if (methodParamAccess.getBindType() == ParamEditor.TYPE_ATTR && paramName.equals(methodParamAccess.getParamName())) { + return methodParamAccess.getParamType(); + } + } + + return null; + } + + @Override + public Object invoke(IBeanContext context) { + return invoke(context, null, null, null); + } + + @Override + public Object invoke(IBeanContext context, Map params, String body, HttpServletRequest request) { + // 通过请求数据转换函数参数值 + Object[] args = null; + + if (!ObjectUtils.isEmpty(paramBinds)) { + args = new Object[paramBinds.length]; + for (int i = 0; i < paramBinds.length; i++) { + MethodParamAccess param = paramBinds[i]; + try { + args[i] = param.getParamValue(context, params, body, request); + } catch (Exception e) { + throw new BindBeanException(String.format("%s error value: %s", param.getParamName(), param.toString()), e); + } + } + } + + // 获取服务实例,然后执行方法 + try { + Object instance = controllerAccess.getOrNewInstance(context, params); + return method.invoke(instance, args); + } catch (IllegalAccessException | InstantiationException e) { + throw new SwException(e); + } catch (InvocationTargetException e) { + throw new SwException(e.getCause()); + } + } + + @Override + public String fullName() { + return controllerAccess.getFullName() + "." + method.getName(); + } + + public Class findParam(Class clazz) { + for (MethodParamAccess param: paramBinds) { + Class paramType = param.getParamType(); + if (clazz.equals(paramType)) { + return paramType; + } + } + + return null; + } + + public String controllerFullName() { + return this.controllerAccess.getFullName(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/MethodParamAccess.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/MethodParamAccess.java new file mode 100644 index 0000000..cf66b21 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/MethodParamAccess.java @@ -0,0 +1,43 @@ +package cc.smtweb.framework.core.mvc.controller.access; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import lombok.Getter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +/** + * API方法参数访问器 + * @author xkliu + */ +@Getter +public class MethodParamAccess { + private int bindType; + private final IEditor editor; + private final String paramName; + private final Class collectionType; + private final Class paramType; + + public MethodParamAccess(IEditor editor, String paramName, Class paramType, int bindType, Class collectionType) { + this.editor = editor; + this.paramName = paramName; + this.paramType = paramType; + this.bindType = bindType; + this.collectionType = collectionType; + } + + public Object getParamValue(IBeanContext context, Map params, String body, HttpServletRequest request) { + if (editor != null) { + return editor.getParamValue(paramName, paramType, context, params, body, request); + } + + return null; + } + + @Override + public String toString() { + return paramName + ":" + paramType.getSimpleName() + "-" + editor.getClass().getSimpleName(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SchedulerMethodAccess.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SchedulerMethodAccess.java new file mode 100644 index 0000000..6104a6a --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SchedulerMethodAccess.java @@ -0,0 +1,21 @@ +package cc.smtweb.framework.core.mvc.controller.access; + +import cc.smtweb.framework.core.annotation.SwScheduling; +import com.serotonin.timer.CronTimerTrigger; +import com.serotonin.timer.TimerTrigger; +import lombok.Getter; + +import java.text.ParseException; + +@Getter +public class SchedulerMethodAccess { + private final MethodAccess methodAccess; + private final SwScheduling swScheduling; + private final TimerTrigger timerTrigger; + + public SchedulerMethodAccess(MethodAccess methodAccess, SwScheduling swScheduling) throws ParseException { + this.timerTrigger = new CronTimerTrigger(swScheduling.value()); + this.methodAccess = methodAccess; + this.swScheduling = swScheduling; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SingletonFieldAccess.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SingletonFieldAccess.java new file mode 100644 index 0000000..88a5d7f --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/access/SingletonFieldAccess.java @@ -0,0 +1,20 @@ +package cc.smtweb.framework.core.mvc.controller.access; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; + +import java.lang.reflect.Field; +import java.util.Map; + +public class SingletonFieldAccess extends FieldAccess { + private final Object fieldValue; + + public SingletonFieldAccess(Object fieldValue, Field field) { + super(field); + this.fieldValue = fieldValue; + } + + @Override + protected Object getFieldValue(Object instance, IBeanContext context, Map params) { + return fieldValue; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/BeanContext.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/BeanContext.java new file mode 100644 index 0000000..7510494 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/BeanContext.java @@ -0,0 +1,47 @@ +package cc.smtweb.framework.core.mvc.controller.binder; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; + +import java.util.Map; + +public class BeanContext implements IBeanContext { + private final Map, Object> beanMap; + private final BeanFactory applicationContext; + + public BeanContext(Map, Object> beanMap, BeanFactory applicationContext) { + this.beanMap = beanMap; + this.applicationContext = applicationContext; + } + + @Override + public T getBean(String name, Class type) throws BeansException { + Object controllerAccess = beanMap.get(type); + + if (controllerAccess == null) { + return applicationContext.getBean(name, type); + } else { + return (T)controllerAccess; + } + } + + @Override + public T getBean(Class type) throws BeansException { + Object controllerAccess = beanMap.get(type); + + if (controllerAccess != null) { + return (T) controllerAccess; + } + + controllerAccess = applicationContext.getBean(type); + if (controllerAccess != null) { + return (T) controllerAccess; + } + + return null; + } + + public void init() { + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/CacheEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/CacheEditor.java new file mode 100644 index 0000000..cc67d88 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/CacheEditor.java @@ -0,0 +1,26 @@ +package cc.smtweb.framework.core.mvc.controller.binder; + +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +public class CacheEditor implements IEditor { + private CacheManager cacheManager; + + public CacheEditor(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + if (StringUtils.isNotBlank(paramName)) { + return cacheManager.getCache(paramName); + } else { + return cacheManager.getCache(paramType.getSimpleName()); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/ParamEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/ParamEditor.java new file mode 100644 index 0000000..fcd56e3 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/ParamEditor.java @@ -0,0 +1,40 @@ +package cc.smtweb.framework.core.mvc.controller.binder; + +import cc.smtweb.framework.core.mvc.controller.IEditor; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +public class ParamEditor { + public static final int TYPE_UNKNOWN = 0; + public static final int TYPE_FIELD = 1; + public static final int TYPE_PARAM = 2; + public static final int TYPE_BODY = 3; + public static final int TYPE_PATH = 4; + public static final int TYPE_HEADER = 5; + public static final int TYPE_COOKIE = 6; + public static final int TYPE_ATTR = 7; + + private final Map, IEditor> editors = new HashMap<>(); + + @Setter + private IEditor defaultEditor; + + ParamEditor(IEditor defaultEditor) { + this.defaultEditor = defaultEditor; + } + + public void add(Class clazz, IEditor editor) { + editors.put(clazz, editor); + } + + public IEditor find(Class paramType) { + IEditor result = editors.get(paramType); + if (result == null) { + result = defaultEditor; + } + + return result; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/WebDataBinder.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/WebDataBinder.java new file mode 100644 index 0000000..f46ec50 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/WebDataBinder.java @@ -0,0 +1,225 @@ +package cc.smtweb.framework.core.mvc.controller.binder; + +import cc.smtweb.framework.core.SwIpAddr; +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.cache.AbstractCache; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.framework.core.mvc.controller.binder.attr.BeanAttrEditor; +import cc.smtweb.framework.core.mvc.controller.binder.bean.*; +import cc.smtweb.framework.core.mvc.controller.binder.body.BeanBodyEditor; +import cc.smtweb.framework.core.mvc.controller.binder.body.StringBodyEditor; +import cc.smtweb.framework.core.mvc.controller.binder.body.SwMapBodyEditor; +import cc.smtweb.framework.core.mvc.controller.binder.param.*; +import cc.smtweb.framework.core.mvc.controller.binder.path.PathParamEditor; +import cc.smtweb.framework.core.mvc.scheduler.SchedulerPoint; +import cc.smtweb.framework.core.session.UserSession; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import java.sql.Timestamp; + +@Slf4j +public class WebDataBinder { + private ParamEditor paramEditor = new ParamEditor(new BeanEditor()); + private ParamEditor bodyEditor = new ParamEditor(new BeanBodyEditor()); + private ParamEditor pathEditor = new ParamEditor(new PathParamEditor()); + private ParamEditor headerEditor = new ParamEditor(new NullEditor()); + private ParamEditor attributeEditor = new ParamEditor(new BeanAttrEditor()); + private IEditor cacheEditor; + + public WebDataBinder(IEditor cacheEditor) { + this.cacheEditor = cacheEditor; + initParamEditor(); + initBodyEditor(); + initHeaderEditor(); + } + +// public void addParamEditor(Class type, IEditor editor) { +// paramEditor.add(String.class, editor); +// } + + public IEditor findEditor(Class paramType, int bindType) { + IEditor result; + + switch (bindType) { + case ParamEditor.TYPE_UNKNOWN: + case ParamEditor.TYPE_PARAM: + case ParamEditor.TYPE_FIELD: + if (AbstractCache.class.isAssignableFrom(paramType)) { + result = cacheEditor; + } else { + result = paramEditor.find(paramType); + } + break; + case ParamEditor.TYPE_BODY: + // @SwBody + result = bodyEditor.find(paramType); + break; + case ParamEditor.TYPE_PATH: + result = pathEditor.find(paramType); + break; + case ParamEditor.TYPE_HEADER: + result = headerEditor.find(paramType); + break; + case ParamEditor.TYPE_ATTR: + result = attributeEditor.find(paramType); + break; + default: + throw new IllegalStateException("Unexpected type value: " + bindType); + } + + return result; + } + + private void initParamEditor() { + TimestampEditor timestampEditor = new TimestampEditor(ParamEditor.TYPE_PARAM); + paramEditor.add(String.class, new StringEditor(ParamEditor.TYPE_PARAM)); + paramEditor.add(Timestamp.class, timestampEditor); + paramEditor.add(java.util.Date.class, timestampEditor); + paramEditor.add(java.sql.Date.class, new DateEditor(ParamEditor.TYPE_PARAM)); + paramEditor.add(java.sql.Time.class, new TimeEditor(ParamEditor.TYPE_PARAM)); + + paramEditor.add(Byte.TYPE, new ByteEditor(ParamEditor.TYPE_PARAM,true)); + paramEditor.add(Long.TYPE, new LongEditor(ParamEditor.TYPE_PARAM, true)); + paramEditor.add(Double.TYPE, new DoubleEditor(ParamEditor.TYPE_PARAM, true)); + paramEditor.add(Float.TYPE, new FloatEditor(ParamEditor.TYPE_PARAM, true)); + paramEditor.add(Short.TYPE, new ShortEditor(ParamEditor.TYPE_PARAM, true)); + paramEditor.add(Integer.TYPE, new IntegerEditor(ParamEditor.TYPE_PARAM, true)); + paramEditor.add(Boolean.TYPE, new BooleanEditor(ParamEditor.TYPE_PARAM, true)); + paramEditor.add(Character.TYPE,new CharEditor(ParamEditor.TYPE_PARAM, true)); + + paramEditor.add(Byte.class, new ByteEditor(ParamEditor.TYPE_PARAM, false)); + paramEditor.add(Long.class, new LongEditor(ParamEditor.TYPE_PARAM, false)); + paramEditor.add(Double.class, new DoubleEditor(ParamEditor.TYPE_PARAM,false)); + paramEditor.add(Float.class, new FloatEditor(ParamEditor.TYPE_PARAM,false)); + paramEditor.add(Short.class, new ShortEditor(ParamEditor.TYPE_PARAM,false)); + paramEditor.add(Integer.class, new IntegerEditor(ParamEditor.TYPE_PARAM,false)); + paramEditor.add(Boolean.class, new BooleanEditor(ParamEditor.TYPE_PARAM,false)); + + paramEditor.add(HttpServletRequest.class, new HttpServletRequestEditor()); +// paramEditor.add(HttpServletResponse.class, new HttpServletResponseEditor()); + paramEditor.add(UserSession.class, new UserSessionEditor()); + paramEditor.add(SwIpAddr.class, new SwIpAddrEditor()); + paramEditor.add(SchedulerPoint.class, new BeanTypeEditor()); + } + + private void initBodyEditor() { + TimestampEditor timestampEditor = new TimestampEditor(ParamEditor.TYPE_BODY); + bodyEditor.add(String.class, new StringEditor(ParamEditor.TYPE_BODY)); + bodyEditor.add(Timestamp.class, timestampEditor); + bodyEditor.add(java.util.Date.class, timestampEditor); + bodyEditor.add(java.sql.Date.class, new DateEditor(ParamEditor.TYPE_BODY)); + bodyEditor.add(java.sql.Time.class, new TimeEditor(ParamEditor.TYPE_BODY)); + + bodyEditor.add(Byte.TYPE, new ByteEditor(ParamEditor.TYPE_BODY,true)); + bodyEditor.add(Long.TYPE, new LongEditor(ParamEditor.TYPE_BODY, true)); + bodyEditor.add(Double.TYPE, new DoubleEditor(ParamEditor.TYPE_BODY, true)); + bodyEditor.add(Float.TYPE, new FloatEditor(ParamEditor.TYPE_BODY, true)); + bodyEditor.add(Short.TYPE, new ShortEditor(ParamEditor.TYPE_BODY, true)); + bodyEditor.add(Integer.TYPE, new IntegerEditor(ParamEditor.TYPE_BODY, true)); + bodyEditor.add(Boolean.TYPE, new BooleanEditor(ParamEditor.TYPE_BODY, true)); + bodyEditor.add(Character.TYPE,new CharEditor(ParamEditor.TYPE_BODY, true)); + + bodyEditor.add(Byte.class, new ByteEditor(ParamEditor.TYPE_BODY, false)); + bodyEditor.add(Long.class, new LongEditor(ParamEditor.TYPE_BODY, false)); + bodyEditor.add(Double.class, new DoubleEditor(ParamEditor.TYPE_BODY,false)); + bodyEditor.add(Float.class, new FloatEditor(ParamEditor.TYPE_BODY,false)); + bodyEditor.add(Short.class, new ShortEditor(ParamEditor.TYPE_BODY,false)); + bodyEditor.add(Integer.class, new IntegerEditor(ParamEditor.TYPE_BODY,false)); + bodyEditor.add(Boolean.class, new BooleanEditor(ParamEditor.TYPE_BODY,false)); + + bodyEditor.add(String.class, new StringBodyEditor()); + bodyEditor.add(SwMap.class, new SwMapBodyEditor()); + } + + private void initHeaderEditor() { + TimestampEditor timestampEditor = new TimestampEditor(ParamEditor.TYPE_HEADER); + headerEditor.add(String.class, new StringEditor(ParamEditor.TYPE_HEADER)); + headerEditor.add(Timestamp.class, timestampEditor); + headerEditor.add(java.util.Date.class, timestampEditor); + headerEditor.add(java.sql.Date.class, new DateEditor(ParamEditor.TYPE_HEADER)); + headerEditor.add(java.sql.Time.class, new TimeEditor(ParamEditor.TYPE_HEADER)); + + headerEditor.add(Byte.TYPE, new ByteEditor(ParamEditor.TYPE_HEADER,true)); + headerEditor.add(Long.TYPE, new LongEditor(ParamEditor.TYPE_HEADER, true)); + headerEditor.add(Double.TYPE, new DoubleEditor(ParamEditor.TYPE_HEADER, true)); + headerEditor.add(Float.TYPE, new FloatEditor(ParamEditor.TYPE_HEADER, true)); + headerEditor.add(Short.TYPE, new ShortEditor(ParamEditor.TYPE_HEADER, true)); + headerEditor.add(Integer.TYPE, new IntegerEditor(ParamEditor.TYPE_HEADER, true)); + headerEditor.add(Boolean.TYPE, new BooleanEditor(ParamEditor.TYPE_HEADER, true)); + headerEditor.add(Character.TYPE,new CharEditor(ParamEditor.TYPE_HEADER, true)); + + headerEditor.add(Byte.class, new ByteEditor(ParamEditor.TYPE_HEADER, false)); + headerEditor.add(Long.class, new LongEditor(ParamEditor.TYPE_HEADER, false)); + headerEditor.add(Double.class, new DoubleEditor(ParamEditor.TYPE_HEADER,false)); + headerEditor.add(Float.class, new FloatEditor(ParamEditor.TYPE_HEADER,false)); + headerEditor.add(Short.class, new ShortEditor(ParamEditor.TYPE_HEADER,false)); + headerEditor.add(Integer.class, new IntegerEditor(ParamEditor.TYPE_HEADER,false)); + headerEditor.add(Boolean.class, new BooleanEditor(ParamEditor.TYPE_HEADER,false)); + } + + public void bindParam(Class type, IEditor editor) { + paramEditor.add(type, editor); + } + + public void bindBody(Class type, IEditor editor) { + bodyEditor.add(type, editor); + } + + /** + * 绑定自定义参数读取器 + * @param bindType 绑定类型ParamEditor.TYPE_XXX + * @param type 参数类型 + * @param editor 参数读取器 + */ + public void bind(int bindType, Class type, IEditor editor) { + switch (bindType) { + case ParamEditor.TYPE_PARAM: + paramEditor.add(type, editor); + break; + case ParamEditor.TYPE_BODY: + // @SwBody + bodyEditor.add(type, editor); + break; + case ParamEditor.TYPE_PATH: + pathEditor.add(type, editor); + break; + case ParamEditor.TYPE_HEADER: + headerEditor.add(type, editor);; + break; + case ParamEditor.TYPE_ATTR: + attributeEditor.add(type, editor); + break; + default: + throw new IllegalStateException("Unexpected type value: " + bindType); + } + } + + /** + * 绑定默认自定义参数读取器 + * @param bindType 绑定类型ParamEditor.TYPE_XXX + * @param editor 默认参数读取器 + */ + public void bindDefault(int bindType, IEditor editor) { + switch (bindType) { + case ParamEditor.TYPE_PARAM: + paramEditor.setDefaultEditor(editor); + break; + case ParamEditor.TYPE_BODY: + // @SwBody + bodyEditor.setDefaultEditor(editor); + break; + case ParamEditor.TYPE_PATH: + pathEditor.setDefaultEditor(editor); + break; + case ParamEditor.TYPE_HEADER: + headerEditor.setDefaultEditor(editor);; + break; + case ParamEditor.TYPE_ATTR: + attributeEditor.setDefaultEditor(editor); + break; + default: + throw new IllegalStateException("Unexpected type value: " + bindType); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/AbstractAttrEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/AbstractAttrEditor.java new file mode 100644 index 0000000..e28d213 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/AbstractAttrEditor.java @@ -0,0 +1,20 @@ +package cc.smtweb.framework.core.mvc.controller.binder.attr; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * API函数@SwBody参数的转换处理,POST请求直接转换Json内容为Bean对象,GET请求转换Map请求参数为Bean对 + * @author xkliu + */ +public abstract class AbstractAttrEditor implements IEditor { + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + return getValue(paramName, paramType, request); + } + + public abstract Object getValue(String paramName, Class paramType, HttpServletRequest request); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/BeanAttrEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/BeanAttrEditor.java new file mode 100644 index 0000000..fc6058f --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/attr/BeanAttrEditor.java @@ -0,0 +1,14 @@ +package cc.smtweb.framework.core.mvc.controller.binder.attr; + +import javax.servlet.http.HttpServletRequest; + +/** + * API函数@SwBody参数的转换处理,POST请求直接转换Json内容为Bean对象,GET请求转换Map请求参数为Bean对 + * @author xkliu + */ +public class BeanAttrEditor extends AbstractAttrEditor { + @Override + public Object getValue(String paramName, Class paramType, HttpServletRequest request) { + return request.getAttribute(paramName); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/AbstractContextEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/AbstractContextEditor.java new file mode 100644 index 0000000..92fa9a3 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/AbstractContextEditor.java @@ -0,0 +1,17 @@ +package cc.smtweb.framework.core.mvc.controller.binder.bean; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public abstract class AbstractContextEditor implements IEditor { + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + return getValue(paramName, paramType, context); + } + + protected abstract Object getValue(String paramName, Class paramType, IBeanContext context); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/BeanEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/BeanEditor.java new file mode 100644 index 0000000..399673a --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/BeanEditor.java @@ -0,0 +1,15 @@ +package cc.smtweb.framework.core.mvc.controller.binder.bean; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import org.apache.commons.lang3.StringUtils; + +public class BeanEditor extends AbstractContextEditor { + @Override + public Object getValue(String paramName, Class paramType, IBeanContext context) { + if (StringUtils.isBlank(paramName)) { + return context.getBean(paramType); + } else { + return context.getBean(paramName, paramType); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/HttpServletRequestEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/HttpServletRequestEditor.java new file mode 100644 index 0000000..3cdbfa2 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/HttpServletRequestEditor.java @@ -0,0 +1,12 @@ +package cc.smtweb.framework.core.mvc.controller.binder.bean; + +import cc.smtweb.framework.core.mvc.controller.binder.param.AbstractRequestEditor; + +import javax.servlet.http.HttpServletRequest; + +public class HttpServletRequestEditor extends AbstractRequestEditor { + @Override + public Object getValue(HttpServletRequest request) { + return request; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/NullEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/NullEditor.java new file mode 100644 index 0000000..3b54cbd --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/NullEditor.java @@ -0,0 +1,15 @@ +package cc.smtweb.framework.core.mvc.controller.binder.bean; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public class NullEditor implements IEditor { + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + return null; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/SwIpAddrEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/SwIpAddrEditor.java new file mode 100644 index 0000000..b838308 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/SwIpAddrEditor.java @@ -0,0 +1,38 @@ +package cc.smtweb.framework.core.mvc.controller.binder.bean; + +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.framework.core.mvc.controller.binder.param.AbstractRequestEditor; +import cc.smtweb.framework.core.session.SessionUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.BeanFactory; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/*** + * 获取客户端IP地址;这里通过了Nginx获取;X-Real-IP + */ +public class SwIpAddrEditor extends AbstractRequestEditor { + @Override + public Object getValue(HttpServletRequest request) { + String ip = request.getHeader("X-Real-IP"); + + if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Forwarded-For"); + } + + if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + + if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + + if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + return ip; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/UserSessionEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/UserSessionEditor.java new file mode 100644 index 0000000..3478ebb --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/bean/UserSessionEditor.java @@ -0,0 +1,13 @@ +package cc.smtweb.framework.core.mvc.controller.binder.bean; + +import cc.smtweb.framework.core.mvc.controller.binder.param.AbstractRequestEditor; +import cc.smtweb.framework.core.session.SessionUtil; + +import javax.servlet.http.HttpServletRequest; + +public class UserSessionEditor extends AbstractRequestEditor { + @Override + public Object getValue(HttpServletRequest request) { + return SessionUtil.getSession(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanBodyEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanBodyEditor.java new file mode 100644 index 0000000..968c0cc --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanBodyEditor.java @@ -0,0 +1,82 @@ +package cc.smtweb.framework.core.mvc.controller.binder.body; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.exception.BindBeanException; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.framework.core.util.JsonUtil; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * API函数@SwBody参数的转换处理,POST请求直接转换Json内容为Bean对象,GET请求转换Map请求参数为Bean对 + * @author xkliu + */ +public class BeanBodyEditor implements IEditor { + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + if (paramName != null) { + Object map = request.getAttribute(BODY_MAP); + if (map != null) { + return getFieldValue((SwMap) map, paramName, paramType); + } + } + + if (body != null) { + return getPostValue(paramName, paramType, body, request); + } else { + return getGetValue(paramName, paramType, params); + } + } + + private Object getGetValue(String paramName, Class paramType, Map params) { + if (paramName == null) { + return JsonUtil.parse(params, paramType); + } + + // Json字符进行类型转化 + Object result = params.get(paramName); + if (result == null) { + return null; + } + + if (paramType.isAssignableFrom(result.getClass())) { + return result; + } + + throw new BindBeanException("传入的 @SwBody(" + paramName + ") " + paramType.getName() + "类型不一致! " + result.getClass().getName()); + } + + private Object getPostValue(String paramName, Class paramType, String body, HttpServletRequest request) { + if (body == null || StringUtils.isBlank(body)) { + return null; + } + + if (paramName == null) { + return JsonUtil.parse(body, paramType); + } else { + SwMap map = JsonUtil.parse(body, SwMap.class); + if (map != null) { + request.setAttribute(BODY_MAP, map); + return getFieldValue(map, paramName, paramType); + } + + return null; + } + } + + private Object getFieldValue(SwMap result, String paramName, Class paramType) { + Object value = result.get(paramName); + if (value == null) { + return null; + } + + if (value instanceof Map) { + return BeanUtil.toBean((Map)value, paramType); + } + + throw new BindBeanException("传入的 @SwBody(" + paramName + ") 值非Object类型! " + value.getClass().getName()); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanUtil.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanUtil.java new file mode 100644 index 0000000..841da79 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/BeanUtil.java @@ -0,0 +1,160 @@ +package cc.smtweb.framework.core.mvc.controller.binder.body; + +import cc.smtweb.framework.core.SwMap; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.sql.Timestamp; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class BeanUtil { + private static final Set> simpleType = new HashSet<>(); + + static { + simpleType.add(String.class); + simpleType.add(Timestamp.class); + simpleType.add(java.util.Date.class); + simpleType.add(java.sql.Date.class); + simpleType.add(java.sql.Time.class); + + simpleType.add(Byte.TYPE); + simpleType.add(Long.TYPE); + simpleType.add(Double.TYPE); + simpleType.add(Float.TYPE); + simpleType.add(Short.TYPE); + simpleType.add(Integer.TYPE); + simpleType.add(Boolean.TYPE); + simpleType.add(Character.TYPE); + + simpleType.add(Byte.class); + simpleType.add(Long.class); + simpleType.add(Double.class); + simpleType.add(Float.class); + simpleType.add(Short.class); + simpleType.add(Integer.class); + simpleType.add(Boolean.class); + } + + private BeanUtil() {} + + public static Object toBean(Map source, Class clazz) { + Object target = null; + try { + target = clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new FatalBeanException( + "Could not copy property '" + clazz.getName() + "' from source to target", e); + } + mapToBean(source, target); + return target; + } + + private static void mapToBean(Map source, Object target) { + mapToBean(source, target, true); + } + + private static void mapToBean(Map source, Object target, boolean root) throws BeansException { + Assert.notNull(source, "Source must not be null"); + Assert.notNull(target, "Target must not be null"); + + Class actualEditable = target.getClass(); +// if (editable != null) { +// if (!editable.isInstance(target)) { +// throw new IllegalArgumentException("Target class [" + target.getClass().getName() + +// "] not assignable to Editable class [" + editable.getName() + "]"); +// } +// actualEditable = editable; +// } + PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable); + + for (PropertyDescriptor targetPd : targetPds) { + Method writeMethod = targetPd.getWriteMethod(); + Object value = source.get(targetPd.getName()); + + if (value == null) { + Class parameterType = writeMethod.getParameterTypes()[0]; + + if (ClassUtils.isAssignable(parameterType, value.getClass())) { + writeBeanValue(target, targetPd, writeMethod, value); + } else { + if (root && value instanceof Map) { + Object propValue; + try { + propValue = parameterType.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new FatalBeanException( + "Could not copy property '" + targetPd.getName() + "' from source to target", e); + } + + mapToBean((Map) value, propValue, false); + writeBeanValue(target, targetPd, writeMethod, propValue); + } + } + } + } + } + + private static void writeBeanValue(Object target, PropertyDescriptor targetPd, Method writeMethod, Object value) { + try { + if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { + writeMethod.setAccessible(true); + } + writeMethod.invoke(target, value); + } catch (Throwable ex) { + throw new FatalBeanException( + "Could not copy property '" + targetPd.getName() + "' from source to target", ex); + } + } + + public static void beanToMap(Object source, SwMap target) throws BeansException { + beanToMap(source, target, true); + } + + public static void beanToMap(Object source, SwMap target, boolean root) throws BeansException { + Assert.notNull(source, "Source must not be null"); + Assert.notNull(target, "Target must not be null"); + PropertyDescriptor[] sourcePds = BeanUtils.getPropertyDescriptors(source.getClass()); + + for (PropertyDescriptor sourcePd: sourcePds) { +// PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getKey()); +// if (sourcePd != null) { + Method readMethod = sourcePd.getReadMethod(); + if (readMethod != null) { + try { + if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { + readMethod.setAccessible(true); + } + Object value = readMethod.invoke(source); + target.put(sourcePd.getName(), root ? toMapValue(value) : value); + } + catch (Throwable ex) { + throw new FatalBeanException( + "Could not copy property '" + sourcePd.getName() + "' from source to target", ex); + } + } +// } + } + } + + private static Object toMapValue(Object value) { + if (value != null && isBeanType(value.getClass())) { + SwMap mapValue = new SwMap(); + beanToMap(value, mapValue, false); + return mapValue; + } + + return value; + } + + private static boolean isBeanType(Class type) { + return !simpleType.contains(type); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/StringBodyEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/StringBodyEditor.java new file mode 100644 index 0000000..9de8a32 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/StringBodyEditor.java @@ -0,0 +1,18 @@ +package cc.smtweb.framework.core.mvc.controller.binder.body; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * API函数@SwBody参数的转换处理,直接获取POST请求文本内容 + * @author xkliu + */ +public class StringBodyEditor implements IEditor { + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + return body; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/SwMapBodyEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/SwMapBodyEditor.java new file mode 100644 index 0000000..cc88a8a --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/body/SwMapBodyEditor.java @@ -0,0 +1,69 @@ +package cc.smtweb.framework.core.mvc.controller.binder.body; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.exception.BindBeanException; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.framework.core.util.JsonUtil; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * API函数@SwBody参数的转换处理,POST请求直接转换Json内容为Bean对象,GET请求转换Map请求参数为Bean对 + * @author xkliu + */ +public class SwMapBodyEditor implements IEditor { + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + Object map = request.getAttribute(BODY_MAP); + if (map != null) { + return getFieldValue((SwMap)map, paramName); + } + + if (body != null) { + return getPostValue(paramName, paramType, body, request); + } else { + return getGetValue(paramName, paramType, params, request); + } + } + + private Object getGetValue(String paramName, Class paramType, Map params, HttpServletRequest request) { + SwMap result = JsonUtil.parse(params, SwMap.class); + request.setAttribute(BODY_MAP, result); + return getFieldValue(result, paramName); + } + + private Object getPostValue(String paramName, Class paramType, String body, HttpServletRequest request) { + if (body == null || StringUtils.isBlank(body)) { + return null; + } + + SwMap result = JsonUtil.parse(body, SwMap.class); + request.setAttribute(BODY_MAP, result); + return getFieldValue(result, paramName); + } + + private Object getFieldValue(SwMap result, String paramName) { + if (paramName != null) { + Object value = result.get(paramName); + if (value == null) { + return null; + } + + if (value instanceof Map) { + SwMap map = new SwMap(); + ((Map) value).forEach((k, v) -> { + map.put(k.toString(), v); + }); + return map; + } + + throw new BindBeanException("传入的 @SwBody(" + paramName + ") 值非MAP类型! " + value.getClass().getName()); + } + + return result; + } + +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/AbstractNumberEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/AbstractNumberEditor.java new file mode 100644 index 0000000..24d2feb --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/AbstractNumberEditor.java @@ -0,0 +1,46 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +import org.apache.commons.lang3.StringUtils; + + +/** + * 字符串到Number类型的转换 + * + * @author kevin + * + */ +public abstract class AbstractNumberEditor extends AbstractParameterEditor { + protected boolean automicType; + + public AbstractNumberEditor(int paramEditType, boolean automicType) { + super(paramEditType); + this.automicType = automicType; + } + + @Override + protected Object parseText(Object value) { + Object result = null; + + if (value == null) { + return automicType ? defaultValue() : null; + } + + if (value instanceof Number) { + return convert((Number)value); + } + + String text = value.toString(); + + if (StringUtils.isNotBlank(text)) { + result = getValue(text); + } else { + result = automicType ? defaultValue() : null; + } + + return result; + } + + protected abstract Object convert(Number value); + protected abstract Object getValue(String text); + protected abstract Object defaultValue(); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/AbstractParameterEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/AbstractParameterEditor.java new file mode 100644 index 0000000..217a502 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/AbstractParameterEditor.java @@ -0,0 +1,109 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.framework.core.mvc.controller.binder.ParamEditor; +import cc.smtweb.framework.core.util.JsonUtil; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; + +/** + * 字符串参数到对象的转换; + * @author xkliu + */ +public abstract class AbstractParameterEditor implements IEditor { + private static final IEditorValue paramValue = (paramName, params, body, request) -> params.get(paramName); + private static final IEditorValue headerValue = (paramName, params, body, request) -> request.getHeader(paramName); + private static final IEditorValue bodyValue = (paramName, params, body, request) -> { + if (body == null) { + if (paramName != null) { + return params.get(paramName); + } + } else { + SwMap map = (SwMap) request.getAttribute(BODY_MAP); + if (map != null) { + return map.get(paramName); + } + + map = JsonUtil.parse(body, SwMap.class); + if (map != null) { + request.setAttribute(BODY_MAP, map); + return map.get(paramName); + } + } + + return null; + }; + + private final IEditorValue editorValue; + +// AbstractParameterEditor() { +// editorValue = paramValue; +// } + + AbstractParameterEditor(int paramEditorType) { + switch (paramEditorType) { + case ParamEditor.TYPE_HEADER: + editorValue = headerValue; + break; + case ParamEditor.TYPE_PARAM: + editorValue = paramValue; + break; + case ParamEditor.TYPE_BODY: + editorValue = bodyValue; + break; + default: + throw new UnsupportedOperationException("not implement type: " + paramEditorType); + } + } + + protected static Date parseDateTime(String text) throws ParseException { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + df.setLenient(false); + + return df.parse(text); + } + + protected static Date parseShortDateTime(String text) throws ParseException { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + df.setLenient(false); + + return df.parse(text); + } + + protected static Date parseDate(String text) throws ParseException { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + df.setLenient(false); + + return df.parse(text); + } + + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + if (StringUtils.isBlank(paramName)) { + return null; + } + + return parseText(editorValue.getValue(paramName, params, body, request)); + } + + protected abstract Object parseText(Object text); +} + +interface IEditorValue { + /** + * 获取参数值 + * @param paramName 参数名 + * @param params HTTP请求参数集合 + * @param request HTTP请求对象 + * @return 参数值 + */ + Object getValue(String paramName, Map params, String body, HttpServletRequest request); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/AbstractRequestEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/AbstractRequestEditor.java new file mode 100644 index 0000000..758d1b0 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/AbstractRequestEditor.java @@ -0,0 +1,17 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public abstract class AbstractRequestEditor implements IEditor { + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + return getValue(request); + } + + protected abstract Object getValue(HttpServletRequest request); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/BeanTypeEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/BeanTypeEditor.java new file mode 100644 index 0000000..dcb2721 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/BeanTypeEditor.java @@ -0,0 +1,15 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public class BeanTypeEditor implements IEditor { + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + return params.get(paramType.getName()); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/BooleanEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/BooleanEditor.java new file mode 100644 index 0000000..30572c0 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/BooleanEditor.java @@ -0,0 +1,30 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +import org.apache.commons.lang3.StringUtils; + +public class BooleanEditor extends AbstractParameterEditor { + private final boolean automicType; + + public BooleanEditor(int header, boolean automicType) { + super(header); + this.automicType = automicType; + } + + @Override + public Object parseText(Object text) { + if (text == null) { + return automicType ? Boolean.FALSE : null; + } + + if (text instanceof Boolean) { + return text; + } + + String s = text.toString(); + if (StringUtils.isBlank(s)) { + return automicType ? Boolean.FALSE : null; + } + + return Boolean.valueOf(s); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/ByteEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/ByteEditor.java new file mode 100644 index 0000000..fd3a27e --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/ByteEditor.java @@ -0,0 +1,24 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +public class ByteEditor extends AbstractNumberEditor { + private static final Byte BYTE_ZERO = 0; + + public ByteEditor(int header, boolean automicType) { + super(header, automicType); + } + + @Override + protected Object convert(Number value) { + return value.byteValue(); + } + + @Override + protected Object getValue(String text) { + return Byte.valueOf(text); + } + + @Override + protected Object defaultValue() { + return BYTE_ZERO; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/CharEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/CharEditor.java new file mode 100644 index 0000000..a723b20 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/CharEditor.java @@ -0,0 +1,29 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +import org.apache.commons.lang3.StringUtils; + + +public class CharEditor extends AbstractParameterEditor { + private static final char CHAR_ZERO = '\u0000'; + private final boolean automicType; + + public CharEditor(int header, boolean automicType) { + super(header); + this.automicType = automicType; + } + + @Override + protected Object parseText(Object value) { + if (value == null) { + return automicType ? CHAR_ZERO : null; + } + + String text = value.toString(); + + if(StringUtils.isBlank(text)){ + return automicType ? CHAR_ZERO : null; + } + + return text.charAt(0); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/DateEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/DateEditor.java new file mode 100644 index 0000000..8f49da3 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/DateEditor.java @@ -0,0 +1,53 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +import cc.smtweb.framework.core.exception.BindParamException; +import org.apache.commons.lang3.StringUtils; + +import java.text.ParseException; +import java.util.Date; + +public class DateEditor extends AbstractParameterEditor { + public DateEditor(int header) { + super(header); + } + + @Override + protected Object parseText(Object value) { + if (value == null) { + return null; + } + + if (value instanceof Date) { + return new java.sql.Date(((Date) value).getTime()); + } + + String text = value.toString(); + if (StringUtils.isBlank(text)) { + return null; + } + + Object result = null; + + if (StringUtils.isNotBlank(text)) { + text = text.trim(); + + if ("null".equals(text)) { + return null; + } else { + int pos = text.indexOf(' '); + if (pos > 0) { + text = text.substring(0, pos); + } + + try { + result = new java.sql.Date(parseDate(text).getTime()); + } catch (ParseException e) { + throw new BindParamException(e); + } + } + + } + + return result; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/DoubleEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/DoubleEditor.java new file mode 100644 index 0000000..8b8d7d9 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/DoubleEditor.java @@ -0,0 +1,25 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + + +public class DoubleEditor extends AbstractNumberEditor { + private static final Double DOUBLE_ZERO = 0.0d; + + public DoubleEditor(int header, boolean automicType) { + super(header, automicType); + } + + @Override + protected Object convert(Number value) { + return value.doubleValue(); + } + + @Override + protected Object getValue(String text) { + return Double.valueOf(text); + } + + @Override + protected Object defaultValue() { + return DOUBLE_ZERO; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/FloatEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/FloatEditor.java new file mode 100644 index 0000000..c6cfe12 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/FloatEditor.java @@ -0,0 +1,25 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + + +public class FloatEditor extends AbstractNumberEditor { + private static final Float FLOAT_ZERO = 0.0f; + + public FloatEditor(int header, boolean automicType) { + super(header, automicType); + } + + @Override + protected Object convert(Number value) { + return value.floatValue(); + } + + @Override + protected Object getValue(String text) { + return Float.valueOf(text); + } + + @Override + protected Object defaultValue() { + return FLOAT_ZERO; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/IntegerEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/IntegerEditor.java new file mode 100644 index 0000000..e3666e2 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/IntegerEditor.java @@ -0,0 +1,24 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +public class IntegerEditor extends AbstractNumberEditor { + private static final Integer INT_ZERO = 0; + + public IntegerEditor(int header, boolean automicType) { + super(header, automicType); + } + + @Override + protected Object convert(Number value) { + return value.intValue(); + } + + @Override + protected Object getValue(String text) { + return Integer.valueOf(text); + } + + @Override + protected Object defaultValue() { + return INT_ZERO; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/LongEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/LongEditor.java new file mode 100644 index 0000000..359cc0b --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/LongEditor.java @@ -0,0 +1,25 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + + +public class LongEditor extends AbstractNumberEditor { + private static final Long LONG_ZERO = 0L; + + public LongEditor(int header, boolean automicType) { + super(header, automicType); + } + + @Override + protected Object convert(Number value) { + return value.longValue(); + } + + @Override + protected Object getValue(String text) { + return Long.valueOf(text); + } + + @Override + protected Object defaultValue() { + return LONG_ZERO; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/ShortEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/ShortEditor.java new file mode 100644 index 0000000..5587c17 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/ShortEditor.java @@ -0,0 +1,25 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + + +public class ShortEditor extends AbstractNumberEditor { + private static final Short SHORT_ZERO = 0; + + public ShortEditor(int header, boolean automicType) { + super(header, automicType); + } + + @Override + protected Object convert(Number value) { + return value.shortValue(); + } + + @Override + protected Object getValue(String text) { + return Short.valueOf(text); + } + + @Override + protected Object defaultValue() { + return SHORT_ZERO; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/StringEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/StringEditor.java new file mode 100644 index 0000000..c6f7e69 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/StringEditor.java @@ -0,0 +1,17 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + + +public class StringEditor extends AbstractParameterEditor { + public StringEditor(int header) { + super(header); + } + + @Override + protected Object parseText(Object text) { + if (text == null) { + return text; + } + + return text.toString(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/TimeEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/TimeEditor.java new file mode 100644 index 0000000..5377898 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/TimeEditor.java @@ -0,0 +1,35 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +import cc.smtweb.framework.core.exception.BindParamException; +import org.apache.commons.lang3.StringUtils; + +import java.text.ParseException; +import java.util.Date; + +public class TimeEditor extends AbstractParameterEditor { + public TimeEditor(int header) { + super(header); + } + + @Override + public Object parseText(Object value) { + if (value == null) { + return null; + } + + if (value instanceof Date) { + return new java.sql.Time(((Date) value).getTime()); + } + + String text = value.toString(); + if (StringUtils.isBlank(text)) { + return null; + } + + try { + return new java.sql.Time(parseDateTime(text).getTime()); + } catch (ParseException e) { + throw new BindParamException(e); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/TimestampEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/TimestampEditor.java new file mode 100644 index 0000000..44141bf --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/param/TimestampEditor.java @@ -0,0 +1,57 @@ +package cc.smtweb.framework.core.mvc.controller.binder.param; + +import cc.smtweb.framework.core.exception.BindParamException; +import org.apache.commons.lang3.StringUtils; + +import java.sql.Timestamp; +import java.text.ParseException; +import java.util.Date; + +public class TimestampEditor extends AbstractParameterEditor { + public TimestampEditor(int header) { + super(header); + } + + @Override + public Object parseText(Object value) throws IllegalArgumentException { + if (value == null) { + return null; + } + + if (value instanceof Date) { + return new Timestamp(((Date) value).getTime()); + } + + String text = value.toString(); + if (StringUtils.isBlank(text)) { + return null; + } + + text = text.trim(); + + if ("null".equals(text)) { + return null; + } + + Timestamp result = null; + + try { + int pos = text.indexOf(':'); + if (pos < 0 && text.indexOf("-") < 0) { + result = new Timestamp(Long.parseLong(text)); + } else if (text.length() > 10) { + if (text.indexOf(':', pos + 1) > pos) { + result = new Timestamp(parseDateTime(text).getTime()); + } else { + result = new Timestamp(parseDateTime(text).getTime()); + } + } else { + result = new Timestamp(parseDateTime(text).getTime()); + } + } catch (ParseException ex) { + throw new BindParamException(ex); + } + + return result; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/path/PathParamEditor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/path/PathParamEditor.java new file mode 100644 index 0000000..cb42ef7 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/binder/path/PathParamEditor.java @@ -0,0 +1,14 @@ +package cc.smtweb.framework.core.mvc.controller.binder.path; + +import cc.smtweb.framework.core.mvc.controller.binder.param.AbstractRequestEditor; + +import javax.servlet.http.HttpServletRequest; + +public class PathParamEditor extends AbstractRequestEditor { + public final static String ATTR_KEY = "SMTWEB_PATH"; + + @Override + public Object getValue(HttpServletRequest request) { + return request.getAttribute(ATTR_KEY); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ApplicationScanner.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ApplicationScanner.java new file mode 100644 index 0000000..e018aa0 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ApplicationScanner.java @@ -0,0 +1,72 @@ +package cc.smtweb.framework.core.mvc.controller.scan; + +import cc.smtweb.framework.core.cache.AbstractCache; +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.cache.ISwCache; +import cc.smtweb.framework.core.mvc.config.ControllerConfig; +import cc.smtweb.framework.core.mvc.controller.ApiConfigBean; +import cc.smtweb.framework.core.mvc.controller.MethodAccessManager; +import cc.smtweb.framework.core.mvc.controller.binder.CacheEditor; +import cc.smtweb.framework.core.mvc.controller.binder.WebDataBinder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ConfigurableApplicationContext; + +import java.util.ArrayList; +import java.util.List; + +/** + * 微服务API扫描,扫描生成api接口实现放入methodAccessManager + * + * @author xkliu + */ +@Slf4j +public class ApplicationScanner { + public static void scan(ConfigurableApplicationContext applicationContext) throws Exception { + MethodAccessManager methodAccessManager = applicationContext.getBean(MethodAccessManager.class); + BeanManager beanManager = new BeanManager(applicationContext); + + String[] names = applicationContext.getBeanNamesForType(ControllerConfig.class); + if (names != null) { + WebDataBinder webDataBinder = new WebDataBinder(new CacheEditor(methodAccessManager.getCacheManager())); + ApiConfigBean apiConfig = applicationContext.getBean(ApiConfigBean.class); + List controllerConfigs = new ArrayList<>(); + + ScanContext scanContext = new ScanContext(beanManager, webDataBinder); + + for (String name : names) { + ControllerConfig controllerConfig = applicationContext.getBean(name, ControllerConfig.class); + controllerConfigs.add(controllerConfig); + + // 先注册自定义注解解析器 + IScanActionBuilder scanActionBuilder = controllerConfig.getScanActionBuilder(); + if (scanActionBuilder != null) { + scanContext.addScanAction(scanActionBuilder.build(applicationContext, webDataBinder)); + } + } + + for (ControllerConfig controllerConfig : controllerConfigs) { + scanContext.setModule(controllerConfig.getModule()); + log.info("[/api/{}/*]start scan {}", controllerConfig.getModule(), controllerConfig.getPackagePath()); + + // 扫描 Service 类 + PackageScanner packageScanner = new PackageScanner( + new ClassParser(new UrlMaker(apiConfig, controllerConfig.getModule()), scanContext)); + packageScanner.scan(controllerConfig.getPackagePath()); + } + + // 单例进行实例化,并设置实例的field(@SwParam),关联扩展的action(@SwAction)到方法执行器 + beanManager.init(); + + log.info("[smt] scan ok."); + } + + // 处理权限管理和启动定时任务 + AbstractCache realmCache = CacheManager.getIntance().getCache(ISwCache.REALM_CACHE); + + if (realmCache == null) { + log.error("not find RealmCache"); + } + + methodAccessManager.init(beanManager, realmCache); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/BeanManager.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/BeanManager.java new file mode 100644 index 0000000..3d9582d --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/BeanManager.java @@ -0,0 +1,139 @@ +package cc.smtweb.framework.core.mvc.controller.scan; + +import cc.smtweb.framework.core.mvc.controller.IActionManager; +import cc.smtweb.framework.core.mvc.controller.access.ControllerAccess; +import cc.smtweb.framework.core.mvc.controller.access.IMethodAccess; +import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; +import cc.smtweb.framework.core.mvc.controller.access.SchedulerMethodAccess; +import cc.smtweb.framework.core.mvc.controller.binder.BeanContext; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class BeanManager implements IActionManager { + private ApplicationContext applicationContext; + private List beans = new ArrayList<>(); + private Map, Object> beanMap = new HashMap<>(); + private List singletonServices = new ArrayList<>(); + @Getter + private Map controllers = new HashMap<>(); + @Getter + private BeanContext beanContext; + @Getter + private List tasks = new ArrayList<>(); + + private List constructMethods = new ArrayList<>(); + private List destroyMethods = new ArrayList<>(); + private List beanMethods = new ArrayList<>(); + + BeanManager(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + beanContext = new BeanContext(beanMap, applicationContext); + } + + public void init() throws Exception { + // 读取DbEngine,用以初始化 EntityDao + beanContext.init(); + + // 自动设置@SwBean注解对象的@SwParam注解属性 + for (ControllerAccess controllerAccess: beans) { + Object bean = controllerAccess.getSingletonInstance(); + addBeanMap(controllerAccess.getBeanType(), bean); + } + + // 映射单例Bean字段值 + for (ControllerAccess controllerAccess: beans) { + controllerAccess.initSingletonFields(beanContext, null); + } + + for (IMethodAccess methodAccess: beanMethods) { + Object bean = methodAccess.invoke(beanContext); + addBeanMap(methodAccess.getReturnType(), bean); + log.debug("[smt]init bean:" + methodAccess.getReturnType() + " by " + methodAccess.fullName()); + } + + // 映射单例控制器字段值 + for (ControllerAccess controllerAccess: singletonServices) { + controllerAccess.initSingletonFields(beanContext, null); + } + + // 执行初始化方法 + constructMethods.sort((a, b) -> a.order - b.order); + + for (OrderMethodAccess orderMethodAccess: constructMethods) { + IMethodAccess methodAccess = orderMethodAccess.methodAccess; + methodAccess.invoke(beanContext); + } + } + + private void addBeanMap(Class beanType, Object bean) { + if (bean != null) { + beanMap.put(beanType, bean); + } + } + + IMethodAccess putIfAbsent(String url, IMethodAccess methodAccess) { + return controllers.putIfAbsent(url, methodAccess); + } + + void addBean(ControllerAccess controllerAccess) { + this.beans.add(controllerAccess); + } + + void addSingletonController(ControllerAccess controllerAccess) { + this.singletonServices.add(controllerAccess); + } + + void addTask(SchedulerMethodAccess schedulerMethodAccess) { + this.tasks.add(schedulerMethodAccess); + } + + void addConstruct(int order, MethodAccess methodAccess) { + constructMethods.add(new OrderMethodAccess(order, methodAccess)); + } + + void addDestroy(int order, MethodAccess methodAccess) { + destroyMethods.add(new OrderMethodAccess(order, methodAccess)); + } + + public MethodAccess[] loadDestroyMethods() { + if (destroyMethods.isEmpty()) { + return null; + } + + destroyMethods.sort((a, b) -> b.order - a.order); + + int size = destroyMethods.size(); + MethodAccess[] result = new MethodAccess[size]; + for (int i = 0; i < size; i++) { + result[i] = destroyMethods.get(i).methodAccess; + } + + return result; + } + + void addBeanMethod(IMethodAccess methodAccess) { + this.beanMethods.add(methodAccess); + } + + @Override + public boolean api(String url, IMethodAccess methodAccess) { + return this.putIfAbsent(url, methodAccess) == null; + } + + private static class OrderMethodAccess { + private final int order; + private final MethodAccess methodAccess; + + OrderMethodAccess(int order, MethodAccess methodAccess) { + this.order = order; + this.methodAccess = methodAccess; + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ClassParser.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ClassParser.java new file mode 100644 index 0000000..692f55e --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ClassParser.java @@ -0,0 +1,166 @@ +package cc.smtweb.framework.core.mvc.controller.scan; + +import cc.smtweb.framework.core.annotation.*; +import cc.smtweb.framework.core.cache.AbstractCache; +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.EntityDao; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.framework.core.mvc.controller.access.BindFieldAccess; +import cc.smtweb.framework.core.mvc.controller.access.ControllerAccess; +import cc.smtweb.framework.core.mvc.controller.access.FieldAccess; +import cc.smtweb.framework.core.mvc.controller.access.SingletonFieldAccess; +import cc.smtweb.framework.core.mvc.controller.binder.ParamEditor; +import cc.smtweb.framework.core.mvc.controller.binder.WebDataBinder; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.collection.SynchronizedCollection; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +@Slf4j +public class ClassParser { + private BeanManager beanManager; + private UrlMaker urlMaker; + private WebDataBinder webDataBinder; + private ScanContext scanContext; + private Field singletonField; + private boolean singleton; + // private List cacheList; + private MethodParser methodParser; + private List> threadSafeTypes = new ArrayList<>(); + private DbEngine dbEngine; + + public ClassParser(UrlMaker urlMaker, ScanContext scanContext) { + this.urlMaker = urlMaker; + this.scanContext = scanContext; + this.beanManager = scanContext.getBeanManager(); +// this.cacheList = cacheList; + this.webDataBinder = scanContext.getWebDataBinder(); + this.methodParser = new MethodParser(urlMaker, scanContext); + this.dbEngine = beanManager.getBeanContext().getBean(DbEngine.class); + + threadSafeTypes.add(AtomicBoolean.class); + threadSafeTypes.add(AtomicInteger.class); + threadSafeTypes.add(AtomicLong.class); + threadSafeTypes.add(SynchronizedCollection.class); + threadSafeTypes.add(ConcurrentMap.class); + } + + public void parse(String dirName, Class clazz) throws InstantiationException, IllegalAccessException, ParseException { + SwBean swBean = clazz.getAnnotation(SwBean.class); + + // TODO ISwCache.class.isAssignableFrom(clazz) + if (swBean != null) { + ControllerAccess controllerAccess = buildControllerAccess(clazz, swBean.alwaysCreate()); + if (controllerAccess.isSingleton()) { + controllerAccess.newSingletonInstance(); + log.debug("[smt]init bean: " + clazz.getName()); + beanManager.addBean(controllerAccess); + + methodParser.parse(clazz, controllerAccess, false); + } else { + log.error(clazz.getName() + " must be singleton, can not use field: " + singletonField.getName()); + } + } else { + SwService swService = clazz.getAnnotation(SwService.class); + + if (swService != null) { + urlMaker.setServiceUrl(swService.value()); + log.debug("[smt]init service: " + clazz.getName()); + + ControllerAccess controllerAccess = buildControllerAccess(clazz, false); + if (controllerAccess.isSingleton()) { + controllerAccess.newSingletonInstance(); + beanManager.addSingletonController(controllerAccess); + } + + // 处理url路径 + methodParser.parse(clazz, controllerAccess, true); + } else { + SwCache swCache = clazz.getAnnotation(SwCache.class); + if (swCache != null) { + CacheManager cacheManager = CacheManager.getIntance(); + cacheManager.install((AbstractCache)clazz.newInstance()); + } + } + } + } + + private ControllerAccess buildControllerAccess(Class clazz, boolean alwaysCreate) { + List fieldList = new ArrayList<>(); + singleton = true; + singletonField = null; + // 扫描本类和父类的 field + Class parentClazz = clazz; + + // !parentClazz.equals(stopClazz) + while (parentClazz != null && !parentClazz.isInterface()) { + parseFields(parentClazz, fieldList); + parentClazz = parentClazz.getSuperclass(); + } + + return new ControllerAccess(clazz, fieldList, alwaysCreate || singleton); + } + + private void parseFields(Class clazz, List fieldList) { + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + FieldAccess fieldAccess = parseField(field); + + if (fieldAccess != null) { + fieldList.add(fieldAccess); + } + } + } + + private FieldAccess parseField(Field field) { + // 得到属性上的注解 + SwParam swParam = field.getAnnotation(SwParam.class); + + if (swParam != null) { + if (Object.class.equals(swParam.type())) { + IEditor editor = webDataBinder.findEditor(field.getType(), ParamEditor.TYPE_FIELD); + return new BindFieldAccess(editor, field, swParam.value()); + } else { + EntityDao entityDao = dbEngine.findDao(swParam.type()); + return new SingletonFieldAccess(entityDao, field); + } + } else { + if (singleton) { + if (!isConstance(field) && !isThreadSafe(field)) { + // 记录首个发现的线程不安全字段 + singletonField = field; + singleton = false; + } + } + } + + return null; + } + + private static boolean isConstance(Field field) { + int fieldModifiers = field.getModifiers(); + return Modifier.isStatic(fieldModifiers) && Modifier.isFinal(fieldModifiers); + } + + private boolean isThreadSafe(Field field) { + Class fieldType = field.getDeclaringClass(); + for (Class type : threadSafeTypes) { + if (type.isAssignableFrom(fieldType)) { + return true; + } + } + + return false; + } + +} + diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/IScanAction.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/IScanAction.java new file mode 100644 index 0000000..798fc2e --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/IScanAction.java @@ -0,0 +1,18 @@ +package cc.smtweb.framework.core.mvc.controller.scan; + +import cc.smtweb.framework.core.mvc.controller.IActionManager; +import cc.smtweb.framework.core.mvc.controller.access.IMethodAccess; + +/** + * 实现自定义@SwAction的拦截器,需要配合@SwAction继承注解配置到函数上使用 + * @author kevin + */ +public interface IScanAction { + Class actionType(); + /** + * 一种@SwAction子类型只能声明一个SwActionPointcut + * @param module 模块名称 + * @param actionManager action管理器,用以注册API服务 + */ + void load(IActionManager actionManager, String module, T anno, IMethodAccess methodAccess); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/IScanActionBuilder.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/IScanActionBuilder.java new file mode 100644 index 0000000..dcb9529 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/IScanActionBuilder.java @@ -0,0 +1,12 @@ +package cc.smtweb.framework.core.mvc.controller.scan; + +import cc.smtweb.framework.core.mvc.controller.binder.WebDataBinder; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * 实现自定义@SwAction的拦截器,需要配合@SwAction继承注解配置到函数上使用 + * @author kevin + */ +public interface IScanActionBuilder { + IScanAction build(ConfigurableApplicationContext applicationContext, WebDataBinder webDataBinder); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/MethodParser.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/MethodParser.java new file mode 100644 index 0000000..9a355ce --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/MethodParser.java @@ -0,0 +1,161 @@ +package cc.smtweb.framework.core.mvc.controller.scan; + +import cc.smtweb.framework.core.annotation.*; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.framework.core.mvc.controller.access.ControllerAccess; +import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; +import cc.smtweb.framework.core.mvc.controller.access.MethodParamAccess; +import cc.smtweb.framework.core.mvc.controller.binder.ParamEditor; +import cc.smtweb.framework.core.mvc.controller.binder.WebDataBinder; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.SynthesizingMethodParameter; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.text.ParseException; +import java.util.List; + +@Slf4j +public class MethodParser { + private final UrlMaker urlMaker; + private final BeanManager controllers; + private final WebDataBinder webDataBinder; + private ScanContext scanContext; +// private ControllerAccess controllerAccess; + + public MethodParser(UrlMaker urlMaker, ScanContext scanContext) { + this.urlMaker = urlMaker; + this.controllers = scanContext.getBeanManager(); + this.webDataBinder = scanContext.getWebDataBinder(); + this.scanContext = scanContext; + } + + public void parse(Class clazz, ControllerAccess controllerAccess, boolean isApi) throws ParseException { +// this.controllerAccess = controllerAccess; + // 服务的默认权限 + String classPerm = ""; + SwPerm swPerm = clazz.getAnnotation(SwPerm.class); + if (swPerm != null) { + classPerm = swPerm.value(); + } + + // 扫描方法注解 + for (Method m : clazz.getMethods()) { + int modifier = m.getModifiers(); + + // && (R.class.isAssignableFrom(m.getReturnType())) + if (Modifier.isPublic(modifier) && !Modifier.isStatic(modifier) && !Object.class.equals(m.getDeclaringClass())) { + MethodAccess methodAccess = parseMethod(controllerAccess, m, classPerm); + + if (scanContext.dealMethod(m, methodAccess)) { + if (isApi) { + urlMaker.addApi(clazz, m, methodAccess, controllers); + } + } + +// SwScheduling scheduling = m.getAnnotation(SwScheduling.class); +// SwConstruct construct = m.getAnnotation(SwConstruct.class); +// SwDestroy destroy = m.getAnnotation(SwDestroy.class); +// SwBean bean = m.getAnnotation(SwBean.class); +// SwAction action = m.getAnnotation(SwAction.class); +// +// if (scheduling != null) { +// // 定时任务用seed值限定 +// log.debug(" add timer task: " + clazz.getSimpleName() + "." + m.getName()); +// controllers.addTask(new SchedulerMethodAccess(methodAccess, scheduling)); +// } else if(construct != null) { +// controllers.addConstruct(construct.order(), methodAccess); +// } else if(destroy != null) { +// controllers.addDestroy(destroy.order(), methodAccess); +// } else if(bean != null) { +// controllers.addBeanMethod(methodAccess); +// } else if(action != null) { +// controllers.addAction(action, methodAccess); +// } else if (isApi) { +// urlMaker.addApi(clazz, m, methodAccess, controllers); +// } + } + } + } + + private MethodAccess parseMethod(ControllerAccess controllerAccess, Method method, String defaultPerm) { + Class[] paramTypes = method.getParameterTypes(); + Annotation[][] paramAnnotations = method.getParameterAnnotations(); + + MethodParamAccess[] paramBinds = new MethodParamAccess[paramTypes.length]; + + for (int i = 0; i < paramTypes.length; i++) { + Class paramType = paramTypes[i]; + String paramName = null; + int bindType = ParamEditor.TYPE_UNKNOWN; + if (!ObjectUtils.isEmpty(paramAnnotations[i])) { + for (Annotation annotation: paramAnnotations[i]) { + // isAssignableFrom + Class annotationType = annotation.annotationType(); + if (SwParam.class.equals(annotationType)) { + bindType = ParamEditor.TYPE_PARAM; + String value = ((SwParam) annotation).value(); + if (StringUtils.isNotBlank(value)) { + paramName = value; + } + break; + } else if (SwPathParam.class.equals(annotationType)) { + bindType = ParamEditor.TYPE_PATH; + break; + } else if (SwHeaderParam.class.equals(annotationType)) { + bindType = ParamEditor.TYPE_HEADER; + String value = ((SwHeaderParam) annotation).value(); + if (StringUtils.isNotBlank(value)) { + paramName = value; + } + break; + } else if (SwBody.class.equals(annotationType)) { + bindType = ParamEditor.TYPE_BODY; + String value = ((SwBody) annotation).value(); + if (StringUtils.isNotBlank(value)) { + paramName = value; + } + break; + } else if (SwAttr.class.equals(annotationType)) { + bindType = ParamEditor.TYPE_ATTR; + String value = ((SwAttr) annotation).value(); + if (StringUtils.isNotBlank(value)) { + paramName = value; + } + break; + } + } + } + + IEditor editor = webDataBinder.findEditor(paramType, bindType); + + if (paramType.isAssignableFrom(List.class)) { + // List bind类型使用泛型声明的对象 + ResolvableType resolvableType = ResolvableType.forMethodParameter(method, i); + paramType = resolvableType.getGeneric(0).getRawClass(); + paramBinds[i] = new MethodParamAccess(editor, paramName, resolvableType.getGeneric(0).getRawClass(), bindType, paramType); + } else { + paramBinds[i] = new MethodParamAccess(editor, paramName, paramType, bindType, null); + } + + if (editor == null) { + log.error("not bind type:" + controllerAccess.getFullName() + "." + method.getName()); + } + } + + // 得到权限注解 + String perm = defaultPerm; + SwPerm swPerm = method.getAnnotation(SwPerm.class); + if (swPerm != null) { + perm = swPerm.value(); + } + + return new MethodAccess(controllerAccess, method, perm, paramBinds); + } + +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/PackageScanner.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/PackageScanner.java new file mode 100644 index 0000000..cb6532f --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/PackageScanner.java @@ -0,0 +1,182 @@ +package cc.smtweb.framework.core.mvc.controller.scan; + +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.net.JarURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +@Slf4j +public class PackageScanner { + private ClassParser classParser; + + public PackageScanner(ClassParser classParser) { + this.classParser = classParser; + } + + /** + * 1. 遍历所有目录 2. 处理注解类,进行适配类匹配 3. 放入路径映射map + * + * @throws IOException + */ + public void scan(String packagePath) throws IOException { +// log.info("[smt]start scan " + packagePath); + directorize(packagePath, true); +// log.info("[smt]end scan " + packagePath); + } + + /** + * 扫描目录下的注解类 + * + * @param packageName 目录对象 + * @param recursive 是否递归扫描包 + */ + private void directorize(String packageName, boolean recursive) + throws IOException { + String packageDirName = packageName.replace('.', '/'); + + Enumeration dirs = Thread.currentThread().getContextClassLoader() + .getResources(packageDirName); + + // 循环迭代下去 + while (dirs.hasMoreElements()) { + // 获取下一个元素 + URL url = dirs.nextElement(); + // 得到协议的名称 + String protocol = url.getProtocol(); + // 如果是以文件的形式保存在服务器上 + if ("file".equals(protocol)) { +// log.debug("scan action in file"); + // 获取包的物理路径 + String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); + // 以文件的方式扫描整个包下的文件 并添加到集合中 + findAndAddClassesInPackageByFile(packageName, "", filePath, + recursive); + // 执行属性设置 +// for (FieldAutowriter field : fieldList) { +// boolean ret = field.writeValue(this.context); +// if (ret) { +// log.debug("write bean " + field.getName() +// + " success."); +// } else { +// log.error("write bean " + field.getName() +// + " failure."); +// } +// } + } else if ("jar".equals(protocol)) { + // 如果是jar包文件 + // 定义一个JarFile + try { + // 获取jar + JarFile jar = ((JarURLConnection) url.openConnection()) + .getJarFile(); + // 从此jar包 得到一个枚举类 + Enumeration entries = jar.entries(); + // 同样的进行循环迭代 + while (entries.hasMoreElements()) { + // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件 + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + // 如果是以/开头的 + if (name.charAt(0) == '/') { + // 获取后面的字符串 + name = name.substring(1); + } + // 如果前半部分和定义的包名相同 + if (name.startsWith(packageDirName)) { + int idx = name.lastIndexOf('/'); + // 如果以"/"结尾 是一个包 + if (idx != -1) { + // 获取包名 把"/"替换成"." + packageName = name.substring(0, idx).replace( + '/', '.'); + } + // 如果可以迭代下去 并且是一个包 + if ((idx != -1) || recursive) { + // 如果是一个.class文件 而且不是目录 + if (!entry.isDirectory()) { + parseFile( + packageName, "", + name.substring(packageName.length() + 1)); + } + } + } + } + } catch (IOException e) { + log.error("scan jar error", e); + // log.error("在扫描用户定义视图时从jar包获取文件出错"); + // e.printStackTrace(); + } + } + } + } + + /** + * 以文件的形式来获取包下的所有Class + * + * @param packageName + * @param packagePath + * @param recursive + */ + public void findAndAddClassesInPackageByFile(String packageName, String dirName, + String packagePath, final boolean recursive) { + // 获取此包的目录 建立一个File + File dir = new File(packagePath); + // 如果不存在或者 也不是目录就直接返回 + if (!dir.exists() || !dir.isDirectory()) { + // log.warn("用户定义包名 " + packageName + " 下没有任何文件"); + return; + } + + // 如果存在 就获取包下的所有文件 包括目录 + // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件) + File[] dirFiles = dir.listFiles(file -> { + String fileName = file.getName(); + return (recursive && file.isDirectory()) + || fileName.endsWith(".class"); + }); + + // 循环所有文件 + if (dirFiles != null) { + for (File file : dirFiles) { + // 如果是目录 则继续扫描 + if (file.isDirectory()) { + findAndAddClassesInPackageByFile(packageName + "." + file.getName(), + file.getName(), file.getAbsolutePath(), recursive); + } else { + parseFile(packageName, dirName, file.getName()); + } + } + } + } + + private void parseFile(String packageName, String dirName, String fileName) { + if (fileName.endsWith(".class")) { + // 去掉后面的".class" 获取真正的类名 + String className = fileName.substring(0, fileName.length() - 6); + + className = packageName + '.' + className; + + try { + Class clazz = Class.forName(className); + + int modifiers = clazz.getModifiers(); + + if (Modifier.isPublic(modifiers) && !Modifier.isAbstract(modifiers)) { + classParser.parse(dirName, clazz); + } + } catch (Exception e) { + log.error("init class error " + className, e); + throw new RuntimeException(e); + } + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ScanContext.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ScanContext.java new file mode 100644 index 0000000..6c9d430 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/ScanContext.java @@ -0,0 +1,112 @@ +package cc.smtweb.framework.core.mvc.controller.scan; + +import cc.smtweb.framework.core.SwException; +import cc.smtweb.framework.core.annotation.*; +import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; +import cc.smtweb.framework.core.mvc.controller.access.SchedulerMethodAccess; +import cc.smtweb.framework.core.mvc.controller.binder.WebDataBinder; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; + +@Getter +@Slf4j +public class ScanContext { + private final BeanManager beanManager; + private final WebDataBinder webDataBinder; +// private Map, List> actions = new HashMap<>(); + private Map, BiFunction> dealMethodAnnotation = new HashMap<>(); + @Setter + private String module; + + public ScanContext(BeanManager beanManager, WebDataBinder webDataBinder) { + this.beanManager = beanManager; + this.webDataBinder = webDataBinder; +// if (scheduling != null) { +// // 定时任务用seed值限定 +// log.debug(" add timer task: " + clazz.getSimpleName() + "." + m.getName()); +// controllers.addTask(new SchedulerMethodAccess(methodAccess, scheduling)); +// } else if(construct != null) { +// controllers.addConstruct(construct.order(), methodAccess); +// } else if(destroy != null) { +// controllers.addDestroy(destroy.order(), methodAccess); +// } else if(bean != null) { +// controllers.addBeanMethod(methodAccess); +// } else if(action != null) { +// controllers.addAction(action, methodAccess); +// } else if (isApi) { +// urlMaker.addApi(clazz, m, methodAccess, controllers); +// } + dealMethodAnnotation.put(SwScheduling.class, (a, methodAccess) -> { + try { + // 定时任务用seed值限定 + beanManager.addTask(new SchedulerMethodAccess(methodAccess, ((SwScheduling)a))); + log.debug(" add timer task: " + methodAccess.fullName()); + } catch (ParseException e) { + throw new SwException(e); + } + + return false; + }); + + dealMethodAnnotation.put(SwConstruct.class, (a, methodAccess) -> { + beanManager.addConstruct(((SwConstruct)a).order(), methodAccess); + return false; + }); + + dealMethodAnnotation.put(SwDestroy.class, (a, methodAccess) -> { + beanManager.addDestroy(((SwDestroy)a).order(), methodAccess); + return false; + }); + + dealMethodAnnotation.put(SwBean.class, (a, methodAccess) -> { + beanManager.addBeanMethod(methodAccess); + return false; + }); +// +// dealMethodAnnotation.put(SwAction.class, (a, methodAccess) -> { +// return true; +// }); +// +// dealMethodAnnotation.put(SwPerm.class, (a, methodAccess) -> { +// return true; +// }); + } + + public void addScanAction(IScanAction apiLoader) { + Object old = dealMethodAnnotation.putIfAbsent(apiLoader.actionType(), (a, methodAccess) -> { + apiLoader.load(beanManager, module, a, methodAccess); + return false; + }); + + if (old != null) { + log.error("已经存在动作解析器:" + apiLoader.actionType()); + } + } + + public boolean dealMethod(Method m, MethodAccess methodAccess) { + boolean result = true; + Annotation[] annos = m.getDeclaredAnnotations(); + if (annos != null) { + for (Annotation anno: annos) { + BiFunction func = dealMethodAnnotation.get(anno.annotationType()); + if (func != null) { + boolean ret = func.apply(anno, methodAccess); + if (!ret) { + result = false; + break; + } + } + } + } + + return result; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/UrlMaker.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/UrlMaker.java new file mode 100644 index 0000000..7aa6aa9 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/controller/scan/UrlMaker.java @@ -0,0 +1,95 @@ +package cc.smtweb.framework.core.mvc.controller.scan; + +import cc.smtweb.framework.core.annotation.SwAction; +import cc.smtweb.framework.core.mvc.controller.ApiConfigBean; +import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.lang.reflect.Method; + +/** + * URL构造器,用于API的访问路径构造 + * @author xkliu + */ +@Slf4j +class UrlMaker { + private static final String SERVICE_SUFFIX = "Service"; + + private final ApiConfigBean apiConfig; + @Getter + private final String module; + private String serviceUrl; + + UrlMaker(ApiConfigBean apiConfig, String module) { + this.apiConfig = apiConfig; + this.module = module; + } + + private String getServiceName(Class clazz) { + String simpleName = clazz.getSimpleName(); + if (simpleName.endsWith(SERVICE_SUFFIX)) { + simpleName = simpleName.substring(0, simpleName.length() - SERVICE_SUFFIX.length()); + } + + return simpleName; + } + + /** + * 添加API映射到BeanManager,API的url先取SwApi配置的值,然后取SwService配置的值+“/”+方法名,最后使用默认规则: “服务名” / "方法名" + * url都以模块名开头,避免应用模块服务名称重复 + * @param clazz 服务类 + * @param method 执行方法,用于映射API + * @param methodAccess 方法实际执行 + * @param controllers 控制器管理容器 + */ + void addApi(Class clazz, Method method, MethodAccess methodAccess, BeanManager controllers) { + String serviceName = getServiceName(clazz); + String methodName = method.getName(); + String urlPrefix = module + "/"; + SwAction swAction = method.getAnnotation(SwAction.class); + + if (swAction != null && StringUtils.isNotBlank(swAction.value())) { + // SwApi设置了URL + mappingApi(methodAccess, controllers, urlPrefix + swAction.value()); + } else if (this.serviceUrl != null) { + // SwService设置了URL + mappingApi(methodAccess, controllers, urlPrefix + this.serviceUrl + "/" + methodName); + } else { + // 小驼峰路径 + String littleCamelCaseUrl = urlPrefix + toLittleCamelName(serviceName) + "/" + methodName; + + if (mappingApi(methodAccess, controllers, littleCamelCaseUrl)) { + // 大驼峰路径,类原名 + if (apiConfig.isBigCameCaseUrl()) { + mappingApi(methodAccess, controllers, urlPrefix + serviceName + "/" + methodName); + } + + // 小写服务名路径 + if (apiConfig.isLowerCaseUrl()) { + mappingApi(methodAccess, controllers, urlPrefix + serviceName.toLowerCase() + "/" + methodName); + } + } + } + } + + private String toLittleCamelName(String serviceName) { + return Character.toLowerCase(serviceName.charAt(0)) + + serviceName.substring(1); + } + + private boolean mappingApi(MethodAccess methodAccess, BeanManager controllers, String url) { + if (controllers.putIfAbsent(url, methodAccess) != null) { + log.error("url is repeat: " + url + ", " + methodAccess.controllerFullName()); + return false; + } + + log.debug("[smt]init: " + url + ", " + methodAccess.controllerFullName()); + return true; + } + + void setServiceUrl(String value) { + this.serviceUrl = StringUtils.trimToNull(value); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/IRealmLoader.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/IRealmLoader.java new file mode 100644 index 0000000..5d5a490 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/IRealmLoader.java @@ -0,0 +1,18 @@ +package cc.smtweb.framework.core.mvc.realm; + +import cc.smtweb.framework.core.session.UserSession; + +import java.util.Set; + +/** + * @author kevin + * 用户权限加载器 + */ +public interface IRealmLoader { + /** + * 读取用户权限集合 + * @param userSession 用户会话 + * @return 权限集合 + */ + Set getPermits(UserSession userSession); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/AuthenticationException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/AuthenticationException.java new file mode 100644 index 0000000..3a65419 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/AuthenticationException.java @@ -0,0 +1,7 @@ +package cc.smtweb.framework.core.mvc.realm.exception; + +public class AuthenticationException extends AuthorizationException { + public AuthenticationException(String s) { + super(s); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/AuthorizationException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/AuthorizationException.java new file mode 100644 index 0000000..d3bd6f6 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/AuthorizationException.java @@ -0,0 +1,9 @@ +package cc.smtweb.framework.core.mvc.realm.exception; + +import cc.smtweb.framework.core.SwException; + +public class AuthorizationException extends SwException { + public AuthorizationException(String s) { + super(s); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/ForbiddenException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/ForbiddenException.java new file mode 100644 index 0000000..2aa0cdd --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package cc.smtweb.framework.core.mvc.realm.exception; + +public class ForbiddenException extends AuthorizationException { + public ForbiddenException(String s) { + super(s); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/UnauthenticatedException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/UnauthenticatedException.java new file mode 100644 index 0000000..52ea554 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/UnauthenticatedException.java @@ -0,0 +1,7 @@ +package cc.smtweb.framework.core.mvc.realm.exception; + +public class UnauthenticatedException extends AuthorizationException { + public UnauthenticatedException(String s) { + super(s); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/UnknownAccountException.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/UnknownAccountException.java new file mode 100644 index 0000000..3ac1681 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/exception/UnknownAccountException.java @@ -0,0 +1,7 @@ +package cc.smtweb.framework.core.mvc.realm.exception; + +public class UnknownAccountException extends AuthorizationException { + public UnknownAccountException(String s) { + super(s); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/AbstractPermInterceptor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/AbstractPermInterceptor.java new file mode 100644 index 0000000..21d9212 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/AbstractPermInterceptor.java @@ -0,0 +1,109 @@ +package cc.smtweb.framework.core.mvc.realm.interceptor; + +import cc.smtweb.framework.core.annotation.SwPerm; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.framework.core.mvc.realm.exception.ForbiddenException; +import cc.smtweb.framework.core.mvc.realm.exception.UnauthenticatedException; +import cc.smtweb.framework.core.mvc.realm.service.PermCheckItem; +import cc.smtweb.framework.core.mvc.realm.service.PermChecker; +import cc.smtweb.framework.core.session.SessionUtil; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.core.cache.ISwCache; +import cc.smtweb.framework.core.redis.RedisManager; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; + +public class AbstractPermInterceptor { + private final RedisManager redisManager; + private ISwCache cacheService; + + public AbstractPermInterceptor(RedisManager redisManager) { + this.redisManager = redisManager; + } + + public void setCache(ISwCache cacheService) { + this.cacheService = cacheService; + } + + protected boolean handle(HttpServletRequest request, String permissionValue) { + // 如果注解为null, 说明不需要拦截, 直接放过 + if (permissionValue == null || SwPerm.NONE.equals(permissionValue)) { + return true; + } + + // redis读取session,判断是否登录 + String token = SessionUtil.readToken(request); + if (StringUtils.isBlank(token)) { + throw new UnauthenticatedException("not find Auth-Token in header"); + } + + UserSession us = redisManager.get(token, UserSession.class); + if (us == null) { + throw new UnauthenticatedException("not find UserSession by token: " + token); + } + + request.setAttribute(IEditor.USER_TOKEN, token); + request.setAttribute(IEditor.USER_SESSION, us); + + // 如果标记了权限注解,则判断权限 + if (checkPermission(permissionValue, us)) { + // 更新Token redis TTL + redisManager.expire(token, RedisManager.SESSION_EXPIRE_SEC); + return true; + } else { + throw new ForbiddenException("user not permission: " + permissionValue); + } + } + + /** + * 权限检查 + */ + private boolean checkPermission(String permissionValue, UserSession us) { + if (StringUtils.isBlank(permissionValue)) { + return true; + } + + // 从本地缓存或数据库中获取该用户的权限信息 + PermChecker permissionSet = cacheService.get(us.getUserId()); + +// if (MapUtils.isEmpty(permissionSet)) { +// throw new ForbiddenException("empty permission"); +// } + + PermCheckItem permChecker = permissionSet.get(permissionValue); + + if (permChecker != null) { + return true; + } + + while (true) { + permissionValue = getParentPermValue(permissionValue); + + if (permissionValue != null) { + permChecker = permissionSet.get(permissionValue); + if (permChecker != null && permChecker.isPerfixMath()) { + return true; + } + } else { + break; + } + } + + return false; + } + + private static String getParentPermValue(String permissionValue) { + if (permissionValue.length() > 0) { + + int pos = permissionValue.lastIndexOf(':'); + if (pos > 0) { + return permissionValue.substring(0, pos); + } + + return ""; + } + + return null; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/AuthorizationInterceptor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/AuthorizationInterceptor.java new file mode 100644 index 0000000..85da726 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/AuthorizationInterceptor.java @@ -0,0 +1,42 @@ +package cc.smtweb.framework.core.mvc.realm.interceptor; + +import cc.smtweb.framework.core.annotation.SwPerm; +import cc.smtweb.framework.core.redis.RedisManager; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 暂时未使用,spring的拦截器方式判断权限 + */ +public class AuthorizationInterceptor extends AbstractPermInterceptor implements HandlerInterceptor { + + public AuthorizationInterceptor(RedisManager redisManager) { + super(redisManager); + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + // 获取方法上的注解 + SwPerm requiredSwPerm = handlerMethod.getMethod().getAnnotation(SwPerm.class); + // 如果方法上的注解为空 则获取类的注解 + if (requiredSwPerm == null) { + requiredSwPerm = handlerMethod.getMethod().getDeclaringClass().getAnnotation(SwPerm.class); + } + + String requiredValue = null; + if (requiredSwPerm != null) { + requiredValue = requiredSwPerm.value(); + } + + return super.handle(request, requiredValue); + } + + return true; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/PermInterceptor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/PermInterceptor.java new file mode 100644 index 0000000..ee6a9ac --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/interceptor/PermInterceptor.java @@ -0,0 +1,25 @@ +package cc.smtweb.framework.core.mvc.realm.interceptor; + +import cc.smtweb.framework.core.redis.RedisManager; + +import javax.servlet.http.HttpServletRequest; + +/** + * 权限拦截器,在API请求处理中一起完成 + * @author xkliu + */ +public class PermInterceptor extends AbstractPermInterceptor { + public PermInterceptor(RedisManager redisManager) { + super(redisManager); + } + + /** + * 校验用户是否有API权限 + * @param request http请求 + * @param permissionValue 权限值 + * @return 是否有权限 + */ + public boolean preHandle(HttpServletRequest request, String permissionValue) { + return super.handle(request, permissionValue); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/service/PermCheckItem.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/service/PermCheckItem.java new file mode 100644 index 0000000..2cecf28 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/service/PermCheckItem.java @@ -0,0 +1,21 @@ +package cc.smtweb.framework.core.mvc.realm.service; + +public class PermCheckItem { + // 全匹配 + private static final int TYPE_ALL_MATCH = 0; + // 前缀匹配 + private static final int TYPE_PREFIX_MATCH = 1; + + private int type; + + public static final PermCheckItem allMatch = new PermCheckItem(TYPE_ALL_MATCH); + public static final PermCheckItem prefixMatch = new PermCheckItem(TYPE_PREFIX_MATCH); + + private PermCheckItem(int type) { + this.type = type; + } + + public boolean isPerfixMath() { + return this.type == TYPE_PREFIX_MATCH; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/service/PermChecker.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/service/PermChecker.java new file mode 100644 index 0000000..8bb2cc5 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/realm/service/PermChecker.java @@ -0,0 +1,35 @@ +package cc.smtweb.framework.core.mvc.realm.service; + +import java.util.HashMap; +import java.util.Set; + +public class PermChecker extends HashMap { + private static final PermChecker CHECKER_ZERO = new PermChecker(); + + private PermChecker(){ + super(); + } + + private PermChecker(int size){ + super(size); + } + + public static PermChecker build(Set permits) { + if (permits != null) { + PermChecker result = new PermChecker(permits.size()); + + for (String perm: permits) { + if (perm.endsWith(":*")) { + perm = perm.substring(0, perm.length() - 2); + result.put(perm, PermCheckItem.prefixMatch); + } else { + result.putIfAbsent(perm, PermCheckItem.allMatch); + } + } + + return result; + } + + return CHECKER_ZERO; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJob.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJob.java new file mode 100644 index 0000000..d3d8030 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJob.java @@ -0,0 +1,59 @@ +package cc.smtweb.framework.core.mvc.scheduler; + +import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class AbstractJob { + final MethodAccess method; + @Getter + private String key; + boolean hasPointParam; +// @Getter @Setter +// private AbstractJobQueue jobQueue; + + public AbstractJob(MethodAccess methodAccess) { + this.method = methodAccess; + this.hasPointParam = methodAccess.findParam(SchedulerPoint.class) != null; + } + + protected void initKey(String key) { + this.key = key; + } + +// public void invoke(Map params) { +// try { +// // 组织断点对象参数 +// Map params = null; +// SchedulerPoint schedulerPoint = null; +// if (hasPointParam) { +// schedulerPoint = jobManager.loadBreakPoint(this); +// if (schedulerPoint != null) { +// params = new HashMap<>(1); +// params.put(SchedulerPoint.class.getName(), schedulerPoint); +// } +// } +// +// method.invoke(beanContext, params, null, null, null); +// +// if (hasPointParam) { +// jobManager.saveBreakPoint(this, schedulerPoint); +// } +// } catch (InvocationTargetException e) { +// if (e.getCause() != null) { +// log.error(e.getCause().getMessage(), e.getCause()); +// } else { +// log.error(e.getMessage(), e); +// } +// } catch (Exception e) { +// log.error(e.getMessage(), e); +// } +// } + + public abstract AbstractJobQueue createJobQueue(AbstractJobExecutor jobManager); + + public String getName() { + return this.method.fullName(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJobExecutor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJobExecutor.java new file mode 100644 index 0000000..9a0aaa4 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJobExecutor.java @@ -0,0 +1,85 @@ +package cc.smtweb.framework.core.mvc.scheduler; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.util.DateUtil; +import cc.smtweb.framework.core.util.JsonUtil; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +public abstract class AbstractJobExecutor { + @Getter + private IBeanContext beanContext; + private ExecutorService executor; + protected Long redisLockValue; + private DbEngine dbEngine; + protected final Map jobMap = new HashMap<>(); + + public AbstractJobExecutor(IBeanContext beanContext, ExecutorService executor, Long redisLockValue, DbEngine dbEngine) { + this.beanContext = beanContext; + this.executor = executor; + this.redisLockValue = redisLockValue; + this.dbEngine = dbEngine; + } + + public abstract boolean tryLock(AbstractJob job); + + public abstract void unlock(AbstractJob job); + + // 执行任务 + public void execute(final AbstractJobQueue jobQueue, final AbstractJob job) { + this.executor.submit(() -> { + if (tryLock(job)) { + try { + jobQueue.execute(job); + } finally { + unlock(job); + } + } + }); + } + + void saveBreakPoint(AbstractJob job, SchedulerPoint schedulerPoint) { + if (schedulerPoint != null && schedulerPoint.changed) { + String value = JsonUtil.encodeString(schedulerPoint.value); + long now = DateUtil.nowDateTimeLong(); + if (schedulerPoint.insert) { + dbEngine.update("insert into sw_user.sch_break_point(bp_id, bp_job_name, bp_machine_id, bp_value, bp_create_time, bp_last_time) values(?,?,?,?,?,?)", + dbEngine.nextId(), job.getName(), this.redisLockValue, value, now, now); + } else { + dbEngine.update("update sw_user.sch_break_point set bp_machine_id=?, bp_value=?, bp_last_time=? where bp_job_name=?", + this.redisLockValue, value, now, job.getName()); + } + } + } + + SchedulerPoint loadBreakPoint(AbstractJob job) { + String value = dbEngine.queryString("select bp_value from sw_user.sch_break_point where bp_job_name=?", job.getName()); + + SchedulerPoint result; + if (value != null) { + SwMap swMap = JsonUtil.parse(value, SwMap.class); + result = new SchedulerPoint(swMap); + } else { + result = new SchedulerPoint(); + } + + return result; + } + + AbstractJobQueue initJobQueue(AbstractJob job) { + String key = job.getKey(); + + AbstractJobQueue jobQueue = jobMap.get(key); + if (jobQueue == null) { + jobQueue = job.createJobQueue(this); + jobMap.put(key, jobQueue); + } + + return jobQueue; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJobQueue.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJobQueue.java new file mode 100644 index 0000000..2c71c4f --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/AbstractJobQueue.java @@ -0,0 +1,73 @@ +package cc.smtweb.framework.core.mvc.scheduler; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Job执行队列 + * @author xkliu + */ +@Slf4j +public abstract class AbstractJobQueue { + private AtomicLong lastTime = new AtomicLong(); + private ReentrantLock lock = new ReentrantLock(); + @Getter + private AbstractJobExecutor jobExecutor; + + public AbstractJobQueue(AbstractJobExecutor jobExecutor) { + this.jobExecutor = jobExecutor; + } + + public void execute(AbstractJob job) { + try { + // 组织断点对象参数 + Map params = null; + SchedulerPoint schedulerPoint = null; + if (job.hasPointParam) { + schedulerPoint = jobExecutor.loadBreakPoint(job); + if (schedulerPoint != null) { + params = new HashMap<>(1); + params.put(SchedulerPoint.class.getName(), schedulerPoint); + } + } + + job.method.invoke(jobExecutor.getBeanContext(), params, null, null); + + if (job.hasPointParam) { + jobExecutor.saveBreakPoint(job, schedulerPoint); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + } + + public long getLastTime() { + return lastTime.get(); + } + + protected boolean tryLock() { + boolean result = lock.tryLock(); + + if (result) { + lastTime.set(System.currentTimeMillis()); + } + + return result; + } + + protected void unlock() { + lastTime.set(0); + lock.unlock(); + } + + public void updateLastTime() { + lastTime.set(System.currentTimeMillis()); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/CronTimerTask.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/CronTimerTask.java new file mode 100644 index 0000000..4184df9 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/CronTimerTask.java @@ -0,0 +1,30 @@ +package cc.smtweb.framework.core.mvc.scheduler; + +import com.serotonin.timer.TimerTask; +import com.serotonin.timer.TimerTrigger; +import lombok.extern.slf4j.Slf4j; + + +/** + * 包装定时器任务 + * + * @author xkliu + * + */ +@Slf4j +public class CronTimerTask extends TimerTask { + private final AbstractJobQueue jobQueue; + private AbstractJob job; + + CronTimerTask(TimerTrigger timerTrigger, AbstractJobQueue jobQueue, AbstractJob job) { + super(timerTrigger); + this.jobQueue = jobQueue; + this.job = job; + } + + @Override + public void run(long time) { + // 开启线程执行 + jobQueue.getJobExecutor().execute(jobQueue, job); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/FixedTimerTask.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/FixedTimerTask.java new file mode 100644 index 0000000..7f33937 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/FixedTimerTask.java @@ -0,0 +1,19 @@ +package cc.smtweb.framework.core.mvc.scheduler; + +import com.serotonin.timer.FixedRateTrigger; +import com.serotonin.timer.TimerTask; + +public class FixedTimerTask extends TimerTask { + private final Runnable job; + + // new TimerTask(new FixedRateTrigger(30, 60)), result.redisJobManager + public FixedTimerTask(int delay, int period, Runnable job) { + super(new FixedRateTrigger(delay, period)); + this.job = job; + } + + @Override + public void run(long runtime) { + job.run(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/SchedulerPoint.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/SchedulerPoint.java new file mode 100644 index 0000000..fda4d9e --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/SchedulerPoint.java @@ -0,0 +1,40 @@ +package cc.smtweb.framework.core.mvc.scheduler; + +import cc.smtweb.framework.core.SwMap; + +/** + * 定时器断点数据 + * @author xkliu + */ +public class SchedulerPoint { + SwMap value; + boolean changed; + boolean insert; + + public SchedulerPoint() { + insert = true; + value = new SwMap(); + } + + public SchedulerPoint(SwMap value) { + this.value = value; + } + + public Object get(String key) { + return value.get(key); + } + + public Long readLong(String key) { + return value.readLong(key); + } + public Long readLong(String key, Long defaultValue) { + return value.readLong(key, defaultValue); + } + + public SchedulerPoint put(String key, Object value) { + this.value.put(key, value); + this.changed = true; + + return this; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/SchedulerTaskManager.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/SchedulerTaskManager.java new file mode 100644 index 0000000..cedf460 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/SchedulerTaskManager.java @@ -0,0 +1,97 @@ +package cc.smtweb.framework.core.mvc.scheduler; + +import cc.smtweb.framework.core.annotation.SwScheduling; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.mvc.ISchedulerWakeup; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.access.SchedulerMethodAccess; +import cc.smtweb.framework.core.mvc.scheduler.job.GroupJob; +import cc.smtweb.framework.core.mvc.scheduler.job.LocalJobExecutor; +import cc.smtweb.framework.core.mvc.scheduler.job.RedisJobExecutor; +import cc.smtweb.framework.core.mvc.scheduler.job.SimpleJob; +import cc.smtweb.framework.core.redis.RedisManager; +import com.serotonin.timer.RealTimeTimer; +import com.serotonin.timer.TimerTask; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class SchedulerTaskManager implements ISchedulerWakeup { + private final ExecutorService executor; + private final RealTimeTimer timer; + private final Map timerTaskMap = new HashMap<>(); + + private SchedulerTaskManager() { + int poolSize = Runtime.getRuntime().availableProcessors(); + this.executor = new ThreadPoolExecutor(2, poolSize * 2 + 2, 5, TimeUnit.MINUTES, + new SynchronousQueue<>(), + new BasicThreadFactory.Builder().namingPattern("sw-scheduler-").build()); + + // 保证2个线程就够了,1个触发定时器,1个定时同步redis状态 + this.timer = new RealTimeTimer(); + this.timer.init(1, 4); + } + + public static SchedulerTaskManager build(IBeanContext beanContext, List tasks) { + if (!tasks.isEmpty()) { + SchedulerTaskManager result = new SchedulerTaskManager(); + + DbEngine dbEngine = beanContext.getBean(DbEngine.class); + RedisManager redisManager = beanContext.getBean(RedisManager.class); + Long redisLockValue = dbEngine.nextId(); + + LocalJobExecutor localJobExecutor = new LocalJobExecutor(beanContext, result.executor, redisLockValue, dbEngine); + RedisJobExecutor redisJobExecutor = new RedisJobExecutor(beanContext, result.executor, redisLockValue, dbEngine, redisManager); + + for (SchedulerMethodAccess schedulerMethodAccess: tasks) { + SwScheduling swScheduling = schedulerMethodAccess.getSwScheduling(); + + AbstractJob job; + String group = swScheduling.group(); + AbstractJobExecutor jobManager = swScheduling.multiServer() ? localJobExecutor : redisJobExecutor; + + if (StringUtils.isNotBlank(group)) { + job = new GroupJob(group, schedulerMethodAccess.getMethodAccess()); + } else { + job = new SimpleJob(schedulerMethodAccess.getMethodAccess()); + } + + AbstractJobQueue jobQueue = jobManager.initJobQueue(job); + CronTimerTask task = new CronTimerTask(schedulerMethodAccess.getTimerTrigger(), jobQueue, job); + + result.timer.schedule(task); + result.timerTaskMap.put(job.getName(), task); + } + + int time = RedisJobExecutor.TIME_CHECK_SEC * 1000; + result.timer.schedule(new FixedTimerTask(time, time, redisJobExecutor)); + + return result; + } + + return null; + } + + public void shutdown() { + this.timer.cancel(); + this.executor.shutdown(); + } + + @Override + public boolean wakeup(Class clazz, String methodName) { + TimerTask task = timerTaskMap.get(clazz.getName() + "." + methodName); + if (task != null) { + task.run(System.currentTimeMillis()); + return true; + } + + return false; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/GroupJob.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/GroupJob.java new file mode 100644 index 0000000..189574b --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/GroupJob.java @@ -0,0 +1,19 @@ +package cc.smtweb.framework.core.mvc.scheduler.job; + +import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJob; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJobExecutor; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJobQueue; +import cc.smtweb.framework.core.redis.RedisManager; + +public class GroupJob extends AbstractJob { + @Override + public AbstractJobQueue createJobQueue(AbstractJobExecutor jobManager) { + return new GroupJobQueue(jobManager); + } + + public GroupJob(String group, MethodAccess methodAccess) { + super(methodAccess); + initKey(RedisManager.PREFIX_TIMER + '.' + group); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/GroupJobQueue.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/GroupJobQueue.java new file mode 100644 index 0000000..2fd9a56 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/GroupJobQueue.java @@ -0,0 +1,82 @@ +package cc.smtweb.framework.core.mvc.scheduler.job; + +import cc.smtweb.framework.core.mvc.scheduler.AbstractJob; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJobExecutor; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJobQueue; + +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +public class GroupJobQueue extends AbstractJobQueue { + private Deque queue; + private Set keySet; + private String currentJobName; + + public GroupJobQueue(AbstractJobExecutor jobManager) {super(jobManager);} + + private synchronized void setCurrent(AbstractJob job) { + currentJobName = job.getName(); + } + + private synchronized AbstractJob pull() { + AbstractJob result = null; + + if (queue != null) { + result = queue.pop(); + + if (result != null) { + keySet.remove(result.getName()); + } + } + + if (result != null) { + this.currentJobName = result.getName(); + } else { + this.currentJobName = null; + } + + return result; + } + + private synchronized boolean push(AbstractJob job) { + String jobName = job.getName(); + + if (jobName.equals(currentJobName)) { + return false; + } + + if (keySet == null) { + keySet = new HashSet<>(); + } else if (keySet.contains(jobName)) { + // 避免重复加入 + return false; + } + + keySet.add(jobName); + + if (queue == null) { + queue = new LinkedList<>(); + } + + queue.push(job); + return true; + } + + @Override + public void execute(AbstractJob job) { + if (tryLock()) { + try { + setCurrent(job); + do { + super.execute(job); + } while((job = pull()) != null); + } finally { + unlock(); + } + } else { + push(job); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/LocalJobExecutor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/LocalJobExecutor.java new file mode 100644 index 0000000..d085307 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/LocalJobExecutor.java @@ -0,0 +1,23 @@ +package cc.smtweb.framework.core.mvc.scheduler.job; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJob; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJobExecutor; + +import java.util.concurrent.ExecutorService; + +public class LocalJobExecutor extends AbstractJobExecutor { + public LocalJobExecutor(IBeanContext beanContext, ExecutorService executor, Long redisLockValue, DbEngine dbEngine) { + super(beanContext, executor, redisLockValue, dbEngine); + } + + @Override + public boolean tryLock(AbstractJob job) { + return true; + } + + @Override + public void unlock(AbstractJob job) { + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/RedisJobExecutor.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/RedisJobExecutor.java new file mode 100644 index 0000000..1251de5 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/RedisJobExecutor.java @@ -0,0 +1,70 @@ +package cc.smtweb.framework.core.mvc.scheduler.job; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJob; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJobExecutor; +import cc.smtweb.framework.core.redis.RedisManager; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.locks.ReentrantLock; + +@Slf4j +public class RedisJobExecutor extends AbstractJobExecutor implements Runnable { + public static final int TIME_CHECK_SEC = 30; + public static final int TIME_LOOK_SEC = 3 * TIME_CHECK_SEC; + + private ReentrantLock lock = new ReentrantLock(); + private RedisManager redisManager; + + public RedisJobExecutor(IBeanContext beanContext, ExecutorService executor, Long redisLockValue, DbEngine dbEngine, RedisManager redisManager) { + super(beanContext, executor, redisLockValue, dbEngine); + this.redisManager = redisManager; + } + + @Override + public boolean tryLock(AbstractJob job) { + // redis检查, 同group需要排队等待执行\ + String key = job.getKey(); + + boolean redisLocked = redisManager.setnx(key, redisLockValue, TIME_LOOK_SEC); + + if (!redisLocked) { + Long oldLockedValue = redisManager.get(key, Long.class); + if (!redisLockValue.equals(oldLockedValue)) { + return false; + } + } + + return redisLocked; + } + + @Override + public void unlock(AbstractJob job) { + redisManager.del(job.getKey()); + } + + @Override + public void run() { + if (lock.tryLock()) { + try { + // 定时更新redis锁定状态 + long now = System.currentTimeMillis(); + jobMap.forEach((key, value) -> { + long lastTime = value.getLastTime(); + + if (lastTime > 0 && now - lastTime >= TIME_CHECK_SEC) { + if (redisManager.set(key, redisLockValue, TIME_LOOK_SEC)) { + value.updateLastTime(); + } else { + log.error("update redis lock failed, key: " + key); + } + } + }); + } finally { + lock.unlock(); + } + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/SimpleJob.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/SimpleJob.java new file mode 100644 index 0000000..ec38b04 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/SimpleJob.java @@ -0,0 +1,19 @@ +package cc.smtweb.framework.core.mvc.scheduler.job; + +import cc.smtweb.framework.core.mvc.controller.access.MethodAccess; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJob; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJobExecutor; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJobQueue; +import cc.smtweb.framework.core.redis.RedisManager; + +public class SimpleJob extends AbstractJob { + public SimpleJob(MethodAccess methodAccess) { + super(methodAccess); + initKey(RedisManager.PREFIX_TIMER + methodAccess.fullName()); + } + + @Override + public AbstractJobQueue createJobQueue(AbstractJobExecutor jobManager) { + return new SimpleJobQueue(jobManager); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/SimpleJobQueue.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/SimpleJobQueue.java new file mode 100644 index 0000000..e0de2d1 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/mvc/scheduler/job/SimpleJobQueue.java @@ -0,0 +1,22 @@ +package cc.smtweb.framework.core.mvc.scheduler.job; + +import cc.smtweb.framework.core.mvc.scheduler.AbstractJob; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJobExecutor; +import cc.smtweb.framework.core.mvc.scheduler.AbstractJobQueue; + +public class SimpleJobQueue extends AbstractJobQueue { + public SimpleJobQueue(AbstractJobExecutor jobManager) { + super(jobManager); + } + + @Override + public void execute(AbstractJob job) { + if (tryLock()) { + try { + super.execute(job); + } finally { + unlock(); + } + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisBroadcastEvent.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisBroadcastEvent.java new file mode 100644 index 0000000..4294b5a --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisBroadcastEvent.java @@ -0,0 +1,24 @@ +package cc.smtweb.framework.core.redis; + +import lombok.Data; + +/** + * redis 广播消息 + */ +@Data +public class RedisBroadcastEvent { + public static final int CODE_REMOVE = 1; + public static final int CODE_CLEAR = 2; + /** 缓存更新 */ + public static final int CODE_CACHE_UPDATE = 3; + public static final int CODE_CACHE_REFRESH = 4; + + private int action; + private String ident; + private String key; + + @Override + public String toString() { + return "ident=" + ident + "&&key=" + key + "&&act=" + action; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisConnection.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisConnection.java new file mode 100644 index 0000000..a493769 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisConnection.java @@ -0,0 +1,300 @@ +package cc.smtweb.framework.core.redis; + +import cc.smtweb.framework.core.util.CommUtil; +import io.lettuce.core.KeyValue; +import io.lettuce.core.SetArgs; +import io.lettuce.core.api.sync.RedisCommands; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author kevin + */ +@Slf4j +public class RedisConnection { + public static final int DEL_COUNT = 200; + private RedisCommands redis; + + public RedisConnection(RedisCommands redisCommands) { + this.redis = redisCommands; + } + + /** + * 获取指定key对应的值,并更新超时时间 + * + * @param key 关键字KEY + * @return byte[] 对象 + */ + public byte[] get(String key) { + byte[] binKey = getBytes(key); + return redis.get(binKey); + } + +// public T get(String key, Class clazz) { +// byte[] b = get(key); +// if (b != null) { +// return this.readObject(b, clazz); +// } +// +// return null; +// } + + public boolean expire(String key, int expireSec) { + byte[] bkey = getBytes(key); + return redis.expire(bkey, expireSec); + } + + /** + * 设置指定key对应的值,并更新超时时间 + * + * @param key 关键字KEY + * @param value 值 + * @param seconds 超时删除时间(秒) + * @return boolean 是否设置成功 + */ + public boolean set(byte[] key, byte[] value, int seconds) { + if (value != null) { + Boolean result = Boolean.FALSE; + String ret; + if (seconds > 0) { + ret = redis.setex(key, seconds, value); + } else { + ret = redis.set(key, value); + } + + if ("OK".equals(ret)) { + result = Boolean.TRUE; + } + + return result; + } else { + Long ret = redis.del(key); + + return (ret != null && ret == 1L); + } + } + + public boolean setnx(byte[] key, byte[] value, int seconds) { + if (value != null) { + Boolean result = Boolean.FALSE; + String ret; + if (seconds > 0) { + ret = redis.set(key, value, new SetArgs().ex(seconds).nx()); + if ("OK".equals(ret)) { + result = Boolean.TRUE; + } + } else { + result = redis.setnx(key, value); + } + + return result; + } else { + Long ret = redis.del(key); + + return (ret != null && ret == 1L); + } + } + + public boolean setnx(String key, T obj, int seconds) { + return setnx(getBytes(key), CommUtil.writeObject(obj), seconds); + } + + private byte[] getBytes(String key) { + return key.getBytes(StandardCharsets.UTF_8); + } + + /** + * 获取指定key对应的值,并更新超时时间 + * + * @param keys 关键字KEY + * @return byte[] 对象 + */ + public List> mget(String[] keys, int expireSec) { + byte[][] bkeys = new byte[keys.length][]; + for (int i = 0; i < keys.length; i++) { + bkeys[i] = getBytes(keys[i]); + } + + List> result = redis.mget(bkeys); + + if (result != null && expireSec > 0) { + redis.expire(bkeys[0], expireSec); + } + + return result; + } + + /** + * 获取指定key里指定的域对应的值。 此时,key对应的存储对象是map。 + * + * @param key redis关键字KEY + * @param field map的KEY值 + * @return byte[] 对象 + */ + public byte[] hGet(String key, String field) { + return redis.hget(getBytes(key), getBytes(field)); + } + + public List hVals(String key) { + return redis.hvals(getBytes(key)); + } + + public boolean exists(String key) { + return redis.exists(getBytes(key)) == 1; + } + + static byte[] writeObject(Serializable obj) { + return CommUtil.writeObject(obj); + } + + static T readObject(byte[] value, Class clazz) { + return CommUtil.readObject(value, clazz); + } + + public boolean set(String key, T obj, int seconds) { + return set(getBytes(key), CommUtil.writeObject(obj), seconds); + } + + public boolean del(String key) { + Long ret = redis.del(getBytes(key)); + return (ret != null && ret > 0); + } + + public Long delKeys(String... keys) { +// Long[] result = new Long[keys.length]; + + byte[][] batchKey = new byte[keys.length][]; + + for (int i = 0; i < keys.length; i++) { + batchKey[i] = getBytes(keys[i]); + } + + return redis.del(batchKey); + } + + public boolean hSet(String key, byte[] field, byte[] value) { + return redis.hset(getBytes(key), field, value); + } + + private boolean lhmSet(String key, Map values) { + String ret = redis.hmset(getBytes(key), values); + return "OK".equals(ret); + } + + public boolean hmSet(String key, Map values) { + boolean ret = true; + if (values == null || values.isEmpty()) { + return ret; + } + final int size = 200; + Map map = new HashMap<>(size); + int count = 0; + for (Map.Entry e : values.entrySet()) { + map.put(getBytes(e.getKey()), CommUtil.writeObject(e.getValue())); + if (++count >= size) { + ret = ret && lhmSet(key, map); + count = 0; + map.clear(); + } + } + if (count > 0) { + ret = ret && lhmSet(key, map); + } + return ret; + } + + /** + * 获取指定key里指定的域对应的值,并转换为指定对象 此时,key对应的存储对象是map。 + * + * @param key redis关键字KEY + * @param field map的KEY值 + * @param clazz 对象存储类型 + * @return T 对象 + */ + public T hGet(String key, String field, Class clazz) { + return CommUtil.readObject(hGet(key, field), clazz); + } + + public boolean hSet(String key, String field, Serializable value) { + return hSet(key, getBytes(field), CommUtil.writeObject(value)); + } + + public long hdel(String key, String field) { + return redis.hdel(getBytes(key), getBytes(field)); + } + + public Long ttl(String key) { + return redis.ttl(getBytes(key)); + } + + public T get(String key, Class clazz) { + return CommUtil.readObject(get(key), clazz); + } + + public Map hGetAll(String key) { + return redis.hgetall(getBytes(key)); + } + + /** + * 模糊查询keys,影响性能,谨慎使用 + * + * @param keyLike 支持*表达模糊搜索 + * @return 查询到的列表 + */ + public List matchKeys(String keyLike) { + List list = redis.keys(getBytes(keyLike)); + if (list != null && !list.isEmpty()) { + List result = new ArrayList<>(list.size()); + for (byte[] b : list) { + result.add(new String(b, StandardCharsets.UTF_8)); + } + return result; + } + + return null; + } + + /** + * 模糊删除keys,影响性能,谨慎使用 + * + * @param keyLike 支持*表达模糊搜索 + * @return 成功删除的条数 + */ + public Long delMatchKeys(String keyLike) { + long result = 0; + List list = redis.keys(getBytes(keyLike)); + if (list != null) { + int size = list.size(); + + for (int i = 0; i < size; i += DEL_COUNT) { + int len = DEL_COUNT; + if (i + len > size) { + len = size - i; + } + + Long ret = delKeys(list, i, len); + if (ret != null) { + result += ret; + } + } + } + + return result; + } + + private Long delKeys(List keys, int start, int len) { + byte[][] batchKey = new byte[len][]; + + for (int i = 0; i < len; i++) { + batchKey[i] = keys.get(i + start); + } + + return redis.del(batchKey); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisManager.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisManager.java new file mode 100644 index 0000000..ece6279 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisManager.java @@ -0,0 +1,221 @@ +package cc.smtweb.framework.core.redis; + +import cc.smtweb.framework.core.SwException; +import cc.smtweb.framework.core.systask.SysTaskManager; +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.framework.core.util.SpringUtil; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; +import io.lettuce.core.pubsub.api.sync.RedisPubSubCommands; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; + +import javax.annotation.PreDestroy; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * Redis管理器 + * + * @author kevin + */ +@Slf4j +public class RedisManager implements DisposableBean { + // SESSION + public static final String PREFIX_SESSION = "SID"; + // 定时器锁 + public static final String PREFIX_TIMER = "TIM"; + /** + * UserSession 超时时间 + */ + public static final int SESSION_EXPIRE_SEC = 30 * 60; + /** + * 订阅发布的主题 + */ + private static final String SCRIBE_SYSTEM = "SW_SYSTEM_"; +// private static final String SCRIBE_CACHED = "SW_CACHED"; + + private final RedisSysTask redisSysTask; + private RedisClient redisClient; + private GenericObjectPool> pool; + // 定义通道名称 + private final String channel; + + public static RedisManager getInstance() { + return SpringUtil.getBean(RedisManager.class); + } + + /** + * 初始化Redis连接池 + */ + public RedisManager(final ApplicationContext applicationContext, SysTaskManager sysTaskManager, RedisURI redisUri) { +// this.applicationContext = applicationContext; + redisClient = RedisClient.create(redisUri); + channel = SCRIBE_SYSTEM + redisUri.getDatabase() + "_" + redisUri.getClientName(); + + GenericObjectPoolConfig config = new GenericObjectPoolConfig(); + config.setTestWhileIdle(true); + pool = new GenericObjectPool<>(new RedisPooledObjectFactory(redisClient), config); + +// pool = ConnectionPoolSupport.createGenericObjectPool( +// () -> redisClient.connect(ByteArrayCodec.INSTANCE), +// new GenericObjectPoolConfig(), false); + + redisSysTask = new RedisSysTask(applicationContext, redisClient, SCRIBE_SYSTEM + redisUri.getDatabase() + "_*", channel); + sysTaskManager.add(redisSysTask); + } + + @PreDestroy + public void shutdown() { + pool.close(); + redisSysTask.close(); + redisClient.shutdown(); + } + + /** + * 执行redis命令 + */ + public T command(Function handler) { +// StatefulRedisConnection connection = redisClient.connect(ByteArrayCodec.INSTANCE); + + StatefulRedisConnection connection = null; + try { + connection = pool.borrowObject(); + } catch (Exception e) { + throw new SwException(e); + } + + try { + RedisCommands redisCommands = connection.sync(); + RedisConnection redis = new RedisConnection(redisCommands); + return handler.apply(redis); + } finally { + pool.returnObject(connection); + } + } + + public boolean expire(String key, int expireSec) { + return command(redis -> { + return redis.expire(key, expireSec); + }); + } + + /** + * 设置指定key对应的值,并更新超时时间 + * + * @param key 关键字KEY + * @param value 值 + * @param seconds 超时删除时间(秒) + * @return boolean 是否设置成功 + */ + public boolean set(byte[] key, byte[] value, int seconds) { + return command(redis -> redis.set(key, value, seconds)); + } + + public boolean setnx(byte[] key, byte[] value, int seconds) { + return command(redis -> redis.setnx(key, value, seconds)); + } + + public boolean setnx(String key, T obj, int seconds) { + return command(redis -> redis.setnx(key, obj, seconds)); + } + + public boolean set(String key, T obj, int seconds) { + return command(redis -> redis.set(key, obj, seconds)); + } + + public boolean del(String key) { + return command(redis -> redis.del(key)); + } + + public Long delKeys(String... keys) { + return command(redis -> redis.delKeys(keys)); + } + + public boolean hSet(String key, byte[] field, byte[] value) { + return command(redis -> redis.hSet(key, field, value)); + } + + + public boolean hmSet(String key, Map values) { + return command(redis -> redis.hmSet(key, values)); + } + + /** + * 获取指定key里指定的域对应的值,并转换为指定对象 此时,key对应的存储对象是map。 + * + * @param key redis关键字KEY + * @param field map的KEY值 + * @param clazz 对象存储类型 + * @return T 对象 + */ + public T hGet(String key, String field, Class clazz) { + return command(redis -> redis.hGet(key, field, clazz)); + } + + public List hVals(String key) { + return command(redis -> redis.hVals(key)); + } + + public boolean hSet(String key, String field, Serializable value) { + return command(redis -> redis.hSet(key, field, value)); + } + + public long hdel(String key, String field) { + return command(redis -> redis.hdel(key, field)); + } + + public Long ttl(String key) { + return command(redis -> redis.ttl(key)); + } + + public boolean exists(String key) { + return command(redis -> redis.exists(key)); + } + + // 发布系统消息 + public void publish(RedisBroadcastEvent message) { + try (StatefulRedisPubSubConnection connection = redisClient.connectPubSub()) { + RedisPubSubCommands sync = connection.sync(); + sync.publish(channel, JsonUtil.encodeString(message)); + } + } + + public Map hGetAll(String key) { + return command(redis -> redis.hGetAll(key)); + } + + public T get(String key, Class clazz) { + return command(redis -> redis.get(key, clazz)); + } + + public List matchKeys(String keyLike) { + return command(redis -> redis.matchKeys(keyLike)); + } + + public Long delMatchKeys(String keyLike) { + return command(redis -> redis.delMatchKeys(keyLike)); + } + + public byte[] writeObject(Serializable entity) { + return RedisConnection.writeObject(entity); + } + + public T readObject(byte[] value, Class clazz) { + return RedisConnection.readObject(value, clazz); + } + + @Override + public void destroy() throws Exception { + this.shutdown(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisPooledObjectFactory.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisPooledObjectFactory.java new file mode 100644 index 0000000..d53eda7 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisPooledObjectFactory.java @@ -0,0 +1,39 @@ +package cc.smtweb.framework.core.redis; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.codec.ByteArrayCodec; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.PooledObjectFactory; +import org.apache.commons.pool2.impl.DefaultPooledObject; + +class RedisPooledObjectFactory implements PooledObjectFactory> { + private RedisClient redisClient; + + public RedisPooledObjectFactory(RedisClient redisClient) { + this.redisClient = redisClient; + } + + @Override + public void activateObject(PooledObject> pooledObject) throws Exception { + } + + @Override + public void destroyObject(PooledObject> pooledObject) throws Exception { + pooledObject.getObject().close(); + } + + @Override + public PooledObject> makeObject() throws Exception { + return new DefaultPooledObject<>(redisClient.connect(ByteArrayCodec.INSTANCE)); + } + + @Override + public void passivateObject(PooledObject> pooledObject) throws Exception { + } + + @Override + public boolean validateObject(PooledObject> pooledObject) { + return pooledObject.getObject().isOpen(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisSysTask.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisSysTask.java new file mode 100644 index 0000000..518d170 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/RedisSysTask.java @@ -0,0 +1,61 @@ +package cc.smtweb.framework.core.redis; + +import cc.smtweb.framework.core.systask.ISysTask; +import cc.smtweb.framework.core.util.JsonUtil; +import io.lettuce.core.RedisClient; +import io.lettuce.core.pubsub.RedisPubSubAdapter; +import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; +import io.lettuce.core.pubsub.api.sync.RedisPubSubCommands; +import org.springframework.context.ApplicationContext; + +public class RedisSysTask implements ISysTask { + private ApplicationContext applicationContext; + private StatefulRedisPubSubConnection connection; + private RedisClient redisClient; + //订阅消息的channel,一般带通配符 + private String scribeChannel; + //自己的唯一标记,自己发的消息不处理 + private String selfChannel; + + public RedisSysTask(ApplicationContext applicationContext, RedisClient redisClient, String scribeChannel, String selfChannel) { + this.applicationContext = applicationContext; + this.redisClient = redisClient; + this.scribeChannel = scribeChannel; + this.selfChannel = selfChannel; + } + + @Override + public int run() { + // 非集群模式下的发布订阅 + if (connection == null) { + connection = redisClient.connectPubSub(); + + if (connection != null) { + connection.addListener(new RedisPubSubAdapter() { + @Override + public void message(String channel, String message) { + if (channel.equalsIgnoreCase(selfChannel)) return; + RedisBroadcastEvent redisBroadcast = JsonUtil.parse(message, RedisBroadcastEvent.class); + + applicationContext.publishEvent(redisBroadcast); + +// log.debug("Redis [" + channel + "] message:" + redisBroadcast); + } + }); + + // 订阅 + RedisPubSubCommands sync = connection.sync(); + sync.subscribe(scribeChannel); + } + } + + return 0; + } + + public void close() { + if (connection != null) { + connection.close(); + connection = null; + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/config/RedisConfig.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/config/RedisConfig.java new file mode 100644 index 0000000..390aa64 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/redis/config/RedisConfig.java @@ -0,0 +1,65 @@ +package cc.smtweb.framework.core.redis.config; + + +import cc.smtweb.framework.core.redis.RedisManager; +import cc.smtweb.framework.core.systask.SysTaskManager; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 〈redis配置〉 + * + * @author kevin + * @since 1.0.0 + */ +@Configuration +public class RedisConfig { + @Value("${spring.redis.name:127.0.0.1}") + private String clientName; + + @Value("${spring.redis.host:127.0.0.1}") + private String redisHost; + + @Value("${spring.redis.port:6379}") + private int redisPort; + + @Value("${spring.redis.username:}") + private String redisUserName; + + @Value("${spring.redis.password:}") + private String redisPassword; + + @Value("${spring.redis.database:}") + private String redisDb; + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private SysTaskManager sysTaskManager; + + @Bean + public RedisManager redisManager() { + RedisURI redisUri = RedisURI.create (redisHost, redisPort); + if (StringUtils.isNotBlank(clientName)) { + redisUri.setClientName(clientName); + } + if (StringUtils.isNotBlank(redisDb)) { + redisUri.setDatabase(Integer.parseInt(redisDb)); + } + if (StringUtils.isNotBlank(redisUserName)) { + redisUri.setUsername(redisUserName.trim()); + } + if (StringUtils.isNotBlank(redisPassword)) { + redisUri.setPassword((CharSequence)redisPassword.trim()); + } + + return new RedisManager(applicationContext, sysTaskManager, redisUri); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/SessionManager.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/SessionManager.java new file mode 100644 index 0000000..bfae9d5 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/SessionManager.java @@ -0,0 +1,73 @@ +package cc.smtweb.framework.core.session; + +import cc.smtweb.framework.core.db.jdbc.IdGenerator; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.framework.core.redis.RedisBroadcastEvent; +import cc.smtweb.framework.core.redis.RedisManager; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.util.Random; + +public class SessionManager { + private final RedisManager redisManager; + private final IdGenerator idGenerator; + + public SessionManager(RedisManager redisManager, IdGenerator idGenerator) { + this.redisManager = redisManager; + this.idGenerator = idGenerator; + } + + // 登录,产生session + public String login(UserSession userSession) { + if (userSession != null) { + String sid = RedisManager.PREFIX_SESSION + Long.toHexString(idGenerator.nextId()) + "_" + Integer.toHexString(new Random().nextInt()); + + redisManager.set(sid, userSession, RedisManager.SESSION_EXPIRE_SEC); + + return sid; + } + + return null; + } + + public void logout() { + String sid = (String)RequestContextHolder.currentRequestAttributes().getAttribute(IEditor.USER_TOKEN, RequestAttributes.SCOPE_REQUEST); + if (StringUtils.isNoneBlank(sid)) { + redisManager.del(sid); + } + } + + // token延时 + public boolean flush(String token) { + if (token != null) { + // 更新Token redis TTL + return redisManager.expire(token, RedisManager.SESSION_EXPIRE_SEC); + } + + return false; + } + + /** + * 清除指定用户权限本地缓存 + * @param userId 用户ID + */ + public void removePermits(long userId) { + RedisBroadcastEvent event = new RedisBroadcastEvent(); + event.setAction(RedisBroadcastEvent.CODE_REMOVE); + event.setIdent("ReamlCache"); + event.setKey(userId + ""); + redisManager.publish(event); + } + + /** + * 清除所有用户权限本地缓存 + */ + public void clearPermits() { + RedisBroadcastEvent event = new RedisBroadcastEvent(); + event.setAction(RedisBroadcastEvent.CODE_CLEAR); + event.setKey("ReamlCache"); + redisManager.publish(event); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/SessionUtil.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/SessionUtil.java new file mode 100644 index 0000000..6baae17 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/SessionUtil.java @@ -0,0 +1,109 @@ +package cc.smtweb.framework.core.session; + +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.framework.core.mvc.realm.exception.UnauthenticatedException; +import cc.smtweb.framework.core.redis.RedisManager; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; + +import javax.servlet.http.HttpServletRequest; + +/** + * 〈session工具类〉 + * + * @author kevin + * @since 1.0.0 + */ +public class SessionUtil { + private SessionUtil() { + } + + public static Object getSession() { + return RequestContextHolder.currentRequestAttributes().getAttribute(IEditor.USER_SESSION, 0); + } + + public static String readToken(HttpServletRequest request) { + String token = request.getHeader("Auth-Token"); + + if (token == null) { + token = request.getParameter("auth_token"); + } + return token; + } + + public static UserSession checkSession(HttpServletRequest request, RedisManager redisManager) { + String token = readToken(request); + + if (StringUtils.isBlank(token)) { + throw new UnauthenticatedException("not find Auth-Token in header"); + } + + UserSession us = redisManager.get(token, UserSession.class); + if (us == null) { + throw new UnauthenticatedException("not find UserSession by token: " + token); + } + + return us; + } + + + public static UserSession checkSession(String accessToken, RedisManager redisManager) { + if (StringUtils.isBlank(accessToken)) { + throw new UnauthenticatedException("not find Auth-Token in header"); + } + + UserSession us = redisManager.get(accessToken, UserSession.class); + if (us == null) { + throw new UnauthenticatedException("not find UserSession by token: " + accessToken); + } + + return us; + } + +// private static Session getShiroSession() { +// Session session = SecurityUtils.getSubject().getSession(); +// if (session == null) { +// throw new SmartException(ExceptionMessage.NO_LOGIN_ERROR); +// } +// return session; +// } + +// public static String getSid() { +// return getShiroSession().getId().toString(); +// } + +// public static UserSession getSession() { +// Object session = getShiroSession().getAttribute(USER_SESSION); +// if (session == null) { +// throw new SmartException(ExceptionMessage.NO_LOGIN_ERROR); +// } +// UserSession userSession = (UserSession) session; +// if (userSession.isChange()) { +// throw new SmartException(ExceptionMessage.USER_PERMISSION_CHANGE); +// } +// return userSession; +// } +// +// public static void updateSession(UserSession userSession) { +// getShiroSession().setAttribute(USER_SESSION, userSession); +// } + +// public static String login(String userName, UserSession userSession) { +// // 通过 shiro 登录验证 +// Subject subject = SecurityUtils.getSubject(); +// try { +// // 统一默认密码登录 +// subject.login(new UsernamePasswordToken(userName, CommonUtil.INIT_PASSWORD)); +// } catch (ShiroException ex) { +// throw new SmartException(ExceptionMessage.USER_ERROR); +// } +// +// Session session = subject.getSession(); +// if (session == null) { +// throw new SmartException(ExceptionMessage.NO_LOGIN_ERROR); +// } +// +// session.setAttribute(USER_SESSION, userSession); +// return session.getId().toString(); +// } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/UserSession.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/UserSession.java new file mode 100644 index 0000000..a393dd6 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/UserSession.java @@ -0,0 +1,23 @@ +package cc.smtweb.framework.core.session; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +/** + * 用户会话缓存,前端调用API时传输 Auto_Token 参数来查找用户会话 + * @author xkliu + */ +@Getter @Setter +public class UserSession implements Serializable { + private static final long serialVersionUID = 3854315462714888716L; + // 用户ID + private long userId; + // 当前组织ID + private long companyId; + // 站点ID + private long siteId; + // 终端类型 + private byte terminalType; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/UserSessionArgumentResolver.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/UserSessionArgumentResolver.java new file mode 100644 index 0000000..fc9e21c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/session/UserSessionArgumentResolver.java @@ -0,0 +1,26 @@ +package cc.smtweb.framework.core.session; + +import cc.smtweb.framework.core.mvc.controller.IEditor; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * spring 参数拦截器,提供UserSession在@Service的函数参数中@Param使用 + * @author kevin + */ +public class UserSessionArgumentResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter methodParameter) { + return methodParameter.getParameterType().isAssignableFrom(UserSession.class); + } + + @Override + public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { + return nativeWebRequest.getAttribute(IEditor.USER_SESSION, RequestAttributes.SCOPE_REQUEST); +// return SessionUtil.getSession(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/ISysTask.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/ISysTask.java new file mode 100644 index 0000000..cb5d8ec --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/ISysTask.java @@ -0,0 +1,8 @@ +package cc.smtweb.framework.core.systask; + +/** + * 任务接口 + */ +public interface ISysTask { + int run(); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/SysTaskManager.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/SysTaskManager.java new file mode 100644 index 0000000..2a5a5c1 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/SysTaskManager.java @@ -0,0 +1,40 @@ +package cc.smtweb.framework.core.systask; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class SysTaskManager { + @Autowired + private ApplicationContext applicationContext; + + private List tasks = new ArrayList<>(); + + public void add(ISysTask task) { + tasks.add(task); + } + + @EventListener + public void onTaskStartEvent(TaskStartEvent event) { + runAll(); + } + + // 每隔60秒定时执行 +// @Scheduled(fixedRate = 60000) +// public void fixedRateJob() { +// System.out.println("fixedRate 每隔60秒" + new java.util.Date()); +// +// runAll(); +// } + + private void runAll() { + for (ISysTask task : tasks) { + task.run(); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/TaskStartEvent.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/TaskStartEvent.java new file mode 100644 index 0000000..35b8bf0 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/TaskStartEvent.java @@ -0,0 +1,4 @@ +package cc.smtweb.framework.core.systask; + +public class TaskStartEvent { +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/WebStartedEvent.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/WebStartedEvent.java new file mode 100644 index 0000000..cd8bba0 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/systask/WebStartedEvent.java @@ -0,0 +1,4 @@ +package cc.smtweb.framework.core.systask; + +public class WebStartedEvent { +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/CommUtil.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/CommUtil.java new file mode 100644 index 0000000..427c4c9 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/CommUtil.java @@ -0,0 +1,84 @@ +package cc.smtweb.framework.core.util; + +import cc.smtweb.framework.core.db.impl.BaseBean; +import cc.smtweb.framework.core.db.impl.DefaultEntity; +import cc.smtweb.framework.core.util.kryo.KryoTool; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * Created by Akmm at 2022/1/5 9:10 + * 通用工具类 + */ +@Slf4j +public class CommUtil { + + /** + * 找指定类的泛型类 + * @param cls + * @param + * @return + */ + public static Class getParameterizedType(Class cls) { + while (cls != null) { //找到泛型类 + Type pt = cls.getGenericSuperclass(); + if (pt instanceof ParameterizedType) { + return (Class) ((ParameterizedType) pt).getActualTypeArguments()[0]; + } + cls = (Class) pt; + } + return null; + } + + /** + * 序列化对象 + * @param obj + * @param + * @return + */ + public static byte[] writeObject(T obj) { + return KryoTool.getINST().writeObject(obj); + } + + /** + * 反序列化 + * @param data + * @param clazz + * @param + * @return + */ + public static T readObject(byte[] data, Class clazz) { + return KryoTool.getINST().readObject(data, clazz); + } + + public static T cloneObj(T obj, Class cls) { + byte[] data = writeObject(obj); + return readObject(data, cls); + } + + public static boolean isEmpty(Collection c) { + return c == null || c.isEmpty(); + } + + /** + * 从DefaultEntity实体转成自己的对象类 + * @param bean + * @param clazz + * @param + * @return + */ + public static T castEntity(DefaultEntity bean, Class clazz) { + try { + T ret = clazz.newInstance(); + ret.getData().putAll(bean.getData()); + return ret; + } catch (Exception e) { + log.error("转换bean失败:", e); + return null; + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/DateUtil.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/DateUtil.java new file mode 100644 index 0000000..6c55dd3 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/DateUtil.java @@ -0,0 +1,319 @@ +package cc.smtweb.framework.core.util; + +import org.apache.commons.lang3.time.DateUtils; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * 日期工具类 + */ +public class DateUtil { +// private static ThreadLocal stdTimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm:ss")); + private static ThreadLocal stdDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); + private static ThreadLocal stdDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); +// private static ThreadLocal stdLongDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")); + + private static ThreadLocal simpleWeekFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("EEEE")); + private static ThreadLocal simpleTimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("HHmmss")); + private static ThreadLocal simpleDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd")); + private static ThreadLocal simpleDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmss")); + private static ThreadLocal simpleLongDatetimeFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmssSSS")); + + + private DateUtil(){} + + /** + * 获取当前时间 + * @return + */ + public static Date now() { + return new Date(System.currentTimeMillis()); + } + + /** + * 按yyyy-MM-dd格式化 + * @param date + * @return + */ + public static String formatDate(Date date) { + return stdDateFormat.get().format(date); + } + + /** + * 按yyyy-MM-dd HH:mm:ss格式化 + * @param date + * @return + */ + public static String formatDateTime(Date date) { + return stdDatetimeFormat.get().format(date); + } + + /** + * 格式化 + * @param date YYYYMMDD格式 + * @return + */ + public static String formatDate(long date) { + String d = String.valueOf(date); + if(d.length() < 8) return d; //就是2011-03-04 13:11:01这种形式 + return d.substring(0, 4) + "-" + d.substring(4, 6) + "-" + d.substring(6, 8); + } + + public static String formatDateTime(long date) { + String d = String.valueOf(date); + if(d.length() < 8) return d; //就是2011-03-04 13:11:01这种形式 + if(d.length() < 14) return d.substring(0, 4) + "-" + d.substring(4, 6) + "-" + d.substring(6, 8); + return d.substring(0, 4) + "-" + d.substring(4, 6) + "-" + d.substring(6, 8) + " " + d.substring(8, 10) + ":" + d.substring(10, 12) + ":" + d.substring(12); + } + + /** + * 按yyyyMMdd格式化 + * @param date + * @return + */ + public static String formatSimpleDate(Date date) { + return simpleDateFormat.get().format(date); + } + + /** + * 按yyyyMMddHHmmss格式化 + * @param date + * @return + */ + public static String formatSimpleDateTime(Date date) { + return simpleDatetimeFormat.get().format(date); + } + + /** + * 按yyyyMMddHHmmssSSS格式化 + * @param date + * @return + */ + public static String formatSimpleDateTimeS(Date date) { + return simpleLongDatetimeFormat.get().format(date); + } + + /** + * 按yyyyMMdd格式化 + * @param date + * @return + */ + public static long date2Long(Date date) { + return Long.parseLong(formatSimpleDate(date)); + } + + /** + * 按yyyyMMddHHmmss格式化 + * @param date + * @return + */ + public static long dateTime2Long(Date date) { + return Long.parseLong(formatSimpleDateTime(date)); + } + + public static String nowDate() { + return formatDate(now()); + } + + public static String nowDateTime() { + return formatDateTime(now()); + } + + public static long nowDateLong() { + return date2Long(now()); + } + + public static long nowDateTimeLong() { + return dateTime2Long(now()); + } + + // 获得当天0点时间 + public static long getTimesmorning() { + return getTimesmorning(System.currentTimeMillis()); + } + + // 获得指定时间0点时间 + public static long getTimesmorning(long time) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(time); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTimeInMillis(); + } + + // 获得昨天0点时间 + public static long getYesterdaymorning() { + return getTimesmorning() - DateUtils.MILLIS_PER_DAY; + } + + // 获得当天近7天时间 + public static long getWeekFromNow() { + return getTimesmorning() - DateUtils.MILLIS_PER_DAY * 7; + } + + // 获得当天24点时间 + public static long getTimesnight() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 24); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTimeInMillis(); + } + + public static long getTimesnight(long time) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(time); + cal.set(Calendar.HOUR_OF_DAY, 24); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTimeInMillis(); + } + + // 获得本周一0点时间 + public static long getTimesWeekmorning() { + Calendar cal = Calendar.getInstance(); + cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); + cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + return cal.getTimeInMillis(); + } + + // 获得本周日24点时间 + public static long getTimesWeeknight() { + return getTimesWeekmorning() + DateUtils.MILLIS_PER_DAY * 7; + } + + // 获得本月第一天0点时间 + public static long getTimesMonthMorning() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(System.currentTimeMillis()); + cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH)); + return cal.getTimeInMillis(); + } + + // 获得本月最后一天24点时间 + public static long getTimesMontgHight() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(System.currentTimeMillis()); + cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); + cal.set(Calendar.HOUR_OF_DAY, 24); + return cal.getTimeInMillis(); + } + + public static long getLastMonthStartMorning() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(getTimesMonthMorning()); + cal.add(Calendar.MONTH, -1); + return cal.getTimeInMillis(); + } + + public static long getLastMonthStartMorning(long timeInMillis) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(timeInMillis); + cal.add(Calendar.MONTH, -1); + return cal.getTimeInMillis(); + } + + public static Date getCurrentQuarterStartTime() { + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(getTimesMonthMorning()); + int currentMonth = c.get(Calendar.MONTH) + 1; + SimpleDateFormat longSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + SimpleDateFormat shortSdf = new SimpleDateFormat("yyyy-MM-dd"); + Date now = null; + try { + if (currentMonth <= 3) { + c.set(Calendar.MONTH, 0); + } else if (currentMonth <= 6) { + c.set(Calendar.MONTH, 3); + } else if (currentMonth <= 9) { + c.set(Calendar.MONTH, 4); + } else if (currentMonth <= 12) { + c.set(Calendar.MONTH, 9); + } + + c.set(Calendar.DATE, 1); + now = longSdf.parse(shortSdf.format(c.getTime()) + " 00:00:00"); + } catch (Exception e) { + e.printStackTrace(); + } + return now; + } + + /** + * 当前季度的结束时间,即2012-03-31 23:59:59 + * + * @return + */ + public static Date getCurrentQuarterEndTime() { + Calendar cal = Calendar.getInstance(); + cal.setTime(getCurrentQuarterStartTime()); + cal.add(Calendar.MONTH, 3); + return cal.getTime(); + } + + public static long getCurrentYearStartTime() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(getTimesMonthMorning()); + cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.YEAR)); + return cal.getTimeInMillis(); + } + + public static long getCurrentYearEndTime() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(getCurrentYearStartTime()); + cal.add(Calendar.YEAR, 1); + return cal.getTimeInMillis(); + } + + public static long getLastYearStartTime() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(getCurrentYearStartTime()); + cal.add(Calendar.YEAR, -1); + return cal.getTimeInMillis(); + } + + public static long getLastYearStartTime(long timeInMillis) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(timeInMillis); + cal.add(Calendar.YEAR, -1); + return cal.getTimeInMillis(); + } + + public static String parseTimeTag(Date date){ + if(date == null){ + return ""; + } + int now = date2Day(new Date()); + int day = date2Day(date); + int deDay = now - day; +// int deMonth = now/100 - day/100; + int deYear = now/10000 - day/10000; + if(deYear < 1){ + switch(deDay){ + case 0: return new SimpleDateFormat("HH:mm").format(date); + case 1: return "昨天"; + default: + return new SimpleDateFormat("MM-dd").format(date); + } + } + return new SimpleDateFormat("yyyy-MM-dd").format(date); + } + + private static int date2Day(Date date){ + return Integer.parseInt(new SimpleDateFormat("yyyyMMdd").format(date)); + } + + public static void main(String[] args) { + System.out.println(getTimesmorning()); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/IpAddrUtil.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/IpAddrUtil.java new file mode 100644 index 0000000..0bee08f --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/IpAddrUtil.java @@ -0,0 +1,100 @@ +package cc.smtweb.framework.core.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; + +/** + * 〈IP〉工具类 + * + * @author kevin + * @since 1.0.0 + */ +public class IpAddrUtil { + + private IpAddrUtil() { + } + + //指定字符串是否工具类 + public static boolean isIpAddress(String ipAddr) { + if (StringUtils.isEmpty(ipAddr)) { + return false; + } else { + String regTxt = + "\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b"; + return regMatch(ipAddr, regTxt); + } + } + + //正则匹配校验 + private static boolean regMatch(String source, String regTxt) { + Pattern pattern = Pattern.compile(regTxt); + Matcher matcher = pattern.matcher(source); + return matcher.matches(); + } + + //ip转long + public static long ip2Long(String ipAddr) { + long[] ip = new long[4]; + int position1 = ipAddr.indexOf("."); + int position2 = ipAddr.indexOf(".", position1 + 1); + int position3 = ipAddr.indexOf(".", position2 + 1); + ip[0] = Long.parseLong(ipAddr.substring(0, position1)); + ip[1] = Long.parseLong(ipAddr.substring(position1 + 1, position2)); + ip[2] = Long.parseLong(ipAddr.substring(position2 + 1, position3)); + ip[3] = Long.parseLong(ipAddr.substring(position3 + 1)); + return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3]; + } + + //long转ip格式 + public static String long2IP(long ipAddr) { + return (ipAddr >>> 24) + + "." + + ((ipAddr & 16777215L) >>> 16) + + "." + + ((ipAddr & 65535L) >>> 8) + + "." + + (ipAddr & 255L); + } + + //获取当前请求ip,返回long格式 + public static long getLongIp(HttpServletRequest request) { + try { + String ipAddr = getIpAddr(request); + return StringUtils.isBlank(ipAddr) ? 0L : ip2Long(ipAddr); + } catch (Exception var2) { + return 0L; + } + } + + //获取当前请求的ip,返回ip格式 + public static String getIpAddr(HttpServletRequest request) { + if (request == null) { + return ""; + } else { + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + return ip; + } + } +} \ No newline at end of file diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/JsonUtil.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/JsonUtil.java new file mode 100644 index 0000000..1867380 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/JsonUtil.java @@ -0,0 +1,270 @@ +package cc.smtweb.framework.core.util; + +import cc.smtweb.framework.core.exception.JsonParseException; +import cc.smtweb.framework.core.util.jackson.*; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; + +import java.beans.PropertyDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.springframework.beans.BeanUtils.getPropertyDescriptors; + +/** + * json工具类 + */ +@Slf4j +public class JsonUtil { + public final static ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public final static ObjectMapper API_OBJECT_MAPPER = new ObjectMapper(); + + static { + init(OBJECT_MAPPER); + // 不序列化空值 + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); +// OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); +// OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); +// // 设置将MAP转换为JSON时候只转换值不等于NULL的 +// objectMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); + + init(API_OBJECT_MAPPER); + API_OBJECT_MAPPER.getSerializerProvider().setNullValueSerializer(new NullSerializer()); + } + + private static void init(ObjectMapper mapper) { + // JSON转化为对象时忽略未对应的属性 + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + SimpleModule module = new SimpleModule(); + LongSerializer longSerializer = new LongSerializer(); + module.addSerializer(Long.class, longSerializer); + module.addSerializer(Long.TYPE, longSerializer); + + DateSerializer dateSerializer = new DateSerializer(); + module.addSerializer(java.sql.Date.class, dateSerializer); + TimeSerializer timeSerializer = new TimeSerializer(); + module.addSerializer(java.sql.Time.class, timeSerializer); + + module.addDeserializer(Timestamp.class, new TimestampDeserializer()); + module.addDeserializer(java.util.Date.class, new DateDeserializer()); + + mapper.registerModule(module); + } + + public static JsonNode readTree(String body) { + try { + return OBJECT_MAPPER.readTree(body); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + + return null; + } + + // 创建新节点 + public static ObjectNode createObjectNode() { + return OBJECT_MAPPER.createObjectNode(); + } + + // 将 JsonNode 对象转成 json + public static String writeValueAsString(JsonNode newNode) { + try { + return OBJECT_MAPPER.writeValueAsString(newNode); + } catch (JsonProcessingException e) { + log.error(e.getMessage(), e); + } + + return null; + } + + public static T parse(String str, Class clazz) { + try { + if (StringUtils.isBlank(str)) { + return null; + } else { + return OBJECT_MAPPER.readValue(str, clazz); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this json to " + clazz + " type", e); + } + } + + /** + * 将Map对象转换为Bean对象 + * @param map Map对象 + * @param clazz Bean对象类 + * @param Bean对象类型 + * @return Bean对象实例 + */ + public static T parse(Map map, Class clazz) { + try { + if (map == null || map.isEmpty()) { + return null; + } else { + // 对象实例化 + T bean = BeanUtils.instantiateClass(clazz); + // 循环设置对象属性 + PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz); + for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { + String properName = propertyDescriptor.getName(); + // 过滤class属性 + if (!"class".equals(properName)) { + if (map.containsKey(properName)) { + Method writeMethod = propertyDescriptor.getWriteMethod(); + if (writeMethod != null) { + Object value = map.get(properName); + if (!writeMethod.isAccessible()) { + writeMethod.setAccessible(true); + } + writeMethod.invoke(bean, value); + } + } + } + } + + return bean; + } + } catch (Exception e) { + throw new JsonParseException("can't convert map to " + clazz + " type", e); + } + } + + public static T parse(InputStream is, Class clazz) { + try { + if (is == null) { + return null; + } else { + return OBJECT_MAPPER.readValue(is, clazz); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this json to " + clazz + " type", e); + } + } + + public static T parse(byte[] str, Class clazz) { + try { + if (str == null || str.length == 0) { + return null; + } else { + return OBJECT_MAPPER.readValue(str, clazz); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this json to " + clazz + " type", e); + } + } + + public static JsonNode parse(String str) { + try { + if (StringUtils.isBlank(str)) { + return null; + } else { + return OBJECT_MAPPER.readTree(str); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this json to JsonNode", e); + } + } + + /** + * 转换json文本为对象列表 + * @param is json文本流 + * @param 列表中对象类 + * @param clazz 列表中的对象类 + * @return 列表对象 + */ + public static List parseList(InputStream is, Class clazz) { + try { + if (is == null) { + return null; + } else { + JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(ArrayList.class, clazz); + return OBJECT_MAPPER.readValue(is, javaType); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this json to list type", e); + } + } + + /** + * 转换json文本为对象列表 + * @param str json文本字节数组 + * @param 列表中对象类 + * @param clazz 列表中的对象类 + * @return 列表对象 + */ + public static List parseList(byte[] str, Class clazz) { + try { + if (str == null || str.length == 0) { + return null; + } else { + JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(ArrayList.class, clazz); + return OBJECT_MAPPER.readValue(str, javaType); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this json to list type", e); + } + } + + /** + * 转换json文本为对象列表 + * @param str json文本 + * @param 列表中对象类 + * @param clazz 列表中的对象类 + * @return 列表对象 + */ + public static List parseList(String str, Class clazz) { + try { + if (StringUtils.isBlank(str)) { + return null; + } else { + JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(ArrayList.class, clazz); + return OBJECT_MAPPER.readValue(str, javaType); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this json to list<" + clazz.getSimpleName() + "> type", e); + } + } + + public static Map parseMap(String str) { + try { + if (StringUtils.isBlank(str)) { + return null; + } else { + return OBJECT_MAPPER.readValue(str, Map.class); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this json to Map type", e); + } + } + + public static String encodeString(Object obj) { + try { + return OBJECT_MAPPER.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new JsonParseException("can't convert type " + obj.getClass() + " to json string", e); + } + } + + public static byte[] encodeBytes(Object obj) { + try { + return OBJECT_MAPPER.writeValueAsBytes(obj); + } catch (JsonProcessingException e) { + throw new JsonParseException("can't convert type " + obj.getClass() + " to json string", e); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/MapUtil.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/MapUtil.java new file mode 100644 index 0000000..056b8ea --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/MapUtil.java @@ -0,0 +1,215 @@ +package cc.smtweb.framework.core.util; + +import cc.smtweb.framework.core.SwMap; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +/** + * map工具类 + */ +public class MapUtil { + private MapUtil() {} + + public static String readString(Map map, String name) { + return readString(map, name, null); + } + + public static String readString(Map map, String name, String defaultValue) { + Object s = map.get(name); + + if (s != null) { + return s.toString(); + } + + return defaultValue; + } + + public static Long readLong(Map map, String name) { + return readLong(map, name, null); + } + + public static Long readLong(Map map, String name, Long defaultValue) { + Object s = map.get(name); + + if (s != null) { + if (s instanceof Number) { + return ((Number) s).longValue(); + } else { + String value = s.toString(); + if (StringUtils.isNotBlank(value)) { + return Long.parseLong(value); + } + } + } + + return defaultValue; + } + + public static Long[] readLongArray(Map map, String name) { + return readLongArray(map, name, null); + } + + public static Long[] readLongArray(Map map, String name, Long[] defaultValue) { + Object value = map.get(name); + + if (value != null) { + if (value instanceof Long[]) { + return (Long[]) value; + } else if (value instanceof Object[]) { + Object[] items = (Object[])value; + List result = new ArrayList<>(items.length); + + for (Object item: items) { + if (item != null) { + String s = item.toString(); + if (StringUtils.isNotBlank(s)) { + result.add(Long.valueOf(s)); + } + } + } + + if (result.size() > 0) { + return result.toArray(new Long[result.size()]); + } + } else if (value instanceof List) { + List items = (List)value; + List result = new ArrayList<>(items.size()); + + for (Object item: items) { + if (item != null) { + String s = item.toString(); + if (StringUtils.isNotBlank(s)) { + result.add(Long.valueOf(s)); + } + } + } + + if (result.size() > 0) { + return result.toArray(new Long[result.size()]); + } + } else if (value instanceof String) { + String[] ary = value.toString().split(","); + if (ary.length > 0) { + List result = new ArrayList<>(ary.length); + + for (String item: ary) { + if (StringUtils.isNotBlank(item)) { + result.add(Long.valueOf(item)); + } + } + + if (result.size() > 0) { + return result.toArray(new Long[result.size()]); + } + } + } + } + + return defaultValue; + } + + public static Integer readInt(Map map, String name) { + return readInt(map, name, null); + } + + public static Integer readInt(Map map, String name, Integer defaultValue) { + Object s = map.get(name); + + if (s != null) { + if (s instanceof Number) { + return ((Number) s).intValue(); + } else { + String value = s.toString(); + if (StringUtils.isNotBlank(value)) { + return Integer.parseInt(value); + } + } + } + + return defaultValue; + } + + public static Float readFloat(Map map, String name) { + return readFloat(map, name, null); + } + + public static Float readFloat(Map map, String name, Float defaultValue) { + Object s = map.get(name); + + if (s != null) { + if (s instanceof Number) { + return ((Number) s).floatValue(); + } else { + String value = s.toString(); + if (StringUtils.isNotBlank(value)) { + return Float.parseFloat(value); + } + } + } + + return defaultValue; + } + + public static Double readDouble(Map map, String name) { + return readDouble(map, name, null); + } + + public static Double readDouble(Map map, String name, Double defaultValue) { + Object s = map.get(name); + + if (s != null) { + if (s instanceof Number) { + return ((Number) s).doubleValue(); + } else { + String value = s.toString(); + if (StringUtils.isNotBlank(value)) { + return Double.parseDouble(value); + } + } + } + + return defaultValue; + } + + public static Boolean readBool(Map map, String name) { + return readBool(map, name, null); + } + + public static Boolean readBool(Map map, String name, Boolean defaultValue) { + Object s = map.get(name); + + if (s != null) { + if (s instanceof Boolean) { + return (Boolean) s; + } else { + String value = s.toString(); + if ("true".equalsIgnoreCase(value)) { + return Boolean.TRUE; + } + } + } + + return defaultValue; + } + + public static Set readLongSet(SwMap swMap, String name) { + Object value = swMap.get(name); + if (value != null) { + String[] ary = value.toString().split(","); + if (ary.length > 0) { + Set result = new HashSet<>(ary.length); + + for (String item : ary) { + if (StringUtils.isNotBlank(item)) { + result.add(Long.valueOf(item)); + } + } + + return result; + } + } + + return null; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/NumberUtil.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/NumberUtil.java new file mode 100644 index 0000000..0d6cd8e --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/NumberUtil.java @@ -0,0 +1,621 @@ +package cc.smtweb.framework.core.util; + +import org.apache.commons.jexl3.JexlBuilder; +import org.apache.commons.jexl3.JexlContext; +import org.apache.commons.jexl3.JexlExpression; +import org.apache.commons.jexl3.MapContext; +import org.apache.commons.lang3.StringUtils; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.util.Map; + +public class NumberUtil { + private NumberUtil() {} + + //整数 + private static DecimalFormat dfLng = new DecimalFormat("##############0"); + private static DecimalFormat dfLong = new DecimalFormat("###,###,###,###,##0"); + //一位小数 + private static DecimalFormat df1 = new DecimalFormat("##############0.0"); + private static DecimalFormat df1Format = new DecimalFormat("###,###,###,###,##0.0"); + //两位小数 + private static DecimalFormat df2 = new DecimalFormat("##############0.00"); + private static DecimalFormat df2Format = new DecimalFormat("###,###,###,###,##0.00"); + //四位小数 + private static DecimalFormat df4 = new DecimalFormat("###,###,###,###,##0.0000"); + //六位小数 + private static DecimalFormat df6Number = new DecimalFormat("#######################0.000000"); + private static DecimalFormat df6NumberF = new DecimalFormat("#,###,###,###,###,###,##0.000000"); + + public final static DecimalFormat stdAmountFormat = new DecimalFormat("###,###,###,###,##0.00"); + public final static DecimalFormat stdNumberFormat = new DecimalFormat("#0.00"); + public final static String DEF_NUM_TEN_THOUSAND = "10000";//万 + public static final double MAX_VALUE = 9999999999999.99D; + public static final double MIN_VALUE = -9999999999999.99D; + + private final static BigDecimal ONE_BIG = new BigDecimal(1.00D); + private static final String UNIT = "万仟佰拾亿仟佰拾万仟佰拾元角分"; + private static final String DIGIT = "零壹贰叁肆伍陆柒捌玖"; + + /** + * 4舍5入double,2位小数 + */ + public static double roundDouble(double src) { + return roundDouble(src, 2); + } + + /** + * 4舍5入double,N 位小数 + * + * @param src + * @param scale 小数位数 + * @return + */ + public static double roundDouble(Object src, int scale) { + if (src == null) return 0.0; + String v = src.toString(); + if (StringUtils.isEmpty(v)) return 0.0; + if (scale < 0) scale = 2; + + BigDecimal src_b = new BigDecimal(v); + BigDecimal src_v = src_b.divide(ONE_BIG, scale + 2, BigDecimal.ROUND_HALF_UP);// 4舍5入 + src_v = src_v.divide(ONE_BIG, scale, BigDecimal.ROUND_HALF_UP);// 4舍5入 + return src_v.doubleValue(); + } + + /** + * 舍位处理,原生floor有坑,部分浮点数记录为.99999999999形式,导致floor结果错误 + * + * @param d + * @return + */ + public static double floor(double d) { + return Math.floor(roundDouble(d, 2)); + } + + /** + * 比较两Double是否相等,将会吧他们专程BigDecimal进行比较; + * + * @param src double1 + * @param tag double2 + * @return src > tag 返回1, src < tag 返回-1, 否则返回0 + */ + public static int compare(double src, double tag) { + BigDecimal src_b = new BigDecimal(src); + BigDecimal src_v = src_b.divide(ONE_BIG, 2, BigDecimal.ROUND_HALF_UP);// 4舍5入 + + BigDecimal tag_b = new BigDecimal(tag); + BigDecimal tag_v = tag_b.divide(ONE_BIG, 2, BigDecimal.ROUND_HALF_UP);// 4舍5入 + + return src_v.compareTo(tag_v); + } + + /** + * 自动过滤金额中的逗号转换为double,如果出错,则返回0 + * + * @param s 源串,可能为带逗号的金额串; + * @return double + */ + public static Double toDouble(String s) { + return todouble(s); + } + + /** + * 自动过滤金额中的逗号转换为double,如果出错,则返回0 + * + * @param s 源串,可能为带逗号的金额串; + * @return double + */ + public static double todouble(String s) { + try { + return Double.parseDouble(s.replaceAll(",", "")); + } catch (Exception e) { + return 0.00; + } + } + + /** + * 获取double,主要过滤d为null的情况; + * + * @param d Double对象; + * @return double + */ + public static double todouble(Double d) { + if (d == null) return 0.0d; + return d.doubleValue(); + } + + /** + * 自动过滤金额中的逗号转换为float,如果出错,则返回0 + * + * @param s 源串,可能为带逗号的金额串; + * @return Float + */ + public static Float toFloat(String s) { + return tofloat(s); + } + + /** + * 自动过滤金额中的逗号转换为float,如果出错,则返回0 + * + * @param s 源串,可能为带逗号的金额串; + * @return Float + */ + public static float tofloat(String s) { + try { + return Float.parseFloat(s.replaceAll(",", "")); + } catch (Exception e) { + return 0.0f; + } + } + + public static long tolong(String src, long defaultvalue) { + try { + return Long.parseLong(src); + } catch (Exception e) { + return defaultvalue; + } + } + + public static int toint(String src, int defaultvalue) { + try { + return Integer.parseInt(src); + } catch (Exception e) { + return defaultvalue; + } + } + + /** + * 考虑使用中的精度,判断一个Value是否>0,实际是>0.00001 + * + * @param value double类型 + * @return boolean + */ + public static boolean isBigThanZero(double value) { + return (value > 0.00001); + } + + /** + * 考虑使用中的精度,判断一个Value是否>0,实际是>0.00001 + * + * @param value String类型 + * @return boolean + */ + public static boolean isBigThanZero(String value) { + return !StringUtils.isEmpty(value) && isBigThanZero(toDouble(value)); + } + + /** + * 考虑使用中的精度,判断一个Value是否=0,实际是给出一个值范围。 + * + * @param value double类型 + * @return boolean + */ + public static boolean isEqualsZero(double value) { + return (-0.00001 < value && value < 0.00001); + } + + /** + * 考虑使用中的精度,判断一个Value是否=0,实际是给出一个值范围。 + * + * @param value String类型 + * @return boolean + */ + public static boolean isEqualsZero(String value) { + return StringUtils.isEmpty(value) || isEqualsZero(toDouble(value)); + } + + /** + * 是否是负数 + * + * @param db_val 要判断的double + * @return 负数则返回true; + */ + public static boolean isNegative(double db_val) { + return (compare(db_val, 0.00D) == -1); + } + + /** + * 是否是正数 + * + * @param db_val 要判断的double + * @return 正数则返回true; + */ + public static boolean isPlus(double db_val) { + return (compare(db_val, 0.00D) == 1); + } + + /** + * 得到金额字符串,保持小数点2位 + * + * @param db 将double转换为金额字符串; + * @return 金额字符串#0.00; + */ + public static String toStdNumberString(double db) { + try { + return stdNumberFormat.format(db); + } catch (Exception e) { + return "0.00"; + } + } + + public static String toStdNumberStringEx(double db) { + try { + if (compare(db, -1d) == 0) return "-"; + return stdNumberFormat.format(db); + } catch (Exception e) { + return "0.00"; + } + } + + /** + * 将金额格式字符串,如23,333,093.01 去掉逗号 + * + * @param s 金额串 + * @return String 去掉逗号后的串,如果amount为空,则返回0.00 + */ + public static String toStdNumberString(String s) { + if (StringUtils.isEmpty(s)) + return "0.00"; + return stdNumberFormat.format(todouble(s)); + } + + /** + * 将数据转换为两位小数的数字格式; + * + * @param d 数据 + * @param isZeroToEmpty 如果未0,是否返回“”; + * @return 两位小数的字符串; + */ + public static String toStdNumberString(double d, boolean isZeroToEmpty) { + if (isEqualsZero(d)) { + return isZeroToEmpty ? "" : "0.00"; + } + return stdNumberFormat.format(d); + } + + public static String toStdNumberString(String s, boolean isZeroToEmpty) { + return toStdNumberString(todouble(s), isZeroToEmpty); + } + + public static String toStdNumberString(double d, int scale) { + DecimalFormat dfn = null; + if (scale == 1) dfn = df1Format; + if (scale == 2) dfn = df2Format; + else if (scale == 4) dfn = df4; + else if (scale == 6) dfn = df6NumberF; + else if (scale <= 0) dfn = dfLong; + else { + StringBuilder sb = new StringBuilder("###,###,###,###,##0."); + for (int i = 0; i < scale; i++) sb.append("0"); + dfn = new DecimalFormat(sb.toString()); + } + return dfn.format(d); + } + + /** + * 将数字乘100,保留小数点后两位, 然后后面添加% + * + * @param d 值 + * @param isZeroToEmpty,如果值为0,是否返回空; + * @return 字符串; + */ + public static String toStdPercentNumberStr(double d, boolean isZeroToEmpty) { + if (d > -0.00000000001 && d < 0.00000000001) { + return isZeroToEmpty ? "" : "0.00%"; + } + return toStdNumberString(d * 100) + "%"; + } + + + public static String toStdAmountString(double d) { + return toStdAmountString(d, false); + } + + /** + * 将数据转换为两位小数的金额格式,带逗号; + * + * @param d 数据 + * @param isZeroToEmpty 如果未0,是否返回“”; + * @return 金额格式的字符串; + */ + public static String toStdAmountString(double d, boolean isZeroToEmpty) { + if (isEqualsZero(d)) { + return isZeroToEmpty ? "" : "0.00"; + } + return stdAmountFormat.format(d); + } + + public static String toStdAmountString(String s) { + return toStdAmountString(todouble(s), false); + } + + public static String toStdAmountString(String s, boolean isZeroToEmpty) { + return toStdAmountString(todouble(s), isZeroToEmpty); + } + + + /** + * 将小写金额转换为人民币大写金额 + * + * @param s 金额格式的串 + * @return String 转换结果 + */ + public static String toCapsAmountString(String s) { + if (StringUtils.isEmpty(s)) return ""; + return toCapsAmountString(todouble(s)); + } + + /** + * 将小写金额转换为人民币大写金额 + * + * @param v double + * @return String 转换结果 + */ + public static String toCapsAmountString(double v) { + if (v < MIN_VALUE || v > MAX_VALUE) return "参数非法!"; + + boolean negative = isNegative(v); + + if (negative) v = Math.abs(v); + long l = Math.round(v * 100); + if (l == 0) return "零元整"; + + String strValue = String.valueOf(l); + // i用来控制数 + int i = 0; + // j用来控制单位 + int j = UNIT.length() - strValue.length(); + StringBuilder rs = new StringBuilder(32); + boolean isZero = false; + for (; i < strValue.length(); i++, j++) { + char ch = strValue.charAt(i); + if (ch == '0') { + isZero = true; + if (UNIT.charAt(j) == '亿' || UNIT.charAt(j) == '万' || UNIT.charAt(j) == '元') { + rs.append(UNIT.charAt(j)); + isZero = false; + } + } else { + if (isZero) { + rs.append('零'); + isZero = false; + } + rs.append(DIGIT.charAt(ch - '0')).append(UNIT.charAt(j)); + } + } + if (rs.charAt(rs.length() - 1) != '分') + rs.append('整'); + + i = rs.indexOf("亿万"); + if (i > 0) rs.delete(i + 1, i + 2); // i+1 ->万 + + if (negative) + return rs.insert(0, '负').toString(); + else + return rs.toString(); + } + + /** + * 返回0 到 maxvalue的随机数 + * + * @param maxvalue 随机数的最大值 + * @return int + */ + public static int rnd(int maxvalue) { + return (int) (Math.random() * (maxvalue + 1)); + } + + + public static double chkDbNull(Double v) { + return v == null ? 0 : v; + } + + public static double max(double d1, double d2) { + return compare(d1, d2) < 0 ? d2 : d1; + } + + public static double min(double d1, double d2) { + return compare(d1, d2) < 0 ? d1 : d2; + } + + /** + * 获取浮点数(有错误默认为0),可以识别金额中的逗号格式 + * + * @param str 带转换的字符串 + * @return 浮点数 + */ + public static double getDoubleIgnoreErr(String str) { + if (str == null) + return 0.0; + str = str.trim(); + if (str.equals("")) + return 0.0; + str = str.replaceAll(",", "").replaceAll(",", ""); + try { + return Double.valueOf(str); + } catch (Exception e) { + return 0.0; + } + } + + public static int getIntIgnoreErr(String str) { + if (StringUtils.isEmpty(str)) + return 0; + str = str.replaceAll(",", "").replaceAll(",", ""); + if (str.contains(".")) + str = str.substring(0, str.indexOf('.')); + try { + return Integer.valueOf(str); + } catch (Exception e) { + return 0; + } + } + + public static long getLongIgnoreErr(String str) { + if (StringUtils.isEmpty(str)) + return 0L; + str = str.replaceAll(",", "").replaceAll(",", ""); + try { + return Long.valueOf(str); + } catch (Exception e) { + return 0L; + } + } + + /** + * 计算公式 参数以Map方式传入 + * + * @param expr 表达式 + * @param mapVar 变量 + * @return 计算结果 + */ + public static Object calcExprMapObject(String expr, Map mapVar) { + if (StringUtils.isEmpty(expr)) return ""; + JexlContext jc = new MapContext(); + for (String k : mapVar.keySet()) jc.set(k, mapVar.get(k)); + JexlExpression e = new JexlBuilder().create().createExpression(expr); + return e.evaluate(jc); + } + + /** + * 计算公式,参数以数组方式传入,key、value交替 + * + * @param expr 表达式 + * @param vars 变量 + * @return + */ + public static Object calcExprObjectEx(String expr, Object... vars) { + if (StringUtils.isEmpty(expr)) return ""; + JexlContext jc = new MapContext(); + for (int i = 0, len = vars.length; i < len; ) { + jc.set((String) vars[i++], vars[i++]); + } + JexlExpression e = new JexlBuilder().create().createExpression(expr); + return e.evaluate(jc); + } + + public static double calcExprMapDouble(String expr, Map mapVar) { + Object o = calcExprMapObject(expr, mapVar); + if (o != null) return getDoubleIgnoreErr(o.toString()); + return 0.0; + } + + public static double calcExprDoubleEx(String expr, Object... vars) { + Object o = calcExprObjectEx(expr, vars); + if (o != null) return getDoubleIgnoreErr(o.toString()); + return 0.0; + } + + public static int calcExprMapInt(String expr, Map mapVar) { + Object o = calcExprMapObject(expr, mapVar); + if (o != null) return getIntIgnoreErr(o.toString()); + return 0; + } + + public static int calcExprIntEx(String expr, Object... vars) { + Object o = calcExprObjectEx(expr, vars); + if (o != null) return getIntIgnoreErr(o.toString()); + return 0; + } + + public static boolean calcExprMapBool(String expr, Map mapVar) { + Object o = calcExprMapObject(expr, mapVar); + if (o != null) return (Boolean)o; + return false; + } + + public static boolean calcExprBoolEx(String expr, Object... vars) { + Object o = calcExprObjectEx(expr, vars); + if (o != null) return (Boolean)o; + return false; + } + +/* + public static Object calcExprObjectJ(String expr, Object... vars) throws Exception{ + if (StringUtils.isEmpty(expr)) return ""; + ExpressionEvaluator ee = new ExpressionEvaluator(); + ee.setExpressionType(int.class); + ee.setParameters(new String[]{"a", "b", "c"}, new Class[]{int.class, int.class, String.class}); + ee.cook(expr); + return ee.evaluate(vars); + } +*/ + + public static void main(String[] args) throws Exception{ + /*long t = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + System.out.print(calcExprIntEx("\"add\".equals(c) ? a + b : a - b", "a", 5, "b", 2, "c", "t")); + } + System.out.println(); + System.out.println(System.currentTimeMillis() - t); +// System.out.println(calcExprBoolEx("1==2")); + + t = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + System.out.print(calcExprObjectJ("\"add\".equals(c) ? a + b : a - b", 5, 2, "t")); + } + System.out.println(); + System.out.println(System.currentTimeMillis() - t);*/ + } + + /** + * double 去尾法 + * + * @param src 待处理数据 + * @param scale 保留小数位数 + * @return + */ + public static double cutDouble(double src, int scale) { + String v = toStdNumberString(src, 6);//先到6位小数,再去计算,否则容易出错,如8.3成8.29999999999,舍位就成了8.29了 + DecimalFormat formater = new DecimalFormat(); + formater.setMaximumFractionDigits(scale); + formater.setGroupingSize(0); + formater.setRoundingMode(RoundingMode.FLOOR); + return new BigDecimal(formater.format(toDouble(v))).doubleValue(); + } + + /** + * double 进位法 + * + * @param src 待处理数据 + * @param scale 保留小数位数 + * @return + */ + public static double upDouble(double src, int scale) { + String v = toStdNumberString(src, 6);//先到6位小数,再去计算 + DecimalFormat formater = new DecimalFormat(); + formater.setMaximumFractionDigits(scale); + formater.setGroupingSize(0); + formater.setRoundingMode(RoundingMode.UP); + return new BigDecimal(formater.format(toDouble(v))).doubleValue(); + } + + public static byte[] longToBytes(long l) { + byte[] result = new byte[8]; + for (int i = 7; i >= 0; i--) { + result[i] = (byte) (l & 0xFF); + l >>= 8; + } + return result; + } + + public static long bytesToLong(byte[] b) { + long result = 0; + for (int i = 0; i < 8; i++) { + result <<= 8; + result |= (b[i] & 0xFF); + } + return result; + } + + public static boolean longEquals(Long l1, Long l2) { + if (l1 != null) { + return l1.equals(l2); + } + + return l2 == null; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/SpringUtil.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/SpringUtil.java new file mode 100644 index 0000000..7063c85 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/SpringUtil.java @@ -0,0 +1,42 @@ +package cc.smtweb.framework.core.util; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * @Author: tanghp + * @Date: 2021-07-28 11:00 + * @Desc: + */ +@Component +public class SpringUtil implements ApplicationContextAware { + private static ApplicationContext applicationContext; + + public SpringUtil() { + } + + public void setApplicationContext(ApplicationContext applicationContext) { + if (SpringUtil.applicationContext == null) { + SpringUtil.applicationContext = applicationContext; + } + + } + + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + public static T getBean(String name) { + return (T)applicationContext.getBean(name); + } + + public static T getBean(Class clazz) { + return applicationContext.getBean(clazz); + } + + public static T getBean(String name, Class clazz) { + return applicationContext.getBean(name, clazz); + } +} + diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/VariableUtil.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/VariableUtil.java new file mode 100644 index 0000000..a425403 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/VariableUtil.java @@ -0,0 +1,65 @@ +package cc.smtweb.framework.core.util; + +/** + * 变量命名规则工具类 + */ +public class VariableUtil { + private VariableUtil() {} + + /*** + * 下划线命名转为驼峰命名 + * + * @param para + * 下划线命名的字符串 + */ + public static String underlineToHump(String para){ + StringBuilder result = new StringBuilder(); + String[] a = para.split("_"); + for(String s:a){ + if(result.length()==0){ + result.append(s.toLowerCase()); + }else{ + result.append(s.substring(0, 1).toUpperCase()); + result.append(s.substring(1).toLowerCase()); + } + } + return result.toString(); + } + + /*** + * 下划线命名转为大写驼峰命名 + * 大驼峰式命名法(upper camel case) + * + * @param para + * 下划线命名的字符串 + */ + public static String underlineToUpperHump(String para){ + StringBuilder result=new StringBuilder(); + String[] a = para.split("_"); + for(String s : a){ + result.append(s.substring(0, 1).toUpperCase()); + result.append(s.substring(1).toLowerCase()); + } + return result.toString(); + } + + + /*** + * 驼峰命名转为下划线命名 + * + * @param para + * 驼峰命名的字符串 + */ + public static String humpToUnderline(String para){ + StringBuilder sb = new StringBuilder(para); + //偏移量,第i个下划线的位置是 当前的位置+ 偏移量(i-1),第一个下划线偏移量是0 + int temp = 0; + for(int i = 0; i { + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + if (jsonParser != null && StringUtils.isNotEmpty(jsonParser.getText())) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + try { + return dateFormat.parse(jsonParser.getText()); + } catch (ParseException e) { + throw new InvalidFormatException(jsonParser, e.getMessage(), jsonParser.getText(), Long.class); + } + } else { + return null; + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/DateSerializer.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/DateSerializer.java new file mode 100644 index 0000000..c0e3961 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/DateSerializer.java @@ -0,0 +1,70 @@ +package cc.smtweb.framework.core.util.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Date; + +public class DateSerializer extends JsonSerializer { + + @Override + public void serialize(Date value, JsonGenerator gen, + SerializerProvider serializers) throws IOException, + JsonProcessingException { + if (value == null) { + gen.writeNull(); + } else { + gen.writeString(toString(value)); +// gen.writeNumber(value); +// gen.writeString("\""); + } + } + + @SuppressWarnings("deprecation") + public String toString (Date value) { + int year = value.getYear() + 1900; + int month = value.getMonth() + 1; + int day = value.getDate(); + String yearString; + String monthString; + String dayString; + String yearZeros = "0000"; + StringBuffer timestampBuf; + + if (year < 1000) { + // Add leading zeros + yearString = "" + year; + yearString = yearZeros.substring(0, (4-yearString.length())) + + yearString; + } else { + yearString = "" + year; + } + if (month < 10) { + monthString = "0" + month; + } else { + monthString = Integer.toString(month); + } + if (day < 10) { + dayString = "0" + day; + } else { + dayString = Integer.toString(day); + } + + // do a string buffer here instead. + timestampBuf = new StringBuffer(10); + timestampBuf.append(yearString); + timestampBuf.append("-"); + timestampBuf.append(monthString); + timestampBuf.append("-"); + timestampBuf.append(dayString); + + return (timestampBuf.toString()); + } + + public static void main(String[] args) { + System.out.println(new DateSerializer().toString(new Date())); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/LongDeserializer.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/LongDeserializer.java new file mode 100644 index 0000000..ed8225b --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/LongDeserializer.java @@ -0,0 +1,25 @@ +package cc.smtweb.framework.core.util.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; + +public class LongDeserializer extends JsonDeserializer { + @Override + public Long deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + if (jsonParser != null && StringUtils.isNotBlank(jsonParser.getText())) { + try { + return Long.valueOf(jsonParser.getText()); + } catch (NumberFormatException e) { + throw new InvalidFormatException(jsonParser, e.getMessage(), jsonParser.getText(), Long.class); + } + } else { + return null; + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/LongSerializer.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/LongSerializer.java new file mode 100644 index 0000000..ca2c0ea --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/LongSerializer.java @@ -0,0 +1,19 @@ +package cc.smtweb.framework.core.util.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +public class LongSerializer extends JsonSerializer { + + @Override + public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (value == null) { + gen.writeNull(); + } else { + gen.writeString(value.toString()); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/NullSerializer.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/NullSerializer.java new file mode 100644 index 0000000..1c57c81 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/NullSerializer.java @@ -0,0 +1,15 @@ +package cc.smtweb.framework.core.util.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +public class NullSerializer extends JsonSerializer { + @Override + public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { + jgen.writeString(""); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/TimeSerializer.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/TimeSerializer.java new file mode 100644 index 0000000..ffc457b --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/TimeSerializer.java @@ -0,0 +1,61 @@ +package cc.smtweb.framework.core.util.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Date; + +public class TimeSerializer extends JsonSerializer { + + @Override + public void serialize(Date value, JsonGenerator gen, + SerializerProvider serializers) throws IOException, + JsonProcessingException { + if (value == null) { + gen.writeNull(); + } else { + gen.writeString(toString(value)); +// gen.writeNumber(value); +// gen.writeString("\""); + } + } + + @SuppressWarnings("deprecation") + public String toString (Date value) { + int hour = value.getHours(); + int minute = value.getMinutes(); + int second = value.getSeconds(); + String hourString; + String minuteString; + String secondString; + if (hour < 10) { + hourString = "0" + hour; + } else { + hourString = Integer.toString(hour); + } + if (minute < 10) { + minuteString = "0" + minute; + } else { + minuteString = Integer.toString(minute); + } + if (second < 10) { + secondString = "0" + second; + } else { + secondString = Integer.toString(second); + } + + // do a string buffer here instead. + StringBuffer timestampBuf = new StringBuffer(8); + timestampBuf.append(hourString); + timestampBuf.append(":"); + timestampBuf.append(minuteString); + timestampBuf.append(":"); + timestampBuf.append(secondString); + + return (timestampBuf.toString()); + } + +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/TimestampDeserializer.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/TimestampDeserializer.java new file mode 100644 index 0000000..c9a4dd3 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/jackson/TimestampDeserializer.java @@ -0,0 +1,31 @@ +package cc.smtweb.framework.core.util.jackson; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; + +public class TimestampDeserializer extends JsonDeserializer { + @Override + public Timestamp deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + if (jsonParser != null && StringUtils.isNotEmpty(jsonParser.getText())) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + try { + return new Timestamp(dateFormat.parse(jsonParser.getText()).getTime()); + } catch (ParseException e) { + throw new InvalidFormatException(jsonParser, e.getMessage(), jsonParser.getText(), Long.class); + } + } else { + return null; + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/kryo/KryoTool.java b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/kryo/KryoTool.java new file mode 100644 index 0000000..6da195f --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/cc/smtweb/framework/core/util/kryo/KryoTool.java @@ -0,0 +1,77 @@ +package cc.smtweb.framework.core.util.kryo; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.pool.KryoFactory; +import com.esotericsoftware.kryo.pool.KryoPool; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Serializable; + +/** + * 序列化工具类,单例 + */ +public class KryoTool { + private static KryoTool INST; + + static { + INST = new KryoTool(); + } + + public static KryoTool getINST() { + return INST; + } + + private KryoPool pool; + + public KryoTool() { + KryoFactory factory = () -> { + Kryo kryo = new Kryo(); + kryo.setReferences(false); + // 关闭注册 + kryo.setRegistrationRequired(false); + return kryo; + }; + + // Build pool with SoftReferences enabled (optional) + pool = new KryoPool.Builder(factory).softReferences().build(); + } + + public byte[] writeObject(T obj) { + byte[] result = null; + + if (obj != null) { + Kryo kryo = pool.borrow(); + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Output output = new Output(baos); + kryo.writeObject(output, obj); + output.close(); + + result = baos.toByteArray(); + } finally { + pool.release(kryo); + } + } + + return result; + } + + public T readObject(byte[] data, Class clazz) { + T result = null; + + if (data != null) { + Kryo kryo = pool.borrow(); + try { + ByteArrayInputStream bais = new ByteArrayInputStream(data); + Input input = new Input(bais); + result = kryo.readObject(input, clazz); + } finally { + pool.release(kryo); + } + } + + return result; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/AbstractTimer.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/AbstractTimer.java new file mode 100644 index 0000000..c2713d0 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/AbstractTimer.java @@ -0,0 +1,54 @@ +package com.serotonin.timer; + +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +abstract public class AbstractTimer { + abstract public boolean isInitialized(); + + abstract public long currentTimeMillis(); + + abstract public void execute(Runnable command); + + public void execute(Runnable command, String name) { + if (StringUtils.isBlank(name)) + execute(command); + else + execute(new NamedRunnable(command, name)); + } + + abstract public void execute(ScheduledRunnable command, long fireTime); + + public void execute(ScheduledRunnable command, long fireTime, String name) { + if (StringUtils.isBlank(name)) + execute(command, fireTime); + else + execute(new ScheduledNamedRunnable(command, name), fireTime); + } + + final public TimerTask schedule(TimerTask task) { + if (task.getTimer() == this) + throw new IllegalStateException("Task already scheduled or cancelled"); + + task.setTimer(this); + scheduleImpl(task); + + return task; + } + + public void scheduleAll(AbstractTimer that) { + for (TimerTask task : that.cancel()) + schedule(task); + } + + abstract protected void scheduleImpl(TimerTask task); + + abstract public List cancel(); + + abstract public int purge(); + + abstract public int size(); + + abstract public List getTasks(); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/AbstractTimerTrigger.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/AbstractTimerTrigger.java new file mode 100644 index 0000000..cd1ecbf --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/AbstractTimerTrigger.java @@ -0,0 +1,25 @@ +package com.serotonin.timer; + +import java.util.Date; + +abstract public class AbstractTimerTrigger extends TimerTrigger { + private final boolean delayed; + private final long first; + + public AbstractTimerTrigger(long delay) { + this.delayed = true; + first = delay; + } + + public AbstractTimerTrigger(Date start) { + delayed = false; + first = start.getTime(); + } + + @Override + final protected long getFirstExecutionTime() { + if (delayed) + return timer.currentTimeMillis() + first; + return first; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/CronExpression.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/CronExpression.java new file mode 100644 index 0000000..8439758 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/CronExpression.java @@ -0,0 +1,1444 @@ +package com.serotonin.timer; + +import java.text.ParseException; +import java.util.*; + +/** + * Significant portions of this code (i.e. most of it) were lifted from the Quartz scheduling library for Java. + * + * @see http://www.quartz-scheduler.org/api/2.1.0/ + * + * @author Matthew Lohbihler (edits to make compatible with this package) + */ +public class CronExpression implements Cloneable { + protected static final int YEAR_AT_WHICH_TO_GIVE_UP_SCHEDULING = 2299; + + protected static final int SECOND = 0; + protected static final int MINUTE = 1; + protected static final int HOUR = 2; + protected static final int DAY_OF_MONTH = 3; + protected static final int MONTH = 4; + protected static final int DAY_OF_WEEK = 5; + protected static final int YEAR = 6; + protected static final int ALL_SPEC_INT = 99; // '*' + protected static final int NO_SPEC_INT = 98; // '?' + protected static final Integer ALL_SPEC = new Integer(ALL_SPEC_INT); + protected static final Integer NO_SPEC = new Integer(NO_SPEC_INT); + + protected static final Map monthMap = new HashMap(20); + protected static final Map dayMap = new HashMap(60); + static { + monthMap.put("JAN", new Integer(0)); + monthMap.put("FEB", new Integer(1)); + monthMap.put("MAR", new Integer(2)); + monthMap.put("APR", new Integer(3)); + monthMap.put("MAY", new Integer(4)); + monthMap.put("JUN", new Integer(5)); + monthMap.put("JUL", new Integer(6)); + monthMap.put("AUG", new Integer(7)); + monthMap.put("SEP", new Integer(8)); + monthMap.put("OCT", new Integer(9)); + monthMap.put("NOV", new Integer(10)); + monthMap.put("DEC", new Integer(11)); + + dayMap.put("SUN", new Integer(1)); + dayMap.put("MON", new Integer(2)); + dayMap.put("TUE", new Integer(3)); + dayMap.put("WED", new Integer(4)); + dayMap.put("THU", new Integer(5)); + dayMap.put("FRI", new Integer(6)); + dayMap.put("SAT", new Integer(7)); + } + + private String cronExpression = null; + private TimeZone timeZone = null; + protected transient TreeSet seconds; + protected transient TreeSet minutes; + protected transient TreeSet hours; + protected transient TreeSet daysOfMonth; + protected transient TreeSet months; + protected transient TreeSet daysOfWeek; + protected transient TreeSet years; + + protected transient boolean lastdayOfWeek = false; + protected transient int nthdayOfWeek = 0; + protected transient boolean lastdayOfMonth = false; + protected transient boolean nearestWeekday = false; + protected transient boolean expressionParsed = false; + + /** + * Constructs a new CronExpression based on the specified parameter. + * + * @param cronExpression + * String representation of the cron expression the new object should represent + * @throws ParseException + * if the string expression cannot be parsed into a valid CronExpression + */ + public CronExpression(String cronExpression) throws ParseException { + if (cronExpression == null) { + throw new IllegalArgumentException("cronExpression cannot be null"); + } + + this.cronExpression = cronExpression.toUpperCase(Locale.US); + + buildExpression(this.cronExpression); + } + + /** + * Indicates whether the given date satisfies the cron expression. Note that milliseconds are ignored, so two Dates + * falling on different milliseconds of the same second will always have the same result here. + * + * @param date + * the date to evaluate + * @return a boolean indicating whether the given date satisfies the cron expression + */ + public boolean isSatisfiedBy(Date date) { + Calendar testDateCal = Calendar.getInstance(getTimeZone()); + testDateCal.setTime(date); + testDateCal.set(Calendar.MILLISECOND, 0); + Date originalDate = testDateCal.getTime(); + + testDateCal.add(Calendar.SECOND, -1); + + Date timeAfter = getTimeAfter(testDateCal.getTime()); + + return ((timeAfter != null) && (timeAfter.equals(originalDate))); + } + + /** + * Returns the next date/time after the given date/time which satisfies the cron expression. + * + * @param date + * the date/time at which to begin the search for the next valid date/time + * @return the next valid date/time + */ + public Date getNextValidTimeAfter(Date date) { + return getTimeAfter(date); + } + + /** + * Returns the next date/time after the given date/time which does not satisfy the expression + * + * @param date + * the date/time at which to begin the search for the next invalid date/time + * @return the next valid date/time + */ + public Date getNextInvalidTimeAfter(Date date) { + long difference = 1000; + + // move back to the nearest second so differences will be accurate + Calendar adjustCal = Calendar.getInstance(getTimeZone()); + adjustCal.setTime(date); + adjustCal.set(Calendar.MILLISECOND, 0); + Date lastDate = adjustCal.getTime(); + + Date newDate = null; + + // TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very + // bad here, depending on the cron expression. It is, however A solution. + + // keep getting the next included time until it's farther than one second + // apart. At that point, lastDate is the last valid fire time. We return + // the second immediately following it. + while (difference == 1000) { + newDate = getTimeAfter(lastDate); + + difference = newDate.getTime() - lastDate.getTime(); + + if (difference == 1000) { + lastDate = newDate; + } + } + + return new Date(lastDate.getTime() + 1000); + } + + /** + * Returns the time zone for which this CronExpression will be resolved. + */ + public TimeZone getTimeZone() { + if (timeZone == null) { + timeZone = TimeZone.getDefault(); + } + + return timeZone; + } + + /** + * Sets the time zone for which this CronExpression will be resolved. + */ + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; + } + + /** + * Returns the string representation of the CronExpression + * + * @return a string representation of the CronExpression + */ + @Override + public String toString() { + return cronExpression; + } + + /** + * Indicates whether the specified cron expression can be parsed into a valid cron expression + * + * @param cronExpression + * the expression to evaluate + * @return a boolean indicating whether the given expression is a valid cron expression + */ + public static boolean isValidExpression(String cronExpression) { + + try { + new CronExpression(cronExpression); + } + catch (ParseException pe) { + return false; + } + + return true; + } + + // ////////////////////////////////////////////////////////////////////////// + // + // Expression Parsing Functions + // + // ////////////////////////////////////////////////////////////////////////// + + protected void buildExpression(String expression) throws ParseException { + expressionParsed = true; + + try { + + if (seconds == null) { + seconds = new TreeSet(); + } + if (minutes == null) { + minutes = new TreeSet(); + } + if (hours == null) { + hours = new TreeSet(); + } + if (daysOfMonth == null) { + daysOfMonth = new TreeSet(); + } + if (months == null) { + months = new TreeSet(); + } + if (daysOfWeek == null) { + daysOfWeek = new TreeSet(); + } + if (years == null) { + years = new TreeSet(); + } + + int exprOn = SECOND; + + StringTokenizer exprsTok = new StringTokenizer(expression, " \t", false); + + while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { + String expr = exprsTok.nextToken().trim(); + + // throw an exception if L is used with other days of the month + if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.indexOf(",") >= 0) { + throw new ParseException( + "Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1); + } + // throw an exception if L is used with other days of the week + if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.indexOf(",") >= 0) { + throw new ParseException( + "Support for specifying 'L' with other days of the week is not implemented", -1); + } + + StringTokenizer vTok = new StringTokenizer(expr, ","); + while (vTok.hasMoreTokens()) { + String v = vTok.nextToken(); + storeExpressionVals(0, v, exprOn); + } + + exprOn++; + } + + if (exprOn <= DAY_OF_WEEK) { + throw new ParseException("Unexpected end of expression.", expression.length()); + } + + if (exprOn <= YEAR) { + storeExpressionVals(0, "*", YEAR); + } + + TreeSet dow = getSet(DAY_OF_WEEK); + TreeSet dom = getSet(DAY_OF_MONTH); + + // Copying the logic from the UnsupportedOperationException below + boolean dayOfMSpec = !dom.contains(NO_SPEC); + boolean dayOfWSpec = !dow.contains(NO_SPEC); + + if (dayOfMSpec && !dayOfWSpec) { + // skip + } + else if (dayOfWSpec && !dayOfMSpec) { + // skip + } + else { + throw new ParseException( + "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0); + } + } + catch (ParseException pe) { + throw pe; + } + catch (Exception e) { + throw new ParseException("Illegal cron expression format (" + e.toString() + ")", 0); + } + } + + protected int storeExpressionVals(int pos, String s, int type) throws ParseException { + + int incr = 0; + int i = skipWhiteSpace(pos, s); + if (i >= s.length()) { + return i; + } + char c = s.charAt(i); + if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW"))) { + String sub = s.substring(i, i + 3); + int sval = -1; + int eval = -1; + if (type == MONTH) { + sval = getMonthNumber(sub) + 1; + if (sval <= 0) { + throw new ParseException("Invalid Month value: '" + sub + "'", i); + } + if (s.length() > i + 3) { + c = s.charAt(i + 3); + if (c == '-') { + i += 4; + sub = s.substring(i, i + 3); + eval = getMonthNumber(sub) + 1; + if (eval <= 0) { + throw new ParseException("Invalid Month value: '" + sub + "'", i); + } + } + } + } + else if (type == DAY_OF_WEEK) { + sval = getDayOfWeekNumber(sub); + if (sval < 0) { + throw new ParseException("Invalid Day-of-Week value: '" + sub + "'", i); + } + if (s.length() > i + 3) { + c = s.charAt(i + 3); + if (c == '-') { + i += 4; + sub = s.substring(i, i + 3); + eval = getDayOfWeekNumber(sub); + if (eval < 0) { + throw new ParseException("Invalid Day-of-Week value: '" + sub + "'", i); + } + } + else if (c == '#') { + try { + i += 4; + nthdayOfWeek = Integer.parseInt(s.substring(i)); + if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { + throw new Exception(); + } + } + catch (Exception e) { + throw new ParseException("A numeric value between 1 and 5 must follow the '#' option", i); + } + } + else if (c == 'L') { + lastdayOfWeek = true; + i++; + } + } + + } + else { + throw new ParseException("Illegal characters for this position: '" + sub + "'", i); + } + if (eval != -1) { + incr = 1; + } + addToSet(sval, eval, incr, type); + return (i + 3); + } + + if (c == '?') { + i++; + if ((i + 1) < s.length() && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) { + throw new ParseException("Illegal character after '?': " + s.charAt(i), i); + } + if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) { + throw new ParseException("'?' can only be specfied for Day-of-Month or Day-of-Week.", i); + } + if (type == DAY_OF_WEEK && !lastdayOfMonth) { + int val = (daysOfMonth.last()).intValue(); + if (val == NO_SPEC_INT) { + throw new ParseException("'?' can only be specfied for Day-of-Month -OR- Day-of-Week.", i); + } + } + + addToSet(NO_SPEC_INT, -1, 0, type); + return i; + } + + if (c == '*' || c == '/') { + if (c == '*' && (i + 1) >= s.length()) { + addToSet(ALL_SPEC_INT, -1, incr, type); + return i + 1; + } + else if (c == '/' && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t')) { + throw new ParseException("'/' must be followed by an integer.", i); + } + else if (c == '*') { + i++; + } + c = s.charAt(i); + if (c == '/') { // is an increment specified? + i++; + if (i >= s.length()) { + throw new ParseException("Unexpected end of string.", i); + } + + incr = getNumericValue(s, i); + + i++; + if (incr > 10) { + i++; + } + if (incr > 59 && (type == SECOND || type == MINUTE)) { + throw new ParseException("Increment > 60 : " + incr, i); + } + else if (incr > 23 && (type == HOUR)) { + throw new ParseException("Increment > 24 : " + incr, i); + } + else if (incr > 31 && (type == DAY_OF_MONTH)) { + throw new ParseException("Increment > 31 : " + incr, i); + } + else if (incr > 7 && (type == DAY_OF_WEEK)) { + throw new ParseException("Increment > 7 : " + incr, i); + } + else if (incr > 12 && (type == MONTH)) { + throw new ParseException("Increment > 12 : " + incr, i); + } + } + else { + incr = 1; + } + + addToSet(ALL_SPEC_INT, -1, incr, type); + return i; + } + else if (c == 'L') { + i++; + if (type == DAY_OF_MONTH) { + lastdayOfMonth = true; + } + if (type == DAY_OF_WEEK) { + addToSet(7, 7, 0, type); + } + if (type == DAY_OF_MONTH && s.length() > i) { + c = s.charAt(i); + if (c == 'W') { + nearestWeekday = true; + i++; + } + } + return i; + } + else if (c >= '0' && c <= '9') { + int val = Integer.parseInt(String.valueOf(c)); + i++; + if (i >= s.length()) { + addToSet(val, -1, -1, type); + } + else { + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(val, s, i); + val = vs.value; + i = vs.pos; + } + i = checkNext(i, s, val, type); + return i; + } + } + else { + throw new ParseException("Unexpected character: " + c, i); + } + + return i; + } + + protected int checkNext(int pos, String s, int val, int type) throws ParseException { + + int end = -1; + int i = pos; + + if (i >= s.length()) { + addToSet(val, end, -1, type); + return i; + } + + char c = s.charAt(pos); + + if (c == 'L') { + if (type == DAY_OF_WEEK) { + lastdayOfWeek = true; + } + else { + throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i); + } + TreeSet set = getSet(type); + set.add(new Integer(val)); + i++; + return i; + } + + if (c == 'W') { + if (type == DAY_OF_MONTH) { + nearestWeekday = true; + } + else { + throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i); + } + TreeSet set = getSet(type); + set.add(new Integer(val)); + i++; + return i; + } + + if (c == '#') { + if (type != DAY_OF_WEEK) { + throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i); + } + i++; + try { + nthdayOfWeek = Integer.parseInt(s.substring(i)); + if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { + throw new Exception(); + } + } + catch (Exception e) { + throw new ParseException("A numeric value between 1 and 5 must follow the '#' option", i); + } + + TreeSet set = getSet(type); + set.add(new Integer(val)); + i++; + return i; + } + + if (c == '-') { + i++; + c = s.charAt(i); + int v = Integer.parseInt(String.valueOf(c)); + end = v; + i++; + if (i >= s.length()) { + addToSet(val, end, 1, type); + return i; + } + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(v, s, i); + int v1 = vs.value; + end = v1; + i = vs.pos; + } + if (i < s.length() && ((c = s.charAt(i)) == '/')) { + i++; + c = s.charAt(i); + int v2 = Integer.parseInt(String.valueOf(c)); + i++; + if (i >= s.length()) { + addToSet(val, end, v2, type); + return i; + } + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(v2, s, i); + int v3 = vs.value; + addToSet(val, end, v3, type); + i = vs.pos; + return i; + } + + addToSet(val, end, v2, type); + return i; + } + + addToSet(val, end, 1, type); + return i; + } + + if (c == '/') { + i++; + c = s.charAt(i); + int v2 = Integer.parseInt(String.valueOf(c)); + i++; + if (i >= s.length()) { + addToSet(val, end, v2, type); + return i; + } + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(v2, s, i); + int v3 = vs.value; + addToSet(val, end, v3, type); + i = vs.pos; + return i; + } + + throw new ParseException("Unexpected character '" + c + "' after '/'", i); + } + + addToSet(val, end, 0, type); + i++; + return i; + } + + public String getCronExpression() { + return cronExpression; + } + + public String getExpressionSummary() { + StringBuffer buf = new StringBuffer(); + + buf.append("seconds: "); + buf.append(getExpressionSetSummary(seconds)); + buf.append("\n"); + buf.append("minutes: "); + buf.append(getExpressionSetSummary(minutes)); + buf.append("\n"); + buf.append("hours: "); + buf.append(getExpressionSetSummary(hours)); + buf.append("\n"); + buf.append("daysOfMonth: "); + buf.append(getExpressionSetSummary(daysOfMonth)); + buf.append("\n"); + buf.append("months: "); + buf.append(getExpressionSetSummary(months)); + buf.append("\n"); + buf.append("daysOfWeek: "); + buf.append(getExpressionSetSummary(daysOfWeek)); + buf.append("\n"); + buf.append("lastdayOfWeek: "); + buf.append(lastdayOfWeek); + buf.append("\n"); + buf.append("nearestWeekday: "); + buf.append(nearestWeekday); + buf.append("\n"); + buf.append("NthDayOfWeek: "); + buf.append(nthdayOfWeek); + buf.append("\n"); + buf.append("lastdayOfMonth: "); + buf.append(lastdayOfMonth); + buf.append("\n"); + buf.append("years: "); + buf.append(getExpressionSetSummary(years)); + buf.append("\n"); + + return buf.toString(); + } + + protected String getExpressionSetSummary(Set set) { + + if (set.contains(NO_SPEC)) { + return "?"; + } + if (set.contains(ALL_SPEC)) { + return "*"; + } + + StringBuffer buf = new StringBuffer(); + + Iterator itr = set.iterator(); + boolean first = true; + while (itr.hasNext()) { + Integer iVal = itr.next(); + String val = iVal.toString(); + if (!first) { + buf.append(","); + } + buf.append(val); + first = false; + } + + return buf.toString(); + } + + protected String getExpressionSetSummary(List list) { + + if (list.contains(NO_SPEC)) { + return "?"; + } + if (list.contains(ALL_SPEC)) { + return "*"; + } + + StringBuffer buf = new StringBuffer(); + + Iterator itr = list.iterator(); + boolean first = true; + while (itr.hasNext()) { + Integer iVal = itr.next(); + String val = iVal.toString(); + if (!first) { + buf.append(","); + } + buf.append(val); + first = false; + } + + return buf.toString(); + } + + protected int skipWhiteSpace(int i, String s) { + for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) { + ; + } + + return i; + } + + protected int findNextWhiteSpace(int i, String s) { + for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) { + ; + } + + return i; + } + + protected void addToSet(int val, int end, int incr, int type) throws ParseException { + + TreeSet set = getSet(type); + + if (type == SECOND || type == MINUTE) { + if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) { + throw new ParseException("Minute and Second values must be between 0 and 59", -1); + } + } + else if (type == HOUR) { + if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) { + throw new ParseException("Hour values must be between 0 and 23", -1); + } + } + else if (type == DAY_OF_MONTH) { + if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) { + throw new ParseException("Day of month values must be between 1 and 31", -1); + } + } + else if (type == MONTH) { + if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) { + throw new ParseException("Month values must be between 1 and 12", -1); + } + } + else if (type == DAY_OF_WEEK) { + if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) { + throw new ParseException("Day-of-Week values must be between 1 and 7", -1); + } + } + + if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) { + if (val != -1) { + set.add(new Integer(val)); + } + else { + set.add(NO_SPEC); + } + + return; + } + + int startAt = val; + int stopAt = end; + + if (val == ALL_SPEC_INT && incr <= 0) { + incr = 1; + set.add(ALL_SPEC); // put in a marker, but also fill values + } + + if (type == SECOND || type == MINUTE) { + if (stopAt == -1) { + stopAt = 59; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 0; + } + } + else if (type == HOUR) { + if (stopAt == -1) { + stopAt = 23; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 0; + } + } + else if (type == DAY_OF_MONTH) { + if (stopAt == -1) { + stopAt = 31; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1; + } + } + else if (type == MONTH) { + if (stopAt == -1) { + stopAt = 12; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1; + } + } + else if (type == DAY_OF_WEEK) { + if (stopAt == -1) { + stopAt = 7; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1; + } + } + else if (type == YEAR) { + if (stopAt == -1) { + stopAt = YEAR_AT_WHICH_TO_GIVE_UP_SCHEDULING; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1970; + } + } + + // if the end of the range is before the start, then we need to overflow into + // the next day, month etc. This is done by adding the maximum amount for that + // type, and using modulus max to determine the value being added. + int max = -1; + if (stopAt < startAt) { + switch (type) { + case SECOND: + max = 60; + break; + case MINUTE: + max = 60; + break; + case HOUR: + max = 24; + break; + case MONTH: + max = 12; + break; + case DAY_OF_WEEK: + max = 7; + break; + case DAY_OF_MONTH: + max = 31; + break; + case YEAR: + throw new IllegalArgumentException("Start year must be less than stop year"); + default: + throw new IllegalArgumentException("Unexpected type encountered"); + } + stopAt += max; + } + + for (int i = startAt; i <= stopAt; i += incr) { + if (max == -1) { + // ie: there's no max to overflow over + set.add(new Integer(i)); + } + else { + // take the modulus to get the real value + int i2 = i % max; + + // 1-indexed ranges should not include 0, and should include their max + if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) { + i2 = max; + } + + set.add(new Integer(i2)); + } + } + } + + protected TreeSet getSet(int type) { + switch (type) { + case SECOND: + return seconds; + case MINUTE: + return minutes; + case HOUR: + return hours; + case DAY_OF_MONTH: + return daysOfMonth; + case MONTH: + return months; + case DAY_OF_WEEK: + return daysOfWeek; + case YEAR: + return years; + default: + return null; + } + } + + protected ValueSet getValue(int v, String s, int i) { + char c = s.charAt(i); + String s1 = String.valueOf(v); + while (c >= '0' && c <= '9') { + s1 += c; + i++; + if (i >= s.length()) { + break; + } + c = s.charAt(i); + } + ValueSet val = new ValueSet(); + + val.pos = (i < s.length()) ? i : i + 1; + val.value = Integer.parseInt(s1); + return val; + } + + protected int getNumericValue(String s, int i) { + int endOfVal = findNextWhiteSpace(i, s); + String val = s.substring(i, endOfVal); + return Integer.parseInt(val); + } + + protected int getMonthNumber(String s) { + Integer integer = monthMap.get(s); + + if (integer == null) { + return -1; + } + + return integer.intValue(); + } + + protected int getDayOfWeekNumber(String s) { + Integer integer = dayMap.get(s); + + if (integer == null) { + return -1; + } + + return integer.intValue(); + } + + // ////////////////////////////////////////////////////////////////////////// + // + // Computation Functions + // + // ////////////////////////////////////////////////////////////////////////// + + protected Date getTimeAfter(Date afterTime) { + + // Computation is based on Gregorian year only. + Calendar cl = new GregorianCalendar(getTimeZone()); + + // move ahead one second, since we're computing the time *after* the + // given time + afterTime = new Date(afterTime.getTime() + 1000); + // CronTrigger does not deal with milliseconds + cl.setTime(afterTime); + cl.set(Calendar.MILLISECOND, 0); + + boolean gotOne = false; + // loop until we've computed the next time, or we've past the endTime + while (!gotOne) { + + // if (endTime != null && cl.getTime().after(endTime)) return null; + if (cl.get(Calendar.YEAR) > 2999) { // prevent endless loop... + return null; + } + + SortedSet st = null; + int t = 0; + + int sec = cl.get(Calendar.SECOND); + int min = cl.get(Calendar.MINUTE); + + // get second................................................. + st = seconds.tailSet(new Integer(sec)); + if (st != null && st.size() != 0) { + sec = (st.first()).intValue(); + } + else { + sec = (seconds.first()).intValue(); + min++; + cl.set(Calendar.MINUTE, min); + } + cl.set(Calendar.SECOND, sec); + + min = cl.get(Calendar.MINUTE); + int hr = cl.get(Calendar.HOUR_OF_DAY); + t = -1; + + // get minute................................................. + st = minutes.tailSet(new Integer(min)); + if (st != null && st.size() != 0) { + t = min; + min = (st.first()).intValue(); + } + else { + min = (minutes.first()).intValue(); + hr++; + } + if (min != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, min); + setCalendarHour(cl, hr); + continue; + } + cl.set(Calendar.MINUTE, min); + + hr = cl.get(Calendar.HOUR_OF_DAY); + int day = cl.get(Calendar.DAY_OF_MONTH); + t = -1; + + // get hour................................................... + st = hours.tailSet(new Integer(hr)); + if (st != null && st.size() != 0) { + t = hr; + hr = (st.first()).intValue(); + } + else { + hr = (hours.first()).intValue(); + day++; + } + if (hr != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + setCalendarHour(cl, hr); + continue; + } + cl.set(Calendar.HOUR_OF_DAY, hr); + + day = cl.get(Calendar.DAY_OF_MONTH); + int mon = cl.get(Calendar.MONTH) + 1; + // '+ 1' because calendar is 0-based for this field, and we are + // 1-based + t = -1; + int tmon = mon; + + // get day................................................... + boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC); + boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC); + if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule + st = daysOfMonth.tailSet(new Integer(day)); + if (lastdayOfMonth) { + if (!nearestWeekday) { + t = day; + day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + } + else { + t = day; + day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + + Calendar tcal = Calendar.getInstance(getTimeZone()); + tcal.set(Calendar.SECOND, 0); + tcal.set(Calendar.MINUTE, 0); + tcal.set(Calendar.HOUR_OF_DAY, 0); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); + + int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + int dow = tcal.get(Calendar.DAY_OF_WEEK); + + if (dow == Calendar.SATURDAY && day == 1) { + day += 2; + } + else if (dow == Calendar.SATURDAY) { + day -= 1; + } + else if (dow == Calendar.SUNDAY && day == ldom) { + day -= 2; + } + else if (dow == Calendar.SUNDAY) { + day += 1; + } + + tcal.set(Calendar.SECOND, sec); + tcal.set(Calendar.MINUTE, min); + tcal.set(Calendar.HOUR_OF_DAY, hr); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + Date nTime = tcal.getTime(); + if (nTime.before(afterTime)) { + day = 1; + mon++; + } + } + } + else if (nearestWeekday) { + t = day; + day = (daysOfMonth.first()).intValue(); + + Calendar tcal = Calendar.getInstance(getTimeZone()); + tcal.set(Calendar.SECOND, 0); + tcal.set(Calendar.MINUTE, 0); + tcal.set(Calendar.HOUR_OF_DAY, 0); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); + + int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + int dow = tcal.get(Calendar.DAY_OF_WEEK); + + if (dow == Calendar.SATURDAY && day == 1) { + day += 2; + } + else if (dow == Calendar.SATURDAY) { + day -= 1; + } + else if (dow == Calendar.SUNDAY && day == ldom) { + day -= 2; + } + else if (dow == Calendar.SUNDAY) { + day += 1; + } + + tcal.set(Calendar.SECOND, sec); + tcal.set(Calendar.MINUTE, min); + tcal.set(Calendar.HOUR_OF_DAY, hr); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + Date nTime = tcal.getTime(); + if (nTime.before(afterTime)) { + day = (daysOfMonth.first()).intValue(); + mon++; + } + } + else if (st != null && st.size() != 0) { + t = day; + day = (st.first()).intValue(); + // make sure we don't over-run a short month, such as february + int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + if (day > lastDay) { + day = (daysOfMonth.first()).intValue(); + mon++; + } + } + else { + day = (daysOfMonth.first()).intValue(); + mon++; + } + + if (day != t || mon != tmon) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, and we + // are 1-based + continue; + } + } + else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule + if (lastdayOfWeek) { // are we looking for the last XXX day of + // the month? + int dow = (daysOfWeek.first()).intValue(); // desired + // d-o-w + int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w + int daysToAdd = 0; + if (cDow < dow) { + daysToAdd = dow - cDow; + } + if (cDow > dow) { + daysToAdd = dow + (7 - cDow); + } + + int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + + if (day + daysToAdd > lDay) { // did we already miss the + // last one? + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon); + // no '- 1' here because we are promoting the month + continue; + } + + // find date of last occurance of this day in this month... + while ((day + daysToAdd + 7) <= lDay) { + daysToAdd += 7; + } + + day += daysToAdd; + + if (daysToAdd > 0) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' here because we are not promoting the month + continue; + } + + } + else if (nthdayOfWeek != 0) { + // are we looking for the Nth XXX day in the month? + int dow = (daysOfWeek.first()).intValue(); // desired + // d-o-w + int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w + int daysToAdd = 0; + if (cDow < dow) { + daysToAdd = dow - cDow; + } + else if (cDow > dow) { + daysToAdd = dow + (7 - cDow); + } + + boolean dayShifted = false; + if (daysToAdd > 0) { + dayShifted = true; + } + + day += daysToAdd; + int weekOfMonth = day / 7; + if (day % 7 > 0) { + weekOfMonth++; + } + + daysToAdd = (nthdayOfWeek - weekOfMonth) * 7; + day += daysToAdd; + if (daysToAdd < 0 || day > getLastDayOfMonth(mon, cl.get(Calendar.YEAR))) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon); + // no '- 1' here because we are promoting the month + continue; + } + else if (daysToAdd > 0 || dayShifted) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' here because we are NOT promoting the month + continue; + } + } + else { + int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w + int dow = (daysOfWeek.first()).intValue(); // desired + // d-o-w + st = daysOfWeek.tailSet(new Integer(cDow)); + if (st != null && st.size() > 0) { + dow = (st.first()).intValue(); + } + + int daysToAdd = 0; + if (cDow < dow) { + daysToAdd = dow - cDow; + } + if (cDow > dow) { + daysToAdd = dow + (7 - cDow); + } + + int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + + if (day + daysToAdd > lDay) { // will we pass the end of + // the month? + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon); + // no '- 1' here because we are promoting the month + continue; + } + else if (daysToAdd > 0) { // are we swithing days? + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, + // and we are 1-based + continue; + } + } + } + else { // dayOfWSpec && !dayOfMSpec + throw new UnsupportedOperationException( + "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."); + // TODO: + } + cl.set(Calendar.DAY_OF_MONTH, day); + + mon = cl.get(Calendar.MONTH) + 1; + // '+ 1' because calendar is 0-based for this field, and we are + // 1-based + int year = cl.get(Calendar.YEAR); + t = -1; + + // test for expressions that never generate a valid fire date, + // but keep looping... + if (year > YEAR_AT_WHICH_TO_GIVE_UP_SCHEDULING) { + return null; + } + + // get month................................................... + st = months.tailSet(new Integer(mon)); + if (st != null && st.size() != 0) { + t = mon; + mon = (st.first()).intValue(); + } + else { + mon = (months.first()).intValue(); + year++; + } + if (mon != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, and we are + // 1-based + cl.set(Calendar.YEAR, year); + continue; + } + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, and we are + // 1-based + + year = cl.get(Calendar.YEAR); + t = -1; + + // get year................................................... + st = years.tailSet(new Integer(year)); + if (st != null && st.size() != 0) { + t = year; + year = (st.first()).intValue(); + } + else { + return null; // ran out of years... + } + + if (year != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, 0); + // '- 1' because calendar is 0-based for this field, and we are + // 1-based + cl.set(Calendar.YEAR, year); + continue; + } + cl.set(Calendar.YEAR, year); + + gotOne = true; + } // while( !done ) + + return cl.getTime(); + } + + /** + * Advance the calendar to the particular hour paying particular attention to daylight saving problems. + * + * @param cal + * @param hour + */ + protected void setCalendarHour(Calendar cal, int hour) { + cal.set(Calendar.HOUR_OF_DAY, hour); + if (cal.get(Calendar.HOUR_OF_DAY) != hour && hour != 24) { + cal.set(Calendar.HOUR_OF_DAY, hour + 1); + } + } + + /** + * NOT YET IMPLEMENTED: Returns the time before the given time that the CronExpression matches. + */ + protected Date getTimeBefore(@SuppressWarnings("unused") Date endTime) { + // TODO: implement QUARTZ-423 + return null; + } + + /** + * NOT YET IMPLEMENTED: Returns the final time that the CronExpression will match. + */ + public Date getFinalFireTime() { + // TODO: implement QUARTZ-423 + return null; + } + + protected boolean isLeapYear(int year) { + return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); + } + + protected int getLastDayOfMonth(int monthNum, int year) { + + switch (monthNum) { + case 1: + return 31; + case 2: + return (isLeapYear(year)) ? 29 : 28; + case 3: + return 31; + case 4: + return 30; + case 5: + return 31; + case 6: + return 30; + case 7: + return 31; + case 8: + return 31; + case 9: + return 30; + case 10: + return 31; + case 11: + return 30; + case 12: + return 31; + default: + throw new IllegalArgumentException("Illegal month number: " + monthNum); + } + } + + private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException { + stream.defaultReadObject(); + try { + buildExpression(cronExpression); + } + catch (Exception ignore) { + // never happens + } + } + + @Override + public Object clone() { + CronExpression copy = null; + try { + copy = new CronExpression(getCronExpression()); + if (getTimeZone() != null) + copy.setTimeZone((TimeZone) getTimeZone().clone()); + } + catch (ParseException ex) { // never happens since the source is valid... + throw new IncompatibleClassChangeError("Not Cloneable."); + } + return copy; + } +} + +class ValueSet { + public int value; + + public int pos; +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/CronTimerTrigger.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/CronTimerTrigger.java new file mode 100644 index 0000000..3c55a1b --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/CronTimerTrigger.java @@ -0,0 +1,34 @@ +package com.serotonin.timer; + +import java.text.ParseException; +import java.util.Date; + +public class CronTimerTrigger extends TimerTrigger { + private final CronExpression cronExpression; + private long mostRecent; + + public CronTimerTrigger(String pattern) throws ParseException { + cronExpression = new CronExpression(pattern); + } + + @Override + protected long calculateNextExecutionTimeImpl() { + mostRecent = nextExecutionTime; + return cronExpression.getNextValidTimeAfter(new Date(nextExecutionTime)).getTime(); + } + + @Override + protected long calculateNextExecutionTimeImpl(long after) { + return cronExpression.getNextValidTimeAfter(new Date(after)).getTime(); + } + + @Override + protected long getFirstExecutionTime() { + return cronExpression.getNextValidTimeAfter(new Date(timer.currentTimeMillis())).getTime(); + } + + @Override + public long mostRecentExecutionTime() { + return mostRecent; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ExecutionRejectedException.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ExecutionRejectedException.java new file mode 100644 index 0000000..abf9455 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ExecutionRejectedException.java @@ -0,0 +1,12 @@ +package com.serotonin.timer; + +public class ExecutionRejectedException extends Exception { + /** + * + */ + private static final long serialVersionUID = 1L; + + public ExecutionRejectedException() { + super(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/FixedDelayTrigger.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/FixedDelayTrigger.java new file mode 100644 index 0000000..9f7e141 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/FixedDelayTrigger.java @@ -0,0 +1,32 @@ +package com.serotonin.timer; + +import java.util.Date; + +public class FixedDelayTrigger extends AbstractTimerTrigger { + private final long period; + + public FixedDelayTrigger(long delay, long period) { + super(delay); + this.period = period; + } + + public FixedDelayTrigger(Date start, long period) { + super(start); + this.period = period; + } + + @Override + protected long calculateNextExecutionTimeImpl() { + return timer.currentTimeMillis() + period; + } + + @Override + protected long calculateNextExecutionTimeImpl(long after) { + return after + period; + } + + @Override + public long mostRecentExecutionTime() { + return nextExecutionTime - period; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/FixedRateTrigger.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/FixedRateTrigger.java new file mode 100644 index 0000000..0e778e2 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/FixedRateTrigger.java @@ -0,0 +1,49 @@ +package com.serotonin.timer; + +import java.util.Date; + +public class FixedRateTrigger extends AbstractTimerTrigger { + private final long period; + + /** + * Use this constructor to quantize the start of the trigger to the period. + * + * @param period + * @param now + * @param quantize + * unused. Required for signature uniqueness. + */ + public FixedRateTrigger(long period, long now, boolean quantize) { + this(new Date(now + period - (now % period)), period); + } + + public FixedRateTrigger(long delay, long period) { + super(delay); + this.period = period; + } + + public FixedRateTrigger(Date start, long period) { + super(start); + this.period = period; + } + + @Override + protected long calculateNextExecutionTimeImpl() { + return nextExecutionTime + period; + } + + @Override + protected long calculateNextExecutionTimeImpl(long after) { + long d = after - nextExecutionTime; + if (d < 0) + return nextExecutionTime + period; + + long periods = d / period; + return nextExecutionTime + period * (periods + 1); + } + + @Override + public long mostRecentExecutionTime() { + return nextExecutionTime - period; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ModelTimeoutClient.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ModelTimeoutClient.java new file mode 100644 index 0000000..91c0f1e --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ModelTimeoutClient.java @@ -0,0 +1,9 @@ +/* + Copyright (C) 2006-2011 Serotonin Software Technologies Inc. All rights reserved. + @author Matthew Lohbihler + */ +package com.serotonin.timer; + +public interface ModelTimeoutClient { + void scheduleTimeout(T model, long fireTime); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ModelTimeoutTask.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ModelTimeoutTask.java new file mode 100644 index 0000000..5f5438d --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ModelTimeoutTask.java @@ -0,0 +1,34 @@ +package com.serotonin.timer; + +import java.util.Date; + +/** + * A parameterizable one-time task. Allows the pass-through of a model to the target. + * + * @author Matthew + * + * @param + */ +public class ModelTimeoutTask extends TimerTask { + private final ModelTimeoutClient client; + private final T model; + + public ModelTimeoutTask(long delay, ModelTimeoutClient client, T model) { + this(new OneTimeTrigger(delay), client, model); + } + + public ModelTimeoutTask(Date date, ModelTimeoutClient client, T model) { + this(new OneTimeTrigger(date), client, model); + } + + public ModelTimeoutTask(TimerTrigger trigger, ModelTimeoutClient client, T model) { + super(trigger); + this.client = client; + this.model = model; + } + + @Override + public void run(long runtime) { + client.scheduleTimeout(model, runtime); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/NamedRunnable.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/NamedRunnable.java new file mode 100644 index 0000000..6d4b116 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/NamedRunnable.java @@ -0,0 +1,32 @@ +package com.serotonin.timer; + +/** + * A class that wraps a Runnable and sets the thread name to the given name. + * + * @author Matthew Lohbihler + */ +public class NamedRunnable implements Runnable { + private final Runnable runnable; + private final String name; + + public NamedRunnable(Runnable runnable, String name) { + this.runnable = runnable; + this.name = name; + } + + public void run() { + String originalName = Thread.currentThread().getName(); + + // Append the given name to the original name. + Thread.currentThread().setName(originalName + " --> " + name); + + try { + // Ok, go ahead and run the thingy. + runnable.run(); + } + finally { + // Return the name to its original. + Thread.currentThread().setName(originalName); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/NonConcurrentTask.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/NonConcurrentTask.java new file mode 100644 index 0000000..37229a4 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/NonConcurrentTask.java @@ -0,0 +1,49 @@ +package com.serotonin.timer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.Date; + +/** + * Extends the timer task to prevent tasks from running concurrently. This is useful when a job is run regularly on a + * schedule, and there may be cases where a particular run could go on so long that is not finished before the next is + * scheduled to begin. In such cases, the next job will be aborted, and a warning message will be written to the log. + * + * @author Matthew Lohbihler + */ +abstract public class NonConcurrentTask extends TimerTask { + private static final Log LOG = LogFactory.getLog(NonConcurrentTask.class); + + private Thread thread; + private long threadRuntime; + + public NonConcurrentTask(TimerTrigger trigger) { + super(trigger); + } + + public NonConcurrentTask(TimerTrigger trigger, String name) { + super(trigger, name); + } + + @Override + final public void run(long runtime) { + Thread localThread = thread; + if (localThread != null) { + LOG.warn("NonConcurrentTask run at " + new Date(runtime) + " aborted because another run at " + + new Date(threadRuntime) + " has not yet completed: " + getClass().getName()); + return; + } + + try { + thread = Thread.currentThread(); + threadRuntime = runtime; + runNonConcurrent(runtime); + } + finally { + thread = null; + } + } + + abstract public void runNonConcurrent(long runtime); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/OneTimeTrigger.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/OneTimeTrigger.java new file mode 100644 index 0000000..a804238 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/OneTimeTrigger.java @@ -0,0 +1,28 @@ +package com.serotonin.timer; + +import java.util.Date; + +public class OneTimeTrigger extends AbstractTimerTrigger { + public OneTimeTrigger(long delay) { + super(delay); + } + + public OneTimeTrigger(Date start) { + super(start); + } + + @Override + protected long calculateNextExecutionTimeImpl() { + return 0; + } + + @Override + protected long calculateNextExecutionTimeImpl(long after) { + return 0; + } + + @Override + final public long mostRecentExecutionTime() { + return nextExecutionTime; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/RealTimeTimer.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/RealTimeTimer.java new file mode 100644 index 0000000..22f43b3 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/RealTimeTimer.java @@ -0,0 +1,320 @@ +package com.serotonin.timer; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + + +public class RealTimeTimer extends AbstractTimer { + private static final Log LOG = LogFactory.getLog(RealTimeTimer.class); + + /** + * The timer task queue. This data structure is shared with the timer thread. The timer produces tasks, via its + * various schedule calls, and the timer thread consumes, executing timer tasks as appropriate, and removing them + * from the queue when they're obsolete. + */ + private final TaskQueue queue = new TaskQueue(); + + /** + * The timer thread. + */ + private TimerThread thread; + + // Do i own the executor? + private boolean ownsExecutor; + private Exception cancelStack; + + private TimeSource timeSource = new SystemTimeSource(); + + public void setTimeSource(TimeSource timeSource) { + this.timeSource = timeSource; + } + + public void init(int minThread, int maxThread) { + ownsExecutor = true; + ThreadFactory threadFactory = new BasicThreadFactory.Builder() + .namingPattern("sw-timer-").build(); + init(new ThreadPoolExecutor(minThread, maxThread, 30L, TimeUnit.SECONDS, new SynchronousQueue<>(), threadFactory)); + } + + public void init(ExecutorService executorService) { + thread = new TimerThread(queue, executorService, timeSource); + thread.setName("Serotonin Timer"); + thread.setDaemon(false); + thread.start(); + } + + @Override + public boolean isInitialized() { + return thread != null; + } + + /** + * This object causes the timer's task execution thread to exit gracefully when there are no live references to the + * Timer object and no tasks in the timer queue. It is used in preference to a finalizer on Timer as such a + * finalizer would be susceptible to a subclass's finalizer forgetting to call it. + */ + @Override + protected void finalize() { + synchronized (queue) { + if (thread != null) + thread.newTasksMayBeScheduled = false; + if (cancelStack == null) + cancelStack = new Exception(); + queue.notify(); + } + } + + /** + * A convenience method that executes the given command in the executor service immediately. + * + * @param command + * @throws ExecutionRejectedException + */ + @Override + public void execute(Runnable command) { + if (thread == null) + throw new IllegalStateException("Run init first"); + thread.execute(command); + } + + /** + * A convenience method that executes the given command in the executor service immediately. + * + * @param command + * @throws ExecutionRejectedException + */ + @Override + public void execute(ScheduledRunnable command, long fireTime) { + if (thread == null) + throw new IllegalStateException("Run init first"); + thread.execute(command, fireTime); + } + + /** + * Schedule the specified timer task for execution at the specified time with the specified period, in milliseconds. + * If period is positive, the task is scheduled for repeated execution; if period is zero, the task is scheduled for + * one-time execution. Time is specified in Date.getTime() format. This method checks timer state, task state, and + * initial execution time, but not period. + * + * @throws IllegalArgumentException + * if time() is negative. + * @throws IllegalStateException + * if task was already scheduled or cancelled, timer was cancelled, or timer thread terminated. + */ + @Override + protected void scheduleImpl(TimerTask task) { + if (thread == null) + throw new IllegalStateException("Run init first"); + + if (task.state == TimerTask.CANCELLED || task.state == TimerTask.EXECUTED) + throw new IllegalStateException("Task already executed or cancelled"); + + synchronized (queue) { + if (!thread.newTasksMayBeScheduled) { + if (cancelStack != null) { + LOG.error("Timer already cancelled."); + LOG.error(" Cancel stack:", cancelStack); + LOG.error(" Current stack:", new Exception()); + throw new IllegalStateException("Timer already cancelled.", cancelStack); + } + throw new IllegalStateException("Timer already cancelled."); + } + + synchronized (task.lock) { + if (task.state == TimerTask.VIRGIN) { + long time = task.trigger.getFirstExecutionTime(); + + if (time < 0) + throw new IllegalArgumentException("Illegal execution time."); + + task.trigger.nextExecutionTime = time; + task.state = TimerTask.SCHEDULED; + } + } + + queue.add(task); + if (queue.getMin() == task) + queue.notify(); + } + } + + /** + * Terminates this timer, discarding any currently scheduled tasks. Does not interfere with a currently executing + * task (if it exists). Once a timer has been terminated, its execution thread terminates gracefully, and no more + * tasks may be scheduled on it. + * + *

+ * Note that calling this method from within the run method of a timer task that was invoked by this timer + * absolutely guarantees that the ongoing task execution is the last task execution that will ever be performed by + * this timer. + * + *

+ * This method may be called repeatedly; the second and subsequent calls have no effect. + */ + @Override + public List cancel() { + List tasks; + synchronized (queue) { + thread.newTasksMayBeScheduled = false; + if (cancelStack == null) + cancelStack = new Exception(); + tasks = getTasks(); + queue.clear(); + queue.notify(); // In case queue was already empty. + } + + if (ownsExecutor) + getExecutorService().shutdown(); + + return tasks; + } + + public ExecutorService getExecutorService() { + return thread.getExecutorService(); + } + + /** + * Removes all canceled tasks from this timer's task queue. Calling this method has no effect on the behavior of + * the timer, but eliminates the references to the canceled tasks from the queue. If there are no external + * references to these tasks, they become eligible for garbage collection. + * + *

+ * Most programs will have no need to call this method. It is designed for use by the rare application that cancels + * a large number of tasks. Calling this method trades time for space: the runtime of the method may be proportional + * to n + c log n, where n is the number of tasks in the queue and c is the number of canceled tasks. + * + *

+ * Note that it is permissible to call this method from within a a task scheduled on this timer. + * + * @return the number of tasks removed from the queue. + * @since 1.5 + */ + @Override + public int purge() { + int result = 0; + + synchronized (queue) { + for (int i = queue.size(); i > 0; i--) { + if (queue.get(i).state == TimerTask.CANCELLED) { + queue.quickRemove(i); + result++; + } + } + + if (result != 0) + queue.heapify(); + } + + return result; + } + + @Override + public int size() { + return queue.size(); + } + + @Override + public List getTasks() { + List result = new ArrayList(); + synchronized (queue) { + for (int i = 0; i < queue.size(); i++) + result.add(queue.get(i + 1)); + } + return result; + } + + @Override + public long currentTimeMillis() { + return timeSource.currentTimeMillis(); + } + // + // @SuppressWarnings("unchecked") + // public static void main(String[] args) throws Exception { + // RealTimeTimer timer = new RealTimeTimer(); + // ExecutorService executorService = Executors.newCachedThreadPool(); + // timer.init(executorService); + // + // timer.schedule(new NamedTask("task 7", new OneTimeTrigger(25000))); + // timer.schedule(new NamedTask("task 1", new OneTimeTrigger(1000))); + // timer.schedule(new NamedTask("task 2", new OneTimeTrigger(2000))); + // timer.schedule(new NamedTask("task 4", new OneTimeTrigger(20000))); + // timer.schedule(new NamedTask("task 5", new OneTimeTrigger(21000))); + // timer.schedule(new NamedTask("task 3", new OneTimeTrigger(10000))); + // timer.schedule(new NamedTask("task 6", new OneTimeTrigger(22000))); + // timer.schedule(new NamedTask("rate", new FixedRateTrigger(5000, 1800))); + // timer.schedule(new NamedTask("delay", new FixedDelayTrigger(6000, + // 2100))); + // timer.schedule(new NamedTask("cron", new + // CronTimerTrigger("0/6 * * * * ?"))); + // + // Thread.sleep(15000); + // System.out.println("Serializing"); + // + // List tasks = timer.cancel(); + // timer = null; + // + // ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // ObjectOutputStream oos = new ObjectOutputStream(baos); + // oos.writeObject(tasks); + // + // Thread.sleep(20000); + // + // ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + // ObjectInputStream ois = new ObjectInputStream(bais); + // tasks = (List) ois.readObject(); + // + // System.out.println("Simulating"); + // SimulationTimer sim = new SimulationTimer(); + // for (TimerTask task : tasks) + // sim.schedule(task); + // + // for (int i = 0; i < 10; i++) { + // long start = System.currentTimeMillis(); + // sim.fastForwardTo(start); + // System.out.println("Run " + i + " took " + (System.currentTimeMillis() - + // start) + " ms"); + // } + // + // System.out.println("Real time"); + // timer = new RealTimeTimer(); + // timer.init(executorService); + // timer.scheduleAll(sim); + // + // Thread.sleep(10000); + // + // timer.cancel(); + // executorService.shutdown(); + // } + // + // static class NamedTask extends TimerTask { + // private static final long serialVersionUID = 1L; + // + // String name; + // + // NamedTask(String name, TimerTrigger trigger) { + // super(trigger); + // this.name = name; + // } + // + // @Override + // public String toString() { + // return "NamedTask(" + name + ")"; + // } + // + // @Override + // protected void run(long runtime) { + // System.out.println(name + " ran at " + runtime); + // try { + // Thread.sleep(300); + // } + // catch (InterruptedException e) { + // // no op + // } + // } + // } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ScheduledNamedRunnable.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ScheduledNamedRunnable.java new file mode 100644 index 0000000..0f75b9c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ScheduledNamedRunnable.java @@ -0,0 +1,33 @@ +package com.serotonin.timer; + +/** + * A class that wraps a Runnable and sets the thread name to the given name. + * + * @author Matthew Lohbihler + */ +public class ScheduledNamedRunnable implements ScheduledRunnable { + private final ScheduledRunnable runnable; + private final String name; + + public ScheduledNamedRunnable(ScheduledRunnable runnable, String name) { + this.runnable = runnable; + this.name = name; + } + + @Override + public void run(long fireTime) { + String originalName = Thread.currentThread().getName(); + + // Append the given name to the original name. + Thread.currentThread().setName(originalName + " --> " + name); + + try { + // Ok, go ahead and run the thingy. + runnable.run(fireTime); + } + finally { + // Return the name to its original. + Thread.currentThread().setName(originalName); + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ScheduledRunnable.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ScheduledRunnable.java new file mode 100644 index 0000000..e16ef8c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/ScheduledRunnable.java @@ -0,0 +1,8 @@ +package com.serotonin.timer; + +/** + * Same as {@link Runnable}, but has a fireTime (millis) parameter. + */ +public interface ScheduledRunnable { + void run(long fireTime); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/SimulationTimeSource.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/SimulationTimeSource.java new file mode 100644 index 0000000..a2b1e69 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/SimulationTimeSource.java @@ -0,0 +1,13 @@ +package com.serotonin.timer; + +public class SimulationTimeSource implements TimeSource { + private long time; + + public long currentTimeMillis() { + return time; + } + + public void setTime(long time) { + this.time = time; + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/SimulationTimer.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/SimulationTimer.java new file mode 100644 index 0000000..542691c --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/SimulationTimer.java @@ -0,0 +1,194 @@ +package com.serotonin.timer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * The simulation timer is a single threaded timer under the temporal control of the next and fastForward methods. Tasks + * are run in the same thread as the timer, so they will seem to complete instantly. Running them in an executor has the + * opposite effect of making them appear to take an awful long time to complete. + * + * @author Matthew Lohbihler + */ +public class SimulationTimer extends AbstractTimer { + private final List queue = new ArrayList(); + private boolean cancelled; + private long currentTime; + + @Override + public boolean isInitialized() { + return true; + } + + public void setStartTime(long startTime) { + currentTime = startTime; + } + + public void next() { + fastForwardTo(currentTime + 1); + } + + public void fastForwardTo(long time) { + while (!queue.isEmpty() && queue.get(0).trigger.nextExecutionTime <= time) { + TimerTask task = queue.get(0); + + currentTime = task.trigger.nextExecutionTime; + + if (task.state == TimerTask.CANCELLED) + queue.remove(0); + else { + long next = task.trigger.calculateNextExecutionTime(); + if (next <= 0) { // Non-repeating, remove + queue.remove(0); + task.state = TimerTask.EXECUTED; + } + else { + // Repeating task, reschedule + task.trigger.nextExecutionTime = next; + updateQueue(); + } + + task.run(); + } + } + + currentTime = time; + } + + @Override + public void execute(Runnable command) { + command.run(); + } + + @Override + public void execute(ScheduledRunnable command, long fireTime) { + command.run(fireTime); + } + + @Override + protected void scheduleImpl(TimerTask task) { + if (cancelled) + throw new IllegalStateException("Timer already cancelled."); + + if (task.state == TimerTask.CANCELLED || task.state == TimerTask.EXECUTED) + throw new IllegalStateException("Task already executed or cancelled"); + + if (task.state == TimerTask.VIRGIN) { + long time = task.trigger.getFirstExecutionTime(); + if (time < 0) + throw new IllegalArgumentException("Illegal execution time."); + + task.trigger.nextExecutionTime = time; + task.state = TimerTask.SCHEDULED; + } + + queue.add(task); + updateQueue(); + } + + private void updateQueue() { + Collections.sort(queue, new Comparator() { + @Override + public int compare(TimerTask t1, TimerTask t2) { + long diff = t1.trigger.nextExecutionTime - t2.trigger.nextExecutionTime; + if (diff < 0) + return -1; + if (diff == 0) + return 0; + return 1; + } + }); + } + + @Override + public List cancel() { + cancelled = true; + List tasks = getTasks(); + queue.clear(); + return tasks; + } + + @Override + public int purge() { + int result = 0; + + for (int i = queue.size(); i > 0; i--) { + if (queue.get(i).state == TimerTask.CANCELLED) { + queue.remove(i); + result++; + } + } + + return result; + } + + @Override + public int size() { + return queue.size(); + } + + @Override + public List getTasks() { + return new ArrayList(queue); + } + + @Override + public long currentTimeMillis() { + return currentTime; + } + // + // public static void main(String[] args) throws Exception { + // long startTime = System.currentTimeMillis() - 32000; + // SimulationTimer simTimer = new SimulationTimer(); + // simTimer.setStartTime(startTime); + // + // simTimer.schedule(new NamedTask("task 7", new OneTimeTrigger(25000))); + // simTimer.schedule(new NamedTask("task 1", new OneTimeTrigger(1000))); + // simTimer.schedule(new NamedTask("task 2", new OneTimeTrigger(2000))); + // simTimer.schedule(new NamedTask("task 4", new OneTimeTrigger(20000))); + // simTimer.schedule(new NamedTask("task 5", new OneTimeTrigger(21000))); + // simTimer.schedule(new NamedTask("task 3", new OneTimeTrigger(10000))); + // simTimer.schedule(new NamedTask("task 6", new OneTimeTrigger(22000))); + // simTimer.schedule(new NamedTask("rate", new FixedRateTrigger(5000, + // 1800))); + // simTimer.schedule(new NamedTask("delay", new FixedDelayTrigger(6000, + // 2100))); + // simTimer.schedule(new NamedTask("cron", new + // CronTimerTrigger("0/6 * * * * ?"))); + // + // simTimer.fastForwardTo(System.currentTimeMillis()); + // + // System.out.println("Rescheduling"); + // + // RealTimeTimer rtTimer = new RealTimeTimer(); + // ExecutorService executorService = Executors.newCachedThreadPool(); + // rtTimer.init(executorService); + // rtTimer.scheduleAll(simTimer); + // + // Thread.sleep(20000); + // + // rtTimer.cancel(); + // executorService.shutdown(); + // } + // + // static class NamedTask extends TimerTask { + // String name; + // + // NamedTask(String name, TimerTrigger trigger) { + // super(trigger); + // this.name = name; + // } + // + // @Override + // public String toString() { + // return "NamedTask(" + name + ")"; + // } + // + // @Override + // protected void run(long runtime) { + // System.out.println(name + " ran at " + runtime); + // } + // } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/SystemTimeSource.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/SystemTimeSource.java new file mode 100644 index 0000000..3f377c1 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/SystemTimeSource.java @@ -0,0 +1,12 @@ +package com.serotonin.timer; + +/** + * An implementation of TimeSource that returns the host time via System. + * + * @author Matthew Lohbihler + */ +public class SystemTimeSource implements TimeSource { + public long currentTimeMillis() { + return System.currentTimeMillis(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TaskQueue.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TaskQueue.java new file mode 100644 index 0000000..92cf612 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TaskQueue.java @@ -0,0 +1,148 @@ +package com.serotonin.timer; + +import java.util.Arrays; + +class TaskQueue { + /** + * Priority queue represented as a balanced binary heap: the two children of queue[n] are queue[2*n] and + * queue[2*n+1]. The priority queue is ordered on the nextExecutionTime field: The TimerTask with the lowest + * nextExecutionTime is in queue[1] (assuming the queue is nonempty). For each node n in the heap, and each + * descendant of n, d, n.nextExecutionTime <= d.nextExecutionTime. + */ + private TimerTask[] queue = new TimerTask[128]; + + /** + * The number of tasks in the priority queue. (The tasks are stored in queue[1] up to queue[size]). + */ + private int size = 0; + + /** + * Returns the number of tasks currently on the queue. + */ + int size() { + return size; + } + + /** + * Adds a new task to the priority queue. + */ + void add(TimerTask task) { + // Grow backing store if necessary + if (size + 1 == queue.length) + queue = Arrays.copyOf(queue, 2 * queue.length); + + queue[++size] = task; + fixUp(size); + } + + /** + * Return the "head task" of the priority queue. (The head task is an task with the lowest nextExecutionTime.) + */ + TimerTask getMin() { + return queue[1]; + } + + /** + * Return the ith task in the priority queue, where i ranges from 1 (the head task, which is returned by getMin) to + * the number of tasks on the queue, inclusive. + */ + TimerTask get(int i) { + return queue[i]; + } + + /** + * Remove the head task from the priority queue. + */ + void removeMin() { + queue[1] = queue[size]; + queue[size--] = null; // Drop extra reference to prevent memory leak + fixDown(1); + } + + /** + * Removes the ith element from queue without regard for maintaining the heap invariant. Recall that queue is + * one-based, so 1 <= i <= size. + */ + void quickRemove(int i) { + assert i <= size; + + queue[i] = queue[size]; + queue[size--] = null; // Drop extra ref to prevent memory leak + } + + /** + * Sets the nextExecutionTime associated with the head task to the specified value, and adjusts priority queue + * accordingly. + */ + void rescheduleMin(long newTime) { + queue[1].trigger.nextExecutionTime = newTime; + fixDown(1); + } + + /** + * Returns true if the priority queue contains no elements. + */ + boolean isEmpty() { + return size == 0; + } + + /** + * Removes all elements from the priority queue. + */ + void clear() { + // Null out task references to prevent memory leak + for (int i = 1; i <= size; i++) + queue[i] = null; + + size = 0; + } + + /** + * Establishes the heap invariant (described above) assuming the heap satisfies the invariant except possibly for + * the leaf-node indexed by k (which may have a nextExecutionTime less than its parent's). + * + * This method functions by "promoting" queue[k] up the hierarchy (by swapping it with its parent) repeatedly until + * queue[k]'s nextExecutionTime is greater than or equal to that of its parent. + */ + private void fixUp(int k) { + while (k > 1) { + int j = k >> 1; + if (queue[j].trigger.nextExecutionTime <= queue[k].trigger.nextExecutionTime) + break; + TimerTask tmp = queue[j]; + queue[j] = queue[k]; + queue[k] = tmp; + k = j; + } + } + + /** + * Establishes the heap invariant (described above) in the subtree rooted at k, which is assumed to satisfy the heap + * invariant except possibly for node k itself (which may have a nextExecutionTime greater than its children's). + * + * This method functions by "demoting" queue[k] down the hierarchy (by swapping it with its smaller child) + * repeatedly until queue[k]'s nextExecutionTime is less than or equal to those of its children. + */ + private void fixDown(int k) { + int j; + while ((j = k << 1) <= size && j > 0) { + if (j < size && queue[j].trigger.nextExecutionTime > queue[j + 1].trigger.nextExecutionTime) + j++; // j indexes smallest kid + if (queue[k].trigger.nextExecutionTime <= queue[j].trigger.nextExecutionTime) + break; + TimerTask tmp = queue[j]; + queue[j] = queue[k]; + queue[k] = tmp; + k = j; + } + } + + /** + * Establishes the heap invariant (described above) in the entire tree, assuming nothing about the order of the + * elements prior to the call. + */ + void heapify() { + for (int i = size / 2; i >= 1; i--) + fixDown(i); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimeSource.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimeSource.java new file mode 100644 index 0000000..488fb87 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimeSource.java @@ -0,0 +1,11 @@ +package com.serotonin.timer; + +/** + * An interface to abstract the source of current time away from System. This allows code to run in simulations where + * the time is controlled explicitly. + * + * @author Matthew Lohbihler + */ +public interface TimeSource { + long currentTimeMillis(); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimeoutTask.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimeoutTask.java new file mode 100644 index 0000000..eb6d679 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimeoutTask.java @@ -0,0 +1,30 @@ +package com.serotonin.timer; + +import java.util.Date; + +/** + * A simple way of creating a timeout. + * + * @author Matthew + */ +public class TimeoutTask extends TimerTask { + private final ScheduledRunnable client; + + public TimeoutTask(long delay, ScheduledRunnable client) { + this(new OneTimeTrigger(delay), client); + } + + public TimeoutTask(Date date, ScheduledRunnable client) { + this(new OneTimeTrigger(date), client); + } + + public TimeoutTask(TimerTrigger trigger, ScheduledRunnable client) { + super(trigger); + this.client = client; + } + + @Override + public void run(long runtime) { + client.run(runtime); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimerTask.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimerTask.java new file mode 100644 index 0000000..17648d8 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimerTask.java @@ -0,0 +1,167 @@ +package com.serotonin.timer; + +import org.apache.commons.lang3.StringUtils; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public abstract class TimerTask implements Runnable { + /** + * This object is used to control access to the TimerTask internals. + */ + Object lock = new Object(); + + /** + * The state of this task, chosen from the constants below. + */ + volatile int state = VIRGIN; + + /** + * This task has not yet been scheduled. + */ + static final int VIRGIN = 0; + + /** + * This task is scheduled for execution. If it is a non-repeating task, it has not yet been executed. + */ + static final int SCHEDULED = 1; + + /** + * This non-repeating task has already executed (or is currently executing) and has not been cancelled. + */ + static final int EXECUTED = 2; + + /** + * This task has been cancelled (with a call to TimerTask.cancel). + */ + static final int CANCELLED = 3; + + TimerTrigger trigger; + private final String name; + + /** + * Indicates that if the task is running at the moment it is cancelled, the cancellation should wait until the task + * is done. This is useful if the task uses resources that need to be shut down before the timer is shutdown. + */ + private boolean completeBeforeCancel; + + private final ReadWriteLock cancelLock = new ReentrantReadWriteLock(); + + public TimerTask(TimerTrigger trigger) { + this(trigger, null); + } + + public TimerTask(TimerTrigger trigger, String name) { + if (trigger == null) + throw new NullPointerException(); + this.trigger = trigger; + this.name = name; + } + + public boolean isCompleteBeforeCancel() { + return completeBeforeCancel; + } + + public void setCompleteBeforeCancel(boolean completeBeforeCancel) { + this.completeBeforeCancel = completeBeforeCancel; + } + + /** + * Cancels this timer task. If the task has been scheduled for one-time execution and has not yet run, or has not + * yet been scheduled, it will never run. If the task has been scheduled for repeated execution, it will never run + * again. (If the task is running when this call occurs, the task will run to completion, but will never run again.) + * + *

+ * Note that calling this method from within the run method of a repeating timer task absolutely guarantees + * that the timer task will not run again. + * + *

+ * This method may be called repeatedly; the second and subsequent calls have no effect. + * + * @return true if this task is scheduled for one-time execution and has not yet run, or this task is scheduled for + * repeated execution. Returns false if the task was scheduled for one-time execution and has already run, + * or if the task was never scheduled, or if the task was already cancelled. (Loosely speaking, this method + * returns true if it prevents one or more scheduled executions from taking place.) + */ + public boolean cancel() { + synchronized (lock) { + boolean result = (state == SCHEDULED); + + if (completeBeforeCancel) { + try { + cancelLock.writeLock().lock(); + state = CANCELLED; + } + finally { + cancelLock.writeLock().unlock(); + } + } + else + state = CANCELLED; + + return result; + } + } + + abstract public void run(long runtime); + + @Override + final public void run() { + // TODO if the task is not called quickly enough, the nextExecutionTime + // may have already been changed. It would + // be better to have the value assigned at the moment the task is + // submitted to the thread pool, but the + // interface doesn't allow it. For now, this will have to do. + long t; + synchronized (lock) { + t = trigger.mostRecentExecutionTime(); + } + + String originalName = null; + try { + if (!StringUtils.isBlank(name)) { + // This uses roughly the same code as in NamedRunnable to rename + // the thread for the duration of the task execution. + originalName = Thread.currentThread().getName(); + + // Append the given name to the original name. + Thread.currentThread().setName(originalName + " --> " + name); + } + + if (completeBeforeCancel) { + try { + cancelLock.readLock().lock(); + if (state != CANCELLED) + run(t); + } + finally { + cancelLock.readLock().unlock(); + } + } + else + // Ok, go ahead and run the thingy. + run(t); + } + finally { + if (originalName != null) + // Return the name to its original. + Thread.currentThread().setName(originalName); + } + } + + public boolean isCancelled() { + return state == CANCELLED; + } + + public long getNextExecutionTime() { + return trigger.nextExecutionTime; + } + + void setTimer(AbstractTimer timer) { + trigger.setTimer(timer); + } + + AbstractTimer getTimer() { + return trigger.getTimer(); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimerThread.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimerThread.java new file mode 100644 index 0000000..5c52599 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimerThread.java @@ -0,0 +1,124 @@ +package com.serotonin.timer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; + +class TimerThread extends Thread { + private static final Log LOG = LogFactory.getLog(TimerThread.class); + + /** + * This flag is set to false by the reaper to inform us that there are no more live references to our Timer object. + * Once this flag is true and there are no more tasks in our queue, there is no work left for us to do, so we + * terminate gracefully. Note that this field is protected by queue's monitor! + */ + boolean newTasksMayBeScheduled = true; + + /** + * Our Timer's queue. We store this reference in preference to a reference to the Timer so the reference graph + * remains acyclic. Otherwise, the Timer would never be garbage-collected and this thread would never go away. + */ + private final TaskQueue queue; + + private final ExecutorService executorService; + private final TimeSource timeSource; + + TimerThread(TaskQueue queue, ExecutorService executorService, TimeSource timeSource) { + this.queue = queue; + this.executorService = executorService; + this.timeSource = timeSource; + } + + @Override + public void run() { + try { + mainLoop(); + } + catch (Throwable t) { + LOG.fatal("TimerThread failed", t); + } + finally { + // Someone killed this Thread, behave as if Timer was cancelled + synchronized (queue) { + newTasksMayBeScheduled = false; + queue.clear(); // Eliminate obsolete references + } + } + } + + void execute(Runnable command) { + executorService.execute(command); + } + + void execute(final ScheduledRunnable command, final long fireTime) { + executorService.execute(new Runnable() { + @Override + public void run() { + command.run(fireTime); + } + }); + } + + ExecutorService getExecutorService() { + return executorService; + } + + /** + * The main timer loop. (See class comment.) + */ + private void mainLoop() { + while (true) { + try { + TimerTask task; + boolean taskFired; + synchronized (queue) { + // Wait for queue to become non-empty + while (queue.isEmpty() && newTasksMayBeScheduled) + queue.wait(); + if (queue.isEmpty()) + break; // Queue is empty and will forever remain; die + + // Queue nonempty; look at first evt and do the right thing + long executionTime; + task = queue.getMin(); + synchronized (task.lock) { + if (task.state == TimerTask.CANCELLED) { + queue.removeMin(); + continue; // No action required, poll queue again + } + executionTime = task.trigger.nextExecutionTime; + if (taskFired = (executionTime <= timeSource.currentTimeMillis())) { + long next = task.trigger.calculateNextExecutionTime(); + if (next <= 0) { // Non-repeating, remove + queue.removeMin(); + task.state = TimerTask.EXECUTED; + } + else + // Repeating task, reschedule + queue.rescheduleMin(next); + } + } + if (!taskFired) {// Task hasn't yet fired; wait + long wait = executionTime - timeSource.currentTimeMillis(); + if (wait > 0) + queue.wait(wait); + } + } + if (taskFired) { + // Task fired; run it, holding no locks + try { + executorService.execute(task); + } + catch (RejectedExecutionException e) { + LOG.warn("Rejected task: " + task, e); + } + } + } + catch (InterruptedException e) { + // no op + } + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimerTrigger.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimerTrigger.java new file mode 100644 index 0000000..be2e734 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/TimerTrigger.java @@ -0,0 +1,80 @@ +package com.serotonin.timer; + +import java.util.Date; + +abstract public class TimerTrigger { + // The maximum time that a task can run late. If the next run time is calculated to be more than this in the past + // it will be adjusted to be more current. + private static final int MAX_TARDINESS = 1000 * 60 * 10; // 10 minutes. + + protected AbstractTimer timer; + + void setTimer(AbstractTimer timer) { + this.timer = timer; + } + + AbstractTimer getTimer() { + return timer; + } + + /** + * Next execution time for this task in the format returned by System.currentTimeMillis, assuming this task is + * scheduled for execution. For repeating tasks, this field is updated prior to each task execution. + */ + long nextExecutionTime; + + /** + * Returns the scheduled execution time of the most recent actual execution of this task. (If this + * method is invoked while task execution is in progress, the return value is the scheduled execution time of the + * ongoing task execution.) + * + *

+ * This method is typically invoked from within a task's run method, to determine whether the current execution of + * the task is sufficiently timely to warrant performing the scheduled activity: + * + *

+     * public void run() {
+     *     if (System.currentTimeMillis() - scheduledExecutionTime() >= MAX_TARDINESS)
+     *         return; // Too late; skip this execution.
+     *     // Perform the task
+     * }
+     * 
+ * + * This method is typically not used in conjunction with fixed-delay execution repeating tasks, as + * their scheduled execution times are allowed to drift over time, and so are not terribly significant. + * + * @return the time at which the most recent execution of this task was scheduled to occur, in the format returned + * by Date.getTime(). The return value is undefined if the task has yet to commence its first execution. + * @see Date#getTime() + */ + abstract public long mostRecentExecutionTime(); + + abstract protected long getFirstExecutionTime(); + + public long getNextExecutionTime() { + return nextExecutionTime; + } + + /** + * Return the time of the next execution, or -1 if there isn't one. + * + * @return + */ + protected final long calculateNextExecutionTime() { + long next = calculateNextExecutionTimeImpl(); + + // If the system time changes on the O/S (due to NTP, manual change, or some other reason) this calculation + // can cause a good amount of disturbance (either a schedule that doesn't run for a while, or one that runs + // repeatedly in order to catch up). We check here to assure that the next execution time is not entirely + // ridiculous, and adjust it if so. + long now = timer.currentTimeMillis(); + if (now - next >= MAX_TARDINESS) + next = calculateNextExecutionTimeImpl(now); + + return next; + } + + abstract protected long calculateNextExecutionTimeImpl(); + + abstract protected long calculateNextExecutionTimeImpl(long after); +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/sync/AsyncJobSink.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/sync/AsyncJobSink.java new file mode 100644 index 0000000..b506920 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/sync/AsyncJobSink.java @@ -0,0 +1,99 @@ +package com.serotonin.timer.sync; + +import java.util.concurrent.ConcurrentLinkedQueue; + +public class AsyncJobSink implements Runnable { + private final ConcurrentLinkedQueue inbox = new ConcurrentLinkedQueue(); + + private Thread thread; + private volatile boolean running; + + public synchronized boolean initialize() { + if (thread == null) { + running = true; + thread = new Thread(this); + thread.start(); + return true; + } + return false; + } + + public synchronized void terminate() { + if (thread != null) { + running = false; + try { + thread.join(); + } + catch (InterruptedException e) { + // no op + } + thread = null; + } + } + + public boolean add(Event event) { + if (running) + return inbox.offer(event); + return false; + } + + @Override + public void run() { + int processed = 0; + + while (true) { + Event event = inbox.poll(); + if (event != null) { + System.out.println("Processed " + event.getId()); + processed++; + } + else if (!running) + break; + else { + System.out.println("null"); + try { + Thread.sleep(50); + } + catch (InterruptedException e) { + // no op + } + } + } + + System.out.println("Exiting having processed " + processed); + } + + static class Event { + static int nextId = 0; + + private final String id; + + public Event() { + id = Integer.toString(nextId++); + } + + public String getId() { + return id; + } + } + + public static void main(String[] args) throws Exception { + AsyncJobSink sink = new AsyncJobSink(); + sink.initialize(); + + long start = System.currentTimeMillis(); + + int failed = 0; + for (int i = 0; i < 100; i++) { + Event event = new Event(); + if (!sink.add(event)) + failed++; + } + + Thread.sleep(10000); + sink.terminate(); + + System.out.println("Failed to add " + failed); + System.out.println("Runtime: " + (System.currentTimeMillis() - start)); + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/sync/SingleExecutorSingleWaiter.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/sync/SingleExecutorSingleWaiter.java new file mode 100644 index 0000000..b087ad6 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/sync/SingleExecutorSingleWaiter.java @@ -0,0 +1,137 @@ +/* + Copyright (C) 2006-2009 Serotonin Software Technologies Inc. + @author Matthew Lohbihler + */ +package com.serotonin.timer.sync; + +import com.serotonin.timer.AbstractTimer; + +/** + * This class is useful when an exclusive, long-running task gets called more often than is practical. For example, a + * large object needs to be persisted, but it changes fairly often. This class will execute the submitted runnable + * if there is currently nothing running. If another task is already running, the new task will wait. If there is + * already a task waiting, the new task will replace the waiting task. So, tasks that are started are always allowed to + * complete, but tasks that are no necessary to run are discarded. + * + * @author Matthew Lohbihler + */ +public class SingleExecutorSingleWaiter { + final AbstractTimer timer; + final Object lock = new Object(); + + Runnable executing; + Runnable waiting; + + public SingleExecutorSingleWaiter(AbstractTimer timer) { + this.timer = timer; + } + + public void execute(Runnable task) { + synchronized (lock) { + if (executing != null) { + waiting = task; + return; + } + + executing = task; + executeImpl(); + } + } + + void executeImpl() { + timer.execute(new TaskWrapper(executing)); + } + + class TaskWrapper implements Runnable { + private final Runnable command; + + public TaskWrapper(Runnable command) { + this.command = command; + } + + @Override + public void run() { + try { + command.run(); + } + finally { + synchronized (lock) { + if (waiting != null) { + executing = waiting; + waiting = null; + executeImpl(); + } + else + executing = null; + } + } + } + } + // + // public static void main(String[] args) throws Exception { + // ExecutorService executorService = Executors.newCachedThreadPool(); + // RealTimeTimer timer = new RealTimeTimer(); + // timer.init(executorService); + // + // SingleExecutorSingleWaiter e = new SingleExecutorSingleWaiter(timer); + // TestRunnable r1 = new TestRunnable(1); + // TestRunnable r2 = new TestRunnable(2); + // TestRunnable r3 = new TestRunnable(3); + // TestRunnable r4 = new TestRunnable(4); + // TestRunnable r5 = new TestRunnable(5); + // TestRunnable r6 = new TestRunnable(6); + // TestRunnable r7 = new TestRunnable(7); + // TestRunnable r8 = new TestRunnable(8); + // TestRunnable r9 = new TestRunnable(9); + // + // e.execute(r1); // 1 + // e.execute(r2); // 2 + // e.execute(r3); // 3 + // e.execute(r4); // 3 + // + // Thread.sleep(1000); + // e.execute(r5); // 3 + // + // Thread.sleep(1500); + // e.execute(r6); // 2 + // + // Thread.sleep(4000); + // e.execute(r7); // 1 + // e.execute(r8); // 2 + // e.execute(r9); // 3 + // + // // Running 1 + // // Finished 1 + // // Running 5 + // // Finished 5 + // // Running 6 + // // Finished 6 + // // Running 7 + // // Finished 7 + // // Running 9 + // // Finished 9 + // + // Thread.sleep(4000); + // timer.cancel(); + // executorService.shutdown(); + // } + // + // static class TestRunnable implements Runnable { + // int id; + // + // public TestRunnable(int id) { + // this.id = id; + // } + // + // @Override + // public void run() { + // System.out.println("Running " + id); + // try { + // Thread.sleep(2000); + // } + // catch (InterruptedException e) { + // } + // System.out.println("Finished " + id); + // } + // } +} diff --git a/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/sync/Synchronizer.java b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/sync/Synchronizer.java new file mode 100644 index 0000000..5a9e7c4 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/java/com/serotonin/timer/sync/Synchronizer.java @@ -0,0 +1,235 @@ +package com.serotonin.timer.sync; + +import com.serotonin.timer.AbstractTimer; +import com.serotonin.timer.RealTimeTimer; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * This class acts as a synchronizing monitor where an arbitrary number of tasks can be added with string identifiers. + * Then, the tasks can be executed using the provided executor and the calling thread will be blocked until they are all + * completed, after which then tasks can be asked for by name to get their results. + * + * This allows parts of processing that would benefit from being asynchronous - such as database concurrency - to easily + * be managed by a controlling thread. + * + * @author Matthew Lohbihler + * + * @param + * a task object that implements Runnable + */ +public class Synchronizer { + private final List tasks = new ArrayList(); + + // A lock that is used when there are more tasks to run than allowed concurrently. This monitor notifies the + // completion of any task. + final Object multiLock = new Object(); + + private final int maxConcurrent; + + public Synchronizer() { + this(0); + } + + public Synchronizer(int maxConcurrent) { + this.maxConcurrent = maxConcurrent; + } + + public void addTask(T task) { + addTask(null, task); + } + + public void addTask(String name, T task) { + tasks.add(new TaskWrapper(name, task)); + } + + public void executeAndWait(AbstractTimer timer) { + List running; + + if (maxConcurrent <= 0 || tasks.size() <= maxConcurrent) { + // Start all of the tasks. + for (TaskWrapper tw : tasks) + timer.execute(tw); + running = tasks; + } + else { + List remaining = new ArrayList(tasks); + running = new ArrayList(); + + // We have more tasks to run than we can run concurrently. We need a polling method to monitor the tasks. + while (!remaining.isEmpty()) { + // Start tasks + while (running.size() < maxConcurrent && !remaining.isEmpty()) { + TaskWrapper tw = remaining.remove(remaining.size() - 1); + timer.execute(tw); + running.add(tw); + } + + // Wait for completed tasks. + while (true) { + synchronized (multiLock) { + boolean completions = false; + for (int i = running.size() - 1; i >= 0; i--) { + TaskWrapper tw = running.get(i); + synchronized (tw) { + if (tw.isComplete()) { + running.remove(i); + completions = true; + } + } + } + + if (completions) + // Completed task(s) were found, so break. + break; + + // No tasks completed, so wait for a notification. + try { + multiLock.wait(); + } + catch (InterruptedException e) { + // no op + } + } + } + } + } + + // Wait for the tasks to complete. + for (TaskWrapper tw : running) { + synchronized (tw) { + try { + if (!tw.isComplete()) { + // System.out.println("Waiting on " + tw.task); + tw.wait(); + } + } + catch (InterruptedException e) { + // no op + } + } + } + } + + public T getTask(String name) { + for (TaskWrapper tw : tasks) { + if (StringUtils.equals(name, tw.name)) + return tw.task; + } + return null; + } + + public int getSize() { + return tasks.size(); + } + + public List getTasks() { + List result = new ArrayList(); + for (TaskWrapper tw : tasks) + result.add(tw.task); + return result; + } + + class TaskWrapper implements Runnable { + final String name; + final T task; + private volatile boolean complete; + + public TaskWrapper(String name, T task) { + this.name = name; + this.task = task; + } + + public String getName() { + return name; + } + + @Override + public void run() { + try { + task.run(); + } + finally { + synchronized (this) { + complete = true; + notify(); + } + synchronized (multiLock) { + multiLock.notify(); + } + } + } + + public boolean isComplete() { + return complete; + } + } + + public static void main(String[] args) { + Synchronizer synchronizer = new Synchronizer(20); + synchronizer.addTask("A", new TestTask("a", 4000)); + synchronizer.addTask("B", new TestTask("b", 5000)); + synchronizer.addTask("C", new TestTask("c", 10000)); + synchronizer.addTask("D", new TestTask("d", 3000)); + synchronizer.addTask("E", new TestTask("e", 1000)); + synchronizer.addTask("F", new TestTask("f", 70)); + synchronizer.addTask("G", new TestTask("g", 1)); + synchronizer.addTask("H", new TestTask("h", 10)); + synchronizer.addTask("I", new TestTask("i", 100)); + synchronizer.addTask("J", new TestTask("j", 800)); + synchronizer.addTask("K", new TestTask("k", 40)); + synchronizer.addTask("L", new TestTask("l", 250)); + synchronizer.addTask("M", new TestTask("m", 300)); + synchronizer.addTask("N", new TestTask("n", 700)); + synchronizer.addTask("O", new TestTask("o", 700)); + synchronizer.addTask("P", new TestTask("p", 700)); + synchronizer.addTask("Q", new TestTask("q", 4)); + synchronizer.addTask("R", new TestTask("r", 5)); + synchronizer.addTask("S", new TestTask("s", 6)); + synchronizer.addTask("T", new TestTask("t", 7)); + + RealTimeTimer timer = new RealTimeTimer(); + ExecutorService executor = Executors.newCachedThreadPool(); + timer.init(executor); + + System.out.println("*** Begin"); + synchronizer.executeAndWait(timer); + System.out.println("*** Done"); + + timer.cancel(); + executor.shutdown(); + } + + static long start = System.currentTimeMillis(); + + static class TestTask implements Runnable { + private final String id; + private final long sleepTime; + + public TestTask(String id, long sleepTime) { + this.id = id; + this.sleepTime = sleepTime; + } + + @Override + public void run() { + System.out.println((System.currentTimeMillis() - start) + ": started " + id); + try { + Thread.sleep(sleepTime); + } + catch (InterruptedException e) { + // no op + } + System.out.println((System.currentTimeMillis() - start) + ": ended " + id); + } + + @Override + public String toString() { + return id; + } + } +} diff --git a/smtweb-framework/sw-framework-core/src/main/resources/META-INF/spring.factories b/smtweb-framework/sw-framework-core/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..341ca1d --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cc.smtweb.framework.core.CoreAutoConfiguration diff --git a/smtweb-framework/sw-framework-core/src/main/resources/config/app-config.yaml b/smtweb-framework/sw-framework-core/src/main/resources/config/app-config.yaml new file mode 100644 index 0000000..f2bbe16 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/resources/config/app-config.yaml @@ -0,0 +1,60 @@ +smtweb: + machine-id: 1 + file: + local-path: /data/files/smart/ + host: http://127.0.0.1 + url: ${smtweb.file.host}:${server.port}${server.servlet.context-path}/${smtweb.file.local-path} + db: + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + # 设置服务名 + application: + name: smtweb_core + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + password: + database: 0 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/sw_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true&useSSL=false + username: root + password: 1681860 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + profiles: + include: role + cache: + type: caffeine + cache-names: + - core + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h +park: + secret: + key: null +# key: cmVmb3JtZXJyZWZvcm1lcg== +swagger: + name: smtweb-core + version: 2.0 + enabled: true + + + diff --git a/smtweb-framework/sw-framework-core/src/main/resources/config/application-dev.yaml b/smtweb-framework/sw-framework-core/src/main/resources/config/application-dev.yaml new file mode 100644 index 0000000..bf8c0cf --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/resources/config/application-dev.yaml @@ -0,0 +1,61 @@ +smtweb: + machine-id: 1 + file: + local-path: /data/files/smart/ + host: http://127.0.0.1 + url: ${smtweb.file.host}:${server.port}${server.servlet.context-path}/${smtweb.file.local-path} + db: + type: mysql + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + # 设置服务名 + application: + name: smtweb_core + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + name: ${smtweb.machine-id} + host: 127.0.0.1 + port: 6379 + password: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_asp?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false + username: root + password: root + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 +# profiles: +# include: role + cache: + type: caffeine + cache-names: + - core + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h +park: + secret: + key: null +# key: cmVmb3JtZXJyZWZvcm1lcg== +swagger: + name: smtweb-core + version: 2.0 + enabled: true + + + diff --git a/smtweb-framework/sw-framework-core/src/main/resources/config/application-prod.yaml b/smtweb-framework/sw-framework-core/src/main/resources/config/application-prod.yaml new file mode 100644 index 0000000..eaed90b --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/resources/config/application-prod.yaml @@ -0,0 +1,52 @@ +sme: + machine-id: 1 + file-local-path: /data/files/smart/ + file-host: http://member.sumi168.cn + file-url: ${sme.file-host}:${server.port}${server.servlet.context-path}/${sme.file-local-path} + +server: + port: 10001 + servlet: + context-path: /user + +feign: + hystrix: + enabled: false +logging: + level: + smtweb: DEBUG + +spring: + # 设置服务名 + application: + name: smtweb-user + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + datasource: + user: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: smt + password: smt_123456 + + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + +swagger: + name: smart + version: 1.0.1 + enabled: false + +weixin: + url: http://devpk.smart.smefdd.com + appId: wx8d11474c01b92378 + appSecret: e1fa7bcdf27b46e7d69edad68ea8724b + + diff --git a/smtweb-framework/sw-framework-core/src/main/resources/config/application.yaml b/smtweb-framework/sw-framework-core/src/main/resources/config/application.yaml new file mode 100644 index 0000000..caf4dfc --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/resources/config/application.yaml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev \ No newline at end of file diff --git a/smtweb-framework/sw-framework-core/src/main/resources/config/demo.json b/smtweb-framework/sw-framework-core/src/main/resources/config/demo.json new file mode 100644 index 0000000..c74b816 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/resources/config/demo.json @@ -0,0 +1,169 @@ +{ + "fields": [ + { + "name": "table_id", + "fieldType": "ID", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "ID" + }, + { + "name": "prjoect_id", + "fieldType": "", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "ID", + "link": "asp_model_project" + },{ + "name": "catalog_id", + "fieldType": "", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "ID", + "link": "asp_model_catalog" + },{ + "name": "database_id", + "fieldType": "", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "ID", + "link": "asp_model_database" + }, + { + "name": "table_extends", + "fieldType": "", + "dataType": "REMARK", + "null": "0", + "default": "", + "title": "继承关系", + "link": "", + "editor": "" + }, + { + "name": "table_name", + "fieldType": "CODE", + "dataType": "CODE", + "null": "0", + "default": "", + "title": "表名", + "link": "", + "editor": "" + }, + { + "name": "table_title", + "fieldType": "NAME", + "dataType": "NAME", + "null": "0", + "default": "", + "title": "中文名", + "link": "", + "editor": "" + }, + { + "name": "table_abbr", + "fieldType": "", + "dataType": "CODE", + "null": "1", + "default": "", + "title": "缩写,用于字段和索引组成", + "link": "", + "editor": "" + }, + { + "name": "table_type", + "fieldType": "", + "dataType": "SMALLINT", + "null": "1", + "default": "0", + "title": "类别:0-普通表,1 树型表 2 编码表 9-虚拟抽象表 11 视图", + "link": "", + "editor": "" + }, + { + "name": "need_cache", + "fieldType": "", + "dataType": "BOOL", + "null": "1", + "default": "0", + "title": "需要缓存", + "link": "", + "editor": "" + }, + { + "name": "table_content", + "fieldType": "", + "dataType": "TEXT", + "null": "1", + "default": "0", + "title": "表详细信息", + "link": "", + "editor": "" + }, + { + "name": "table_create_uid", + "fieldType": "", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "创建人", + "link": "", + "editor": "" + }, + { + "name": "table_update_uid", + "fieldType": "", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "最后更新人", + "link": "", + "editor": "" + }, + { + "name": "table_create_at", + "fieldType": "", + "dataType": "DATETIME", + "null": "1", + "default": "-1", + "title": "创建时间", + "link": "", + "editor": "" + }, + { + "name": "table_update_at", + "fieldType": "", + "dataType": "DATETIME", + "null": "1", + "default": "-1", + "title": "最后更新时间", + "link": "", + "editor": "" + } + ], + "indexs": [ + { + "name": "pk", + "fields": "table_id", + "type": "P" + } + ], + "caches": [ + { + "name": "n", + "title": "按库名", + "fields": "db_name", + "type": "M" + }, + { + "name": "prj", + "title": "按项目", + "fields": "db_prj_id", + "type": "L" + } + ] +} + diff --git a/smtweb-framework/sw-framework-core/src/main/resources/config/design_db.yaml b/smtweb-framework/sw-framework-core/src/main/resources/config/design_db.yaml new file mode 100644 index 0000000..b010868 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/main/resources/config/design_db.yaml @@ -0,0 +1,14 @@ +design: + data-type: + - {type: "id", name: "ID", sql-type: "bigint", java-type: "long", widget: "fz-field-long", defaultValue: "0"} + - {type: "code", name: "编码", sql-type: "varchar", java-type: "string", data-length: 32, widget: "fz-field-string", defaultValue: ""} + - {type: "name", name: "名字", sql-type: "varchar", java-type: "string", data-length: 100, widget: "fz-field-string", defaultValue: ""} + - {type: "remark", name: "备注", sql-type: "varchar", data-length: 255, java-type: "string", widget: "fz-field-string", defaultValue: ""} + - {type: "text", name: "大文本", sql-type: "text", java-type: "string", widget: "fz-field-string", defaultValue: ""} + - {type: "currency", name: "货币", sql-type: "bigint", java-type: "long", widget: "fz-field-long", defaultValue: "0"} + - {type: "datetime", name: "时间日期", sql-type: "bigint", java-type: "long", widget: "fz-field-datetime", defaultValue: "0"} + - {type: "date", name: "日期", sql-type: "int", java-type: "int", widget: "fz-field-date", defaultValue: "0"} + - {type: "time", name: "时间", sql-type: "int", java-type: "int", widget: "fz-field-time", defaultValue: "0"} + - {type: "int", name: "整型", sql-type: "int", java-type: "int", widget: "fz-field-int", defaultValue: "0"} + - {type: "smallint", name: "短整型", sql-type: "smallint", java-type: "short", widget: "fz-field-int", defaultValue: "0"} + - {type: "bool", name: "布尔型", sql-type: "tinyint", java-type: "boolean", widget: "fz-field-bool", defaultValue: "0"} diff --git a/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/DaoTransTest.java b/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/DaoTransTest.java new file mode 100644 index 0000000..2e17aee --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/DaoTransTest.java @@ -0,0 +1,31 @@ +package cc.smtweb.framework.test; + + +import cc.smtweb.framework.core.SwMap; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("TransSql测试") +public class DaoTransTest { + + @Test + public void testSelect() { + + } + + @Test + public void testMapClass() { + boolean ret = call(SwMap.class); + + System.out.println(ret); + } + + private boolean call(Class clazz) { + if (java.util.Map.class.isAssignableFrom(clazz)) { + return true; + } + + return false; + } +} diff --git a/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/JsonTest.java b/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/JsonTest.java new file mode 100644 index 0000000..b2de949 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/JsonTest.java @@ -0,0 +1,33 @@ +package cc.smtweb.framework.test; + +import cc.smtweb.framework.core.util.JsonUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.omg.PortableInterceptor.SYSTEM_EXCEPTION; + +import java.util.ArrayList; +import java.util.List; + +public class JsonTest { + @Test + public void testList() throws JsonProcessingException { + { + List list = JsonUtil.OBJECT_MAPPER.readValue("[{ \"value\": 1 }, { \"value\": 2 }, { \"value\": 3 }]", ArrayList.class); + + System.out.println(list.get(0).getClass()); + } + + { + List list = JsonUtil.parseList("[{ \"value\": 1 }, { \"value\": 2 }, { \"value\": 3 }]", JsonItem.class); + + System.out.println(list.get(0).getClass()); + } + } +} + +@Data +class JsonItem { + private Integer value; +} diff --git a/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/SqlBuilderTest.java b/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/SqlBuilderTest.java new file mode 100644 index 0000000..8ad25ee --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/SqlBuilderTest.java @@ -0,0 +1,69 @@ +package cc.smtweb.framework.test; + + +import cc.smtweb.framework.core.db.sqlbuilder.SelectSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("SqlBuilder测试") +public class SqlBuilderTest { + + @Test + public void select() { + SelectSqlBuilder sb = SqlBuilder.createSelect("sw_im_log.im_user_msg_log"); + sb.add("uml_Id,uml_content"); + sb.addWhereOrBegin(); + sb.addWhere("uml_small_uid", 1); + sb.addWhere("uml_big_uid", 2); + sb.addWhereOrEnd(); + + sb.exec((sql, params) -> { + Assertions.assertEquals("select uml_Id,uml_content from sw_im_log.im_user_msg_log where (uml_small_uid=? or uml_big_uid=?)", sql); + return true; + }); + + sb = SqlBuilder.createSelect("sw_im_log.im_user_msg_log"); + sb.add("uml_Id,uml_content"); + sb.addWhere("uml_time", 1); + sb.addWhereOrBegin(); + sb.addWhere("uml_small_uid", 2); + sb.addWhere("uml_big_uid", 3); + sb.addWhereOrEnd(); + sb.addWhere("uml_id", 4); + + sb.exec((sql, params) -> { + Assertions.assertEquals("select uml_Id,uml_content from sw_im_log.im_user_msg_log where uml_time=? and (uml_small_uid=? or uml_big_uid=?) and uml_id=?", sql); + return true; + }); + + sb = SqlBuilder.createSelect("sw_im_log.im_user_msg_log"); + sb.add("uml_Id,uml_content"); + sb.addWhere("uml_time", 1); + sb.addWhereOrBegin(); + sb.addWhere("uml_small_uid", 2); + sb.addWhere("uml_big_uid", 3); + sb.addWhereOrEnd(); + + sb.exec((sql, params) -> { + Assertions.assertEquals("select uml_Id,uml_content from sw_im_log.im_user_msg_log where uml_time=? and (uml_small_uid=? or uml_big_uid=?)", sql); + return true; + }); + + sb = SqlBuilder.createSelect("sw_im_log.im_user_msg_log"); + sb.add("uml_Id,uml_content"); + sb.addWhereOrBegin(); + sb.addWhere("uml_small_uid", 2); + sb.addWhere("uml_big_uid", 3); + sb.addWhereOrEnd(); + sb.addWhere("uml_id", 4); + + sb.exec((sql, params) -> { + Assertions.assertEquals("select uml_Id,uml_content from sw_im_log.im_user_msg_log where (uml_small_uid=? or uml_big_uid=?) and uml_id=?", sql); + return true; + }); + + } + +} diff --git a/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/TestMain.java b/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/TestMain.java new file mode 100644 index 0000000..359ec52 --- /dev/null +++ b/smtweb-framework/sw-framework-core/src/test/java/cc/smtweb/framework/test/TestMain.java @@ -0,0 +1,21 @@ +package cc.smtweb.framework.test; + +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.framework.core.util.DateUtil; +import com.github.benmanes.caffeine.cache.*; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.concurrent.TimeUnit; + +/** + * Created by Akmm at 2021/12/25 22:21 + */ +public class TestMain { + + public static void main(String[] args) { + ModelTable table = new ModelTable(); + table.setId(1); + table.setTableContent("{\"fields\":[{\"name\":\"db_id\",\"fieldType\":\"ID\",\"dataType\":\"ID\",\"null\":\"1\",\"default\":\"-1\",\"title\":\"ID\"},{\"name\":\"db_prj_id\",\"fieldType\":\"\",\"dataType\":\"ID\",\"null\":\"1\",\"default\":\"-1\",\"title\":\"ID\",\"link\":\"asp_model_project\"},{\"name\":\"db_name\",\"fieldType\":\"CODE\",\"dataType\":\"CODE\",\"null\":\"0\",\"default\":\"\",\"title\":\"库名\",\"link\":\"\",\"editor\":\"\"},{\"name\":\"db_title\",\"fieldType\":\"NAME\",\"dataType\":\"NAME\",\"null\":\"0\",\"default\":\"\",\"title\":\"中文名\",\"link\":\"\",\"editor\":\"\"},{\"name\":\"db_status\",\"fieldType\":\"\",\"dataType\":\"BOOL\",\"null\":\"1\",\"default\":\"0\",\"title\":\"启用状态\",\"link\":\"\",\"editor\":\"\"},{\"name\":\"db_version\",\"fieldType\":\"\",\"dataType\":\"INT-4\",\"null\":\"1\",\"default\":\"0\",\"title\":\"版本\",\"link\":\"\",\"editor\":\"\"},{\"name\":\"db_create_uid\",\"fieldType\":\"\",\"dataType\":\"ID\",\"null\":\"1\",\"default\":\"-1\",\"title\":\"创建人\",\"link\":\"\",\"editor\":\"\"},{\"name\":\"db_update_uid\",\"fieldType\":\"\",\"dataType\":\"ID\",\"null\":\"1\",\"default\":\"-1\",\"title\":\"最后更新人\",\"link\":\"\",\"editor\":\"\"},{\"name\":\"db_create_at\",\"fieldType\":\"\",\"dataType\":\"DATETIME\",\"null\":\"1\",\"default\":\"-1\",\"title\":\"创建时间\",\"link\":\"\",\"editor\":\"\"},{\"name\":\"db_update_at\",\"fieldType\":\"\",\"dataType\":\"DATETIME\",\"null\":\"1\",\"default\":\"-1\",\"title\":\"最后更新时间\",\"link\":\"\",\"editor\":\"\"}],\"indexes\":[{\"name\":\"pk\",\"fields\":\"db_id\",\"type\":\"P\"}],\"caches\":[{\"name\":\"n\",\"title\":\"按库名\",\"fields\":\"db_name\",\"type\":\"M\"},{\"name\":\"prj\",\"title\":\"按项目\",\"fields\":\"db_prj_id\",\"type\":\"L\"}]}"); + } +} diff --git a/smtweb-framework/sw-framework-core/target/classes/META-INF/spring.factories b/smtweb-framework/sw-framework-core/target/classes/META-INF/spring.factories new file mode 100644 index 0000000..341ca1d --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/classes/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cc.smtweb.framework.core.CoreAutoConfiguration diff --git a/smtweb-framework/sw-framework-core/target/classes/config/app-config.yaml b/smtweb-framework/sw-framework-core/target/classes/config/app-config.yaml new file mode 100644 index 0000000..f2bbe16 --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/classes/config/app-config.yaml @@ -0,0 +1,60 @@ +smtweb: + machine-id: 1 + file: + local-path: /data/files/smart/ + host: http://127.0.0.1 + url: ${smtweb.file.host}:${server.port}${server.servlet.context-path}/${smtweb.file.local-path} + db: + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + # 设置服务名 + application: + name: smtweb_core + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + password: + database: 0 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/sw_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true&useSSL=false + username: root + password: 1681860 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + profiles: + include: role + cache: + type: caffeine + cache-names: + - core + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h +park: + secret: + key: null +# key: cmVmb3JtZXJyZWZvcm1lcg== +swagger: + name: smtweb-core + version: 2.0 + enabled: true + + + diff --git a/smtweb-framework/sw-framework-core/target/classes/config/application-dev.yaml b/smtweb-framework/sw-framework-core/target/classes/config/application-dev.yaml new file mode 100644 index 0000000..bf8c0cf --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/classes/config/application-dev.yaml @@ -0,0 +1,61 @@ +smtweb: + machine-id: 1 + file: + local-path: /data/files/smart/ + host: http://127.0.0.1 + url: ${smtweb.file.host}:${server.port}${server.servlet.context-path}/${smtweb.file.local-path} + db: + type: mysql + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + # 设置服务名 + application: + name: smtweb_core + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + name: ${smtweb.machine-id} + host: 127.0.0.1 + port: 6379 + password: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_asp?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false + username: root + password: root + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 +# profiles: +# include: role + cache: + type: caffeine + cache-names: + - core + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h +park: + secret: + key: null +# key: cmVmb3JtZXJyZWZvcm1lcg== +swagger: + name: smtweb-core + version: 2.0 + enabled: true + + + diff --git a/smtweb-framework/sw-framework-core/target/classes/config/application-prod.yaml b/smtweb-framework/sw-framework-core/target/classes/config/application-prod.yaml new file mode 100644 index 0000000..eaed90b --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/classes/config/application-prod.yaml @@ -0,0 +1,52 @@ +sme: + machine-id: 1 + file-local-path: /data/files/smart/ + file-host: http://member.sumi168.cn + file-url: ${sme.file-host}:${server.port}${server.servlet.context-path}/${sme.file-local-path} + +server: + port: 10001 + servlet: + context-path: /user + +feign: + hystrix: + enabled: false +logging: + level: + smtweb: DEBUG + +spring: + # 设置服务名 + application: + name: smtweb-user + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + datasource: + user: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: smt + password: smt_123456 + + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + +swagger: + name: smart + version: 1.0.1 + enabled: false + +weixin: + url: http://devpk.smart.smefdd.com + appId: wx8d11474c01b92378 + appSecret: e1fa7bcdf27b46e7d69edad68ea8724b + + diff --git a/smtweb-framework/sw-framework-core/target/classes/config/application.yaml b/smtweb-framework/sw-framework-core/target/classes/config/application.yaml new file mode 100644 index 0000000..caf4dfc --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/classes/config/application.yaml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev \ No newline at end of file diff --git a/smtweb-framework/sw-framework-core/target/classes/config/demo.json b/smtweb-framework/sw-framework-core/target/classes/config/demo.json new file mode 100644 index 0000000..c74b816 --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/classes/config/demo.json @@ -0,0 +1,169 @@ +{ + "fields": [ + { + "name": "table_id", + "fieldType": "ID", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "ID" + }, + { + "name": "prjoect_id", + "fieldType": "", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "ID", + "link": "asp_model_project" + },{ + "name": "catalog_id", + "fieldType": "", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "ID", + "link": "asp_model_catalog" + },{ + "name": "database_id", + "fieldType": "", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "ID", + "link": "asp_model_database" + }, + { + "name": "table_extends", + "fieldType": "", + "dataType": "REMARK", + "null": "0", + "default": "", + "title": "继承关系", + "link": "", + "editor": "" + }, + { + "name": "table_name", + "fieldType": "CODE", + "dataType": "CODE", + "null": "0", + "default": "", + "title": "表名", + "link": "", + "editor": "" + }, + { + "name": "table_title", + "fieldType": "NAME", + "dataType": "NAME", + "null": "0", + "default": "", + "title": "中文名", + "link": "", + "editor": "" + }, + { + "name": "table_abbr", + "fieldType": "", + "dataType": "CODE", + "null": "1", + "default": "", + "title": "缩写,用于字段和索引组成", + "link": "", + "editor": "" + }, + { + "name": "table_type", + "fieldType": "", + "dataType": "SMALLINT", + "null": "1", + "default": "0", + "title": "类别:0-普通表,1 树型表 2 编码表 9-虚拟抽象表 11 视图", + "link": "", + "editor": "" + }, + { + "name": "need_cache", + "fieldType": "", + "dataType": "BOOL", + "null": "1", + "default": "0", + "title": "需要缓存", + "link": "", + "editor": "" + }, + { + "name": "table_content", + "fieldType": "", + "dataType": "TEXT", + "null": "1", + "default": "0", + "title": "表详细信息", + "link": "", + "editor": "" + }, + { + "name": "table_create_uid", + "fieldType": "", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "创建人", + "link": "", + "editor": "" + }, + { + "name": "table_update_uid", + "fieldType": "", + "dataType": "ID", + "null": "1", + "default": "-1", + "title": "最后更新人", + "link": "", + "editor": "" + }, + { + "name": "table_create_at", + "fieldType": "", + "dataType": "DATETIME", + "null": "1", + "default": "-1", + "title": "创建时间", + "link": "", + "editor": "" + }, + { + "name": "table_update_at", + "fieldType": "", + "dataType": "DATETIME", + "null": "1", + "default": "-1", + "title": "最后更新时间", + "link": "", + "editor": "" + } + ], + "indexs": [ + { + "name": "pk", + "fields": "table_id", + "type": "P" + } + ], + "caches": [ + { + "name": "n", + "title": "按库名", + "fields": "db_name", + "type": "M" + }, + { + "name": "prj", + "title": "按项目", + "fields": "db_prj_id", + "type": "L" + } + ] +} + diff --git a/smtweb-framework/sw-framework-core/target/classes/config/design_db.yaml b/smtweb-framework/sw-framework-core/target/classes/config/design_db.yaml new file mode 100644 index 0000000..b010868 --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/classes/config/design_db.yaml @@ -0,0 +1,14 @@ +design: + data-type: + - {type: "id", name: "ID", sql-type: "bigint", java-type: "long", widget: "fz-field-long", defaultValue: "0"} + - {type: "code", name: "编码", sql-type: "varchar", java-type: "string", data-length: 32, widget: "fz-field-string", defaultValue: ""} + - {type: "name", name: "名字", sql-type: "varchar", java-type: "string", data-length: 100, widget: "fz-field-string", defaultValue: ""} + - {type: "remark", name: "备注", sql-type: "varchar", data-length: 255, java-type: "string", widget: "fz-field-string", defaultValue: ""} + - {type: "text", name: "大文本", sql-type: "text", java-type: "string", widget: "fz-field-string", defaultValue: ""} + - {type: "currency", name: "货币", sql-type: "bigint", java-type: "long", widget: "fz-field-long", defaultValue: "0"} + - {type: "datetime", name: "时间日期", sql-type: "bigint", java-type: "long", widget: "fz-field-datetime", defaultValue: "0"} + - {type: "date", name: "日期", sql-type: "int", java-type: "int", widget: "fz-field-date", defaultValue: "0"} + - {type: "time", name: "时间", sql-type: "int", java-type: "int", widget: "fz-field-time", defaultValue: "0"} + - {type: "int", name: "整型", sql-type: "int", java-type: "int", widget: "fz-field-int", defaultValue: "0"} + - {type: "smallint", name: "短整型", sql-type: "smallint", java-type: "short", widget: "fz-field-int", defaultValue: "0"} + - {type: "bool", name: "布尔型", sql-type: "tinyint", java-type: "boolean", widget: "fz-field-bool", defaultValue: "0"} diff --git a/smtweb-framework/sw-framework-core/target/maven-archiver/pom.properties b/smtweb-framework/sw-framework-core/target/maven-archiver/pom.properties new file mode 100644 index 0000000..769fb71 --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Apache Maven +#Tue Nov 02 19:04:55 CST 2021 +version=2.1.0-SNAPSHOT +groupId=cc.smtweb +artifactId=sw-framework-core diff --git a/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e153979 --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1 @@ +cc\smtweb\framework\core\db\cache\DbCacheList$IdName.class diff --git a/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..93b5610 --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,208 @@ +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\dao\EntityColumn.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\cache\EntityCache.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\exception\UnknownAccountException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\session\SessionUtil.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwColumn.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\cache\AbstractTwoKeyCache.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\sync\SingleExecutorSingleWaiter.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\jdbc\JdbcEngine.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\IEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\CoreApplication.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\UpdateSqlBuilder.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\config\WebMvcConfig.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\TimerTrigger.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\NonConcurrentTask.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\cache\CachedListPart.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\dao\EntityColumnForeign.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\SqlBuilder.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\access\ControllerAccess.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\body\StringBodyEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwPerm.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\job\SimpleJob.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\FixedRateTrigger.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\DateEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\scan\ClassParser.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\session\SessionManager.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\OneTimeTrigger.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\ISwService.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\IRealmLoader.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\MapUtil.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\jdbc\JdbcTrans.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\exception\BizException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\IBeanContext.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\job\LocalJobExecutor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\systask\SysTaskManager.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\exception\BindParamException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwBean.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\ShortEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\exception\AuthenticationException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\redis\config\RedisConfig.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\FixedDelayTrigger.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\CacheEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\bean\HttpServletResponseEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\jackson\NullSerializer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\cache\CacheItem.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\SelectSqlBuilder.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\interceptor\AbstractPermInterceptor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\CoreAutoConfiguration.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\SwMap.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\SqlJoinTable.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\bean\UserSessionEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\ISchedulerWakeup.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\DeleteSqlBuilder.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\TimerThread.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\path\PathParamEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\EntityDao.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\redis\kryo\KryoTool.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\SchedulerManager.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\ModelTimeoutTask.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\CronTimerTask.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\session\UserSessionArgumentResolver.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwService.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwConstruct.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\VariableUtil.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\jdbc\TransSql.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\IntegerEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwHeaderParam.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\access\MethodAccess.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\redis\RedisManager.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\access\FieldAccess.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\cache\EntityTree.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\access\MethodParamAccess.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\exception\BindBeanException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\SchedulerTaskManager.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\jackson\TimestampDeserializer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\exception\CacheException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\job\RedisJobExecutor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\session\UserSession.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\DefaultPageController.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\NamedRunnable.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\cache\AbstractLongKeyCache.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\AbstractRequestEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\cache\CacheEngine.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\AbstractSelectSqlBuilder.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\RealTimeTimer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\cache\CacheSysTask.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\SwException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\job\SimpleJobQueue.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\DbEngine.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\systask\TaskStartEvent.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\exception\ExceptionMessage.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\scan\BeanManager.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\bean\NullEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\DbCache.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\access\SchedulerMethodAccess.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\TimeEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\cache\AbstractCache.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\IpAddrUtil.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\CommonUtil.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\AbstractTimer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\sync\Synchronizer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\cache\DbCacheMap.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\job\GroupJobQueue.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\LongEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\DateUtil.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\config\SettingsEnvironmentPostProcessor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\SqlFieldValue.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\BooleanEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\redis\RedisConnection.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\systask\WebStartedEvent.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\ByteEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\sync\AsyncJobSink.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\JsonUtil.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\service\PermChecker.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\redis\RedisSysTask.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\scan\MethodParser.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\FixedTimerTask.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwColumnForeign.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\WebDataBinder.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\AbstractUpdateSqlBuilder.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\SimulationTimer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\CronTimerTrigger.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\SchedulerPoint.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\ModelTimeoutClient.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\NumberTimeUtil.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\cache\CacheManager.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\MethodAccessManager.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwDestroy.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwPathParam.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\jdbc\IdGenerator.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\cache\config\CacheEntity.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\cache\EntityTreeUtil.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\AbstractJobQueue.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwScheduling.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\interceptor\PermInterceptor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\systask\ISysTask.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\ScheduledNamedRunnable.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\jackson\TimeSerializer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\bean\SwIpAddrEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\cache\AbstractStringKeyCache.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\jackson\DateSerializer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\body\AbstractBodyEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\body\BeanBodyEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\cache\config\CacheProperties.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\jdbc\MapPropertyRowMapper.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\jackson\LongSerializer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\scan\PackageScanner.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\config\DbEngineConfiguration.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\InsertSqlBuilder.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\AbstractParameterEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\jackson\DateDeserializer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\access\SingletonFieldAccess.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\exception\ForbiddenException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\TimestampEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\SqlWhereValue.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\cache\ISwCache.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\exception\AuthorizationException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\TaskQueue.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwParam.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\ApiController.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\NumberUtil.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\redis\RedisBroadcastEvent.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\BeanTypeEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\bean\HttpServletRequestEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\CharEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\access\IBeanAccess.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\SystemTimeSource.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\ScheduledRunnable.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\interceptor\AuthorizationInterceptor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\cache\CachedMap.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\bean\AbstractContextEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\util\jackson\LongDeserializer.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\TimeoutTask.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\cache\ColumnField.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\BeanContext.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\redis\RedisPooledObjectFactory.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\service\PermCheckItem.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\AbstractJobExecutor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\DoubleEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwTable.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\exception\JsonParseException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\cache\DbCacheList.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\CoreApplicationStartedListener.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\StringEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\DirectSelectSqlBuilder.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\ParamEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\AbstractNumberEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\job\GroupJob.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\config\GlobalExceptionHandler.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\exception\DbException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\TimeSource.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\sqlbuilder\SqlJoinField.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\scheduler\AbstractJob.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\SimulationTimeSource.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\R.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\bean\BeanEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\config\ControllerConfig.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\scan\ApplicationScanner.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\AbstractTimerTrigger.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\TimerTask.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\config\RuleProperties.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\CronExpression.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\db\dao\AbstractEntityDao.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\realm\exception\UnauthenticatedException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\access\BindFieldAccess.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\com\serotonin\timer\ExecutionRejectedException.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\mvc\controller\binder\param\FloatEditor.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\SwIpAddr.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\main\java\cc\smtweb\framework\core\annotation\SwBody.java diff --git a/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..5dacf1a --- /dev/null +++ b/smtweb-framework/sw-framework-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1,3 @@ +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\test\java\cc\smtweb\framework\test\JsonTest.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\test\java\cc\smtweb\framework\test\SqlBuilderTest.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-core\src\test\java\cc\smtweb\framework\test\DaoTransTest.java diff --git a/smtweb-framework/sw-framework-file/pom.xml b/smtweb-framework/sw-framework-file/pom.xml new file mode 100644 index 0000000..63e45f7 --- /dev/null +++ b/smtweb-framework/sw-framework-file/pom.xml @@ -0,0 +1,94 @@ + + + cc.smtweb + sw-framework-file + 2.2.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.5.6 + + + + 4.0.0 + + + + org.springframework.boot + spring-boot-starter-web + + + cc.smtweb + sw-framework-core + 2.2.0-SNAPSHOT + + + net.coobird + thumbnailator + [0.4, 0.5) + + + org.jclarion + image4j + 0.7 + + + + commons-codec + commons-codec + 1.15 + + + commons-io + commons-io + 2.11.0 + + + + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + config/*.yaml + + + + + + + + + nexus-releases + Nexus Release Repository + http://47.92.149.153:7000/repository/maven-releases/ + + + nexus-snapshots + Nexus Snapshot Repository + http://47.92.149.153:7000/repository/maven-snapshots/ + + + + diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/FileHelper.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/FileHelper.java new file mode 100644 index 0000000..fb71ce3 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/FileHelper.java @@ -0,0 +1,19 @@ +package cc.smtweb.framework.file; + +import cc.smtweb.framework.core.db.jdbc.JdbcEngine; + +/** + * 文件服务帮助类 + * @author admin + */ +public class FileHelper { + private FileHelper() { + } + /** + * 业务已经采用文件,文件服务需要删除文件记录,避免定时任务清除文件 + * @param fileId 文件服务上传时提供的文件ID + */ + public static void commit(JdbcEngine dbEngine, long fileId) { + + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/FilePathGenerator.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/FilePathGenerator.java new file mode 100644 index 0000000..2f3b9d4 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/FilePathGenerator.java @@ -0,0 +1,134 @@ +package cc.smtweb.framework.file; + +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.Map; + +import cc.smtweb.framework.file.util.FileDynPath; +import cc.smtweb.framework.file.util.FileFixPath; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.tika.mime.MimeType; +import org.apache.tika.mime.MimeTypeException; +import org.apache.tika.mime.MimeTypes; +import org.springframework.web.multipart.MultipartFile; +import cc.smtweb.framework.core.db.jdbc.IdGenerator; + +/** + * 文件名生成规则 subDir/[yyyymm]/[d]/[hex(fileid)]_[hex(rand)].[fileExt] 如果文件是图片格式,会生成缩略图,文件名会直接添加.thumb.jpg后缀 规则参数 yyyymm: + * 时间的年月,固定6位字符。如200505 d: 时间的日期,值范围1~31。如5 fileid: 上传文件的ID,hex(int64) rand: 防盗链随机数,hex(int32)。 fileExt: 文件扩展名。 + */ +@Slf4j +public class FilePathGenerator { + + public static final String THUMB_FILE_EXT = ".thumb.jpg"; + // 文件时间是否作为PK + private SimpleDateFormat sdf; + @Getter + private String rootPath; + private Map fileFxPathMap = new HashMap<>(); + private Map fileDynPathMap = new HashMap<>(); + private String fileUrl; + private IdGenerator idGenerator; + + public FilePathGenerator(String rootPath, String fileUrl, IdGenerator idGenerator) { + this.fileUrl = fixEnd(fileUrl); + this.idGenerator = idGenerator; + this.rootPath = fixEnd(rootPath); + + sdf = new SimpleDateFormat("yyyyMM/dd/"); + } + + private static String fixEnd(String path) { + if (path.endsWith("/") || path.endsWith("\\")) { + return path; + } else { + return path + "/"; + } + } + + /** + * 生成文件路径,根据日期分目录存储 + * + * @param subPath 子目录,区分不同应用的文件 + * @param originalFileName 原始的文件名,用于提取扩展名用 + * @return 文件路径信息类 + */ + public FilePathInfo make(String subPath, String originalFileName) { + return make(subPath, originalFileName, null, false); + } + + public FilePathInfo make(String subPath, String originalFileName, boolean keepName) { + return make(subPath, originalFileName, null, keepName); + } + /** + * 生成文件路径,根据日期分目录存储 + * + * @param subPath 子目录,区分不同应用的文件 + * @param multipartFile 上传文件流,用于提取扩展名用 + * @return 文件路径信息类 + */ + public FilePathInfo make(String subPath, MultipartFile multipartFile) { + return make(subPath, multipartFile.getOriginalFilename(), multipartFile.getContentType(), false); + } + + private synchronized FilePathInfo make(String subPath, String originFileName, String contentType, boolean keepName) { + if (keepName) { + FileFixPath filePathSub = fileFxPathMap.get(subPath); + if (filePathSub == null) { + filePathSub = new FileFixPath(this.rootPath, subPath); + fileFxPathMap.put(subPath, filePathSub); + } + + return filePathSub.makeDatePath(this.idGenerator.nextId(), originFileName); + } else { + FileDynPath filePathSub = fileDynPathMap.get(subPath); + if (filePathSub == null) { + filePathSub = new FileDynPath(this.rootPath, subPath, sdf); + fileDynPathMap.put(subPath, filePathSub); + } + + return filePathSub.makeDatePath(this.idGenerator.nextId(), ext(originFileName, contentType)); + } + } + + private static String ext(String filename, String contentType) { + int index = filename.lastIndexOf("."); + + if (index == -1) { + if (contentType != null) { + MimeTypes allTypes = MimeTypes.getDefaultMimeTypes(); + try { + MimeType jpeg = allTypes.forName(contentType); + return jpeg.getExtension(); + } catch (MimeTypeException e) { + log.error(contentType, e); + } + } + + return ""; + } + + return filename.substring(index); + } + + // 根据数据库存储文件路径获取URL + public String getFileUrl(FilePathInfo filePathInfo) { + return this.fileUrl + filePathInfo.getMysqlFilePath(); + } + + // 根据数据库存储文件路径获取URL + public String getFileUrl(String mysqlFilePath) { + return this.fileUrl + mysqlFilePath; + } + + // 根据数据库存储文件路径获取磁盘存储路径 + public String getFileDiskPath(String mysqlFilePath) { + return this.rootPath + mysqlFilePath; + } + + // 获取下载路径前缀 + public String getDownloadUrl() { + return this.fileUrl; + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/FilePathInfo.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/FilePathInfo.java new file mode 100644 index 0000000..48a6d98 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/FilePathInfo.java @@ -0,0 +1,48 @@ +package cc.smtweb.framework.file; + +import lombok.Getter; + + +/** + * 数据库需要存储 + * fileId, fileTime, subPath + fileName + */ +@Getter +public class FilePathInfo { + // 文件ID + private long fileId; + // 文件创建时间,数据库需要存储 + private long fileTime; + // 文件子路径 + private String subPath; + // 文件名 + private String fileName; + // 本地根路径 + private String rootPath; + + public FilePathInfo(String rootPath, String subPath, long fileTime, String fileName, long fileId) { + this.rootPath = rootPath; + this.subPath = subPath; + this.fileTime = fileTime; + this.fileName = fileName; + this.fileId = fileId; + } + + /** + * 获取本地需要存储的文件全路径 + */ + public String getFullFileName() { + return getDiskFilePath(); + } + + public String getDiskFilePath() { + return this.rootPath + subPath + fileName; + } + + /** + * 获取数据库存储需要的文件全路径 + */ + public String getMysqlFilePath() { + return subPath + fileName; + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/FileApplication.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/FileApplication.java new file mode 100644 index 0000000..6e9a3af --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/FileApplication.java @@ -0,0 +1,15 @@ +package cc.smtweb.framework.file.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author kevin + */ +@SpringBootApplication +public class +FileApplication { + public static void main(String[] args) { + SpringApplication.run(FileApplication.class, args); + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/FileAutoConfiguration.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/FileAutoConfiguration.java new file mode 100644 index 0000000..a42c604 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/FileAutoConfiguration.java @@ -0,0 +1,21 @@ +package cc.smtweb.framework.file.spring; + +import cc.smtweb.framework.core.mvc.config.ControllerConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * @author kevin + */ +@Configuration +@ComponentScan +public class FileAutoConfiguration { + + /** 配置自定义service扫描路径 {module}/{service}/{method} */ + @Bean + public ControllerConfig fileControllerConfig() { + return new ControllerConfig("file", "cc.smtweb.framework.file.web"); + } + +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/config/FileConfig.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/config/FileConfig.java new file mode 100644 index 0000000..2c655af --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/config/FileConfig.java @@ -0,0 +1,26 @@ +package cc.smtweb.framework.file.spring.config; + +import cc.smtweb.framework.file.FilePathGenerator; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import cc.smtweb.framework.core.db.jdbc.IdGenerator; + +/** + * 微服务框架封装自动配置类 + */ +@Configuration +public class FileConfig { + // 文件本地存储配置 + @Value("${smtweb.file.local-path}") + private String fileLocalPath; + + // 文件请求URL路径配置 http://127.0.0.1:${server.port}/${server.servlet.context-path}/files/ + @Value("${smtweb.file.url}") + private String fileUrl; + + @Bean + public FilePathGenerator filePathGenerator(IdGenerator idGenerator) { + return new FilePathGenerator(fileLocalPath, fileUrl, idGenerator); + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/controller/FileDownloadController.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/controller/FileDownloadController.java new file mode 100644 index 0000000..cf07861 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/controller/FileDownloadController.java @@ -0,0 +1,185 @@ +package cc.smtweb.framework.file.spring.controller; + +import cc.smtweb.framework.core.redis.RedisManager; +import cc.smtweb.framework.core.session.SessionUtil; +import cc.smtweb.framework.file.FilePathGenerator; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.InputStreamResource; +import org.springframework.http.*; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +@RestController +public class FileDownloadController { + private static final MediaType APPLICATION_JAVASCRIPT = new MediaType("application", "javascript"); + @Value("${smtweb.static.local-path:}") + private String staticLocalPath; + + @Autowired + private FilePathGenerator filePathGenerator; + + @Autowired + private RedisManager redisManager; + + /** path方式下载文件 */ + @GetMapping("/fs/files/**") + public ResponseEntity files(@RequestParam(value="name", required=false) String name, + @RequestParam(value="noCache", required=false) Boolean noCache, + HttpServletRequest request + ) throws FileNotFoundException { + String filePath = request.getRequestURI().substring(10); + return download(filePath, name, noCache, request); + } + + /** 参数方式下载文件 */ + @GetMapping("/fs/download") + public ResponseEntity download(@RequestParam(value="path") String path, + @RequestParam(value="name", required=false) String name, + @RequestParam(value="noCache", required=false) Boolean noCache, + HttpServletRequest request + ) throws FileNotFoundException { + SessionUtil.checkSession(request, redisManager); + + File file = new File(filePathGenerator.getFileDiskPath(path)); + + if (!file.exists()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + + if (StringUtils.isBlank(name)) { + name = file.getName(); + } + + HttpHeaders headers = new HttpHeaders(); + if (Boolean.TRUE.equals(noCache)) { + headers.setCacheControl("no-cache, no-store, must-revalidate"); + headers.setPragma("no-cache"); + headers.setExpires(0); + } + + headers.setLastModified(file.lastModified()); + headers.add("Content-Disposition", + String.format("attachment; filename=\"%s\"", new String(name.getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1))); + + return ResponseEntity.ok() + .headers(headers) + .contentLength(file.length()) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(new InputStreamResource(new FileInputStream(file))); + } + + /** path方式读取静态目录文件 */ + @GetMapping("/fs/static/**") + public ResponseEntity resource(@RequestParam(value="default", required=false) String defaultPath, + @RequestParam(value="noCache", required=false) Boolean noCache, + @RequestHeader(value="If-Modified-Since", required = false) String ifModifiedSince, + HttpServletRequest request) throws FileNotFoundException { + String filePath = request.getRequestURI().substring(11); + + HttpHeaders headers = new HttpHeaders(); + + if (Boolean.TRUE.equals(noCache)) { + headers.setCacheControl("no-cache, no-store, must-revalidate"); + headers.setPragma("no-cache"); + headers.setExpires(0); + } else { + // 暂时缓存1天 + headers.setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)); + headers.setExpires(Instant.ofEpochMilli(System.currentTimeMillis() + DateUtils.MILLIS_PER_DAY)); + } + + String name = getFileName(filePath); + headers.add("Content-Disposition", + String.format("attachment; filename=\"%s\"", new String(name.getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1))); + + MediaType contentType = getContentType(filePath); + // 先找文件 + if (StringUtils.isNotBlank(staticLocalPath)) { + File file = new File(staticLocalPath + filePath); + + if (file.exists()) { + headers.setLastModified(file.lastModified()); + + return ResponseEntity.ok() + .headers(headers) + .contentLength(file.length()) + .contentType(contentType) + .body(new InputStreamResource(new FileInputStream(file))); + } + } + + // 再找资源目录 + InputStream inputStream = getClass().getResourceAsStream("/static/" + filePath); + if (inputStream != null) { + return buildResource(inputStream, contentType, headers); + } else if (StringUtils.isNotBlank(defaultPath)) { + inputStream = getClass().getResourceAsStream("/static/" + defaultPath); + if (inputStream != null) { + return buildResource(inputStream, contentType, headers); + } + } + + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + + private String getFileName(String filePath) { + int pos = filePath.lastIndexOf("/"); + if (pos >= 0) { + return filePath.substring(pos + 1); + } + + return filePath; + } + + private ResponseEntity buildResource(InputStream inputStream, MediaType contentType, HttpHeaders headers) { + return ResponseEntity.ok() + .headers(headers) +// .contentLength(file.length()) + .contentType(contentType) + .body(new InputStreamResource(inputStream)); + } + + private static MediaType getContentType(String filePath) { + int pos = filePath.lastIndexOf("."); + + if (pos >= 0) { + String fileExt = filePath.substring(pos + 1).toLowerCase(); + switch (fileExt) { + case "htm": + case "html": + case "css": + return MediaType.TEXT_HTML; + case "js": + return APPLICATION_JAVASCRIPT; + case "txt": + return MediaType.TEXT_PLAIN; + case "pdf": + return MediaType.APPLICATION_PDF; + case "xml": + return MediaType.TEXT_XML; + case "gif": + return MediaType.IMAGE_GIF; + case "jpeg": + case "jpg": + return MediaType.IMAGE_JPEG; + case "png": + return MediaType.IMAGE_PNG; + default: + return MediaType.APPLICATION_OCTET_STREAM; + } + } + + return MediaType.APPLICATION_OCTET_STREAM; + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/controller/FileUploadController.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/controller/FileUploadController.java new file mode 100644 index 0000000..5f25aea --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/controller/FileUploadController.java @@ -0,0 +1,157 @@ +package cc.smtweb.framework.file.spring.controller; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.redis.RedisManager; +import cc.smtweb.framework.core.session.SessionUtil; +import cc.smtweb.framework.file.FilePathGenerator; +import cc.smtweb.framework.file.FilePathInfo; +import cc.smtweb.framework.file.spring.dao.ImageAttachDao; +import cc.smtweb.framework.file.spring.entity.FileDataVO; +import cc.smtweb.framework.file.spring.entity.UploadDataVO; +import cc.smtweb.framework.file.util.MemMultipartFile; +import cc.smtweb.framework.file.util.ThumbImage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.*; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; + +@RestController +public class FileUploadController { + @Autowired + private FilePathGenerator filePathGenerator; + + @Autowired + private DbEngine dbEngine; + + @Autowired + private RedisManager redisManager; + + @Autowired + private ImageAttachDao imageAttachDao; + + // TODO: 权限处理,临时文件处理 + @PostMapping("/fs/upload/{path}") + public R upload(@RequestParam("file") MultipartFile file, @PathVariable("path") String path, + @RequestParam(value="thumb", required=false) String thumb, + @RequestParam(value="thumbHeight", required=false) Integer thumbHeight, + @RequestParam(value="commit", required=false) Boolean insert, + @RequestParam(value="keepName", required=false) Boolean keepName, + HttpServletRequest request + ) { + SessionUtil.checkSession(request, redisManager); + return uploadFile(path, file, ThumbImage.type(thumb), thumbHeight, insert, keepName); + } + + @PostMapping("/fs/uploadImage/{path}") + public R upload(@RequestBody FileDataVO data, @PathVariable("path") String path, + @RequestParam(value="thumb", required=false) String thumb, + @RequestParam(value="thumbHeight", required=false) Integer thumbHeight, + @RequestParam(value="commit", required=false) Boolean insert, + HttpServletRequest request) { + SessionUtil.checkSession(request, redisManager); + + MultipartFile file = MemMultipartFile.build(data.getData()); + if (file == null) { + return R.error("数据内容格式有错"); + } + + return uploadFile(path, file, ThumbImage.type(thumb), thumbHeight, insert, false); + } + + @PostMapping("/fs/uploadAvatar/{path}") + public R uploadAvatar(@RequestParam("file") MultipartFile file, @PathVariable("path") String path, + @RequestParam(value="size", required=false) Integer size, + @RequestParam(value="commit", required=false) Boolean insert, + @RequestParam(value="keepName", required=false) Boolean keepName, + HttpServletRequest request) { + SessionUtil.checkSession(request, redisManager); + return uploadFile(path, file, ThumbImage.TYPE_AVATAR, size, insert, keepName); + } + + // 保存文件和插入数据库数据 + @PostMapping("/fs/commit/{path}") + public R commit(@RequestParam("file") MultipartFile file, @PathVariable("path") String path, + @RequestParam(value="thumb", required=false) String thumb, + @RequestParam(value="thumbHeight", required=false) Integer thumbHeight, + @RequestParam(value="keepName", required=false) Boolean keepName, + HttpServletRequest request) { + SessionUtil.checkSession(request, redisManager); + return uploadFile(path, file, ThumbImage.type(thumb), thumbHeight, true, keepName); + } + + private R uploadFile(String path, MultipartFile file, int type, Integer size, Boolean insert, Boolean keepName) { + //获取上传时的文件名 + String fileName = file.getOriginalFilename(); + + //判断文件是否为空 + if(file.isEmpty() && fileName != null){ + return R.error("文件为空"); + } + + // 判断保持文件名不变 + FilePathInfo fileInfo = filePathGenerator.make(path, fileName, Boolean.TRUE.equals(keepName)); + + // 注意是路径+文件名 + File targetFile = new File(fileInfo.getFullFileName()); + + try(InputStream inputStream = file.getInputStream(); OutputStream outputStream = new FileOutputStream(targetFile)) { + // 最后使用资源访问器FileCopyUtils的copy方法拷贝文件 + FileCopyUtils.copy(inputStream, outputStream); + } catch (IOException e) { + //出现异常,则告诉页面失败 + return R.error("上传失败", e); + } + + // 生成缩略图 +// String contentType = file.getContentType(); + UploadDataVO data = new UploadDataVO(); + + data.setPath(fileInfo.getMysqlFilePath()); + data.setName(fileName); + data.setSize(file.getSize()); + data.setContentType(file.getContentType()); + data.setUrl(filePathGenerator.getFileUrl(fileInfo.getMysqlFilePath())); + + if (type == ThumbImage.TYPE_THUMB || type == ThumbImage.TYPE_AVATAR) { + try { + imageAttachDao.makeThumb(data, type == ThumbImage.TYPE_THUMB, targetFile, size); + } catch (IOException e) { + return R.error("生成缩略图失败", e); + } + } + + if (Boolean.TRUE.equals(insert)) { + Long id = dbEngine.nextId(); + Timestamp now = new Timestamp(System.currentTimeMillis()); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + + dbEngine.update("insert into sw_user.sys_attach(attach_id, attach_name, attach_path, attach_content_type, attach_size, attach_create_time) values(?, ?, ?, ?, ?, ?)", + id, data.getName(), data.getPath(), data.getContentType(), data.getSize(), sdf.format(now)); + + data.setId(id); + } + + return R.success(data); + } + + // TODO: 修改为安全的后台删除方式 + @PostMapping("/fs/remove") + public R remove(@RequestParam(value="filePath") String filePath, HttpServletRequest request) { + SessionUtil.checkSession(request, redisManager); + + File file = new File(filePathGenerator.getFileDiskPath(filePath)); + if (file.exists() && file.isFile()) { + if (file.delete()) { + R.success(filePath); + } + } + + return R.success(); + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/dao/ImageAttachDao.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/dao/ImageAttachDao.java new file mode 100644 index 0000000..abacbb4 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/dao/ImageAttachDao.java @@ -0,0 +1,64 @@ +package cc.smtweb.framework.file.spring.dao; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.file.spring.entity.UploadDataVO; +import cc.smtweb.framework.file.util.ThumbImage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; + +@Service +public class ImageAttachDao { + public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + + public void makeThumb(UploadDataVO data, boolean isThumb, File targetFile, Integer size) throws IOException { + boolean imageType = false; + String fileName = data.getName(); + String contentType = data.getContentType(); + + if (contentType.startsWith("image/")) { + imageType = true; + } else if (contentType.equals(APPLICATION_OCTET_STREAM)) { + String fileExt = fileName.substring(fileName.lastIndexOf(".")); + + if (StringUtils.isNotEmpty(fileExt)) { + switch (fileExt.toLowerCase()) { + case ".jpg": + case ".jpeg": + contentType = "image/jpg"; + imageType = true; + break; + case ".gif": + contentType = "image/gif"; + imageType = true; + break; + case ".png": + contentType = "image/png"; + imageType = true; + break; + default: + break; + } + + if (imageType) { + data.setContentType(contentType); + } + } + } + + if (imageType) { + int thumbHeight = 80; + if (size != null) { + thumbHeight = (size > 500) ? 500 : size; + } + + ThumbImage thumbImage = new ThumbImage(); + + thumbImage.makeThumb(isThumb, targetFile, thumbHeight); + data.setWidth(thumbImage.getImageWidth()); + data.setHeight(thumbImage.getImageHeight()); + } + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/dao/SysAttachDao.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/dao/SysAttachDao.java new file mode 100644 index 0000000..6641421 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/dao/SysAttachDao.java @@ -0,0 +1,114 @@ +package cc.smtweb.framework.file.spring.dao; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.file.FilePathGenerator; +import cc.smtweb.framework.file.web.entity.AttachPathPO; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.yaml.snakeyaml.util.UriEncoder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class SysAttachDao { + @Autowired + private FilePathGenerator filePathGenerator; + + @Autowired + private DbEngine dbEngine; + + /** + * 获取文件本地文件路径 + * + * @param filePath 相对路径 + * @return 本地文件全路径 + */ + public String getDiskPath(String filePath) { + return filePathGenerator.getFileDiskPath(filePath); + } + + /** + * 获取访问文件的URL地址 + * + * @param filePath 文件相对路径 + * @return 文件URL地址 + */ + public String getFileUrl(String filePath) { + return filePathGenerator.getFileUrl(filePath); + } + + /** + * 获取访问文件的URL地址 + * + * @param filePath 文件相对路径 + * @param filePath 文件名 + * @return 文件URL地址 + */ + public String getFileUrl(String filePath, String fileName) { + return "/fs/download?path=" + UriEncoder.encode(filePath) + "&name=" + UriEncoder.encode(fileName); + } + + public AttachPathPO get(Long id) { + if (id != null) { + return dbEngine.queryEntity("select attach_id, attach_name, attach_path, attach_content_type, attach_size, attach_create_time from sw_user.sys_attach where attach_id=?", + AttachPathPO.class, id); + } + + return null; + } + + // 删除文件记录和文件 + public void remove(Long fileId) { +// if (id != null) { +// return dbEngine.queryEntity("select attach_id, attach_name, attach_path, attach_content_type, attach_size, attach_create_time from sw_user.sys_attach where attach_id=?", +// AttachPathPO.class, id); +// } +// +// return null; + } + + // 删除文件 + public void remove(String filePath) { +// if (id != null) { +// return dbEngine.queryEntity("select attach_id, attach_name, attach_path, attach_content_type, attach_size, attach_create_time from sw_user.sys_attach where attach_id=?", +// AttachPathPO.class, id); +// } +// +// return null; + } + + public List list(Long[] ids) { + if (ids != null && ids.length > 0) { + return dbEngine.query("select attach_id, attach_name, attach_path, attach_content_type, attach_size, attach_create_time from sw_user.sys_attach where attach_id in( " + + StringUtils.join(ids, ",") + ")", + AttachPathPO.class); + } + + return null; + } + + public Map map(Long[] ids) { + List list = list(ids); + if (list != null && !list.isEmpty()) { + Map map = new HashMap<>(list.size()); + list.forEach((item) -> map.put(item.getAttachId(), item)); + + return map; + } + + return null; + } + + // 保持文件,删除临时文件记录,避免被定时删除 + public void retain(String filePath) { + + } + + // 保持文件,删除临时文件记录,避免被定时删除 + public void retain(Long fileId) { + + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/entity/FileDataVO.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/entity/FileDataVO.java new file mode 100644 index 0000000..6f2c3f2 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/entity/FileDataVO.java @@ -0,0 +1,8 @@ +package cc.smtweb.framework.file.spring.entity; + +import lombok.Data; + +@Data +public class FileDataVO { + private String data; +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/entity/UploadDataVO.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/entity/UploadDataVO.java new file mode 100644 index 0000000..c7d0ad0 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/spring/entity/UploadDataVO.java @@ -0,0 +1,15 @@ +package cc.smtweb.framework.file.spring.entity; + +import lombok.Data; + +@Data +public class UploadDataVO { + private Long id; + private Integer height; + private Integer width; + private long size; + private String path; + private String name; + private String contentType; + private String url; +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/FileDynPath.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/FileDynPath.java new file mode 100644 index 0000000..978ee9a --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/FileDynPath.java @@ -0,0 +1,107 @@ +package cc.smtweb.framework.file.util; + +import cc.smtweb.framework.core.util.DateUtil; +import cc.smtweb.framework.file.FilePathInfo; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.DateUtils; + +import java.io.File; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Random; + +/** + * 动态文件〈文件路径〉 + * + * @author kevin + * @since 1.0.0 + */ +@Slf4j +public class FileDynPath extends FileFixPath { + // 目录允许的最大文件数量,避免批量导入文件时文件太多 + private static final int MAX_FILE_COUNT = 2000; + private static final int MAX_DIR_COUNT = 100000; + private long startTime; + private long endTime; + private final SimpleDateFormat sdf; + // 文件数量 + private int fileCount; + // 目录子索引 + private int pathIndex; + + public FileDynPath(String rootPath, String typeDir, SimpleDateFormat sdf) { + super(rootPath, typeDir); + this.sdf = sdf; + } + + /** + * 返回日期路径字符串 + */ + @Override + public FilePathInfo makeDatePath(long fileId, String fileExt) { + long now = System.currentTimeMillis(); + String fileName; + + // 如果不在就需要重新创建子目录 + if (now < startTime || now >= endTime) { + startTime = DateUtil.getTimesmorning(now); + endTime = startTime + DateUtils.MILLIS_PER_DAY; + + this.path = this.typeDir + "/" + sdf.format(new Timestamp(now)); + createFolder(rootPath + this.path); + } + + // 如果文件数量太大就需要创建新子目录 + while (this.fileCount >= MAX_FILE_COUNT) { + this.pathIndex++; + if(this.pathIndex > MAX_DIR_COUNT) { + throw new RuntimeException("dir is two many"); + } + + createFolder(rootPath + getSubPath()); + } + + Random random = new Random(); + int randomId = random.nextInt(Integer.MAX_VALUE); + + fileName = Long.toHexString(fileId) + "_" + Integer.toHexString(randomId) + fileExt; + + return new FilePathInfo(rootPath, getSubPath(), now, fileName, fileId); + } + + private String getSubPath() { + if (this.pathIndex > 0) { + return String.format("%s%02d/%04d", this.path, MAX_DIR_COUNT / 1000, this.pathIndex % 1000); + } + + return this.path; + } + + private boolean createFolder(String path) { + File file = new File(path); + if (file.exists()) { + if (!file.isDirectory()) { + return false; + } + + File[] list = file.listFiles(); + if (list != null) { + this.fileCount = list.length; + } else { + this.fileCount = 0; + } + + return true; + } + + if (!file.mkdirs()) { + log.error("unable to create folders {}.", rootPath + this.path); + return false; + } + + log.debug("create folders {}.", file); + this.fileCount = 0; + + return true; + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/FileFixPath.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/FileFixPath.java new file mode 100644 index 0000000..46c18e2 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/FileFixPath.java @@ -0,0 +1,52 @@ +package cc.smtweb.framework.file.util; + +import cc.smtweb.framework.file.FilePathInfo; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; + +/** + * 〈文件路径〉 + * + * @author kevin + * @since 1.0.0 + */ +@Slf4j +public class FileFixPath { + protected String path; + protected String rootPath; + protected String typeDir; + + public FileFixPath(String rootPath, String typeDir) { + this.rootPath = rootPath; + this.typeDir = typeDir; + } + + public FilePathInfo makeDatePath(long fileId, String fileName) { + long now = System.currentTimeMillis(); + this.path = this.typeDir + "/"; + createFolder(rootPath + this.path); + + return new FilePathInfo(rootPath, this.path, now, fileName, fileId); + } + + private boolean createFolder(String path) { + File file = new File(path); + if (file.exists()) { + if (!file.isDirectory()) { + return false; + } + + return true; + } + + if (!file.mkdirs()) { + log.error("unable to create folders {}.", rootPath + this.path); + return false; + } + + log.debug("create folders {}.", file); + + return true; + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/MemMultipartFile.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/MemMultipartFile.java new file mode 100644 index 0000000..3cad914 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/MemMultipartFile.java @@ -0,0 +1,79 @@ +package cc.smtweb.framework.file.util; + +import org.apache.commons.codec.binary.Base64; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; + +public class MemMultipartFile implements MultipartFile { + private static final String DATA_IMAGE = "data:image/"; + private byte[] data; + private String contentType; + private String filename; + + public static MemMultipartFile build(String dataUrl) { + if (dataUrl != null && dataUrl.startsWith(DATA_IMAGE)) { + // data:image/png;base64, + int pos1 = dataUrl.indexOf(';', DATA_IMAGE.length()); + int pos2 = dataUrl.indexOf(',', DATA_IMAGE.length()); + if (pos1 > 0 && pos2 > pos1) { + byte[] data = Base64.decodeBase64(dataUrl.substring(pos2)); + + if (data != null) { + String contentType = dataUrl.substring(5, pos1); + return new MemMultipartFile(contentType.replace('/', '.'), contentType, data); + } + } + } + + return null; + } + + private MemMultipartFile(String filename, String contentType, byte[] data) { + this.data = data; + this.contentType = contentType; + this.filename = filename; + } + + @Override + public String getName() { + return "data"; + } + + @Override + public String getOriginalFilename() { + return filename; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean isEmpty() { + return data.length == 0; + } + + @Override + public long getSize() { + return data.length; + } + + @Override + public byte[] getBytes() throws IOException { + return data; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(data); + } + + @Override + public void transferTo(File file) throws IOException, IllegalStateException { + try(FileOutputStream os = new FileOutputStream(file)) { + os.write(data); + } + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/ThumbImage.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/ThumbImage.java new file mode 100644 index 0000000..f1f6029 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/util/ThumbImage.java @@ -0,0 +1,111 @@ +package cc.smtweb.framework.file.util; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.List; + +import javax.imageio.ImageIO; + +import cc.smtweb.framework.file.FilePathGenerator; +import lombok.Getter; +import net.coobird.thumbnailator.Thumbnails; +import net.coobird.thumbnailator.Thumbnails.Builder; +import net.coobird.thumbnailator.geometry.Positions; +import net.coobird.thumbnailator.resizers.configurations.Antialiasing; +import net.sf.image4j.codec.ico.ICODecoder; +import org.apache.commons.lang3.StringUtils; + +/** + * 缩略图生成工具 + * @author xkliu + */ +@Getter +public class ThumbImage { + // 图片处理方式 + public static final int TYPE_DEFAULT = 1; + public static final int TYPE_THUMB = 2; + public static final int TYPE_AVATAR = 3; + + private int imageWidth; + private int imageHeight; + + public static int type(String thumb) { + // 解决历史遗留boolean类型 + if (StringUtils.isBlank(thumb) || "false".equalsIgnoreCase(thumb)) { + return TYPE_DEFAULT; + } + + if ("true".equalsIgnoreCase(thumb)) { + return TYPE_THUMB; + } + + return Integer.parseInt(thumb); + } + + public void makeThumb(boolean isThumb, File file, int size) throws IOException { + makeThumb(file, size, size, isThumb); + } + // 后台等比压缩后大小最好控制在20k以内 + public void makeThumb(File file, int w, int h, boolean keepAspectRatio) throws IOException { + String fileName = file.getName().toLowerCase(); + + BufferedImage image; + + if (fileName.endsWith(".ico")) { + List images = ICODecoder.read(file); + image = images.get(images.size() - 1); + } else { + image = ImageIO.read(file); + } + + imageWidth = image.getWidth(); + imageHeight = image.getHeight(); + + if (fileName.endsWith(".png") || fileName.endsWith(".gif")) { + // 把透明的图填充白色背景 + BufferedImage newBufferedImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB); + newBufferedImage.createGraphics().drawImage(image, 0, 0, Color.WHITE, null); + image = newBufferedImage; + } + + Builder builder = Thumbnails.of(image); + + if (keepAspectRatio) { + if (h > 0) { + // 高度为基准调整宽度到达原图缩放比例 + int imageR = imageWidth * 1000 / imageHeight; + w = h * imageR / 1000; + } else { + // 宽度为基准调整宽度到达原图缩放比例 + int imageR = imageHeight * 1000 / imageWidth; + h = w * imageR / 1000; + } + +// int r = w * 1000 / h; +// int imageR = imageWidth * 1000 / imageHeight; +// if (r != imageR) { +// w = imageHeight * r / 1000; +// } + } else { + int r = w * 1000 / h; + int imageR = imageWidth * 1000 / imageHeight; + + if (r != imageR) { + int width = imageWidth; + int height = imageHeight; + if (r > imageR) { + width = imageHeight * r / 1000; + } else { + height = imageWidth * 1000 / r; + } + + builder.sourceRegion(Positions.CENTER, width, height); + } + } + + builder.size(w, h).antialiasing(Antialiasing.ON).outputFormat("jpg").outputQuality(0.9) + .toFile(file.getAbsolutePath() + FilePathGenerator.THUMB_FILE_EXT); + } +} diff --git a/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/web/entity/AttachPathPO.java b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/web/entity/AttachPathPO.java new file mode 100644 index 0000000..35fdba5 --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/java/cc/smtweb/framework/file/web/entity/AttachPathPO.java @@ -0,0 +1,13 @@ +package cc.smtweb.framework.file.web.entity; + +import lombok.Data; + +@Data +public class AttachPathPO { + private Long attachId; + private String attachName; + private String attachPath; + private String attachContentType; + private Long attachSize; + private Long attachCreate; +} diff --git a/smtweb-framework/sw-framework-file/src/main/resources/META-INF/spring.factories b/smtweb-framework/sw-framework-file/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..3e6e64f --- /dev/null +++ b/smtweb-framework/sw-framework-file/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cc.smtweb.framework.file.spring.FileAutoConfiguration diff --git a/smtweb-framework/sw-framework-file/target/classes/META-INF/spring.factories b/smtweb-framework/sw-framework-file/target/classes/META-INF/spring.factories new file mode 100644 index 0000000..3e6e64f --- /dev/null +++ b/smtweb-framework/sw-framework-file/target/classes/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cc.smtweb.framework.file.spring.FileAutoConfiguration diff --git a/smtweb-framework/sw-framework-file/target/maven-archiver/pom.properties b/smtweb-framework/sw-framework-file/target/maven-archiver/pom.properties new file mode 100644 index 0000000..0344c3b --- /dev/null +++ b/smtweb-framework/sw-framework-file/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Apache Maven +#Tue Nov 02 19:05:00 CST 2021 +version=2.1.0-SNAPSHOT +groupId=cc.smtweb +artifactId=sw-framework-file diff --git a/smtweb-framework/sw-framework-file/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/smtweb-framework/sw-framework-file/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/smtweb-framework/sw-framework-file/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/smtweb-framework/sw-framework-file/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..ca66508 --- /dev/null +++ b/smtweb-framework/sw-framework-file/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,17 @@ +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\spring\controller\FileUploadController.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\util\FileDynPath.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\FilePathInfo.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\FileHelper.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\spring\FileAutoConfiguration.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\util\MemMultipartFile.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\spring\dao\SysAttachDao.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\spring\FileApplication.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\FilePathGenerator.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\spring\dao\ImageAttachDao.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\util\FileFixPath.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\spring\config\FileConfig.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\spring\entity\FileDataVO.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\util\ThumbImage.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\spring\controller\FileDownloadController.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\spring\entity\UploadDataVO.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-file\src\main\java\cc\smtweb\framework\file\web\entity\AttachPathPO.java diff --git a/smtweb-framework/sw-framework-web/pom.xml b/smtweb-framework/sw-framework-web/pom.xml new file mode 100644 index 0000000..1ab085f --- /dev/null +++ b/smtweb-framework/sw-framework-web/pom.xml @@ -0,0 +1,78 @@ + + + cc.smtweb + sw-framework-web + 2.2.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.5.6 + + + + 4.0.0 + + + + org.springframework.boot + spring-boot-starter-web + + + cc.smtweb + sw-framework-auth + 2.2.0-SNAPSHOT + + + cc.smtweb + sw-framework-file + 2.2.0-SNAPSHOT + + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + config/*.yaml + + + + + + + + + nexus-releases + Nexus Release Repository + http://47.92.149.153:7000/repository/maven-releases/ + + + nexus-snapshots + Nexus Snapshot Repository + http://47.92.149.153:7000/repository/maven-snapshots/ + + + + diff --git a/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/spring/WebApplication.java b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/spring/WebApplication.java new file mode 100644 index 0000000..8f29165 --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/spring/WebApplication.java @@ -0,0 +1,14 @@ +package cc.smtweb.framework.web.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author kevin + */ +@SpringBootApplication +public class WebApplication { + public static void main(String[] args) { + SpringApplication.run(WebApplication.class, args); + } +} diff --git a/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/spring/WebAutoConfiguration.java b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/spring/WebAutoConfiguration.java new file mode 100644 index 0000000..77d0af2 --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/spring/WebAutoConfiguration.java @@ -0,0 +1,20 @@ +package cc.smtweb.framework.web.spring; + +import cc.smtweb.framework.core.mvc.config.ControllerConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * @author kevin + */ +@Configuration +@ComponentScan +public class WebAutoConfiguration { + + /** 配置自定义service扫描路径 {module}/{service}/{method} */ + @Bean + public ControllerConfig webControllerConfig() { + return new ControllerConfig("web", "cc.smtweb.framework.web.web"); + } +} diff --git a/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/api/DemoDbService.java b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/api/DemoDbService.java new file mode 100644 index 0000000..8de3360 --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/api/DemoDbService.java @@ -0,0 +1,37 @@ +package cc.smtweb.framework.web.web.api; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwPerm; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SwService +@SwPerm(SwPerm.NONE) +public class DemoDbService { +// @SwParam +// protected DbCache dbCache; + + @SwParam + protected DbEngine dbEngine; + + public R get(@SwParam("code") String code) { +// DictPO dictPo = dbCache.entityByCode(DictPO.class, code); + return null;//R.success(dictPo); + } + + public R trans(@SwParam("code") String code) { +// dbEngine.doTrans(() -> { +// dbEngine.update("insert into sw_im.im_site(site_id, site_name, site_secret_part) values(?,?,?)", 10, "test", "test"); +// +// dbEngine.update("insert into sw_im_log.im_web_socket_log(wsl_id, wsl_user_id, wsl_client_type) values(?,?,?)", 10, 1000, 0); +// return true; +// }); + + dbEngine.queryInt("select 1"); + + return R.success(); + } +} diff --git a/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/api/DemoTimerService.java b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/api/DemoTimerService.java new file mode 100644 index 0000000..85464df --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/api/DemoTimerService.java @@ -0,0 +1,58 @@ +package cc.smtweb.framework.web.web.api; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.*; +import cc.smtweb.framework.core.mvc.SchedulerManager; +import cc.smtweb.framework.core.mvc.scheduler.SchedulerPoint; +import cc.smtweb.framework.core.redis.RedisManager; +import cc.smtweb.framework.core.util.DateUtil; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SwService +@SwPerm(SwPerm.NONE) +public class DemoTimerService { + @SwParam + private RedisManager redisManager; + + @SwParam + private SchedulerManager schedulerManager; + + // defaultRun 命名的函数是默认函数 + public R defaultRun(@SwPathParam String path) { + return R.success(path); + } + + public R redis(@SwParam("time") int time) { + boolean value = schedulerManager.wakeup(DemoTimerService.class, "timer3"); + + return R.success(value); + } + +// @SwScheduling(value = "*/10 * * * * ?", group = "web-timer") + public void timer1(SchedulerPoint schedulerPoint) throws InterruptedException { + String now = DateUtil.nowDateTime(); + System.out.printf("timer 1: %s start\n", now); + + Thread.sleep(30 * 1000); + + now = DateUtil.nowDateTime(); + System.out.printf("timer 1: %s end\n", now); + + schedulerPoint.put("id", now); + } + +// @SwScheduling(value = "*/10 * * * * ?", group = "web-timer") + public void timer2(SchedulerPoint schedulerPoint) { + String now = DateUtil.nowDateTime(); + + System.out.printf("timer 2: %s\n", now); + + schedulerPoint.put("time", now); + } + +// @SwScheduling(value = "*/10 * * * * ?") + public void timer3() { + System.out.printf("timer 3: %s\n", DateUtil.nowDateTime()); + } +} diff --git a/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/dao/TimerDao.java b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/dao/TimerDao.java new file mode 100644 index 0000000..2d288a6 --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/dao/TimerDao.java @@ -0,0 +1,19 @@ +package cc.smtweb.framework.web.web.dao; + +import cc.smtweb.framework.core.annotation.*; +import cc.smtweb.framework.core.mvc.scheduler.SchedulerPoint; +import cc.smtweb.framework.core.util.DateUtil; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SwBean +public class TimerDao { +// @SwScheduling(value = "*/10 * * * * ?") + public void timer(SchedulerPoint schedulerPoint) { + String now = DateUtil.nowDateTime(); + + System.out.printf("dao timer: %s\n", now); + + schedulerPoint.put("time", now); + } +} diff --git a/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/entity/DictPO.java b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/entity/DictPO.java new file mode 100644 index 0000000..5f6df76 --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/entity/DictPO.java @@ -0,0 +1,22 @@ +package cc.smtweb.framework.web.web.entity; + +import cc.smtweb.framework.core.annotation.SwColumn; +import cc.smtweb.framework.core.annotation.SwColumnForeign; +import cc.smtweb.framework.core.annotation.SwTable; +import lombok.Data; + +@Data +@SwTable("sw_user.sys_dict") +public class DictPO { + @SwColumn(type = SwColumn.Type.ID) + private Long dictId; + +// @SwColumn(type = SwColumn.Type.MASTER_ID) +// @SwColumnForeign(table="sw_user.sys_dict_type", id="dt_id", code="dt_name") +// private Long dictDtId; + + @SwColumn(type = {SwColumn.Type.CODE, SwColumn.Type.ORDER}) + private String dictCode; + + private String dictLabel; +} diff --git a/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/entity/UserPO.java b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/entity/UserPO.java new file mode 100644 index 0000000..981dca6 --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/java/cc/smtweb/framework/web/web/entity/UserPO.java @@ -0,0 +1,32 @@ +package cc.smtweb.framework.web.web.entity; + +import cc.smtweb.framework.core.annotation.SwColumn; +import cc.smtweb.framework.core.annotation.SwTable; +import lombok.Data; + +import java.io.Serializable; + +@Data +@SwTable("sw_user.sys_user") +public class UserPO implements Serializable { + @SwColumn(type={SwColumn.Type.ID}) + private Long userId; + + private String userName; + + private Long userSiteId; + +// @SwColumn(type={SwColumn.Type.CREATE_TIME}) +// private Long userCreateAt; + + @SwColumn(type={SwColumn.Type.LAST_TIME}) + private Long userDeptId; + + private String userPassword; + + private String userPhone; + + private Integer userStatus; + + private String userAvatar; +} diff --git a/smtweb-framework/sw-framework-web/src/main/resources/config/application-dev.yaml b/smtweb-framework/sw-framework-web/src/main/resources/config/application-dev.yaml new file mode 100644 index 0000000..2e6c459 --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/resources/config/application-dev.yaml @@ -0,0 +1,58 @@ +smtweb: + machine-id: 1 + static: + local-path: /data/sw/static/ + file: + local-path: /data/sw/files/ + url: http://127.0.0.1:8888/files + db: + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + # 设置服务名 + application: + name: smtweb_core + main: + allow-bean-definition-overriding: true + redis: + host: 127.0.0.1 + port: 6379 + password: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: root + password: 1681860 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + profiles: + include: role + cache: + type: caffeine + cache-names: + - core + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h +park: + secret: + key: null +# key: cmVmb3JtZXJyZWZvcm1lcg== +swagger: + name: smtweb-core + version: 2.0 + enabled: true + + + diff --git a/smtweb-framework/sw-framework-web/src/main/resources/config/application-prod.yaml b/smtweb-framework/sw-framework-web/src/main/resources/config/application-prod.yaml new file mode 100644 index 0000000..eaed90b --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/resources/config/application-prod.yaml @@ -0,0 +1,52 @@ +sme: + machine-id: 1 + file-local-path: /data/files/smart/ + file-host: http://member.sumi168.cn + file-url: ${sme.file-host}:${server.port}${server.servlet.context-path}/${sme.file-local-path} + +server: + port: 10001 + servlet: + context-path: /user + +feign: + hystrix: + enabled: false +logging: + level: + smtweb: DEBUG + +spring: + # 设置服务名 + application: + name: smtweb-user + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + datasource: + user: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: smt + password: smt_123456 + + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + +swagger: + name: smart + version: 1.0.1 + enabled: false + +weixin: + url: http://devpk.smart.smefdd.com + appId: wx8d11474c01b92378 + appSecret: e1fa7bcdf27b46e7d69edad68ea8724b + + diff --git a/smtweb-framework/sw-framework-web/src/main/resources/config/application.yaml b/smtweb-framework/sw-framework-web/src/main/resources/config/application.yaml new file mode 100644 index 0000000..0422894 --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/resources/config/application.yaml @@ -0,0 +1,47 @@ +smtweb: + machine-id: 1 + static: + local-path: /data/sw/static/ + file: + local-path: /data/sw/files/ + url: http://127.0.0.1:8888/files + db: + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + # 设置服务名 + application: + name: smtweb_core + main: + allow-bean-definition-overriding: true + redis: + host: 127.0.0.1 + port: 6379 + password: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/sw_user?serverTimezone=UTC&allowMultiQueries=true&useSSL=false + username: root + password: 1681860 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + profiles: + include: role + cache: + type: caffeine + cache-names: + - core + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h diff --git a/smtweb-framework/sw-framework-web/src/main/resources/static/events.js b/smtweb-framework/sw-framework-web/src/main/resources/static/events.js new file mode 100644 index 0000000..e0806e0 --- /dev/null +++ b/smtweb-framework/sw-framework-web/src/main/resources/static/events.js @@ -0,0 +1,17 @@ +// 注册事件类 +window.$swEvent.setup("defaultEvent", { + init(options) { + }, + + page$onInit(e) { + console.log("onInit", e); + }, + + sysUser$onLoad(e) { + console.log("onLoad", e); + }, + + userNickCode$onChange(e) { + console.log("onChange", e); + }, +}); diff --git a/smtweb-framework/sw-framework-web/target/classes/config/application-dev.yaml b/smtweb-framework/sw-framework-web/target/classes/config/application-dev.yaml new file mode 100644 index 0000000..2e6c459 --- /dev/null +++ b/smtweb-framework/sw-framework-web/target/classes/config/application-dev.yaml @@ -0,0 +1,58 @@ +smtweb: + machine-id: 1 + static: + local-path: /data/sw/static/ + file: + local-path: /data/sw/files/ + url: http://127.0.0.1:8888/files + db: + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + # 设置服务名 + application: + name: smtweb_core + main: + allow-bean-definition-overriding: true + redis: + host: 127.0.0.1 + port: 6379 + password: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: root + password: 1681860 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + profiles: + include: role + cache: + type: caffeine + cache-names: + - core + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h +park: + secret: + key: null +# key: cmVmb3JtZXJyZWZvcm1lcg== +swagger: + name: smtweb-core + version: 2.0 + enabled: true + + + diff --git a/smtweb-framework/sw-framework-web/target/classes/config/application-prod.yaml b/smtweb-framework/sw-framework-web/target/classes/config/application-prod.yaml new file mode 100644 index 0000000..eaed90b --- /dev/null +++ b/smtweb-framework/sw-framework-web/target/classes/config/application-prod.yaml @@ -0,0 +1,52 @@ +sme: + machine-id: 1 + file-local-path: /data/files/smart/ + file-host: http://member.sumi168.cn + file-url: ${sme.file-host}:${server.port}${server.servlet.context-path}/${sme.file-local-path} + +server: + port: 10001 + servlet: + context-path: /user + +feign: + hystrix: + enabled: false +logging: + level: + smtweb: DEBUG + +spring: + # 设置服务名 + application: + name: smtweb-user + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + datasource: + user: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: smt + password: smt_123456 + + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + +swagger: + name: smart + version: 1.0.1 + enabled: false + +weixin: + url: http://devpk.smart.smefdd.com + appId: wx8d11474c01b92378 + appSecret: e1fa7bcdf27b46e7d69edad68ea8724b + + diff --git a/smtweb-framework/sw-framework-web/target/classes/config/application.yaml b/smtweb-framework/sw-framework-web/target/classes/config/application.yaml new file mode 100644 index 0000000..0422894 --- /dev/null +++ b/smtweb-framework/sw-framework-web/target/classes/config/application.yaml @@ -0,0 +1,47 @@ +smtweb: + machine-id: 1 + static: + local-path: /data/sw/static/ + file: + local-path: /data/sw/files/ + url: http://127.0.0.1:8888/files + db: + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + # 设置服务名 + application: + name: smtweb_core + main: + allow-bean-definition-overriding: true + redis: + host: 127.0.0.1 + port: 6379 + password: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/sw_user?serverTimezone=UTC&allowMultiQueries=true&useSSL=false + username: root + password: 1681860 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + profiles: + include: role + cache: + type: caffeine + cache-names: + - core + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h diff --git a/smtweb-framework/sw-framework-web/target/classes/static/events.js b/smtweb-framework/sw-framework-web/target/classes/static/events.js new file mode 100644 index 0000000..e0806e0 --- /dev/null +++ b/smtweb-framework/sw-framework-web/target/classes/static/events.js @@ -0,0 +1,17 @@ +// 注册事件类 +window.$swEvent.setup("defaultEvent", { + init(options) { + }, + + page$onInit(e) { + console.log("onInit", e); + }, + + sysUser$onLoad(e) { + console.log("onLoad", e); + }, + + userNickCode$onChange(e) { + console.log("onChange", e); + }, +}); diff --git a/smtweb-framework/sw-framework-web/target/maven-archiver/pom.properties b/smtweb-framework/sw-framework-web/target/maven-archiver/pom.properties new file mode 100644 index 0000000..08e8d95 --- /dev/null +++ b/smtweb-framework/sw-framework-web/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Apache Maven +#Tue Nov 02 19:05:00 CST 2021 +version=2.1.0-SNAPSHOT +groupId=cc.smtweb +artifactId=sw-framework-web diff --git a/smtweb-framework/sw-framework-web/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/smtweb-framework/sw-framework-web/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/smtweb-framework/sw-framework-web/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/smtweb-framework/sw-framework-web/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..4472a1d --- /dev/null +++ b/smtweb-framework/sw-framework-web/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,7 @@ +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-web\src\main\java\cc\smtweb\framework\web\web\api\DemoTimerService.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-web\src\main\java\cc\smtweb\framework\web\web\api\DemoDbService.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-web\src\main\java\cc\smtweb\framework\web\spring\WebApplication.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-web\src\main\java\cc\smtweb\framework\web\web\entity\DictPO.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-web\src\main\java\cc\smtweb\framework\web\web\dao\TimerDao.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-web\src\main\java\cc\smtweb\framework\web\spring\WebAutoConfiguration.java +E:\jujia\git\6.0\sw\smtweb-framework\sw-framework-web\src\main\java\cc\smtweb\framework\web\web\entity\UserPO.java diff --git a/smtweb-system/.gitignore b/smtweb-system/.gitignore new file mode 100644 index 0000000..c7a782d --- /dev/null +++ b/smtweb-system/.gitignore @@ -0,0 +1,27 @@ +# ---> Java +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.idea/ +*.iml diff --git a/smtweb-system/pom.xml b/smtweb-system/pom.xml new file mode 100644 index 0000000..123832b --- /dev/null +++ b/smtweb-system/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + cc.smtweb + smtweb-system + pom + 2.1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.3.1.RELEASE + + + + UTF-8 + UTF-8 + 1.8 + 1.8 + 1.8 + + + + sw-system-bpm + sw-system-bpm-vue + + diff --git a/smtweb-system/sw-system-bpm/.gitignore b/smtweb-system/sw-system-bpm/.gitignore new file mode 100644 index 0000000..29fde54 --- /dev/null +++ b/smtweb-system/sw-system-bpm/.gitignore @@ -0,0 +1,28 @@ +# ---> Java +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.idea/ +*.iml +target diff --git a/smtweb-system/sw-system-bpm/doc/SwBody.puml b/smtweb-system/sw-system-bpm/doc/SwBody.puml new file mode 100644 index 0000000..27862c3 --- /dev/null +++ b/smtweb-system/sw-system-bpm/doc/SwBody.puml @@ -0,0 +1,10 @@ +@startuml +(*) --> "check input" +If "input is verbose" then +--> [Yes] "turn on verbosity" +--> "run command" +else +--> "run command" +Endif +--> (*) +@enduml diff --git a/smtweb-system/sw-system-bpm/doc/TODO.md b/smtweb-system/sw-system-bpm/doc/TODO.md new file mode 100644 index 0000000..4160543 --- /dev/null +++ b/smtweb-system/sw-system-bpm/doc/TODO.md @@ -0,0 +1,4 @@ +[db design] +1. 同步数据库时返回需要手段删除的字段和表 +2. 导出和导入数据结构 +3. 数据库名不允许重复 diff --git a/smtweb-system/sw-system-bpm/doc/TODO20210714.md b/smtweb-system/sw-system-bpm/doc/TODO20210714.md new file mode 100644 index 0000000..42998d4 --- /dev/null +++ b/smtweb-system/sw-system-bpm/doc/TODO20210714.md @@ -0,0 +1,11 @@ +[ui design] +1. 数据库源字段有多个N:N非ID字段取值的情况,避免重复取值的情况 +2. bug outline-panel上下移动 +3. panel 调整方向后没有重新计算位置 +4. redo/undo +[bpm] +1. ** 后台检测数据集需要配置ID作用字段 +2. 后台检测数据集必须配置idField +3. 后台验证和前台统一 +4. PO代码生成加字段注解 +5. ** 卡片保存时未返回id值 diff --git a/smtweb-system/sw-system-bpm/doc/smt_asp20201229.txt b/smtweb-system/sw-system-bpm/doc/smt_asp20201229.txt new file mode 100644 index 0000000..ce4160c --- /dev/null +++ b/smtweb-system/sw-system-bpm/doc/smt_asp20201229.txt @@ -0,0 +1,6 @@ +ALTER TABLE `asp_model` + ADD COLUMN `mode_md_id` BIGINT(20) NULL DEFAULT NULL COMMENT '����ģ��id, asp_model_delply.md_id' AFTER `model_order`; + +ALTER TABLE `asp_model` + ADD CONSTRAINT `FK_asp_model_asp_model_deploy` FOREIGN KEY (`mode_md_id`) REFERENCES `asp_model_deploy` (`md_id`); + diff --git a/smtweb-system/sw-system-bpm/doc/trans.sql b/smtweb-system/sw-system-bpm/doc/trans.sql new file mode 100644 index 0000000..c4fb911 --- /dev/null +++ b/smtweb-system/sw-system-bpm/doc/trans.sql @@ -0,0 +1,26 @@ +-- 用户中心迁移数据 + +INSERT INTO sys_area(area_id, area_code, area_full_code, area_order, area_name, area_full_name, area_type, area_province_name, area_city_name, area_town_name, + area_town_full_name, area_village_name, area_village_full_name, area_community_name, area_community_full_name, + area_tree_level, area_parent_id, area_parent_id_list, area_status, area_stop_desc, area_desc, area_last_time, area_create_party_id + ) SELECT `area_id`, `area_code`, `area_code_12`, `sequence_code`, `area_name`, `area_full_name`, `area_type`, `area_name_shengshi`, `area_name_dishi`, `area_name_quxian`, `area_full_name_quxian`, `area_name_xiangz`, `area_full_name_xiangz`, `area_name_chun`, `area_full_name_chun`, `tree_level`, `parent_id`, `parent_id_list`, `statu`, `stop_desc`, `description`, `last_time`, `create_party_id` FROM tb_sys_area; + + +INSERT INTO sw_user.sys_user(user_id, user_code, user_nick_code, user_nick_name, user_kind, user_sex, user_head, user_signature, user_phone, user_email, + user_pwd, user_score, user_level, user_otp_id, user_err_count, user_lock_time, user_stop_info, user_pwd_time, user_last_time, user_last_ip, + user_last_info, user_status, user_create_party_id, user_create_time) +SELECT user_id, user_code, nick_code, nick_name, user_kind, user_sex, user_head, user_signature, user_phone, user_email, + user_pwd, user_score, user_level, otp_id, err_count, lock_time, stop_info, pwd_time, last_time, last_ip, + last_info, statu, create_party_id, last_time FROM demo.tb_sys_user_base; + +INSERT INTO sw_user.sys_user_card(uc_id, uc_card_name, uc_card_type, uc_card_number, uc_card_pic_url, uc_card_from_time, + uc_card_end_time, uc_card_authority, uc_card_addr, uc_id_status, uc_status, uc_desc, uc_last_time, uc_create_party_id) +SELECT user_id, user_name, card_type, card_number, card_pic_url, card_from, + card_end, id_authority, id_addr, id_statu, statu, remarks, last_time, create_party_id +FROM demo.tb_sys_user_card; + +INSERT INTO sw_user.sys_user_ex (ue_id, ue_cert_id, ue_party_id, ue_dept_id, ue_portal_id, + ue_portal_type, ue_portal_bi_url, ue_station, ue_tel, ue_otp_id, ue_is_fix, ue_msspid, ue_photo_id, ue_last_time, ue_create_party_id) +SELECT user_id, user_cert_id, party_id, department_id, portal_id, + portal_type, portal_bi_url, station, tel, otp_id, is_fix, msspid, photo_id, last_time, create_party_id +FROM demo.tb_sys_user_ex; diff --git a/smtweb-system/sw-system-bpm/doc/update20210907.sql b/smtweb-system/sw-system-bpm/doc/update20210907.sql new file mode 100644 index 0000000..9d0ac10 --- /dev/null +++ b/smtweb-system/sw-system-bpm/doc/update20210907.sql @@ -0,0 +1,75 @@ +CREATE TABLE `bpm_model` ( + `model_id` BIGINT(20) NOT NULL, + `model_full_name` VARCHAR(64) NULL DEFAULT NULL COMMENT '用.连接唯一地址:模块名+全路径名称' COLLATE 'utf8_unicode_ci', + `model_name` VARCHAR(200) NOT NULL COMMENT '名称' COLLATE 'utf8_unicode_ci', + `model_type` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '模型类型: 1 数据模型,2 工作流模型,3 PC界面,4 手机界面', + `model_content` MEDIUMTEXT NULL DEFAULT NULL COMMENT '内容,json格式' COLLATE 'utf8_unicode_ci', + `model_content_sign` VARCHAR(100) NULL DEFAULT NULL COMMENT 'sha256(内容),内容长度', + `model_status` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '状态:0 启用,1 禁用', + `model_version` INT(11) NOT NULL DEFAULT '0' COMMENT '版本', + `model_order` INT(11) NOT NULL DEFAULT '0' COMMENT '排序', + `model_site_id` BIGINT(20) NULL DEFAULT NULL COMMENT '站点ID', + `model_create_time` BIGINT(20) NOT NULL DEFAULT '0' COMMENT '创建时间', + `model_last_time` BIGINT(20) NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`model_id`) USING BTREE, + UNIQUE INDEX `model_full_name` (`model_full_name`) USING BTREE +) +COMMENT='应用支撑平台模型定义' +COLLATE='utf8_unicode_ci' +ENGINE=InnoDB +ROW_FORMAT=DYNAMIC +; + +ALTER TABLE `asp_model` + CHANGE COLUMN `model_create_at` `model_create_time` BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间' AFTER `model_update_uid`, + CHANGE COLUMN `model_update_at` `model_last_time` BIGINT NOT NULL DEFAULT 0 COMMENT '更新时间' AFTER `model_create_time`; + +ALTER TABLE `asp_model_catalog` + CHANGE COLUMN `mc_create_at` `mc_create_time` BIGINT NOT NULL DEFAULT 0 AFTER `mc_module`, + CHANGE COLUMN `mc_update_at` `mc_last_time` BIGINT NOT NULL DEFAULT 0 AFTER `mc_create_time`; + +ALTER TABLE `asp_model_deploy` + CHANGE COLUMN `md_create_at` `md_create_time` BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间' AFTER `md_update_uid`, + CHANGE COLUMN `md_update_at` `md_last_time` BIGINT NOT NULL DEFAULT 0 COMMENT '更新时间' AFTER `md_create_time`; + +ALTER TABLE `bpm_dyn_form_log` + CHANGE COLUMN `dfl_create_at` `dfl_create_time` BIGINT NOT NULL DEFAULT 0 AFTER `dfl_lock_rev`, + CHANGE COLUMN `dfl_update_at` `dfl_last_time` BIGINT NOT NULL DEFAULT 0 AFTER `dfl_create_time`; + +ALTER TABLE `bpm_node_log` + CHANGE COLUMN `nl_create_at` `nl_create_time` BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间' AFTER `nl_duration`; + +ALTER TABLE `bpm_process` + CHANGE COLUMN `process_create_at` `process_create_time` BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间' AFTER `process_route`, + CHANGE COLUMN `process_update_at` `process_last_time` BIGINT NOT NULL DEFAULT 0 COMMENT '更新时间' AFTER `process_create_time`; + +ALTER TABLE `bpm_process_user` + CHANGE COLUMN `pu_create_at` `pu_create_time` BIGINT NOT NULL DEFAULT 0 AFTER `pu_type`, + CHANGE COLUMN `pu_update_at` `pu_last_time` BIGINT NOT NULL DEFAULT 0 AFTER `pu_create_time`; + +ALTER TABLE `bpm_task` + CHANGE COLUMN `task_create_at` `task_create_time` BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间' AFTER `task_due_time`, + CHANGE COLUMN `task_update_at` `task_last_time` BIGINT NOT NULL DEFAULT 0 COMMENT '更新时间' AFTER `task_create_time`; + +ALTER TABLE `bpm_process_log` + CHANGE COLUMN `pl_create_at` `pl_create_time` BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间' AFTER `pl_route`; + +ALTER TABLE `bpm_process_attachment_log` + CHANGE COLUMN `pal_create_at` `pal_create_time` BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间' AFTER `pal_type`; + +ALTER TABLE `bpm_process_comment_log` + CHANGE COLUMN `pcl_create_at` `pcl_create_time` BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间' AFTER `pcl_content`; + +ALTER TABLE `bpm_process_detail_log` + CHANGE COLUMN `pdl_create_at` `pdl_create_time` BIGINT NOT NULL DEFAULT 0 AFTER `pdl_lock_rev`, + CHANGE COLUMN `pdl_update_at` `pdl_last_time` BIGINT NOT NULL DEFAULT 0 AFTER `pdl_create_time`; + +ALTER TABLE `bpm_task_log` + CHANGE COLUMN `tl_create_at` `tl_create_time` BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间' AFTER `tl_claim_time`; + +ALTER TABLE `bpm_variable` + CHANGE COLUMN `var_create_at` `var_create_time` BIGINT NOT NULL DEFAULT 0 AFTER `var_lock_rev`, + CHANGE COLUMN `var_update_at` `var_update_time` BIGINT NOT NULL DEFAULT 0 AFTER `var_create_time`; + +ALTER TABLE `bpm_variable_log` + CHANGE COLUMN `vl_create_at` `vl_create_time` BIGINT NOT NULL DEFAULT 0 AFTER `vl_lock_rev`; diff --git a/smtweb-system/sw-system-bpm/doc/update20211122.sql b/smtweb-system/sw-system-bpm/doc/update20211122.sql new file mode 100644 index 0000000..d87633b --- /dev/null +++ b/smtweb-system/sw-system-bpm/doc/update20211122.sql @@ -0,0 +1,6 @@ +ALTER TABLE `asp_model` + ADD COLUMN `model_sub_type` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '模型子类型:0 默认 1 页面 2 子页面 3 目录' AFTER `model_type`; +ALTER TABLE `asp_model` + ADD COLUMN `model_icon` VARCHAR(50) NULL COMMENT '图标' AFTER `model_sub_type`; +ALTER TABLE `asp_model_catalog` + ADD COLUMN `mc_icon` VARCHAR(50) NOT NULL COMMENT '项目图标' AFTER `mc_name`; diff --git a/smtweb-system/sw-system-bpm/doc/版本修改说明.md b/smtweb-system/sw-system-bpm/doc/版本修改说明.md new file mode 100644 index 0000000..290d484 --- /dev/null +++ b/smtweb-system/sw-system-bpm/doc/版本修改说明.md @@ -0,0 +1,8 @@ +2.1.0 break changes +1. 数据库升级 update20210907 + smt_ => sw_ +2. 必填和验证规则设置到数据集里面了 +3. 移除数据集的表类型属性tableType, 添加 initData + + +后台写 event.js 后台 ctrl+shift+f9 diff --git a/smtweb-system/sw-system-bpm/pom.xml b/smtweb-system/sw-system-bpm/pom.xml new file mode 100644 index 0000000..2328202 --- /dev/null +++ b/smtweb-system/sw-system-bpm/pom.xml @@ -0,0 +1,176 @@ + + + 4.0.0 + + sw-system-bpm + cc.smtweb + 2.2.2-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.5.6 + + + + + 1.8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + cc.smtweb + sw-framework-auth + 2.2.0-SNAPSHOT + + + cc.smtweb + sw-framework-file + 2.2.0-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.11.0 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.11.0 + + + + + + + + + org.apache.velocity + velocity-engine-core + 2.3 + compile + + + org.springframework.boot + spring-boot-test + test + + + + org.junit.platform + junit-platform-launcher + 1.6.2 + test + + + + org.junit.jupiter + junit-jupiter-api + 5.6.2 + test + + + + org.junit.jupiter + junit-jupiter-engine + 5.6.2 + test + + + org.junit.vintage + junit-vintage-engine + 5.6.2 + test + + + org.junit.jupiter + junit-jupiter-params + 5.6.2 + test + + + + + + + + + + org.springframework + spring-test + 5.2.7.RELEASE + test + + + + org.mockito + mockito-all + 1.10.19 + test + + + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + config/application.yaml + config/application-dev.yaml + config/application-prod.yaml + + + + + + + + + nexus-releases + Nexus Release Repository + http://47.92.149.153:7000/repository/maven-releases/ + + + nexus-snapshots + Nexus Snapshot Repository + http://47.92.149.153:7000/repository/maven-snapshots/ + + + diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/annotation/BpmEvent.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/annotation/BpmEvent.java new file mode 100644 index 0000000..c3cad60 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/annotation/BpmEvent.java @@ -0,0 +1,20 @@ +package cc.smtweb.system.bpm.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据模型注解,放到@SwService类中使用 + * @author xkliu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface BpmEvent { + + /** 数据集地址,如 uc.perm.perm.sysRoleMenuPrivilege */ + String dataset(); + /** 事件类型, BpmEventType */ + int event(); +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/annotation/BpmEventType.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/annotation/BpmEventType.java new file mode 100644 index 0000000..5118b84 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/annotation/BpmEventType.java @@ -0,0 +1,39 @@ +package cc.smtweb.system.bpm.core.annotation; + +/** + * 数据模型事件类型 + * @author xkliu + */ +public interface BpmEventType { + /** + * 卡片模型 + */ + int CARD_BEFORE_LOAD = 101; + int CARD_LOAD = 102; + int CARD_AFTER_LOAD = 103; + int CARD_BEFORE_SAVE = 104; + int CARD_SAVE = 105; + int CARD_AFTER_SAVE = 106; + int CARD_BEFORE_REMOVE = 107; + int CARD_REMOVE = 108; + int CARD_AFTER_REMOVE = 109; +// int CARD_LOAD_LOOKUP = 16; + /** + * 列表模型 + */ + int LIST_BEFORE_LIST = 201; + int LIST_LIST = 202; + int LIST_AFTER_LIST = 203; + int LIST_BEFORE_COUNT = 204; + int LIST_COUNT = 205; + int LIST_AFTER_COUNT = 206; + /** + * 树模型 + */ + int TREE_BEFORE_TREE = 301; + int TREE_TREE = 302; + int TREE_AFTER_TREE = 303; + int TREE_BEFORE_MOVE = 304; + int TREE_MOVE = 305; + int TREE_AFTER_MOVE = 306; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmDataException.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmDataException.java new file mode 100644 index 0000000..08b4c7e --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmDataException.java @@ -0,0 +1,7 @@ +package cc.smtweb.system.bpm.core.exception; + +public class BpmDataException extends BpmException { + public BpmDataException(String msg) { + super(msg); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmException.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmException.java new file mode 100644 index 0000000..a7015f2 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmException.java @@ -0,0 +1,19 @@ +package cc.smtweb.system.bpm.core.exception; + +import cc.smtweb.framework.core.SwException; + +import java.io.IOException; + +public class BpmException extends SwException { + public BpmException(String message) { + super(message); + } + + public BpmException(Exception e) { + super(e); + } + + public BpmException(String message, IOException e) { + super(message, e); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmFieldError.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmFieldError.java new file mode 100644 index 0000000..52ffb71 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmFieldError.java @@ -0,0 +1,14 @@ +package cc.smtweb.system.bpm.core.exception; + +import lombok.Data; + +@Data +public class BpmFieldError { + private String field; + private String message; + + public BpmFieldError(String field, String message) { + this.field = field; + this.message = message; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmIlegalArgumentException.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmIlegalArgumentException.java new file mode 100644 index 0000000..43b6222 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmIlegalArgumentException.java @@ -0,0 +1,12 @@ +package cc.smtweb.system.bpm.core.exception; + +import java.io.IOException; + +public class BpmIlegalArgumentException extends RuntimeException { + public BpmIlegalArgumentException(String message) { + super(message); + } + public BpmIlegalArgumentException(String message, IOException e) { + super(message, e); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmLockRevException.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmLockRevException.java new file mode 100644 index 0000000..d4ffb70 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmLockRevException.java @@ -0,0 +1,7 @@ +package cc.smtweb.system.bpm.core.exception; + +public class BpmLockRevException extends BpmException { + public BpmLockRevException() { + super("update lock rev error"); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmObjectNotFoundException.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmObjectNotFoundException.java new file mode 100644 index 0000000..d1444e0 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmObjectNotFoundException.java @@ -0,0 +1,7 @@ +package cc.smtweb.system.bpm.core.exception; + +public class BpmObjectNotFoundException extends BpmException { + public BpmObjectNotFoundException(String msg) { + super(msg); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmValidException.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmValidException.java new file mode 100644 index 0000000..ae22ad4 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/BpmValidException.java @@ -0,0 +1,15 @@ +package cc.smtweb.system.bpm.core.exception; + +import lombok.Getter; + +import java.util.List; + +public class BpmValidException extends BpmException { + @Getter + private List fieldErrors; + + public BpmValidException(List fieldErrors) { + super("数据验证失败"); + this.fieldErrors = fieldErrors; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/ModelLoaderError.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/ModelLoaderError.java new file mode 100644 index 0000000..4c4c8c2 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/exception/ModelLoaderError.java @@ -0,0 +1,46 @@ +package cc.smtweb.system.bpm.core.exception; + +import lombok.Getter; + +@Getter +public class ModelLoaderError { + public static final int ERROR_FLOW = 11; + public static final int ERROR_NODE = 12; + public static final int ERROR_ACTION = 13; + public static final int ERROR_CONNECTION = 14; + public static final int ERROR_DB = 21; + public static final int ERROR_TABLE = 22; + public static final int ERROR_FIELD = 23; + public static final int ERROR_INDEX = 24; + public static final int ERROR_UI = 31; + public static final int ERROR_UI_DATASET = 32; + + private int type; + private final String id; + private final String subId; + private String msg; + + public ModelLoaderError(int type, String msg) { + this(type, null, null, msg); + } + + public ModelLoaderError(int type, String id, String msg) { + this(type, id, null, msg); + } + + public ModelLoaderError(int type, String id, String subId, String msg) { + this.type = type; + this.id = id; + this.subId = subId; + this.msg = msg; + } + + @Override + public String toString() { + if (id != null) { + return msg; + } else { + return "[id=" + id + "]"+ msg; + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmKeyValue.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmKeyValue.java new file mode 100644 index 0000000..c8cdd7b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmKeyValue.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.core.ui; + +import lombok.Data; + +@Data +public class BpmKeyValue { + private String optKey; + private String optValue; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmListData.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmListData.java new file mode 100644 index 0000000..c4974fe --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmListData.java @@ -0,0 +1,37 @@ +package cc.smtweb.system.bpm.core.ui; + +import cc.smtweb.framework.core.SwMap; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class BpmListData { + public static final BpmListData EMPTY = new BpmListData(new ArrayList<>(), 0); + + private final List rows; + // 总数, -1 表示需要异步获取数量, >=0 表示总数 + private int total; + + private BpmListData(List rows, int total) { + this.rows = rows; + this.total = total; + } + + public static BpmListData create(List list, boolean loadCount) { + if (list == null) { + return BpmListData.EMPTY; + } + + if (loadCount) { + return new BpmListData(list, -1); + } else { + return new BpmListData(list, list.size()); + } + } + + public boolean isEmpty() { + return rows == null || rows.isEmpty(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmMapData.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmMapData.java new file mode 100644 index 0000000..5ec3107 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmMapData.java @@ -0,0 +1,13 @@ +package cc.smtweb.system.bpm.core.ui; + +import java.util.HashMap; + +public class BpmMapData extends HashMap { + public BpmMapData() { + + } + + public BpmMapData(int initialCapacity) { + super(initialCapacity); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmPageContext.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmPageContext.java new file mode 100644 index 0000000..8a0b018 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/BpmPageContext.java @@ -0,0 +1,60 @@ +package cc.smtweb.system.bpm.core.ui; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.session.SessionUtil; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmFieldLookup; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +@Getter +public class BpmPageContext { + static final String ATTR_MAP_BODY = "_bpm_map_body"; + private final IBeanContext context; + private final Map params; + private final HttpServletRequest request; + private final String postBody; + + private SwMap body; + private UserSession session; + @Setter + private boolean deploy = true; + @Setter + private String pageName; + @Setter + private BpmDataset dataset; + @Setter + private BpmFieldLookup fieldLookup; + +// BpmPageContext(SwMap body, UserSession session) { +// this.body = body; +// this.session = session; +// } + + public BpmPageContext(IBeanContext context, Map params, String body, HttpServletRequest request) { + this.context = context; + this.params = params; + this.request = request; + this.postBody = body; + + SwMap mapBody = null; + if (StringUtils.isNotBlank(body)) { + mapBody = JsonUtil.parse(body, SwMap.class); + request.setAttribute(ATTR_MAP_BODY, mapBody); + } + + this.body = mapBody; + this.session = (UserSession) SessionUtil.getSession(); + } + + public long getParamId() { + return body.readLong(IParamConst.PARAM_ID, 0L); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/IParamConst.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/IParamConst.java new file mode 100644 index 0000000..97acc79 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/IParamConst.java @@ -0,0 +1,29 @@ +package cc.smtweb.system.bpm.core.ui; + +/** + * 前端页面参数名 + * @author xkliu + */ +public interface IParamConst { + // 请求参数 + String PARAM_PAGE_ID = "pageId"; + String PARAM_PAGE_NAME = "pageName"; + String PARAM_DATASET = "dataset"; + String PARAM_ID = "id"; + String PARAMS_IDS = "ids"; + String PARAM_PARENT_ID = "dataParentId"; + String PARAM_ORDERS = "dataOrders"; + // 分页参数 + String PARAM_PAGE_INDEX = "pageIndex"; + String PARAM_PAGE_SIZE = "pageSize"; + int PAGE_DEFAULT_SIZE = 20; + // 字段参数后缀 + String LOOKUP_TEXT = "_text"; + String LOOKUP_KEY = "_key"; + // lookup : select/tree + String OPTION_KEY = "optKey"; + String OPTION_LABEL = "optLabel"; + String OPTION_ORDER = "optOrder"; + // tree 返回值 + String TREE_CHILDREN = "children"; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/BpmBeanAttrEditor.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/BpmBeanAttrEditor.java new file mode 100644 index 0000000..4fa4fd1 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/BpmBeanAttrEditor.java @@ -0,0 +1,80 @@ +package cc.smtweb.system.bpm.core.ui.bind; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.exception.BindBeanException; +import cc.smtweb.framework.core.mvc.controller.binder.attr.AbstractAttrEditor; +import cc.smtweb.system.bpm.util.BeanUtil; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * API函数@SwBody参数的转换处理,POST请求直接转换Json内容为Bean对象,GET请求转换Map请求参数为Bean对 + * @author xkliu + */ +public class BpmBeanAttrEditor extends AbstractAttrEditor implements IBpmBeanConst { + @Override + public Object getValue(String paramName, Class paramType, HttpServletRequest request) { + if (ATTRIBUTE_ROW.equals(paramName)) { + return getBeanRow(paramType, request); + } else if (ATTRIBUTE_ROWS.equals(paramName)) { + return getBeanRows(paramType, request); + } + + return request.getAttribute(paramName); + } + + private Object getBeanRow(Class paramType, HttpServletRequest request) { + SwMap map = (SwMap)request.getAttribute(BEAN_ROW_MAP); + Object bean = request.getAttribute(BEAN_ROW_BEAN); + + if (bean != null) { + if (map != null) { + // 逻辑有错误 map 和 bean 不会同时有值 + request.removeAttribute(BEAN_ROW_BEAN); + } + +// if (paramType != null) { +// if (!paramType.equals(bean.getClass())) { +// throw new BindBeanException("type noe equals " + paramType.getName() + "," + bean.getClass().getName()); +// } +// } + } else if (map != null) { + try { + bean = paramType.newInstance(); + } catch (IllegalAccessException | InstantiationException e) { + throw new BindBeanException(e); + } + + BeanUtil.mapToBean(map, bean); + request.setAttribute(BEAN_ROW_MAP, bean); + request.removeAttribute(BEAN_ROW_BEAN); + } + + return bean; + } + + private List getBeanRows(Class paramType, HttpServletRequest request) { + List map = (List)request.getAttribute(BEAN_ROWS_MAP); + List bean = (List)request.getAttribute(BEAN_ROWS_BEAN); + + if (bean != null) { + if (map != null) { + // 逻辑有错误 map 和 bean 不会同时有值 + request.removeAttribute(BEAN_ROWS_MAP); + } + +// if (paramType != null) { +// if (!paramType.equals(bean.getClass())) { +// throw new BindBeanException("type noe equals " + paramType.getName() + "," + bean.getClass().getName()); +// } +// } + } else if (map != null) { + bean = BeanUtil.toBeanList(map, paramType); + request.setAttribute(BEAN_ROWS_BEAN, bean); + request.removeAttribute(BEAN_ROWS_MAP); + } + + return bean; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/BpmMapAttrEditor.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/BpmMapAttrEditor.java new file mode 100644 index 0000000..26cd03c --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/BpmMapAttrEditor.java @@ -0,0 +1,62 @@ +package cc.smtweb.system.bpm.core.ui.bind; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.mvc.controller.binder.attr.AbstractAttrEditor; +import cc.smtweb.system.bpm.util.BeanUtil; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * API函数@SwBody参数的转换处理,POST请求直接转换Json内容为Bean对象,GET请求转换Map请求参数为Bean对 + * @author xkliu + */ +public class BpmMapAttrEditor extends AbstractAttrEditor implements IBpmBeanConst { + @Override + public Object getValue(String paramName, Class paramType, HttpServletRequest request) { + if (ATTRIBUTE_ROW.equals(paramName)) { + return getMapRow(request); + } else if (ATTRIBUTE_ROWS.equals(paramName)) { + return getMapRows(request); + } + + return request.getAttribute(paramName); + } + + private Object getMapRow(HttpServletRequest request) { + SwMap map = (SwMap)request.getAttribute(BEAN_ROW_MAP); + Object bean = request.getAttribute(BEAN_ROW_BEAN); + + if (map != null) { + if (bean != null) { + // 逻辑有错误 map 和 bean 不会同时有值 + request.removeAttribute(BEAN_ROW_BEAN); + } + } else if (bean != null) { + map = new SwMap(); + BeanUtil.beanToMap(bean, map); + request.setAttribute(BEAN_ROW_MAP, map); + request.removeAttribute(BEAN_ROW_BEAN); + } + + return map; + } + + private Object getMapRows(HttpServletRequest request) { + List map = (List)request.getAttribute(BEAN_ROWS_MAP); + List bean = (List)request.getAttribute(BEAN_ROWS_BEAN); + + if (map != null) { + if (bean != null) { + // 逻辑有错误 map 和 bean 不会同时有值 + request.removeAttribute(BEAN_ROWS_BEAN); + } + } else if (bean != null) { + map = BeanUtil.toMapList(bean); + request.setAttribute(BEAN_ROWS_MAP, map); + request.removeAttribute(BEAN_ROWS_BEAN); + } + + return map; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/BpmPageContextEditor.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/BpmPageContextEditor.java new file mode 100644 index 0000000..d11ce04 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/BpmPageContextEditor.java @@ -0,0 +1,70 @@ +package cc.smtweb.system.bpm.core.ui.bind; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.mvc.controller.IBeanContext; +import cc.smtweb.framework.core.mvc.controller.IEditor; +import cc.smtweb.system.bpm.core.exception.BpmException; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.spring.cache.BpmPageCache; +import cc.smtweb.system.bpm.spring.dao.DatasetConfigDao; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +public class BpmPageContextEditor implements IEditor { + + public BpmPageContextEditor(){ + } + + @Override + public Object getParamValue(String paramName, Class paramType, IBeanContext context, Map params, String body, HttpServletRequest request) { + BpmPageContext pageContext = new BpmPageContext(context, params, body, request); + SwMap mapBody = pageContext.getBody(); + String pageName = mapBody.readString(IParamConst.PARAM_PAGE_NAME); + if (pageName == null) { + pageName = mapBody.readString(IParamConst.PARAM_PAGE_ID); + } + + BpmPage page = loadPage(pageName); + pageContext.setPageName(page.getFullName()); + pageContext.setDeploy(page.isDeploy()); + pageContext.setDataset(findUiDataset(page, mapBody.readString(IParamConst.PARAM_DATASET))); + + return pageContext; + } + + private BpmPage loadPage(String pageName) { + if (pageName == null) { + throw new BpmException("参数 pageName 必填"); + } + + BpmPage uiPage = BpmPageCache.getInstance().get(pageName); + if (uiPage == null) { + uiPage = BpmPageCache.getInstance().getByOtherKey(pageName); + } + + if (uiPage == null) { + throw new BpmException("未找到页面, name=" + pageName); + } + + return uiPage; + } + + private BpmDataset findUiDataset(BpmPage uiPage, String datasetName) { + if (datasetName == null) { + throw new BpmException("参数 dataset 必填"); + } + + BpmDataset dataset = uiPage.findDatasetByName(datasetName); + + if (dataset == null) { + throw new BpmException("未找到dataset, pageId=" + uiPage.getId() + ", id=" + datasetName); + } + + return dataset; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/IBpmBeanConst.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/IBpmBeanConst.java new file mode 100644 index 0000000..18a61e9 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/bind/IBpmBeanConst.java @@ -0,0 +1,13 @@ +package cc.smtweb.system.bpm.core.ui.bind; + +public interface IBpmBeanConst { + String ATTRIBUTE_ROW = "row"; + String ATTRIBUTE_ROWS = "rows"; + + String BEAN_ROW = "_sw_bpm_" + ATTRIBUTE_ROW; + String BEAN_ROW_MAP = BEAN_ROW + "_map"; + String BEAN_ROW_BEAN = BEAN_ROW + "_bean"; + String BEAN_ROWS = "_sw_bpm_" + ATTRIBUTE_ROWS; + String BEAN_ROWS_MAP = BEAN_ROWS + "_map"; + String BEAN_ROWS_BEAN = BEAN_ROWS + "_bean"; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/CardDataBuilder.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/CardDataBuilder.java new file mode 100644 index 0000000..148a307 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/CardDataBuilder.java @@ -0,0 +1,310 @@ +package cc.smtweb.system.bpm.core.ui.builder; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.sqlbuilder.AbstractUpdateSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.DeleteSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.InsertSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.core.util.DateUtil; +import cc.smtweb.system.bpm.core.exception.BpmValidException; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmField; +import cc.smtweb.system.bpm.core.exception.BpmFieldError; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmFieldLookup; +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; +import java.util.regex.Pattern; + +/** + * 卡片界面保存操作的SQL构建 + * @author xkliu + */ +public class CardDataBuilder { + private DbEngine dbEngine; + + @Getter + private long masterId; + + private Long masterParentId; + + @Getter + private boolean instert; + private UserSession userSession; + private Long now; + private CardTreeDataBuilder cardTreeDataBuilder; + + private List sqlBuilders = new ArrayList<>(); + private BpmDataset masterDataset; + + /* 字段验证错误列表 */ + @Getter + private List fieldErrors; + + public CardDataBuilder(DbEngine dbEngine, long dataId, Long parentDataId, UserSession userSession) { + this.dbEngine = dbEngine; + this.masterId = dataId; + this.masterParentId = parentDataId; + this.instert = dataId == 0; + this.userSession = userSession; + } + + public void build(BpmDataset dataset, SwMap body, UserSession us) { + masterDataset = dataset; + build(dataset, body, masterId, masterParentId); + List slaves = dataset.getOneToOneDataset(); + if (slaves != null) { + for (BpmDataset slave : slaves) { + long slaveId = body.readLong(slave.getIdField().getName(), 0L); + build(slave, body, slaveId, null); + } + } + } + + private void build(BpmDataset dataset, SwMap body, long dataId, Long parentDataId) { + AbstractUpdateSqlBuilder sqlBuilder; + if (dataId == 0) { + // insert + sqlBuilder = SqlBuilder.createInsert(dataset.getDatabase(), dataset.getTable()); + + if (masterId == 0) { + masterId = dbEngine.nextId(); + if (dataset.getIdField() != null) { + body.put(dataset.getIdField().getName(), masterId); + } + } + // remove: 放到后面统一处理 + // sqlBuilder.add(dataset.getIdFieldName(), masterId); + +// if (parentDataId != null && parentDataId > 0) { +// BpmField parentIdField = dataset.findFieldByRefType(UiEnum.FieldRefType.PARENT_ID); +// if (parentIdField != null) { +// sqlBuilder.add(parentIdField.getFieldName(), parentDataId); +// } +// } + } else { + // update + sqlBuilder = SqlBuilder.createUpdate(dataset.getDatabase(), dataset.getTable()); + sqlBuilder.addWhere(dataset.readIdFieldName(), masterId); + } + + cardTreeDataBuilder = null; + + for (Map.Entry entry: dataset.getFields().entrySet()) { + BpmField bpmField = entry.getValue(); + + String fieldName = bpmField.getFieldName(); + // 只读和虚拟控件不进行保存 + if (!bpmField.isReadonly() && StringUtils.isNotBlank(fieldName)) { + if (dataset.getIdField() == bpmField) { + // id字段新增才需要设置值 + if (dataId == 0) { + sqlBuilder.add(fieldName, masterId); + } + } else { + Object fieldValue = body.get(bpmField.getName()); + + validField(bpmField, fieldValue); + + if (bpmField.isLookupManyToMay()) { + // 中间表数据修改 + buildManyToMany(dataset, bpmField, (String) fieldValue); + } else { + // 处理String空串为NULL值 + if (fieldValue != null) { + if (fieldValue instanceof String && StringUtils.isBlank((String) fieldValue)) { + fieldValue = null; + } + } + + // 处理默认值字段和计算字段 + switch (bpmField.getRefType()) { + case UiEnum.FieldRefType.SITE_ID: + if (dataId == 0) { + // 新增才保存数据 + sqlBuilder.add(fieldName, userSession.getSiteId()); + } + break; + case UiEnum.FieldRefType.CREATE_TIME: + if (dataId == 0) { + // 新增才保存数据 + sqlBuilder.add(fieldName, loadLastTime()); + } + break; + case UiEnum.FieldRefType.LAST_TIME: + sqlBuilder.add(fieldName, loadLastTime()); + break; + case UiEnum.FieldRefType.PARENT_ID: + sqlBuilder.add(fieldName, fieldValue); + updateCardTreeData(UiEnum.FieldRefType.PARENT_ID, fieldName, fieldValue); + break; + case UiEnum.FieldRefType.PARENT_ID_LIST: + updateCardTreeData(UiEnum.FieldRefType.PARENT_ID_LIST, fieldName, fieldValue); + break; + case UiEnum.FieldRefType.TREE_LEVEL: + updateCardTreeData(UiEnum.FieldRefType.TREE_LEVEL, fieldName, fieldValue); + break; + default: + sqlBuilder.add(fieldName, fieldValue); + break; + } + } + } + } + } + + saveCardTreeData(dataset, dataId, sqlBuilder); + + sqlBuilders.add(sqlBuilder); + } + + private void updateCardTreeData(int type, String fieldName, Object fieldValue) { + if (cardTreeDataBuilder == null) { + cardTreeDataBuilder = new CardTreeDataBuilder(dbEngine); + } + + cardTreeDataBuilder.add(type, fieldName, fieldValue); + } + + private void saveCardTreeData(BpmDataset dataset, long dataId, AbstractUpdateSqlBuilder sqlBuilder) { + if (cardTreeDataBuilder != null) { + AbstractUpdateSqlBuilder newSqlBuilder = cardTreeDataBuilder.save(dataset, dataId, sqlBuilder); + cardTreeDataBuilder = null; + + if (newSqlBuilder != null) { + sqlBuilders.add(newSqlBuilder); + } + } + } + + private Long loadLastTime() { + if (now == null) { + now = DateUtil.nowDateTimeLong(); + } + + return now; + } + + private void validField(BpmField bpmField, Object fieldValue) { + if (bpmField.isRequired()) { + if (fieldValue == null || StringUtils.isBlank(fieldValue.toString())) { + // 对应前端的验证错误对象 ValidateError(message, field) + addFieldError(bpmField.getName(), bpmField.getLabel() + "必须填写"); + return; + } + } + + if (fieldValue != null && StringUtils.isNotBlank(bpmField.getValidPatten())) { + if (!Pattern.matches(bpmField.getValidPatten(), fieldValue.toString())) { + if (StringUtils.isNotBlank(bpmField.getValidMessage())) { + addFieldError(bpmField.getName(), bpmField.getValidMessage()); + } else { + addFieldError(bpmField.getName(), bpmField.getLabel() + "格式不正确"); + } + } + } + } + + private void addFieldError(String field, String message) { + if (this.fieldErrors == null) { + this.fieldErrors = new ArrayList<>(); + } + + this.fieldErrors.add(new BpmFieldError(field, message)); + } + + private void buildManyToMany(BpmDataset dataset, BpmField bpmField, String newValue) { + BpmFieldLookup lookup = (BpmFieldLookup) bpmField.getLookup(); + + // 读取旧记录 + Set oldIds = dbEngine.queryStringSet("SELECT " + lookup.getMiddleValueField() + " FROM " + dataset.getDatabase() + "." + lookup.getMiddleTable() + + " WHERE " + lookup.getMiddleKeyField() + "=?", masterId); + Set newIds = toIdSet(newValue); + + for (String newId: newIds) { + if (!oldIds.remove(newId)) { + // insert + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert(dataset.getDatabase(), lookup.getMiddleTable()); + sqlBuilder.add(lookup.getMiddleIdField(), dbEngine.nextId()); + sqlBuilder.add(lookup.getMiddleKeyField(), masterId); + sqlBuilder.add(lookup.getMiddleValueField(), newId); + sqlBuilders.add(sqlBuilder); + } + } + + for (String oldId: oldIds) { + // delete + DeleteSqlBuilder sqlBuilder = SqlBuilder.createDelete(dataset.getDatabase(), lookup.getMiddleTable()); + sqlBuilder.addWhere(lookup.getMiddleKeyField(), masterId); + sqlBuilder.addWhere(lookup.getMiddleValueField(), oldId); + sqlBuilders.add(sqlBuilder); + } + } + + private Set toIdSet(String str) { + Set result = new HashSet<>(); + + if (StringUtils.isNotBlank(str)) { + Collections.addAll(result, str.split(",")); + } + + return result; + } + + public long update() { + if (sqlBuilders.size() > 1) { + dbEngine.doTrans(() -> { + for (AbstractUpdateSqlBuilder sqlBuilder : sqlBuilders) { + sqlBuilder.update(dbEngine); + } + + return true; + }); + } else { + for (AbstractUpdateSqlBuilder sqlBuilder : sqlBuilders) { + sqlBuilder.update(dbEngine); + } + } + + return masterId; + } + + public SwMap update(SwMap data) { + if (data == null) { + data = new SwMap(); + } + + for (AbstractUpdateSqlBuilder sqlBuilder : sqlBuilders) { + sqlBuilder.updateMap(data); + } + + return data; + } + + public String getTableName() { + return masterDataset.fullName(); + } + + public boolean hasFieldErrors() { + return this.fieldErrors != null; + } + + public static CardDataBuilder create(DbEngine dbEngine, BpmDataset dataset, SwMap body, UserSession us) { + long dataId = body.readLong(IParamConst.PARAM_ID, 0L); + Long parentDataId = body.readLong(IParamConst.PARAM_PARENT_ID); + CardDataBuilder cardDataBuilder = new CardDataBuilder(dbEngine, dataId, parentDataId, us); + + cardDataBuilder.build(dataset, body, us); + + if (cardDataBuilder.hasFieldErrors()) { + throw new BpmValidException(cardDataBuilder.getFieldErrors()); + } + + return cardDataBuilder; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/CardTreeDataBuilder.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/CardTreeDataBuilder.java new file mode 100644 index 0000000..4f87513 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/CardTreeDataBuilder.java @@ -0,0 +1,131 @@ +package cc.smtweb.system.bpm.core.ui.builder; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.sqlbuilder.*; +import cc.smtweb.system.bpm.core.exception.BpmDataException; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; +import org.apache.commons.lang3.StringUtils; + +public class CardTreeDataBuilder { + private Long parentId; + private String fieldParentId; + private String fieldParentIdList; + private String fieldLevel; + private DbEngine dbEngine; + + public CardTreeDataBuilder(DbEngine dbEngine) { + this.dbEngine = dbEngine; + } + + public void add(int type, String fieldName, Object fieldValue) { + switch (type) { + case UiEnum.FieldRefType.PARENT_ID: + parentId = readLong(fieldValue); + fieldParentId = fieldName; + break; + case UiEnum.FieldRefType.PARENT_ID_LIST: + fieldParentIdList = fieldName; + break; + case UiEnum.FieldRefType.TREE_LEVEL: + fieldLevel = fieldName; + break; + default: + throw new IllegalStateException("Unexpected value: " + type); + } + } + + public static Long readLong(Object s) { + if (s != null) { + if (s instanceof Number) { + return ((Number) s).longValue(); + } else { + String value = s.toString(); + if (StringUtils.isNotBlank(value)) { + return Long.parseLong(value); + } + } + } + + return null; + } + + public AbstractUpdateSqlBuilder save(BpmDataset dataset, long dataId, AbstractUpdateSqlBuilder sqlBuilder) { + if (fieldParentId != null) { + if (fieldParentIdList == null && fieldLevel == null) { + return null; + } + + if (fieldParentIdList == null || fieldLevel == null) { + throw new BpmDataException("必须同时设置parentIdList和level作用字段"); + } + + // TODO 检测需要同时设置idList和level + String idFileName = dataset.readIdFieldName(); + String parentIdList = null; + int treeLevel = 0; + if (parentId == null) { + sqlBuilder.add(fieldParentIdList, ""); + sqlBuilder.add(fieldLevel, 0); + parentIdList = parentId + "-"; + } else { + // 读取SQL + AbstractSelectSqlBuilder select = SqlBuilder.createSelect(dataset.getDatabase(), dataset.getTable()); + select.add(fieldParentIdList + " parentIdList"); + select.add(fieldLevel + " level"); + + select.addWhere(idFileName, parentId); + + TreeItemVO parentVO = select.queryEntity(dbEngine, TreeItemVO.class); + if (parentVO != null) { + parentIdList = parentVO.getParentIdList() + parentId + "-"; + treeLevel = parentVO.getLevel() + 1; + sqlBuilder.add(fieldParentIdList, parentIdList); + sqlBuilder.add(fieldLevel, treeLevel); + } else { + throw new BpmDataException("not find parent by " + dataset.getTable() + "." + idFileName + "=" + parentId); + } + } + + // 如果parentId有修改,需要处理所有下级的 parentIdList 和 level + if (dataId != 0) { + AbstractSelectSqlBuilder select = SqlBuilder.createSelect(dataset.getDatabase(), dataset.getTable()); + select.add(fieldParentIdList, parentIdList); + select.add(fieldParentId + " parentId"); + select.add(fieldLevel + " level"); + select.addWhere(idFileName, dataId); + + TreeItemVO curVO = select.queryEntity(dbEngine, TreeItemVO.class); + if (curVO != null) { + if (!LongEquals(curVO.getParentId(), parentId)) { + parentIdList = parentIdList + dataId + "-"; + // update table set parentIdList=CONCAT('1-2-4-', SUBSTR(parentIdList, 6)) where parentIdList like "1-2-3-%", level = level + 1; + UpdateSqlBuilder sb = SqlBuilder.createUpdate(dataset.getDatabase(), dataset.getTable()); + sb.add(String.format("%s=CONCAT('%s', SUBSTR(%s, %d))", fieldParentIdList, parentIdList, fieldParentIdList, curVO.getParentIdList().length() + 1), + UpdateSqlBuilder.VALUE_INVALID); + if (curVO.getLevel() > treeLevel) { + sb.add(String.format("%s=%s + %d", fieldLevel, fieldLevel, curVO.getLevel() - treeLevel), UpdateSqlBuilder.VALUE_INVALID); + } else if (curVO.getLevel() < treeLevel) { + sb.add(String.format("%s=%s - %d", fieldLevel, fieldLevel, treeLevel - curVO.getLevel()), UpdateSqlBuilder.VALUE_INVALID); + } + sb.addWhere(fieldParentIdList, curVO.getParentIdList() + "%", " like "); + + return sb; + } + } else { + throw new BpmDataException("not find data by " + dataset.getTable() + "." + idFileName + "=" + dataId); + } + } + } + + return null; + } + + private static boolean LongEquals(Long oldValue, Long newValue) { + if (oldValue != null) { + return oldValue.equals(newValue); + } + + return newValue == null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/SqlUtil.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/SqlUtil.java new file mode 100644 index 0000000..4ff9d1b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/SqlUtil.java @@ -0,0 +1,106 @@ +package cc.smtweb.system.bpm.core.ui.builder; + +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; + +import java.util.List; +import java.util.function.Function; + +public class SqlUtil { + // 包含 + static final int OP_SUB = 1; + // 全匹配 + static final int OP_ALL = 2; + // 前匹配 + static final int OP_BEGIN = 3; + // 后匹配 + static final int OP_END = 4; + // 等于 + static final int OP_EQUAL = 5; + // 不等于 + static final int OP_NOT_EQUAL = 6; + // 大于 + static final int OP_GRATER = 7; + // 大于等于 + static final int OP_NOT_LESS = 8; + // 小于 + static final int OP_LESS = 9; + // 小于等于 + static final int OP_NOT_GRATER = 10; + + // 查询参数 + public static final String PN_FILTER = "filter"; + public static final String PN_SEARCH = "search"; + public static final String PN_SEARCH_TYPE = "searchType"; + + private SqlUtil(){} + + public static void addSqlWhere(SqlBuilder sqlBuilder, String field, int op, Object keyword) { + if (keyword instanceof String) { + keyword = ((String) keyword).trim(); + } + + switch (op) { + default: + case OP_EQUAL: + case OP_ALL: + sqlBuilder.addWhere(field, keyword, "="); + break; + case OP_NOT_EQUAL: + sqlBuilder.addWhere(field, keyword, "<>"); + break; + case OP_GRATER: + sqlBuilder.addWhere(field, keyword, ">"); + break; + case OP_NOT_LESS: + sqlBuilder.addWhere(field, keyword, ">="); + break; + case OP_LESS: + sqlBuilder.addWhere(field, keyword, "<"); + break; + case OP_NOT_GRATER: + sqlBuilder.addWhere(field, keyword, "<="); + break; + case OP_SUB: + sqlBuilder.addWhere(field, "%" + keyword + "%", " like "); + break; + case OP_BEGIN: + sqlBuilder.addWhere(field, "%" + keyword, " like "); + break; + case OP_END: + sqlBuilder.addWhere(field, keyword + "%", " like "); + break; + } + } + + public static String makeSummaryField(String rule, String fieldName) { + switch (rule) { + case "${count}": + return "count(" + fieldName + ")"; + case "${sum}": + return "sum(" + fieldName + ")"; + case "${avg}": + return "avg(" + fieldName + ")"; + case "${min}": + return "min(" + fieldName + ")"; + case "${max}": + return "max(" + fieldName + ")"; + default: + return rule; + } + } + + public static String join(List list, Function action) { + if (list.isEmpty()) { + return null; + } + + StringBuilder sb = new StringBuilder(); + for (T t: list) { + sb.append(action.apply(t)).append(','); + } + + sb.setLength(sb.length() - 1); + + return sb.toString(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/TreeDataBuilder.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/TreeDataBuilder.java new file mode 100644 index 0000000..8cd243b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/TreeDataBuilder.java @@ -0,0 +1,207 @@ +package cc.smtweb.system.bpm.core.ui.builder; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.db.sqlbuilder.AbstractSelectSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.system.bpm.core.exception.BpmException; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmField; +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; +import cc.smtweb.system.bpm.engine.ui.entity.po.DynFormLogPO; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 数据模型处理,读取和排序处理 + */ +@Getter +public class TreeDataBuilder { + private BpmField idField = null; + private BpmField parentField = null; + private BpmField orderField = null; + private BpmField titleField = null; + private BpmDataset dataset; + private Long parentId; + private Long[] orders; + private String idName; + + public static TreeDataBuilder create(BpmPageContext ctx) { + SwMap body = ctx.getBody(); + Long parentId = body.readLong(IParamConst.PARAM_PARENT_ID); + Long[] orders = body.readLongArray(IParamConst.PARAM_ORDERS); + + if (orders == null) { + throw new BpmException(IParamConst.PARAM_ORDERS + " 参数必填"); + } + + TreeDataBuilder treeDataBuilder = new TreeDataBuilder(ctx.getDataset(), parentId, orders); + + if (treeDataBuilder.getParentField() == null) { + throw new BpmException("dataset未设置‘上级ID’字段"); + } + + if (treeDataBuilder.getOrderField() == null) { + throw new BpmException("dataset未设置‘排序’字段"); + } + return treeDataBuilder; + } + + public TreeDataBuilder(BpmDataset dataset, Long parentId, Long[] orders) { + this.dataset = dataset; + this.parentId = parentId; + this.orders = orders; + for (Map.Entry entry: dataset.getFields().entrySet()) { + BpmField bpmField = entry.getValue(); + + switch (bpmField.getRefType()) { + case UiEnum.FieldRefType.ID: + idField = bpmField; + break; + case UiEnum.FieldRefType.PARENT_ID: + parentField = bpmField; + break; + case UiEnum.FieldRefType.ORDER: + orderField = bpmField; + break; + case UiEnum.FieldRefType.TITLE: + titleField = bpmField; + break; + default: + break; + } + } + + idName = idField != null ? idField.getName() : IParamConst.PARAM_ID; + } + + public AbstractSelectSqlBuilder tableSqlBuilder() { + AbstractSelectSqlBuilder sqlBuilder = SqlBuilder.createSelect(dataset.getDatabase(), dataset.getTable()); + + if (idField != null) { + sqlBuilder.add(idField.getFieldName() + " " + idField.getName()); + } else { + sqlBuilder.add(dataset.readIdFieldName() + " id"); + } + sqlBuilder.add(parentField.getFieldName() + " " + parentField.getName()); + sqlBuilder.add(titleField.getFieldName() + " " + titleField.getName()); + + if (orderField != null) { + sqlBuilder.addOrderBy(orderField.getFieldName() + "," + dataset.readIdFieldName()); + } else { + sqlBuilder.addOrderBy(dataset.readIdFieldName()); + } + + return sqlBuilder; + } + + public SwMap mapTreeFields(BpmDataset dataset, DynFormLogPO po) { + if (StringUtils.isNotBlank(po.getDflContent())) { + SwMap item = new SwMap(); + SwMap entity = JsonUtil.parse(po.getDflContent(), SwMap.class); + + item.put(parentField.getName(), entity.get(parentField.getFieldName())); + item.put(titleField.getName(), entity.get(titleField.getFieldName())); + item.put("id", po.getDflId()); + + return item; + } + + return null; + } + + public SwMap updateOrder(DynFormLogPO po, Long parentId, int order) { + SwMap entity; + if (StringUtils.isNotBlank(po.getDflContent())) { + entity = JsonUtil.parse(po.getDflContent(), SwMap.class); + } else { + entity = new SwMap(); + } + + entity.put(this.parentField.getFieldName(), parentId); + entity.put(this.orderField.getFieldName(), order); + + return entity; + } + + public static boolean longEquals(Long l1, Long l2) { + if (l1 != null) { + return l1.equals(l2); + } + + return l2 == null; + } + + private void addChild(SwMap parent, SwMap item) { + List children = readChildren(parent); + + if (children == null) { + children = new ArrayList<>(); + parent.put(IParamConst.TREE_CHILDREN, children); + } + + children.add(item); + } + + private boolean findAndAdd(SwMap parent, SwMap item) { + if (longEquals(parent.readLong(idName), item.readLong(parentField.getName()))) { + addChild(parent, item); + return true; + } else { + List children = readChildren(parent); + + if (children != null) { + for (SwMap child : children) { + if (findAndAdd(child, item)) { + return true; + } + } + } + } + + return false; + } + + private List readChildren(SwMap parent) { + return (List) parent.get(IParamConst.TREE_CHILDREN); + } + + // 读取树结构 + public List buildTree(List list, boolean forceAdd) { + SwMap root = new SwMap(); + + if (list != null) { + for (SwMap item: list) { + if (!findAndAdd(root, item)) { + for (SwMap it: list) { + // TODO IParamConst.PARAM_ID => idField.getId() + if (longEquals(it.readLong(idName), item.readLong(parentField.getName()))) { + addChild(it, item); + item = null; + break; + } + } + + // not find item parent + if (item != null && forceAdd) { + addChild(root, item); + } + } + } + } + + List result = readChildren(root); + + if (result == null) { + result = new ArrayList<>(); + } + + return result; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/TreeItemVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/TreeItemVO.java new file mode 100644 index 0000000..5ab5b8b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/builder/TreeItemVO.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.core.ui.builder; + +import lombok.Data; + +@Data +public class TreeItemVO { + private Long parentId; + private String parentIdList; + private int level; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/DateFieldFormater.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/DateFieldFormater.java new file mode 100644 index 0000000..7aa3d97 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/DateFieldFormater.java @@ -0,0 +1,20 @@ +package cc.smtweb.system.bpm.core.ui.dataset; + + +import cc.smtweb.framework.core.util.DateUtil; + +public class DateFieldFormater extends FieldFormatter { + + public DateFieldFormater(String name) { + super(name); + } + + @Override + public String format(Object value) { + if (value instanceof Number) { + return DateUtil.formatDate(((Number) value).longValue()); + } + + return ""; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/DateTimeFieldFormater.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/DateTimeFieldFormater.java new file mode 100644 index 0000000..e68f906 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/DateTimeFieldFormater.java @@ -0,0 +1,20 @@ +package cc.smtweb.system.bpm.core.ui.dataset; + + +import cc.smtweb.framework.core.util.DateUtil; + +public class DateTimeFieldFormater extends FieldFormatter { + + public DateTimeFieldFormater(String name) { + super(name); + } + + @Override + public String format(Object value) { + if (value instanceof Number) { + return DateUtil.formatDateTime(((Number) value).longValue()); + } + + return ""; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/FieldFormatTool.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/FieldFormatTool.java new file mode 100644 index 0000000..7778f73 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/FieldFormatTool.java @@ -0,0 +1,42 @@ +package cc.smtweb.system.bpm.core.ui.dataset; + +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmField; +import org.apache.commons.lang3.StringUtils; + +/** + * 字段格式化工具 + * + * @author xkliu + */ +public class FieldFormatTool { + public static boolean needFormat(BpmField bpmField) { + return StringUtils.isNotBlank(bpmField.getDisplayFormat()); + } + + public static FieldFormatter buildFormatter(BpmField bpmField) { + + switch (bpmField.getType()) { + case BpmField.TYPE_DATE: + return new DateFieldFormater(bpmField.getName()); + case BpmField.TYPE_DATE_TIME: + return new DateTimeFieldFormater(bpmField.getName()); + default: + return new FieldFormatter(bpmField.getName()); + } + } + + public static void fixDisplayFormat(BpmField bpmField) { + if (StringUtils.isBlank(bpmField.getDisplayFormat())) { + switch (bpmField.getType()) { + case BpmField.TYPE_DATE: + bpmField.setDisplayFormat("yyyy-MM-dd"); + break; + case BpmField.TYPE_DATE_TIME: + bpmField.setDisplayFormat("yyyy-MM-dd HH:mm:ss"); + break; + default: + break; + } + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/FieldFormatter.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/FieldFormatter.java new file mode 100644 index 0000000..91a3ed9 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/dataset/FieldFormatter.java @@ -0,0 +1,16 @@ +package cc.smtweb.system.bpm.core.ui.dataset; + +import lombok.Getter; + +public class FieldFormatter { + @Getter + private String name; + + FieldFormatter(String name) { + this.name = name; + } + + public String format(Object value) { + return value.toString(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/BpmPage.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/BpmPage.java new file mode 100644 index 0000000..f073680 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/BpmPage.java @@ -0,0 +1,43 @@ +package cc.smtweb.system.bpm.core.ui.entity; + +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmForm; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Data +public class BpmPage implements Serializable { + private long id; + private String fullName; + private int modelType; + private boolean isDeploy = true; + private Boolean destroyOnClose; + // 数据集 + private List datasets = new ArrayList<>(); + // 界面 + private List forms = new ArrayList<>(); + + public BpmDataset findDatasetByName(String name) { + if (StringUtils.isNotBlank(name)) { + for (BpmDataset dataset : datasets) { + if (name.equals(dataset.getName())) { + return dataset; + } + } + } + + return null; + } + + public BpmDataset findMasterDataset() { + return datasets.get(0); + } + + public void addDataset(BpmDataset dataset) { + datasets.add(dataset); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/AbstractBpmFieldLookup.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/AbstractBpmFieldLookup.java new file mode 100644 index 0000000..59db0d9 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/AbstractBpmFieldLookup.java @@ -0,0 +1,33 @@ +package cc.smtweb.system.bpm.core.ui.entity.dataset; + +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import lombok.Getter; + +public abstract class AbstractBpmFieldLookup { + @Getter + private final String db; + + @Getter + private final String table; + + @Getter + private final boolean number; + + /** + * lookup对象类型 + * @return Lookup对象类型 + */ + public abstract int type(); + + public AbstractBpmFieldLookup(String dbName, String table, boolean isNumber) { + this.db = dbName; + this.table = table; + this.number = isNumber; + } + + public abstract MappingTypeEnum getMappingType(); + + public boolean isManyToMany() { + return MappingTypeEnum.manyToMany.equals(getMappingType()); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmDataset.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmDataset.java new file mode 100644 index 0000000..a7214d6 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmDataset.java @@ -0,0 +1,186 @@ +package cc.smtweb.system.bpm.core.ui.entity.dataset; + +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * BPM界面引擎后台执行用到的对象 + * @author xkliu + */ +@Data +public class BpmDataset { + public static final int DATA_MODEL_CARD = 1; + public static final int DATA_MODEL_LIST = 2; + public static final int DATA_MODEL_TREE = 3; + + private String id; + private String name; + private String database; + private String table; + /** 主键字段名 */ + private BpmField idField; + + private String fieldPrefix; + // 字段列表,name => UiField + private Map fields = new HashMap<>(); + // 是否只读 + private boolean readonly; + // 是否有统计字段 + private boolean useSummary; + // 是否有查找字段 + private boolean useLookup; + // 外键关联属性 + private AbstractBpmFieldLookup lookup; + // 与主表的映射关系 + private BpmMasterMapping masterMapping = new BpmMasterMapping(); + // 1:1 映射的从表 + private List oneToOneDataset; + // 数据模型 + private int dataModel; + + // 是否有级联数据集,TODO: 分离前台界面需要的参数 +// private boolean cascade; + + private String customApi; + + private boolean initData; + + private BpmDatasetSql sql; + + public void addField(BpmField bpmField) { + fields.put(bpmField.getName(), bpmField); + + String summary = bpmField.getSummary(); + if (StringUtils.isNotBlank(summary)) { + this.useSummary = true; + } + + if (bpmField.getLookup() != null) { + this.useLookup = true; + } + + if (bpmField.getRefType() == UiEnum.FieldRefType.ID) { + setIdField(bpmField); +// props.setIdField(bpmField.getName()); + } + } + + public BpmField addField(String fieldName, String name) { + return addField(fieldName, name, 0); + } + + public BpmField addField(String fieldName, String name, int refType) { + BpmField bpmField = null; + if (StringUtils.isNotBlank(fieldName)) { + bpmField = new BpmField(); + bpmField.setId(name); + bpmField.setFieldName(fieldName); + bpmField.setName(name); + bpmField.setRefType(refType); + this.addField(bpmField); + } + + return bpmField; + } + + public BpmField findFieldByName(String name) { + return fields.get(name); + } + + public BpmField findFieldById(String fieldId) { + return findFieldById(fieldId, false); + } + + public BpmField findFieldById(String fieldId, boolean findSlave) { + if (StringUtils.isNotBlank(fieldId)) { + for (BpmField field : fields.values()) { + if (fieldId.equals(field.getId())) { + return field; + } + } + } + + if(findSlave && this.oneToOneDataset != null) { + for (BpmDataset slave: this.oneToOneDataset) { + BpmField field = slave.findFieldById(fieldId); + if (field != null) { + return field; + } + } + } + + return null; + } + + public String fullName() { + return database + "." + table; + } + +// public String getFieldPrefix() { +// return props.getFieldPrefix(); +// } +// +// public String getId() { +// return props.getId(); +// } + +// public BpmField findFieldByRefType(int refType) { +// for (Map.Entry entry: fields.entrySet()) { +// BpmField bpmField = entry.getValue(); +// +// if (bpmField.getRefType() == refType) { +// return bpmField; +// } +// } +// +// return null; +// } + + public BpmField findFieldByFieldName(String fieldName) { + if (fieldName != null) { + for (Map.Entry entry : fields.entrySet()) { + BpmField bpmField = entry.getValue(); + + if (fieldName.equals(bpmField.getFieldName())) { + return bpmField; + } + } + } + + return null; + } + + public void addOneToOneDataset(BpmDataset slave) { + if (oneToOneDataset == null) { + oneToOneDataset = new ArrayList<>(); + } + + oneToOneDataset.add(slave); + } + +// public BpmDataset findById(String datasetId) { +// if (datasetId.equals(getProps().getId())) { +// return this; +// } +// +// if (this.oneToOneDataset != null) { +// for (BpmDataset slave: this.oneToOneDataset) { +// if (datasetId.equals(slave.getProps().getId())) { +// return slave; +// } +// } +// } +// +// return null; +// } + + public String readIdFieldName() { + return idField != null ? idField.getFieldName() : null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmDatasetSql.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmDatasetSql.java new file mode 100644 index 0000000..1bf0342 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmDatasetSql.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.core.ui.entity.dataset; + +import lombok.Data; + +@Data +public class BpmDatasetSql { + private String select; + private String insert; + private String update; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmField.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmField.java new file mode 100644 index 0000000..7b7acd5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmField.java @@ -0,0 +1,51 @@ +package cc.smtweb.system.bpm.core.ui.entity.dataset; + +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import lombok.Data; + +@Data +public class BpmField { + public static final String TYPE_DATE = "date"; + public static final String TYPE_DATE_TIME = "datetime"; +// /** ID */ +// public static final int REF_TYPE_ID = 1; +// /** 上级ID */ +// public static final int REF_TYPE_PARENT_ID = 2; +// /** CODE编码 */ +// public static final int REF_TYPE_CODE = 3; +// /** 显示顺序 */ +// public static final int REF_TYPE_ORDER = 11; +// /** 标题字段 */ +// public static final int REF_TYPE_TITLE = 21; +// /** 描述字段 */ +// public static final int REF_TYPE_REMARK = 22; + + private String id; + private String name; + private String label; + private String fieldName; + private String type; + private String summary; + private boolean required; + private boolean readonly; + private String validPatten; + private String validMessage; + private int refType; + private String displayFormat; + private AbstractBpmFieldLookup lookup; + private int filter; + private int filterType; + + public void setLookup(AbstractBpmFieldLookup lookup) { + this.lookup = lookup; + } + + // 判断是否多对多字段 + public boolean isLookupManyToMay() { + if (this.lookup != null) { + return MappingTypeEnum.manyToMany.equals(this.lookup.getMappingType()); + } + + return false; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmFieldDict.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmFieldDict.java new file mode 100644 index 0000000..8dfa259 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmFieldDict.java @@ -0,0 +1,26 @@ +package cc.smtweb.system.bpm.core.ui.entity.dataset; + +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; +import lombok.Getter; + +@Getter +public class BpmFieldDict extends AbstractBpmFieldLookup { + private final String dictType; +// public static final int TYPE = 2; + + @Override + public int type() { + return UiEnum.FieldLookupType.DICT; + } + + @Override + public MappingTypeEnum getMappingType() { + return MappingTypeEnum.manyToOne; + } + + public BpmFieldDict(String dbName, String table, boolean isNumber, String dictType) { + super(dbName, table, isNumber); + this.dictType = dictType; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmFieldFilter.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmFieldFilter.java new file mode 100644 index 0000000..1cbf3e8 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmFieldFilter.java @@ -0,0 +1,11 @@ +package cc.smtweb.system.bpm.core.ui.entity.dataset; + +import lombok.Data; + +@Data +public class BpmFieldFilter { + private String field; + private int op; + private String keyword; + private boolean cascade; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmFieldLookup.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmFieldLookup.java new file mode 100644 index 0000000..d4df3ec --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmFieldLookup.java @@ -0,0 +1,50 @@ +package cc.smtweb.system.bpm.core.ui.entity.dataset; + +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter @Setter +public class BpmFieldLookup extends AbstractBpmFieldLookup { +// public static final int TYPE = 1; + private final MappingTypeEnum mappingType; + + private String keyField; + private String labelField; + private String parentField; + private String orderField; + private String middleTable; + private String middleIdField; + private String middleKeyField; + private String middleValueField; + + private List filters; + + @Override + public int type() { + return UiEnum.FieldLookupType.TABLE; + } + + public BpmFieldLookup(String dbName, String table, boolean isNumber, MappingTypeEnum mappingType) { + super(dbName, table, isNumber); + this.mappingType = mappingType; + } + + public void addFilter(String field, int op, String keyword, boolean cascade) { + if (filters == null) { + filters = new ArrayList<>(); + } + + BpmFieldFilter filter = new BpmFieldFilter(); + filter.setField(field); + filter.setKeyword(keyword); + filter.setOp(op); + filter.setCascade(cascade); + + filters.add(filter); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmMappingField.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmMappingField.java new file mode 100644 index 0000000..6a636ae --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmMappingField.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.core.ui.entity.dataset; + +import lombok.Data; + +@Data +public class BpmMappingField { + private String master; + private String slave; + private String middle; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmMasterMapping.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmMasterMapping.java new file mode 100644 index 0000000..ccfa225 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/BpmMasterMapping.java @@ -0,0 +1,36 @@ +package cc.smtweb.system.bpm.core.ui.entity.dataset; + +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class BpmMasterMapping { + // 映射到主表的类型 MappingTypeEnum + private MappingTypeEnum mappingType; + // 主表表名 + private String master; + // 中间表名 + private String middle; + // 映射字段列表 + private List mappingFields; + + public void add(String masterFieldName, String slaveFieldName, String middleFieldName) { + if (mappingFields == null) { + mappingFields = new ArrayList<>(); + } + + BpmMappingField linkField = new BpmMappingField(); + linkField.setMaster(masterFieldName); + linkField.setSlave(slaveFieldName); + linkField.setMiddle(middleFieldName); + + mappingFields.add(linkField); + } + + public boolean isOneToOne() { + return MappingTypeEnum.oneToOne.equals(mappingType); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/IBpmDatasetConst.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/IBpmDatasetConst.java new file mode 100644 index 0000000..faf2c1d --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/dataset/IBpmDatasetConst.java @@ -0,0 +1,78 @@ +package cc.smtweb.system.bpm.core.ui.entity.dataset; + +/** 数据集常量 */ +public interface IBpmDatasetConst { + /** 数据模型类型 */ + int DATA_MODEL_CARD = 1; + int DATA_MODEL_LIST = 2; + int DATA_MODEL_TREE = 3; + int DATA_MODEL_MAX = 10; + + /** 表类型 -> 延迟加载 */ +// public static final int TABLE_TYPE_MASTER = 1; +// public static final int TABLE_TYPE_SLAVE = 2; +// public static final int TABLE_TYPE_LOOKUP = 3; + + String PN_ITEM_KEYWORD = "keyword"; + String PN_ITEM_FIELD = "field"; + String PN_ITEM_OP = "op"; + String PN_ITEM_CASCADE = "cascade"; +// +// private String id; // repeat +// private String name; // repeat +// private int tableType; // -> 可以移动 +// private String fieldPrefix; // repeat +// private int listModel; // repeat dataModel +// private String idField; // -> 可以移动 +// // 主表名 +// private String master; // repeat masterMapping +// // 是否级联过滤表 +// private Boolean cascade; // notuse 仅在生成界面时设置和使用 +// +// private String customApi; // -> 可以移动 +// /** 表单验证规则 */ +// private Map rules; // -> 移动到字段属性中了 +// +//// private Map apis; +// private Map events; // -> 可以移动 +// +//// public void addApi(FzApiVO api) { +//// if (apis == null) { +//// apis = new HashMap<>(); +//// } +//// +//// apis.put(api.getName(), api); +//// } +// +// public void addRule(String name, String type, Boolean required, String patten, String message) { +// BpmFieldValidator rule = new BpmFieldValidator(); +// boolean add = false; +// +// if (Boolean.TRUE.equals(required)) { +// rule.setRequired(Boolean.TRUE); +// add = true; +// } +// +// if (StringUtils.isNotBlank(type)) { +// rule.setType(type); +//// add = true; +//// } else { +//// rule.setType("string"); +// } +// +// if (StringUtils.isNotBlank(patten)) { +// rule.setPattern(patten); +// add = true; +// } +// +// rule.setMessage(message); +// +// if (add) { +// if (rules == null) { +// rules = new HashMap<>(); +// } +// +// rules.put(name, rule); +// } +// } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/form/BpmForm.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/form/BpmForm.java new file mode 100644 index 0000000..626f417 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/form/BpmForm.java @@ -0,0 +1,4 @@ +package cc.smtweb.system.bpm.core.ui.entity.form; + +public class BpmForm extends BpmWidget { +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/form/BpmWidget.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/form/BpmWidget.java new file mode 100644 index 0000000..0845b37 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/form/BpmWidget.java @@ -0,0 +1,76 @@ +package cc.smtweb.system.bpm.core.ui.entity.form; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.engine.ui.entity.vo.widiget.UiControlDataVO; +import cc.smtweb.system.bpm.engine.ui.entity.vo.widiget.UiControlVO; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * 设计器的控件配置数据 + * @author xkliu + */ +@Data +public class BpmWidget { + private String type; + private String name; + private BpmWidgetLayout layout; + private SwMap props = new SwMap(); + private Map slots; + private Map events; + /** 前端响应变量,key和value值相同表示要生成前端变量 */ + private Map vars; + /** 子控件列表 */ + private BpmWidget[] children; + + public void addProp(String key, String value) { + props.put(key, value); + } + + public void addEvents(Map events) { + if (events != null && !events.isEmpty()) { + this.events = events; + } + } + + public void addProps(Map props) { + this.props.putAll(props); + } + + public void addVars(Map vars) { + if (vars != null && !vars.isEmpty()) { + for (Map.Entry entry: vars.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + if (StringUtils.isNotBlank(value)) { + if (this.vars == null) { + this.vars = new HashMap<>(vars.size()); + } + + this.vars.put(key, value); + } + } + } + } + + public void addSlots(Map slots) { + if (slots != null && !slots.isEmpty()) { + this.slots = new HashMap<>(slots.size()); + slots.forEach((k, v) -> { + if (v.length > 0) { + this.slots.put(k, v); + } + }); + + if (this.slots.isEmpty()) { + this.slots = null; + } + } else { + this.slots = null; + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/form/BpmWidgetLayout.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/form/BpmWidgetLayout.java new file mode 100644 index 0000000..5b7ed01 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/entity/form/BpmWidgetLayout.java @@ -0,0 +1,12 @@ +package cc.smtweb.system.bpm.core.ui.entity.form; + +import lombok.Data; + +@Data +public class BpmWidgetLayout { + private int col; + + public BpmWidgetLayout(int col) { + this.col = col; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/AbstractDatasetAgent.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/AbstractDatasetAgent.java new file mode 100644 index 0000000..118059e --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/AbstractDatasetAgent.java @@ -0,0 +1,204 @@ +package cc.smtweb.system.bpm.core.ui.service; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.exception.BpmException; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.bind.IBpmBeanConst; +import cc.smtweb.system.bpm.util.BeanUtil; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractDatasetAgent implements IBpmBeanConst { + protected Class clazz; + protected boolean mapClass; + + public Class getBodyClass() { + return this.clazz; + } + + public void checkType(Class clazz) { + if (clazz == null) { + return; + } + + if (this.clazz == null) { + this.clazz = clazz; + this.mapClass = SwMap.class.equals(clazz); + return; + } + + if (!this.clazz.equals(clazz)) { + throw new BpmException("type " + this.clazz.getName() + " is not equals " + clazz.getName()); + } + } + +// private Object createBean(SwMap body) { +// if (!this.mapClass) { +// Object bean; +// try { +// bean = clazz.newInstance(); +// } catch (InstantiationException | IllegalAccessException e) { +// throw new BpmException(e); +// } +// +// BeanUtil.mapToBean(body, bean); +// return bean; +// } else { +// return body; +// } +// } + + private void mapToBean(SwMap body, Object bean) { + if (!this.mapClass && bean != null) { + BeanUtil.mapToBean(body, bean); + } + } + + private void beanToMap(Object bean, SwMap body) { + if (!this.mapClass && bean != null) { + BeanUtil.beanToMap(bean, body); + } + } + + private List createListBean(List bodyList) { + if (!this.mapClass) { + List beans = new ArrayList<>(bodyList.size()); + try { + for (SwMap body: bodyList) { + Object bean = clazz.newInstance(); + BeanUtil.mapToBean(body, bean); + beans.add(bean); + } + } catch (InstantiationException | IllegalAccessException e) { + throw new BpmException(e); + } + return beans; + } else { + return bodyList; + } + } + + private List createListMap(List beans) { + List bodyList = null; + if (beans != null) { + if (!this.mapClass) { + bodyList = new ArrayList<>(beans.size()); + for (Object bean: beans) { + SwMap body = new SwMap(); + BeanUtil.beanToMap(bean, body); + bodyList.add(body); + } + } else { + return (List)beans; + } + } + + return bodyList; + } + +// public static Class resolveArgClass(Class type, Class subType, int index) { +// Class clazz = null; +// if (type != null) { +// Class[] typeArgs = TypeResolver.resolveRawArguments(type, subType); +// if (typeArgs.length > index) { +// clazz = (Class) typeArgs[index]; +// } else { +// throw new BpmException("resolveRawArguments error " + type.getName() + "," + subType.getName()); +// } +// } +// return clazz; +// } + + protected void saveAttrRowMap(BpmPageContext pageContext, SwMap value) { + HttpServletRequest request = pageContext.getRequest(); + if (value != null) { + request.setAttribute(BEAN_ROW_MAP, value); + } else { + request.removeAttribute(BEAN_ROW_MAP); + } + request.removeAttribute(BEAN_ROW_BEAN); + } + + protected void saveAttrRow(BpmPageContext pageContext, Object value) { + if (this.mapClass) { + saveAttrRowMap(pageContext, (SwMap)value); + } else { + HttpServletRequest request = pageContext.getRequest(); + if (value != null) { + request.setAttribute(BEAN_ROW_BEAN, value); + } else { + request.removeAttribute(BEAN_ROW_BEAN); + } + request.removeAttribute(BEAN_ROW_MAP); + } + } + + protected SwMap loadAttrRowMap(BpmPageContext pageContext) { + HttpServletRequest request = pageContext.getRequest(); + + Object map = request.getAttribute(BEAN_ROW_MAP); + if (map != null) { + return (SwMap)map; + } + + Object bean = request.getAttribute(BEAN_ROW_BEAN); + if (bean != null) { + SwMap body = new SwMap(); + beanToMap(bean, body); + return body; + } + + return null; + } + + protected void saveAttr(BpmPageContext pageContext, String key, Object value) { + HttpServletRequest request = pageContext.getRequest(); + if (value != null) { + request.setAttribute(key, value); + } else { + request.removeAttribute(key); + } + } + + protected List loadAttrRowsMap(BpmPageContext pageContext) { + HttpServletRequest request = pageContext.getRequest(); + + Object map = request.getAttribute(BEAN_ROWS_MAP); + if (map != null) { + return (List)map; + } + + Object bean = request.getAttribute(BEAN_ROWS_BEAN); + if (bean != null) { + return createListMap((List)bean); + } + + return null; + } + + protected void saveAttrRows(BpmPageContext pageContext, List value) { + if (this.mapClass) { + saveAttrRowsMap(pageContext, (List)value); + } else { + HttpServletRequest request = pageContext.getRequest(); + if (value != null) { + request.setAttribute(BEAN_ROWS_BEAN, value); + } else { + request.removeAttribute(BEAN_ROWS_BEAN); + } + request.removeAttribute(BEAN_ROW_MAP); + } + } + + protected void saveAttrRowsMap(BpmPageContext pageContext, List value) { + HttpServletRequest request = pageContext.getRequest(); + if (value != null) { + request.setAttribute(BEAN_ROWS_MAP, value); + } else { + request.removeAttribute(BEAN_ROWS_MAP); + } + request.removeAttribute(BEAN_ROWS_BEAN); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/AbstractDatasetService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/AbstractDatasetService.java new file mode 100644 index 0000000..f439ee1 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/AbstractDatasetService.java @@ -0,0 +1,37 @@ +package cc.smtweb.system.bpm.core.ui.service; + +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.spring.dao.DatasetConfigDao; + +/** + * 基于数据集的服务 + * @author xkliu + */ +public abstract class AbstractDatasetService { + @SwParam + protected DatasetConfigDao datasetConfigDao; + + @SwParam + protected DbEngine dbEngine; + + // 非服务继承时设置 + protected void init(DbEngine dbEngine, DatasetConfigDao datasetConfigDao) { + this.dbEngine = dbEngine; + this.datasetConfigDao = datasetConfigDao; + } + + /** + * 初始化事件调度器中的事件 + * @param handler 事件调度器 + */ +// protected abstract void setup(T handler); +// +// /** +// * 加载对应的页面上下问,包括数据集 +// * @param body 页面参数 +// * @param userSession 用户会话 +// * @return 返回数据集对象 +// */ +// protected abstract PageContext context(SwMap body, UserSession userSession); +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/DatasetApiInvoker.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/DatasetApiInvoker.java new file mode 100644 index 0000000..d3cb86c --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/DatasetApiInvoker.java @@ -0,0 +1,47 @@ +package cc.smtweb.system.bpm.core.ui.service; + +import cc.smtweb.framework.core.mvc.controller.access.IMethodAccess; +import cc.smtweb.system.bpm.core.exception.BpmException; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; + +/** + * Bpm事件执行器,执行@BpmEvent注解的函数 + * @author xkliu + */ +public class DatasetApiInvoker { + private IMethodAccess methodAccess; + + public DatasetApiInvoker(IMethodAccess methodAccess) { + this.methodAccess = methodAccess; + } + + public Object invoke(BpmPageContext pageContext) { + try { + // TODO 检测返回值 + return methodAccess.invoke(pageContext.getContext(), pageContext.getParams(), pageContext.getPostBody(), pageContext.getRequest()); + } catch (Exception e) { + throw new BpmException(e); + } + } + + public Object invoke(BpmPageContext pageContext, Object bean) { + try { + // TODO bean放入上下文 + return methodAccess.invoke(pageContext.getContext(), pageContext.getParams(), pageContext.getPostBody(), pageContext.getRequest()); + } catch (Exception e) { + throw new BpmException(e); + } + } + + public Class getReturnType() { + return methodAccess.getReturnType(); + } + + public Class getBodyType() { + return methodAccess.getBodyType(); + } + + public Class getAttrType(String paramName) { + return methodAccess.getAttrType(paramName); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/DatasetWorker.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/DatasetWorker.java new file mode 100644 index 0000000..f011b50 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/DatasetWorker.java @@ -0,0 +1,20 @@ +package cc.smtweb.system.bpm.core.ui.service; + +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; + +public abstract class DatasetWorker { + protected BpmPageContext pageContext; + + public DatasetWorker(BpmPageContext pageContext) { + this.pageContext = pageContext; + } + + public BpmDataset getDataset() { + return pageContext.getDataset(); + } + + public BpmPageContext getPageContext() { + return pageContext; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/IDatasetHandler.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/IDatasetHandler.java new file mode 100644 index 0000000..61339b5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/IDatasetHandler.java @@ -0,0 +1,13 @@ +package cc.smtweb.system.bpm.core.ui.service; + +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; + +/** + * 数据集事件调度 + * @author xkliu + */ +public interface IDatasetHandler { + BpmDataset getDataset(); + BpmPageContext getPageContext(); +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/AbstractDatasetCardService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/AbstractDatasetCardService.java new file mode 100644 index 0000000..32d16c8 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/AbstractDatasetCardService.java @@ -0,0 +1,160 @@ +package cc.smtweb.system.bpm.core.ui.service.card; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmKeyValue; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.dataset.FieldFormatTool; +import cc.smtweb.system.bpm.core.ui.dataset.FieldFormatter; +import cc.smtweb.system.bpm.core.ui.entity.dataset.*; +import cc.smtweb.system.bpm.core.ui.service.AbstractDatasetService; +import cc.smtweb.system.bpm.engine.entity.DictPO; +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; + +/** + * card数据模型处理服务抽象类 + * @author xkliu + */ +public abstract class AbstractDatasetCardService extends AbstractDatasetService { + /** + * 创建卡片数据集load处理器 + * @param pageContext BPM页面上小文 + * @return 列表数据集时间处理器 + */ + protected abstract DatasetCardWorker createCardLoadHandler(BpmPageContext pageContext); + + /** + * 创建卡片数据集save处理器 + * @param pageContext BPM页面上小文 + * @return 列表数据集时间处理器 + */ + protected abstract DatasetCardWorker createCardSaveHandler(BpmPageContext pageContext); + + /** + * 创建卡片数据集remove处理器 + * @param pageContext BPM页面上小文 + * @return 列表数据集时间处理器 + */ + protected abstract DatasetCardWorker createCardRemoveHandler(BpmPageContext pageContext); + + /** + * 初始化Load事件调度器中的事件 + * @param bpmPageContext Page上下文 + */ +// protected abstract void setupRemove(IDatasetRemoveHandler handler); + + public R load(BpmPageContext bpmPageContext) { + DatasetCardWorker handler = createCardLoadHandler(bpmPageContext); +// setupLoad(handler); + BpmDataset dataset = handler.getDataset(); + + // 读取主表数据 + long id = bpmPageContext.getParamId(); + SwMap result = handler.load(dataset, id); + + // 读取关联表数据 + if (result != null && dataset.isUseLookup()) { + for (Map.Entry entry: dataset.getFields().entrySet()) { + String uiKey = entry.getKey(); + BpmField bpmField = entry.getValue(); + + AbstractBpmFieldLookup lookup = bpmField.getLookup(); + if (lookup != null) { + // 读取查找表数据 select in, FieldId + // 生成ID列表查询条件 + // uiField.getId()); + Object lookupKey = result.get(uiKey); + + if (lookupKey != null) { + BpmKeyValue lookupValue = null; + if (lookup.type() == UiEnum.FieldLookupType.TABLE) { + lookupValue = handler.loadLookup(dataset, (BpmFieldLookup) lookup, lookupKey); + } else if (lookup.type() == UiEnum.FieldLookupType.DICT) { + lookupValue = loadDict((BpmFieldDict) lookup, lookupKey); + } + + if (lookupValue != null) { + String textKey = uiKey + IParamConst.LOOKUP_TEXT; + + if (lookupValue.getOptValue() != null) { + result.put(textKey, lookupValue.getOptValue()); + } + + boolean isManyToMany = lookup.getMappingType().equals(MappingTypeEnum.manyToMany); + if (isManyToMany) { + result.put(uiKey, lookupValue.getOptKey()); + result.put(uiKey + IParamConst.LOOKUP_KEY, lookupValue.getOptKey()); + } + } + } + } else { + // 格式化字段显示 + FieldFormatter fieldFormatter = FieldFormatTool.buildFormatter(bpmField); + if (fieldFormatter != null) { + Object value = result.get(fieldFormatter.getName()); + if (value != null) { + result.put(fieldFormatter.getName() + IParamConst.LOOKUP_TEXT, fieldFormatter.format(value)); + } + } + } + } + } + + return R.success(result); + } + + private BpmKeyValue loadDict(BpmFieldDict lookup, Object lookupKey) { + /*if (lookupKey != null) { + // 字典数据 + String key = lookupKey.toString(); + DictPO dictPo = dbCache.dictEntity(DictPO.class, lookup.getDictType(), key); + + if (dictPo != null) { + BpmKeyValue item = new BpmKeyValue(); + item.setOptKey(key); + item.setOptValue(dictPo.getDictLabel()); + + return item; + } + }*/ + + return null; + } + + public R save(BpmPageContext bpmPageContext) { + DatasetCardWorker handler = createCardSaveHandler(bpmPageContext); +// setupSave(handler); + Long id = handler.save(); + SwMap body = bpmPageContext.getBody(); + if (id != null) { + body.put("id", id); + } + return R.success(body); + } + + public R remove(BpmPageContext bpmPageContext) { + DatasetCardWorker handler = createCardRemoveHandler(bpmPageContext); +// setupRemove(handler); + BpmDataset dataset = handler.getDataset(); + + long dataId = bpmPageContext.getParamId(); + + int data = 0; + if (dataId != 0) { + data = handler.remove(dataset, dataId); + } else { + String dataIds = bpmPageContext.getBody().readString(IParamConst.PARAMS_IDS); + + if (StringUtils.isNotBlank(dataIds)) { + data = handler.removeList(dataset, dataIds); + } + } + + return R.success(data); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/DatasetCardAgentWorker.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/DatasetCardAgentWorker.java new file mode 100644 index 0000000..ec7b274 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/DatasetCardAgentWorker.java @@ -0,0 +1,86 @@ +package cc.smtweb.system.bpm.core.ui.service.card; + +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.bind.IBpmBeanConst; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import cc.smtweb.system.bpm.core.ui.service.card.agent.DatasetLoadAgent; +import cc.smtweb.system.bpm.core.ui.service.card.agent.DatasetRemoveAgent; +import cc.smtweb.system.bpm.core.ui.service.card.agent.DatasetSaveAgent; + +public class DatasetCardAgentWorker extends DatasetCardWorker { + private DatasetSaveAgent datasetSaveAgent; + private DatasetLoadAgent datasetLoadAgent; + private DatasetRemoveAgent datasetRemoveAgent; + + public DatasetCardAgentWorker(BpmPageContext ctx) { + super(ctx); + } + + public void onBeforeSave(DatasetApiInvoker func) { + loadDatasetSaveAgent(func.getBodyType()).setBeforeSave(func); + } + + public void onAfterSave(DatasetApiInvoker func) { + loadDatasetSaveAgent(func.getBodyType()).setAfterSave(func); + } + + public void onSave(DatasetApiInvoker func) { + loadDatasetSaveAgent(func.getBodyType()).setSave(func); + } + + private DatasetSaveAgent loadDatasetSaveAgent(Class clazz) { + if (datasetSaveAgent == null) { + datasetSaveAgent = new DatasetSaveAgent(saveFunc); + this.handleSave(datasetSaveAgent::doSave); + } + + datasetSaveAgent.checkType(clazz); + + return datasetSaveAgent; + } + + public void onBeforeLoad(DatasetApiInvoker func) { + loadDatasetLoadAgent(null).setBeforeLoad(func); + } + + public void onLoad(DatasetApiInvoker func) { + loadDatasetLoadAgent(func.getReturnType()).setLoad(func); + } + + public void onAfterLoad(DatasetApiInvoker func) { + loadDatasetLoadAgent(func.getAttrType(IBpmBeanConst.ATTRIBUTE_ROW)).setAfterLoad(func); + } + + private DatasetLoadAgent loadDatasetLoadAgent(Class clazz) { + if (datasetLoadAgent == null) { + datasetLoadAgent = new DatasetLoadAgent(loadFunc); + this.handleLoad(datasetLoadAgent::doLoad); + } + + datasetLoadAgent.checkType(clazz); + + return datasetLoadAgent; + } + + public void onBeforeRemove(DatasetApiInvoker func) { + loadDatasetRemoveAgent().setBeforeRemove(func); + } + + public void onAfterRemove(DatasetApiInvoker func) { + loadDatasetRemoveAgent().setAfterRemove(func); + } + + public void onRemove(DatasetApiInvoker func) { + loadDatasetRemoveAgent().setRemove(func); + } + + private DatasetRemoveAgent loadDatasetRemoveAgent() { + if (datasetRemoveAgent == null) { + datasetRemoveAgent = new DatasetRemoveAgent(removeFunc); + this.handleRemove(datasetRemoveAgent::doRemove); + this.handleRemoveList(datasetRemoveAgent::doRemove); + } + + return datasetRemoveAgent; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/DatasetCardWorker.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/DatasetCardWorker.java new file mode 100644 index 0000000..00d8084 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/DatasetCardWorker.java @@ -0,0 +1,71 @@ +package cc.smtweb.system.bpm.core.ui.service.card; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmKeyValue; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmFieldLookup; +import cc.smtweb.system.bpm.core.ui.service.DatasetWorker; + +import java.util.function.BiFunction; +import java.util.function.Function; + +public class DatasetCardWorker extends DatasetWorker { + protected Function loadFunc; + protected Function saveFunc; + protected Function removeFunc; + protected Function removeListFunc; + protected BiFunction loadLookupFunc; + + public DatasetCardWorker(BpmPageContext ctx) { + super(ctx); + } + + public void handleLoad(Function loadFunc) { + this.loadFunc = loadFunc; + } + + public void handleLoadLookup(BiFunction loadLookupFunc) { + this.loadLookupFunc = loadLookupFunc; + } + + public void handleSave(Function saveFunc) { + this.saveFunc = saveFunc; + } + + public void handleRemove(Function removeFunc) { + this.removeFunc = removeFunc; + } + + public void handleRemoveList(Function removeListFunc) { + this.removeListFunc = removeListFunc; + } + + public SwMap load(BpmDataset dataset, long id) { + pageContext.setDataset(dataset); + pageContext.setFieldLookup(null); + return loadFunc.apply(pageContext); + } + + public BpmKeyValue loadLookup(BpmDataset dataset, BpmFieldLookup lookup, Object lookupKey) { + pageContext.setDataset(dataset); + pageContext.setFieldLookup(lookup); + return loadLookupFunc.apply(pageContext, lookupKey); + } + + public Long save() { + return saveFunc.apply(pageContext); + } + + public int remove(BpmDataset dataset, long dataId) { + pageContext.setDataset(dataset); + pageContext.setFieldLookup(null); + return removeFunc.apply(pageContext); + } + + public int removeList(BpmDataset dataset, String dataIds) { + pageContext.setDataset(dataset); + pageContext.setFieldLookup(null); + return removeListFunc.apply(pageContext); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/agent/DatasetLoadAgent.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/agent/DatasetLoadAgent.java new file mode 100644 index 0000000..2d20936 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/agent/DatasetLoadAgent.java @@ -0,0 +1,48 @@ +package cc.smtweb.system.bpm.core.ui.service.card.agent; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.AbstractDatasetAgent; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import lombok.Setter; + +import java.util.function.Function; + +/** + * Card数据集读取执行代理,调用@BpmEvent的事件 + * @author xkliu + */ +public class DatasetLoadAgent extends AbstractDatasetAgent { + @Setter + private DatasetApiInvoker beforeLoad; + @Setter + private DatasetApiInvoker afterLoad; + @Setter + private DatasetApiInvoker load; + private final Function defaultLoadFunc; + + public DatasetLoadAgent(Function loadFunc) { + this.defaultLoadFunc = loadFunc; + } + + public SwMap doLoad(BpmPageContext pageContext) { + // bean 处理 + if (beforeLoad != null) { + beforeLoad.invoke(pageContext); + } + + if (this.load != null) { + Object bean = this.load.invoke(pageContext); + saveAttrRow(pageContext, bean); + } else { + SwMap body = this.defaultLoadFunc.apply(pageContext); + saveAttrRowMap(pageContext, body); + } + + if (afterLoad != null) { + afterLoad.invoke(pageContext); + } + + return loadAttrRowMap(pageContext); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/agent/DatasetRemoveAgent.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/agent/DatasetRemoveAgent.java new file mode 100644 index 0000000..a2b1055 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/agent/DatasetRemoveAgent.java @@ -0,0 +1,69 @@ +package cc.smtweb.system.bpm.core.ui.service.card.agent; + +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.service.AbstractDatasetAgent; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; + +import java.util.function.Function; + +/** + * card数据集删除方法代理执行,调用@BpmEvent的事件 + * @author xkliu + */ +public class DatasetRemoveAgent extends AbstractDatasetAgent { + @Setter + private DatasetApiInvoker beforeRemove; + @Setter + private DatasetApiInvoker afterRemove; + @Setter + private DatasetApiInvoker remove; + + private final Function defaultRemoveFunc; + + public DatasetRemoveAgent(Function defaultRemoveFunc) { + this.defaultRemoveFunc = defaultRemoveFunc; + } + + public Integer doRemove(BpmPageContext pageContext) { + Integer result = null; + Long[] ids; + String dataIds = pageContext.getBody().readString(IParamConst.PARAMS_IDS); + if (StringUtils.isNotBlank(dataIds)) { + ids = parse(dataIds); + } else { + ids = new Long[]{pageContext.getParamId()}; + } + + saveAttr(pageContext, "ids", ids); + + // bean 处理 + if (beforeRemove != null) { + beforeRemove.invoke(pageContext); + } + + if (this.remove != null) { + result = (Integer)this.remove.invoke(pageContext); + } else { + result = this.defaultRemoveFunc.apply(pageContext); + } + + if (afterRemove != null) { + afterRemove.invoke(pageContext); + } + + return result; + } + + private Long[] parse(String ids) { + String[] ary = ids.split(","); + Long[] result = new Long[ary.length]; + for (int i = 0; i < result.length; i++) { + result[i] = Long.valueOf(ary[i]); + } + + return result; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/agent/DatasetSaveAgent.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/agent/DatasetSaveAgent.java new file mode 100644 index 0000000..925cd57 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/card/agent/DatasetSaveAgent.java @@ -0,0 +1,52 @@ +package cc.smtweb.system.bpm.core.ui.service.card.agent; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.AbstractDatasetAgent; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import lombok.Setter; + +import java.util.function.Function; + +/** + * card数据集保存方法代理执行,调用@BpmEvent的事件 + * @author xkliu + */ +public class DatasetSaveAgent extends AbstractDatasetAgent { + @Setter + private DatasetApiInvoker beforeSave; + @Setter + private DatasetApiInvoker afterSave; + @Setter + private DatasetApiInvoker save; + private final Function defaultSaveFunc; + + public DatasetSaveAgent(Function defaultSaveFunc) { + this.defaultSaveFunc = defaultSaveFunc; + } + + public Long doSave(BpmPageContext pageContext) { + SwMap body = pageContext.getBody(); + saveAttrRowMap(pageContext, body); + // bean 处理 + if (beforeSave != null) { + beforeSave.invoke(pageContext); + } + + Long result; + if (this.save != null) { + result = (Long)this.save.invoke(pageContext); + } else { + result = this.defaultSaveFunc.apply(pageContext); + } + + if (afterSave != null) { + afterSave.invoke(pageContext); + } + + return result; + } + + + +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/AbstractDatasetListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/AbstractDatasetListService.java new file mode 100644 index 0000000..417898b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/AbstractDatasetListService.java @@ -0,0 +1,217 @@ +package cc.smtweb.system.bpm.core.ui.service.list; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmListData; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.dataset.FieldFormatTool; +import cc.smtweb.system.bpm.core.ui.dataset.FieldFormatter; +import cc.smtweb.system.bpm.core.ui.entity.dataset.*; +import cc.smtweb.system.bpm.core.ui.service.AbstractDatasetService; +import cc.smtweb.system.bpm.engine.entity.DictPO; +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; + +import java.util.*; + +/** + * 列表数据集抽象类 + * @author xkliu + */ +public abstract class AbstractDatasetListService extends AbstractDatasetService { + /** + * 创建列表数据集ListPart处理器 + * @param pageContext 页面上下文参数 + * @return 列表数据集处理器 + */ + protected abstract DatasetListWorker createListPartHandler(BpmPageContext pageContext); + /** + * 创建列表数据集ListPart处理器 + * @param pageContext 页面上下文参数 + * @return 列表数据集处理器 + */ + protected abstract DatasetListWorker createListAllHandler(BpmPageContext pageContext); + + public R list(BpmPageContext pageContext) { + DatasetListWorker handler = createListPartHandler(pageContext); +// setupListPart(handler); + BpmListData data = doList(handler, false); + return R.success(data); + } + + private BpmListData doList(DatasetListWorker handler, boolean listAll) { + BpmDataset dataset = handler.getDataset(); + +// IDynFormList dynFormList = uiPage.isDelpoy() ? dynFormTableList : dynFormJsonList; + List list = null; + boolean loadCount = false; + + AbstractBpmFieldLookup lookup = dataset.getLookup(); + if (lookup != null) { + // 读取字典表 + if (lookup.type() == UiEnum.FieldLookupType.DICT) { + BpmFieldDict fieldDict = (BpmFieldDict) lookup; + // 字典数据 + /*Map dictMap = dbCache.dictMap(DictPO.class, fieldDict.getDictType()); + + if (dictMap != null) { + List dictList = new ArrayList<>(dictMap.size()); + + for (DictPO dictPo : dictMap.values()) { + SwMap item = new SwMap(); + + item.put(IParamConst.OPTION_LABEL, dictPo.getDictLabel()); + if (lookup.isNumber()) { + item.put(IParamConst.OPTION_KEY, Integer.parseInt(dictPo.getDictCode())); + } else { + item.put(IParamConst.OPTION_KEY, dictPo.getDictCode()); + } + + dictList.add(item); + } + + list = dictList; + }*/ + } else if (lookup.type() == UiEnum.FieldLookupType.TABLE) { + list = handler.listAll(dataset, (BpmFieldLookup)lookup); + } + } else { + // TODO 减少无需要显示的字段,同时减少lookup + if (listAll) { + list = handler.listAll(dataset, null); + } else { + list = handler.list(dataset); + if (list != null) { + if (dataset.isUseSummary()) { + loadCount = true; + } else { + int limit = handler.getPageContext().getBody().readInt(IParamConst.PARAM_PAGE_SIZE, IParamConst.PAGE_DEFAULT_SIZE); + if (list.size() >= limit) { + loadCount = true; + } + } + } + } + + // 统一返回列表数据 data[datasetId].list.data + if (list != null) { + // lookupField 查询 + if (!list.isEmpty()) { + listLookup(handler, dataset, list); + + if (dataset.getOneToOneDataset() != null) { + for (BpmDataset slave : dataset.getOneToOneDataset()) { + listLookup(handler, slave, list); + } + } + } + } + } + + // TODO -1表示需要异步获取count + + return BpmListData.create(list, loadCount); + } + + private Map listDict(BpmFieldDict lookup, Set ids) { + // 字典数据 + /*Map dictMap = dbCache.dictMap(DictPO.class, lookup.getDictType()); + + if (dictMap != null) { + Map result = new HashMap<>(ids.size()); + + for (String id : ids) { + DictPO dictPo = dictMap.get(id); + + if (dictPo != null) { + result.put(id, dictPo.getDictLabel()); + } + } + + return result; + }*/ + + return null; + } + + private void listLookup(DatasetListWorker context, BpmDataset dataset, List list) { + List fieldFormatters = null; + + // 查找lookup表数据,并初始化格式化器 + for (BpmField bpmField : dataset.getFields().values()) { + AbstractBpmFieldLookup lookup = bpmField.getLookup(); + if (lookup != null) { + String uiFieldName = bpmField.getName(); + // TODO 处理N:N非ID字段情况 + if (bpmField == dataset.getIdField()) { + uiFieldName = IParamConst.PARAM_ID; + } + + // 读取查找表数据 + Set keys = new HashSet<>(); + for (SwMap item : list) { + Object key = item.get(uiFieldName); + if (key != null) { + keys.add(key.toString()); + } + } + + if (!keys.isEmpty()) { + Map lookValues = null; + if (lookup.type() == UiEnum.FieldLookupType.TABLE) { + lookValues = context.listLookup(dataset, (BpmFieldLookup)lookup, keys); + } else if (lookup.type() == UiEnum.FieldLookupType.DICT) { + lookValues = listDict((BpmFieldDict) lookup, keys); + } + + if (lookValues != null) { + String textKey = bpmField.getName() + IParamConst.LOOKUP_TEXT; + for (SwMap item : list) { + Object key = item.get(uiFieldName); + if (key != null) { + String value = lookValues.get(key.toString()); + if (value != null) { + item.put(textKey, value); + } + } + } + } + } + } else if(FieldFormatTool.needFormat(bpmField)) { + if (fieldFormatters == null) { + fieldFormatters = new ArrayList<>(); + } + + fieldFormatters.add(FieldFormatTool.buildFormatter(bpmField)); + } + } + + // 格式化字段显示 + if (fieldFormatters != null) { + for (SwMap item : list) { + for (FieldFormatter fieldFormatter: fieldFormatters) { + Object value = item.get(fieldFormatter.getName()); + if (value != null) { + item.put(fieldFormatter.getName() + IParamConst.LOOKUP_TEXT, fieldFormatter.format(value)); + } + } + } + } + } + + public R count(BpmPageContext pageContext) { + DatasetListWorker handler = createListPartHandler(pageContext); +// setupListPart(handler); + BpmDataset dataset = handler.getDataset(); + + SwMap data = handler.count(dataset); + return R.success(data); + } + + public R listAll(BpmPageContext pageContext) { + DatasetListWorker handler = createListAllHandler(pageContext); +// setupListAll(handler); + BpmListData data = doList(handler, true); + return R.success(data); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/DatasetListAgentWorker.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/DatasetListAgentWorker.java new file mode 100644 index 0000000..2a0d92a --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/DatasetListAgentWorker.java @@ -0,0 +1,79 @@ +package cc.smtweb.system.bpm.core.ui.service.list; + +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.bind.IBpmBeanConst; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import cc.smtweb.system.bpm.core.ui.service.list.agent.DatasetCountAgent; +import cc.smtweb.system.bpm.core.ui.service.list.agent.DatasetListAgent; + +public class DatasetListAgentWorker extends DatasetListWorker { + private DatasetListAgent listAllAgent; + private DatasetListAgent listPartAgent; + private DatasetCountAgent countAgent; + + public DatasetListAgentWorker(BpmPageContext pageContext) { + super(pageContext); + } + + private DatasetListAgent loadListAllAgent(Class clazz) { + if (listAllAgent == null) { + listAllAgent = new DatasetListAgent(listAllFunc); + this.handleListAll(listAllAgent::doList); + } + + listAllAgent.checkType(clazz); + + return listAllAgent; + } + + private DatasetListAgent loadListPartAgent(Class clazz) { + if (listPartAgent == null) { + listPartAgent = new DatasetListAgent(listFunc); + this.handleListPart(listPartAgent::doList); + } + + listPartAgent.checkType(clazz); + + return listPartAgent; + } + + private DatasetCountAgent loadCountAgent(Class clazz) { + if (countAgent == null) { + countAgent = new DatasetCountAgent(countFunc); + this.handleCount(countAgent::doCount); + } + + countAgent.checkType(clazz); + return countAgent; + } + + public void onBeforeList(DatasetApiInvoker beforeFunc) { + loadListAllAgent(beforeFunc.getAttrType(IBpmBeanConst.ATTRIBUTE_ROWS)).setBeforeList(beforeFunc); + loadListPartAgent(beforeFunc.getAttrType(IBpmBeanConst.ATTRIBUTE_ROWS)).setBeforeList(beforeFunc); + } + + public void onListAll(DatasetApiInvoker listAllFunc) { + loadListAllAgent(listAllFunc.getAttrType(IBpmBeanConst.ATTRIBUTE_ROWS)).setList(listAllFunc); + } + + public void onAfterList(DatasetApiInvoker afterFunc) { + loadListAllAgent(afterFunc.getAttrType(IBpmBeanConst.ATTRIBUTE_ROWS)).setAfterList(afterFunc); + loadListPartAgent(afterFunc.getAttrType(IBpmBeanConst.ATTRIBUTE_ROWS)).setAfterList(afterFunc); + } + + public void onListPart(DatasetApiInvoker listFunc) { + loadListPartAgent(listFunc.getAttrType(IBpmBeanConst.ATTRIBUTE_ROWS)).setList(listFunc); + } + + public void onBeforeCount(DatasetApiInvoker beforeFunc) { + loadCountAgent(null).setBeforeCount(beforeFunc); + } + + public void onAfterCount(DatasetApiInvoker afterFunc) { + loadCountAgent(null).setAfterCount(afterFunc); + } + + public void onCount(DatasetApiInvoker countFunc) { + loadCountAgent(null).setCount(countFunc); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/DatasetListWorker.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/DatasetListWorker.java new file mode 100644 index 0000000..ce9fb08 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/DatasetListWorker.java @@ -0,0 +1,64 @@ +package cc.smtweb.system.bpm.core.ui.service.list; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.BpmMapData; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmFieldLookup; +import cc.smtweb.system.bpm.core.ui.service.DatasetWorker; + +import java.util.List; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class DatasetListWorker extends DatasetWorker { + protected Function> listFunc; + protected Function> listAllFunc; + protected BiFunction, BpmMapData> listLookupFunc; + protected Function countFunc; + + public DatasetListWorker(BpmPageContext body) { + super(body); + } + + public void handleListPart(Function> listFunc) { + this.listFunc = listFunc; + } + + public void handleCount(Function countFunc) { + this.countFunc = countFunc; + } + + public void handleListAll(Function> listAllFunc) { + this.listAllFunc = listAllFunc; + } + + public void handleListLookup(BiFunction, BpmMapData> listLookupFunc) { + this.listLookupFunc = listLookupFunc; + } + + public List list(BpmDataset dataset) { + pageContext.setDataset(dataset); + pageContext.setFieldLookup(null); + return this.listFunc.apply(pageContext); + } + + public SwMap count(BpmDataset dataset) { + pageContext.setDataset(dataset); + pageContext.setFieldLookup(null); + return this.countFunc.apply(pageContext); + } + + public List listAll(BpmDataset dataset, BpmFieldLookup lookup) { + pageContext.setDataset(dataset); + pageContext.setFieldLookup(lookup); + return this.listAllFunc.apply(pageContext); + } + + public BpmMapData listLookup(BpmDataset dataset, BpmFieldLookup lookup, Set idSet) { + pageContext.setDataset(dataset); + pageContext.setFieldLookup(lookup); + return this.listLookupFunc.apply(pageContext, idSet); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/agent/DatasetCountAgent.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/agent/DatasetCountAgent.java new file mode 100644 index 0000000..4004ff0 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/agent/DatasetCountAgent.java @@ -0,0 +1,48 @@ +package cc.smtweb.system.bpm.core.ui.service.list.agent; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.AbstractDatasetAgent; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import lombok.Setter; + +import java.util.function.Function; +/** + * list数据集统计方法代理执行,调用@BpmEvent的事件 + * @author xkliu + */ +public class DatasetCountAgent extends AbstractDatasetAgent { + @Setter + private DatasetApiInvoker beforeCount; + @Setter + private DatasetApiInvoker afterCount; + @Setter + private DatasetApiInvoker count; + + private final Function defaultCountFunc; + + public DatasetCountAgent(Function countFunc) { + this.defaultCountFunc = countFunc; + } + + public SwMap doCount(BpmPageContext pageContext) { + // bean 处理 + if (beforeCount != null) { + beforeCount.invoke(pageContext); + } + + if (this.count != null) { + Object bean = this.count.invoke(pageContext); + saveAttrRow(pageContext, bean); + } else { + SwMap body = this.defaultCountFunc.apply(pageContext); + saveAttrRowMap(pageContext, body); + } + + if (afterCount != null) { + afterCount.invoke(pageContext); + } + + return loadAttrRowMap(pageContext); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/agent/DatasetListAgent.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/agent/DatasetListAgent.java new file mode 100644 index 0000000..dfa97f8 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/list/agent/DatasetListAgent.java @@ -0,0 +1,54 @@ +package cc.smtweb.system.bpm.core.ui.service.list.agent; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.AbstractDatasetAgent; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import lombok.Setter; + +import java.util.List; +import java.util.function.Function; + +/** + * card数据集读取列表方法代理执行,调用@BpmEvent的事件 + * @author xkliu + */ +public class DatasetListAgent extends AbstractDatasetAgent { + @Setter + private DatasetApiInvoker beforeList; + @Setter + private DatasetApiInvoker list; + @Setter + private DatasetApiInvoker afterList; + + private final Function> defaultListFunc; + + public DatasetListAgent(Function> listFunc) { + this.defaultListFunc = listFunc; + } + + public List doList(BpmPageContext pageContext) { + // bean 处理 + if (beforeList != null) { + beforeList.invoke(pageContext); + } + + if (this.list != null) { + List beans = (List)this.list.invoke(pageContext); + saveAttrRows(pageContext, beans); + + if (afterList != null) { + afterList.invoke(pageContext); + } + } else { + List map = this.defaultListFunc.apply(pageContext); + saveAttrRowsMap(pageContext, map); + + if (afterList != null) { + afterList.invoke(pageContext); + } + } + + return loadAttrRowsMap(pageContext); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/AbstractDatasetTreeService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/AbstractDatasetTreeService.java new file mode 100644 index 0000000..bfd472e --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/AbstractDatasetTreeService.java @@ -0,0 +1,43 @@ +package cc.smtweb.system.bpm.core.ui.service.tree; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.service.AbstractDatasetService; + +import java.util.List; + +public abstract class AbstractDatasetTreeService extends AbstractDatasetService { + /** + * 创建树数据集treeAll处理器 + * @param pageContext 页面上下文参数 + * @return 树数据集处理器 + */ + protected abstract DatasetTreeWorker createTreeAllHandler(BpmPageContext pageContext); + /** + * 创建树数据集treeMove处理器 + * @param pageContext 页面上下文参数 + * @return 树数据集处理器 + */ + protected abstract DatasetTreeWorker createTreeMoveHandler(BpmPageContext pageContext); + + public R treeAll(BpmPageContext pageContext) { + DatasetTreeWorker handler = createTreeAllHandler(pageContext); +// setupTreeAll(handler); + + List data = handler.treeAll(handler.getDataset()); + + return R.success(data); + } + + public R move(BpmPageContext pageContext) { + DatasetTreeWorker handler = createTreeMoveHandler(pageContext); +// setupMove(handler); + BpmDataset dataset = handler.getDataset(); + + handler.move(dataset); + + return null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/DatasetTreeAgentWorker.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/DatasetTreeAgentWorker.java new file mode 100644 index 0000000..f634eaa --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/DatasetTreeAgentWorker.java @@ -0,0 +1,59 @@ +package cc.smtweb.system.bpm.core.ui.service.tree; + +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.bind.IBpmBeanConst; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import cc.smtweb.system.bpm.core.ui.service.tree.agent.DatasetMoveAgent; +import cc.smtweb.system.bpm.core.ui.service.tree.agent.DatasetTreeAllAgent; + +public class DatasetTreeAgentWorker extends DatasetTreeWorker { + private DatasetTreeAllAgent treeAllAgent; + private DatasetMoveAgent moveAgent; + + public DatasetTreeAgentWorker(BpmPageContext pageContext) { + super(pageContext); + } + + private DatasetTreeAllAgent loadTreeAllAgent(Class clazz) { + if (treeAllAgent == null) { + treeAllAgent = new DatasetTreeAllAgent(treeAllFunc); + this.handleTreeAll(treeAllAgent::doTreeAll); + } + + treeAllAgent.checkType(clazz); + return treeAllAgent; + } + + private DatasetMoveAgent loadMoveAgent() { + if (moveAgent == null) { + moveAgent = new DatasetMoveAgent(moveFunc); + this.handleMove(moveAgent::doMove); + } + + return moveAgent; + } + + public void onBeforeMove(DatasetApiInvoker func) { + loadMoveAgent().setBeforeMove(func); + } + + public void onMove(DatasetApiInvoker func) { + loadMoveAgent().setMove(func); + } + + public void onAfterMove(DatasetApiInvoker func) { + loadMoveAgent().setAfterMove(func); + } + + public void onBeforeTreeAll(DatasetApiInvoker func) { + loadTreeAllAgent(null).setBeforeTreeAll(func); + } + + public void onTreeAll(DatasetApiInvoker func) { + loadTreeAllAgent(func.getAttrType(IBpmBeanConst.ATTRIBUTE_ROWS)).setTreeAll(func); + } + + public void onAfterTreeAll(DatasetApiInvoker func) { + loadTreeAllAgent(func.getAttrType(IBpmBeanConst.ATTRIBUTE_ROWS)).setAfterTreeAll(func); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/DatasetTreeWorker.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/DatasetTreeWorker.java new file mode 100644 index 0000000..f723e94 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/DatasetTreeWorker.java @@ -0,0 +1,46 @@ +package cc.smtweb.system.bpm.core.ui.service.tree; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.service.DatasetWorker; + +import java.util.List; +import java.util.function.Function; + +public class DatasetTreeWorker extends DatasetWorker { + protected Function> treeAllFunc; + protected Function> treeFunc; + protected Function moveFunc; + + public DatasetTreeWorker(BpmPageContext pageContext) { + super(pageContext); + } + + public void handleTreePart(Function> treeFunc) { + this.treeFunc = treeFunc; + } + + public void handleMove(Function moveFunc) { + this.moveFunc = moveFunc; + } + + public void handleTreeAll(Function> treeAllFunc) { + this.treeAllFunc = treeAllFunc; + } + + public List tree(BpmDataset dataset) { + pageContext.setDataset(dataset); + return treeFunc.apply(pageContext); + } + + public List treeAll(BpmDataset dataset) { + pageContext.setDataset(dataset); + return treeAllFunc.apply(pageContext); + } + + public Integer move(BpmDataset dataset) { + pageContext.setDataset(dataset); + return moveFunc.apply(pageContext); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/agent/DatasetMoveAgent.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/agent/DatasetMoveAgent.java new file mode 100644 index 0000000..5b1acf9 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/agent/DatasetMoveAgent.java @@ -0,0 +1,46 @@ +package cc.smtweb.system.bpm.core.ui.service.tree.agent; + +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import lombok.Setter; + +import java.util.function.Function; +/** + * tree数据集排序方法代理执行,调用@BpmEvent的事件 + * @author xkliu + */ +public class DatasetMoveAgent { + @Setter + private DatasetApiInvoker beforeMove; + @Setter + private DatasetApiInvoker afterMove; + @Setter + private DatasetApiInvoker move; + + private final Function defaultMoveFunc; + + public DatasetMoveAgent(Function defaultMoveFunc) { + this.defaultMoveFunc = defaultMoveFunc; + } + + public Integer doMove(BpmPageContext pageContext) { + Integer result = null; + + // bean 处理 + if (beforeMove != null) { + beforeMove.invoke(pageContext); + } + + if (this.move != null) { + result = (Integer)this.move.invoke(pageContext); + } else { + result = this.defaultMoveFunc.apply(pageContext); + } + + if (afterMove != null) { + afterMove.invoke(pageContext); + } + + return result; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/agent/DatasetTreeAllAgent.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/agent/DatasetTreeAllAgent.java new file mode 100644 index 0000000..6c3ee76 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/core/ui/service/tree/agent/DatasetTreeAllAgent.java @@ -0,0 +1,49 @@ +package cc.smtweb.system.bpm.core.ui.service.tree.agent; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.AbstractDatasetAgent; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import lombok.Setter; + +import java.util.List; +import java.util.function.Function; +/** + * tree数据获取全部树方法代理执行,调用@BpmEvent的事件 + * @author xkliu + */ +public class DatasetTreeAllAgent extends AbstractDatasetAgent { + @Setter + private DatasetApiInvoker beforeTreeAll; + @Setter + private DatasetApiInvoker treeAll; + @Setter + private DatasetApiInvoker afterTreeAll; + + private final Function> defaultTreeAllFunc; + + public DatasetTreeAllAgent(Function> treePartFunc) { + this.defaultTreeAllFunc = treePartFunc; + } + + public List doTreeAll(BpmPageContext pageContext) { + // bean 处理 + if (beforeTreeAll != null) { + beforeTreeAll.invoke(pageContext); + } + + if (this.treeAll != null) { + List beans = (List)this.treeAll.invoke(pageContext); + saveAttrRows(pageContext, beans); + } else { + List body = this.defaultTreeAllFunc.apply(pageContext); + saveAttrRowsMap(pageContext, body); + } + + if (afterTreeAll != null) { + afterTreeAll.invoke(pageContext); + } + + return loadAttrRowsMap(pageContext); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/AbstractLoader.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/AbstractLoader.java new file mode 100644 index 0000000..d7dc6fd --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/AbstractLoader.java @@ -0,0 +1,54 @@ +package cc.smtweb.system.bpm.engine; + +import cc.smtweb.system.bpm.core.exception.ModelLoaderError; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public abstract class AbstractLoader { + /** 错误信息 */ + @Getter + protected List errors = new ArrayList<>(); + + protected void clearErrors() { + this.errors.clear(); + } + + public void addError(int type, String msg) { + this.errors.add(new ModelLoaderError(type, msg)); + } + + public void addError(int type, String id, String msg) { + this.errors.add(new ModelLoaderError(type, id, msg)); + } + + public void addError(int type, String id, String detailId, String msg) { + this.errors.add(new ModelLoaderError(type, id, detailId, msg)); + } + + /** 根据map转换String为int值 */ + protected int mapValue(Map map, String orginValue, int defaultValue) { + Integer value = map.get(orginValue); + return (value != null) ? value : defaultValue; + } + + protected int mapValue(Map map, String orginValue) { + return mapValue(map, orginValue, 0); + } + + public String getLastError() { + if (this.errors.isEmpty()) { + return null; + } + + return this.errors.get(0).getMsg(); + } + + public abstract T load(String jsonText); + +// public T load(InputStream is) { +// return null; +// } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/AspModelCatalogPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/AspModelCatalogPO.java new file mode 100644 index 0000000..d002439 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/AspModelCatalogPO.java @@ -0,0 +1,20 @@ +package cc.smtweb.system.bpm.engine.entity; + +import cc.smtweb.framework.core.annotation.SwTable; +import lombok.Data; + +/** + * asp_model_catalog + * @author xkliu + */ +@Data +@SwTable("sw_bpm.asp_model_catalog") +public class AspModelCatalogPO { + private Long mcId; + private Long mcParentId; + private String mcCode; + private String mcName; + private Long mcCreateTime; + private Long mcLastTime; + private String mcModule; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/AspModelPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/AspModelPO.java new file mode 100644 index 0000000..29edb0d --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/AspModelPO.java @@ -0,0 +1,37 @@ +package cc.smtweb.system.bpm.engine.entity; + +import lombok.Data; + +/** + * asp_model + * @author xkliu + */ +@Data +public class AspModelPO { + // 模型类型: + /** 数据模型 */ + public static final byte TYPE_DB = 1; + /** 工作流模型 */ + public static final byte TYPE_FLOW = 2; + /** PC界面模型 */ + public static final byte TYPE_UI_PC = 3; + /** 手机界面模型 */ + public static final byte TYPE_UI_MOBILE = 4; + + private Long modelId; + private Long modelParentId; + private Long modelMcId; + private String modelKey; + private String modelName; + private int modelStatus; + private String modelContent; + private String modelContentSign; + private byte modelType; + private byte modelSubType; + private int modelVersion; + private Long modelCreateTime; + private Long modelLastTime; + private String modelFullName; + private byte modelUseVue; + private byte modelUseYaml; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/AspModelTreeVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/AspModelTreeVO.java new file mode 100644 index 0000000..d4a0a48 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/AspModelTreeVO.java @@ -0,0 +1,47 @@ +package cc.smtweb.system.bpm.engine.entity; + +import cc.smtweb.system.bpm.util.ITreeDataLevelHandler; +import lombok.Data; + +import java.util.List; + +@Data +public class AspModelTreeVO extends AspModelPO { + private List children; + private int level; + + // 树结构数据句柄 + public static ITreeDataLevelHandler createTreeHandler() { + return new ITreeDataLevelHandler () { + @Override + public Long getId (AspModelTreeVO node){ + return node.getModelId(); + } + + @Override + public Long getParentId (AspModelTreeVO node){ + return node.getModelParentId(); + } + + @Override + public List getChildren (AspModelTreeVO node){ + return node.children; + } + + @Override + public void setChildren (AspModelTreeVO node, List < AspModelTreeVO > children){ + node.children = children; + } + + @Override + public int getLevel (AspModelTreeVO node){ + return node.level; + } + + @Override + public void setLevel (AspModelTreeVO node,int level){ + node.level = level; + } + } ; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/DictPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/DictPO.java new file mode 100644 index 0000000..7d618d4 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/entity/DictPO.java @@ -0,0 +1,22 @@ +package cc.smtweb.system.bpm.engine.entity; + +import cc.smtweb.framework.core.annotation.SwColumn; +import cc.smtweb.framework.core.annotation.SwColumnForeign; +import cc.smtweb.framework.core.annotation.SwTable; +import lombok.Data; + +@Data +@SwTable("sw_user.sys_dict") +public class DictPO { + @SwColumn(type = SwColumn.Type.ID) + private Long dictId; + + @SwColumn(type = SwColumn.Type.MASTER_ID) + @SwColumnForeign(table="sw_user.sys_dict_type", id="dt_id", code="dt_name") + private Long dictDtId; + + @SwColumn(type = {SwColumn.Type.CODE, SwColumn.Type.ORDER}) + private String dictCode; + + private String dictLabel; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/AbstractTaskCardService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/AbstractTaskCardService.java new file mode 100644 index 0000000..972f9e5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/AbstractTaskCardService.java @@ -0,0 +1,146 @@ +package cc.smtweb.system.bpm.engine.process.impl; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwBody; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.core.db.vo.ModelField; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.system.bpm.engine.process.impl.entity.CommitTaskVO; +import cc.smtweb.system.bpm.engine.process.impl.entity.LoadTaskAckVO; +import cc.smtweb.system.bpm.engine.process.impl.entity.LoadTaskVO; +import cc.smtweb.system.bpm.engine.process.impl.entity.SaveTaskVO; +import cc.smtweb.system.bpm.engine.process.runtime.ProcessInstance; +import cc.smtweb.system.bpm.engine.process.task.Task; +import cc.smtweb.system.bpm.engine.process.vo.FlowAction; +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import cc.smtweb.system.bpm.engine.process.vo.FlowNodeField; +import cc.smtweb.system.bpm.spring.IProcessEngine; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// "工作流任务接口" +public abstract class AbstractTaskCardService { + @SwParam + protected IProcessEngine processEngine; + + @SwParam + protected DbEngine dbEngine; + + /** 读取表单界面结构和数据 */ + protected abstract LoadTaskAckVO loadForm(Task task); + + /** 保存表单数据 */ + protected void saveForm(Task task, SaveTaskVO postTaskVO, UserSession us) { + ProcessInstance processInstance = task.getProcessInstance(); + FlowNode flowNode = processInstance.getCurNode(); + List fields = flowNode.getFields(); + + if (fields != null) { + ModelTable modelTable = processInstance.getModelTable(); + SwMap body = new SwMap(fields.size()); + for (FlowNodeField nodeField : fields) { + ModelField modelField = modelTable.findField(nodeField.getId()); + int perm = nodeField.getPerm(); + + if (modelField != null && perm == FlowNodeField.PERM_WRITE) { + Object value = postTaskVO.get(modelField.getName()); + if (value != null) { + body.put(modelField.getName(), value); + } + } + } + + processEngine.getFormDataStore().save(processInstance.getId(), processInstance.getRootId(), processInstance.getRootId(), + postTaskVO.readTaskId(), modelTable.fullName(), body, us); + } + } + + private Task loadTask(long flowId, long taskId, String nodeKey) { + Task task; + + if (taskId == 0) { + task = processEngine.newTask(flowId, nodeKey); + } else { + task = processEngine.loadTask(taskId); + } + + return task; + } + + // @ApiOperation(value = "读取界面结构") + public R load(UserSession us, @SwBody LoadTaskVO loadTaskVO) { +// String form = "{\"form\": [{\"component\":\"fy-text\",\"props\":{\"name\":\"用户名\",\"label\":\"用户名\",\"placeholder\":\"用户名\",\"rules\":[{\"required\":true,\"message\":\"请填写用户名\"}]}},{\"component\":\"fy-text\",\"props\":{\"type\":\"password\",\"name\":\"密码\",\"label\":\"密码\",\"placeholder\":\"密码\",\"rules\":[{\"required\":true,\"message\":\"请填写密码\"}]}}]}"; + + Task task = loadTask(loadTaskVO.getFlowId(), loadTaskVO.getTaskId(), null); + if (task == null) { + return R.error("创建任务失败"); + } + + LoadTaskAckVO data = loadForm(task); + + if (data != null) { + Map formData = data.getData(); + ProcessInstance processInstance = task.getProcessInstance(); + if (task.getCreateAt() == 0L) { + formData.put(SaveTaskVO.FLOW_ID, processInstance.getFlow().getId()); + } else { + formData.put(SaveTaskVO.TASK_ID, task.getId()); + } + + FlowNode curNode = processInstance.getCurNode(); + formData.put(SaveTaskVO.FLOW_NODE_ID, curNode.getId()); + formData.put("bpm_flowNodeLabel", curNode.getLabel()); + + return R.success(data); + } + + return R.error("读取表单数据出错"); + } + + // @ApiOperation(value = "保存表单数据") + public R save(UserSession us, @SwBody SaveTaskVO saveTaskVO) { + long taskId = saveTaskVO.readTaskId(); + Task task = this.loadTask(saveTaskVO.readFlowId(), taskId, saveTaskVO.readFlowNodeId()); + + if (task == null) { + return R.error("加载任务失败"); + } + + task.setAssigneeUserId(us.getUserId()); + + dbEngine.doTrans(() -> { + processEngine.saveTask(task); + + // 保存数据到表 + saveForm(task, saveTaskVO, us); + + return true; + }); + + Map data = new HashMap<>(8); + data.put(SaveTaskVO.FLOW_ID, task.getProcessInstance().getFlow().getId()); + data.put(SaveTaskVO.TASK_ID, task.getId()); + data.put(SaveTaskVO.TASK_LOCK_REVID, task.getLockRev()); + data.put(SaveTaskVO.FLOW_ACTION_ID, saveTaskVO.readFlowActionId()); + return R.success(data); + } + + // @ApiOperation(value = "有选人情况下的提交") + public R commit(UserSession us, @SwBody CommitTaskVO commitTaskVO) { + if (commitTaskVO.getUsers() == null || commitTaskVO.getUsers().size() == 0) { + return R.error("执行人数据为空"); + } + + Task task = processEngine.loadTask(commitTaskVO.getTaskId()); + FlowAction flowAction = task.getProcessInstance().getCurNode().checkAndGetAction(commitTaskVO.getActionKey()); + + processEngine.commitTask(task, flowAction, commitTaskVO.getUsers()); + + return R.success(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/dao/TaskDao.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/dao/TaskDao.java new file mode 100644 index 0000000..6f0936f --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/dao/TaskDao.java @@ -0,0 +1,353 @@ +package cc.smtweb.system.bpm.engine.process.impl.dao; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.cache.ModelDatabaseCache; +import cc.smtweb.framework.core.db.cache.ModelTableCache; +import cc.smtweb.framework.core.db.sqlbuilder.InsertSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.UpdateSqlBuilder; +import cc.smtweb.framework.core.util.DateUtil; +import cc.smtweb.system.bpm.core.exception.BpmObjectNotFoundException; +import cc.smtweb.system.bpm.core.exception.BpmLockRevException; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.system.bpm.engine.process.impl.entity.ProcessLogPO; +import cc.smtweb.system.bpm.engine.process.impl.entity.ProcessPO; +import cc.smtweb.system.bpm.engine.process.impl.entity.TaskPO; +import cc.smtweb.system.bpm.engine.process.impl.entity.VariablePO; +import cc.smtweb.system.bpm.engine.process.runtime.ProcessInstance; +import cc.smtweb.system.bpm.engine.process.task.Task; +import cc.smtweb.system.bpm.engine.process.vo.Flow; +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import cc.smtweb.system.bpm.spring.cache.BpmFlowCache; +import org.apache.commons.lang3.StringUtils; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +public class TaskDao { + private DbEngine dbEngine; + + public TaskDao(DbEngine dbEngine) { + this.dbEngine = dbEngine; + } + + public void saveProcess(ProcessInstance processInstance, Timestamp now) { + String route = processInstance.postRoute(); + long lastTime = DateUtil.nowDateTimeLong(); + + if (processInstance.getCreateAt() == 0L) { + Flow flow = processInstance.getFlow(); + + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert("sw_bpm.bpm_process"); + sqlBuilder.add("process_id", processInstance.getId()) + .add("process_parent_id", processInstance.getParentId()) + .add("process_root_id", processInstance.getRootId()) + .add("process_flow_id", flow.getId()) + .add("process_business_key", processInstance.getBusinessKey()) + .add("process_name", processInstance.getName()) + .add("process_node_key", processInstance.getCurNode().getId()) + .add("process_priority", processInstance.getPriority()) + .add("process_active", 1) + .add("process_route", route) + .add("process_is_concurrent", processInstance.isConcurrent() ? 1 : 0) + .add("process_create_time", lastTime) + .add("process_last_time", lastTime); + sqlBuilder.update(dbEngine); + + processInstance.setCreateAt(DateUtil.nowDateTimeLong()); + } else { + UpdateSqlBuilder sqlBuilder = SqlBuilder.createUpdate("sw_bpm.bpm_process"); + sqlBuilder.add("process_business_key", processInstance.getBusinessKey()) + .add("process_node_key", processInstance.getCurNode().getId()) + .add("process_priority", processInstance.getPriority()) + .add("process_route", route) + .add("process_lock_rev", processInstance.getLockRev() + 1) + .add("process_last_time", lastTime) + .addWhere("process_id", processInstance.getId()) + .addWhere("process_parent_id", processInstance.getLockRev()); + int ret = sqlBuilder.update(dbEngine); + + if (ret == 0) { + throw new BpmLockRevException(); + } + + processInstance.incLockRev(); + } + + processInstance.setUpdateAt(DateUtil.nowDateTimeLong()); + } + + public void saveTask(Task task, Timestamp now) { + if (task.getCreateAt() == 0L) { + ProcessInstance processInstance = task.getProcessInstance(); + + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert("sw_bpm.bpm_task"); + sqlBuilder.add("task_id", task.getId()) + .add("task_parent_id", task.getParentId()) + .add("task_process_id", processInstance.getId()) + .add("task_process_root_id", processInstance.getRootId()) + .add("task_ui_id", task.getUiId()) + .add("task_assignee_uid", task.getAssigneeUserId()) + .add("task_name", task.getName()) + .add("task_priority", task.getPriority()); + sqlBuilder.update(dbEngine); + } else { + UpdateSqlBuilder sqlBuilder = SqlBuilder.createUpdate("sw_bpm.bpm_task"); + sqlBuilder.add("task_assignee_uid", task.getAssigneeUserId()) + .add("task_name", task.getName()) + .add("task_priority", task.getPriority()) + .add("task_lock_rev", task.getLockRev() + 1) + .add("task_last_time", DateUtil.nowDateTimeLong()) + .addWhere("task_id", task.getId()) + .addWhere("task_lock_rev", task.getLockRev()); + int ret = sqlBuilder.update(dbEngine); + + if (ret == 0) { + throw new BpmLockRevException(); + } + + task.incLockRev(); + } + + task.setUpdateAt(DateUtil.nowDateTimeLong()); + } + + public void suspensionTask(Task task) { + // 修改挂起状态 + dbEngine.update("update sw_bpm.bpm_task set task_is_suspension=1 where task_id=?", task.getId()); + } + + public void deleteProcess(ProcessInstance processInstance) { + String route = processInstance.postRoute(); + // 添加日志 + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert("sw_bpm.bpm_process_log"); + sqlBuilder.add("pl_id", processInstance.getId()) + .add("pl_parent_id", processInstance.getParentId()) + .add("pl_root_id", processInstance.getRootId()) + .add("pl_flow_id", processInstance.getFlow().getId()) + .add("pl_business_key", processInstance.getBusinessKey()) + .add("pl_name", processInstance.getName()) + .add("pl_delete_reason", 1) + .add("pl_priority", processInstance.getPriority()) + .add("pl_is_concurrent", processInstance.isConcurrent() ? 1 : 0) + .add("pl_route", route) + .add("pl_begin_time", processInstance.getCreateAt()) + .add("pl_end_time", processInstance.getUpdateAt()) + .add("pl_duration", tsDur(processInstance.getCreateAt(), processInstance.getUpdateAt())); + sqlBuilder.update(dbEngine); + + // 删除运行时数据 + dbEngine.update("delete from sw_bpm.bpm_process where process_id=?", processInstance.getId()); + } + + public void deleteTask(Task task) { + if (task.getCreateAt() > 0L) { + // 添加日志 + ProcessInstance process = task.getProcessInstance(); + + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert("sw_bpm.bpm_task_log"); + sqlBuilder.add("tl_id", task.getId()) + .add("tl_parent_id", task.getParentId()) + .add("tl_process_id", process.getId()) + .add("tl_process_root_id", process.getRootId()) + .add("tl_ui_id", task.getUiId()) + .add("tl_assignee_uid", task.getAssigneeUserId()) + .add("tl_owner_uid", task.getOwnerUserId()) + .add("tl_delegation_status", 0) + .add("tl_name", task.getName()) + .add("tl_delete_reason", 1) + .add("tl_node_key", task.getNodeKey()) + .add("tl_priority", task.getPriority()) + .add("tl_is_suspension", task.getIsSuspension()) + .add("tl_begin_time", task.getCreateAt()) + .add("tl_end_time", task.getUpdateAt()) + .add("tl_duration", tsDur(task.getCreateAt(), task.getUpdateAt())); + sqlBuilder.update(dbEngine); + + // 删除数据 + dbEngine.update("delete from sw_bpm.bpm_task where task_id=?", task.getId()); + // 避免并行节点重复删除 + task.setCreateAt(0L); + } + } + + private long tsDur(long begin, long end) { + long diff = (end - begin);// / 1000; + + long result = 0; + if (diff > 0) { + // sec + result = diff % 60; + diff /= 60; + // min + result += (diff % 60) * 100; + diff /= 60; + // hour + result += (diff % 24) * 10000; + // day + result += diff / 24; + } + + return result; + } + + public Task loadTask(long taskId) { + TaskPO taskPO = dbEngine.queryEntity("select task_id,task_parent_id,task_process_id,task_process_root_id,task_ui_id,task_assignee_uid,task_name,task_priority,task_lock_rev,task_create_time,task_last_time from sw_bpm.bpm_task where task_id=?", TaskPO.class, taskId); + + if (taskPO == null) { + throw new BpmObjectNotFoundException("未找到任务, task_id=" + taskId); + } + + Task task = new Task(dbEngine.nextId(), loadProcess(taskPO.getTaskProcessId())); + task.setId(taskId); + task.setAssigneeUserId(taskPO.getTaskAssigneeUid()); + task.setLockRev(taskPO.getTaskLockRev()); + task.setName(taskPO.getTaskName()); + task.setParentId(taskPO.getTaskParentId()); + task.setPriority(taskPO.getTaskPriority()); + task.setCreateAt(taskPO.getTaskCreateTime()); + task.setUpdateAt(taskPO.getTaskLastTime()); + + return task; + } + + public List loadProcessList(List ids) { + List processPos = dbEngine.query("select process_id,process_parent_id,process_root_id,process_flow_id,process_business_key,process_node_key,process_priority,process_active,process_is_concurrent,process_lock_rev,process_create_time,process_last_time,process_route from sw_bpm.bpm_process where process_id in(" + + StringUtils.join(ids, ",") + ")", ProcessPO.class); + + if (processPos != null) { + List result = new ArrayList<>(); + for (ProcessPO processPo : processPos) { + result.add(buildProcessInstance(processPo)); + } + + return result; + } + + return null; + } + + public ProcessInstance loadProcess(long processId) { + ProcessPO processPo = dbEngine.queryEntity("select process_id,process_parent_id,process_root_id,process_flow_id,process_business_key,process_node_key,process_priority,process_active,process_is_concurrent,process_lock_rev,process_create_time,process_last_time,process_route from sw_bpm.bpm_process where process_id=?", + ProcessPO.class, processId); + + if (processPo == null) { + throw new BpmObjectNotFoundException("未找到流程实例, process_id=" + processId); + } + + return buildProcessInstance(processPo); + } + + private ProcessInstance buildProcessInstance(ProcessPO processPo) { + ProcessInstance processInstance = startProcessInstanceByKey(processPo.getProcessFlowId(), processPo.getProcessNodeKey()); + processInstance.setId(processPo.getProcessId()); + processInstance.setRootId(processPo.getProcessRootId()); + processInstance.setLockRev(processPo.getProcessLockRev()); + processInstance.setBusinessKey(processPo.getProcessBusinessKey()); + processInstance.setPriority(processPo.getProcessPriority()); + processInstance.setParentId(processPo.getProcessParentId()); + processInstance.fillRoute(processPo.getProcessRoute()); + processInstance.setCreateAt(processPo.getProcessCreateAt()); + processInstance.setUpdateAt(processPo.getProcessUpdateAt()); + processInstance.setConcurrent(processPo.getProcessIsConcurrent() != 0); + return processInstance; + } + + public ProcessInstance loadProcessByLog(Long processId) { + ProcessLogPO processPO = dbEngine.queryEntity("select pl_id,pl_parent_id,pl_root_id,pl_flow_id,pl_business_key,pl_node_key,pl_priority,pl_is_concurrent,pl_create_time,pl_route from sw_bpm.bpm_process_log where pl_id=?", + ProcessLogPO.class, processId); + + if (processPO == null) { + throw new BpmObjectNotFoundException("未找到流程实例, process_id=" + processId); + } + + ProcessInstance processInstance = startProcessInstanceByKey(processPO.getPlFlowId(), null); + processInstance.setId(processPO.getPlId()); + processInstance.setRootId(processPO.getPlRootId()); + processInstance.setBusinessKey(processPO.getPlBusinessKey()); + processInstance.setPriority(processPO.getPlPriority()); + processInstance.setParentId(processPO.getPlParentId()); + processInstance.fillRoute(processPO.getPlRoute()); + processInstance.setCreateAt(processPO.getPlCreateAt()); + processInstance.setUpdateAt(processPO.getPlCreateAt()); + processInstance.setConcurrent(processPO.getPlIsConcurrent() != 0); + return processInstance; + } + + + public SwMap loadVars(ProcessInstance processInstance) { + // 读取流程变量 + List list = dbEngine.query("select var_id,var_name,var_type,var_scope,var_value from bpm_variable where (var_root_process_id=? and var_scope=1) or (var_process_id=? and var_scope=2)", + VariablePO.class, processInstance.getRootId(), processInstance.getId()); + + if (list != null) { + SwMap vars = new SwMap(list.size()); + for (VariablePO variablePo : list) { + String name = variablePo.getVarName(); + String value = variablePo.getVarValue(); + + if (StringUtils.isNotBlank(value)) { + switch (variablePo.getType()) { + case VariablePO.TYPE_CHAR: + vars.put(name, value); + break; + case VariablePO.TYPE_INT: + vars.put(name, Long.parseLong(value)); + break; + case VariablePO.TYPE_FLOAT: + vars.put(name, Double.parseDouble(value)); + break; + case VariablePO.TYPE_BOOL: + vars.put(name, Boolean.parseBoolean(value)); + break; +// case VariablePO.TYPE_DATE: +// vars.put(name, Boolean.parseBoolean(value)); +// break; + default: + break; + } + } else if (variablePo.getType() == VariablePO.TYPE_CHAR) { + vars.put(name, ""); + } + } + + return vars; + } + + return null; + } + + public ProcessInstance startProcessInstanceByKey(long flowId, String flowNodeKey) { + // 读取流程(新建流程) + Flow flow = BpmFlowCache.getInstance().get(flowId); + if (flow == null) { + throw new BpmObjectNotFoundException("读取流程失败, model_id=" + flowId); + } + + // 读取关联表 + ModelTable table = ModelTableCache.getInstance().get(flow.getTable().getId()); + if (table == null) { + throw new BpmObjectNotFoundException("读取表失败, model_id=" + flow.getTable().getModelId() + ", table=" + flow.getTable().getName()); + } + + ProcessInstance processInstance = new ProcessInstance(dbEngine.nextId(), flow, table); + FlowNode flowNode; + + if (StringUtils.isNotBlank(flowNodeKey)) { + flowNode = flow.findNode(flowNodeKey); + } else { + flowNode = flow.getBeginNode(); + } + + if (flowNode == null) { + throw new BpmObjectNotFoundException("未找到流程节点, model_id=" + flowId + ", nodeId=" + flowNodeKey); + } + + processInstance.setCurNode(flowNode, false); + + return processInstance; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/CommitTaskUserVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/CommitTaskUserVO.java new file mode 100644 index 0000000..796bcce --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/CommitTaskUserVO.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import lombok.Data; + +@Data +public class CommitTaskUserVO { + String nodeKey; + Long userId; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/CommitTaskVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/CommitTaskVO.java new file mode 100644 index 0000000..ac75457 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/CommitTaskVO.java @@ -0,0 +1,17 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import lombok.Data; + +import java.util.List; + +/** + * + * @author xkliu + */ +@Data +public class CommitTaskVO { + private long taskId; + private int taskLockRev; + private String actionKey; + private List users; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/LoadTaskAckVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/LoadTaskAckVO.java new file mode 100644 index 0000000..773c806 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/LoadTaskAckVO.java @@ -0,0 +1,14 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import cc.smtweb.framework.core.SwMap; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class LoadTaskAckVO { + private SwMap data = new SwMap(); + private List form = new ArrayList<>(); + private List action = new ArrayList<>(); +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/LoadTaskVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/LoadTaskVO.java new file mode 100644 index 0000000..bddc042 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/LoadTaskVO.java @@ -0,0 +1,15 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import lombok.Data; + +/** + * + * @author xkliu + */ +@Data +public class LoadTaskVO { + private long flowId; + private long taskId; + private String actionKey; + private String toNodeKey; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/NodeUserVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/NodeUserVO.java new file mode 100644 index 0000000..2e4e397 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/NodeUserVO.java @@ -0,0 +1,24 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import cc.smtweb.system.bpm.web.task.entity.UserPO; +import lombok.Getter; + +import java.util.List; + +/** + * + * @author xkliu + */ +@Getter +public class NodeUserVO { + private String label; + private String nodeKey; + private List users; + + public NodeUserVO(FlowNode flowNode, List users) { + this.users = users; + this.label =flowNode.getLabel(); + this.nodeKey = flowNode.getId(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/NodeUsersVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/NodeUsersVO.java new file mode 100644 index 0000000..214d3b1 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/NodeUsersVO.java @@ -0,0 +1,30 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import cc.smtweb.system.bpm.web.task.entity.UserPO; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class NodeUsersVO { + private String actionKey; + private String nodeKey; + private List nodes = new ArrayList<>(); + + public void add(NodeUserVO nodeUserVO) { + nodes.add(nodeUserVO); + } + + public NodeUserVO add(FlowNode flowNode) { + return add(flowNode, null); + } + + public NodeUserVO add(FlowNode flowNode, List users) { + NodeUserVO result = new NodeUserVO(flowNode, users); + nodes.add(result); + + return result; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/ProcessLogPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/ProcessLogPO.java new file mode 100644 index 0000000..32f6427 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/ProcessLogPO.java @@ -0,0 +1,20 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import lombok.Data; + +import java.sql.Timestamp; + +@Data +public class ProcessLogPO { + private Long plId; + private Long plParentId; + private Long plRootId; + private Long plFlowId; + private Long plBusinessKey; + private byte plPriority; + private byte plActive; + private byte plIsConcurrent; + private String plRoute; + private long plCreateAt; + private long plUpdateAt; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/ProcessPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/ProcessPO.java new file mode 100644 index 0000000..c8231ad --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/ProcessPO.java @@ -0,0 +1,22 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import lombok.Data; + +import java.sql.Timestamp; + +@Data +public class ProcessPO { + private Long processId; + private Long processParentId; + private Long processRootId; + private Long processFlowId; + private Long processBusinessKey; + private String processNodeKey; + private byte processPriority; + private byte processActive; + private byte processIsConcurrent; + private int processLockRev; + private String processRoute; + private long processCreateAt; + private long processUpdateAt; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/SaveTaskVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/SaveTaskVO.java new file mode 100644 index 0000000..450726a --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/SaveTaskVO.java @@ -0,0 +1,30 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import cc.smtweb.framework.core.SwMap; + +public class SaveTaskVO extends SwMap { + public static final String TASK_ID = "bpm_taskId"; + public static final String FLOW_ID = "bpm_flowId"; + public static final String FLOW_NODE_ID = "bpm_flowNodeId"; + public static final String FLOW_ACTION_ID = "bpm_flowActionId"; + public static final String TASK_LOCK_REVID = "bpm_taskLockRev"; + /** 选人节点 */ + public static final String NEXT_NODE_ID = "bpm_nextNodeId"; + + public long readTaskId() { + return readLong(TASK_ID, 0L); + } + + public long readFlowId() { + return readLong(FLOW_ID, 0L); + } + + public String readFlowNodeId() { + return readString(FLOW_NODE_ID); + } + + public String readFlowActionId() { + return readString(FLOW_ACTION_ID); + } + +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/TaskPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/TaskPO.java new file mode 100644 index 0000000..5263870 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/TaskPO.java @@ -0,0 +1,19 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import lombok.Data; + +@Data +public class TaskPO { + private Long taskId; + private Long taskParentId; + private Long taskProcessId; + private Long taskProcessRootId; + private Long taskUiId; + private Long taskAssigneeUid; + private String taskName; + private byte taskPriority; + private int taskLockRev; + private Long taskCreateTime; + private Long taskLastTime; + private String taskNodeKey; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/VariablePO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/VariablePO.java new file mode 100644 index 0000000..386218a --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/impl/entity/VariablePO.java @@ -0,0 +1,25 @@ +package cc.smtweb.system.bpm.engine.process.impl.entity; + +import lombok.Data; + +@Data +public class VariablePO { + // 字符 + public static final byte TYPE_CHAR = 1; + // 整型 + public static final byte TYPE_INT = 2; + // 时间 + public static final byte TYPE_DATE = 3; + // 浮点 + public static final byte TYPE_FLOAT = 4; + // 布尔 + public static final byte TYPE_BOOL = 5; + + private Long varId; + private Long processId; + private Long processRootId; + private String varName; + private String varValue; + private byte scope; + private byte type; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/FlowLoader.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/FlowLoader.java new file mode 100644 index 0000000..7b8a0cf --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/FlowLoader.java @@ -0,0 +1,263 @@ +package cc.smtweb.system.bpm.engine.process.loader; + +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.system.bpm.core.exception.ModelLoaderError; +import cc.smtweb.system.bpm.engine.AbstractLoader; +import cc.smtweb.system.bpm.engine.process.loader.vo.*; +import cc.smtweb.system.bpm.engine.process.vo.*; +import org.apache.commons.lang3.StringUtils; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author xkliu + * 流程定义读取器 + */ +public class FlowLoader extends AbstractLoader { + private static final Map PERM_MAP = new HashMap<>(); + private static final Map ACTION_TYPE_MAP = new HashMap<>(); + private static final Map USER_TYPE_MAP = new HashMap<>(); + private static final Map USER_FILTER_MAP = new HashMap<>(); + + static { + PERM_MAP.put(NodeFieldVO.PERM_READ, FlowNodeField.PERM_READ); + PERM_MAP.put(NodeFieldVO.PERM_WRITE, FlowNodeField.PERM_WRITE); + PERM_MAP.put(NodeFieldVO.PERM_DISABLE, FlowNodeField.PERM_DISABLE); + PERM_MAP.put(NodeFieldVO.PERM_HIDE, FlowNodeField.PERM_HIDE); + + ACTION_TYPE_MAP.put(ActionVO.TYPE_COMMIT, FlowAction.TYPE_COMMIT); + ACTION_TYPE_MAP.put(ActionVO.TYPE_SAVE, FlowAction.TYPE_SAVE); + ACTION_TYPE_MAP.put(ActionVO.TYPE_RETURN, FlowAction.TYPE_RETURN); + ACTION_TYPE_MAP.put(ActionVO.TYPE_TRANSMIT, FlowAction.TYPE_TRANSMIT); + ACTION_TYPE_MAP.put(ActionVO.TYPE_FINISH, FlowAction.TYPE_FINISH); + ACTION_TYPE_MAP.put(ActionVO.TYPE_REJECT, FlowAction.TYPE_REJECT); + + USER_TYPE_MAP.put(NodeUsersVO.TYPE_ALL, FlowNodeUser.TYPE_ALL); + USER_TYPE_MAP.put(NodeUsersVO.TYPE_ROLE, FlowNodeUser.TYPE_ROLE); + USER_TYPE_MAP.put(NodeUsersVO.TYPE_USER_GROUP, FlowNodeUser.TYPE_USER_GROUP); + USER_TYPE_MAP.put(NodeUsersVO.TYPE_USER, FlowNodeUser.TYPE_USER); + USER_TYPE_MAP.put(NodeUsersVO.TYPE_DEPT, FlowNodeUser.TYPE_DEPT); + USER_TYPE_MAP.put(NodeUsersVO.TYPE_FIELD, FlowNodeUser.TYPE_FIELD); + USER_TYPE_MAP.put(NodeUsersVO.TYPE_EXP, FlowNodeUser.TYPE_EXP); + USER_TYPE_MAP.put(NodeUsersVO.TYPE_API, FlowNodeUser.TYPE_API); + + USER_FILTER_MAP.put(NodeUsersVO.FILTER_CUR_DEPT, FlowNodeUser.FILTER_CUR_DEPT); + USER_FILTER_MAP.put(NodeUsersVO.FILTER_SUB_DEPT, FlowNodeUser.FILTER_SUB_DEPT); + USER_FILTER_MAP.put(NodeUsersVO.FILTER_UP_DEPT, FlowNodeUser.FILTER_UP_DEPT); + USER_FILTER_MAP.put(NodeUsersVO.FILTER_CUR_USER, FlowNodeUser.FILTER_CUR_USER); + USER_FILTER_MAP.put(NodeUsersVO.FILTER_CUR_MANAGER, FlowNodeUser.FILTER_CUR_MANAGER); + USER_FILTER_MAP.put(NodeUsersVO.FILTER_UP_MANAGER, FlowNodeUser.FILTER_UP_MANAGER); + } + + @Override + public Flow load(String text) { + clearErrors(); + + if (StringUtils.isBlank(text)) { + addError(ModelLoaderError.ERROR_FLOW, "流程为空"); + return null; + } + + FlowVO flowVO = JsonUtil.parse(text, FlowVO.class); + + return convert(flowVO); + } + + public Flow load(InputStream is) { + clearErrors(); + + if (is == null) { + addError(ModelLoaderError.ERROR_FLOW, "流程为空"); + return null; + } + + FlowVO flowVO = JsonUtil.parse(is, FlowVO.class); + + return convert(flowVO); + } + + private Flow convert(FlowVO flowVO) { + Flow result = new Flow(); + + FlowPropsVO flowProps = flowVO.getProps(); + if (flowProps == null) { + addError(ModelLoaderError.ERROR_FLOW, "流程缺少属性"); + return null; + } + + if (flowProps.getDm() == null) { + addError(ModelLoaderError.ERROR_FLOW, "流程缺少关联库"); + return null; + } + + FlowTableVO flowTableVO = flowProps.getTable(); + if (flowTableVO == null) { + addError(ModelLoaderError.ERROR_FLOW, "流程缺少关联表"); + return null; + } + + FlowTable flowTable = new FlowTable(); + flowTable.setModelId(flowProps.getDm().getModelId()); + flowTable.setId(flowTableVO.getId()); + flowTable.setName(flowTableVO.getName()); + result.setTable(flowTable); + + List nodes = flowVO.getNodes(); + + if (nodes == null) { + addError(ModelLoaderError.ERROR_FLOW, "流程缺少活动节点"); + return null; + } + + Map nodeMap = new HashMap<>(nodes.size()); + FlowNode beginFlowNode = null; + FlowNode endFlowNode = null; + + for (NodeVO nodeVO: nodes) { + FlowNode flowNode = new FlowNode(); + flowNode.setId(nodeVO.getId()); + + NodePropsVO nodePropsVO = nodeVO.getProps(); + + if (nodePropsVO != null) { + // 读取节点基本属性 + flowNode.setLabel(nodePropsVO.getLabel()); + flowNode.setName(nodePropsVO.getName()); + flowNode.setDisableNotify(nodePropsVO.getDisableNotify()); + + switch (nodeVO.getType()) { + case NodeVO.TYPE_BEGIN: + flowNode.setType(FlowNode.TYPE_BEGIN); + if (beginFlowNode == null) { + beginFlowNode = flowNode; + } else { + addError(ModelLoaderError.ERROR_NODE, nodeVO.getId(), "开始节点重复:" + beginFlowNode.getId()); + } + break; + case NodeVO.TYPE_END: + flowNode.setType(FlowNode.TYPE_END); + if (endFlowNode == null) { + endFlowNode = flowNode; + } else { + addError(ModelLoaderError.ERROR_NODE, nodeVO.getId(), "结束节点重复:" + endFlowNode.getId()); + } + break; + case NodeVO.TYPE_MANUAL: + flowNode.setType(FlowNode.TYPE_MANUAL); + break; + case NodeVO.TYPE_MULTI_SIGN: + flowNode.setType(FlowNode.TYPE_MULTI_SIGN); + break; + case NodeVO.TYPE_CONDITION: + flowNode.setType(FlowNode.TYPE_CONDITION); + break; + case NodeVO.TYPE_SPLIT: + flowNode.setType(FlowNode.TYPE_SPLIT); + break; + case NodeVO.TYPE_MERGE: + flowNode.setType(FlowNode.TYPE_MERGE); + break; + case NodeVO.TYPE_SUB_FLOW: + flowNode.setType(FlowNode.TYPE_SUB_FLOW); + break; + case NodeVO.TYPE_AUTO: + flowNode.setType(FlowNode.TYPE_AUTO); + break; + default: + addError(ModelLoaderError.ERROR_NODE, nodeVO.getId(), "未支持的活动节点类型:" + nodeVO.getType()); + break; + } + + // 读取节点字段权限 + List nodeFields = nodePropsVO.getFields(); + if (nodeFields != null) { + List fields = new ArrayList<>(); + + for (NodeFieldVO nodeFieldVO: nodeFields) { + FlowNodeField field = new FlowNodeField(); + field.setId(nodeFieldVO.getId()); + field.setLabel(nodeFieldVO.getLabel()); + field.setPerm(mapValue(PERM_MAP, nodeFieldVO.getPerm(), FlowNodeField.PERM_READ)); + field.setNotNull(nodeFieldVO.isNotNull()); + field.setDefaultValue(nodeFieldVO.getAssignValue()); + + fields.add(field); + } + + flowNode.setFields(fields); + } + + // 读取动作和动作跳转 + List actionVOs = nodeVO.getColumns(); + if (actionVOs != null) { + List flowActions = new ArrayList<>(); + for (ActionVO actionVO: actionVOs) { + FlowAction flowAction = new FlowAction(); + + flowAction.setId(actionVO.getId()); + flowAction.setType(mapValue(ACTION_TYPE_MAP, actionVO.getType())); + + ActionPropsVO actionPropsVO = actionVO.getProps(); + if (actionPropsVO != null) { + flowAction.setLabel(actionPropsVO.getLabel()); + } + + flowActions.add(flowAction); + + List connectionVOS = actionVO.getConnections(); + if (connectionVOS != null && connectionVOS.size() > 0) { + ConnectionVO connectionVO = connectionVOS.get(0); + + FlowConnection flowConnection = new FlowConnection(); + flowConnection.setToNodeId(connectionVO.getTo()); + + flowAction.setConnection(flowConnection); + } + } + + flowNode.setActions(flowActions); + } else { + if (flowNode.getType() != FlowNode.TYPE_END) { + addError(ModelLoaderError.ERROR_NODE, nodeVO.getId(), "活动节点缺少动作"); + } + } + + // 执行对象 + List nodeUsersVos = nodePropsVO.getUsers(); + if (nodeUsersVos != null) { + List flowNodeUsers = new ArrayList<>(); + for (NodeUsersVO nodeUsersVo: nodeUsersVos) { + FlowNodeUser flowNodeUser = new FlowNodeUser(); + flowNodeUser.setType(mapValue(USER_TYPE_MAP, nodeUsersVo.getType())); + flowNodeUser.setFilter(mapValue(USER_FILTER_MAP, nodeUsersVo.getFilter())); + + NodeUserTargetVO target = nodeUsersVo.getTarget(); + if (target != null && target.getUgId() != null) { + flowNodeUser.setTarget(target.getUgId() + ""); + } + + flowNodeUsers.add(flowNodeUser); + } + + flowNode.setUsers(flowNodeUsers); + } + } else { + addError(ModelLoaderError.ERROR_NODE, nodeVO.getId(), "活动节点缺少属性"); + } + + if (nodeMap.putIfAbsent(nodeVO.getId(), flowNode) != null) { + addError(ModelLoaderError.ERROR_NODE, nodeVO.getId(), "活动节点ID重复:" + nodeVO.getId()); + } + } + + // TODO 检查节点的连通性,死循环 + result.setNodeMap(nodeMap); + result.setBeginNode(beginFlowNode); + + return result; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/ActionPropsVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/ActionPropsVO.java new file mode 100644 index 0000000..2559508 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/ActionPropsVO.java @@ -0,0 +1,8 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +@Data +public class ActionPropsVO { + private String label; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/ActionVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/ActionVO.java new file mode 100644 index 0000000..71189e9 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/ActionVO.java @@ -0,0 +1,20 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class ActionVO { + public static final String TYPE_COMMIT = "commitAction"; + public static final String TYPE_SAVE = "saveAction"; + public static final String TYPE_RETURN = "returnAction"; + public static final String TYPE_TRANSMIT = "transmitAction"; + public static final String TYPE_FINISH = "finishAction"; + public static final String TYPE_REJECT = "rejectAction"; + + private String id; + private String type; + private List connections; + private ActionPropsVO props; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/ConnectionVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/ConnectionVO.java new file mode 100644 index 0000000..e228210 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/ConnectionVO.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +@Data +public class ConnectionVO { + private String id; + private String to; + private String type; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowDatabaseVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowDatabaseVO.java new file mode 100644 index 0000000..5a0370d --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowDatabaseVO.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +@Data +public class FlowDatabaseVO { + private long modelId; + private String modelName; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowPropsVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowPropsVO.java new file mode 100644 index 0000000..d8fd2ae --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowPropsVO.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +@Data +public class FlowPropsVO { + private FlowDatabaseVO dm; + private FlowTableVO table; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowTableVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowTableVO.java new file mode 100644 index 0000000..5c8a409 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowTableVO.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +@Data +public class FlowTableVO { + private String id; + private String label; + private String name; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowVO.java new file mode 100644 index 0000000..e8db866 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/FlowVO.java @@ -0,0 +1,15 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +import java.util.List; + +/** + * @author xkliu + */ +@Data +public class FlowVO { + private String id; + private List nodes; + private FlowPropsVO props; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeFieldVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeFieldVO.java new file mode 100644 index 0000000..1633989 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeFieldVO.java @@ -0,0 +1,18 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +@Data +public class NodeFieldVO { + public static final String PERM_READ = "read"; + public static final String PERM_WRITE = "write"; + public static final String PERM_HIDE = "hide"; + public static final String PERM_DISABLE = "disable"; + + private String id; + private String name; + private String label; + private String perm; + private boolean notNull; + private String assignValue; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodePropsVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodePropsVO.java new file mode 100644 index 0000000..2c9644b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodePropsVO.java @@ -0,0 +1,21 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class NodePropsVO { + /** 标题 */ + private String label; + /** 英文名称 */ + private String name; + /** 禁止提醒 */ + private String disableNotify; + /** 表单权限 */ + private List fields; + /** 执行人参照 */ + private String userReference; + /** 执行人列表 */ + private List users; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeUserTargetVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeUserTargetVO.java new file mode 100644 index 0000000..046f19c --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeUserTargetVO.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +@Data +public class NodeUserTargetVO { + private Long ugId; + private String ugName; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeUsersVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeUsersVO.java new file mode 100644 index 0000000..8c0e74c --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeUsersVO.java @@ -0,0 +1,44 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +/** 执行人 */ +@Data +public class NodeUsersVO { + /** 所有人 */ + public static final String TYPE_ALL = "all"; + /** 角色 */ + public static final String TYPE_ROLE = "role"; + /** 用户组 */ + public static final String TYPE_USER_GROUP = "userGroup"; + /** 用户 */ + public static final String TYPE_USER = "user"; + /** 部门 */ + public static final String TYPE_DEPT = "dept"; + /** 表单字段 */ + public static final String TYPE_FIELD = "field"; + /** 表达式 */ + public static final String TYPE_EXP = "exp"; + /** API接口 */ + public static final String TYPE_API = "api"; + + /** 本部门 */ + public static final String FILTER_CUR_DEPT = "curDept"; + /** 子部门 */ + public static final String FILTER_SUB_DEPT = "subDept"; + /** 上级部门 */ + public static final String FILTER_UP_DEPT = "upDept"; + /** 本人 */ + public static final String FILTER_CUR_USER = "curUser"; + /** 本部门负责人 */ + public static final String FILTER_CUR_MANAGER = "curManager"; + /** 上级部门负责人 */ + public static final String FILTER_UP_MANAGER = "upManager"; + + /** 类型 */ + private String type; + /** 目标值 */ + private NodeUserTargetVO target; + /** 过滤条件 */ + private String filter; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeVO.java new file mode 100644 index 0000000..7b0c72b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/loader/vo/NodeVO.java @@ -0,0 +1,32 @@ +package cc.smtweb.system.bpm.engine.process.loader.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class NodeVO { + /** 开始 */ + public static final String TYPE_BEGIN = "beginNode"; + /** 结束 */ + public static final String TYPE_END = "endNode"; + /** 任务 */ + public static final String TYPE_MANUAL = "manualNode" ; + /** 会签 */ + public static final String TYPE_MULTI_SIGN = "multSignNode"; + /** 条件 */ + public static final String TYPE_CONDITION = "conditionNode"; + /** 分支 */ + public static final String TYPE_SPLIT = "splitNode"; + /** 聚合 */ + public static final String TYPE_MERGE = "mergeNode"; + /** 子流程 */ + public static final String TYPE_SUB_FLOW = "subFlowNode"; + /** 自动 */ + public static final String TYPE_AUTO = "autoNode"; + + private String id; + private String type; + private List columns; + private NodePropsVO props; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ConditionExpression.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ConditionExpression.java new file mode 100644 index 0000000..15a3d46 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ConditionExpression.java @@ -0,0 +1,26 @@ +package cc.smtweb.system.bpm.engine.process.runtime; + +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * 条件表达式处理器 + * @author kevin + */ +public class ConditionExpression { + private ExpressionParser parser = new SpelExpressionParser(); + private StandardEvaluationContext context; + + public ConditionExpression(ProcessContext rootObject) { + context = new StandardEvaluationContext(rootObject); + } + + public Object execute(String condition) { + return parser.parseExpression(condition).getValue(context); + } + + public T execute(String condition, Class clazz) { + return parser.parseExpression(condition).getValue(context, clazz); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessContext.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessContext.java new file mode 100644 index 0000000..4ab8b9e --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessContext.java @@ -0,0 +1,12 @@ +package cc.smtweb.system.bpm.engine.process.runtime; + +import cc.smtweb.framework.core.SwMap; +import lombok.Data; + +@Data +public class ProcessContext { + /** 流程上下文变量 */ + private SwMap vars; + /** 数据 */ + private SwMap data; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessInfo.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessInfo.java new file mode 100644 index 0000000..978b305 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessInfo.java @@ -0,0 +1,32 @@ +package cc.smtweb.system.bpm.engine.process.runtime; + +import lombok.Data; + +import java.sql.Timestamp; + +@Data +public class ProcessInfo { + protected Long id; + private Long parentId; + private Long rootId; + private Long businessKey; + private String name; + private byte priority; + /** 乐观锁 */ + protected int lockRev; + + private boolean concurrent; + + private long createAt; + + private long updateAt; + + public void assign(ProcessInfo source) { + this.parentId = source.parentId; + this.rootId = source.rootId; + this.businessKey = source.businessKey; + this.name = source.name; + this.priority = source.priority; + } + +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessInstance.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessInstance.java new file mode 100644 index 0000000..60753d5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessInstance.java @@ -0,0 +1,160 @@ +package cc.smtweb.system.bpm.engine.process.runtime; + +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.system.bpm.core.exception.BpmObjectNotFoundException; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.system.bpm.engine.process.vo.Flow; +import cc.smtweb.system.bpm.engine.process.vo.FlowAction; +import cc.smtweb.system.bpm.engine.process.vo.FlowConnection; +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +public class ProcessInstance extends ProcessInfo { + @Getter + private Flow flow; + + @Getter + private ModelTable modelTable; + + @Getter + private FlowNode curNode; + + @Getter @Setter + private ConditionExpression conditionExpression; + + @Getter + private ProcessRoute processRoute = new ProcessRoute(); + + public ProcessInstance(long id, Flow flow, ModelTable modelTable) { + this.id = id; + this.flow = flow; + this.modelTable = modelTable; + } + + /** 添加乐观锁计数,在修改记录成功后调用 */ + public void incLockRev() { + this.lockRev++; + } + + public FlowNode nextNode(int actionType) { + if (curNode != null) { + List actions = curNode.getActions(); + if (actions != null) { + for (FlowAction action: actions) { + if (action.getType() == actionType) { + FlowConnection connection = action.getConnection(); + + if (connection != null) { + String nodeId = connection.getToNodeId(); + return this.flow.findNode(nodeId); + } + + break; + } + } + } + } + + return null; + } + + public FlowAction checkAndGetCurNodeAction(String actionId) { + if (curNode == null) { + throw new BpmObjectNotFoundException("current node is null"); + } + + return curNode.checkAndGetAction(actionId); + } + + public FlowNode nextNode(String actionId) { + if (curNode != null) { + List actions = curNode.getActions(); + if (actions != null) { + for (FlowAction action: actions) { + if (actionId.equals(action.getId())) { + FlowConnection connection = action.getConnection(); + + if (connection != null) { + String nodeId = connection.getToNodeId(); + return this.flow.findNode(nodeId); + } + + break; + } + } + } + } + + return null; + } + + public void setCurNode(FlowNode flowNode, boolean addRoute) { + this.curNode = flowNode; + if (addRoute) { + this.processRoute.setNode(flowNode); + } + } + + public FlowNode nextNode(FlowAction flowAction) { + FlowConnection connection = flowAction.getConnection(); + + if (connection != null) { + String nodeId = connection.getToNodeId(); + return this.flow.findNode(nodeId); + } + + return null; + } + + public ProcessInstance createSubProcess(long id, FlowNode flowNode) { + ProcessInstance result = new ProcessInstance(id, flow, modelTable); + + result.assign(this); + result.setParentId(this.getId()); + result.curNode = flowNode; + result.conditionExpression = conditionExpression; + + // 添加当前路由节点 + this.processRoute.addSubProcess(result); + + return result; + } + + public String postRoute() { + ProcessRouteInfo info = this.processRoute.post(); + + if (info != null) { + return JsonUtil.encodeString(info); + } + + return null; + } + + public void fillRoute(String processRoute) { + if (StringUtils.isNotBlank(processRoute)) { + this.processRoute.setInfo(JsonUtil.parse(processRoute, ProcessRouteInfo.class)); + } + } + + public ProcessInstance createSame(long id) { + ProcessInstance result = new ProcessInstance(id, flow, modelTable); + + result.assign(this); + result.setParentId(this.getId()); + result.conditionExpression = conditionExpression; + result.processRoute = this.processRoute; + + return result; + } + + // 从日志恢复数据,新建流程 + public void resume(long newId) { + this.id = newId; + this.setCreateAt(0L); + this.setUpdateAt(0L); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessRoute.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessRoute.java new file mode 100644 index 0000000..163ed5a --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessRoute.java @@ -0,0 +1,91 @@ +package cc.smtweb.system.bpm.engine.process.runtime; + +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +public class ProcessRoute { + @Getter @Setter + private FlowNode node; + @Getter + private List subList; + + @Getter @Setter + private ProcessRouteInfo info; + + public void addSubProcess(ProcessInstance process) { + if (subList == null) { + subList = new ArrayList<>(); + } + + subList.add(process); + } + + public ProcessRouteInfo post() { + if (info == null) { + info = new ProcessRouteInfo(); + info.setRoutes(new ArrayList<>()); + } + + if (node != null || subList != null) { + ProcessRouteItem item = new ProcessRouteItem(); + item.setNode(node.getId()); + + if (subList != null) { + List list = new ArrayList<>(); + for (ProcessInstance pi: subList) { + list.add(pi.getId()); + } + + item.setSub(list); + subList = null; + } + + node = null; + } + + return info; + } + + public ProcessRouteItem findRouteByProcess(long processId) { + if (info != null) { + List routes = info.getRoutes(); + if (routes != null) { + for (int i = routes.size() - 1; i >= 0; i--) { + ProcessRouteItem item = routes.get(i); + + if (item.findProcess(processId)) { + return item; + } + } + } + } + + return null; + } + + public ProcessRouteItem returnRoute() { + if (info != null) { + List routes = info.getRoutes(); + if (routes != null && routes.size() > 0) { + return routes.remove(routes.size() - 1); + } + } + + return null; + } + + public ProcessRouteItem lastRoute() { + if (info != null) { + List routes = info.getRoutes(); + if (routes != null && routes.size() > 0) { + return routes.get(routes.size() - 1); + } + } + + return null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessRouteInfo.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessRouteInfo.java new file mode 100644 index 0000000..adcb407 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessRouteInfo.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.engine.process.runtime; + +import lombok.Data; + +import java.util.List; + +@Data +public class ProcessRouteInfo { + private List routes; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessRouteItem.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessRouteItem.java new file mode 100644 index 0000000..fe34437 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/ProcessRouteItem.java @@ -0,0 +1,25 @@ +package cc.smtweb.system.bpm.engine.process.runtime; + +import lombok.Data; + +import java.util.List; + +@Data +public class ProcessRouteItem { + /** 节点Key */ + private String node; + /** 子流程ID */ + private List sub; + + public boolean findProcess(long processId) { + if (sub != null) { + for (Long id: sub) { + if (id == processId) { + return true; + } + } + } + + return false; + } +} \ No newline at end of file diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/Route.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/Route.java new file mode 100644 index 0000000..f35468a --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/Route.java @@ -0,0 +1,15 @@ +package cc.smtweb.system.bpm.engine.process.runtime; + +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import lombok.Getter; + +@Getter +public class Route { + private final ProcessInstance process; + private final FlowNode node; + + public Route(ProcessInstance process, FlowNode node) { + this.process = process; + this.node = node; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/RouteInfo.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/RouteInfo.java new file mode 100644 index 0000000..b2ba447 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/RouteInfo.java @@ -0,0 +1,23 @@ +package cc.smtweb.system.bpm.engine.process.runtime; + +import cc.smtweb.system.bpm.core.exception.BpmIlegalArgumentException; +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class RouteInfo { + List routes = new ArrayList<>(); + + public void add(ProcessInstance process, FlowNode node) { + if (routes.size() > 1000) { + // 避免死循环 + throw new BpmIlegalArgumentException("route count out of range"); + } + + process.setCurNode(node, true); + routes.add(new Route(process, node)); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/VariableInstance.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/VariableInstance.java new file mode 100644 index 0000000..1fbec5b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/runtime/VariableInstance.java @@ -0,0 +1,4 @@ +package cc.smtweb.system.bpm.engine.process.runtime; + +public class VariableInstance { +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/task/Task.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/task/Task.java new file mode 100644 index 0000000..267efec --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/task/Task.java @@ -0,0 +1,32 @@ +package cc.smtweb.system.bpm.engine.process.task; + +import cc.smtweb.system.bpm.engine.process.runtime.ProcessInstance; +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import lombok.Getter; + +public class Task extends TaskInfo { + @Getter + private ProcessInstance processInstance; + + /** 需要选人的节点ID */ +// @Getter @Setter +// private String nextNodeKey; + + @Getter + private String nodeKey; + + public Task(long id, ProcessInstance processInstance) { + this.id = id; + this.processInstance = processInstance; + FlowNode curNode = processInstance.getCurNode(); + + if (curNode != null) { + this.setName(curNode.getLabel()); + this.nodeKey = curNode.getId(); + } + } + + public void incLockRev() { + this.lockRev++; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/task/TaskCommitInfo.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/task/TaskCommitInfo.java new file mode 100644 index 0000000..83b1f0a --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/task/TaskCommitInfo.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.engine.process.task; + +import lombok.Data; + +@Data +public class TaskCommitInfo { + private String nodeKey; + private long userId; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/task/TaskInfo.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/task/TaskInfo.java new file mode 100644 index 0000000..f96ca6f --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/task/TaskInfo.java @@ -0,0 +1,30 @@ +package cc.smtweb.system.bpm.engine.process.task; + +import lombok.Data; + +import java.sql.Timestamp; + +@Data +public class TaskInfo { + protected long id; + + private Long uiId; + + private Long parentId; + /** 处理人 */ + private long assigneeUserId; + /** 委托人 */ + private Long ownerUserId; + + private String name; + + private byte priority; + /** 乐观锁 */ + protected int lockRev; + + private int isSuspension; + + private long createAt; + + private long updateAt; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/Flow.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/Flow.java new file mode 100644 index 0000000..d6917cd --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/Flow.java @@ -0,0 +1,29 @@ +package cc.smtweb.system.bpm.engine.process.vo; + +import cc.smtweb.system.bpm.core.exception.BpmObjectNotFoundException; +import lombok.Data; + +import java.io.Serializable; +import java.util.Map; + +@Data +public class Flow implements Serializable { + private Map nodeMap; + private FlowNode beginNode; + private FlowTable table; + private long id; + + public FlowNode findNode(String nodeId) { + return this.nodeMap.get(nodeId); + } + + public FlowNode checkAndGetNode(String nodeKey) { + FlowNode flowNode = findNode(nodeKey); + + if (flowNode == null) { + throw new BpmObjectNotFoundException("not find node by key: " + nodeKey); + } + + return flowNode; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowAction.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowAction.java new file mode 100644 index 0000000..0ec48a7 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowAction.java @@ -0,0 +1,29 @@ +package cc.smtweb.system.bpm.engine.process.vo; + +import cc.smtweb.system.bpm.core.exception.BpmObjectNotFoundException; +import lombok.Data; + +@Data +public class FlowAction { + public static final int TYPE_COMMIT = 1; + public static final int TYPE_SAVE = 2; + public static final int TYPE_RETURN = 3; + public static final int TYPE_TRANSMIT = 4; + public static final int TYPE_FINISH = 5; + public static final int TYPE_REJECT = 6; + + private String id; + private int type; + private String label; + private FlowConnection connection; + /** 条件表达式 */ + private String expression; + + public FlowConnection checkAndGetConnection() { + if (connection == null) { + throw new BpmObjectNotFoundException("未找Action的连接, action_id=" + id); + } + + return connection; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowConnection.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowConnection.java new file mode 100644 index 0000000..d584e61 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowConnection.java @@ -0,0 +1,8 @@ +package cc.smtweb.system.bpm.engine.process.vo; + +import lombok.Data; + +@Data +public class FlowConnection { + private String toNodeId; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowNode.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowNode.java new file mode 100644 index 0000000..284fc55 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowNode.java @@ -0,0 +1,79 @@ +package cc.smtweb.system.bpm.engine.process.vo; + +import cc.smtweb.system.bpm.core.exception.BpmObjectNotFoundException; +import lombok.Data; + +import java.util.List; + +/** + * @author xkliu + * 流程的活动节点对象 + */ +@Data +public class FlowNode { + /** 开始 */ + public static final int TYPE_BEGIN = 10; + /** 结束 */ + public static final int TYPE_END = 20; + /** 任务 */ + public static final int TYPE_MANUAL = 30; + /** 会签 */ + public static final int TYPE_MULTI_SIGN = 40; + /** 条件 */ + public static final int TYPE_CONDITION = 50; + /** 分支 */ + public static final int TYPE_SPLIT = 60; + /** 聚合 */ + public static final int TYPE_MERGE = 70; + /** 子流程 */ + public static final int TYPE_SUB_FLOW = 80; + /** 自动 */ + public static final int TYPE_AUTO = 90; + + /** 活动类型,枚举值参考TYPE_XXX */ + private int type; + + private String id; + /** 标题 */ + private String label; + /** 英文名称 */ + private String name; + /** 禁止提醒 */ + private String disableNotify; + /** 字段权限列表 */ + private List fields; + /** 执行人列表 */ + private List users; + /** 动作列表 */ + private List actions; + + public FlowAction findAction(int typeCommit) { + for (FlowAction flowAction: actions) { + if (flowAction.getType() == typeCommit) { + return flowAction; + } + } + + return null; + } + + public FlowAction checkAndGetAction(String actionKey) { + FlowAction nextFlowAction = findAction(actionKey); + + if (nextFlowAction == null) { + throw new BpmObjectNotFoundException("未找到Action, action_key=" + actionKey); + } + + return nextFlowAction; + } + + private FlowAction findAction(String actionKey) { + for (FlowAction flowAction: actions) { + if (flowAction.getId().equals(actionKey)) { + return flowAction; + } + } + + return null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowNodeField.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowNodeField.java new file mode 100644 index 0000000..904071b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowNodeField.java @@ -0,0 +1,17 @@ +package cc.smtweb.system.bpm.engine.process.vo; + +import lombok.Data; + +@Data +public class FlowNodeField { + public static final int PERM_READ = 1; + public static final int PERM_WRITE = 2; + public static final int PERM_HIDE = 3; + public static final int PERM_DISABLE = 4; + + private String id; + private String label; + private int perm; + private boolean notNull; + private String defaultValue; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowNodeUser.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowNodeUser.java new file mode 100644 index 0000000..fa1af65 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowNodeUser.java @@ -0,0 +1,42 @@ +package cc.smtweb.system.bpm.engine.process.vo; + +import lombok.Data; + +@Data +public class FlowNodeUser { + /** 所有人 */ + public static final int TYPE_ALL = 0; + /** 角色 */ + public static final int TYPE_ROLE = 1; + /** 用户组 */ + public static final int TYPE_USER_GROUP = 2; + /** 用户 */ + public static final int TYPE_USER = 3; + /** 部门 */ + public static final int TYPE_DEPT = 4; + /** 表单字段 */ + public static final int TYPE_FIELD = 5; + /** 表达式 */ + public static final int TYPE_EXP = 6; + /** API接口 */ + public static final int TYPE_API = 7; + + /** 本部门 */ + public static final int FILTER_NONE = 0; + /** 本部门 */ + public static final int FILTER_CUR_DEPT = 1; + /** 子部门 */ + public static final int FILTER_SUB_DEPT = 2; + /** 上级部门 */ + public static final int FILTER_UP_DEPT = 3; + /** 本人 */ + public static final int FILTER_CUR_USER = 4; + /** 本部门负责人 */ + public static final int FILTER_CUR_MANAGER = 5; + /** 上级部门负责人 */ + public static final int FILTER_UP_MANAGER = 6; + + private int type; + private String target; + private int filter; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowTable.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowTable.java new file mode 100644 index 0000000..74daacf --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/process/vo/FlowTable.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.engine.process.vo; + +import lombok.Data; + +@Data +public class FlowTable { + private long modelId; + private String id; + private String name; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/IFormDataStore.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/IFormDataStore.java new file mode 100644 index 0000000..13be368 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/IFormDataStore.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.engine.ui; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.session.UserSession; + +public interface IFormDataStore { + SwMap load(Long processId, Long processRootId); + void save(Long processId, Long processRootId, Long taskId, Long businessKey, String tabelName, SwMap body, UserSession us); +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/AbstractVelocityEngine.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/AbstractVelocityEngine.java new file mode 100644 index 0000000..d277d4b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/AbstractVelocityEngine.java @@ -0,0 +1,184 @@ +package cc.smtweb.system.bpm.engine.ui.codegen; + +import cc.smtweb.framework.core.R; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; + +import java.io.*; + +public abstract class AbstractVelocityEngine { + private static final int DIGEST_SIZE = 9+64+9+17; + private static final int MIN_FILE_SIZE = DIGEST_SIZE + 4; + private static final int MAX_FILE_SIZE = 1024 * 1024 *10; + + private VelocityEngine ve = new VelocityEngine(); + protected VelocityContext ctx; + + public AbstractVelocityEngine() { + ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); + ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName()); + ve.init(); + } + + /** 生成文件,通过签名检查就替换文件 */ + public R buildUseSign(long id, String templateFile, File destFile) throws IOException { + // 载入(获取)模板对象 + Template t = ve.getTemplate(templateFile); +// VelocityContext ctx = getVelocityContext(uiPage, uiPageRef); + File tmpFile = new File(destFile.getAbsoluteFile() + ".tmp"); + + Sha256OutputStream shaOutputStream = new Sha256OutputStream(new FileOutputStream(tmpFile), true); + + try(Writer writer = new OutputStreamWriter(shaOutputStream)) { + t.merge(ctx, writer); + + writer.flush(); + String sha256 = shaOutputStream.sha256Hex(); + int digestSize = shaOutputStream.digestSize(); + + // 写入11+64+9+17字节签名 + if (destFile.getName().endsWith(".vue")) { + writer.write(String.format("\n\n", sha256, digestSize, id)); + } else { // ts / java + writer.write(String.format("\n/* ![%s,%08x,%016x] */\n", sha256, digestSize, id)); + } + } + + // 签名对比是否手动修改 + if (destFile.exists()) { + R r = checkOldFile(id, destFile); + if (r != null) { + return r; + } + + destFile.delete(); + } + + // 更名正式文件 + if (!tmpFile.renameTo(destFile)) { + return R.error("写文件失败,请用tmp文件手动替换!"); + } + + return null; + } + + /** 生成文件,文件不存在就替换文件 */ + public R build(String templateFile, File destFile) throws IOException { + if (!destFile.exists()) { + // 载入(获取)模板对象 + Template t = ve.getTemplate(templateFile); +// VelocityContext ctx = getVelocityContext(uiPage, uiPageRef); + + try(Writer writer = new OutputStreamWriter(new FileOutputStream(destFile))) { + t.merge(ctx, writer); + } + } + + return null; + } + + protected R checkOldFile(Long modelId, File vmFile) throws IOException { + // TODO 判断签名是否可以覆盖 + long vmFileLength = vmFile.length(); + if (vmFileLength <= MIN_FILE_SIZE) { + return R.error("文件太小:" + vmFileLength); + } + + if (vmFileLength > MAX_FILE_SIZE) { + return R.error("文件太大:" + vmFileLength); + } + + try (RandomAccessFile raFile = new RandomAccessFile(vmFile, "r")) { + raFile.seek(vmFile.length() - MIN_FILE_SIZE); + byte[] digest = new byte[MIN_FILE_SIZE]; + if (raFile.read(digest) != MIN_FILE_SIZE) { + return R.error("读取签名数据失败"); + } + + // 查找签名行 + String[] tokens = null; + int digestEnd = 0; + for (int i = digest.length - 1; i >= 0; i--) { + byte b = digest[i]; + if (b >= 0 && b <= 20) { + // 换行符 + if (digestEnd > 0) { + if (digestEnd - i == DIGEST_SIZE) { + String line = new String(digest, i + 1, digestEnd - i); + tokens = line.substring(5, line.length() - 4).split(","); + vmFileLength -= digest.length - i; + } + + break; + } + } else { + // 有效字符 + if (digestEnd == 0) { + digestEnd = i; + } + } + } + + if (tokens == null) { + return R.error("未找到签名数据"); + } + +// String[] tokens = line.substring(6, line.length() - 5).split(","); + if (tokens.length < 3) { + return R.error("签名格式出错"); + } + // sha256,size,id + long id = Long.parseLong(tokens[2].trim(), 16); + if (id != modelId) { + return R.error("文件ID值不一致:" + id); + } + + int size = (int) (vmFileLength); // - DIGEST_SIZE + + raFile.seek(0); + byte[] fileData = new byte[size]; + if (raFile.read(fileData) != size) { + return R.error("读取文件失败"); + } + + Sha256Helper sha256Helper = new Sha256Helper(true); + sha256Helper.write(fileData); + String sha256 = sha256Helper.sha256Hex(); + + if (!sha256.equals(tokens[0].trim())) { + return R.error("文件签名比对失败,文件内容请参考tmp文件手动调整!"); + } + + int oldSize = Integer.parseInt(tokens[1].trim(), 16); + if (oldSize != sha256Helper.digestSize()) { + return R.error("文件尺寸校验失败,文件内容请参考tmp文件手动调整!"); + } + } + + return null; + } + + private String[] findDigestLine(byte[] digest) { + int stop = 0; + for (int i = digest.length - 1; i >= 0; i--) { + byte b = digest[i]; + if (stop > 0 && b >= 0 && b <= 20) { + if (stop - i == DIGEST_SIZE) { + String line = new String(digest, i, stop - i); +// String[] tokens = line.substring(6, line.length() - 5).split(","); + return line.substring(5, line.length() - 4).split(","); + } else { + return null; + } + } else if (b < 0 || b > 20) { + stop = i; + } + } + + return null; + } + +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/DefaultVelocityEngine.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/DefaultVelocityEngine.java new file mode 100644 index 0000000..28cbbcd --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/DefaultVelocityEngine.java @@ -0,0 +1,16 @@ +package cc.smtweb.system.bpm.engine.ui.codegen; + +import org.apache.velocity.VelocityContext; + +public class DefaultVelocityEngine extends AbstractVelocityEngine{ + public DefaultVelocityEngine() { + this.ctx = new VelocityContext(); + ctx.put("vmTool", new VmTool()); + } + + public DefaultVelocityEngine put(String key, Object value) { + this.ctx.put(key, value); + + return this; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/Sha256Helper.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/Sha256Helper.java new file mode 100644 index 0000000..5cf8ddf --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/Sha256Helper.java @@ -0,0 +1,80 @@ +package cc.smtweb.system.bpm.engine.ui.codegen; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; + +import java.io.IOException; +import java.security.MessageDigest; + +public class Sha256Helper { + // 是否跳过空字符(\r\n\t )和(逗号,分号,单引号,双引号),避免格式化后不能生成代码 + private boolean skipSpaceChar; + private MessageDigest md = DigestUtils.getSha256Digest(); + private int size; + public static final byte[] SPACE_CHAR = new byte[256]; + + static { + SPACE_CHAR[' '] = 1; + SPACE_CHAR['\r'] = 2; + SPACE_CHAR['\n'] = 3; + SPACE_CHAR['\t'] = 1; + SPACE_CHAR[','] = 1; + SPACE_CHAR[';'] = 1; + SPACE_CHAR['\''] = 1; + SPACE_CHAR['"'] = 1; + } + + public Sha256Helper(boolean skipSpaceChar) { + this.skipSpaceChar = skipSpaceChar; + } + + public void write(int b) throws IOException { + if (md != null) { + if (skipSpaceChar) { + digitUpdate(md, (byte) b); + } else { + md.update((byte) b); + size ++; + } + } + } + + public void write(byte[] b) { + write(b, 0, b.length); + } + + public void write(byte[] b, int off, int len) { + if (md != null) { + if (skipSpaceChar) { + for (int i = off; i < off + len; i++) { + digitUpdate(md, b[i]); + } + } else { + md.update(b, off, len); + size += len; + } + } + } + + private void digitUpdate(MessageDigest md, byte ch) { + // != ' ' && ch != '\r' && ch != '\n' && ch != '\t' && ch != ',' && ch != ';' && ch != '\'' && ch != '"' + if (ch < 0 || SPACE_CHAR[ch] == 0) { + md.update(ch); + size++; + } + } + + public String sha256Hex() { + String reuslt = null; + if (md != null) { + reuslt = Hex.encodeHexString(md.digest()); + md = null; + } + + return reuslt; + } + + public int digestSize() { + return size; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/Sha256OutputStream.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/Sha256OutputStream.java new file mode 100644 index 0000000..6630104 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/Sha256OutputStream.java @@ -0,0 +1,45 @@ +package cc.smtweb.system.bpm.engine.ui.codegen; + +import java.io.IOException; +import java.io.OutputStream; + +public class Sha256OutputStream extends OutputStream { + private final OutputStream os; + // 是否跳过空字符和逗号(\r\n\t ),避免格式化后不能生成代码 + private Sha256Helper md; + + public Sha256OutputStream(OutputStream os, boolean skipSpaceChar) { + this.os = os; + this.md = new Sha256Helper(skipSpaceChar); + } + + @Override + public void write(int b) throws IOException { + os.write(b); + md.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + os.write(b, off, len); + md.write(b, off, len); + } + + @Override + public void flush() throws IOException { + os.flush(); + } + + @Override + public void close() throws IOException { + os.close(); + } + + public String sha256Hex() { + return md.sha256Hex(); + } + + public int digestSize() { + return md.digestSize(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/VmTool.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/VmTool.java new file mode 100644 index 0000000..2cf1966 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/codegen/VmTool.java @@ -0,0 +1,122 @@ +package cc.smtweb.system.bpm.engine.ui.codegen; + +import cc.smtweb.framework.core.util.jackson.*; +import cc.smtweb.framework.core.db.vo.ModelField; +import cc.smtweb.system.bpm.util.CodeGenUtil; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.json.JsonWriteFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.apache.commons.lang3.StringUtils; + +import java.sql.Timestamp; + +/** + * Velocity生成用的工具函数 + * @author xkliu + */ +public class VmTool { + private final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() + .disable(JsonWriteFeature.QUOTE_FIELD_NAMES).build(); + + private final ObjectMapper OBJECT_MAPPER_FULL = JsonMapper.builder() + .disable(JsonWriteFeature.QUOTE_FIELD_NAMES).build(); + + VmTool() { + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); + initObjectMapper(OBJECT_MAPPER); + + OBJECT_MAPPER_FULL.setSerializationInclusion(JsonInclude.Include.ALWAYS); + initObjectMapper(OBJECT_MAPPER_FULL); + } + + private void initObjectMapper(ObjectMapper objectMapper) { + SimpleModule module = new SimpleModule(); + LongSerializer longSerializer = new LongSerializer(); + module.addSerializer(Long.class, longSerializer); + module.addSerializer(Long.TYPE, longSerializer); + + DateSerializer dateSerializer = new DateSerializer(); + module.addSerializer(java.sql.Date.class, dateSerializer); + TimeSerializer timeSerializer = new TimeSerializer(); + module.addSerializer(java.sql.Time.class, timeSerializer); + + module.addDeserializer(Timestamp.class, new TimestampDeserializer()); + module.addDeserializer(java.util.Date.class, new DateDeserializer()); + + objectMapper.registerModule(module); + } + + public String json(Object obj) throws JsonProcessingException { + return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + } + + public String jsonFull(Object obj) throws JsonProcessingException { + return OBJECT_MAPPER_FULL.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + } + + public String gridArea(int x, int y, int w, int h) { + return (y + 1) + "/" + (x + 1) + "/ span " + h + "/span " + w; + } + + public int length(Object[] list) { + if (list != null) { + return list.length; + } + + return 0; + } + + public String toHump(String value) { + return CodeGenUtil.underlineToHump(value); + } + + public String toUpperHump(String value) { + return CodeGenUtil.underlineToUpperHump(value); + } + + /** + * 生成模板属性,根据不同的值类型进行处理 + */ + public String htmlProp(String key, Object value) throws JsonProcessingException { + if (value != null) { + String name = CodeGenUtil.humpToDash(key); + if (value instanceof Boolean) { + if (Boolean.TRUE.equals(value)) { + return name; + } + return ""; + } else if (value instanceof Number) { + return ":" + name + "=\"" + value + "\""; + } else if (value instanceof String) { + if (StringUtils.isNotBlank((String)value)) { + return name + "=\"" + value + "\""; + } + } else { + return ":" + name + "=\"" + escapeJson(OBJECT_MAPPER.writeValueAsString(value)) + "\""; + } + } + + return ""; + } + + private String escapeJson(String value) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (ch == '\"') { + sb.append('\''); + } else if (ch == '\'') { + sb.append("\\'"); + } else { + sb.append(ch); + } + } + + return sb.toString(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/consts/BiMapEnum.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/consts/BiMapEnum.java new file mode 100644 index 0000000..c2a1a53 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/consts/BiMapEnum.java @@ -0,0 +1,54 @@ +package cc.smtweb.system.bpm.engine.ui.entity.consts; + +import java.util.HashMap; +import java.util.Map; + +public class BiMapEnum { + private Map normalMap = new HashMap<>(); + private Map reverseMap = new HashMap<>(); + + public V put(K key, V value) { + if (key != null && value != null) { + V result = normalMap.put(key, value); + reverseMap.put(value, key); + + return result; + } + + return null; + } + + public V getValue(K key) { + return normalMap.get(key); + } + + public K getKey(V value) { + return reverseMap.get(value); + } + + public V remove(K key) { + V result = normalMap.remove(key); + if (result != null) { + reverseMap.remove(result); + } + + return result; + } + + public K addEnum(K key, V value) { + put(key, value); + return key; + } + + public static class IntEnum extends BiMapEnum {} + +// public static class EnumBean { +// public K key; +// public V value; +// +// public EnumBean(K key, V value) { +// this.key = key; +// this.value = value; +// } +// } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/consts/MappingTypeEnum.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/consts/MappingTypeEnum.java new file mode 100644 index 0000000..92fc779 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/consts/MappingTypeEnum.java @@ -0,0 +1,48 @@ +package cc.smtweb.system.bpm.engine.ui.entity.consts; + +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +/** + * 数据集映射关系 + * @author xkliu + */ +@Getter +public enum MappingTypeEnum { + // none, 1:1, 1:N, N:1, N:N + none, oneToOne, oneToMany, manyToOne, manyToMany; + + public static int toOrdinal(String name) { + return toEnum(name).ordinal(); + } + + public static MappingTypeEnum toEnum(String name) { + if (StringUtils.isNotBlank(name)) { + return valueOf(name); + } + + return none; + } + + public boolean sameOrdinal(int value) { + return ordinal() == value; + } + + public boolean sameName(String name) { + if (StringUtils.isNotBlank(name)) { + return name().equals(name); + } + + return this.equals(none); + } + + public boolean include(MappingTypeEnum ... args) { + for (MappingTypeEnum arg: args) { + if (arg.equals(this)) { + return true; + } + } + + return false; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/consts/UiEnum.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/consts/UiEnum.java new file mode 100644 index 0000000..adb628f --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/consts/UiEnum.java @@ -0,0 +1,47 @@ +package cc.smtweb.system.bpm.engine.ui.entity.consts; + +/** + * 界面相关枚举,字段作用 + * @author xkliu + */ +public interface UiEnum { + // 字段作用类型 + class FieldRefType extends BiMapEnum.IntEnum{ + /** 字段作用 */ + public static final int ID = 1; + public static final int PARENT_ID = 2; + public static final int CODE = 3; + public static final int PARENT_ID_LIST = 4; + public static final int TREE_LEVEL = 5; + public static final int CREATE_TIME = 6; + public static final int LAST_TIME = 7; + public static final int SITE_ID = 8; +// public static final int USER_ID = 9; + public static final int ORDER = 11; + public static final int TITLE = 21; + public static final int REMARK = 22; + + public static FieldRefType INSTANCE = new FieldRefType(); + private FieldRefType() { + addEnum(ID, "id"); + addEnum(PARENT_ID, "parentId"); + addEnum(CODE, "code"); + addEnum(ORDER, "order"); + addEnum(TITLE, "title"); + addEnum(REMARK, "remark"); + addEnum(PARENT_ID_LIST, "parentIdList"); + addEnum(TREE_LEVEL, "level"); + addEnum(CREATE_TIME, "createTime"); + addEnum(LAST_TIME, "lastTime"); + addEnum(SITE_ID, "siteId"); + } + } + + // 字段外键类型 + final class FieldLookupType extends BiMapEnum.IntEnum { + public static final FieldLookupType INSTANCE = new FieldLookupType(); + public static final int TABLE = INSTANCE.addEnum(1, "table"); + public static final int DICT = INSTANCE.addEnum(2, "dict"); + public static final int DATASET = INSTANCE.addEnum(3, "dataset"); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/po/DynFormLogPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/po/DynFormLogPO.java new file mode 100644 index 0000000..023c56d --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/po/DynFormLogPO.java @@ -0,0 +1,18 @@ +package cc.smtweb.system.bpm.engine.ui.entity.po; + +import lombok.Data; + +import java.sql.Timestamp; + +@Data +public class DynFormLogPO { + private Long dflId; + + private String dflContent; + + private Integer dflLockRev; + + private Timestamp dflCreateAt; + + private Timestamp dflUpdateAt; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzApiVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzApiVO.java new file mode 100644 index 0000000..3fb0ce8 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzApiVO.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.dataset; + +import lombok.Data; + +@Data +public class FzApiVO { + private String name; + private String url; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzDatasetPropsVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzDatasetPropsVO.java new file mode 100644 index 0000000..101d3dc --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzDatasetPropsVO.java @@ -0,0 +1,25 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.dataset; + +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDatasetSql; +import lombok.Data; + +@Data +public class FzDatasetPropsVO { + public static final String TYPE_MASTER = "master"; + public static final String TYPE_SLAVE = "slave"; + public static final String TYPE_LOOKUP = "lookup"; + + private String datasource; + private String table; + private String tableType; + private Integer dataModel; + private FzFieldVO[] fields; + private String masterDataset; + private FzMasterMappingVO masterFields; + private String customApi; + private String name; + private String label; + private boolean initData; + + private BpmDatasetSql sql; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzDatasetVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzDatasetVO.java new file mode 100644 index 0000000..d637dc8 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzDatasetVO.java @@ -0,0 +1,14 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.dataset; + +import lombok.Data; + +import java.util.Map; + +@Data +public class FzDatasetVO { + private String type; + private String id; + private FzDatasetPropsVO props; + private Map events; + private Map apis; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzFieldVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzFieldVO.java new file mode 100644 index 0000000..620a832 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzFieldVO.java @@ -0,0 +1,12 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.dataset; + +import cc.smtweb.system.bpm.engine.ui.entity.vo.widiget.UiControlPropsVO; +import lombok.Data; + +@Data +public class FzFieldVO { + private String type; + private String id; + + private UiControlPropsVO props; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzMasterMappingVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzMasterMappingVO.java new file mode 100644 index 0000000..59d1d74 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/dataset/FzMasterMappingVO.java @@ -0,0 +1,11 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.dataset; + +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmMappingField; +import lombok.Data; + +@Data +public class FzMasterMappingVO { + private String mappingType; + private String middleDataset; + private BpmMappingField[] fields; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/AbstractUiControlVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/AbstractUiControlVO.java new file mode 100644 index 0000000..4b8ae6c --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/AbstractUiControlVO.java @@ -0,0 +1,30 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.widiget; + +import lombok.Data; + +import java.util.Map; + +@Data +//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true) +//@JsonSubTypes({ +// @JsonSubTypes.Type(value = FzDatasetVO.class, name = "fz-dataset"), +// @JsonSubTypes.Type(value = FyControlVO.class, name = "fy-datetime"), +// @JsonSubTypes.Type(value = FyControlVO.class, name = "fy-file"), +// @JsonSubTypes.Type(value = FyControlVO.class, name = "fy-image"), +// @JsonSubTypes.Type(value = FyControlVO.class, name = "fy-select"), +// @JsonSubTypes.Type(value = FyControlVO.class, name = "fy-switch"), +// @JsonSubTypes.Type(value = FyControlVO.class, name = "fy-text"), +// @JsonSubTypes.Type(value = FyControlVO.class, name = "fy-textarea"), +//}) + +public abstract class AbstractUiControlVO { + private String type; + private String id; + private int x; + private int y; + private int w; + private int h; + + private T props; + private Map events; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlDataVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlDataVO.java new file mode 100644 index 0000000..9dedb90 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlDataVO.java @@ -0,0 +1,18 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.widiget; + +import lombok.Data; + +import java.util.Map; + +@Data +public class UiControlDataVO { + private String id; + // 变量名,系统自动根据 props.name 和 id 值设置 + private String name; + private String type; + private UiControlLayoutVO layout; + private UiControlPropsVO props; + private Map slots; + private Map vars; + private Map events; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlLayoutVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlLayoutVO.java new file mode 100644 index 0000000..f8e5e8f --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlLayoutVO.java @@ -0,0 +1,8 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.widiget; + +import lombok.Data; + +@Data +public class UiControlLayoutVO { + private Integer col; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlPropsVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlPropsVO.java new file mode 100644 index 0000000..2ba76ef --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlPropsVO.java @@ -0,0 +1,38 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.widiget; + +import cc.smtweb.framework.core.SwMap; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; + +public class UiControlPropsVO extends SwMap { + public List readList(String name) { + Object v = get(name); + + if (v instanceof List) { + return (List)v; + } + + return null; + } + + public boolean isNotBlank(String name) { + Object v = get(name); + if (v == null) { + return false; + } + + return StringUtils.isNotBlank(v.toString()); + } + + public Map readMap(String name) { + Object item = get(name); + + if (item instanceof Map) { + return (Map) item; + } + + return null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlVO.java new file mode 100644 index 0000000..e3bca63 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiControlVO.java @@ -0,0 +1,15 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.widiget; + +import lombok.Data; + +@Data +public class UiControlVO { + private int x; + private int y; + private int w; + private int h; + private int zIndex; + private String shape; + private UiControlDataVO data; + private UiControlVO[] children; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPageControlVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPageControlVO.java new file mode 100644 index 0000000..10ca533 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPageControlVO.java @@ -0,0 +1,8 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.widiget; + +import lombok.Data; + +@Data +public class UiPageControlVO { + private UiPagePropsVO props; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPageFormVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPageFormVO.java new file mode 100644 index 0000000..7857ab0 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPageFormVO.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.widiget; + +import lombok.Data; + +@Data +public class UiPageFormVO { + private UiPageControlVO widget; + // tree struct + private UiControlVO graph; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPagePropsVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPagePropsVO.java new file mode 100644 index 0000000..621085c --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPagePropsVO.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.widiget; + +import lombok.Data; + +@Data +public class UiPagePropsVO { + private String title; + private String key; + private Boolean destroyOnClose; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPageVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPageVO.java new file mode 100644 index 0000000..a3f2763 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/vo/widiget/UiPageVO.java @@ -0,0 +1,14 @@ +package cc.smtweb.system.bpm.engine.ui.entity.vo.widiget; + +import cc.smtweb.system.bpm.engine.ui.entity.vo.dataset.FzDatasetVO; +import lombok.Data; + +@Data +public class UiPageVO { + private Integer version; +// private UiPageControlVO page; +// // tree struct +// private UiControlVO graph; + private UiPageFormVO[] forms; + private FzDatasetVO[] model; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlDataset.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlDataset.java new file mode 100644 index 0000000..71d9724 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlDataset.java @@ -0,0 +1,24 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import lombok.Data; + +import java.util.List; + +@Data +public class YamlDataset { + private String name; + private String database; + private String table; + // 数据模型 + private String dataModel; + // 是否只读 + private boolean readonly; + private String customApi; + private Boolean initData; + // 与主表的映射关系 + private YamlMasterMapping masterMapping; + // 字段列表,name => UiField + private List fields; + + private YamlDatasetSql sql; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlDatasetSql.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlDatasetSql.java new file mode 100644 index 0000000..63fc864 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlDatasetSql.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import lombok.Data; + +@Data +public class YamlDatasetSql { + private String select; + private String update; + private String insert; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlField.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlField.java new file mode 100644 index 0000000..ff7f1ca --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlField.java @@ -0,0 +1,22 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import lombok.Data; + +@Data +public class YamlField { + private String name; + private String label; + private String fieldName; + private String type; + private String summary; + private String refType; + private String displayFormat; + private Boolean required; + private Boolean readonly; + private String validPatten; + private String validMessage; + private int filter; + private int filterType; + + private YamlFieldLookup lookup; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldFilter.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldFilter.java new file mode 100644 index 0000000..7b2d556 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldFilter.java @@ -0,0 +1,16 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; + +@Data +public class YamlFieldFilter { + @JacksonXmlProperty(isAttribute=true) + private String field; + @JacksonXmlProperty(isAttribute=true) + private int op; + @JacksonXmlProperty(isAttribute=true) + private String keyword; + @JacksonXmlProperty(isAttribute=true) + private boolean cascade; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldLookup.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldLookup.java new file mode 100644 index 0000000..d895540 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldLookup.java @@ -0,0 +1,36 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class YamlFieldLookup { + @JacksonXmlProperty(isAttribute=true) + private String type; + @JacksonXmlProperty(isAttribute=true) + private MappingTypeEnum mappingType; + @JacksonXmlProperty(isAttribute=true) + private boolean numberValue; + @JacksonXmlProperty(isAttribute=true) + private String db; + @JacksonXmlProperty(isAttribute=true) + private String table; + @JacksonXmlProperty(isAttribute=true) + private String keyField; + @JacksonXmlProperty(isAttribute=true) + private String labelField; + @JacksonXmlProperty(isAttribute=true) + private String parentField; + @JacksonXmlProperty(isAttribute=true) + private String orderField; + + private YamlFieldLookupMiddle middle; + + @JacksonXmlProperty(localName = "filter") + @JacksonXmlElementWrapper(useWrapping = false) + private List filters; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldLookupMiddle.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldLookupMiddle.java new file mode 100644 index 0000000..d6d23ba --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldLookupMiddle.java @@ -0,0 +1,16 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; + +@Data +public class YamlFieldLookupMiddle { + @JacksonXmlProperty(isAttribute=true) + private String table; + @JacksonXmlProperty(isAttribute=true) + private String idField; + @JacksonXmlProperty(isAttribute=true) + private String keyField; + @JacksonXmlProperty(isAttribute=true) + private String valueField; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldValidator.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldValidator.java new file mode 100644 index 0000000..2675dd4 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlFieldValidator.java @@ -0,0 +1,16 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; + +@Data +public class YamlFieldValidator { + @JacksonXmlProperty(isAttribute=true) + private String type; + @JacksonXmlProperty(isAttribute=true) + private Boolean required; + @JacksonXmlProperty(isAttribute=true) + private String pattern; + @JacksonXmlProperty(isAttribute=true) + private String message; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlForm.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlForm.java new file mode 100644 index 0000000..acbfd8b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlForm.java @@ -0,0 +1,8 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import lombok.Data; + +@Data +public class YamlForm extends YamlWidget { + +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlMappingField.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlMappingField.java new file mode 100644 index 0000000..d3b60e4 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlMappingField.java @@ -0,0 +1,14 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; + +@Data +public class YamlMappingField { + @JacksonXmlProperty(isAttribute=true) + private String masterField; + @JacksonXmlProperty(isAttribute=true) + private String slaveField; + @JacksonXmlProperty(isAttribute=true) + private String middleField; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlMasterMapping.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlMasterMapping.java new file mode 100644 index 0000000..891dc26 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlMasterMapping.java @@ -0,0 +1,24 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class YamlMasterMapping { + // 映射到主表的类型 + @JacksonXmlProperty(isAttribute=true) + private String mappingType; + // 主表名 + @JacksonXmlProperty(isAttribute=true) + private String masterTable; + // 中间表名 + @JacksonXmlProperty(isAttribute=true) + private String middleTable; + // 映射字段列表 + @JacksonXmlProperty(localName = "mappingField") + @JacksonXmlElementWrapper(useWrapping = false) + private List mappingFields; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlPage.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlPage.java new file mode 100644 index 0000000..5b86673 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlPage.java @@ -0,0 +1,13 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import lombok.Data; + +import java.util.List; + +@Data +public class YamlPage { + private Long id; + private List datasets; + private List forms; + private Boolean destroyOnClose; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlWidget.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlWidget.java new file mode 100644 index 0000000..1e95ba6 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlWidget.java @@ -0,0 +1,29 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class YamlWidget { + private String type; + private String name; + private YamlWidgetLayout layout; + private Map props; + private Map events; + private YamlWidget[] children; + + public void addProps(Map props) { + this.props = new HashMap<>(props.size()); + // 深度对象应用的问题,不运行修改 + this.props.putAll(props); + } + + public void addEvents(Map events) { + if (events != null) { + this.events = new HashMap<>(events.size()); + this.events.putAll(events); + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlWidgetLayout.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlWidgetLayout.java new file mode 100644 index 0000000..554ce8c --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/entity/yaml/YamlWidgetLayout.java @@ -0,0 +1,8 @@ +package cc.smtweb.system.bpm.engine.ui.entity.yaml; + +import lombok.Data; + +@Data +public class YamlWidgetLayout { + private Integer col; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/impl/FormDataStoreImpl.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/impl/FormDataStoreImpl.java new file mode 100644 index 0000000..95534fe --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/impl/FormDataStoreImpl.java @@ -0,0 +1,62 @@ +package cc.smtweb.system.bpm.engine.ui.impl; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.sqlbuilder.InsertSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.UpdateSqlBuilder; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.core.util.DateUtil; +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.system.bpm.engine.ui.IFormDataStore; +import org.apache.commons.lang3.StringUtils; + +public class FormDataStoreImpl implements IFormDataStore { + private DbEngine dbEngine; + + public FormDataStoreImpl(DbEngine dbEngine) { + this.dbEngine = dbEngine; + } + + @Override + public SwMap load(Long processId, Long processRootId) { + if (processRootId != null) { + String content = dbEngine.queryString("select pdl_content from sw_bpm.bpm_process_detail_log where pdl_id=?", processRootId); + if (StringUtils.isNotBlank(content)) { + return JsonUtil.parse(content, SwMap.class); + } + } + + return null; + } + + @Override + public void save(Long processId, Long processRootId, Long taskId, Long businessKey, String tableName, SwMap body, UserSession us) { + String content = JsonUtil.encodeString(body); + + if (processId == null) { + processId = businessKey; + } + + if (taskId == null || taskId == 0) { + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert("sw_bpm.bpm_process_detail_log"); + sqlBuilder.add("pdl_id", businessKey) + .add("pdl_process_id", processId) + .add("pdl_process_root_id", processRootId) + .add("pdl_table", tableName) + .add("pdl_content", content) + .add("pdl_create_uid", us.getUserId()) + .add("pdl_update_uid", us.getUserId()); + sqlBuilder.update(dbEngine); + } else { + UpdateSqlBuilder sqlBuilder = SqlBuilder.createUpdate("sw_bpm.bpm_process_detail_log"); + sqlBuilder.add("pdl_process_id", processId) + .add("pdl_task_id", taskId) + .add("pdl_content", content) + .add("pdl_last_time", DateUtil.nowDateTimeLong()) + .add("pdl_update_uid", us.getUserId()) + .addWhere("pdl_id", businessKey); + sqlBuilder.update(dbEngine); + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/IPageRouter.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/IPageRouter.java new file mode 100644 index 0000000..2fed018 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/IPageRouter.java @@ -0,0 +1,5 @@ +package cc.smtweb.system.bpm.engine.ui.loader; + +public interface IPageRouter { + String loadFullName(Long pageId); +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/TreePageRouter.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/TreePageRouter.java new file mode 100644 index 0000000..372a241 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/TreePageRouter.java @@ -0,0 +1,52 @@ +package cc.smtweb.system.bpm.engine.ui.loader; + +import cc.smtweb.system.bpm.engine.entity.AspModelTreeVO; +import cc.smtweb.system.bpm.util.CodeGenUtil; + +import java.util.ArrayList; +import java.util.List; + +public class TreePageRouter implements IPageRouter { + private AspModelTreeVO root; + private String moduleName; + + public TreePageRouter(AspModelTreeVO root, String moduleName) { + this.root = root; + this.moduleName = moduleName; + } + + @Override + public String loadFullName(Long pageId) { + List names = new ArrayList<>(); + + if (getRouterName(pageId, root, names)) { + if (moduleName != null) { + names.add(0, moduleName); + } + + return String.join(".", names); + } + + return null; + } + + private boolean getRouterName(Long pageId, AspModelTreeVO parent, List names) { + List children = parent.getChildren(); + + if (children != null) { + for (AspModelTreeVO child: children) { + if (pageId.equals(child.getModelId())) { + names.add(CodeGenUtil.toLowerHump(child.getModelKey())); + return true; + } else { + if (getRouterName(pageId, child, names)) { + names.add(0, CodeGenUtil.toLowerHump(child.getModelKey())); + return true; + } + } + } + } + + return false; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/UiMobileLoader.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/UiMobileLoader.java new file mode 100644 index 0000000..7eb57a7 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/UiMobileLoader.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.engine.ui.loader; + +import cc.smtweb.system.bpm.engine.entity.AspModelPO; + +public class UiMobileLoader extends UiPageLoader { + public UiMobileLoader(IPageRouter pageRouter) { + super(pageRouter, AspModelPO.TYPE_UI_MOBILE); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/UiPageLoader.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/UiPageLoader.java new file mode 100644 index 0000000..862c893 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/UiPageLoader.java @@ -0,0 +1,44 @@ +package cc.smtweb.system.bpm.engine.ui.loader; + +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.engine.AbstractLoader; +import cc.smtweb.system.bpm.engine.ui.loader.json.JsonPageDecoder; +import cc.smtweb.system.bpm.engine.ui.loader.yaml.YamlPageDecoder; +import lombok.extern.slf4j.Slf4j; + +import java.io.InputStream; + +@Slf4j +public abstract class UiPageLoader extends AbstractLoader { + private IPageRouter pageRouter; + private int modelType; + + public UiPageLoader(IPageRouter pageRouter, int modelType) { + this.pageRouter = pageRouter; + this.modelType = modelType; + } + + @Override + public BpmPage load(String content) { + JsonPageDecoder jsonPageDecoder = new JsonPageDecoder(this, pageRouter); + + BpmPage result = jsonPageDecoder.decode(content); + if (result != null) { + result.setModelType(modelType); + } + + return result; + } + + public BpmPage load(InputStream is) { + YamlPageDecoder yamlPageDecoder = new YamlPageDecoder(); + + BpmPage result = yamlPageDecoder.decode(is); + if (result != null) { + result.setModelType(modelType); + } + + return result; + } +} + diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/UiPcLoader.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/UiPcLoader.java new file mode 100644 index 0000000..3efa2fc --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/UiPcLoader.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.engine.ui.loader; + +import cc.smtweb.system.bpm.engine.entity.AspModelPO; + +public class UiPcLoader extends UiPageLoader { + public UiPcLoader(IPageRouter pageRouter) { + super(pageRouter, AspModelPO.TYPE_UI_PC); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/json/JsonDatasetDecoder.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/json/JsonDatasetDecoder.java new file mode 100644 index 0000000..75f027b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/json/JsonDatasetDecoder.java @@ -0,0 +1,551 @@ +package cc.smtweb.system.bpm.engine.ui.loader.json; + +import cc.smtweb.framework.core.db.cache.ModelDatabaseCache; +import cc.smtweb.framework.core.db.cache.ModelTableCache; +import cc.smtweb.framework.core.util.MapUtil; +import cc.smtweb.system.bpm.core.exception.ModelLoaderError; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.dataset.FieldFormatTool; +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.core.ui.entity.dataset.*; +import cc.smtweb.system.bpm.engine.AbstractLoader; +import cc.smtweb.framework.core.db.vo.ModelDatabase; +import cc.smtweb.framework.core.db.vo.ModelField; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; +import cc.smtweb.system.bpm.engine.ui.entity.vo.dataset.FzDatasetPropsVO; +import cc.smtweb.system.bpm.engine.ui.entity.vo.dataset.FzDatasetVO; +import cc.smtweb.system.bpm.engine.ui.entity.vo.dataset.FzFieldVO; +import cc.smtweb.system.bpm.engine.ui.entity.vo.dataset.FzMasterMappingVO; +import cc.smtweb.system.bpm.engine.ui.entity.vo.widiget.UiControlPropsVO; +import cc.smtweb.system.bpm.engine.ui.entity.vo.widiget.UiPageVO; +import cc.smtweb.system.bpm.engine.ui.loader.yaml.YamlDatasetDecoder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class JsonDatasetDecoder { + private AbstractLoader loader; + private Map datasetMap = new HashMap<>(); + private Map databases = new HashMap<>(); + @Getter + private List slaveDatasetList = new ArrayList<>(); +// private Map lookupDatasetMap = new HashMap<>(); + + public JsonDatasetDecoder(AbstractLoader loader) { + this.loader = loader; + } + + protected void decode(BpmPage result, UiPageVO uiPageVO) { + // 处理DataSet + buildDataset(uiPageVO.getModel(), result, slaveDatasetList); + + fixMasterDataset(result, slaveDatasetList); + } + + private String getMasterName(BpmDataset dataset) { +// BpmMasterMapping bpmMasterMapping = dataset.getMasterMapping(); +// if (bpmMasterMapping != null && bpmMasterMapping.isOneToOne()) { +// return bpmMasterMapping.getMaster(); +// } + + return dataset.getName(); + } + +// private void addLookupField(BpmDataset dataset, String fieldName, String name, int refType) { +// if (StringUtils.isNotBlank(fieldName)) { +// BpmField bpmField = new BpmField(); +// bpmField.setFieldName(fieldName); +// bpmField.setName(name); +// bpmField.setRefType(refType); +// dataset.addField(bpmField); +// } +// } + + private void fixMasterDataset(BpmPage result, List slaveDatasetList) { +// if (result.getMasterDataset() != null) { +// UiPageFormDataset pageFormDataset = result.getMasterDataset().getPageFormDataset(); +// if (pageFormDataset.getTableType() != UiPageFormDataset.TABLE_TYPE_MASTER) { +// pageFormDataset.setTableType(UiPageFormDataset.TABLE_TYPE_MASTER); +// } +// } + + for (SlaveDataset slaveDataset: slaveDatasetList) { + BpmDataset slave = slaveDataset.getDataset(); +// UiPageFormDataset slaveProps = slaveDataset.getPageFormDataset(); + String masterId = slaveDataset.getMasterId(); + BpmDataset master = findDataset(masterId); + + if (master != null) { + // 1:1 映射需要重新设置从表名称 + FzMasterMappingVO masterMappingVO = slaveDataset.getMasterMapping(); + BpmDataset middle = null; + + BpmMasterMapping masterMapping = slave.getMasterMapping(); + + masterMapping.setMappingType(MappingTypeEnum.toEnum(masterMappingVO.getMappingType())); + masterMapping.setMaster(master.getName()); + + // 处理从表映射 + if (MappingTypeEnum.oneToOne.equals(masterMapping.getMappingType())) { + // 名字改成一样,便于控件引用 +// slaveProps.setName(master.getName()); + slave.setName(master.getName()); + master.addOneToOneDataset(slave); + } else if (MappingTypeEnum.manyToMany.equals(masterMapping.getMappingType())) { + String middleId = masterMappingVO.getMiddleDataset(); + if (StringUtils.isBlank(middleId)) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, slave.getId(), "未设置Middle数据集"); + continue; + } + + middle = findDataset(middleId); + if (middle == null) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, slave.getId(), "未找到Middle数据集, id=" + masterId); + continue; + } + + masterMapping.setMiddle(middle.getName()); + } + + // 处理字段映射 + for (BpmMappingField linkFieldVO: masterMappingVO.getFields()) { + BpmField masterField = master.findFieldById(linkFieldVO.getMaster()); + + if (masterField == null) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, slave.getId(), "未找到Master数据集字段, id=" + linkFieldVO.getMaster()); + continue; + } + + BpmField slaveField = slave.findFieldById(linkFieldVO.getSlave()); + if (slaveField == null) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, slave.getId(), "未找到Slave数据集字段, id=" + linkFieldVO.getSlave()); + continue; + } + + String middleFieldName = null; + if (middle != null) { + BpmField middleField = middle.findFieldById(linkFieldVO.getMiddle()); + if (middleField == null) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, slave.getId(), "未找到Middle数据集字段, id=" + linkFieldVO.getMiddle()); + continue; + } + + middleFieldName = middleField.getFieldName(); + } + + masterMapping.add(masterField.getFieldName(), slaveField.getFieldName(), middleFieldName); + } + } else { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, slave.getId(), "未找到Master数据集, id=" + masterId); + } + } + } + + public BpmDataset findDataset(String masterId) { + BpmDataset result = null; + SlaveDataset linkDataset = datasetMap.get(masterId); + if (linkDataset == null) { + for (SlaveDataset slaveDataset : slaveDatasetList) { + if (masterId.equals(slaveDataset.getDataset().getId())) { + result = slaveDataset.getDataset(); + break; + } + } + } else { + result = linkDataset.getDataset(); + } + + return result; + } + + private void buildDataset(FzDatasetVO[] fzDatasetAry, BpmPage result, List slaveDatasetList) { + if (fzDatasetAry != null) { + for (FzDatasetVO datasetVO : fzDatasetAry) { + FzDatasetPropsVO datasetPropsVO = datasetVO.getProps(); + + if (datasetPropsVO.getSql() != null) { + buildBpmDataset(result, slaveDatasetList, datasetVO, null, null); + } else { + if (StringUtils.isBlank(datasetPropsVO.getDatasource())) { + loader.addError(ModelLoaderError.ERROR_DB, "未设置数据库"); + } else { + String datasource = datasetPropsVO.getDatasource(); + + ModelDatabase db = getModelDatabase(datasource); + + if (db == null) { + loader.addError(ModelLoaderError.ERROR_DB, "未找到数据库, name= " + datasource); + } else { + ModelTable mobileTable = ModelTableCache.getInstance().getByName(datasetPropsVO.getTable()); + + if (mobileTable == null) { + loader.addError(ModelLoaderError.ERROR_TABLE, "未找到数据表 " + datasetPropsVO.getTable() + ", datasource: " + datasource); + } else { + buildBpmDataset(result, slaveDatasetList, datasetVO, db, mobileTable); + } + } + } + } + } + } + } + + private void buildBpmDataset(BpmPage result, List slaveDatasetList, FzDatasetVO datasetVO, ModelDatabase db, ModelTable mobileTable) { + boolean addToPage = true; + FzDatasetPropsVO datasetPropsVO = datasetVO.getProps(); + BpmDataset dataset = new BpmDataset(); + + dataset.setId(datasetVO.getId()); + dataset.setDataModel(toInt(datasetPropsVO.getDataModel())); + dataset.setInitData(datasetPropsVO.isInitData()); + dataset.setCustomApi(StringUtils.trimToNull(datasetPropsVO.getCustomApi())); + + if (db != null) { + dataset.setDatabase(db.getName()); + dataset.setTable(mobileTable.getName()); + dataset.setFieldPrefix((YamlDatasetDecoder.getFieldPrefix(mobileTable.getIdField()))); + } else { + dataset.setSql(datasetPropsVO.getSql()); + } + + if (StringUtils.isNotBlank(datasetPropsVO.getName())) { + dataset.setName(datasetPropsVO.getName()); + } else { + dataset.setName(datasetVO.getId()); + } + + SlaveDataset linkDataset = new SlaveDataset(dataset, datasetVO.getId(), datasetPropsVO.getMasterDataset(), datasetPropsVO.getMasterFields()); + if (linkDataset.isSlave()) { + slaveDatasetList.add(linkDataset); + if (MappingTypeEnum.oneToOne.sameName(datasetPropsVO.getMasterFields().getMappingType())) { + addToPage = false; + } + } + + FzFieldVO[] fields = datasetPropsVO.getFields(); + + for (FzFieldVO fieldVO : fields) { + BpmField bpmField = new BpmField(); + UiControlPropsVO fieldProps = fieldVO.getProps(); + String field = fieldProps.readString("field"); + + if (mobileTable != null) { + ModelField modelField = mobileTable.findField(field); + + if (StringUtils.isNotBlank(field) && modelField == null) { + loader.addError(ModelLoaderError.ERROR_FIELD, "未找到字段 " + dataset.getTable() + "." + field); + continue; + } + + // 处理自动名,虚拟数据集用ID作为字段名 + if (modelField != null) { + if (StringUtils.isNotBlank(modelField.getName())) { + bpmField.setFieldName(modelField.getName()); + } else { + // 测试时可以使用ID来作为字段名 + bpmField.setFieldName(modelField.getName()); + } + } + } else { + bpmField.setFieldName(field); + } + + bpmField.setId(fieldVO.getId()); + bpmField.setSummary(fieldProps.readString("summary")); + bpmField.setRefType(fieldProps.readInt("refType", 0)); + bpmField.setRequired(fieldProps.readBool("required", false)); + bpmField.setValidPatten(fieldProps.readString("validPatten")); + bpmField.setValidMessage(fieldProps.readString("validMessage")); + bpmField.setLabel(fieldProps.readString("label")); + bpmField.setDisplayFormat(fieldProps.readString("displayFormat")); + bpmField.setFilter(fieldProps.readInt("filter", 0)); + bpmField.setFilterType(fieldProps.readInt("filterType", 0)); + + // 处理查找属性 + Map lookup = fieldProps.readMap("lookup"); + if (lookup != null) { + fixFieldLookup(datasetVO, fieldVO, bpmField, lookup); + } + + // 处理变量名,控件关联 + String name = fieldProps.readString("name"); + if (StringUtils.isNotBlank(name)) { + bpmField.setName(name); + } else { + bpmField.setName(bpmField.getId()); + } + + String fieldType = fieldVO.getType(); + if (fieldType != null && fieldType.startsWith("fz-field-")) { + bpmField.setType(fieldType.substring(9)); + } else { + loader.addError(ModelLoaderError.ERROR_FIELD, datasetVO.getId(), fieldVO.getId(), "无效字段类型 " + fieldType); + } + + FieldFormatTool.fixDisplayFormat(bpmField); + dataset.addField(bpmField); + } + + if (dataset.getIdField() == null) { + if (mobileTable != null) { + BpmField idField = dataset.findFieldByFieldName(mobileTable.getIdField()); + dataset.setIdField(idField); + } + + if (dataset.getIdField() == null) { + loader.addError(ModelLoaderError.ERROR_TABLE, "数据表未设置ID字段 " + datasetPropsVO.getTable() + ", dataset: " + dataset.getName()); + return; + } + } + +// datasetProps.setIdField(dataset.getIdField().getName()); + + if (addToPage) { + addDataset(result, linkDataset); + } + } + + private boolean fixFieldLookup(FzDatasetVO datasetVO, FzFieldVO fieldVO, BpmField bpmField, Map lookup) { + String type = MapUtil.readString(lookup, "type"); + String dbName = MapUtil.readString(lookup, "db"); + String table = MapUtil.readString(lookup, "table"); + boolean isNumber = isNumberType(fieldVO.getType()); + + if ("dict".equals(type)) { + String dictType = MapUtil.readString(lookup, "dictType"); + + if (StringUtils.isBlank(dictType)) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, datasetVO.getId(), fieldVO.getId(), "lookup未配置dictType"); + return true; + } + + bpmField.setLookup(new BpmFieldDict(dbName, table, isNumber, dictType)); + } else if ("table".equals(type) || "lookup".equals(type)) { // "lookup" 是兼容旧配置 + MappingTypeEnum mappingType = MappingTypeEnum.toEnum(MapUtil.readString(lookup, "mappingType")); + if (!mappingType.include(MappingTypeEnum.manyToOne, MappingTypeEnum.manyToMany)) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, datasetVO.getId(), fieldVO.getId(), "lookup未配置映射类型"); + return true; + } + + // 数据库字段名 + String keyField = MapUtil.readString(lookup, "keyField"); + if (StringUtils.isBlank(keyField)) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, datasetVO.getId(), fieldVO.getId(), "lookup未配置keyField"); + return true; + } + + // 数据库字段名 + String labelField = MapUtil.readString(lookup, "labelField"); + if (StringUtils.isBlank(labelField)) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, datasetVO.getId(), fieldVO.getId(), "lookup未配置labelField"); + return true; + } + + BpmFieldLookup fieldLookup = new BpmFieldLookup(dbName, table, isNumber, mappingType); + + fieldLookup.setKeyField(keyField); + fieldLookup.setLabelField(labelField); + + // 数据库上级字段名 + String parentField = MapUtil.readString(lookup, "parentField"); + if (StringUtils.isNotBlank(parentField)) { + fieldLookup.setParentField(parentField); + } + + List> filters = (List>)lookup.get("filter"); + if (filters != null && !filters.isEmpty()) { + for (Map filter: filters) { + String filterField = MapUtil.readString(filter, IBpmDatasetConst.PN_ITEM_FIELD); + String filterKeyword = MapUtil.readString(filter, IBpmDatasetConst.PN_ITEM_KEYWORD); + int filterOp = MapUtil.readInt(filter, IBpmDatasetConst.PN_ITEM_OP, 0); + boolean cascade = MapUtil.readBool(filter, IBpmDatasetConst.PN_ITEM_CASCADE, false); + + fieldLookup.addFilter(filterField, filterOp, filterKeyword, cascade); + } + } + + if (mappingType.equals(MappingTypeEnum.manyToMany)) { + // 数据库字段名 + String middleTable = MapUtil.readString(lookup, "middleTable"); + if (StringUtils.isBlank(middleTable)) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, datasetVO.getId(), fieldVO.getId(), "lookup未配置中间表"); + return true; + } + fieldLookup.setMiddleTable(middleTable); + + String middleKeyField = MapUtil.readString(lookup, "middleKeyField"); + if (StringUtils.isBlank(middleKeyField)) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, datasetVO.getId(), fieldVO.getId(), "lookup未配置中间表映射字段"); + return true; + } + fieldLookup.setMiddleKeyField(middleKeyField); + + String middleValueField = MapUtil.readString(lookup, "middleValueField"); + if (StringUtils.isBlank(middleValueField)) { + loader.addError(ModelLoaderError.ERROR_UI_DATASET, datasetVO.getId(), fieldVO.getId(), "lookup未配置中间表映射字段"); + return true; + } + fieldLookup.setMiddleValueField(middleValueField); + } + + bpmField.setLookup(fieldLookup); + } // else "dataset" + + return false; + } + + private void addDataset(BpmPage result, SlaveDataset slaveDataset) { + List datasets = result.getDatasets(); + int insert = -1; + + String id = slaveDataset.getId(); + if (id != null) { + // 保证主表在前 + for (int i = 0; i < datasets.size(); i++) { + SlaveDataset linkDataset = datasetMap.get(datasets.get(i).getId()); + + if (linkDataset != null) { + if (id.equals(linkDataset.getMasterId())) { + insert = i; + break; + } + } + } + + datasetMap.put(slaveDataset.getId(), slaveDataset); + } + + BpmDataset dataset = slaveDataset.getDataset(); + if (insert >= 0) { + datasets.add(insert, dataset); + } else { + datasets.add(dataset); + } + } + + public void addLookupDataset(BpmPage result, BpmDataset masterDataset, BpmField field, boolean isTree) { + // 1:1关联需要找到主表名称 + String masterName = getMasterName(masterDataset); + + String name = masterName + "_" + field.getName(); + + BpmDataset datasetWrapper = result.findDatasetByName(name); + if (datasetWrapper == null) { + AbstractBpmFieldLookup lookup = field.getLookup(); + + ModelDatabase db = getModelDatabase(lookup.getDb()); + if (db == null) { + loader.addError(ModelLoaderError.ERROR_UI, "未找到数据源, database= " + lookup.getDb()); + return; + } + + BpmDataset dataset = new BpmDataset(); + + dataset.setInitData(true); + dataset.setName(name); + dataset.setDatabase(lookup.getDb()); + dataset.setTable(lookup.getTable()); + dataset.setLookup(lookup); + dataset.setDataModel(isTree ? BpmDataset.DATA_MODEL_TREE : BpmDataset.DATA_MODEL_LIST); + + if (lookup instanceof BpmFieldLookup) { + BpmFieldLookup fieldLookup = (BpmFieldLookup)lookup; + + dataset.setFieldPrefix(YamlDatasetDecoder.getFieldPrefix(fieldLookup.getKeyField())); + + ModelTable modelTable = ModelTableCache.getInstance().getByName(lookup.getTable()); + + if (modelTable == null) { + loader.addError(ModelLoaderError.ERROR_UI, "未找到数据表, table= " + lookup.getDb() + "." + lookup.getTable()); + return; + } + + dataset.addField(modelTable.getIdField(), "optId", UiEnum.FieldRefType.ID); + dataset.addField(fieldLookup.getKeyField(), IParamConst.OPTION_KEY); + dataset.addField(fieldLookup.getLabelField(), IParamConst.OPTION_LABEL, UiEnum.FieldRefType.TITLE); + dataset.addField(fieldLookup.getOrderField(), IParamConst.OPTION_ORDER, UiEnum.FieldRefType.ORDER); + dataset.setFieldPrefix(YamlDatasetDecoder.getFieldPrefix(modelTable.getIdField())); + + if (isTree) { + if (fieldLookup.getParentField() == null) { + loader.addError(ModelLoaderError.ERROR_UI, "树控件未配置上级字段, field= " + field.getName()); + return; + } + + dataset.addField(fieldLookup.getParentField(), "optParentId", UiEnum.FieldRefType.PARENT_ID); + } + + if (fieldLookup.isManyToMany()) { + modelTable = ModelTableCache.getInstance().getByName(fieldLookup.getMiddleTable()); + + if (modelTable == null) { + loader.addError(ModelLoaderError.ERROR_UI, "未找到中间数据表, table= " + lookup.getDb() + "." + lookup.getTable()); + return; + } + + fieldLookup.setMiddleIdField(modelTable.getIdField()); + } + + // 级联属性设置,不初始化,与级联库关联(master) + if (fieldLookup.getFilters() != null) { + for (BpmFieldFilter bpmFieldFilter: fieldLookup.getFilters()) { + if (bpmFieldFilter.isCascade()) { +// pageRef.addCascade(bpmDataset.getMasterMapping().getMaster(), bpmFieldFilter.getKeyword(), bpmDataset.getName(), bpmFieldFilter.getField()); +// dataset.setCascade(true); + dataset.getMasterMapping().setMaster(masterName); + dataset.setInitData(false); + } + } + } + + if (dataset.getIdField() == null) { + loader.addError(ModelLoaderError.ERROR_TABLE, "数据表未设置ID字段 " + dataset.getTable() + ", db: " + dataset.getDatabase()); + } + } + + addDataset(result, new SlaveDataset(dataset, null, null, null)); + } else { + int dataModel = isTree ? BpmDataset.DATA_MODEL_TREE : BpmDataset.DATA_MODEL_LIST; + if (datasetWrapper.getDataModel() != dataModel) { + loader.addError(ModelLoaderError.ERROR_UI, "多控件关联数据集时数据模型冲突 " + name); + } + } + } + + private boolean isNumberType(String type) { + return "fz-field-int".equals(type) || "fz-field-short".equals(type) || "fz-field-byte".equals(type); + } + + private int toInt(Integer value) { + return value != null ? value : 0; + } + + private ModelDatabase getModelDatabase(String datasource) { + ModelDatabase result = databases.get(datasource); + + if(result != null) { + return result; + } + + return ModelDatabaseCache.getInstance().get(datasource); + } + +// public void addLookupDataset(BpmPage result, BpmField bpmField, boolean isTree) { +// BpmDataset dataset = this.lookupDatasetMap.get(bpmField); +// if (dataset != null) { +// // 查找页面路由 +// addLookupDataset(result, dataset, bpmField, isTree); +// } else { +// loader.addError(ModelLoaderError.ERROR_UI, "未找到lookup数据集, field= " + bpmField.getName()); +// } +// } +} + diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/json/JsonPageDecoder.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/json/JsonPageDecoder.java new file mode 100644 index 0000000..a6c47ce --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/json/JsonPageDecoder.java @@ -0,0 +1,385 @@ +package cc.smtweb.system.bpm.engine.ui.loader.json; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.system.bpm.core.exception.ModelLoaderError; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.dataset.FieldFormatTool; +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.core.ui.entity.dataset.AbstractBpmFieldLookup; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmField; +import cc.smtweb.system.bpm.core.ui.entity.dataset.IBpmDatasetConst; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmForm; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmWidget; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmWidgetLayout; +import cc.smtweb.system.bpm.engine.AbstractLoader; +import cc.smtweb.system.bpm.engine.ui.entity.vo.widiget.*; +import cc.smtweb.system.bpm.engine.ui.loader.IPageRouter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class JsonPageDecoder { + private static final int LIST_MODEL_SELECT = 1000; + private static final int LIST_MODEL_SELECT_TREE = IBpmDatasetConst.DATA_MODEL_TREE + LIST_MODEL_SELECT; + private static final int LIST_MODEL_SELECT_LIST = IBpmDatasetConst.DATA_MODEL_LIST + LIST_MODEL_SELECT; + + private AbstractLoader loader; + private IPageRouter pageRouter; + private JsonDatasetDecoder jsonDatasetDecoder; + + private final static Map CONTROL_LIST_MODEL = new HashMap() {{ + put("fx-list", IBpmDatasetConst.DATA_MODEL_LIST); + put("fx-table", IBpmDatasetConst.DATA_MODEL_LIST); + put("fy-list", IBpmDatasetConst.DATA_MODEL_LIST); + put("fy-table", IBpmDatasetConst.DATA_MODEL_LIST); + put("fx-tree", IBpmDatasetConst.DATA_MODEL_TREE); + put("fy-tree", IBpmDatasetConst.DATA_MODEL_TREE); + put("fx-select", LIST_MODEL_SELECT); + put("fy-select", LIST_MODEL_SELECT); + put("fx-select-tree", LIST_MODEL_SELECT_TREE); + put("fy-select-tree", LIST_MODEL_SELECT_TREE); + put("fx-select-table", LIST_MODEL_SELECT_LIST); + }}; + + public JsonPageDecoder(AbstractLoader loader, IPageRouter pageRouter) { + this.loader = loader; + this.pageRouter = pageRouter; + jsonDatasetDecoder = new JsonDatasetDecoder(loader); + } + + public BpmPage decode(String content) { + UiPageVO uiPageVO = JsonUtil.parse(content, UiPageVO.class); + + if (uiPageVO.getVersion() == null || uiPageVO.getVersion() < 5) { + log.error("界面版本不能小于 5.0; version=" + uiPageVO.getVersion()); + return new BpmPage(); + } + + BpmPage result = new BpmPage(); + + // 处理DataSet + jsonDatasetDecoder.decode(result, uiPageVO); + + // 处理界面控件 + UiPagePropsVO pageProps = null; + for (UiPageFormVO formVO: uiPageVO.getForms()) { + BpmForm form = new BpmForm(); + fixUiForm(result, form, formVO.getGraph()); + result.getForms().add(form); + + // 处理主界面熟悉 + if (pageProps == null) { + pageProps = formVO.getWidget().getProps(); + if (pageProps != null) { + result.setDestroyOnClose(pageProps.getDestroyOnClose()); + } + } + } + return result; + } + + /** 处理界面控件 */ + private void fixUiForm(BpmPage result, BpmWidget widget, UiControlVO parent) { + /* + 控件进行dataset, 通用规则 data[dataset][dataModel][field] + 卡片界面控件设置 data[datasetId].form[fieldId] + 列表控件设置 data[datasetId].list.data + */ + UiControlDataVO controlData = parent.getData(); + // 校正数据,设置数据集 + BpmDataset dataset = fixContrData(controlData); + + widget.setName(controlData.getName()); + widget.setType(controlData.getType()); + widget.addProps(controlData.getProps()); + widget.addSlots(controlData.getSlots()); + widget.addVars(controlData.getVars()); + widget.addEvents(controlData.getEvents()); + + if (dataset != null) { + fixWidget(result, widget, dataset, controlData); + } + + // 设置布局尺寸 + UiControlLayoutVO layout = controlData.getLayout(); + if (layout != null && layout.getCol() != null) { + widget.setLayout(new BpmWidgetLayout(layout.getCol())); + } + + // String datasetId = props.readString("dataset"); +// if (StringUtils.isNotBlank(datasetId)) { +// BpmDataset dataset = jsonDatasetDecoder.findDataset(datasetId); +// +// if (dataset != null) { +// fixWidget(result, widget, dataset, controlData); +// +// // 转换 id 为 name +// widget.addProp("dataset", dataset.getName()); +// } else { +// loader.addError(ModelLoaderError.ERROR_UI, controlData.getId(),"控件关联数据集未找到, datasetId= " + datasetId); +// } +// } + + // 处理panel的子控件 + UiControlVO[] children = parent.getChildren(); + if (children != null && children.length > 0) { + BpmWidget[] widgetChildren = new BpmWidget[children.length]; + for (int i = 0; i < children.length; i++) { + BpmWidget childWidget = new BpmWidget(); + fixUiForm(result, childWidget, children[i]); + widgetChildren[i] = childWidget; + } + + widget.setChildren(widgetChildren); + } + + // 处理插槽的控件 + if (widget.getSlots() != null) { + for (UiControlDataVO[] slotWidgets: widget.getSlots().values()) { + for (UiControlDataVO slotWidget: slotWidgets) { + fixContrData(slotWidget); + if (dataset != null) { + fixSlotWidget(slotWidget, dataset, controlData.getId()); + } + } + } + } + } + + // 设置table.column的显示字段 + private void fixSlotWidget(UiControlDataVO controlWidget, BpmDataset dataset, String widgetId) { + SwMap props = controlWidget.getProps(); + String field = props.readString("field"); + if (StringUtils.isNotBlank(field)) { + BpmField bpmField = dataset.findFieldById(field, true); + if (bpmField != null) { + String fieldName = bpmField.getName(); + if (StringUtils.isBlank(fieldName)) { + fieldName = bpmField.getId(); + } + + // 切换到显示字段 + if (bpmField.getLookup() != null || FieldFormatTool.needFormat(bpmField)) { + props.put("field", fieldName + IParamConst.LOOKUP_TEXT); + } else { + props.put("field", fieldName); + } + +// fixWidgetFormat(widget, map, bpmField); + } else { + loader.addError(ModelLoaderError.ERROR_UI, widgetId, + controlWidget.getId(), "未找到表格控件的列字段 " + field); + } + } + } + + // 预处理控件属性,包括slot控件 + private BpmDataset fixContrData(UiControlDataVO controlData) { + SwMap props = controlData.getProps(); + // 保持name不为空 + String name = props.readString("name"); + if (StringUtils.isNotBlank(name)) { + controlData.setName(name); + } else { + controlData.setName(controlData.getId()); + } + + // 处理button链接地址 + if (controlData.getType().endsWith("-button")) { + String link = (String) props.get("link"); + if (StringUtils.isNotBlank(link)) { + Long pageId = Long.valueOf(link); + // 查找页面路由 + String routerName = pageRouter.loadFullName(pageId); + if (routerName != null) { + props.put("link", routerName); + } else { + loader.addError(ModelLoaderError.ERROR_UI, "控件关联页面未找到, pageId= " + pageId); + } +// pageRef.getRoutes().put("id_" + pageId, routerName); + } + } + + // 处理数据集,转换id为name + String datasetId = props.readString("dataset"); + if (StringUtils.isNotBlank(datasetId)) { + BpmDataset dataset = jsonDatasetDecoder.findDataset(datasetId); + + if (dataset != null) { + // 转换 id 为 name + props.put("dataset", dataset.getName()); + } else { + loader.addError(ModelLoaderError.ERROR_UI, controlData.getId(),"控件关联数据集未找到, datasetId= " + datasetId); + } + return dataset; + } + + return null; + } + + // 主控件检测数据模型和处理lookup + private void fixWidget(BpmPage result, BpmWidget widget, BpmDataset dataset, UiControlDataVO controlData) { + UiControlPropsVO controlProps = controlData.getProps(); + String field = controlProps.readString("field"); + + int listModel = getListMode(controlData.getType(), field); + + if (listModel == IBpmDatasetConst.DATA_MODEL_LIST) { + if (dataset.getDataModel() != IBpmDatasetConst.DATA_MODEL_LIST) { + loader.addError(ModelLoaderError.ERROR_UI, controlData.getId(), "控件需要引用列表模式数据集 " + dataset.getName()); + } + + // 1. field id => name, 2. fix lookup field +// fixColumnField(widget, dataset, readList(widget.getProps(), "columns"), controlData.getId()); + } else if (listModel == IBpmDatasetConst.DATA_MODEL_TREE) { + if (dataset.getDataModel() != IBpmDatasetConst.DATA_MODEL_TREE) { + loader.addError(ModelLoaderError.ERROR_UI, controlData.getId(), "控件需要引用树模式数据集 " + dataset.getName()); + } + } else { + if (StringUtils.isNotBlank(field)) { + BpmField bpmField = dataset.findFieldById(field); + + if (bpmField != null) { + // 处理关联表 + AbstractBpmFieldLookup lookup = bpmField.getLookup(); + + if (lookup != null) { +// controlProps.put("lookup", lookup); + if (listModel >= LIST_MODEL_SELECT) { // || listModel == LIST_MODEL_SELECT_TREE + // 设置使用了查找数据集,后面进行实际添加 + jsonDatasetDecoder.addLookupDataset(result, dataset, bpmField, listModel == LIST_MODEL_SELECT_TREE); + } + } + + widget.addProp("field", bpmField.getName()); +// fixWidgetFormat(widget, controlProps, bpmField); +// datasetProps.addRule(bpmField.getName(), validType, required, validPatten, validMessage); + } else { + loader.addError(ModelLoaderError.ERROR_UI, controlData.getId(), "控件未找到字段, id= " + field); + } + } + } + + // 处理 button 的链接地址 +// fixPageRef(widget); + } + + private int getListMode(String controlType, String field) { + Integer type = CONTROL_LIST_MODEL.get(controlType); + + if (type != null) { + if (StringUtils.isBlank(field) || type >= LIST_MODEL_SELECT) { + return type; + } else { + return type + LIST_MODEL_SELECT; + } + } + + return IBpmDatasetConst.DATA_MODEL_CARD; + } + + /** 完善table列field,切换到显示字段 */ +// private void fixColumnField(BpmWidget widget, BpmDataset dataset, List columns, String widgetId) { +// if (columns != null) { +// for (Object item: columns) { +// if (item instanceof Map) { +// Map column = (Map) item; +// Map map = (Map)column.get("props"); +// +// String field = MapUtil.readString(map, "field"); +// if (StringUtils.isNotBlank(field)) { +// BpmField bpmField = dataset.findFieldById(field, true); +// if (bpmField != null) { +// String fieldName = bpmField.getName(); +// if (StringUtils.isBlank(fieldName)) { +// fieldName = bpmField.getId(); +// } +// +// // 切换到显示字段 +// if (bpmField.getLookup() != null || FieldFormatTool.needFormat(bpmField)) { +// map.put("field", fieldName + IParamConst.LOOKUP_TEXT); +// } else { +// map.put("field", fieldName); +// } +// +// } else { +// loader.addError(ModelLoaderError.ERROR_UI, widgetId, +// MapUtil.readString(column, "id"), "未找到表格控件的列字段 " + field); +// } +// } +// } +// } +// } +// } + +// public List readList(SwMap map, String name) { +// Object v = map.get(name); +// +// if (v instanceof List) { +// return (List)v; +// } +// +// return null; +// } + + // pageId -> 转化为pagePath +// private void fixPageRef(BpmWidget widget) { +// SwMap controlPropsVO = widget.getProps(); +// +// switch (widget.getType()) { +// case "fx-table": +// case "fy-table": +// fillButtons(controlPropsVO, "columnButtons"); +// fillButtons(controlPropsVO, "toolbarButtons"); +// // table.columnButtons, table.toolbarButtons +// break; +// case "fx-button-group": +// case "fz-button-group": +// fillButtons(controlPropsVO, "buttons"); +// // button-group.buttons +// break; +// case "fx-button": +// case "fz-button": +// fillButton(controlPropsVO); +// break; +// default: +// break; +// } +// } +// +// private void fillButtons(SwMap controlPropsVO, String name) { +// Object props = controlPropsVO.get(name); +// if (props instanceof List) { +// for (Object prop: (List)props) { +// if (prop instanceof Map) { +// Object controlProps = ((Map)prop).get("props"); +// if (controlProps instanceof Map) { +// fillButton((Map) controlProps); +// } +// } +// } +//// log.error("" + props.getClass()); +// } +// } +// +// private void fillButton(Map controlPropsVO) { +// String link = (String)controlPropsVO.get("link"); +// if (StringUtils.isNotBlank(link)) { +// Long pageId = Long.valueOf(link); +// // 查找页面路由 +// String routerName = pageRouter.loadFullName(pageId); +// if(routerName != null) { +// controlPropsVO.put("link", routerName); +// } else { +// loader.addError(ModelLoaderError.ERROR_UI, "控件关联页面未找到, pageId= " + pageId); +// } +//// pageRef.getRoutes().put("id_" + pageId, routerName); +// } +// } +} + diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/json/SlaveDataset.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/json/SlaveDataset.java new file mode 100644 index 0000000..54c7cdc --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/json/SlaveDataset.java @@ -0,0 +1,31 @@ +package cc.smtweb.system.bpm.engine.ui.loader.json; + +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.engine.ui.entity.vo.dataset.FzMasterMappingVO; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +@Getter +class SlaveDataset { + private BpmDataset dataset; + private String id; + private String masterId; + private FzMasterMappingVO masterMapping; + + public SlaveDataset(BpmDataset dataset, String id, String masterId, FzMasterMappingVO masterMapping) { + this.dataset = dataset; + this.id = id; + this.masterId = masterId; + if (StringUtils.isNotBlank(masterId) && masterMapping != null && masterMapping.getFields() != null) { + this.masterMapping = masterMapping; + } + } + + public String getMasterId() { + return masterId; + } + + public boolean isSlave() { + return masterMapping != null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlDatasetDecoder.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlDatasetDecoder.java new file mode 100644 index 0000000..2669ecd --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlDatasetDecoder.java @@ -0,0 +1,164 @@ +package cc.smtweb.system.bpm.engine.ui.loader.yaml; + +import cc.smtweb.system.bpm.core.exception.BpmException; +import cc.smtweb.system.bpm.core.ui.entity.dataset.*; +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; +import cc.smtweb.system.bpm.engine.ui.entity.yaml.*; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +public class YamlDatasetDecoder { + private YamlDatasetDecoder() {} + + public static BpmDataset decode(YamlDataset yamlDataset) { + BpmDataset bpmDataset = new BpmDataset(); + bpmDataset.setName(yamlDataset.getName()); + bpmDataset.setDatabase(yamlDataset.getDatabase()); + bpmDataset.setTable(yamlDataset.getTable()); + bpmDataset.setDataModel(decodeDataModel(yamlDataset.getDataModel())); + bpmDataset.setReadonly(yamlDataset.isReadonly()); + bpmDataset.setCustomApi(StringUtils.trimToNull(yamlDataset.getCustomApi())); + bpmDataset.setInitData(decodeBoolean(yamlDataset.getInitData())); + bpmDataset.setSql(decodeSql(yamlDataset.getSql())); + decodeMasterMapping(bpmDataset.getMasterMapping(), yamlDataset.getMasterMapping()); + + yamlDataset.getFields().forEach((field) -> { + BpmField bpmField = new BpmField(); + + bpmField.setType(field.getType()); + bpmField.setName(field.getName()); + bpmField.setLabel(field.getLabel()); + bpmField.setDisplayFormat(field.getDisplayFormat()); + bpmField.setFieldName(field.getFieldName()); + bpmField.setSummary(field.getSummary()); + bpmField.setLookup(decodeLookup(field.getLookup())); + bpmField.setRefType(decodeRefType(field.getRefType())); + bpmField.setRequired(decodeBoolean(field.getRequired())); + bpmField.setReadonly(decodeBoolean(field.getReadonly())); + bpmField.setValidPatten(StringUtils.trimToNull(field.getValidPatten())); + bpmField.setValidMessage(StringUtils.trimToNull(field.getValidMessage())); + bpmField.setFilter(field.getFilter()); + bpmField.setFilterType(field.getFilterType()); + + bpmDataset.addField(bpmField); + }); + + bpmDataset.setFieldPrefix(getFieldPrefix(bpmDataset.readIdFieldName())); + + return bpmDataset; + } + + private static BpmDatasetSql decodeSql(YamlDatasetSql sql) { + if (sql != null && StringUtils.isNotBlank(sql.getSelect())) { + BpmDatasetSql result = new BpmDatasetSql(); + result.setSelect(sql.getSelect()); + + if (StringUtils.isNotBlank(sql.getInsert())) { + result.setInsert(sql.getInsert()); + } + + if (StringUtils.isNotBlank(sql.getUpdate())) { + result.setUpdate(sql.getUpdate()); + } + + return result; + } + + return null; + } + + private static boolean decodeBoolean(Boolean value) { + return Boolean.TRUE.equals(value); + } + + private static int decodeRefType(String refType) { + Integer value = UiEnum.FieldRefType.INSTANCE.getKey(refType); + + if (value != null) { + return value; + } + + return 0; + } + + private static void decodeMasterMapping(BpmMasterMapping bpmMasterMapping, YamlMasterMapping masterMapping) { + if (masterMapping != null && masterMapping.getMappingFields() != null && !MappingTypeEnum.none.equals(masterMapping.getMappingType())) { + bpmMasterMapping.setMappingType(MappingTypeEnum.toEnum(masterMapping.getMappingType())); + bpmMasterMapping.setMaster(masterMapping.getMasterTable()); + bpmMasterMapping.setMiddle(masterMapping.getMiddleTable()); + + List mappingFields = new ArrayList<>(masterMapping.getMappingFields().size()); + for (YamlMappingField mappingField: masterMapping.getMappingFields()) { + BpmMappingField bpmMappingField = new BpmMappingField(); + bpmMappingField.setMaster(mappingField.getMasterField()); + bpmMappingField.setSlave(mappingField.getSlaveField()); + bpmMappingField.setMiddle(mappingField.getMiddleField()); + + mappingFields.add(bpmMappingField); + } + + bpmMasterMapping.setMappingFields(mappingFields); + } + } + + private static int decodeDataModel(String dataModel) { + switch (dataModel) { + case "card": + return BpmDataset.DATA_MODEL_CARD; + case "list": + return BpmDataset.DATA_MODEL_LIST; + case "tree": + return BpmDataset.DATA_MODEL_TREE; + default: + throw new BpmException(String.format("不支持的refType类型:%s", dataModel)); + } + } + + protected static AbstractBpmFieldLookup decodeLookup(YamlFieldLookup lookup) { + if (lookup != null) { + int lookupType = UiEnum.FieldLookupType.INSTANCE.getKey(lookup.getType()); + if (lookupType == UiEnum.FieldLookupType.DICT) { + String dictType = lookup.getFilters().get(0).getKeyword(); + return new BpmFieldDict(lookup.getDb(), lookup.getTable(), lookup.isNumberValue(), dictType); + } else if (lookupType == UiEnum.FieldLookupType.TABLE) { + BpmFieldLookup bpmFieldLookup = new BpmFieldLookup(lookup.getDb(), lookup.getTable(), lookup.isNumberValue(), lookup.getMappingType()); + bpmFieldLookup.setKeyField(lookup.getKeyField()); + bpmFieldLookup.setLabelField(lookup.getLabelField()); + bpmFieldLookup.setOrderField(lookup.getOrderField()); + bpmFieldLookup.setParentField(lookup.getParentField()); + + if (bpmFieldLookup.isManyToMany()) { + YamlFieldLookupMiddle middle = lookup.getMiddle(); + bpmFieldLookup.setMiddleTable(middle.getTable()); + bpmFieldLookup.setMiddleIdField(middle.getIdField()); + bpmFieldLookup.setMiddleKeyField(middle.getKeyField()); + bpmFieldLookup.setMiddleValueField(middle.getValueField()); + } + + if (lookup.getFilters() != null) { + for (YamlFieldFilter yamlFieldFilter : lookup.getFilters()) { + bpmFieldLookup.addFilter(yamlFieldFilter.getField(), yamlFieldFilter.getOp(), yamlFieldFilter.getKeyword(), yamlFieldFilter.isCascade()); + } + } + + return bpmFieldLookup; + } + } + + return null; + } + + public static String getFieldPrefix(String idField) { + if (idField != null) { + String[] ary = idField.split("_"); + if (ary.length > 0) { + return ary[0]; + } + } + + return null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlDatasetEncoder.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlDatasetEncoder.java new file mode 100644 index 0000000..d10b363 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlDatasetEncoder.java @@ -0,0 +1,194 @@ +package cc.smtweb.system.bpm.engine.ui.loader.yaml; + +import cc.smtweb.system.bpm.core.exception.BpmException; +import cc.smtweb.system.bpm.core.ui.entity.dataset.*; +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import cc.smtweb.system.bpm.engine.ui.entity.consts.UiEnum; +import cc.smtweb.system.bpm.engine.ui.entity.yaml.*; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +public class YamlDatasetEncoder { + private BpmDataset dataset; + + public YamlDatasetEncoder(BpmDataset dataset) { + this.dataset = dataset; + } + + public YamlDataset encode() { + YamlDataset yamlDataset = new YamlDataset(); + yamlDataset.setName(dataset.getName()); + yamlDataset.setDatabase(dataset.getDatabase()); + yamlDataset.setTable(dataset.getTable()); + yamlDataset.setDataModel(encodeDataModel(dataset.getDataModel())); + yamlDataset.setReadonly(dataset.isReadonly()); + yamlDataset.setCustomApi(StringUtils.trimToNull(dataset.getCustomApi())); + yamlDataset.setInitData(encodeBoolean(dataset.isInitData())); + yamlDataset.setMasterMapping(encodeMasterMapping(dataset.getMasterMapping())); + yamlDataset.setSql(encodeSql(dataset.getSql())); + + List yamlFields = new ArrayList<>(); + dataset.getFields().forEach((name, field) -> { + YamlField yamlField = new YamlField(); + + yamlField.setType(field.getType()); + yamlField.setName(field.getName()); + yamlField.setLabel(field.getLabel()); + yamlField.setDisplayFormat(field.getDisplayFormat()); + yamlField.setFieldName(field.getFieldName()); + yamlField.setSummary(field.getSummary()); + yamlField.setLookup(encodeLookup(field.getLookup())); + yamlField.setRefType(encodeRefType(field.getRefType())); + yamlField.setRequired(encodeBoolean(field.isRequired())); + yamlField.setReadonly(encodeBoolean(field.isReadonly())); + yamlField.setValidPatten(StringUtils.trimToNull(field.getValidPatten())); + yamlField.setValidMessage(StringUtils.trimToNull(field.getValidMessage())); + yamlField.setFilter(field.getFilter()); + yamlField.setFilterType(field.getFilterType()); + + yamlFields.add(yamlField); + }); + + yamlDataset.setFields(yamlFields); + + return yamlDataset; + } + + private YamlDatasetSql encodeSql(BpmDatasetSql sql) { + if (sql != null && StringUtils.isNotBlank(sql.getSelect())) { + YamlDatasetSql result = new YamlDatasetSql(); + result.setSelect(sql.getSelect()); + + if (StringUtils.isNotBlank(sql.getInsert())) { + result.setInsert(sql.getInsert()); + } + + if (StringUtils.isNotBlank(sql.getUpdate())) { + result.setUpdate(sql.getUpdate()); + } + + return result; + } + + return null; + } + + private Boolean encodeBoolean(boolean value) { + return value ? Boolean.TRUE : null; + } + + private YamlMasterMapping encodeMasterMapping(BpmMasterMapping masterMapping) { + if (masterMapping != null && masterMapping.getMappingFields() != null && !MappingTypeEnum.none.equals(masterMapping.getMappingType())) { + YamlMasterMapping yamlMasterMapping = new YamlMasterMapping(); + yamlMasterMapping.setMappingType(masterMapping.getMappingType().name()); + yamlMasterMapping.setMasterTable(masterMapping.getMaster()); + yamlMasterMapping.setMiddleTable(masterMapping.getMiddle()); + + List mappingFields = new ArrayList<>(masterMapping.getMappingFields().size()); + for (BpmMappingField mappingField: masterMapping.getMappingFields()) { + YamlMappingField yamlMappingField = new YamlMappingField(); + yamlMappingField.setMasterField(mappingField.getMaster()); + yamlMappingField.setSlaveField(mappingField.getSlave()); + yamlMappingField.setMiddleField(mappingField.getMiddle()); + + mappingFields.add(yamlMappingField); + } + + yamlMasterMapping.setMappingFields(mappingFields); + + return yamlMasterMapping; + } + + return null; + } + + private YamlFieldLookup encodeLookup(AbstractBpmFieldLookup lookup) { + YamlFieldLookup yamlFieldLookup = null; + if (lookup != null) { + String lookupType = UiEnum.FieldLookupType.INSTANCE.getValue(lookup.type()); + yamlFieldLookup = new YamlFieldLookup(); + yamlFieldLookup.setType(lookupType); + yamlFieldLookup.setNumberValue(lookup.isNumber()); + yamlFieldLookup.setMappingType(lookup.getMappingType()); + yamlFieldLookup.setDb(lookup.getDb()); + yamlFieldLookup.setTable(lookup.getTable()); + + if (lookup.type() == UiEnum.FieldLookupType.DICT) { + BpmFieldDict bpmFieldDict = (BpmFieldDict)lookup; + yamlFieldLookup.setKeyField("dict_code"); + yamlFieldLookup.setLabelField("dict_label"); + yamlFieldLookup.setOrderField("dict_order"); + + List yamlFieldFilters = new ArrayList<>(1); + YamlFieldFilter yamlFieldFilter = new YamlFieldFilter(); + yamlFieldFilter.setField("dt_name"); + yamlFieldFilter.setKeyword(bpmFieldDict.getDictType()); + yamlFieldFilters.add(yamlFieldFilter); + + yamlFieldLookup.setFilters(yamlFieldFilters); + } else if (lookup.type() == UiEnum.FieldLookupType.TABLE) { + BpmFieldLookup bpmFieldLookup = (BpmFieldLookup)lookup; + yamlFieldLookup.setKeyField(bpmFieldLookup.getKeyField()); + yamlFieldLookup.setLabelField(bpmFieldLookup.getLabelField()); + yamlFieldLookup.setOrderField(bpmFieldLookup.getOrderField()); + yamlFieldLookup.setParentField(bpmFieldLookup.getParentField()); + + if (bpmFieldLookup.isManyToMany()) { + YamlFieldLookupMiddle middle = new YamlFieldLookupMiddle(); + middle.setTable(bpmFieldLookup.getMiddleTable()); + middle.setIdField(bpmFieldLookup.getMiddleIdField()); + middle.setKeyField(bpmFieldLookup.getMiddleKeyField()); + middle.setValueField(bpmFieldLookup.getMiddleValueField()); + + yamlFieldLookup.setMiddle(middle); + } + + if (bpmFieldLookup.getFilters() != null) { + List yamlFieldFilters = new ArrayList<>(); + for (BpmFieldFilter filter : bpmFieldLookup.getFilters()) { + YamlFieldFilter yamlFieldFilter = new YamlFieldFilter(); + yamlFieldFilter.setField(filter.getField()); + yamlFieldFilter.setKeyword(filter.getKeyword()); + yamlFieldFilter.setOp(filter.getOp()); + yamlFieldFilter.setCascade(filter.isCascade()); + yamlFieldFilters.add(yamlFieldFilter); + } + yamlFieldLookup.setFilters(yamlFieldFilters); + } + } + } + + return yamlFieldLookup; + } + + private String encodeRefType(int refType) { + if (refType == 0) { + return null; + } + + String result = UiEnum.FieldRefType.INSTANCE.getValue(refType); + + if (result != null) { + return result; + } + + throw new BpmException(String.format("%s不支持的refType类型:%d", dataset.getName(), refType)); + } + + private String encodeDataModel(int dataModel) { + switch (dataModel) { + case BpmDataset.DATA_MODEL_CARD: + return "card"; + case BpmDataset.DATA_MODEL_LIST: + return "list"; + case BpmDataset.DATA_MODEL_TREE: + return "tree"; + case 0: + throw new BpmException(String.format("未设置%s.dataModel类型", dataset.getName())); + default: + throw new BpmException(String.format("不支持的%s.dataModel类型:%d", dataset.getName(), dataModel)); + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlPageDecoder.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlPageDecoder.java new file mode 100644 index 0000000..1ced008 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlPageDecoder.java @@ -0,0 +1,64 @@ +package cc.smtweb.system.bpm.engine.ui.loader.yaml; + +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmForm; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmWidget; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmWidgetLayout; +import cc.smtweb.system.bpm.engine.ui.entity.yaml.*; +import cc.smtweb.system.bpm.util.YamlUtil; + +import java.io.InputStream; +import java.util.List; + +// TODO 关联信息的正确性检测 +public class YamlPageDecoder { + public YamlPageDecoder() { + } + + public BpmPage decode(InputStream is) { + YamlPage yamlPage = YamlUtil.readValue(is, YamlPage.class); + + BpmPage bpmPage = new BpmPage(); + bpmPage.setId(yamlPage.getId()); + bpmPage.setDestroyOnClose(yamlPage.getDestroyOnClose()); + + List datasets = yamlPage.getDatasets(); + for (YamlDataset yamlDataset: datasets) { + bpmPage.addDataset(YamlDatasetDecoder.decode(yamlDataset)); + } + + for (BpmForm form: bpmPage.getForms()) { +// BpmForm form = bpmPage.getForm(); + YamlForm yamlForm = new YamlForm(); + decodeWidget(form ,yamlForm); + yamlPage.getForms().add(yamlForm); + } + + return bpmPage; + } + + private void decodeWidget(BpmWidget bpmForm, YamlWidget yamlForm) { + bpmForm.setType(yamlForm.getType()); + bpmForm.setName(yamlForm.getName()); + + YamlWidgetLayout layout = yamlForm.getLayout(); + if (layout != null && layout.getCol() != null) { + bpmForm.setLayout(new BpmWidgetLayout(layout.getCol())); + } + + bpmForm.addProps(yamlForm.getProps()); + bpmForm.addEvents(yamlForm.getEvents()); + + YamlWidget[] children = yamlForm.getChildren(); + if (children != null && children.length > 0) { + BpmWidget[] bpmWidgets = new BpmWidget[children.length]; + for (int i = 0; i < children.length; i++) { + BpmWidget bpmWidget = new BpmWidget(); + decodeWidget(bpmWidget, children[i]); + bpmWidgets[i] = bpmWidget; + } + + bpmForm.setChildren(bpmWidgets); + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlPageEncoder.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlPageEncoder.java new file mode 100644 index 0000000..9a90237 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/engine/ui/loader/yaml/YamlPageEncoder.java @@ -0,0 +1,44 @@ +package cc.smtweb.system.bpm.engine.ui.loader.yaml; + +import cc.smtweb.system.bpm.core.ui.entity.form.BpmForm; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmWidget; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmWidgetLayout; +import cc.smtweb.system.bpm.engine.ui.entity.yaml.YamlForm; +import cc.smtweb.system.bpm.engine.ui.entity.yaml.YamlWidget; +import cc.smtweb.system.bpm.engine.ui.entity.yaml.YamlWidgetLayout; + +public class YamlPageEncoder { + public static YamlForm encode(BpmForm form) { + YamlForm yamlForm = new YamlForm(); + encodeWidget(yamlForm, form); + + return yamlForm; + } + + private static void encodeWidget(YamlWidget yamlForm, BpmWidget bpmForm) { + yamlForm.setType(bpmForm.getType()); + yamlForm.setName(bpmForm.getName()); + + BpmWidgetLayout layout = bpmForm.getLayout(); + if (layout != null && layout.getCol() > 1) { + YamlWidgetLayout yamlWidgetLayout = new YamlWidgetLayout(); + yamlWidgetLayout.setCol(layout.getCol()); + yamlForm.setLayout(yamlWidgetLayout); + } + + yamlForm.addProps(bpmForm.getProps()); + yamlForm.addEvents(bpmForm.getEvents()); + + BpmWidget[] children = bpmForm.getChildren(); + if (children != null && children.length > 0) { + YamlWidget[] yamlWidgets = new YamlWidget[children.length]; + for (int i = 0; i < children.length; i++) { + YamlWidget yamlWidget = new YamlWidget(); + encodeWidget(yamlWidget, children[i]); + yamlWidgets[i] = yamlWidget; + } + + yamlForm.setChildren(yamlWidgets); + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/BpmApplication.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/BpmApplication.java new file mode 100644 index 0000000..3c3543b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/BpmApplication.java @@ -0,0 +1,14 @@ +package cc.smtweb.system.bpm.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author xkliu + */ +@SpringBootApplication +public class BpmApplication { + public static void main(String[] args) { + SpringApplication.run(BpmApplication.class, args); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/BpmAutoConfiguration.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/BpmAutoConfiguration.java new file mode 100644 index 0000000..0a6e36a --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/BpmAutoConfiguration.java @@ -0,0 +1,33 @@ +package cc.smtweb.system.bpm.spring; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.mvc.config.ControllerConfig; +import cc.smtweb.framework.core.mvc.controller.binder.ParamEditor; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.bind.BpmBeanAttrEditor; +import cc.smtweb.system.bpm.core.ui.bind.BpmMapAttrEditor; +import cc.smtweb.system.bpm.core.ui.bind.BpmPageContextEditor; +import cc.smtweb.system.bpm.spring.action.BpmEventDao; +import cc.smtweb.system.bpm.spring.action.BpmEventLoader; +import cc.smtweb.system.bpm.spring.cache.BpmPageCache; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * @author kevin + */ +@Configuration +@ComponentScan +public class BpmAutoConfiguration { + /** 配置自定义service扫描路径 {module}/{service}/{method} */ + @Bean + public ControllerConfig bpmControllerConfig() { + return new ControllerConfig("bpm", "cc.smtweb.system.bpm.web", (applicationContext, webDataBinder) -> { + webDataBinder.bind(ParamEditor.TYPE_PARAM, BpmPageContext.class, new BpmPageContextEditor()); + webDataBinder.bind(ParamEditor.TYPE_ATTR, SwMap.class, new BpmMapAttrEditor()); + webDataBinder.bindDefault(ParamEditor.TYPE_ATTR, new BpmBeanAttrEditor()); + return new BpmEventLoader(applicationContext.getBean(BpmEventDao.class)); + }); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/BpmConfigBean.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/BpmConfigBean.java new file mode 100644 index 0000000..8adb65e --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/BpmConfigBean.java @@ -0,0 +1,20 @@ +package cc.smtweb.system.bpm.spring; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "smtweb.bpm") +@Data +public class BpmConfigBean { + // 是否debug模式 + private boolean debug; + private boolean codeBuild; + private String codeVuePath; + private String codeVueDir; + private String codeJavaPath; + private String codeJavaPackage; + /** UI控件扩展定义路径 */ + private String configUiPath; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/IProcessEngine.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/IProcessEngine.java new file mode 100644 index 0000000..ad43a57 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/IProcessEngine.java @@ -0,0 +1,49 @@ +package cc.smtweb.system.bpm.spring; + +import cc.smtweb.system.bpm.engine.process.impl.entity.CommitTaskUserVO; +import cc.smtweb.system.bpm.engine.process.runtime.RouteInfo; +import cc.smtweb.system.bpm.engine.process.task.Task; +import cc.smtweb.system.bpm.engine.process.vo.FlowAction; +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import cc.smtweb.system.bpm.engine.ui.IFormDataStore; + +import java.util.List; + +public interface IProcessEngine { + /** + * 新建任务,一般在流程开始时新建 + * @param flowId + * @param nodeKey + * @return + */ + Task newTask(long flowId, String nodeKey); + + /** + * 保存任务数据 + * @param task + * @return + */ + Task saveTask(Task task); + +// void claim(long taskId, String userId); +// +// void unclaim(String taskId); + + /** + * 完成任务 + * @param task + */ + void commitTask(Task task, FlowAction flowAction, List taskCommitInfo); + + void deleteTask(Task task); + + Task loadTask(long taskId); + + /** 提交路由 */ + RouteInfo routeCommit(Task task, FlowNode toFlowNode); + + /** 驳回路由 */ + RouteInfo routeReturn(Task task); + + IFormDataStore getFormDataStore(); +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/ProcessEngineImpl.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/ProcessEngineImpl.java new file mode 100644 index 0000000..f1f9913 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/ProcessEngineImpl.java @@ -0,0 +1,323 @@ +package cc.smtweb.system.bpm.spring; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.core.exception.BpmObjectNotFoundException; +import cc.smtweb.system.bpm.core.exception.BpmIlegalArgumentException; +import cc.smtweb.system.bpm.engine.process.impl.dao.TaskDao; +import cc.smtweb.system.bpm.engine.process.impl.entity.CommitTaskUserVO; +import cc.smtweb.system.bpm.engine.process.runtime.*; +import cc.smtweb.system.bpm.engine.process.task.Task; +import cc.smtweb.system.bpm.engine.process.vo.Flow; +import cc.smtweb.system.bpm.engine.process.vo.FlowAction; +import cc.smtweb.system.bpm.engine.process.vo.FlowConnection; +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import cc.smtweb.system.bpm.engine.ui.IFormDataStore; +import cc.smtweb.system.bpm.engine.ui.impl.FormDataStoreImpl; +import cc.smtweb.system.bpm.spring.cache.BpmFlowCache; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.sql.Timestamp; +import java.util.List; + +@Service +public class ProcessEngineImpl implements IProcessEngine { + private DbEngine dbEngine; + private TaskDao taskDao; + + private IFormDataStore formDataStore; + + public ProcessEngineImpl(DbEngine dbEngine) { + taskDao = new TaskDao(dbEngine); + this.dbEngine = dbEngine; + this.formDataStore = new FormDataStoreImpl(dbEngine); + } + + @Override + public Task newTask(long flowId, String nodeKey) { + ProcessInstance processInstance = taskDao.startProcessInstanceByKey(flowId, nodeKey); + + FlowNode flowNode; + + if (nodeKey == null) { + // 找到第一个执行节点,读取字段权限 + flowNode = processInstance.nextNode(FlowAction.TYPE_COMMIT); + } else { + flowNode = processInstance.getFlow().findNode(nodeKey); + } + + if (flowNode == null) { + throw new BpmObjectNotFoundException("not find node by key: " + nodeKey); + } + + processInstance.setCurNode(flowNode, nodeKey == null); + return new Task(dbEngine.nextId(), processInstance); + } + + @Override + public Task saveTask(Task task) { + Timestamp now = new Timestamp(System.currentTimeMillis()); + + ProcessInstance processInstance = task.getProcessInstance(); + taskDao.saveProcess(processInstance, now); + taskDao.saveTask(task, now); + + return task; + } + + private CommitTaskUserVO findTaskUser(List users, String nodeKey) { + for (CommitTaskUserVO user : users) { + // 判断flowNode的合法性和必填性 + if (nodeKey.equals(user.getNodeKey())) { + return user; + } + } + + return null; + } + + @Override + public void commitTask(Task task, FlowAction flowAction, List users) { + switch (flowAction.getType()) { + case FlowAction.TYPE_COMMIT: + FlowNode flowNode = task.getProcessInstance().getFlow().checkAndGetNode(flowAction.checkAndGetConnection().getToNodeId()); + RouteInfo nextProcessInfo = this.routeCommit(task, flowNode); + commitRoute(task, nextProcessInfo, users); + break; + case FlowAction.TYPE_TRANSMIT: + // 转发 + CommitTaskUserVO commitTaskUser = users.get(0); + task.setAssigneeUserId(commitTaskUser.getUserId()); + saveTask(task); + break; + case FlowAction.TYPE_RETURN: + // 1. 终止所有同级子流程 + // 2. 回到来源节点(可能是多个) + nextProcessInfo = this.routeReturn(task); + commitRoute(task, nextProcessInfo, users); + break; + default: + throw new BpmIlegalArgumentException("error action type: " + flowAction.getType()); + } + } + + public void commitRoute(Task task, RouteInfo nextProcessInfo, List users) { + ProcessInstance processInstance = task.getProcessInstance(); + Timestamp now = new Timestamp(System.currentTimeMillis()); + + dbEngine.doTrans(() -> { + for (Route route: nextProcessInfo.getRoutes()) { + FlowNode flowNode = route.getNode(); + if (flowNode != null) { + CommitTaskUserVO user = findTaskUser(users, flowNode.getId()); + + // 判断flowNode的合法性和必填性 + if (user == null) { + throw new BpmObjectNotFoundException("not find task user by node key: " + flowNode.getId()); + } + + Long userId = user.getUserId(); + Task nextTask; + + switch (flowNode.getType()) { + case FlowNode.TYPE_END: + case FlowNode.TYPE_MERGE: + break; + case FlowNode.TYPE_MANUAL: + if (userId == null) { + throw new BpmIlegalArgumentException("not set assignee user"); + } + default: + ProcessInstance subProcess = route.getProcess(); + nextTask = new Task(dbEngine.nextId(), subProcess); + + nextTask.setAssigneeUserId(userId); + this.saveTask(nextTask); + break; + } + } else { + taskDao.deleteProcess(route.getProcess()); + } + } + + taskDao.deleteTask(task); + + return true; + }); + } + + @Override + public void deleteTask(Task task) { + taskDao.deleteTask(task); + } + + @Override + public Task loadTask(long taskId) { + return taskDao.loadTask(taskId); + } + + @Override + public RouteInfo routeCommit(Task task, FlowNode toFlowNode) { + RouteInfo result = new RouteInfo(); + fillNextNodes(result, task.getProcessInstance(), toFlowNode); + return result; + } + + @Override + public IFormDataStore getFormDataStore() { + return formDataStore; + } + + private void fillNextNodes(RouteInfo result, ProcessInstance processInstance, FlowNode toFlowNode) { + Flow flow = processInstance.getFlow(); + + switch (toFlowNode.getType()) { + case FlowNode.TYPE_END: + result.add(processInstance, null); + case FlowNode.TYPE_AUTO: + case FlowNode.TYPE_MANUAL: + result.add(processInstance, toFlowNode); + break; + case FlowNode.TYPE_SPLIT: { + List flowActions = toFlowNode.getActions(); + if (flowActions != null) { + for (FlowAction splitFlowAction : flowActions) { + FlowConnection splitFlowConnection = splitFlowAction.checkAndGetConnection(); + FlowNode splitFlowNode = flow.checkAndGetNode(splitFlowConnection.getToNodeId()); + + // create subProcess + ProcessInstance subProcess = processInstance.createSubProcess(dbEngine.nextId(), splitFlowNode); + subProcess.setConcurrent(true); + fillNextNodes(result, subProcess, splitFlowNode); + } + } + } + break; + case FlowNode.TYPE_MERGE: + // 结束并行流程 + result.add(processInstance, null); + + ProcessInstance parentProcess = taskDao.loadProcess(processInstance.getParentId()); + + if (parentProcess == null) { + throw new BpmObjectNotFoundException("not find process by id " + processInstance.getParentId()); + } + + ProcessRouteItem processRouteItem = parentProcess.getProcessRoute().findRouteByProcess(processInstance.getId()); + + if (processRouteItem == null) { + throw new BpmObjectNotFoundException("not find route by process id " + processInstance.getId()); + } + + // TODO 调整为通过路由列表来判断是否最后一个执行人 + if (dbEngine.queryInt("select count(*) from sw_bpm.bpm_task where task_process_id in (" + StringUtils.join(processRouteItem.getSub(), ",") + ")") == 1) { + FlowAction nextFlowAction = toFlowNode.findAction(FlowAction.TYPE_COMMIT); + + if (nextFlowAction == null) { + throw new BpmObjectNotFoundException("未找到下一步Action, node_id=" + toFlowNode.getId()); + } + + // 回到上级流程继续执行 + FlowConnection nextFlowConnection = nextFlowAction.checkAndGetConnection(); + FlowNode nextFlowNode = flow.checkAndGetNode(nextFlowConnection.getToNodeId()); + + fillNextNodes(result, parentProcess, nextFlowNode); + } + break; + case FlowNode.TYPE_CONDITION: { + List flowActions = toFlowNode.getActions(); + if (flowActions != null) { + for (FlowAction action : flowActions) { + if (checkCondition(action, processInstance)) { + FlowConnection splitFlowConnection = action.checkAndGetConnection(); + FlowNode splitFlowNode = flow.checkAndGetNode(splitFlowConnection.getToNodeId()); + fillNextNodes(result, processInstance, splitFlowNode); + break; + } + } + } + } + break; + default: + break; + } + } + + @Override + public RouteInfo routeReturn(Task task) { + RouteInfo result = new RouteInfo(); + + task.getProcessInstance().getProcessRoute().returnRoute(); + fillReturnNodes(result, task.getProcessInstance(), true); + + return result; + } + + private void fillReturnNodes(RouteInfo result, ProcessInstance processInstance, boolean resume) { + ProcessRouteItem routeItem = processInstance.getProcessRoute().lastRoute(); + if (routeItem != null) { + if (routeItem.getNode() != null) { + // 恢复一个节点 + result.add(processInstance, processInstance.getFlow().checkAndGetNode(routeItem.getNode())); + } else { + List ids = routeItem.getSub(); + if (resume) { + // 恢复并行流程 + for (int i = 0; i < ids.size(); i++) { + // 到历史记录中读取子流程 + ProcessInstance subProcess = taskDao.loadProcessByLog(ids.get(i)); + if (subProcess == null) { + throw new BpmObjectNotFoundException("not find process log by id " + ids.get(i)); + } + + subProcess.resume(dbEngine.nextId()); + ids.set(i, subProcess.getId()); + fillReturnNodes(result, subProcess, true); + } + } else { + // 停止同级并行流程 + List processList = taskDao.loadProcessList(ids); + if (processList != null) { + for (ProcessInstance pi: processList) { + result.add(pi, null); + } + } + } + } + } else if (processInstance.getParentId() != null) { + // 读取上级流程 + ProcessInstance parentProcess = taskDao.loadProcess(processInstance.getParentId()); + fillReturnNodes(result, parentProcess, false); + } + } + + /** SpEL 表达式计算 */ + private boolean checkCondition(FlowAction action, ProcessInstance processInstance) { + String expression = action.getExpression(); + if (StringUtils.isBlank(expression)) { + return true; + } + + ConditionExpression conditionExpression = processInstance.getConditionExpression(); + if (conditionExpression == null) { + ProcessContext rootObject = createProcessContext(processInstance); + + conditionExpression = new ConditionExpression(rootObject); + processInstance.setConditionExpression(conditionExpression); + } + + Boolean value = conditionExpression.execute(expression, Boolean.class); + return value != null ? value: false; + } + + private ProcessContext createProcessContext(ProcessInstance processInstance) { + ProcessContext context = new ProcessContext(); + + context.setVars(taskDao.loadVars(processInstance)); + + // 读取表单数据 + context.setData(this.formDataStore.load(processInstance.getId(), processInstance.getRootId())); + + return context; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/BpmEventDao.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/BpmEventDao.java new file mode 100644 index 0000000..d0e19a5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/BpmEventDao.java @@ -0,0 +1,195 @@ +package cc.smtweb.system.bpm.spring.action; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.mvc.controller.access.IMethodAccess; +import cc.smtweb.system.bpm.core.annotation.BpmEvent; +import cc.smtweb.system.bpm.core.annotation.BpmEventType; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import cc.smtweb.system.bpm.core.ui.service.card.AbstractDatasetCardService; +import cc.smtweb.system.bpm.core.ui.service.list.AbstractDatasetListService; +import cc.smtweb.system.bpm.core.ui.service.tree.AbstractDatasetTreeService; +import cc.smtweb.system.bpm.spring.action.service.BpmActionCardService; +import cc.smtweb.system.bpm.spring.action.service.BpmActionListService; +import cc.smtweb.system.bpm.spring.action.service.BpmActionTreeService; +import cc.smtweb.system.bpm.spring.dao.DatasetCardDao; +import cc.smtweb.system.bpm.spring.dao.DatasetConfigDao; +import cc.smtweb.system.bpm.spring.dao.DatasetListDao; +import cc.smtweb.system.bpm.spring.dao.DatasetTreeDao; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * BpmEvent事件处理Dao + * @author xkliu + */ +@Slf4j +@Service +public class BpmEventDao { + private DbEngine dbEngine; + private DatasetConfigDao datasetConfigDao; + private DatasetCardDao datasetCardDao; + private DatasetListDao datasetListDao; + private DatasetTreeDao datasetTreeDao; + + private Map cardServiceMap = new HashMap<>(); + private Map listServiceMap = new HashMap<>(); + private Map treeServiceMap = new HashMap<>(); + + private BpmActionCardService defaultActionCardService; + private BpmActionListService defaultActionListService; + private BpmActionTreeService defaultActionTreeService; + + public BpmEventDao(DbEngine dbEngine, DatasetConfigDao datasetConfigDao, + DatasetCardDao datasetCardDao, DatasetListDao datasetListDao, DatasetTreeDao datasetTreeDao) { + this.dbEngine = dbEngine; + this.datasetConfigDao = datasetConfigDao; + this.datasetCardDao = datasetCardDao; + this.datasetListDao = datasetListDao; + this.datasetTreeDao = datasetTreeDao; + + defaultActionCardService = new BpmActionCardService(dbEngine, datasetConfigDao, datasetCardDao, null); + defaultActionListService = new BpmActionListService(dbEngine, datasetConfigDao, datasetListDao, null); + defaultActionTreeService = new BpmActionTreeService(dbEngine, datasetConfigDao, datasetTreeDao, null); + } + + // 系统启动时添加扫描的BpmEvent + // 检测是否重复设置和相同动作时类型匹配 + void addAction(BpmEvent bpmEvent, IMethodAccess methodAccess) { + switch (bpmEvent.event()) { + case BpmEventType.CARD_BEFORE_LOAD: + buildCardService(bpmEvent).setBeforeLoad(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.CARD_LOAD: { + buildCardService(bpmEvent).setLoad(new DatasetApiInvoker(methodAccess)); + } + break; + case BpmEventType.CARD_AFTER_LOAD: + buildCardService(bpmEvent).setAfterLoad(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.CARD_BEFORE_SAVE: + buildCardService(bpmEvent).setBeforeSave(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.CARD_SAVE: + buildCardService(bpmEvent).setSave(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.CARD_AFTER_SAVE: + buildCardService(bpmEvent).setAfterSave(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.CARD_BEFORE_REMOVE: + buildCardService(bpmEvent).setBeforeRemove(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.CARD_REMOVE: + buildCardService(bpmEvent).setRemove(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.CARD_AFTER_REMOVE: + buildCardService(bpmEvent).setAfterRemove(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.LIST_BEFORE_LIST: + buildListService(bpmEvent).setBeforeList(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.LIST_LIST: + buildListService(bpmEvent).setList(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.LIST_AFTER_LIST: + buildListService(bpmEvent).setAfterList(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.LIST_BEFORE_COUNT: + buildListService(bpmEvent).setBeforeCount(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.LIST_AFTER_COUNT: + buildListService(bpmEvent).setAfterCount(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.LIST_COUNT: + buildListService(bpmEvent).setCount(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.TREE_BEFORE_TREE: + buildTreeService(bpmEvent).setBeforeTreeAll(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.TREE_AFTER_TREE: + buildTreeService(bpmEvent).setAfterTreeAll(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.TREE_TREE: + buildTreeService(bpmEvent).setTreeAll(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.TREE_BEFORE_MOVE: + buildTreeService(bpmEvent).setBeforeMove(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.TREE_AFTER_MOVE: + buildTreeService(bpmEvent).setAfterMove(new DatasetApiInvoker(methodAccess)); + break; + case BpmEventType.TREE_MOVE: + buildTreeService(bpmEvent).setMove(new DatasetApiInvoker(methodAccess)); + break; + default: + log.error("unknown type :" + bpmEvent.event()); + break; + } + } + + private BpmActionCardService buildCardService(BpmEvent bpmEvent) { + BpmActionCardService result = cardServiceMap.get(bpmEvent.dataset()); + if (result == null) { + result = new BpmActionCardService(dbEngine, datasetConfigDao, datasetCardDao, bpmEvent.dataset()); + cardServiceMap.put(bpmEvent.dataset(), result); + } + + return result; + } + + private BpmActionListService buildListService(BpmEvent bpmEvent) { + BpmActionListService result = listServiceMap.get(bpmEvent.dataset()); + if (result == null) { + result = new BpmActionListService(dbEngine, datasetConfigDao, datasetListDao, bpmEvent.dataset()); + listServiceMap.put(bpmEvent.dataset(), result); + } + + return result; + } + + private BpmActionTreeService buildTreeService(BpmEvent bpmEvent) { + BpmActionTreeService result = treeServiceMap.get(bpmEvent.dataset()); + if (result == null) { + result = new BpmActionTreeService(dbEngine, datasetConfigDao, datasetTreeDao, bpmEvent.dataset()); + treeServiceMap.put(bpmEvent.dataset(), result); + } + + return result; + } + + public AbstractDatasetCardService getCardService(BpmPageContext pageContext) { + String dataset = pageContext.getPageName() + "." + pageContext.getDataset().getName(); + BpmActionCardService result = cardServiceMap.get(dataset); + if (result == null) { + // null就返回默认service + result = this.defaultActionCardService; + } + + return result; + } + + public AbstractDatasetListService getListService(BpmPageContext pageContext) { + String dataset = pageContext.getPageName() + "." + pageContext.getDataset().getName(); + BpmActionListService result = listServiceMap.get(dataset); + if (result == null) { + // null就返回默认service + result = this.defaultActionListService; + } + + return result; + } + + public AbstractDatasetTreeService getTreeService(BpmPageContext pageContext) { + String dataset = pageContext.getPageName() + "." + pageContext.getDataset().getName(); + BpmActionTreeService result = treeServiceMap.get(dataset); + if (result == null) { + // null就返回默认service + result = this.defaultActionTreeService; + } + + return result; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/BpmEventLoader.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/BpmEventLoader.java new file mode 100644 index 0000000..0ec6b91 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/BpmEventLoader.java @@ -0,0 +1,27 @@ +package cc.smtweb.system.bpm.spring.action; + +import cc.smtweb.framework.core.mvc.controller.IActionManager; +import cc.smtweb.framework.core.mvc.controller.access.IMethodAccess; +import cc.smtweb.framework.core.mvc.controller.scan.IScanAction; +import cc.smtweb.system.bpm.core.annotation.BpmEvent; + +public class BpmEventLoader implements IScanAction { + private BpmEventDao bpmEventDao; + + public BpmEventLoader(BpmEventDao bpmActionService) { + this.bpmEventDao = bpmActionService; + } + + @Override + public Class actionType() { + return BpmEvent.class; + } + + @Override + public void load(IActionManager apiManager, String module, BpmEvent webAction, IMethodAccess methodAccess) { +// WebMethodAccess webMethodAccess = new WebMethodAccess(methodAccess); +// apiManager.api("web/webAction", webMethodAccess); + // 给 bpmActionService 添加数据集扩展实现,先判断是否有重复 + bpmEventDao.addAction(webAction, methodAccess); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/service/BpmActionCardService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/service/BpmActionCardService.java new file mode 100644 index 0000000..548c69e --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/service/BpmActionCardService.java @@ -0,0 +1,115 @@ +package cc.smtweb.system.bpm.spring.action.service; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import cc.smtweb.system.bpm.core.ui.service.card.AbstractDatasetCardService; +import cc.smtweb.system.bpm.core.ui.service.card.DatasetCardWorker; +import cc.smtweb.system.bpm.core.ui.service.card.DatasetCardAgentWorker; +import cc.smtweb.system.bpm.spring.dao.DatasetCardDao; +import cc.smtweb.system.bpm.spring.dao.DatasetConfigDao; +import lombok.Setter; + +/** + * 动态界面Card数据服务 + * @author xkliu + */ +public class BpmActionCardService extends AbstractDatasetCardService { + protected DatasetCardDao datasetCardDao; + // 动态数据集 + private String dataset; + // 检测是否重复设置 +// @Setter +// protected BiFunction loadFunc; +// @Setter +// protected BiFunction saveFunc; +// @Setter +// protected BiFunction removeFunc; +// @Setter +// protected BiFunction removeListFunc; +// @Setter +// protected BiFunction loadLookupFunc; + + @Setter + private DatasetApiInvoker beforeLoad; + @Setter + private DatasetApiInvoker afterLoad; + @Setter + private DatasetApiInvoker load; + @Setter + private DatasetApiInvoker beforeSave; + @Setter + private DatasetApiInvoker afterSave; + @Setter + private DatasetApiInvoker save; + @Setter + private DatasetApiInvoker beforeRemove; + @Setter + private DatasetApiInvoker afterRemove; + @Setter + private DatasetApiInvoker remove; + + public BpmActionCardService(DbEngine dbEngine, DatasetConfigDao datasetConfigDao, DatasetCardDao datasetCardDao, String dataset) { + this.dataset = dataset; + super.init(dbEngine, datasetConfigDao); + this.datasetCardDao = datasetCardDao; + } + + @Override + protected DatasetCardWorker createCardLoadHandler(BpmPageContext pageContext) { + datasetConfigDao.loadDataset(pageContext, dataset); + DatasetCardAgentWorker worker = new DatasetCardAgentWorker(pageContext); + + worker.handleLoad(datasetCardDao::load); + worker.handleLoadLookup(datasetCardDao::loadLookup); + + if (beforeLoad != null) { + worker.onBeforeLoad(beforeLoad); + } + + if (afterLoad != null) { + worker.onAfterLoad(afterLoad); + } + + if (load != null) { + worker.onLoad(load); + } + + return worker; + } + + @Override + protected DatasetCardWorker createCardSaveHandler(BpmPageContext pageContext) { + datasetConfigDao.loadDataset(pageContext, dataset); + DatasetCardAgentWorker worker = new DatasetCardAgentWorker(pageContext); + worker.handleSave(datasetCardDao::save); + + if (beforeSave != null) { + worker.onBeforeSave(beforeSave); + } + + if (afterSave != null) { + worker.onAfterSave(afterSave); + } + + if (save != null) { + worker.onSave(save); + } + + return worker; + } + + @Override + protected DatasetCardWorker createCardRemoveHandler(BpmPageContext pageContext) { + datasetConfigDao.loadDataset(pageContext, dataset); + DatasetCardAgentWorker worker = new DatasetCardAgentWorker(pageContext); + + worker.handleRemove(datasetCardDao::remove); + + worker.onBeforeRemove(beforeRemove); + worker.onAfterRemove(afterRemove); + worker.onRemove(remove); + + return worker; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/service/BpmActionListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/service/BpmActionListService.java new file mode 100644 index 0000000..5a1eba2 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/service/BpmActionListService.java @@ -0,0 +1,99 @@ +package cc.smtweb.system.bpm.spring.action.service; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import cc.smtweb.system.bpm.core.ui.service.list.AbstractDatasetListService; +import cc.smtweb.system.bpm.core.ui.service.list.DatasetListWorker; +import cc.smtweb.system.bpm.core.ui.service.list.DatasetListAgentWorker; +import cc.smtweb.system.bpm.spring.dao.DatasetConfigDao; +import cc.smtweb.system.bpm.spring.dao.DatasetListDao; +import lombok.Setter; + +/** + * 动态界面List数据服务 + * @author xkliu + */ +public class BpmActionListService extends AbstractDatasetListService { + protected DatasetListDao datasetListDao; + private String dataset; + + @Setter + private DatasetApiInvoker beforeCount; + @Setter + private DatasetApiInvoker afterCount; + @Setter + private DatasetApiInvoker count; + + @Setter + private DatasetApiInvoker beforeList; + @Setter + private DatasetApiInvoker list; + @Setter + private DatasetApiInvoker afterList; + + @Setter + private DatasetApiInvoker listAll; + + public BpmActionListService(DbEngine dbEngine, DatasetConfigDao datasetConfigDao, DatasetListDao datasetListDao, String dataset) { + this.dataset = dataset; + super.init(dbEngine, datasetConfigDao); + this.datasetListDao = datasetListDao; + } + + @Override + protected DatasetListWorker createListPartHandler(BpmPageContext pageContext) { + datasetConfigDao.loadDataset(pageContext, dataset); + + DatasetListAgentWorker worker = new DatasetListAgentWorker(pageContext); + + worker.handleListPart(datasetListDao::list); + worker.handleCount(datasetListDao::count); + worker.handleListLookup(datasetListDao::listLookup); + + if (beforeCount != null) { + worker.onBeforeCount(beforeCount); + } + if (afterCount != null) { + worker.onAfterCount(afterCount); + } + if (count != null) { + worker.onCount(count); + } + if (beforeList != null) { + worker.onBeforeList(beforeList); + } + if (afterList != null) { + worker.onAfterList(afterList); + } + if (list != null) { + worker.onListPart(list); + } + + return worker; + } + + @Override + protected DatasetListWorker createListAllHandler(BpmPageContext pageContext) { + datasetConfigDao.loadDataset(pageContext, dataset); + + DatasetListAgentWorker worker = new DatasetListAgentWorker(pageContext); + + worker.handleListAll(datasetListDao::listAll); + worker.handleListLookup(datasetListDao::listLookup); + + if (beforeList != null) { + worker.onBeforeList(beforeList); + } + + if (afterList != null) { + worker.onAfterList(afterList); + } + + if (listAll != null) { + worker.onListAll(listAll); + } + + return worker; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/service/BpmActionTreeService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/service/BpmActionTreeService.java new file mode 100644 index 0000000..7b51f1b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/action/service/BpmActionTreeService.java @@ -0,0 +1,79 @@ +package cc.smtweb.system.bpm.spring.action.service; + +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.DatasetApiInvoker; +import cc.smtweb.system.bpm.core.ui.service.tree.AbstractDatasetTreeService; +import cc.smtweb.system.bpm.core.ui.service.tree.DatasetTreeWorker; +import cc.smtweb.system.bpm.core.ui.service.tree.DatasetTreeAgentWorker; +import cc.smtweb.system.bpm.spring.dao.DatasetConfigDao; +import cc.smtweb.system.bpm.spring.dao.DatasetTreeDao; +import lombok.Setter; + +/** + * 动态界面List数据服务 + * @author xkliu + */ +public class BpmActionTreeService extends AbstractDatasetTreeService { + protected DatasetTreeDao datasetTreeDao; + private String dataset; + + @Setter + private DatasetApiInvoker beforeMove; + @Setter + private DatasetApiInvoker afterMove; + @Setter + private DatasetApiInvoker move; + @Setter + private DatasetApiInvoker beforeTreeAll; + @Setter + private DatasetApiInvoker treeAll; + @Setter + private DatasetApiInvoker afterTreeAll; + + public BpmActionTreeService(DbEngine dbEngine, DatasetConfigDao datasetConfigDao, DatasetTreeDao datasetTreeDao, String dataset) { + this.dataset = dataset; + super.init(dbEngine, datasetConfigDao); + this.datasetTreeDao = datasetTreeDao; + } + + @Override + protected DatasetTreeWorker createTreeAllHandler(BpmPageContext pageContext) { + datasetConfigDao.loadDataset(pageContext, dataset); + + DatasetTreeAgentWorker worker = new DatasetTreeAgentWorker(pageContext); + worker.handleTreeAll(datasetTreeDao::treeAll); + + if (beforeTreeAll != null) { + worker.onBeforeTreeAll(beforeTreeAll); + } + if (afterTreeAll != null) { + worker.onAfterTreeAll(afterTreeAll); + } + if (treeAll != null) { + worker.onTreeAll(treeAll); + } + + return worker; + } + + @Override + protected DatasetTreeWorker createTreeMoveHandler(BpmPageContext pageContext) { + datasetConfigDao.loadDataset(pageContext, dataset); + + DatasetTreeAgentWorker worker = new DatasetTreeAgentWorker(pageContext); + worker.handleMove(datasetTreeDao::move); + + if (beforeMove != null) { + worker.onBeforeMove(beforeMove); + } + if (afterMove != null) { + worker.onAfterMove(afterMove); + } + if (move != null) { + worker.onMove(move); + } + + return worker; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/cache/BpmFlowCache.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/cache/BpmFlowCache.java new file mode 100644 index 0000000..305d337 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/cache/BpmFlowCache.java @@ -0,0 +1,49 @@ +package cc.smtweb.system.bpm.spring.cache; + +import cc.smtweb.framework.core.annotation.SwCache; +import cc.smtweb.framework.core.cache.AbstractCache; +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.engine.entity.AspModelPO; +import cc.smtweb.system.bpm.engine.process.loader.FlowLoader; +import cc.smtweb.system.bpm.engine.process.vo.Flow; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.List; + +@SwCache(ident = "BPM_FLOW", title = "工作流定义") +@Slf4j +public class BpmFlowCache extends AbstractCache { + @Autowired + private DbEngine dbEngine; + + public static BpmFlowCache getInstance() { + return CacheManager.getIntance().getCache(BpmFlowCache.class); + } + + @Override + protected String getId(Flow bean) { + return String.valueOf(bean.getId()); + } + + @Override + protected List loadAll() { + String sql = "select model_id, model_mc_id, model_site_id, model_name, model_status, model_content, model_type, model_version, model_create_uid, model_update_uid, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_type=?"; + List list = dbEngine.query(sql, AspModelPO.class, 2); + List listRet = new ArrayList<>(list.size()); + for (AspModelPO po: list) { + FlowLoader flowLoader = new FlowLoader(); + Flow flow = flowLoader.load(po.getModelContent()); + + if (flow == null) { + log.error(flowLoader.getLastError()); + } else { + listRet.add(flow); + } + } + return listRet; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/cache/BpmPageCache.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/cache/BpmPageCache.java new file mode 100644 index 0000000..7b44065 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/cache/BpmPageCache.java @@ -0,0 +1,209 @@ +package cc.smtweb.system.bpm.spring.cache; + +import cc.smtweb.framework.core.annotation.SwCache; +import cc.smtweb.framework.core.cache.AbstractCache; +import cc.smtweb.framework.core.cache.CacheManager; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.engine.entity.AspModelPO; +import cc.smtweb.system.bpm.engine.entity.AspModelTreeVO; +import cc.smtweb.system.bpm.engine.ui.loader.*; +import cc.smtweb.system.bpm.spring.BpmConfigBean; +import cc.smtweb.system.bpm.util.ITreeDataLevelHandler; +import cc.smtweb.system.bpm.util.TreeDataUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +@SwCache(ident = "BPM_PAGE", title = "页面设计", lazy = true) +@Slf4j +public class BpmPageCache extends AbstractCache { + private final static String fn = "n"; + @Autowired + private DbEngine dbEngine; + + @Autowired + private BpmConfigBean bpmConfigBean; + + public static BpmPageCache getInstance() { + return CacheManager.getIntance().getCache(BpmPageCache.class); + } + + public BpmPageCache() { + regMap(fn, BpmPage::getFullName); + } + + @Override + protected String getId(BpmPage bean) { + return String.valueOf(bean.getId()); + } + + @Override + protected List loadAll() { + return null; + } + + protected BpmPage load(Long key) { + BpmPage result = null; + + if (key != null) { + IPageRouter pageRouter = null; + if (bpmConfigBean.isDebug()) { + // 设计界面界面数据 + // json内容 + String sql = "select model_id, model_mc_id, model_parent_id, model_site_id, model_key, model_name, model_status, model_content, model_type, model_version, model_create_uid, model_update_uid, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_id=? and model_type in (3,4)"; + AspModelPO po = dbEngine.queryEntity(sql, AspModelPO.class, key); + + // 项目树 + ITreeDataLevelHandler handler = AspModelTreeVO.createTreeHandler(); + + List list = dbEngine.query("select model_id, model_parent_id, model_key, model_name from sw_bpm.asp_model where model_mc_id=?", + AspModelTreeVO.class, po.getModelMcId()); + AspModelTreeVO root = new AspModelTreeVO(); + TreeDataUtil.buildLevelTree(root, list, handler); + String moduleName = dbEngine.queryString("select mc_module from sw_bpm.asp_model_catalog where mc_id=?", po.getModelMcId()); + + pageRouter = new TreePageRouter(root, moduleName); + result = loadFromJson(po, pageRouter); + } else { + // 部署界面数据 + String sql = "select model_id, model_site_id, model_full_name, model_name, model_status, model_content, model_type, model_version, model_create_time, model_last_time from sw_bpm.bpm_model" + + " where model_id=? and model_type in (3,4)"; + AspModelPO po = dbEngine.queryEntity(sql, AspModelPO.class, key); + + if (po != null) { + // TODO 路由缓存性能 + sql = "select model_id, model_site_id, model_full_name, model_name, model_status, model_type from sw_bpm.bpm_model" + + " where model_type=? and model_status=?"; + List list = dbEngine.query(sql, AspModelPO.class, po.getModelType(), 0); + pageRouter = new ListPageRouter(list); + result = loadFromJson(po, pageRouter); + } + } + + if (result != null && pageRouter != null) { + result.setFullName(pageRouter.loadFullName(key)); + } + } + + return result; + } + + private BpmPage loadFromJson(AspModelPO po, IPageRouter pageRouter) { + BpmPage result = null; + if (po != null) { + UiPageLoader loader; + + if (po.getModelType() == AspModelPO.TYPE_UI_PC) { + loader = new UiPcLoader(pageRouter); + } else { + loader = new UiMobileLoader(pageRouter); + } + + result = loader.load(po.getModelContent()); + + if (result == null) { + log.error(loader.getLastError()); + } else { + // 表存储或者json存储设置 + result.setDeploy(true); + result.setId(po.getModelId()); +// result.getForm().setKey(po.getModelKey()); +// result.getForm().setTitle(po.getModelName()); +// result.setParentId(po.getModelParentId()); +// result.setUpdateAt(NumberTimeUtil.toTimestamp(po.getModelLastTime())); + } + } + + return result; + } + +// public BpmPage get(Long pageId) { +// return super.get(String.valueOf(pageId)); +// } + + public BpmDataset getDataset(String key) { + BpmDataset result = null; + int pos = key.lastIndexOf('.'); + + if (pos > 0) { + BpmPage bpmPage = loadFromOtherKey(key.substring(0, pos)); + if (bpmPage != null) { + result = bpmPage.findDatasetByName(key.substring(pos + 1)); + if (result == null) { + log.error("not find dataset in page:" + key); + } + } + } + + return result; + } + + public BpmPage getByOtherKey(String otherKey) { + BpmPage bean = localGetByKey(fn, otherKey); + if (bean == null) { + bean = loadFromOtherKey(otherKey); + if (bean != null) { + this.put(bean); + } + } + return bean; + } + + protected BpmPage loadFromOtherKey(String key) { + BpmPage result = null; + + if (key != null) { + if (bpmConfigBean.isDebug()) { + // 部署界面数据 + String sql = "select model_id, model_site_id, model_full_name, model_name, model_status, model_content, model_type, model_version, model_use_vue, model_use_yaml, model_create_time, model_last_time from sw_bpm.bpm_model" + + " where model_full_name=? and model_type in (3,4)"; + AspModelPO po = dbEngine.queryEntity(sql, AspModelPO.class, key); + + if (po != null) { + if (po.getModelUseYaml() != 0) { + result = loadFromYaml(key); + } else { + // TODO 路由缓存性能 + sql = "select model_id, model_site_id, model_full_name, model_name, model_status, model_type from sw_bpm.bpm_model" + + " where model_type=? and model_status=?"; + List list = dbEngine.query(sql, AspModelPO.class, po.getModelType(), 0); + result = loadFromJson(po, new ListPageRouter(list)); + } + } + } else { + result = loadFromYaml(key); + } + } + + if (result != null) { + result.setFullName(key); + } + + return result; + } + + // 读yaml配置文件 + private BpmPage loadFromYaml(String key) { + // 读取其他包的资源文件不能以 "/" 开头 + String path = "plugin/" + key.replace('.', '/') + ".yaml"; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try (InputStream is = classLoader.getResourceAsStream(path)) { + if (is != null) { + UiPageLoader loader = new UiPcLoader(null); + return loader.load(is); + } else { + log.error("not find resource: " + path); + } + } catch (IOException e) { + log.error("load resource error: " + path, e); + } + + return null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/cache/ListPageRouter.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/cache/ListPageRouter.java new file mode 100644 index 0000000..b8dfb4c --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/cache/ListPageRouter.java @@ -0,0 +1,28 @@ +package cc.smtweb.system.bpm.spring.cache; + +import cc.smtweb.system.bpm.engine.entity.AspModelPO; +import cc.smtweb.system.bpm.engine.ui.loader.IPageRouter; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ListPageRouter implements IPageRouter { + private Map map = new HashMap<>(); + + public ListPageRouter(List list) { + for (AspModelPO item: list) { + map.put(item.getModelId(), item); + } + } + + @Override + public String loadFullName(Long pageId) { + AspModelPO po = map.get(pageId); + if (po != null) { + return po.getModelFullName(); + } + + return null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/AbstractDatasetPlugin.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/AbstractDatasetPlugin.java new file mode 100644 index 0000000..f6c82ac --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/AbstractDatasetPlugin.java @@ -0,0 +1,248 @@ +package cc.smtweb.system.bpm.spring.dao; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.sqlbuilder.AbstractSelectSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlJoinTable; +import cc.smtweb.system.bpm.core.ui.builder.SqlUtil; +import cc.smtweb.system.bpm.core.ui.entity.dataset.*; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Map; + +@Slf4j +public abstract class AbstractDatasetPlugin { + @Autowired + protected DbEngine dbEngine; + + protected AbstractSelectSqlBuilder makeSqlBuilderForTable(BpmDataset dataset, SwMap body, boolean countSql) { + AbstractSelectSqlBuilder sqlBuilder = createSqlBuilderForTable(dataset, countSql); + + List> filter = (List>)body.get(SqlUtil.PN_FILTER); + if (filter != null) { + addSqlWhereForTable(dataset, sqlBuilder, filter, false); + } + + List> search = (List>) body.get(SqlUtil.PN_SEARCH); + + if (search != null) { + int filterType = body.readInt(SqlUtil.PN_SEARCH_TYPE, 0); + + addSqlWhereForTable(dataset, sqlBuilder, search, filterType != 0); + } + + return sqlBuilder; + } + + protected AbstractSelectSqlBuilder createSqlBuilderForTable(BpmDataset dataset, boolean countSql) { + AbstractSelectSqlBuilder sqlBuilder = null; + + if (dataset.getDatabase() != null) { + sqlBuilder = SqlBuilder.createSelect(dataset.getDatabase(), dataset.getTable()); + } else if (dataset.getSql() != null){ + sqlBuilder = SqlBuilder.createDirectSelect(dataset.getSql().getSelect()); + } else { + return null; + } + + if (countSql) { + sqlBuilder.add("count(*) total"); + + // 主表统计字段 + addSummaryFields(dataset, sqlBuilder, ""); + + // 1:1从表统计字段 + List slaves = dataset.getOneToOneDataset(); + if (slaves != null) { + int index = 0; + for (BpmDataset slave: slaves) { + String tableAlias = "b" + (++index); + if (addSummaryFields(slave, sqlBuilder, tableAlias + ".")) { + addJoinTable(sqlBuilder, slave, tableAlias); + } + } + } + } else { + // 主表字段 + // TODO: 后期不需要返回ID字段,控件设置具体id字段值 + sqlBuilder.add(dataset.readIdFieldName() + " id"); + + addFields(dataset, sqlBuilder, ""); + + // 1:1从表字段 + List slaves = dataset.getOneToOneDataset(); + if (slaves != null) { + int index = 0; + for (BpmDataset slave: slaves) { + String tableAlias = "b" + (++index); + BpmDataset slaveDataset = slave; + if (addFields(slaveDataset, sqlBuilder, tableAlias + ".")) { + addJoinTable(sqlBuilder, slave, tableAlias); + } + } + } + } + return sqlBuilder; + } + + private static SqlJoinTable addJoinTable(AbstractSelectSqlBuilder sqlBuilder, BpmDataset slave, String tableAlias) { + SqlJoinTable joinTable = sqlBuilder.addJoinTable(slave.getDatabase(), slave.getTable(), tableAlias); + for (BpmMappingField bpmMappingField : slave.getMasterMapping().getMappingFields()) { + joinTable.add(bpmMappingField.getMaster(), tableAlias + "." + bpmMappingField.getSlave()); + } + + return joinTable; + } + + private static boolean addFields(BpmDataset dataset, AbstractSelectSqlBuilder sqlBuilder, String tableAlias) { + boolean result = false; + for (Map.Entry entry : dataset.getFields().entrySet()) { + BpmField bpmField = entry.getValue(); + +// if (!uiField.isVirtual()) { + sqlBuilder.add(tableAlias + bpmField.getFieldName() + " " + bpmField.getName()); + result = true; +// } + } + + return result; + } + + private static boolean addSummaryFields(BpmDataset dataset, AbstractSelectSqlBuilder sqlBuilder, String tableAlias) { + boolean result = false; + for (Map.Entry entry: dataset.getFields().entrySet()) { + String field = entry.getKey(); + BpmField bpmField = entry.getValue(); + String rule = bpmField.getSummary(); + + if (StringUtils.isNotBlank(rule)) { + String fieldName = tableAlias + bpmField.getFieldName(); + + sqlBuilder.add(SqlUtil.makeSummaryField(rule, fieldName) + " " + field); + result = true; + } + } + + return result; + } + +// protected SelectSqlBuilder makeSqlBuilderForTable(BpmDataset dataset, SwMap query, List fieldFilters) { +// SelectSqlBuilder sqlBuilder = createSqlBuilderForTable(dataset, false); +// +// List> filters = (List>)query.get(SqlUtil.PN_FILTER); +// +// if (fieldFilters != null) { +// for (BpmFieldFilter fieldFilter : fieldFilters) { +// String keyword = fieldFilter.getKeyword(); +// String fieldName = fieldFilter.getField(); +// +// if (StringUtils.isNotBlank(keyword) && StringUtils.isNotBlank(fieldName)) { +// // 级联动态数据 +// if (fieldFilter.isCascade()) { +// if (filters != null) { +// for (Map filter : filters) { +// String queryField = (String) filter.get(IBpmDatasetConst.PN_ITEM_FIELD); +// +// if (fieldName.equals(queryField)) { +// String queryKeyword = (String) filter.get(IBpmDatasetConst.PN_ITEM_KEYWORD); +// if (StringUtils.isNotBlank(queryKeyword)) { +// SqlUtil.addSqlWhere(sqlBuilder, fieldName, fieldFilter.getOp(), queryKeyword); +// } +// break; +// } +// } +// } +// } else { +// SqlUtil.addSqlWhere(sqlBuilder, fieldName, fieldFilter.getOp(), keyword); +// } +// } +// } +// } +// +// return sqlBuilder; +// } + + private void addSqlWhereForTable(BpmDataset dataset, AbstractSelectSqlBuilder sqlBuilder, List> filters, boolean whereOr) { + if (!filters.isEmpty()) { + if (whereOr) { + sqlBuilder.addWhereOrBegin(); + } + + for (Map filter : filters) { + String keyword = (String) filter.get(IBpmDatasetConst.PN_ITEM_KEYWORD); + String field = (String) filter.get(IBpmDatasetConst.PN_ITEM_FIELD); + int op = MapUtils.getIntValue(filter, IBpmDatasetConst.PN_ITEM_OP, 0); + + if (StringUtils.isNotBlank(keyword) && StringUtils.isNotBlank(field)) { + BpmField bpmField = dataset.findFieldByName(field); + + if (bpmField != null) { + SqlUtil.addSqlWhere(sqlBuilder, bpmField.getFieldName(), op, keyword); + } else { + List slaves = dataset.getOneToOneDataset(); + if (slaves != null) { + int index = 0; + for (BpmDataset slave : slaves) { + bpmField = slave.findFieldByName(field); + if (bpmField != null) { + String tableAlias; + + SqlJoinTable joinTable = sqlBuilder.findJoinTable(slave.getDatabase(), slave.getTable()); + if (joinTable != null) { + // select 重设为 inner join + tableAlias = joinTable.getTableAlias(); + } else { + // count 需要加 table + tableAlias = "c" + (++index); + joinTable = addJoinTable(sqlBuilder, slave, tableAlias); + } + + joinTable.setJoinType(SqlJoinTable.INNER_JOIN); + SqlUtil.addSqlWhere(sqlBuilder, tableAlias + "." + bpmField.getFieldName(), op, keyword); + break; + } + } + } + } + + if (bpmField == null) { + // error + log.error("not find filter field " + dataset.getName() + "." + field); + } + } + } + + if (whereOr) { + sqlBuilder.addWhereOrEnd(); + } + } + } + + StringBuilder makeLookupSql(BpmFieldLookup fieldLookup, boolean manyToMany) { + StringBuilder sb = new StringBuilder("SELECT "); + + if (manyToMany) { + sb.append("m.").append(fieldLookup.getMiddleKeyField()).append(" optKey,") + .append("b.").append(fieldLookup.getKeyField()).append(" optChildKey,") + .append("b.").append(fieldLookup.getLabelField()).append(" optValue ").append(" FROM ") + .append(fieldLookup.getDb()).append(".").append(fieldLookup.getMiddleTable()).append(" m LEFT JOIN ") + .append(fieldLookup.getDb()).append(".").append(fieldLookup.getTable()).append(" b ON(b.") + .append(fieldLookup.getKeyField()).append("=m.").append(fieldLookup.getMiddleValueField()).append(") WHERE m.") + .append(fieldLookup.getMiddleKeyField()); + } else { + sb.append(fieldLookup.getKeyField()).append(" optKey,").append(fieldLookup.getLabelField()).append(" optValue FROM ") + .append(fieldLookup.getDb()).append(".").append(fieldLookup.getTable()).append(" WHERE ") + .append(fieldLookup.getKeyField()); + } + +// StringBuilder sb = new StringBuilder("SELECT "); + + return sb; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/BpmKeyManyValue.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/BpmKeyManyValue.java new file mode 100644 index 0000000..34de38c --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/BpmKeyManyValue.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.spring.dao; + +import cc.smtweb.system.bpm.core.ui.BpmKeyValue; +import lombok.Data; + +@Data +public class BpmKeyManyValue extends BpmKeyValue { + private String optChildKey; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetCardDao.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetCardDao.java new file mode 100644 index 0000000..2d69dce --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetCardDao.java @@ -0,0 +1,71 @@ +package cc.smtweb.system.bpm.spring.dao; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.db.sqlbuilder.AbstractSelectSqlBuilder; +import cc.smtweb.system.bpm.core.ui.BpmKeyValue; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.builder.CardDataBuilder; +import cc.smtweb.system.bpm.core.ui.builder.SqlUtil; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmFieldLookup; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class DatasetCardDao extends AbstractDatasetPlugin { + public SwMap load(BpmPageContext ctx) { + long id = ctx.getParamId(); + if (id != 0) { + BpmDataset dataset = ctx.getDataset(); + // select + AbstractSelectSqlBuilder sqlBuilder = makeSqlBuilderForTable(dataset, ctx.getBody(), false); + sqlBuilder.addWhere(dataset.readIdFieldName(), id); + + return sqlBuilder.queryEntity(dbEngine, SwMap.class); + } + + return null; + } + + public Long save(BpmPageContext ctx) { + CardDataBuilder builder = CardDataBuilder.create(dbEngine, ctx.getDataset(), ctx.getBody(), ctx.getSession()); + return builder.update(); + } + + public int remove(BpmPageContext ctx) { + String ids = ctx.getBody().readString(IParamConst.PARAMS_IDS); + + BpmDataset dataset = ctx.getDataset(); + if (StringUtils.isNotBlank(ids)) { + return dbEngine.update("delete from " + dataset.fullName() + " where " + dataset.readIdFieldName() + " in(" + ids + ")"); + } else { + long id = ctx.getParamId(); + if (id != 0) { + return dbEngine.update("delete from " + dataset.fullName() + " where " + dataset.readIdFieldName() + "=?", id); + } + } + + return 0; + } + + public BpmKeyValue loadLookup(BpmPageContext ctx, Object lookupKey) { + BpmFieldLookup fieldLookup = ctx.getFieldLookup(); + if (fieldLookup.isManyToMany()) { + StringBuilder sb = makeLookupSql(fieldLookup, true).append("=?"); + List list = dbEngine.query(sb.toString(), BpmKeyManyValue.class, lookupKey); + BpmKeyValue result = new BpmKeyValue(); + + result.setOptKey(SqlUtil.join(list, BpmKeyManyValue::getOptChildKey)); + result.setOptValue(SqlUtil.join(list, BpmKeyManyValue::getOptValue)); + + return result; + } else { + StringBuilder sb = makeLookupSql(fieldLookup, false).append("=?"); + return dbEngine.queryEntity(sb.toString(), BpmKeyValue.class, lookupKey); + } + } + +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetConfigDao.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetConfigDao.java new file mode 100644 index 0000000..7d69532 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetConfigDao.java @@ -0,0 +1,141 @@ +package cc.smtweb.system.bpm.spring.dao; + +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.core.exception.BpmException; +import cc.smtweb.system.bpm.core.exception.BpmIlegalArgumentException; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.engine.entity.AspModelPO; +import cc.smtweb.system.bpm.spring.cache.BpmPageCache; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class DatasetConfigDao { + @SwParam + private BpmPageCache bpmPageCache; + + @Autowired + private DbEngine dbEngine; + + public BpmPage loadPage(Long pageId) { + if (pageId == null) { + throw new BpmException("参数 pageId 必填"); + } + + BpmPage uiPage = bpmPageCache.get(pageId); + + if (uiPage == null) { + throw new BpmException("未找到页面, id=" + pageId); + } + + return uiPage; + } + + public BpmPage loadPage(String pageName) { + if (pageName == null) { + throw new BpmException("参数 pageName 必填"); + } + + BpmPage uiPage = bpmPageCache.get(pageName); + if (uiPage == null) { + uiPage = bpmPageCache.getByOtherKey(pageName); + } + + if (uiPage == null) { + throw new BpmException("未找到页面, name=" + pageName); + } + + return uiPage; + } + +// private BpmDataset findUiDataset(BpmPage uiPage, String datasetName) { +// if (datasetName == null) { +// throw new BpmException("参数 dataset 必填"); +// } +// +// BpmDataset dataset = uiPage.findDatasetByName(datasetName); +// +// if (dataset == null) { +// throw new BpmException("未找到dataset, pageId=" + uiPage.getId() + ", id=" + datasetName); +// } +// +// return dataset; +// } + +// public void loadDataset(BpmPageContext pageContext) { +// SwMap body = pageContext.getBody(); +// String pageName = body.readString(IParamConst.PARAM_PAGE_NAME); +// if (pageName == null) { +// pageName = body.readString(IParamConst.PARAM_PAGE_ID); +// } +// +// BpmPage page = loadPage(pageName); +// pageContext.setDeploy(page.isDeploy()); +// pageContext.setDataset(findUiDataset(page, body.readString(IParamConst.PARAM_DATASET))); +// } + + public void loadDataset(BpmPageContext pageContext, String datasetPath) { + if (datasetPath != null) { + BpmDataset pageDataset = bpmPageCache.getDataset(datasetPath); + + if (pageDataset == null) { + throw new BpmIlegalArgumentException("not find dataset: " + datasetPath); + } + + pageContext.setDeploy(true); + pageContext.setDataset(pageDataset); +// } else { +// loadDataset(pageContext); + } + } + + /** + * 读取页面全路径 + * + * @param uiPage 页面对象 + * @param includeModule 是否包含模块路径 + * @return 页面全路径,全局唯一 + */ + public List loadPagePath(BpmPage uiPage, boolean includeModule) { + String sql = "select model_id, model_parent_id, model_key, model_name from sw_bpm.asp_model where model_id=?"; + AspModelPO curPage = dbEngine.queryEntity(sql, AspModelPO.class, uiPage.getId()); + + if (StringUtils.isBlank(curPage.getModelKey())) { + throw new BpmException("页面 '" + curPage.getModelName() + "' 未设置名称"); + } + + List names = new ArrayList<>(); + + Long parentId = curPage.getModelParentId(); + while (parentId != null) { + AspModelPO po = dbEngine.queryEntity(sql, AspModelPO.class, parentId); + + if (StringUtils.isBlank(po.getModelKey())) { + throw new BpmException("页面 '" + po.getModelName() + "' 未设置名称"); + } + + names.add(0, po.getModelKey()); + parentId = po.getModelParentId(); + } + + if (includeModule) { + String module = dbEngine.queryString("SELECT mc_module FROM sw_bpm.asp_model_catalog WHERE mc_id IN (SELECT model_mc_id FROM sw_bpm.asp_model WHERE model_id=?)", uiPage.getId()); + if (StringUtils.isBlank(module)) { + throw new BpmException("未设置服务模块名称"); + } + + names.add(0, module); + } + + names.add(curPage.getModelKey()); + + return names; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetListDao.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetListDao.java new file mode 100644 index 0000000..a91ac14 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetListDao.java @@ -0,0 +1,128 @@ +package cc.smtweb.system.bpm.spring.dao; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.db.sqlbuilder.AbstractSelectSqlBuilder; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.BpmKeyValue; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.BpmMapData; +import cc.smtweb.system.bpm.core.ui.builder.SqlUtil; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmFieldFilter; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmFieldLookup; +import cc.smtweb.system.bpm.core.ui.entity.dataset.IBpmDatasetConst; +import cc.smtweb.system.bpm.engine.ui.entity.consts.MappingTypeEnum; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +public class DatasetListDao extends AbstractDatasetPlugin { + public List list(BpmPageContext ctx) { + BpmDataset dataset = ctx.getDataset(); + SwMap query = ctx.getBody(); + // select + AbstractSelectSqlBuilder sqlBuilder = makeSqlBuilderForTable(dataset, query, false); + + int limit = query.readInt(IParamConst.PARAM_PAGE_SIZE, IParamConst.PAGE_DEFAULT_SIZE); + int start = query.readInt(IParamConst.PARAM_PAGE_INDEX, 0) * limit; + + return sqlBuilder.pagedQuery(dbEngine, SwMap.class, start, limit); + } + + public SwMap count(BpmPageContext ctx) { + AbstractSelectSqlBuilder sqlBuilder = makeSqlBuilderForTable(ctx.getDataset(), ctx.getBody(), true); + + // select + return sqlBuilder.queryEntity(dbEngine, SwMap.class); + } + + public BpmMapData listLookup(BpmPageContext ctx, Set idSet) { + BpmFieldLookup fieldLookup = ctx.getFieldLookup(); + if (fieldLookup.getMappingType().equals(MappingTypeEnum.manyToMany)) { + return listLookupManyToMany(fieldLookup, idSet); + } else { + return listLookupManyToOne(fieldLookup, idSet); + } + } + + public List listAll(BpmPageContext ctx) { + // select + AbstractSelectSqlBuilder sqlBuilder = makeSqlBuilderForTable(ctx.getDataset(), ctx.getBody(), ctx.getFieldLookup().getFilters()); + + return sqlBuilder.query(dbEngine, SwMap.class); + } + + private AbstractSelectSqlBuilder makeSqlBuilderForTable(BpmDataset dataset, SwMap query, List fieldFilters) { + AbstractSelectSqlBuilder sqlBuilder = createSqlBuilderForTable(dataset, false); + + List> filters = (List>)query.get(SqlUtil.PN_FILTER); + + if (fieldFilters != null) { + for (BpmFieldFilter fieldFilter : fieldFilters) { + String keyword = fieldFilter.getKeyword(); + String fieldName = fieldFilter.getField(); + + if (StringUtils.isNotBlank(keyword) && StringUtils.isNotBlank(fieldName)) { + // 级联动态数据 + if (fieldFilter.isCascade()) { + if (filters != null) { + for (Map filter : filters) { + String queryField = (String) filter.get(IBpmDatasetConst.PN_ITEM_FIELD); + + if (fieldName.equals(queryField)) { + String queryKeyword = (String) filter.get(IBpmDatasetConst.PN_ITEM_KEYWORD); + if (StringUtils.isNotBlank(queryKeyword)) { + SqlUtil.addSqlWhere(sqlBuilder, fieldName, fieldFilter.getOp(), queryKeyword); + } + break; + } + } + } + } else { + SqlUtil.addSqlWhere(sqlBuilder, fieldName, fieldFilter.getOp(), keyword); + } + } + } + } + + return sqlBuilder; + } + + private BpmMapData listLookupManyToOne(BpmFieldLookup fieldLookup, Set ids) { + StringBuilder sb = makeLookupSql(fieldLookup, false) + .append(" IN (").append(String.join(",", ids)).append(")"); + + List list = dbEngine.query(sb.toString(), BpmKeyValue.class); + + BpmMapData result = new BpmMapData(list.size()); + for (BpmKeyValue item : list) { + result.put(item.getOptKey(), item.getOptValue()); + } + + return result; + } + + private BpmMapData listLookupManyToMany(BpmFieldLookup fieldLookup, Set ids) { + // id, 子表ID, 子表值 + // select m.key, b.key, b.value from m left join b on(b.key = m.value) where m.key in ... + StringBuilder sb = makeLookupSql(fieldLookup, true) + .append(" IN (").append(String.join(",", ids)).append(")"); + + List list = dbEngine.query(sb.toString(), BpmKeyManyValue.class); + + BpmMapData result = new BpmMapData(list.size()); + for (BpmKeyManyValue item : list) { + String value = item.getOptValue(); + + if (StringUtils.isNotBlank(value)) { + result.merge(item.getOptKey(), value, (a, b) -> a + "," + b); + } + } + + return result; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetTreeDao.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetTreeDao.java new file mode 100644 index 0000000..feb4caf --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/spring/dao/DatasetTreeDao.java @@ -0,0 +1,61 @@ +package cc.smtweb.system.bpm.spring.dao; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.db.sqlbuilder.AbstractSelectSqlBuilder; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.builder.TreeDataBuilder; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.engine.entity.AspModelTreeVO; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class DatasetTreeDao extends AbstractDatasetPlugin { + public List treeAll(BpmPageContext ctx) { + TreeDataBuilder treeDataBuilder = new TreeDataBuilder(ctx.getDataset(), null, null); + + AbstractSelectSqlBuilder sqlBuilder = treeDataBuilder.tableSqlBuilder(); + + List list = sqlBuilder.query(dbEngine, SwMap.class); + + AspModelTreeVO root = new AspModelTreeVO(); + root.setModelName("项目"); + + return treeDataBuilder.buildTree(list, true); + } + + public List tree(BpmPageContext ctx) { + SwMap body = ctx.getBody(); + Long parentId = body.readLong(IParamConst.PARAM_PARENT_ID); + TreeDataBuilder treeDataBuilder = new TreeDataBuilder(ctx.getDataset(), parentId, null); + + AbstractSelectSqlBuilder sqlBuilder = treeDataBuilder.tableSqlBuilder(); + + sqlBuilder.addWhere(treeDataBuilder.getParentField().getFieldName(), parentId); + + return sqlBuilder.query(dbEngine, SwMap.class); + } + + public int move(BpmPageContext ctx) { + TreeDataBuilder treeDataBuilder = TreeDataBuilder.create(ctx); + + List params = new ArrayList<>(); + Long parentId = treeDataBuilder.getParentId(); + Long[] orders = treeDataBuilder.getOrders(); + + int index = 0; + for (Long order: orders) { + params.add(new Object[]{parentId, ++index, order}); + } + + BpmDataset dataset = treeDataBuilder.getDataset(); + + int[] ret = dbEngine.batchUpdate("update " + dataset.fullName() + " set " + treeDataBuilder.getParentField().getFieldName() + "=?, " + + treeDataBuilder.getOrderField().getFieldName() + "=? where " + dataset.readIdFieldName() + "=?", params); + + return ret.length; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/BeanUtil.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/BeanUtil.java new file mode 100644 index 0000000..b8e62c1 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/BeanUtil.java @@ -0,0 +1,120 @@ +package cc.smtweb.system.bpm.util; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.system.bpm.core.exception.BpmException; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class BeanUtil { + private BeanUtil() {} + + public static void mapToBean(SwMap source, Object target) { + mapToBean(source, target, null); + } + + private static void mapToBean(SwMap source, Object target, @Nullable Class editable, + @Nullable String... ignoreProperties) throws BeansException { + Assert.notNull(source, "Source must not be null"); + Assert.notNull(target, "Target must not be null"); + + Class actualEditable = target.getClass(); + if (editable != null) { + if (!editable.isInstance(target)) { + throw new IllegalArgumentException("Target class [" + target.getClass().getName() + + "] not assignable to Editable class [" + editable.getName() + "]"); + } + actualEditable = editable; + } + PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable); + List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null); + + for (PropertyDescriptor targetPd : targetPds) { + Method writeMethod = targetPd.getWriteMethod(); + if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { + Object value = source.get(targetPd.getName()); + + if (value == null || ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], value.getClass())) { + try { + if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { + writeMethod.setAccessible(true); + } + writeMethod.invoke(target, value); + } catch (Throwable ex) { + throw new FatalBeanException( + "Could not copy property '" + targetPd.getName() + "' from source to target", ex); + } + } + } + } + } + + public static void beanToMap(Object source, SwMap target, @Nullable String... ignoreProperties) throws BeansException { + Assert.notNull(source, "Source must not be null"); + Assert.notNull(target, "Target must not be null"); + + List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null); + + PropertyDescriptor[] sourcePds = BeanUtils.getPropertyDescriptors(source.getClass()); + + for (PropertyDescriptor sourcePd: sourcePds) { + if (ignoreList == null || !ignoreList.contains(sourcePd.getName())) { +// PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getKey()); +// if (sourcePd != null) { + Method readMethod = sourcePd.getReadMethod(); + if (readMethod != null) { + try { + if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { + readMethod.setAccessible(true); + } + Object value = readMethod.invoke(source); + target.put(sourcePd.getName(), value); + } + catch (Throwable ex) { + throw new FatalBeanException( + "Could not copy property '" + sourcePd.getName() + "' from source to target", ex); + } + } +// } + } + } + } + + public static List toBeanList(List bodyList, Class clazz) { + List beans = new ArrayList<>(bodyList.size()); + try { + for (SwMap body: bodyList) { + Object bean = clazz.newInstance(); + BeanUtil.mapToBean(body, bean); + beans.add(bean); + } + } catch (InstantiationException | IllegalAccessException e) { + throw new BpmException(e); + } + return beans; + } + + public static List toMapList(List beans) { + List bodyList = null; + if (beans != null) { + bodyList = new ArrayList<>(beans.size()); + for (Object bean: beans) { + SwMap body = new SwMap(); + beanToMap(bean, body); + bodyList.add(body); + } + } + + return bodyList; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/CodeGenUtil.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/CodeGenUtil.java new file mode 100644 index 0000000..b8ea738 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/CodeGenUtil.java @@ -0,0 +1,95 @@ +package cc.smtweb.system.bpm.util; + +import org.apache.commons.lang3.StringUtils; + +public class CodeGenUtil { + private CodeGenUtil() {} + + /*** + * 下划线命名转为驼峰命名 + * + * @param para + * 下划线命名的字符串 + */ + public static String underlineToHump(String para){ + StringBuilder result=new StringBuilder(); + String[] a = para.split("_"); + for(String s:a){ + if(result.length() == 0){ + result.append(s.toLowerCase()); + }else{ + result.append(s.substring(0, 1).toUpperCase()); + result.append(s.substring(1).toLowerCase()); + } + } + return result.toString(); + } + + /*** + * 下划线命名转为大写驼峰命名 + * 大驼峰式命名法(upper camel case) + * + * @param para + * 下划线命名的字符串 + */ + public static String underlineToUpperHump(String para){ + StringBuilder result=new StringBuilder(); + String[] a = para.split("_"); + for(String s : a){ + result.append(s.substring(0, 1).toUpperCase()); + result.append(s.substring(1).toLowerCase()); + } + return result.toString(); + } + + /*** + * 驼峰命名转为下划线命名 + * + * @param para + * 驼峰命名的字符串 + */ + public static String humpToUnderline(String para){ + return humpTo(para, "_"); + } + + private static String humpTo(String para, String splitChar){ + StringBuilder sb = new StringBuilder(para); + //偏移量,第i个下划线的位置是 当前的位置+ 偏移量(i-1),第一个下划线偏移量是0 + int temp=0; + for(int i=0;i { + /** + * 获取ID + * @param node 节点 + * @return 节点ID + */ + Long getId(T node); + + /** + * 获取上级ID + * @param node 当前节点 + * @return 上级ID + */ + Long getParentId(T node); + +// void addChild(IWebTreeVO item); + + /** + * 获取下级对象列表 + * @param node 当前节点 + * @return 下级对象列表 + */ + List getChildren(T node); + + /** + * 设置下级对象列表 + * @param children 下级对象列表 + */ + void setChildren(T node, List children); + + /** + * 是否强行添加未找到上级的错误节点到顶级 + * @param node 通过node有parentId值,但未找到对应上级节点 + * @return 是否强行添加错误节点到顶级 + */ + default boolean forceAdd(T node) { return false; } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/ITreeDataLevelHandler.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/ITreeDataLevelHandler.java new file mode 100644 index 0000000..2e43c00 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/ITreeDataLevelHandler.java @@ -0,0 +1,6 @@ +package cc.smtweb.system.bpm.util; + +public interface ITreeDataLevelHandler extends ITreeDataHandler { + int getLevel(T t); + void setLevel(T t, int level); +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/TreeDataUtil.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/TreeDataUtil.java new file mode 100644 index 0000000..7c1e490 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/TreeDataUtil.java @@ -0,0 +1,217 @@ +package cc.smtweb.system.bpm.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * 树结构数据创建器 + * @author xkliu + */ +public class TreeDataUtil { + private TreeDataUtil(){} + + public static boolean longEquals(Long l1, Long l2) { + if (l1 != null) { + return l1.equals(l2); + } + + return l2 == null; + } + + private static boolean findAndAdd(T parent, T item, ITreeDataHandler handler) { + if (longEquals(handler.getId(parent), handler.getParentId(item))) { + addChild(parent, item, handler); + return true; + } else { + List children = handler.getChildren(parent); + + if (children != null) { + for (T child : children) { + if (findAndAdd(child, item, handler)) { + return true; + } + } + } + } + + return false; + } + + private static boolean findAndAddLevel(T parent, T item, ITreeDataLevelHandler handler) { + if (longEquals(handler.getId(parent), handler.getParentId(item))) { + addChildLevel(parent, item, handler); + return true; + } else { + List children = handler.getChildren(parent); + + if (children != null) { + for (T child : children) { + if (findAndAddLevel(child, item, handler)) { + return true; + } + } + } + } + + return false; + } + + private static void addChild(T parent, T item, ITreeDataHandler handler) { + List children = handler.getChildren(parent); + if (children == null) { + children = new ArrayList<>(); + handler.setChildren(parent, children); + } + + children.add(item); + } + + private static void addChildLevel(T parent, T item, ITreeDataLevelHandler handler) { + List children = handler.getChildren(parent); + if (children == null) { + children = new ArrayList<>(); + handler.setChildren(parent, children); + } + + updateLevel(item, handler.getLevel(parent) + 1, handler); + children.add(item); + } + + private static void updateLevel(T parent, int parentLevel, ITreeDataLevelHandler handler) { + handler.setLevel(parent, parentLevel); + List children = handler.getChildren(parent); + if (children != null) { + int level = parentLevel + 1; + for (T t: children) { + handler.setLevel(t, level); + updateLevel(t, level, handler); + } + } + } + + // 读取树结构 + public static List buildTree(T root, List list, ITreeDataHandler handler) { + if (list != null) { + for (T item: list) { + if (!findAndAdd(root, item, handler)) { + for (T it: list) { + if (longEquals(handler.getId(it), handler.getParentId(item))) { + addChild(it, item, handler); + item = null; + break; + } + } + + // not find item parent + if (item != null && handler.forceAdd(item)) { + addChild(root, item, handler); + } + } + } + } + + return handler.getChildren(root); + } + + // 读取树结构 + public static List buildLevelTree(T root, List list, ITreeDataLevelHandler handler) { + if (list != null) { + handler.setLevel(root, 0); + for (T item: list) { + if (!findAndAddLevel(root, item, handler)) { + for (T it: list) { + if (longEquals(handler.getId(it), handler.getParentId(item))) { + addChildLevel(it, item, handler); + item = null; + break; + } + } + + // not find item parent + if (item != null && handler.forceAdd(item)) { + addChildLevel(root, item, handler); + } + } + } + } + + return handler.getChildren(root); + } + + public static T findParent(T root, Long id, int treeLevel, ITreeDataLevelHandler handler) { + T result = findParentInLevel(root, id, treeLevel, handler); + if (result != null && handler.getLevel(result) != treeLevel) { + result = null; + } + + return result; + } + + private static T findParentInLevel(T parent, Long id, int treeLevel, ITreeDataLevelHandler handler) { + List children = handler.getChildren(parent); + if (children != null) { + for (T child: children) { + if (id.equals(handler.getId(child))) { + return parent; + } else { + T find = findParentInLevel(child, id, treeLevel - 1, handler); + if (find != null) { + if (treeLevel > 0) { + return find; + } else { + return parent; + } + } + } + } + } + + return null; + } + + public static T findParent(T root, Long id,ITreeDataHandler handler) { + List children = handler.getChildren(root); + if (children != null) { + for (T child: children) { + if (id.equals(handler.getId(child))) { + return root; + } else { + T find = findParent(child, id, handler); + if (find != null) { + return find; + } + } + } + } + + return null; + } + + public static T find(T root, Long id, ITreeDataHandler handler) { + List children = handler.getChildren(root); + if (children != null) { + for (T child: children) { + if (id.equals(handler.getId(child))) { + return child; + } else { + T find = find(child, id, handler); + if (find != null) { + return find; + } + } + } + } + + return null; + } + + public static void forEach(T node, ITreeDataHandler handler, Consumer action) { + List children = handler.getChildren(node); + if (children != null) { + for (T child: children) { + action.accept(child); + } + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/XmlUtil.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/XmlUtil.java new file mode 100644 index 0000000..f2b7bad --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/XmlUtil.java @@ -0,0 +1,76 @@ +package cc.smtweb.system.bpm.util; + +import cc.smtweb.framework.core.exception.JsonParseException; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class XmlUtil { + private static final XmlMapper OBJECT_MAPPER = new XmlMapper(); + + static { + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + OBJECT_MAPPER.enable(SerializationFeature.INDENT_OUTPUT); + } + + private XmlUtil() {} + + public static T readValue(String str, Class clazz) { + try { + if (StringUtils.isBlank(str)) { + return null; + } else { + return OBJECT_MAPPER.readValue(str, clazz); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this json to " + clazz + " type", e); + } + } + + public static T readValue(InputStream is, Class clazz) { + try { + if (is == null) { + return null; + } else { + return OBJECT_MAPPER.readValue(is, clazz); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this xml to " + clazz + " type", e); + } + } + + public static String writeValue(Object obj) { + try { + return OBJECT_MAPPER.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new JsonParseException("can't convert type " + obj.getClass() + " to json string", e); + } + } + + public static void writeValue(OutputStream outputStream, Object obj) { + try { + OBJECT_MAPPER.writeValue(outputStream, obj); + } catch (JsonProcessingException e) { + throw new JsonParseException("can't convert type " + obj.getClass() + " to json string", e); + } catch (IOException e) { + throw new JsonParseException(e.getMessage(), e); + } + } + + public static void writeValue(File file, Object obj) { + try { + OBJECT_MAPPER.writeValue(file, obj); + } catch (JsonProcessingException e) { + throw new JsonParseException("can't convert type " + obj.getClass() + " to json string", e); + } catch (IOException e) { + throw new JsonParseException(e.getMessage(), e); + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/YamlUtil.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/YamlUtil.java new file mode 100644 index 0000000..24ebd47 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/util/YamlUtil.java @@ -0,0 +1,112 @@ +package cc.smtweb.system.bpm.util; + +import cc.smtweb.framework.core.exception.JsonParseException; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; +import org.apache.commons.lang3.StringUtils; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.introspector.Property; +import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.representer.Representer; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class YamlUtil { + private static final ObjectMapper OBJECT_MAPPER; + private static final Representer representer; + + static { + YAMLFactory yamlFactory = new YAMLFactory(); + +// yamlFactory.enable(YAMLGenerator.Feature.CANONICAL_OUTPUT); + yamlFactory.disable(YAMLGenerator.Feature.SPLIT_LINES); + yamlFactory.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES); + + OBJECT_MAPPER = new ObjectMapper(yamlFactory); + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + // 生成yaml时忽略null属性 + representer = new Representer() { + @Override + protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) { + // if value of property is null, ignore it. + if (propertyValue == null) { + return null; + } + else { + return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); + } + } + }; + } + + private YamlUtil() {} + + public static T readValue(String str, Class clazz) { + try { + if (StringUtils.isBlank(str)) { + return null; + } else { + return OBJECT_MAPPER.readValue(str, clazz); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this json to " + clazz + " type", e); + } + } + + public static T readValue(File file, Class clazz) { + try { + return OBJECT_MAPPER.readValue(file, clazz); + } catch (Exception e) { + throw new JsonParseException("can't convert this json to " + clazz + " type", e); + } + } + + public static T readValue(InputStream is, Class clazz) { + try { + if (is == null) { + return null; + } else { + return OBJECT_MAPPER.readValue(is, clazz); + } + } catch (Exception e) { + throw new JsonParseException("can't convert this yaml to " + clazz + " type", e); + } + } + + public static String writeValue(Object obj) { + Yaml yaml = buildYaml(); + StringWriter sw = new StringWriter(); + yaml.dump(obj, sw); + return sw.toString(); + } + + private static Yaml buildYaml() { + Yaml result = new Yaml(representer); + + return result; + } + + public static void writeValue(OutputStream outputStream, Object obj) { + Yaml yaml = buildYaml(); + yaml.dump(obj, new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); + } + + public static void writeValue(File file, Object obj) { + Yaml yaml = buildYaml(); + try(OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { + yaml.dump(obj, out); +// OBJECT_MAPPER.writeValue(file, obj); + } catch (IOException e) { + throw new JsonParseException(e.getMessage(), e); + } + + +// StringWriter sw = new StringWriter(); +// System.out.println(sw.toString()); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/AbstractCodeService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/AbstractCodeService.java new file mode 100644 index 0000000..d47f0ae --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/AbstractCodeService.java @@ -0,0 +1,28 @@ +package cc.smtweb.system.bpm.web.design.codegen; + +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.engine.entity.AspModelTreeVO; +import cc.smtweb.system.bpm.util.ITreeDataLevelHandler; +import cc.smtweb.system.bpm.util.TreeDataUtil; +import cc.smtweb.system.bpm.spring.dao.DatasetConfigDao; + +import java.util.List; + +public abstract class AbstractCodeService { + @SwParam + protected DbEngine dbEngine; + + @SwParam + protected DatasetConfigDao datasetConfigDao; + + protected AspModelTreeVO loadParentPage(Long pageId) { + List list = dbEngine.query("select model_id, model_parent_id, model_key, model_name from sw_bpm.asp_model where model_mc_id in" + + " (select model_mc_id from sw_bpm.asp_model where model_id=?)", + AspModelTreeVO.class, pageId); + AspModelTreeVO root = new AspModelTreeVO(); + ITreeDataLevelHandler handler = AspModelTreeVO.createTreeHandler(); + TreeDataUtil.buildLevelTree(root, list, handler); + return TreeDataUtil.findParent(root, pageId, 1, handler); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/JavaCodeService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/JavaCodeService.java new file mode 100644 index 0000000..650fb61 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/JavaCodeService.java @@ -0,0 +1,413 @@ +package cc.smtweb.system.bpm.web.design.codegen; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.cache.ModelDatabaseCache; +import cc.smtweb.framework.core.db.cache.ModelTableCache; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.file.FilePathGenerator; +import cc.smtweb.system.bpm.core.exception.ModelLoaderError; +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmForm; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmWidget; +import cc.smtweb.framework.core.db.vo.ModelDatabase; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.system.bpm.engine.entity.AspModelTreeVO; +import cc.smtweb.system.bpm.engine.ui.codegen.DefaultVelocityEngine; +import cc.smtweb.system.bpm.engine.ui.entity.yaml.YamlDataset; +import cc.smtweb.system.bpm.engine.ui.entity.yaml.YamlPage; +import cc.smtweb.system.bpm.engine.ui.loader.yaml.YamlDatasetEncoder; +import cc.smtweb.system.bpm.engine.ui.loader.yaml.YamlPageEncoder; +import cc.smtweb.system.bpm.spring.BpmConfigBean; +import cc.smtweb.system.bpm.util.CodeGenUtil; +import cc.smtweb.system.bpm.util.YamlUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +// 后台java代码生成 +@Slf4j +@SwService +public class JavaCodeService extends AbstractCodeService { + public static final String SRC_MAIN_JAVA = "/src/main/java/"; + public static final String SRC_MAIN_RES = "/src/main/resources"; + public static final String RESOURCE_PLUGIN = "/plugin/"; + public static final String RESOURCE_STATIC = "/static/bpm/"; + + @SwParam + protected DbEngine dbEngine; + + @SwParam + private FilePathGenerator filePathGenerator; + + @SwParam + private BpmConfigBean bpmConfigBean; + + public R info(UserSession us) throws IOException { + URL url = getClass().getResource("/"); + + return R.success(url); + } + + public R buildEntity(UserSession us, @SwParam("id") Long id) throws IOException { + R x = checkConfig(); + if (x != null) { + return x; + } + + String codeJavaPath = bpmConfigBean.getCodeJavaPath(); + + ModelDatabase modelDatabase = ModelDatabaseCache.getInstance().get(id); + + if (modelDatabase == null) { + return R.error("数据模型对象不存在, id=" + id); + } + + String codeJavaPackage = bpmConfigBean.getCodeJavaPackage(); + String[] names = codeJavaPackage.split("\\."); + + File entityPath = new File(codeJavaPath + String.join("/", names) + "/entity"); + + if (!entityPath.exists()) { + if (!entityPath.mkdirs()) { + return R.error("创建目录失败:" + entityPath.getAbsolutePath()); + } + } + + DefaultVelocityEngine velocityEngine = makeJavaVelocityEngine(codeJavaPackage); + + List errors = new ArrayList<>(); + + for (ModelTable modelTable: ModelTableCache.getInstance().getDbTables(modelDatabase.getId())) { + File javaFile = new File(entityPath.getAbsolutePath() + "/" + + CodeGenUtil.underlineToUpperHump(modelTable.getName()) + ".java"); + + // 代码生成到临时文件 + velocityEngine.put("table", modelTable); + R r = velocityEngine.buildUseSign(id, "/codegen/vm/server/entity.java.vm", javaFile); + + if (r != null) { + errors.add(new ModelLoaderError(0, "生成文件‘" + javaFile.getName() + "’失败,请用tmp文件手动替换!")); + } + } + + R r = R.success(entityPath.getAbsolutePath()); + + if (!errors.isEmpty()) { + r.put("errors", errors); + } + + return r; + } + + private R checkConfig() { + if (!bpmConfigBean.isCodeBuild()) { + return R.error("禁用代码生成(bpm.code-build)"); + } + + String codeJavaPath = bpmConfigBean.getCodeJavaPath(); + if (StringUtils.isBlank(codeJavaPath)) { + return R.error("未配置Java文件路径(bpm.code-java-path)"); + } + + if (!codeJavaPath.endsWith(SRC_MAIN_JAVA)) { + return R.error("Java文件路径必须用 '/src/main/java/' 结尾(bpm.code-java-path)"); + } + + File srcPath = new File(codeJavaPath); + + if (!(srcPath.isDirectory() && srcPath.exists())) { + return R.error("Java文件路径不存在:" + codeJavaPath); + } + +// String codeJavaResource = bpmConfigBean.getCodeJavaResource(); +// if (StringUtils.isBlank(codeJavaResource)) { +// return R.error("未配置Java resource文件路径(bpm.code-java-resource)"); +// } +// +// if (!codeJavaResource.startsWith("/plugin/") || !codeJavaResource.endsWith("/")) { +// return R.error("Java resource文件路径必须用 '/plugin/' 开头, '微服务名/' 结尾(bpm.code-java-resource)"); +// } + + String packageName = bpmConfigBean.getCodeJavaPackage(); + if (StringUtils.isBlank(packageName)) { + return R.error("未配置生成Java包名(bpm.code-java-package)"); + } + + if (!packageName.endsWith(".web")) { + return R.error("包名路径必须用 '.web' 结尾(bpm.code-java-package)"); + } + + String[] names = packageName.split("\\."); + + File webPath = new File(codeJavaPath + String.join("/", names)); + if (!(webPath.isDirectory() && webPath.exists())) { + return R.error("包目录不存在:" + webPath.getAbsolutePath()); + } + + return null; + } + + public R buildService(UserSession us, @SwParam("pageId") Long pageId) throws IOException { + R x = checkConfig(); + if (x != null) { + return x; + } + + String modelFullName = dbEngine.queryString("select model_full_name from sw_bpm.bpm_model where model_id=?", pageId); + if (modelFullName == null) { + return R.error("页面未提交"); + } + + AspModelTreeVO moduleNode = loadParentPage(pageId); + + if (moduleNode == null) { + return R.error("未找到页面的模块信息"); + } + + String codeJavaPath = bpmConfigBean.getCodeJavaPath(); + String codeJavaPackage = bpmConfigBean.getCodeJavaPackage(); + JavaPath javaSrcPath = new JavaPath(codeJavaPath, codeJavaPackage); + String[] packageNames = modelFullName.split("\\."); + if (packageNames.length < 2) { + return R.error("未找到页面的节点信息"); + } + + // 生成后台java service代码 + List files = new ArrayList<>(); + BpmPage uiPage = datasetConfigDao.loadPage(pageId); + + DefaultVelocityEngine velocityEngine = new DefaultVelocityEngine(); + for (BpmDataset dataset: uiPage.getDatasets()) { + String customApi = dataset.getCustomApi(); + if (customApi != null) { + String modelName; + switch (dataset.getDataModel()) { + case BpmDataset.DATA_MODEL_CARD: + modelName = "Card"; + break; + case BpmDataset.DATA_MODEL_LIST: + modelName = "List"; + break; + case BpmDataset.DATA_MODEL_TREE: + modelName = "Tree"; + break; + default: + return R.error(dataset.getName() + " 未设置正确的数据模型: " + dataset.getDataModel()); + } + + // Java服务文件路径 + javaSrcPath.setPackages(packageNames); + // Java服务类名 + String[] customNames = customApi.split("/"); + if (customNames.length > 1) { + // 配置了路径和类名 + javaSrcPath.setClassName(customNames[customNames.length - 1]); + } else { + // 只配置了类名 +// javaSrcPath.setPackages(serviceNames).addPackage(serviceNode.getModelKey()); + javaSrcPath.setClassName(customApi); + } + + if (!javaSrcPath.makeDirs()) { + return R.error("创建目录失败:" + javaSrcPath.getAbsolutePath()); + } + + String className = javaSrcPath.getClassName() + "Service"; + + File javaFile = new File(javaSrcPath.getAbsolutePath() + "/" + className + ".java"); + + if (!javaFile.exists()) { + velocityEngine.put("package", javaSrcPath.getPackage()); + velocityEngine.put("dataset", dataset); + velocityEngine.put("className", className); + velocityEngine.put("resourceUrl", modelFullName + "." + dataset.getName()); + + R r = velocityEngine.build("/codegen/vm/server/" + modelName + "Service.java.vm", javaFile); + + if (r != null) { + return r; + } + + files.add(javaFile.getAbsolutePath()); + } + } else { + // TODO: 附加的Lookup表,通过字段的lookup产生的 + } + } + + return R.success(files); + } + + /** 创建配置Yaml文件,项目部署时需要 */ + public R buildYaml(UserSession us, @SwParam("pageId") Long pageId) { + R x = checkConfig(); + if (x != null) { + return x; + } + + String modelFullName = dbEngine.queryString("select model_full_name from sw_bpm.bpm_model where model_id=?", pageId); + if (modelFullName == null) { + return R.error("页面未提交"); + } + +// AspModelTreeVO moduleNode = loadParentPage(pageId); +// +// if (moduleNode == null) { +// return R.error("未找到页面的模块信息"); +// } + + String codeJavaPath = bpmConfigBean.getCodeJavaPath(); + String[] packageNames = modelFullName.split("\\."); + if (packageNames.length < 2) { + return R.error("未找到页面的节点信息"); + } + + // 生成后台java service代码 + List files = new ArrayList<>(); + BpmPage uiPage = datasetConfigDao.loadPage(pageId); + + List yamlDatasets = new ArrayList<>(); + for (BpmDataset dataset: uiPage.getDatasets()) { + yamlDatasets.add(new YamlDatasetEncoder(dataset).encode()); + } + + YamlPage page = new YamlPage(); + page.setId(pageId); + page.setDestroyOnClose(uiPage.getDestroyOnClose()); + + if (!yamlDatasets.isEmpty()) { + page.setDatasets(yamlDatasets); + } + + for (BpmForm form: uiPage.getForms()) { + page.getForms().add(YamlPageEncoder.encode(form)); + } + + // 生成数据集代码 + // 资源文件路径 + String resourceRootPath = codeJavaPath.substring(0, codeJavaPath.length() - SRC_MAIN_JAVA.length()) + SRC_MAIN_RES; + File yamlFile = new File(resourceRootPath + RESOURCE_PLUGIN + StringUtils.join(packageNames, "/") + ".yaml"); + File resourcePath = yamlFile.getParentFile(); + if (!resourcePath.exists()) { + if (!resourcePath.mkdirs()) { + return R.error("创建目录失败:" + resourcePath.getAbsolutePath()); + } + } + + YamlUtil.writeValue(yamlFile, page); + files.add(yamlFile.getAbsolutePath()); + + return R.success(files); + } + +// private AspModelTreeVO loadServiceNames(AspModelTreeVO node, Long pageId, List names) { +// if (pageId.equals(node.getModelId())) { +// return node; +// } +// +// List children = node.getChildren(); +// +// if (children != null) { +// for (AspModelTreeVO child: children) { +// AspModelTreeVO result = loadServiceNames(child, pageId, names); +// if (result != null) { +// names.add(0, node.getModelKey().toLowerCase()); +// +// return result; +// } +// } +// } +// +// return null; +// } + + public R buildEvent(UserSession us, @SwParam("pageId") Long pageId) throws IOException { + R x = checkConfig(); + if (x != null) { + return x; + } + + String modelFullName = dbEngine.queryString("select model_full_name from sw_bpm.bpm_model where model_id=?", pageId); + if (modelFullName == null) { + return R.error("页面未提交"); + } + + String codeJavaPath = bpmConfigBean.getCodeJavaPath(); + String[] packageNames = modelFullName.split("\\."); + if (packageNames.length < 2) { + return R.error("未找到页面的节点信息"); + } + + // 生成后台java service代码 + BpmPage uiPage = datasetConfigDao.loadPage(pageId); + + // 资源文件路径 + String resourceRootPath = codeJavaPath.substring(0, codeJavaPath.length() - SRC_MAIN_JAVA.length()) + SRC_MAIN_RES; + File eventFile = new File(resourceRootPath + RESOURCE_STATIC + StringUtils.join(packageNames, "/") + "-event.js"); + + if (eventFile.exists()) { + return R.error("事件文件已经存在: " + eventFile.getAbsolutePath()); + } + + File eventDir = eventFile.getParentFile(); + if (!eventDir.exists()) { + if (!eventDir.mkdirs()) { + return R.error("创建目录失败:" + eventDir.getAbsolutePath()); + } + } + + List events = new ArrayList<>(); + for (BpmDataset dataset: uiPage.getDatasets()) { + events.add(dataset.getName() + "$onLoad"); + } + + for (BpmForm form: uiPage.getForms()) { + fillEvent(form, events); + } + + DefaultVelocityEngine velocityEngine = new DefaultVelocityEngine(); + velocityEngine.put("url", modelFullName); + velocityEngine.put("events", events); + + R r = velocityEngine.build("/codegen/vm/server/event.js.vm", eventFile); + + if (r != null) { + return r; + } + + return R.success(eventFile.getAbsolutePath()); + } + + private void fillEvent(BpmWidget widget, List events) { + Map widgetEvents = widget.getEvents(); + if (widgetEvents != null) { + widgetEvents.forEach((key, value) -> { + if (StringUtils.isNotBlank(value)) { + events.add(widget.getName() + "$" + value); + } + }); + } + + BpmWidget[] children = widget.getChildren(); + if (children != null) { + for (BpmWidget child: children) { + fillEvent(child, events); + } + } + } + + + private DefaultVelocityEngine makeJavaVelocityEngine(String codeJavaPackage) { + return new DefaultVelocityEngine().put("package", codeJavaPackage); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/JavaPath.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/JavaPath.java new file mode 100644 index 0000000..baaa1d2 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/JavaPath.java @@ -0,0 +1,77 @@ +package cc.smtweb.system.bpm.web.design.codegen; + +import cc.smtweb.system.bpm.util.CodeGenUtil; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JavaPath { + private final String rootPath; + private final String[] rootPackages; + private List packageNames; + @Getter + private String className; + @Getter + private String moduleName; + + public JavaPath(String rootPath, String rootPackage) { + this.rootPath = rootPath; + this.rootPackages = rootPackage.split("\\."); + } + + public void setPackages(String[] names) { + this.packageNames = null; + this.moduleName = names[0]; + for (int i = 1; i < names.length; i++) { + addPackage(names[i]); + } + } + + public JavaPath addPackage(String name) { + if (packageNames == null) { + packageNames = new ArrayList<>(); + } + + packageNames.add(name.toLowerCase()); + return this; + } + + public boolean makeDirs() { + File servicePath = new File(getAbsolutePath()); + + if (servicePath.exists()) { + return true; + } + + return servicePath.mkdirs(); + } + + public String getAbsolutePath() { + return rootPath + String.join("/", rootPackages) + "/" + StringUtils.join(packageNames, "/"); + } + + public JavaPath setPackageUrl(String customApi) { + return setPackageUrl(customApi.split("/")); + } + + public JavaPath setPackageUrl(String[] names) { + this.packageNames = null; + for (int i = 0; i < names.length - 1; i++) { + addPackage(names[i].toLowerCase()); + } + + this.className = CodeGenUtil.toUpperHump(names[names.length - 1]); + return this; + } + + public void setClassName(String className) { + this.className = CodeGenUtil.toUpperHump(className); + } + + public String getPackage() { + return StringUtils.join(rootPackages, ".") + "." + StringUtils.join(packageNames, "."); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/PageMetaVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/PageMetaVO.java new file mode 100644 index 0000000..1167b46 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/PageMetaVO.java @@ -0,0 +1,11 @@ +package cc.smtweb.system.bpm.web.design.codegen; + +import lombok.Data; + +import java.sql.Timestamp; + +@Data +public class PageMetaVO { + private Long id; + private Timestamp updateAt; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/RouterVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/RouterVO.java new file mode 100644 index 0000000..6b07f87 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/RouterVO.java @@ -0,0 +1,11 @@ +package cc.smtweb.system.bpm.web.design.codegen; + +import lombok.Data; + +@Data +public class RouterVO { + private String path; + private String name; + private String title; + private String filePath; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/UiPageRef.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/UiPageRef.java new file mode 100644 index 0000000..78ca089 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/UiPageRef.java @@ -0,0 +1,24 @@ +package cc.smtweb.system.bpm.web.design.codegen; + +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class UiPageRef { + private Map routes = new HashMap<>(); + private Map> widgets = new HashMap<>(); + private Long id; + private String name; + private String dir; + private String routerName; + + public void addWidget(String name, Map props) { + widgets.put(name, props); + } + +// public void addLookupDataset(String dataset, String field) { +// lookups.add(dataset + "_" + field); +// } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/UiPageRefLoader.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/UiPageRefLoader.java new file mode 100644 index 0000000..d33d672 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/UiPageRefLoader.java @@ -0,0 +1,58 @@ +package cc.smtweb.system.bpm.web.design.codegen; + +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmForm; +import cc.smtweb.system.bpm.core.ui.entity.form.BpmWidget; +import cc.smtweb.system.bpm.util.CodeGenUtil; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class UiPageRefLoader { + // 处理button link 页面 + protected UiPageRef loadPageRef(BpmPage bpmPage, String dir, String routerName) { + UiPageRef pageRef = new UiPageRef(); + pageRef.setId(bpmPage.getId()); + pageRef.setName(bpmPage.getFullName()); + pageRef.setDir(dir); + pageRef.setRouterName(routerName); + for (BpmForm form: bpmPage.getForms()) { + buildWidgets(form, pageRef); + } + + return pageRef; + } + + private void buildWidgets(BpmWidget parent, UiPageRef pageRef) { + // 生成响应对象 + if (parent.getVars() != null) { + Map props = new HashMap<>(parent.getVars().size()); + for (Map.Entry entry: parent.getVars().entrySet()) { + String key = entry.getKey(); + Object value = parent.getProps().get(key); + props.put(key, value); + } + + if (!props.isEmpty()) { + pageRef.addWidget(parent.getName(), props); + } + } + + // 循环子控件 + if (parent.getChildren() != null) { + for (BpmWidget widget: parent.getChildren()) { + buildWidgets(widget, pageRef); + } + } + } + + String getRouterName(List names) { + StringBuilder sb = new StringBuilder(); + for (String name: names) { + sb.append(CodeGenUtil.toUpperHump(name)); + } + + return sb.toString(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/VueCodeService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/VueCodeService.java new file mode 100644 index 0000000..4edfac5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/VueCodeService.java @@ -0,0 +1,226 @@ +package cc.smtweb.system.bpm.web.design.codegen; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.file.FilePathGenerator; +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.engine.entity.AspModelTreeVO; +import cc.smtweb.system.bpm.engine.ui.codegen.DefaultVelocityEngine; +import cc.smtweb.system.bpm.spring.BpmConfigBean; +import cc.smtweb.system.bpm.util.CodeGenUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.tomcat.util.http.fileupload.IOUtils; + +import java.io.*; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +// 前端界面代码生成 +@Slf4j +@SwService +public class VueCodeService extends AbstractCodeService { + @SwParam + private FilePathGenerator filePathGenerator; + + @SwParam + private BpmConfigBean bpmConfigBean; + + public R info(UserSession us) throws IOException { + URL url = getClass().getResource("/"); + + return R.success(url); + } + + /** 生成代码 */ + public R build(UserSession us, @SwParam("pageId") Long pageId, @SwParam("type") String type) throws IOException { + if (!bpmConfigBean.isCodeBuild()) { + return R.error("禁用代码生成(bpm.code-build)"); + } + + String codeVuePath = bpmConfigBean.getCodeVuePath(); + if (StringUtils.isBlank(codeVuePath)) { + return R.error("未配置生成VUE文件路径(bpm.code-vue-path)"); + } + + String codeVueDir = getDir(); + + if (!codeVuePath.endsWith("/src/")) { + return R.error("VUE文件路径必须用 '/src/' 结尾(bpm.code-vue-path)"); + } + + File srcPath = new File(codeVuePath); + + if (!(srcPath.isDirectory() && srcPath.exists())) { + return R.error("VUE文件路径不存在:" + codeVuePath); + } + + if ("router".equals(type)) { + return makeRouterCode(pageId, codeVuePath); + } else if (dbEngine.queryLong("select model_id from sw_bpm.bpm_model where model_id=?", pageId) == null) { + return R.error("页面未提交"); + } + + return makePageCode(pageId, codeVuePath, codeVueDir); + } + + private R makeRouterCode(Long pageId, String codeVuePath) throws IOException { + AspModelTreeVO moduleNode = loadParentPage(pageId); + + if (moduleNode == null) { + return R.error("未找到页面的模块信息"); + } + + // src/router/modules/{moduleNode.name}.ts + List routers = new ArrayList<>(); + R r = buildRouter(moduleNode, routers, new ArrayList<>()); + if (r != null) { + return r; + } + + DefaultVelocityEngine velocityEngine = new DefaultVelocityEngine(); + velocityEngine.put("routers", routers); + + // 生成vue主页面 + File vmFile = new File(codeVuePath + "/router/modules/" + moduleNode.getModelKey() + ".ts"); + r = velocityEngine.buildUseSign(moduleNode.getModelId(), "/codegen/vm/router.ts.vm", vmFile); + if (r != null) { + return r; + } + + return R.success(vmFile.getAbsolutePath()); + } + + private R buildRouter(AspModelTreeVO node, List routers, List names) { + if (StringUtils.isBlank(node.getModelKey())) { + return R.error("页面 '" + node.getModelName() + "' 未设置命名"); + } + names.add(node.getModelKey()); + + List children = node.getChildren(); + + if (children != null) { + for (AspModelTreeVO child: children) { + R r = buildRouter(child, routers, names); + if (r != null) { + return r; + } + } + } + + // 最后一级才生成页面路由 + if (node.getChildren() == null) { + StringBuilder sb = new StringBuilder(); + names.forEach((name) -> sb.append(CodeGenUtil.toLowerHump(name))); + sb.setCharAt(0, Character.toLowerCase(sb.charAt(0))); + + RouterVO router = new RouterVO(); + router.setFilePath(String.join("/", names)); + router.setPath(String.join("/", names)); + router.setName(getRouterName(names)); + router.setTitle(node.getModelName()); + + routers.add(router); + } + + names.remove(names.size() - 1); + + return null; + } + + private static String getRouterName(List names) { + StringBuilder sb = new StringBuilder(); + for (String name: names) { + sb.append(CodeGenUtil.toUpperHump(name)); + } + + return sb.toString(); + } + + private R makePageCode(@SwParam("pageId") Long pageId, String codeVuePath, String codeVueDir) throws IOException { + // 重新读取页面,获取加载错误值 + BpmPage uiPage = datasetConfigDao.loadPage(pageId); + + // 创建页面目录 + List names = datasetConfigDao.loadPagePath(uiPage, false); + + File viewPath = new File(codeVuePath + codeVueDir + "/" + String.join("/", names)); + + if (!viewPath.exists()) { + if (!viewPath.mkdirs()) { + return R.error("创建目录失败:" + viewPath.getAbsolutePath()); + } + } + + /* + * 启用VM引擎生成页面 + */ + DefaultVelocityEngine velocityEngine = makeVueVelocityEngine(codeVueDir, uiPage, names); + + // 生成vue主页面 + File vmFile = new File(viewPath.getAbsolutePath() + "/index.vue"); + R r = velocityEngine.buildUseSign(uiPage.getId(), "/codegen/vm/index.vue.vm", vmFile); + if (r != null) { + return r; + } + + // 生成dataset.ts文件 + File tsFile = new File(viewPath.getAbsolutePath() + "/dataset.ts"); + r = velocityEngine.buildUseSign(uiPage.getId(), "/codegen/vm/dataset.ts.vm", tsFile); + if (r != null) { + return r; + } + + // 生成 events.ts 事件文件 + r = velocityEngine.build("/codegen/vm/events.ts.vm", new File(viewPath.getAbsolutePath() + "/event.ts")); + if (r != null) { + return r; + } + + // 复制基础文件 + File commonPath = new File(codeVuePath + "/" + codeVueDir + "/page"); + if (!commonPath.exists()) { + if (!commonPath.mkdirs()) { + return R.error("创建目录失败:" + commonPath.getAbsolutePath()); + } + } + + copy("dataset.ts", commonPath); + copy("events.ts", commonPath); + copy("page.types.ts", commonPath); + + return R.success(vmFile.getAbsolutePath()); + } + + private DefaultVelocityEngine makeVueVelocityEngine(String codeVueDir, BpmPage uiPage, List routerNames) { + DefaultVelocityEngine velocityEngine = new DefaultVelocityEngine(); + + UiPageRefLoader pageRefLoader = new UiPageRefLoader(); + + velocityEngine.put("page", uiPage).put("pageRef", pageRefLoader.loadPageRef(uiPage, codeVueDir, pageRefLoader.getRouterName(routerNames))); + + return velocityEngine; + } + + private void copy(String srcFileName, File commonPath) throws IOException { + File destFile = new File(commonPath.getAbsoluteFile() + "/" + srcFileName); + + if (!destFile.exists()) { + try (InputStream is = this.getClass().getResourceAsStream("/codegen/ts/" + srcFileName); + OutputStream os = new FileOutputStream(destFile)) { + IOUtils.copy(is, os); + } + } + } + + private String getDir() { + String codeVueDir = bpmConfigBean.getCodeVueDir(); + if (StringUtils.isBlank(codeVueDir)) { + codeVueDir = "views"; + } + return codeVueDir; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/WidgetCodeService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/WidgetCodeService.java new file mode 100644 index 0000000..d9a0b88 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/codegen/WidgetCodeService.java @@ -0,0 +1,40 @@ +package cc.smtweb.system.bpm.web.design.codegen; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwBody; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.file.FilePathGenerator; +import cc.smtweb.system.bpm.spring.BpmConfigBean; +import cc.smtweb.system.bpm.util.YamlUtil; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; + +// 控件配置代码后台生成 +@Slf4j +@SwService +public class WidgetCodeService extends AbstractCodeService { + @SwParam + private FilePathGenerator filePathGenerator; + + @SwParam + private BpmConfigBean bpmConfigBean; + + /** 生成代码 */ + public R build(@SwParam("type") String type, @SwBody SwMap body) throws IOException { + // 资源文件路径 + String codeJavaPath = bpmConfigBean.getCodeJavaPath(); + String resourceRootPath = codeJavaPath.substring(0, codeJavaPath.length() - JavaCodeService.SRC_MAIN_JAVA.length()) + + JavaCodeService.SRC_MAIN_RES + "/bpm/ui/"; + File file = new File(resourceRootPath + type + ".yaml"); + if (file.exists()) { + return R.success(); + } + + YamlUtil.writeValue(file, body); + return R.success(file.getAbsolutePath()); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/db/DbConfigService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/db/DbConfigService.java new file mode 100644 index 0000000..a1dd69a --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/db/DbConfigService.java @@ -0,0 +1,16 @@ +package cc.smtweb.system.bpm.web.design.db; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.config.DesignConfig; + +@SwService +public class DbConfigService { + @SwParam + private DesignConfig designConfig; + + public R dataType() { + return R.success(designConfig.getDataTypes()); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/db/DbQueryDatasetService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/db/DbQueryDatasetService.java new file mode 100644 index 0000000..8cb9a58 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/db/DbQueryDatasetService.java @@ -0,0 +1,191 @@ +package cc.smtweb.system.bpm.web.design.db; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwBody; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.cache.ModelDatabaseCache; +import cc.smtweb.framework.core.db.cache.ModelTableCache; +import cc.smtweb.framework.core.db.vo.ModelDatabase; +import cc.smtweb.framework.core.db.vo.ModelField; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.system.bpm.engine.ui.entity.vo.dataset.FzDatasetPropsVO; +import cc.smtweb.system.bpm.engine.ui.entity.vo.dataset.FzDatasetVO; +import cc.smtweb.system.bpm.engine.ui.entity.vo.dataset.FzFieldVO; +import cc.smtweb.system.bpm.engine.ui.entity.vo.widiget.UiControlPropsVO; +import cc.smtweb.system.bpm.util.CodeGenUtil; +import org.apache.commons.lang3.StringUtils; + +import java.sql.ResultSetMetaData; +import java.sql.Types; +import java.util.*; + +@SwService +public class DbQueryDatasetService { + @SwParam + private DbEngine dbEngine; + + public R query(@SwBody SwMap body) { + String sql = body.readString("sql"); + + if (StringUtils.isBlank(sql)) { + return R.error("sql参数为空"); + } + + if(!sql.trim().toLowerCase().startsWith("select ")){ + return R.error("sql必须是查询语句"); + } + + if (sql.endsWith(";")) { + return R.error("sql语句需要去掉结束符合';'"); + } + + ModelDatabase modelDatabase = getModelDatabase(sql); + + FzDatasetVO uiDataset = new FzDatasetVO(); + uiDataset.setType("fz-dataset"); + uiDataset.setProps(new FzDatasetPropsVO()); + + dbEngine.query(sql + " limit 1", resultSet -> { + // 解析数据库中的库名 + ModelTable modelTable = null; + ResultSetMetaData rsmd = resultSet.getMetaData(); + int columnCount = rsmd.getColumnCount(); + List fields = new ArrayList<>(); + String masterTableName = null; + String masterTableLabel = null; + + for (int index = 1; index <= columnCount; ++index) { + FzFieldVO uiField = new FzFieldVO(); + UiControlPropsVO props = new UiControlPropsVO(); + uiField.setProps(props); + + String fieldName = rsmd.getColumnName(index); + String name = rsmd.getColumnLabel(index); + int size = rsmd.getColumnDisplaySize(index); + uiField.setId(name); + props.put("fieldName", fieldName); + props.put("name", CodeGenUtil.underlineToHump(name)); + props.put("length", size); + + switch (rsmd.getColumnType(index)) { + case Types.BIGINT: + uiField.setType("fz-field-long"); + props.put("dataType", "id"); + break; + case Types.INTEGER: + case Types.SMALLINT: + case Types.TINYINT: + uiField.setType("fz-field-int"); + props.put("dataType", "int"); + break; + case Types.CHAR: + case Types.NCHAR: + case Types.VARCHAR: + case Types.NVARCHAR: + uiField.setType("fz-field-string"); + props.put("dataType", "string"); + break; + case Types.FLOAT: + uiField.setType("fz-field-float"); + props.put("dataType", "float"); + break; + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + uiField.setType("fz-field-date"); + props.put("dataType", "date"); + break; + default: + throw new IllegalStateException("Unexpected value: " + rsmd.getColumnType(index)); + } + + String tableName = rsmd.getTableName(index); + + if (modelDatabase != null) { + modelTable = findModelTable(modelDatabase, modelTable, tableName); + + if (modelTable != null) { + ModelField modelField = modelTable.findFieldByName(name); + + if (modelField != null) { + props.put("label", modelField.getTitle()); + } + if (masterTableName == null) { + masterTableName = tableName; + masterTableLabel = modelTable.getTitle(); + } + } + } + + if (masterTableName == null) { + masterTableName = tableName; + masterTableLabel = tableName; + } + + fields.add(uiField); + } + + uiDataset.getProps().setFields(fields.toArray(new FzFieldVO[0])); + uiDataset.getProps().setName(masterTableName); + uiDataset.getProps().setLabel(masterTableLabel); + + return null; + }); + + return R.success(uiDataset); + } + + private ModelDatabase getModelDatabase(String sql) { + ModelDatabase modelDatabase = null; + String dbName = extractSqlDbName(sql); + Long modelId = dbEngine.queryLong("select model_id from sw_bpm.asp_model where model_key=? and model_type=1", dbName); + if (modelId != null) { + modelDatabase = ModelDatabaseCache.getInstance().get(modelId); + } + + return modelDatabase; + } + + private ModelTable findModelTable(ModelDatabase modelDatabase, ModelTable modelTable, String tableName) { + if (tableName != null) { + if (modelTable != null && tableName.equalsIgnoreCase(modelTable.getName())) { + return modelTable; + } + + return ModelTableCache.getInstance().getByName(tableName); + } + + return null; + } + + private static final Set KEYWORD_SET = new HashSet(){{ + this.add("from"); + this.add("join"); + }}; + + public static String extractSqlDbName(String sql) { + StringTokenizer st = new StringTokenizer(sql, " "); + + int step = 0; + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (step == 0) { + if (KEYWORD_SET.contains(token.toLowerCase())) { + step = 1; + } + } else { + String[] ary = token.split("\\."); + if (ary.length > 1) { + return ary[0]; + } + + step = 0; + } + } + + return null; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/dict/DictTypeListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/dict/DictTypeListService.java new file mode 100644 index 0000000..9341edc --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/dict/DictTypeListService.java @@ -0,0 +1,21 @@ +package cc.smtweb.system.bpm.web.design.dict; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.web.design.dict.entity.DictTypePO; + +import java.util.List; + +@SwService +public class DictTypeListService { + @SwParam + private DbEngine dbEngine; + + public R list() { + List list = dbEngine.query("select dt_id, dt_name, dt_desc from sw_user.sys_dict_type order by dt_name", + DictTypePO.class); + return R.success(list); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/dict/entity/DictTypePO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/dict/entity/DictTypePO.java new file mode 100644 index 0000000..e5e013b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/dict/entity/DictTypePO.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.web.design.dict.entity; + +import lombok.Data; + +@Data +public class DictTypePO { + private Long dtId; + private String dtName; + private String dtDesc; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspDbCardService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspDbCardService.java new file mode 100644 index 0000000..bda2f01 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspDbCardService.java @@ -0,0 +1,135 @@ +package cc.smtweb.system.bpm.web.design.model; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwBody; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.cache.ModelDatabaseCache; +import cc.smtweb.framework.core.db.cache.ModelTableCache; +import cc.smtweb.framework.core.db.sqlbuilder.InsertSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.UpdateSqlBuilder; +import cc.smtweb.framework.core.db.vo.ModelDatabase; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.core.util.DateUtil; +import cc.smtweb.framework.core.db.config.DesignConfig; +import cc.smtweb.system.bpm.spring.dao.DatasetConfigDao; +import org.apache.commons.lang3.StringUtils; + +//@Api(tags = "数据库设计") +@SwService +public class AspDbCardService { + @SwParam + private DbEngine dbEngine; + + @SwParam + private ModelDatabaseCache bpmDbCache; + private ModelTableCache bpmTableCache; + + @SwParam + private DesignConfig designConfig; + + @SwParam + protected DatasetConfigDao datasetConfigDao; + + // @ApiOperation(value = "读取数据模型数据") + public R load(@SwParam("id") long id, UserSession us) { + ModelDatabase db = bpmDbCache.get(id); + if (db == null) { + return R.error("没有找到指定数据库定义(" + id + ")!"); + } + + return R.success(db); + } + + public R loadByKey(@SwParam("key") String key, @SwParam("type") String type, UserSession us) { + ModelDatabase db = bpmDbCache.getByName(key); + if (db == null) { + return R.error("没有找到指定数据库定义(" + key + ")!"); + } + return R.success(db); + } + + private boolean isNullId(Long id) { + return id == null || id == 0; + } + + // @ApiOperation(value = "创建数据模型") + public R save(@SwBody ModelDatabase modelPo, UserSession us) { + + if (StringUtils.isBlank(modelPo.getName())) { + return R.error("库名不能为空"); + } + if (StringUtils.isBlank(modelPo.getTitle())) { + return R.error("库标题不能为空"); + } + + modelPo.setUpdateTime(DateUtil.nowDateTimeLong()); + + // 保存数据到部署表和部署历史表 + if (isNullId(modelPo.getId())) { + dbEngine.doTrans(() -> { + modelPo.setId(dbEngine.nextId()); + + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert("sw_bpm.asp_model_database"); + sqlBuilder.add("db_id", modelPo.getId()) + .add("db_name", modelPo.getName()) + .add("db_title", modelPo.getTitle()) + .add("db_status", modelPo.getStatus()) + .add("db_version", modelPo.getVersion()) + .add("db_create_at", modelPo.getCreateTime()) + .add("db_update_at", modelPo.getUpdateTime()) + .add("db_create_uid", us.getUserId()) + .add("db_update_uid", us.getUserId()); + sqlBuilder.update(dbEngine); + + return true; + }); + + bpmDbCache.put(modelPo); + + return R.success(modelPo.getId()); + } else { + modelPo.setVersion(modelPo.getVersion() + 1); + + // 更新数据 + boolean r = dbEngine.doTrans(() -> { + + UpdateSqlBuilder sqlBuilder = SqlBuilder.createUpdate("sw_bpm.asp_model_database"); + + sqlBuilder.add("db_name", modelPo.getName()) + .add("db_title", modelPo.getTitle()) + .add("db_status", modelPo.getStatus()) + .add("db_version", modelPo.getVersion()) + .add("db_update_at", modelPo.getUpdateTime()) + .add("db_update_uid", us.getUserId()); + + int ret = sqlBuilder.update(dbEngine); + + if (ret > 0) { + bpmDbCache.put(modelPo); + return true; + } else { + bpmDbCache.reset(modelPo); + } + + return false; + }); + + + return R.error("保存失败,未找到记录"); + } + } + + // @ApiOperation(value = "删除数据模型") + public R delete(@SwBody ModelDatabase modelPo, UserSession us) { + String sql = "delete from sw_bpm.asp_model_database where model_id=?"; + if (dbEngine.update(sql, modelPo.getId()) > 0) { + bpmDbCache.remove(modelPo.getId()); + return R.success(); + } + + return R.error("删除失败"); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelCardService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelCardService.java new file mode 100644 index 0000000..86149b0 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelCardService.java @@ -0,0 +1,310 @@ +package cc.smtweb.system.bpm.web.design.model; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwBody; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.sqlbuilder.InsertSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.UpdateSqlBuilder; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.core.util.DateUtil; +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.engine.AbstractLoader; +import cc.smtweb.system.bpm.engine.entity.AspModelPO; +import cc.smtweb.system.bpm.engine.entity.AspModelTreeVO; +import cc.smtweb.system.bpm.engine.process.loader.FlowLoader; +import cc.smtweb.system.bpm.engine.ui.loader.IPageRouter; +import cc.smtweb.system.bpm.engine.ui.loader.TreePageRouter; +import cc.smtweb.system.bpm.engine.ui.loader.UiMobileLoader; +import cc.smtweb.system.bpm.engine.ui.loader.UiPcLoader; +import cc.smtweb.framework.core.db.config.DesignConfig; +import cc.smtweb.system.bpm.spring.cache.BpmFlowCache; +import cc.smtweb.system.bpm.spring.cache.BpmPageCache; +import cc.smtweb.system.bpm.util.ITreeDataLevelHandler; +import cc.smtweb.system.bpm.util.TreeDataUtil; +import cc.smtweb.system.bpm.spring.dao.DatasetConfigDao; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +//@Api(tags = "数据模型") +@SwService +public class AspModelCardService { + @SwParam + private DbEngine dbEngine; + + @SwParam + private BpmFlowCache bpmFlowCache; + + @SwParam + private BpmPageCache bpmPageCache; + + @SwParam + private DesignConfig designConfig; + + @SwParam + protected DatasetConfigDao datasetConfigDao; + + // @ApiOperation(value = "读取数据模型数据") + public R load(@SwParam("id") long id, UserSession us) { + String sql = "select model_id, model_parent_id, model_mc_id, model_site_id, model_key, model_name, model_status, model_content, model_type, model_version, model_create_uid, model_update_uid, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_id=? and model_site_id=?"; + AspModelPO po = dbEngine.queryEntity(sql, AspModelPO.class, id, us.getSiteId()); + return R.success(po); + } + + public R loadByKey(@SwParam("key") String key, @SwParam("type") String type, UserSession us) { + String sql = "select model_id, model_parent_id, model_mc_id, model_site_id, model_key, model_name, model_status, model_content, model_type, model_version, model_create_uid, model_update_uid, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_key=? and model_type=? and model_site_id=?"; + AspModelPO po = dbEngine.queryEntity(sql, AspModelPO.class, key, type, us.getSiteId()); + return R.success(po); + } + + private boolean isNullId(Long id) { + return id == null || id == 0; + } + +// @ApiOperation(value = "创建数据模型") + public R save(@SwParam("commit") boolean commit, @SwBody AspModelPO modelPo, UserSession us) { + String modelContent = modelPo.getModelContent(); + if (StringUtils.isBlank(modelContent)) { + return R.error("内容不能为空"); + } + + if (commit) { + // 检查数据 + AbstractLoader loader = null; + switch (modelPo.getModelType()) { + case AspModelPO.TYPE_FLOW: + loader = new FlowLoader(); + break; + case AspModelPO.TYPE_UI_PC: + loader = new UiPcLoader(loadTree(modelPo.getModelId())); + break; + case AspModelPO.TYPE_UI_MOBILE: + loader = new UiMobileLoader(loadTree(modelPo.getModelId())); + break; + default: + return R.error("error model type: " + modelPo.getModelType()); + } + + Object page = loader.load(modelContent); + + if (loader.getLastError() != null) { + return R.error(loader.getLastError()).put("modelErrors", loader.getErrors()); + } + + // 检测 modern + path + if (page instanceof BpmPage) { + // 检测和读取页面目录 + BpmPage uiPage = (BpmPage)page; + uiPage.setId(modelPo.getModelId()); +// uiPage.getForm().setKey(modelPo.getModelKey()); +// uiPage.getForm().setTitle(modelPo.getModelName()); +// uiPage.setParentId(modelPo.getModelParentId()); + + List names = datasetConfigDao.loadPagePath(uiPage, true); + modelPo.setModelFullName(String.join(".", names)); + } + + modelPo.setModelStatus(0); + // 计算签名 + modelPo.setModelContentSign(DigestUtils.sha256Hex(modelContent) + "," + modelContent.length()); + } else { + modelPo.setModelStatus(-1); + } + + modelPo.setModelLastTime(DateUtil.nowDateTimeLong()); + + // 保存数据到部署表和部署历史表 + if (isNullId(modelPo.getModelId())) { + if (isNullId(modelPo.getModelMcId()) && isUiType(modelPo)) { + return R.error("页面缺少分类ID"); + } + + dbEngine.doTrans(() -> { + modelPo.setModelId(dbEngine.nextId()); + modelPo.setModelVersion(commit ? 1 : 0); + + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert("sw_bpm.asp_model"); + sqlBuilder.add("model_id", modelPo.getModelId()) + .add("model_parent_id", modelPo.getModelParentId()) + .add("model_mc_id", modelPo.getModelMcId()) + .add("model_site_id", us.getSiteId()) + .add("model_key", modelPo.getModelKey()) + .add("model_name", modelPo.getModelName()) + .add("model_status", modelPo.getModelStatus()) + .add("model_content", modelContent) +// .add("model_content_sign", contentSign) + .add("model_type", modelPo.getModelType()) + .add("model_sub_type", modelPo.getModelSubType()) + .add("model_version", modelPo.getModelVersion()) + .add("model_create_time", modelPo.getModelLastTime()) + .add("model_last_time", modelPo.getModelLastTime()) + .add("model_create_uid", us.getUserId()) + .add("model_update_uid", us.getUserId()); + sqlBuilder.update(dbEngine); + + if (commit) { + this.insertDeployLog(modelPo, us); + } + + return true; + }); + + updateCache(modelPo.getModelId(), modelPo.getModelType()); + + return R.success(modelPo.getModelId()); + } else { + if (commit) { + // 比较签名 + AspModelPO old = dbEngine.queryEntity("select model_version, model_content_sign from sw_bpm.bpm_model where model_id=?", AspModelPO.class, modelPo.getModelId()); + if (old != null) { + if (old.getModelContentSign() != null && old.getModelContentSign().equals(modelPo.getModelContentSign())) { + return R.success(); + } + + modelPo.setModelVersion(old.getModelVersion() + 1); + } else { + modelPo.setModelVersion(1); + } + } + + // 更新数据 + boolean r = dbEngine.doTrans(() -> { +// Timestamp now = new Timestamp(System.currentTimeMillis()); + UpdateSqlBuilder sqlBuilder = SqlBuilder.createUpdate("sw_bpm.asp_model"); +// .add("model_parent_id", modelPo.getModelParentId()) +// .add("model_mc_id", modelPo.getModelMcId()) + sqlBuilder.add("model_key", modelPo.getModelKey()) + .add("model_name", modelPo.getModelName()) + .add("model_content", modelContent) +// .add("model_content_sign", contentSign) + .add("model_status", modelPo.getModelStatus()) + .add("model_update_uid", us.getUserId()) + .add("model_last_time", modelPo.getModelLastTime()) + .addWhere("model_id", modelPo.getModelId()) + .addWhere("model_site_id", us.getSiteId()); + + if (commit) { + sqlBuilder.add("model_version", modelPo.getModelVersion()); + } + + int ret = sqlBuilder.update(dbEngine); + + if (ret > 0) { + if (commit) { + this.insertDeployLog(modelPo, us); + } + + return true; + } + + return false; + }); + + if (r) { + updateCache(modelPo.getModelId(), modelPo.getModelType()); + return R.success(); + } + + return R.error("保存失败,未找到记录"); + } + } + + private IPageRouter loadTree(Long pageId) { + ITreeDataLevelHandler handler = AspModelTreeVO.createTreeHandler(); + + List list = dbEngine.query("select model_id, model_parent_id, model_key, model_name from sw_bpm.asp_model where model_mc_id in" + + " (select model_mc_id from sw_bpm.asp_model where model_id=?)", + AspModelTreeVO.class, pageId); + AspModelTreeVO root = new AspModelTreeVO(); + TreeDataUtil.buildLevelTree(root, list, handler); + return new TreePageRouter(root, null); + } + + private boolean isUiType(@SwBody AspModelPO modelPo) { + return modelPo.getModelType() == AspModelPO.TYPE_UI_PC || modelPo.getModelType() == AspModelPO.TYPE_UI_MOBILE; + } + + private void insertDeployLog(AspModelPO modelPo, UserSession us) { + // 部署 + Long oldId = dbEngine.queryLong("select model_id from sw_bpm.bpm_model where model_id=?", modelPo.getModelId()); + + if (oldId != null) { + UpdateSqlBuilder sqlBuilder = SqlBuilder.createUpdate("sw_bpm.bpm_model"); + sqlBuilder.add("model_full_name", modelPo.getModelFullName()) + .add("model_name", modelPo.getModelName()) + .add("model_status", 0) + .add("model_use_vue", modelPo.getModelUseVue()) + .add("model_use_yaml", modelPo.getModelUseYaml()) + .add("model_content", modelPo.getModelContent()) + .add("model_content_sign", modelPo.getModelContentSign()) + .add("model_version", modelPo.getModelVersion()) + .add("model_last_time", modelPo.getModelLastTime()) + .addWhere("model_id", modelPo.getModelId()); + sqlBuilder.update(dbEngine); + } else { + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert("sw_bpm.bpm_model"); + sqlBuilder.add("model_id", modelPo.getModelId()) + .add("model_full_name", modelPo.getModelFullName()) + .add("model_site_id", us.getSiteId()) + .add("model_name", modelPo.getModelName()) + .add("model_status", 0) + .add("model_content", modelPo.getModelContent()) + .add("model_content_sign", modelPo.getModelContentSign()) + .add("model_type", modelPo.getModelType()) + .add("model_version", modelPo.getModelVersion()) + .add("model_create_time", modelPo.getModelLastTime()) + .add("model_last_time", modelPo.getModelLastTime()); + sqlBuilder.update(dbEngine); + } + + // 产生部署日志 + Long dmId = dbEngine.nextId(); + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert("sw_bpm.asp_model_deploy"); + sqlBuilder.add("md_id", dmId) + .add("md_model_id", modelPo.getModelId()) + .add("md_model_key", modelPo.getModelKey()) + .add("md_name", modelPo.getModelName()) + .add("md_status", modelPo.getModelStatus()) + .add("md_content", modelPo.getModelContent()) + .add("md_type", modelPo.getModelType()) + .add("md_version", modelPo.getModelVersion()) + .add("md_create_uid", us.getUserId()); + sqlBuilder.update(dbEngine); + } + + private void updateCache(Long modelId, byte modelType) { + switch (modelType) { + case AspModelPO.TYPE_FLOW: + bpmFlowCache.remove(modelId); + break; + case AspModelPO.TYPE_UI_PC: + case AspModelPO.TYPE_UI_MOBILE: + bpmPageCache.remove(modelId); + break; + default: + } + } + + // @ApiOperation(value = "删除数据模型") + public R delete(@SwBody AspModelPO modelPo, UserSession us) { + String sql = "delete from sw_bpm.asp_model where model_id=? and model_site_id=?"; + if (dbEngine.update(sql, modelPo.getModelId(), us.getSiteId()) > 0) { + updateCache(modelPo.getModelId(), modelPo.getModelType()); + return R.success(); + } + + return R.error("删除失败"); + } + + // 修改子类型 + public R saveSubType(@SwBody AspModelPO modelPo, UserSession us) { + dbEngine.update("update sw_bpm.asp_model set model_sub_type=? where model_id=? and model_site_id=?", modelPo.getModelSubType(), modelPo.getModelId(), us.getSiteId()); + return R.success(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelConfigService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelConfigService.java new file mode 100644 index 0000000..21c4161 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelConfigService.java @@ -0,0 +1,55 @@ +package cc.smtweb.system.bpm.web.design.model; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.system.bpm.spring.BpmConfigBean; +import cc.smtweb.framework.core.db.config.DesignConfig; +import cc.smtweb.system.bpm.util.YamlUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@SwService +public class AspModelConfigService { + @SwParam + private DesignConfig designConfig; + + @SwParam + private BpmConfigBean bpmConfigBean; + + public R load() { + R result = R.success().put("dataType", designConfig.getDataTypes()); + String configUiPath = bpmConfigBean.getConfigUiPath(); + + if (StringUtils.isNotBlank(configUiPath)) { + File file = new File(configUiPath); + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + Map config = new HashMap<>(); + for (File yamlFile: files) { + String fileName = yamlFile.getName(); + if (fileName.endsWith(".yaml") && fileName.startsWith("fx-")) { + SwMap meta = YamlUtil.readValue(yamlFile, SwMap.class); + config.put(fileName.substring(0, fileName.length() - 5), meta); + } + } + + if (!config.isEmpty()) { + result.put("schema", config); + } + } + } else { + log.error("not find path:" + configUiPath); + } + } + + return result; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelExportService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelExportService.java new file mode 100644 index 0000000..3430e8b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelExportService.java @@ -0,0 +1,73 @@ +package cc.smtweb.system.bpm.web.design.model; + +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.jdbc.IdGenerator; +import cc.smtweb.framework.core.db.config.DesignConfig; +import cc.smtweb.system.bpm.spring.cache.BpmFlowCache; +import cc.smtweb.system.bpm.spring.cache.BpmPageCache; + +@SwService +public class AspModelExportService { + @SwParam + private DbEngine dbEngine; + + @SwParam + private BpmFlowCache bpmFlowCache; + + @SwParam + private BpmPageCache bpmPageCache; + + @SwParam + private IdGenerator idGenerator; + + @SwParam + private DesignConfig designConfig; + + /*// @ApiOperation(value = "读取数据模型数据") + public R mysql(@SwBody SwMap body, UserSession us) { + String sql = "select model_id, model_parent_id, model_mc_id, model_site_id, model_key, model_name, model_status, model_content, model_type, model_version, model_create_uid, model_update_uid, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_id=? and model_site_id=?"; + AspModelPO po = dbEngine.queryEntity(sql, AspModelPO.class, body.readLong("modelId"), us.getSiteId()); + + DbLoader loader = new DbLoader(designConfig.getDataType()); + + ModelDatabase modelDatabase = loader.load(po.getModelContent()); + + if (loader.getLastError() != null) { + return R.error(loader.getLastError()).put("modelErrors", loader.getErrors()); + } + + // 创建数据库 + HikariDataSource datasource = new HikariDataSource(); + + String host = body.readString("host", "127.0.0.1"); + Integer port = body.readInt("port", 3306); + String dbName = body.readString("database", modelDatabase.getName()); + modelDatabase.setName(dbName); + + datasource.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + dbName + "?serverTimezone=UTC&allowMultiQueries=true&useSSL=false"); + datasource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + datasource.setUsername(body.readString("username")); + datasource.setPassword(body.readString("password")); + + try { + JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource); + DbEngine dynDbEngine = new DbEngine(jdbcTemplate, idGenerator); + + // 导入数据库结构 + SqlImport sqlImport = new SqlImport(dynDbEngine); + ModelDatabase oldDb = sqlImport.importDb(dbName); + + SqlExport sqlExport = new SqlExport(); + + // 导出数据库结构 + sqlExport.export(modelDatabase, oldDb, dynDbEngine::update); + } finally { + datasource.close(); + } + + return R.success(); + }*/ +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelListService.java new file mode 100644 index 0000000..1a7dfee --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelListService.java @@ -0,0 +1,25 @@ +package cc.smtweb.system.bpm.web.design.model; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.system.bpm.engine.entity.AspModelPO; + +import java.util.List; + +// @Api(tags = "数据模型") +@SwService +public class AspModelListService { + @SwParam + private DbEngine dbEngine; + +// @ApiOperation(value = "数据模型列表") + public R list(@SwParam("type") long type, UserSession us) { + String sql = "select model_id, model_mc_id, model_site_id, model_name, model_key, model_status, model_type, model_sub_type, model_version, model_create_uid, model_update_uid, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_site_id=? and model_type=?"; + List list = dbEngine.query(sql, AspModelPO.class,us.getSiteId(), type); + return R.success(list); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelTreeService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelTreeService.java new file mode 100644 index 0000000..5e0d836 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/AspModelTreeService.java @@ -0,0 +1,103 @@ +package cc.smtweb.system.bpm.web.design.model; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwBody; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.system.bpm.engine.entity.AspModelCatalogPO; +import cc.smtweb.system.bpm.engine.entity.AspModelPO; +import cc.smtweb.system.bpm.engine.entity.AspModelTreeVO; +import cc.smtweb.system.bpm.util.TreeDataUtil; +import cc.smtweb.system.bpm.web.design.model.entity.TreeMoveVO; + +import java.util.ArrayList; +import java.util.List; + +@SwService +public class AspModelTreeService { + @SwParam + private DbEngine dbEngine; + + public R treeAll(@SwParam("mcId") long mcId, @SwParam("modelType") long modelType, UserSession us) { + return R.success(loadTreeAll(mcId, modelType, us)); + } + + private List loadTreeAll(@SwParam("mcId") long mcId, @SwParam("modelType") long modelType, UserSession us) { + String sql = "select model_id, model_parent_id, model_key, model_name, model_status, model_type, model_sub_type, model_version, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_mc_id=? and model_type=? and model_site_id=? order by model_order, model_id"; + List list = dbEngine.query(sql, AspModelTreeVO.class, mcId, modelType, us.getSiteId()); + + AspModelTreeVO root = new AspModelTreeVO(); + root.setModelName("项目"); + + return TreeDataUtil.buildTree(root, list, AspModelTreeVO.createTreeHandler()); + } + + public R menu(@SwParam("mcId") long mcId, @SwParam("modelType") long modelType, UserSession us) { + List tree = loadTreeAll(mcId, modelType, us); + + // 强制只显示2级 + if (tree != null) { + for (AspModelTreeVO item : tree) { + List children = item.getChildren(); + + if (children != null) { + for (AspModelTreeVO child : item.getChildren()) { + child.setChildren(null); + } + } + } + } + + return R.success(tree); + } + + /** 动态Tree数据 */ + public R tree(@SwParam("type") long type, @SwParam("catalog") long catalog, @SwParam("parentId") Long parentId, UserSession us) { + R result = null; + if (parentId == null) { + String sql = "select model_id, model_mc_id, model_key, model_name, model_status, model_version, model_create_uid, model_update_uid, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_mc_id=? and model_parent_id is null and model_site_id=? and model_type=? order by model_order, model_id"; + List list = dbEngine.query(sql, AspModelPO.class, catalog, us.getSiteId(), type); + result = R.success(list); + + sql = "select mc_id, mc_parent_id, mc_code, mc_site_id, mc_name, mc_create_time, mc_last_time from sw_bpm.asp_model_catalog" + + " where mc_id=? and mc_site_id=?"; + AspModelCatalogPO po = dbEngine.queryEntity(sql, AspModelCatalogPO.class, catalog, us.getSiteId()); + result.put("catalog", po); + } else { + String sql = "select model_id, model_mc_id, model_key, model_name, model_status, model_version, model_create_uid, model_update_uid, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_mc_id=? and model_parent_id=? and model_site_id=? and model_type=? order by model_order, model_id"; + List list = dbEngine.query(sql, AspModelPO.class, catalog, parentId, us.getSiteId(), type); + result = R.success(list); + + AspModelPO parent = dbEngine.queryEntity("select model_id, model_mc_id, model_parent_id, model_key, model_name, model_status, model_version, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_id=?",AspModelPO.class, parentId); + + result.put("parent", parent); + } + + return result; + } + + public R move(@SwBody TreeMoveVO vo, UserSession us) { + if (vo.getId() != null && vo.getOrders() != null) { + List params = new ArrayList<>(); + + int index = 0; + for (Long order: vo.getOrders()) { + params.add(new Object[]{vo.getParentId(), ++index, order}); + } + + int[] ret = dbEngine.batchUpdate("update sw_bpm.asp_model set model_parent_id=?, model_order=? where model_id=?", params); + + if (ret.length > 0) { + return R.success(); + } + } + + return R.error(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/entity/TreeMoveVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/entity/TreeMoveVO.java new file mode 100644 index 0000000..8f9c389 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/model/entity/TreeMoveVO.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.web.design.model.entity; + +import lombok.Data; + +@Data +public class TreeMoveVO { + private Long id; + private Long parentId; + private Long[] orders; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/modelcatalog/AspModelCatalogCardService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/modelcatalog/AspModelCatalogCardService.java new file mode 100644 index 0000000..5344ff6 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/modelcatalog/AspModelCatalogCardService.java @@ -0,0 +1,81 @@ +package cc.smtweb.system.bpm.web.design.modelcatalog; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwBody; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.core.util.DateUtil; +import cc.smtweb.system.bpm.engine.entity.AspModelCatalogPO; +import org.apache.commons.lang3.StringUtils; + +@SwService +public class AspModelCatalogCardService { + @SwParam + private DbEngine dbEngine; + + public R load(@SwParam("id") long id, UserSession us) { + String sql = "select mc_id, mc_parent_id, mc_code, mc_site_id, mc_name, mc_create_time, mc_last_time from sw_bpm.asp_model_catalog" + + " where mc_id=? and mc_site_id=?"; + AspModelCatalogPO po = dbEngine.queryEntity(sql, AspModelCatalogPO.class, id, us.getSiteId()); + return R.success(po); + } + + public R save(@SwBody AspModelCatalogPO po, UserSession us) { + if (StringUtils.isBlank(po.getMcName())) { + return R.error("必须填写项目名称"); + } + + if (StringUtils.isBlank(po.getMcModule())) { + return R.error("必须填写模块名"); + } + + Long oldId = dbEngine.queryLong("select mc_id from sw_bpm.asp_model_catalog where mc_module=? and mc_id<>?", + po.getMcModule(), po.getMcId() == null ? 0 : po.getMcId()); + if (oldId != null) { + return R.error("模块名有重复"); + } + + po.setMcLastTime(DateUtil.nowDateTimeLong()); + if (po.getMcId() == null || po.getMcId() == 0L) { + po.setMcId(dbEngine.nextId()); + + SqlBuilder.createInsert("sw_bpm.asp_model_catalog") + .add("mc_id", po.getMcId()) + .add("mc_name", po.getMcName()) + .add("mc_module", po.getMcModule()) + .add("mc_site_id", us.getSiteId()) + .add("mc_create_time", po.getMcLastTime()) + .add("mc_last_time", po.getMcLastTime()) + .update(dbEngine); + + return R.success(po.getMcId()); + } else { + SqlBuilder.createUpdate("sw_bpm.asp_model_catalog") + .add("mc_name", po.getMcName()) + .add("mc_module", po.getMcModule()) + .add("mc_last_time", po.getMcLastTime()) + .addWhere("mc_id", po.getMcId()) + .addWhere("mc_site_id", us.getSiteId()) + .update(dbEngine); + + return R.success(); + } + } + + public R delete(@SwBody AspModelCatalogPO modelPo, UserSession us) { + Long id = dbEngine.queryLong("select model_id from sw_bpm.asp_model where model_mc_id=? limit 1", modelPo.getMcId()); + if (id != null) { + return R.error("已有页面存在,不允许删除项目"); + } + + String sql = "delete from sw_bpm.asp_model_catalog where mc_id=? and mc_site_id=?"; + if (dbEngine.update(sql, modelPo.getMcId(), us.getSiteId()) > 0) { + return R.success(); + } + + return R.error("删除失败"); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/modelcatalog/AspModelCatalogListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/modelcatalog/AspModelCatalogListService.java new file mode 100644 index 0000000..305077d --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/modelcatalog/AspModelCatalogListService.java @@ -0,0 +1,25 @@ +package cc.smtweb.system.bpm.web.design.modelcatalog; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.system.bpm.engine.entity.AspModelCatalogPO; +import cc.smtweb.system.bpm.web.design.preview.entity.AppVO; + +import java.util.List; + +@SwService +public class AspModelCatalogListService { + @SwParam + private DbEngine dbEngine; + + public R list(UserSession us) { + String sql = "select mc_id, mc_parent_id, mc_code, mc_site_id, mc_name, mc_module, mc_create_time, mc_last_time from sw_bpm.asp_model_catalog" + + " where mc_site_id=?"; + List list = dbEngine.query(sql, AspModelCatalogPO.class, us.getSiteId()); + + return R.success(list); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/PreviewAppCardService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/PreviewAppCardService.java new file mode 100644 index 0000000..ea3cf01 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/PreviewAppCardService.java @@ -0,0 +1,24 @@ +package cc.smtweb.system.bpm.web.design.preview; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.system.bpm.web.design.preview.entity.AppVO; + +@SwService +public class PreviewAppCardService { + @SwParam + private DbEngine dbEngine; + + public R load(@SwParam("module") String module, UserSession us) { + String sql = "select mc_id id, mc_name name, mc_module module from sw_bpm.asp_model_catalog" + + " where mc_module=? and mc_site_id=?"; + AppVO app = dbEngine.queryEntity(sql, AppVO.class, module, us.getSiteId()); + if (app == null) { + return R.error(module + " 模块不存在!"); + } + return R.success(app); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/PreviewAppListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/PreviewAppListService.java new file mode 100644 index 0000000..b8fa430 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/PreviewAppListService.java @@ -0,0 +1,31 @@ +package cc.smtweb.system.bpm.web.design.preview; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.system.bpm.engine.entity.AspModelCatalogPO; +import cc.smtweb.system.bpm.util.TreeDataUtil; +import cc.smtweb.system.bpm.web.design.preview.entity.AppVO; +import cc.smtweb.system.bpm.web.design.preview.entity.MenuVO; + +import java.util.List; + +@SwService +public class PreviewAppListService { + @SwParam + private DbEngine dbEngine; + + public R listAll(UserSession us) { + String sql = "select mc_id id, mc_name name, mc_module module from sw_bpm.asp_model_catalog" + + " where mc_site_id=? order by mc_name"; + List list = dbEngine.query(sql, AppVO.class, us.getSiteId()); + if (list != null) { + for (AppVO app: list) { + app.setUrl("/static/uc/index.html#/?module=" + app.getModule()); + } + } + return R.success(list); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/PreviewMenuTreeService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/PreviewMenuTreeService.java new file mode 100644 index 0000000..50f00f0 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/PreviewMenuTreeService.java @@ -0,0 +1,50 @@ +package cc.smtweb.system.bpm.web.design.preview; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.system.bpm.util.TreeDataUtil; +import cc.smtweb.system.bpm.web.design.preview.entity.MenuVO; + +import java.util.List; + +@SwService +public class PreviewMenuTreeService { + @SwParam + private DbEngine dbEngine; + + public R treeAll(@SwParam("module") String module, UserSession us) { + Long mcId = dbEngine.queryLong("select mc_id from sw_bpm.asp_model_catalog where mc_module=? and mc_site_id=?", module, us.getSiteId()); + if (mcId == null) { + return R.error(module + " 模块不存在!"); + } + /* + * id: "11", + * name: "用户管理", + * icon: "", + * path: "/bpm/uc.system.user.list", + */ + String sql = "select model_id id, model_parent_id parent_id, model_key path, model_name name from sw_bpm.asp_model" + + " where model_mc_id=? and model_type=? and model_site_id=? order by model_order, model_id"; + List list = dbEngine.query(sql, MenuVO.class, mcId, 3, us.getSiteId()); + + MenuVO root = new MenuVO(); + root.setName("项目"); + root.setPath(module); + List data = TreeDataUtil.buildTree(root, list, MenuVO.createTreeHandler()); + fixPath(data, "/bpm/" + module); + + return R.success(data); + } + + private void fixPath(List children, String path) { + if (children != null) { + for (MenuVO menu : children) { + menu.setPath(path + "." + menu.getPath()); + fixPath(menu.getChildren(), menu.getPath()); + } + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/entity/AppVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/entity/AppVO.java new file mode 100644 index 0000000..5dac2e6 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/entity/AppVO.java @@ -0,0 +1,18 @@ +package cc.smtweb.system.bpm.web.design.preview.entity; + +import cc.smtweb.framework.core.annotation.SwTable; +import lombok.Data; + +/** + * asp_model_catalog + * @author xkliu + */ +@Data +@SwTable("sw_bpm.asp_model_catalog") +public class AppVO { + private Long id; + private String name; + private String module; + private String url; + private String icon; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/entity/MenuVO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/entity/MenuVO.java new file mode 100644 index 0000000..6dd9ded --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/preview/entity/MenuVO.java @@ -0,0 +1,52 @@ +package cc.smtweb.system.bpm.web.design.preview.entity; + +import cc.smtweb.system.bpm.util.ITreeDataLevelHandler; +import lombok.Data; + +import java.util.List; + +@Data +public class MenuVO { + private Long id; + private Long parentId; + private int level; + private String name; + private String path; + private String icon; + private List children; + + // 树结构数据句柄 + public static ITreeDataLevelHandler createTreeHandler() { + return new ITreeDataLevelHandler () { + @Override + public Long getId (MenuVO node){ + return node.getId(); + } + + @Override + public Long getParentId (MenuVO node){ + return node.getParentId(); + } + + @Override + public List getChildren (MenuVO node){ + return node.children; + } + + @Override + public void setChildren (MenuVO node, List children){ + node.children = children; + } + + @Override + public int getLevel (MenuVO node){ + return node.level; + } + + @Override + public void setLevel (MenuVO node, int level){ + node.level = level; + } + } ; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/user/UserGroupListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/user/UserGroupListService.java new file mode 100644 index 0000000..39a77f9 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/user/UserGroupListService.java @@ -0,0 +1,30 @@ +package cc.smtweb.system.bpm.web.design.user; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.web.design.user.entity.UserGroupPO; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + + +/** + * 角色 + * @author xkliu + * @since 1.0.0 + */ +//@Api(tags = "用户组") +@Slf4j +@SwService +public class UserGroupListService { + @SwParam + private DbEngine dbEngine; + +// @ApiOperation(value = "用户组列表", nickname = "1.0.2", response = RolePO.class) + public R list() { + List userGroupPos = dbEngine.query("SELECT ug_id, ug_name, ug_create_time, ug_last_time FROM sw_bpm.bpm_user_group", UserGroupPO.class); + return R.success(userGroupPos); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/user/entity/UserGroupPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/user/entity/UserGroupPO.java new file mode 100644 index 0000000..cd0355f --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/design/user/entity/UserGroupPO.java @@ -0,0 +1,14 @@ +package cc.smtweb.system.bpm.web.design.user.entity; + +import lombok.Data; + +@Data +public class UserGroupPO { + private Long ugId; + + private String ugName; + + private String ugCreateAt; + + private String ugUpdateAt; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/flow/BpmFlowListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/flow/BpmFlowListService.java new file mode 100644 index 0000000..8c85222 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/flow/BpmFlowListService.java @@ -0,0 +1,26 @@ +package cc.smtweb.system.bpm.web.flow; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.system.bpm.engine.entity.AspModelPO; + +import java.util.List; + +// @Api(tags = "工作流流程接口") +@SwService +public class BpmFlowListService { + @SwParam + private DbEngine dbEngine; + +// @ApiOperation(value = "读取流程列表") + public R list(UserSession us) { + String sql = "select model_id, model_mc_id, model_site_id, model_name, model_status, model_content, model_type, model_version, model_create_uid, model_update_uid, model_create_time, model_last_time from sw_bpm.asp_model" + + " where model_type=? order by model_id"; + List list = dbEngine.query(sql, AspModelPO.class, 2); + + return R.success(list); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/entity/ProcessDetailLogPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/entity/ProcessDetailLogPO.java new file mode 100644 index 0000000..c19f84a --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/entity/ProcessDetailLogPO.java @@ -0,0 +1,24 @@ +package cc.smtweb.system.bpm.web.task.entity; + +import lombok.Data; + +import java.sql.Timestamp; + +@Data +public class ProcessDetailLogPO { + private Long pdlId; + + private Long pdlProcessId; + + private Long pdlProcessRootId; + + private Long pdlTaskId; + + private String pdlContent; + + private Integer pdlLockRev; + + private Timestamp pdlCreateAt; + + private Timestamp pdlUpdateAt; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/entity/UserGroupTargetPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/entity/UserGroupTargetPO.java new file mode 100644 index 0000000..d30dbd2 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/entity/UserGroupTargetPO.java @@ -0,0 +1,10 @@ +package cc.smtweb.system.bpm.web.task.entity; + +import lombok.Data; + +@Data +public class UserGroupTargetPO { + private long ugtId; + private byte ugtType; + private String ugtTarget; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/entity/UserPO.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/entity/UserPO.java new file mode 100644 index 0000000..a7620a5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/entity/UserPO.java @@ -0,0 +1,14 @@ +package cc.smtweb.system.bpm.web.task.entity; + +import lombok.Data; + +@Data +public class UserPO { + private Long userId; + + private String userName; + + private Long userSiteId; + + private Integer userStatus; +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/service/BpmTaskCardService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/service/BpmTaskCardService.java new file mode 100644 index 0000000..9835c07 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/service/BpmTaskCardService.java @@ -0,0 +1,155 @@ +package cc.smtweb.system.bpm.web.task.service; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.config.DesignConfig; +import cc.smtweb.framework.core.db.vo.ModelField; +import cc.smtweb.framework.core.db.vo.ModelTable; +import cc.smtweb.framework.core.db.vo.def.DataType; +import cc.smtweb.system.bpm.engine.process.impl.AbstractTaskCardService; +import cc.smtweb.system.bpm.engine.process.impl.entity.LoadTaskAckVO; +import cc.smtweb.system.bpm.engine.process.runtime.ProcessInstance; +import cc.smtweb.system.bpm.engine.process.task.Task; +import cc.smtweb.system.bpm.engine.process.vo.FlowAction; +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import cc.smtweb.system.bpm.engine.process.vo.FlowNodeField; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; + +// "工作流任务接口, 手机界面" +@SwService +public class BpmTaskCardService extends AbstractTaskCardService { + @Override + protected LoadTaskAckVO loadForm(Task task) { + ProcessInstance processInstance = task.getProcessInstance(); + FlowNode flowNode = processInstance.getCurNode(); + + LoadTaskAckVO data = new LoadTaskAckVO(); + + loadFormStructAndData(task, data); + + // 动作按钮 + List flowActions = flowNode.getActions(); + if (flowActions != null) { + List uiActions = data.getAction(); + + for (FlowAction flowAction: flowActions) { + SwMap action = new SwMap(4); + + action.put("id", flowAction.getId()); + action.put("label", flowAction.getLabel()); + action.put("type", flowAction.getType()); + switch (flowAction.getType()) { + case FlowAction.TYPE_COMMIT: + action.put("btnType", "primary"); + break; + case FlowAction.TYPE_REJECT: + case FlowAction.TYPE_FINISH: + action.put("btnType", "danger"); + break; + case FlowAction.TYPE_TRANSMIT: + case FlowAction.TYPE_RETURN: + action.put("btnType", "warning"); + break; + case FlowAction.TYPE_SAVE: + action.put("btnType", "info"); + break; + default: + action.put("btnType", "default"); + break; + } + + uiActions.add(action); + } + } + + return data; + } + + private void loadFormStructAndData(Task task, LoadTaskAckVO data) { + ProcessInstance processInstance = task.getProcessInstance(); + FlowNode flowNode = processInstance.getCurNode(); + + List fields = flowNode.getFields(); + + if (fields != null) { + // 读取数据 + SwMap body = loadFormData(task); + + // 生成界面结构和数据 + Map formData = data.getData(); + List uiFields = data.getForm(); + ModelTable modelTable = processInstance.getModelTable(); + for (FlowNodeField nodeField: fields) { + ModelField modelField = modelTable.findField(nodeField.getId()); + int perm = nodeField.getPerm(); + + if (modelField != null && (perm == FlowNodeField.PERM_READ || perm == FlowNodeField.PERM_WRITE)) { + SwMap uiField = new SwMap(8); + uiField.put("id", modelField.getName()); + + if (perm == FlowNodeField.PERM_READ) { + uiField.put("component", "fy-label"); + + if (body != null) { + // 从历史数据中读取 + Object value = body.get(modelField.getName()); + if (value != null) { + formData.put(modelField.getName(), value); + } + } + } else { + switch (modelField.getDataType()) { + case DataType.TYPE_BOOL: + uiField.put("component", "fy-checkbox"); + break; + case DataType.TYPE_DATETIME: + case DataType.TYPE_DATE: + uiField.put("component", "fy-datetime"); + break; + default: + uiField.put("component", "fy-text"); + break; + } + + if (task.getLockRev() == 0) { + if (StringUtils.isNotBlank(nodeField.getDefaultValue())) { + formData.put(modelField.getName(), nodeField.getDefaultValue()); + } + } else if (body != null) { + // 从历史数据中读取 + Object value = body.get(modelField.getName()); + if (value != null) { + formData.put(modelField.getName(), value); + } + } + } + + SwMap uiFieldProps = new SwMap(16); + uiField.put("props", uiFieldProps); + + uiFieldProps.put("name", modelField.getName()); + uiFieldProps.put("label", modelField.getTitle()); + uiFieldProps.put("placeholder", modelField.getTitle()); + if (nodeField.isNotNull()) { + uiFieldProps.put("required", true); + } + + uiFields.add(uiField); + } + } + } // else {R.error("读取节点字段权限, id=" + flowNode.getId());} + } + + protected SwMap loadFormData(Task task) { + if (task.getId() != 0) { + ProcessInstance processInstance = task.getProcessInstance(); + return this.processEngine.getFormDataStore().load(processInstance.getId(), processInstance.getRootId()); + } + + return null; + } + +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/service/BpmTaskListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/service/BpmTaskListService.java new file mode 100644 index 0000000..8c2e286 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/service/BpmTaskListService.java @@ -0,0 +1,29 @@ +package cc.smtweb.system.bpm.web.task.service; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.system.bpm.engine.process.impl.entity.TaskPO; +import cc.smtweb.system.bpm.spring.IProcessEngine; + +import java.util.List; + +//@RequestMapping("bpm/task") +// "工作流任务接口" +@SwService +public class BpmTaskListService { + @SwParam + private DbEngine dbEngine; + + @SwParam + private IProcessEngine processEngine; + +// @ApiOperation(value = "读取任务列表") + public R list(UserSession us) { + List taskPOS = dbEngine.query("select task_id,task_ui_id,task_assignee_uid,task_name,task_priority,task_create_time,task_last_time from sw_bpm.bpm_task where task_assignee_uid=? order by task_last_time desc", TaskPO.class, us.getUserId()); + + return R.success(taskPOS); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/service/BpmTaskUserListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/service/BpmTaskUserListService.java new file mode 100644 index 0000000..a57dacd --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/task/service/BpmTaskUserListService.java @@ -0,0 +1,154 @@ +package cc.smtweb.system.bpm.web.task.service; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwBody; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.system.bpm.core.exception.BpmException; +import cc.smtweb.system.bpm.engine.process.impl.entity.LoadTaskVO; +import cc.smtweb.system.bpm.engine.process.impl.entity.NodeUsersVO; +import cc.smtweb.system.bpm.engine.process.runtime.Route; +import cc.smtweb.system.bpm.engine.process.runtime.RouteInfo; +import cc.smtweb.system.bpm.engine.process.task.Task; +import cc.smtweb.system.bpm.engine.process.vo.FlowAction; +import cc.smtweb.system.bpm.engine.process.vo.FlowNode; +import cc.smtweb.system.bpm.engine.process.vo.FlowNodeUser; +import cc.smtweb.system.bpm.spring.IProcessEngine; +import cc.smtweb.system.bpm.web.task.entity.UserGroupTargetPO; +import cc.smtweb.system.bpm.web.task.entity.UserPO; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +//// "工作流任务接口" +@SwService +public class BpmTaskUserListService { + @SwParam + private DbEngine dbEngine; + + @SwParam + private IProcessEngine processEngine; + +// @ApiOperation(value = "读取执行候选人名单") + public R list(@SwBody LoadTaskVO loadTaskVO) { + Task task = processEngine.loadTask(loadTaskVO.getTaskId()); + if (task == null) { + return R.error("加载任务失败"); + } + + FlowAction flowAction = task.getProcessInstance().checkAndGetCurNodeAction(loadTaskVO.getActionKey()); + NodeUsersVO data = null; + + switch (flowAction.getType()) { + case FlowAction.TYPE_COMMIT: + FlowNode toFlowNode = task.getProcessInstance().getFlow().checkAndGetNode(loadTaskVO.getActionKey()); + data = loadUser(task, flowAction, toFlowNode); + break; + case FlowAction.TYPE_TRANSMIT: + data = loadUser(task, flowAction, task.getProcessInstance().getCurNode()); + break; + case FlowAction.TYPE_RETURN: + data = loadReturnUser(task); + break; + default: + break; + } + + if (data != null) { + data.setActionKey(loadTaskVO.getActionKey()); + data.setNodeKey(loadTaskVO.getToNodeKey()); + + return R.success(data); + } + + return R.error(); + } + + private NodeUsersVO loadUser(Task task, FlowAction flowAction, FlowNode toFlowNode) { + NodeUsersVO data = new NodeUsersVO(); + + RouteInfo nextProcessInfo = processEngine.routeCommit(task, toFlowNode); + + for (Route route: nextProcessInfo.getRoutes()) { + List users = loadUsers(task.getProcessInstance().getId(), flowAction, route.getNode()); + data.add(toFlowNode, users); + } + + return data; + } + + private List loadUsers(long processId, FlowAction flowAction, FlowNode toFlowNode) { + // toFlowNode.getUsers(); + // 用户组 + 过滤条件 + List flowNodeUsers = toFlowNode.getUsers(); + if (flowNodeUsers == null) { + throw new BpmException("节点未设置用户组, id=" + toFlowNode.getId()); + } + + StringBuilder ids = new StringBuilder("("); + for (FlowNodeUser flowNodeUser : flowNodeUsers) { + if (flowNodeUser.getType() == FlowNodeUser.TYPE_USER_GROUP) { + ids.append(flowNodeUser.getTarget()).append(","); + } + } + + List data = new ArrayList<>(); + Map userMap = new HashMap<>(); + + if (ids.length() > 1) { + ids.setCharAt(ids.length() - 1, ')'); + List userGroupTargetPos = dbEngine.query("SELECT ugt_id,ugt_type,ugt_target FROM sw_user.sys_user_group_target WHERE ugt_ug_id in " + ids.toString(), UserGroupTargetPO.class); + + for (UserGroupTargetPO userGroupTargetPo : userGroupTargetPos) { + List users = null; + switch (userGroupTargetPo.getUgtType()) { + case FlowNodeUser.TYPE_ROLE: + users = dbEngine.query("SELECT user_id, user_name, user_status FROM sw_user.sys_user_role LEFT JOIN smt_user.sys_user ON(ur_user_id=user_id) WHERE ur_role_id in (" + userGroupTargetPo.getUgtTarget() + ")", UserPO.class); + break; + case FlowNodeUser.TYPE_USER: + users = dbEngine.query("select user_id, user_name, user_status from sw_user.sys_user where user_id in (" + userGroupTargetPo.getUgtTarget() + ")", UserPO.class); + break; + case FlowNodeUser.TYPE_DEPT: + users = dbEngine.query("select user_id, user_name, user_status from sw_user.sys_user where user_dept_id in (" + userGroupTargetPo.getUgtTarget() + ")", UserPO.class); + break; + default: + break; + } + + if (users != null) { + for (UserPO userPo : users) { + // 去除重复 + if (userMap.putIfAbsent(userPo.getUserId(), userPo) == null) { + data.add(userPo); + } + } + } + } + } + + return data; + } + + private NodeUsersVO loadReturnUser(Task task) { + RouteInfo nextProcessInfo = processEngine.routeReturn(task); + + NodeUsersVO data = new NodeUsersVO(); + + for (Route route: nextProcessInfo.getRoutes()) { + Long userId = dbEngine.queryLong("SELECT tl_assignee_uid FROM sw_bpm.bpm_task_log WHERE tl_process_id=? AND tl_node_key=? ORDER BY tl_create_time desc LIMIT 1", + task.getProcessInstance().getId(), route.getNode().getId()); + + if (userId != null) { + List users = dbEngine.query("select user_id, user_name, user_status from sw_user.sys_user where user_id=?", + UserPO.class, userId); + + data.add(route.getNode(), users); + } + } + + return data; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/DatasetService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/DatasetService.java new file mode 100644 index 0000000..dbf8969 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/DatasetService.java @@ -0,0 +1,60 @@ +package cc.smtweb.system.bpm.web.ui; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.card.AbstractDatasetCardService; +import cc.smtweb.system.bpm.core.ui.service.list.AbstractDatasetListService; +import cc.smtweb.system.bpm.core.ui.service.tree.AbstractDatasetTreeService; +import cc.smtweb.system.bpm.spring.action.BpmEventDao; + +/** + * 动态界面数据服务 + * @author xkliu + */ +@SwService +public class DatasetService { + @SwParam + private BpmEventDao bpmEventDao; + + public R load(BpmPageContext pageContext) { + AbstractDatasetCardService cardService = bpmEventDao.getCardService(pageContext); + return cardService.load(pageContext); + } + + public R save(BpmPageContext pageContext) { + AbstractDatasetCardService cardService = bpmEventDao.getCardService(pageContext); + return cardService.save(pageContext); + } + + public R remove(BpmPageContext pageContext) { + AbstractDatasetCardService cardService = bpmEventDao.getCardService(pageContext); + return cardService.remove(pageContext); + } + + public R list(BpmPageContext pageContext) { + AbstractDatasetListService listService = bpmEventDao.getListService(pageContext); + return listService.list(pageContext); + } + + public R count(BpmPageContext pageContext) { + AbstractDatasetListService listService = bpmEventDao.getListService(pageContext); + return listService.count(pageContext); + } + + public R listAll(BpmPageContext pageContext) { + AbstractDatasetListService listService = bpmEventDao.getListService(pageContext); + return listService.listAll(pageContext); + } + + public R treeAll(BpmPageContext pageContext) { + AbstractDatasetTreeService treeService = bpmEventDao.getTreeService(pageContext); + return treeService.treeAll(pageContext); + } + + public R move(BpmPageContext pageContext) { + AbstractDatasetTreeService treeService = bpmEventDao.getTreeService(pageContext); + return treeService.move(pageContext); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/AbstractJsonDatasetPlugin.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/AbstractJsonDatasetPlugin.java new file mode 100644 index 0000000..3f2ed54 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/AbstractJsonDatasetPlugin.java @@ -0,0 +1,118 @@ +package cc.smtweb.system.bpm.web.ui.dao; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.sqlbuilder.SelectSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.system.bpm.core.ui.builder.SqlUtil; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmField; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmFieldFilter; +import cc.smtweb.system.bpm.core.ui.entity.dataset.IBpmDatasetConst; +import cc.smtweb.system.bpm.engine.ui.entity.po.DynFormLogPO; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; + +@Slf4j +public abstract class AbstractJsonDatasetPlugin { + @SwParam + protected DbEngine dbEngine; + + protected SelectSqlBuilder makeSqlBuilderForJson(BpmDataset dataset, SwMap body) { + SelectSqlBuilder sqlBuilder = SqlBuilder.createSelect("sw_bpm.bpm_dyn_form_log"); + sqlBuilder.addWhere("dfl_table", dataset.fullName()); + + List> query = (List>)body.get(SqlUtil.PN_FILTER); + if (query != null) { + addSqlWhereForJson(sqlBuilder, query, false); + } + + List> filters = (List>)body.get(SqlUtil.PN_SEARCH); + + if (filters != null) { + int filterType = body.readInt(SqlUtil.PN_SEARCH_TYPE, 0); + addSqlWhereForJson(sqlBuilder, filters, filterType != 0); + } + + return sqlBuilder; + } + + protected SelectSqlBuilder makeSqlBuilderForJson(BpmDataset dataset, List filters) { + SelectSqlBuilder sqlBuilder = SqlBuilder.createSelect("sw_bpm.bpm_dyn_form_log"); + sqlBuilder.addWhere("dfl_table", dataset.fullName()); + + if (filters != null) { + for (BpmFieldFilter filter : filters) { + String keyword = filter.getKeyword(); + String fieldName = filter.getField(); + + if (StringUtils.isNotBlank(keyword) && StringUtils.isNotBlank(fieldName)) { + SqlUtil.addSqlWhere(sqlBuilder, sqlJsonField(fieldName), filter.getOp(), keyword); + } + } + } + + return sqlBuilder; + } + + private void addSqlWhereForJson(SqlBuilder sqlBuilder, List> filters, boolean whereOr) { + if (!filters.isEmpty()) { + if (whereOr) { + // START OR + sqlBuilder.addWhereOrBegin(); + } + + for (Map filter : filters) { + Object keyword = filter.get(IBpmDatasetConst.PN_ITEM_KEYWORD); + String field = (String) filter.get(IBpmDatasetConst.PN_ITEM_FIELD); + + if (keywordIsNotBlank(keyword) && StringUtils.isNotBlank(field)) { + int op = MapUtils.getIntValue(filter, IBpmDatasetConst.PN_ITEM_OP, 0); + SqlUtil.addSqlWhere(sqlBuilder, sqlJsonField(field), op, keyword); + } + } + + if (whereOr) { + // END OR + sqlBuilder.addWhereOrEnd(); + } + } + } + + private static boolean keywordIsNotBlank(Object keyword) { + if (keyword instanceof String) { + return StringUtils.isNotBlank((String)keyword); + } + + return ObjectUtils.isNotEmpty(keyword); + } + + SwMap mapFields(BpmDataset dataset, DynFormLogPO po) { + if (StringUtils.isNotBlank(po.getDflContent())) { + SwMap item = new SwMap(); + SwMap entity = JsonUtil.parse(po.getDflContent(), SwMap.class); + + for (Map.Entry entry : dataset.getFields().entrySet()) { + BpmField bpmField = entry.getValue(); + item.put(bpmField.getId(), entity.get(bpmField.getFieldName())); + } + + item.put("id", po.getDflId()); + + return item; + } + + return null; + } + + static String sqlJsonField(String field) { + return "JSON_EXTRACT(dfl_content, '$." + field + "')"; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/JsonDatasetCardDao.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/JsonDatasetCardDao.java new file mode 100644 index 0000000..6efefd7 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/JsonDatasetCardDao.java @@ -0,0 +1,120 @@ +package cc.smtweb.system.bpm.web.ui.dao; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwBean; +import cc.smtweb.framework.core.db.sqlbuilder.InsertSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.UpdateSqlBuilder; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.framework.core.util.DateUtil; +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.system.bpm.core.ui.BpmKeyValue; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.IParamConst; +import cc.smtweb.system.bpm.core.ui.builder.CardDataBuilder; +import cc.smtweb.system.bpm.core.ui.builder.SqlUtil; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmField; +import cc.smtweb.system.bpm.core.ui.entity.dataset.IBpmDatasetConst; +import cc.smtweb.system.bpm.engine.ui.entity.po.DynFormLogPO; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; + +@SwBean +public class JsonDatasetCardDao extends AbstractJsonDatasetPlugin { + public SwMap load(BpmPageContext ctx) { + BpmDataset dataset = ctx.getDataset(); + SwMap body = ctx.getBody(); + long id = ctx.getParamId(); + + if (id != 0) { + DynFormLogPO po = dbEngine.queryEntity("select dfl_id, dfl_content from sw_bpm.bpm_dyn_form_log where dfl_id=?", + DynFormLogPO.class, id); + + if (po != null){ + return mapFields(dataset, po); + } + } else { + List> filters = (List>)body.get(SqlUtil.PN_FILTER); + if (filters != null) { + SwMap data = new SwMap(); + // addSqlWhereForJson(sqlBuilder, query, false); + for (Map filter : filters) { + String keyword = (String) filter.get(IBpmDatasetConst.PN_ITEM_KEYWORD); + String field = (String) filter.get(IBpmDatasetConst.PN_ITEM_FIELD); + + if (StringUtils.isNotBlank(keyword) && StringUtils.isNotBlank(field)) { + BpmField bpmField = dataset.findFieldByFieldName(field); + if (bpmField != null) { + data.put(bpmField.getId(), keyword); + } + } + } + + return data; + } + } + + return null; + } + + public Long save(BpmPageContext ctx) { + CardDataBuilder builder = CardDataBuilder.create(dbEngine, ctx.getDataset(), ctx.getBody(), ctx.getSession()); + long dataId = builder.getMasterId(); + UserSession us = ctx.getSession(); + SwMap body = null; + + // TODO 处理1:1多表更新 + if (!builder.isInstert()) { + // 读取旧数据进行部分更新 + String oldContent = dbEngine.queryString("select dfl_content from sw_bpm.bpm_dyn_form_log where dfl_id=?", dataId); + + if (StringUtils.isNotBlank(oldContent)) { + body = JsonUtil.parse(oldContent, SwMap.class); + } + } + + String content = JsonUtil.encodeString(builder.update(body)); + + if (builder.isInstert()) { + InsertSqlBuilder sqlBuilder = SqlBuilder.createInsert("sw_bpm.bpm_dyn_form_log"); + sqlBuilder.add("dfl_id", dataId) + .add("dfl_table", builder.getTableName()) + .add("dfl_content", content) + .add("dfl_create_uid", us.getUserId()) + .add("dfl_update_uid", us.getUserId()); + sqlBuilder.update(dbEngine); + } else { + UpdateSqlBuilder sqlBuilder = SqlBuilder.createUpdate("sw_bpm.bpm_dyn_form_log"); + sqlBuilder.add("dfl_content", content) + .add("dfl_last_time", DateUtil.nowDateTimeLong()) + .add("dfl_update_uid", us.getUserId()) + .addWhere("dfl_id", dataId); + sqlBuilder.update(dbEngine); + } + + return dataId; + } + + public int remove(BpmPageContext ctx) { + String ids = ctx.getBody().readString(IParamConst.PARAMS_IDS); + + if (StringUtils.isNotBlank(ids)) { + return dbEngine.update("delete from sw_bpm.bpm_dyn_form_log where dfl_id in(" + ids + ")"); + } else { + long id = ctx.getParamId(); + if (id != 0) { + return dbEngine.update("delete from sw_bpm.bpm_dyn_form_log where dfl_id=?", ctx.getParamId()); + } + } + + return 0; + } + + public BpmKeyValue loadLookup(BpmPageContext ctx, Object key) { + return dbEngine.queryEntity("select dfl_id id, " + ctx.getFieldLookup().getLabelField() + + " value from sw_bpm.bpm_dyn_form_log where dfl_id=?", BpmKeyValue.class, key); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/JsonDatasetListDao.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/JsonDatasetListDao.java new file mode 100644 index 0000000..07bc390 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/JsonDatasetListDao.java @@ -0,0 +1,109 @@ +package cc.smtweb.system.bpm.web.ui.dao; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwBean; +import cc.smtweb.framework.core.db.sqlbuilder.SelectSqlBuilder; +import cc.smtweb.system.bpm.core.ui.BpmKeyValue; +import cc.smtweb.system.bpm.core.ui.BpmMapData; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.builder.SqlUtil; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmField; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmFieldLookup; +import cc.smtweb.system.bpm.engine.ui.entity.po.DynFormLogPO; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@SwBean +public class JsonDatasetListDao extends AbstractJsonDatasetPlugin { + public List list(BpmPageContext ctx) { + BpmDataset dataset = ctx.getDataset(); + SwMap query = ctx.getBody(); + // 列表不能用 mobilePage.id 过滤,存储的是编辑界面的pageId + // 处理查询参数 + SelectSqlBuilder sqlBuilder = makeSqlBuilderForJson(dataset, query); + + sqlBuilder.add("dfl_id"); + sqlBuilder.add("dfl_content"); + sqlBuilder.addOrderBy("dfl_id"); + + // 处理翻页参数 + int limit = query.readInt("pageSize", 20); + int start = query.readInt("pageIndex", 0) * limit; + + List list = sqlBuilder.pagedQuery(dbEngine, DynFormLogPO.class, start, limit); + + if (list != null) { + return makeListResult(dataset, list, dataset.isUseSummary() || list.size() >= limit); + } + + return null; + } + + private List makeListResult(BpmDataset dataset, List list, boolean loadCount) { + List data = new ArrayList<>(); + for (DynFormLogPO po : list) { + SwMap item = mapFields(dataset, po); + + if (item == null) { + item = new SwMap(); + item.put("id", po.getDflId()); + } + + data.add(item); + } + + return data; + } + + public BpmMapData listLookup(BpmPageContext ctx, Set idSet) { + // 外键数据 + List list = dbEngine.query("select dfl_id optKey, " + ctx.getFieldLookup().getLabelField() + + " optValue from sw_bpm.bpm_dyn_form_log where dfl_id in (" + String.join(",", idSet) + ")", + BpmKeyValue.class); + + BpmMapData result = new BpmMapData(list.size()); + for (BpmKeyValue item: list) { + result.put(item.getOptKey(), item.getOptValue()); + } + + return result; + } + + public List listAll(BpmPageContext ctx) { + BpmDataset dataset = ctx.getDataset(); + BpmFieldLookup fieldLookup = ctx.getFieldLookup(); + // 处理查询参数 + SelectSqlBuilder sqlBuilder = makeSqlBuilderForJson(dataset, fieldLookup.getFilters()); + + sqlBuilder.add("dfl_id"); + sqlBuilder.add("dfl_content"); + sqlBuilder.addOrderBy("dfl_id"); + + List list = sqlBuilder.query(dbEngine, DynFormLogPO.class); + + return makeListResult(dataset, list, false); + } + + public SwMap count(BpmPageContext ctx) { + BpmDataset dataset = ctx.getDataset(); + SelectSqlBuilder sqlBuilder = makeSqlBuilderForJson(dataset, ctx.getBody()); + sqlBuilder.add("count(*) total"); + + for (Map.Entry entry: dataset.getFields().entrySet()) { + String field = entry.getKey(); + BpmField bpmField = entry.getValue(); + String rule = bpmField.getSummary(); + + if (StringUtils.isNotBlank(rule)) { + sqlBuilder.add(SqlUtil.makeSummaryField(rule, sqlJsonField(field)) + " " + field); + } + } + + return sqlBuilder.queryEntity(dbEngine, SwMap.class); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/JsonDatasetTreeDao.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/JsonDatasetTreeDao.java new file mode 100644 index 0000000..fabdcb0 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dao/JsonDatasetTreeDao.java @@ -0,0 +1,82 @@ +package cc.smtweb.system.bpm.web.ui.dao; + +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwBean; +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.builder.TreeDataBuilder; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.engine.ui.entity.po.DynFormLogPO; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +@SwBean +public class JsonDatasetTreeDao extends AbstractJsonDatasetPlugin { + public List treeAll(BpmPageContext ctx) { + BpmDataset dataset = ctx.getDataset(); + TreeDataBuilder treeDataBuilder = new TreeDataBuilder(dataset, null, null); + + List poList = dbEngine.query("select dfl_id, dfl_content from sw_bpm.bpm_dyn_form_log where dfl_table=?", + DynFormLogPO.class, dataset.fullName()); + + List list = new ArrayList<>(); + + for (DynFormLogPO po: poList) { + list.add(treeDataBuilder.mapTreeFields(dataset, po)); + } + + return treeDataBuilder.buildTree(list, true); + } + + public List tree(BpmPageContext ctx, Long parentId) { + BpmDataset dataset = ctx.getDataset(); + TreeDataBuilder treeDataBuilder = new TreeDataBuilder(dataset, parentId, null); + + List poList = dbEngine.query("select dfl_id, dfl_content from sw_bpm.bpm_dyn_form_log where dfl_table=? and " + + "JSON_EXTRACT(dfl_content, '$." + treeDataBuilder.getParentField().getFieldName() + "')=?", + DynFormLogPO.class, dataset.fullName(), parentId); + + List list = new ArrayList<>(); + + for (DynFormLogPO po: poList) { + list.add(treeDataBuilder.mapTreeFields(dataset, po)); + } + + return list; + } + + public int move(BpmPageContext ctx, TreeDataBuilder treeDataBuilder) { + Long parentId = treeDataBuilder.getParentId(); + Long[] orders = treeDataBuilder.getOrders(); + + String tableName = treeDataBuilder.getDataset().fullName(); + List poList = dbEngine.query("select dfl_id, dfl_content from sw_bpm.bpm_dyn_form_log where dfl_table=? and dfl_id in (" + + StringUtils.join(orders, ",") + ")", + DynFormLogPO.class, tableName); + + int count = 0; + + for (DynFormLogPO po: poList) { + SwMap content = treeDataBuilder.updateOrder(po, parentId, findOrderValue(orders, po.getDflId())); + int ret = dbEngine.update("update sw_bpm.bpm_dyn_form_log set dfl_content=? where dfl_id=? and dfl_table=?", + JsonUtil.encodeString(content), po.getDflId(), tableName); + if (ret > 0) { + count++; + } + } + + return count; + } + + private static int findOrderValue(Long[] orders, Long id) { + for (int i = 0; i < orders.length; i++) { + if (id.equals(orders[i])) { + return i + 1; + } + } + + return 0; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormCardService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormCardService.java new file mode 100644 index 0000000..3019dd8 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormCardService.java @@ -0,0 +1,60 @@ +package cc.smtweb.system.bpm.web.ui.dynform; + +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.card.*; +import cc.smtweb.system.bpm.core.ui.service.card.DatasetCardWorker; +import cc.smtweb.system.bpm.spring.dao.DatasetCardDao; +import cc.smtweb.system.bpm.web.ui.dao.JsonDatasetCardDao; + +/** + * 动态界面Card数据服务 + * @author xkliu + */ +@SwService +public class DynFormCardService extends AbstractDatasetCardService { + @SwParam + private DatasetCardDao datasetCardDao; + + @SwParam + private JsonDatasetCardDao jsonDatasetCardDao; + + @Override + protected DatasetCardWorker createCardLoadHandler(BpmPageContext pageContext) { + DatasetCardWorker handler = new DatasetCardWorker(pageContext); + if (handler.getPageContext().isDeploy()) { + handler.handleLoad(datasetCardDao::load); + handler.handleLoadLookup(datasetCardDao::loadLookup); + } else { + handler.handleLoad(jsonDatasetCardDao::load); + handler.handleLoadLookup(jsonDatasetCardDao::loadLookup); + } + + return handler; + } + + @Override + protected DatasetCardWorker createCardSaveHandler(BpmPageContext pageContext) { + DatasetCardWorker handler = new DatasetCardWorker(pageContext); + if (handler.getPageContext().isDeploy()) { + handler.handleSave(datasetCardDao::save); + } else { + handler.handleSave(jsonDatasetCardDao::save); + } + + return handler; + } + + @Override + protected DatasetCardWorker createCardRemoveHandler(BpmPageContext pageContext) { + DatasetCardWorker handler = new DatasetCardWorker(pageContext); + if (handler.getPageContext().isDeploy()) { + handler.handleRemove(datasetCardDao::remove); + } else { + handler.handleRemove(jsonDatasetCardDao::remove); + } + + return handler; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormListService.java new file mode 100644 index 0000000..c101fe4 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormListService.java @@ -0,0 +1,48 @@ +package cc.smtweb.system.bpm.web.ui.dynform; + +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.system.bpm.core.ui.service.list.*; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.spring.dao.DatasetListDao; +import cc.smtweb.system.bpm.web.ui.dao.JsonDatasetListDao; + +@SwService +public class DynFormListService extends AbstractDatasetListService { + @SwParam + private DatasetListDao datasetListDao; + + @SwParam + private JsonDatasetListDao jsonDatasetListDao; + + @Override + protected DatasetListWorker createListPartHandler(BpmPageContext pageContext) { + DatasetListWorker handler = new DatasetListWorker(pageContext); + handler.handleListLookup(datasetListDao::listLookup); + + if (handler.getPageContext().isDeploy()) { + handler.handleListPart(datasetListDao::list); + handler.handleCount(datasetListDao::count); + } else { + handler.handleListPart(jsonDatasetListDao::list); + handler.handleCount(jsonDatasetListDao::count); + } + + return handler; + } + + @Override + protected DatasetListWorker createListAllHandler(BpmPageContext pageContext) { + DatasetListWorker handler = new DatasetListWorker(pageContext); + handler.handleListLookup(datasetListDao::listLookup); + + if (handler.getPageContext().isDeploy()) { + handler.handleListAll(datasetListDao::listAll); + } else { + handler.handleListAll(jsonDatasetListDao::listAll); + } + + + return handler; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormPageService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormPageService.java new file mode 100644 index 0000000..6e2e185 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormPageService.java @@ -0,0 +1,35 @@ +package cc.smtweb.system.bpm.web.ui.dynform; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.spring.cache.BpmPageCache; +import org.apache.commons.lang3.StringUtils; + +/** + * 获取界面结构 + */ +@SwService +public class DynFormPageService { + @SwParam + private BpmPageCache bpmPageCache; + + // @ApiOperation(value = "读取界面结构") + public R load(UserSession us, @SwParam("pageName") String pageName) { + if (StringUtils.isBlank(pageName)) { + return R.error("必须传入参数: pageName"); + } + + BpmPage bpmPage = bpmPageCache.get(pageName); + if (bpmPage == null) { + bpmPage = bpmPageCache.getByOtherKey(pageName); + } + if (bpmPage == null) { + return R.error("未找到页面: " + pageName); + } + + return R.success(bpmPage); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormTreeService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormTreeService.java new file mode 100644 index 0000000..9e95368 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/DynFormTreeService.java @@ -0,0 +1,41 @@ +package cc.smtweb.system.bpm.web.ui.dynform; + +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.system.bpm.core.ui.BpmPageContext; +import cc.smtweb.system.bpm.core.ui.service.tree.AbstractDatasetTreeService; +import cc.smtweb.system.bpm.core.ui.service.tree.DatasetTreeWorker; +import cc.smtweb.system.bpm.spring.dao.DatasetTreeDao; + +@SwService +public class DynFormTreeService extends AbstractDatasetTreeService { + @SwParam + private DatasetTreeDao datasetTreeDao; + + @SwParam + private DatasetTreeDao jsonDatasetTreeDao; + + @Override + protected DatasetTreeWorker createTreeAllHandler(BpmPageContext pageContext) { + DatasetTreeWorker handler = new DatasetTreeWorker(pageContext); + if (handler.getPageContext().isDeploy()) { + handler.handleTreeAll(datasetTreeDao::treeAll); + } else { + handler.handleTreeAll(jsonDatasetTreeDao::treeAll); + } + + return handler; + } + + @Override + protected DatasetTreeWorker createTreeMoveHandler(BpmPageContext pageContext) { + DatasetTreeWorker handler = new DatasetTreeWorker(pageContext); + if (handler.getPageContext().isDeploy()) { + handler.handleMove(datasetTreeDao::move); + } else { + handler.handleMove(jsonDatasetTreeDao::move); + } + + return handler; + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/TaskListService.java b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/TaskListService.java new file mode 100644 index 0000000..f92a905 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/java/cc/smtweb/system/bpm/web/ui/dynform/TaskListService.java @@ -0,0 +1,68 @@ +package cc.smtweb.system.bpm.web.ui.dynform; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.annotation.SwParam; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.framework.core.db.DbEngine; +import cc.smtweb.framework.core.db.sqlbuilder.AbstractSelectSqlBuilder; +import cc.smtweb.framework.core.db.sqlbuilder.SqlBuilder; +import cc.smtweb.framework.core.session.UserSession; +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.core.ui.entity.dataset.BpmDataset; +import cc.smtweb.system.bpm.spring.cache.BpmPageCache; +import cc.smtweb.system.bpm.spring.dao.DatasetConfigDao; + +import java.util.List; + +@SwService +public class TaskListService { + @SwParam + private DatasetConfigDao datasetConfigDao; + + @SwParam + protected BpmPageCache bpmPageCache; + + public R list(@SwParam("pageId") long pageId, @SwParam("start") int start, @SwParam("limit") int limit, UserSession us) { + BpmPage mobilePage = bpmPageCache.get(pageId); + + if (mobilePage != null) { + return list(mobilePage, us); + } + + return R.error("未找到定义的界面, id=" + pageId); + } + + @SwParam + protected DbEngine dbEngine; + + protected R list(BpmPage page, UserSession us) { + if (page.isDeploy()) { + BpmDataset dataset = page.findMasterDataset(); + + if (dataset != null) { + return queryList(dataset.getDatabase(), dataset.getTable(), dataset.getFieldPrefix(), us.getUserId(), 0); + } + } else { + return queryList("sw_bpm", "bpm_process_detail_log", "pdl", us.getUserId(), page.getId()); + } + + return R.error("界面未定义数据集"); + } + + private R queryList(String database, String table, String fieldPrefix, long userId, long pageId) { + AbstractSelectSqlBuilder sb = SqlBuilder.createSelect(database, table) + .add(fieldPrefix + "_id", null) + .add(fieldPrefix + "_create_time", null) + .add(fieldPrefix + "_last_time", null) + .addWhere(fieldPrefix + "_update_uid", userId); + + if (pageId != 0) { + sb.addWhere(fieldPrefix + "_process_root_id", pageId); + } + + List list = sb.query(dbEngine, SwMap.class); + + return R.success(list).put("fieldPrefix", fieldPrefix); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/resources/META-INF/spring.factories b/smtweb-system/sw-system-bpm/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..86f1fdb --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cc.smtweb.system.bpm.spring.BpmAutoConfiguration diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/dataset.ts b/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/dataset.ts new file mode 100644 index 0000000..45ed53b --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/dataset.ts @@ -0,0 +1,730 @@ +import {onMounted, watch} from 'vue'; +import {RouteLocationNormalizedLoaded, useRoute} from 'vue-router'; +import Schema, {ErrorList, FieldErrorList} from 'async-validator'; +import api from '/@/api'; +import commonUtil from '/@/utils/commonUtil'; +import {confirm, notify} from '/@/utils/message'; +import {IPageParams} from './page.types'; +import {tabRouter} from '/@/pages/layout/tab/useTab'; + +interface IDatasetEvent { + action: string + dataset: any + row: any +} + +export class PageDataset { + private params: IPageParams; + readonly data: any; + readonly page: any; + private eventHandle?: Function; + + constructor(route: RouteLocationNormalizedLoaded, data: any, page: any) { + // title: this.$route.query.title, + // 页面参数 + + this.params = { + pageId: route.params.pageI || page.id, + id: route.params.id as string, + parentId: route.params.parentId as string, + }; + + this.data = data; + this.page = page; + // 混入的主页面需要提供 page 和 data + } + + setEventHandle(eventHandle: Function) { + this.eventHandle = eventHandle; + } + + // 通过dataset对象读取数据 + async loadDataset(dataset: any, row?: any, params?: any, callFn?: Function) { + if (row && dataset.masterFields) { + // 处理子表过滤条件 + const filter = []; + + for (const mapField of dataset.masterFields) { + filter.push({ field: mapField.slave, keyword: row[mapField.master] }); + } + + dataset.filter = filter; + } + + switch (dataset.listModel) { + case 1: + await this.loadList(dataset.name, params, callFn); + break; + case 2: + await this.loadTree(dataset.name, params, callFn); + break; + default: + await this.loadCard(dataset.name, params, callFn); + break; + } + } + + // async searchList() { + // // 点击搜索按钮 + // if (this.getMainDatasetId()) { + // await this.loadList(this.getMainDatasetId(), 0); + // } + // } + // + // async resetList() { + // // 重置按钮 + // if (this.getMainDatasetId()) { + // const dataset = this.getDatasetById(this.getMainDatasetId()); + // // this.$set(dataset, 'form', {}); + // dataset.form = {}; + // await this.loadList(this.getMainDatasetId(), 0); + // } + // } + // + // getMainDatasetId(): string | null { + // const { datasets } = this.page; + // if (datasets && datasets.length > 0) { + // return datasets[0].id; + // } + // return null; + // } + + findDatasetMeta(datasetName: string): any { + return this.page.datasets[datasetName]; + // 得到 datasets + // return this.page.datasets.find((e: any) => e.id === datasetId || e.name === datasetId); + // return Object.values(this.page.datasets).find((dataset: any) => { + // return dataset.id === datasetId || dataset.name === datasetId; + // }); + } + + // async loadDatasetById(datasetId: string) { + // await this.loadDataset(this.findDataset(datasetId)); + // } + + async loadDatasetByName(name: string, params?: any, callFn?: Function) { + await this.loadDataset(this.page.datasets[name], null, params, callFn); + } + + async doApi(name: string, params: any): Promise { + const dataset = this.findDatasetMeta(params.dataset); + let url = null; + if (dataset && dataset.apis) { + url = dataset.apis[name]; + } + + if (!url) { + switch (name) { + case 'cardGet': + url = 'bpm/DynFormCard/load'; + break; + case 'cardSave': + url = 'bpm/DynFormCard/save'; + break; + case 'cardDel': + url = 'bpm/DynFormCard/remove'; + break; + case 'listGet': + url = 'bpm/DynFormList/list'; + break; + case 'listCount': + url = 'bpm/DynFormList/count'; + break; + case 'treeAll': + url = 'bpm/DynFormTree/treeAll'; + break; + case 'treeGet': + url = 'bpm/DynFormTree/tree'; + break; + case 'treeMove': + url = 'bpm/DynFormTree/move'; + break; + } + } + if (url) { + return await api.post(url, params); + // return await api.post(url, params); + } + + return {}; + } + + // async doCustomApi(apiName: string, params: any, callFn: Function) { + // // 执行自定义的API + // const re = await api.post(apiName, params); + // if (callFn) { + // callFn(re); + // } + // } + + async loadCard(datasetName: string, params: any, callFn?: Function) { + const ds = this.loadDatasetData(datasetName); + console.log('loadCard= ', this.params.id, ds); + if (this.params.id) { + // 查询一条数据 + const { data } = await this.doApi('cardGet', { + pageId: this.page.id, + dataset: datasetName, + id: this.params.id, + filter: ds.filter, + }); + + if (data) { + ds.form = data; + + this.scrollDataset(datasetName, data); + } + + if (callFn) { + callFn(data); + } + } + } + + loadDatasetData(datasetName: string | null): any { + if (datasetName) { + return this.data[datasetName]; + } + + return null; + } + + async loadList(datasetName: string | null, pageIndex: number, callFn?: Function) { + const ds = this.loadDatasetData(datasetName); + + if (!ds) { + console.error(`not find dataset name: ${datasetName}`); + return; + } + + const params = { + pageId: this.page.id, + parentId: this.params.parentId, + dataset: datasetName, + pageIndex, + pageSize: ds.list.pageSize, + filter: ds.list.filter, + // search: ds.list.search || [], + // searchType: ds.list.searchType, + // params: { ...(ds.form || {}), ..._search }, + }; + + ds.list.loading = true; + const { data } = await this.doApi('listGet', params); + ds.list.loading = false; + + if (data) { + ds.list.data = data.rows; + + // console.log(this.data, data.rows.length); + + if (ds.list.total < 0) { // 不需要每次都查询条数和统计 + try { + // 更新总数 + if (data.total < 0) { + // 异步加载数据总数和统计数据 + const retCount = await this.doApi('listCount', params); + if (retCount.data) { + ds.list.total = retCount.data.total / 1; // (sql条数是java long类型) + ds.list.summary = retCount.data; + } + } else { + ds.list.total = data.total; + } + } catch (e) { + console.log('e', e); + } + } + + this.scrollDataset(datasetName!); + } + + if (callFn) { + callFn(data); + } + } + + async loadTree(datasetName: string, params = {}, callFn?: Function) { + // 加载树数据 + const ds = this.loadDatasetData(datasetName); + + ds.list.loading = true; + const ret = await this.doApi('treeAll', { + ...params, + pageId: this.page.id, + dataset: datasetName, + }); + ds.list.loading = false; + if (ret.data) { + ds.list.data = ret.data || []; + this.scrollDataset(datasetName, null); + if (callFn) { + callFn(); + } + } + } + + scrollDataset(datasetName: string, row?: any) { + // 加载从表数据 + Object.values(this.page.datasets).forEach((dataset: any) => { + if (dataset && dataset.tableType === 2 && dataset.master === datasetName) { + if (row) { + this.loadDataset(dataset, row); + } else { + // 直接清空 + this.scrollDataset(dataset.id, null); + const slaveData = this.loadDatasetData(dataset.name); + slaveData.list.data.length = 0; + } + } + }); + + this.doEvent({ action: 'afterScroll', dataset: datasetName, row }); + } + + _removeItem(listData: any, id: string, _mainField: string) { + if (!_mainField) { + _mainField = 'id'; // 对比的键 + } + for (let i = 0; i < listData.length; i++) { + const child = listData[i]; + if (child[_mainField] === id) { + listData.splice(i, 1); + return true; + } + + if (child.children) { + if (this._removeItem(child.children, id, _mainField)) { + return true; + } + } + } + + return false; + } + + _doSave(datasetName: string, formData: any, callFn: Function) { + this.doApi('cardSave', { + ...formData, + pageId: this.page.id, + id: this.params.id, + parentId: this.params.parentId, + dataset: datasetName, + }).then((ret) => { + this.params.id = ret.data; + notify.success('操作成功'); + if (callFn) { + callFn(ret); + } + }); + } + + onSubmit(datasetName: string, isReturn = false) { + if (datasetName) { + const ds = this.loadDatasetData(datasetName); + const formData = ds.form; + formData.parentId = this.params.parentId; + const dataset = this.findDatasetMeta(datasetName); + console.log('onSubmit', formData, dataset); + + if (dataset && dataset.rules && !commonUtil.isEmptyObj(dataset.rules)) { + const validator = new Schema(dataset.rules); + validator.validate(formData, {}, (errors: ErrorList, fields: FieldErrorList) => { + if (errors) { + // validation failed, errors is an array of all errors + // fields is an object keyed by field name with an array of + // errors per field + console.log(errors, fields); + notify.error('验证失败'); + } else { + this._doSave(datasetName, formData, () => { + if (isReturn) { + this.goBack(); + } + }); + } + // validation passed + }); + } else { + if (!this.customValidator(datasetName)) { + return; + } + this._doSave(datasetName, formData, () => { + if (isReturn) { + this.goBack(); + } + }); + } + } + } + + customValidator(datasetName: string) { + // 验证成功 返回 true 失败 返回 false + // 自定义 输入 验证 + const ds = this.loadDatasetData(datasetName); + if (ds) { + const formData = ds.form || {}; + let _b = true; + console.log('customValidator', formData); + if (this.page && this.page.widgets) { + const { widgets = {} } = this.page; + // 验证必填 (只验证是否有值,不能格式) + Object.values(widgets).forEach((_widget: any) => { + if (_widget.dataset === datasetName && _widget.required) { + const _name = _widget.name || _widget.field; + if (!commonUtil.isEmpty(_name)) { + if (commonUtil.isEmpty(formData[_name])) { + _b = false; + _widget.showError = true; + } else { + _widget.showError = false; + } + } + } + }); + } + return _b; + } + return false; + } + + goBack() { + // 返回上一页面 + tabRouter.back(); + // router.back(); + } + + getIdFieldByDataset(datasetName: string) { + // 得到 dataset 的ID属性值 + if (this.page && this.page.datasets && this.page.datasets[datasetName]) { + return this.page.datasets[datasetName].idField; + } + return null; + } + + // 生成查询条件用于向后台提交参数 + _buildSearch(datasetId: string, ds: any): Array { + // 用户输入的搜索条件 + const result: any[] = []; + + const { widgets } = this.page; + + Object.keys(this.page.widgets).forEach((key) => { + const widget = widgets[key]; + if (widget.dataset === datasetId && widget.field && widget.filterType) { + const keyword = ds.form[widget.field]; + if (keyword) { + result.push({ field: widget.field, keyword, op: widget.filterType }); + } + } + }); + + // const _search = {}; + // if (ds.list.search && ds.list.search.length > 0) { + // for (const _obj of ds.list.search) { + // if (_obj.field) { + // _search[_obj.field] = _obj.keyword; + // } + // } + // } + + return result; + } + + // 界面按钮产生的数据集动作 + async onWidgetAction(datasetName: string, params: any) { + const ds = this.loadDatasetData(datasetName); + const dsMeta = this.findDatasetMeta(datasetName); + // 得到主键值 + if (params.row && dsMeta && dsMeta.idField) { + params.id = params.row[dsMeta.idField]; + // params.mainField = _mainField; + } + + console.log('onWidgetAction', params, ds); + + if (params.link) { + const routeName = this.page.ref.routes[`id_${params.link}`]; + + if (routeName) { + const query = { + // pageId: params.link, + action: params.action, + } as any; + + switch (params.action) { + case 'button:add': + query.parentId = this.params.parentId; + // query.dataParentId = params.parentId; + break; + case 'button:edit': + // query.parentId = params.id || ''; + query.id = params.id; + // row = JSON.stringify(params.row || {}), + break; + } + await tabRouter.push(routeName, query); + // await router.push({ name: routeName, params: query }); + // } else { + // await router.push({ + // path: `/form/${params.link}`, + // query: { parentId: this.params.id }, + // }); + } else { + console.error(`not find link page by id ${params.link}`); + } + } else { + switch (params.action) { + case 'list:page': // list控件事件 + if (params.reset) { + ds.list.total = -1; + } + ds.list.pageSize = params.pageSize; + + await this.loadList(datasetName, params.pageIndex); + break; + case 'list:filter': // list控件事件,快速查询 + ds.list.total = -1; + ds.list.search = params.search; + ds.list.searchType = params.searchType; + ds.list.pageSize = params.pageSize; + + await this.loadList(datasetName, 0); + break; + case 'button:search': // 搜索按钮 + ds.list.total = -1; + ds.list.filter = this._buildSearch(datasetName, ds); + ds.list.pageSize = params.pageSize; + + await this.loadList(datasetName, 0); + break; + case 'button:return': + this.goBack(); + break; + case 'button:add': + // this._load({pageId: params.link, dataParentId: params.parentId, filter: ds.filter}); + // 弹出新页面 + // this.$router.push({ path: '/form/' + params.link, query: { dataParentId: params.parentId, filter: ds.filter }}); + // 替换当前新页面,path需要相同 + + // router.push({ + // path: `/form/${this.page.id}`, + // query: { + // pageId: params.link, + // parentId: this.params.parentId, + // dataParentId: params.parentId, + // filter: ds.filter, + // // title: this.title, + // }, + // }); + notify.warning('未配置链接页面'); + break; + case 'button:edit': + // this._load({pageId: params.link, id: params.id}); + // 替换当前新页面,path需要相同 + // router.push({ + // path: `/form/${this.page.id}`, + // query: { + // pageId: params.link, + // parentId: params.parentId, + // id: params.id, + // filter: ds.filter, + // // title: this.title, + // row: JSON.stringify(params.row || {}), + // }, + // }); + break; + case 'button:save': + this.onSubmit(datasetName, false); + break; + case 'button:saveAndReturn': + this.onSubmit(datasetName, true); + break; + case 'button:submit': { + const { isReturn } = params; + this.onSubmit(datasetName, isReturn); + } + break; + case 'button:remove': + // 删除一条数据 + this.onWidgetActionRemove('此操作将删除当前记录, 是否继续?', datasetName, [params.id], { + mainField: params.mainField || 'id', + }); + break; + case 'button:batchRemove': + if (params.sels && params.sels.length > 0) { + const ids = []; + + for (const item of params.sels) { + ids.push(item.id); + } + + this.onWidgetActionRemove(`此操作将删除选中的 "${ids.length}" 条记录, 是否继续?`, datasetName, ids); + } + break; + case 'button:move': + await this.doApi('treeMove', { + pageId: this.page.id, + dataset: datasetName, + id: params.id, + parentId: params.parentId, + dataOrders: params.orders, + }); + break; + case 'list:selected': + this.scrollDataset(datasetName, params.row); + break; + case 'tree:loadTreeAll': + await this.loadTree(params.dataset); + break; + // case 'event': { + // const { events, name, eventName } = params; + // params.datasetId = datasetId; + // if ((events && events.click) || name) { + // let _funName = null; + // if (events && events.click) { + // _funName = events.click; + // } else if (name || eventName) { + // _funName = eventName || name; + // } + // if (_funName && this[_funName]) { + // this[_funName](params); + // console.info(`call [${_funName}] function success`); + // } else { + // console.error(`dataset not found [${_funName}] function`); + // } + // } + // } + // break; + default: + console.log('unknown action', params); + break; + } + } + } + + onWidgetActionRemove(title: string, datasetName: string, ids: Array, otherArgsObj?: any, callFn?: Function) { + // console.log('onWidgetActionRemove', ids, ids.join()); + + confirm('删除确认', () => { + (async() => { + const ret = await this.doApi('cardDel', { + pageId: this.page.id, + dataset: datasetName, + ids: ids.join(), + }); + if (ret && (ret.data || ret.isSuccess)) { + const listData = this.loadDatasetData(datasetName).list.data; + let _mainField = null; + + if (otherArgsObj) { + _mainField = otherArgsObj.mainField; + } else { + _mainField = this.getIdFieldByDataset(datasetName); + } + + for (const id of ids) { + this._removeItem(listData, id, _mainField); + } + + callFn && callFn(); + notify.success('删除成功'); + } else { + notify.error('删除失败'); + } + })(); + }); + } + + // 处理事件 + doEvent(evtValue: IDatasetEvent) { + if (this.eventHandle) { + this.eventHandle(evtValue); + } + } + + getWidgetByName(name: string) { + if (this.page.widgets && name) { + Object.values(this.page.widgets).find((value: any) => { return value.name === name; }); + // for (const _key in this.page.widgets) { + // if (this.page.widgets[_key].name === name) { + // return this.page.widgets[_key]; + // } + // } + } + return null; + } + + updateData(params: any = {}) { + // 根据列表组件 更新 row 字段值 + const { dataset, id, fieldName, value } = params; + const ds = this.loadDatasetData(dataset); + const idField = this.getIdFieldByDataset(dataset); + if (ds && idField) { + const data = ds.list.data; + for (const _obj of data) { + if (_obj[idField] === id) { + _obj[fieldName] = value; + break; + } + } + } + } + + async init(loadLookup: boolean) { + const values = Object.values(this.page.datasets) as any[]; + + if (loadLookup) { + for (const dataset of values) { + if (dataset.tableType === 3) { + await this.loadDataset(dataset); + } + } + } + + for (const dataset of values) { + if (dataset.tableType === 1) { + await this.loadDataset(dataset); + } + } + // Object.values(this.page.datasets).forEach((dataset: any) => { + // if (dataset.tableType === 1 || dataset.tableType === 3) { // master/lookup + // this.loadDataset(dataset); + // } + // }); + } + + update(route: RouteLocationNormalizedLoaded) { + // title: this.$route.query.title, + // 页面参数 + const id = route.params.id as string; + + if (id !== this.params.id) { + this.params.id = id; + this.params.parentId = route.params.parentId as string; + this.init(false); + } + } +} + +export const useDataset = (props: any, data: any, page: any) => { + // console.log('useDataset', route.params); + const route = useRoute(); + + const pageDataset = new PageDataset(route, data, page); + + onMounted(() => { + pageDataset.init(true); + }); + + watch(() => props.id, () => { + pageDataset.update(route); + // console.log('watch', val, route.params); + }); + + // watch('$route.params.id', (val: any) => { + // console.log(page.id, val); + // }); + + return pageDataset; +}; diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/events.ts b/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/events.ts new file mode 100644 index 0000000..c9bff89 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/events.ts @@ -0,0 +1,13 @@ +import {PageDataset} from './dataset'; + +export class PageEvents { + private dataset: PageDataset; + + constructor(dataset: PageDataset) { + this.dataset = dataset; + } + + onWidgetAction(datasetId: string, params: any) { + this.dataset.onWidgetAction(datasetId, params); + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/formatter.ts b/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/formatter.ts new file mode 100644 index 0000000..f292db8 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/formatter.ts @@ -0,0 +1,28 @@ +/** + * 常用 table formatter 方法 + */ +export default { + methods: { + timeFormatter(row: string, column: any, cellValue: string) { + // 日期时间 + // temp 20210226113627 -> 2021-02-26 11:36:27 + if (cellValue && cellValue.length >= 14) { + const pattern = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/; + return cellValue.replace(pattern, '$1-$2-$3 $4:$5:$6'); + } else { + return cellValue || ''; + } + }, + statusFormatter(row: any, column: any, cellValue: any) { + if (cellValue === '0') { + return '正常'; + } else if (cellValue === '1') { + return '停用'; + } else if (cellValue === '2') { + return '锁定'; + } else { + return cellValue; + } + }, + }, +}; diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/page.less b/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/page.less new file mode 100644 index 0000000..43509f2 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/page.less @@ -0,0 +1,14 @@ +.sw-container { + height: 100%; +} + +.sw-layout { + &-col1 {} + &-col2 {grid-column-start: span 2;} + &-col3 {grid-column-start: span 3;} + &-col4 {grid-column-start: span 4;} + &-col5 {grid-column-start: span 5;} + &-col6 {grid-column-start: span 6;} + &-col7 {grid-column-start: span 7;} + &-col8 {grid-column-start: span 8;} +} diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/page.types.ts b/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/page.types.ts new file mode 100644 index 0000000..f4d5425 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/ts/page.types.ts @@ -0,0 +1,5 @@ +export interface IPageParams { + pageId: string + id?: string + parentId?: string +} diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/dataset.ts.vm b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/dataset.ts.vm new file mode 100644 index 0000000..f9295e7 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/dataset.ts.vm @@ -0,0 +1,2 @@ +// 数据集 +export default $vmTool.json($page.datasets); diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/events.ts.vm b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/events.ts.vm new file mode 100644 index 0000000..3958dd4 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/events.ts.vm @@ -0,0 +1,8 @@ +import { usePageDatasets } from "/@/sw-form"; + +export default { + create({ pageDataset }) { + this.pageDataset = pageDataset; + } +} + diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/index.vue.vm b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/index.vue.vm new file mode 100644 index 0000000..888f4cf --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/index.vue.vm @@ -0,0 +1,62 @@ + + +#macro(printWidget $widget, $path) + <$widget.type + #if($widget.props.name && $widget.vars['$ref']) + ref="$widget.props.name.$ref" + #end + #if($widget.layout.col) + class="sw-layout-col$widget.layout.col" + #end + #foreach($prop in $widget.props.entrySet()) + #if($widget.vars[$prop.key]) + :$prop.key="wigets.$widget.props.name.$prop.key" + #else + $vmTool.htmlProp($prop.key, $prop.value) + #end + #end + #if($widget.props.dataset) + @action="onAction('$widget.props.dataset', $event)" + #end + #foreach($event in $widget.events.entrySet()) + @$event.key="onEvent('$widget.name', '$event.value', $event)" + #end + #if($widget.children) + > + #foreach($child in $widget.children) + #printWidget($child) + #end + + #else + /> + #end +#end + + + + + diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/router.ts.vm b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/router.ts.vm new file mode 100644 index 0000000..007ce08 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/router.ts.vm @@ -0,0 +1,17 @@ +import {AppRouteRecordRaw} from "/@/router/types"; +// 导出根节点路由 +export const rootRoutes: AppRouteRecordRaw[] = []; +// 导出layout下的路由 +export const layoutRoutes: AppRouteRecordRaw[] = [ + #foreach($router in $routers) + { + path: "/$router.path", + name: "$router.name", + component: () => import("/@/pages/$router.filePath/index.vue"), + props: (route: any) => ({ json: route.params.json || "{}" }), + meta: { + title: "$router.title", + }, + }, + #end +]; diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/CardService.java.vm b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/CardService.java.vm new file mode 100644 index 0000000..7411373 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/CardService.java.vm @@ -0,0 +1,37 @@ +package $package; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.system.bpm.core.ui.worker.IDatasetLoadWorker; +import cc.smtweb.system.bpm.core.ui.worker.IDatasetRemoveWorker; +import cc.smtweb.system.bpm.core.ui.worker.IDatasetSaveWorker; +import cc.smtweb.system.bpm.web.ui.DatasetCardService; + +import cc.smtweb.system.bpm.web.ui.DatasetCardService; + +/** + * 卡片数据模型服务 + * + * @author bpm + */ +@SwService +public class $className extends DatasetCardService { + @Override + protected String datasetPath() { + return "$resourceUrl/$dataset.name"; + } + + @Override + protected void onSetupLoad(IDatasetLoadWorker worker) { + // TODO 自定义表单读取处理方法 + } + + @Override + protected void onSetupSave(IDatasetSaveWorker worker) { + // TODO 自定义表单保存处理方法 + } + + @Override + protected void onSetupRemove(IDatasetRemoveWorker worker) { + // TODO 自定义表单删除处理方法 + } +} + diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/ListService.java.vm b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/ListService.java.vm new file mode 100644 index 0000000..7e632ba --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/ListService.java.vm @@ -0,0 +1,28 @@ +package $package; +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.system.bpm.core.ui.worker.IDatasetListAllWorker; +import cc.smtweb.system.bpm.core.ui.worker.IDatasetListPartWorker; +import cc.smtweb.system.bpm.web.ui.DatasetListService; + +/** + * 列表数据模型服务 + * + * @author bpm + */ +@SwService +public class $className extends DatasetListService { + @Override + protected String datasetPath() { + return "$resourceUrl"; + } + + @Override + protected void onSetupListPart(IDatasetListPartWorker worker) { + // TODO 自定义部分表(支持翻页)查询 + } + + @Override + protected void onSetupListAll(IDatasetListAllWorker worker) { + // TODO 自定义全表查询 + } +} diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/TreeService.java.vm b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/TreeService.java.vm new file mode 100644 index 0000000..4542d58 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/TreeService.java.vm @@ -0,0 +1,30 @@ +package $package; + +import cc.smtweb.framework.core.annotation.SwService; +import cc.smtweb.system.bpm.core.ui.worker.IDatasetTreePartWorker; +import cc.smtweb.system.bpm.core.ui.worker.IDatasetTreeAllWorker; +import cc.smtweb.system.bpm.web.ui.DatasetTreeService; + +/** + * 树数据模型服务 + * + * @author LowCode + */ +@SwService +public class $className extends DatasetTreeService { + @Override + protected String datasetPath() { + return "$resourceUrl"; + } + + @Override + protected void onSetupTreePart(IDatasetTreePartWorker worker) { + // TODO 自定义部分树(延迟加载)查询 + }; + + @Override + protected void onSetupTreeAll(IDatasetTreeAllWorker worker) { + // TODO 自定义全树查询 + }; +} + diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/entity.java.vm b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/entity.java.vm new file mode 100644 index 0000000..7d8be58 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/entity.java.vm @@ -0,0 +1,23 @@ +package $package; +import cc.smtweb.framework.core.annotation.SwColumn; +import cc.smtweb.framework.core.annotation.SwTable; +import lombok.Data; +/** + * 以下是机器生成代码,请勿修改! + * + * @author LowCode + * $table.name + * $table.remark + */ +@Data +@SwTable("$table.dbName.$table.name") +public class $vmTool.toUpperHump($table.name) { + #foreach($field in $table.fields) + /** $field.label $!{field.remark} */ + #if ($field.name == $table.idField) + @SwColumn(type = SwColumn.Type.ID) + #end + private $vmTool.javaType($field.dataType) $vmTool.toHump($field.name); + #end +} + diff --git a/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/event.js.vm b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/event.js.vm new file mode 100644 index 0000000..31a8031 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/codegen/vm/server/event.js.vm @@ -0,0 +1,28 @@ +/** + * 页面 ${url} 事件处理,有默认属性: + * $refs map, 控件ref引用,可以访问控件函数 + * $model map, 数据集对象 + * $widgets map, 控件响应变量 + */ +window["$swEvent"].setup("$url", { + /** + * FxPage的setup中调用的初始化对象函数 + * @param props FxPage的控件属性,其中用页面params参数 + * @param context FxPage的vue3上下文对象,包括常用的emit事件属性 + */ + init(props, context) { + }, + + page$onMounted(e) { + }, + + page$onUnmounted(e) { + }, + +#foreach($child in $events) + ${child}(e) { + console.log("${child}", e); + }, + +#end +}); diff --git a/smtweb-system/sw-system-bpm/src/main/resources/config/application-dev.yaml b/smtweb-system/sw-system-bpm/src/main/resources/config/application-dev.yaml new file mode 100644 index 0000000..216f5b5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/config/application-dev.yaml @@ -0,0 +1,48 @@ +smtweb: + machine-id: 1 + file: + local-path: /data/sw/files/ + url: http://127.0.0.1:8888/sw/files/ + bpm: + debug: true + code-build: true + code-vue-path: '/code/2021/nodejs/sw-sys-uc-vue/src/' + code-vue-dir: 'pages' + code-java-path: '/code/2021/java/sw-sys-uc/sw-sys-uc-web/src/main/java/' + code-java-package: 'cc.smtweb.system.uc.web' + config-ui-path: '/code/2021/java/sw-sys-uc/sw-sys-uc-web/src/main/resources/plugin/config/' + db: + type: mysql + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + redis: + host: 127.0.0.1 + port: 6379 + password: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/sw_user?serverTimezone=UTC&allowMultiQueries=true&useSSL=false + username: root + password: 1681860 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + cache: + type: caffeine + cache-names: + - core + - bpm + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h diff --git a/smtweb-system/sw-system-bpm/src/main/resources/config/application-prod.yaml b/smtweb-system/sw-system-bpm/src/main/resources/config/application-prod.yaml new file mode 100644 index 0000000..6514125 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/config/application-prod.yaml @@ -0,0 +1,33 @@ +sme: + machine-id: 1 + file-local-path: /data/files/smart/ + file-host: http://bpm.smtweb.cc + file-url: ${sme.file-host}:${server.port}${server.servlet.context-path}/${sme.file-local-path} +server: + port: 10001 + servlet: + context-path: / +logging: + level: + smtweb: DEBUG +spring: + main: + allow-bean-definition-overriding: true + mvc: + static-path-pattern: /static/** + redis: + host: 127.0.0.1 + port: 6379 + datasource: + user: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/smt_user?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=CTT&allowMultiQueries=true + username: smt + password: smt_123456 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + + + diff --git a/smtweb-system/sw-system-bpm/src/main/resources/config/application.yaml b/smtweb-system/sw-system-bpm/src/main/resources/config/application.yaml new file mode 100644 index 0000000..216f5b5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/config/application.yaml @@ -0,0 +1,48 @@ +smtweb: + machine-id: 1 + file: + local-path: /data/sw/files/ + url: http://127.0.0.1:8888/sw/files/ + bpm: + debug: true + code-build: true + code-vue-path: '/code/2021/nodejs/sw-sys-uc-vue/src/' + code-vue-dir: 'pages' + code-java-path: '/code/2021/java/sw-sys-uc/sw-sys-uc-web/src/main/java/' + code-java-package: 'cc.smtweb.system.uc.web' + config-ui-path: '/code/2021/java/sw-sys-uc/sw-sys-uc-web/src/main/resources/plugin/config/' + db: + type: mysql + default: + rule: + prefix: _smt_ + replace: smt_ +server: + port: 8888 + servlet: + context-path: / +logging: + level: + root: INFO + cc.smtweb: DEBUG +spring: + redis: + host: 127.0.0.1 + port: 6379 + password: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/sw_user?serverTimezone=UTC&allowMultiQueries=true&useSSL=false + username: root + password: 1681860 + servlet: + multipart: + max-file-size: 104857600000 + max-request-size: 10485760000000 + cache: + type: caffeine + cache-names: + - core + - bpm + caffeine: + spec: maximumSize=1024,expireAfterWrite=2h diff --git a/smtweb-system/sw-system-bpm/src/main/resources/config/logback.xml b/smtweb-system/sw-system-bpm/src/main/resources/config/logback.xml new file mode 100644 index 0000000..ddd583f --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/config/logback.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/smtweb-system/sw-system-bpm/src/main/resources/static/bpm/event.js b/smtweb-system/sw-system-bpm/src/main/resources/static/bpm/event.js new file mode 100644 index 0000000..e0806e0 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/static/bpm/event.js @@ -0,0 +1,17 @@ +// 注册事件类 +window.$swEvent.setup("defaultEvent", { + init(options) { + }, + + page$onInit(e) { + console.log("onInit", e); + }, + + sysUser$onLoad(e) { + console.log("onLoad", e); + }, + + userNickCode$onChange(e) { + console.log("onChange", e); + }, +}); diff --git a/smtweb-system/sw-system-bpm/src/main/resources/static/bpm/events.js b/smtweb-system/sw-system-bpm/src/main/resources/static/bpm/events.js new file mode 100644 index 0000000..e0806e0 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/main/resources/static/bpm/events.js @@ -0,0 +1,17 @@ +// 注册事件类 +window.$swEvent.setup("defaultEvent", { + init(options) { + }, + + page$onInit(e) { + console.log("onInit", e); + }, + + sysUser$onLoad(e) { + console.log("onLoad", e); + }, + + userNickCode$onChange(e) { + console.log("onChange", e); + }, +}); diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/BpmTest.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/BpmTest.java new file mode 100644 index 0000000..0f4085f --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/BpmTest.java @@ -0,0 +1,38 @@ +package cc.smtweb.system.bpm.test; + +import cc.smtweb.framework.core.R; +import cc.smtweb.framework.core.SwMap; +import cc.smtweb.framework.core.mvc.controller.MethodAccessManager; +import cc.smtweb.system.bpm.spring.BpmApplication; +import org.junit.jupiter.api.Assertions; +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.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Map; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = {BpmApplication.class}) +public class BpmTest { + @Autowired + private MethodAccessManager methodAccessManager; + + private R invoke(String url, Map body) throws Exception { +// Map params = new HashMap<>(); +// return methodAccessManager.invokeDirect(url, params, JsonUtil.encodeString(body)); + return null; + } + + @Test + public void load() throws Exception { + R r = invoke("bpm/BpmTaskCard/load", SwMap.of("flowId", 476347272070696960L)); + + System.out.println(r); + + Assertions.assertEquals(0, r.readInt("code")); + Assertions.assertNotEquals(null, r.get("data")); + } + +} diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/HexTest.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/HexTest.java new file mode 100644 index 0000000..277a042 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/HexTest.java @@ -0,0 +1,36 @@ +package cc.smtweb.system.bpm.test; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; + +import java.io.FileInputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; + +public class HexTest { + + @Test + public void hexToText() throws Exception { + + FileInputStream fis = new FileInputStream("/var/1.txt"); + + byte[] data = new byte[2 * 1024 * 1024]; + int len = fis.read(data); + + byte[] buf = Hex.decodeHex(new String(data, 0, len)); + + System.out.println(new String(buf, StandardCharsets.UTF_8)); + } + + @Test + public void joinTest() { + Set ids = new HashSet<>(); + ids.add("1"); + ids.add("2"); + ids.add("3"); + ids.add("1"); + + System.out.println(String.join(",", ids)); + } +} diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/SpelTest.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/SpelTest.java new file mode 100644 index 0000000..40b7eea --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/SpelTest.java @@ -0,0 +1,41 @@ +package cc.smtweb.system.bpm.test; + +import cc.smtweb.framework.core.SwMap; +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +public class SpelTest { + @Test + public void test() { + //创建ExpressionParser解析表达式 + ExpressionParser parser = new SpelExpressionParser(); + //表达式放置 +// Expression exp = parser.parseExpression("T(java.lang.Math).random() + name + obj['name']"); + Expression exp = parser.parseExpression("T(java.lang.Math).random() > 10"); + + //向容器内添加bean + SpelContext spelContext = new SpelContext(); + spelContext.setName("ddd999"); + spelContext.getObj().put("name", "111abc"); +// ctx.setVariable("obj", beanA); + + StandardEvaluationContext ctx = new StandardEvaluationContext(spelContext); + /** 如果使用其他的容器,则用下面的方法 */ + //setRootObject并非必须;一个EvaluationContext只能有一个RootObject,引用它的属性时,可以不加前缀 +// ctx.setRootObject(XXX); + + //getValue有参数ctx,从新的容器中根据SpEL表达式获取所需的值 + Object value = exp.getValue(ctx, Boolean.class); + System.out.println(value); + } +} + +@Data +class SpelContext { + private String name; + private SwMap obj = new SwMap(); +} \ No newline at end of file diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/TreeDataBuilderTest.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/TreeDataBuilderTest.java new file mode 100644 index 0000000..06f5271 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/TreeDataBuilderTest.java @@ -0,0 +1,97 @@ +package cc.smtweb.system.bpm.test; + +import cc.smtweb.system.bpm.util.ITreeDataLevelHandler; +import cc.smtweb.system.bpm.util.TreeDataUtil; +import lombok.Data; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +public class TreeDataBuilderTest { + @Test + public void testFindParent() { + // 树结构数据句柄 + TreeDataHandler handler = new TreeDataHandler(); + + List list = new ArrayList<>(); + list.add(new TreeData(1L, null)); + list.add(new TreeData(2L, 1L)); + list.add(new TreeData(3L, 2L)); + list.add(new TreeData(4L, 3L)); + list.add(new TreeData(5L, 6L)); + + TreeData root = new TreeData(); + + TreeDataUtil.buildTree(root, list, handler); + + { + TreeData node = TreeDataUtil.findParent(root, 4L, 1, handler); + Assertions.assertEquals(1L, node.getId()); + } + + { + TreeData node = TreeDataUtil.findParent(root, 3L, 1, handler); + Assertions.assertEquals(1L, node.getId()); + } + + { + TreeData node = TreeDataUtil.findParent(root, 3L, 2, handler); + Assertions.assertEquals(2L, node.getId()); + } + + { + TreeData node = TreeDataUtil.findParent(root, 1L, 2, handler); + Assertions.assertNull(node); + } + } +} + +@Data +class TreeData { + private Long id; + private Long parentId; + private int level; + private List children; + + TreeData() { + } + + TreeData(Long id, Long parentId) { + this.id = id; + this.parentId = parentId; + } +} + +class TreeDataHandler implements ITreeDataLevelHandler { + @Override + public Long getId(TreeData node) { + return node.getId(); + } + + @Override + public Long getParentId(TreeData node) { + return node.getParentId(); + } + + @Override + public List getChildren(TreeData node) { + return node.getChildren(); + } + + @Override + public void setChildren(TreeData node, List children) { + node.setChildren(children); + } + + @Override + public int getLevel(TreeData node) { + return node.getLevel(); + } + + @Override + public void setLevel(TreeData node, int level) { + node.setLevel(level); + } +} diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/FileCompareTest.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/FileCompareTest.java new file mode 100644 index 0000000..cc39b2a --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/FileCompareTest.java @@ -0,0 +1,84 @@ +package cc.smtweb.system.bpm.test.codegen.test; + +import cc.smtweb.system.bpm.engine.ui.codegen.Sha256Helper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 文件签名比较 + */ +public class FileCompareTest { + @Test + public void testCompare() throws IOException { + try (DataReader source = new DataReader("code/index.vue"); + DataReader target = new DataReader("code/index.vue.tmp")) { + while (source.hasNext() || target.hasNext()) { + byte sourceChar = source.next(); + byte targetChat = target.next(); + if (sourceChar != targetChat) { + System.out.println(sourceChar + " <> " + targetChat); + break; + } + } + + if (source.hasNext() || target.hasNext()) { + Assertions.fail("source line: " + source.getLine() + ", target line: " + target.getLine()); + } + } + } +} + +class DataReader implements AutoCloseable { + private InputStream is; + private int line = 1; + private byte[] buffer = new byte[4096]; + private int readBytes; + private int readIndex; + + public DataReader(String resource) throws IOException { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + is = loader.getResourceAsStream(resource); + readBytes = is.read(buffer); + } + + @Override + public void close() throws IOException { + is.close(); + } + + public boolean hasNext() { + return readIndex < readBytes; + } + + public byte next() throws IOException { + while (readIndex < readBytes) { + byte ch = buffer[readIndex]; + + if (++readIndex >= readBytes) { + readBytes = is.read(buffer); + readIndex = 0; + } + + if (ch < 0) { + return ch; + } + + int type = Sha256Helper.SPACE_CHAR[ch]; + + if (type == 0) { + return ch; + } else if (type == 3) { + line++; + } + } + + return 0; + } + + public int getLine() { + return line; + } +} diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/FileUtils.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/FileUtils.java new file mode 100644 index 0000000..0a8c7d5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/FileUtils.java @@ -0,0 +1,55 @@ +package cc.smtweb.system.bpm.test.codegen.test; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class FileUtils { + /** + * 获取文件的行 + * + * @param fileName + * 文件名称 + * @return List + */ + public static String readFile(String fileName) { + StringBuilder lines = new StringBuilder(); + InputStreamReader read = null; + BufferedReader bufferedReader = null; + try { + String configPath = FileUtils.class.getResource(fileName).getPath(); + configPath = configPath.replaceAll("%20", " ");// 处理文件路径中空格问题 + File file = new File(configPath); + if (file.isFile() && file.exists()) { // 判断文件是否存在 + read = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8); + bufferedReader = new BufferedReader(read); + String lineTxt; + while ((lineTxt = bufferedReader.readLine()) != null) { + if (lineTxt.length() == 0) { + continue; + } + lines.append(lineTxt); + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (read != null) { + read.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + return lines.toString(); + } +} + diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/VmTool.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/VmTool.java new file mode 100644 index 0000000..b501451 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/VmTool.java @@ -0,0 +1,9 @@ +package cc.smtweb.system.bpm.test.codegen.test; + +import cc.smtweb.framework.core.util.JsonUtil; + +public class VmTool { + public String json(Object obj) { + return JsonUtil.encodeString(obj); + } +} diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/VuegenTest.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/VuegenTest.java new file mode 100644 index 0000000..f3f99ae --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/VuegenTest.java @@ -0,0 +1,45 @@ +package cc.smtweb.system.bpm.test.codegen.test; + +import cc.smtweb.system.bpm.core.ui.entity.BpmPage; +import cc.smtweb.system.bpm.engine.ui.loader.UiPcLoader; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; +import org.junit.jupiter.api.Test; + +import java.io.OutputStreamWriter; +import java.io.Writer; + +public class VuegenTest { + private BpmPage loadUiPage() throws Exception { +// UiPcLoader uiPageLoader = new UiPcLoader(new FileDbCache(), null); +// +// BpmPage result = uiPageLoader.load(FileUtils.readFile("/ui/508660168293093376.json")); +// result.setId(508660168293093376L); +// +// return result; + return null; + } + + @Test + public void testPrint() throws Exception { + VelocityEngine ve = new VelocityEngine(); + ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); + ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName()); + ve.init(); + // 载入(获取)模板对象 + Template t = ve.getTemplate("/template/index.vm"); + VelocityContext ctx = new VelocityContext(); + // 域对象加入参数值 + BpmPage uiPage = loadUiPage(); + ctx.put("forms", uiPage.getForms()); + ctx.put("vmTool", new VmTool()); + ctx.put("pageId", uiPage.getId()); + + Writer writer = new OutputStreamWriter(System.out); + t.merge(ctx, writer); + writer.flush(); + } +} diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/XmlTest.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/XmlTest.java new file mode 100644 index 0000000..b4b62fb --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/XmlTest.java @@ -0,0 +1,17 @@ +package cc.smtweb.system.bpm.test.codegen.test; + +import cc.smtweb.system.bpm.engine.ui.entity.yaml.YamlPage; +import cc.smtweb.system.bpm.util.XmlUtil; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; + +public class XmlTest { + @Test + public void testLoad() throws Exception { + try(InputStream is = getClass().getResourceAsStream("/code/list.xml")) { + YamlPage yamlPage = XmlUtil.readValue(is, YamlPage.class); + System.out.println(yamlPage); + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/YamlTest.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/YamlTest.java new file mode 100644 index 0000000..216e0fd --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/YamlTest.java @@ -0,0 +1,40 @@ +package cc.smtweb.system.bpm.test.codegen.test; + +import cc.smtweb.system.bpm.util.YamlUtil; +import lombok.Data; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class YamlTest { + @Test + public void testLoad() throws Exception { + YamlTable table = new YamlTable(); + table.setName("table"); + for (int i = 0; i < 10; i++) { + YamlField field = new YamlField(); + field.setFieldName("field" + i); + field.setName("name" + i); + field.setLabel("label" + i); + table.getFields().add(field); + } + + YamlUtil.writeValue(new File("/var/1.yaml"), table); + } +} + +@Data +class YamlTable { + private String name; + private List fields = new ArrayList<>(); +} + +@Data +class YamlField { +// @JacksonYamlProperty + private String fieldName; + private String name; + private String label; +} diff --git a/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/YamlToJsonTest.java b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/YamlToJsonTest.java new file mode 100644 index 0000000..a081e20 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/java/cc/smtweb/system/bpm/test/codegen/test/YamlToJsonTest.java @@ -0,0 +1,33 @@ +package cc.smtweb.system.bpm.test.codegen.test; + +import cc.smtweb.framework.core.util.JsonUtil; +import cc.smtweb.system.bpm.util.YamlUtil; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; + + +import java.io.*; +import java.util.HashMap; + +public class YamlToJsonTest { + public static void main(String[] args) throws Exception { + File dir = new File("/var/config/"); + + for (File file: dir.listFiles()) { + if (file.getName().endsWith(".yaml")) { + try (FileInputStream is = new FileInputStream(file)) { + HashMap map = YamlUtil.readValue(is, HashMap.class); + String name = (String)map.get("name"); + if (StringUtils.isNotBlank(name)) { + String json = "export default " + JsonUtil.encodeString(map); + try (OutputStream writer = new FileOutputStream("/var/config/json/" + name + ".ts")) { + IOUtils.write(json, writer, "utf8"); + } + System.out.println(name); + } + } + } + } + } +} diff --git a/smtweb-system/sw-system-bpm/src/test/resources/code/index.vue b/smtweb-system/sw-system-bpm/src/test/resources/code/index.vue new file mode 100644 index 0000000..b0cf895 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/resources/code/index.vue @@ -0,0 +1,116 @@ + + + + + + + + diff --git a/smtweb-system/sw-system-bpm/src/test/resources/code/index.vue.tmp b/smtweb-system/sw-system-bpm/src/test/resources/code/index.vue.tmp new file mode 100644 index 0000000..44e8a73 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/resources/code/index.vue.tmp @@ -0,0 +1,119 @@ + + + + + + + + diff --git a/smtweb-system/sw-system-bpm/src/test/resources/code/list.xml b/smtweb-system/sw-system-bpm/src/test/resources/code/list.xml new file mode 100644 index 0000000..22bebe5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/resources/code/list.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/smtweb-system/sw-system-bpm/src/test/resources/db/smt_uc.json b/smtweb-system/sw-system-bpm/src/test/resources/db/smt_uc.json new file mode 100644 index 0000000..3505ab3 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/resources/db/smt_uc.json @@ -0,0 +1 @@ +{"type":"chart","props":{"name":"_test_user","label":"测试用户数据库"},"nodes":[{"id":"node-172fda7eb8f","type":"table","props":{"label":"用户信息","name":"sys_user"},"columns":[{"type":"field","props":{"label":"ID","name":"user_id","dataType":"long","notNull":true,"keyType":"primary"},"id":"column-172fda7eb90"},{"id":"col-173284e59df","type":"field","label":"name","props":{"label":"姓名","name":"user_name","dataType":"char","notNull":true,"dataLength":"50"}},{"id":"column-175ba645939","type":"field","props":{"label":"部门ID","name":"user_dept_id","dataType":"long","keyType":"foreign","fkTable":"sys_dept"}},{"id":"column-175ba64593b","type":"field","props":{"label":"性别","name":"user_sex","dataType":"int","defaultValue":"0"}}],"left":101.3125,"top":34,"selected":false},{"id":"node-1746c68deac","type":"table","props":{"name":"sys_dept","label":"部门"},"columns":[{"id":"column-1746c68dead","type":"field","props":{"name":"dept_id","label":"ID","dataType":"long","notNull":true,"keyType":"primary"}},{"id":"column-175923f5b94","type":"field","props":{"label":"名称","name":"dept_name","dataType":"char"}},{"id":"column-175923f5b95","type":"field","props":{"label":"上级ID","name":"dept_parent_id","dataType":"long","keyType":"foreign","fkTable":"sys_dept"}},{"id":"column-175923f5b96","type":"field","props":{"label":"显示顺序","name":"dept_order","dataType":"int","defaultValue":"0"}}]},{"id":"node-174b89aabaf","type":"table","props":{"name":"fire_base_info","label":"基本概况"},"columns":[{"id":"column-174b89aabb0","type":"field","props":{"label":"ID","name":"bi_id","dataType":"long","keyType":"primary","notNull":true}},{"id":"column-174b89aabb1","type":"field","props":{"label":"单位名称","dataType":"char"}},{"id":"column-174b89aabb2","type":"field","props":{"label":"地址","dataType":"char"}},{"id":"column-174b89aabb3","type":"field","props":{"label":"登记类别","dataType":"char"}},{"id":"column-174b89aabb4","type":"field","props":{"label":"消防安全重点单位序码","dataType":"char"}},{"id":"column-174b89aabb5","type":"field","props":{"label":"单位性质","dataType":"char"}},{"id":"column-174b89aabb6","type":"field","props":{"label":"上级主管部门","dataType":"char"}},{"id":"column-174b89aabb7","type":"field","props":{"label":"消防安全责任人","dataType":"char"}},{"id":"column-174b89aabb8","type":"field","props":{"label":"消防安全责任人联系电话","dataType":"char"}},{"id":"column-174b89aabb9","type":"field","props":{"label":"消防安全责任人归口管理部门负责人","dataType":"char"}},{"id":"column-174b89aabba","type":"field","props":{"label":"消防安全责任人联系电话","dataType":"char"}},{"id":"column-174b89aabbb","type":"field","props":{"label":"消防安全管理人","dataType":"char"}},{"id":"column-174b89aabbc","type":"field","props":{"label":"消防安全管理人联系电话","dataType":"char"}},{"id":"column-174b89aabbd","type":"field","props":{"label":"消防安全管理人归口管理部门负责人","dataType":"char"}},{"id":"column-174b89aabbe","type":"field","props":{"label":"消防安全管理人联系电话","dataType":"char"}},{"id":"column-174b89aabbf","type":"field","props":{"label":"专职消防队负责人","dataType":"char"}},{"id":"column-174b89aabc0","type":"field","props":{"label":"专职消防队人数","dataType":"char"}},{"id":"column-174b89aabc1","type":"field","props":{"label":"专职消防队电话","dataType":"char"}},{"id":"column-174b89aabc2","type":"field","props":{"label":"主要灭火装备","dataType":"char"}},{"id":"column-174b89aabc3","type":"field","props":{"label":"供电情况","dataType":"char"}},{"id":"column-174b89aabc4","type":"field","props":{"label":"电力负荷等级","dataType":"char"}},{"id":"column-174b89aabc5","type":"field","props":{"label":"用电设备负荷(KW)","dataType":"char"}},{"id":"column-174b89aabc6","type":"field","props":{"label":"实际用电量(KW)","dataType":"char"}},{"id":"column-174b89aabc7","type":"field","props":{"label":"市政进水管数量及管径","dataType":"char"}},{"id":"column-174b89aabc8","type":"field","props":{"label":"天然消防水源数及容量","dataType":"char"}},{"id":"column-174b89aabc9","type":"field","props":{"label":"室内管网形式","dataType":"char"}},{"id":"column-174b89aabca","type":"field","props":{"label":"室外管网形式","dataType":"char"}},{"id":"column-174b89aabcb","type":"field","props":{"label":"室内最不利点消火栓压力","dataType":"char"}},{"id":"column-174b89aabcc","type":"field","props":{"label":"室外最不利点消火栓压力","dataType":"char"}},{"id":"column-174b89aabcd","type":"field","props":{"label":"消防安全重点部位名称","dataType":"char"}},{"id":"column-174b89aabce","type":"field","props":{"label":"是否已审核","dataType":"bool","notNull":true,"defaultValue":"false"}},{"id":"column-174b89aabcf","type":"field","props":{"label":"是否已验收","dataType":"bool","notNull":true,"defaultValue":"false"}},{"id":"column-174b89aabd0","type":"field","props":{"label":"备注","dataType":"char"}}]},{"id":"node-174b89aabd1","type":"table","props":{"name":"fire_emergency_plan","label":"灭火和应急疏散方案"},"columns":[{"id":"column-174b89aabd2","type":"field","props":{"label":"ID","name":"ep_id","dataType":"long","notNull":true,"keyType":"primary"}},{"id":"column-174b89aabd3","type":"field","props":{"label":"单位名称","dataType":"char"}},{"id":"column-174b89aabd4","type":"field","props":{"label":"单位地址","dataType":"char"}},{"id":"column-174b89aabd5","type":"field","props":{"label":"辖区公安消防队","dataType":"char"}},{"id":"column-174b89aabd6","type":"field","props":{"label":"增援消防队","dataType":"char"}},{"id":"column-174b89aabd7","type":"field","props":{"label":"消防安全责任人","dataType":"char"}},{"id":"column-174b89aabd8","type":"field","props":{"label":"联系电话","dataType":"char"}},{"id":"column-174b89aabd9","type":"field","props":{"label":"专职消防队队长","dataType":"char"}},{"id":"column-174b89aabda","type":"field","props":{"label":"专职消防队人数","dataType":"char"}},{"id":"column-174b89aabdb","type":"field","props":{"label":"专职消防队车辆数","dataType":"char"}},{"id":"column-174b89aabdc","type":"field","props":{"label":"义务消防队队长","dataType":"char"}},{"id":"column-174b89aabdd","type":"field","props":{"label":"义务消防队人数","dataType":"char"}},{"id":"column-174b89aabde","type":"field","props":{"label":"义务消防队主要装备","dataType":"char"}},{"id":"column-174b89aabdf","type":"field","props":{"label":"机构组织总指挥","dataType":"char"}},{"id":"column-174b89aabe0","type":"field","props":{"label":"机构组织副总指挥","dataType":"char"}},{"id":"column-174b89aabe1","type":"field","props":{"label":"机构组织灭火组","dataType":"char"}},{"id":"column-174b89aabe2","type":"field","props":{"label":"机构组织通讯组","dataType":"char"}},{"id":"column-174b89aabe3","type":"field","props":{"label":"机构组织疏散组","dataType":"char"}},{"id":"column-174b89aabe4","type":"field","props":{"label":"机构组织救护组","dataType":"char"}},{"id":"column-174b89aabe5","type":"field","props":{"label":"报警方式","dataType":"char"}},{"id":"column-174b89aabe6","type":"field","props":{"label":"报警和接警程序","dataType":"char"}},{"id":"column-174b89aabe7","type":"field","props":{"label":"重点部位1","dataType":"char"}},{"id":"column-174b89aabe8","type":"field","props":{"label":"重点部位2","dataType":"char"}},{"id":"column-174b89aabe9","type":"field","props":{"label":"应急疏散程序及措施","dataType":"char"}},{"id":"column-174b89aabea","type":"field","props":{"label":"扑救初起火灾程序及措施","dataType":"char"}},{"id":"column-174b89aabeb","type":"field","props":{"label":"通讯联络、安全防护的程序和措施","dataType":"char"}}]},{"id":"node-174de176370","type":"table","props":{"name":"fire_important_part","label":"消防安全重点部位","remark":"基本概况的子表"},"columns":[{"id":"column-174de176371","type":"field","props":{"label":"ID","name":"ip_id","dataType":"long","keyType":"primary","notNull":true}},{"id":"column-174de176373","type":"field","props":{"label":"基本概况","name":"ip_bi_id","keyType":"foreign","notNull":true,"remark":"基本概况ID","fkTable":"fire_base_info"}},{"id":"column-174de176372","type":"field","props":{"label":"部位名称","name":"ip_name","dataType":"char"}}]}]} \ No newline at end of file diff --git a/smtweb-system/sw-system-bpm/src/test/resources/template/index.vm b/smtweb-system/sw-system-bpm/src/test/resources/template/index.vm new file mode 100644 index 0000000..65568b5 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/resources/template/index.vm @@ -0,0 +1,63 @@ +#set($ui_widgets = []) +#macro(ui_print_vue $parent) + #if($parent.shape == 'ui-panel') + <$parent.data.type v-bind="pages.widgets.$parent.data.id"> + #foreach($child in $parent.children) + #ui_print_vue(${child}) + #end + + #else + <$parent.data.type v-bind="pages.widgets.$parent.data.id" @action="onWidgetAction($parent.data.id.props.dataset, $event)" :data="data"/> + #end + $ui_widgets.add($parent.data) +#end + + + + + diff --git a/smtweb-system/sw-system-bpm/src/test/resources/ui/508660168293093376.json b/smtweb-system/sw-system-bpm/src/test/resources/ui/508660168293093376.json new file mode 100644 index 0000000..3eb4063 --- /dev/null +++ b/smtweb-system/sw-system-bpm/src/test/resources/ui/508660168293093376.json @@ -0,0 +1,1115 @@ +{ + "version": 2, + "page": { + "type": "fx-page", + "props": { + "title": "基本情况编辑" + } + }, + "graph": { + "x": 1, + "y": 1, + "w": 1000, + "h": 720, + "zIndex": 1, + "shape": "ui-panel", + "data": { + "id": "root", + "type": "fz-split-panel", + "layout": { + "name": "split" + }, + "props": { + "horizontal": true + } + }, + "children": [{ + "x": 1, + "y": 1, + "w": 1000, + "h": 720, + "zIndex": 2, + "shape": "ui-panel", + "data": { + "id": "p1", + "type": "fz-form-panel", + "layout": { + "name": "form" + }, + "props": { + "paddingX": 4, + "paddingY": 4, + "label": "面板", + "colNum": 2, + "minHeight": 400, + "dataset": "widget-17424353df4" + } + }, + "children": [{ + "x": 503, + "y": 797, + "w": 494, + "h": 40, + "zIndex": 101, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9fb", + "type": "fx-switch", + "props": { + "label": "是否已验收", + "labelWidth": 120, + "activeValue": "1", + "inactiveValue": "0", + "activeColor": "#409EFF", + "inactiveColor": "#C0CCDA", + "field": "id-1755410a9db", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 797, + "w": 494, + "h": 40, + "zIndex": 102, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9f8", + "type": "fx-text", + "props": { + "label": "室外最不利点消火栓压力", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9d8", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 753, + "w": 494, + "h": 40, + "zIndex": 103, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9f6", + "type": "fx-text", + "props": { + "label": "室外管网形式", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9d6", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 753, + "w": 494, + "h": 40, + "zIndex": 104, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9f1", + "type": "fx-text", + "props": { + "label": "用电设备负荷(KW)", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9d1", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 709, + "w": 494, + "h": 40, + "zIndex": 105, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9ec", + "type": "fx-text", + "props": { + "label": "人数", + "type": "text", + "maxlength": 50, + "placeholder": "专职消防队人数", + "labelWidth": 120, + "field": "id-1755410a9cc", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 709, + "w": 494, + "h": 40, + "zIndex": 106, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9ea", + "type": "fx-text", + "props": { + "label": "联系电话", + "type": "text", + "maxlength": 50, + "placeholder": "归口管理部门负责人联系电话", + "labelWidth": 120, + "field": "id-1755410a9ca", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 665, + "w": 494, + "h": 40, + "zIndex": 107, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9e8", + "type": "fx-text", + "props": { + "label": "联系电话", + "type": "text", + "maxlength": 50, + "placeholder": "消防安全管理人联系电话", + "labelWidth": 120, + "field": "id-1755410a9c8", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 665, + "w": 494, + "h": 40, + "zIndex": 108, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9e6", + "type": "fx-text", + "props": { + "label": "联系电话", + "type": "text", + "maxlength": 50, + "placeholder": "归口管理部门负责人联系电话", + "labelWidth": 120, + "field": "id-1755410a9c6", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 621, + "w": 494, + "h": 40, + "zIndex": 109, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9e4", + "type": "fx-text", + "props": { + "label": "联系电话", + "type": "text", + "maxlength": 50, + "placeholder": "消防安全责任人联系电话", + "labelWidth": 120, + "field": "id-1755410a9c4", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 621, + "w": 494, + "h": 40, + "zIndex": 110, + "shape": "vue-shape", + "data": { + "id": "widget-1755456a2bc", + "type": "fx-textarea", + "props": { + "label": "备注", + "type": "textarea", + "maxlength": 200, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9dc", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 577, + "w": 494, + "h": 40, + "zIndex": 111, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9df", + "type": "fx-text", + "props": { + "label": "登记类别", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9bf", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 577, + "w": 494, + "h": 40, + "zIndex": 112, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9e0", + "type": "fx-text", + "props": { + "label": "消防安全重点单位序码", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9c0", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 533, + "w": 494, + "h": 40, + "zIndex": 113, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9fa", + "type": "fx-switch", + "props": { + "label": "是否已审核", + "labelWidth": 120, + "activeValue": "1", + "inactiveValue": "0", + "activeColor": "#409EFF", + "inactiveColor": "#C0CCDA", + "field": "id-1755410a9da", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 533, + "w": 494, + "h": 40, + "zIndex": 114, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9f5", + "type": "fx-text", + "props": { + "label": "室内管网形式", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9d5", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 489, + "w": 494, + "h": 40, + "zIndex": 115, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9f0", + "type": "fx-text", + "props": { + "label": "电力负荷等级", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9d0", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 489, + "w": 494, + "h": 40, + "zIndex": 116, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9e1", + "type": "fx-text", + "props": { + "label": "单位性质", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9c1", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 445, + "w": 494, + "h": 40, + "zIndex": 117, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9e2", + "type": "fx-text", + "props": { + "label": "上级主管部门", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9c2", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 445, + "w": 494, + "h": 40, + "zIndex": 118, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9f3", + "type": "fx-text", + "props": { + "label": "市政进水管数量及管径", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9d3", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 401, + "w": 494, + "h": 40, + "zIndex": 119, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9f4", + "type": "fx-text", + "props": { + "label": "天然消防水源数及容量", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9d4", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 401, + "w": 494, + "h": 40, + "zIndex": 120, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9f9", + "type": "fx-text", + "props": { + "label": "消防安全重点部位名称", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9d9", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 357, + "w": 494, + "h": 40, + "zIndex": 121, + "shape": "vue-shape", + "data": { + "id": "widget-1755456a2bb", + "type": "fx-divider", + "props": { + "label": "", + "contentPosition": "left", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 357, + "w": 494, + "h": 40, + "zIndex": 122, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9f7", + "type": "fx-text", + "props": { + "label": "室内最不利点消火栓压力", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9d7", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 313, + "w": 494, + "h": 40, + "zIndex": 123, + "shape": "vue-shape", + "data": { + "id": "widget-1755456a2ba", + "type": "fx-divider", + "props": { + "label": "消防给水情况", + "contentPosition": "left", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 313, + "w": 494, + "h": 40, + "zIndex": 124, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9f2", + "type": "fx-text", + "props": { + "label": "实际用电量(KW)", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9d2", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 269, + "w": 494, + "h": 40, + "zIndex": 125, + "shape": "vue-shape", + "data": { + "id": "widget-1755456a2b9", + "type": "fx-divider", + "props": { + "label": "供电情况", + "contentPosition": "left", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 269, + "w": 494, + "h": 40, + "zIndex": 126, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9ee", + "type": "fx-text", + "props": { + "label": "主要灭火装备", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9ce", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 225, + "w": 494, + "h": 40, + "zIndex": 127, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9ed", + "type": "fx-text", + "props": { + "label": "电话", + "type": "text", + "maxlength": 50, + "placeholder": "专职消防队电话", + "labelWidth": 120, + "field": "id-1755410a9cd", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 225, + "w": 494, + "h": 40, + "zIndex": 128, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9eb", + "type": "fx-text", + "props": { + "label": "负责人", + "type": "text", + "maxlength": 50, + "placeholder": "专职消防队负责人", + "labelWidth": 120, + "field": "id-1755410a9cb", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 181, + "w": 494, + "h": 40, + "zIndex": 129, + "shape": "vue-shape", + "data": { + "id": "widget-1755456a2b8", + "type": "fx-divider", + "props": { + "label": "专职消防队", + "contentPosition": "left", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 181, + "w": 494, + "h": 40, + "zIndex": 130, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9e9", + "type": "fx-text", + "props": { + "label": "归口管理部门负责人", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9c9", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 137, + "w": 494, + "h": 40, + "zIndex": 131, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9e7", + "type": "fx-text", + "props": { + "label": "消防安全管理人", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9c7", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 137, + "w": 494, + "h": 40, + "zIndex": 132, + "shape": "vue-shape", + "data": { + "id": "widget-1755456a2b7", + "type": "fx-divider", + "props": { + "label": "消防安全管理人", + "contentPosition": "left", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 93, + "w": 494, + "h": 40, + "zIndex": 133, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9e5", + "type": "fx-text", + "props": { + "label": "归口管理部门负责人", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9c5", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 93, + "w": 494, + "h": 40, + "zIndex": 134, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9e3", + "type": "fx-text", + "props": { + "label": "消防安全责任人", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9c3", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 49, + "w": 494, + "h": 40, + "zIndex": 135, + "shape": "vue-shape", + "data": { + "id": "widget-1755456a2b6", + "type": "fx-divider", + "props": { + "label": "消防安全责任人", + "contentPosition": "left", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 49, + "w": 494, + "h": 40, + "zIndex": 136, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9de", + "type": "fx-text", + "props": { + "label": "地址", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9be", + "required": true, + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 503, + "y": 5, + "w": 494, + "h": 40, + "zIndex": 137, + "shape": "vue-shape", + "data": { + "id": "id-1755410a9dd", + "type": "fx-text", + "props": { + "label": "单位名称", + "type": "text", + "maxlength": 50, + "placeholder": "请输入内容", + "labelWidth": 120, + "field": "id-1755410a9bc", + "required": true, + "dataset": "widget-17424353df4" + } + }, + "children": [] + }, { + "x": 5, + "y": 5, + "w": 494, + "h": 40, + "zIndex": 138, + "shape": "vue-shape", + "data": { + "id": "widget-175547b3b04", + "type": "fx-button-group", + "props": { + "gutter": 16, + "LineSpace": 8, + "buttons": [{ + "id": "col-175547b3b11", + "type": "fx-button", + "w": 1, + "h": 1, + "value": null, + "props": { + "label": "返回", + "span": 8, + "type": "success", + "size": "mini", + "plain": true, + "action": "return" + } + }, { + "type": "fx-button", + "props": { + "label": "提交", + "type": "primary", + "size": "mini", + "plain": false, + "round": false, + "circle": false, + "autofocus": false, + "action": "submit" + }, + "id": "id-175547b3b05" + }, { + "id": "col-175547b3b09", + "type": "fx-button", + "w": 1, + "h": 1, + "value": null, + "props": { + "label": "删除", + "span": 8, + "type": "danger", + "size": "mini", + "action": "remove" + } + }], + "lineSpace": 8, + "justifyContent": "flex-start", + "slot": "header", + "dataset": "widget-17424353df4" + } + }, + "children": [] + }] + }] + }, + "model": [{ + "id": "widget-17424353df4", + "type": "fz-dataset", + "value": null, + "props": { + "label": "数据集", + "fields": [{ + "id": "id-1755410a9bc", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabb1", + "label": "单位名称" + } + }, { + "id": "id-1755410a9bd", + "type": "fz-field-long", + "props": { + "field": "column-174b89aabb0", + "name": "bi_id", + "label": "ID" + } + }, { + "id": "id-1755410a9be", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabb2", + "label": "地址" + } + }, { + "id": "id-1755410a9bf", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabb3", + "label": "登记类别" + } + }, { + "id": "id-1755410a9c0", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabb4", + "label": "消防安全重点单位序码" + } + }, { + "id": "id-1755410a9c1", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabb5", + "label": "单位性质" + } + }, { + "id": "id-1755410a9c2", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabb6", + "label": "上级主管部门" + } + }, { + "id": "id-1755410a9c3", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabb7", + "label": "消防安全责任人" + } + }, { + "id": "id-1755410a9c4", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabb8", + "label": "消防安全责任人联系电话" + } + }, { + "id": "id-1755410a9c5", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabb9", + "label": "消防安全责任人归口管理部门负责人" + } + }, { + "id": "id-1755410a9c6", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabba", + "label": "消防安全责任人联系电话" + } + }, { + "id": "id-1755410a9c7", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabbb", + "label": "消防安全管理人" + } + }, { + "id": "id-1755410a9c8", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabbc", + "label": "消防安全管理人联系电话" + } + }, { + "id": "id-1755410a9c9", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabbd", + "label": "消防安全管理人归口管理部门负责人" + } + }, { + "id": "id-1755410a9ca", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabbe", + "label": "消防安全管理人联系电话" + } + }, { + "id": "id-1755410a9cb", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabbf", + "label": "专职消防队负责人" + } + }, { + "id": "id-1755410a9cc", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabc0", + "label": "专职消防队人数" + } + }, { + "id": "id-1755410a9cd", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabc1", + "label": "专职消防队电话" + } + }, { + "id": "id-1755410a9ce", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabc2", + "label": "主要灭火装备" + } + }, { + "id": "id-1755410a9cf", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabc3", + "label": "供电情况" + } + }, { + "id": "id-1755410a9d0", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabc4", + "label": "电力负荷等级" + } + }, { + "id": "id-1755410a9d1", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabc5", + "label": "用电设备负荷(KW)" + } + }, { + "id": "id-1755410a9d2", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabc6", + "label": "实际用电量(KW)" + } + }, { + "id": "id-1755410a9d3", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabc7", + "label": "市政进水管数量及管径" + } + }, { + "id": "id-1755410a9d4", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabc8", + "label": "天然消防水源数及容量" + } + }, { + "id": "id-1755410a9d5", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabc9", + "label": "室内管网形式" + } + }, { + "id": "id-1755410a9d6", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabca", + "label": "室外管网形式" + } + }, { + "id": "id-1755410a9d7", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabcb", + "label": "室内最不利点消火栓压力" + } + }, { + "id": "id-1755410a9d8", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabcc", + "label": "室外最不利点消火栓压力" + } + }, { + "id": "id-1755410a9d9", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabcd", + "label": "消防安全重点部位名称" + } + }, { + "id": "id-1755410a9da", + "type": "fz-field-bool", + "props": { + "field": "column-174b89aabce", + "label": "是否已审核" + } + }, { + "id": "id-1755410a9db", + "type": "fz-field-bool", + "props": { + "field": "column-174b89aabcf", + "label": "是否已验收" + } + }, { + "id": "id-1755410a9dc", + "type": "fz-field-char", + "props": { + "field": "column-174b89aabd0", + "label": "备注" + } + }], + "datasource": "smt_uc", + "table": "node-174b89aabaf", + "tableType": "master" + }, + "events": {}, + "apis": {} + }] +}