17、构建电商前端与拍卖应用

构建电商前端与拍卖应用

1. 构建电商前端

1.1 新架构尝试

在构建电商前端时,我们没有选择构建单页应用,而是尝试采用服务器端渲染页面,即构建经典的网页。这样做是为了充分利用无头核心应用的优势,并探索如何将其与不同的客户端应用集成。

1.2 前端微应用

在应用的管理部分,我们将其从主应用中解耦为一个微应用。因此,我们可以随时从该应用中提取前端所需的代码,添加到一个全新的 Express 应用中,并通过网络进行所有调用。这种架构在应用增长和扩展时,有助于区分哪些部分需要扩展或迁移到单独的应用中。

1.3 技术选择

我们使用 Nunjucks 作为 JavaScript 的模板引擎,它可以在服务器端和客户端使用。在开始处理模板之前,需要进行以下准备工作:
1. 在 apps/storefront 下创建一个新的 apps 文件夹。
2. 添加一个新文件 apps/storefront/index.js
3. 定义微应用的类:

'use strict';
const express = require('express');
const nunjucks = require('nunjucks');
const router = express.Router();
const ProductController = require('./controllers/products');
class Storefront {
  constructor(config, core, app) {
    this.config = config;
    this.core = core;
    this.app = app;
    this.router = router;
    this.rootUrl = '/';
    this.productCtrl = new ProductController(core);
    this.configureViews();
    this.regiterRoutes();
    this.app.use(this.rootUrl, this.router);
  }
}
  1. 配置视图引擎:
configureViews() {
  let opts = {};
  if (!this.config.nunjucks.cache) {
    opts.noCache = true;
  }
  if (this.config.nunjucks.watch) {
    opts.watch = true;
  }
  let loader = new nunjucks.FileSystemLoader('apps/frontstore/views', opts);
  this.nunjucksEnv = new nunjucks.Environment(loader);
  this.nunjucksEnv.express(this.app);
}
  1. 注册路由:
registerRoutes() {
  this.router.get('/', this.productCtrl.home);
}

1.4 控制器代码

以下是 apps/storefront/controllers/product.js 的部分代码:

'use strict';
let productCatalog;
class ProductsController {
  constructor(core) {
    this.core = core;
    productCatalog = new core.services.ProductCatalog();
  }
  home(req, res, next) {
    productCatalog.list({}, 10, 0, (err, products) => {
      if (err) {
        next(err);
      }
      res.render('home', { products: products });
    });
  }
}
module.exports = ProductsController;

这个控制器类用于从持久存储(如 MongoDB)中检索产品,并使用 render() 方法渲染 HTML 响应。

1.5 前端页面

1.5.1 主布局

几乎每个模板都会扩展一个主模板文件 apps/storefront/views/layout.html ,它包含完整 HTML 文档的必要标记:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>ecommerce</title>
    {% include "includes/stylesheets.html" %}
  </head>
  <body>
    <div class="container">
      <div class="app-wrapper card whiteframe-z2">
        {% block header %}
        <header>
          <div class="row">
            <div class="col">
              <h1><a href="#">Awesome store</a></h1>
              <span class="pull-right">{{ currentUser.email }}</span>
            </div>
          </div>
        </header>
        {% endblock %}
        <div class="row">
          {% block content %}{% endblock %}
        </div>
      </div>
    <div>
    {% block footer %}
    <footer></footer>
    {% endblock %}
  </body>
</html>

其中, stylesheets.html 文件用于导入必要的样式表:

<link href='https://fonts.googleapis.com/css?family=Work+Sans' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="/assets/css/style.css">
1.5.2 产品列表

创建一个新的模板文件 apps/storefront/views/home.html 来列出产品:

{% extends "layout.html" %}
{% block content %}
  <div class="product-list row">
    <div class="col">
    {% for product in products %}
      {% include "partials/product.html" %}
    {% endfor %}
    </div>
  </div>
{% endblock %}

apps/storefront/views/partials/product.html 是一个部分视图,用于显示单个产品:

