Taogen's Blog

Stay hungry stay foolish.

Get Task Results Concurrently

ExecutorService

the order to get future results is the tasks order. If the later task executes faster than the earlier task, you still need to wait for the earlier task to complete.

public static void executorService() {
int threadNum = 10;
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
long start = System.currentTimeMillis();
List<Future> futureList = new ArrayList<>(threadNum);
for (int i = 0; i < threadNum; i++) {
Future<String> future = executorService.submit(() -> {
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomNumber = random.nextInt(10);
System.out.println(Thread.currentThread().getName() + ": " + randomNumber);
Thread.sleep(1000 * randomNumber);
return Thread.currentThread().getName();
});
futureList.add(future);
}
for (int i = 0; i < threadNum; i++) {
try {
System.out.println(futureList.get(i).get());
System.out.println("elapsed time: " + (System.currentTimeMillis() - start) + "ms");
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("elapsed time: " + (System.currentTimeMillis() - start) + "ms");
executorService.shutdown();
}

ExecutorCompletionService

the order to get future results is the execution time of tasks from short to long.

public static void executorCompletionService() {
int threadNum = 10;
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
ExecutorCompletionService completionService =
new ExecutorCompletionService<>(executorService);
long start = System.currentTimeMillis();
for (int i = 0; i < threadNum; i++) {
completionService.submit(() -> {
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomNumber = random.nextInt(10);
System.out.println(Thread.currentThread().getName() + ": " + randomNumber);
Thread.sleep(1000 * randomNumber);
return Thread.currentThread().getName();
});
}
for (int i = 0; i < threadNum; i++) {
try {
System.out.println(completionService.take().get());
System.out.println("elapsed time: " + (System.currentTimeMillis() - start) + "ms");
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("elapsed time: " + (System.currentTimeMillis() - start) + "ms");
executorService.shutdown();
}

Convert String to Date Time from Request Query String, payload of x-www-urlencoded or form-data

@GetMapping("testDateTime1")
public String testDateTime(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date dateTime) {
...
}
@RequestMapping("testDateTime2")
public String testDateTime2(QueryVo queryVo) {
...
}
public class QueryVo {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date dateTime;
}

Convert String to Date Time from Request payload of application/json

@PostMapping("testDateTime3")
public String testDateTime3(@RequestBody QueryVo queryVo) {
System.out.println("testDateTime");
return queryVo.toString();
}

public class QueryVo {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date dateTime;
}

Random Values

Random Integer

new Random().nextInt(min, max+1) return [min, max]

// Java 8
int size = 1, min = 0, max = 10;
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomNumber = random.ints(size, min, max + 1).findFirst().getAsInt();

int size = 5, min = 0, max = 10;
ThreadLocalRandom random = ThreadLocalRandom.current();
int[] randomNumbers = random.ints(size, min, max + 1).toArray();

int size = 5, min = 0, max = 10;
Random random = new Random();
int[] randomNumbers = random.ints(size, min, max + 1).toArray();
// Java 1.7 or later
int min = 0, max = 10;
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomNum = random.nextInt(min, max + 1);
// Before Java 1.7
int min = 0, max = 10;
Random random = new Random();
int randomNum = random.nextInt((max - min) + 1) + min;

Math.random() return [0, 1)

Random.nextInt(n) is more efficient than Math.random() * n

Math.random() uses Random.nextDouble() internally. Random.nextDouble() uses Random.next() twice to generate a double that has approximately uniformly distributed bits in its mantissa, so it is uniformly distributed in the range 0 to 1-(2^-53).

Random.nextInt(n) uses Random.next() less than twice on average- it uses it once, and if the value obtained is above the highest multiple of n below MAX_INT it tries again, otherwise is returns the value modulo n (this prevents the values above the highest multiple of n below MAX_INT skewing the distribution), so returning a value which is uniformly distributed in the range 0 to n-1.

int randomNum = min + (int) (Math.random() * ((max - min) + 1));

Random float

// TODO

Random String

// TODO

Regex Expression

matches() - Checks if the regexp matches the complete string.

String source = "hello there, I am a Java developer. She is a Web developer.";
Pattern pattern = Pattern.compile("a (.*?) developer");
Matcher matcher = pattern.matcher(source);
System.out.println(matcher.matches()); // false

Pattern pattern2 = Pattern.compile(".*a (.*?) developer.*");
Matcher matcher2 = pattern2.matcher(source);
System.out.println(matcher2.matches()); // true

find() - Get matching substrings

String source = "hello there, I am a Java developer. She is a Web developer.";
Pattern pattern = Pattern.compile("a (.*?) developer", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(source);
// find all match strings
while (matcher.find()) {
System.out.println(matcher.group()); // a Java developer, a Web developer
for (int i = 1; i < matcher.groupCount(); i++) {
System.out.println(matcher.group(i)); // Java, Web
}
}
// reset matcher for reuse
matcher.reset();
// only find once
if (matcher.find()) {
System.out.println(matcher.group()); // a Java developer
System.out.println(matcher.group(1)); // Java
}

Replace group string

String source = "hello there, I am a Java developer. She is a Web developer.";
String regex = "a (.*?) developer";
int groupToReplace = 1;
String replacement = "Good";
Matcher matcher = Pattern.compile(regex).matcher(source);
StringBuilder result = new StringBuilder(source);
int adjust = 0;
while (matcher.find()) {
int start = matcher.start(groupToReplace);
int end = matcher.end(groupToReplace);
result.replace(start + adjust, end + adjust, replacement);
adjust += replacement.length() - (end - start);
}
System.out.println(result); //hello there, I am a Good developer. She is a Good developer.

Non-greedy regular expression

Followed by the ?. For example .*?

Match special characters

[\\special_character]

For example:

[\\(\\)\\[\\]]

Match multiple line string

// Pattern.DOTALL
Pattern.compile("regexStr", Pattern.DOTALL);

Match case-insensitive string

// Pattern.CASE_INSENSITIVE
Pattern.compile("regexStr", Pattern.CASE_INSENSITIVE)
// (?i) enables case-insensitivity
String regexStr = "(?i).*";
src.matches(regexStr);
// convert string cases
src.toLowerCase();
src.matches(regexStr);

Multiple flags

Pattern.compile("regexStr", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);

Create a Multi-Modules Project

  1. Create Parent Maven Project
$ mvn -B archetype:generate -DgroupId=com.taogen.demo -DartifactId=parent-project -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
# or
$ mvn archetype:generate -DgroupId=com.taogen.demo -DartifactId=parent-project -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
  1. Create submodules
$ cd parent-project
$ mvn -B archetype:generate -DgroupId=com.taogen.demo -DartifactId=web -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
$ mvn -B archetype:generate -DgroupId=com.taogen.demo -DartifactId=service -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
  1. Configuring Parent Project’s pom.xml

3.1. Setting <packaging> to pom

$ cd parent-project
$ vim pom.xml

Add the following <packaging> configuration below the <artifactId> tag In the parent-project‘s pom.xml

<packaging>pom</packaging>

By setting the packaging to pom type, we’re declaring that the project will serve as a parent or an aggregator; it won’t produce further artifacts.

3.2. Configuring Parent Submodules

$ cd parent-project
$ vim pom.xml

Add the following <modules> configuration below the <version> tag In the parent-project‘s pom.xml

<modules>
<module>web</module>
<module>service</module>
</modules>
  1. Configuring Submodule Project’s pom.xml

In the individual submodules’ pom.xml, add the parent-project in the parent section.

$ vim web/pom.xml
$ vim service/pom.xml

Add the following <parent> configuration below the <moduleVersion> tag In the submodule project’s pom.xml

<parent>
<artifactId>parent-project</artifactId>
<groupId>com.taogen.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>

Inherent Parent Dependencies Version

  1. Declaring dependencies version in the parent project pom.xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20211205</version>
</dependency>
</dependencies>
</dependencyManagement>
  1. Inherent dependencies version in Submodules pom.xml

By declaring version in the parent, all submodules using only the groupId and artifactId, and the version will be inherited

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
</dependencies>

if a child module needs to use a different version of a managed dependency, you can override the managed version.

<dependencies>
...
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>xxx</version>
</dependency>
...
</dependencies>
  1. Verifying Inherent dependencies version is working
$ cd parent-project
$ mvn clean package

Dependency Between Submodules

In this tutorial, the web module depends on the service module. You can add service module dependency in web module project pom.xml.

<dependency>
<groupId>com.taogen.demo</groupId>
<artifactId>service</artifactId>
<version>${project.parent.version}</version>
</dependency>

Notice: To avoid a circular dependency between submodules. A circular dependency occurs when a module A depends on another module B, and the module B depends on module A.

Testing in Spring Boot Multi-Modules Maven Project

Test All Submodules

  1. Configuring spring boot
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

or

<properties>
<!-- included spring-boot-starter-parent -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- custom properties -->
<spring-boot.version>2.6.4</spring-boot.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- included spring-boot-starter-parent -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<!-- included spring-boot-starter-parent. For unit testing. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</build>
  1. Running all submodule unit tests
$ cd parent-project
$ mvn test

References

Gotenberg is a great open source project for converting files to PDF.

Starting Up Gotenberg

docker run --rm -p 3000:3000 gotenberg/gotenberg:7
# Run container in background
docker run --rm -d -p 3000:3000 gotenberg/gotenberg:7

View console output of Gotenberg

docker container ls
docker logs -f <containerId>

API Usage

Health Check

http://localhost:3000/health

Chromium

The Chromium module interacts with the Chromium browser to convert HTML documents to PDF.

Converting HTML URLs to PDF

curl \
--request POST 'http://localhost:3000/forms/chromium/convert/url' \
--form 'url="https://my.url"' \
-o my.pdf

Converting local HTML files to PDF

curl \
--request POST 'http://localhost:3000/forms/chromium/convert/html' \
--form 'files=@"/file/path/to/index.html"' \
-o my.pdf

LibreOffice

The LibreOffice module interacts with LibreOffice to convert documents to PDF.

To convert documents to PDF

Files with the following extensions:

.bib` `.doc` `.xml` `.docx` `.fodt` `.html` `.ltx` `.txt` `.odt` `.ott` `.pdb` `.pdf` `.psw` `.rtf` `.sdw` `.stw` `.sxw` `.uot` `.vor` `.wps` `.epub` `.png` `.bmp` `.emf` `.eps` `.fodg` `.gif` `.jpg` `.met` `.odd` `.otg` `.pbm` `.pct` `.pgm` `.ppm` `.ras` `.std` `.svg` `.svm` `.swf` `.sxd` `.sxw` `.tiff` `.xhtml` `.xpm` `.fodp` `.potm` `.pot` `.pptx` `.pps` `.ppt` `.pwp` `.sda` `.sdd` `.sti` `.sxi` `.uop` `.wmf` `.csv` `.dbf` `.dif` `.fods` `.ods` `.ots` `.pxl` `.sdc` `.slk` `.stc` `.sxc` `.uos` `.xls` `.xlt` `.xlsx` `.tif` `.jpeg` `.odp

Converting local document files to PDF

Relative file path

curl \
--request POST 'http://localhost:3000/forms/libreoffice/convert' \
--form 'files=@"test.docx"' \
-o test.pdf

Absolute file path

curl \
--request POST 'http://localhost:3000/forms/libreoffice/convert' \
--form 'files=@"D:\My Workspace\test.docx"' \
-o test.pdf

Multiple files

curl \
--request POST 'http://localhost:3000/forms/libreoffice/convert' \
--form 'files=@"/path/to/file.docx"' \
--form 'files=@"/path/to/file.xlsx"' \
-o my.zip

Request by Java OkHttp

private static final String GOTENBERG_SERVICE_HOST = "http://xxx.xxx.xxx.xxx:3000";

private static final String CONVERT_TO_PDF_URI = "/forms/libreoffice/convert";

private static final OkHttpClient OKHTTP_CLIENT = new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build();

public static byte[] convertToPdfByGotenberg(InputStream inputStream, String fileName) throws IOException {
Request.Builder requestBuilder = new Request.Builder()
.url(HttpUrl.parse(GOTENBERG_SERVICE_HOST + CONVERT_TO_PDF_URI).newBuilder().build());
String mediaType = URLConnection.guessContentTypeFromName(fileName);
if (mediaType == null) {
mediaType = "application/octet-stream";
}
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("files", fileName,
RequestBody.create(MediaType.parse(mediaType),
IOUtils.toByteArray(inputStream)));
requestBuilder.post(builder.build());
try (Response response = OKHTTP_CLIENT.newCall(requestBuilder.build()).execute()) {
return response.body().bytes();
}
}

PDF Engines

Merge multiple PDF files to one PDF file.

curl \
--request POST 'http://localhost:3000/forms/pdfengines/merge' \
--form 'files=@"/path/to/pdf1.pdf"' \
--form 'files=@"/path/to/pdf2.pdf"' \
--form 'files=@"/path/to/pdf3.pdf"' \
--form 'files=@"/path/to/pdf4.pdf"' \
-o my.pdf

Webhook

The Webhook module provides a middleware that allows you to upload the output file from multipart/form-data routes to the destination of your choice.

curl \
--request POST 'http://localhost:3000/forms/chromium/convert/url' \
--header 'Gotenberg-Webhook-Extra-Http-Headers: {"MyHeader": "MyValue"}' \
--header 'Gotenberg-Webhook-Url: https://my.webhook.url' \
--header 'Gotenberg-Webhook-Method: PUT' \
--header 'Gotenberg-Webhook-Error-Url: https://my.webhook.error.url' \
--header 'Gotenberg-Webhook-Error-Method: POST' \
--form 'url="https://my.url"'

The middleware reads the following headers:

  • Gotenberg-Webhook-Url - the callback to use - required
  • Gotenberg-Webhook-Error-Url - the callback to use if error - required
  • Gotenberg-Webhook-Method - the HTTP method to use (POST, PATCH, or PUT - default POST).
  • Gotenberg-Webhook-Error-Method - the HTTP method to use if error (POST, PATCH, or PUT - default POST).
  • Gotenberg-Webhook-Extra-Http-Headers - the extra HTTP headers to send to both URLs (JSON format).

Common Errors

Error: file name is too long

{"level":"error","ts":1649232587.172472,"logger":"api","msg":"create request context: copy to disk: create local file: open /tmp/9e10e36d-c5a9-4623-9fac-92db4a0d0982/xxx.doc: file name too long","trace":"de0b5ce2-5a99-406e-a61d-4abb65ef0294","remote_ip":"xxx.xxx.xxx.xxx","host":"xxx.xxx.xxx.xxx:3000","uri":"/forms/libreoffice/convert","method":"POST","path":"/forms/libreoffice/convert","referer":"","user_agent":"okhttp/4.9.3","status":500,"latency":13161094736,"latency_human":"13.161094736s","bytes_in":6983097,"bytes_out":21}

Solutions

Decreasing your file name length.

Error: file name contains UTF-8 characters

{"level":"error","ts":1649234692.9638329,"logger":"api","msg":"convert to PDF: unoconv PDF: unix process error: wait for unix process: exit status 6","trace":"28d9a196-10e5-4c7d-af6a-178494f49cd1","remote_ip":"xxx.xxx.xxx.xxx","host":"xxx.xxx.xxx.xxx:3000","uri":"/forms/libreoffice/convert","method":"POST","path":"/forms/libreoffice/convert","referer":"","user_agent":"okhttp/4.9.3","status":500,"latency":130617774,"latency_human":"130.617774ms","bytes_in":11559,"bytes_out":21}

Solutions

Encoding filename by URLEncoder.encode(fileName, "UTF-8").

Error: file extension name is not right

{"level":"error","ts":1649234777.9096093,"logger":"api","msg":"validate form data: no form file found for extensions: [.bib .doc .xml .docx .fodt .html .ltx .txt .odt .ott .pdb .pdf .psw .rtf .sdw .stw .sxw .uot .vor .wps .epub .png .bmp .emf .eps .fodg .gif .jpg .jpeg .met .odd .otg .pbm .pct .pgm .ppm .ras .std .svg .svm .swf .sxd .sxw .tif .tiff .xhtml .xpm .odp .fodp .potm .pot .pptx .pps .ppt .pwp .sda .sdd .sti .sxi .uop .wmf .csv .dbf .dif .fods .ods .ots .pxl .sdc .slk .stc .sxc .uos .xls .xlt .xlsx]","trace":"10e6ffd8-2d00-4374-b6fb-0c4ff6af3043","remote_ip":"xxx.xxx.xxx.xxx","host":"xxx.xxx.xxx.xxx:3000","uri":"/forms/libreoffice/convert","method":"POST","path":"/forms/libreoffice/convert","referer":"","user_agent":"okhttp/4.9.3","status":400,"latency":3885166,"latency_human":"3.885166ms","bytes_in":11551,"bytes_out":449}

Solutions

Files in form data should have a correct file extension.

References

Gotenberg Documentation

To get a good job, to become a good developer first.

Keep Learning

  • Reading textbooks, documentation.
  • Reading open-source code.
  • Writing blogs.

Practice Your Craft

  • Design yourself systems.
  • Build yourself systems.
  • Optimizes systems.
  • Solving coding problems on LeetCode.

Build Your Reputations

  • Contributing to open-source projects on GitHub.
  • Answering questions on Stack Overflow.
  • Keep writing technical blogs on your website.

What is Empty Cache and Hard Reload for Specific Domain

Clear current domain website cached static files, like HTML, CSS, JS, and images files.

Note: “Hard reload” is only clear current page cached static files not current domain. For current domain we need use “Empty Cache and Hard Reload”

Empty Cache and Hard Reload

Chrome

Method One

  1. Press control+shift+I or F12 to open Chrome Developer Tools.
  2. Right-click the reload button next to the address bar.
  3. Choose: “Empty Cache and Hard Reload”.

Method Two

  1. Press control+shift+I or F12 to open Chrome Developer Tools.
  2. Under Network (previously General) check Disable cache. (re-enable caching by un-checking this box)
  3. Reload the page.

Solution 1: Setting Proxy

First, use a US proxy instead of others, like HK.

Setting HTTP Proxy for git clone by HTTPS URL

setting HTTP proxy for git

git config --global http.proxy socks5h://127.0.0.1:1080
# or just proxy GitHub
git config --global http.https://github.com.proxy socks5h://127.0.0.1:1080

or temporarily setting HTTP proxy for your terminal

// linux
export ALL_PROXY=socks5://127.0.0.1:1080
// windows
set ALL_PROXY=socks5://127.0.0.1:1080

Test Case

Before Setting Proxy: about 200 KiB/s.

git clone https://github.com/mybatis/mybatis-3.git mybatis-clone-test
Cloning into 'mybatis-clone-test'...
remote: Enumerating objects: 397085, done.
remote: Counting objects: 100% (195/195), done.
remote: Compressing objects: 100% (79/79), done.
Receiving objects: 4% (15884/397085), 5.25 MiB | 197.00 KiB/s

After Setting Proxy: about 3 MiB/s.

git clone https://github.com/mybatis/mybatis-3.git mybatis-clone-test
Cloning into 'mybatis-clone-test'...
remote: Enumerating objects: 397085, done.
remote: Counting objects: 100% (195/195), done.
remote: Compressing objects: 100% (79/79), done.
Receiving objects: 14% (55592/397085), 18.63 MiB | 3.16 MiB/s

Proxy types

or Setting SSH Proxy for git clone by SSH URL

On Windows, add the following content to your git SSH config file ~/.ssh/config:

ProxyCommand "C:\Program Files\Git\mingw64\bin\connect.exe" -S 127.0.0.1:1080 %h %p

Host github.com
User git
Port 22
Hostname github.com
IdentityFile "C:\Users\Taogen\.ssh\id_ed25519"
TCPKeepAlive yes
IdentitiesOnly yes

Host ssh.github.com
User git
Port 443
Hostname ssh.github.com
IdentityFile "C:\Users\Taogen\.ssh\id_ed25519"
TCPKeepAlive yes
IdentitiesOnly yes

You can put the ProxyCommand ... under a specific Host xxx.xxx.xxx like this if you need to access multiple git hosting sites but set an SSH proxy for only one site.

Host ssh.github.com
ProxyCommand "C:\Program Files\Git\mingw64\bin\connect.exe" -S 127.0.0.1:1080 %h %p
...

Git SSH config file path

  • Windows:
    • per user SSH configuration file: ~/.ssh/config
    • system SSH configuration file: C:\ProgramData\ssh\ssh_config
  • Linux:
    • per user SSH configuration file: ~/.ssh/config
    • system SSH configuration file: /etc/ssh/ssh_config

Git connect program

  • Windows: {git_install_path}\Git\mingw64\bin\connect.exe (You can get the file path by running the command line where connect.exe)
  • Linux: /usr/bin/nc (You can get the file path by running the command line whereis nc)

Solution 2: Switching from using SSH to HTTPS

Using SSH: about 20 KiB/s

git clone git@github.com:xxx/xxx.git
Receiving objects: 0% (309/396867), 172.01 KiB | 26.00 KiB/s

Using HTTPS: about 400 KiB/s

git clone https://github.com/xxx/xxx.git
Receiving objects: 100% (396867/396867), 113.49 MiB | 402.00 KiB/s, done.

Other Improvements

git config --global http.postbuffer 524288000
git config --global credential.helper "cache --timeout=86400"

References

[1] GitHub 加速终极教程

[2] SSH in git behind proxy on windows 7

[3] ssh_config - OpenBSD

[4] nc - OpenBSD

Running the project

How to configure your local environment to run the project.

How the project was designed

Business Architecture Design

  • Business Process
  • System Function Structure

Application(System) Architecture Design

Type: Monolithic, Distributed, SOA

Division

  • Horizontal division: frontend, middle server, backend server.

  • Vertical division: subsystems.

Technical Architecture Design

Technology Stack

Techniques of every layer.

Layers: Persistence layer, data layer, logic layer, application layer, view layer.

Database Design

Interface Design

Implementation of Core Functions

Data Access

Data Source configuration, encapsulation, use

Redis configuration, encapsulation, use

Elasticsearch configuration, encapsulation, use

System Core Functions

User, role, privilege, and menu

Login authentication and authorization

Exception Handler

Logging

File upload and download

Data import and export

Scheduling Tasks

i18n

Code generation

A Module’s CRUD Function Implementation

How to Develop

Git Management

Project File Structure

The process of developing a module

Development Specification

Developing a New Module

Create a database table.

Write module source code.

Running project.

Test module functions.

Java

Constants

java.lang.Math

  • E
  • PI

java.net.HttpURLConnection

  • HTTP_OK
  • HTTP_NO_CONTENT
  • HTTP_BAD_REQUEST
  • HTTP_UNAUTHORIZED
  • HTTP_FORBIDDEN
  • HTTP_NOT_FOUND
  • HTTP_BAD_METHOD
  • HTTP_INTERNAL_ERROR

javax.management.timer.Timer

  • ONE_DAY
  • ONE_HOUR
  • ONE_MINUTE
  • ONE_SECOND
  • ONE_WEEK

Enum

java.nio.charset.StandardCharsets

  • ISO_8859_1
  • UTF_8.toString()

Utilities

java.util

  • Arrays
    • asList(T… a)
    • sort(int[] a)
    • toString(Object[] a)
  • Collections
    • emptyList()
    • emptyMap()
    • emptySet()
    • sort(List list)
    • sort(List list, Comparator<? super T> c)
  • Objects
    • nonNull()
    • isNull(Object obj)
    • equals(Object a, Object b)
    • deepEquals(Object a, Object b)
    • compare(T a, T b, Comparator<? super T> c)
    • requireNonNull(T obj, String message)
    • toString(Object o, String nullDefault)
  • UUID
    • randomUUID()

Others

  • Optional
  • Random
  • StringJoiner

Spring Framework

Constants

org.springframework.http.HttpHeaders

  • ACCEPT_LANGUAGE
  • AUTHORIZATION
  • CACHE_CONTROL
  • CONTENT_DISPOSITION
  • CONTENT_TYPE
  • USER_AGENT

org.springframework.http.MediaType

  • APPLICATION_JSON_VALUE
  • APPLICATION_FORM_URLENCODED_VALUE
  • MULTIPART_FORM_DATA_VALUE

Enum

org.springframework.http.HttpMethod

  • GET.toString()
  • POST
  • PUT
  • DELETE

org.springframework.http.HttpStatus

  • OK
  • NO_CONTENT
  • BAD_REQUEST
  • UNAUTHORIZED
  • FORBIDDEN
  • NOT_FOUND
  • METHOD_NOT_ALLOWED
  • INTERNAL_SERVER_ERROR

Utilities

org.springframework.util

  • StringUtils
    • hasLength(String str)
  • CollectionUtils
    • isEmpty(Collection<?> collection)
  • ClassUtils

org.springframework.beans

  • BeanUtils
    • copyProperties(Object source, Object target)

References

[1] Spring Framework Constant Field Values

[2] Java 8 Constant Field Values

0%