16、微服务架构下的电商系统开发实践

微服务架构下的电商系统开发实践

1. Auth 微应用

Auth 微应用用于处理不同场景下的用户认证,它是使用有状态和无状态方法对用户进行认证的主要入口。核心模块已经提供了检查用户是否已认证的中间件以及与授权相关的中间件,这些功能可在任何模块或微应用中使用。

1.1 定义类

创建 Auth 微应用的步骤如下:
1. 创建一个名为 apps/auth/index.js 的新微应用文件。
2. 添加以下基础内容:

'use strict'
const express = require('express');
const router = express.Router();
const Controller = require('./controller');
class Auth {
}
  1. 定义构造函数:
constructor(config, core, app) {
    this.core = core;
    this.controller = new Controller(core);
    this.app = app;
    this.router = router;
    this.rootUrl = '/auth';
    this.regiterRoutes();
    this.app.use(this.rootUrl, this.router);
}

这里为微应用定义了一个基础 URL,并将路由挂载到主 Express 应用上,同时创建了 Auth 微应用中使用的控制器实例。

  1. 注册所有必要的路由:
regiterRoutes() {
    this.router.post('/register', this.controller.register);
    /**
     *  Stateful authentication
     */
    this.router.post('/signin', this.controller.signin);
    this.router.get('/signout', this.controller.signout);
    /**
     *  Stateless authentication
     */
    this.router.post('/basic', this.controller.basic);
}
  1. 在主 server.js 文件中初始化微应用:
const Auth = require('./apps/auth');
let auth = new Auth(config, core, app);
1.2 控制器

将控制器转换为名为 AuthController 的类,并暴露以下方法:
1. 使用有状态认证策略让用户登录:

signin(req, res, next) {
    passport.authenticate('local', (err, user, info) => {
        if (err || !user) {
            return res.status(400).json(info);
        }
        req.logIn(user, function(err) {
            if (err) {
                return next(err);
            }
            res.status(200).json(user);
        });
    })(req, res, next);
}
  1. 使用无状态策略进行认证:
basic(req, res, next) {
    passport.authenticate('basic', (err, user, info) => {
        if (err) {
            return next(err);
        }
        if (!user) {
            return res.status(400).json({ message: 'Invalid email or password.' });
        }
        Token.generate({ user: user.id }, (err, token) => {
            if (err) {
                return next(err);
            }
            if (!token) {
                return res.status(400).json({ message: 'Invalid email or password.' });
            }
            const result = user.toJSON();
            result.token = _.pick(token, ['hash', 'expiresAt']);
            res.json(result);
        });
    })(req, res, next);
}
  1. 在系统中注册用户:
register(req, res, next) {
    const userData = _.pick(req.body, 'name', 'email', 'password');
    User.register(userData, (err, user) => {
        if (err && (11000 === err.code || 11001 === err.code)) {
            return res.status(400).json({ message: 'E-mail is already in use.' });
        }
        if (err) {
            return next(err);
        }
        // just in case :)
        delete user.password;
        delete user.passwordSalt;
        res.json(user);
    });
}
2. 暴露 API

核心业务逻辑需要通过某种方式进行访问,RESTful API 是一个不错的选择。为了更好地理解和开发整个应用,这里仅展示 API 的一部分。

2.1 Api 类

创建微应用类 apps/api/index.js ,并添加以下内容:

'use strict';
const ProductsRoutes = require('./routes/products');
const ProductController = require('./controllers/product');
class Api {
    constructor(config, core, app) {
        let productController = new ProductController(core);
        let productRoutes = new ProductsRoutes(core, productController);
        this.config = config;
        this.core = core;
        this.app = app;
        this.root = app.get('root');
        this.rootUrl = '/api';
        this.app.get('/api/status', (req, res, next) => {
            res.json({ message: 'API is running.' });
        });
        this.app.use(this.rootUrl, productRoutes.router);
    }
}
module.exports = Api;

该部分将 ProductRoutes 暴露的路由挂载到主 Express 应用上, ProductRoutes 类需要一个 ProductController 作为必需参数。

2.2 产品控制器

实现产品控制器的步骤如下:
1. 创建一个名为 apps/api/controller/product.js 的新文件。
2. 定义控制器:

'use strict';
const _ = require('lodash');
let productCatalog;
class ProductsController {
    constructor(core) {
        this.core = core;
        productCatalog = new core.services.ProductCatalog();
    }
    create(req, res, next) {
        productCatalog.add(req.body, (err, product) => {
            if (err && err.name === 'ValidationError') {
                return res.status(400).json(err);
            }
            if (err) {
                return next(err);
            }
            res.status(201).json(product);
        });
    }
    getAll(req, res, next) {
        const limit = +req.query.limit || 10;
        const skip = +req.query.skip || 0;
        const query = {} // you cloud filter products
        productCatalog.list(query, limit, skip, (err, products) => {
            if (err) {
                return next(err);
            }
            res.json(products);
        });
    }
    getOne(req, res, next) {
        productCatalog.details(req.params.sku, (err, product) => {
            if (err) {
                return next(err);
            }
            res.json(product);
        });
    }
}
2.3 产品路由

将路由定义在 apps/api/routes/products.js 文件中:

'use strict';
const express = require('express');
const router = express.Router();
class ProductsRoutes {
    constructor(core, controller) {
        this.core = core;
        this.controller = controller;
        this.router = router;
        this.authBearer = this.core.authentication.bearer;
        this.regiterRoutes();
    }
    regiterRoutes() {
        this.router.post(
            '/products',
            this.authBearer(),
            this.controller.create
        );
        this.router.get(
            '/products',
            this.authBearer(),
            this.controller.getAll
        );
        this.router.get(
            '/products/:sku',
            this.authBearer(),
            this.controller.getOne
        );
    }
}
module.exports = ProductsRoutes;

这里使用了核心模块中的 bearer 认证中间件来检查用户是否有有效的令牌,其函数体如下:

function bearerAuthentication(req, res, next) {
    return passport.authenticate('bearer', { session: false });
}
3. 共享资源

为了避免在各个应用中复制相同的静态资源,创建一个微应用来提供所有共享资源。每个想要提供静态文件的微应用都可以有一个单独的公共文件夹,将所有共享静态资源移动到内部公共文件夹中。

文件夹结构如下:

apps/
-- shared/
---- public
------ assets/
---- index.js

index.js 文件内容如下:

'use strict';
const path = require('path');
const serveStatic = require('serve-static');
class Shared {
    constructor(config, core, app) {
        this.app = app;
        this.root = app.get('root');
        this.rootUrl = '/';
        this.serverStaticFiles();
    }
    serverStaticFiles() {
        let folderPath = path.resolve(this.root, __dirname, './public');
        this.app.use(this.rootUrl, serveStatic(folderPath));
    }
}
module.exports = Shared;
4. 管理部分

电商解决方案通常有一个管理部分,用于管理产品和库存。本应用的管理部分将使用 Angular 2 构建。

4.1 管理微应用

创建文件 apps/admin/index.js ,内容如下:

'use strict';
const path = require('path');
const serveStatic = require('serve-static');
class Admin {
    constructor(config, core, app) {
        this.app = app;
        this.root = app.get('root');
        this.rootUrl = '/admin';
        this.serverStaticFiles();
    }
    serverStaticFiles() {
        let folderPath = path.resolve(this.root, __dirname, './public');
        this.app.use(this.rootUrl, serveStatic(folderPath));
    }
}
module.exports = Admin;

该类定义了管理微应用,并使用 serverStaticFiles() 方法将公共文件夹的内容暴露给外部使用,文件服务挂载在 /admin URL 路径上。

4.2 修改认证模块

管理应用使用令牌来授予对 API 端点的访问权限,因此需要对 apps/admin/public/src/auth/auth-http.ts 中的 AuthHttp 服务进行一些更改:

private request(requestArgs: RequestOptionsArgs, additionalArgs?: RequestOptionsArgs) {
    let opts = new RequestOptions(requestArgs);
    if (additionalArgs) {
        opts = opts.merge(additionalArgs);
    }
    let req:Request = new Request(opts);
    if (!req.headers) {
        req.headers = new Headers();
    }
    if (!req.headers.has('Authorization')) {
        req.headers.append('Authorization', `Bearer ${this.getToken()}`);
    }
    return this._http.request(req).catch((err: any) => {
        if (err.status === 401) {
            this.unauthorized.next(err);
        }
        return Observable.throw(err);
    });
}
private getToken() {
    return localStorage.getItem('token');
}

AuthService 中,存储当前用户及其令牌并持久化到本地存储:

public setCurrentUser(user: any) {
    this.currentUser.next(user);
}
private _initSession() {
    let user = this._deserialize(localStorage.getItem('currentUser'));
    this.currentUser = new BehaviorSubject<Response>(user);
    // persist the user to the local storage
    this.currentUser.subscribe((user) => {
        localStorage.setItem('currentUser', this._serialize(user));
        localStorage.setItem('token', user.token.hash || '');
    });
}
5. 产品管理

