收货地址列表展示 1.收货地址列表展示-持久层 1.1规划需要执行的SQL语句 数据库数据的查询操作
1 select * from t_address where uid ? order by is_default DESC ,created_time DESC
其中order by is_default DESC是为了让默认收货地址展示在最上面,order by可以有多个字句,中间用逗号隔开,后面加的create_time DESC是为了让非默认收货地址创建的越晚越展示在上面
1.2设计接口和抽象方法 在AddressMapper接口追加抽象方法findByUid
1 2 3 4 5 6 List<Address> findByUid (Integer uid) ;
1.3编写映射 在xml文件添加相应的sql语句映射
1 2 3 4 < select id= "findByUid" resultMap= "AddressEntityMap"> select * from t_address where uid= #{uid} order by is_default DESC ,created_time DESC < / select >
1.4单元测试 1 2 3 4 5 @Test public void findByUid () { List<Address> list = addressMapper.findByUid(11 ); System.out.println(list); }
2.收货地址列表展示-业务层 2.1规划异常 该模块只是为了展示列表,不涉及到增删改,即便没有拿到任何数据,那无非就是不展示呗,所以不涉及到异常,不需要在业务层抛出异常
2.2设计接口和抽象方法及实现 1.定义抽象方法
1 List<Address> getByUid (Integer uid) ;
2.实现该方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public List<Address> getByUid (Integer uid) { List<Address> list = addressMapper.findByUid(uid); for (Address address : list) { address.setUid(null ); address.setProvinceCode(null ); address.setCityCode(null ); address.setAreaCode(null ); address.setZip(null ); address.setIsDefault(null ); address.setCreatedTime(null ); address.setCreatedUser(null ); address.setModifiedTime(null ); address.setModifiedUser(null ); } return list; }
2.3单元测试 这里不再进行单元测试
3.收货地址列表展示-控制层 3.1处理异常 因为业务层没有抛出异常,所以这里不需要处理异常
3.2设计请求
/addresses
HttpSession session
get(该功能模块只需要uid,不需要别的数据,而且uid也是在后端封装的,所以前端没有提交什么数据,体量很小可以用get)
JsonResult<List>
3.3处理请求 实现请求方法的编写
1 2 3 4 5 6 @RequestMapping({"","/"}) public JsonResult<List<Address>> getByUid (HttpSession session) { Integer uid = getUidFromSession(session); List<Address> data = addressService.getByUid(uid); return new JsonResult <>(OK,data); }
启动服务,登录账号后在地址栏输入http://localhost:8080/addresses测试能否拿到数据
###4.收货地址列表展示-前端页面
在address.html中编写js代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <script> $(document ).ready (function ( ) { showAddressList (); });function showAddressList ( ) { $("#address-list" ).empty (); $.ajax ({ url : "/addresses" , type : "get" , dataType : "JSON" , success : function (json ) { if (json.state == 200 ) { var list = json.data ; console .log (list); for (var i = 0 ; i < list.length ; i++) { var tr = '<tr>\n' + '<td>#{tag}</td>\n' + '<td>#{name}</td>\n' + '<td>#{address}</td>\n' + '<td>#{phone}</td>\n' + '<td><a class="btn btn-xs btn-info"><span class="fa fa-edit"></span> 修改</a></td>\n' + '<td><a class="btn btn-xs add-del btn-info"><span class="fa fa-trash-o"></span> 删除</a></td>\n' + '<td><a class="btn btn-xs add-def btn-default">设为默认</a></td>\n' + '</tr>' ; tr = tr.replace (/#{tag}/g ,list[i].tag ); tr = tr.replace (/#{name}/g ,list[i].name ); tr = tr.replace ("#{address}" ,list[i].address ); tr = tr.replace ("#{phone}" ,list[i].phone ); $("#address-list" ).append (tr); } $(".add-def:eq(0)" ).hide (); } else { <!--这个其实永远不会执行,因为没有编写 异常,控制层返回的状态码永远是OK --> alert ("用户收货地址数据加载失败" ) } } }); } </script>
设置默认收货地址 1.设置默认收货地址-持久层 1.1规划需要执行的SQL语句 无论选择的是哪一条数据,都把所有的数据设为非默认,再把当前数据设为默认
我们可能会想着把第一条设为非默认,再将该条设为默认,但这样处理的话需要额外做一条查询语句拿到默认地址的数据
1.检测当前用户想设置为默认收货地址的这条数据是否存在
1 select * from t_address where aid= ?
2.在修改用户的默认收货地址之前先将所有的收货地址设置为非默认
1 update t_address set is_default= 0 where uid= ?
3.将用户选中的这条记录设置为默认收货地址
1 update t_address set is_default= 1 ,modified_user= ?,modified_time= ? where aid= ?
1.2设计接口和抽象方法 在AddressMapper接口中来定义实现该模块所需的三个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Address findByAid (Integer aid) ; Integer updateNonDefault (Integer uid) ; Integer updateDefaultByAid ( @Param("aid") Integer aid, @Param("modifiedUser") String modifiedUser, @Param("modifiedTime") Date modifiedTime) ;
1.3编写映射 在AddressMapper.xml中编写映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <select id ="findByAid" resultMap ="AddressEntityMap" > select * from t_address where aid=#{aid}</select > <update id ="updateNonDefault" > update t_address set is_default=0 where uid=#{uid}</update > <update id ="updateDefaultByAid" > update t_address set is_default=1, modified_user=#{modifiedUser}, modified_time=#{modifiedTime} where aid=#{aid}</update >
1.4单元测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void findByAid () { System.err.println(addressMapper.findByAid(9 )); }@Test public void updateNonDefault () { System.out.println(addressMapper.updateNonDefault(11 )); }@Test public void updateDefaultByAid () { addressMapper.updateDefaultByAid(9 ,"明明" ,new Date ()); }
2.设置默认收货地址-业务层 2.1规划异常
在执行更新时产生未知的UpdateException异常,已经创建无需重复创建
访问的数据不是当前登录用户的收货地址数据,属于非法访问,AccessDeniedException异常(就比如说,展示收货地址列表的sql语句写错了,然后这里展示的是别人的收货地址,此时想要将某个收货地址改为默认就属于非法访问了)
收货地址可能不存在的AddressNotFoundException异常,(比如,刚展示完收货地址列表,管理员误删地址了,此时地址就不存在了)
在业务层的ex包下创建如下两个异常类,并使其继承ServiceException类
1 2 3 4 5 6 7 8 public class AddressNotFoundException extends ServiceException { }public class AccessDeniedException extends ServiceException { }
2.2设计接口和抽象方法及实现 1.在IAddressService接口中编写抽象方法setDefault,并使其在方法内部统一实现持久层的三个方法
分析一下该方法需要什么参数:
先看持久层的三个方法需要什么参数:aid,uid,modifiedUser,modifiedTime.
其中aid是从前端一步一步传到业务层的,所以需要该参数
uid和modifiedUser是一样的,都是由控制层从session获取的uid并传给业务层,所以需要该参数
modifiedTime可以在业务层new Date,所以不需要该参数
1 2 3 4 5 6 7 void setDefault (Integer aid,Integer uid,String username) ;
2.在AddressServiceImpl类编写该方法的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Override public void setDefault (Integer aid, Integer uid, String username) { Address result = addressMapper.findByAid(aid); if (result == null ) { throw new AddressNotFoundException ("收货地址不存在" ); } if (!result.getUid().equals(uid)) { throw new AccessDeniedException ("非法数据访问" ); } Integer rows = addressMapper.updateNonDefault(uid); if (rows < 1 ) { throw new UpdateException ("更新数据时产生未知的异常" ); } rows = addressMapper.updateDefaultByAid(aid, username, new Date ()); if (rows != 1 ) { throw new UpdateException ("更新数据时产生未知的异常" ); } }
2.3单元测试 在AddressServiceTests类中编写单元测试方法
1 2 3 4 @Test public void setDefault () { addressService.setDefault(9 ,11 ,"管理员" ); }
3.设置默认收货地址-控制层 3.1处理异常 在BaseController类中处理业务层抛出的两个异常
1 2 3 4 5 6 7 else if (e instanceof AddressNotFoundException) { result.setState(4004 ); result.setMessage("用户的收货地址数据不存在的异常" ); } else if (e instanceof AccessDeniedException) { result.setState(4005 ); result.setMessage("收货地址数据非法访问的异常" ); }
3.2设计请求
/addresses/{aid}/set_default(以前的数据是通过表单直接提交的,还有一种提交方式就是RestFul风格,这种提交方式可以提交更多的数据,这里用这个提交方式)
GET
Integer aid,HttpSession session(如果这里是id那就必须在Integer aid前加@PathVariable(“aid”)强行将aid的值注入到id中)
JsonResult
3.3处理请求 在AddressController类中编写请求处理方法.
RestFul编写时不管参数名和占位符是否一致都必须加@PathVariable(“aid”)
1 2 3 4 5 6 7 8 9 10 @RequestMapping("{aid}/set_default") public JsonResult<Void> setDefault ( @PathVariable("aid") Integer aid,HttpSession session) { addressService.setDefault( aid, getUidFromSession(session), getUsernameFromSession(session)); return new JsonResult <>(OK); }
启动服务,登录账号后在地址栏输入http://localhost:8080/addresses/8/set_default进行测试
4.设置默认收货地址-前端页面 观察address.html代码发现”设为默认”按钮没有id属性,那应该怎么获取”设为默认”按钮以监听是否被点击了呢?
法一:给”设为默认”的标签添加id属性(我觉得不对,因为id必须是唯一的,如果给该按钮加id属性,那么该用户有几个收货地址就会给几个按钮加同样的id,这显然不对,我认为可以用按钮上本就存在的class属性)。
法二:给”设置默认”按钮添加一个onclick属性,指向一个方法的调用,在这个方法中来完成ajax请求的方法
在这里用第二种方法:
1.展示用户收货地址数据列表的js函数中用for循环给页面增加地址数据的tr标签,我们需要在for循环中为每一个tr标签增加onclick属性并指向setDefault(#{aid})函数,括号里面占位符是为了给外部的setDefault函数传参,可以随便写,只要给占位符赋值时对应就可以了,.注意,即使调用的是无参函数也要加括号
1 <td><a onclick ="setDefault(#{aid})" class ="btn btn-xs add-def btn-default" > 设为默认</a > </td>
2.在for循环中为占位符赋值:
1 tr = tr.replace ("#{aid}" ,list[i].aid );
3.完成setDefault方法的声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function setDefault (aid ) { $.ajax ({ url : "/addresses/" +aid+"/set_default" , type : "POST" , dataType : "JSON" , success : function (json ) { if (json.state == 200 ) { showAddressList (); } else { alert ("设置默认收货地址失败" ) } }, error : function (xhr ) { alert ("设置默认收货地址时产生未知的异常!" +xhr.message ); } }); }
删除收货地址 1.删除收货地址-持久层 1.1规划需要执行的SQL语句 1.在删除之前判断该数据是否存在,需要执行查询语句看能否查到该数据,还需要根据返回的aid获取uid并和session中的uid进行比较判断归属是否正确,这一条SQL语句在设置收货地址时已经开发,无需重复开发
2.开发执行删除的SQL语句
1 delete from t_address where aid= ?
3.需要判断删除的地址是否是默认地址(使用aid查询到的地址对象的getIsDefault方法),如果判断出删的是默认地址,则还需要定义把哪个地址设为默认,这里定义最新修改的为默认地址.
开发该SQL语句
1 select * from t_address where uid= ? order by modified_time DESC limit 0 ,1
其中limit 0,1表示查询到的第一条数据(limit (n-1),pageSize),这样查询后就只会获得第一条数据
4.如果用户本身就只有一条地址,那么删除后其他操作就可以不进行了,所以需要查询该用户的所有地址数量,在设置收货地址时已经开发,无需重复开发
1.2设计接口和抽象方法 在AddressMapper接口中进行抽象方法的设计
1 2 3 4 5 6 7 8 9 10 11 12 13 Integer deleteByAid (Integer aid) ; Address findLastModified (Integer uid) ;
1.3编写映射 在AddressMapper.xml文件中进行映射
1 2 3 4 5 6 7 8 9 <delete id ="deleteByAid" > delete from t_address where aid=#{aid}</delete > <select id ="findLastModified" resultMap ="AddressEntityMap" > select * from t_address where uid=#{uid} order by modified_time DESC limit 0,1</select >
1.4单元测试 1 2 3 4 5 6 7 8 9 @Test public void deleteByAid () { addressMapper.deleteByAid(11 ); }@Test public void findLastModified () { System.out.println(addressMapper.findLastModified(11 )); }
2.删除收货地址-业务层 2.1规划异常
可能没有该条地址数据(已开发)
可能地址数据归属错误(已开发)
在执行删除的时候可能会产生未知的异常导致数据不能够删除成功,则抛出DeleteException异常,在service创建该异常并使其继承业务层异常
1 2 3 4 public class DeleteException extends ServiceException { }
2.2设计接口和抽象方法及实现 1.在IAddressService接口中定义抽象方法
需要给抽象方法声明哪些参数呢:
根据分析可得,该抽象方法的实现依赖于持久层的以下方法:
1.findByAid:查询该条地址数据是否存在,参数是aid
3.deleteByAid:删除地址数据,参数是aid
5.countByUid:统计用户地址数量,参数是uid
6.findLastModified:查询得到最后修改的一条地址,参数是uid
7.updateDefaultByAid:设置默认收货地址,参数是aid,modifiedUser,modifiedTime
稍加分析可以得出接下来定义的抽象方法的参数是:aid,uid,username
把上面的分析补上:2.判断地址数据归属是否正确4.判断删除的是否是默认地址.这七步就是业务层完整的开发流程
1 2 3 4 5 6 7 void delete (Integer aid,Integer uid,String username) ;
2.实现该抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Override public void delete (Integer aid, Integer uid, String username) { Address result = addressMapper.findByAid(aid); if (result == null ) { throw new AddressNotFoundException ("收货地址数据不存在" ); } if (!result.getUid().equals(uid)) { throw new AccessDeniedException ("非法数据访问" ); } Integer rows = addressMapper.deleteByAid(aid); if (rows != 1 ) { throw new DeleteException ("删除数据时产生未知的异常" ); } if (result.getIsDefault() == 0 ) { return ; } Integer count = addressMapper.countByUid(uid); if (count == 0 ) { return ; } Address address = addressMapper.findLastModified(uid); rows = addressMapper.updateDefaultByAid(address.getAid(), username, new Date ()); if (rows != 1 ) { throw new UpdateException ("更新数据时产生未知的异常" ); } }
2.3单元测试 1 2 3 4 @Test public void delete () { addressService.delete(1 ,11 ,"4.11删除" ); }
3.删除收货地址-控制层 3.1处理异常 需要在BaseController类中处理异常类
1 2 3 4 else if (e instanceof DeleteException) { result.setState(5002 ); result.setMessage("删除数据时产生未知的异常" ); }
3.2设计请求
/addresses/{aid}/delete
POST
Integer aid,HttpSession session
JsonResult
3.3处理请求 1 2 3 4 5 6 7 8 @RequestMapping("{aid}/delete") public JsonResult<Void> delete (@PathVariable("aid") Integer aid,HttpSession session) { addressService.delete( aid, getUidFromSession(session), getUsernameFromSession(session)); return new JsonResult <>(OK); }
3.4单元测试 在AddressController类编写请求处理方法的实现
这个方法就只是调用业务层方法然后给前端返回一些信息,可以选择不用测试
4.删除收货地址-前端页面 处理该前端页面的所有步骤和处理”设置默认收货地址”的一样
1.给”删除”按钮添加onclick属性并指向deleteByAid(aid)方法
1 <td><a onclick ="delete(#{aid})" class ="btn btn-xs add-del btn-info" > <span class ="fa fa-trash-o" > </span > 删除</a > </td>
2.给占位符赋值
因为处理”设置默认收货地址”时已经编写tr = tr.replace(“#{aid}”,list[i].aid);用来给占位符#{aid}赋值,所以这里不需要再写.但是需要把replace改为replaceAll
3.完成deleteByAid(aid)方法的声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function setDefault (aid) { $.ajax({ url: "/addresses/" +aid+"/set_default" , type: "POST" , dataType: "JSON" , success: function (json) { if (json.state == 200 ) { showAddressList(); } else { alert("删除收货地址失败" ) } }, error: function (xhr) { alert("删除收货地址时产生未知的异常!" +xhr.message); } }); }
商品热销排行 1.创建数据表 1.在store数据库中创建t_product数据表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 CREATE TABLE t_product ( id int(20) NOT NULL COMMENT '商品id', category_id int(20) DEFAULT NULL COMMENT '分类id', item_type varchar(100) DEFAULT NULL COMMENT '商品系列', title varchar(100) DEFAULT NULL COMMENT '商品标题', sell_point varchar(150) DEFAULT NULL COMMENT '商品卖点', price bigint(20) DEFAULT NULL COMMENT '商品单价', num int(10) DEFAULT NULL COMMENT '库存数量', image varchar(500) DEFAULT NULL COMMENT '图片路径', `status` int(1) DEFAULT '1' COMMENT '商品状态 1:上架 2:下架 3:删除', priority int(10) DEFAULT NULL COMMENT '显示优先级', created_time datetime DEFAULT NULL COMMENT '创建时间', modified_time datetime DEFAULT NULL COMMENT '最后修改时间', created_user varchar(50) DEFAULT NULL COMMENT '创建人', modified_user varchar(50) DEFAULT NULL COMMENT '最后修改人', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.向该表插入数据
1 2 3 LOCK TABLES t_product WRITE;INSERT INTO t_product VALUES (10000001 ,238 ,'牛皮纸记事本' ,'广博(GuangBo)10本装40张A5牛皮纸记事本子日记本办公软抄本GBR0731' ,'经典回顾!超值特惠!' ,23 ,99999 ,'/images/portal/00GuangBo1040A5GBR0731/' ,1 ,62 ,'2017-10-25 15:08:55' ,'2017-10-25 15:08:55' ,'admin' ,'admin' ),等等等等; UNLOCK TABLES;
2.创建商品的实体类 创建Product实体类并使其继承BaseEntity类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Product extends BaseEntity { private Integer id; private Integer categoryId; private String itemType; private String title; private String sellPoint; private Long price; private Integer num; private String image; private Integer status; private Integer priority; }
3.商品热销排行-持久层 3.1 规划需要执行的SQL语句 查询热销商品列表的SQL语句
1 SELECT * FROM t_product WHERE status= 1 ORDER BY priority DESC LIMIT 0 ,4
3.2 设计接口和抽象方法 在mapper包下创建ProductMapper接口并在接口中添加查询热销商品findHotList()的方法
1 2 3 4 5 6 7 public interface ProductMapper { List<Product> findHotList () ; }
3.3 编写映射 在main\resources\mapper文件夹下创建ProductMapper.xml文件,并在文件中配置findHotList()方法的映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.cy.store.mapper.ProductMapper" > <resultMap id ="ProductEntityMap" type ="com.cy.store.entity.Product" > <id column ="id" property ="id" /> <result column ="category_id" property ="categoryId" /> <result column ="item_type" property ="itemType" /> <result column ="sell_point" property ="sellPoint" /> <result column ="created_user" property ="createdUser" /> <result column ="created_time" property ="createdTime" /> <result column ="modified_user" property ="modifiedUser" /> <result column ="modified_time" property ="modifiedTime" /> </resultMap > <select id ="findHotList" resultMap ="ProductEntityMap" > select * from t_product where status=1 order by priority desc limit 0,4 </select > </mapper >
4.商品热销排行-业务层 4.1 规划异常
只要是查询,不涉及到增删改的,都没有异常,无非就是没有该数据然后返回空
4.2 设计接口和抽象方法及实现 1.创建IProductService接口,并在接口中添加findHotList()方法
1 2 3 4 5 6 7 public interface IProductService { List<Product> findHotList () ; }
2.在业务层创建ProductServiceImpl类并实现该方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.cy.store.service.impl;import com.cy.store.entity.Product;import com.cy.store.mapper.ProductMapper;import com.cy.store.service.IProductService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;@Service public class ProductServiceImpl implements IProductService { @Autowired private ProductMapper productMapper; @Override public List<Product> findHotList () { List<Product> list = productMapper.findHotList(); for (Product product : list) { product.setPriority(null ); product.setCreatedUser(null ); product.setCreatedTime(null ); product.setModifiedUser(null ); product.setModifiedTime(null ); } return list; } }
5.商品热销排行-控制层 5.1 处理异常
无异常。
5.2 设计请求
/products/hot_list
GET
不需要请求参数
JsonResult<List>
5.3 处理请求 1.创建ProductController类并使其继承BaseController类,在类中编写处理请求的方法
1 2 3 4 5 6 7 8 9 10 11 12 @RestController @RequestMapping("products") public class ProductController extends BaseController { @Autowired private IProductService productService; @RequestMapping("hot_list") public JsonResult<List<Product>> getHotList () { List<Product> data = productService.findHotList(); return new JsonResult <List<Product>>(OK, data); } }
2.为了能不登录也可以访问该数据,需要将products/**请求添加到白名单中:
在LoginInterceptorConfigure类的addInterceptors方法中添加代码:
1 patterns.add("/products/**" );
6.商品-热销排行-前端页面 1.在index.html页面给“热销排行”列表的div标签设置id属性值
1 2 3 <div id ="hot-list" class ="panel-body panel-item" > </div >
2.在index.html页面中添加展示热销排行商品的js代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <script type="text/javascript" > $(document ).ready (function ( ) { showHotList (); });function showHotList ( ) { $("#hot-list" ).empty (); $.ajax ({ url : "/products/hot_list" , type : "GET" , dataType : "JSON" , success : function (json ) { var list = json.data ; for (var i = 0 ; i < list.length ; i++) { console .log (list[i].title ); var html = '<div class="col-md-12">' + '<div class="col-md-7 text-row-2"><a href="product.html?id=#{id}">#{title}</a></div>' + '<div class="col-md-2">¥#{price}</div>' + '<div class="col-md-3"><img src="..#{image}collect.png" class="img-responsive" /></div>' + '</div>' ; html = html.replace (/#{id}/g , list[i].id ); html = html.replace (/#{title}/g , list[i].title ); html = html.replace (/#{price}/g , list[i].price ); html = html.replace (/#{image}/g , list[i].image ); $("#hot-list" ).append (html); } } }); } </script>
关于image标签里面的属性src=“…#{image}collect.png” class=“img-responsive”
…代表跳到父文件夹,即index.html的父文件夹static
…后面和collect前面不需要单斜杠,因为数据库中图片地址的数据前面后面加的有
关于a标签里面的href=“product.html?id=#{id}”
这里是为了点击超链接进入商品详情页时可以把商品id传给详情页,使两个页面形成联系