B2C电商项目

首页多类别热门商品接口

外界发起请求先到的位置是商品服务,商品服务调用类别服务,类别服务查询到数据后返回商品服务,商品服务再进行数据库的查询,封装结果后返回给前端。前端传过来的是categoryName:[数组]数组里边装的是类别名称。类别名称到商品服务后要调用对应的类别服务。类别服务:/category/hots,数组就没办法路径传参了,用请求体传类别的集合,这个集合装的是类别的名称,通过类别集合到类别服务要进行数据库的查询,之后返回封装。之前的是返回封装的类别对象,但是封装的类别对象也会转成LinkedHashMap。我们希望返回R,但R的data中装类别的id,也就是类别名称对应的类别集合。到商品服务就:1、判断类别查询效果,2、根据类别的id进行商品的查询(只查询热门商品,并且7条),最后结果封装返回就可以。
开发步骤:
1、编写param(编写一个接集合的,上次的是单个值)
2、先完成类别的一套业务(根据类别的集合,怎么查到对应的值)
3、定义feign客户端(在原来的里边加方法就可以了),商品服务通过客户端调用类别服务
4、编写商品的一套业务
B2C电商项目_第1张图片
1、

package com.qqhru.param;

import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
 * 热门商品参数接收对象
 */
@Data
public class ProductHotParam {
    @NotEmpty//集合不能为空,集合是NotEmpty
    private List<String> categoryName;
}

2、

/**
     * 热门类别id查询
     * @return
     */
    @PostMapping("/hots")
    public R hots(@RequestBody @Validated ProductHotParam productHotParam, BindingResult result){
        if (result.hasErrors()){
            return R.fail("类别集合查询失败!");
        }
        return categoryService.hotsCategory(productHotParam);
    }


/**
     * 根据传入的热门类别名称集合,返回类别对应的id集合
     * @param productHotParam
     * @return
     */
    R hotsCategory(ProductHotParam productHotParam);

3、

/**
     * 根据传入的热门类别名称集合,返回类别对应的id集合
     * @param productHotParam
     * @return
     */
    @Override
    public R hotsCategory(ProductHotParam productHotParam) {
        //1、封装查询类别名称
        QueryWrapper<Category> categoryQueryWrapper = new QueryWrapper<>();
        //eq是单个值,in是集合
        categoryQueryWrapper.in("category_name",productHotParam.getCategoryName());//哪一列:category_name 值:productHotParam.getCategoryName()
        //如果不指定列,默认查询表中的和类别名称有关的所有列
        categoryQueryWrapper.select("category_id");//查询指定类别(查询指定列)

        //查询数据库
        List<Object> ids = categoryMapper.selectObjs(categoryQueryWrapper);//类别id的集合
        /*将类别集合封装返回*/
        R ok = R.ok("类别集合查询成功", ids);
        log.info("CategoryServiceImpl.hotsCategory业务结束,结果:{}",ok);
        return ok;
    }
/**
 * 类别的调用接口
 */
@FeignClient("category-service")//客户端调用的服务名称
public interface CategoryClient {
    //根据类别集合查询类别id的方法
    /*调用类别里边根据类别集合查询类别id的方法*/
     /*注意:写全路径,根据路径配上服务去注册中心找服务的对应地址,最终向服务发起请求*/
    @PostMapping("/category/hots")
    R hots(@RequestBody ProductPromoParam productPromoParam);
}
 @PostMapping("/hots")
    public R hots(@RequestBody @Validated ProductPromoParam productPromoParam,BindingResult result){
        if (result.hasErrors()){
            return R.fail("数据查询失败!");
        }
        return productService.hots(productPromoParam);
    }

 /**
     * 多类别热门商品查询,根据类别名称集合!最多查询7条
     * 1、调用类别服务
     * 2、类别集合id查询商品
     * 3、结果集封装即可
     * @param productPromoParam 类别名称集合
     * @return
     */
    R hots(ProductPromoParam productPromoParam);
  /**
     * 多类别热门商品查询,根据类别名称集合!最多查询7条
     * 1、调用类别服务
     * 2、类别集合id查询商品
     * 3、结果集封装即可
     * @param productPromoParam 类别名称集合
     * @return
     */
    @Override
    public R hots(ProductPromoParam productPromoParam) {
        R r = categoryClient.hots(productPromoParam);
        if (r.getCode().equals(R.FAIL_CODE)){
            log.info("ProductServiceImpl.hots业务结束,结果:{}",r.getMsg());
            return r;
        }
        //
        List<Object> ids = (List<Object>) r.getData();
        //进行商品数据查询
        QueryWrapper<Product> productQueryWrapper = new QueryWrapper<>();
        productQueryWrapper.in("category_id",ids);//商品表中的category_id等于ids
        productQueryWrapper.orderByDesc("product_sales");//热门商品要倒叙,根据商品价格倒叙
        //封装分页参数
        IPage<Product> page = new Page<>();

        page = productMapper.selectPage(page,productQueryWrapper);
        List<Product> records = page.getRecords();
        R ok = R.ok("多类别热门商品查询成功");
        return ok;
    }
