let persons = [
{id: 1, name: "Rick", age: 19},
{id: 2, name: "Ashley", age: 19},
{id: 3, name: "Jerry", age: 22},
{id: 4, name: "Lucy", age: 25},
]
// for
const length = persons.length
for (let index = 0; index < length; index++) {
if (index === 2) {
return; // 可以中断遍历
}
p = persons[index]
console.log(p.id, ":" ,p.name, index)
}
console.log('-----------')
// forEach
persons.forEach((p, index) => {
if (index === 2) {
return; // 不可以 中断遍历,这地方也不能使用 `break`。这个一个函数,函数在循环中
}
console.log(p.id, ":" ,p.name, index)
})
console.log('-----------')
// for...in
for (index in persons) {
if (index === "2") {
return; // 可以中断遍历
}
p = persons[index]
console.log(p.id, ":" ,p.name, index)
}
// for...of
console.log('-----------')
for (p of persons) {
if (p.id ===3 ) {
return; // 可以中断遍历
}
console.log(p.id, ":" ,p.name)
}
类继承在typescript编译后prototype的实现方法
extends.ts
class E {
constructor(private name: string, private age: number) {
}
sayHello() {}
}
class F extends E {
constructor(name: string, age: number, private score: number) {
super(name, age)
}
sayHello() {}
sayGoodbye(){}
}
let f = new F('Rick.Xu', 33, 100)
let e = new E('Ashley', 23);
extends.js
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
// test by Rick
console.log(__.prototype == b.prototype) // true
console.log(d.prototype instanceof __) // true
var a = new __()
a.sayHello()
// a.sayGoodbye() // a.sayGoodbye is not a function
// test by Rick end
};
})();
var E = /** @class */ (function () {
function E(name, age) {
this.name = name;
this.age = age;
}
E.prototype.sayHello = function () { };
return E;
}());
var F = /** @class */ (function (_super) {
__extends(F, _super);
function F(name, age, score) {
var _this = _super.call(this, name, age) || this;
_this.score = score;
return _this;
}
F.prototype.sayHello = function () { };
F.prototype.sayGoodbye = function () { };
return F;
}(E));
var f = new F('Rick.Xu', 33, 100);
var e = new E('Ashley', 23);
typescript 编译后成 ES3后,原型链中加了一个中间对象 __ 。子类的原型对象是 __,__的原型对象是父类的原型对象。和下面的方法等效:
d.prototype = Object.create(b.prototype);
Springboot集成Https双向验证
目的
如果现在当前系统需要开放一个接口 /say-hello 给第三方应用调用。对于认证可以有两种方式:
- 提供username/password认证后返回token,当访问接口时携带token访问
- 接口是通过客户端证书来分配权限的,把客户端的cert导入到服务端,客户端通过配置认证才能访问
本文章将讨论「如何使用客户端证书」访问接口。
概念
自签名证书
自签名证书是由不受信的CA机构颁发的数字证书,也就是自己签发的证书。与受信任的CA签发的传统数字证书不同,自签名证书是由一些公司或软件开发商创建、颁发和签名的。虽然自签名证书使用的是与X.509证书相同的加密密钥对架构,但是却缺少受信任第三方(如Sectigo)的验证。
创建自签名SSL证书
1、生成私钥
要创建SSL证书,需要私钥和证书签名请求(CSR)。您可以使用一些生成工具或向CA申请生成私钥,私钥是使用RSA和ECC等算法生成的加密密钥。生成RSA私钥的代码示例:openssl genrsa -aes256 -out servername.pass.key 4096,随后该命令会提示您输入密码。
2、生成CSR
私钥生成后,您的私钥文件现在将作为 servername.key 保存在您的当前目录中,并将用于生成CSR。自签名证书的CSR的代码示例:openssl req -nodes -new -key servername.key -out servername.csr。然后需要输入几条信息,包括组织、组织单位、国家、地区、城市和通用名称。通用名称即域名或IP地址。
输入此信息后,servername.csr 文件将位于当前目录中,其中包含 servername.key 私钥文件。
3、颁发证书
最后,使用server.key(私钥文件)和server.csr 文件生成新证书(.crt)。以下是生成新证书的命令示例:openssl x509 -req -sha256 -days 365 -in servername.csr -signkey servername.key -out servername.crt。最后,在您的当前目录中找到servername.crt文件即可。
参考链接:https://segmentfault.com/a/1190000040834174
SSL证书格式
- .DER .CER,文件是二进制格式,只保存证书,不保存私钥。
- .PEM,一般是文本格式,可保存证书,可保存私钥。
- .CRT,可以是二进制格式,可以是文本格式,与 .DER 格式相同,不保存私钥。
- .PFX .P12,二进制格式,同时包含证书和私钥,一般有密码保护。
- .JKS,二进制格式,同时包含证书和私钥,一般有密码保护。
参考链接:
https://blog.csdn.net/farrellcn/article/details/119779348
对称加密与非对称加密
- 对称加密(Symmetric Cryptography)
对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)。 - 非对称加密(Asymmetric Cryptography)
非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥(public key)和私钥(private key)。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。
常用的命令
- OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。
- keytool 是个密钥和证书管理工具。它使用户能够管理自己的公钥/私钥对及相关证书,用于(通过数字签名)自我认证(用户向别的用户/服务认证自己)或数据完整性以及认证服务。它还允许用户储存他们的通信对等者的公钥(以证书形式)。
- JKS和PKCS#12:都是比较常用的两种密钥库格式/标准。对于前者,搞Java开发,尤其是接触过HTTPS平台的朋友,并不陌生。JKS文件(通常为.jks或.keystore,扩展名无关)可以通过Java原生工具——KeyTool生成;而后者PKCS#12文件(通常为.p12或.pfx,意味个人信息交换文件),则是通过更为常用的OpenSSL工具产生。
当然,这两者之间是可以通过导入/导出的方式进行转换的!当然,这种转换需要通过KeyTool工具进行!
生成私钥private.key
openssl genrsa -out private.key 2048
生成用于申请请求的证书文件csr,一般会将该文件发送给CA机构进行认证,本例使用自签名证书request.csr
openssl req -new -key private.key -out request.csr
自签名证书root.crt
openssl x509 -req -days 365 -in request.csr -signkey private.key -out root.crt
查看证书信息
openssl x509 -noout -text -in root.crt
openssl生成p12文件cert.p12
openssl pkcs12 -export -out cert.p12 -inkey private.key -in root.crt
p12文件转换为keystore(jks)文件my.keystore
keytool -importkeystore -srckeystore cert.p12 -destkeystore my.keystore -deststoretype pkcs12
keytool生成p12文件ssl-key.p12
keytool -genkeypair -alias serverssl -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore ssl-key.p12 -validity 3650
生成keystore(jks)文件
keytool -genkeypair -dname "cn=clientAuth_vmi_th, ou=IS, o=SGM, c=CN" -alias serverssl -keypass Pass1234 -keystore serverssl.jks -validity 3600 -keyalg RSA -keysize 2048 -sigalg SHA256WithRSA
查看密钥库
keytool -list -rfc --keystore clientAuth_vmi_th.jks -storepass Pass1234
密钥库导出别名clientAuthCert证书clientAuthCert.cer
keytool -export -file clientAuthCert.cer -keystore clientAuthCert.jks -storepass Pass1234 -alias clientAuthCert
keytool -export -file clientAuthCert.cer -keystore clientAuthCert.p12 -storepass Pass1234 -alias clientAuthCert
导入证书到密钥库
keytool -import -alias localhost -file localhost.cer -keystore client.jks
参考链接:
https://www.cnblogs.com/qq931399960/p/11889349.html
http://t.zoukankan.com/zwh0910-p-15214672.html
https://www.cnblogs.com/kabi/p/6232966.html
https://www.jianshu.com/p/e83150254ef8
https://zhuanlan.zhihu.com/p/406815419
示例
后端代码SpringBoot
application.yml
server:
# p12密钥匙库:keytool -genkeypair -alias serverssl -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore ssl-key.p12 -validity 3650
# ssl:
# key-store: classpath:ssl-key.p12
# key-store-password: Pass1234
# keyStoreType: PKCS12
# keyAlias: serverssl
# client-auth: need
# JKS密钥匙库:keytool -genkeypair -dname "cn=clientAuth_vmi_th, ou=IS, o=SGM, c=CN" -alias clientAuthCert -keypass Pass1234 -keystore clientAuthCert.jks -storepass Pass1234 -validity 3600 -keyalg RSA -keysize 2048 -sigalg SHA256WithRSA
ssl:
key-store: classpath:clientAuth_vmi_th.jks
key-store-password: Pass1234
keyStoreType: JKS
keyAlias: clientAuthCert
key-password: Pass1234
client-auth: need # 需要客户端认证
port: 8443
SslApplication.java
@SpringBootApplication
@RestController
public class SslApplication {
public static void main(String[] args) {
SpringApplication.run(SslApplication.class, args);
}
@GetMapping("say-hello")
public String sayHello() {
return "hello";
}
}
ServerConfig.java http跳转https
@Configuration
public class ServerConfig {
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(getHttpConnector());
return tomcat;
}
private Connector getHttpConnector() {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}
}
客户端请求
Postman请求
- 生成公钥
public.key
keytool -list -rfc --keystore clientAuth_vmi_th.jks | openssl x509 -inform pem –pubkey
显示的内容拷贝到文件 public.key
- 生成私钥
private.key
keytool -v -importkeystore -srckeystore clientAuth_vmi_th.jks -srcstoretype jks -srcstorepass Pass1234 -destkeystore clientAuth_vmi_th.pfx -deststoretype pkcs12 -deststorepass Pass1234 -destkeypass Pass1234
openssl pkcs12 -in clientAuth_vmi_th.pfx -nocerts -nodes -out private.key
- 添加证书