<div class="col col-25 product-item">
  <a href="{{ baseUrl }}/products/{{ product.slug }}">
    <img src="http://placehold.it/208x140?text=product+image&txtsize=18" />
  </a>
  <h2>
    <a href="{{ baseUrl }}/products/{{ product.slug }}">{{ product.title }}</a>
  </h2>
  <p>{{ product.summary }}</p>
  <p>price: {{ product.price.display }} {{ product.price.currency}}</p>
  <p><button class="button">add to cart</button></p>
</div> 

1.6 流程图

graph TD;
    A[创建文件夹和文件] --> B[定义微应用类];
    B --> C[配置视图引擎];
    C --> D[注册路由];
    D --> E[创建控制器];
    E --> F[渲染页面];

1.7 总结

通过这种方式,我们展示了如何使用不同的技术构建 Express 应用,为我们的 MEAN 栈增加了新的可能性。

2. 构建拍卖应用

2.1 基础应用设置

我们从经典的 Express 应用模板开始构建拍卖应用,具体步骤如下:
1. 从 GitHub 克隆项目: git clone https://github.com/robert52/express-api-starter
2. 将模板项目重命名为 auction-app
3. 可选:停止指向初始的 Git 远程仓库: git remote remove origin
4. 进入工作目录: cd auction-app
5. 安装所有依赖: npm install
6. 创建开发配置文件: cp config/environments/example.js config/environments/development.js

配置文件 auction-app/config/environments/development.js 示例如下:

'use strict';
module.exports = {
  port: 3000,
  hostname: '127.0.0.1',
  baseUrl: 'http://localhost:3000',
  mongodb: {
    uri: 'mongodb://localhost/auction_dev_db'
  },
  app: {
    name: 'MEAN Blueprints - auction application'
  },
  serveStatic: true,
  session: {
    type: 'mongo',
    secret: 'someVeRyN1c3S#cr3tHer34U',
    resave: false,
    saveUninitialized: true
  },
  proxy: {
    trust: true
  },
  logRequests: false  
};

2.2 构建内容

我们要构建一个英式拍卖网站,之前的电商应用将为我们提供产品,管理员可以使用这些产品创建拍卖。英式拍卖是最常见的拍卖类型,是一维拍卖,只考虑商品的出价。通常有一个起拍价(保留价),卖家不会以低于该价格出售商品。每个买家出价,所有人都知道每个出价,拍卖结束时,出价最高者获胜并支付成交价。

2.3 数据建模

2.3.1 拍卖模型

拍卖模型包含拍卖事件的所有必要信息。以下是 Mongoose 拍卖模式的代码:

'use strict';
const mongoose = require('mongoose');
const Money = require('./money').schema;
const Schema = mongoose.Schema;
const ObjectId = Schema.ObjectId;
const Mixed = Schema.Types.Mixed;
var AuctionSchema = new Schema({
  item:           { type: Mixed },
  startingPrice:  { type: Money },
  currentPrice:   { type: Money },
  endPrice:       { type: Money },
  minAmount:      { type: Money },
  bids: [{
    bidder:       { type: ObjectId, ref: 'Bidder' },
    amount:       { type: Number, default: 0 },
    createdAt:    { type: Date, default: Date.now }
  }],
  startsAt:       { type: Date },
  endsAt:         { type: Date },
  createdAt:      { type: Date, default: Date.now }
});

module.exports = mongoose.model('Auction', AuctionSchema);
2.3.2 竞拍者模型

竞拍者模型用于存储竞拍者的额外数据。以下是 app/models/bidder.js 的代码:

'use strict';
const mongoose = require('mongoose');
const Money = require('./money').schema;
const Schema = mongoose.Schema;
const ObjectId = Schema.ObjectId;
const Mixed = Schema.Types.Mixed;
const BidderSchema = new Schema({
  profileId:      { type: String },
  additionalData: { type: Mixed },
  auctions: [{
    auction:      { type: ObjectId, ref: 'Auction' },
    status:       { type: String, default: 'active'},
    joinedAt:     { type: Date, default: Date.now }
  }],
  createdAt:      { type: Date, default: Date.now }
});
module.exports = mongoose.model('Bidder', BidderSchema);

2.4 拍卖后端

2.4.1 中介者模式

我们引入了一个名为 Mediator 的组件,作为与不同模块通信的单点入口。以下是 Mediator 的实现代码:

