gitee源码地址

收货地址列表展示

1.收货地址列表展示-持久层

1.1数据库数据的查询操作:

1
select * from t_address where uid=? order by is_default DESC,created_time DESC

1.2 接口和抽象方法

1
2
3
4
5
6
/**
* 根据用户id查询用户的收货地址数据
* @param uid 用户id
* @return 收货地址数据
*/
List<Address> findByUid(Integer uid);

1.3在xml文件中添加对应的sql语句映射。

1
2
3
4
5
6
<select id="findByUid" resultMap="AddressEntityMap">
select * from stores.t_address where 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(4);
System.out.println(list);
}

2.收货地址列表展示-业务层

1.不用抛出相关的异常,不需要进行异常的设计。

2.设计业务层的接口IAddressService和抽象方法

1
List<Address> getByUid(Integer uid);

3.需要在实现类中实现此方法的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public List<Address> getByUid(Integer uid) {
List<Address> addressList = addressMapper.findByUid(uid);
//清空不必要的数据,减小数据传输的压力
for (Address address : addressList) {
address.setAid(null);
address.setUid(null);
address.setProvinceCode(null);
address.setCityCode(null);
address.setAreaCode(null);
address.setTel(null);
address.setCreatedTime(null);
address.setCreatedUser(null);
address.setModifiedTime(null);
address.setModifiedUser(null);
}
return addressList;
}

4.单元测试

省略

3.收货地址列表展示-控制层

1.请求设计

1
2
3
4
/addresses
HttpSession session
GET
JsonResult<List<Address>>

2.请求方法的编写

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);
}

3.先登录,在访问请求地址进行数据的测试。

http://localhost:8080/addresses

image-20220424200203992

4.收货地址列表展示-前端页面

在address.html页面

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
<script type="text/javascript">
$(document).ready(function () {
showAddressList();
});

function showAddressList() {
$.ajax({
url:"/addresses/",
type:"GET",
dataType:"JSON",
success:function (json) {
if(json.state==200){
let list=json.data;
for (let i=0;i<list.length;i++){
let 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 className="fa fa-edit"></span> 修改</a></td>\n' +
'<td><a class="btn btn-xs add-del btn-info"><span\n' +
'className="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);
}
//将某个元素隐藏使用hide()方法
$(".add-def:eq(0)").hide();
}else {
alert("用户收货地址数据加载失败");
}
}
});
}
</script>

设置默认收货地址

1.持久层

1.1 SQL语句的规划

1.检测当前用户向设置为默认收货地址的这条数据是否存在。

1
select * from t_address where aid=?

2.在修改用户的收货默认地址之前,先将所有的收货地址设置为非默认(1 默认,0 非默认)。

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
/**
* 根据aid查询收货地址数据
* @param aid 收货地址id
* @return 收货地址数据,如果没有找到则返回null值
*/
Address findByAid(Integer aid);

/**
* 根据用户的uid值来修改用户的收货地址设置为非默认
* @param uid 用户的id
* @return 受影响的行数
*/
Integer updateNonDefault(Integer uid);

Integer updateDefaultByAid(Integer aid,
String modifiedUser,
Date modifiedTime);

1.3 配置SQL映射

AddressMapper.xml文件中进行配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<select id="findByAid" resultMap="AddressEntityMap">
select *
from stores.t_address
where aid = #{aid}
</select>

<update id="updateNonDefault">
update stores.t_address
set is_default=0
where uid = #{uid}
</update>

<update id="updateDefaultByAid">
update stores.t_address
set is_default=1,
modified_user=#{modifiedUser},
modified_time=#{modifiedTime}
where aid = #{aid}
</update>

单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void findByAid(){
System.out.println(addressMapper.findByAid(3));
}

@Test
public void updateNonDefault(){
addressMapper.updateNonDefault(1);
}

@Test
public void updateDefaultByAid(){
addressMapper.updateDefaultByAid(6,"安梦赫",new Date());
}

2. 业务层

2.1 规划异常

1.在执行设置默认收货地址之前,需要先检查该收货地址数据是否存在,如果不存在则抛出AddressNotFoundException异常。

2.然后还需要检查数据归属是否正确,也就是不可以操作他人的数据,如果该数据中记录的uid与当前登录的用户的uid不一致,则抛出AccessDeniedException异常。

3.检查通过后先全部设置为非默认,然后将指定的收货地址设置为默认;这两种操作都是更新数据的操作,则可能抛出UpdateException异常(已创建无需重复创建)。

4.在com.amh.service.ex包下创建AddressNotFoundException和AccessDeniedException异常类。

1
2
3
4
5
6
package com.amh.service.ex;

/** 收货地址数据不存在的异常 */
public class AddressNotFoundException extends ServiceException {
// Override Methods...
}
1
2
3
4
5
6
package com.amh.service.ex;

/** 非法访问的异常 */
public class AccessDeniedException extends ServiceException {
// Override Methods...
}

2.2 接口与抽象方法

在IAddressService接口中添加setDefault(Integer aid, Integer uid, String username)抽象方法。

1
2
3
4
5
6
7
/**
* 设置默认收货地址
* @param aid 收货地址id
* @param uid 归属的用户id
* @param username 当前登录的用户名(表示执行的人)
*/
void setDefault(Integer aid, Integer uid, String username);

2.3 实现抽象方法

1.在AddressServiceImpl类中重写setDefault(Integer aid, Integer uid, String username)方法。该方法需要添加@Transactional注解。

事务:基于Spring JDBC的事务(Transaction)处理,使用事务可以保证一系列的增删改操作,要么全部执行成功,要么全部执行失败。@Transactional注解可以用来修饰类也可以用来修饰方法。如果添加在业务类之前,则该业务类中的方法均以事务的机制运行,但是一般并不推荐这样处理。

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
@Transactional
@Override
public void setDefault(Integer aid, Integer uid, String username) {
// 根据参数aid,调用addressMapper中的findByAid()查询收货地址数据
Address result = addressMapper.findByAid(aid);
// 判断查询结果是否为null
if (result == null) {
// 是:抛出AddressNotFoundException
throw new AddressNotFoundException("尝试访问的收货地址数据不存在");
}

// 检测当前取到的收货地址数据的归属
if (!result.getUid().equals(uid)) {
// 是:抛出AccessDeniedException
throw new AccessDeniedException("非法访问的异常");
}

//将该用户的所有收货地址全部设置为非默认,并获取返回受影响的行数
Integer rows = addressMapper.updateNonDefault(uid);
// 判断受影响的行数是否小于1(不大于0)
if (rows < 1) {
// 是:抛出UpdateException
throw new UpdateException("设置默认收货地址时出现未知错误[1]");
}

// 将用户选中的某条地址设置为默认收货地址
rows = addressMapper.updateDefaultByAid(aid, username, new Date());
// 判断受影响的行数是否不为1
if (rows != 1) {
// 是:抛出UpdateException
throw new UpdateException("设置默认收货地址时出现未知错误[2]");
}
}

单元测试

1
2
3
4
@Test
public void setDefault(){
addressService.setDefault(5,1,"赫");
}

3 默认收货地址-控制器

3.1 处理异常

在BaseController类中添加处理AddressNotFoundException和AccessDeniedException的异常。

1
2
3
4
5
6
7
8
9
// ...
else if (e instanceof AddressNotFoundException) {
result.setState(4004);
result.setMessage(e.getMessage());
} else if (e instanceof AccessDeniedException) {
result.setState(4005);
result.setMessage(e.getMessage());
}
// ...

3.2 设计请求

设计用户提交的请求,并设计响应的方式。

1
2
3
4
请求路径:/addresses/{aid}/set_default
请求参数:@PathVaraible("aid") Integer aid, HttpSession sesion
请求类型:POST
响应结果:JsonResult<Void>

REST即表述性状态传递(Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。

3.3 处理请求

1.在AddressController类中添加处理请求的setDefault(@PathVariable(“aid”) Integer aid, HttpSession session)方法。

1
2
3
4
5
6
7
@RequestMapping("{aid}/set_default")
public JsonResult<Void> setDefault(@PathVariable("aid") Integer aid, HttpSession session) {
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
addressService.setDefault(aid, uid, username);
return new JsonResult<Void>(OK);
}

2.完成后启动项目,打开浏览器先登录,再访问http://localhost:8080/addresses/13/set_default进行测试。

4 默认收货地址-前端页面

1.给设置默认收货地址按钮添加一个onclick属性,指向一个方法的调用,在这个方法中来完成ajax请求的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let list=json.data;
for (let i=0;i<list.length;i++){
let 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 className="fa fa-edit"></span> 修改</a></td>\n' +
'<td><a class="btn btn-xs add-del btn-info"><span\n' +
'className="fa fa-trash-o"></span> 删除</a></td>\n' +
'<td><a class="btn btn-xs add-def btn-default" onclick="setDefault(#{aid}})">设为默认</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)
tr = tr.replace("#{aid}",list[i].aid)

$("#address-list").append(tr);
}

address.html页面点击“设置默认” 按钮,来发送ajax请求。完成setDefault()方法的声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function setDefault(aid) {
$.ajax({
url: "/addresses/" + aid + "/set_default",
type: "POST",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
//先清空列表
$("#address-list").empty();
//重新加载收货地址列表页面
showAddressList();
} else {
alert("设置默认收货地址失败!" + json.message);
}
},
error: function(xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html";
}
});
}

删除收货地址

1 删除收货地址-持久层

1.1 规划需要执行的SQL语句

1.在删除之前,需检查数据是否存在,数据归属是否正确。此功能已完成,无需再次开发。

2.删除指定的收货地址的SQL语句大致是。

1
delete from t_address where aid=?

3.如果删除的这条数据是默认收货地址,则应该将剩余的收货地址中的某一条设置为默认收货地址,可以设定规则“将最近修改的设置为默认收货地址”,要实现此功能就必须要知道“最近修改的收货地址的id是多少”。则通过以下查询语句完成。

1
select * from t_address where uid=? order by modified_time desc limit 0,1

4.在执行以上操作之前,还需检查该用户的收货地址数据的数量,如果删除的收货地址是最后一条收货地址,则删除成功后无需再执行其他操作。统计收货地址数量的功能此前已经完成,无需再次开发。

1.2 接口与抽象方法

在AddressMapper接口中添加抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 根据收货地址id删除数据
* @param aid 收货地址id
* @return 受影响的行数
*/
Integer deleteByAid(Integer aid);