Java 代码OkHttp请求
SSLTest.java
class SSLTest {
private static final String KEYSTOREPASS = "Pass1234";
private static final String KEYPASS = "Pass1234";
static KeyStore readStore() throws Exception {
try (InputStream keyStoreStream = new FileInputStream("/Users/rick/jkxyx205/ca/clientAuth_vmi_th.jks")) {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(keyStoreStream, KEYSTOREPASS.toCharArray());
return keyStore;
}
}
static SSLSocketFactory getSSLSocketFactory() {
SSLSocketFactory factory = null;
try {
//初始化SSLContext
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(readStore(), KEYPASS.toCharArray())
.build();
factory = sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(SSLHelper2.getSSLSocketFactory())
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.connectTimeout(6, TimeUnit.SECONDS)
.hostnameVerifier(new NoopHostnameVerifier())
.build();
Request request = new Request.Builder()
.url("https://test.com:8443/say-hello")
.get()
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string()); // hello
}
}
参考链接:
https://www.cnblogs.com/youngdeng/p/12868717.html
认证工具类
CertificateCoder.java
public class CertificateCoder {
/**
* Java密钥库(Java Key Store,JKS)KEY_STORE
*/
public static final String KEY_STORE = "JKS";
public static final String X509 = "X.509";
/**
* 由 KeyStore获得私钥
*
* @param keyStorePath
* @param keyStorePassword
* @param alias
* @param aliasPassword
* @return
* @throws Exception
*/
private static PrivateKey getPrivateKey(String keyStorePath,
String keyStorePassword, String alias, String aliasPassword)
throws Exception {
KeyStore ks = getKeyStore(keyStorePath, keyStorePassword);
PrivateKey key = (PrivateKey) ks.getKey(alias,
aliasPassword.toCharArray());
return key;
}
/**
* 由 Certificate获得公钥
*
* @param certificatePath
* @return
* @throws Exception
*/
private static PublicKey getPublicKey(String certificatePath)
throws Exception {
Certificate certificate = getCertificate(certificatePath);
PublicKey key = certificate.getPublicKey();
return key;
}
/**
* 获得Certificate
*
* @param certificatePath
* @return
* @throws Exception
*/
private static Certificate getCertificate(String certificatePath)
throws Exception {
CertificateFactory certificateFactory = CertificateFactory
.getInstance(X509);
FileInputStream in = new FileInputStream(certificatePath);
Certificate certificate = certificateFactory.generateCertificate(in);
in.close();
return certificate;
}
/**
* 获得Certificate
*
* @param keyStorePath
* @param keyStorePassword
* @param alias
* @return
* @throws Exception
*/
private static Certificate getCertificate(String keyStorePath,
String keyStorePassword, String alias) throws Exception {
KeyStore ks = getKeyStore(keyStorePath, keyStorePassword);
Certificate certificate = ks.getCertificate(alias);
return certificate;
}
/**
* 获得KeyStore
*
* @param keyStorePath
* @param password
* @return
* @throws Exception
*/
private static KeyStore getKeyStore(String keyStorePath, String password)
throws Exception {
FileInputStream is = new FileInputStream(keyStorePath);
KeyStore ks = KeyStore.getInstance(KEY_STORE);
ks.load(is, password.toCharArray());
is.close();
return ks;
}
/**
* 私钥加密
*
* @param data
* @param keyStorePath
* @param keyStorePassword
* @param alias
* @param aliasPassword
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
String keyStorePassword, String alias, String aliasPassword)
throws Exception {
// 取得私钥
PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
alias, aliasPassword);
// 对数据加密
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 私钥解密
*
* @param data
* @param keyStorePath
* @param alias
* @param keyStorePassword
* @param aliasPassword
* @return
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
String alias, String keyStorePassword, String aliasPassword)
throws Exception {
// 取得私钥
PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
alias, aliasPassword);
// 对数据加密
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公钥加密
*
* @param data
* @param keyStorePath
* @param alias
* @param keyStorePassword
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String keyStorePath,
String alias, String keyStorePassword)
throws Exception {
// 取得公钥
PublicKey publicKey = getCertificate(keyStorePath, keyStorePassword, alias).getPublicKey();
// 对数据加密
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 公钥加密
*
* @param data
* @param certificatePath
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String certificatePath)
throws Exception {
// 取得公钥
PublicKey publicKey = getPublicKey(certificatePath);
// 对数据加密
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 公钥解密
*
* @param data
* @param certificatePath
* @return
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
throws Exception {
// 取得公钥
PublicKey publicKey = getPublicKey(certificatePath);
// 对数据加密
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 验证Certificate
*
* @param certificatePath
* @return
*/
public static boolean verifyCertificate(String certificatePath) {
return verifyCertificate(new Date(), certificatePath);
}
/**
* 验证Certificate是否过期或无效
*
* @param date
* @param certificatePath
* @return
*/
public static boolean verifyCertificate(Date date, String certificatePath) {
boolean status = true;
try {
// 取得证书
Certificate certificate = getCertificate(certificatePath);
// 验证证书是否过期或无效
status = verifyCertificate(date, certificate);
} catch (Exception e) {
status = false;
}
return status;
}
/**
* 验证证书是否过期或无效
*
* @param date
* @param certificate
* @return
*/
private static boolean verifyCertificate(Date date, Certificate certificate) {
boolean status = true;
try {
X509Certificate x509Certificate = (X509Certificate) certificate;
x509Certificate.checkValidity(date);
} catch (Exception e) {
status = false;
}
return status;
}
/**
* 签名
*
* @param keyStorePath
* @param alias
* @param keyStorePassword
* @param aliasPassword
* @return
* @throws Exception
*/
public static byte[] sign(byte[] sign, String keyStorePath, String alias,
String keyStorePassword, String aliasPassword) throws Exception {
// 获得证书
X509Certificate x509Certificate = (X509Certificate) getCertificate(
keyStorePath, keyStorePassword, alias);
// 取得私钥
PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
alias, aliasPassword);
// 构建签名
Signature signature = Signature.getInstance(x509Certificate
.getSigAlgName());
signature.initSign(privateKey);
signature.update(sign);
return signature.sign();
}
/**
* 验证签名
*
* @param data
* @param sign
* @param certificatePath
* @return
* @throws Exception
*/
public static boolean verify(byte[] data, byte[] sign,
String certificatePath) throws Exception {
// 获得证书
X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);
// 获得公钥
PublicKey publicKey = x509Certificate.getPublicKey();
// 构建签名
Signature signature = Signature.getInstance(x509Certificate
.getSigAlgName());
signature.initVerify(publicKey);
signature.update(data);
return signature.verify(sign);
}
/**
* 验证Certificate
*
* @param keyStorePath
* @param keyStorePassword
* @param alias
* @return
*/
public static boolean verifyCertificate(Date date, String keyStorePath,
String keyStorePassword, String alias) {
boolean status = true;
try {
Certificate certificate = getCertificate(keyStorePath,
keyStorePassword, alias);
status = verifyCertificate(date, certificate);
} catch (Exception e) {
status = false;
}
return status;
}
/**
* 验证Certificate
*
* @param keyStorePath
* @param keyStorePassword
* @param alias
* @return
*/
public static boolean verifyCertificate(String keyStorePath,
String keyStorePassword, String alias) {
return verifyCertificate(new Date(), keyStorePath, keyStorePassword,
alias);
}
}
参考链接:
https://xhope.top/?p=1336
理解
- 一般情况,公钥加密,私钥解密。证书中包含公钥发送给客户端用于加密。
- 密钥库中既有公钥又有私钥
- 证书有很多格式,服务器不一样需要的格式也不一样。大体分为文本和二进制格式,是否保存私钥,是否需要密码。
其他参考链接
https://www.thomasvitale.com/https-spring-boot-ssl-certificate/
https://www.cnblogs.com/xa-xiaochen/p/15671213.html
sharp-database实例详解
POJO
实体
总共4个实体类:School.java SchoolLicense.java Student.java Teacher.java
- 「学校」和「证书」1对1外键
- 「学校」和「学生」1对多
- 「学校」和「老师」多对多
School.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table(value = "t_school", comment = "学校")
public class School extends BasePureEntity {
@Column(comment = "学校名称")
private String name;
@Column(comment = "建校日期")
private LocalDate buildDate;
@Column(comment = "学校性质 PRIVATE:私立;PUBLIC:公立")
private TypeEnum type;
@Column(comment = "每年经费预算")
private BigDecimal budget;
@Column(comment = "专业数")
private Integer score;
@Column(comment = "学校地址")
private Address address;
@Column(comment = "其他信息")
private Map<String, Object> additionalInfo;
@Column(comment = "历届领导信息")
private List<Map<String, Object>> leadershipInformationList;
@Column(comment = "历届获奖信息")
private Set<Map<String, Object>> awardsSet;
@Column(comment = "历届学校评分")
private List<Float> scoreList;
@Column(comment = "学校评价")
private Evaluate evaluate;
/**
* 「学校」和「证书」1对1外键
* 1 <==> 1
*/
@ManyToOne(value = "school_license_id", parentTable = "t_school_license", comment = "证书信息")
private SchoolLicense schoolLicense;
/**
* 「学校」和「学生」1对多
* 1 <==> N
*/
@OneToMany(subTable = "t_school_student", joinValue = "school_id")
private List<Student> studentList;
/**
* 「学校」和「老师」多对多
* N <==> N
*/
@ManyToMany(thirdPartyTable = "t_school_teacher_related", columnDefinition = "school_id", referenceTable = "t_school_teacher", referenceColumnName = "teacher_id")
private List<Teacher> teacherList;
@AllArgsConstructor
@Getter
public enum TypeEnum {
PRIVATE("私立"),
PUBLIC("公立");
@JsonValue
public String getCode() {
return this.name();
}
private final String label;
public static TypeEnum valueOfCode(String code) {
return valueOf(code);
}
}
}
SchoolLicense.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table(value = "t_school_license", comment = "学校证书")
public class SchoolLicense extends BasePureEntity {
@Column(comment = "证书编号")
private String number;
@Column(comment = "备注")
private String remark;
@OneToMany(subTable = "t_school", joinValue = "school_license_id", oneToOne = true)
private School school;
}
Student.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table(value = "t_school_student", comment = "学生")
public class Student extends BasePureEntity {
@Column(comment = "姓名")
private String name;
@Column(comment = "年级")
private Integer grade;
@Column(comment = "性别")
private SexEnum sex;
@ManyToOne(value = "school_id", parentTable = "t_school")
private School school;
@AllArgsConstructor
@Getter
public enum SexEnum {
MALE(0, "男"),
FEMALE(1, "女");
private static final Map<Integer, SexEnum> codeMap = new HashMap<>();
static {
for (SexEnum e : values()) {
codeMap.put(e.code, e);
}
}
private final int code;
private final String label;
@JsonValue
public int getCode() {
return code;
}
public static SexEnum valueOfCode(int code) {
return codeMap.get(code);
}
}
}
Teacher.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table(value = "t_school_teacher", comment = "教师")
public class Teacher extends BasePureEntity {
@Column(comment = "姓名")
private String name;
@Column(comment = "年龄")
private Integer age;
@ManyToMany(thirdPartyTable = "t_school_teacher_related", columnDefinition = "teacher_id", referenceTable = "t_school", referenceColumnName = "school_id")
private List<School> schoolList;
}
存储Model
Evaluate.java
@AllArgsConstructor
@Getter
public class Evaluate {
/**
* 评价等级
*/
private Integer grade;
/**
* 评价内容
*/
private String description;
@Override
public String toString() {
return "Evaluate{" +
"grade=" + grade +
", description='" + description + '\'' +
'}';
}
}
Evaluate.java 没有实现接口 JsonStringToObjectConverterFactory.JsonValue,所以必须自定义converter反序列号字符串。
EvaluateConverterFactory.java
@Component
public class EvaluateConverterFactory implements ConverterFactory<String, Evaluate> {
@Override
public <T extends Evaluate> Converter<String, T> getConverter(Class<T> aClass) {
return value -> {
// value => Evaluate{grade=1, description='GREAT!'}
String substring = value.substring(value.indexOf("{") + 1, value.length() - 1);
String[] values = substring.split(",\\s+");
return (T) new Evaluate(Integer.parseInt(values[0].split("=")[1]), values[1].split("=")[1]);
};
}
}
Address.java
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address implements JsonStringToObjectConverterFactory.JsonValue {
private String code;
private String detail;
}
生成SQL
运行 TableGenerator 生成SQL,参考sharp-database中的TableGenerator根据实体对象生成表
tableGenerator.createTable(School.class);
tableGenerator.createTable(Teacher.class);
tableGenerator.createTable(Student.class);
tableGenerator.createTable(SchoolLicense.class);
生成的SQL DDL
create table t_school_license
(
id bigint not null comment '主键'
primary key,
number varchar(32) null comment '证书编号',
remark varchar(32) null comment '备注',
created_by bigint null,
created_at datetime null,
updated_by bigint null,
updated_at datetime null,
is_deleted bit null
)
comment '学校证书';
create table t_school
(
id bigint not null comment '主键'
primary key,
name varchar(32) null comment '学校名称',
build_date date null comment '建校日期',
type varchar(16) null comment '学校性质 PRIVATE:私立;PUBLIC:公立',
budget decimal(10,4) null comment '每年经费预算',
score int null comment '专业数',
address json null comment '学校地址',
additional_info json null comment '其他信息',
leadership_information_list json null comment '历届领导信息',
awards_set varchar(32) null comment '历届获奖信息',
score_list json null comment '历届学校评分',
evaluate varchar(128) null comment '学校评价',
school_license_id bigint null comment '证书信息',
created_by bigint null,
created_at datetime null,
updated_by bigint null,
updated_at datetime null,
is_deleted bit null,
constraint t_school_t_school_license_id_fk
foreign key (school_license_id) references t_school_license (id)
)
comment '学校';
create table t_school_student
(
id bigint not null comment '主键'
primary key,
name varchar(32) null comment '姓名',
grade int null comment '年级',
sex varchar(16) null comment '性别',
school_id bigint null,
created_by bigint null,
created_at datetime null,
updated_by bigint null,
updated_at datetime null,
is_deleted bit null,
constraint t_school_student_t_school_id_fk
foreign key (school_id) references t_school (id)
)
comment '学生';
create table t_school_teacher
(
id bigint not null comment '主键'
primary key,
name varchar(32) null comment '姓名',
age int null comment '年龄',
created_by bigint null,
created_at datetime null,
updated_by bigint null,
updated_at datetime null,
is_deleted bit null
)
comment '教师';
create table t_school_teacher_related
(
school_id bigint not null,
teacher_id bigint not null,
is_deleted bit default b'0' not null,
constraint t_school_teacher_related_pk
unique (school_id, teacher_id),
constraint t_school_teacher_related_t_school_id_fk
foreign key (school_id) references t_school (id),
constraint t_school_teacher_related_t_school_teacher_id_fk
foreign key (teacher_id) references t_school_teacher (id)
);

