1 购物车模块 1.0 购物车的存储形式
1 购物车的存储形式--->cookie
1)无需登录,无需查询数据库,保存在浏览器端
2)优点:性能好,访问快,没有和数据库交互
3)缺点:换电脑数据会丢失的,
电脑被其他人登录,存在隐私安全问题
2 购物车的存储形式---->Session
1 用户登录后,购物车数据放入用户会话
2 优点:初期性能比较好,访问速度快
3 缺点:session基于内存,用户庞大的时候影响服务器性能
只存在于当前会话,不适合集群和分布式系统
3 购物车的存储形式 ----> 数据库
1 用户登录后,购物车数据存入数据库
2 优点:数据持久化,可以在任何时间和任何地点访问
3 确点:频繁读写数据库,造成数据库压力
4 购物车的存储形式 ----> redis
1 用户登录后,购物车数据存入redis缓存的
2 优点:数据持久化,可以在任何时间和任何地点访问
频繁读写只基于缓存,不会造成数据库压力
适合于集群和分布式系统,可扩展性强
1.1 商品数据添加到购物车中 ShopcatController.class
@Api(value = "购物车接口controller", tags = {"购物车接口相关的api"})
@RequestMapping("shopcart")
@RestController
public class ShopcatController {
@ApiOperation(value = "添加商品到购物车", notes = "添加商品到购物车", httpMethod = "POST")
@PostMapping("/add")
public IMOOCJSONResult add(
@RequestParam String userId,
@RequestBody ShopcartBO shopcartBO,
HttpServletRequest request,
HttpServletResponse response
) {
if (StringUtils.isBlank(userId)) {
return IMOOCJSONResult.errorMsg("");
}
System.out.println(shopcartBO);
return IMOOCJSONResult.ok();
}
ShopcartBO.class:需要保存到购物车中的数据
package com.imooc.pojo.bo;
public class ShopcartBO {
private String itemId;
private String itemImgUrl;
private String itemName;
private String specId;
private String specName;
private Integer buyCounts;
private String priceDiscount;
private String priceNormal;
public String getItemId() {
return itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public String getItemImgUrl() {
return itemImgUrl;
}
public void setItemImgUrl(String itemImgUrl) {
this.itemImgUrl = itemImgUrl;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public String getSpecId() {
return specId;
}
public void setSpecId(String specId) {
this.specId = specId;
}
public String getSpecName() {
return specName;
}
public void setSpecName(String specName) {
this.specName = specName;
}
public Integer getBuyCounts() {
return buyCounts;
}
public void setBuyCounts(Integer buyCounts) {
this.buyCounts = buyCounts;
}
public String getPriceDiscount() {
return priceDiscount;
}
public void setPriceDiscount(String priceDiscount) {
this.priceDiscount = priceDiscount;
}
public String getPriceNormal() {
return priceNormal;
}
public void setPriceNormal(String priceNormal) {
this.priceNormal = priceNormal;
}
@Override
public String toString() {
return "ShopcartVO{" +
"itemId='" + itemId + '\'' +
", itemImgUrl='" + itemImgUrl + '\'' +
", itemName='" + itemName + '\'' +
", specId='" + specId + '\'' +
", specName='" + specName + '\'' +
", buyCounts=" + buyCounts +
", priceDiscount='" + priceDiscount + '\'' +
", priceNormal='" + priceNormal + '\'' +
'}';
}
}
用户登录和注册模块中,需要整合分布式会话token以及购物车中的数据
@ApiOperation(value = "用户注册", notes = "用户注册", httpMethod = "POST")
@PostMapping("/regist")
public IMOOCJSONResult regist(@RequestBody UserBO userBO,
HttpServletRequest request,
HttpServletResponse response) {
String username = userBO.getUsername();
String password = userBO.getPassword();
String confirmPwd = userBO.getConfirmPassword();
if (StringUtils.isBlank(username) ||
StringUtils.isBlank(password) ||
StringUtils.isBlank(confirmPwd)) {
return IMOOCJSONResult.errorMsg("用户名或密码不能为空");
}
boolean isExist = userService.queryUsernameIsExist(username);
if (isExist) {
return IMOOCJSONResult.errorMsg("用户名已经存在");
}
if (password.length() < 6) {
return IMOOCJSONResult.errorMsg("密码长度不能少于6");
}
if (!password.equals(confirmPwd)) {
return IMOOCJSONResult.errorMsg("两次密码输入不一致");
}
Users userResult = userService.createUser(userBO);
userResult = setNullProperty(userResult);
CookieUtils.setCookie(request, response, "user",
JsonUtils.objectToJson(userResult), true);
return IMOOCJSONResult.ok();
}
@ApiOperation(value = "用户登录", notes = "用户登录", httpMethod = "POST")
@PostMapping("/login")
public IMOOCJSONResult login(@RequestBody UserBO userBO,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
String username = userBO.getUsername();
String password = userBO.getPassword();
if (StringUtils.isBlank(username) ||
StringUtils.isBlank(password)) {
return IMOOCJSONResult.errorMsg("用户名或密码不能为空");
}
Users userResult = userService.queryUserForLogin(username,
MD5Utils.getMD5Str(password));
if (userResult == null) {
return IMOOCJSONResult.errorMsg("用户名或密码不正确");
}
userResult = setNullProperty(userResult);
CookieUtils.setCookie(request, response, "user",
JsonUtils.objectToJson(userResult), true);
return IMOOCJSONResult.ok(userResult);
}
private Users setNullProperty(Users userResult) {
userResult.setPassword(null);
userResult.setMobile(null);
userResult.setEmail(null);
userResult.setCreatedTime(null);
userResult.setUpdatedTime(null);
userResult.setBirthday(null);
return userResult;
}
@ApiOperation(value = "用户退出登录", notes = "用户退出登录", httpMethod = "POST")
@PostMapping("/logout")
public IMOOCJSONResult logout(@RequestParam String userId,
HttpServletRequest request,
HttpServletResponse response) {
CookieUtils.deleteCookie(request, response, "user");
return IMOOCJSONResult.ok();
}
1.2 购物车中的数据在页面渲染 1.2.1 查询购物车中的商品数据SQL
select t_items.id as itemId,
t_items.item_name as itemName,
t_items_img.url as itemImgUrl,
t_items_spec.id as specId,
t_items_spec.`name` as specName,
t_items_spec.price_discount as priceDiscount,
t_items_spec.price_normal as priceNormal
from items_spec t_items_spec
left join items t_items
on t_items_spec.item_id = t_items.id
left join items_img t_items_img
on t_items.id = t_items_img.item_id
where is_main = 1
and t_items_spec.id in
("1", "3", "5");
1.2.2 代码实现 ItemsMapperCustom.class
public List<ShopcartVO> queryItemsBySpecIds(@Param("paramsList") List specIdsList);
ItemsMapperCustom.xml
<select id="queryItemsBySpecIds" parameterType="List" resultType="com.imooc.pojo.vo.ShopcartVO">
SELECT
t_items.id as itemId,
t_items.item_name as itemName,
t_items_img.url as itemImgUrl,
t_items_spec.id as specId,
t_items_spec.`name` as specName,
t_items_spec.price_discount as priceDiscount,
t_items_spec.price_normal as priceNormal
FROM
items_spec t_items_spec
LEFT JOIN
items t_items
ON
t_items.id = t_items_spec.item_id
LEFT JOIN
items_img t_items_img
on
t_items_img.item_id = t_items.id
WHERE
t_items_img.is_main = 1
AND
t_items_spec.id IN
<foreach collection="paramsList" index="index" item="specId" open="(" separator="," close=")">
#{specId}
</foreach>
</select>
ItemService.class
public List<ShopcartVO> queryItemsBySpecIds(String specIds);
ItemServiceImpl.class
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public List<ShopcartVO> queryItemsBySpecIds(String specIds) {
String ids[] = specIds.split(",");
List<String> specIdsList = new ArrayList<>();
Collections.addAll(specIdsList, ids);
return itemsMapperCustom.queryItemsBySpecIds(specIdsList);
}
ItemController.class
@ApiOperation(value = "根据商品规格ids查找最新的商品数据", notes = "根据商品规格ids查找最新的商品数据", httpMethod = "GET")
@GetMapping("/refresh")
public IMOOCJSONResult refresh(
@ApiParam(name = "itemSpecIds", value = "拼接的规格ids", required = true, example = "1001,1003,1005")
@RequestParam String itemSpecIds) {
if (StringUtils.isBlank(itemSpecIds)) {
return IMOOCJSONResult.ok();
}
List<ShopcartVO> list = itemService.queryItemsBySpecIds(itemSpecIds);
return IMOOCJSONResult.ok(list);
}
1.3 购物车中删除商品
@ApiOperation(value = "从购物车中删除商品", notes = "从购物车中删除商品", httpMethod = "POST")
@PostMapping("/del")
public IMOOCJSONResult del(
@RequestParam String userId,
@RequestParam String itemSpecId,
HttpServletRequest request,
HttpServletResponse response
) {
if (StringUtils.isBlank(userId) || StringUtils.isBlank(itemSpecId)) {
return IMOOCJSONResult.errorMsg("参数不能为空");
}
return IMOOCJSONResult.ok();
}
2 收获地址 2.0 收获地址数据表
CREATE TABLE `user_address` (
`id` varchar(64) NOT NULL COMMENT '地址主键id',
`user_id` varchar(64) NOT NULL COMMENT '关联用户id',
`receiver` varchar(32) NOT NULL COMMENT '收件人姓名',
`mobile` varchar(32) NOT NULL COMMENT '收件人手机号',
`province` varchar(32) NOT NULL COMMENT '省份',
`city` varchar(32) NOT NULL COMMENT '城市',
`district` varchar(32) NOT NULL COMMENT '区县',
`detail` varchar(128) NOT NULL COMMENT '详细地址',
`extand` varchar(128) DEFAULT NULL COMMENT '扩展字段',
`is_default` int(11) DEFAULT NULL COMMENT '是否默认地址', # 对应的值是0和1
`created_time` datetime NOT NULL COMMENT '创建时间',
`updated_time` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户地址表 ';
2.1 查询用户所有的收获地址列表 AddressService.class
public interface AddressService {
public List<UserAddress> queryAll(String userId);
}
AddressServiceImpl.class
@Service
public class AddressServiceImpl implements AddressService {
@Autowired
private UserAddressMapper userAddressMapper;
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public List<UserAddress> queryAll(String userId) {
UserAddress ua = new UserAddress();
ua.setUserId(userId);
return userAddressMapper.select(ua);
}
}
AddressController.class
@Autowired
private AddressService addressService;
@ApiOperation(value = "根据用户id查询收货地址列表", notes = "根据用户id查询收货地址列表", httpMethod = "POST")
@PostMapping("/list")
public IMOOCJSONResult list(
@RequestParam String userId) {
if (StringUtils.isBlank(userId)) {
return IMOOCJSONResult.errorMsg("");
}
List<UserAddress> list = addressService.queryAll(userId);
return IMOOCJSONResult.ok(list);
}
2.2 新增收获地址 AddressService.class
public void addNewUserAddress(AddressBO addressBO);
AddressServiceImpl.class
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void addNewUserAddress(AddressBO addressBO) {
Integer isDefault = 0;
List<UserAddress> addressList = this.queryAll(addressBO.getUserId());
if (addressList == null || addressList.isEmpty() || addressList.size() == 0) {
isDefault = 1;
}
String addressId = sid.nextShort();
UserAddress newAddress = new UserAddress();
BeanUtils.copyProperties(addressBO, newAddress);
newAddress.setId(addressId);
newAddress.setIsDefault(isDefault);
newAddress.setCreatedTime(new Date());
newAddress.setUpdatedTime(new Date());
userAddressMapper.insert(newAddress);
}
AddressBO.class:前端传来的新增地址信息的BO
package com.imooc.pojo.bo;
public class AddressBO {
private String addressId;
private String userId;
private String receiver;
private String mobile;
private String province;
private String city;
private String district;
private String detail;
public String getAddressId() {
return addressId;
}
public void setAddressId(String addressId) {
this.addressId = addressId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getDistrict() {
return district;
}
public void setDistrict(String district) {
this.district = district;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
}
AddressController.class
@ApiOperation(value = "用户新增地址", notes = "用户新增地址", httpMethod = "POST")
@PostMapping("/add")
public IMOOCJSONResult add(@RequestBody AddressBO addressBO) {
IMOOCJSONResult checkRes = checkAddress(addressBO);
if (checkRes.getStatus() != 200) {
return checkRes;
}
addressService.addNewUserAddress(addressBO);
return IMOOCJSONResult.ok();
}
private IMOOCJSONResult checkAddress(AddressBO addressBO) {
String receiver = addressBO.getReceiver();
if (StringUtils.isBlank(receiver)) {
return IMOOCJSONResult.errorMsg("收货人不能为空");
}
if (receiver.length() > 12) {
return IMOOCJSONResult.errorMsg("收货人姓名不能太长");
}
String mobile = addressBO.getMobile();
if (StringUtils.isBlank(mobile)) {
return IMOOCJSONResult.errorMsg("收货人手机号不能为空");
}
if (mobile.length() != 11) {
return IMOOCJSONResult.errorMsg("收货人手机号长度不正确");
}
boolean isMobileOk = MobileEmailUtils.checkMobileIsOk(mobile);
if (!isMobileOk) {
return IMOOCJSONResult.errorMsg("收货人手机号格式不正确");
}
String province = addressBO.getProvince();
String city = addressBO.getCity();
String district = addressBO.getDistrict();
String detail = addressBO.getDetail();
if (StringUtils.isBlank(province) ||
StringUtils.isBlank(city) ||
StringUtils.isBlank(district) ||
StringUtils.isBlank(detail)) {
return IMOOCJSONResult.errorMsg("收货地址信息不能为空");
}
return IMOOCJSONResult.ok();
}
2.3 修改收获地址 AddressService.class
public void updateUserAddress(AddressBO addressBO);
AddressServiceImpl.class
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void updateUserAddress(AddressBO addressBO) {
String addressId = addressBO.getAddressId();
UserAddress pendingAddress = new UserAddress();
BeanUtils.copyProperties(addressBO, pendingAddress);
pendingAddress.setId(addressId);
pendingAddress.setUpdatedTime(new Date());
userAddressMapper.updateByPrimaryKeySelective(pendingAddress);
}
AddressController.class
private IMOOCJSONResult checkAddress(AddressBO addressBO) {
String receiver = addressBO.getReceiver();
if (StringUtils.isBlank(receiver)) {
return IMOOCJSONResult.errorMsg("收货人不能为空");
}
if (receiver.length() > 12) {
return IMOOCJSONResult.errorMsg("收货人姓名不能太长");
}
String mobile = addressBO.getMobile();
if (StringUtils.isBlank(mobile)) {
return IMOOCJSONResult.errorMsg("收货人手机号不能为空");
}
if (mobile.length() != 11) {
return IMOOCJSONResult.errorMsg("收货人手机号长度不正确");
}
boolean isMobileOk = MobileEmailUtils.checkMobileIsOk(mobile);
if (!isMobileOk) {
return IMOOCJSONResult.errorMsg("收货人手机号格式不正确");
}
String province = addressBO.getProvince();
String city = addressBO.getCity();
String district = addressBO.getDistrict();
String detail = addressBO.getDetail();
if (StringUtils.isBlank(province) ||
StringUtils.isBlank(city) ||
StringUtils.isBlank(district) ||
StringUtils.isBlank(detail)) {
return IMOOCJSONResult.errorMsg("收货地址信息不能为空");
}
return IMOOCJSONResult.ok();
}
@ApiOperation(value = "用户修改地址", notes = "用户修改地址", httpMethod = "POST")
@PostMapping("/update")
public IMOOCJSONResult update(@RequestBody AddressBO addressBO) {
if (StringUtils.isBlank(addressBO.getAddressId())) {
return IMOOCJSONResult.errorMsg("修改地址错误:addressId不能为空");
}
IMOOCJSONResult checkRes = checkAddress(addressBO);
if (checkRes.getStatus() != 200) {
return checkRes;
}
addressService.updateUserAddress(addressBO);
return IMOOCJSONResult.ok();
}
2.4 用户删除收获地址 AddressService.class
public void deleteUserAddress(String userId, String addressId);
AddressServiceImpl.class
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void deleteUserAddress(String userId, String addressId) {
UserAddress address = new UserAddress();
address.setId(addressId);
address.setUserId(userId);
userAddressMapper.delete(address);
}
AddressController.class
@ApiOperation(value = "用户删除地址", notes = "用户删除地址", httpMethod = "POST")
@PostMapping("/delete")
public IMOOCJSONResult delete(
@RequestParam String userId,
@RequestParam String addressId) {
if (StringUtils.isBlank(userId) || StringUtils.isBlank(addressId)) {
return IMOOCJSONResult.errorMsg("");
}
addressService.deleteUserAddress(userId, addressId);
return IMOOCJSONResult.ok();
}
2.5 设置为默认地址 AddressService.class
public void updateUserAddressToBeDefault(String userId, String addressId);
AddressServiceImpl.class
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void updateUserAddressToBeDefault(String userId, String addressId) {
UserAddress queryAddress = new UserAddress();
queryAddress.setUserId(userId);
queryAddress.setIsDefault(YesOrNo.YES.type);
List<UserAddress> list = userAddressMapper.select(queryAddress);
for (UserAddress ua : list) {
ua.setIsDefault(YesOrNo.NO.type);
userAddressMapper.updateByPrimaryKeySelective(ua);
}
UserAddress defaultAddress = new UserAddress();
defaultAddress.setId(addressId);
defaultAddress.setUserId(userId);
defaultAddress.setIsDefault(YesOrNo.YES.type);
userAddressMapper.updateByPrimaryKeySelective(defaultAddress);
}
AddressController.class
@ApiOperation(value = "用户设置默认地址", notes = "用户设置默认地址", httpMethod = "POST")
@PostMapping("/setDefalut")
public IMOOCJSONResult setDefalut(
@RequestParam String userId,
@RequestParam String addressId) {
if (StringUtils.isBlank(userId) || StringUtils.isBlank(addressId)) {
return IMOOCJSONResult.errorMsg("");
}
addressService.updateUserAddressToBeDefault(userId, addressId);
return IMOOCJSONResult.ok();
}
2.6 根据用户id和地址id查询收获地址 AddressService.class
public void deleteUserAddress(String userId, String addressId);
AddressServiceImpl.class
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void deleteUserAddress(String userId, String addressId) {
UserAddress address = new UserAddress();
address.setId(addressId);
address.setUserId(userId);
userAddressMapper.delete(address);
}
AddressController.class
@ApiOperation(value = "用户删除地址", notes = "用户删除地址", httpMethod = "POST")
@PostMapping("/delete")
public IMOOCJSONResult delete(
@RequestParam String userId,
@RequestParam String addressId) {
if (StringUtils.isBlank(userId) || StringUtils.isBlank(addressId)) {
return IMOOCJSONResult.errorMsg("");
}
addressService.deleteUserAddress(userId, addressId);
return IMOOCJSONResult.ok();
}
2.5 设置为默认地址 AddressService.class
public UserAddress queryUserAddres(String userId, String addressId);
AddressServiceImpl.class
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public UserAddress queryUserAddres(String userId, String addressId) {
UserAddress singleAddress = new UserAddress();
singleAddress.setId(addressId);
singleAddress.setUserId(userId);
return userAddressMapper.selectOne(singleAddress);
}
3 订单流程 3.1 订单相关的数据表设计 订单表
CREATE TABLE `orders` (
`id` varchar(64) NOT NULL COMMENT '订单主键;同时也是订单编号',
`user_id` varchar(64) NOT NULL COMMENT '用户id',
`receiver_name` varchar(32) NOT NULL COMMENT '收货人快照',
`receiver_mobile` varchar(32) NOT NULL COMMENT '收货人手机号快照',
`receiver_address` varchar(128) NOT NULL COMMENT '收货地址快照',
`total_amount` int(11) NOT NULL COMMENT '订单总价格',
`real_pay_amount` int(11) NOT NULL COMMENT '实际支付总价格',
`post_amount` int(11) NOT NULL COMMENT '邮费;默认可以为零,代表包邮',
`pay_method` int(11) NOT NULL COMMENT '支付方式',
`left_msg` varchar(128) DEFAULT NULL COMMENT '买家留言',
`extand` varchar(32) DEFAULT NULL COMMENT '扩展字段',
`is_comment` int(11) NOT NULL COMMENT '买家是否评价;1:已评价,0:未评价',
`is_delete` int(11) NOT NULL COMMENT '逻辑删除状态;1: 删除 0:未删除',
`created_time` datetime NOT NULL COMMENT '创建时间(成交时间)',
`updated_time` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表;';
订单商品关联表order_items
CREATE TABLE `order_items` (
`id` varchar(64) NOT NULL COMMENT '主键id',
`order_id` varchar(64) NOT NULL COMMENT '归属订单id',
`item_id` varchar(64) NOT NULL COMMENT '商品id',
`item_img` varchar(128) NOT NULL COMMENT '商品图片',
`item_name` varchar(32) NOT NULL COMMENT '商品名称',
`item_spec_id` varchar(32) NOT NULL COMMENT '规格id',
`item_spec_name` varchar(32) NOT NULL COMMENT '规格名称',
`price` int(11) NOT NULL COMMENT '成交价格',
`buy_counts` int(11) NOT NULL COMMENT '购买数量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单商品关联表 ';
订单状态表order_status
CREATE TABLE `order_status` (
`order_id` varchar(64) NOT NULL COMMENT '订单ID;对应订单表的主键id',
`order_status` int(11) NOT NULL COMMENT '订单状态',
`created_time` datetime DEFAULT NULL COMMENT '订单创建时间;对应[10:待付款]状态',
`pay_time` datetime DEFAULT NULL COMMENT '支付成功时间;对应[20:已付款,待发货]状态',
`deliver_time` datetime DEFAULT NULL COMMENT '发货时间;对应[30:已发货,待收货]状态',
`success_time` datetime DEFAULT NULL COMMENT '交易成功时间;对应[40:交易成功]状态',
`close_time` datetime DEFAULT NULL COMMENT '交易关闭时间;对应[50:交易关闭]状态',
`comment_time` datetime DEFAULT NULL COMMENT '留言时间;用户在交易成功后的留言时间',
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单状态表;订单的每个状态更改都需要进行记录\n10:待付款 20:已付款,待发货 30:已发货,待收货(7天自动确认) 40:交易成功(此时可以评价)50:交易关闭(待付款时,用户取消 或 长时间未付款,系统识别后自动关闭)\n退货/退货,此分支流程不做,所以不加入';
3.2 聚合支付中心 3.3 提交订单并且接受订单信息 SubmitOrderBO.class:提交订单时前端的数据
public class SubmitOrderBO {
private String userId;
private String itemSpecIds;
private String addressId;
private Integer payMethod;
private String leftMsg;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getItemSpecIds() {
return itemSpecIds;
}
public void setItemSpecIds(String itemSpecIds) {
this.itemSpecIds = itemSpecIds;
}
public String getAddressId() {
return addressId;
}
public void setAddressId(String addressId) {
this.addressId = addressId;
}
public Integer getPayMethod() {
return payMethod;
}
public void setPayMethod(Integer payMethod) {
this.payMethod = payMethod;
}
public String getLeftMsg() {
return leftMsg;
}
public void setLeftMsg(String leftMsg) {
this.leftMsg = leftMsg;
}
@Override
public String toString() {
return "SubmitOrderBO{" +
"userId='" + userId + '\'' +
", itemSpecIds='" + itemSpecIds + '\'' +
", addressId='" + addressId + '\'' +
", payMethod=" + payMethod +
", leftMsg='" + leftMsg + '\'' +
'}';
}
}
OrderService.class
public interface OrderService {
public OrderVO createOrder(SubmitOrderBO submitOrderBO);
}
OrderServiceImpl.class
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrdersMapper ordersMapper;
@Autowired
private OrderItemsMapper orderItemsMapper;
@Autowired
private OrderStatusMapper orderStatusMapper;
@Autowired
private AddressService addressService;
@Autowired
private ItemService itemService;
@Autowired
private Sid sid;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public String createOrder(SubmitOrderBO submitOrderBO) {
String userId = submitOrderBO.getUserId();
String addressId = submitOrderBO.getAddressId();
String itemSpecIds = submitOrderBO.getItemSpecIds();
Integer payMethod = submitOrderBO.getPayMethod();
String leftMsg = submitOrderBO.getLeftMsg();
Integer postAmount = 0;
String orderId = sid.nextShort();
UserAddress address = addressService.queryUserAddres(userId, addressId);
Orders newOrder = new Orders();
newOrder.setId(orderId);
newOrder.setUserId(userId);
newOrder.setReceiverName(address.getReceiver());
newOrder.setReceiverMobile(address.getMobile());
newOrder.setReceiverAddress(address.getProvince() + " "
+ address.getCity() + " "
+ address.getDistrict() + " "
+ address.getDetail());
newOrder.setPostAmount(postAmount);
newOrder.setPayMethod(payMethod);
newOrder.setLeftMsg(leftMsg);
newOrder.setIsComment(YesOrNo.NO.type);
newOrder.setIsDelete(YesOrNo.NO.type);
newOrder.setCreatedTime(new Date());
newOrder.setUpdatedTime(new Date());
String itemSpecIdArr[] = itemSpecIds.split(",");
Integer totalAmount = 0;
Integer realPayAmount = 0;
for (String itemSpecId : itemSpecIdArr) {
int buyCounts = 1;
ItemsSpec itemSpec = itemService.queryItemSpecById(itemSpecId);
totalAmount += itemSpec.getPriceNormal() * buyCounts;
realPayAmount += itemSpec.getPriceDiscount() * buyCounts;
String itemId = itemSpec.getItemId();
Items item = itemService.queryItemById(itemId);
String imgUrl = itemService.queryItemMainImgById(itemId);
String subOrderId = sid.nextShort();
OrderItems subOrderItem = new OrderItems();
subOrderItem.setId(subOrderId);
subOrderItem.setOrderId(orderId);
subOrderItem.setItemId(itemId);
subOrderItem.setItemName(item.getItemName());
subOrderItem.setItemImg(imgUrl);
subOrderItem.setBuyCounts(buyCounts);
subOrderItem.setItemSpecId(itemSpecId);
subOrderItem.setItemSpecName(itemSpec.getName());
subOrderItem.setPrice(itemSpec.getPriceDiscount());
orderItemsMapper.insert(subOrderItem);
itemService.decreaseItemSpecStock(itemSpecId, buyCounts);
}
newOrder.setTotalAmount(totalAmount);
newOrder.setRealPayAmount(realPayAmount);
ordersMapper.insert(newOrder);
OrderStatus waitPayOrderStatus = new OrderStatus();
waitPayOrderStatus.setOrderId(orderId);
waitPayOrderStatus.setOrderStatus(OrderStatusEnum.WAIT_PAY.type);
waitPayOrderStatus.setCreatedTime(new Date());
orderStatusMapper.insert(waitPayOrderStatus);
return orderId;
}
}
支付方式枚举PayMethod.class
public enum PayMethod {
WEIXIN(1, "微信"),
ALIPAY(2, "支付宝");
public final Integer type;
public final String value;
PayMethod(Integer type, String value){
this.type = type;
this.value = value;
}
}
订单状态的枚举
public enum OrderStatusEnum {
WAIT_PAY(10, "待付款"),
WAIT_DELIVER(20, "已付款,待发货"),
WAIT_RECEIVE(30, "已发货,待收货"),
SUCCESS(40, "交易成功"),
CLOSE(50, "交易关闭");
public final Integer type;
public final String value;
OrderStatusEnum(Integer type, String value){
this.type = type;
this.value = value;
}
}
OrdersController.class
@Api(value = "订单相关", tags = {"订单相关的api接口"})
@RequestMapping("orders")
@RestController
public class OrdersController extends BaseController {
final static Logger logger = LoggerFactory.getLogger(OrdersController.class);
@Autowired
private OrderService orderService;
public static final String FOODIE_SHOPCART = "shopcart";
@Autowired
private RestTemplate restTemplate;
@ApiOperation(value = "用户下单", notes = "用户下单", httpMethod = "POST")
@PostMapping("/create")
public IMOOCJSONResult create(
@RequestBody SubmitOrderBO submitOrderBO,
HttpServletRequest request,
HttpServletResponse response) {
if (submitOrderBO.getPayMethod() != PayMethod.WEIXIN.type
&& submitOrderBO.getPayMethod() != PayMethod.ALIPAY.type ) {
return IMOOCJSONResult.errorMsg("支付方式不支持!");
}
OrderVO orderVO = orderService.createOrder(submitOrderBO);
String orderId = orderVO.getOrderId();
return IMOOCJSONResult.ok(orderId);
}
}
3.4 根据商品id获取商品的主图 ItemService.class
public String queryItemMainImgById(String itemId);
ItemServiceImpl.class
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public String queryItemMainImgById(String itemId) {
ItemsImg itemsImg = new ItemsImg();
itemsImg.setItemId(itemId);
itemsImg.setIsMain(YesOrNo.YES.type);
ItemsImg result = itemsImgMapper.selectOne(itemsImg);
return result != null ? result.getUrl() : "";
}
3.5 根据商品规格id和购买数量扣除库存 ItemsMapperCustom.class
public int decreaseItemSpecStock(@Param("specId") String specId,
@Param("pendingCounts") int pendingCounts);
ItemsMapperCustom.xml
<update id="decreaseItemSpecStock">
update
items_spec
set
stock = stock - #{pendingCounts}
where
id = #{specId} # 规格id
and
stock >= #{pendingCounts} # 扣除的数量
</update>
ItemService.class
public void decreaseItemSpecStock(String specId, int buyCounts);
ItemServiceImpl.class
@Autowired
private ItemsMapperCustom itemsMapperCustom;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void decreaseItemSpecStock(String specId, int buyCounts) {
int result = itemsMapperCustom.decreaseItemSpecStock(specId, buyCounts);
if (result != 1) {
throw new RuntimeException("订单创建失败,原因:库存不足!");
}
}
4 支付模块 4.1 支付中心订单数据表设计
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` varchar(64) NOT NULL COMMENT '订单主键',
`merchant_order_id` varchar(64) NOT NULL COMMENT '商户订单号',
`merchant_user_id` varchar(64) NOT NULL COMMENT '商户方的发起用户的用户主键id',
`amount` int(11) NOT NULL COMMENT '实际支付总金额(包含商户所支付的订单费邮费总额)',
`pay_method` int(11) NOT NULL COMMENT '支付方式',
`pay_status` int(11) NOT NULL COMMENT '支付状态 10:未支付 20:已支付 30:支付失败 40:已退款',
`come_from` varchar(128) NOT NULL COMMENT '从哪一端来的,比如从天天吃货这门实战过来的',
`return_url` varchar(255) NOT NULL COMMENT '支付成功后的通知地址,这个是开发者那一段的,不是第三方支付通知的地址',
`is_delete` int(11) NOT NULL COMMENT '逻辑删除状态;1: 删除 0:未删除',
`created_time` datetime NOT NULL COMMENT '创建时间(成交时间)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表;';
SET FOREIGN_KEY_CHECKS = 1;
4.2 微信支付 4.2.1 微信支付时许图 4.2.2 构建商户端支付成功的回调接口 OrderService.class
public void updateOrderStatus(String orderId, Integer orderStatus);
OrderServiceImpl.class
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void updateOrderStatus(String orderId, Integer orderStatus) {
OrderStatus paidStatus = new OrderStatus();
paidStatus.setOrderId(orderId);
paidStatus.setOrderStatus(orderStatus);
paidStatus.setPayTime(new Date());
orderStatusMapper.updateByPrimaryKeySelective(paidStatus);
}
OrdersController.class
@PostMapping("notifyMerchantOrderPaid")
public Integer notifyMerchantOrderPaid(String merchantOrderId) {
orderService.updateOrderStatus(merchantOrderId, OrderStatusEnum.WAIT_DELIVER.type);
return HttpStatus.OK.value();
}
4.2.3 构建商户订单并传给支付中心 BaseController.class
String payReturnUrl = "http://api.z.mukewang.com/foodie-dev-api/orders/notifyMerchantOrderPaid";
OrderService.class
public OrderVO createOrder(SubmitOrderBO submitOrderBO);
MerchantOrdersVO.class
package com.imooc.pojo.vo;
public class MerchantOrdersVO {
private String merchantOrderId;
private String merchantUserId;
private Integer amount;
private Integer payMethod;
private String returnUrl;
public String getMerchantOrderId() {
return merchantOrderId;
}
public void setMerchantOrderId(String merchantOrderId) {
this.merchantOrderId = merchantOrderId;
}
public String getMerchantUserId() {
return merchantUserId;
}
public void setMerchantUserId(String merchantUserId) {
this.merchantUserId = merchantUserId;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public Integer getPayMethod() {
return payMethod;
}
public void setPayMethod(Integer payMethod) {
this.payMethod = payMethod;
}
public String getReturnUrl() {
return returnUrl;
}
public void setReturnUrl(String returnUrl) {
this.returnUrl = returnUrl;
}
}
OrderVO.class
public class OrderVO {
private String orderId;
private MerchantOrdersVO merchantOrdersVO;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public MerchantOrdersVO getMerchantOrdersVO() {
return merchantOrdersVO;
}
public void setMerchantOrdersVO(MerchantOrdersVO merchantOrdersVO) {
this.merchantOrdersVO = merchantOrdersVO;
}
}
OrderServiceImpl.class
@Transactional(propagation = Propagation.REQUIRED)
@Override
public OrderVO createOrder(SubmitOrderBO submitOrderBO) {
String userId = submitOrderBO.getUserId();
String addressId = submitOrderBO.getAddressId();
String itemSpecIds = submitOrderBO.getItemSpecIds();
Integer payMethod = submitOrderBO.getPayMethod();
String leftMsg = submitOrderBO.getLeftMsg();
Integer postAmount = 0;
String orderId = sid.nextShort();
UserAddress address = addressService.queryUserAddres(userId, addressId);
Orders newOrder = new Orders();
newOrder.setId(orderId);
newOrder.setUserId(userId);
newOrder.setReceiverName(address.getReceiver());
newOrder.setReceiverMobile(address.getMobile());
newOrder.setReceiverAddress(address.getProvince() + " "
+ address.getCity() + " "
+ address.getDistrict() + " "
+ address.getDetail());
newOrder.setPostAmount(postAmount);
newOrder.setPayMethod(payMethod);
newOrder.setLeftMsg(leftMsg);
newOrder.setIsComment(YesOrNo.NO.type);
newOrder.setIsDelete(YesOrNo.NO.type);
newOrder.setCreatedTime(new Date());
newOrder.setUpdatedTime(new Date());
String itemSpecIdArr[] = itemSpecIds.split(",");
Integer totalAmount = 0;
Integer realPayAmount = 0;
for (String itemSpecId : itemSpecIdArr) {
int buyCounts = 1;
ItemsSpec itemSpec = itemService.queryItemSpecById(itemSpecId);
totalAmount += itemSpec.getPriceNormal() * buyCounts;
realPayAmount += itemSpec.getPriceDiscount() * buyCounts;
String itemId = itemSpec.getItemId();
Items item = itemService.queryItemById(itemId);
String imgUrl = itemService.queryItemMainImgById(itemId);
String subOrderId = sid.nextShort();
OrderItems subOrderItem = new OrderItems();
subOrderItem.setId(subOrderId);
subOrderItem.setOrderId(orderId);
subOrderItem.setItemId(itemId);
subOrderItem.setItemName(item.getItemName());
subOrderItem.setItemImg(imgUrl);
subOrderItem.setBuyCounts(buyCounts);
subOrderItem.setItemSpecId(itemSpecId);
subOrderItem.setItemSpecName(itemSpec.getName());
subOrderItem.setPrice(itemSpec.getPriceDiscount());
orderItemsMapper.insert(subOrderItem);
itemService.decreaseItemSpecStock(itemSpecId, buyCounts);
}
newOrder.setTotalAmount(totalAmount);
newOrder.setRealPayAmount(realPayAmount);
ordersMapper.insert(newOrder);
OrderStatus waitPayOrderStatus = new OrderStatus();
waitPayOrderStatus.setOrderId(orderId);
waitPayOrderStatus.setOrderStatus(OrderStatusEnum.WAIT_PAY.type);
waitPayOrderStatus.setCreatedTime(new Date());
orderStatusMapper.insert(waitPayOrderStatus);
MerchantOrdersVO merchantOrdersVO = new MerchantOrdersVO();
merchantOrdersVO.setMerchantOrderId(orderId);
merchantOrdersVO.setMerchantUserId(userId);
merchantOrdersVO.setAmount(realPayAmount + postAmount);
merchantOrdersVO.setPayMethod(payMethod);
OrderVO orderVO = new OrderVO();
orderVO.setOrderId(orderId);
orderVO.setMerchantOrdersVO(merchantOrdersVO);
return orderVO;
}
使用RestTemplate向支付中心发送订单
@Autowired
private RestTemplate restTemplate;
@Configuration
public class WebMvcConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
OrdersController.class
@Autowired
private RestTemplate restTemplate;
@ApiOperation(value = "用户下单", notes = "用户下单", httpMethod = "POST")
@PostMapping("/create")
public IMOOCJSONResult create(
@RequestBody SubmitOrderBO submitOrderBO,
HttpServletRequest request,
HttpServletResponse response) {
if (submitOrderBO.getPayMethod() != PayMethod.WEIXIN.type
&& submitOrderBO.getPayMethod() != PayMethod.ALIPAY.type ) {
return IMOOCJSONResult.errorMsg("支付方式不支持!");
}
OrderVO orderVO = orderService.createOrder(submitOrderBO);
String orderId = orderVO.getOrderId();
MerchantOrdersVO merchantOrdersVO = orderVO.getMerchantOrdersVO();
merchantOrdersVO.setReturnUrl(payReturnUrl);
merchantOrdersVO.setAmount(1);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("imoocUserId","imooc");
headers.add("password","imooc");
HttpEntity<MerchantOrdersVO> entity =
new HttpEntity<>(merchantOrdersVO, headers);
ResponseEntity<IMOOCJSONResult> responseEntity =
restTemplate.postForEntity(paymentUrl,
entity,
IMOOCJSONResult.class);
IMOOCJSONResult paymentResult = responseEntity.getBody();
if (paymentResult.getStatus() != 200) {
logger.error("发送错误:{}", paymentResult.getMsg());
return IMOOCJSONResult.errorMsg("支付中心订单创建失败,请联系管理员!");
}
return IMOOCJSONResult.ok(orderId);
}
4.2.4 查询订单状态信息,用于前端判断订单是轮询查询支付结果 OrderService.class
public OrderStatus queryOrderStatusInfo(String orderId);
OrderServiceImpl.class
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public OrderStatus queryOrderStatusInfo(String orderId) {
return orderStatusMapper.selectByPrimaryKey(orderId);
}
OrdersCntroller.class
@PostMapping("getPaidOrderInfo")
public IMOOCJSONResult getPaidOrderInfo(String orderId) {
OrderStatus orderStatus = orderService.queryOrderStatusInfo(orderId);
return IMOOCJSONResult.ok(orderStatus);
}
4.3 支付宝支付 4.3.1 支付宝支付时序图 5 构建定时任务
用户一直不去支付的适合,需要修改订单超期状态为关闭
OrderService.class
public interface OrderService {
public void closeOrder();
}
OrderServiceImpl.class
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void closeOrder() {
OrderStatus queryOrder = new OrderStatus();
queryOrder.setOrderStatus(OrderStatusEnum.WAIT_PAY.type);
List<OrderStatus> list = orderStatusMapper.select(queryOrder);
for (OrderStatus os : list) {
Date createdTime = os.getCreatedTime();
int days = DateUtil.daysBetween(createdTime, new Date());
if (days >= 1) {
doCloseOrder(os.getOrderId());
}
}
}
@Transactional(propagation = Propagation.REQUIRED)
void doCloseOrder(String orderId) {
OrderStatus close = new OrderStatus();
close.setOrderId(orderId);
close.setOrderStatus(OrderStatusEnum.CLOSE.type);
close.setCloseTime(new Date());
orderStatusMapper.updateByPrimaryKeySelective(close);
}
定时任务配置类
@Component
public class OrderJob {
@Autowired
private OrderService orderService;
public void autoCloseOrder() {
orderService.closeOrder();
System.out.println("执行定时任务,当前时间为:"
+ DateUtil.getCurrentDateString(DateUtil.DATETIME_PATTERN));
}
}
|