我平時的工作中,偶爾會用 Java 做一些解析HTML的工作。有的時候我需要刪除所有的HTML標簽,只保留純文字內容。這個問題在做過一些爬蟲工作的朋友來說很簡單。下面來說說,我們平時使用到的集中解析的方法。
通過爬蟲爬到的HTML內容,從程序角度來講,就是一個字符串。我們可以對其按照純文本處理的方式來處理。
我們在做文本處理的時候,第一個想到的就是正則表達式。從一個字符串中刪除HTML,對于正則來說,還是比較簡單的。畢竟還是有固定的格式,比如“<...>”。
我們常用的的正則就是 <[^>]> 或者 <.*?> 。
我們在使用正則的時候,需要注意的是正則默認是貪婪匹配。也就是說,正則表達式<.*> 能夠匹配到更多的HTML內容,而不是單個標簽。
現在,讓我們測試一下它是否能從HTML源中刪除標簽。
在我們測試刪除HTML標簽之前,首先讓我們創建一個HTML例子,例如example1.html。
<!DOCTYPE html>
<html>
<head>
<title>這是標題</title>
</head>
<body>
<p>
如果應用程序X沒有啟動,可能的原因是<br/>
1. <a href="https://maven.apache.org">Maven</a>沒有安裝<br/>
2. 磁盤空間不足<br/>
3. 內存不足
</p>
</body>
</html>
現在,讓我們寫一個測試,用String.replaceAll()來刪除HTML標簽。
String html = ... // load example1.html
String result = html.replaceAll("<[^>]`>", "");
System.out.println(result);
如果我們運行這個測試方法,我們會看到結果。
這是標題
如果應用程序X沒有啟動,可能的原因是
1.Maven沒有安裝
2.磁盤空間不足
3.沒有足夠的內存
輸出結果保留了剝離后的HTML的空白處。我們在處理提取的文本時,可以很容易地刪除或跳過這些空行或空白處。
我們剛才已經看到了,通過使用Regex來刪除HTML標簽是非常簡單。但是粗暴的使用這種方法會有很多問題,我們不能預測最終的結果會是怎么樣的。
例如,一個HTML文檔可能有<script>或<style>標簽,而我們可能不希望在結果中出現它們的內容。
此外,<script>、<style>、甚至是<body>標簽中的文本可能包含 <或 >字符。如果是這種情況,我們的正則方法可能會出錯。
現在,讓我們看看另一個例子,比如example2.html。
<!DOCTYPE HTML>
<html>
<head>
<title>這是標題</title>
</head>
<script>
// some js function
</script>
<body>
<p>
如果應用程序X沒有啟動,可能的原因是<br/>
1. <a
id="link"
href="http://maven.apache.org/">
Maven
</a> 沒有安裝<br/>
2. 磁盤空間不足 (<1G) <br/>
3. 內存不足(<64MB)<br/>
</p>
</body>
</html>
現在我們有一個<script>標簽和 <字符在<body>標簽內。
如果我們對example2.html使用同樣的方法,我們會得到如下內容。
這是標題
// some js function
如果應用程序X沒有啟動,可能的原因是
1.
Maven
沒有安裝
2. 磁盤空間不足 (
3. 內存不足(
顯然,由于"<"字符的存在,我們丟失了一些文本。所以正則在處理文本的時候并不是萬能的。我們可以使用一些 HTML 解析器來做這些比較復雜的場景。
Jsoup 是一個流行的HTML解析庫,如果想要從一個HTML文檔中提取文本,我們可以簡單地調用Jsoup.parse(htmlString).text()。
在項目中使用的時候,我們首先需要添加 jsoup 的依賴庫,我們這里就通過maven的方式引入。
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
</dependency>
我們用 example2.html來測試一下。
String html = ... // load example2.html
System.out.println(Jsoup.parse(html).text());
如果我們讓這個方法運行,它就會打印出來。
這是標題 如果應用程序X沒有啟動,可能的原因是 1.Maven沒有安裝 2.沒有足夠的(<1G)磁盤空間 3.沒有足夠的(<64MB)內存
從輸出結果可知,Jsoup已經成功地從HTML文檔中提取了文本。另外,<script>元素中的文本已經被忽略了。
此外,默認情況下,Jsoup會刪除所有的文本格式和空白處,比如換行符。
HTMLCleaner 也是一個HTML解析庫。
首先,我們需要在pom.xml中添加HTMLCleaner 依賴。
<dependency>
<groupId>net.sourceforge.htmlcleaner</groupId>
<artifactId>htmlcleaner</artifactId>
<version>2.25</version>
</dependency>
我們可以設置[各種參數](http://htmlcleaner.sourceforge.net/parameters.php)來控制HTMLCleaner的解析行為。我們在這里使用HTMLCleaner在解析example2.html時跳過<script>元素。
String html = ... // load example2.html
CleanerProperties props = new CleanerProperties();
props.setPruneTags("script");
String result = new HtmlCleaner(props).clean(html).getText().toString();
System.out.println(result);
運行一下,HTMLCleaner將產生這樣的輸出。
這是標題
如果應用程序X沒有啟動,可能的原因是:
1.Maven沒有安裝
2.沒有足夠的(<1G)磁盤空間
3.內存不足(<64MB)
我們可以看到,<script>元素中的內容被忽略了, <br/>標簽轉換為提取的文本中的換行符。另外, HTMLCleaner 保留了HTML的空白內容。
在這篇文章中,我們學習了幾種去除HTML的方法,我們需要注意的是,正則在文本處理的過程中并不是萬能的。
程目標
目標1:完成商家后臺商品列表的功能
目標2:完成商家后臺商品修改的功能
目標3:完成運營商后臺商品審核的功能
目標4:完成運營商后臺商品刪除的功能
目標5:掌握注解式事務的配置
1.1需求分析
在商家后臺,顯示該商家的商品列表信息,如下圖:
1.2查詢商家商品列表
1.2.1后端代碼
修改pinyougou-shop-web工程的GoodsController.java的search方法
@RequestMapping("/search") public PageResult search(@RequestBody TbGoods goods, int page, int rows ){ //獲取商家ID String sellerId = SecurityContextHolder.getContext().getAuthentication().getName(); //添加查詢條件 goods.setSellerId(sellerId); return goodsService.findPage(goods, page, rows); }
修改pinyougou-sellergoods-service 工程com.pinyougou.sellergoods.service.impl 的findPage方法,修改條件構建部分代碼,將原來的模糊匹配修改為精確匹配
if(goods.getSellerId()!=null && goods.getSellerId().length()>0){ //criteria.andSellerIdLike("%"+goods.getSellerId()+"%"); criteria.andSellerIdEqualTo(goods.getSellerId()); }
1.2.2前端代碼
修改goods.html. 引入js
<script type="text/javascript" src="../plugins/angularjs/angular.min.js"></script> <!-- 分頁組件開始 --> <script src="../plugins/angularjs/pagination.js"></script> <link rel="stylesheet" href="../plugins/angularjs/pagination.css"> <!-- 分頁組件結束 --> <script type="text/javascript" src="../js/base_pagination.js"></script> <script type="text/javascript" src="../js/service/goodsService.js"></script> <script type="text/javascript" src="../js/service/itemCatService.js"></script> <script type="text/javascript" src="../js/service/uploadService.js"></script> <script type="text/javascript" src="../js/service/typeTemplateService.js"></script> <script type="text/javascript" src="../js/controller/baseController.js"></script> <script type="text/javascript" src="../js/controller/goodsController.js"></script>
添加指令
<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController">
在頁面上放置分頁控件
<tm-pagination conf="paginationConf"></tm-pagination>
循環列表
<tr ng-repeat="entity in list"> <td><input type="checkbox"></td> <td>{{entity.id}}</td> <td>{{entity.goodsName}}</td> <td>{{entity.price}}</td> <td>{{entity.category1Id}}</td> <td>{{entity.category2Id}}</td> <td>{{entity.category3Id}}</td> <td> {{entity.auditStatus}} </td> <td class="text-center"> <button type="button" class="btn bg-olive btn-xs">修改</button> </td> </tr>
顯示效果如下:
1.3顯示狀態
修改goodsController.js,添加state數組
$scope.status=['未審核','已審核','審核未通過','關閉'];//商品狀態
修改列表顯示
{{status[entity.auditStatus]}}
顯示效果如下:
1.4顯示分類
我們現在的列表中的分類仍然顯示ID
如何才能顯示分類的名稱呢?
方案一:在后端代碼寫關聯查詢語句,返回的數據中直接有分類名稱。
方案二:在前端代碼用ID去查詢后端,異步返回商品分類名稱。
我們目前采用方案二:
(1)修改goodsController
$scope.itemCatList=[];//商品分類列表 //加載商品分類列表 $scope.findItemCatList=function(){ itemCatService.findAll().success( function(response){ for(var i=0;i<response.length;i++){ $scope.itemCatList[response[i].id]=response[i].name; } } ); }
代碼解釋:因為我們需要根據分類ID得到分類名稱,所以我們將返回的分頁結果以數組形式再次封裝。
(2)修改goods.html ,增加初始化調用
<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="findItemCatList()">
(3)修改goods.html , 修改列表
<td>{{itemCatList[entity.category1Id]}}</td> <td>{{itemCatList[entity.category2Id]}}</td> <td>{{itemCatList[entity.category3Id]}}</td>
1.5條件查詢
根據狀態和商品名稱進行查詢
修改goods.html
<div class="has-feedback"> 狀態:<select ng-model="searchEntity.auditStatus"> <option value="">全部</option> <option value="0">未審核</option> <option value="1">已審核</option> <option value="2">審核未通過</option> <option value="3">關閉</option> </select> 商品名稱:<input ng-model="searchEntity.goodsName"> <button class="btn btn-default" ng-click="reloadList()">查詢</button> </div>
2.1需求分析
在商品列表頁面點擊修改,進入商品編輯頁面,并傳遞參數商品ID,商品編輯頁面接受該參數后從數據庫中讀取商品信息,用戶修改后保存信息。
2.2基本信息讀取
我們首選讀取商品分類、商品名稱、品牌,副標題,價格等信息
2.2.1后端代碼
(1)修改pinyougou-sellergoods-interface的GoodsService.java
/** * 根據ID獲取實體 * @param id * @return */ public Goods findOne(Long id);
(2)修改pinyougou-sellergoods-service的GoodsServiceImpl.java
@Override public Goods findOne(Long id) { Goods goods=new Goods(); TbGoods tbGoods = goodsMapper.selectByPrimaryKey(id); goods.setGoods(tbGoods); TbGoodsDesc tbGoodsDesc = goodsDescMapper.selectByPrimaryKey(id); goods.setGoodsDesc(tbGoodsDesc); return goods; }
(3)修改pinyougou-shop-web(和pinyougou-manager-web)的GoodsController.java
/** * 獲取實體 * @param id * @return */ @RequestMapping("/findOne") public Goods findOne(Long id){ return goodsService.findOne(id); }
2.2.2前端代碼
(1)在goodsController中引入$location服務
//商品控制層(商家后臺) app.controller('goodsController',function($scope,$controller,$location,goodsService,uploadService,item_catService,type_templateService){ ......
(2)修改goodsController 添加代碼:
//查詢實體 $scope.findOne=function(){ var id= $location.search()['id'];//獲取參數值 if(id==null){ return ; } goodsService.findOne(id).success( function(response){ $scope.entity= response; } ); }
在goods_edit.html頁面上添加指令
<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="selectItemCat1List();findOne()">
測試:
地址欄輸入
http://localhost:9102/admin/goods_edit.html#?id=149187842867969
注意: ?前要加# ,則是angularJS的地址路由的書寫形式
2.3讀取商品介紹(富文本編輯器)
修改前端代碼 goodsController
//查詢實體 $scope.findOne=function(){ ................. goodsService.findOne(id).success( function(response){ $scope.entity= response; //向富文本編輯器添加商品介紹 editor.html($scope.entity.goodsDesc.introduction); } ); }
2.4顯示商品圖片列表
修改goodsController.js ,在dataLogic方法添加代碼,將圖片列表由字符串轉換為json集合對象
//查詢實體 $scope.findOne=function(){ .............. //如果有ID,則查詢實體 goodsService.findOne(id).success( function(response){ $scope.entity= response; //向富文本編輯器添加商品介紹 editor.html($scope.entity.goodsDesc.introduction); //顯示圖片列表 $scope.entity.goodsDesc.itemImages= JSON.parse($scope.entity.goodsDesc.itemImages); } ); }
2.5讀取商品擴展屬性
修改goodsController.js
//查詢實體 $scope.findOne=function(){ ......... goodsService.findOne(id).success( function(response){ ....................... //顯示擴展屬性 $scope.entity.goodsDesc.customAttributeItems= JSON.parse($scope.entity.goodsDesc.customAttributeItems); } ); }
經過測試,我們發現擴展屬性值并沒有讀取出來,這是因為與下列代碼發生沖突
$scope.$watch('entity.goods.typeTemplateId',function(newValue,oldValue){ ...... $scope.entity.goodsDesc.customAttributeItems = JSON.parse($scope.typeTemplate.customAttributeItems);//擴展屬性 }
我們讀取出來的值被覆蓋了,我們需要改寫代碼, 添加判斷,當用戶沒有傳遞id參數時再執行此邏輯
//監控模板ID ,讀取品牌列表 $scope.$watch('entity.goods.typeTemplateId',function(newValue,oldValue){ //讀取品牌列表和擴展屬性 typeTemplateService.findOne(newValue).success( function(response){ ....... //如果沒有ID,則加載模板中的擴展數據 if($location.search()['id']==null){ $scope.entity.goodsDesc.customAttributeItems = JSON.parse($scope.typeTemplate.customAttributeItems);//擴展屬性 } } ); ....... });
2.6讀取商品規格屬性
修改goodsController
//查詢實體 $scope.findOne=function(){ ...... goodsService.findOne(id).success( function(response){ $scope.entity= response; editor.html($scope.entity.goodsDesc.introduction);//商品介紹 $scope.entity.goodsDesc.itemImages= JSON.parse($scope.entity.goodsDesc.itemImages);//圖片列表 //擴展屬性列表 $scope.entity.goodsDesc.customAttributeItems =JSON.parse($scope.entity.goodsDesc.customAttributeItems); //規格 $scope.entity.goodsDesc.specificationItems=JSON.parse($scope.entity.goodsDesc.specificationItems); } ); } //根據規格名稱和選項名稱返回是否被勾選 $scope.checkAttributeValue=function(specName,optionName){ var items= $scope.entity.goodsDesc.specificationItems; var object= $scope.searchObjectByKey(items,'attributeName',specName); if(object==null){ return false; }else{ if(object.attributeValue.indexOf(optionName)>=0){ return true; }else{ return false; } } }
修改頁面上規格面板的復選框,運用 ng-checked指令控制復選框的勾選狀態
<input type="checkbox" ng-click="updateSpecAttribute($event,pojo.text,p.optionName);createSKUTable()" ng-checked="checkAttributeValue(pojo.text,p.optionName)">{{p.optionName}}
2.7讀取SKU數據
顯示SKU商品列表,并自動讀取價格、庫存等數據加載到列表中
2.7.1后端代碼
在GoodsServiceImpl的findOne方法中加載SKU商品數據
//查詢SKU商品列表 TbItemExample example=new TbItemExample(); com.pinyougou.pojo.TbItemExample.Criteria criteria = example.createCriteria(); criteria.andGoodsIdEqualTo(id);//查詢條件:商品ID List<TbItem> itemList = itemMapper.selectByExample(example); goods.setItemList(itemList);
2.7.2前端代碼
在goodsController.js修改findOne方法的代碼
//查詢實體 $scope.findOne=function(){ ........ goodsService.findOne(id).success( function(response){ $scope.entity= response; ......... //SKU列表規格列轉換 for( var i=0;i<$scope.entity.itemList.length;i++ ){ $scope.entity.itemList[i].spec = JSON.parse( $scope.entity.itemList[i].spec); } } ); }
2.8保存數據
2.8.1后端代碼
修改 pinyougou-sellergoods-interface 的 GoodsService.java
public void update(Goods goods);
修改pinyougou-sellergoods-service的GoodsServiceImpl ,將SKU列表插入的代碼提取出來,封裝到私有方法中
/** * 插入SKU列表數據 * @param goods */ private void saveItemList(Goods goods){ if("1".equals(goods.getGoods().getIsEnableSpec())){ for(TbItem item :goods.getItemList()){ .........中間代碼略 } }else{ TbItem item=new TbItem(); .........中間代碼略 itemMapper.insert(item); } }
在add方法中調用 此方法,修改如下:
public void add(Goods goods) { goods.getGoods().setAuditStatus("0"); goodsMapper.insert(goods.getGoods()); //插入商品表 goods.getGoodsDesc().setGoodsId(goods.getGoods().getId()); goodsDescMapper.insert(goods.getGoodsDesc());//插入商品擴展數據 saveItemList(goods);//插入商品SKU列表數據 }
怎么樣,是不是比原來更加清爽了呢?
接下來,我們修改update方法,實現修改
public void update(Goods goods){ goods.getGoods().setAuditStatus("0");//設置未申請狀態:如果是經過修改的商品,需要重新設置狀態 goodsMapper.updateByPrimaryKey(goods.getGoods());//保存商品表 goodsDescMapper.updateByPrimaryKey(goods.getGoodsDesc());//保存商品擴展表 //刪除原有的sku列表數據 TbItemExample example=new TbItemExample(); com.pinyougou.pojo.TbItemExample.Criteria criteria = example.createCriteria(); criteria.andGoodsIdEqualTo(goods.getGoods().getId()); itemMapper.deleteByExample(example); //添加新的sku列表數據 saveItemList(goods);//插入商品SKU列表數據 }
修改pinyougou-manager-web工程的GoodsController.java
@RequestMapping("/update") public Result update(@RequestBody Goods goods){ ...... }
修改pinyougou-shop-web工程的GoodsController.java
/** * 修改 * @param goods * @return */ @RequestMapping("/update") public Result update(@RequestBody Goods goods){ //校驗是否是當前商家的id Goods goods2 = goodsService.findOne(goods.getGoods().getId()); //獲取當前登錄的商家ID String sellerId = SecurityContextHolder.getContext().getAuthentication().getName(); //如果傳遞過來的商家ID并不是當前登錄的用戶的ID,則屬于非法操作 if(!goods2.getGoods().getSellerId().equals(sellerId) || !goods.getGoods().getSellerId().equals(sellerId) ){ return new Result(false, "操作非法"); } try { goodsService.update(goods); return new Result(true, "修改成功"); } catch (Exception e) { e.printStackTrace(); return new Result(false, "修改失敗"); } }
代碼解釋:出于安全考慮,在商戶后臺執行的商品修改,必須要校驗提交的商品屬于該商戶
2.8.2前端代碼
(1)修改goodsController.js ,新增保存的方法
//保存 $scope.save=function(){ //提取文本編輯器的值 $scope.entity.goodsDesc.introduction=editor.html(); var serviceObject;//服務層對象 if($scope.entity.goods.id!=null){//如果有ID serviceObject=goodsService.update( $scope.entity ); //修改 }else{ serviceObject=goodsService.add( $scope.entity );//增加 } serviceObject.success( function(response){ if(response.success){ alert('保存成功'); $scope.entity={}; editor.html(""); }else{ alert(response.message); } } ); }
(2)修改goods_edit.html 調用
<button class="btn btn-primary" ng-click="save()"><i class="fa fa-save"></i>保存</button>
2.9頁面跳轉
(1)由商品列表頁跳轉到商品編輯頁
修改goods.html表格行的修改按鈕
<a href="goods_edit.html#?id={{entity.id}}" class="btn bg-olive btn-xs">修改</a>
(2)由商品編輯頁跳轉到商品列表
修改goods_edit.html 的返回列表按鈕
<a href="goods.html" class="btn btn-default">返回列表</a>
(3)保存成功后返回列表頁面
//保存 $scope.save=function(){ ..... serviceObject.success( function(response){ if(response.success){ location.href="goods.html";//跳轉到商品列表頁 }else{ alert(response.message); } } ); }
3.1待審核商品列表
需求:參照商家后臺商品列表。代碼:
(1)修改pinyougou-manager-web的goodsController.js,注入itemCatService,添加代碼
$scope.status=['未審核','已審核','審核未通過','關閉'];//商品狀態 $scope.itemCatList=[];//商品分類列表 //查詢商品分類 $scope.findItemCatList=function(){ itemCatService.findAll().success( function(response){ for(var i=0;i<response.length;i++){ $scope.itemCatList[response[i].id ]=response[i].name; } } ); }
(2)修改goods.html ,引入js
<script type="text/javascript" src="../plugins/angularjs/angular.min.js"></script> <!-- 分頁組件開始 --> <script src="../plugins/angularjs/pagination.js"></script> <link rel="stylesheet" href="../plugins/angularjs/pagination.css"> <!-- 分頁組件結束 --> <script type="text/javascript" src="../js/base_pagination.js"></script> <script type="text/javascript" src="../js/service/goodsService.js"></script> <script type="text/javascript" src="../js/service/itemCatService.js"></script> <script type="text/javascript" src="../js/controller/baseController.js"></script> <script type="text/javascript" src="../js/controller/goodsController.js"></script>
(3)指令,完成初始調用
<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="searchEntity={auditStatus:'0'};findItemCatList()">
(4)循環列表
<tr ng-repeat="entity in list"> <td><input type="checkbox"></td> <td>{{entity.id}}</td> <td>{{entity.goodsName}}</td> <td>{{entity.price}}</td> <td>{{itemCatList[entity.category1Id]}}</td> <td>{{itemCatList[entity.category2Id]}}</td> <td>{{itemCatList[entity.category3Id]}}</td> <td>{{status[entity.auditStatus]}}</td> <td class="text-center"> </td> </tr>
(5)分頁控件
<tm-pagination conf="paginationConf"></tm-pagination>
3.2商品詳情展示(學員實現)
需求:點擊列表右側的“詳情”按鈕,彈出窗口顯示商品信息。代碼略。
3.3商品審核與駁回
需求:商品審核的狀態值為1,駁回的狀態值為2 。用戶在列表中選中ID后,點擊審核或駁回,修改商品狀態,并刷新列表。
3.3.1后端代碼
(1)在pinyougou-sellergoods-interface的GoodsService.java新增方法定義
/** * 批量修改狀態 * @param ids * @param status */ public void updateStatus(Long []ids,String status);
(2)在pinyougou-sellergoods-service的GoodsServiceImpl.java實現該方法
public void updateStatus(Long[] ids, String status) { for(Long id:ids){ TbGoods goods = goodsMapper.selectByPrimaryKey(id); goods.setAuditStatus(status); goodsMapper.updateByPrimaryKey(goods); } }
(3)在pinyougou-shop-web的GoodsController.java新增方法
/** * 更新狀態 * @param ids * @param status */ @RequestMapping("/updateStatus") public Result updateStatus(Long[] ids, String status){ try { goodsService.updateStatus(ids, status); return new Result(true, "成功"); } catch (Exception e) { e.printStackTrace(); return new Result(false, "失敗"); } }
3.3.2前端代碼
(1)修改pinyougou-manager-web的goodsService.js ,增加方法
//更改狀態 this.updateStatus=function(ids,status){ return $http.get('../goods/updateStatus.do?ids='+ids+"&status="+status); }
(2)修改pinyougou-manager-web的goodsController.js ,增加方法
//更改狀態 $scope.updateStatus=function(status){ goodsService.updateStatus($scope.selectIds,status).success( function(response){ if(response.success){//成功 $scope.reloadList();//刷新列表 $scope.selectIds=[];//清空ID集合 }else{ alert(response.message); } } ); }
(3)修改pinyougou-manager-web的goods.html 頁面,為復選框綁定事件指令
<input type="checkbox" ng-click="updateSelection($event,entity.id)" >
(4)修改頁面上的審核通過和駁回按鈕
<button type="button" class="btn btn-default" title="審核通過" ng-click="updateStatus('1')"><i class="fa fa-check"></i> 審核通過</button> <button type="button" class="btn btn-default" title="駁回" ng-click="updateStatus('2')" ><i class="fa fa-ban"></i> 駁回</button>
4.1需求分析
我們為商品管理提供商品刪除功能,用戶選中部分商品,點擊刪除按鈕即可實現商品刪除。注意,這里的刪除并非是物理刪除,而是修改tb_goods表的is_delete字段為1 ,我們可以稱之為“邏輯刪除”
4.2邏輯刪除的實現
4.2.1后端代碼
修改pinyougou-sellergoods-service工程的GoodsServiceImpl.java的delete方法
/** * 批量刪除 */ @Override public void delete(Long[] ids) { for(Long id:ids){ TbGoods goods = goodsMapper.selectByPrimaryKey(id); goods.setIsDelete("1"); goodsMapper.updateByPrimaryKey(goods); } }
4.2.2前端代碼
修改pinyougou-manager-web的goods.html上的刪除按鈕
<button type="button" class="btn btn-default" title="刪除" ng-click="dele()"><i class="fa fa-trash-o"></i> 刪除</button>
4.3排除已刪除記錄
修改pinyougou-sellergoods-service工程GoodsServiceImpl.java的findPage方法,添加以下代碼:
criteria.andIsDeleteIsNull();//非刪除狀態
5.1需求分析
什么是商品上下架?其實上下架也是商品的一個狀態,但是不同于審核狀態。審核狀態的控制權在運營商手中,而上下架的控制權在商戶手中。商戶可以隨時將一個已審核狀態的商品上架或下架。上架表示正常銷售,而下架則表示暫停銷售。
5.2實現思路提示
其實商品的上下架就是對上下架狀態的修改。字段為tb_goods表的is_marketable字段。1表示上架、0表示下架。
6.1事務異常測試
我們修改pinyougou-sellergoods-service工程GoodsServiceImpl.java的add方法
/** * 增加 */ @Override public void add(Goods goods) { goods.getGoods().setAuditStatus("0"); goodsMapper.insert(goods.getGoods()); //插入商品表 int x=1/0; goods.getGoodsDesc().setGoodsId(goods.getGoods().getId()); goodsDescMapper.insert(goods.getGoodsDesc());//插入商品擴展數據 saveItemList(goods);//插入商品SKU列表數據 }
在插入商品表后,人為制造一個異常。我們運行程序,新增商品數據,觀察運行結果。
通過觀察,我們發現,程序發生異常 ,商品表仍然會存儲記錄,這是不符合我們要求的。這是因為我們目前的系統還沒有配置事務。
6.2注解式事務解決方案
6.2.1配置文件
在pinyougou-sellergoods-service工程的spring目錄下創建applicationContext-tx.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 開啟事務控制的注解支持 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
6.2.2在方法上添加注解
/** * 服務實現層 * @author Administrator * */ @Service @Transactional public class GoodsServiceImpl implements GoodsService{ ........ }
經過測試,我們發現,系統發生異常,商品表不會新增記錄,事務配置成功。
刪除掉測試代碼int x=1/0
我們需要將所有涉及多表操作的服務類添加事務注解,例如SpecificationServiceImpl類
文總結了幾點刪除按鈕的設計技巧,以防止誤操作導致防止數據丟失。enjoy~
數據丟失是用戶使用計算機時可能遇到的最大意外之一。他們不僅丟失了數據,還損失了投入的時間和金錢。對于企業而言,這可能意味著數百個工時和數千美元的損失。不要讓這種情況發生在您的用戶身上。
一項研究發現,人為錯誤導致30%的數據丟失。這意味著良好的用戶體驗設計可以防止這些意外發生。以下是一些預防技巧。
當用戶按下刪除按鈕時,請勿立即執行,有可能是用戶誤點擊了按鈕,您需提示用戶通過確認頁面確認操作。
刪除按鈕請勿使用如藍色等常規顏色。應使用紅色按鈕警示用戶即將觸發危險性操作。紅色具有很強的視覺警告提示,因其更容易吸引用戶注意力。
避免正常操作使用紅色按鈕,否則即為告警用戶。保留紅色按鈕僅用作刪除操作。冷色更適合使用于正常的行為呼叫按鈕,因為其警示作用較弱。
雖然紅色按鈕能警示大部分用戶,但是有些用戶也許會忽視。額外的視覺提示,能增加警示作用。尤其是有助于無法區分色差的色盲和弱視用戶群。
若要加強警告信號,請在確認屏幕上添加代表刪除操作的圖標。例如,用戶熟悉的刪除圖標是垃圾桶。當用戶看到圖標時,他們會將當前操作與刪除相關聯。
您可以通過在屏幕頂部添加紅色條塊來加強警告信號。現在此確認頁面,用戶可以看到三個紅色警告信號,表明他們即將進行危險性操作。這使用戶更加注意他們的行為和處境,以防按錯按鈕。
用戶使用移動應用程序次數越多,他們越有可能養成慣性點擊的習慣。無意識慣性點擊可以更快更容易完成任務,但也更容易點擊刪除按鈕。
紅色通常與警告和危險有聯系,并且具有負面含義。我們常看到許多用紅色來傳達警告和危險的標志。在設計中,紅色按鈕會引起用戶對傷害或損失的恐懼,以防止錯誤。這是人類為了生存而躲避危險的本能。
研究表明,紅色物體會引起注意并促進一致的運動反應。這意味著,當用戶看到紅色的刪除按鈕時,他們可能會更快、更準確地做出響應。用戶對任務的關注越多,他們就會越好的執行該任務。
紅色警告信號可以防止刪除意外,但這不是您須考慮的唯一事項。您還需編寫簡潔的會話文本來確保其易于瀏覽。
在會話窗標題的末尾添加問號來替代詢問用戶“您確定要刪除嗎?”。例如,標題為“刪除帳戶?”,用較少的字詞表示“您確定要刪除帳戶嗎?”。
不僅如此,不要使用冗長的句子來解釋點擊確認后會發生什么。以列表格式列出他們將丟失的內容,來替代通知用戶,“如果您刪除了自己的帳戶,則會永久丟失您的個人資料,消息和照片”,以便用戶快速閱覽。
在該示例中,會話窗文本通過從25個單詞減少到僅9個單詞來簡化。使得會話窗更容易瀏覽和理解。
確認對話框的目的是描述刪除操作的后果。用戶需要閱讀并理解這些,否則他們可能會得到意想不到的結果。但在冗長的對話中很難做到這一點。
大多數用戶會跳過冗長的文字,因為閱讀需要花費時間和精力。簡潔的文本可以防止用戶跳過,幫助用戶更快地執行任務,減少錯誤,并記住更多信息。使用簡潔的對話文本,用戶可以更好地理解其行為的后果并做出正確的決定。
簡潔的文本使其容易瀏覽。但你還可以通過中心對齊布局使整個會話窗更進一步加強其易讀性。中心對齊布局將圖標與會話窗文本對齊,以便用戶可以同時瀏覽。還使會話窗對稱,圖標更加突出,以防遮擋。
當使用視覺追蹤圖觀察一個左對齊布局和冗長的文本會話窗時,會發現有更多的注視點和更長的瀏覽路徑。
簡潔的會話窗和中心對齊的布局只需較少的視覺工作。通過更少的注視點和更短的瀏覽路徑,用戶可以更快地瀏覽屏幕以做出明智的決定。
如果刪除按鈕不在確認頁面上時,請勿使用紅色警告信號。例如,設置頁面可以有一個“刪除帳戶”按鈕,但它不需像呼叫行為按鈕來吸引不必要的注意。
最好將刪除按鈕設為僅帶有紅色文本標簽的獨立按鈕。使用過多的紅色會導致用戶將其誤認為是屏幕上的主呼叫行為按鈕。
用戶在確認頁面上的停留時間越多,他們按錯按鈕的可能性就越大。通過思考其他屏幕上的刪除按鈕,用戶不太可能意外進入確認頁面。這使得他們遠離危險。
在其他頁面上將刪除按鈕與正常按鈕分開也可以使用戶遠離危險。用戶不會將其誤認為是正常的號召性按鈕并想按下它。
即使有確認屏幕,意外仍然可能發生。有些用戶仍然可能誤讀了會話窗或按錯了按鈕。在確認屏幕之后,向用戶提供撤消按鈕,其中包含告知用戶已執行操作的消息。
將撤消按鈕和完成消息放在屏幕底部的通知橫幅中。您可以根據刪錯操作的上下文使撤消按鈕成為臨時或持久的。
臨時撤消將使橫幅在幾秒鐘后自動消失。持久撤消顯示橫幅,直到用戶通過按“關閉”按鈕關閉橫幅。請注意,持久性撤消的技術實現比臨時撤消更復雜。
撤消刪除操作的選項允許用戶從事故中恢復操作以防止數據丟失。數據丟失對企業和人們的生活造成嚴重后果。發布確認撤消按鈕不僅可以保存用戶的數據,還可以保存用戶的工作。
如果撤消按鈕不是可選項,則可以提示用戶在文本框中輸入刪除以確認。提示用戶輸入確認使他們意識到刪除行為。雖然容易意外按錯按鈕,但是不可能輸入意外出錯,因為此操作需要很多步驟。
此方法對于用戶經常使用刪除操作的效率不高。例如,刪除帖子是社交媒體應用上的常見操作。若要求用戶每次通過輸入來確認將大大降低用戶操作效率。僅用于類型罕見的刪除操作。
無意識地按下按鈕比輸入單詞要容易得多。當用戶輸入時,他們必須考慮他們正在輸入的內容然后點擊右邊的確認按鈕。與按下按鈕相比,出錯空間更大。這使用戶意識到他們的確認行為,以防止意外按下按鈕。
當用戶進入確認屏幕時,他們處于意外發生的邊緣。如果您沒有采取措施防止這種情況發生,按錯了按鈕可能會損壞數據。將這些方法應用到您的應用中,將避免用戶遇到數據丟失意外。
PS:翻譯過程中為適合我們的閱讀習慣以及個人的理解,有對原文進行一定的內容簡化和語義修飾,如有不妥歡迎大家根據官網鏈接進行比對并留言互動。
(本文翻譯已獲得該網站的正式授權)
原文鏈接:https://uxmovement.com/buttons/how-to-design-destructive-actions-that-prevent-data-loss/
原作者:anthony
譯文地址:https://www.zcool.com.cn/article/ZMTAxNzcwMA==.html
編譯作者:黎沫limo
本文由 @黎沫limo 翻譯發布于人人都是產品經理,未經作者許可,禁止轉載。
題圖來自Unsplash,基于CC0協議。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。