'use strict';
const EventEmitter = require('events');
let instance;
class Mediator extends EventEmitter {
  constructor() {
    super();
  }
}
module.exports = function singleton() {
  if (!instance) {
    instance = new Mediator();
  }
  return instance;
} 

使用示例:

'use strict';
const mediator = require('./mediator')();
mediator.on('some:awesome:event', (msg) => {
  console.log(`received the following message: ${msg}`);
});
mediator.emit('some:awesome:event', 'Nice!');
2.4.2 拍卖管理器

我们构建了一个名为 AuctionManager 的服务,用于处理拍卖的业务逻辑。实现步骤如下:
1. 创建新文件 /app/services/auction-manager.js
2. 添加必要的依赖:

const MAX_LIMIT = 30;
const mongoose = require('mongoose');
const mediator = require('./mediator')();
const Auction = mongoose.model('Auction');
const Bidder = mongoose.model('Bidder');
  1. 定义基类:
class AuctionManager {
  constructor(AuctionModel, BidderModel) {
    this._Auction = AuctionModel || Auction;
    this._Bidder = BidderModel || Bidder;
  }
}
module.exports = AuctionManager;
  1. 获取所有拍卖的方法:
getAllAuctions(query, limit, skip, callback) {
  if (limit > MAX_LIMIT) {
    limit = MAX_LIMIT;
  }
  this._Auction
  .find(query)
  .limit(limit)
  .skip(skip)
  .exec(callback);
}
  1. 加入拍卖的方法:
joinAuction(bidderId, auctionId, callback) {
  this._Bidder.findById(bidderId, (err, bidder) => {
    if (err) {
      return callback(err);
    }

    bidder.auctions.push({ auction: auctionId });
    bidder.save((err, updatedBidder) => {
      if (err) {
        return callback(err);
      }
      mediator.emit('bidder:joined:auction', updatedBidder);
      callback(null, updatedBidder);
    });
  });
}
  1. 出价的方法:
placeBid(auctionId, bidderId, amount, callback) {
  if (amount <= 0) {
    let err = new Error('Bid amount cannot be negative.');
    err.type = 'negative_bit_amount';
    err.status = 409;
    return callback(err);
  }
  let bid = {
    bidder: bidderId,
    amount: amount
  };
  this._Auction.update(
    // query
    {
      _id: auctionId.toString()
    },
    // update
    {
      currentPrice: { $inc: amount },
      bids: { $push: bid }
    },
    // results
    (err, result) => {
      if (err) {
        return callback(err);
      }
      if (result.nModified === 0) {
        let err = new Error('Could not place bid.');
        err.type = 'new_bid_error';
        err.status = 500;
        return callback(err);
      }
      mediator.emit('auction:new:bid', bid);

      callback(null, bid);
    }
  );
}

2.5 流程图

graph TD;
    A[设置基础应用] --> B[定义数据模型];
    B --> C[实现中介者模式];
    C --> D[构建拍卖管理器];
    D --> E[处理拍卖业务逻辑];

2.6 总结

通过构建拍卖应用,我们进一步探索了如何在现有电商应用的基础上扩展功能,同时使用中介者模式和服务层来管理业务逻辑。在实际开发中,我们可以根据需求进一步优化和扩展这些功能。

2.7 代码逻辑分析

2.7.1 中介者模式的作用

中介者模式在拍卖应用中起到了关键的协调作用。通过 Mediator 类,不同模块之间的通信变得更加清晰和可控。以下是中介者模式的主要优势:
|优势|说明|
|----|----|
|解耦模块|各个模块不需要直接相互引用,而是通过中介者进行通信,降低了模块之间的耦合度。|
|事件驱动|使用 EventEmitter 实现事件的发布和订阅,使得模块之间的交互更加灵活。例如,当竞拍者加入拍卖或出价时,可以通过中介者发布相应的事件,其他模块可以订阅这些事件并做出响应。|
|单例模式|确保在应用的整个生命周期内只有一个 Mediator 实例,避免了多个实例可能带来的冲突。|

2.7.2 拍卖管理器的业务逻辑