/**
* 查询某用户最后修改的收货地址
* @param uid 归属的用户id
* @return 该用户最后修改的收货地址,如果该用户没有收货地址数据则返回null
*/
Address findLastModified(Integer uid);

1.3 配置SQL映射

1.在AddressMapper.xml文件中添加以上两个抽象方法的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 根据收货地址id删除数据:Integer deleteByAid(Integer aid) -->
<delete id="deleteByAid">
DELETE FROM
t_address
WHERE
aid=#{aid}
</delete>

<!-- 查询某用户最后修改的收货地址:Address findLastModified(Integer uid) -->
<select id="findLastModified" resultMap="AddressEntityMap">
SELECT
*
FROM
t_address
WHERE
uid=#{uid}
ORDER BY
modified_time DESC
LIMIT 0,1
</select>

2.在AddressMapperTests测试类中添加单元测试方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void deleteByAid() {
Integer aid = 4;
Integer rows = addressMapper.deleteByAid(aid);
System.out.println("rows=" + rows);
}

@Test
public void findLastModified() {
Integer uid = 30;
Address result = addressMapper.findLastModified(uid);
System.out.println(result);
}

2 删除收货地址-业务层

2.1 规划异常

在执行删除操作时,可能会删除数据失败,此时抛出DeleteException异常。在创建com.amh.service.ex.DeleteException异常类,并继承自ServiceException类。

1
2
3
4
5
6
package com.cy.store.service.ex;

/** 删除数据失败的异常 */
public class DeleteException extends ServiceException {
// Override Methods...
}

2.2 接口与抽象方法

在IAddressService接口中添加删除收货地址的抽象方法。

1
2
3
4
5
6
7
/**
* 删除收货地址
* @param aid 收货地址id
* @param uid 归属的用户id
* @param username 当前登录的用户名
*/
void delete(Integer aid, Integer uid, String username);

2.3 实现抽象方法

1.在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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Transactional
@Override
public void delete(Integer aid, Integer uid, String username) {
// 根据参数aid,调用findByAid()查询收货地址数据
Address result = addressMapper.findByAid(aid);
// 判断查询结果是否为null
if (result == null) {
// 是:抛出AddressNotFoundException
throw new AddressNotFoundException("尝试访问的收货地址数据不存在");
}

// 判断查询结果中的uid与参数uid是否不一致(使用equals()判断)
if (!result.getUid().equals(uid)) {
// 是:抛出AccessDeniedException:非法访问
throw new AccessDeniedException("非常访问");
}

// 根据参数aid,调用deleteByAid()执行删除
Integer rows1 = addressMapper.deleteByAid(aid);
if (rows1 != 1) {
throw new DeleteException("删除收货地址数据时出现未知错误,请联系系统管理员");
}

// 判断查询结果中的isDefault是否为0
if (result.getIsDefault() == 0) {
return;
}

// 调用持久层的countByUid()统计目前还有多少收货地址
Integer count = addressMapper.countByUid(uid);
// 判断目前的收货地址的数量是否为0
if (count == 0) {
return;
}

// 调用findLastModified()找出用户最近修改的收货地址数据
Address lastModified = addressMapper.findLastModified(uid);
// 从以上查询结果中找出aid属性值
Integer lastModifiedAid = lastModified.getAid();
// 调用持久层的updateDefaultByAid()方法执行设置默认收货地址,并获取返回的受影响的行数
Integer rows2 = addressMapper.updateDefaultByAid(lastModifiedAid, username, new Date());
// 判断受影响的行数是否不为1
if (rows2 != 1) {
// 是:抛出UpdateException
throw new UpdateException("更新收货地址数据时出现未知错误,请联系系统管理员");
}
}

2.在AddressServiceTests测试类中添加单元测试方法。

1
2
3
4
5
6
7
8
@Test
public void delete() {
Integer aid = 6;
Integer uid = 4;
String username = "明明";
addressService.delete(aid, uid, username);
System.out.println("OK");
}

3 删除收货地址-控制器

3.1 处理异常

在BaseController类中添加DeleteException异常的处理。

1
2
3
4
5
6
// ...
else if (e instanceof DeleteException) {
result.setState(5002);
result.setMessage(e.getMessage());
}
// ...

3.2 设计请求

设计用户提交的请求,并设计响应的方式。

请求路径:/addresses/{aid}/delete
请求参数:@PathVariable("aid") Integer aid, HttpSession session
请求类型:POST
响应结果:JsonResult<Void>

3.3 处理请求

1.在AddressController类中添加处理请求的delete()方法。

1
2
3
4
5
6
7
@RequestMapping("{aid}/delete")
public JsonResult<Void> delete(@PathVariable("aid") Integer aid, HttpSession session) {
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
addressService.delete(aid, uid, username);
return new JsonResult<Void>(OK);
}

2.完成后启动项目,打开浏览器先登录,再访问http://localhost:8080/addresses/26/delete进行测试。

4 删除收货地址-前端页面

1.在address.html页面中body标签内部的script标签内,添加设置用户删除收货地址的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function deleteByAid(aaid) {
$.ajax({
url: "/addresses/" + aaid + "/delete",
type: "POST",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
showAddressList();
} else {
alert("删除收货地址失败!" + json.message);
}
},
error: function(json) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + json.status);
location.href = "login.html";
}
});
}

2.给showAddressList()方法中的“设为默认”超链接按钮添加设置默认收货地址的点击事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for (let i=0;i<list.length;i++){
let 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 className="fa fa-edit"></span> 修改</a></td>\n' +
'<td><a onclick="deleteByAid(#{aaid})" class="btn btn-xs add-del btn-info"><span\n' +
'className="fa fa-trash-o"></span> 删除</a></td>\n' +
'<td><a onclick="setDefault(#{aid})" 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)
tr = tr.replace("#{aaid}",list[i].aid)
tr = tr.replace("#{aid}",list[i].aid)


$("#address-list").append(tr);
}

修改收货地址

1.修改收货地址-持久层

1.1 SQL语句的规划

1
2
UPDATE t_address SET name=? province_name=?, province_code=?, city_name=?, city_code=?, area_name=?,area_code=?, zip=?,address=?, phone=?, tel=?, tag=?,modified_user=?,modified_time=?
WHERE aid=?

1.2 接口与抽象方法

在AddressMapper接口中添加抽象方法。

已编写过

1
2
3
4
5
6
>/**
>* 根据aid查询收货地址数据
>* @param aid 收货地址id
>* @return 收货地址数据,如果没有找到则返回null值
>*/
>Address findByAid(Integer aid);
1
2
3
4
5
6
/**
* 根据aid更改用户收货地址
* @param address 地址信息
* @return 影响的行数
*/
Integer updateAddressByAid(Address address);

1.3 配置SQL映射

1.在AddressMapper.xml文件中添加以上一个抽象方法的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<update id="updateAddressByAid">
UPDATE stores.t_address SET
<if test="name!=null"> name=#{name},</if>
<if test="provinceName!=null"> province_name=#{provinceName},</if>
<if test="provinceCode!=null">province_code=#{provinceCode},</if>
<if test="cityName!=null">city_name=#{cityName},</if>
<if test="cityCode!=null"> city_code=#{cityCode},</if>
<if test="areaName!=null">area_name=#{areaName},</if>
<if test="areaCode!=null">area_code=#{areaCode},</if>
<if test="zip!=null"> zip=#{zip},</if>
<if test="address!=null"> address=#{address},</if>
<if test="phone!=null"> phone=#{phone},</if>
<if test="tel!=null">tel=#{tel},</if>
<if test="tag!=null"> tag=#{tag},</if>
modified_user=#{modifiedUser},
modified_time=#{modifiedTime}
WHERE aid=#{aid}
</update>

单元测试

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void updateAddressByAid(){
Address address=new Address();
String username="赫";
address.setAid(5);
address.setName("梦赫");
address.setProvinceName("陕西");
address.setModifiedUser(username);
address.setModifiedTime(new Date());
addressMapper.updateAddressByAid(address);

}

2.修改收货地址-业务层

2.1 规划异常

1.在删除之前,需检查数据是否存在,数据归属是否正确。此功能已完成,无需再次开发。

2.更新数据时产生未知的异常UpdateException,不需要再重复的创建。

2.2 接口与抽象方法

在IAddressService接口中添加根据aid查询收货地址,修改收货地址的抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 根据aid查询地址信息
* @param aid 地址id
*/
Address selectAddressAid(Integer aid);

/**
* 根据aid更改用户收货地址
* @param uid 用户id
* @param username 用户名称
* @param address 地址信息
*/
void updateAddressByAid(Integer uid,String username,Address address);

实现抽象方法

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
@Override
public Address selectAddressAid(Integer aid) {
return addressMapper.findByAid(aid);
}

@Transactional
@Override
public void updateAddressByAid(Integer uid, String username, Address address) {
// 根据参数aid,调用findByAid()查询收货地址数据
Address result = addressMapper.findByAid(address.getAid());
// 判断查询结果是否为null
if (result == null) {
// 是:抛出AddressNotFoundException
throw new AddressNotFoundException("尝试访问的收货地址数据不存在");
}

// 判断查询结果中的uid与参数uid是否不一致(使用equals()判断)
if (!result.getUid().equals(uid)) {
// 是:抛出AccessDeniedException:非法访问
throw new AccessDeniedException("非常访问");
}
//完善数据
address.setModifiedUser(username);
address.setModifiedTime(new Date());
//更新地址信息
Integer row = addressMapper.updateAddressByAid(address);
if (row != 1) {
throw new UpdateException("修改收货地址数据时出现未知错误,请联系系统管理员");
}
}

单元测试

1
2
3
4
5
6
7
8
@Test
public void updateAddressByAid(){
Address address=new Address();
address.setAid(3);
address.setName("梦赫");
address.setProvinceName("湖南");
addressService.updateAddressByAid(5,"梦想",address);
}

3 修改收货地址-控制器

3.1 处理异常

已处理

3.2 设计请求

1.跳到修改页面就发送根据当前aid的查询

1
2
3
4
请求路径:{uAid}/selectAid
请求参数:Integer aid
请求类型:GET
响应结果:JsonResult<Address>

2.设计用户提交的请求,并设计响应的方式。

1
2
3
4
请求路径:/update
请求参数:Address address, HttpSession session
请求类型:POST
响应结果:JsonResult<Void>

3.3 处理请求

