微服务开发实战微服务(microservices)互金

微服务从零开始之登录与注册一

2016-11-29  本文已影响233人  老瓦在霸都

�概述

任何一个微服务需要基本的安全保证, 也是遵循AAA原则: 鉴证,授权和计帐

让我们从需求分析到代码实现,尽量啰嗦地来说说怎么做一个看似简单的登录注册模块, 假设该服务叫做 Checklist 我的清单

需求分析

用例 Use case

除了使用绘图工具和画用例图, 还有有�几种方法通过脚本来�生成用例图

一是使用在线网站 yuml.me

https://yuml.me/608ca377

use case

UML 生成脚本如下

[User]-(Sign In)
[User]-(Sign Out)
[User]-(Sign Up)
[User]-(Forget Password)
[User]-(Change Password)
(Sign In)>(Remember Me)
(Sign Up)>(Send Verification Email)
(Forget Password)>(Send Reset Password Email)
(Change Password)<(Send Reset Password Email)
[Admin]^[User]
[Admin]-(Add User)
[Admin]-(Delete User)
[Admin]-(Lock User)
[Admin]-(Change Password Policy)

�二是使用是通过 plantuml 来生成

http://plantuml.com/ 上下载 plantuml.jar , 然后用如下命令生成用例图

java -jar plantuml.jar usecase.txt

示例UML 生成脚本如下

@startuml

User -> (Sign In)
User --> (Sign Out) 
User --> (Sign Up)
User --> (activate)
User --> (forget/reset password)
:Admin: ---> (lock user)
:Admin: ---> (add user) 
:Admin: ---> (delete user) 

@enduml

三是使用graphviz

先安装graphviz, 再运行如下命令

dot usecase1.gv -Tpng -o usecase1.png

示例UML生成脚本如下

digraph G {
    rankdir=LR;

    subgraph clusterUser {label="User"; labelloc="b"; peripheries=0; user};
    
    user [shapefile="stick.png", peripheries=0];

    signin [label="Sign In", shape=ellipse];

    signout [label="Sign Out", shape=ellipse];

    signup [label="Sign Up", shape=ellipse];

    user->signin [arrowhead=none];

    user->signout [arrowhead=none];

    user->signup [arrowhead=none];
}

用户故事 User Story

User Story 讲究 INVEST 原则

Sign Up 注册

  1. 作为一个未注册用户, 我想输入我的电子邮件地址和密码,注册到 Checklist
    1.1 我必须输入合法和邮件地址,符合密码策略的密码以及一致的验证码进行注册
    默认的密码策略是最低8个字符, �必须包含大小写字母和至少一个数字

| # | Story | Priority | Estimation | Deadline| Comments |
|---|---|---|---|---|
| 1.1.1 | �生成验证码 |---|---|---|--- |
| 1.1.2 | 显示注册表单|---|---|---|--- |
| 1.1.3 | 邮件地址格式验证|---|---|---|--- |
| 1.1.4 | 比较两次输入的密码是否相同|---|---|---|--- |
| 1.1.5 | 验证密码是否符合密码策略|---|---|---|--- |
| 1.1.6 | 验证输入的验证码|---|---|---|--- |
| 1.1.7 | 检查是否已有相同的邮件地址存在|---|---|---|--- |
| 1.1.8 | 输入验证无误后存入数据库,状态为pending|---|---|---|--- |
| 1.1.9 | 生成此用户的激活链接|---|---|---|--- |
| 1.1.10 | 向注册邮箱发送一封确认邮件|---|---|---|--- |

1.2 我的注册邮箱会收到一封验证邮件, 提示我点击注册连接, �从而激活我的注册帐户

1.3 当我完成激活后会自动跳到 Checklist 的首页, 提示我进行登录

实现

这次我们用Java实现,选择的框架是Spring Boot, 先从最笨最直接的方法入手, 之后再看看相关的框架 Spring Security 和 Apache Shiro 是怎么做的

Model

model

View

字段 �控件
username text
email email
password password
confirmPassword password
rememberMe checkbox
forgetPassword link

创建项目

spring boot starter

