Sử dụng Swagger UI trong jersey REST WS project

Công Nghệ
Sử dụng Swagger UI trong jersey REST WS project

Bài viết được sự cho phép của tác giả Giang Phan

Trong các bài viết trước, tôi đã giới thiệu với các bạn Swagger và cách cài đặt, sử dụng Swagger UI. Trong thực tế, các API thường được thay đổi bởi các developer và họ ít khi mở Swagger editor để cập nhật lại các document hoặc vì lý do nào đó mà họ không cập nhật document mới nhất theo source code. Một vấn đề đặt ra là có cách nào để API document có thể cập nhật một cách tự động mỗi khi code thay đổi và đặt cùng một nơi trong code để developer dễ dàng cập nhật hay không? Câu trả là là có và tôi sẽ hướng dẫn các bạn thực hiện trong phần tiếp theo của bài viết này.

Xem thêm việc làm Linux lương cao trên Station D

Tích hợp JAX-RS REST project với Swagger UI để tạo API document một cách tự động

Tiếp tục bài viết về xây dựng RESTful API với Jersey, trong bài này tôi sẽ hướng dẫn các bạn cách tích hợp JAX-RS REST project với Swagger UI để tạo API document một cách tự động. Nếu các bạn làm Spring project có thể tìm hiểu về Springfox – đây là một thư viện rất mạnh mẽ và dễ sử dụng để tạo REST API document. Tôi sẽ hướng dẫn các bạn Springfox ở series bài viết về Spring Framework.

Ý tưởng:

  • Tạo REST API sử dụng Jersey 2.
  • Sử dụng Swagger Annotation để mô tả thông tin về resource.
  • Sử dụng Swagger core để sinh file đặc tả Swagger API theo chuẩn OpenAPI 3, output có thể là json hoặc yaml.
  • Sử dụng thư viện Swagger UI để sinh ra giao diện cho document từ file config dưới chuẩn OpenAPI.

Để mọi thứ có thể thực hiện một cách tự động, chúng ta sẽ sử dụng Maven – một chương trình quản lý dự án cho phép các developers có thể quản lý về version, các dependencies (các component, thư viện sử dụng trong dự án) , quản lý build, tự động download javadoc & source, ….

Các maven plugin cần thiết để tích hợp Jersey với Swagger UI:

  • maven-dependency-plugin : plugin này giúp donwload các static Swagger UI file được đóng gói trong webjar từ Maven dependency.
  • maven-war-plugin : plugin này giúp copy các static Swagger UI file vào thư mục swagger-ui trong war file của project.
  • replacer : được sử dụng để thay thế URL đến file OpenAPI specification trong file index.html của Swaggers UI.

Ví dụ

Tôi sẽ sử dụng lại ví dụ ở bài viết “JWT – Token-based Authentication trong Jersey 2.x” để hướng dẫn các bạn cách tạo document tự động cho các API này.

Các bước thực hiện:

  • Tạo maven project và khai báo các thư viện, plugin cần thiết.
  • Cấu hình Jersey.
  • Thêm cấu hình API trong file web.xml.
  • Tạo file đặc tả API cho project: openapi.yaml.
  • Sử dụng các Swagger Annotation để mô tả API.
  • Build project và sử dụng.

Tạo maven project và khai báo các thư viện, plugin cần thiết

Tạo maven project với cấu trúc như sau:

Mở file pom.xml và thêm các dependency sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>SwaggerWithJersey2Example</groupId>
    <artifactId>SwaggerWithJersey2Example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <jersey.version>2.28</jersey.version>
        <lombok.version>1.16.20</lombok.version>
        <javax.servlet-api.version>4.0.1</javax.servlet-api.version>
        <javax.ws.rs-api.version>2.1.1</javax.ws.rs-api.version>
        <swagger.version>2.0.8</swagger.version>
        <swagger-ui.version>3.23.4</swagger-ui.version>
        <replacer.version>1.5.3</replacer.version>
    </properties>
    <dependencies>
        <!-- Javax -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${javax.servlet-api.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>javax.ws.rs-api</artifactId>
            <version>${javax.ws.rs-api.version}</version>
        </dependency>
        <!-- Jersey -->
        <dependency>
            <groupId>org.glassfish.jersey.connectors</groupId>
            <artifactId>jersey-apache-connector</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-server -->
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-server</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-client -->
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-common -->
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-common</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-json-jackson -->
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-multipart -->
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-multipart</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- Java JWT: JSON Web Token for Java -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.5</version>
            <scope>runtime</scope>
        </dependency>
        <!-- Uncomment this next dependency if you want to use RSASSA-PSS (PS256, PS384, PS512) algorithms: -->
        <!-- <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.60</version>
            <scope>runtime</scope>
        </dependency> -->
        <!-- Swagger -->
        <!-- https://mvnrepository.com/artifact/io.swagger.core.v3/swagger-jaxrs2 -->
        <dependency>
            <groupId>io.swagger.core.v3</groupId>
            <artifactId>swagger-jaxrs2</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.swagger.core.v3/swagger-jaxrs2-servlet-initializer -->
        <dependency>
            <groupId>io.swagger.core.v3</groupId>
            <artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
            <version>${swagger.version}</version>
        </dependency>
    </dependencies>
    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <!-- Build with specified Java version -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <!-- Download Swagger UI webjar -->
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.1.1</version>
                <executions>
                    <execution>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>unpack</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <!-- https://mvnrepository.com/artifact/org.webjars/swagger-ui -->
                                <dependency>
                                    <groupId>org.webjars</groupId>
                                    <artifactId>swagger-ui</artifactId>
                                    <version>${swagger-ui.version}</version>
                                </dependency>
                            </artifactItems>
                            <outputDirectory>${project.build.directory}/swagger-ui</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <!-- Add Swagger UI resources to the war file. -->
                <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-war-plugin -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.3</version>
                <configuration>
                    <webResources combine.children="append">
                        <resource>
                            <directory>${project.build.directory}/swagger-ui/META-INF/resources/webjars/swagger-ui/${swagger-ui.version}</directory>
                            <includes>
                                <include>**/*.*</include>
                            </includes>
                            <targetPath>swagger-ui</targetPath>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>
            <plugin>
                <!-- Replace the OpenAPI specification example URL with the local one. -->
                <!-- https://mvnrepository.com/artifact/com.google.code.maven-replacer-plugin/replacer -->
                <groupId>com.google.code.maven-replacer-plugin</groupId>
                <artifactId>replacer</artifactId>
                <version>${replacer.version}</version>
                <executions>
                    <execution>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>replace</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <file>${project.build.directory}/swagger-ui/META-INF/resources/webjars/swagger-ui/${swagger-ui.version}/index.html</file>
                    <replacements>
                        <replacement>
                            <token>https://petstore.swagger.io/v2/swagger.json</token>
                            <value>/api/openapi.json</value>
                        </replacement>
                    </replacements>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Các bạn nhớ update maven project để download tất cả thư viện về máy nhé.

Cấu hình Jersey

Mở file com.gpcoder.config.JerseyServletContainerConfig thêm khai báo package swagger: io.swagger.v3.jaxrs2.integration.resources.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.gpcoder.config;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.logging.LoggingFeature;
//Deployment of a JAX-RS application using @ApplicationPath with Servlet 3.0
//Descriptor-less deployment
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
public class JerseyServletContainerConfig extends ResourceConfig {
    public JerseyServletContainerConfig() {
        // if there are more than two packages then separate them with semicolon
        packages("io.swagger.v3.jaxrs2.integration.resources,com.gpcoder");
        register(new LoggingFeature(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), Level.INFO,
                LoggingFeature.Verbosity.PAYLOAD_ANY, 10000));
        register(JacksonFeature.class);
        
        // This authorization feature is not automatically turned on.
        // We need to turn it on by ourself.
        register(RolesAllowedDynamicFeature.class);
    }
}

Thêm cấu hình API trong file web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
    <display-name>Swagger with Jersey2 Example by gpcoder</display-name>
    <servlet>
        <servlet-name>api</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.gpcoder.config.JerseyServletContainerConfig</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>api</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
</web-app>

Tạo file đặc tả API cho project: openapi.yaml

Khai báo một số thông tin cơ bản về API và security theo OpenAPI Specification.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
prettyPrint: true
cacheTTL: 0
openAPI:
  info:
    description: "Swagger UI demo by gpcoder.com"
    version: "1.0.0"
    title: "Swagger UI Demo"
    termsOfService: "http://swagger.io/terms/"
    contact:
      email: "contact@gpcoder.com"
    license:
      name: "Apache 2.0"
      url: "http://www.apache.org/licenses/LICENSE-2.0.html"
  servers:
    - url: '/api'
  # 1) Define the security scheme type (HTTP bearer)
  components:
    securitySchemes:
      bearerAuth:            # arbitrary name for the security scheme
        type: http
        scheme: bearer
        bearerFormat: JWT    # optional, arbitrary value for documentation purposes
  # 2) Apply the security globally to all operations
  security:
    - bearerAuth: []         # use the same name as above

Trong project này, chúng ta cần chứng thực user sử dụng JWT, nên cần khai báo một số thông tin về security. Chi tiết về Security OpenAPI Specification, các bạn tham khảo thêm ở link sau: https://swagger.io/docs/specification/authentication/bearer-authentication/

Sử dụng các Swagger Annotation để mô tả API

Bây giờ chúng ta sử dụng các Swagger Annotation được cung cấp bởi Swagger core để mô tả các API.

Chi tiết tất cả các Swagger Annotation, các bạn tham khảo tại đây: https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X—Annotations

AuthService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.gpcoder.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.gpcoder.helper.JwTokenHelper;
import com.gpcoder.model.User;
import com.gpcoder.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
@Path("/auth")
@Tag(name = "Authentication services",
    description = "Authenticating a user and issuing a JSON Web Token (JWT)")
public class AuthService {
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Operation(summary = "Authenticating a user",
        description = "Authenticating a user with their username/ password and issuing a JSON Web Token (JWT)",
        responses = {
                 @ApiResponse(description = "Authenticated user based on the given information",
                         content = @Content(mediaType = "application/json",
                         schema = @Schema(implementation = String.class))),
                 @ApiResponse(responseCode = "200", description = "success"),
                 @ApiResponse(responseCode = "403", description = "Wrong username or password."),
                 @ApiResponse(responseCode = "500", description = "Internal Server Error.")
             })
    public Response authenticateUser(
            @Parameter(description = "The user name for login. Some test account: admin, customer, gpcoder", required = true)
            @FormParam("username") String username,
            @Parameter(description = "The password for login", required = true)
            @FormParam("password") String password) {
        // Authenticate the user using the credentials provided
        UserService userService = new UserService();
        User user = userService.getUser(username);
        if (user == null || !user.getPassword().equals(password)) {
            return Response.status(Response.Status.FORBIDDEN) // 403 Forbidden
                    .entity("Wrong username or password") // the response entity
                    .build();
        }
        // Issue a token for the user
        String token = JwTokenHelper.createJWT(user);
        // Return the token on the response
        return Response.ok(token).build();
    }
}

OrderService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.gpcoder.api;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import com.gpcoder.model.Order;
import com.gpcoder.model.Role;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
// URI:
// http(s)://<domain>:(port)/<YourApplicationName>/<UrlPattern in web.xml>/<path>
// http://localhost:8080/api/orders
@Path("/orders")
@PermitAll
@Consumes(value = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
@Produces(value = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
@Tag(name = "Order service",
    description = "CRUD operations for order service. User must be authorized with some resources.")
public class OrderService {
    @GET
    @Path("/{id}")
    @Operation(summary = "Get order by id", description = "This action published for everyone")
    @ApiResponse(responseCode = "200", description = "Get order by id")
    public Response get(@PathParam("id") int id) {
        System.out.println("OrderService->get()");
        return Response.ok("OrderService->get()").build();
    }
    @Operation(
        summary = "Insert an order",
        responses = {
             @ApiResponse(description = "Inserted an order based on the given information",
                     content = @Content(mediaType = "application/json",
                     schema = @Schema(implementation = String.class))),
             @ApiResponse(responseCode = "200", description = "success"),
             @ApiResponse(responseCode = "401", description = "User cannot access this resource."),
             @ApiResponse(responseCode = "403", description = "User not authorized."),
             @ApiResponse(responseCode = "500", description = "Internal Server Error.")
         })
    @POST
    @RolesAllowed(Role.ROLE_CUSTOMER)
    public Response insert(Order order, @Context SecurityContext securityContext) {
        System.out.println("User: " + securityContext.getUserPrincipal().getName());
        System.out.println("OrderService->insert()");
        return Response.ok("OrderService->insert()").build();
    }
    @PUT
    @RolesAllowed({ Role.ROLE_ADMIN, Role.ROLE_CUSTOMER })
    @Operation(summary = "Update order", description = "Admin and Customer can do this action")
    public Response update(Order order) {
        System.out.println("OrderService->update()");
        return Response.ok("OrderService->update()").build();
    }
    @DELETE
    @Path("/{id}")
    @RolesAllowed(Role.ROLE_ADMIN)
    @Operation(summary = "Delete order by id", description = "Only Admin can do this action")
    public Response delete(@PathParam("id") int id) {
        System.out.println("OrderService->delete()");
        return Response.ok("OrderService->delete()").build();
    }
}

Build project và sử dụng

Các bạn chọn chuột phải lên project -> Run As -> Maven install. Sau khi chạy xong bạn sẽ thấy các thư viện Swagger UI đã được tải về và giải nén trong thư mục target của project như sau:

Mở file index.html trong thư mục target/swagger-ui/META-INF/resources/webjars/swagger-ui/${swagger-ui.version} , bạn sẽ thấy đường dẫn đến file mô tả swagger.json đã được thay thế bằng /api/openapi.json như đã config trong file pom.xml.

Mọi thứ đã xong, bây giờ các bạn có thể start server lên để kiểm tra kết quả.

  • Kiểm tra file openapi.json tồn tại và chứa tất cả các đặc tả về API document. Truy cập vào địa chỉ: http://localhost:8080/api/openapi.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
{
  "openapi" : "3.0.1",
  "info" : {
    "title" : "Swagger UI Demo",
    "description" : "Swagger UI demo by gpcoder.com",
    "termsOfService" : "http://swagger.io/terms/",
    "contact" : {
      "email" : "contact@gpcoder.com"
    },
    "license" : {
      "name" : "Apache 2.0",
      "url" : "http://www.apache.org/licenses/LICENSE-2.0.html"
    },
    "version" : "1.0.0"
  },
  "servers" : [ {
    "url" : "/api"
  } ],
  "security" : [ {
    "bearerAuth" : [ ]
  } ],
  "tags" : [ {
    "name" : "Authentication services",
    "description" : "Authenticating a user and issuing a JSON Web Token (JWT)"
  }, {
    "name" : "Order service",
    "description" : "CRUD operations for order service. User must be authorized with some resources."
  } ],
  "paths" : {
    "/auth" : {
      "post" : {
        "tags" : [ "Authentication services" ],
        "summary" : "Authenticating a user",
        "description" : "Authenticating a user with their username/ password and issuing a JSON Web Token (JWT)",
        "operationId" : "authenticateUser",
        "requestBody" : {
          "content" : {
            "application/x-www-form-urlencoded" : {
              "schema" : {
                "type" : "object",
                "properties" : {
                  "username" : {
                    "type" : "string"
                  },
                  "password" : {
                    "type" : "string"
                  }
                }
              }
            }
          }
        },
        "responses" : {
          "default" : {
            "description" : "Authenticated user based on the given information",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "string"
                }
              }
            }
          },
          "200" : {
            "description" : "success"
          },
          "403" : {
            "description" : "Wrong username or password."
          },
          "500" : {
            "description" : "Internal Server Error."
          }
        }
      }
    },
    "/orders/{id}" : {
      "get" : {
        "tags" : [ "Order service" ],
        "summary" : "Get order by id",
        "description" : "This action published for everyone",
        "operationId" : "get",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "integer",
            "format" : "int32"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Get order by id"
          }
        }
      },
      "delete" : {
        "tags" : [ "Order service" ],
        "summary" : "Delete order by id",
        "description" : "Only Admin can do this action",
        "operationId" : "delete",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "integer",
            "format" : "int32"
          }
        } ],
        "responses" : {
          "default" : {
            "description" : "default response",
            "content" : {
              "application/json" : { },
              "application/xml" : { },
              "text/plain" : { }
            }
          }
        }
      }
    },
    "/orders" : {
      "put" : {
        "tags" : [ "Order service" ],
        "summary" : "Update order",
        "description" : "Admin and Customer can do this action",
        "operationId" : "update",
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/Order"
              }
            },
            "application/xml" : {
              "schema" : {
                "$ref" : "#/components/schemas/Order"
              }
            },
            "text/plain" : {
              "schema" : {
                "$ref" : "#/components/schemas/Order"
              }
            }
          }
        },
        "responses" : {
          "default" : {
            "description" : "default response",
            "content" : {
              "application/json" : { },
              "application/xml" : { },
              "text/plain" : { }
            }
          }
        }
      },
      "post" : {
        "tags" : [ "Order service" ],
        "summary" : "Insert an order",
        "operationId" : "insert",
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/Order"
              }
            },
            "application/xml" : {
              "schema" : {
                "$ref" : "#/components/schemas/Order"
              }
            },
            "text/plain" : {
              "schema" : {
                "$ref" : "#/components/schemas/Order"
              }
            }
          }
        },
        "responses" : {
          "default" : {
            "description" : "Inserted an order based on the given information",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "string"
                }
              }
            }
          },
          "200" : {
            "description" : "success"
          },
          "401" : {
            "description" : "User cannot access this resource."
          },
          "403" : {
            "description" : "User not authorized."
          },
          "500" : {
            "description" : "Internal Server Error."
          }
        }
      }
    }
  },
  "components" : {
    "schemas" : {
      "Order" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "integer",
            "format" : "int32"
          },
          "name" : {
            "type" : "string"
          }
        }
      }
    },
    "securitySchemes" : {
      "bearerAuth" : {
        "type" : "http",
        "scheme" : "bearer",
        "bearerFormat" : "JWT"
      }
    }
  }
}
  • Kiểm tra Swagger UI đã hiển thị đầy đủ các API document như đã config trong file WebContent/WEB-INF/openapi.yaml và đúng như mô tả trong Swagger Annotation. Truy cập địa chỉ: http://localhost:8080/swagger-ui/index.html

Các bạn có thể chạy thử POST /auth để lấy jwt token và chọn Authorize để config token cho các request đến API cần chứng thực.

Tiếp tục, chúng ta sẽ kiểm tra thông tin đã mô tả trong phương thức OrderService#insert.

Như các bạn có thể thấy, Swagger có thể generate ra 1 file config (.json hoặc .yaml) từ chính code của chương trình. Tuy nhiên phần UI của swagger lại khá rườm rà không đẹp lắm. Để có một beautiful document cho các API, các bạn có thể tìm hiểu thêm về Slate – một công cụ open source giúp generate ra các file html, css,… từ 1 file config .md . Slate có UI cực kì đẹp và khoa học.

Slate rất đẹp nhưng lại không thể generate ra file config như swagger. Vì vậy để tận dụng thế mạnh của Swagger và Slate, chúng ta cần phải convert 1 file config.yaml của Swagger sang 1 file config.md. Sau đó dùng Slate để generate ra các file html, css,… để publish ra bên ngoài. Chi tiết các bạn có thể tham khảo thêm tại link sau: https://github.com/lord/slate.

 

Bài viết gốc được đăng tải tại gpcoder.com

Có thể bạn quan tâm:

Xem thêm tuyển dụng IT hấp dẫn trên Station D

Bài viết liên quan

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bài viết được sự cho phép của tác giả Chung Nguyễn Hôm nay, nhóm Laravel đã phát hành một phiên bản chính mới của “ laravel/installer ” bao gồm hỗ trợ khởi động nhanh các dự án Jetstream. Với phiên bản mới này khi bạn chạy laravel new project-name , bạn sẽ nhận được các tùy chọn Jetstream. Ví dụ: API Authentication trong Laravel-Vue SPA sử dụng Jwt-auth Cách sử dụng Laravel với Socket.IO laravel new foo --jet --dev Sau đó, nó sẽ hỏi bạn thích stack Jetstream nào hơn: Which Jetstream stack do you prefer? [0] Livewire [1] inertia > livewire Will your application use teams? (yes/no) [no]: ... Nếu bạn đã cài bộ Laravel Installer, để nâng cấp lên phiên bản mới bạn chạy lệnh: composer global update Một số trường hợp cập nhật bị thất bại, bạn hãy thử, gỡ đi và cài đặt lại nha composer global remove laravel/installer composer global require laravel/installer Bài viết gốc được đăng tải tại chungnguyen.xyz Có thể bạn quan tâm: Cài đặt Laravel Làm thế nào để chạy Sql Server Installation Center sau khi đã cài đặt xong Sql Server? Quản lý các Laravel route gọn hơn và dễ dàng hơn Xem thêm Tuyển dụng lập trình Laravel hấp dẫn trên Station D

By stationd
Principle thiết kế của các sản phẩm nổi tiếng

Principle thiết kế của các sản phẩm nổi tiếng

Tác giả: Lưu Bình An Phù hợp cho các bạn thiết kế nào ko muốn làm code dạo, design dạo nữa, bạn muốn cái gì đó cao hơn ở tầng khái niệm Nếu lập trình chúng ta có các nguyên tắc chung khi viết code như KISS , DRY , thì trong thiết kế cũng có những nguyên tắc chính khi làm việc. Những nguyên tắc này sẽ là kim chỉ nam, nếu có tranh cãi giữa các member trong team, thì cứ đè nguyên tắc này ra mà giải quyết (nghe hơi có mùi cứng nhắc, mình thì thích tùy cơ ứng biến hơn) Tìm các vị trí tuyển dụng designer lương cao cho bạn Nguyên tắc thiết kế của GOV.UK Đây là danh sách của trang GOV.UK Bắt đầu với thứ user cần Làm ít hơn Thiết kế với dữ liệu Làm mọi thứ thật dễ dàng Lặp. Rồi lặp lại lần nữa Dành cho tất cả mọi người Hiểu ngữ cảnh hiện tại Làm dịch vụ digital, không phải làm website Nhất quán, nhưng không hòa tan (phải có chất riêng với thằng khác) Cởi mở, mọi thứ tốt hơn Bao trừu tượng luôn các bạn, trang Gov.uk này cũng có câu tổng quát rất hay Thiết kế tốt là thiết kế có thể sử dụng. Phục vụ cho nhiều đối tượng sử dụng, dễ đọc nhất nhất có thể. Nếu phải từ bỏ đẹp tinh tế – thì cứ bỏ luôn . Chúng ta tạo sản phẩm cho nhu cầu sử dụng, không phải cho người hâm mộ . Chúng ta thiết kế để cả nước sử dụng, không phải những người đã từng sử dụng web. Những người cần dịch vụ của chúng ta nhất là những người đang cảm thấy khó sử dụng dịch...

By stationd
Hiểu về trình duyệt – How browsers work

Hiểu về trình duyệt – How browsers work

Bài viết được sự cho phép của vntesters.com Khi nhìn từ bên ngoài, trình duyệt web giống như một ứng dụng hiển thị những thông tin và tài nguyên từ server lên màn hình người sử dụng, nhưng để làm được công việc hiển thị đó đòi hỏi trình duyệt phải xử lý rất nhiều thông tin và nhiều tầng phía bên dưới. Việc chúng ta (Developers, Testers) tìm hiểu càng sâu tầng bên dưới để nắm được nguyên tắc hoạt động và xử lý của trình duyệt sẽ rất hữu ích trong công việc viết code, sử dụng các tài nguyên cũng như kiểm thử ứng dụng của mình. Cách để npm packages chạy trong browser Câu hỏi phỏng vấn mẹo về React: Component hay element được render trong browser? Khi hiểu được cách thức hoạt động của trình duyệt chúng ta có thể trả lời được rất nhiều câu hỏi như: Tại sao cùng một trang web lại hiển thị khác nhau trên hai trình duyệt? Tại sao chức năng này đang chạy tốt trên trình duyệt Firefox nhưng qua trình duyệt khác lại bị lỗi? Làm sao để trang web hiển thị nội dung nhanh và tối ưu hơn một chút?… Hy vọng sau bài này sẽ giúp các bạn có một cái nhìn rõ hơn cũng như giúp ích được trong công việc hiện tại. 1. Cấu trúc của một trình duyệt Trước tiên chúng ta đi qua cấu trúc, thành phần chung và cơ bản nhất của một trình duyệt web hiện đại, nó sẽ gồm các thành phần (tầng) như sau: Thành phần nằm phía trên là những thành phần gần với tương tác của người dùng, càng phía dưới thì càng sâu và nặng về xử lý dữ liệu hơn tương tác. Nhiệm...

By stationd
Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Lĩnh vực EdTech (ứng dụng công nghệ vào các sản phẩm giáo dục) trên toàn cầu hiện nay đã tương đối phong phú với nhiều tên tuổi lớn phân phối đều trên các hạng mục như Broad Online Learning Platforms (nền tảng cung cấp khóa học online đại chúng – tiêu biểu như Coursera, Udemy, KhanAcademy,…) Learning Management Systems (hệ thống quản lý lớp học – tiêu biểu như Schoology, Edmodo, ClassDojo,…) Next-Gen Study Tools (công cụ hỗ trợ học tập – tiểu biểu như Kahoot!, Lumosity, Curriculet,…) Tech Learning (đào tạo công nghệ – tiêu biểu như Udacity, Codecademy, PluralSight,…), Enterprise Learning (đào tạo trong doanh nghiệp – tiêu biểu như Edcast, ExecOnline, Grovo,..),… Hiện nay thị trường EdTech tại Việt Nam đã đón nhận khoảng đầu tư khoảng 55 triệu đô cho lĩnh vực này nhiều đơn vị nước ngoài đang quan tâm mạnh đến thị trường này ngày càng nhiều hơn. Là một trong những xu hướng phát triển tốt, và có doanh nghiệp đã hoạt động khá lâu trong ngành nêu tại infographic như Topica, nhưng EdTech vẫn chỉ đang trong giai đoạn sơ khai tại Việt Nam. Tại Việt Nam, hệ sinh thái EdTech trong nước vẫn còn rất non trẻ và thiếu vắng nhiều tên tuổi trong các hạng mục như Enterprise Learning (mới chỉ có MANA), School Administration (hệ thống quản lý trường học) hay Search (tìm kiếm, so sánh trường và khóa học),… Với chỉ dưới 5% số dân công sở có sử dụng một trong các dịch vụ giáo dục online, EdTech cho thấy vẫn còn một thị trường rộng lớn đang chờ được khai phá. *** Vừa qua Station D đã công bố Báo cáo Vietnam IT Landscape 2019 đem đến cái nhìn toàn cảnh về các ứng dụng công...

By stationd