Skip to content

项目2个人接口开发指导 ​

该文档用来指导大家开发个人接口

依赖 ​

整合web、整合mybatis、整合pagehelper、整合shiro

xml
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.1</version>
  </dependency>
  <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.1</version>
  </dependency>

  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    <version>5.1.47</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
  </dependency>
  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.7.1</version>
  </dependency>

</dependencies>

通用部分 &#8203;

跨域 &#8203;

跨域问题,需要在响应报文中增加一些响应头,这个是通用的部分,这时候我们使用CorsFilter来完成

java
@Configuration
public class CorsConfig {
  private CorsConfiguration buildConfig() {
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址
    corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
    corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
    return corsConfiguration;
  }

  @Bean
  public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", buildConfig()); // 4 对接口配置跨域设置

    return new CorsFilter(source);
  }
}
java
// 已经不使用了
@Override
public void addCorsMappings(CorsRegistry registry) {
  registry.addMapping("/**")
    .allowedHeaders("*")
    .allowedOrigins("*")
    .allowedMethods("*");
}

整合MyBatis &#8203;

引入mybatis-spring-boot-starter依赖,然后配置spring-datasource的相关配置

yaml
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mall40?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 123456
    type: druid的全限定类名
mybatis:
  type-handlers-package: typehandler的包目录

然后在启动类上增加Mapper的包目录配置

java
@MapperScan("com.cskaoyan.mapper")
@SpringBootApplication
public class MarketApplication {

    public static void main(String[] args) {
        SpringApplication.run(MarketApplication.class, args);
    }

}

个人信息 &#8203;

需要整合Shiro之后,通过Shiro能够拿到信息

java
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
  Object primaryPrincipal = subject.getPrincipals().getPrimaryPrincipal();
  //Object principal = subject.getPrincipal();
}

json格式转换 &#8203;

可以使用SpringBoot的配置文件配置其格式信息

yml
spring:
  jackson:
    date-format: yyyy-MM-dd
    time-zone: GMT+8

@JsonFormat写在成员变量上,指定该成员变量的日期格式

  1. 接收JSON字符串,@RequestBody接收为一个引用类型对象 ,引用类型对象中有Date成员变量
  2. 响应JSON字符串,@ResponseBody响应引用类型对象 ,引用类型对象中有Date成员变量

分页配置 &#8203;

引入pagehelper的starter之后,引入对应的dialect即可

yaml
pagehelper:
  helper-dialect: mysql

配置文件 &#8203;

spring.profiles.active

yaml
# application.yml → 主配置文件
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cskaoyan_market?useUnicode=true&characterEncoding=utf-8
  profiles:
    active: root,web

比如数据用户名密码不一致的情况会导致冲突

不同的开发者,写不同的配置文件

zhangsan的配置文件

yaml
# application-zhangsan.yml
spring:
  datasource:
    username: zhangsan
    password: lovexiaofang

lisi的配置文件

yaml
# application-lisi.yml
spring:
  datasource:
    username: lisi
    password: 654321

root用户的配置文件

yaml
# application-root.yml
spring:
  datasource:
    username: root
    password: 123456

spring.profiles.active里的值不还是会冲突么?

.gitignore 把application.yml给忽略掉

HibernateValidation &#8203;

校验,参数校验

具体部分 &#8203;

首页信息 (/wx/home/index) &#8203;

核心点:查询首页需要的信息

响应的JSON字符串可以使用Map来封装

java
Map<String, Object> entity = new HashMap<>();
try {
  entity.put("banner", bannerTask.get());
  entity.put("channel", channelTask.get());
  entity.put("couponList", couponListTask.get());
  entity.put("newGoodsList", newGoodsListTask.get());
  entity.put("hotGoodsList", hotGoodsListTask.get());
  entity.put("brandList", brandListTask.get());
  entity.put("topicList", topicListTask.get());
  entity.put("floorGoodsList", floorGoodsListTask.get());
}

上面的结果可以通过多线程来分别查询,提高效率 → 每个线程中分别做以上的list的查询

其中SystemConfig.getNewLimit(),SystemConfig.getHotLimit()这些值来源于market_system表中的小程序的相关配置(也可以参考后台管理中的小程序配置)

image-20230419010708592

