乐优商城(15)--订单服务
樂優商城(15)–訂單服務
一、訂單結算頁
頁面跳轉
在購物車頁面的最下方,有一個去結算按鈕:
當點擊結算,應該跳轉到訂單結算頁,即:getOrderInfo.html
查看購物車的結算按鈕:
可以看到,地址是正確的。但是只有登錄用戶才可以去結算付款,因此不能直接跳轉,而是在跳轉前校驗用戶的登錄狀態,如果發現是未登錄,應該重定向到登錄頁!
給這個按鈕綁定點擊事件:
事件中判斷登錄狀態,進行頁面跳轉:
toOrderInfo() {// 判斷是否登錄ly.verifyUser().then(() => {// 已登錄window.location.href = "/getOrderInfo.html"}).catch(() => {// 未登錄window.location.href = "/login.html?returnUrl=" + window.location.href;}) }登錄后測試:
此處頁面需要渲染的內容主要包含3部分:
- 收貨人信息
- 支付方式
- 商品信息
二、地址管理
2.1、頁面效果
點擊“新增收貨地址”和“編輯”都會彈出模態框,如下所示:
2.2、數據庫設計
CREATE TABLE `tb_address` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '地址id',`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用戶id',`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收貨人姓名',`phone` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收貨人電話',`zip_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '郵編',`state` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '省份',`city` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '市',`district` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '區/縣',`address` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '詳細地址',`default_address` tinyint(1) NULL DEFAULT NULL COMMENT '1:默認地址 0:非默認地址',`label` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址標簽',PRIMARY KEY (`id`) USING BTREE,INDEX `userId`(`user_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;2.3、頁面渲染
2.3.1、定義數據模型
2.3.2、表單改造
<div tabindex="-1" role="dialog" data-hasfoot="false" class="sui-modal hide fade edit"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><button type="button" @click="clear" data-dismiss="modal" aria-hidden="true" class="sui-close">×</button><h4 id="myModalLabel" class="modal-title">{{isEdit ? "編輯" : "添加"}}收貨地址</h4></div><div class="modal-body"><form id="myform" action="" class="sui-form form-horizontal"><div class="control-group"><label class="control-label">收貨人:</label><div class="controls"><input type="text" v-model="addressForm.name" class="input-medium"></div></div><div class="control-group"><label class="control-label">聯系電話:</label><div class="controls"><input type="text" v-model="addressForm.phone" class="input-medium"></div></div><div class="control-group"><label class="control-label">省:</label><div class="controls"><input type="text" v-model="addressForm.state" class="input-medium"></div></div><div class="control-group"><label class="control-label">市:</label><div class="controls"><input type="text" v-model="addressForm.city" class="input-medium"></div></div><div class="control-group"><label class="control-label">區/縣:</label><div class="controls"><input type="text" v-model="addressForm.district" class="input-medium"></div></div><div class="control-group"><label class="control-label">郵編:</label><div class="controls"><input type="text" v-model="addressForm.zipCode" class="input-medium"></div></div><div class="control-group"><label class="control-label">詳細地址:</label><div class="controls"><input type="text" v-model="addressForm.address" class="input-large"></div></div><div class="control-group"><label class="control-label">地址標簽:</label><div class="controls"><select class="select" v-model="addressForm.label"><option value="家">家</option><option value="公司">公司</option><option value="學校">學校</option></select></div></div><div class="control-group"><div style="margin-left: 100px"><input type="checkbox" v-model="addressForm.defaultAddress" class="checkbox">設為默認收貨地址</div></div></form></div><div class="modal-footer"><button type="button" @click="addressSave" data-ok="modal" class="sui-btn btn-primary btn-large">確定</button><button type="button" @click="clear" data-dismiss="modal" class="sui-btn btn-default btn-large">取消</button></div></div></div> </div>2.3.3、方法綁定
新增地址
修改地址
提交和取消表單
取消的地方有兩個,一個為取消另一個為X
2.4、后端實現
2.4.1、實體類
@Table(name = "tb_address") public class Address {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@NotNullprivate Long userId; //用戶idprivate String name; //收貨人名稱private String phone; //收貨手機private String zipCode; //郵編private String state; //省份private String city; //市private String district; //區/縣private String address; //具體地址private Boolean defaultAddress; //是否為默認地址private String label; //標簽//get和set }2.4.2、AddressMapper
/*** Address 的通用 mapper*/ public interface AddressMapper extends Mapper<Address> { }2.4.3、AddressController
基本的增刪改查
@RestController @Api("地址管理接口") @RequestMapping("/address") public class AddressController {@Autowiredprivate AddressService addressService;/*** 新增收貨地址* @param address* @return*/@PostMapping@ApiOperation(value = "新增用戶的一個收貨地址",notes = "創建收貨地址")@ApiImplicitParam(name = "address",required = true,value = "收貨地址對象")@ApiResponses({@ApiResponse(code = 201,message = "地址新增成功"),@ApiResponse(code = 500,message = "服務器異常")})public ResponseEntity<Void> addAddress(@RequestBody Address address){this.addressService.addAddress(address);return ResponseEntity.status(HttpStatus.CREATED).build();}/*** 根據用戶id查詢所有收貨地址* @return*/@GetMapping@ApiOperation(value = "查詢當前登錄用戶下的所有收貨地址",notes = "查詢收貨地址")@ApiResponses({@ApiResponse(code = 200,message = "成功查詢該用戶的所有收貨地址"),@ApiResponse(code = 404,message = "未查到該用戶的收貨地址"),@ApiResponse(code = 500,message = "服務器異常")})public ResponseEntity<List<Address>> queryAddressByUserId(){List<Address> addresses = this.addressService.queryAddressByUserId();if (CollectionUtils.isEmpty(addresses)){return ResponseEntity.notFound().build();}return ResponseEntity.ok(addresses);}/*** 根據用戶id更新收貨地址* @param address* @return*/@PutMapping@ApiOperation(value = "更新當前用戶的收貨地址",notes = "更新收貨地址")@ApiImplicitParam(name = "address",required = true,value = "收貨地址對象")@ApiResponses({@ApiResponse(code = 204,message = "更新收貨地址成功"),@ApiResponse(code = 500,message = "服務器異常")})public ResponseEntity<Void> updateAddressByUserId(@RequestBody Address address){this.addressService.updateAddress(address);return ResponseEntity.noContent().build();}/*** 根據地址id查詢收貨地址* @param addressId* @return*/@GetMapping("{addressId}")@ApiOperation(value = "查詢一個收貨地址",notes = "查詢收貨地址")@ApiImplicitParam(name = "addressId",value = "收貨地址編號",required = true)@ApiResponses({@ApiResponse(code = 200,message = "查詢成功"),@ApiResponse(code = 404,message = "未找到地址"),@ApiResponse(code = 500,message = "服務器異常")})public ResponseEntity<Address> queryAddressById(@PathVariable("addressId") Long addressId){Address address = this.addressService.queryAddressById(addressId);if (null == address){return ResponseEntity.notFound().build();}return ResponseEntity.ok(address);}/*** 刪除一個收貨地址* @param addressId* @return*/@DeleteMapping("{addressId}")@ApiOperation(value = "刪除一個收貨地址",notes = "刪除收貨地址")@ApiImplicitParam(name = "addressId",value = "收貨地址編號",required = true)@ApiResponses({@ApiResponse(code = 200,message = "刪除成功"),@ApiResponse(code = 500,message = "刪除失敗")})public ResponseEntity<Void> deleteAddress(@PathVariable("addressId") Long addressId){this.addressService.deleteAddress(addressId);return ResponseEntity.ok().build();} }2.4.4、AddressService
public interface AddressService {/*** 新增收貨地址* @param address* @return*/void addAddress(Address address);/*** 根據用戶id查詢所有收貨地址* @return*/List<Address> queryAddressByUserId();/*** 根據用戶id更新收貨地址* @param address* @return*/void updateAddress(Address address);/*** 根據地址id查詢收貨地址* @param addressId* @return*/Address queryAddressById(Long addressId);/*** 刪除一個收貨地址* @param addressId* @return*/void deleteAddress(Long addressId); }實現類:
@Service public class AddressServiceImpl implements AddressService {@Autowiredprivate AddressMapper addressMapper;/*** 新增收貨地址** @param address* @return*/@Overridepublic void addAddress(Address address) {UserInfo userInfo = LoginInterceptor.getLoginUser();address.setUserId(userInfo.getId());setDefaultAddress(address);this.addressMapper.insert(address);}/*** 根據用戶id查詢所有收貨地址** @return*/@Overridepublic List<Address> queryAddressByUserId() {UserInfo userInfo = LoginInterceptor.getLoginUser();Example example = new Example(Address.class);example.createCriteria().andEqualTo("userId",userInfo.getId());return this.addressMapper.selectByExample(example);}/*** 根據用戶id更新收貨地址** @param address* @return*/@Overridepublic void updateAddress(Address address) {UserInfo userInfo = LoginInterceptor.getLoginUser();address.setUserId(userInfo.getId());setDefaultAddress(address);this.addressMapper.updateByPrimaryKeySelective(address);}/*** 根據地址id查詢收貨地址** @param addressId* @return*/@Overridepublic Address queryAddressById(Long addressId) {//查詢時還需匹配用戶idUserInfo userInfo = LoginInterceptor.getLoginUser();Example example = new Example(Address.class);example.createCriteria().andEqualTo("id",addressId).andEqualTo("userId",userInfo.getId());return this.addressMapper.selectByExample(example).get(0);}/*** 刪除一個收貨地址** @param addressId* @return*/@Overridepublic void deleteAddress(Long addressId) {UserInfo userInfo = LoginInterceptor.getLoginUser();Example example = new Example(Address.class);example.createCriteria().andEqualTo("id",addressId).andEqualTo("userId",userInfo.getId());this.addressMapper.deleteByExample(example);}/*** 將前一個地址的默認狀態設置為 false* @param address*/private void setDefaultAddress(Address address){if (address.getDefaultAddress()){List<Address> addresses = queryAddressByUserId();//如果將本地址設置為默認地址,那么該用戶下的其他地址都應該是非默認地址addresses.forEach(addr ->{if (addr.getDefaultAddress()){addr.setDefaultAddress(false);this.addressMapper.updateByPrimaryKeySelective(addr);}});}} }2.4.5、網關路由
2.5、接口測試
在進行接口測試時,需要要設置cookie信息
2.5.1、新增地址
數據輸入:
{ "name":"李四", "phone":"13555555555", "zipCode":"123123", "state":"四川省", "city":"成都市", "district":"高新區", "address":"四川省成都市高新區", "label":"公司", "defaultAddress":true }結果
查看數據庫:
2.5.2、查詢所有地址
結果
目前該用戶只有一個地址
2.5.3、修改地址
數據輸入:
{ "id":"4","name":"李四修改", "phone":"13555555555", "zipCode":"123123", "state":"四川省", "city":"成都市", "district":"高新區", "address":"四川省成都市高新區", "label":"公司修改", "defaultAddress":true }結果
數據庫:
2.5.4、查詢一個地址
結果
2.5.5、刪除一個地址
結果
通過查詢所有地址接口查詢當前用戶的地址:
2.6、頁面改造
2.6.1、地址查詢
在頁面加載時,查詢當前登錄用戶下所有地址信息。然后保存在可選地址列表addresses中進行渲染,查詢地址時將默認地址放在第一位:
頁面渲染
效果
默認地址:
一個用戶只能設置一個默認地址,那么在新增和修改地址時就要做處理,如果新增或修改的地址被設置為默認地址,那么該用戶下其它地址都變為非默認地址
2.6.2、地址新增
addressSave(){//1.驗證是否登錄ly.verifyUser().then(() => {//2.登錄,發起請求,保存或者修改地址if (this.isEdit === false) {//2.1新增ly.http.post("/address", this.addressForm).then(() => {//保存成功,重新加載數據this.loadData();//清空表單this.clear();}).catch()}else {//2.2 修改ly.http.put("/address", this.addressForm).then(() => {//修改成功,重新加載數據this.loadData();//清空表單this.clear();}).catch()}}).catch(() => {//3.未登錄window.location.href = "/login.html?returnUrl=" + window.location.href;}); }這里是將新增和修改兩個請求合在一起,用一個數據模型標志是修改還是新增:
2.6.3、數據回顯
當點擊修改時,通過傳入的地址id查詢相應地址信息,然后回顯到表單中
editAddress(id){this.isEdit = true;ly.verifyUser().then(() => {ly.http.get("/address/"+id).then(({data}) => {this.addressForm = data;})}).catch(() => {window.location = "/login.html?returnUrl=" + window.location.href;}) }2.6.4、地址刪除
deleteAddress(id){ly.verifyUser().then(() => {ly.http.delete("/address/"+id).then(() => {this.loadData();})}).catch(() => {window.location.href = "/login.html?returnUrl=" + window.location.href;}); }三、支付方式
支付方式有2種:
- 微信支付
- 貨到付款
與訂單數據中的paymentType關聯:
所以在Vue實例中定義一個數據模型來記錄支付方式:
然后在頁面渲染時與這個變量關聯:
四、商品信息
效果圖
這里的送貨清單,其實就是購物車中用戶選擇的要付款的商品
因此,需要在購物車跳轉過來的同時,攜帶選中的購物車的信息
4.1、購物車信息獲取
修改cart.html中的頁面跳轉邏輯,把用戶選中的購物車信息傳遞過來:
然后在created鉤子函數中獲取購物車數據,保存到本地屬性,要注意的是,應該在獲取數據前校驗用戶登錄狀態,如果發現未登錄,則直接重定向到登錄頁:
然后重新加載頁面,查看控制臺:
4.2、頁面渲染
4.3、總金額
可以看出這里主要有4個數據:
- 總金額:totalPay
- 優惠返現:discount
- 運費:postFee
- 實付金額:actualPay
不過目前沒有做優惠活動模塊,另外運費需要結合物流系統來計算,暫時都沒有且設置為0,在order屬性中寫死:
通過計算屬性來得到totalPay和actualPay值:
computed:{totalNum(){return this.carts.reduce((c1,c2) => c1 + c2.num,0);},totalPay(){return this.carts.reduce((c1,c2) => c1 + c2.price * c2.num,0);},actualPay(){return this.totalPay + this.order.postFee - this.order.discount;} }然后在頁面渲染:
這里還有一個收貨地址的顯示,一并渲染了
4.4、提交訂單
4.4.1、頁面提交
來看下訂單接口所需要的數據:
{"totalPay": 236800,"postFee": 0,"paymentType": 2,"actualPay": 236800,"buyerMessage": null,"buyerNick": "huge","orderDetails": [{"skuId": 3893493,"num": 1,"title": "蘋果(Apple)iPhone 6 (A1586) 16GB 金色 移動聯通電信4G手機3","price": 236800,"ownSpec": "{\"機身顏色\":\"鉆雕藍\",\"內存\":\"4GB\",\"機身存儲\":\"64GB\"}","image": "http://image.leyou.com/images/9/4/1524297342728.jpg"}],"receiver": "銷戶","receiverMobile": "15800000000","receiverState": "上海","receiverCity": "上海","receiverDistrict": "浦東新簽","receiverAddress": "航頭鎮航頭路18號泥頭車3號樓","receiverZip": "210000","invoiceType": 0,"sourceType":2 }分為3部分,分別是
-
訂單本身的基本信息
- 總金額
- 實付金額
- 付款類型
- 買家信息就是當前用戶
-
訂單詳情
-
就是購物車中的商品,不過購物車數據會多出一個userId,去除即可:
-
-
物流信息
- 當前用戶選中的物流地址信息
綁定事件:
注:使用@click.prevent是因為要阻止a標簽的默認跳轉,去執行submit函數。
方法
methods: {submit() {// 把購物車數據處理成訂單詳情const orderDetails = this.carts.map(({userId, ...rest}) => rest);// 處理物流信息const addr = this.addresses[this.selectedAddress];const obj = {receiver: addr.name,receiverState: addr.state,receiverCity: addr.city,receiverAddress: addr.address,receiverDistrict: addr.district,receiverMobile: addr.phone,receiverZip: addr.zipCode};// 復制到訂單對象Object.assign(this.order, obj, {orderDetails,totalPay: this.totalPay,actualPay: this.actualPay,});// 提交訂單ly.http.post("/order", this.order).then(({data}) => {// 在線支付,需要到付款頁window.location = "pay.html?orderId=" + data;}).catch((resp) => {alert("訂單提交失敗,請稍后再試!")})} },4.4.2、精度損失問題
在頁面點擊提交測試:
數據庫中訂單id:
但是仔細看頁面攜帶的訂單id:
前面也說過,會出現精度損失的情況,那是什么原因導致的?
這其實是因為JS的長整數精度有限,java的Long類型數據超出了范圍,所以出現了精度損失。
后臺返回的是Json的字符串,在axios內部會自動調用 JSON.parse()方法把json字符串轉為JS數據,就會出現進度損失。如果不進行轉換,依然當做字符串來使用,就不會有問題了。
當然這里只是其中一種解決方法,前端的解決方案,不夠優雅
采用后端的解決方法:
Spring MVC中默認是使用了Jackson的。通過重寫轉換器解決
@Configuration @EnableWebMvc //需要使用swagger測試需要將該注解暫時注釋 public class MvcConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =new MappingJackson2HttpMessageConverter();ObjectMapper objectMapper = new ObjectMapper();SimpleModule simpleModule = new SimpleModule();simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);simpleModule.addSerializer(Long.class, ToStringSerializer.instance);simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);objectMapper.registerModule(simpleModule);jackson2HttpMessageConverter.setObjectMapper(objectMapper);converters.add(jackson2HttpMessageConverter);converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));} }五、微信支付
5.1、介紹
微信支付官方文檔:https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F
選擇文檔中心,點擊API文檔
選擇基礎支付中的Native支付,點擊開發指引
5.2、開發流程
業務流程說明:
具體工作
這里把商戶要做的事情總結一下:
-
商戶生成訂單
-
商戶調用微信下單接口,獲取預交易的鏈接
-
商戶將鏈接生成二維碼圖片,展示給用戶;
-
用戶支付并確認
-
支付結果通知:
- 微信異步通知商戶支付結果,商戶告知微信支付接收情況
- 商戶如果沒有收到通知,可以調用接口,查詢支付狀態
-
如果支付成功,發貨,修改訂單狀態
在前面的業務中,已經完成了:
- 生成訂單
接下來需要完成的是:
-
調用微信接口,生成鏈接。
-
生成支付二維碼圖片
5.3、生成二維碼
5.3.1、生成預交易鏈接
先根據訂單的編號,調用后臺服務,生成交易鏈接,而后才能根據鏈接生成二維碼
在頁面發起請求:
var payVm = new Vue({el:"#payVm",data:{ly,orderId:0, //訂單id},components:{shortcut: () => import("/js/pages/shortcut.js")},created(){this.loadData();},methods:{loadData(){//1.判斷用戶是否登錄ly.verifyUser().then(() => {//2.獲取訂單編號this.orderId = ly.getUrlParam("orderId");//3.獲取請求鏈接ly.http.get("/order/url/" + this.orderId).then(resp => {console.log(resp.data);new QRCode(document.getElementById("qrImage"),{text:resp.data,width:250,height:250,colorDark: "#000000",colorLight: "#ffffff",correctLevel: QRCode.CorrectLevel.H});});}).catch(() => {//4.未登錄,跳轉至登錄頁location.href = "/login.html?returnUrl=" + location.href;})}}});注意:app.js要最后引入,因為要先有id為app的div,vue才能獲取相應的元素
后臺已經定義好生成付款地址的接口:generateUrl
5.3.2、生成二維碼
使用生成二維碼的JS插件:qrcode,官網:https://github.com/davidshimjs/qrcodejs
將該js引入項目中
頁面引入
頁面定義一個div用來展示二維碼
二維碼相關樣式
5.4、付款狀態查詢
不過,因為不清楚用戶何時會付款,因此這里采用循環的方式,不斷請求判斷是否支付成功
支付成功
六、頁面優化
6.1、支付頁面顯示總金額
getOrderInfo.html
在提交訂單成功返回訂單號時,隨著頁面跳轉到支付頁面的同時將計算好的總金額存入LocalStorage,然后在需要的地方進行展示
pay.html
加載數據時,獲取總金額:
頁面渲染:
paysuccess.html
加載支付成功頁面的時候先認證用戶,然后讀取總金額,刪除本地存儲
頁面渲染:
6.2、修改訂單號的傳遞方式
將訂單號放入本地存儲,不再通過訪問路徑傳遞。
支付成功,將LocalStorage中的訂單號刪掉。
支付失敗還可以跳轉到支付頁面繼續進行支付操作
修改訂單提交函數
支付頁面獲取訂單號
支付成功后,刪除本地存儲的訂單號
6.3、購物車數據更新
支付成功,刪除本地存的數據(paysuccess.html)
用戶支付成功后,將已付款的商品從購物車中刪除。
新增接口:根據訂單id查詢skuId
controller
- 請求方式:GET
- 請求路徑:/order/skuId/{id}
- 請求參數:id,訂單id
- 返回結果:商品編號集合
Service
/*** 根據訂單id查詢該訂單下的所有skuId* @param orderId* @return*/ List<Long> querySkuIdByOrderId(Long orderId);實現類:
/*** 根據訂單id查詢該訂單下的所有skuId** @param orderId* @return*/ @Override public List<Long> querySkuIdByOrderId(Long orderId) {Example example = new Example(OrderDetail.class);example.createCriteria().andEqualTo(orderId);List<OrderDetail> orderDetails = this.orderDetailMapper.selectByExample(example);//返回商品集合List<Long> skuIds = new ArrayList<>();orderDetails.forEach(orderDetail -> skuIds.add(orderDetail.getSkuId()));return skuIds; }前端頁面處理
總結
以上是生活随笔為你收集整理的乐优商城(15)--订单服务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js层级选择框样式_【JavaWeb】8
- 下一篇: ES6-14 Unicode表示法、字符