@@ -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 |
@@ -0,0 +1,34 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<groupId>cc.smtweb</groupId> | |||||
<artifactId>smtweb-framework</artifactId> | |||||
<packaging>pom</packaging> | |||||
<version>2.2.0-SNAPSHOT</version> | |||||
<parent> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-parent</artifactId> | |||||
<version>2.3.1.RELEASE</version> | |||||
<relativePath/> <!-- lookup parent from repository --> | |||||
</parent> | |||||
<properties> | |||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | |||||
<java.version>1.8</java.version> | |||||
<maven.compiler.source>1.8</maven.compiler.source> | |||||
<maven.compiler.target>1.8</maven.compiler.target> | |||||
<skipTests>true</skipTests> | |||||
</properties> | |||||
<modules> | |||||
<module>sw-framework-core</module> | |||||
<module>sw-framework-auth</module> | |||||
<module>sw-framework-file</module> | |||||
<module>sw-framework-web</module> | |||||
<module>../smtweb-system/sw-system-bpm</module> | |||||
</modules> | |||||
</project> |
@@ -0,0 +1,132 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||||
<groupId>cc.smtweb</groupId> | |||||
<artifactId>sw-framework-auth</artifactId> | |||||
<version>2.2.0-SNAPSHOT</version> | |||||
<parent> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-parent</artifactId> | |||||
<version>2.5.6</version> | |||||
<relativePath/> <!-- lookup parent from repository --> | |||||
</parent> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-web</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>cc.smtweb</groupId> | |||||
<artifactId>sw-framework-core</artifactId> | |||||
<version>2.2.0-SNAPSHOT</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-test</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher --> | |||||
<dependency> | |||||
<groupId>org.junit.platform</groupId> | |||||
<artifactId>junit-platform-launcher</artifactId> | |||||
<version>1.6.2</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> | |||||
<dependency> | |||||
<groupId>org.junit.jupiter</groupId> | |||||
<artifactId>junit-jupiter-api</artifactId> | |||||
<version>5.6.2</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine --> | |||||
<dependency> | |||||
<groupId>org.junit.jupiter</groupId> | |||||
<artifactId>junit-jupiter-engine</artifactId> | |||||
<version>5.6.2</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.junit.vintage</groupId> | |||||
<artifactId>junit-vintage-engine</artifactId> | |||||
<version>5.6.2</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.junit.jupiter</groupId> | |||||
<artifactId>junit-jupiter-params</artifactId> | |||||
<version>5.6.2</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework</groupId> | |||||
<artifactId>spring-test</artifactId> | |||||
<version>5.2.7.RELEASE</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all --> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-all</artifactId> | |||||
<version>1.10.19</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
<build> | |||||
<!-- <finalName>user</finalName>--> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-source-plugin</artifactId> | |||||
<executions> | |||||
<execution> | |||||
<id>attach-sources</id> | |||||
<phase>verify</phase> | |||||
<goals> | |||||
<goal>jar-no-fork</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
<!-- 用于生成jar包的plugin --> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-jar-plugin</artifactId> | |||||
<version>2.6</version> | |||||
<configuration> | |||||
<excludes> | |||||
<exclude>config/*.yaml</exclude> | |||||
</excludes> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
<!-- <resources>--> | |||||
<!-- <resource>--> | |||||
<!-- <filtering>true</filtering>--> | |||||
<!-- <directory>src/main/resources</directory>--> | |||||
<!-- <excludes>--> | |||||
<!-- <exclude>config/*.yaml</exclude>--> | |||||
<!-- </excludes>--> | |||||
<!-- </resource>--> | |||||
<!-- </resources>--> | |||||
</build> | |||||
<distributionManagement> | |||||
<repository> | |||||
<id>nexus-releases</id> | |||||
<name>Nexus Release Repository</name> | |||||
<url>http://47.92.149.153:7000/repository/maven-releases/</url> | |||||
</repository> | |||||
<snapshotRepository> | |||||
<id>nexus-snapshots</id> | |||||
<name>Nexus Snapshot Repository</name> | |||||
<url>http://47.92.149.153:7000/repository/maven-snapshots/</url> | |||||
</snapshotRepository> | |||||
</distributionManagement> | |||||
</project> |
@@ -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(); | |||||
} | |||||
} |
@@ -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"); | |||||
} | |||||
} |
@@ -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<PermChecker> { | |||||
@Autowired | |||||
private JdbcEngine dbEngine; | |||||
private static final long ENTERPRISE_ADMIN_ID = 1; | |||||
@Override | |||||
protected String getId(PermChecker bean) { | |||||
return "1";//todo | |||||
} | |||||
@Override | |||||
protected List<PermChecker> loadAll() { | |||||
return null; | |||||
} | |||||
protected PermChecker load(Long key) { | |||||
// admin | |||||
if (key == ENTERPRISE_ADMIN_ID) { | |||||
return PermChecker.build(new HashSet<>(Collections.singletonList("*"))); | |||||
} | |||||
// TODO: 合并相同角色,自己到缓存里面获取 | |||||
Set<String> permissions = new HashSet<>(); | |||||
List<String> 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); | |||||
} | |||||
} |
@@ -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; | |||||
} |
@@ -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; | |||||
} |
@@ -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; | |||||
} |
@@ -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; | |||||
} |
@@ -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); | |||||
// } | |||||
} |
@@ -0,0 +1,2 @@ | |||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ | |||||
cc.smtweb.framework.auth.spring.AuthAutoConfiguration |
@@ -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 | |||||
@@ -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 | |||||
@@ -0,0 +1,3 @@ | |||||
spring: | |||||
profiles: | |||||
active: dev |
@@ -0,0 +1,2 @@ | |||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ | |||||
cc.smtweb.framework.auth.spring.AuthAutoConfiguration |
@@ -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 | |||||
@@ -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 | |||||
@@ -0,0 +1,3 @@ | |||||
spring: | |||||
profiles: | |||||
active: dev |
@@ -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 |
@@ -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 |
@@ -0,0 +1,8 @@ | |||||
20210710 | |||||
1. 定时器手动触发 (done) | |||||
2. 优化BeanReadUtils的TableName预先进行转换 | |||||
2. updateEntity支持悲观锁,并能抛出异常 | |||||
3. 缓存支持自定义 key | |||||
4. 序列号生成器 | |||||
5. redis配置database(0~15) (done) |
@@ -0,0 +1,202 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||||
<groupId>cc.smtweb</groupId> | |||||
<artifactId>sw-framework-core</artifactId> | |||||
<version>2.2.0-SNAPSHOT</version> | |||||
<parent> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-parent</artifactId> | |||||
<version>2.5.6</version> | |||||
<relativePath/> <!-- lookup parent from repository --> | |||||
</parent> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<properties> | |||||
<!-- <maven.build.timestamp.format>MMddHH</maven.build.timestamp.format>--> | |||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | |||||
<java.version>1.8</java.version> | |||||
<maven.compiler.source>1.8</maven.compiler.source> | |||||
<maven.compiler.target>1.8</maven.compiler.target> | |||||
<skipTests>true</skipTests> | |||||
</properties> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-web</artifactId> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core --> | |||||
<dependency> | |||||
<groupId>com.fasterxml.jackson.core</groupId> | |||||
<artifactId>jackson-core</artifactId> | |||||
</dependency> | |||||
<!-- 引入jdbc 依赖 --> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-jdbc</artifactId> | |||||
</dependency> | |||||
<!--janino,经过测试,强项是动态脚本,作为表达式引擎,性能比不上jexl,暂舍弃 | |||||
<dependency> | |||||
<groupId>org.codehaus.janino</groupId> | |||||
<artifactId>janino</artifactId> | |||||
<version>3.0.11</version> | |||||
</dependency>--> | |||||
<!-- Redis Lettuce --> | |||||
<dependency> | |||||
<groupId>io.lettuce</groupId> | |||||
<artifactId>lettuce-core</artifactId> | |||||
<version>6.1.5.RELEASE</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.apache.commons</groupId> | |||||
<artifactId>commons-pool2</artifactId> | |||||
<version>2.11.1</version> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec --> | |||||
<dependency> | |||||
<groupId>commons-codec</groupId> | |||||
<artifactId>commons-codec</artifactId> | |||||
<version>1.15</version> | |||||
</dependency> | |||||
<!-- 将作为Redis对象序列化器 --> | |||||
<dependency> | |||||
<groupId>com.esotericsoftware</groupId> | |||||
<artifactId>kryo</artifactId> | |||||
<version>4.0.0</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.projectlombok</groupId> | |||||
<artifactId>lombok</artifactId> | |||||
<version>1.18.22</version> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> | |||||
<dependency> | |||||
<groupId>org.apache.commons</groupId> | |||||
<artifactId>commons-lang3</artifactId> | |||||
<version>3.12.0</version> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-jexl3 --> | |||||
<dependency> | |||||
<groupId>org.apache.commons</groupId> | |||||
<artifactId>commons-jexl3</artifactId> | |||||
<version>3.2.1</version> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/org.apache.tika/tika-core --> | |||||
<dependency> | |||||
<groupId>org.apache.tika</groupId> | |||||
<artifactId>tika-core</artifactId> | |||||
<version>2.1.0</version> | |||||
</dependency> | |||||
<!-- 本地缓存 --> | |||||
<dependency> | |||||
<groupId>com.github.ben-manes.caffeine</groupId> | |||||
<artifactId>caffeine</artifactId> | |||||
</dependency> | |||||
<!-- jdbc驱动 --> | |||||
<dependency> | |||||
<groupId>mysql</groupId> | |||||
<artifactId>mysql-connector-java</artifactId> | |||||
<version>8.0.27</version> | |||||
<scope>runtime</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>commons-collections</groupId> | |||||
<artifactId>commons-collections</artifactId> | |||||
<version>3.2.2</version> | |||||
<scope>compile</scope> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher --> | |||||
<dependency> | |||||
<groupId>org.junit.platform</groupId> | |||||
<artifactId>junit-platform-launcher</artifactId> | |||||
<version>1.6.2</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> | |||||
<dependency> | |||||
<groupId>org.junit.jupiter</groupId> | |||||
<artifactId>junit-jupiter-api</artifactId> | |||||
<version>5.6.2</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine --> | |||||
<dependency> | |||||
<groupId>org.junit.jupiter</groupId> | |||||
<artifactId>junit-jupiter-engine</artifactId> | |||||
<version>5.6.2</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.junit.vintage</groupId> | |||||
<artifactId>junit-vintage-engine</artifactId> | |||||
<version>5.6.2</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.junit.jupiter</groupId> | |||||
<artifactId>junit-jupiter-params</artifactId> | |||||
<version>5.6.2</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-source-plugin</artifactId> | |||||
<executions> | |||||
<execution> | |||||
<id>attach-sources</id> | |||||
<phase>verify</phase> | |||||
<goals> | |||||
<goal>jar-no-fork</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
<!-- 用于生成jar包的plugin --> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-jar-plugin</artifactId> | |||||
<configuration> | |||||
<excludes> | |||||
<exclude>config/*.yaml</exclude> | |||||
</excludes> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
<!-- <resources>--> | |||||
<!-- <resources>--> | |||||
<!-- <resource>--> | |||||
<!-- <filtering>true</filtering>--> | |||||
<!-- <directory>src/main/resources</directory>--> | |||||
<!-- <excludes>--> | |||||
<!-- <exclude>config/*.yaml</exclude>--> | |||||
<!-- </excludes>--> | |||||
<!-- </resource>--> | |||||
<!-- </resources>--> | |||||
</build> | |||||
<distributionManagement> | |||||
<repository> | |||||
<id>nexus-releases</id> | |||||
<name>Nexus Release Repository</name> | |||||
<url>http://47.92.149.153:7000/repository/maven-releases/</url> | |||||
</repository> | |||||
<snapshotRepository> | |||||
<id>nexus-snapshots</id> | |||||
<name>Nexus Snapshot Repository</name> | |||||
<url>http://47.92.149.153:7000/repository/maven-snapshots/</url> | |||||
</snapshotRepository> | |||||
</distributionManagement> | |||||
</project> |
@@ -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); | |||||
} | |||||
} |
@@ -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<ApplicationStartedEvent> { | |||||
@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()); | |||||
} | |||||
} |
@@ -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"); | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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; | |||||
// } | |||||
} |
@@ -0,0 +1,9 @@ | |||||
package cc.smtweb.framework.core; | |||||
import lombok.Data; | |||||
@Data | |||||
public class SwIpAddr { | |||||
private String ip; | |||||
private int port; | |||||
} |
@@ -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<String, Object> { | |||||
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<Long> 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); | |||||
} | |||||
} |
@@ -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 ""; | |||||
} |
@@ -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 ""; | |||||
} |
@@ -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; | |||||
} |
@@ -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 ""; | |||||
} |
@@ -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; | |||||
} |
@@ -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, | |||||
} | |||||
} |
@@ -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 ""; | |||||
} |
@@ -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; | |||||
} |
@@ -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; | |||||
} |
@@ -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 ""; | |||||
} |
@@ -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; | |||||
} |
@@ -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 ""; | |||||
} |
@@ -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; | |||||
} |
@@ -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; | |||||
} |
@@ -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 ""; | |||||
} |
@@ -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 ""; | |||||
} |
@@ -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<T extends Serializable> implements ISwCache<String, T> { | |||||
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<T> pTypeClass = null; | |||||
private LoadingCache<String, T> cache; | |||||
private Map<String, T> cacheOrg = new ConcurrentHashMap<>(); | |||||
//本地缓存对象,按List缓存,减轻redis压力 | |||||
protected Map<String, Set<T>> mapListLocal = new ConcurrentHashMap<>(); | |||||
//本地按其他键值缓存的对象 | |||||
protected Map<String, Map<String, T>> mapMapLocal = new ConcurrentHashMap<>(); | |||||
/*注册要按list缓存的信息,key=list的类别,按什么字段来缓存。IGetBeanKey为根据bean获取此bean的key | |||||
如资金科目明细项,需按资金科目缓存List。key=sc,value=class_id | |||||
*/ | |||||
protected Map<String, IGetBeanKey<T>> mapListReg = new HashMap<>(); | |||||
protected Map<String, IGetBeanKey<T>> 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<Object, Object> 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<T> iGetBeanKey) { | |||||
mapListReg.put(key, iGetBeanKey); | |||||
} | |||||
/** | |||||
* 注册其他key的Map缓存,如按code缓存 | |||||
* | |||||
* @param key | |||||
* @param iGetBeanKey | |||||
*/ | |||||
protected void regMap(String key, IGetBeanKey<T> iGetBeanKey) { | |||||
mapMapReg.put(key, iGetBeanKey); | |||||
} | |||||
//获取bean的id | |||||
protected abstract String getId(T bean); | |||||
/** | |||||
* 从redis获取全部缓存 | |||||
* redis上只保留<id,Bean>,拉下来后,自行组织其他格式缓存 | |||||
*/ | |||||
public void syncCache() { | |||||
loadStatu = LS_LOADING; | |||||
cache.invalidateAll(); | |||||
cacheOrg.clear(); | |||||
mapListLocal.clear(); | |||||
mapMapLocal.clear(); | |||||
List<byte[]> 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<T> 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<String, IGetBeanKey<T>> entry : mapListReg.entrySet()) { | |||||
doRemoveList(entry.getKey(), getBeanKey(entry.getValue(), bean), bean); | |||||
} | |||||
for (Map.Entry<String, IGetBeanKey<T>> 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<String, IGetBeanKey<T>> entry : mapListReg.entrySet()) { | |||||
doUpdateList(entry.getKey(), getBeanKey(entry.getValue(), value), value); | |||||
} | |||||
for (Map.Entry<String, IGetBeanKey<T>> 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<T> 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<String, T> 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<T> 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<String, T> 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<String, T> map = mapMapLocal.get(rk); | |||||
if (map != null) { | |||||
return map.get(key); | |||||
} | |||||
return null; | |||||
} | |||||
public final Set<T> 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<T> 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<T> iGetBeanKey, T bean) { | |||||
return iGetBeanKey.getKey(bean); | |||||
} | |||||
public Collection<T> getAll() { | |||||
return cacheOrg.values(); | |||||
} | |||||
/** | |||||
* 获取bean缓存key的接口类,用于按非id的缓存,如code等 | |||||
* | |||||
* @param <T> | |||||
*/ | |||||
public interface IGetBeanKey<T> { | |||||
String getKey(T bean); | |||||
} | |||||
} |
@@ -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<String, AbstractCache> cacheMap = new HashMap<>(); | |||||
private final Map<Class, AbstractCache> cacheMapCls = new HashMap<>(); | |||||
//记录顺序用 | |||||
private List<AbstractCache> 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 extends AbstractCache> T getCache(Class<T> 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); | |||||
} | |||||
} |
@@ -0,0 +1,24 @@ | |||||
package cc.smtweb.framework.core.cache; | |||||
public interface ISwCache<K, V> { | |||||
/** 权限缓存名称 */ | |||||
String REALM_CACHE = "RealmCache"; | |||||
/** | |||||
* 根据键值获取缓存对象 | |||||
* @param key 缓存唯一键值 | |||||
* @return 缓存对象 | |||||
*/ | |||||
V get(K key); | |||||
/** | |||||
* 给redis发送广播消息,多服务同步删除相同key值的缓存对象 | |||||
* @param key 缓存键值 | |||||
*/ | |||||
void publishRemove(K key); | |||||
/** | |||||
* 给redis发送广播消息,多服务同步清空相同类型的缓存对象 | |||||
*/ | |||||
void publishClear(); | |||||
} |
@@ -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<Class<?>, EntityDao<?>> daoMap = new ConcurrentHashMap<>(); | |||||
private final Map<String, EntityDao<DefaultEntity>> 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 <T> | |||||
* @return | |||||
*/ | |||||
public <T> EntityDao<T> findDao(Class<T> type) { | |||||
EntityDao<T> handler = (EntityDao<T>) daoMap.get(type); | |||||
if (handler == null) { | |||||
synchronized (daoMap) { | |||||
handler = new EntityDao<>(type, this); | |||||
daoMap.put(type, handler); | |||||
} | |||||
} | |||||
return handler; | |||||
} | |||||
public EntityDao<DefaultEntity> findDao(String tableName) { | |||||
EntityDao<DefaultEntity> 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<String> rse = new ResultSetExtractor<String>() { | |||||
@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 <T> PO对象类型 | |||||
* @return 更新数量 | |||||
*/ | |||||
public <T> int updateEntity(T entity) { | |||||
return updateEntity(entity, null, null); | |||||
} | |||||
/** | |||||
* 使用ID字段更新单行数据 | |||||
* | |||||
* @param entity PO值对象,对象属性是需要更新的值 | |||||
* @param fields 需要更新额字段列表,逗号分隔 | |||||
* @param <T> PO值对象类型 | |||||
* @return 更新数量 | |||||
*/ | |||||
public <T> int updateEntity(T entity, String fields) { | |||||
return updateEntity(entity, fields, null); | |||||
} | |||||
/** | |||||
* 指定自定义条件更新对象 | |||||
* | |||||
* @param entity PO值对象,对象属性是需要更新的值和更新条件值 | |||||
* @param fields 需要更新额字段列表,逗号分隔 | |||||
* @param whereFields 更新条件字段列表,逗号分隔 | |||||
* @param <T> PO值对象类型 | |||||
* @return 更新数量 | |||||
*/ | |||||
public <T> int updateEntity(T entity, String fields, String whereFields) { | |||||
EntityDao<T> dao = findDao((Class<T>) entity.getClass()); | |||||
return dao.updateEntity(entity, fields, whereFields); | |||||
} | |||||
/** | |||||
* 用PO对象所有字段入单行数据 | |||||
* | |||||
* @param entity PO对象 | |||||
* @param <T> PO对象类型 | |||||
* @return 更新数量 | |||||
*/ | |||||
public <T> int insertEntity(T entity) { | |||||
return insertEntity(entity, null); | |||||
} | |||||
/** | |||||
* 插入单行数据 | |||||
* | |||||
* @param entity PO对象 | |||||
* @param fields 逗号分隔的字段列表 | |||||
* @param <T> PO对象类型 | |||||
* @return 更新数量 | |||||
*/ | |||||
public <T> int insertEntity(T entity, String fields) { | |||||
EntityDao<T> dao = findDao((Class<T>) entity.getClass()); | |||||
return dao.insertEntity(entity, fields); | |||||
} | |||||
/** | |||||
* 根据ID值删除单行数据 | |||||
* | |||||
* @param entity PO对象 | |||||
* @param <T> PO对象类型 | |||||
* @return 删除数量 | |||||
*/ | |||||
public <T> int deleteEntity(T entity) { | |||||
EntityDao<T> dao = findDao((Class<T>) entity.getClass()); | |||||
return dao.deleteEntity(entity); | |||||
} | |||||
/** | |||||
* 根据ID值删除单行数据 | |||||
* | |||||
* @param entityType PO对象类型 | |||||
* @param id 记录主建值 | |||||
* @param <T> PO对象 | |||||
* @return 删除数量 | |||||
*/ | |||||
public <T> int deleteEntity(Class<T> entityType, Long id) { | |||||
EntityDao<T> dao = findDao(entityType); | |||||
return dao.deleteEntity(id); | |||||
} | |||||
/** | |||||
* 根据ID值删除单行数据 | |||||
* | |||||
* @param entityType PO对象类型 | |||||
* @param whereSql Where条件SQL语句,以where开头 | |||||
* @param params 条件的值,可以多个 | |||||
* @param <T> PO对象 | |||||
* @return 删除数量 | |||||
*/ | |||||
public <T> int deleteEntity(Class<T> entityType, String whereSql, Object... params) { | |||||
EntityDao<T> dao = findDao(entityType); | |||||
return dao.deleteEntity(whereSql, params); | |||||
} | |||||
/** | |||||
* 读取实体对象ID值 | |||||
* | |||||
* @param entity | |||||
* @param <T> | |||||
* @return | |||||
*/ | |||||
public <T> Long readEntityId(T entity) { | |||||
EntityDao<T> dao = findDao((Class<T>) entity.getClass()); | |||||
return dao.readId(entity); | |||||
} | |||||
/** | |||||
* 查询单行数据,返回bean | |||||
*/ | |||||
public <T> T queryEntity(Class<T> type, Long id) { | |||||
return queryEntity(type, id, null); | |||||
} | |||||
public <T> T queryEntity(Class<T> type, Long id, String fields) { | |||||
return findDao(type).queryEntity(id, fields); | |||||
} | |||||
/** | |||||
* 查询对象所有数据,返回列表 | |||||
*/ | |||||
public <T> List<T> query(Class<T> type) { | |||||
return query(type, null); | |||||
} | |||||
/** | |||||
* 查询对象所有数据,返回列表 | |||||
*/ | |||||
public <T> List<T> query(Class<T> type, String fields) { | |||||
return findDao(type).query(fields); | |||||
} | |||||
/** | |||||
* 传入where条件查询实体类别 | |||||
* | |||||
* @param type 实体类型类 | |||||
* @param sqlWhere sql的where语句部分,不包含from | |||||
* @param params 条件参数值 | |||||
* @param <T> 实体类型 | |||||
* @return | |||||
*/ | |||||
public <T> List<T> queryWhere(Class<T> type, String sqlWhere, Object... params) { | |||||
return findDao(type).queryWhere(sqlWhere, params); | |||||
} | |||||
/** | |||||
* 批量插入单行数据 | |||||
* | |||||
* @param entities PO对象列表 | |||||
* @param <T> 实体类型 | |||||
* @return 更新数量 | |||||
*/ | |||||
public <T> int[] batchInsertEntity(List<T> entities) { | |||||
if (entities == null || entities.isEmpty()) { | |||||
return null; | |||||
} | |||||
return findDao((Class<T>) entities.get(0).getClass()).batchInsertEntity(entities, null); | |||||
} | |||||
/** | |||||
* 批量插入单行数据 | |||||
* | |||||
* @param entities PO对象列表 | |||||
* @param fields 逗号分隔的字段列表 | |||||
* @param <T> 实体类型 | |||||
* @return 更新数量 | |||||
*/ | |||||
public <T> int[] batchInsertEntity(List<T> entities, String fields) { | |||||
if (entities == null || entities.isEmpty()) { | |||||
return null; | |||||
} | |||||
return findDao((Class<T>) entities.get(0).getClass()).batchInsertEntity(entities, fields); | |||||
} | |||||
} |
@@ -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<T> extends AbstractEntityDao<T> { | |||||
@Getter | |||||
protected JdbcEngine jdbcEngine; | |||||
public EntityDao(Class<T> 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<? super T> 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<T> entities) { | |||||
return batchInsertEntity(entities, null); | |||||
} | |||||
/** | |||||
* 批量插入单行数据 | |||||
* | |||||
* @param entities PO对象列表 | |||||
* @param fields 逗号分隔的字段列表 | |||||
* @return 更新数量 | |||||
*/ | |||||
public int[] batchInsertEntity(List<T> entities, String fields) { | |||||
StringBuilder sql = new StringBuilder(); | |||||
sql.append("insert into ").append(tableName).append("("); | |||||
List<String> listFields = adjustFields(fields, FieldType.CREATE_TIME, FieldType.LAST_TIME); | |||||
List<EntityColumn> 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<Object[]> listParams = new ArrayList<>(entities.size()); | |||||
for (T obj : entities) { | |||||
List<Object> 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<T> list = jdbcEngine.query(sb.toString(), type, id); | |||||
if (list != null && !list.isEmpty()) { | |||||
return list.get(0); | |||||
} | |||||
return null; | |||||
} | |||||
/** | |||||
* 查询对象所有数据,返回列表 | |||||
*/ | |||||
public List<T> query() { | |||||
return query(null); | |||||
} | |||||
/** | |||||
* 查询对象所有数据,返回列表 | |||||
*/ | |||||
public List<T> query(String fields) { | |||||
StringBuilder sb = new StringBuilder(); | |||||
handleSelect(sb, fields); | |||||
return jdbcEngine.query(sb.toString(), type); | |||||
} | |||||
/** | |||||
* 查询对象所有数据,返回列表 | |||||
*/ | |||||
public List<T> 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); | |||||
} | |||||
} |
@@ -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<DefaultEntity> { | |||||
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<DefaultEntity> 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<DefaultEntity> loadAll() { | |||||
EntityDao<DefaultEntity> dao = DbEngine.getInstance().findDao(tableName); | |||||
return dao.query(); | |||||
} | |||||
} |
@@ -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<ModelDatabase> { | |||||
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<ModelDatabase> loadAll() { | |||||
EntityDao<ModelDatabase> dao = DbEngine.getInstance().findDao(ModelDatabase.class); | |||||
return dao.query(); | |||||
} | |||||
public final ModelDatabase getByName(String key) { | |||||
return localGetByKey(mk, key); | |||||
} | |||||
} |
@@ -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<ModelTable> { | |||||
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<ModelTable> 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<List<ModelTable>>() { | |||||
@Override | |||||
public List<ModelTable> extractData(ResultSet rs) throws SQLException, DataAccessException { | |||||
List<ModelTable> 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<ModelTable> getDbTables(long dbId) { | |||||
return localGetListByKey(md, String.valueOf(dbId)); | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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<DataType> dataTypes; | |||||
private Map<String, DataType> mapType = null; | |||||
private void adjustMap() { | |||||
if (mapType == null) { | |||||
synchronized (DesignConfig.class) { | |||||
if (mapType == null) { | |||||
Map<String, DataType> 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<DataType> getDataTypes() { | |||||
return dataTypes; | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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 <T> 数据库值对象类型 | |||||
*/ | |||||
public abstract class AbstractEntityDao<T> { | |||||
protected ModelTable modelTable; | |||||
protected String tableName; | |||||
protected Map<String, EntityColumn> columns = new HashMap<>(); | |||||
protected Class<T> type; | |||||
protected CacheManager cacheManager = SpringUtil.getBean(CacheManager.class); | |||||
// public AbstractEntityDao(String tableName) { | |||||
// this.tableName = tableName; | |||||
// } | |||||
/** | |||||
* 通过值对象类型构造值对象数据库访问 | |||||
* | |||||
* @param type 值对象类型 | |||||
*/ | |||||
public AbstractEntityDao(Class<T> type) { | |||||
this.type = type; | |||||
// type.isAnnotationPresent(Table.class); | |||||
SwTable table = type.getAnnotation(SwTable.class); | |||||
Class<? super T> 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<T>)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<String> adjustFields(String fields, FieldType... types) { | |||||
if (StringUtils.isEmpty(fields)) return null; | |||||
String[] fieldNames = fields.toLowerCase().split(","); | |||||
List<String> 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<Object> result; | |||||
List<String> 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<String> listFields = adjustFields(fields, FieldType.LAST_TIME); | |||||
updateTime(obj, FieldType.LAST_TIME); | |||||
sql.append("update ").append(tableName).append(" set "); | |||||
List<Object> 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); | |||||
} | |||||
} | |||||
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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<String, Object> data = new HashMap<>(); | |||||
public Map<String, Object> getData() { | |||||
return data; | |||||
} | |||||
public void setData(Map<String, Object> 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); | |||||
} | |||||
} | |||||
} |
@@ -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<String, TableCheckInfo> getTables() { | |||||
final Map<String, TableCheckInfo> tables = new TreeMap<>(); | |||||
dbEngine.doConn(new Function<Connection, Object>() { | |||||
@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<String> 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<String, List<ColumnCheckInfo>> getColumnInfo(final Map<String, TableCheckInfo> dbTables) { | |||||
// if there are no tableNames, don't even try to get the columns | |||||
final Map<String, List<ColumnCheckInfo>> colInfo = new HashMap<>(); | |||||
if (dbTables.size() == 0) { | |||||
return colInfo; | |||||
} | |||||
dbEngine.doConn(new Function<Connection, Object>() { | |||||
@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<String> 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<ColumnCheckInfo> 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<String, Map<String, Set<String>>> getIndexInfo(final Collection<TableCheckInfo> tableNames, final boolean include_nounique) { | |||||
final Map<String, Map<String, Set<String>>> 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<String, Set<String>> tableIndexList = indexInfo.computeIfAbsent(tableName, k -> new HashMap<>()); | |||||
Set<String> 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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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<String, TableCheckInfo> getTables(); | |||||
Map<String, List<ColumnCheckInfo>> getColumnInfo(Map<String, TableCheckInfo> dbTables); | |||||
Map<String, Map<String, Set<String>>> getIndexInfo(Collection<TableCheckInfo> 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 | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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 <T> 返回得对象类型 | |||||
* @return 返回单个对象数据 | |||||
*/ | |||||
public <T> T queryEntity(String sql, final ResultSetExtractor<T> rowHandler) { | |||||
return jdbcTemplate.query(sql, rowHandler); | |||||
} | |||||
/** | |||||
* 查询单行数据 | |||||
* | |||||
* @param sql 查询SQL | |||||
* @param rowHandler ResultSet处理器 | |||||
* @param params SQL参数 | |||||
* @param <T> 返回得对象类型 | |||||
* @return 返回单个对象数据 | |||||
*/ | |||||
public <T> T queryEntity(String sql, final ResultSetExtractor<T> rowHandler, Object... params) { | |||||
return jdbcTemplate.query(sql, rowHandler, params); | |||||
} | |||||
/** | |||||
* 通过回调函数查询SQL,返回列表或者对象数据 | |||||
*/ | |||||
public <T> T query(String sql, ResultSetExtractor<T> rse, Object... params) { | |||||
return jdbcTemplate.query(sql, rse, params); | |||||
} | |||||
/** | |||||
* 查询SQL,返回列表数据 | |||||
*/ | |||||
public <T> List<T> query(String sql, RowMapper<T> rowMapper) { | |||||
return jdbcTemplate.query(sql, rowMapper); | |||||
} | |||||
/** | |||||
* 查询SQL,返回列表数据 | |||||
*/ | |||||
public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... params) { | |||||
return jdbcTemplate.query(sql, rowMapper, params); | |||||
} | |||||
/** | |||||
* 翻页查询 | |||||
*/ | |||||
public <T> List<T> pagedQuery(String sql, RowMapper<T> 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<String> 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<String> queryStringSet(String sql, Object... params) { | |||||
return jdbcTemplate.query(sql, (resultSet) -> { | |||||
Set<String> 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<String> 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 <T> 对象类型 | |||||
* @return JSON格式,转换为对象返回 | |||||
*/ | |||||
public <T> T queryJson(String sql, Class<T> clazz, Object... params) { | |||||
List<String> 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<Long> list = jdbcTemplate.query(sql, (resultSet, i) -> resultSet.getLong(1), params); | |||||
if (list != null && !list.isEmpty()) { | |||||
return list.get(0); | |||||
} | |||||
return null; | |||||
} | |||||
/** | |||||
* 查询List<Long>对象 | |||||
* | |||||
* @param sql 查询SQL | |||||
* @param params 查询SQL参数 | |||||
* @return List<Long>对象 | |||||
*/ | |||||
public List<Long> queryLongList(String sql, Object... params) { | |||||
return jdbcTemplate.query(sql, (resultSet, i) -> resultSet.getLong(1), params); | |||||
} | |||||
/** | |||||
* 查询Set<Long>对象 | |||||
* | |||||
* @param sql 查询SQL | |||||
* @param params 查询SQL参数 | |||||
* @return Set<Long>对象 | |||||
*/ | |||||
public Set<Long> queryLongSet(String sql, Object... params) { | |||||
return jdbcTemplate.query(sql, (resultSet) -> { | |||||
Set<Long> 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<Integer> 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<Object[]> 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<Boolean> extractor) { | |||||
try (JdbcTrans jdbcTrans = openTrans()) { | |||||
boolean result = extractor.get(); | |||||
if (result) { | |||||
jdbcTrans.commit(); | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
/** | |||||
* 获取原始数据库连接执行命令 | |||||
* | |||||
* @return 数据库连接 | |||||
*/ | |||||
public <T> T doConn(Function<Connection, T> 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> T queryEntity(String sql, Class<T> type) { | |||||
List<T> list = jdbcTemplate.query(sql, createRowMapper(type)); | |||||
if (list != null && !list.isEmpty()) { | |||||
return list.get(0); | |||||
} | |||||
return null; | |||||
} | |||||
/** | |||||
* 查询单行数据,返回bean | |||||
*/ | |||||
public <T> T queryEntity(String sql, Class<T> type, Object... params) { | |||||
List<T> list = query(sql, createRowMapper(type), params); | |||||
if (list != null && !list.isEmpty()) { | |||||
return list.get(0); | |||||
} | |||||
return null; | |||||
} | |||||
/** | |||||
* 通过回调函数查询SQL,返回列表或者对象数据 | |||||
*/ | |||||
public <T> T query(String sql, ResultSetExtractor<T> rse) { | |||||
return jdbcTemplate.query(sql, rse); | |||||
} | |||||
/** | |||||
* 查询SQL,返回列表数据 | |||||
*/ | |||||
public <T> List<T> query(String sql, Class<T> type) { | |||||
return jdbcTemplate.query(sql, createRowMapper(type)); | |||||
} | |||||
/** | |||||
* 查询SQL,返回列表数据 | |||||
*/ | |||||
public <T> List<T> query(String sql, Class<T> type, Object... params) { | |||||
return jdbcTemplate.query(sql, createRowMapper(type), params); | |||||
} | |||||
/** | |||||
* 翻页查询 | |||||
*/ | |||||
public <T> List<T> pagedQuery(String sql, Class<T> type, int start, int limit) { | |||||
return jdbcTemplate.query(sql + " LIMIT " + start + "," + limit, createRowMapper(type)); | |||||
} | |||||
private <T> RowMapper<T> createRowMapper(Class<T> type) { | |||||
RowMapper<T> 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 <T> List<T> pagedQuery(String sql, Class<T> type, int start, int limit, Object... params) { | |||||
return jdbcTemplate.query(sql + " LIMIT " + start + "," + limit, createRowMapper(type), params); | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
} |
@@ -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<T> implements RowMapper<T> { | |||||
private Class<T> mappedClass; | |||||
public MapPropertyRowMapper(Class<T> 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; | |||||
} | |||||
} |
@@ -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<T> implements RowMapper<T> { | |||||
public SwMapPropertyRowMapper(Class<T> 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(); | |||||
} | |||||
} |
@@ -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<AbstractSelectSqlBuilder> { | |||||
private List<String> orderBy; | |||||
protected void makeFields(StringBuilder sb) { | |||||
} | |||||
protected Object[] makeParams(StringBuilder sb) { | |||||
makeFields(sb); | |||||
if (wheres != null) { | |||||
List<Object> 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 <T> List<T> query(DbEngine dbEngine, Class<T> clazz) { | |||||
return exec((sql, params) -> dbEngine.query(sql, clazz, params)); | |||||
} | |||||
public <T> T queryEntity(DbEngine dbEngine, Class<T> 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> T exec(BiFunction<String, Object[], T> execute) { | |||||
StringBuilder sb = new StringBuilder("select "); | |||||
Object[] params = makeParams(sb); | |||||
return execute.apply(sb.toString(), params); | |||||
} | |||||
public <T> List<T> pagedQuery(DbEngine dbEngine, Class<T> 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; | |||||
} | |||||
} |
@@ -0,0 +1,20 @@ | |||||
package cc.smtweb.framework.core.db.sqlbuilder; | |||||
import cc.smtweb.framework.core.db.jdbc.JdbcEngine; | |||||
public abstract class AbstractUpdateSqlBuilder<T extends AbstractUpdateSqlBuilder> extends SqlBuilder<T> { | |||||
// 无效的更新值,用以占位表示不组装值到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<String, Object> map) { | |||||
for (SqlFieldValue field: fields) { | |||||
map.put(field.getName(), field.getValue()); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
package cc.smtweb.framework.core.db.sqlbuilder; | |||||
import cc.smtweb.framework.core.db.jdbc.JdbcEngine; | |||||
public class DeleteSqlBuilder extends AbstractUpdateSqlBuilder<DeleteSqlBuilder> { | |||||
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); | |||||
} | |||||
} |
@@ -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> T exec(BiFunction<String, Object[], T> execute) { | |||||
StringBuilder sb = new StringBuilder(sql); | |||||
Object[] params = makeParams(sb); | |||||
return execute.apply(sb.toString(), params); | |||||
} | |||||
} |
@@ -0,0 +1,38 @@ | |||||
package cc.smtweb.framework.core.db.sqlbuilder; | |||||
import cc.smtweb.framework.core.db.jdbc.JdbcEngine; | |||||
public class InsertSqlBuilder extends AbstractUpdateSqlBuilder<InsertSqlBuilder> { | |||||
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); | |||||
} | |||||
} |
@@ -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<SqlJoinTable> joinTables; | |||||
SelectSqlBuilder(String tableName) { | |||||
this.tableName = tableName; | |||||
} | |||||
// @Override | |||||
// public <T> List<T> query(DbEngine dbEngine, Class<T> 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> T exec(BiFunction<String, Object[], T> 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; | |||||
} | |||||
} |
@@ -0,0 +1,84 @@ | |||||
package cc.smtweb.framework.core.db.sqlbuilder; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
/** | |||||
* SQL语句建造器 | |||||
* @author admin | |||||
* @param <T> 建造器类型 | |||||
*/ | |||||
public abstract class SqlBuilder<T extends SqlBuilder> { | |||||
protected List<SqlFieldValue> fields = new ArrayList<>(); | |||||
protected List<SqlWhereValue> 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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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<SqlJoinField> 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 "; | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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<UpdateSqlBuilder> { | |||||
private String tableName; | |||||
UpdateSqlBuilder(String tableName) { | |||||
this.tableName = tableName; | |||||
} | |||||
@Override | |||||
public int update(JdbcEngine dbEngine) { | |||||
int fieldSize = fields.size() + wheres.size(); | |||||
List<Object> 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()); | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
package cc.smtweb.framework.core.db.vo; | |||||
import lombok.Data; | |||||
@Data | |||||
public class KeyValueVO { | |||||
private String key; | |||||
private String value; | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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; | |||||
} |
@@ -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; | |||||
} |
@@ -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; | |||||
} |
@@ -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; | |||||
} |
@@ -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; | |||||
} |
@@ -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<ModelField> fields = new ArrayList<>(); | |||||
private List<ModelIndex> indexes = new ArrayList<>(); | |||||
private List<ModelCache> 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(); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} |
@@ -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 | |||||
} |
@@ -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 | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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: <JDK1.8> | |||||
* | |||||
* @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"); | |||||
} |