商品类别信息接口
 @GetMapping("/list")
    public R list(){
        return categoryService.list();
    }

/**
     * 查询类别数据,进行返回
     * @return
     */
    R list();
  @GetMapping("/category/list")
    R list();

 @PostMapping("/category/list")
    public R clist(){
        return productService.clist();
    }

 /**
     * 查询类别商品集合
     * @return
     */
    R clist();


 /**
     * 查询类别商品集合
     * @return
     */
    @Override
    public R clist() {
        R r = categoryClient.list();//  R ok = R.ok("类别集合全部数据查询成功!", categoryList);
        log.info("ProductServiceImpl.clist业务结束,结果:{}",r);
        return r;
    }
/**
 * 类别实体类
 * 如果有天改了category,需要在ProductService修改获取category_id的key
 */
@Data
@TableName("category")
public class Category {
    @JsonProperty("category_id")//json格式化,显示在前端
    @TableId(type = IdType.AUTO)
    private Integer categoryId;
    @JsonProperty("category_name")
    private String categoryName;
}
指定类别商品和全部商品接口

1、定义接收参数的param
2、controller、service、mapper

1/*单类别(指定类别)商品展示*/
@Data
public class ProductIdsParam extends PageParam{
    //可以为空的集合,但是不能传空的值
    @NotNull
    private List<Integer> categoryID;

    private int currentPage = 1;
    private int pageSize = 15;//默认值
}
2@PostMapping("/bycategory")
    public R byCategory(@RequestBody @Validated ProductIdsParam productIdsParam,BindingResult result){
        if (result.hasErrors()){
            return R.fail("类别商品查询失败!");
        }
        return productService.byCategory(productIdsParam);
    }
3/**
     * 通用性的业务
     *  如果传入了类别型的id,根据id查询并且分页
     *  如果没有传入类别的id,查询全部!
     * @param productIdsParam
     * @return
     */
    R byCategory(ProductIdsParam productIdsParam);
4@Override
    public R byCategory(ProductIdsParam productIdsParam) {
        List<Integer> categoryID = productIdsParam.getCategoryID();
        /*封装参数*/
        QueryWrapper<Product> queryWrapper = new QueryWrapper<>();

        if (!categoryID.isEmpty()) {
            queryWrapper.in("category_id",categoryID);
        }
        /*分页*/
        IPage<Product> page =  new Page<>(productIdsParam.getCurrentPage(),productIdsParam.getPageSize());

        /*查询:在当页显示的商品。如果queryWrapper为空,就查询全部*/
        page = productMapper.selectPage(page,queryWrapper);

        /*结果集封装时候封装一个对应的total*/
        R ok = R.ok("查询成功", page.getRecords(), page.getTotal());
        log.info("ProductServiceImpl.byCategory业务结束,结果:{}",ok);
        return ok;
    }
5@PostMapping("/all")
    public R all(@RequestBody @Validated ProductIdsParam productIdsParam,BindingResult result){
        if (result.hasErrors()){
            return R.fail("类别商品查询失败!");
        }
        return productService.byCategory(productIdsParam);
    }
商品详情(单个商品)
/*商品id参数接收*/
import lombok.Data;
import javax.validation.constraints.NotNull;