java
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 之前后台管理中的广告管理
Callable<List> bannerListCallable = () -> adService.queryIndex();
// 之前后台管理中的类目管理
Callable<List> channelListCallable = () -> categoryService.queryChannel();
// 获取优惠券列表
Callable<List> couponListCallable;
// 判断是否是登录状态
if(userId == null){
  //如果是未登录,就查询所有的优惠券中的前三条
    couponListCallable = () -> couponService.queryList(0, 3);
} else {
  //如果是已经登录,就查询当前用户还未领取的前三张优惠券
    couponListCallable = () -> couponService.queryAvailableList(userId,0, 3);
}

// 查询新添加的商品Goods,之后后台管理中的商品管理,商品上架时有选项是否是新品,根据添加时间做排序,limit获取market_system表中的值
Callable<List> newGoodsListCallable = () -> goodsService.queryByNew(0, SystemConfig.getNewLimit());
// 查询热门商品Goods,之后后台管理中的商品管理,商品上架时有选项是否是热卖,根据添加时间做排序,limit获取market_system表中的值
Callable<List> hotGoodsListCallable = () -> goodsService.queryByHot(0, SystemConfig.getHotLimit());
// 之后后台管理中的制造商管理,根据添加时间做排序,limit获取market_system表中的值
Callable<List> brandListCallable = () -> brandService.query(0, SystemConfig.getBrandLimit());
// 之前后台管理中的专题管理,根据添加时间做排序,limit获取market_system表中的值
Callable<List> topicListCallable = () -> topicService.queryList(0, SystemConfig.getTopicLimit());

// 查询类目category以及类目下的商品goods,limit获取market_system表中的值
Callable<List> floorGoodsListCallable = this::getCategoryList;

FutureTask<List> bannerTask = new FutureTask<>(bannerListCallable);
FutureTask<List> channelTask = new FutureTask<>(channelListCallable);
FutureTask<List> couponListTask = new FutureTask<>(couponListCallable);
FutureTask<List> newGoodsListTask = new FutureTask<>(newGoodsListCallable);
FutureTask<List> hotGoodsListTask = new FutureTask<>(hotGoodsListCallable);
FutureTask<List> brandListTask = new FutureTask<>(brandListCallable);
FutureTask<List> topicListTask = new FutureTask<>(topicListCallable);
FutureTask<List> floorGoodsListTask = new FutureTask<>(floorGoodsListCallable);

executorService.submit(bannerTask);
executorService.submit(channelTask);
executorService.submit(couponListTask);
executorService.submit(newGoodsListTask);
executorService.submit(hotGoodsListTask);
executorService.submit(brandListTask);
executorService.submit(topicListTask);
executorService.submit(floorGoodsListTask);

最后floorGoodsList要先获取一级类目,然后获得其二级类目,然后获得其二级类目下的商品list,期间根据market_system表中的limit做限制数据量

java
private List<Map> getCategoryList() {
    List<Map> categoryList = new ArrayList<>();
    List<CskaoyanMarketCategory> catL1List = categoryService.queryL1WithoutRecommend(0, SystemConfig.getCatlogListLimit());
    for (CskaoyanMarketCategory catL1 : catL1List) {
        List<CskaoyanMarketCategory> catL2List = categoryService.queryByPid(catL1.getId());
        List<Integer> l2List = new ArrayList<>();
        for (CskaoyanMarketCategory catL2 : catL2List) {
            l2List.add(catL2.getId());
        }

        List<CskaoyanMarketGoods> categoryGoods;
        if (l2List.size() == 0) {
            categoryGoods = new ArrayList<>();
        } else {
            categoryGoods = goodsService.queryByCategory(l2List, 0, SystemConfig.getCatlogMoreLimit());
        }

        Map<String, Object> catGoods = new HashMap<>();
        catGoods.put("id", catL1.getId());
        catGoods.put("name", catL1.getName());
        catGoods.put("goodsList", categoryGoods);
        categoryList.add(catGoods);
    }
    return categoryList;
}

商品详情信息(/wx/goods/detail) &#8203;

核心点:获取商品的详细信息

传入的值是goodsId,然后查询相关的信息

java
Map<String, Object> data = new HashMap<>();
// goods的信息
data.put("info", info);
// 已经收藏该商品的用户数量
data.put("userHasCollect", userHasCollect);
// 后台管理中的通用问题
data.put("issue", issueCallableTask.get());
// 商品评论
data.put("comment", commentsCallableTsk.get());
// 规格列表 goods_specification表 → 注意按照响应结果的格式来封装
data.put("specificationList", objectCallableTask.get());
// 货品信息 goods_product表
data.put("productList", productListCallableTask.get());
// 参数信息 goods_attribute表
data.put("attribute", goodsAttributeListTask.get());
// 制造商信息
data.put("brand", brandCallableTask.get());
// 是否分享
data.put("share", SystemConfig.isAutoCreateShareImage());
// 商品分享图片地址
data.put("shareImage", info.getShareUrl());