AuctionManager 类封装了拍卖的核心业务逻辑,包括获取所有拍卖、加入拍卖和出价等操作。以下是对这些方法的详细分析:
- 获取所有拍卖 getAllAuctions 方法通过 Auction 模型从数据库中查询拍卖信息,并支持分页查询。为了防止查询结果过多,设置了最大限制 MAX_LIMIT
- 加入拍卖 joinAuction 方法首先根据竞拍者的 ID 查找竞拍者信息,然后将拍卖 ID 添加到竞拍者的 auctions 数组中,并保存更新后的竞拍者信息。最后,通过中介者发布 bidder:joined:auction 事件。
- 出价 placeBid 方法检查出价金额是否为正数,然后使用原子操作更新拍卖的当前价格和出价列表。如果更新成功,通过中介者发布 auction:new:bid 事件。

2.8 可能的扩展和优化

2.8.1 实时通信

目前的拍卖应用没有实现实时通信功能,竞拍者无法实时看到其他竞拍者的出价。可以使用 WebSockets 技术来实现实时通信,当有新的出价时,及时通知所有参与竞拍的用户。以下是一个简单的实现思路:
1. 在服务器端,使用 WebSocket 库(如 ws )创建一个 WebSocket 服务器。
2. 当有新的出价时,通过中介者发布 auction:new:bid 事件,在事件处理函数中,将出价信息发送给所有连接的客户端。
3. 在客户端,使用 WebSocket 连接到服务器,并监听服务器发送的出价信息,更新页面显示。

2.8.2 安全优化

在拍卖应用中,安全是至关重要的。可以采取以下措施来提高应用的安全性:
- 身份验证和授权 :确保只有经过身份验证的用户才能参与拍卖,并且根据用户的角色(如管理员、竞拍者)进行不同的授权。
- 输入验证 :对用户输入的信息(如出价金额)进行严格的验证,防止恶意输入。
- 数据加密 :对敏感数据(如用户密码、竞拍信息)进行加密存储,防止数据泄露。

2.9 总结

通过构建电商前端和拍卖应用,我们展示了如何使用不同的技术和架构来构建复杂的 Web 应用。在电商前端部分,我们使用服务器端渲染和视图引擎来构建动态页面;在拍卖应用部分,我们引入了中介者模式和服务层来管理业务逻辑。同时,我们还探讨了可能的扩展和优化方向,如实时通信和安全优化。在实际开发中,可以根据具体需求选择合适的技术和架构,并不断优化和完善应用。

2.10 流程图

graph LR;
    A[现有拍卖应用] --> B[实时通信扩展];
    A --> C[安全优化];
    B --> D[实时更新页面];
    C --> E[增强数据保护];

在未来的开发中,我们可以继续探索更多的技术和功能,如移动应用开发、数据分析等,以进一步提升应用的用户体验和业务价值。

【无人车路径跟踪】基于神经网络的数据驱动迭代学习控制(ILC)算法,用于具有未知模型和重复任务的非线性单输入单输出(SISO)离散时间系统的无人车的路径跟踪(Matlab代码实现)内容概要:本文介绍了一种基于神经网络的数据驱动迭代学习控制(ILC)算法,用于解决具有未知模型和重复任务的非线性单输入单输出(SISO)离散时间系统的无人车路径跟踪问题,并提供了完整的Matlab代码实现。该方法无需精确系统模型,通过数据驱动方式结合神经网络逼近系统动态,利用迭代学习机制不断提升控制性能,从而实现高精度的路径跟踪控制。文档还列举了大量相关科研方向和技术应用案例,涵盖智能优化算法、机器学习、路径规划、电力系统等多个领域,展示了该技术在科研仿真中的广泛应用前景。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的研究生、科研人员及从事无人车控制、智能算法开发的工程技术人员。; 使用场景及目标:①应用于无人车在重复任务下的高精度路径跟踪控制;②为缺乏精确数学模型的非线性系统提供有效的控制策略设计思路;③作为科研复现算法验证的学习资源,推动数据驱动控制方法的研究应用。; 阅读建议:建议读者结合Matlab代码深入理解算法实现细节,重点关注神经网络ILC的结合机制,并尝试在不同仿真环境中进行参数调优性能对比,以掌握数据驱动控制的核心思想工程应用技巧。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值