1.在AddressController类中添加处理请求的update()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("{uAid}/selectAid")
public JsonResult<Address> selectAid(@PathVariable("uAid")Integer aid) {
Address address = addressService.selectAddressAid(aid);
return new JsonResult<>(OK,address);
}

@RequestMapping("update")
public JsonResult<Void> update(Address address, HttpSession session) {
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
addressService.updateAddressByAid(uid, username,address);
return new JsonResult<Void>(OK);
}

4 修改收货地址-前端页面

给对应组件如果没有id值则加id值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--注意:需要使用隐藏域来传递aid的值,后端才可以根据aid进行修改内容-->
<input type="hidden" id="aid" name="aid">

<input id="name" name="name" type="text" class="form-control" placeholder="请输入收货人姓名">

<input id="zip" name="zip" type="text" class="form-control" placeholder="请输入邮政编码">

<textarea id="address" name="address" class="form-control" rows="3"
placeholder="输入详细的收货地址,小区名称、门牌号等"></textarea>

<input id="phone" name="phone" type="text" class="form-control" placeholder="请输入手机号码">

<input id="tel" name="tel" type="text" class="form-control" placeholder="请输入固定电话号码">

<input id="tag" name="tag" type="text" class="form-control" placeholder="请输入地址类型,如:家、公司或者学校">

在address.html页面中body标签内部的script标签内,添加设置用户修改收货地址的代码。

首先需要加入

1
2
<!--获取url的参数($.getUrlParam("aid"))-->
<script type="text/javascript" src="../js/jquery-getUrlParam.js"></script>

修改ready的条件

1
2
3
4
5
6
7
8
9
10
11
12
$(document).ready(function () {
let aid = $.getUrlParam("aid");
console.log("ready-aid=" + aid);
if(aid!=null){
//根据aid查询结果给组件进行赋值
updateAid();
}
showProvinceList();
//追加数据(设置默认的“请选择”的值,作为控件的默认值)
$("#city-list").append(defaultOption);
$("#area-list").append(defaultOption);
});

编写updateAid()方法

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
function updateAid() {
let aid = $.getUrlParam("aid");
console.log("aid=" + aid);
$.ajax({
url: "/addresses/" + aid + "/selectAid",
type: "GET",
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
//利用隐藏域来传递aid
$("#aid").val(json.data.aid)
$("#name").val(json.data.name);
$("#province-list").append("<option value='" + json.data.provinceCode + "'>" + json.data.provinceName + "</option>");
$("#city-list").append("<option value='" + json.data.cityCode + "'>" + json.data.cityName + "</option>");
$("#area-list").append("<option value='" + json.data.areaCode + "'>" + json.data.areaName + "</option>");
$("#zip").val(json.data.zip);
$("#address").val(json.data.address);
$("#phone").val(json.data.phone);
$("#tel").val(json.data.tel);
$("#tag").val(json.data.tag);
}else {
alert("获取信息失败!" + json.message);
}
}
});

}

提交按钮的条件判断

是新增数据,还是修改数据

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
52
let aid = $.getUrlParam("aid");
//新增提交
if(aid==null){
//1.监听按键是否被点击
$("#btn-add-new-address").click(function () {
$.ajax({
url: "/addresses/add_new_address",
type: "POST",
//自动检测表单中的控件,并获取它们的值,
//自动拼接成---> username=amh&password=123456
data: $("#form-add-new-address").serialize(),//获取表单数据(并序列化)
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
//获取后端传来的 信息
alert(json.message);
} else {
//获取后端传来的 信息
alert(json.message);
}
},
error: function (xhr) {
alert("新增收货地址产生未知的错!" + xhr.status)
}
});
});
}else {
//修改提交 注意:需要使用隐藏域来传递aid的值,后端才可以根据aid进行修改内容
//1.监听按键是否被点击
$("#btn-add-new-address").click(function () {
$.ajax({
url: "/addresses/update",
type: "POST",
//自动检测表单中的控件,并获取它们的值,
//自动拼接成---> username=amh&password=123456
data: $("#form-add-new-address").serialize(),//获取表单数据(并序列化)
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
//获取后端传来的 信息
alert(json.message);
} else {
//获取后端传来的 信息
alert(json.message);
}
},
error: function (xhr) {
alert("新增收货地址产生未知的错!" + xhr.status)
}
});
});
}

商品

商品-数据表的创建

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 商品-创建实体类

创建com.amh.entity.Product类,并继承自BaseEntity类。在类中声明与数据表中对应的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.amh.entity;

/** 商品数据的实体类 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product extends BaseEntity implements Serializable {
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 接口与抽象方法

在com.amh.mapper包下创建ProductMapper接口并在接口中添加查询热销商品findHotList()的方法。

1
2
3
4
5
6
7
8
/** 处理商品数据的持久层接口 */
public interface ProductMapper {
/**
* 查询热销商品的前四名
* @return 热销商品前四名的集合
*/
List<Product> findHotList();
}

3.3 配置SQL映射

1.在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
21
22
23
24
25
26
27
28
29
<?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>

<!-- 查询热销商品的前四名:List<Product> findHostList() -->
<select id="findHotList" resultMap="ProductEntityMap">
SELECT
*
FROM
t_product
WHERE
status=1
ORDER BY
priority DESC
LIMIT 0,4
</select>
</mapper>

2.在com.cy.store.mapper包下创建ProductMapperTests测试类,并添加测试方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductMapperTests {
@Autowired
private ProductMapper productMapper;

@Test
public void findHotList() {
List<Product> list = productMapper.findHotList();
System.out.println("count=" + list.size());
for (Product item : list) {
System.out.println(item);
}
}
}

4 商品-热销排行-业务层

4.1 规划异常

说明:无异常。

4.2 接口与抽象方法

创建com.amh.service.IProductService接口,并在接口中添加findHotList()方法。

1
2
3
4
5
6
7
8
/** 处理商品数据的业务层接口 */
public interface IProductService {
/**
* 查询热销商品的前四名
* @return 热销商品前四名的集合
*/
List<Product> findHotList();
}

4.3 实现抽象方法

1.创建com.amh.service.impl.ProductServiceImpl类,并添加@Service注解;在类中声明持久层对象以及实现接口中的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** 处理商品数据的业务层实现类 */
@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;
}
}