@Data
public class ProductIdParam {
    @NotNull
    private Integer productID;
}


 @PostMapping("/detail")
    public R detail(@RequestBody @Validated ProductIdParam productIdParam, BindingResult result){
        if (result.hasErrors()){
            return R.fail("商品详情查询失败!");
        }
        return productService.detail(productIdParam.getProductID());
    }


/**
     * 根据商品id查询商品详情信息
     * @param productID
     * @return
     */
    R detail(Integer productID);

  @Override
    public R detail(Integer productID) {
        Product product = productMapper.selectById(productID);

        /*查到数据封装*/
        R ok = R.ok(product);
        log.info("ProductServiceImpl.detail业务结束,结果:{}",ok);
        return ok;
    }
图片详情接口

商品图片返回的是一个新的表,还要声明一个对应的实体类,实体类在声明的时候需不需要json格式化
B2C电商项目_第2张图片

/**
 * 商品图片实体类
 */
@TableName("product_picture")
public class Picture {
    @TableId(type = IdType.AUTO)
    private Integer id;

    @TableField("product_id")//数据库表中字段,不声明也没事,会自动改成驼峰式,显示声明效率会更高
    @JsonProperty("product_id")//前台测试结果中显示成的JSON格式
    private Integer productId;

    @TableField("product_picture")
    @JsonProperty("product_picture")
    private String productPicture;
    
    private String intro;
}

 @PostMapping("/pictures")
    public R pictures(@RequestBody @Validated ProductIdParam productIdParam, BindingResult result){
        if (result.hasErrors()){
            return R.fail("商品图片详情查询失败!");
        }
        return productService.pictures(productIdParam.getProductID());
    }


 /**
     * 查询商品对应的图片详情集合
     * @param productID
     * @return
     */
    R pictures(Integer productID);

//现在不能用productMapper了,因为返回结果已经指定是product_pictures表了
public interface PictureMapper extends BaseMapper<Picture> {
}


/**
     * 查询商品对应的图片详情集合
     * @param productID
     * @return
     */
    @Override
    public R pictures(Integer productID) {
        QueryWrapper<Picture> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("product_id",productID);
        List<Picture> pictureList = pictureMapper.selectList(queryWrapper);
        R ok = R.ok(pictureList);
        log.info("ProductServiceImpl.pictures业务结束,结果:{}",ok);
        return ok;
    }

缓存设计之缓存介绍和缓存配置引入

每一次点击(商品)都要进行mysql的数据库的查询,每一次查询都会消耗一定的性能,而缓存是把它存储到运行内存中,用更高效的数据库给缓存起来,最后在查询的时候就不用走mysql了,提升查询的性能和提高用户体验度。
B2C电商项目_第3张图片
缓存本身利用的是redis缓存,所以要连接配置redis的地址和激活缓存配置
如何把配置类应用到每个服务中?把配置类放到通用模块,哪个模块想要使用配置类,只需要声明配置类去继承它。配置文件也放到通用模块,只要下边任何服务需要,只需要激活它就可以了。

缓存配置类
/*缓存配置类,被其他子类继承*/
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.time.Duration;
/**
 * description: 缓存配置类 放置配置的方法,子模板继承和复用
 */
public class CacheConfiguration {
    
    //配置缓存manager
    @Bean
    @Primary  //同类型,多个bean,默认生效! 默认缓存时间1小时!  可以选择!
    public RedisCacheManager cacheManagerHour(RedisConnectionFactory redisConnectionFactory){

        RedisCacheConfiguration instanceConfig = instanceConfig(1 * 3600L);//缓存时间1小时

        //构建缓存对象
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(instanceConfig)
                .transactionAware()
                .build();
    }

    //缓存一天配置
    @Bean
    public RedisCacheManager cacheManagerDay(RedisConnectionFactory redisConnectionFactory){

        RedisCacheConfiguration instanceConfig = instanceConfig(24 * 3600L);//缓存时间1小时

        //构建缓存对象
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(instanceConfig)
                .transactionAware()
                .build();
    }


