Skip to content

Commit

Permalink
Merge pull request #42 from vnobo/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
vnobo authored Dec 19, 2024
2 parents 73692e6 + 660e006 commit 5094008
Show file tree
Hide file tree
Showing 9 changed files with 575 additions and 70 deletions.
9 changes: 8 additions & 1 deletion .github/workflows/gradle-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Upload Platform Image to Registry
run: |
echo "Upload images ${{ steps.meta.outputs.tags }} to registry."
echo "Upload labels ${{ steps.meta.outputs.labels }} to registry"
- name: Extract oauth2 metadata (tags, labels) for Docker
id: oauth2
Expand All @@ -120,4 +124,7 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.oauth2.outputs.tags }}
labels: ${{ steps.oauth2.outputs.labels }}

- name: Upload Oauth2 Image to Registry
run: |
echo "Upload images ${{ steps.oauth2.outputs.tags }} to registry."
echo "Upload labels ${{ steps.oauth2.outputs.labels }} to registry"
151 changes: 137 additions & 14 deletions boot/platform/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
## Project Structure

The project is structured in a typical Spring Boot fashion, with the main application entry point in the
`com.plate.boot` package. It includes various sub-packages for different concerns such as security, relational data
handling, and common utilities.
The project is organized in a typical Spring Boot manner, with the main application entry point located in the
`com.plate.boot` package. It contains various sub-packages dealing with different aspects such as security, relational
data handling, and common utilities.

## Features

The application includes the following key features:
The application encompasses the following key features:

- **User Management**: Handles user-related operations and authentication.
- **Security**: Implements security configurations and OAuth2 support.
- **Logging**: Manages application logs efficiently with pagination and cleanup.
- **User Management**: Deals with user-related operations and authentication.
- **Security**: Implements security configurations and provides OAuth2 support.
- **Logging**: Effectively manages application logs with pagination and cleanup functions.
- **Menus**: Manages menu items and their associated permissions.
- **Tenant Support**: Provides multi-tenancy capabilities.
- **Caching**: Utilizes caching mechanisms to improve performance.
- **Tenant Support**: Offers multi-tenancy capabilities.
- **Caching**: Utilizes caching mechanisms to enhance performance.

## Dependencies

The project relies on several dependencies, including:
The project depends on several dependencies, including:

- Spring Boot 3.x.x
- Spring Data R2DBC
Expand All @@ -30,16 +30,16 @@ The project relies on several dependencies, including:
To run the application, you need to have Java 17 and Maven installed. Then, you can start the application by running the
following command from the root directory of the project:

This will start the Spring Boot application, and it should be accessible at `http://localhost:8080`.
This will launch the Spring Boot application, and it should be accessible at `http://localhost:8080`.

## API Documentation

The API documentation is available using Swagger UI. Once the application is running, you can access the Swagger UI at:
The API documentation can be accessed via Swagger UI. Once the application is running, you can visit the Swagger UI at:

## Contributing

Contributions are welcome! If you find any issues or want to add new features, feel free to open an issue or submit a
pull request.
Contributions are welcome! If you come across any issues or wish to add new features, feel free to open an issue or
submit a pull request.

## SSL Certificate Generation

Expand All @@ -51,4 +51,127 @@ pull request.
keytool -importkeystore -srckeystore plate.p12 -srcstoretype pkcs12 -srcalias 1 -destkeystore plate.jks -deststoretype jks -deststorepass 123456 -destalias plate
```

### Developer Documentation: Using QueryHelper, QueryJsonHelper and QueryFragment

#### 1. Overview

`QueryHelper`, `QueryJsonHelper` and `QueryFragment` are three utility classes that work together to assist developers
in constructing and executing dynamic SQL queries, especially those involving JSON fields. These utility classes offer a
secure and flexible way to handle database queries, reducing the risk of SQL injection and simplifying the query
construction process.

#### 2. QueryFragment

`QueryFragment` is a core class used for building different parts of an SQL query, including selected columns, query
conditions, sorting and pagination.

**Usage Example**:

```java
QueryFragment queryFragment = QueryFragment.withNew()
.addColumn("id", "name", "email")
.addQuery("users")
.addWhere("age > :age")
.addOrder("name ASC");

