This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-docs.git


The following commit(s) were added to refs/heads/master by this push:
     new 5a5cba3  update porter applications (#301)
5a5cba3 is described below

commit 5a5cba32a95e826050d28d2fc8d0ac7104e088be
Author: liubao68 <[email protected]>
AuthorDate: Thu Nov 23 17:41:43 2023 +0800

    update porter applications (#301)
---
 .../docs/featured-topics/application-porter.md     |   2 +-
 .../application-porter/authentication.md           |  83 +++++-----
 .../featured-topics/application-porter/design.md   |  13 +-
 .../application-porter/file-service.md             |  57 ++++---
 .../application-porter/gateway-service.md          | 175 +++++++--------------
 .../featured-topics/application-porter/https.md    |   6 +-
 .../application-porter/porter-website.md           |  78 +--------
 .../application-porter/user-service.md             |  72 +++++----
 .../application-porter/user-story.md               |   8 +-
 .../application-porter/usercase.png                | Bin 16992 -> 0 bytes
 java-chassis-reference/zh_CN/mkdocs.yml            |   4 +-
 11 files changed, 183 insertions(+), 315 deletions(-)

diff --git 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter.md 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter.md
index 757b626..66ac879 100644
--- a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter.md
+++ b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter.md
@@ -31,5 +31,5 @@
 
 
在实际的代码中,我们还会遵循其他一些和微服务开发有关的原则,包括无状态设计等。这里的例子的目的是搭建一个商业可用的微服务,因此我们会在架构设计、方案设计上也给出一定的建议以及说明这样处理的目的。
 
-本专题的涉及的代码均托管在github,参考 
[Porter应用](https://github.com/apache/servicecomb-samples/tree/master/porter_lightweight)
 。开发者可以clone一份供学习使用,或者作为正式项目的模板。
+本专题的涉及的代码均托管在github,参考 
[Porter应用](https://github.com/apache/servicecomb-samples/tree/master/porter) 
。开发者可以clone一份供学习使用,或者作为正式项目的模板。
 
diff --git 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/authentication.md
 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/authentication.md
index d459eb6..eefddf0 100644
--- 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/authentication.md
+++ 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/authentication.md
@@ -1,3 +1,5 @@
+# 进行认证和鉴权设计
+
 传统的WEB容器都提供了会话管理,在微服务架构下,这些会话管理存在很多的限制,如果需要做到弹性扩缩容,则需要做大量的定制。 
在porter中,我们使用user-service做会话管理,可以通过login和session两个接口创建和获取会话信息。会话信息持久化到数据库中,从而实现微服务本身的无状态,微服务可以弹性扩缩容。在更大规模并发或者高性能要求的情况下,可以考虑将会话信息存储到高速缓存。
 
 ```
@@ -30,36 +32,40 @@ public SessionInfo getSession(@RequestParam(name = 
"sessionId") String sessionId
 
 * gateway-service进行HTTP协议到Invocation的转换
 
-这个通过重载EdgeInvocation的createInvocation实现。将会话ID通过Context传递给handler。如果开发者需要实现诸如增加响应头,设计Cookie等操作,则可以通过重载sendResponse来实现。
+这个通过重载 InvocationCreator 实现。将会话ID通过Context传递给handler。
 
 ```
-EdgeInvocation invoker = new EdgeInvocation() {
-  // 认证鉴权:构造Invocation的时候,设置会话信息。如果是认证请求,则添加Cookie。
-  protected void createInvocation(Object[] args) {
-    super.createInvocation(args);
-    // 既从cookie里面读取会话ID,也从header里面读取,方便各种独立的测试工具联调
+InvocationCreator creator = new EdgeInvocationCreator(context, requestEx, 
responseEx,
+    microserviceName, path) {
+  @Override
+  protected Invocation createInstance() {
+    Invocation invocation = super.createInstance();
+    // get session id from header and cookie for debug reasons
     String sessionId = context.request().getHeader("session-id");
     if (sessionId != null) {
-      this.invocation.addContext("session-id", sessionId);
+      invocation.addContext("session-id", sessionId);
     } else {
-      Cookie sessionCookie = context.getCookie("session-id");
+      Cookie sessionCookie = context.request().getCookie("session-id");
       if (sessionCookie != null) {
-        this.invocation.addContext("session-id", sessionCookie.getValue());
+        invocation.addContext("session-id", sessionCookie.getValue());
       }
     }
+    return invocation;
   }
 };
 ```
 
-* 通过handler来进行认证和会话管理
+* 通过处理链来进行认证和会话管理
 
 
对于ui界面,不提供认证,用户可以直接访问。对于REST接口需要进行认证,因此我们将认证和会话管理的功能在Hanlder中实现。下面的代码对user-service的login接口直接转发请求,其他请求先经过会话校验,再进行转发。
 
-***注意***: 在网关执行的Hanlder逻辑,是reactive模式的,不能使用阻塞调用,否则会导致线程阻塞。
+***注意***: 在网关执行的处理链逻辑,是reactive模式的,不能使用阻塞调用,否则会导致线程阻塞。
 
 ```
-public class AuthHandler implements Handler {
-  private UserServiceClient userServiceClient = 
BeanUtils.getBean("UserServiceClient");
+@Component
+public class AuthHandler extends AbstractFilter implements EdgeFilter {
+  @RpcReference(microserviceName = "user-service", schemaId = "user")
+  private UserService userService;
 
   // session expires in 10 minutes, cache for 1 seconds to get rid of 
concurrent scenarios.
   private Cache<String, String> sessionCache = CacheBuilder.newBuilder()
@@ -67,12 +73,12 @@ public class AuthHandler implements Handler {
       .build();
 
   @Override
-  public void handle(Invocation invocation, AsyncResponse asyncResponse) 
throws Exception {
+  public CompletableFuture<Response> onFilter(Invocation invocation, 
FilterNode nextNode) {
     if (invocation.getMicroserviceName().equals("user-service")
         && (invocation.getOperationName().equals("login")
-            || (invocation.getOperationName().equals("getSession")))) {
+        || (invocation.getOperationName().equals("getSession")))) {
       // login:return session id, set cookie by javascript
-      invocation.next(asyncResponse);
+      return nextNode.onFilter(invocation);
     } else {
       // check session
       String sessionId = invocation.getContext("session-id");
@@ -86,58 +92,47 @@ public class AuthHandler implements Handler {
           // session info stored in InvocationContext. Microservices can get 
it. 
           invocation.addContext("session-id", sessionId);
           invocation.addContext("session-info", sessionInfo);
-          invocation.next(asyncResponse);
+          return nextNode.onFilter(invocation);
         } catch (Exception e) {
-          asyncResponse.complete(Response.failResp(new 
InvocationException(500, "", e.getMessage())));
+          return CompletableFuture.completedFuture(Response.failResp(new 
InvocationException(500, "", e.getMessage())));
         }
-        return;
       }
 
       // In edge, handler is executed in reactively. Must have no blocking 
logic.
-      CompletableFuture<SessionInfo> result = 
userServiceClient.getGetSessionOperation().getSession(sessionId);
-      result.whenComplete((info, e) -> {
+      CompletableFuture<SessionInfo> result = 
userService.getSession(sessionId);
+      return result.whenComplete((info, e) -> {
         if (result.isCompletedExceptionally()) {
-          asyncResponse.complete(Response.failResp(new 
InvocationException(403, "", "session is not valid.")));
+          throw new InvocationException(403, "", "session is not valid.");
         } else {
           if (info == null) {
-            asyncResponse.complete(Response.failResp(new 
InvocationException(403, "", "session is not valid.")));
-            return;
+            throw new InvocationException(403, "", "session is not valid.");
           }
           try {
             // session info stored in InvocationContext. Microservices can get 
it. 
             invocation.addContext("session-id", sessionId);
             String sessionInfoStr = JsonUtils.writeValueAsString(info);
             invocation.addContext("session-info", sessionInfoStr);
-            invocation.next(asyncResponse);
             sessionCache.put(sessionId, sessionInfoStr);
           } catch (Exception ee) {
-            asyncResponse.complete(Response.failResp(new 
InvocationException(500, "", ee.getMessage())));
+            throw new InvocationException(500, "", ee.getMessage());
           }
         }
-      });
+      }).thenCompose(info -> nextNode.onFilter(invocation));
     }
   }
-}
-```
 
-启用该Hanlder,需要增加cse.handler.xml文件
-
-```
-<config>
-  <handler id="auth"
-    class="org.apache.servicecomb.samples.porter.gateway.AuthHandler" />
-</config>
-```
+  @Override
+  public int getOrder() {
+    return -1890;
+  }
 
-并且在microservice.yaml中启用auth,将新增加的auth处理链放到流控之后。
+  @Override
+  public String getName() {
+    return "edge-auth";
+  }
+}
 
 ```
-servicecomb:
-  handler:
-    chain:
-      Consumer:
-        default: internalAccess,auth,qps-flowcontrol-consumer,loadbalance
-```
 
 * 给删除文件增加鉴权
 
diff --git 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/design.md
 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/design.md
index 0ed37b0..36ebbff 100644
--- 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/design.md
+++ 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/design.md
@@ -1,16 +1,16 @@
+# 设计微服务
+
 根据User Story,应用至少包含用户管理、文件管理等微服务,每个微服务都行使不一样的功能。下面是分解后设计的微服务结构:
 
 ![](microservices.png)
 
-
-
 
**网关:**负责进行请求转发、用户认证以及其他内容,比如解决跨站访问、设置HTTP安全消息头等。通过设置防火墙,所有的请求都必须经过网关,这样就将内部服务与外部用户隔离起来,防止内部服务被非法访问。
 
-**文件管理:**提供文件上传、删除等文件管理功能。
+**文件管理:** 提供文件上传、删除等文件管理功能。
 
-**用户管理:**提供认证、角色和权限管理等功能。
+**用户管理:** 提供认证、角色和权限管理等功能。
 
-**界面: ** 采用静态页面技术, html+js+css实现。界面可以作为一个单独的微服务,也可以直接放到网关服务里面。
+**界面:**  采用静态页面技术, 
html+js+css实现。界面可以作为一个单独的微服务,也可以直接放到网关服务里面。或者使用CDN等进行部署。在本例子中,为了部署简单,将界面使用网关进行托管。
 
 
为了可靠性,这些服务都应该支持分布式集群部署。因此在业务逻辑中涉及到并发和负载均衡的场景,都需要考虑无状态设计。可以给网关配置域名或者在上层再挂一个弹性负载均衡器,实现网关的多实例部署。
 
@@ -19,6 +19,3 @@
  
[Porter应用](https://github.com/apache/servicecomb-samples/tree/master/porter_lightweight)
 
 
 下载该项目。
-
-初始的项目是一个maven项目,主要内容包括pom.xml文件、microservice.yam文件和一个Main函数。microservice.yam文件配置了微服务的基本信息和访问服务中心的地址。
-
diff --git 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/file-service.md
 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/file-service.md
index 9973374..154022c 100644
--- 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/file-service.md
+++ 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/file-service.md
@@ -1,12 +1,18 @@
-文件上传功能、用户管理功能都只需要提供REST接口,在技术选型上,使用轻量级REST框架。开发新的微服务都涉及到配置微服务信息,写一个新的Main函数,这些公共步骤在文档前面已经描述,后续文档会省略这些内容。
+# 开发文件上传功能
+
+文件上传功能、用户管理功能都只需要提供REST接口。
 
 * 增加依赖关系
 
 ```
-    <dependency>
-      <groupId>org.apache.servicecomb</groupId>
-      <artifactId>solution-basic</artifactId>
-    </dependency>
+<dependency>
+  <groupId>org.apache.servicecomb</groupId>
+  <artifactId>solution-basic</artifactId>
+</dependency>
+<dependency>
+  <groupId>org.apache.servicecomb</groupId>
+  <artifactId>java-chassis-spring-boot-starter-standalone</artifactId>
+</dependency>
 ```
 
 * 定义服务接口
@@ -16,33 +22,34 @@
 ```
 @RestSchema(schemaId = "file")
 @RequestMapping(path = "/")
-public class FileServiceEndpoint {
-    @Autowired
-    private FileService fileService;
-
-    /**
-     * 上传文件接口,用户上传一个文件,返回文件ID。
-     */
-    @PostMapping(path = "/upload", produces = MediaType.TEXT_PLAIN_VALUE)
-    public String uploadFile(@RequestPart(name = "fileName") MultipartFile 
file) {
-        return fileService.uploadFile(file);
-    }
-
-    /**
-     * 删除文件接口。指定ID,返回删除成功还是失败.
-     */
-    @DeleteMapping(path = "/delete", produces = 
MediaType.APPLICATION_JSON_VALUE)
-    public boolean deleteFile(@RequestParam(name = "id") String id) {
-        return fileService.deleteFile(id);
-    }
+public class FileEndpoint {
+  @Autowired
+  private FileService fileService;
+  
+  /**
+   * upload and return file id
+   */
+  @PostMapping(path = "/upload", produces = MediaType.TEXT_PLAIN_VALUE)
+  public String uploadFile(@RequestPart(name = "fileName") MultipartFile file) 
{
+      return fileService.uploadFile(file);
+  }
+
+  /**
+   * delete file by id
+   */
+  @DeleteMapping(path = "/delete", produces = MediaType.APPLICATION_JSON_VALUE)
+  public boolean deleteFile(@RequestParam(name = "id") String id) {
+      return fileService.deleteFile(id);
+  }
 }
+
 ```
 
 
为了实现不同方式的文件存储,将实现抽象出来FileService。为了简单,当前只提供了本地文件实现。这个实现限制了该服务无法进行多实例部署。可以考虑使用对象存储服务器、分布式文件系统等满足存储要求。
 
 * 设置临时目录
 
-需要在microservice.yaml中增加servicecomb.uploads.directory配置项,指定临时目录的路径。需要保证目录有写权限。默认情况下如果没设置临时目录,不允许启用上传功能。如果使用网关,网关也需要增加这个配置项。
+需要增加 `servicecomb.uploads.directory` 
配置项,指定临时目录的路径。需要保证目录有写权限。默认情况下如果没设置临时目录,不允许启用上传功能。如果使用网关,网关也需要增加这个配置项。
 
 * 开发测试HTML,访问上传服务
 
diff --git 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/gateway-service.md
 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/gateway-service.md
index 4aa3d53..5f07d45 100644
--- 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/gateway-service.md
+++ 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/gateway-service.md
@@ -1,4 +1,6 @@
-这个章节中,介绍如何通过网关转发请求。java-chassis提供了非常灵活的网关服务,开发者能够非常简单的实现微服务之间的转发,网关拥有客户端一样的服务治理能力。同时,开发者可以使用vert.x暴漏的HTTP
 API,实现非常灵活的转发控制。
+# 开发网关
+
+这个章节中,介绍如何通过网关转发请求。Java Chassis 
提供了非常灵活的网关服务,开发者能够非常简单的实现微服务之间的转发,网关拥有客户端一样的服务治理能力。
 
 网关服务由一系列的VertxHttpDispatcher组成,开发者通过继承AbstractEdgeDispatcher,来实现自己的转发机制。
 
@@ -12,28 +14,56 @@
 
 ```
 public class ApiDispatcher extends AbstractEdgeDispatcher {
-    @Override
-    public int getOrder() {
-        return 10002;
-    }
-
-    @Override
-    public void init(Router router) {
-        String regex = "/api/([^\\/]+)/(.*)";
-        router.routeWithRegex(regex).handler(CookieHandler.create());
-        router.routeWithRegex(regex).handler(createBodyHandler());
-        
router.routeWithRegex(regex).failureHandler(this::onFailure).handler(this::onRequest);
-    }
-
-    protected void onRequest(RoutingContext context) {
-        Map<String, String> pathParams = context.pathParams();
-        String microserviceName = pathParams.get("param0");
-        String path = "/" + pathParams.get("param1");
-
-        EdgeInvocation invoker = new EdgeInvocation();
-        invoker.init(microserviceName, context, path, httpServerFilters);
-        invoker.edgeInvoke();
-    }
+  public static final String MICROSERVICE_NAME = "param0";
+
+  @Override
+  public int getOrder() {
+    return 10002;
+  }
+
+  @Override
+  public void init(Router router) {
+    String regex = "/api/([^\\/]+)/(.*)";
+    router.routeWithRegex(regex).handler(createBodyHandler());
+    
router.routeWithRegex(regex).failureHandler(this::onFailure).handler(this::onRequest);
+  }
+
+  protected void onRequest(RoutingContext context) {
+    String microserviceName = extractMicroserviceName(context);
+    String path = Utils.findActualPath(context.request().path(), 2);
+
+    requestByFilter(context, microserviceName, path);
+  }
+
+  @Nullable
+  private String extractMicroserviceName(RoutingContext context) {
+    return context.pathParam(MICROSERVICE_NAME);
+  }
+
+  protected void requestByFilter(RoutingContext context, String 
microserviceName, String path) {
+    HttpServletRequestEx requestEx = new 
VertxServerRequestToHttpServletRequest(context);
+    HttpServletResponseEx responseEx = new 
VertxServerResponseToHttpServletResponse(context.response());
+    InvocationCreator creator = new EdgeInvocationCreator(context, requestEx, 
responseEx,
+        microserviceName, path) {
+      @Override
+      protected Invocation createInstance() {
+        Invocation invocation = super.createInstance();
+        // get session id from header and cookie for debug reasons
+        String sessionId = context.request().getHeader("session-id");
+        if (sessionId != null) {
+          invocation.addContext("session-id", sessionId);
+        } else {
+          Cookie sessionCookie = context.request().getCookie("session-id");
+          if (sessionCookie != null) {
+            invocation.addContext("session-id", sessionCookie.getValue());
+          }
+        }
+        return invocation;
+      }
+    };
+    new RestProducerInvocationFlow(creator, requestEx, responseEx)
+        .run();
+  }
 }
 ```
 
@@ -43,105 +73,6 @@ public class ApiDispatcher extends AbstractEdgeDispatcher {
 
 * 通过网关:GET 
[http://localhost:9090/ui/porter-website/index.html](http://localhost:9090/ui/porter-website/index.html)
 
-UI静态页面信息不需要实现治理能力(服务治理能力需要契约,静态页面不存在接口契约),因此直接使用vert.x的API实现请求转发。在下面的代码中,还使用java
 chassis API做了服务发现,并实现了一个简单的RoundRobin负载均衡策略,从而允许porter-website也进行多实例部署。
-
-```
-public class UiDispatcher extends AbstractEdgeDispatcher {
-    private static Logger LOGGER = LoggerFactory.getLogger(UiDispatcher.class);
-
-    private static Vertx vertx = 
VertxUtils.getOrCreateVertxByName("web-client", null);
-
-    private static HttpClient httpClient = vertx.createHttpClient(new 
HttpClientOptions());
-
-    private Map<String, DiscoveryTree> discoveryTrees = new 
ConcurrentHashMapEx<>();
-
-    private AtomicInteger counter = new AtomicInteger(0);
-
-    @Override
-    public int getOrder() {
-        return 10001;
-    }
-
-    @Override
-    public void init(Router router) {
-        String regex = "/ui/([^\\/]+)/(.*)";
-        
router.routeWithRegex(regex).failureHandler(this::onFailure).handler(this::onRequest);
-    }
-
-    protected void onRequest(RoutingContext context) {
-        Map<String, String> pathParams = context.pathParams();
-
-        String microserviceName = pathParams.get("param0");
-        String path = "/" + pathParams.get("param1");
-
-        URI uri = chooseServer(microserviceName);
-
-        if (uri == null) {
-            context.response().setStatusCode(404);
-            context.response().end();
-            return;
-        }
-
-        // 使用HttpClient转发请求
-        HttpClientRequest clietRequest =
-            httpClient.request(context.request().method(),
-                    uri.getPort(),
-                    uri.getHost(),
-                    "/" + path,
-                    clientResponse -> {
-                        context.request().response().setChunked(true);
-                        
context.request().response().setStatusCode(clientResponse.statusCode());
-                        
context.request().response().headers().setAll(clientResponse.headers());
-                        clientResponse.handler(data -> {
-                            context.request().response().write(data);
-                        });
-                        clientResponse.endHandler((v) -> 
context.request().response().end());
-                    });
-        clietRequest.setChunked(true);
-        clietRequest.headers().setAll(context.request().headers());
-        context.request().handler(data -> {
-            clietRequest.write(data);
-        });
-        context.request().endHandler((v) -> clietRequest.end());
-    }
-
-    private URI chooseServer(String serviceName) {
-        URI uri = null;
-
-        DiscoveryContext context = new DiscoveryContext();
-        context.setInputParameters(serviceName);
-        DiscoveryTree discoveryTree = 
discoveryTrees.computeIfAbsent(serviceName, key -> {
-            return new DiscoveryTree();
-        });
-        VersionedCache serversVersionedCache = discoveryTree.discovery(context,
-                RegistryUtils.getAppId(),
-                serviceName,
-                DefinitionConst.VERSION_RULE_ALL);
-        Map<String, MicroserviceInstance> servers = 
serversVersionedCache.data();
-        String[] endpoints = asArray(servers);
-        if (endpoints.length > 0) {
-            int index = Math.abs(counter.getAndIncrement() % endpoints.length);
-            String endpoint = endpoints[index];
-            try {
-                uri = new URI(endpoint);
-            } catch (URISyntaxException e) {
-                LOGGER.error("", e);
-            }
-        }
-        return uri;
-    }
-
-    private String[] asArray(Map<String, MicroserviceInstance> servers) {
-        List<String> endpoints = new LinkedList<>();
-        for (MicroserviceInstance instance : servers.values()) {
-            endpoints.addAll(instance.getEndpoints());
-        }
-        return endpoints.toArray(new String[endpoints.size()]);
-    }
-}
-```
-
 
完成VertxHttpDispatcher开发后,需要通过SPI的方式加载到系统中,需要增加META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher配置文件,并将增加的两个实现写入该配置文件中。
 
 网关服务开发完成后,所有的用户请求都可以通过网关来发送。开发者通过通过设置防火墙等机制,限制用户直接访问内部服务,保证内部服务的安全。
-
diff --git 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/https.md 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/https.md
index 2b488d9..76c615b 100644
--- 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/https.md
+++ 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/https.md
@@ -1,3 +1,5 @@
+# 网关HTTPS安全配置
+
 
HTTP协议已逐渐被标记为不安全,配置HTTPS可以防止用户数据被窃取和篡改,提升了安全性。考虑到性能的影响,我们只在网关使用HTTPS接入,内部服务之间仍然使用HTTP。
 
 
使用HTTPS之前,需要准备证书。通常是向权威机构申请,这样的证书才会被浏览器等设备标记为可信。在这个例子中,我们使用通过工具已经生成好的证书。并且将自己的证书通过PKCS12格式存储在server.p12文件中,将CA的证书使用JKS格式存储在trust.jks中。
@@ -7,7 +9,7 @@ HTTP协议已逐渐被标记为不安全,配置HTTPS可以防止用户数据
 ```
 servicecomb:
   rest:
-    address: 0.0.0.0:9090 ?sslEnabled=true
+    address: 0.0.0.0:9090?sslEnabled=true
 ```
 
 
然后增加ssl相关的配置。下面的配置包含了TLS的协议、是否认证对端以及证书和密码信息。其中```EdgeSSLCustom```用于证书路径和证书密码的转换,不实现的时候,默认从当前目录读取证书文件,证书的密码明文存储。当业务需要做一些高级安全特性,比如密码保护的时候,可以通过扩展这个类实现。
@@ -32,5 +34,3 @@ ssl.sslCustomClass: 
org.apache.servicecomb.samples.porter.gateway.EdgeSSLCustom
 https://localhost:9090/ui/porter-website/index.html
 ```
 
-
-
diff --git 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/porter-website.md
 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/porter-website.md
index 69fbe4c..71ad2ec 100644
--- 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/porter-website.md
+++ 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/porter-website.md
@@ -12,15 +12,14 @@
 这几种方式都被广泛使用。
 
 ## 将静态页面直接部署到gateway-service
-在[porter_lightweight](https://github.com/apache/servicecomb-samples/tree/master/porter_lightweight)项目中,采用了将静态页面直接部署到gateway-service的方式,这种方式简洁高效。这种方式的核心代码是StaticWebpageDispatcher。它采用vert.x提供的静态页面功能,直接挂载了静态页面服务。
+在 porter 
项目中,采用了将静态页面直接部署到gateway-service的方式,这种方式简洁高效。这种方式的核心代码是StaticWebpageDispatcher。它采用vert.x提供的静态页面功能,直接挂载了静态页面服务。
 
 ```
 public class StaticWebpageDispatcher implements VertxHttpDispatcher {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(StaticWebpageDispatcher.class);
 
-  private static final String WEB_ROOT = DynamicPropertyFactory.getInstance()
-      .getStringProperty("gateway.webroot", "/var/static")
-      .get();
+  private static final String WEB_ROOT = LegacyPropertyFactory
+      .getStringProperty("gateway.webroot", "/var/static");
 
   @Override
   public int getOrder() {
@@ -30,80 +29,11 @@ public class StaticWebpageDispatcher implements 
VertxHttpDispatcher {
   @Override
   public void init(Router router) {
     String regex = "/ui/(.*)";
-    StaticHandler webpageHandler = StaticHandler.create();
-    webpageHandler.setWebRoot(WEB_ROOT);
+    StaticHandler webpageHandler = StaticHandler.create(WEB_ROOT);
     LOGGER.info("server static web page for WEB_ROOT={}", WEB_ROOT);
     router.routeWithRegex(regex).failureHandler((context) -> {
       LOGGER.error("", context.failure());
     }).handler(webpageHandler);
   }
-
 }
 ```
-
-## 静态页面通过Tomcat、Spring Boot等Web容器部署,并注册
-
-在架构图中,界面的请求需要被网关转发,并且需要支持多实例部署,因此界面服务需要增加的功能是服务注册和发现。有两种方法集成和使用J2EE:
-
-1. 运行于独立的web服务器中,如tomcat等。
-
-2. 运行于Spring Boot的Embedded Tomcat中。
-
-
-在Spring Boot中提供静态页面服务,核心问题是解决服务注册、发现能力。在Spring Boot的Embeded 
Tomcat中使用ServiceComb的服务注册发现,需要完成如下步骤:
-
-* 增加依赖关系
-
-依赖关系定义了对于Spring Boot的依赖和java-chassis的依赖。
-
-```
-<dependency>
-
-  <groupId>org.apache.servicecomb</groupId>
-
-  <artifactId>java-chassis-spring-boot-starter-servlet</artifactId>
-
-</dependency>
-```
-
-* 配置微服务信息\(microservice.yaml\)
-
-需要注意配置项servicecomb.rest.address的端口与application.yml的server.port保持一致。application.yml是Spring
 Boot的配置文件,用于指定Embeded 
Tomcat的监听端口。microservice.yam的信息用于服务注册。另外也需要注意一下配置项servicecomb.rest.servlet.urlPattern,当使用@EnableServiceComb时,会加载REST框架org.apache.servicecomb.transport.rest.servlet.
 
RestServlet,而且默认接管了/\*的请求。在我们的场景下,仅仅需要提供web页面,不需要提供REST服务,这个配置项的含义就是将它的路径改为一个和静态页面不冲突的路径,以保证静态页面能够被正常访问。
-
-```
-APPLICATION_ID: porter
-service_description:
-  name: porter-website
-  version: 0.0.1
-
-servicecomb:
-  rest:
-    address: 0.0.0.0:9093
-    servlet:
-      urlPattern: /servicecomb/rest/*
-```
-
-* 增加静态页面
-
-按照Spring Boot的惯例,静态页面需要放到源代码的resources/static目录。项目开始前,增加了如下静态页面和目录:
-
-```
-css
-js
-index.html
-```
-
-* 使用@EnableServiceComb启用注册发现
-
-```
-@SpringBootApplication
-@EnableServiceComb
-public class WebsiteMain {
-    public static void main(final String[] args) {
-        SpringApplication.run(WebsiteMain.class, args);
-    }
-}
-```
-
-经过以上的步骤,界面服务就开发完成了。通过运行WebsiteMain,就可以通过[http://localhost:9093](http://localhost:9093)
 来访问。
-
diff --git 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/user-service.md
 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/user-service.md
index ee7970f..12e0436 100644
--- 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/user-service.md
+++ 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/user-service.md
@@ -1,3 +1,5 @@
+# 使用MyBatis访问数据库
+
 访问数据库可以使用第三方提供的组件。这里选择了MyBatis说明如何访问数据库。开发者也可以直接参考:
 
 
[http://www.mybatis.org/spring/zh/index.html](http://www.mybatis.org/spring/zh/index.html)
@@ -33,9 +35,14 @@
 
 ```
 <dependency>
-  <groupId>org.mybatis</groupId>
-  <artifactId>mybatis</artifactId>
-  <version>3.4.5</version>
+  <groupId>org.mybatis.spring.boot</groupId>
+  <artifactId>mybatis-spring-boot-starter</artifactId>
+  <exclusions>
+    <exclusion>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-logging</artifactId>
+    </exclusion>
+  </exclusions>
 </dependency>
 <dependency>
   <groupId>mysql</groupId>
@@ -45,11 +52,6 @@
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-dbcp2</artifactId>
 </dependency>
-<dependency>
-  <groupId>org.mybatis</groupId>
-  <artifactId>mybatis-spring</artifactId>
-  <version>1.3.0</version>
-</dependency>
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-jdbc</artifactId>
@@ -71,40 +73,42 @@
 
 * 配置数据源和SqlSessionFactory
 
-数据源使用DBCP2。SqlSessionFactory里面指定了dataSource和configLocation两个属性,并新增加了mybatis-config.xml文件,用于配置mapper文件的路径。
 在本微服务场景中,只需要使用简单的数据库连接和简单事务管理,如果需要使用复杂的事务管理,还需要配置XA数据源和相关的事务管理器。 
有关MyBatis的Configuration更加详细的信息可以参考:[http://www.mybatis.org/mybatis-3/configuration.html](http://www.mybatis.org/mybatis-3/configuration.html)
 。
+本例子使用Spring Data Source, 只需要在配置文件中加上配置信息:
 
 ```
-<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" 
destroy-method="close">
-  <property name="driverClassName" value="${db.url:com.mysql.jdbc.Driver}" />
-  <property name="url" value="${db.url:jdbc:mysql://localhost/porter_user_db}" 
/>
-  <property name="username" value="${db.username:root}" />
-  <property name="password" value="${db.password:}" />
-</bean>
-
-<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
-  <property name="dataSource" ref="dataSource" />
-  <property name="configLocation" 
value="classpath:/config/mybatis-config.xml"></property>
-</bean>
+spring:
+  datasource:
+    url: jdbc:mysql://localhost/porter_user_db
+    username: root
+    password: root
+    driver-class-name: com.mysql.jdbc.Driver
 ```
 
 * 书写Mapper文件
 
-涉及到JAVA的Mapper定义UserMapper,XML中定义SQL与JAVA的映射关系UserMapper.xml。定义完成后,需要将内容配置到Mybatis的扫描路径和Spring的扫描路径中,涉及文件mybatis-config.xml和user.bean.xml。
+涉及到JAVA的Mapper定义UserMapper。
 
 ```
-### mybatis-config.xml
-<configuration>
-    <mappers>
-        <mapper resource="config/UserMapper.xml"/>
-    </mappers>
-</configuration>
-
-### user.bean.xml
-<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
-    <property name="mapperInterface"
-        value="org.apache.servicecomb.samples.porter.user.dao.UserMapper" />
-    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
-</bean>
+@Mapper
+public interface UserMapper {
+  @Insert("""
+      insert into T_USER (ID, USER_NAME, PASSWORD, ROLE_NAME)
+        values(#{id,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR},
+               #{password,jdbcType=VARCHAR}, #{roleName,jdbcType=VARCHAR})""")
+  void createUser(UserInfo userInfo);
+
+  @Select("""
+      select ID, USER_NAME, PASSWORD, ROLE_NAME
+        from T_USER where USER_NAME = #{userName,jdbcType=VARCHAR}""")
+  @Results({
+      @Result(property = "id", column = "ID"),
+      @Result(property = "userName", column = "USER_NAME"),
+      @Result(property = "password", column = "PASSWORD"),
+      @Result(property = "roleName", column = "ROLE_NAME")
+  })
+  UserInfo getUserInfo(String userName);
+}
+
 ```
 
 ## 设计用户服务
diff --git 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/user-story.md
 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/user-story.md
index 61f01e3..07fc18d 100644
--- 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/user-story.md
+++ 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/user-story.md
@@ -1,6 +1,10 @@
-开始前,给应用取一个名字porter。
+# User Story
 
-![](usercase.png)
+开始前,给应用取一个名字 `porter`。 
 
+`porter` 应用主要实现如下几个用户故事:
 
+* 输入用户名、密码登录系统。
+* 上传文件到系统中。系统会检查用户的权限,如果用户被授权,文件上传成功并返回文件标识;如果用户未被授权,则返回错误。
+* 删除文件。输入文件标识删除文件。删除文件也会检查用户是否被授权。
 
diff --git 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/usercase.png
 
b/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/usercase.png
deleted file mode 100644
index 00106ac..0000000
Binary files 
a/java-chassis-reference/zh_CN/docs/featured-topics/application-porter/usercase.png
 and /dev/null differ
diff --git a/java-chassis-reference/zh_CN/mkdocs.yml 
b/java-chassis-reference/zh_CN/mkdocs.yml
index e3753d5..b655a3f 100644
--- a/java-chassis-reference/zh_CN/mkdocs.yml
+++ b/java-chassis-reference/zh_CN/mkdocs.yml
@@ -11,8 +11,8 @@ nav:
   - 微服务系统架构: start/architecture.md
   - 安装本地开发环境: start/development-environment.md
   - 开发第一个微服务应用: start/first-sample.md
-  - 完整例子-bmi应用: featured-topics/application-bmi.md
-  - 完整例子-porter应用: featured-topics/application-porter.md
+  - BMI应用-使用服务治理: featured-topics/application-bmi.md
+  - PORTER应用-典型开发场景: featured-topics/application-porter.md
 - 微服务定义: build-provider/definition/service-definition.md
 - Spring Boot集成Java Chassis:
     - Spring Boot集成Java Chassis介绍: spring-boot/introduction.md


Reply via email to