可以参考上面/wx/home/index使用多线程来完成,也可以直接写业务代码

核心点:根据当前商品查询其相关的商品

请求参数可以直接接收

java
@GetMapping("related")
public BaseRespVo related(Integer id) {
   CskaoyanMarketGoods goods = goodsService.findById(id);
   if (goods == null) {
      return BaseRespVo.badArgumentValue();
   }

   // 推荐同类目的其他商品
   int cid = goods.getCategoryId();

   // 查找六个相关商品
   int related = 6;
   List<CskaoyanMarketGoods> goodsList = goodsService.queryByCategory(cid, 0, related);
   return BaseRespVo.ok(goodsList);
}

购物车中货品的数量(/wx/cart/goodscount) &#8203;

核心点:查询购物车中的货品数量

先查询到用户id的信息,然后根据用户用户id查询该用户的购物车中的货品数量

接下来开始继续开发吧

购物车中添加记录(/wx/cart/add) &#8203;

核心点:接收JSON数据对象,添加到数据库中,要绑定用户信息,信息要来源于Shiro,

首先获得用户信息,用户信息参考通用部分/个人信息

判断商品是否存在,

需要判断购物车是否有这个商品的记录,如果已经有的话,需要合并购物车,在已有的购物车记录上添加数量

需要根据规格判断库存

购物车(/wx/cart/index) &#8203;

查看购物车首页,查询当前用户的购物车记录,计算选中的商品(checked=1)的数量和金额

首先获得用户信息,用户信息参考通用部分/个人信息

购物车商品标记(/wx/cart/checked) &#8203;

修改选中商品的选中状态(checked = !checked),然后重新执行/wx/cart/index做的事情,返回结果类型一致

立即购买(/wx/cart/fastadd) &#8203;

做的事情和/wx/cart/add基本上是一样的,add请求如果购物车中已经有这个商品了,可以做合并;fastadd如果购物车中已经有该商品了,可以覆盖掉

这里的返回值是在market_cart表中该记录的id

结账信息(/wx/cart/checkout) &#8203;

生成购物车结账信息

这个接口的计算金额部分会比较繁琐,耐心点儿做

如果cartid=0则是通过购物车下单的,查询当前用户选中的商品(checked=1)的结账信息;如果cartid !=0 则是通过商品详情页立即购买生成的下单信息,然后根据fastadd添加的cart记录计算下单信息

然后

  1. 判断地址状态,addressId=0,查询该用户的默认收货地址;不为0查询当前地址

  2. 判断优惠券状态,如果为couponId=-1,则查询可用的优惠券数量;如果大于0,则使用指定的优惠券

    • 要注意判断优惠券的可用状态:优惠券是否已经删除、该用户是否有该id对应的优惠券、优惠券是否过期、结账总金额是否满足
  3. 根据该用户的购物车中选中的记录(checked=1),计算总金额,如果金额超过运费满减金额(后台配置管理),则不添加运费,如果不超过运费满减金额,则需要添加运费,如果可以使用指定优惠券,减去优惠券的金额;也就是

    • 实付金额 = 商品合计 - (商品合计 > 运费满减金额?0:运费) - 优惠券金额

提交订单(/wx/order/submit) &#8203;

购物车结账信息再重新计算一次;额外去做的事情添加order和orderGoods记录,由于项目2不做支付,订单状态可以直接定义为待付款,删除购物车中的记录(可以逻辑删除),同时减去库存,如果库存不足可以抛出异常,给出提示信息,如果使用了优惠券,要将优惠券状态标记为已使用

额外说明 &#8203;

订单信息中的handleOption是可以操作的选项,handleOption中的哪些值为true,基本上就是显示哪一些按钮

比如已付款的订单的handleOption

image-20230419232558161

aftersale: 申请售后(这个也可以不管)

cancel:取消订单

comment:去评论(该值为true且该order_goods没有评论的前提下才会显示该按钮)

confirm:确认收货

delete:删除订单

pay:去付款

rebuy:再次购买

refun:申请退款

其中refund为true,意味着申请退款按钮显示

image-20230419232641618

其余 &#8203;

有问题及时问,目前我就是大家的产品经理