    /**
     * 实例化具体的缓存配置!
     *    设置缓存方式JSON
     *    设置缓存时间 单位秒
     * @param ttl
     * @return
     */
    private RedisCacheConfiguration instanceConfig(Long ttl){

        //设置jackson序列化工具
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer
                = new Jackson2JsonRedisSerializer<Object>(Object.class);

        //常见jackson的对象映射器,并设置一些基本属性
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.configure(MapperFeature.USE_ANNOTATIONS,false);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
        ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(ttl)) //设置缓存时间
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
    }
}
spring:
  cache:
    type: redis
  redis:
    host: 192.168.23.140
    port: 6379
    jedis: # 设置Redis连接池
      pool:
        max-wait: 2000ms
        min-idle: 2
        max-idle: 8
        max-active: 10
/*商品模块配置类:哪个想要哪个继承CacheConfiguration就可以了*/
@Configuration
public class ProductConfiguration extends CacheConfiguration {

}

激活缓存:
profiles:
active: cache
让缓存生效需要在启动类上加@EnableCaching//开启缓存,让缓存生效

缓存设计之商品服务缓存设计考量

怎么看一个接口适不适合缓存?要看接口的使用率以及接口的更新率。
商品搜索是不宜添加缓存的,因为每次搜索的关键字(key)是不一样的,很少两次搜索相同的数据。缓存时候有key和事件
单类别热门商品缓存时间不宜过长,因为它会受销售的影响,所以设置为1小时
B2C电商项目_第4张图片

B2C电商项目_第5张图片
B2C电商项目_第6张图片

缓存一般在业务层

收藏服务值收藏添加接口

什么时候创建param?只有param和实体类完全不符或者局部属性的时候就要声明param,但这次不要求数据库store_collect的collect表中时间和id不是前端传的,前端就穿两个东西,刚好和实体类的属性相等,所以不用创建param,所以直接使用实体类接值就可以了。第二收藏数据用mybatisplus进行插入,用BaseMapperinsert方法,insert方法里边要的是数据库对应的实体类,所以不创建param。

/*收藏实体类*/
@Data
@TableName("collect")
public class Collect implements Serializable {

    public static final Long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private Integer id;

    @JsonProperty("user_id")
    @TableField("user_id")//对应数据库的属性名
    private Integer userId;

    @TableId("product_id")
    @JsonProperty("product_id")
    private Integer productId;

    @JsonProperty("collect_time")
    @TableField("collect_time")
    private Long collectTime;
}


@RestController
@RequestMapping("/collect")
public class CollectionController {

    @Autowired
    private CollectService collectService;

    @PostMapping("/save")
    public R save(@RequestBody Collect collect){

        return collectService.save(collect);
    }
}


public interface CollectService {
    /**
     * 收藏添加的方法
     * @param collect
     * @return 001 004
     */
    R save(Collect collect);
}
/*收藏实现类*/
@Service
@Slf4j
public class CollectServiceImpl implements CollectService {

    @Autowired
    private CollectMapper collectMapper;

    /**
     * 收藏添加的方法
     * @param collect
     * @return 001 004
     */
    @Override
    public R save(Collect collect) {
        //1.先判断(查询)数据是否存在,
        QueryWrapper<Collect> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id",collect.getUserId());
        queryWrapper.eq("product_id",collect.getProductId());
        //查询有几条
        Long count = collectMapper.selectCount(queryWrapper);

        if (count > 0){//有
            //说明已经添加了,不能再添加
            return R.fail("该商品已经添加收藏,请到我的收藏查看");
        }

        //2.如果不存在,则添加,在添加之前补充时间值,因为前端不传时间
        collect.setCollectTime(System.currentTimeMillis());
        int rows = collectMapper.insert(collect);//影响行数

        log.info("CollectServiceImpl.save业务结束,结果:{}",rows);
        return R.ok("添加收藏成功");
    }
}

/*数据库接口*/
public interface CollectMapper extends BaseMapper<Collect> {
}
收藏展示接口

根据user_id去收藏表中去查出用户对应有哪些商品,结果返回的是商品的详情,还需要把当前用户对应的商品集合,最终完成信息的查询和返回。
1、在收藏服务中根据user_id去查询对应的product_id集合
2、调用商品服务,传入id集合,完成数据查询
3、结果返回即可
收藏服务要调用商品服务,要传入商品id集合。商品服务中根据传入的商品id集合进行数据库的查询,返回数据库信息封装的R