生成DAO
SchoolDAO
@Repository
public class SchoolDAO extends BaseDAOImpl<School> {
}
SchoolLicenseDAO
@Repository
public class SchoolLicenseDAO extends BaseDAOImpl<SchoolLicense> {
}
StudentDAO
@Repository
public class StudentDAO extends BaseDAOImpl<Student> {
}
TeacherDAO
@Repository
public class TeacherDAO extends BaseDAOImpl<Teacher> {
}
测试
Junit5
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class SchoolTest {
@Autowired
private SchoolDAO schoolDAO;
@Autowired
private SchoolLicenseDAO schoolLicenseDAO;
@Autowired
private StudentDAO studentDAO;
@Autowired
private TeacherDAO teacherDAO;
@Order(0)
@Test
public void testSave() {
SchoolLicense schoolLicense = createSchoolLicense();
schoolLicenseDAO.insert(schoolLicense);
School school = createSchool(schoolLicense);
schoolDAO.insert(school);
Student student = createStudent(school);
studentDAO.insert(student);
Teacher teacher = createTeacher(Arrays.asList(school));
teacherDAO.insert(teacher);
}
@Order(1)
@Test
public void testSchoolFindById() {
Optional<School> option = schoolDAO.selectById(552173736070144000L);
School school = option.get();
checkSchool(school);
checkSchoolLicense(school.getSchoolLicense());
checkStudent(school.getStudentList().get(0));
checkTeacher(school.getTeacherList().get(0));
}
@Order(1)
@Test
public void testStudentFindById() {
Student student = studentDAO.selectById(552173736246304768L).get();
checkStudent(student);
checkSchool(student.getSchool());
}
@Order(2)
@Test
public void testTeacherFindById() {
Teacher teacher = teacherDAO.selectById(552173736518934528L).get();
checkTeacher(teacher);
checkSchool(teacher.getSchoolList().get(0));
}
@Order(3)
@Test
public void testSchoolLicenseFindById() {
SchoolLicense schoolLicense = schoolLicenseDAO.selectById(552173735432609792L).get();
checkSchoolLicense(schoolLicense);
checkSchool(schoolLicense.getSchool());
}
private School createSchool(SchoolLicense schoolLicense) {
return School.builder()
.name("清华大学")
.buildDate(LocalDate.now())
.type(School.TypeEnum.PUBLIC)
.score(100)
.budget(new BigDecimal("111323.23"))
.address(Address.builder().code("100084").detail("中华人民共和国北京市海淀区清华园").build())
.additionalInfo(Params.builder(1).pv("Teacher Count", 3641).build())
.evaluate(new Evaluate(1, "GREAT!"))
.leadershipInformationList(Arrays.asList(Params.builder(1).pv("Principal", "王希勤").build()))
.awardsSet(Sets.newHashSet(Params.builder(1).pv("c", "C9联盟").build()))
.scoreList(Arrays.asList(99.9f, 98f, 100f))
.schoolLicense(schoolLicense)
.build();
}
private Student createStudent(School school) {
return Student.builder()
.name("Rick.Xu")
.sex(Student.SexEnum.MALE)
.grade(1)
.school(school)
.build();
}
private Teacher createTeacher(List<School> schoolList) {
return Teacher.builder()
.name("姚期智")
.age(50)
.schoolList(schoolList)
.build();
}
private SchoolLicense createSchoolLicense() {
return SchoolLicense.builder()
.number("124535354C34X")
.remark("自强不息 厚德载物")
.build();
}
private void checkSchool(School school) {
assertThat(school.getName(), equalTo("清华大学"));
assertThat(school.getBuildDate(), notNullValue());
assertThat(school.getType(), equalTo(School.TypeEnum.PUBLIC));
assertThat(school.getScore(), equalTo(100));
assertThat(school.getBudget(), equalTo(new BigDecimal("111323.2300")));
assertThat(school.getAddress(), anyOf(
hasProperty("code", equalTo("100084")),
hasProperty("address", equalTo("中华人民共和国北京市海淀区清华园"))
));
assertThat(school.getAdditionalInfo(), hasKey("Teacher Count"));
assertThat(school.getEvaluate(), anyOf(
hasProperty("grade", equalTo(1)),
hasProperty("description", equalTo("GREAT!"))
));
assertThat(school.getLeadershipInformationList().get(0).get("Principal"), equalTo("王希勤"));
assertThat(school.getAwardsSet().iterator().next().get("c"), equalTo("C9联盟"));
assertThat(school.getScoreList(), hasItem(99.9f));
}
private void checkStudent(Student student) {
assertThat(student.getName(), equalTo("Rick.Xu"));
assertThat(student.getGrade(), equalTo(1));
assertThat(student.getSex(), equalTo(Student.SexEnum.MALE));
}
private void checkTeacher(Teacher teacher) {
assertThat(teacher.getName(), equalTo("姚期智"));
assertThat(teacher.getAge(), equalTo(50));
}
private void checkSchoolLicense(SchoolLicense schoolLicense) {
assertThat(schoolLicense.getNumber(), equalTo("124535354C34X"));
assertThat(schoolLicense.getRemark(), equalTo("自强不息 厚德载物"));
}
}
Postman
SchoolController.java
@RestController
@RequestMapping("schools")
@RequiredArgsConstructor
public class SchoolController {
private final SchoolDAO schoolDAO;
@PostMapping
public School save(@RequestBody School school) {
schoolDAO.insert(school);
return schoolDAO.selectById(school.getId()).get();
}
}
curl -X POST \
http://localhost:8080/schools \
-H 'Content-Type: application/json' \
-H 'Postman-Token: 468b3efe-3014-4f6c-bb28-8cd5905e309e' \
-H 'cache-control: no-cache' \
-d '{
"name": "北京大学",
"buildDate": [2022, 5, 1],
"type": "PUBLIC",
"budget": 111323.2300,
"score": 100,
"address": {
"code": "100084",
"detail": "中华人民共和国北京市海淀区清华园"
},
"additionalInfo": {
"Teacher Count": 3641
},
"leadershipInformationList": [{
"Principal": "王希勤"
}],
"awardsSet": [{
"c": "C9联盟"
}],
"scoreList": [99.9, 98.0, 100.0],
"evaluate": {
"grade": 1,
"description": "'\''GREAT!'\''"
}
}'
测试仓库
https://github.com/jkxyx205/sharp-project/tree/master/sharp-demo
sharp-database中的BaseDAOImpl实现一对一外键关联@OneToMany @ManyToOne
实体

