diff --git a/README.md b/README.md index d856dff..4bc163e 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,20 @@ # JDevKit -JDevKit is a Java Development Kit that offers a set of convenient tools for writing code efficiently. - -## Modules - -> For more information, please visit the README file of each module. - -### `devkit-core` _[Learn more](https://github.com/CodeCraftersCN/jdevkit/devkit-core/README.md)_ - -The core module for `JDevKit`, by now, this module contains the commonly used classes of the whole `dev-kit`. - -### `devkit-utils` _[Learn more](https://github.com/CodeCraftersCN/jdevkit/devkit-utils/README.md)_ - -A collection of common utility classes to simplify Java development. It includes tools for Base64 encoding/decoding of strings, reducing if-else code blocks using Lambda expressions, converting between maps and arbitrary objects, high-precision chained mathematical calculations, and string hashing or message digest calculations. - -### `guid` _[Learn more](https://github.com/CodeCraftersCN/jdevkit/guid/README.md)_ - -A module for generating globally unique IDs. It includes a facade interface and an implementation of GUID generation using the Snowflake algorithm. More globally unique ID generation modes will be added in the future. - -### `WebCal` _[Learn more](https://github.com/CodeCraftersCN/jdevkit/webcal/README.md)_ - -The module `webcal` is a Java library that facilitates the generation and resolution of iCalendar content for web-based calendar applications. It provides a flexible and easy-to-use API for creating web calendars with customisable settings and events. - -With the `webcal` module, developers can easily integrate calendar functionality into web applications, enabling users to view, add, and manage events in a structured and standardized format. It is designed to simplify calendar-related tasks and enhance the overall user experience when dealing with calendar data on the web. - -Please note that the `webcal` module adheres to the iCalendar standard specified in RFC 5545, ensuring compatibility with other calendar applications that support this format. - -### `simple-jwt-facade` _[Learn more](https://github.com/CodeCraftersCN/jdevkit/simple-jwt-facade/README.md)_ - -A facade for Simple JWT (JSON Web Token) implementations in Java. This module provides a unified interface to work with JWTs regardless of the underlying implementation. - -### `simple-jwt-authzero` _[Learn more](https://github.com/CodeCraftersCN/jdevkit/simple-jwt-authzero/README.md)_ - -A Simple JWT implementation using the com.auth0:java-jwt library. - -### `simple-jwt-jjwt` _[Learn more](https://github.com/CodeCraftersCN/jdevkit/simple-jwt-jjwt/README.md)_ +![Static Badge](https://img.shields.io/badge/tag-v2.0.0-orange) +![Static Badge](https://img.shields.io/badge/maven_central-v2.0.0-orange) +![Static Badge](https://img.shields.io/badge/licence-Apache_2.0-green) +![Static Badge](https://img.shields.io/badge/JDK-%E2%89%A517-blue) -A Simple JWT implementation using the `io.jsonwebtoken:jjwt-api` library. -### `simple-jwt-spring-boot-starter` _[Learn more](https://github.com/CodeCraftersCN/jdevkit/simple-jwt-spring-boot-starter/README.md)_ - -A Spring Boot autoconfiguration wrapper for the simple-jwt module, making it easier to integrate JWT functionality into Spring Boot applications. +JDevKit is a Java Development Kit that offers a set of convenient tools for writing code efficiently. ## Installation and Usage -### Before Installation - -For **Chinese Mainland** users, it is suggested to use maven packages services provided by coding.net. - -You could follow the following steps to configure. - -#### For Maven - -You could add the following codes to register Coding Nexus to your Maven. - -```xml - - codecrafters-coding-nexus - codecrafters-coding-nexus - https://codecrafters-maven.pkg.coding.net/repository/common-productions/maven-packages/ - - true - - - true - - -``` - -#### For Gradle - -You could add a new repository to Gradle repositories closure. - -```groovy -maven { url 'https://codecrafters-maven.pkg.coding.net/repository/common-productions/maven-packages/' } -``` - -```kotlin -maven(url = "https://codecrafters-maven.pkg.coding.net/repository/common-productions/maven-packages/") -``` - If you are using **Maven**, please paste the following codes to _pom.xml_ in your project. ```xml - cn.org.codecrafters + com.onixbyte ${artifactId} ${version} @@ -93,11 +23,11 @@ If you are using **Maven**, please paste the following codes to _pom.xml_ in you If you are using **Gradle**, please paste the following codes to _buile.gradle\[.kts\]_ in your project. ```groovy -implementation 'cn.org.codecrafters:$artifactId:$version' +implementation 'com.onixbyte:$artifactId:$version' ``` ```kotlin -implementation("cn.org.codecrafters:$artifactId:$version") +implementation("com.onixbyte:$artifactId:$version") ``` If you want to check the available versions, please check out at our [official site](https://codecrafters.org.cn/devkit/changelog). diff --git a/devkit-core/README.md b/devkit-core/README.md index 00050c9..c38ceb3 100644 --- a/devkit-core/README.md +++ b/devkit-core/README.md @@ -2,18 +2,4 @@ ## Introduction -This module is the main part of `JDevKit`, an open-source Java class library that provides a set of convenient tools to streamline code development and enhance productivity. This module serves as the basement of other modules. - -## Prerequisites - -This whole `JDevKit` is developed by **JDK 17**, which means you have to use JDK 17 for better experience. - -## Installation - -You don't have to install this module at all, any module which is from `JDevKit` contains this `devkit-core` module. - -## Contact - -If you have any suggestions, ideas, don't hesitate contacting us via [GitHub Issues](https://github.com/CodeCraftersCN/jdevkit/issues/new) or [Discord Community](https://discord.gg/NQK9tjcBB8). - -If you face any bugs while using our library and you are able to fix any bugs in our library, we would be happy to accept pull requests from you on [GitHub](https://github.com/CodeCraftersCN/jdevkit/compare). \ No newline at end of file +This module serves as the basement of `JDevKit`. It provides some base exceptions that `JDevKit` might use. \ No newline at end of file diff --git a/devkit-utils/README.md b/devkit-utils/README.md index 540284a..6d2b02f 100644 --- a/devkit-utils/README.md +++ b/devkit-utils/README.md @@ -2,189 +2,15 @@ ## Introduction -This module is part of `JDevKit`, an open-source Java Development Kit that provides a set of convenient tools to streamline code development and enhance productivity. This module serves as the useful toolkit for the library, contains a collection of utility classes commonly used in all Java Application development. +This module provides a set of utilities to streamline Java codes. -## Prerequisites +## Features -This whole `JDevKit` is developed by **JDK 17**, which means you have to use JDK 17 for better experience. +- AES encryption and decryption; +- Base64 encode and decode; +- Boolean calculation; +- Reduce `if...else...` with **lambdas**; +- Hash calculation for strings; +- Convert Java beans to map and map to Java beans; +- Simplified range generator. -## Installation - -### If you are using `Maven` - -It is quite simple to install this module by `Maven`. The only thing you need to do is find your `pom.xml` file in the project, then find the `` node in the `` node, and add the following codes to `` node: - -```xml - - cn.org.codecrafters - devkit-utils - ${devkit-utils.version} - -``` - -And run `mvn dependency:get` in your project root folder(i.e., if your `pom.xml` is located at `/path/to/your/project/pom.xml`, then your current work folder should be `/path/to/your/project`), then `Maven` will automatically download the `jar` archive from `Maven Central Repository`. This could be **MUCH EASIER** if you are using IDE(i.e., IntelliJ IDEA), the only thing you need to do is click the refresh button of `Maven`. - -If you are restricted using the Internet, and have to make `Maven` offline, you could follow the following steps. - -1. Download the `jar` file from any place you can get and transfer the `jar` files to your work computer. -2. Move the `jar` files to your local `Maven` Repository as the path of `/path/to/maven_local_repo/cn/org/codecrafters/devkit-utils/`. - -### If you are using `Gradle` - -Add this module to your project with `Gradle` is much easier than doing so with `Maven`. - -Find `build.gradle` in the needed project, and add the following code to the `dependencies` closure in the build script: - -```groovy -implementation 'cn.org.codecrafters:devkit-utils:${devkit-utils.version}' -``` - -### If you are not using `Maven` or `Gradle` - -1. Download the `jar` file from the Internet. -2. Create a folder in your project and name it as a name you like(i.e., for me, I prefer `vendor`). -3. Put the `jar` file to the folder you just created in Step 2. -4. Add this folder to your project `classpath`. - -### Base64 Encode and Decode - -If you are trying to encode a string to Base64 string or decode a Base64 string to normal string, then you can try this: - -```java -import utils.com.onixbyte.devkit.Base64Util; - -// To reduce sample codes, let me use the simplified main method that is upcoming in Java 21 -void main(String... args) { - var aString = "The string you need to encode."; - // Encode it from original text. - var encodedString = Base64Util.encode(aString); - // Decode the encoded text. - var decodedString = Base64Util.decode(encodedString); -} -``` - -## Reduce `if...else...` - -I believe those `if...else...` blocks make you headache, and Java imported lambda since Java 8, why not try to replace those `if...else` with lambda expressions? - -```java -import utils.com.onixbyte.devkit.BranchUtil; - -void main(String... args) { - var a = 1; - var b = 2; - - if (a < b) { - // do something... - } else { - // do something different... - } - // The codes above is really annoying, have a try of the lambda version. - - BranchUtil.or(a < b) // If multiple logical expressions are combined using the OR operator. - .handle(() -> { - // do something if a < b. - }, () -> { - // do something if a > b. - }); - - BranchUtil.and(a < b) // If multiple logical expressions are combined using the AND operator - .handle(() -> { - // do something if a < b. - }, () -> { - // do something if a > b. - }); -} -``` - -What if you have to get some data from this branch? Don't worry, those **lambda supports Supplier**. Just add a return in those lambda is fine. - -## CHAINED High-precision Calculation - -If you have faced high-precision mathematical calculation in Java, you might know it is very troubled to do it in **ANY** programming languages. I'm certain you remember that `0.1 + 0.2 != 0.3` right? - -In Java, we usually do high-precision mathematical calculation with `BigDecimal` which is quite tricky when using it. - -```java -import utils.com.onixbyte.devkit.ChainedCalcUtil; - -void main(String... args) { - // If you are trying to calculate the expression of 1 * 2 / 2 - 3 + 4 - ChainedCalcUtil.startWith(1).multiply(2).divide(2).subtract(3).add(4).getInteger(); // you can also get a double by getDouble([int scale]), get a BigDecimal by getDecimal([int scale]) or get a long by getLong(). -} -``` - -If you are facing a divide calculation which has an infinite decimal expansion, then DON'T use `divide(dividend)`, use `divideWithScale(dividend, scale, [beforeOperateScale])`. - -## Hash a `String` - -As is well known, storing plain text passwords in a database is very insecure. Therefore, you need to encrypt the passwords stored in the database, or at least make it difficult for hackers to see the real password at a glance. As a result, hash calculation is often used in database password obfuscation due to its ease of use. - -This `HashUtil` supports these following hash or message digest algorithms: - -- MD2 -- MD5 -- SHA-1 -- SHA-224 -- SHA-256 -- SHA-384 -- SHA-512 - -If you want to run a hash calculation to a string, you can use the following codes: - -```java -import utils.com.onixbyte.devkit.HashUtil; - -void main(String... args) { - var plaintext = "This is a plain text"; - var hashedText = HashUtil.md2(plaintext); // if you want to use other algorithm, just change the method name such as md5, sha1, sha224 and so on. -} -``` - -Besides, if you want to use other character set to do the hash calculation, you can add the specified charset after the text to be calculated. - -```java -HashUtil.$algorithm$(String textToCalculate, Charset charset); -``` - -## Convert a Map to Any Object and Vice Versa - -A Map is a data structure that allows you to store key-value pairs. The keys can be of any type and the values can be of any type. In this case, the key is the user's name and the value is the user's profile. - -Imagine you are developing a website where users can register an account and store their profile. A profile can contain information like their name, age, address, and email address. You store the user's profile in a Map. When the user registers an account, you need to store their profile in a database. A database is a system for storing data. A database can store any type of data, including Maps. - -In order to store the Map in a database, you need to convert the Map to an Object. An Object is a generic data type that can store any type of data. - -```java -import utils.com.onixbyte.devkit.MapUtil; - -class Data { - private String name; - private Long id; - private Integer age; - - // Setters and getters here. - - // No-args constructor and all-args constructor here. -} - -void main(String... args) { - // Create a map. - var dataMap = new HashMap(); - dataMap.put("name", "Zihlu Wang"); - dataMap.put("id", 1L); - dataMap.put("age", 18); - - // Convert this map to an instance of class Data - var data = MapUtil.mapToObject(dataMap, Data.class); // Then you got an instance of Data("Zihlu Wang", 1L, 18); - - // Then you can convert this object to a map. - var anotherDataMap = MapUtil.objectToMap(data) -} -``` - -## Contact - -If you have any suggestions, ideas, don't hesitate contacting us via [GitHub Issues](https://github.com/CodeCraftersCN/jdevkit/issues/new) or [Discord Community](https://discord.gg/NQK9tjcBB8). - -If you face any bugs while using our library and you are able to fix any bugs in our library, we would be happy to accept pull requests from you on [GitHub](https://github.com/CodeCraftersCN/jdevkit/compare). diff --git a/gradle.properties b/gradle.properties index b7f925f..85f12ee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,10 +20,10 @@ javaJwtVersion=4.4.0 junitVersion=5.11.4 logbackVersion=1.5.16 slf4jVersion=2.0.16 -springVersion=6.2.1 -springBootVersion=3.4.1 +springVersion=6.2.2 +springBootVersion=3.4.2 -artefactVersion=1.8.0 +artefactVersion=2.0.0 projectUrl=https://onixbyte.com/JDevKit projectGithubUrl=https://github.com/OnixByte/JDevKit licenseName=The Apache License, Version 2.0 diff --git a/guid/README.md b/guid/README.md index 961db76..501a1f5 100644 --- a/guid/README.md +++ b/guid/README.md @@ -2,76 +2,37 @@ ## Introduction -Module `guid` is a library that provides utilities for generating and working with globally unique identifiers (GUIDs). GUIDs are globally unique across all devices and systems, making them ideal for various use cases like database record keys, distributed systems, and tracking objects. +Module `guid` serves as a guid creator for other `JDevKit` modules. You can also use this module as a guid creator standards. -The `guid` module offers a reliable and efficient GUID generator, allowing developers to create unique identifiers with low collision probability. It also provides features to customize the format of generated GUIDs, making it flexible and suitable for different applications. +We have already implemented `SnowflakeGuidCreator`, you can also implement a custom guid creations by implementing `com.onixbyte.guid.GuidCreator`. -With `guid`, developers can easily integrate globally unique identifiers into their projects, ensuring data integrity, avoiding duplicates, and simplifying the identification of objects across various systems. The module is designed to be simple to use, highly performant, and compatible with different programming languages and frameworks. +## Example usage -## Prerequisites - -This whole `JDevKit` is developed by **JDK 17**, which means you have to use JDK 17 for better experience. - -## Installation - -### If you are using `Maven` - -It is quite simple to install this module by `Maven`. The only thing you need to do is find your `pom.xml` file in the project, then find the `` node in the `` node, and add the following codes to `` node: - -```xml - - cn.org.codecrafters - devkit-utils - ${devkit-utils.version} - -``` - -And run `mvn dependency:get` in your project root folder(i.e., if your `pom.xml` is located at `/path/to/your/project/pom.xml`, then your current work folder should be `/path/to/your/project`), then `Maven` will automatically download the `jar` archive from `Maven Central Repository`. This could be **MUCH EASIER** if you are using IDE(i.e., IntelliJ IDEA), the only thing you need to do is click the refresh button of `Maven`. - -If you are restricted using the Internet, and have to make `Maven` offline, you could follow the following steps. - -1. Download the `jar` file from any place you can get and transfer the `jar` files to your work computer. -2. Move the `jar` files to your local `Maven` Repository as the path of `/path/to/maven_local_repo/cn/org/codecrafters/devkit-utils/`. - -### If you are using `Gradle` - -Add this module to your project with `Gradle` is much easier than doing so with `Maven`. - -Find `build.gradle` in the needed project, and add the following code to the `dependencies` closure in the build script: - -```groovy -implementation 'cn.org.codecrafters:devkit-utils:${devkit-utils.version}' -``` - -### If you are not using `Maven` or `Gradle` - -1. Download the `jar` file from the Internet. -2. Create a folder in your project and name it as a name you like(i.e., for me, I prefer `vendor`). -3. Put the `jar` file to the folder you just created in Step 2. -4. Add this folder to your project `classpath`. - -## Implement a `GuidCreator` - -You can build your own `GuidCreator` by implements the interface `GuidCreator`. This interface accepts a type parameter which determine the type of the GUID. - -## Generate a `guid` with Snowflake Algorithm - -If you are willing to use Snowflake algorithm to create a guid, just use `SnowflakeGuidCreator` is find. To create a guid in Snowflake algorithm, you'll need an instance of `SnowflakeGuidCreator` first. - -To create an instance of `SnowflakeGuidCreator`, you should use its constructor. +### A UUID creator ```java -var guidCreator = new SnowflakeGuidCreator(long workerId, long dataCentreId[, long startEpoch]); +GuidCreator uuidCreator = (GuidCreator) UUID::randomUUID; ``` -Then, use the instance to create a guid. +### A custom guid creator + +Assume that you need serial guid creator. ```java -long guid = guidCreator.nextId(); +@Component +public class CustomGuidCreator implementes GuidCreator { + + public final RedisTemplate serialRedisTemplate; + + @Autowired + public CustomGuidCreator(RedisTemplate serialRedisTemplate) { + this.serialRedisTemplate = serialRedisTemplate; + } + + @Override public String nextId() { + return "SOME_PREFIX" + serialRedisTemplate.opsForValue().get("some_serial_key"); + } + +} ``` -## Contact - -If you have any suggestions, ideas, don't hesitate contacting us via [GitHub Issues](https://github.com/CodeCraftersCN/jdevkit/issues/new) or [Discord Community](https://discord.gg/NQK9tjcBB8). - -If you face any bugs while using our library and you are able to fix any bugs in our library, we would be happy to accept pull requests from you on [GitHub](https://github.com/CodeCraftersCN/jdevkit/compare). \ No newline at end of file diff --git a/key-pair-loader/README.md b/key-pair-loader/README.md new file mode 100644 index 0000000..2a8186d --- /dev/null +++ b/key-pair-loader/README.md @@ -0,0 +1,64 @@ +# KeyLoader + +KeyLoader provides utility methods to load keys from pem-formatted key texts. + +## ECDSA-based algorithm + +### Generate key pair + +#### Generate private key + +Generate a private key by `genpkey` command provided by OpenSSL: + +```shell +openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec_private_key.pem +``` + +The output of this command is a file called `ec_private_key.pem` and its content looks like the +following: + +```text +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs79JlARgXEf6EDV7 ++PHQCTHEMtqIoHOy1GZ1+ynQJ6yhRANCAARkA7GRY2i4gg8qx0XViAXUP9cPw9pn +Jg1wfrQ41FaMyqVBejNYxvaLtamErF/ySimnjafMJ+VZCh34lBj6Ez8R +-----END PRIVATE KEY----- +``` + +#### Generate public key by private key + +Export public key from private key with `ec` command provided by OpenSSL: + +```shell +openssl ec -in ec_private_key.pem -pubout -out ec_public_key.pem +``` + +The output of this command is a file called `ec_public_key.pem` and its content looks like the +following: + +```text +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZAOxkWNouIIPKsdF1YgF1D/XD8Pa +ZyYNcH60ONRWjMqlQXozWMb2i7WphKxf8kopp42nzCflWQod+JQY+hM/EQ== +-----END PUBLIC KEY----- +``` + +#### Convert private key to EC formats which could be acceptable by Java + +Java's `PKCS8EncodedKeySpec` requires the private key to be in PKCS#8 format, while OpenSSL by +default generates private keys in traditional PEM format. To convert the private key, run the +following command: + +```shell +openssl pkcs8 -topk8 -inform PEM -outform PEM -in ec_private_key.pem -out ec_private_key_pkcs8.pem -nocrypt +``` + +The converted private key will look like this: + +```text +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs79JlARgXEf6EDV7 ++PHQCTHEMtqIoHOy1GZ1+ynQJ6yhRANCAARkA7GRY2i4gg8qx0XViAXUP9cPw9pn +Jg1wfrQ41FaMyqVBejNYxvaLtamErF/ySimnjafMJ+VZCh34lBj6Ez8R +-----END PRIVATE KEY----- +``` \ No newline at end of file diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java index b8fb3f1..0fa6a63 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java @@ -17,106 +17,36 @@ package com.onixbyte.security; -import com.onixbyte.security.exception.KeyLoadingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; +import java.security.PrivateKey; +import java.security.PublicKey; /** - * The {@code KeyLoader} class provides utility methods for loading ECDSA keys from PEM-formatted + * The {@code KeyLoader} class provides utility methods for loading keys pairs from PEM-formatted * key text. This class supports loading both private and public keys. *