queryFragment.

put("age",18); // Bind parameters

String sqlQuery = queryFragment.querySql(); // Generate the SQL query string
```

#### 3. QueryHelper

`QueryHelper` provides static methods to build a `QueryFragment` from an object, especially when the object contains
pagination information.

**Usage Example**:

```java
UserRequest userRequest = new UserRequest();
userRequest.

setUsername("john");

Pageable pageable = PageRequest.of(0, 10);

QueryFragment queryFragment = QueryHelper.query(userRequest, pageable);
String sqlQuery = queryFragment.querySql();
```

#### 4. QueryJsonHelper

`QueryJsonHelper` focuses on handling queries for JSON fields, allowing developers to construct SQL query conditions for
JSON data.

**Usage Example**:

```java
Map<String, Object> jsonParams = new HashMap<>();
jsonParams.

put("extend.requestBody.nameEq","Test User");
jsonParams.

put("extend.emailEq","[email protected]");

QueryFragment queryFragment = QueryJsonHelper.queryJson(jsonParams, "a");
String sqlQuery = queryFragment.querySql();
```

#### 5. Full-text Search Use Case

For full-text search, you can use the `addQuery` method of `QueryFragment` to add conditions for full-text search.

**Usage Example**:

```java
QueryFragment queryFragment = QueryFragment.withNew()
.addColumn("id", "name", "email")
.addQuery("users")
.addWhere("to_tsvector('english', bio) @@ to_tsquery('english', :search)")
.addOrder("ts_rank(to_tsvector('english', bio), to_tsquery('english', :search)) DESC");

queryFragment.

put("search","test user"); // Bind the full-text search parameter

String sqlQuery = queryFragment.querySql(); // Generate the SQL query string containing the full-text search
```

In this example, `to_tsvector` and `to_tsquery` are PostgreSQL's full-text search functions used to convert text into
vectors and query strings respectively.

#### 6. Integration with UserRequest

The `UserRequest` class extends the `User` class and adds additional attributes and methods for handling user requests.

**Usage Example**:

```java
UserRequest userRequest = new UserRequest();
userRequest.

setUsername("john");
userRequest.

setSecurityCode("secure-code");

