diff --git a/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/UserProductSpuCollectionsService.java b/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/UserProductSpuCollectionsService.java
new file mode 100644
index 000000000..95693ea23
--- /dev/null
+++ b/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/UserProductSpuCollectionsService.java
@@ -0,0 +1,36 @@
+package cn.iocoder.mall.user.api;
+
+import cn.iocoder.mall.user.api.bo.UserProductSpuCollectionsBO;
+import cn.iocoder.mall.user.api.dto.UserProductSpuCollectionsAddDTO;
+import cn.iocoder.mall.user.api.dto.UserProductSpuCollectionsUpdateDTO;
+
+/**
+ * UserProductSpuCollectionsService
+ * @author xiaofeng
+ * @date 2019/07/01 20:27
+ * @version 1.0
+ */
+public interface UserProductSpuCollectionsService {
+
+ /**
+ * 添加商品收藏
+ * @return
+ */
+ int addUserSkuCollections(UserProductSpuCollectionsAddDTO userProductSpuCollectionsAddDTO);
+
+ /**
+ * 获取用户商品收藏
+ * @param userId 用户ID
+ * @param spuId 商品ID
+ * @return
+ */
+ UserProductSpuCollectionsBO getUserSpuCollectionsByUserIdAndSpuId(Integer userId, Integer spuId);
+
+ /**
+ * 取消商品收藏
+ * @param userProductSpuCollectionsUpdateDTO
+ * @return
+ */
+ int updateUserProductSpuCollections(UserProductSpuCollectionsUpdateDTO userProductSpuCollectionsUpdateDTO);
+
+}
diff --git a/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/bo/UserProductSpuCollectionsBO.java b/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/bo/UserProductSpuCollectionsBO.java
new file mode 100644
index 000000000..694cd73a0
--- /dev/null
+++ b/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/bo/UserProductSpuCollectionsBO.java
@@ -0,0 +1,64 @@
+package cn.iocoder.mall.user.api.bo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 用户_商品_收藏记录表
+ * @author xiaofeng
+ * @date 2019-07-01 20:23:30
+ */
+@Data
+@Accessors(chain = true)
+public class UserProductSpuCollectionsBO implements Serializable {
+
+ /**
+ * id自增长
+ */
+ private Integer id;
+
+ /**
+ * 用户id
+ */
+ private Integer userId;
+
+ /**
+ * 用户名称
+ */
+ private String nickname;
+
+ /**
+ * 商品id
+ */
+ private Integer spuId;
+
+ /**
+ * 商品名字
+ */
+ private String spuName;
+
+ /**
+ * 图片名字
+ */
+ private String spuImage;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ /**
+ * 删除状态
+ */
+ private Integer deleted;
+
+
+}
diff --git a/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/dto/UserProductSpuCollectionsAddDTO.java b/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/dto/UserProductSpuCollectionsAddDTO.java
new file mode 100644
index 000000000..e949c3d1b
--- /dev/null
+++ b/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/dto/UserProductSpuCollectionsAddDTO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.mall.user.api.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 添加商品收藏参数
+ * @author xiaofeng
+ * @date 2019/07/01 20:38
+ * @version 1.0
+ */
+@Data
+@Accessors(chain = true)
+public class UserProductSpuCollectionsAddDTO implements Serializable {
+
+ /**
+ * id自增长
+ */
+ private Integer id;
+
+ /**
+ * 用户id
+ */
+ private Integer userId;
+
+ /**
+ * 用户名称
+ */
+ private String nickname;
+
+ /**
+ * 商品id
+ */
+ private Integer spuId;
+
+ /**
+ * 商品名字
+ */
+ private String spuName;
+
+ /**
+ * 图片名字
+ */
+ private String spuImage;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ /**
+ * 删除状态
+ */
+ private Integer deleted;
+
+
+}
diff --git a/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/dto/UserProductSpuCollectionsUpdateDTO.java b/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/dto/UserProductSpuCollectionsUpdateDTO.java
new file mode 100644
index 000000000..7dfd501a3
--- /dev/null
+++ b/user/user-service-api/src/main/java/cn/iocoder/mall/user/api/dto/UserProductSpuCollectionsUpdateDTO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.mall.user.api.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 更新商品收藏参数
+ * @author xiaofeng
+ * @date 2019/07/01 20:38
+ * @version 1.0
+ */
+@Data
+@Accessors(chain = true)
+public class UserProductSpuCollectionsUpdateDTO implements Serializable {
+
+ /**
+ * id自增长
+ */
+ private Integer id;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ /**
+ * 删除状态
+ */
+ private Integer deleted;
+
+
+}
diff --git a/user/user-service-impl/pom.xml b/user/user-service-impl/pom.xml
index 7f0f6123e..c3c92503d 100644
--- a/user/user-service-impl/pom.xml
+++ b/user/user-service-impl/pom.xml
@@ -17,6 +17,12 @@
1.0-SNAPSHOT
+
+ cn.iocoder.mall
+ product-service-api
+ 1.0-SNAPSHOT
+
+
mysql
@@ -67,6 +73,12 @@
curator-framework
+
+
+ org.apache.rocketmq
+ rocketmq-spring-boot-starter
+
+
diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/convert/UserProductSpuCollectionsConvert.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/convert/UserProductSpuCollectionsConvert.java
new file mode 100644
index 000000000..fbbafbd89
--- /dev/null
+++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/convert/UserProductSpuCollectionsConvert.java
@@ -0,0 +1,66 @@
+package cn.iocoder.mall.user.biz.convert;
+
+import cn.iocoder.mall.product.api.message.ProductSpuCollectionMessage;
+import cn.iocoder.mall.user.api.bo.UserProductSpuCollectionsBO;
+import cn.iocoder.mall.user.api.dto.UserProductSpuCollectionsAddDTO;
+import cn.iocoder.mall.user.api.dto.UserProductSpuCollectionsUpdateDTO;
+import cn.iocoder.mall.user.biz.dataobject.UserProductSpuCollectionsDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mappings;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 用户_商品_收藏记录表
+ *
+ * @author xiaofeng
+ * @date 2019-07-01 20:23:30
+ */
+@Mapper
+public interface UserProductSpuCollectionsConvert {
+
+ UserProductSpuCollectionsConvert INSTANCE = Mappers.getMapper(UserProductSpuCollectionsConvert.class);
+
+ /**
+ * DTO convert DO
+ * @param userSkuCollectionsAddDTO
+ * @return
+ */
+ @Mappings({})
+ UserProductSpuCollectionsDO convert(UserProductSpuCollectionsAddDTO userSkuCollectionsAddDTO);
+
+ /**
+ * update DTO convert DO
+ * @param userProductSpuCollectionsUpdateDTO
+ * @return
+ */
+ @Mappings({})
+ UserProductSpuCollectionsDO convert(UserProductSpuCollectionsUpdateDTO userProductSpuCollectionsUpdateDTO);
+
+ /**
+ * DO Convert BO
+ * @param userSkuCollectionsDO
+ * @return
+ */
+ @Mappings({})
+ UserProductSpuCollectionsBO convert(UserProductSpuCollectionsDO userSkuCollectionsDO);
+
+ /**
+ * DO List convert BO LIST
+ * @param userSkuCollectionsDOS
+ * @return
+ */
+ @Mappings({})
+ List convert(List userSkuCollectionsDOS);
+
+ /**
+ * 消处数据转换
+ * @param productSpuCollectionMessage
+ * @return
+ */
+ @Mappings({})
+ UserProductSpuCollectionsAddDTO convert(ProductSpuCollectionMessage productSpuCollectionMessage);
+
+
+}
diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/dao/UserProductSpuCollectionsMapper.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/dao/UserProductSpuCollectionsMapper.java
new file mode 100644
index 000000000..c94a28d4a
--- /dev/null
+++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/dao/UserProductSpuCollectionsMapper.java
@@ -0,0 +1,28 @@
+package cn.iocoder.mall.user.biz.dao;
+
+import cn.iocoder.mall.user.biz.dataobject.UserProductSpuCollectionsDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * 用户_商品_收藏记录表
+ *
+ * @author xiaofeng
+ * @date 2019-07-01 20:23:30
+ */
+public interface UserProductSpuCollectionsMapper extends BaseMapper {
+
+ /**
+ * 根据用户id 和 spuId 查找用户商品收藏
+ * @param userId
+ * @param spuId
+ * @return
+ */
+ default UserProductSpuCollectionsDO getUserSpuCollectionsByUserIdAndSpuId(final Integer userId,
+ final Integer spuId) {
+ QueryWrapper query = new QueryWrapper()
+ .eq("user_id", userId).eq("spu_id", spuId);
+ return selectOne(query);
+ }
+
+}
diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/dataobject/UserProductSpuCollectionsDO.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/dataobject/UserProductSpuCollectionsDO.java
new file mode 100644
index 000000000..bda45a973
--- /dev/null
+++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/dataobject/UserProductSpuCollectionsDO.java
@@ -0,0 +1,69 @@
+package cn.iocoder.mall.user.biz.dataobject;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 用户_商品_收藏记录表
+ *
+ * @author xiaofeng
+ * @date 2019-07-01 20:23:30
+ */
+@TableName("user_spu_collections")
+@Data
+@Accessors(chain = true)
+public class UserProductSpuCollectionsDO implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * id自增长
+ */
+ private Integer id;
+
+ /**
+ * 用户id
+ */
+ private Integer userId;
+
+ /**
+ * 用户名称
+ */
+ private String nickname;
+
+ /**
+ * 商品id
+ */
+ private Integer spuId;
+
+ /**
+ * 商品名字
+ */
+ private String spuName;
+
+ /**
+ * 图片名字
+ */
+ private String spuImage;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ /**
+ * 删除状态
+ */
+ private Integer deleted;
+
+
+}
diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/mq/UserProductSpuCollectionsConsumer.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/mq/UserProductSpuCollectionsConsumer.java
new file mode 100644
index 000000000..967bae562
--- /dev/null
+++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/mq/UserProductSpuCollectionsConsumer.java
@@ -0,0 +1,124 @@
+package cn.iocoder.mall.user.biz.mq;
+
+import cn.iocoder.common.framework.constant.DeletedStatusEnum;
+import cn.iocoder.common.framework.util.ServiceExceptionUtil;
+import cn.iocoder.mall.product.api.message.ProductSpuCollectionMessage;
+import cn.iocoder.mall.user.api.UserProductSpuCollectionsService;
+import cn.iocoder.mall.user.api.UserService;
+import cn.iocoder.mall.user.api.bo.UserBO;
+import cn.iocoder.mall.user.api.bo.UserProductSpuCollectionsBO;
+import cn.iocoder.mall.user.api.constant.UserErrorCodeEnum;
+import cn.iocoder.mall.user.api.dto.UserProductSpuCollectionsAddDTO;
+import cn.iocoder.mall.user.api.dto.UserProductSpuCollectionsUpdateDTO;
+import cn.iocoder.mall.user.biz.convert.UserProductSpuCollectionsConvert;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.core.RocketMQListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+/**
+ * 商品收藏 消费者
+ * @author xiaofeng
+ * @date 2019/07/02 19:57
+ * @version 1.0
+ */
+@Service
+@RocketMQMessageListener(topic = ProductSpuCollectionMessage.TOPIC, consumerGroup = "product-spu-consumer-group-"
+ + ProductSpuCollectionMessage.TOPIC)
+public class UserProductSpuCollectionsConsumer implements RocketMQListener {
+
+ @Autowired
+ private UserProductSpuCollectionsService userProductSpuCollectionsService;
+
+ @Autowired
+ private UserService userService;
+
+ @Override
+ public void onMessage(ProductSpuCollectionMessage productSpuCollectionMessage) {
+ UserBO userBO = userService.getUser(productSpuCollectionMessage.getUserId());
+ if (userBO == null) {
+ throw ServiceExceptionUtil.exception(UserErrorCodeEnum.USER_NOT_EXISTS.getCode());
+ }
+ // 收藏
+ if (productSpuCollectionMessage.getHasCollectionType().equals(1)) {
+ this.saveUserProductSpuCollections(productSpuCollectionMessage, userBO.getNickname());
+ } else if (productSpuCollectionMessage.getHasCollectionType().equals(2)) {
+ // 取消收藏
+ this.deleteUserProductSpuCollections(productSpuCollectionMessage.getUserId(),
+ productSpuCollectionMessage.getSpuId());
+ }
+
+ }
+
+ /**
+ * 保存商品收藏
+ * @param productSpuCollectionMessage
+ * @param nickname
+ * @return
+ */
+ private int saveUserProductSpuCollections(final ProductSpuCollectionMessage productSpuCollectionMessage,
+ final String nickname) {
+ int result = 0;
+ UserProductSpuCollectionsBO userProductSpuCollectionsBO = this.userProductSpuCollectionsService
+ .getUserSpuCollectionsByUserIdAndSpuId(productSpuCollectionMessage.getUserId(),
+ productSpuCollectionMessage.getSpuId());
+ if (userProductSpuCollectionsBO == null) {
+ UserProductSpuCollectionsAddDTO userProductSpuCollectionsAddDTO = UserProductSpuCollectionsConvert.INSTANCE
+ .convert(productSpuCollectionMessage);
+ userProductSpuCollectionsAddDTO.setNickname(StringUtils.isEmpty(nickname) ? "" : nickname);
+ userProductSpuCollectionsAddDTO.setCreateTime(new Date());
+ userProductSpuCollectionsAddDTO.setDeleted(DeletedStatusEnum.DELETED_NO.getValue());
+ result = userProductSpuCollectionsService.addUserSkuCollections(userProductSpuCollectionsAddDTO);
+ } else {
+ // 存在重新收藏
+ if (userProductSpuCollectionsBO.getDeleted().equals(DeletedStatusEnum.DELETED_YES.getValue())) {
+ UserProductSpuCollectionsUpdateDTO userProductSpuCollectionsUpdateDTO = this
+ .setUserProductSpuCollectionsUpdateDTO(userProductSpuCollectionsBO.getId(),
+ DeletedStatusEnum.DELETED_NO);
+ result = this.userProductSpuCollectionsService
+ .updateUserProductSpuCollections(userProductSpuCollectionsUpdateDTO);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 取消收藏
+ * @param userId
+ * @param spuId
+ * @return
+ */
+ private int deleteUserProductSpuCollections(final Integer userId, final Integer spuId) {
+ UserProductSpuCollectionsBO userProductSpuCollectionsBO = this.userProductSpuCollectionsService
+ .getUserSpuCollectionsByUserIdAndSpuId(userId, spuId);
+ int result = 0;
+ if (userProductSpuCollectionsBO != null) {
+ // 未取消收藏的数据
+ if (userProductSpuCollectionsBO.getDeleted().equals(DeletedStatusEnum.DELETED_NO.getValue())) {
+ UserProductSpuCollectionsUpdateDTO userProductSpuCollectionsUpdateDTO = this
+ .setUserProductSpuCollectionsUpdateDTO(userProductSpuCollectionsBO.getId(),
+ DeletedStatusEnum.DELETED_YES);
+ result = this.userProductSpuCollectionsService
+ .updateUserProductSpuCollections(userProductSpuCollectionsUpdateDTO);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 设置更新值
+ * @param id
+ * @param deletedStatusEnum
+ * @return
+ */
+ private UserProductSpuCollectionsUpdateDTO setUserProductSpuCollectionsUpdateDTO(final Integer id,
+ final DeletedStatusEnum deletedStatusEnum) {
+ return new UserProductSpuCollectionsUpdateDTO().setId(id).setUpdateTime(new Date())
+ .setDeleted(deletedStatusEnum.getValue());
+ }
+
+
+}
diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/service/UserProductSpuCollectionsServiceImpl.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/service/UserProductSpuCollectionsServiceImpl.java
new file mode 100644
index 000000000..8722bea34
--- /dev/null
+++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/biz/service/UserProductSpuCollectionsServiceImpl.java
@@ -0,0 +1,48 @@
+package cn.iocoder.mall.user.biz.service;
+
+import cn.iocoder.mall.user.api.UserProductSpuCollectionsService;
+import cn.iocoder.mall.user.api.bo.UserProductSpuCollectionsBO;
+import cn.iocoder.mall.user.api.dto.UserProductSpuCollectionsAddDTO;
+import cn.iocoder.mall.user.api.dto.UserProductSpuCollectionsUpdateDTO;
+import cn.iocoder.mall.user.biz.convert.UserProductSpuCollectionsConvert;
+import cn.iocoder.mall.user.biz.dao.UserProductSpuCollectionsMapper;
+import cn.iocoder.mall.user.biz.dataobject.UserProductSpuCollectionsDO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * UserSkuCollectionsServiceImpl
+ * @author xiaofeng
+ * @date 2019/07/01 21:02
+ * @version 1.0
+ */
+@Service
+@org.apache.dubbo.config.annotation.Service(validation = "true", version = "${dubbo.provider.UserProductSpuCollectionsService.version}")
+public class UserProductSpuCollectionsServiceImpl implements UserProductSpuCollectionsService {
+
+
+ @Autowired
+ private UserProductSpuCollectionsMapper userProductSpuCollectionsMapper;
+
+
+ @Override
+ public int addUserSkuCollections(UserProductSpuCollectionsAddDTO userProductSpuCollectionsAddDTO) {
+ return userProductSpuCollectionsMapper
+ .insert(UserProductSpuCollectionsConvert.INSTANCE.convert(userProductSpuCollectionsAddDTO));
+ }
+
+ @Override
+ public UserProductSpuCollectionsBO getUserSpuCollectionsByUserIdAndSpuId(Integer userId, Integer spuId) {
+ UserProductSpuCollectionsDO userProductSpuCollectionsDO = userProductSpuCollectionsMapper
+ .getUserSpuCollectionsByUserIdAndSpuId(userId, spuId);
+ return UserProductSpuCollectionsConvert.INSTANCE.convert(userProductSpuCollectionsDO);
+ }
+
+ @Override
+ public int updateUserProductSpuCollections(UserProductSpuCollectionsUpdateDTO userProductSpuCollectionsUpdateDTO) {
+ return userProductSpuCollectionsMapper
+ .updateById(UserProductSpuCollectionsConvert.INSTANCE.convert(userProductSpuCollectionsUpdateDTO));
+ }
+
+
+}
diff --git a/user/user-service-impl/src/main/resources/config/application.yaml b/user/user-service-impl/src/main/resources/config/application.yaml
index 9d394cb66..aa93e91a6 100644
--- a/user/user-service-impl/src/main/resources/config/application.yaml
+++ b/user/user-service-impl/src/main/resources/config/application.yaml
@@ -39,6 +39,14 @@ dubbo:
version: 1.0.0
UserService:
version: 1.0.0
+ UserProductSpuCollectionsService:
+ version: 1.0.0
consumer:
OAuth2Service:
version: 1.0.0
+
+# rocketmq
+rocketmq:
+ name-server: 127.0.0.1:9876
+ producer:
+ group: user-producer-spu-collection-group