* The utility methods in this class are useful for scenarios where ECDSA keys need to be loaded * from PEM-formatted strings for cryptographic operations. - *

- * - *

Example usage:

- *
{@code
- * String pemPrivateKey = """
- *                        -----BEGIN PRIVATE KEY-----
- *                        ...
- *                        -----END PRIVATE KEY-----""";
- * ECPrivateKey privateKey = KeyLoader.loadEcdsaPrivateKey(pemPrivateKey);
- * 
- * String pemPublicKey = """
- *                       -----BEGIN PUBLIC KEY-----
- *                       ...
- *                       -----END PUBLIC KEY-----""";
- * ECPublicKey publicKey = KeyLoader.loadEcdsaPublicKey(pemPublicKey);
- * }
* * @author zihluwang - * @version 1.6.0 + * @version 2.0.0 * @since 1.6.0 */ -public class KeyLoader { - - private final static Logger log = LoggerFactory.getLogger(KeyLoader.class); - - /** - * Private constructor prevents from being initialised. - */ - private KeyLoader() { - } +public interface KeyLoader { /** - * Load ECDSA private key from pem-formatted key text. + * Load private key from pem-formatted key text. * * @param pemKeyText pem-formatted key text * @return loaded private key - * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, - * or EC Key Factory is not loaded, or key spec is invalid */ - public static ECPrivateKey loadEcdsaPrivateKey(String pemKeyText) { - try { - var decodedKeyString = Base64.getDecoder().decode(pemKeyText); - var keySpec = new PKCS8EncodedKeySpec(decodedKeyString); - var keyFactory = KeyFactory.getInstance("EC"); - var _key = keyFactory.generatePrivate(keySpec); - if (_key instanceof ECPrivateKey privateKey) { - return privateKey; - } else { - throw new KeyLoadingException("Unable to load private key from pem-formatted key text."); - } - } catch (NoSuchAlgorithmException e) { - throw new KeyLoadingException("Cannot get EC Key Factory.", e); - } catch (InvalidKeySpecException e) { - throw new KeyLoadingException("Key spec is invalid.", e); - } - } + PrivateKey loadPrivateKey(String pemKeyText); /** - * Load ECDSA public key from pem-formatted key text. + * Load public key from pem-formatted key text. * * @param pemKeyText pem-formatted key text * @return loaded private key - * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, - * or EC Key Factory is not loaded, or key spec is invalid */ - public static ECPublicKey loadEcdsaPublicKey(String pemKeyText) { - try { - var keyBytes = Base64.getDecoder().decode(pemKeyText); - var spec = new X509EncodedKeySpec(keyBytes); - var keyFactory = KeyFactory.getInstance("EC"); - var key = keyFactory.generatePublic(spec); - if (key instanceof ECPublicKey publicKey) { - return publicKey; - } else { - throw new KeyLoadingException("Unable to load private key from pem-formatted key text."); - } - } catch (NoSuchAlgorithmException e) { - throw new KeyLoadingException("Cannot get EC Key Factory.", e); - } catch (InvalidKeySpecException e) { - throw new KeyLoadingException("Key spec is invalid.", e); - } - } + PublicKey loadPublicKey(String pemKeyText); } diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java b/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java index 853f203..3c4cabc 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java @@ -29,7 +29,8 @@ *

Example usage:

*
{@code
  * try {
- *     ECPrivateKey privateKey = KeyLoader.loadEcdsaPrivateKey(pemPrivateKey);
+ *     KeyLoader keyLoader = new EcKeyLoader();
+ *     ECPrivateKey privateKey = keyLoader.loadPrivateKey(pemPrivateKey);
  * } catch (KeyLoadingException e) {
  *     // Handle the exception
  *     e.printStackTrace();
@@ -37,7 +38,7 @@
  * }
* * @author zihluwang - * @version 1.6.0 + * @version 2.0.0 * @since 1.6.0 */ public class KeyLoadingException extends RuntimeException { diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/impl/EcKeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/impl/EcKeyLoader.java new file mode 100644 index 0000000..b94b1f7 --- /dev/null +++ b/key-pair-loader/src/main/java/com/onixbyte/security/impl/EcKeyLoader.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.security.impl; + +import com.onixbyte.security.KeyLoader; +import com.onixbyte.security.exception.KeyLoadingException; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + * Key pair loader for loading key pairs for ECDSA-based algorithms. + *

+ * + * Example usage for ECDSA: + *

{@code
+ * KeyLoader keyLoader = new EcKeyLoader();
+ * String pemPrivateKey = """
+ *                        -----BEGIN EC PRIVATE KEY-----
+ *                        ...
+ *                        -----END EC PRIVATE KEY-----""";
+ * ECPrivateKey privateKey = KeyLoader.loadEcdsaPrivateKey(pemPrivateKey);
+ *
+ * String pemPublicKey = """
+ *                       -----BEGIN EC PUBLIC KEY-----
+ *                       ...
+ *                       -----END EC PUBLIC KEY-----""";
+ * ECPublicKey publicKey = KeyLoader.loadPublicKey(pemPublicKey);
+ * }
+ * + * @author zihluwang + * @version 2.0.0 + * @since 2.0.0 + */ +public class EcKeyLoader implements KeyLoader { + + private final KeyFactory keyFactory; + + private final Base64.Decoder decoder; + + /** + * Initialise a key loader for EC-based algorithms. + */ + public EcKeyLoader() { + try { + this.keyFactory = KeyFactory.getInstance("EC"); + this.decoder = Base64.getDecoder(); + } catch (NoSuchAlgorithmException e) { + throw new KeyLoadingException(e); + } + } + + /** + * Load ECDSA private key from pem-formatted key text. + * + * @param pemKeyText pem-formatted key text + * @return loaded private key + * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, + * or EC Key Factory is not loaded, or key spec is invalid + */ + @Override + public ECPrivateKey loadPrivateKey(String pemKeyText) { + try { + // remove all unnecessary parts of the pem key text + pemKeyText = pemKeyText + .replaceAll("-----BEGIN (EC )?PRIVATE KEY-----", "") + .replaceAll("-----END (EC )?PRIVATE KEY-----", "") + .replaceAll("\n", ""); + var decodedKeyString = decoder.decode(pemKeyText); + var keySpec = new PKCS8EncodedKeySpec(decodedKeyString); + + var _key = keyFactory.generatePrivate(keySpec); + if (_key instanceof ECPrivateKey privateKey) { + return privateKey; + } else { + throw new KeyLoadingException("Unable to load private key from pem-formatted key text."); + } + } catch (InvalidKeySpecException e) { + throw new KeyLoadingException("Key spec is invalid.", e); + } + } + + /** + * Load public key from pem-formatted key text. + * + * @param pemKeyText pem-formatted key text + * @return loaded private key + * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, + * or EC Key Factory is not loaded, or key spec is invalid + */ + @Override + public ECPublicKey loadPublicKey(String pemKeyText) { + try { + // remove all unnecessary parts of the pem key text + pemKeyText = pemKeyText + .replaceAll("-----BEGIN (EC )?PUBLIC KEY-----", "") + .replaceAll("-----END (EC )?PUBLIC KEY-----", "") + .replaceAll("\n", ""); + var keyBytes = decoder.decode(pemKeyText); + var spec = new X509EncodedKeySpec(keyBytes); + var key = keyFactory.generatePublic(spec); + if (key instanceof ECPublicKey publicKey) { + return publicKey; + } else { + throw new KeyLoadingException("Unable to load public key from pem-formatted key text."); + } + } catch (InvalidKeySpecException e) { + throw new KeyLoadingException("Key spec is invalid.", e); + } + } + +} diff --git a/key-pair-loader/src/test/java/com/onixbyte/security/KeyPairLoaderTest.java b/key-pair-loader/src/test/java/com/onixbyte/security/KeyPairLoaderTest.java index edfc670..ca57f74 100644 --- a/key-pair-loader/src/test/java/com/onixbyte/security/KeyPairLoaderTest.java +++ b/key-pair-loader/src/test/java/com/onixbyte/security/KeyPairLoaderTest.java @@ -17,13 +17,29 @@ package com.onixbyte.security; +import com.onixbyte.security.impl.EcKeyLoader; import org.junit.jupiter.api.Test; public class KeyPairLoaderTest { @Test public void test() { - + var keyLoader = new EcKeyLoader(); + // The following key pair is only used for test only, and is already exposed to public. + // DO NOT USE THEM FOR PRODUCTION! + var privateKey = keyLoader.loadPrivateKey(""" + -----BEGIN PRIVATE KEY----- + MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs79JlARgXEf6EDV7 + +PHQCTHEMtqIoHOy1GZ1+ynQJ6yhRANCAARkA7GRY2i4gg8qx0XViAXUP9cPw9pn + Jg1wfrQ41FaMyqVBejNYxvaLtamErF/ySimnjafMJ+VZCh34lBj6Ez8R + -----END PRIVATE KEY----- + """); + var publicKey = keyLoader.loadPublicKey(""" + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZAOxkWNouIIPKsdF1YgF1D/XD8Pa + ZyYNcH60ONRWjMqlQXozWMb2i7WphKxf8kopp42nzCflWQod+JQY+hM/EQ== + -----END PUBLIC KEY----- + """); } } diff --git a/key-pair-loader/src/test/resources/ec_private_key.pem b/key-pair-loader/src/test/resources/ec_private_key.pem new file mode 100644 index 0000000..02dfcc8 --- /dev/null +++ b/key-pair-loader/src/test/resources/ec_private_key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs79JlARgXEf6EDV7 ++PHQCTHEMtqIoHOy1GZ1+ynQJ6yhRANCAARkA7GRY2i4gg8qx0XViAXUP9cPw9pn +Jg1wfrQ41FaMyqVBejNYxvaLtamErF/ySimnjafMJ+VZCh34lBj6Ez8R +-----END PRIVATE KEY----- diff --git a/key-pair-loader/src/test/resources/ec_private_key_pkcs8.pem b/key-pair-loader/src/test/resources/ec_private_key_pkcs8.pem new file mode 100644 index 0000000..02dfcc8 --- /dev/null +++ b/key-pair-loader/src/test/resources/ec_private_key_pkcs8.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs79JlARgXEf6EDV7 ++PHQCTHEMtqIoHOy1GZ1+ynQJ6yhRANCAARkA7GRY2i4gg8qx0XViAXUP9cPw9pn +Jg1wfrQ41FaMyqVBejNYxvaLtamErF/ySimnjafMJ+VZCh34lBj6Ez8R +-----END PRIVATE KEY----- diff --git a/key-pair-loader/src/test/resources/ec_public_key.pem b/key-pair-loader/src/test/resources/ec_public_key.pem new file mode 100644 index 0000000..ff0054e --- /dev/null +++ b/key-pair-loader/src/test/resources/ec_public_key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZAOxkWNouIIPKsdF1YgF1D/XD8Pa +ZyYNcH60ONRWjMqlQXozWMb2i7WphKxf8kopp42nzCflWQod+JQY+hM/EQ== +-----END PUBLIC KEY----- diff --git a/map-util-unsafe/README.md b/map-util-unsafe/README.md new file mode 100644 index 0000000..e5de9cb --- /dev/null +++ b/map-util-unsafe/README.md @@ -0,0 +1,5 @@ +# Map Util Unsafe + +`map-util-unsafe` provides a set of more convenient utilities for converting Java bean to Map or Map to Java bean, but which are less safe than the `MapUtil` provided in `devkit-utils`. + +This `MapUtil` is implemented with Reflect API, which might be removed in later JDKs. \ No newline at end of file diff --git a/num4j/README.md b/num4j/README.md new file mode 100644 index 0000000..7745ec1 --- /dev/null +++ b/num4j/README.md @@ -0,0 +1,3 @@ +# Num4j + +`num4j` provides some mathematical algorithms and utilities such as chained high-precision mathematical calculator and percentile statistic algorithm. \ No newline at end of file diff --git a/property-guard-spring-boot-starter/README.md b/property-guard-spring-boot-starter/README.md index 51e9e52..32cc31f 100644 --- a/property-guard-spring-boot-starter/README.md +++ b/property-guard-spring-boot-starter/README.md @@ -1,97 +1,37 @@ # Property Guard -## Introduction +`property-guard-spring-boot-starter` is a utility that can help you protect secret values in Spring Boot configurations. -This feature is designed to protect the security of configurations and data, to a certain extent, to control the flow of developers leading to the leakage of sensitive information. +## Example usage -## Prerequisites +### 1. Implementation this module -This whole `JDevKit` is developed by **JDK 17**, which means you have to use JDK 17 for better experience. Except this, this module is designed for Spring Boot framework, so you have to install Spring Boot (v3) in your application. - -## Installation - -### If you are using `Maven` - -It is quite simple to install this module by `Maven`. The only thing you need to do is find your `pom.xml` file in the project, then find the `` node in the `` node, and add the following codes to `` node: - -```xml - - cn.org.codecrafters - property-guard-spring-boot-starter - ${property-guard-spring-boot-starter.version} - -``` - -And run `mvn dependency:get` in your project root folder(i.e., if your `pom.xml` is located at `/path/to/your/project/pom.xml`, then your current work folder should be `/path/to/your/project`), then `Maven` will automatically download the `jar` archive from `Maven Central Repository`. This could be **MUCH EASIER** if you are using IDE(i.e., IntelliJ IDEA), the only thing you need to do is click the refresh button of `Maven`. - -If you are restricted using the Internet, and have to make `Maven` offline, you could follow the following steps. - -1. Download the `jar` file from any place you can get and transfer the `jar` files to your work computer. -2. Move the `jar` files to your local `Maven` Repository as the path of `/path/to/maven_local_repo/cn/org/codecrafters/property-guard-spring-boot-starter/`. - -### If you are using `Gradle` - -Add this module to your project with `Gradle` is much easier than doing so with `Maven`. - -Find `build.gradle` in the needed project, and add the following code to the `dependencies` closure in the build script: - -```groovy -implementation 'cn.org.codecrafters:property-guard-spring-boot-starter:${property-guard-spring-boot-starter.version}' -``` - -### If you are not using `Maven` or `Gradle` - -1. Download the `jar` file from the Internet. -2. Create a folder in your project and name it as a name you like(i.e., for me, I prefer `vendor`). -3. Put the `jar` file to the folder you just created in Step 2. -4. Add this folder to your project `classpath`. - -## Usage - -First, you need a 16-bit-long secret. If you don't have a good way to get a secret, you could consider using our `utils.com.onixbyte.devkit.AesUtil` or `com.onixbyte.simplejwt.SecretCreator` to create a secret. - -For example: -```java -import utils.com.onixbyte.devkit.AesUtil; -import com.onixbyte.simplejwt.SecretCreator; - -class GenerateRandomKeySample { - public static void main(String[] args) { - var secret1 = AesUtil.generateRandomSecret(); - var secret2 = SecretCreator.createSecret(16, true, true, true); - } +```kotlin +dependencies { + implementation(platform("com.onixbyte:devkit-bom:$devKitVersion")) + implementation("com.onixbyte:devkit-utils") + implementation("com.onixbyte:property-guard-spring-boot-starter") } ``` -Then, remember this secret and encrypt the configuration properties that are required high security. For example: +### 2. Generate a secret -```java -import utils.com.onixbyte.devkit.AesUtil; +Use the following codes to get a random secret. -class EncryptSample { - public static void main(String[] args) { - var dataNeedEncryption = "Sample Value"; - var key = "3856faef0d2d4f33"; - var encryptedData = AesUtil.encrypt(dataNeedEncryption, key); +```java +@SpringBootTest +class SpringBootApplicationTest { + + @Test + void contextLoads() { + System.out.println(AesUtil.generateRandomSecret()); // Output: a 16-char long secret } } ``` -After that, copy the encrypted data to `application.properties` or `application.yml`. - -For `yml`: -```yaml -app: - sample-configuration: pe:t4YBfv8M9ZmTzWgTi2gJqg== # "pe:" is the prefix that declare that this property is encrypted. -``` - -For `properties`: -```properties -app.sample-configuration=pe:t4YBfv8M9ZmTzWgTi2gJqg== -``` +Or you can write a 16-char long secret by yourself. -## Contact +### 3. Encrypt your secret properties and place them into your configuration file -If you have any suggestions, ideas, don't hesitate contacting us via [GitHub Issues](https://github.com/CodeCraftersCN/jdevkit/issues/new) or [Discord Community](https://discord.gg/NQK9tjcBB8). +### 4. Run application with parameter `--pg.key=$your_secret` -If you face any bugs while using our library and you are able to fix any bugs in our library, we would be happy to accept pull requests from you on [GitHub](https://github.com/CodeCraftersCN/jdevkit/compare). \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 1a7eb27..084a020 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,5 +27,6 @@ include( "simple-jwt-facade", "simple-jwt-authzero", "simple-jwt-spring-boot-starter", - "property-guard-spring-boot-starter" + "property-guard-spring-boot-starter", + "simple-serial" ) diff --git a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java index 51e0267..759054c 100644 --- a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java +++ b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java @@ -20,6 +20,7 @@ import com.onixbyte.devkit.utils.Base64Util; import com.onixbyte.guid.GuidCreator; import com.onixbyte.security.KeyLoader; +import com.onixbyte.security.impl.EcKeyLoader; import com.onixbyte.simplejwt.TokenPayload; import com.onixbyte.simplejwt.TokenResolver; import com.onixbyte.simplejwt.annotations.ExcludeFromPayload; @@ -42,6 +43,7 @@ import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; +import java.security.NoSuchAlgorithmException; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.time.Duration; @@ -178,8 +180,9 @@ public Builder secret(String secret) { * @return the builder instance */ public Builder keyPair(String publicKey, String privateKey) { - this.publicKey = KeyLoader.loadEcdsaPublicKey(publicKey); - this.privateKey = KeyLoader.loadEcdsaPrivateKey(privateKey); + var keyLoader = new EcKeyLoader(); + this.publicKey = keyLoader.loadPublicKey(publicKey); + this.privateKey = keyLoader.loadPrivateKey(privateKey); return this; } diff --git a/simple-jwt-spring-boot-starter/README.md b/simple-jwt-spring-boot-starter/README.md index 772be19..8b3a8ca 100644 --- a/simple-jwt-spring-boot-starter/README.md +++ b/simple-jwt-spring-boot-starter/README.md @@ -29,7 +29,7 @@ It is quite simple to install this module by `Maven`. The only thing you need to ${simple-jwt-${any-implementation}.version} - cn.org.codecrafters + com.onixbyte simple-jwt-spring-boot-starter ${simple-jwt-spring-boot-starter.version} @@ -50,7 +50,7 @@ Find `build.gradle` in the needed project, and add the following code to the `de ```groovy implementation '${implementation-builder-group-id}:simple-jwt-${any-implementation}:${simple-jwt-${any-implementation}.version}' -implementation 'cn.org.codecrafters:simple-jwt-spring-boot-starter:${simple-jwt-spring-boot-starter.version}' +implementation 'com.onixbyte:simple-jwt-spring-boot-starter:${simple-jwt-spring-boot-starter.version}' ``` ### If you are not using `Maven` or `Gradle` diff --git a/simple-serial/README.md b/simple-serial/README.md new file mode 100644 index 0000000..8a4d20c --- /dev/null +++ b/simple-serial/README.md @@ -0,0 +1,14 @@ +# Simple Serial + +> Thanks to [@siujamo](https://github.com/siujamo)'s donation. + +Simple Serial reuses the configuration of Redis connections to provide am easy-to-use serial +service. + +## Configuration + +Simple Serial reused the redis configuration of Spring Boot to provide redis support for the +service. + +Besides, **Simple Serial** provides a configuration property `onixbyte.serial.start-serial` to +specify the start value of a serial, and default to `0`. \ No newline at end of file diff --git a/simple-serial/build.gradle.kts b/simple-serial/build.gradle.kts new file mode 100644 index 0000000..36f1eda --- /dev/null +++ b/simple-serial/build.gradle.kts @@ -0,0 +1,99 @@ +import java.net.URI + +plugins { + id("java") +} + +val artefactVersion: String by project +val projectUrl: String by project +val projectGithubUrl: String by project +val licenseName: String by project +val licenseUrl: String by project + +val jacksonVersion: String by project +val springBootVersion: String by project + +group = "com.onixbyte" +version = artefactVersion + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") + implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion") + implementation("org.springframework.boot:spring-boot-starter-logging:$springBootVersion") + implementation("org.springframework.boot:spring-boot-configuration-processor:$springBootVersion") + implementation("org.springframework.boot:spring-boot-starter-data-redis:$springBootVersion") + annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:$springBootVersion") + testImplementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion") + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + withSourcesJar() + withJavadocJar() +} + +tasks.test { + useJUnitPlatform() +} + +publishing { + publications { + create("simpleSerialSpringBootStarter") { + groupId = group.toString() + artifactId = "simple-serial-spring-boot-starter" + version = artefactVersion + + pom { + name = "Simple Serial :: Spring Boot Starter" + description = "A Redis based easy-to-use serial service." + url = projectUrl + + licenses { + license { + name = licenseName + url = licenseUrl + } + } + + scm { + connection = "scm:git:git://github.com:OnixByte/JDevKit.git" + developerConnection = "scm:git:git://github.com:OnixByte/JDevKit.git" + url = projectGithubUrl + } + + developers { + developer { + id = "zihluwang" + name = "Zihlu Wang" + email = "really@zihlu.wang" + timezone = "Asia/Hong_Kong" + } + } + } + + from(components["java"]) + + signing { + sign(publishing.publications["simpleSerialSpringBootStarter"]) + } + } + + repositories { + maven { + name = "sonatypeNexus" + url = URI(providers.gradleProperty("repo.maven-central.host").get()) + credentials { + username = providers.gradleProperty("repo.maven-central.username").get() + password = providers.gradleProperty("repo.maven-central.password").get() + } + } + } + } +} \ No newline at end of file diff --git a/simple-serial/src/main/java/com/onixbyte/serial/RedisConfig.java b/simple-serial/src/main/java/com/onixbyte/serial/RedisConfig.java new file mode 100644 index 0000000..99a4527 --- /dev/null +++ b/simple-serial/src/main/java/com/onixbyte/serial/RedisConfig.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.serial; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializer; + +/** + * Redis Configuration provides redis templates for operations to serial. + * + * @author siujamo + */ +@AutoConfiguration +public class RedisConfig { + + /** + * RedisTemplate for serial service. + * + * @param redisConnectionFactory redis connection factory + * @return a configured redis template for serial service + */ + @Bean + public RedisTemplate serialRedisTemplate(RedisConnectionFactory redisConnectionFactory) { + var redisTemplate = new RedisTemplate(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + redisTemplate.setKeySerializer(RedisSerializer.string()); + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Long.class)); + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + +} diff --git a/simple-serial/src/main/java/com/onixbyte/serial/SerialService.java b/simple-serial/src/main/java/com/onixbyte/serial/SerialService.java new file mode 100644 index 0000000..55d6fbf --- /dev/null +++ b/simple-serial/src/main/java/com/onixbyte/serial/SerialService.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.serial; + +import com.onixbyte.serial.properties.SerialProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/** + * {@code SerialService} provides simple serial operations. + * + * @author siujamo + */ +@Service +@EnableConfigurationProperties(SerialProperties.class) +public class SerialService { + + private static final Logger log = LoggerFactory.getLogger(SerialService.class); + + private final String appName; + private final RedisTemplate serialRedisTemplate; + private final SerialProperties serialProperties; + + /** + * Default constructor. + * + * @param appName the name of this application + * @param serialRedisTemplate serial redis template + * @param serialProperties serial properties + */ + public SerialService(@Value("${spring.application.name}") String appName, + RedisTemplate serialRedisTemplate, + SerialProperties serialProperties) { + this.appName = appName; + this.serialRedisTemplate = serialRedisTemplate; + this.serialProperties = serialProperties; + } + + /** + * Build a serial key. + * + * @param tag tag of the serial + * @return key of a serial + */ + public String buildKey(String tag) { + return appName + ":serial:" + tag; + } + + /** + * Get the next available serial for specific tag. + * + * @param tag tag of the serial + * @return next available serial + */ + public Long nextSerial(String tag) { + var key = buildKey(tag); + var next = Optional.ofNullable(serialRedisTemplate.opsForValue().get(key)) + .orElse(serialProperties.getStartSerial()); + serialRedisTemplate.opsForValue().set(key, next + 1); + return next; + } + + /** + * Reset all serial values. + */ + public void reset() { + var keys = serialRedisTemplate.keys(buildKey("*")); + var startSerial = serialProperties.getStartSerial(); + + if (!keys.isEmpty()) { + for (var key : keys) { + serialRedisTemplate.opsForValue().set(key, startSerial); + log.debug("Serial {} has been reset to {}", key, startSerial); + } + } + } + +} diff --git a/simple-serial/src/main/java/com/onixbyte/serial/properties/SerialProperties.java b/simple-serial/src/main/java/com/onixbyte/serial/properties/SerialProperties.java new file mode 100644 index 0000000..cb2552d --- /dev/null +++ b/simple-serial/src/main/java/com/onixbyte/serial/properties/SerialProperties.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.serial.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * SerialProperties can modify the start value of a serial. + * + * @author siujamo + */ +@ConfigurationProperties(prefix = "onixbyte.serial") +public class SerialProperties { + + /** + * The start of the serial, default to 0. + */ + private Long startSerial = 0L; + + /** + * Get the start of the serial. + * + * @return start of the serial + */ + public Long getStartSerial() { + return startSerial; + } + + /** + * Set the start of the serial. + * + * @param startSerial start of the serial + */ + public void setStartSerial(Long startSerial) { + this.startSerial = startSerial; + } + + /** + * Default constructor. + */ + public SerialProperties() { + } + +} diff --git a/simple-serial/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/simple-serial/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..7fd0a37 --- /dev/null +++ b/simple-serial/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,19 @@ +# +# Copyright (C) 2024-2025 OnixByte. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +# See the License for the specific language governing permissions and +# limitations under the License. +# + +com.onixbyte.serial.RedisConfig +com.onixbyte.serial.SerialService \ No newline at end of file diff --git a/simple-serial/src/main/resources/logback.xml b/simple-serial/src/main/resources/logback.xml new file mode 100644 index 0000000..fd31eac --- /dev/null +++ b/simple-serial/src/main/resources/logback.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + ${COLOURFUL_OUTPUT} + + + + + + \ No newline at end of file