摘要:主要遇到的問題如下涉及商品價格的系統(tǒng)眾多各上層系統(tǒng)調(diào)用商品價格接口繁多商品價格相關(guān)字段較多為了實現(xiàn)快速上線,我們在原人民幣的商品價格基礎(chǔ)架構(gòu)上,只能進(jìn)行少量且合適的改造??尚行哉{(diào)研改造商品中心,商品價格支持盧比。
首發(fā)于 樊浩柏科學(xué)院
假若,你是某個國內(nèi)電商平臺的商品中心項目負(fù)責(zé)人。突然今天,接到了一個這樣的需求:商品在原人民幣價格的基礎(chǔ)架構(gòu)上,須支持盧比(印度)價格。
需求需求點,可以描述為:
購買的用戶,商品價格需要支持盧比;
營運人員,商品管理系統(tǒng)依然使用人民幣價格;
同樣這個需求,定了以下兩個硬指標(biāo):
必須實現(xiàn)需求;
必須快速上線;
問題首先,我們必須承認(rèn)的是,這確實是個簡單的需求,但這也是個夠坑爹的需求。主要遇到的問題如下:
涉及商品價格的系統(tǒng)眾多;
各上層系統(tǒng)調(diào)用商品價格接口繁多;
商品價格相關(guān)字段較多;
為了實現(xiàn)快速上線,我們在原人民幣的商品價格基礎(chǔ)架構(gòu)上,只能進(jìn)行少量且合適的改造。所以,最后我們的改造方向為:盡量只改造商品價格源頭系統(tǒng),即商品中心,其他上層系統(tǒng)盡量不改動。
可行性調(diào)研改造商品中心,商品價格支持盧比??尚械母脑旆桨赣?2 種:
1、數(shù)據(jù)表價格字段存盧比
將原人名幣價格相關(guān)的數(shù)據(jù)表字段,存盧比值,數(shù)據(jù)表并新增人名幣字段。
2、接口輸出數(shù)據(jù)時轉(zhuǎn)化為盧比
原人名幣相關(guān)的數(shù)據(jù)表字段依然存人民幣值,在接口輸出數(shù)據(jù)時,將價格相關(guān)字段值轉(zhuǎn)化為盧比。
針對以上方案,我們需要注意 2 個問題:
匯率會每天變化,所以商品價格也會變化;
后續(xù)商品價格,可能須支持多幣種;
上述 方案 ①,商品中心只需改造數(shù)據(jù)表。然后每天根據(jù)匯率刷新商品價格,原價格字段就都變成了盧比。方案相對簡單,也容易操作,但缺點是:對任然需要人民幣價格的系統(tǒng),即商品管理系統(tǒng)須改造。
方案 ②,需要改造商品中心業(yè)務(wù)邏輯。由于涉及的價格字段較多,改造較復(fù)雜,主要優(yōu)點是:匯率變動對商品價格影響較小,且可拓展支持多幣種價格(可以根據(jù)地區(qū)標(biāo)識,獲取相應(yīng)的商品價格)。
最終,為了系統(tǒng)的可擴(kuò)展性,我們選擇了方案 ②。
這里主要改造了商品中心,主要解決 透傳地區(qū)標(biāo)識 和 支持多幣種價格 這 2 個問題。
透傳地區(qū)標(biāo)識我們的業(yè)務(wù)系統(tǒng)主要分為 API 和 Service 項目,API 暴露出 HTTP 接口,API 與 Service 和 Service 與 Service 之前使用 RPC 接口通信。由于商品中心涉及到價格的接口繁多,不可能對每個接口都增加地區(qū)標(biāo)識的參數(shù)。所以我們弄了一套調(diào)用鏈路透傳地區(qū)標(biāo)識的機(jī)制。
機(jī)制原理思路就是,先將地區(qū)標(biāo)識放在全局上下文中,API 接口通過 Header 頭X-Location攜帶地區(qū)標(biāo)識;而對于 RPC 接口,我們的 RPC 框架已支持了 Context,不需要改造。
代碼實現(xiàn)由于 RPC 框架已支持了 Context,所以 API 和 RPC 接口透傳全局上下文略有不同。實現(xiàn)如下:
class Location { public static function init() { global $context; if (empty($context["location"])) { return; } // API在這里直接獲取X-Location頭 if (!empty($_SERVER["HTTP_X_LOCATION"])) { $context["location"] = $_SERVER["HTTP_X_LOCATION"]; } // RPC Server會自動獲取Context } }
上述init()方法,需要在項目入口位置初始化。
其中,RPC 接口不需要操作全局上下文。因為 RPC Client 在調(diào)用時會自動獲取全局變量$context值并在 RPC 協(xié)議數(shù)據(jù)中追加 Context,同時 RPC Server 在收到請求時會自動獲取 RPC 協(xié)議數(shù)據(jù)中的 Context 值并設(shè)置全局變量$context。
RPC Client 傳遞 Context 實現(xiàn)如下:
protected function addGlobalContext($data) { global $context; $context = !is_array($context) ? array() : $context; // data為待請求的RPC協(xié)議數(shù)據(jù) $data["Context"] = $context; return $data; }
RPC Server 獲取 Context 實現(xiàn)如下:
public function getGlobalContext($packet) { global $context; $context = array(); // packet為接收的RPC協(xié)議數(shù)據(jù) if(isset($packet["Context"])) { $context = $packet["Context"]; } }
當(dāng)設(shè)置了 Context 后,RPC 通信時協(xié)議數(shù)據(jù)會攜帶location字段,內(nèi)容如下:
RPC 325 {"data":"{"version":"1.0","user":"xxx","password":"xxx","timestamp":1553225486.5455,"class":"xxx","method":"xxx","params":[1]}","signature":"xxx","Context":{"location":"india"}}
到這里,我們只需要在全局上下文設(shè)置地區(qū)標(biāo)識即可。一旦我們設(shè)置了地區(qū)標(biāo)識,所有業(yè)務(wù)系統(tǒng)就會在本次的調(diào)用鏈路中透傳這個地區(qū)標(biāo)識。實現(xiàn)如下:
class Location { public static function set($location) { global $context; $context["location"] = $location; // API需要在這里多帶帶設(shè)置X-Location頭 header("X-Location: " . $context["location"]); } }
設(shè)置了地區(qū)標(biāo)識后,就可以在本次調(diào)用鏈路的所有業(yè)務(wù)系統(tǒng)中直接獲取。實現(xiàn)如下:
class Location { public static function get() { global $context; if (!isset($context["location"])) { return "china"; } return $context["location"]; } }支持多幣種價格 商品中心
有了地區(qū)標(biāo)識后,商品中心服務(wù)就可以根據(jù)地區(qū)標(biāo)識對價格字段進(jìn)行轉(zhuǎn)化了。因為設(shè)計到價格的數(shù)據(jù)表和價格字段較多,這里直接從數(shù)據(jù)層(Model)進(jìn)行改造。
下述的ReadBase類是所有數(shù)據(jù)表 Model 的基類,所有獲取數(shù)據(jù)表數(shù)據(jù)的方法都繼承或調(diào)用自getOne() 和getAll()方法,所以我們只需要改造這兩個方法。
class ReadBase { public function getOne(array $cond, $fields) { $data = $this->getReader()->select($this->getFields($fields))->from($this->getTableName())->where($cond)->queryRow(); return $this->getExchangePrice($data); } public function getAll(array $cond, $fields) { $data = $this->getReader()->select($this->getFields($fields))->from($this->getTableName())->where($cond)->queryAll(); if ($data) { foreach ($data as &$one) { $this->getExchangePrice($one); } } return $data; } }
由于涉及到價格字段名字較多,且具有不確定性,所以這里使用后綴方式匹配。為了防止一些字段命名不規(guī)范,這里引入了黑名單機(jī)制。
protected function isExchangeField($field) { $priceSuffix = array("cost", "_price"); $black = array(); $len = strlen($field) ; foreach ($priceSuffix as $suffix) { $lastPos = $len - strlen($suffix); // 非黑名單且非is_ if (!in_array($field, $black) && false === strpos($field, "is_") && $lastPos === strpos($field, $suffix) ) { return true; } } return false; }
前綴為is_的字段一般定義為標(biāo)識字段,默認(rèn)為非價格字段。
上述getExchangePrice()方法,用來根據(jù)地區(qū)標(biāo)識轉(zhuǎn)化價格覆蓋到原價格字段,并自增以_origin后綴的人民幣價格字段。
public function getExchangePrice(&$data) { if (empty($data)) { return $data; } $originPrice = array(); foreach ($data as $field => &$value) { // 是否是價格字段 if ($this->isExchangeField($field)) { $originField = $field . "_origin"; $originPrice[$originField] = $value; // 獲取對應(yīng)地區(qū)的價格 $value = $this->getExchangePrice($value); } } $data = array_merge($originPrice, $data); return $data; } public static function getExchangePrice($price) { // 獲取地區(qū)標(biāo)識 $location = Location::get(); // 匯率 $exchangeRateConfig = Config::$exchangeRate; if ($location === "china") { return $price; } else if (isset($exchangeRateConfig[$location])) { $exchangeRate = $exchangeRateConfig[$location]; } else { throw new BusinessException("not found $location exchange rate"); } // 向上取值并保留兩位小數(shù) $exchangePrice = bcmul($price, $exchangeRate, 3); return number_format(ceil($exchangePrice * 100) / 100, 2, ".", ""); }
其中,getExchangePrice()方法會調(diào)用Location::get()獲取地區(qū)標(biāo)識,并根據(jù)匯率計算實時價格。
最終,商品中心改造后,得到的部分商品價格信息,如下:
# 人民幣價格10,匯率10.87 market_price: 108.7 market_price_origin: 10API系統(tǒng)
對于所有 API 的項目,我們只需要讓客戶端在所有的請求中增加X-Location頭即可。
GET /product/detail/1 HTTP/1.1 Request Headers X-Location: india
API 項目需在入口文件處,初始化地區(qū)標(biāo)識。如下:
Location::init();商品管理系統(tǒng)
對于商品管理系統(tǒng),我們?yōu)榱朔奖氵\營操作,所有商品價格都應(yīng)以人民幣。因此,我們只需要初始化地區(qū)標(biāo)識為中國,如下:
Location::init(); // 地區(qū)設(shè)置為中國 Location::set("china");總結(jié)
為了實現(xiàn)需求很容易,但是要做到合理且快速卻不簡單。本文的實現(xiàn)的方案,避免了很多坑,但同時也可能又埋下了一些坑。沒有一套方案是萬能的,慢慢去優(yōu)化吧!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/11987.html
摘要:主要遇到的問題如下涉及商品價格的系統(tǒng)眾多各上層系統(tǒng)調(diào)用商品價格接口繁多商品價格相關(guān)字段較多為了實現(xiàn)快速上線,我們在原人民幣的商品價格基礎(chǔ)架構(gòu)上,只能進(jìn)行少量且合適的改造??尚行哉{(diào)研改造商品中心,商品價格支持盧比。 首發(fā)于 樊浩柏科學(xué)院 假若,你是某個國內(nèi)電商平臺的商品中心項目負(fù)責(zé)人。突然今天,接到了一個這樣的需求:商品在原人民幣價格的基礎(chǔ)架構(gòu)上,須支持盧比(印度)價格。 showIm...
摘要:上周,在杭州歐美金融城創(chuàng)投中心啟動全球黑客馬拉松,本次活動吸引了眾多長三角開發(fā)者的關(guān)注,有多位嘉賓出席活動進(jìn)行項目交流討論,現(xiàn)場更有幣圈大佬現(xiàn)場撒幣。 showImg(https://segmentfault.com/img/bVbbQCS); 上周,SegmentFault 在杭州歐美金融城 G5 創(chuàng)投中心啟動全球黑客馬拉松,本次活動吸引了眾多長三角開發(fā)者的關(guān)注,有多位嘉賓出席活動進(jìn)...
摘要:背景比特幣說好的分叉最后卻分叉不成,如今算力又不夠,于是比特現(xiàn)金想篡位沒一個星期就漲了快倍,錯過這趟快車甚是后悔,于是打算寫一個可不定期推送最新消息的微信公眾號。既然是利用微信這個平臺載體,當(dāng)然要熟悉微信的,遂封裝了一下。 背景:比特幣說好的segwit2x分叉最后卻分叉不成,如今算力又不夠,于是比特現(xiàn)金想篡位? 沒一個星期就漲了快10倍,錯過這趟快車甚是后悔,于是打算寫一個可不定期推...
摘要:很多人將這一波的上漲解讀為比特幣小牛市的到來,無論從技術(shù)層面還是從消息層面來看,比特幣都有逐步回暖的跡象。到日,關(guān)于英雄鏈網(wǎng)絡(luò)詐騙案被破獲的報道便鋪天蓋地地傳播開來。 摘要:不在風(fēng)口上,長了翅膀的項目同樣可以起飛,價值終究會超越時間。 showImg(https://segmentfault.com/img/bVbrS0N?w=4096&h=3575); 自四月初以來,比特幣就開啟了起...
閱讀 2286·2021-11-23 09:51
閱讀 5681·2021-09-22 15:39
閱讀 3355·2021-09-02 15:15
閱讀 3505·2019-08-30 15:54
閱讀 2364·2019-08-30 15:53
閱讀 1404·2019-08-30 14:04
閱讀 2459·2019-08-29 18:33
閱讀 2377·2019-08-29 13:08