2.在com.cy.store.service包下创建测试类ProductServiceTests,并编写测试方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductServiceTests {
@Autowired
private IProductService productService;

@Test
public void findHotList() {
try {
List<Product> list = productService.findHotList();
System.out.println("count=" + list.size());
for (Product item : list) {
System.out.println(item);
}
} catch (ServiceException e) {
System.out.println(e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
}
}

5 商品-热销排行-控制器

5.1 处理异常

说明:无异常。

5.2 设计请求

1.设计用户提交的请求,并设计响应的方式。

请求路径:/products/hot_list
请求参数:无
请求类型:GET
响应结果:JsonResult<List<Product>>
是否拦截:否,需要将index.html和products/**添加到白名单

2.在LoginInterceptorConfigurer类中将index.html页面和products/**请求添加到白名单。

1
2
patterns.add("/web/index.html");
patterns.add("/products/**");

5.3 处理请求

1.创建com.cy.controller.ProductController类继承自BaseController类,类添加@RestController和@RequestMapping(“products”)注解,并在类中添加业务层对象。

1
2
3
4
5
6
@RestController
@RequestMapping("products")
public class ProductController extends BaseController {
@Autowired
private IProductService productService;
}

2.在类中添加处理请求的getHotList()方法。

1
2
3
4
5
@RequestMapping("hot_list")
public JsonResult<List<Product>> getHotList() {
List<Product> data = productService.findHotList();
return new JsonResult<List<Product>>(OK, data);
}

3.完成后启动项目,直接访问http://localhost:8080/products/hot_list进行测试。

6 商品-热销排行-前端页面

1.在index.html页面给“热销排行”列表的div标签设置id属性值。

1
2
3
<div id="hot-list" class="panel-body panel-item">
<!-- ... -->
</div>

2.在index.html页面中body标签内部的最后,添加展示热销排行商品的代码。

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
<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) {
let list = json.data;
console.log("count=" + list.length);
for (let i = 0; i < list.length; i++) {
console.log(list[i].title);
let 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>

3.完成后启动项目,直接访问http://localhost:8080/web/index.html进行测试。

显示商品详情

1 商品-显示商品详情-持久层

1.1 规划需要执行的SQL语句

根据商品id显示商品详情的SQL语句大致是。

1
SELECT * FROM t_product WHERE id=?

1.2 接口与抽象方法

在ProductMapper接口中添加抽象方法。

1
2
3
4
5
6
/**
* 根据商品id查询商品详情
* @param id 商品id
* @return 匹配的商品详情,如果没有匹配的数据则返回null
*/
Product findById(Integer id);

1.3 配置SQL映射

1.在ProductMapper.xml文件中配置findById(Integer id)方法的映射。

1
2
3
4
5
6
7
8
9
<!-- 根据商品id查询商品详情:Product findById(Integer id) -->
<select id="findById" resultMap="ProductEntityMap">
SELECT
*
FROM
t_product
WHERE
id=#{id}
</select>

2.在ProductMapperTests测试类中添加测试方法。

1
2
3
4
5
6
@Test
public void findById() {
Integer id = 10000017;
Product result = productMapper.findById(id);
System.out.println(result);
}

2 商品-显示商品详情-业务层

2.1 规划异常

如果商品数据不存在,应该抛出ProductNotFoundException,需要创建com.amh.service.ex.ProductNotFoundException异常。

1
2
3
4
/** 商品数据不存在的异常 */
public class ProductNotFoundException extends ServiceException {
// Override Methods...
}

2.2 接口与抽象方法

在业务层IProductService接口中添加findById(Integer id)抽象方法。

1
2
3
4
5
6
/**
* 根据商品id查询商品详情
* @param id 商品id
* @return 匹配的商品详情,如果没有匹配的数据则返回null
*/
Product findById(Integer id);

2.3 实现抽象方法

1.在ProductServiceImpl类中,实现接口中的findById(Integer id)抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public Product findById(Integer id) {
// 根据参数id调用私有方法执行查询,获取商品数据
Product product = productMapper.findById(id);
// 判断查询结果是否为null
if (product == null) {
// 是:抛出ProductNotFoundException
throw new ProductNotFoundException("尝试访问的商品数据不存在");
}
// 将查询结果中的部分属性设置为null
product.setPriority(null);
product.setCreatedUser(null);
product.setCreatedTime(null);
product.setModifiedUser(null);
product.setModifiedTime(null);
// 返回查询结果
return product;
}

2.在ProductServiceTests测试类中编写测试方法。

1
2
3
4
5
6
7
8
9
10
11
@Test
public void findById() {
try {
Integer id = 100000179;
Product result = productService.findById(id);
System.out.println(result);
} catch (ServiceException e) {
System.out.println(e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
}

3 商品-显示商品详情-控制器

3.1 处理异常

在BaseController类中的handleException()方法中添加处理ProductNotFoundException的异常。

1
2
3
4
5
// ...
else if (e instanceof ProductNotFoundException) {
result.setState(4006);
}
// ...

3.2 设计请求

设计用户提交的请求,并设计响应的方式。

请求路径:/products/{id}/details
请求参数:@PathVariable("id") Integer id
请求类型:GET
响应结果:JsonResult<Product>

3.3 处理请求

1.在ProductController类中添加处理请求的getById()方法。

1
2
3
4
5
6
7
@GetMapping("{id}/details")
public JsonResult<Product> getById(@PathVariable("id") Integer id) {
// 调用业务对象执行获取数据
Product data = productService.findById(id);
// 返回成功和数据
return new JsonResult<Product>(OK, data);
}

2.完成后启动项目,直接访问http://localhost:8080/products/10000017/details进行测试。

4 商品-显示商品详情-前端页面

1.检查在product.html页面body标签内部的最后是否引入jquery-getUrlParam.js文件,如果引入无需重复引入。

1
2
<!--获取url的参数($.getUrlParam("aid"))-->
<script type="text/javascript" src="../js/jquery-getUrlParam.js"></script>

2.在product.html页面中body标签内部的最后添加获取当前商品详情的代码。

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
<script type="text/javascript">
let id = $.getUrlParam("id");
console.log("id=" + id);
$(document).ready(function() {
$.ajax({
url: "/products/" + id + "/details",
type: "GET",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
console.log("title=" + json.data.title);
$("#product-title").html(json.data.title);
$("#product-sell-point").html(json.data.sellPoint);
$("#product-price").html(json.data.price);

for (let i = 1; i <= 5; i++) {
$("#product-image-" + i + "-big").attr("src", ".." + json.data.image + i + "_big.png");
$("#product-image-" + i).attr("src", ".." + json.data.image + i + ".jpg");
}
} else if (json.state == 4006) { // 商品数据不存在的异常
location.href = "index.html";
} else {
alert("获取商品信息失败!" + json.message);
}
}
});
});
</script>

3.完成后启动项目,先访问http://localhost:8080/web/index.html页面,然后点击“热销排行”中的某个子项,将跳转到product.html商品详情页,观察页面是否加载的是当前的商品信息。

加入购物车

1 购物车-创建数据表

.在store数据库中创建t_cart用户数据表。

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE t_cart (
cid INT AUTO_INCREMENT COMMENT '购物车数据id',
uid INT NOT NULL COMMENT '用户id',
pid INT NOT NULL COMMENT '商品id',
price BIGINT COMMENT '加入时商品单价',
num INT COMMENT '商品数量',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '修改人',
modified_time DATETIME COMMENT '修改时间',
PRIMARY KEY (cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2 购物车-创建实体类

在com.cy.store.entity包下创建购物车的Cart实体类。

1
2
3
4
5
6
7
8
9
10
/** 购物车数据的实体类 */
public class Cart extends BaseEntity implements Serializable {
private Integer cid;
private Integer uid;
private Integer pid;
private Long price;
private Integer num;

// Generate: Getter and Setter、Generate hashCode() and equals()、toString()
}

3 购物车-添加购物车-持久层

3.1 规划需要执行的SQL语句

向购物车表中插入商品数据的SQL语句大致是:

1
insert into t_cart (除了cid以外的字段列表) values (匹配的值列表);

如果用户曾经将某个商品加入到购物车过,则点击“加入购物车”按钮只会对购物车中相同商品数量做递增操作。

1
update t_cart set num=? where cid=?

关于判断“到底应该插入数据,还是修改数量”,可以通过“查询某用户是否已经添加某商品到购物车”来完成。如果查询到某结果,就表示该用户已经将该商品加入到购物车了,如果查询结果为null,则表示该用户没有添加过该商品。

1
select * from t_cart where uid=? and pid=?

3.2 接口与抽象方法

在com.cy.store.mapper包下创建CartMapper接口,并添加抽象相关的方法。

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
/** 处理购物车数据的持久层接口 */
public interface CartMapper {
/**
* 插入购物车数据
* @param cart 购物车数据
* @return 受影响的行数
*/
Integer insert(Cart cart);

/**
* 修改购物车数据中商品的数量
* @param cid 购物车数据的id
* @param num 新的数量
* @param modifiedUser 修改执行人
* @param modifiedTime 修改时间
* @return 受影响的行数
*/
Integer updateNumByCid(
@Param("cid") Integer cid,
@Param("num") Integer num,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);

/**
* 根据用户id和商品id查询购物车中的数据
* @param uid 用户id
* @param pid 商品id
* @return 匹配的购物车数据,如果该用户的购物车中并没有该商品,则返回null
*/
Cart findByUidAndPid(
@Param("uid") Integer uid,
@Param("pid") Integer pid);
}

3.3 配置SQL映射

1.在resources.mapper文件夹下创建CartMapper.xml文件,并在文件中配置以上三个方法的映射。

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
<?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.amh.mapper.CartMapper">
<resultMap id="CartEntityMap" type="com.amh.entity.Cart">
<id column="cid" property="cid"/>
<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>

<!-- 插入购物车数据:Integer insert(Cart cart) -->
<insert id="insert" useGeneratedKeys="true" keyProperty="cid">
INSERT INTO stores.t_cart (uid, pid, price, num, created_user, created_time, modified_user, modified_time)
VALUES (#{uid}, #{pid}, #{price}, #{num}, #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime})
</insert>

<!-- 修改购物车数据中商品的数量 -->
<update id="updateNumByCid">
UPDATE
stores.t_cart
SET
num=#{num},
modified_user=#{modifiedUser},
modified_time=#{modifiedTime}
WHERE
cid=#{cid}
</update>

<!-- 根据用户id和商品id查询购物车中的数据-->
<select id="findByUidAndPid" resultMap="CartEntityMap">
SELECT
*
FROM
stores.t_cart
WHERE
uid=#{uid} AND pid=#{pid}
</select>
</mapper>

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
34
35
@RunWith(SpringRunner.class)
@SpringBootTest
public class CartMapperTests {
@Autowired
private CartMapper cartMapper;

@Test
public void insert() {
Cart cart = new Cart();
cart.setUid(1);
cart.setPid(2);
cart.setNum(3);
cart.setPrice(4L);
Integer rows = cartMapper.insert(cart);
System.out.println("rows=" + rows);
}

@Test
public void updateNumByCid() {
Integer cid = 1;
Integer num = 10;
String modifiedUser = "购物车管理员";
Date modifiedTime = new Date();
Integer rows = cartMapper.updateNumByCid(cid, num, modifiedUser, modifiedTime);
System.out.println("rows=" + rows);
}

@Test
public void findByUidAndPid() {
Integer uid = 1;
Integer pid = 2;
Cart result = cartMapper.findByUidAndPid(uid, pid);
System.out.println(result);
}
}

4 购物车-添加购物车-业务层

4.1 规划异常

在插入数据时,可能抛出InsertException异常;在修改数据时,可能抛出UpdateException异常。如果不限制购物车中的记录的数量,则没有其它异常。

4.2 接口与抽象方法

在com.cy.store.service包下创建ICartService接口,并添加抽象方法。

1
2
3
4
5
6
7
8
9
10
11
/** 处理商品数据的业务层接口 */
public interface ICartService {
/**
* 将商品添加到购物车
* @param uid 当前登录用户的id
* @param pid 商品的id
* @param amount 增加的数量
* @param username 当前登录的用户名
*/
void addToCart(Integer uid, Integer pid, Integer amount, String username);
}

4.3 实现抽象方法

1.创建com.cy.store.service.impl.CartServiceImpl类,并实现ICartService接口,并在类的定义前添加@Service注解。在类中声明CartMapper持久层对象和IProductService处理商品数据的业务对象,并都添加@Autowired注修饰。

1
2
3
4
5
6
7
8
/** 处理购物车数据的业务层实现类 */
@Service
public class CartServiceImpl implements ICartService {
@Autowired
private CartMapper cartMapper;
@Autowired
private IProductService productService;
}

2.在CartServiceImpl类中实现业务层ICartService接口中定义的抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void addToCart(Integer uid, Integer pid, Integer amount, String username) {
// 根据参数pid和uid查询购物车中的数据
// 判断查询结果是否为null
// 是:表示该用户并未将该商品添加到购物车
// -- 创建Cart对象
// -- 封装数据:uid,pid,amount
// -- 调用productService.findById(pid)查询商品数据,得到商品价格
// -- 封装数据:price
// -- 封装数据:4个日志
// -- 调用insert(cart)执行将数据插入到数据表中
// 否:表示该用户的购物车中已有该商品
// -- 从查询结果中获取购物车数据的id
// -- 从查询结果中取出原数量,与参数amount相加,得到新的数量
// -- 执行更新数量
}

3.addToCart(Integer uid, Integer pid, Integer amount, String username)方法的代码具体实现。

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
@Override
public void addToCart(Integer uid, Integer pid, Integer amount, String username) {
// 根据参数pid和uid查询购物车中的数据
Cart result = cartMapper.findByUidAndPid(uid, pid);
Integer cid = result.getCid();
Date now = new Date();
// 判断查询结果是否为null
if (result == null) {
// 是:表示该用户并未将该商品添加到购物车
// 创建Cart对象
Cart cart = new Cart();
// 封装数据:uid,pid,amount
cart.setUid(uid);
cart.setPid(pid);
cart.setNum(amount);
// 调用productService.findById(pid)查询商品数据,得到商品价格
Product product = productService.findById(pid);
// 封装数据:price
cart.setPrice(product.getPrice());
// 封装数据:4个日志
cart.setCreatedUser(username);
cart.setCreatedTime(now);
cart.setModifiedUser(username);
cart.setModifiedTime(now);
// 调用insert(cart)执行将数据插入到数据表中
Integer rows = cartMapper.insert(cart);
if (rows != 1) {
throw new InsertException("插入商品数据时出现未知错误,请联系系统管理员");
}
} else {
// 否:表示该用户的购物车中已有该商品
// 从查询结果中获取购物车数据的id
Integer cid = result.getCid();
// 从查询结果中取出原数量,与参数amount相加,得到新的数量
Integer num = result.getNum() + amount;
// 执行更新数量
Integer rows = cartMapper.updateNumByCid(cid, num, username, now);
if (rows != 1) {
throw new InsertException("修改商品数量时出现未知错误,请联系系统管理员");
}
}
}

4.单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RunWith(SpringRunner.class)
@SpringBootTest
public class CartServiceTests {
@Autowired
private ICartService cartService;

@Test
public void addToCart() {
try {
Integer uid = 2;
Integer pid = 10000007;
Integer amount = 1;
String username = "Tom";
cartService.addToCart(uid, pid, amount, username);
System.out.println("OK.");
} catch (ServiceException e) {
System.out.println(e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
}
}

5 购物车-添加购物车-控制器

5.1 处理异常

说明:无异常。

5.2 设计请求

设计用户提交的请求,并设计响应的方式。

请求路径:/carts/add_to_cart
请求参数:Integer pid, Integer amount, HttpSession session
请求类型:POST
响应结果:JsonResult<Void>

5.3 处理请求

1.在com.amh.controller包下创建CartController类并继承自BaseController类,添加@RequestMapping(“carts”)和@RestController注解;在类中声明ICartService业务对象,并使用@Autowired注解修饰。

1
2
3
4
5
6
7
@RestController
@RequestMapping("carts")
public class CartController extends BaseController {
@Autowired
private ICartService cartService;

}

2.在CartController类中添加处理请求的addToCart()方法。

1
2
3
4
5
6
7
8
9
10
@RequestMapping("add_to_cart")
public JsonResult<Void> addToCart(Integer pid, Integer amount, HttpSession session) {
// 从Session中获取uid和username
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
// 调用业务对象执行添加到购物车
cartService.addToCart(uid, pid, amount, username);
// 返回成功
return new JsonResult<Void>(OK);
}

3.完成后启动项目,先登录再访问http://localhost:8080/carts/add_to_cart?pid=10000017&amount=3进行测试。

6 购物车-添加购物车-前端页面

1.在product.html页面中的body标签内的script标签里为“加入购物车”按钮添加点击事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$("#btn-add-to-cart").click(function() {
$.ajax({
url: "/carts/add_to_cart",
type: "POST",
data: {
"pid": id,
"amount": $("#num").val()
},
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
alert("增加成功!");
} else {
alert("增加失败!" + json.message);
}
},
error: function(xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html";
}
});
});

$.ajax函数中参数data提交请参数的方式:

1
2
3
4
5
6
7
8
9
10
11
// 1.适用于参数较多,且都在同一个表单中
data: $("#form表单id属性值").serialize()
// 2.仅适用于上传文件
data: new FormData($("##form表单id属性值")[0])
// 3.参数拼接形式提交
data: "pid=10000005&amount=3"
// 4.使用JSON格式提交参数
data: {
"pid": 10000005,
"amount": 3
}

2.完成后启动项目,先登录再访问http://localhost:8080/web/index.html页面进行测试。

显示购物车列表

1 购物车-显示列表-持久层

1.1 规划需要执行的SQL语句

显示某用户的购物车列表数据的SQL语句大致是。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT
cid,
uid,
pid,
t_cart.price,
t_cart.num,
t_product.title,
t_product.price AS realPrice,
t_product.image
FROM
t_cart
LEFT JOIN t_product ON t_cart.pid = t_product.id
WHERE
uid = #{uid}
ORDER BY
t_cart.created_time DESC

1.2 接口与抽象方法

1.由于涉及多表关联查询,必然没有哪个实体类可以封装此次的查询结果,因此需要创建VO类。创建com.amh.vo.CartVO类。

1
2
3
4
5
6
7
8
9
10
11
12
13
/** 购物车数据的Value Object类 */
public class CartVO implements Serializable {
private Integer cid;
private Integer uid;
private Integer pid;
private Long price;
private Integer num;
private String title;
private Long realPrice;
private String image;

// Generate: Getter and Setter、Generate hashCode() and equals()、toString()
}

2.在CartMapper接口中添加抽象方法。

1
2
3
4
5
6
/**
* 查询某用户的购物车数据
* @param uid 用户id
* @return 该用户的购物车数据的列表
*/
List<CartVO> findVOByUid(Integer uid);

1.3 配置SQL映射

1.在CartMapper.xml文件中添加findVOByUid()方法的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 查询某用户的购物车数据:List<CartVO> findVOByUid(Integer uid) -->
<select id="findVOByUid" resultType="com.cy.store.vo.CartVO">
SELECT
cid,
uid,
pid,
t_cart.price,
t_cart.num,
t_product.title,
t_product.price AS realPrice,
t_product.image
FROM
t_cart
LEFT JOIN t_product ON t_cart.pid = t_product.id
WHERE
uid = #{uid}
ORDER BY
t_cart.created_time DESC
</select>

2.在CartMapperTests测试类中添加findVOByUid()方法的测试。

1
2
3
4
5
@Test
public void findVOByUid() {
List<CartVO> list = cartMapper.findVOByUid(31);
System.out.println(list);
}

2 购物车-显示列表-业务层

2.1 规划异常

说明:无异常。

2.2 接口与抽象方法

在ICartService接口中添加findVOByUid()抽象方法。

1
2
3
4
5
6
/**
* 查询某用户的购物车数据
* @param uid 用户id
* @return 该用户的购物车数据的列表
*/
List<CartVO> getVOByUid(Integer uid);

2.3 实现抽象方法

1.在CartServiceImpl类中重写业务接口中的抽象方法。

1
2
3
4
@Override
public List<CartVO> getVOByUid(Integer uid) {
return cartMapper.findVOByUid(uid);
}

2.在CartServiceTests测试类中添加getVOByUid()测试方法。

1
2
3
4
5
6
7
8
@Test
public void getVOByUid() {
List<CartVO> list = cartService.getVOByUid(31);
System.out.println("count=" + list.size());
for (CartVO item : list) {
System.out.println(item);
}
}

3 购物车-显示列表-控制器

3.1 处理异常

说明:无异常。

3.2 设计请求

设计用户提交的请求,并设计响应的方式。

请求路径:/carts/
请求参数:HttpSession session
请求类型:GET
响应结果:JsonResult<List<CartVO>>

3.3 处理请求

1.在CartController类中编写处理请求的代码。

1
2
3
4
5
6
7
8
9
@GetMapping({"", "/"})
public JsonResult<List<CartVO>> getVOByUid(HttpSession session) {
// 从Session中获取uid
Integer uid = getUidFromSession(session);
// 调用业务对象执行查询数据
List<CartVO> data = cartService.getVOByUid(uid);
// 返回成功与数据
return new JsonResult<List<CartVO>>(OK, data);
}

2.完成后启动项目,先登录再访问http://localhost:8080/carts请求进行测试。

4 购物车-显示列表-前端页面

1.将cart.html页面的head头标签内引入的cart.js文件注释掉。

1
<!-- <script src="../js/cart.js" type="text/javascript" charset="utf-8"></script> -->

2.给form标签添加action=”orderConfirm.html”属性、tbody标签添加id=”cart-list”属性、结算按钮的类型改为type=”button”值。如果以上属性值已经添加过无需重复添加。

3.在cart.html页面body标签内的script标签中编写展示购物车列表的代码。

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
$(document).ready(function() {
showCartList();
});

function showCartList() {
$("#cart-list").empty();
$.ajax({
url: "/carts",
type: "GET",
dataType: "JSON",
success: function(json) {
let list = json.data;
for (let i = 0; i < list.length; i++) {
let tr = '<tr>'
+ '<td>'
+ '<input name="cids" value="#{cid}" type="checkbox" class="ckitem" />'
+ '</td>'
+ '<td><img src="..#{image}collect.png" class="img-responsive" /></td>'
+ '<td>#{title}#{msg}</td>'
+ '<td>¥<span id="price-#{cid}">#{realPrice}</span></td>'
+ '<td>'
+ '<input type="button" value="-" class="num-btn" onclick="reduceNum(1)" />'
+ '<input id="num-#{cid}" type="text" size="2" readonly="readonly" class="num-text" value="#{num}">'
+ '<input class="num-btn" type="button" value="+" onclick="addNum(#{cid})" />'
+ '</td>'
+ '<td>¥<span id="total-price-#{cid}">#{totalPrice}</span></td>'
+ '<td>'
+ '<input type="button" onclick="delCartItem(this)" class="cart-del btn btn-default btn-xs" value="删除" />'
+ '</td>'
+ '</tr>';
tr = tr.replace(/#{cid}/g, list[i].cid);
tr = tr.replace(/#{title}/g, list[i].title);
tr = tr.replace(/#{image}/g, list[i].image);
tr = tr.replace(/#{realPrice}/g, list[i].realPrice);
tr = tr.replace(/#{num}/g, list[i].num);
tr = tr.replace(/#{totalPrice}/g, list[i].realPrice * list[i].num);

if (list[i].realPrice < list[i].price) {
tr = tr.replace(/#{msg}/g, "比加入时降价" + (list[i].price - list[i].realPrice) + "元");
} else {
tr = tr.replace(/#{msg}/g, "");
}
$("#cart-list").append(tr);
}
}
});
}

4.完成后启动项目,先登录再访问http://localhost:8080/web/cart.html页面进行测试。

增加/减少商品数量

1 购物车-增加/减少商品数量-持久层

1.1 规划需要执行的SQL语句

1.首先进行查询需要操作的购物车数据信息。

1
SELECT * FROM t_cart WHERE cid=?

2.然后计算出新的商品数量值,如果满足更新条件则执行更新操作。此SQL语句无需重复开发。

1
UPDATE t_cart SET num=?, modified_user=?, modified_time=? WHERE cid=?

1.2 接口与抽象方法

在CartMapper接口中添加抽象方法。

1
2
3
4
5
6
/**
* 根据购物车数据id查询购物车数据详情
* @param cid 购物车数据id
* @return 匹配的购物车数据详情,如果没有匹配的数据则返回null
*/
Cart findByCid(Integer cid);

1.3 配置SQL映射

1.在CartMapper文件中添加findByCid(Integer cid)方法的映射。

1
2
3
4
5
6
7
8
9
<!-- 根据购物车数据id查询购物车数据详情:Cart findByCid(Integer cid) -->
<select id="findByCid" resultMap="CartEntityMap">
SELECT
*
FROM
t_cart
WHERE
cid = #{cid}
</select>

2.在CartMapperTests测试类中添加findByCid()测试方法。

1
2
3
4
5
6
@Test
public void findByCid() {
Integer cid = 6;
Cart result = cartMapper.findByCid(cid);
System.out.println(result);
}

2 购物车-增加/减少商品数量-业务层

2.1 规划异常

1.如果尝试访问的购物车数据不存在,则抛出CartNotFoundException异常。创建com.amh.service.ex.CartNotFoundException类。

1
2
3
4
/** 购物车数据不存在的异常 */
public class CartNotFoundException extends ServiceException {
// Override Methods...
}

2.如果尝试访问的数据并不是当前登录用户的数据,则抛出AccessDeniedException异常。此异常类无需再次创建。

3.最终执行更新操作时,可能会抛出UpdateException异常。此异常类无需再次创建。

2.2 接口与抽象方法

在业务层ICartService接口中添加addNum()抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 将购物车中某商品的数量加1
* @param cid 购物车数量的id
* @param uid 当前登录的用户的id
* @param username 当前登录的用户名
* @return 增加成功后新的数量
*/
Integer addNum(Integer cid, Integer uid, String username);

/**
* 将购物车中某商品的数量减1
* @param cid 购物车数量的id
* @param uid 当前登录的用户的id
* @param username 当前登录的用户名
* @return 减少成功后新的数量
*/
Integer reduceNum(Integer cid, Integer uid, String username);

2.3 实现抽象方法

1.在CartServiceImpl类中,实现接口中的抽象方法并规划业务逻辑。

2.实现addNum()方法和reduceNum()方法中的业务逻辑代码。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Override
public Integer addNum(Integer cid, Integer uid, String username) {
// 调用findByCid(cid)根据参数cid查询购物车数据
Cart result = cartMapper.findByCid(cid);
// 判断查询结果是否为null
if (result == null) {
// 是:抛出CartNotFoundException
throw new CartNotFoundException("尝试访问的购物车数据不存在");
}

// 判断查询结果中的uid与参数uid是否不一致
if (!result.getUid().equals(uid)) {
// 是:抛出AccessDeniedException
throw new AccessDeniedException("非法访问");
}

// 可选:检查商品的数量是否大于多少(适用于增加数量)或小于多少(适用于减少数量)
// 根据查询结果中的原数量增加1得到新的数量num
Integer num = result.getNum() + 1;

// 创建当前时间对象,作为modifiedTime
Date now = new Date();
// 调用updateNumByCid(cid, num, modifiedUser, modifiedTime)执行修改数量
Integer rows = cartMapper.updateNumByCid(cid, num, username, now);
if (rows != 1) {
throw new InsertException("修改商品数量时出现未知错误,请联系系统管理员");
}

// 返回新的数量
return num;
}

@Override
public Integer reduceNum(Integer cid, Integer uid, String username) {
// 调用findByCid(cid)根据参数cid查询购物车数据
Cart result = cartMapper.findByCid(cid);
// 判断查询结果是否为null
if (result == null) {
// 是:抛出CartNotFoundException
throw new CartNotFoundException("尝试访问的购物车数据不存在");
}

// 判断查询结果中的uid与参数uid是否不一致
if (!result.getUid().equals(uid)) {
// 是:抛出AccessDeniedException
throw new AccessDeniedException("非法访问");
}

// 可选:检查商品的数量是否大于多少(适用于增加数量)或小于多少(适用于减少数量)
// 根据查询结果中的原数量减少1得到新的数量num

//如果result.getNum()==0,则商品数量不在减少
if(result.getNum()==0){
// 返回数量0
return 0;
}
Integer num = result.getNum() - 1;

// 创建当前时间对象,作为modifiedTime
Date now = new Date();
// 调用updateNumByCid(cid, num, modifiedUser, modifiedTime)执行修改数量
Integer rows = cartMapper.updateNumByCid(cid, num, username, now);
if (rows != 1) {
throw new InsertException("修改商品数量时出现未知错误,请联系系统管理员");
}

// 返回新的数量
return num;
}

3.在CartServiceTests测试类中添加addNum()测试方法。

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
@Test
public void addNum() {
try {
Integer cid = 6;
Integer uid = 31;
String username = "管理员";
Integer num = cartService.addNum(cid, uid, username);
System.out.println("OK. New num=" + num);
} catch (ServiceException e) {
System.out.println(e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
}

@Test
public void reduceNum() {
try {
Integer cid = 6;
Integer uid = 31;
String username = "管理员";
Integer num = cartService.reduceNum(cid, uid, username);
System.out.println("OK. New num=" + num);
} catch (ServiceException e) {
System.out.println(e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
}

3 购物车-增加/减少商品数量-控制器

3.1 处理异常

在BaseController类中添加CartNotFoundException异常类的统一管理。

1
2
3
4
5
// ...
else if (e instanceof CartNotFoundException) {
result.setState(4007);
}
// ...

3.2 设计请求

设计用户提交的请求,并设计响应的方式。

增加数量

请求路径:/carts/{cid}/num/add
请求参数:@PathVariable("cid") Integer cid, HttpSession session
请求类型:POST
响应结果:JsonResult<Integer>

减少数量

请求路径:/carts/{cid}/num/reduce
请求参数:@PathVariable("cid") Integer cid, HttpSession session
请求类型:POST
响应结果:JsonResult<Integer>

3.3 处理请求

1.在CartController类中添加处理请求的addNum()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RequestMapping("{cid}/num/add")
public JsonResult<Integer> addNum(@PathVariable("cid") Integer cid, HttpSession session) {
// 从Session中获取uid和username
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
// 调用业务对象执行增加数量
Integer data = cartService.addNum(cid, uid, username);
// 返回成功
return new JsonResult<Integer>(OK, data);
}

@RequestMapping("{cid}/num/reduce")
public JsonResult<Integer> reduceNum(@PathVariable("cid") Integer cid, HttpSession session) {
// 从Session中获取uid和username
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
// 调用业务对象执行减少数量
Integer data = cartService.reduceNum(cid, uid, username);
// 返回成功
return new JsonResult<Integer>(OK, data);
}

2.完成后启动项目,先登录再访问http://localhost:8080/carts/6/num/add页面进行测试。

4 购物车-增加/减少商品数量-前端页面

1.首先确定在cart.html页面中showCartList()函数中动态拼接的增加购物车按钮是绑定了addNum()事件,如果已经添加无需重复添加。

1
2
3
<input type="button" value="-" class="num-btn" onclick="reduceNum(#{cid})

<input class="num-btn" type="button" value="+" onclick="addNum(#{cid})" />

2.在script标签中定义addNum()函数并编写增加购物车数量的逻辑代码。

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
function addNum(cid) {
$.ajax({
url: "/carts/" + cid + "/num/add",
type: "POST",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
// showCartList();
$("#num-" + cid).val(json.data);
let price = $("#price-" + cid).html();
let totalPrice = price * json.data;
$("#total-price-" + cid).html(totalPrice);
} else {
alert("增加商品数量失败!" + json.message);
}
},
error: function(xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html";
}
});
}

function reduceNum(cid) {
$.ajax({
url: "/carts/" + cid + "/num/reduce",
type: "POST",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
// showCartList();
$("#num-" + cid).val(json.data);
let price = $("#price-" + cid).html();
let totalPrice = price * json.data;
$("#total-price-" + cid).html(totalPrice);
} else {
alert("减少商品数量失败!" + json.message);
}
},
error: function(xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html";
}
});
}

3.完成后启动项目,先登录再访问http://localhost:8080/web/cart.html页面点击“+”按钮进行测试。

显示勾选的购物车数据

1 显示确认订单页-显示勾选的购物车数据-持久层

1.1 规划需要执行的SQL语句

在“确认订单页”显示的商品信息,应来自前序页面(购物车列表)中勾选的数据,所以显示的信息其实是购物车中的数据。到底需要显示哪些取决于用户的勾选操作,当用户勾选了若干条购物车数据后,这些数据的id应传递到当前“确认订单页”中,该页面根据这些id获取需要显示的数据列表。

所以在持久层需要完成“根据若干个不确定的id值,查询购物车数据表,显示购物车中的数据信息”。则需要执行的SQL语句大致是。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT
cid,
uid,
pid,
t_cart.price,
t_cart.num,
t_product.title,
t_product.price AS realPrice,
t_product.image
FROM
t_cart
LEFT JOIN t_product ON t_cart.pid = t_product.id
WHERE
cid IN (?, ?, ?)
ORDER BY
t_cart.created_time DESC

1.2 接口与抽象方法

在CartMapper接口中添加findVOByCids(Integer[] cids)方法。

1
2
3
4
5
6
/**
* 根据若干个购物车数据id查询详情的列表
* @param cids 若干个购物车数据id
* @return 匹配的购物车数据详情的列表
*/
List<CartVO> findVOByCids(@Param("cids") Integer[] cids);

1.3 配置SQL映射

1.在CartMapper.xml文件中添加SQL语句的映射配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 根据若干个购物车数据id查询详情的列表:List<CartVO> findVOByCids(Integer[] cids) -->
<select id="findVOByCids" resultType="com.cy.store.vo.CartVO">
SELECT
cid,
uid,
pid,
t_cart.price,
t_cart.num,
t_product.title,
t_product.price AS realPrice,
t_product.image
FROM
stores.t_cart
LEFT JOIN stores.t_product ON t_cart.pid = t_product.id
WHERE
cid IN
<foreach collection="cids" item="cid" separator="," open="(" close=")">
#{cid}
</foreach>

ORDER BY
t_cart.created_time DESC
</select>

2.在CartMapperTests测试类中添加findVOByCids()测试方法。

1
2
3
4
5
6
7
8
9
@Test
public void findVOByCids() {
Integer[] cids = {1, 2};
List<CartVO> list = cartMapper.findVOByCids(cids);
System.out.println("count=" + list.size());
for (CartVO item : list) {
System.out.println(item);
}
}

2 显示确认订单页-显示勾选的购物车数据-业务层

2.1 规划异常

说明:无异常。

2.2 接口与抽象方法

在ICartService接口中添加getVOByCids()抽象方法。

1
2
3
4
5
6
7
/**
* 根据若干个购物车数据id查询详情的列表
* @param uid 当前登录的用户的id
* @param cids 若干个购物车数据id
* @return 匹配的购物车数据详情的列表
*/
List<CartVO> getVOByCids(Integer uid, Integer[] cids);

2.3 实现抽象方法

1.在CartServiceImpl类中重写业务接口中的抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public List<CartVO> getVOByCids(Integer uid, Integer[] cids) {
List<CartVO> list = cartMapper.findVOByCids(cids);
/**
for (CartVO cart : list) {
if (!cart.getUid().equals(uid)) {
list.remove(cart);
}
}
*/
Iterator<CartVO> it = list.iterator();
while (it.hasNext()) {
CartVO cart = it.next();
//筛选出是当前登录用户的购物车
if (!cart.getUid().equals(uid)) {
it.remove();
}
}
return list;
}

2.在CartServiceTests测试类中添加getVOByCids()测试方法。

1
2
3
4
5
6
7
8
9
10
@Test
public void getVOByCids() {
Integer[] cids = {1, 2};
Integer uid = 31;
List<CartVO> list = cartService.getVOByCids(uid, cids);
System.out.println("count=" + list.size());
for (CartVO item : list) {
System.out.println(item);
}
}

3 显示确认订单页-显示勾选的购物车数据-控制器

3.1 处理异常

说明:无异常。

3.2 设计请求

设计用户提交的请求,并设计响应的方式。

请求路径:/carts/list
请求参数:Integer[] cids, HttpSession session
请求类型:GET
响应结果:JsonResult<List<CartVO>>

3.3 处理请求

1.在CartController类中添加处理请求的getVOByCids()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
@GetMapping("list")
public JsonResult<List<CartVO>> getVOByCids(Integer[] cids, HttpSession session) {
//避免cids为空值会报错
if(cids==null){
cids= new Integer[]{0};
}
// 从Session中获取uid
Integer uid = getUidFromSession(session);
// 调用业务对象执行查询数据
List<CartVO> data = cartService.getVOByCids(uid, cids);
// 返回成功与数据
return new JsonResult<>(OK, data);
}

2.完成后启动项目,先登录再访问http://localhost:8080/carts/list?cids=7&cids=8&cids=13&cids=14&cids=17地址进行测试。

4 显示确认订单页-前端页面

4.1 显示勾选的购物车数据-前端页面

1.在orderConfirm.html页面的head标签里注释掉引入外部的orderConfirm.js文件。

1
<!-- <script src="../js/orderConfirm.js" type="text/javascript" charset="utf-8"></script> -->

2.在orderConfirm.html页面中检查必要控件的属性是否添加,如果已添加无需重复添加。

3.在orderConfirm.html页面中的body标签内的最后添加srcipt标签并在标签内部添加处理购物车“订单商品信息”列表展示的代码。

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
<script type="text/javascript">
$(document).ready(function() {
//showAddressList();
showCartList();
});

function showCartList() {
//先清空,在重新拼接
$("#cart-list").empty();

$.ajax({
url: "/carts/list",
data: location.search.substr(1),
type: "GET",
dataType: "JSON",
success: function(json) {
let list = json.data;
console.log("count=" + list.length);
let allCount = 0;
let allPrice = 0;
for (let i = 0; i < list.length; i++) {
console.log(list[i].title);
let tr = '<tr>'
+ '<td><img src="..#{image}collect.png" class="img-responsive" /></td>'
+ '<td><input type="hidden" name="cids" value="#{cid}" />#{title}</td>'
+ '<td>¥<span>#{realPrice}</span></td>'
+ '<td>#{num}</td>'
+ '<td>¥<span>#{totalPrice}</span></td>'
+ '</tr>';

tr = tr.replace(/#{cid}/g, list[i].cid);
tr = tr.replace(/#{image}/g, list[i].image);
tr = tr.replace(/#{title}/g, list[i].title);
tr = tr.replace(/#{realPrice}/g, list[i].realPrice);
tr = tr.replace(/#{num}/g, list[i].num);
tr = tr.replace(/#{totalPrice}/g, list[i].realPrice * list[i].num);

$("#cart-list").append(tr);

allCount += list[i].num;
allPrice += list[i].realPrice * list[i].num;
}
$("#all-count").html(allCount);
$("#all-price").html(allPrice);
}
});
}
</script>

4.完成后启动项目,先登录再访问http://localhost:8080/web/cart.html页面,勾选商品再点击“结算”按钮进行测试。

4.2 显示选择收货地址-前端页面

1.在orderConfirm.html页面中的body标签内的srcipt标签中添加获取收货地址列表方法的定义。

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
function showAddressList() {
$("#address-list").empty();
$.ajax({
url: "/addresses",
type: "GET",
dataType: "JSON",
success: function(json) {
let list = json.data;
console.log("count=" + list.length);
for (let i = 0; i < list.length; i++) {
console.log(list[i].name);
let opt = '<option value="#{aid}">#{name} | #{tag} | #{province}#{city}#{area}#{address} | #{phone}</option>';

opt = opt.replace(/#{aid}/g, list[i].aid);
opt = opt.replace(/#{tag}/g, list[i].tag);
opt = opt.replace("#{name}", list[i].name);
opt = opt.replace("#{province}", list[i].provinceName);
opt = opt.replace("#{city}", list[i].cityName);
opt = opt.replace("#{area}", list[i].areaName);
opt = opt.replace("#{address}", list[i].address);
opt = opt.replace("#{phone}", list[i].phone);

$("#address-list").append(opt);
}
}
});
}

2.在orderConfirm.html页面中的body标签内的srcipt标签中添加展示收货地址列表方法的调用。

1
2
3
4
5
6
<script type="text/javascript">
$(document).ready(function() {
showAddressList();
showCartList();
});
</script>

3.完成后启动项目,先登录再访问http://localhost:8080/web/orderConfirm.html页面进行测试。

创建订单

1 订单-创建数据表

1.使用use命令先选中store数据库。

1
USE store;

2.在store数据库中创建t_order和t_order_item数据表。

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
CREATE TABLE t_order (
oid INT AUTO_INCREMENT COMMENT '订单id',
uid INT NOT NULL COMMENT '用户id',
recv_name VARCHAR(20) NOT NULL COMMENT '收货人姓名',
recv_phone VARCHAR(20) COMMENT '收货人电话',
recv_province VARCHAR(15) COMMENT '收货人所在省',
recv_city VARCHAR(15) COMMENT '收货人所在市',
recv_area VARCHAR(15) COMMENT '收货人所在区',
recv_address VARCHAR(50) COMMENT '收货详细地址',
total_price BIGINT COMMENT '总价',
status INT COMMENT '状态:0-未支付,1-已支付,2-已取消,3-已关闭,4-已完成',
order_time DATETIME COMMENT '下单时间',
pay_time DATETIME COMMENT '支付时间',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '修改人',
modified_time DATETIME COMMENT '修改时间',
PRIMARY KEY (oid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE t_order_item (
id INT AUTO_INCREMENT COMMENT '订单中的商品记录的id',
oid INT NOT NULL COMMENT '所归属的订单的id',
pid INT NOT NULL COMMENT '商品的id',
title VARCHAR(100) NOT NULL COMMENT '商品标题',
image VARCHAR(500) COMMENT '商品图片',
price BIGINT COMMENT '商品价格',
num INT COMMENT '购买数量',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '修改人',
modified_time DATETIME COMMENT '修改时间',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2 订单-创建实体类

1.在com.amh.entity包下创建Order实体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** 订单数据的实体类 */
public class Order extends BaseEntity implements Serializable {
private Integer oid;
private Integer uid;
private String recvName;
private String recvPhone;
private String recvProvince;
private String recvCity;
private String recvArea;
private String recvAddress;
private Long totalPrice;
private Integer status;
private Date orderTime;
private Date payTime;

// Generate: Getter and Setter、Generate hashCode() and equals()、toString()
}

2.在com.amh.entity包下创建OrderItem实体类。

1
2
3
4
5
6
7
8
9
10
11
12
/** 订单中的商品数据 */
public class OrderItem extends BaseEntity implements Serializable {
private Integer id;
private Integer oid;
private Integer pid;
private String title;
private String image;
private Long price;
private Integer num;

// Generate: Getter and Setter、Generate hashCode() and equals()、toString()
}

3 订单-持久层

3.1 规划需要执行的SQL语句

1.插入订单数据的SQL语句大致是。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
INSERT INTO t_order (
uid,
recv_name,
recv_phone,
recv_province,
recv_city,
recv_area,
recv_address,
total_price,
status,
order_time,
pay_time,
created_user,
created_time,
modified_user,
modified_time
)
VALUES (
#对应字段的值列表
)

2.插入订单商品数据的SQL语句大致是。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
INSERT INTO t_order_item ( 
oid,
pid,
title,
image,
price,
num,
created_user,
created_time,
modified_user,
modified_time
)
VALUES (
#对应字段的值列表
)

3.2 接口与抽象方法

在com.amh.mapper包下创建OrderMapper接口并在接口中添加抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** 处理订单及订单商品数据的持久层接口 */
public interface OrderMapper {
/**
* 插入订单数据
* @param order 订单数据
* @return 受影响的行数
*/
Integer insertOrder(Order order);

/**
* 插入订单商品数据
* @param orderItem 订单商品数据
* @return 受影响的行数
*/
Integer insertOrderItem(OrderItem orderItem);
}

3.3 配置SQL映射

1.在main\resources\mapper文件夹下创建OrderMapper.xml文件,并添加抽象方法的映射。

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
<?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.OrderMapper">
<!-- 插入订单数据:Integer insertOrder(Order order) -->
<insert id="insertOrder" useGeneratedKeys="true" keyProperty="oid">
INSERT INTO t_order (
uid, recv_name, recv_phone, recv_province, recv_city, recv_area, recv_address,
total_price,status, order_time, pay_time, created_user, created_time, modified_user,
modified_time
) VALUES (
#{uid}, #{recvName}, #{recvPhone}, #{recvProvince}, #{recvCity}, #{recvArea},
#{recvAddress}, #{totalPrice}, #{status}, #{orderTime}, #{payTime}, #{createdUser},
#{createdTime}, #{modifiedUser}, #{modifiedTime}
)
</insert>

<!-- 插入订单商品数据:Integer insertOrderItem(OrderItem orderItem) -->
<insert id="insertOrderItem" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_order_item (
oid, pid, title, image, price, num, created_user,
created_time, modified_user, modified_time
) VALUES (
#{oid}, #{pid}, #{title}, #{image}, #{price}, #{num}, #{createdUser},
#{createdTime}, #{modifiedUser}, #{modifiedTime}
)
</insert>
</mapper>

2.在com.amh.mapper包下创建OrderMapperTests测试类,并添加测试方法。

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
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderMapperTests {
@Autowired
private OrderMapper orderMapper;

@Test
public void insertOrder() {
Order order = new Order();
order.setUid(31);
order.setRecvName("小王");
Integer rows = orderMapper.insertOrder(order);
System.out.println("rows=" + rows);
}

@Test
public void insertOrderItem() {
OrderItem orderItem = new OrderItem();
orderItem.setOid(1);
orderItem.setPid(2);
orderItem.setTitle("高档铅笔");
Integer rows = orderMapper.insertOrderItem(orderItem);
System.out.println("rows=" + rows);
}
}

4 订单-业务层

4.1 规划异常

说明:无异常。

4.2 接口与抽象方法

1.由于处理过程中还需要涉及收货地址数据的处理,所以需要先在IAddressService接口中添加getByAid()方法。

1
2
3
4
5
6
7
/**
* 根据收货地址数据的id,查询收货地址详情
* @param aid 收货地址id
* @param uid 归属的用户id
* @return 匹配的收货地址详情
*/
Address getByAid(Integer aid, Integer uid);

2.在AddressServiceImpl类中实现接口中的getByAid()抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public Address getByAid(Integer aid, Integer uid) {
// 根据收货地址数据id,查询收货地址详情
Address address = addressMapper.findByAid(aid);

if (address == null) {
throw new AddressNotFoundException("尝试访问的收货地址数据不存在");
}
if (!address.getUid().equals(uid)) {
throw new AccessDeniedException("非法访问");
}
address.setProvinceCode(null);
address.setCityCode(null);
address.setAreaCode(null);
address.setCreatedUser(null);
address.setCreatedTime(null);
address.setModifiedUser(null);
address.setModifiedTime(null);
return address;
}

3.在com.amh.service包下创建IOrderService业务层接口并添加抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
/** 处理订单和订单数据的业务层接口 */
public interface IOrderService {
/**
* 创建订单
* @param aid 收货地址的id
* @param cids 即将购买的商品数据在购物车表中的id
* @param uid 当前登录的用户的id
* @param username 当前登录的用户名
* @return 成功创建的订单数据
*/
Order create(Integer aid, Integer[] cids, Integer uid, String username);
}

4.3 实现抽象方法

1.在com.amh.service.impl包下创建OrderServiceImpl业务层实现类并实现IOrderService接口;在类定义之前添加@Service注解,在类中添加OrderMapper订单持久层对象、IAddressService处理收货地址对象、ICartService购物车数据对象,并都添加@Autowired注解进行修饰。

1
2
3
4
5
6
7
8
9
10
11
12
/** 处理订单和订单数据的业务层实现类 */
@Service
public class OrderServiceImpl implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private IAddressService addressService;
@Autowired
private ICartService cartService;

// ...
}

2.在OrderServiceImpl类中重写父接口中的create()抽象方法。

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
@Transactional
@Override
public Order create(Integer aid, Integer[] cids, Integer uid, String username) {
// 创建当前时间对象

// 根据cids查询所勾选的购物车列表中的数据

// 计算这些商品的总价

// 创建订单数据对象
// 补全数据:uid
// 查询收货地址数据
// 补全数据:收货地址相关的6项
// 补全数据:totalPrice
// 补全数据:status
// 补全数据:下单时间
// 补全数据:日志
// 插入订单数据

// 遍历carts,循环插入订单商品数据
// 创建订单商品数据
// 补全数据:oid(order.getOid())
// 补全数据:pid, title, image, price, num
// 补全数据:4项日志
// 插入订单商品数据

// 返回
}

3.OrderServiceImpl类中的create()方法具体逻辑代码实现见下。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@Transactional
@Override
public Order create(Integer aid, Integer[] cids, Integer uid, String username) {
// 创建当前时间对象
Date now = new Date();

// 根据cids查询所勾选的购物车列表中的数据
List<CartVO> carts = cartService.getVOByCids(uid, cids);

// 计算这些商品的总价
long totalPrice = 0;
for (CartVO cart : carts) {
totalPrice += cart.getRealPrice() * cart.getNum();
}

// 创建订单数据对象
Order order = new Order();
// 补全数据:uid
order.setUid(uid);
// 查询收货地址数据
Address address = addressService.getByAid(aid, uid);
// 补全数据:收货地址相关的6项
order.setRecvName(address.getName());
order.setRecvPhone(address.getPhone());
order.setRecvProvince(address.getProvinceName());
order.setRecvCity(address.getCityName());
order.setRecvArea(address.getAreaName());
order.setRecvAddress(address.getAddress());
// 补全数据:totalPrice
order.setTotalPrice(totalPrice);
// 补全数据:status
order.setStatus(0);
// 补全数据:下单时间
order.setOrderTime(now);
// 补全数据:日志
order.setCreatedUser(username);
order.setCreatedTime(now);
order.setModifiedUser(username);
order.setModifiedTime(now);
// 插入订单数据
Integer rows1 = orderMapper.insertOrder(order);
if (rows1 != 1) {
throw new InsertException("插入订单数据时出现未知错误,请联系系统管理员");
}

// 遍历carts,循环插入订单商品数据
for (CartVO cart : carts) {
// 创建订单商品数据
OrderItem item = new OrderItem();
// 补全数据:setOid(order.getOid())
item.setOid(order.getOid());
// 补全数据:pid, title, image, price, num
item.setPid(cart.getPid());
item.setTitle(cart.getTitle());
item.setImage(cart.getImage());
item.setPrice(cart.getRealPrice());
item.setNum(cart.getNum());
// 补全数据:4项日志
item.setCreatedUser(username);
item.setCreatedTime(now);
item.setModifiedUser(username);
item.setModifiedTime(now);
// 插入订单商品数据
Integer rows2 = orderMapper.insertOrderItem(item);
if (rows2 != 1) {
throw new InsertException("插入订单商品数据时出现未知错误,请联系系统管理员");
}
}

// 返回
return order;
}

4.在com.cy.store.service测试包下创建OrderServiceTests测试类,并添加create()方法进行功能测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderServiceTests {
@Autowired
private IOrderService orderService;

@Test
public void create() {
try {
Integer aid = 21;
Integer[] cids = {4, 5, 6,7};
Integer uid = 31;
String username = "订单管理员";
Order order = orderService.create(aid, cids, uid, username);
System.out.println(order);
} catch (ServiceException e) {
System.out.println(e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
}
}

5 订单-控制器层

5.1 处理异常

说明:无异常。

5.2 设计请求

设计用户提交的请求,并设计响应的方式。

请求路径:/orders/create
请求参数:Integer aid, Integer[] cids, HttpSession session
请求类型:POST
响应结果:JsonResult<Order>

5.3 处理请求

1.在com.amh.controller包下创建OrderController类,并继承自BaseController类;并在类前添加@RequestMapping(“orders”)注解和@RestController注解;在类中声明IOrderService业务对象,然后添加@Autowired注解修饰;最后在类中添加处理请求的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("orders")
public class OrderController extends BaseController {
@Autowired
private IOrderService orderService;

@RequestMapping("create")
public JsonResult<Order> create(Integer aid, Integer[] cids, HttpSession session) {
// 从Session中取出uid和username
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
// 调用业务对象执行业务
Order data = orderService.create(aid, cids, uid, username);
// 返回成功与数据
return new JsonResult<Order>(OK, data);
}
}

2.完成后启动项目,先登录再访问http://localhost:8080/orders/create?aid=21&cids=4&cids=5&cids=6&cids=7进行测试。

6 订单-前端页面

1.在orderConfirm.xml页面中的body标签内的script标签内添加“在线支付”按钮的点击时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$("#btn-create-order").click(function() {
$.ajax({
url: "/orders/create",
data: $("#form-create-order").serialize(),
type: "POST",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
alert("创建订单成功!");
console.log(json.data);
} else {
alert("创建订单失败!" + json.message);
}
},
error: function(xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html";
}
});
});

2.完成后启动项目,先登录再访问http://localhost:8080/web/cart.html页面,勾选购车中的商品,再点击“结算”按钮,最后在订单确认页中点击“在线支付”按钮进行功能的测试。

AOP

1 Spring AOP

AOP:面向切面(Aspect)编程。AOP并不是Spring框架的特性,只是Spring很好的支持了AOP。

如果需要在处理每个业务时,都执行特定的代码,则可以假设在整个数据处理流程中存在某个切面,切面中可以定义某些方法,当处理流程执行到切面时,就会自动执行切面中的方法。最终实现的效果就是:只需要定义好切面方法,配置好切面的位置(连接点),在不需要修改原有数据处理流程的代码的基础之上,就可以使得若干个流程都执行相同的代码。

2 切面方法

1.切面方法的访问权限是public。

2.切面方法的返回值类型可以是void或Object,如果使用的注解是@Around时,必须使用Object作为返回值类型,并返回连接点方法的返回值;如果使用的注解是@Before或@After等其他注解时,则自行决定。

3.切面方法的名称可以自定义。

4.切面方法的参数列表中可以添加ProceedingJoinPoint接口类型的对象,该对象表示连接点,也可以理解调用切面所在位置对应的方法的对象,如果使用的注解是@Around时,必须添加该参数,反之则不是必须添加。

3 统计业务方法执行时长

1.在使用Spring AOP编程时,需要先在pom.xml文件中添加两个关于AOP的依赖aspectjweaver和aspectjtools。

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
</dependency>

2.在com.cy.store.aop包下创建TimerAspect切面类,在类之前添加@Aspect和@Component注解修饰。

1
2
3
4
5
6
7
8
9
10
11
package com.cy.store.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimerAspect {

}

3.在类中添加切面方法around(ProceedingJoinPoint pjp)。

1
2
3
4
5
6
7
8
9
10
11
12
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 记录起始时间
long start = System.currentTimeMillis();
// 执行连接点方法,即切面所在位置对应的方法。本项目中表示执行注册或执行登录等
Object result = pjp.proceed();
// 记录结束时间
long end = System.currentTimeMillis();
// 计算耗时
System.err.println("耗时:" + (end - start) + "ms.");
// 返回连接点方法的返回值
return result;
}

4.最后需要在方法之前添加@Around注解,以配置连接点,即哪些方法需要应用该切面。

1
@Around("execution(* com.cy.store.service.impl.*.*(..))")

5.启动项目,在前端浏览器访问任意一个功能模块进行功能的测试。

完结撒花~~~