B2C电商项目_第7张图片

购物车添加接口

B2C电商项目_第8张图片
创建vo就是用来返回结果的,实体类需要实现Serializable接口。

购物车展示接口

ProductCollectParam可以复用,里边装的是product的id集合
B2C电商项目_第9张图片

  /**
     * mq序列化方式,选择json!
     * @return
     */
    @Bean
    public MessageConverter messageConverter(){

        return new Jackson2JsonMessageConverter();
    }

B2C电商项目_第10张图片

商品模块值之商品删除B2C电商项目_第11张图片
使用阿里云OSS

导入依赖

 

<dependency>
    <groupId>com.aliyun.ossgroupId>
    <artifactId>aliyun-sdk-ossartifactId>
    <version>3.14.1version>
dependency>

<dependency>
    <groupId>joda-timegroupId>
    <artifactId>joda-timeartifactId>
    <version>2.10.14version>
dependency>
<dependency>
    <groupId>commons-iogroupId>
    <artifactId>commons-ioartifactId>
    <version>2.4version>
dependency>
dependencies>

声明配置

#OSS配置
aliyun:
  oss:
    file:
      # 控制台 - oss - 点击对应桶 - 概览 - 地域节点
      endpoint: 你的地域节点
      keyid: 你的accesskey
      keysecret: 你的accesssecret
      bucketname: 你的桶名

导入工具类

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.ObjectMetadata;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.Date;

/**
 * projectName: b2c-cloud-store
 *
 * @author: hwf
 * description:
 */
@Component
@Data
@ConfigurationProperties(prefix = "aliyun.oss.file")
public class AliyunOSSUtils {

    //基本属性 读取配置文件
    private  String endPoint;
    private  String keyId;
    private  String keySecret;
    private  String bucketName;



    /**
     * byte数组格式上传文件并返回上传后的URL地址
     * @param objectName        完整文件名, 例如abc/efg/123.jpg
     * @param content           文件内容, byte数组格式
     * @param contentType       文件类型   image/png  image/jpeg
     * @param hours             过期时间   单位小时
     * @Author hwf
     */
    public  String uploadImage(String objectName,
                                     byte[] content,String contentType,int hours)  throws Exception {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endPoint, keyId, keySecret);
        // 创建上传文件的元信息,可以通过文件元信息设置HTTP header(设置了才能通过返回的链接直接访问)。
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(contentType);
        // 文件上传
        ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content), objectMetadata);
        // 设置URL过期时间为hours小时。
        Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000 * 300 * hours);
        //返回url地址
        String url = ossClient.generatePresignedUrl(bucketName, objectName, expiration).toString();
        //关闭OSSClient。
        ossClient.shutdown();
        return url;
    }

    /**
     * 下载文件到本地
     * @param objectName        完整文件名, 例如abc/efg/123.jpg
     * @param localFile         下载到本地文件目录
     * @Author hwf
     */
    public  void downFile(String objectName,
                                String localFile) throws Exception {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endPoint, keyId, keySecret);

        // 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖,不存在则新建。
        ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File(localFile));

        // 关闭OSSClient。
        ossClient.shutdown();
    }

    /**
     * 删除文件
     * @param objectName        完整文件名, 例如abc/efg/123.jpg
     * @Author hwf
     */
    public  void deleteFile(String objectName) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endPoint, keyId, keySecret);

        // 删除文件。如需删除文件夹,请将ObjectName设置为对应的文件夹名称。如果文件夹非空,则需要将文件夹下的所有object删除后才能删除该文件夹。
        ossClient.deleteObject(bucketName, objectName);

        // 关闭OSSClient。
        ossClient.shutdown();
    }
}

实战代码:

@PostMapping("upload")
public Object upload(MultipartFile img) throws Exception {

    String filename = img.getOriginalFilename();
    String contentType = img.getContentType();
    long millis = System.currentTimeMillis();

    filename = millis + filename; //防止重复

    String url = aliyunOSSUtils.uploadImage(filename, img.getBytes(), contentType, 1000);
    System.out.println("url = " + url);
    return R.ok("上传成功",url);

}

你可能感兴趣的:(java,数据库,开发语言,spring,boot)