在后端创建了服务并暴露了 API 来管理产品,在客户端需要创建一个模块来消费该 API 并进行不同的操作。

5.1 产品服务

创建文件 apps/admin/public/src/services/product.service.ts ,基础内容如下:

import { Injectable } from 'angular2/core';
import { Http, Response, Headers } from 'angular2/http';
import { Observable } from 'rxjs/Observable';
import { ProductService } from './product.service';
import { contentHeaders } from '../common/headers';
import { Product } from './product.model';
type ObservableProducts = Observable<Array<Product>>;
@Injectable()
export class ProductService {
    public products: ObservableProducts;
    private _authHttp: AuthHttp;
    private _productsObservers: any;
    private _dataStore: { products: Array<Product> };
    constructor(authHttp: Http) {
        this._authHttp = authHttp;
        this.products = new Observable(observer => this._productsObservers = observer).share();
        this._dataStore = { products: [] };
    }
    getAll() {
        this._authHttp
        .get('/api/products', { headers: contentHeaders })
        .map((res: Response) => res.json())
        .subscribe(products => {
            this._dataStore.products = products;
            this._productsObservers.next(this._dataStore.products);
        });
    }
}
5.2 列出产品

创建组件 apps/admin/public/product/components/product-list.component.ts 来列出所有可用产品:

import { Component, OnInit } from 'angular2/core';
import { ProductService } from '../product.service';
import { Router, RouterLink } from 'angular2/router';
import { Product } from '../product.model';
@Component({
    selector: 'product-list',
    directives: [RouterLink],
    template: `
      <div class="product-list row">
        <h2 class="col">Products list</h2>
        <div *ngIf="products.length === 0" class="empty-product-list col">
          <h3>Add your first product to you catalog</h3>
        </div>
        <div class="col col-25">
          <a href="#" [routerLink]="['ProductCreate']" class="add-product-sign">+</a>
        </div>
        <div *ngFor="#product of products" class="col col-25">
          <img src="http://placehold.it/208x140?text=product+image&txtsize=18" />
          <h3>
            <a href="#"
              [routerLink]="['ProductEdit', { sku: product.sku }]">
              {{ product.title }}
            </a>
            </h3>
        </div>
      </div>
    `
})
export class ProductListComponent implements OnInit {
    public products: Array<Product> = [];
    private _productService: ProductService;
    constructor(productService: ProductService) {
        this._productService = productService;
    }
    ngOnInit() {
        this._productService.products.subscribe((products) => {
            this.products = products
        });
        this._productService.getAll();
    }
}
5.3 主产品组件

创建 apps/admin/public/src/product/product.component.ts 来配置产品的路由:

import { Component } from 'angular2/core';
import { RouteConfig, RouterOutlet } from 'angular2/router';
import { ProductListComponent } from './product-list.component';
import { ProductEditComponent } from './product-edit.component';
import { ProductCreateComponent } from './product-create.component';
@RouteConfig([
  { path: '/', as: 'ProductList', component: ProductListComponent, useAsDefault: true },
  { path: '/:sku', as: 'ProductEdit', component: ProductEditComponent },
  { path: '/create', as: 'ProductCreate', component: ProductCreateComponent }
])
@Component({
    selector: 'product-component',
    directives: [
      ProductListComponent,
      RouterOutlet
    ],
    template: `
      <div class="col">
        <router-outlet></router-outlet>
      </div>
    `
})
export class ProductComponent {
    constructor() {}
}
6. 订单处理

系统需要处理订单,订单通常有几种状态,如下表所示:
| 名称 | 状态 | 描述 |
| -------- | --------- | ------------------------------------------------------------ |
| Pending | pending | 订单已收到(通常未付款)。 |
| Failed | failed | 出现问题,例如付款失败或被拒绝。 |
| Processing | processing | 订单正在等待履行。 |
| Completed | completed | 订单已履行并完成,通常无需进一步操作。 |
| On-hold | on_hold | 库存已减少,但等待进一步确认,例如付款。 |
| Cancelled | cancelled | 订单被客户或管理员取消。 |

本应用仅支持其中几种状态:pending、processing、cancelled 和 completed。

6.1 检索订单

public/src/order/order.service.ts 中的服务将处理订单实体的所有操作。可以从后端获取订单流,以便在有新订单添加到系统时立即通知所有客户端。

