waynboot-mall項目
覺得有用的鐵子們給個star就行了慕爬,求求你們啦????
waynboot-mall是一套全部開源的微商城項目澡罚,包含一個運(yùn)營后臺留搔、h5商城和后臺接口隔显。
實(shí)現(xiàn)了一個商城所需的首頁展示括眠、商品分類掷豺、商品詳情当船、sku詳情、商品搜索壹置、加入購物車、結(jié)算下單盖喷、訂單狀態(tài)流轉(zhuǎn)传蹈、商品評論等一系列功能。
技術(shù)上基于Springboot2.0步藕,整合了Redis惦界、RabbitMQ、ElasticSearch等常用中間件咙冗,
貼近生產(chǎn)環(huán)境實(shí)際經(jīng)驗開發(fā)而來不斷完善沾歪、優(yōu)化淆储、改進(jìn)中慨削。
waynboot-mall接口項目
- 商城接口代碼清晰、注釋完善殴蓬、模塊拆分合理
- 使用Spring-Security進(jìn)行訪問權(quán)限控制
- 使用jwt進(jìn)行接口授權(quán)驗證
- ORM層使用Mybatis Plus提升開發(fā)效率
- 添加全局異常處理器,統(tǒng)一異常處理
- 添加https配置代碼,支持https訪問
- 集成七牛云存儲配置丛晦,上傳文件至七牛
- 集成常用郵箱配置隙笆,方便發(fā)送郵件
- 集成druid連接池,進(jìn)行sql監(jiān)控
- 集成swagger洋只,管理接口文檔
- 添加策略模式使用示例妒茬,優(yōu)化首頁金剛區(qū)跳轉(zhuǎn)邏輯
- 拆分出通用的數(shù)據(jù)訪問模塊,統(tǒng)一redis & elastic配置與訪問
- 使用elasticsearch-rest-high-level-client客戶端對elasticsearch進(jìn)行操作
- 支持商品數(shù)據(jù)同步elasticsearch操作以及elasticsearch商品搜索
- RabbitMQ生產(chǎn)者發(fā)送消息采用異步confirm模式,消費(fèi)者消費(fèi)消息時需手動確認(rèn)
- 下單處理過程引入rabbitMQ,異步生成訂單記錄析孽,提高系統(tǒng)下單處理能力
- ...
商城難點(diǎn)整理
1. 庫存扣減操作是在下單操作扣減還是在支付成功時扣減邓尤?(ps:扣減庫存使用樂觀鎖機(jī)制 where goods_num - num >= 0
)
- 下單時扣減赴精,這個方案屬于實(shí)時扣減,當(dāng)有大量下單請求時,由于訂單數(shù)小于請求數(shù)芬迄,會發(fā)生下單失敗,但是無法防止短時間大量惡意請求占用庫存塞耕,
造成普通用戶無法下單 - 支付成功扣減筛谚,這個方案可以預(yù)防惡意請求占用庫存,但是會存在多個請求同時下單后,在支付回調(diào)中扣減庫存失敗,導(dǎo)致訂單還是下單失敗并且還要退還訂單金額(這種請求就是訂單數(shù)超過了庫存數(shù),無法發(fā)貨,影響用戶體驗)
- 還是下單時扣減,但是對于未支付訂單設(shè)置一個超時過期機(jī)制,比如下單時庫存減一,生成訂單后蔬芥,對于未在15分鐘內(nèi)完成支付的訂單,
自動取消超期未支付訂單并將庫存加一次酌,該方案基本滿足了大部分使用場景 - 針對大流量下單場景吊宋,比如一分鐘內(nèi)五十萬次下單請求鳞上,可以通過設(shè)置虛擬庫存的方式減少下單接口對數(shù)據(jù)庫的訪問。具體來說就是把商品實(shí)際庫存保存到redis中,
下單時配合lua腳本原子的get和decr商品庫存數(shù)量(這一步就攔截了大部分請求),執(zhí)行成功后在扣減實(shí)際庫存
2. 首頁商品展示接口利用多線程技術(shù)進(jìn)行查詢優(yōu)化糖权,將多個sql語句的排隊查詢變成異步查詢,接口時長只跟查詢時長最大的sql查詢掛鉤
# 1. 通過創(chuàng)建子線程繼承Callable接口
Callable<List<Banner>> bannerCall = () -> iBannerService.list(new QueryWrapper<Banner>().eq("status", 0).orderByAsc("sort"));
# 2. 傳入Callable的任務(wù)給FutureTask
FutureTask<List<Banner>> bannerTask = new FutureTask<>(bannerCall);
# 3. 放入線程池執(zhí)行
threadPoolTaskExecutor.submit(bannerTask);
# 4. 最后可以在外部通過FutureTask的get方法異步獲取執(zhí)行結(jié)果
List<Banner> list = bannerTask.get()
3. ElasticSearch
查詢操作笆檀,查詢包含搜索關(guān)鍵字并且是上架中的商品士修,在根據(jù)指定字段進(jìn)行排序,最后分頁返回
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
MatchQueryBuilder matchFiler = QueryBuilders.matchQuery("isOnSale", true);
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", keyword);
MatchPhraseQueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery("keyword", keyword);
boolQueryBuilder.filter(matchFiler).should(matchQuery).should(matchPhraseQueryBuilder).minimumShouldMatch(1);
searchSourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));
// 按是否新品排序
if (isNew) {
searchSourceBuilder.sort(new FieldSortBuilder("isNew").order(SortOrder.DESC));
}
// 按是否熱品排序
if (isHot) {
searchSourceBuilder.sort(new FieldSortBuilder("isHot").order(SortOrder.DESC));
}
// 按價格高低排序
if (isPrice) {
searchSourceBuilder.sort(new FieldSortBuilder("retailPrice").order("asc".equals(orderBy) ? SortOrder.ASC : SortOrder.DESC));
}
// 按銷量排序
if (isSales) {
searchSourceBuilder.sort(new FieldSortBuilder("sales").order(SortOrder.DESC));
}
// 篩選新品
if (filterNew) {
MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isNew", true);
boolQueryBuilder.filter(filterQuery);
}
// 篩選熱品
if (filterHot) {
MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isHot", true);
boolQueryBuilder.filter(filterQuery);
}
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.from((int) (page.getCurrent() - 1) * (int) page.getSize());
searchSourceBuilder.size((int) page.getSize());
List<JSONObject> list = elasticDocument.search("goods", searchSourceBuilder, JSONObject.class);
4. 訂單編號生成規(guī)則:秒級時間戳 + 加密用戶ID + 今日第幾次下單
- 秒級時間戳:時間遞增保證唯一性
- 加密用戶ID:加密處理牲蜀,返回用戶ID6位數(shù)字涣达,可以防并發(fā)訪問,同一秒用戶不會產(chǎn)生2個訂單
- 今日第幾次下單:便于運(yùn)營查詢處理用戶當(dāng)日訂單
5. 下單流程處理過程寇窑,通過rabbitMQ異步生成訂單鸦概,提高系統(tǒng)下單處理能力
- 用戶點(diǎn)擊提交訂單按鈕饮笛,后臺生成訂單編號和訂單金額跳轉(zhuǎn)到訂單支付頁面脓诡,并發(fā)送rabbitMQ消息(包含訂單編號等信息)
- 訂單消費(fèi)者接受到訂單消息后生成訂單記錄(未支付)
- 用戶點(diǎn)擊支付按鈕時,前端根據(jù)訂單編號輪詢訂單信息查詢接口,如果訂單編號記錄已經(jīng)入庫則進(jìn)行后續(xù)支付操作,如果訂單編號未入庫則返回錯誤信息(訂單異常)
- 用戶支付完成后在回調(diào)通知里更新訂單狀態(tài)為已支付
6. 金剛區(qū)跳轉(zhuǎn)使用策略模式
# 1. 定義金剛位跳轉(zhuǎn)策略接口
public interface DiamondJumpType {
List<Goods> getGoods(Page<Goods> page, Diamond diamond);
Integer getType();
}
# 2. 定義策略實(shí)現(xiàn)類,并使用@Component注解注入spring
@Component
public class CategoryStrategy implements DiamondJumpType {
@Autowired
private GoodsMapper goodsMapper;
@Override
public List<Goods> getGoods(Page<Goods> page, Diamond diamond) {
List<Long> cateList = Arrays.asList(diamond.getValueId());
return goodsMapper.selectGoodsListPageByl2CateId(page, cateList).getRecords();
}
@Override
public Integer getType() {
return JumpTypeEnum.CATEGORY.getType();
}
}
@Component
public class ColumnStrategy implements DiamondJumpType {
@Autowired
private IColumnGoodsRelationService iColumnGoodsRelationService;
@Autowired
private IGoodsService iGoodsService;
@Override
public List<Goods> getGoods(Page<Goods> page, Diamond diamond) {
List<ColumnGoodsRelation> goodsRelationList = iColumnGoodsRelationService.list(new QueryWrapper<ColumnGoodsRelation>()
.eq("column_id", diamond.getValueId()));
List<Long> goodsIdList = goodsRelationList.stream().map(ColumnGoodsRelation::getGoodsId).collect(Collectors.toList());
Page<Goods> goodsPage = iGoodsService.page(page, new QueryWrapper<Goods>().in("id", goodsIdList).eq("is_on_sale", true));
return goodsPage.getRecords();
}
@Override
public Integer getType() {
return JumpTypeEnum.COLUMN.getType();
}
}
# 3. 定義策略上下文铲球,通過構(gòu)造器注入spring,定義map屬性涕滋,通過key獲取對應(yīng)策略實(shí)現(xiàn)類
@Component
public class DiamondJumpContext {
private Map<Integer, DiamondJumpType> map = new HashMap<>();
/**
* 由spring自動注入DiamondJumpType子類
*
* @param diamondJumpTypes 金剛位跳轉(zhuǎn)類型集合
*/
public DiamondJumpContext(List<DiamondJumpType> diamondJumpTypes) {
for (DiamondJumpType diamondJumpType : diamondJumpTypes) {
map.put(diamondJumpType.getType(), diamondJumpType);
}
}
public DiamondJumpType getInstance(Integer jumpType) {
return map.get(jumpType);
}
}
# 4.使用
@Autowired
private DiamondJumpContext diamondJumpContext;
@Test
public void test(){
DiamondJumpType diamondJumpType = diamondJumpContext.getInstance(1);
}
- todo
文件目錄
|-- waynboot-admin-api // 運(yùn)營后臺api模塊睬辐,提供后臺項目api接口
|-- waynboot-common // 通用模塊,包含項目核心基礎(chǔ)類
|-- waynboot-data // 數(shù)據(jù)模塊,通用中間件數(shù)據(jù)訪問
| |-- waynboot-data-redis // redis訪問配置模塊
| |-- waynboot-data-elastic // elastic訪問配置模塊
|-- waynboot-generator // 代碼生成模塊
|-- waynboot-message-consumer // 消費(fèi)者模塊溯饵,處理訂單消息和郵件消息
|-- waynboot-message-core // 消費(fèi)者核心模塊侵俗,隊列、交換機(jī)配置
|-- waynboot-mobile-api // h5商城api模塊丰刊,提供h5商城api接口
|-- pom.xml // maven父項目依賴隘谣,定義子項目依賴版本
|-- ...
開發(fā)部署
# 1. 克隆項目
git clone git@github.com:wayn111/waynboot-mall.git
# 2. 導(dǎo)入項目依賴
將waynboot-mall目錄用idea打開,導(dǎo)入maven依賴
# 3. 安裝Mysql8.0+啄巧、Redis3.0+寻歧、RabbitMQ3.0+、ElasticSearch7.0+到本地
# 4. 導(dǎo)入sql文件
在項目根目錄下秩仆,找到`wayn_shop_*.sql`文件码泛,新建mysql數(shù)據(jù)庫wayn_shop,導(dǎo)入其中
# 5. 修改Mysql澄耍、Redis噪珊、RabbitMQ、Elasticsearch連接配置
修改`application-dev.yml`以及`application.yml`文件中數(shù)據(jù)連接配置相關(guān)信息
# 6. 啟動項目
后臺api:
進(jìn)入waynboot-admin-api子項目齐莲,找到AdminApplication文件痢站,右鍵`run AdminApplication`,啟動后臺項目
h5商城api:
進(jìn)入waynboot-mobile-api子項目选酗,找到MobileApplication文件阵难,右鍵`run MobileApplication`,啟動h5商城項目
在線體驗
- 注冊一個賬號
- 然后登陸
演示地址:http://www.wayn.ltd
演示圖
<table>
<tr>
<td>商城登陸<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/65b4034d88e84a42b11c7b6a87814edb~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>商城注冊<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2b0e04791564dfa8bb0b9634ea7702b~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>商城首頁<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df80bcb33e474d4197cda0348121dcd5~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>商城搜索<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aeac5c60607b405d919aaaa02370b8fc~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>搜索結(jié)果展示<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/61566ef889464e8d9688d88fcac61f0d~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>金剛位跳轉(zhuǎn)<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b4ce663a2424dc6a7350a5dbb3d5e0f~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>商品分類<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1b83f6aa52f44cbfa44b98afd41524ee~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>商品詳情<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/238ea7cb111845e1b0423de68e81316c~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>商品sku選擇<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fe3fc39d847f40ae9f5e58f7e2586b46~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>購物車查看<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4cb2462cb19247d990d7e701f22c955a~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>確認(rèn)下單<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e9fd55784e034b67bc2b589a8aee645f~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>選擇支付方式<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0cb06a3b3fa24311b8238a8521f6f888~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>商城我的頁面<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da54b9d18f0742529a784746dc87a89d~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>我的訂單列表<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f5c919f1dffc46ffbec1eab3ef7e33a6~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>添加商品評論<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3c477e09b8e3489e9b100671c715d2ae~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>查看商品評論<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/64d012554a9045f5b42c1702b49ef2e2~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>后臺登陸<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45dbbbe3cbc14b63911d35fa12136256~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>后臺首頁<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/811e525fa18844e98ef681bce08653ff~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>后臺會員管理<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8f77c617def347919e0861a53d7c07b1~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>后臺評論管理<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d06dabfe36984ed5847f450e7e4c89ad~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>后臺地址管理<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a73a2db045be4e759ecc97561cf2c0af~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>后臺添加商品<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/14b2bd3639934406ae689524bd4838ab~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>后臺商品管理<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2d5223528f4349928dacfe879b2152e5~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>后臺banner管理<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/67026d2de99448e297e56ae22ad6795e~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>后臺訂單管理<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/61b97057306c47b4925c4f9b33eff5a8~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>后臺分類管理<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/16466b2c54844128bad9aff2257e6e57~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
<tr>
<td>后臺金剛區(qū)管理<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2ebab134a6a4729ace351d5cf894094~tplv-k3u1fbpfcp-zoom-1.image"/></td>
<td>后臺欄目管理<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5cb4d43ae5c2441d8464eb9ca9df9968~tplv-k3u1fbpfcp-zoom-1.image"/></td>
</tr>
</table>
waynboot-mall交流群
QQ群:862417126 有問題可以先提issue??
todo
- 支持多店鋪
- 訂單詳情頁面
- 秒殺專區(qū)
- 優(yōu)惠卷使用
- 團(tuán)購下單
- ...