或者直接用 Spring Cli 直接生成

spring init --build=maven --java-version=1.8 --dependencies=web --packaging=jar --groupId=com.github.walterfan --artifactId=checklist

在实践中始终牢记 三个基本点

领域模型很简单
Register
User
Role

测试用例也简单

  1. 注册
  2. 激活
  3. 登录

度量就只记录

  1. 注册次数
  2. 激活次数
  3. 性能数据

代码结构

废话不多说,上代码 checklist source codes on github

code structure

数据库我们选用两个

  1. h2 作为测试数据库
               <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
  1. mysql 作为产品数据库
                 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.41</version>
        </dependency>

表现层

表现层选用 Freemarker 作为后端模板, 前端选用 AngularJS + BootStrap

freemarker 是比较流行的后端页面生成的模板引擎, 这所以不用 JSP 和 JSF, 就是为了不想在后端模板层面引入太多逻辑和不必要的复杂性, freemarker 就只干模板引擎该干的事

在 src/main/resources/templates 做如下模板

主要的 Freemarker 模板 layout.ftl 如下

<#macro myLayout>
<!DOCTYPE html>
<html >
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <meta name="description" content="Kanban">
    <meta name="author" content="Walter">
    <link rel="icon" href="./images/favicon.ico">

    <title>Check List</title>

    <!-- Bootstrap core CSS -->
    <link href="./css/bootstrap.min.css" rel="stylesheet">

    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <link href="./css/ie10-viewport-bug-workaround.css" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="./css/app.css" rel="stylesheet">
    <link href="./css/jumbotron-narrow.css" rel="stylesheet">
    <script src="./js/vendor/jquery-1.11.2.min.js"></script>

    <script src="./js/vendor/angular.js"></script>
    <script src="./js/vendor/angular-sanitize.js"></script>
    <script src="./js/vendor/angular-resource.js"></script>
    <script src="./js/vendor/ui-bootstrap.js"></script>
    <script src="./js/vendor/ui-bootstrap-tpls.js"></script>

    <script src="./js/vendor/ngDialog.min.js"></script>

    <script src="./js/app.js"></script>

</head>

<body>

<div class="container" >
    <#include "header.ftl"/>

    <div class="panel panel-default" >
    <#nested/>
    </div>

    <#include "footer.ftl"/>

</div> <!-- /container -->


<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="./js/vendor/ie10-viewport-bug-workaround.js"></script>
</body>
</html>

</#macro>

首页

home page

源码 index.ftl 如下

<#import "layout.ftl" as layout>
<@layout.myLayout>
<div class="jumbotron">
    <h2>Checklist</h2>
    <p class="lead">
        Checklist for your work and life
    </p>
    <p><a class="btn btn-lg btn-success" href="/checkist/add" role="button">Add a Check list</a></p>
</div>
<script>
    $('li:eq(0)').addClass('active');
</script>
</@layout.myLayout>
login page

登录页面

register page

源码 login.ftl 如下

<#import "layout.ftl" as layout>
<@layout.myLayout>
<!-- refer to http://bootsnipp.com/snippets/featured/login-and-register-tabbed-form -->
<div class="page-header text-center">
    <div class="row nav nav-tabs nav-justified">
        <div class="col-xs-6">
            <a href="#" class="active" id="login-form-link">Login</a>
        </div>
        <div class="col-xs-6">
            <a href="#" id="register-form-link">Register</a>
        </div>
    </div>
</div>