6.2 查看和更新订单

public/src/order/components/order-details.ts 中的 OrderDetailsComponent 可以在同一上下文中查看和编辑订单。

微服务架构下的电商系统开发实践(续)

7. 订单处理具体实现分析

在前面我们了解了订单的几种状态以及系统需要处理订单的基本需求,下面进一步分析订单处理的具体实现。

7.1 订单状态流转

订单状态的流转是订单处理的核心部分,我们可以用 mermaid 流程图来展示订单状态的流转过程:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([Pending]):::startend -->|Payment Success| B(Processing):::process
    A -->|Payment Failed| C(Failed):::process
    B -->|Fulfilled| D(Completed):::process
    B -->|Cancel Request| E(On - hold):::process
    E -->|Payment Confirm| B
    E -->|Cancel Confirmed| F(Cancelled):::process
    A -->|Cancel Request| F

从这个流程图可以清晰地看到订单从 Pending 状态开始,根据不同的情况流转到其他状态。例如,当付款成功时,订单从 Pending 状态进入 Processing 状态;当付款失败时,订单进入 Failed 状态。

7.2 订单服务代码示例

虽然我们不详细展开 public/src/order/order.service.ts 中的代码,但可以简单分析一下其可能包含的核心方法:

import { Injectable } from 'angular2/core';
import { Http, Response } from 'angular2/http';
import { Observable } from 'rxjs/Observable';
import { Order } from './order.model';

@Injectable()
export class OrderService {
    constructor(private http: Http) {}

    // 获取所有订单
    getAllOrders(): Observable<Order[]> {
        return this.http.get('/api/orders')
           .map((res: Response) => res.json())
           .catch((error: any) => Observable.throw(error.json().error || 'Server error'));
    }

    // 更新订单状态
    updateOrderStatus(orderId: string, status: string): Observable<Order> {
        return this.http.put(`/api/orders/${orderId}/status`, { status })
           .map((res: Response) => res.json())
           .catch((error: any) => Observable.throw(error.json().error || 'Server error'));
    }
}

在这个示例中, getAllOrders 方法用于获取所有订单, updateOrderStatus 方法用于更新订单的状态。

8. 系统架构总结

整个电商系统采用微服务架构,各个微应用分工明确,协同工作。下面是系统架构的总结表格:
| 微应用名称 | 功能描述 | 主要技术 |
| ---- | ---- | ---- |
| Auth 微应用 | 处理用户认证,支持有状态和无状态认证 | Express、Passport |
| Api 微应用 | 暴露 RESTful API 来访问核心业务逻辑 | Express、Angular |
| Shared 微应用 | 提供共享静态资源 | Express、serve - static |
| Admin 微应用 | 管理产品和订单,使用 Angular 2 构建前端 | Angular 2、Express |

9. 开发建议与注意事项

在开发这样的电商系统时,有一些建议和注意事项需要我们关注:

9.1 安全性
  • 认证和授权 :使用如 Passport 这样的认证中间件来确保用户的身份验证和授权,防止未授权访问。
  • 数据加密 :对于用户的敏感信息,如密码,要进行加密存储,避免数据泄露。
9.2 性能优化
  • 缓存机制 :对于一些频繁访问的数据,如产品列表,可以使用缓存机制来减少数据库的访问次数,提高系统性能。
  • 异步处理 :对于一些耗时的操作,如订单处理,可以使用异步处理来避免阻塞主线程。
9.3 代码可维护性
  • 模块化设计 :将不同的功能模块拆分成独立的微应用和组件,提高代码的可维护性和可扩展性。
  • 代码规范 :遵循统一的代码规范,如使用 ESLint 进行代码检查,确保代码的一致性。
10. 未来展望

随着技术的不断发展,这个电商系统还有很多可以改进和扩展的地方:

10.1 引入新技术
  • 微服务编排 :可以使用如 Kubernetes 这样的容器编排工具来管理和部署微服务,提高系统的可伸缩性和可靠性。
  • 人工智能 :引入人工智能技术,如机器学习算法,来进行商品推荐和客户行为分析。
10.2 功能扩展
  • 多语言支持 :支持多种语言,以满足不同地区用户的需求。
  • 第三方支付集成 :集成更多的第三方支付方式,如支付宝、微信支付等。

通过以上的实践和分析,我们可以看到微服务架构在电商系统开发中的优势,它使得系统更加灵活、可维护和可扩展。在未来的开发中,我们可以不断地优化和扩展这个系统,以满足不断变化的业务需求。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值