// Convert UserRequest to QueryFragment
QueryFragment queryFragment = userRequest.toParamSql();
String sqlQuery = queryFragment.querySql();
```

In this example, the `toParamSql` method converts the `UserRequest` object into a `QueryFragment` instance for
constructing an SQL query.

#### 7. Precautions

- Ensure that the database connection and configuration are correctly set when using these utility classes.
- For full-text search, make sure that the database supports the full-text search function and that the corresponding
indexes have been created.
- When binding parameters, ensure that the parameter names and values match the placeholders in the query.

Through these utility classes, developers can build and execute SQL queries more conveniently while maintaining the
security and maintainability of the code.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
return handleExceptionInternal(ex, null, headers, status, exchange);
}

/**
* Handles exceptions of type {@link DataAccessException} by creating an appropriate error response.
* This method is designed to be used within a Spring MVC controller advice context to manage
* exceptions that occur during the execution of RESTful server operations.
*
* @param ex The {@link DataAccessException} instance that was thrown, encapsulating
* error details such as error code, message, and any additional info.
* @param exchange The current server web exchange containing request and response information.
* This is used to extract details necessary for constructing the error response.
* @return A {@link Mono} containing a {@link ResponseEntity} with status {@link HttpStatus#INSUFFICIENT_STORAGE},
* content type set to {@link MediaType#APPLICATION_JSON}, and body containing
* a {@link ProblemDetail} object representing the details of the exception.
* The error response includes the request URI, the error message, and a custom title.
*/
@ExceptionHandler(DataAccessException.class)
public Mono<ResponseEntity<Object>> handleDataAccessException(DataAccessException ex, ServerWebExchange exchange) {
if (logger.isDebugEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,93 @@
import java.util.StringJoiner;

/**
* Represents a SQL parameter structure consisting of a conditional SQL fragment
* and a map of parameters to be bound to a PreparedStatement.
* This record facilitates the construction of dynamic SQL queries with placeholders
* for improved performance and security against SQL injection.
* Represents a SQL parameter structure consisting of a conditional SQL fragment and a map of parameters
* to be bound to a PreparedStatement. This class facilitates the construction of dynamic SQL queries
* with placeholders for improved performance and security against SQL injection.
*
* <p>The QueryFragment class is designed to be flexible and modular, allowing users to build complex
* SQL queries by chaining method calls. It manages the SQL query structure, including the SELECT
* columns, FROM clause, WHERE conditions, ORDER BY clause, and LIMIT/OFFSET for pagination.
*
* <p>Example usage:
* <pre>
* {@code
* QueryFragment queryFragment = QueryFragment.withNew()
* .addColumn("id", "name", "email")
* .addQuery("users")
* .addWhere("age > :age", 18)
* .addOrder("name ASC")
* .addOrder("email DESC");
*
* // Bind parameters
* queryFragment.put("age", 18);
*
* // Generate SQL query
* String sql = queryFragment.querySql();
* System.out.println(sql);
* }
* </pre>
* In this example, a QueryFragment instance is created and configured with columns, a table name,
* a WHERE condition, and ORDER BY clauses. Parameters are added to the query fragment, and finally,
* the SQL query string is generated using the querySql() method.
*
* @see QueryHelper for utility methods to construct QueryFragment instances from objects.
*/
@Getter
public class QueryFragment extends HashMap<String, Object> {

/**
* A StringJoiner to accumulate column names for the SELECT clause.
* Example usage:
* <pre>
* {@code
* queryFragment.addColumn("id", "name", "email");
* }
* </pre>
*/
private final StringJoiner columns = new StringJoiner(",");

/**
* A StringJoiner to accumulate the main SQL query parts (e.g., table names).
* Example usage:
* <pre>
* {@code
* queryFragment.addQuery("users");
* }
* </pre>
*/
private final StringJoiner querySql = new StringJoiner(" ");

/**
* A StringJoiner to accumulate WHERE conditions.
* Example usage:
* <pre>
* {@code
* queryFragment.addWhere("age > :age");
* }
* </pre>
*/
private final StringJoiner whereSql = new StringJoiner(" AND ");

/**
* A StringJoiner to accumulate ORDER BY clauses.
* Example usage:
* <pre>
* {@code
* queryFragment.addOrder("name ASC");
* }
* </pre>
*/
private final StringJoiner orderSql = new StringJoiner(",");

/**
* The maximum number of rows to return (LIMIT clause).
*/
private final int size;

/**
* The number of rows to skip before starting to return rows (OFFSET clause).
*/
private final long offset;

public QueryFragment(int size, long offset, QueryFragment params) {
Expand Down Expand Up @@ -137,6 +206,17 @@ public String orderSql() {
return "";
}

/**
* Generates the complete SQL query string based on the configured columns, table, conditions, and pagination.
*
* <p>The generated SQL query follows this structure:
* <code>SELECT columns FROM table WHERE conditions ORDER BY order LIMIT size OFFSET offset</code>
*
* <p>If no table or columns are specified, an exception is thrown to prevent generating an invalid query.
*
* @return A String representing the complete SQL query.
* @throws QueryException if the querySql is null, indicating that the query structure is incomplete.
*/
public String querySql() {
if (this.querySql.length() > 0) {
return String.format("SELECT %s FROM %s %s %s LIMIT %d OFFSET %d",
Expand All @@ -146,6 +226,17 @@ public String querySql() {
new IllegalArgumentException("This querySql is null, please use whereSql() method"));
}

/**
* Generates the COUNT SQL query string based on the configured conditions.
*
* <p>The generated COUNT SQL query follows this structure:
* <code>SELECT COUNT(*) FROM (SELECT columns FROM table WHERE conditions) t</code>
*
* <p>If no table or columns are specified, an exception is thrown to prevent generating an invalid query.
*
* @return A String representing the COUNT SQL query.
* @throws QueryException if the countSql is null, indicating that the query structure is incomplete.
*/
public String countSql() {
if (this.querySql.length() > 0) {
return "SELECT COUNT(*) FROM (" + String.format("SELECT %s FROM %s", this.columns, this.querySql)
Expand Down
Loading

0 comments on commit 5094008

Please sign in to comment.