<div class="panel-body" ng-app="myApp" ng-controller="myController">
    <div class="row">
        <div class="col-lg-12">
            <form id="login-form" class="form-horizontal" ng-submit="submitLoginForm()">
                <div class="form-group">
                    <input type="text" name="username" id="username" tabindex="1" class="form-control" placeholder="Username" value=""  ng-model="user.username">
                </div>
                <div class="form-group">
                    <input type="password" name="password" id="password" tabindex="2" class="form-control" placeholder="Password"  ng-model="user.password">
                </div>
                <div class="form-group text-center">
                    <input type="checkbox" tabindex="3" class="" name="remember" id="remember">
                    <label for="remember"> Remember Me</label>
                </div>
                <div class="form-group">
                    <div class="row">
                        <div class="col-sm-6 col-sm-offset-3">
                            <input type="submit" name="login-submit" id="login-submit" tabindex="4" class="form-control btn btn-login" value="Log In">
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <div class="row">
                        <div class="col-lg-12">
                            <div class="text-center">
                                <a href="http://phpoll.com/recover" tabindex="5" class="forgot-password">Forgot Password?</a>
                            </div>
                        </div>
                    </div>
                </div>
            </form>
            <form id="register-form" class="form-horizontal" style="display: none;" ng-submit="submitRegisterForm()">
                <div class="form-group">
                    <input type="text" name="username" id="username" tabindex="1" class="form-control" placeholder="Username" value=""  ng-model="user.username">
                </div>
                <div class="form-group">
                    <input type="email" name="email" id="email" tabindex="1" class="form-control" placeholder="Email Address" value=""  ng-model="user.email">
                </div>
                <div class="form-group">
                    <input type="password" name="password" id="password" tabindex="2" class="form-control" placeholder="Password"  ng-model="user.password">
                </div>
                <div class="form-group">
                    <input type="password" name="confirm-password" id="confirm-password" tabindex="2" class="form-control" placeholder="Confirm Password"  ng-model="user.passwordConfirmation">
                </div>
                <div class="form-group">
                    <div class="row">
                        <div class="col-sm-6 col-sm-offset-3">
                            <input type="submit" name="register-submit" id="register-submit" tabindex="4" class="form-control btn btn-register" value="Register Now">
                        </div>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div> <!-- panel-body end -->

<script>
    $('li:eq(1)').addClass('active');
</script>
</@layout.myLayout>

注: 我不太擅长前端页面的界面设计, 这里参考了 http://bootsnipp.com/snippets/jvgVX 的示例, 一个很有用的基于 bootstrap 的样式主题设计网站

当用户点击注册页面, 填写所需字段, 并提交表单时, 用 Angular JS 向后台提交, 代码如下

'use strict';

$(function() {

    $('#login-form-link').click(function(e) {
        $("#login-form").delay(100).fadeIn(100);
        $("#register-form").fadeOut(100);
        $('#register-form-link').removeClass('active');
        $(this).addClass('active');
        e.preventDefault();
    });
    $('#register-form-link').click(function(e) {
        $("#register-form").delay(100).fadeIn(100);
        $("#login-form").fadeOut(100);
        $('#login-form-link').removeClass('active');
        $(this).addClass('active');
        e.preventDefault();
    });

});

// Defining angularjs application.
var myApp = angular.module('myApp', []);
// Controller function and passing $http service and $scope var.
myApp.controller('myController', function($scope, $http) {
    // create a blank object to handle form data.
    $scope.user = {};
    // calling our submit function.
    $scope.submitRegisterForm = function() {
        var postData = {
            username:$scope.user.username,
            email: $scope.user.email,
            password: $scope.user.password,
            passwordConfirmation: $scope.user.passwordConfirmation
        };
        $http({
            method  : 'POST',
            url     : '/checklist/api/v1/users/register',
            data    : postData,
            headers : {'Content-Type': 'application/json'}
        })
            .success(function(data) {
                if (data.errors) {
                    // Showing errors.
                    $scope.errors = data.errors;
                } else {
                    $scope.message = data.message;
                }
            });
    };

    $scope.submitLoginrForm = function() {
        var postData = {
            email: $scope.user.email,
            password: $scope.user.password,
        };
        $http({
            method  : 'POST',
            url     : '/checklist/api/v1/users/login',
            data    : postData,
            headers : {'Content-Type': 'application/json'}
        })
            .success(function(data) {
                if (data.errors) {
                    // Showing errors.
                    $scope.errors = data.errors;
                } else {
                    $scope.message = data.message;
                }
            });
    };
});

好了表现层包括前端的代码大致搞定了, 现在开始写后端的 Java web service 代码, 参见 微服务从零开始之登录与注册二

上一篇下一篇

猜你喜欢

热点阅读