构建电商前端与拍卖应用
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);
}
}
- 配置视图引擎:
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);
}
- 注册路由:
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');
- 定义基类:
class AuctionManager {
constructor(AuctionModel, BidderModel) {
this._Auction = AuctionModel || Auction;
this._Bidder = BidderModel || Bidder;
}
}
module.exports = AuctionManager;
- 获取所有拍卖的方法:
getAllAuctions(query, limit, skip, callback) {
if (limit > MAX_LIMIT) {
limit = MAX_LIMIT;
}
this._Auction
.find(query)
.limit(limit)
.skip(skip)
.exec(callback);
}
- 加入拍卖的方法:
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);
});
});
}
- 出价的方法:
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[增强数据保护];
在未来的开发中,我们可以继续探索更多的技术和功能,如移动应用开发、数据分析等,以进一步提升应用的用户体验和业务价值。
超级会员免费看
1313

被折叠的 条评论
为什么被折叠?