IdCard.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table("t_person_id_card")
public class IdCard extends BasePureEntity {
private String idNum;
private String address;
/**
* 一对一
* oneToOne = true
*/
@OneToMany(subTable = "t_person", joinValue = "id_card_id", cascadeSaveOrUpdate = true, oneToOne = true, reversePropertyName = "idCard")
private Person person;
}
Person.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table("t_person")
public class Person extends BasePureEntity {
private String name;
@ManyToOne(parentTable = "t_person_id_card", value = "id_card_id", cascadeSaveOrUpdate = true)
private IdCard idCard;
}
运行 TableGenerator 生成SQL,参考sharp-database中的TableGenerator根据实体对象生成表
生成的SQL
create table t_person_id_card
(
id bigint not null comment '主键'
primary key,
id_num varchar(32) null,
address varchar(32) null,
created_by bigint null,
created_at datetime null,
updated_by bigint null,
updated_at datetime null,
is_deleted bit null
);
create table t_person
(
id bigint not null comment '主键'
primary key,
name varchar(32) null,
id_card_id bigint null,
created_by bigint null,
created_at datetime null,
updated_by bigint null,
updated_at datetime null,
is_deleted bit null
);
生成DAO
IdCardDAO
@Repository
public class IdCardDAO extends BaseDAOImpl<IdCard> {
}
PersonDAO
@Repository
public class PersonDAO extends BaseDAOImpl<Person> {
}
测试
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class PersonTest {
@Autowired
private PersonDAO personDAO;
@Autowired
private IdCardDAO idCardDAO;
@Test
@Order(0)
public void savePerson() {
Person person = Person.builder()
.name("Rick")
.idCard(IdCard.builder().idNum("32128787988762").address("江苏").build())
.build();
personDAO.insert(person);
}
@Test
@Order(0)
public void saveCardId() {
Person person = Person.builder()
.name("Rick")
.build();
IdCard idCard = IdCard.builder().idNum("32128787988762").address("陕西")
.person(person)
.build();
idCardDAO.insert(idCard);
}
@Order(1)
@Test
public void findPersonById() {
Person person = personDAO.selectById(552098712424472576L).get();
assertThat(person.getName()).isEqualTo("Rick");
assertThat(person.getIdCard().getIdNum()).isEqualTo("32128787988762");
}
@Order(2)
@Test
public void findIdCardById() {
final IdCard idCard = idCardDAO.selectById(552098712365752320L).get();
assertThat(idCard.getPerson().getName()).isEqualTo("Rick");
assertThat(idCard.getIdNum()).isEqualTo("32128787988762");
}
}