谷粒商城 Day03 后台跨域与文件上传

Day03 后台跨域与文件上传

image-20211207192308310

image-20211207192440578

根据接口文档完成各个接口

文档位置:D:\尚硅谷-Java-210301\16_尚硅谷_雷丰阳_谷粒商城\资料\01 后台管理系统

一、根据分类id获取平台属性

接口http://api.gmall.com/admin/product/attrInfoList/{category1Id}/{category2Id}/{category3Id}
请求参数category1Id:一级分类ID
category2Id:二级分类ID
category3Id:三级分类ID
请求方式get
例:http://api.gmall.com/admin/product/attrInfoList/2/0/0
返回值{
“code”:200,
“message”:“成功”,
“data”:[
{
“id”:1,
“attrName”:“价格”,
“categoryId”:61,
“categoryLevel”:3,
“attrValueList”:[
{
“id”:53,
“valueName”:“1000-1699”,
“attrId”:1
},

]
},

],
“ok”:true
}

说明:

1、平台属性可以挂在一级分类、二级分类和三级分类;

2、查询一级分类下面的平台属性,传:category1Id,0,0; 取出该分类的平台属性;

3、查询二级分类下面的平台属性,传:category1Id,category2Id,0;
取出对应一级分类下面的平台属性与二级分类对应的平台属性;

4、查询三级分类下面的平台属性,传:category1Id,category2Id,category3Id;取出对应一级分类、二级分类与三级分类对应的平台属性

1、BaseAttrInfoController

新建 com.atguigu.gmall.product.controller.BaseAttrInfoController

@RequestMapping("/admin/product")
@RestController
@Slf4j
public class BaseAttrInfoController {
    
    @Autowired
    BaseAttrInfoService baseAttrInfoService;

    /**
     * 根据分类ID获取平台属性
     *
     * @param category1Id
     * @param category2Id
     * @param category3Id
     * @return
     */
    @GetMapping("/attrInfoList/{category1Id}/{category2Id}/{category3Id}")
    public Result<List<BaseAttrInfo>> attrInfoList(
            @PathVariable("category1Id") Long category1Id,
            @PathVariable("category2Id") Long category2Id,
            @PathVariable("category3Id") Long category3Id
    ) {
        List<BaseAttrInfo> baseAttrInfos = baseAttrInfoService.attrInfoList(category1Id, category2Id, category3Id);

        return Result.ok(baseAttrInfos);
    }
}

2、BaseAttrInfoService

新建 com.atguigu.gmall.product.service.BaseAttrInfoService

public interface BaseAttrInfoService extends IService<BaseAttrInfo> {
    List<BaseAttrInfo> attrInfoList(Long category1Id, Long category2Id, Long category3Id);
}

3、BaseAttrInfoServiceImpl

新建 com.atguigu.gmall.product.service.impl.BaseAttrInfoServiceImpl

/**
 * 只是操作代表的Service。可以 extends ServiceImpl<表Mapper, 表的Bean> implements 表Service
 */
@Service
@Slf4j
//第一行是固定写法
public class BaseAttrInfoServiceImpl extends ServiceImpl<BaseAttrInfoMapper, BaseAttrInfo> implements BaseAttrInfoService {
    //自动带上
    @Autowired //只要实现类继承了ServiceImpl<BaseAttrInfoMapper, BaseAttrInfo>就会自动带上
    BaseAttrInfoMapper baseAttrInfoMapper;

    @Override
    public List<BaseAttrInfo> attrInfoList(Long category1Id, Long category2Id, Long category3Id) {
        return baseAttrInfoMapper.getAttrInfoList(category1Id, category2Id, category3Id);
    }
}

4、BaseAttrInfoMapper

新建 com.atguigu.gmall.product.mapper.BaseAttrInfoMapper

@Component
public interface BaseAttrInfoMapper extends BaseMapper<BaseAttrInfo> {
        /**
     * 带上行为前缀 get/delete/insert/update
     * 多个参数一定使用 @Param 标注每个参数的名字,这个名字是在sql中#{}位置填的
     * @param category1Id
     * @param category2Id
     * @param category3Id
     * @return
     */
    List<BaseAttrInfo> getAttrInfoList(
            @Param("category1Id") Long category1Id,
            @Param("category2Id") Long category2Id,
            @Param("category3Id") Long category3Id);
}

5、SQL

改进前:

此时查询出来的信息中有两个 id,为了防止它们相互冲突,我们最好给查询出来的 base_attr_value 的 id 起别名

image-20210919101104045

改进后:

image-20210919101722437

6、BaseAttrInfo

image-20210919101337103

7、BaseAttrInfoMapper.xml

新建 service/service-product/src/main/resources/mapper/BaseAttrInfoMapper.xml

<?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.atguigu.gmall.product.mapper.BaseAttrInfoMapper">
    <resultMap id="baseAttrInfoAndValue" type="com.atguigu.gmall.model.product.BaseAttrInfo">
        <id property="id" column="id"></id>
        <result property="attrName" column="attr_name"></result>
        <result property="categoryId" column="category_id"></result>
        <result property="categoryLevel" column="category_level"></result>
        <collection property="attrValueList" ofType="com.atguigu.gmall.model.product.BaseAttrValue">
            <!--collection标签用于处理对多的关联关系。
                属性:
			        property:指定关联属性的名字 
			        ofType: 指定集合中元素的数据类型
			        这里不指定类型的话会报空指针异常-->
            <!-- BaseAttrValue -->
            <id property="id" column="bav_id"></id>
            <result property="valueName" column="value_name"></result>
            <result property="attrId" column="attr_id"></result>
        </collection>
    </resultMap>
    <!--
    #{}:预编译+占位符,没有SQL注入风险
    ${}:拼串,有SQL注入风险
    -->
    <select id="getAttrInfoList" resultMap="baseAttrInfoAndValue">
        SELECT bai.*,bav.id bav_id,bav.value_name,bav.attr_id FROM base_attr_info bai
        LEFT JOIN  base_attr_value bav  ON bai.id = bav.attr_id
        <where>
            <if test="category1Id!=null and category1Id>0">
                OR (bai.category_id = #{category1Id} AND bai.category_level=1)
            </if>
            <if test="category2Id!=null and category2Id>0">
                OR (bai.category_id = #{category2Id} AND bai.category_level=2)
            </if>
            <if test="category3Id!=null and category3Id>0">
                OR (bai.category_id = #{category3Id} AND bai.category_level=3)
            </if>
        </where>
    </select>
</mapper>

8、效果

image-20210919105212609


二、添加属性名和值功能

1、分析

(1)前端页面+base_attr_info

image-20210919105906648

(2)数据表

image-20210919120523087

(3)前端页面+base_attr_value

image-20210919120733670

2、跨域的问题

点击保存的时候

image-20210919120844473

我们之前明明已经设置过跨域了为什么还会有跨域的问题呢?

之前我们配置的网关

(1)api-gateway

application.yaml:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods: "*"
      routes:
        - id: admin-product
          uri: lb://service-product
          predicates:
            - Path=/admin/product/**

首先我们先根据接口文档对比:

image-20210919121956758 image-20210919122033954
(2)跨域

https://developer.mozilla.org/zh-CN/docs/Glossary/CORS

image-20210919122901613

(3)同源安全策略

跨域只是限制 JavaScript 写的 ajax 请求,无论前端的啥 JS 框架,底层都是 JavaScript ,你要发请求都是 ajax,一句话,前端框架发送的请求全部都会被跨域限制,为什么呢?这是浏览器做的事情,有一个东西叫同源安全策略

举一个生活中的例子:比如我和吴某凡都是同一个公司的人,这就属于同源,我给他拍照就是被允许的;但如果是狗仔队想要拍照就不被允许了,因为不是同一个公司的,不属于同源。

如果都是在同一个源服务器的,大家都是在一个网址下的,我给网址下的另外一个地方发请求跟我给网址下的其他地方发请求就相当于大家都在一个公司(同一个网址代表共一个公司)无所谓随便发没有限制, 但如果不是公司内部的,比如狗仔队拍照就会被限制,不是吴某凡的限制,(吴某凡 ≈ 服务端),而我们是从浏览器发送请求,我们得出公司的门,我们出公司门的这一刻就被卡住了,这就是同源安全策略。

那如果狗仔队想要拍吴某凡怎么办呢?

有两种办法:

  • 自己写一个浏览器(想 peach)
  • 先写申请打报告,获得对方服务器允许才行

CORS 头:

image-20210919124910961

总体了解

Cross-Origin Resource Sharing (CORS)

image-20210919125331021

如果 domain-a.com 请求 domain-a.com 的资源是同源请求,但是如果 domain-a.com 请求 domain-b.com 就是跨域请求了,此时的 Web document 就相当于是用户代理,作为 domain-a.com 的代理去请求 domain-b.com

User-Agent

image-20210919125621704

我们接着往下看

(4)简单请求
image-20210919130031498

简单请求我们通过原来的在 api-gateway 中的设置就可以

application.yaml:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods: "*"
      routes:
        - id: admin-product
          uri: lb://service-product
          predicates:
            - Path=/admin/product/**

复杂请求就不行了。因为简单请求不会触发 CORS 预检

image-20210919130808994

3、解决方案—跨域

(1)原理
  • 浏览器要遵循一个“同源安全策略”规范

    • 只要是同一个域(域名)下得所有请求可以直接发送,否则,浏览器会进行限制
  • 同一网站的所有ajax请求,属于同源,不受限制

  • 不同源

    • url路径部分第一个/往前,所有任何地方不一样都是不同源。以下都是不同源
      • eg: www.atguigu.com www.atguigu.cn
      • eg:localhost:8080/xxxx localhost:8888
      • eg:http://atguigu.com https://atguigu.com
      • eg:http://atguigu.com http://39.28.17.66
      • eg:aaa.atguigu.com bbb.atguigu.com
(2)简单请求与复杂请求

简单请求直接发给服务器,如果服务器允许就直接执行

复杂请求先发给服务器一个预检请求 ,服务器同意跨域以后,浏览器再发真实请求

详细看:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

  • 什么简单请求:请求违背下面任何一个都不是简单请求

    image-20210802103332023

我们的 Content-Type 是 json,不属于简单请求中 content-type 指定的类型

image-20211208000455876

点进来发现 request method 竟然是 option,可我们发的明明是 post,其实是因为这个请求是预检请求,说明此请求是复杂请求

image-20211208000540229

复杂请求先 preflight ;发送预检请求。

https://developer.mozilla.org/zh-CN/docs/Glossary/CORS

image-20210802103623104

所以我们需要额外设置一个 Access-Control-Allow-Methods(allowedHeaders)

(3)解决方案

网关层

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*" #生产环境就不能写 * 了,要写直接的网站,例:"gmall.com"
            allowedMethods: "*"
            allowedHeaders: "*"  #允许复杂请求跨域
            allowCredentials: "*"  #允许跨域带cookie(包含一些令牌信息、安全信息)
// 跨域配置类

或者

每个controller用 @CrossOrigin

​ 这种方式会累死

或者

nginx直接设置允许跨域

location / {
if ($request_method = 'OPTIONS') {
        add_header Access-Control-Allow-Origin $http_origin;
        add_header Access-Control-Allow-Methods $http_access_control_request_method;
        add_header Access-Control-Allow-Credentials true;
        add_header Access-Control-Allow-Headers $http_access_control_request_method;
        add_header Access-Control-Max-Age 1728000;
 return 204;
    }

正常的效果:

image-20210919135150532

image-20211208000952770

4、平台属性保存

接口文档

接口http://api.gmall.com/admin/product/saveAttrInfo
请求参数{
“id”:null,
“attrName”:“操作系统”,
“attrValueList”:[
{
“valueName”:“苹果系统”
},

],
“categoryId”:61,
“categoryLevel”:3
}
请求方式Post
例:http://api.gmall.com/admin/product/saveAttrInfo
返回值{
“code”:200,
“message”:“成功”,
“data”:null,
“ok”:true
}
(1)分析

我们解决完跨域后继续来看平台属性保存,当点击保存时

image-20210919135604100

看一下它的 view source 数据

image-20210919135703078

对比着看

image-20210919140022108

image-20210919140208566

image-20210919140721220

(2)BaseAttrInfoController ①
@RequestMapping("/admin/product")
@RestController
@Slf4j
public class BaseAttrInfoController {
        
    @Autowired
    BaseAttrInfoService baseAttrInfoService;

	/**
     * 保存平台属性名和值
     * saveAttrInfo(BaseAttrInfo baseAttrInfo):表单提交的k=v&k=v映射到 BaseAttrInfo
     * saveAttrInfo(@RequestBody BaseAttrInfo baseAttrInfo):前端提交的json封装映射到 BaseAttrInfo
     * @return
     */
    @PostMapping("/saveAttrInfo")
    public Result saveAttrInfo(@RequestBody BaseAttrInfo baseAttrInfo){
        log.info("前端提交的数据:{}",baseAttrInfo);
        return Result.ok();
    }
}
(3)BaseAttrInfo

修改成以下的内容

@Data
@ApiModel(description = "平台属性")
@TableName("base_attr_info")
public class BaseAttrInfo extends BaseEntity {
   
   private static final long serialVersionUID = 1L;
   
   @ApiModelProperty(value = "属性名称")
   @TableField("attr_name")
   private String attrName;

   @ApiModelProperty(value = "分类id")
   @TableField("category_id")
   private Long categoryId;

   @ApiModelProperty(value = "分类层级")
   @TableField("category_level")
   private Integer categoryLevel;

   // 平台属性值集合
   @TableField(exist = false)
   private List<BaseAttrValue> attrValueList;

   @Override
   public String toString() {
      return "BaseAttrInfo{" +
            "id='" + getId() + '\'' +
            "attrName='" + attrName + '\'' +
            ", categoryId=" + categoryId +
            ", categoryLevel=" + categoryLevel +
            ", attrValueList=" + attrValueList +
            '}';
   }
}

先启动测试一下

前端提交的数据过来了

image-20210919141602355

(4)BaseAttrInfoController ②
@RequestMapping("/admin/product")
@RestController
@Slf4j
public class BaseAttrInfoController {
        
    @Autowired
    BaseAttrInfoService baseAttrInfoService;

	/**
     * 保存平台属性名和值
     * saveAttrInfo(BaseAttrInfo baseAttrInfo):表单提交的k=v&k=v映射到 BaseAttrInfo
     * saveAttrInfo(@RequestBody BaseAttrInfo baseAttrInfo):前端提交的json封装映射到 BaseAttrInfo
     * @return
     */
    @PostMapping("/saveAttrInfo")
    public Result saveAttrInfo(@RequestBody BaseAttrInfo baseAttrInfo){
        
        log.info("前端提交的数据:{}",baseAttrInfo);
        
        //单一添加
        baseAttrInfoService.saveAttrInfoAndValue(baseAttrInfo);
        
        return Result.ok();
    }
}
(5)BaseAttrInfoService
void saveAttrInfoAndValue(BaseAttrInfo baseAttrInfo);
(6)BaseAttrInfoServiceImpl
    @Autowired
    BaseAttrInfoMapper baseAttrInfoMapper;

/**
 * 新增平台属性和值
 *
 * @param baseAttrInfo
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void saveAttrInfoAndValue(BaseAttrInfo baseAttrInfo) {
    //1、保存属性名
    baseAttrInfoMapper.insert(baseAttrInfo);

    //获取到刚才保留的记录的id
    Long id = baseAttrInfo.getId();

    //2、保存属性值
    List<BaseAttrValue> attrValueList = baseAttrInfo.getAttrValueList();
    if (!CollectionUtils.isEmpty(attrValueList)) {
        for (BaseAttrValue baseAttrValue : attrValueList) {

            //回填AttrId
            baseAttrValue.setAttrId(id);

            baseAttrValueMapper.insert(baseAttrValue);
        }
    }
}
(7)BaseAttrValueMapper

新建 com.atguigu.gmall.product.mapper.BaseAttrValueMapper

@Component
public interface BaseAttrValueMapper extends BaseMapper<BaseAttrValue> {
}

测试看一下效果

保存成功

image-20210919142905775

数据库

base_attr_info

image-20210919142939873

base_attr_value

image-20210919143019507

三、为什么Mapper下的xml能被自动扫描

因为在 service-product 的 application.yaml 文件中有一个默认配置

mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml

这个配置是默认的,不配置都行,表示在任意类路径下的 mapper 下的 .xml 文件

image-20210919143707011

我们把 XXXMapper.xml 放在 resources 下的 mapper 文件中就可以被扫描到,如果把 XXXMapper.xml 配置在 com.atguigu.gmall.product.mapper 下还要额外配置一些其他的东西,更加麻烦一些

image-20210919144151452

还要在 pom.xml 中配置 build ,麻烦的一批,为什么不选择更加方便的呢?

SpringCloud 的口诀:约定 > 配置 > 编码

四、根据平台属性ID获取平台属性对象数据

接口文档:

接口http://api.gmall.com/admin/product/getAttrValueList/{attrId}
请求参数attrId:平台属性ID
请求方式get
例:http://api.gmall.com/admin/product/getAttrValueList/1
返回值{
“code”:200,
“message”:“成功”,
“data”:[
{
“id”:1,
“valueName”:“0-499”,
“attrId”:1
}

],
“ok”:true
}

1、BaseAttrInfoController

@Autowired
BaseAttrValueService baseAttrValueService;

/**
 * 根据平台属性ID获取平台属性对象数据
 * @param attrId
 * @return
 */
@GetMapping("/getAttrValueList/{attrId}")
public Result<List<BaseAttrValue>> getAttrValueList(@PathVariable("attrId") Long attrId){

    List<BaseAttrValue> list = baseAttrValueService.getAttrValueList(attrId);

    return Result.ok(list);
}

2、BaseAttrValueService

新建 com.atguigu.gmall.product.service.BaseAttrValueService

public interface BaseAttrValueService extends IService<BaseAttrValue> {

    List<BaseAttrValue> getAttrValueList(Long attrId);
}

3、BaseAttrValueServiceImpl

新建 com.atguigu.gmall.product.service.impl.BaseAttrValueServiceImpl

@Service
public class BaseAttrValueServiceImpl extends ServiceImpl<BaseAttrValueMapper, BaseAttrValue> implements BaseAttrValueService {

    @Autowired
    BaseAttrValueMapper baseAttrValueMapper;

    @Override
    public List<BaseAttrValue> getAttrValueList(Long attrId) {

        QueryWrapper<BaseAttrValue> wrapper = new QueryWrapper<>();
        wrapper.eq("attr_id",attrId);

        List<BaseAttrValue> list = baseAttrValueMapper.selectList(wrapper);
        return list;
    }
}

4、BaseAttrValueMapper

新建 com.atguigu.gmall.product.mapper.BaseAttrValueMapper

@Component
public interface BaseAttrValueMapper extends BaseMapper<BaseAttrValue> {
}

看看效果

image-20210919150503230

五、修改平台属性

文档

接口http://api.gmall.com/admin/product/saveAttrInfo
请求参数{
“id”:1,
“attrName”:“价格”,
“attrValueList”:[
{
“id”:1,
“valueName”:“0-499”,
“attrId”:1
},

],
“categoryId”:61,
“categoryLevel”:3
}
请求方式post
例:http://api.gmall.com/admin/product/saveAttrInfo
返回值{
“code”:200,
“message”:“成功”,
“data”:null,
“ok”:true
}

看文档,发现修改平台属性接口竟然也是 saveAttrInfo,说明 saveAttrInfo 是添加修改二合一的接口

1、BaseAttrInfoController

/**
     * 保存平台属性名和值
     * saveAttrInfo(BaseAttrInfo baseAttrInfo):表单提交的k=v&k=v映射到 BaseAttrInfo
     * saveAttrInfo(@RequestBody BaseAttrInfo baseAttrInfo):前端提交的json封装映射到 BaseAttrInfo
     * @return
     */
    @PostMapping("/saveAttrInfo")
    public Result saveAttrInfo(@RequestBody BaseAttrInfo baseAttrInfo){
        log.info("前端提交的数据:{}",baseAttrInfo);

        //单一添加
//        baseAttrInfoService.saveAttrInfoAndValue(baseAttrInfo);

        //添加修改二合一
        baseAttrInfoService.saveOrUpdateInfoAndValue(baseAttrInfo);

        return Result.ok();
    }

2、BaseAttrInfoService

void saveAttrInfoAndValue(BaseAttrInfo baseAttrInfo);

void updateAttrInfoAndValue(BaseAttrInfo baseAttrInfo);

/*
* 保存或更新按需调用上面那两个家伙
*/
void saveOrUpdateInfoAndValue(BaseAttrInfo baseAttrInfo);

3、BaseAttrInfoServiceImpl

    /**
     * 新增平台属性和值
     *
     * @param baseAttrInfo
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveAttrInfoAndValue(BaseAttrInfo baseAttrInfo) {
        //1、保存属性名
        baseAttrInfoMapper.insert(baseAttrInfo);

        //获取到刚才保留的记录的id
        Long id = baseAttrInfo.getId();

        //2、保存属性值
        List<BaseAttrValue> attrValueList = baseAttrInfo.getAttrValueList();
        if (!CollectionUtils.isEmpty(attrValueList)) {
            for (BaseAttrValue baseAttrValue : attrValueList) {

                //回填AttrId
                baseAttrValue.setAttrId(id);

                baseAttrValueMapper.insert(baseAttrValue);
            }
        }
    }

    /**
     * 修改属性平台名和值
     *
     * @param baseAttrInfo
     */
    @Override
    public void updateAttrInfoAndValue(BaseAttrInfo baseAttrInfo) {
        //属性名更新
        baseAttrInfoMapper.updateById(baseAttrInfo);

        //属性名id
        Long id = baseAttrInfo.getId();

        //属性值更新
        List<BaseAttrValue> attrValueList = baseAttrInfo.getAttrValueList();

        //以前:1,2
        //现在:1,x,y,z
        //把以前的全部删除
        QueryWrapper<BaseAttrValue> wrapper = new QueryWrapper<>();
        wrapper.eq("attr_id", id);
        baseAttrValueMapper.delete(wrapper);

        //删除完成后新增所有
        for (BaseAttrValue baseAttrValue : attrValueList) {
            baseAttrValue.setAttrId(id);
            baseAttrValueMapper.insert(baseAttrValue);

//            if (baseAttrValue.getId() != null){
//                //有属性值id是修改
//                baseAttrValueMapper.updateById(baseAttrValue);
//            }else {
//                //无属性值id是新增
//                baseAttrValue.setAttrId(id);
//                baseAttrValueMapper.insert(baseAttrValue);
//            }

        }
    }

    /**
     * @param baseAttrInfo
     */
    @Override
    public void saveOrUpdateInfoAndValue(BaseAttrInfo baseAttrInfo) {
        //新增
        if (baseAttrInfo.getId() == null) {
            saveAttrInfoAndValue(baseAttrInfo);
        } else {
            updateAttrInfoAndValue(baseAttrInfo);
        }

        //修改数据:BaseAttrInfo{id='11'attrName='颜色1111', categoryId=2, categoryLevel=1, attrValueList=[BaseAttrValue{id='59'valueName='黑色', attrId=11}, BaseAttrValue{id='null'valueName='玫瑰金', attrId=null}, BaseAttrValue{id='null'valueName='土豪金', attrId=null}]}
    }
}

修改一下 BaseAttrValue 和 BaseAttrInfo(之前已经修改过了,toString 拿到父类 id)

4、BaseAttrValue

@Data
@ApiModel(description = "平台属性值")
@TableName("base_attr_value")
public class BaseAttrValue extends BaseEntity {

   private static final long serialVersionUID = 1L;

   @ApiModelProperty(value = "属性值名称")
   @TableField("value_name")
   private String valueName;

   @ApiModelProperty(value = "属性id")
   @TableField("attr_id")
   private Long attrId;

   @Override
   public String toString() {
      return "BaseAttrValue{" +
            "id='" + getId() + '\'' +
            "valueName='" + valueName + '\'' +
            ", attrId=" + attrId +
            '}';
   }
}

六、获取品牌分页列表

文档

接口http://api.gmall.com/admin/product/baseTrademark/{page}/{limit}
请求参数page:第几页limit:每页数量
请求方式get
例:http://api.gmall.com/admin/product/baseTrademark/1/10
返回值{
“code”:200,
“message”:“成功”,
“data”:{
“records”:[
{
“id”:1,
“tmName”:“苹果”,
“logoUrl”:“http://127.0.0.1/assets/img/_/phone01.png”
},

],
“total”:4,
“size”:10,
“current”:1,
“pages”:1
},
“ok”:true
}

1、BaseTrademarkController

新建 com.atguigu.gmall.product.controller.BaseTrademarkController

@RequestMapping("/admin/product")
@RestController
@Slf4j
public class BaseTrademarkController {

    @Autowired
    BaseTrademarkService baseTrademarkService;
    
    /**
     * 获取品牌分页列表
     * @param page
     * @param limit
     * @return
     */
    @GetMapping("/baseTrademark/{page}/{limit}")
    public Result baseTrademarkPage(
            @PathVariable("page") Long page,
            @PathVariable("limit") Long limit){

        Page<BaseTrademark> baseTrademarkPage = new Page<>(page,limit);

        Page<BaseTrademark> page1 = baseTrademarkService.page(baseTrademarkPage);

        return Result.ok(page1);
    }
}

2、BaseTrademarkService

新建 com.atguigu.gmall.product.service.BaseTrademarkService

public interface BaseTrademarkService extends IService<BaseTrademark> {

}

运行效果:

image-20210919162022986

控制台 zipkin 报错

image-20210919162957093

先把 zipkin 的配置配好

3、application.yaml

server:
  port: 8000

spring:
  main:
    allow-bean-definition-overriding: true  #允许bean定义信息的重写
  datasource:
    url: jdbc:mysql://192.168.200.188:3306/gmall_product?characterEncoding=utf-8&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  zipkin:
    base-url: http://192.168.200.188:9411/
    sender:
      type: web

4、分页插件

要想分页 mybatis-plus 必须配置分页插件

https://mp.baomidou.com/guide/page.html

image-20210919163642734

//Spring boot方式
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {

    // 旧版
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        // paginationInterceptor.setLimit(500);
        // 开启 count 的 join 优化,只针对部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
    
    // 最新版
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }
    
}

5、MybatisPlusConfig

com.atguigu.gmall.common.config.MybatisPlusConfig

/**
 * MybatisPlus配置类
 *
 */
@EnableTransactionManagement
@Configuration
@MapperScan("com.atguigu.gmall.*.mapper")
public class MybatisPlusConfig {

    /**
     * 分页插件
     * 最新版
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

运行效果

image-20210919164232099


七、MinIo

http://docs.minio.org.cn/docs/master/java-client-quickstart-guide

image-20210919165044123

测试 demo

image-20210919165155077

1、FileUploader

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;

import org.xmlpull.v1.XmlPullParserException;

import io.minio.MinioClient;
import io.minio.errors.MinioException;

public class FileUploader {
  public static void main(String[] args) throws NoSuchAlgorithmException, IOException, InvalidKeyException, XmlPullParserException {
    try {
      // 使用MinIO服务的URL,端口,Access key和Secret key创建一个MinioClient对象
      MinioClient minioClient = new MinioClient("https://play.min.io", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG");

      // 检查存储桶是否已经存在
      boolean isExist = minioClient.bucketExists("asiatrip");
      if(isExist) {
        System.out.println("Bucket already exists.");
      } else {
        // 创建一个名为asiatrip的存储桶,用于存储照片的zip文件。
        minioClient.makeBucket("asiatrip");
      }

      // 使用putObject上传一个文件到存储桶中。
      minioClient.putObject("asiatrip","asiaphotos.zip", "/home/user/Photos/asiaphotos.zip");
      System.out.println("/home/user/Photos/asiaphotos.zip is successfully uploaded as asiaphotos.zip to `asiatrip` bucket.");
    } catch(MinioException e) {
      System.out.println("Error occurred: " + e);
    }
  }
}

我们自己的测试类

image-20210919165556199

2、UploadTest

public class UploadTest {

    public static void main(String[] args) {
        try {
            // 使用MinIO服务的URL,端口,Access key和Secret key创建一个MinioClient对象
            MinioClient minioClient = new MinioClient("http://192.168.200.188:9000",
                    "gmall-oss",
                    "gmall123");
            System.out.println("minioClient = " + minioClient);
//            System.out.println("minioClient.bucketExists(\"gmall\") = " + minioClient.bucketExists("gmall123"));

//            minioClient.makeBucket("haha");
            // 检查存储桶是否已经存在
            boolean isExist = minioClient.bucketExists("gmall");
            if(isExist) {
                System.out.println("Bucket already exists.");
            } else {
                // 创建一个名为asiatrip的存储桶,用于存储照片的zip文件。
                minioClient.makeBucket("asiatrip");
            }
//
            // 使用putObject上传一个文件到存储桶中。
            // //String bucketName, String objectName, PutObjectOptions options, Object data

            FileInputStream fileInputStream = new FileInputStream("D:\\TempFile\\LOqWKCZ5OcVfbrw.txt");
            PutObjectOptions options = new PutObjectOptions(fileInputStream.available(),-1);
            minioClient.putObject("gmall","LOqWKCZ5OcVfbrw.txt", fileInputStream,options);
//            System.out.println("/home/user/Photos/asiaphotos.zip is successfully uploaded as asiaphotos.zip to `asiatrip` bucket.");
        } catch(Exception e) {
            System.out.println("Error occurred: " + e);
        }
    }
}

接下来整合

3、MinioConfig

新建 config 包,专门放配置类

新建 com.atguigu.gmall.product.config.MinioConfig

@ConfigurationProperties(prefix = "minio")
@Configuration
@Data
@Slf4j
public class MinioConfig {

    private String url;
    private String accessKey;
    private String secretKey;
    private String defaultBucket;{}
    @Bean
    public MinioClient minioClient(){
        try {
            MinioClient minioClient = new MinioClient(
                    url,
                    accessKey,
                    secretKey);
            boolean exists = minioClient.bucketExists(defaultBucket);
            if(!exists){
                minioClient.makeBucket(defaultBucket);
            }
            return minioClient;
        } catch (Exception e) {
            //The difference between the request time and
            // the server's time is too large.
            //服务器里面docker 安装的minio 没有做时间同步
            log.info("minio-client故障:{}",e);
            return null;
        }
    }
}

4、application.yaml

minio:
  url: http://192.168.200.188:9000
  accessKey: gmall-oss
  secretKey: gmall123
  defaultBucket: gmall

5、如何把配置和配置类的属性绑定起来呢?

image-20210919170606088

6、MinioService

新建 component 包

minio 的所有功能

新建 com.atguigu.gmall.product.component.MinioService

/**
 * minio的所有功能
 */
@Component
public class MinioService {

    @Autowired
    MinioClient minioClient;

    @Autowired
    MinioConfig minioConfig;

    public String upload(InputStream inputStream,String bucketName,String fileName) throws Exception{
        if(StringUtils.isEmpty(bucketName)){
            //给默认位置上传
            bucketName = minioConfig.getDefaultBucket();
        }else{
            if(!minioClient.bucketExists(bucketName)){
                minioClient.makeBucket(bucketName);
            }
        }
        fileName = UUID.randomUUID().toString().replace("-","")+"_"+fileName;
        PutObjectOptions options = new PutObjectOptions(inputStream.available(),-1);
        minioClient.putObject(bucketName,fileName,inputStream,options);

        //返回整个文件路径???
        //URL+bucket+filename
        String url = "";
        url = minioConfig.getUrl() + "/"+bucketName + "/"+fileName;
        return url;//文件在minio的存储路径
    }
}

MinioService 中的异常为什么不在方法内通过 try-catch 处理,而是选择 throws 抛出呢?

因为实际业务逻辑中,你要是没上传成功人家可能有自己的处理逻辑,可能是重新上传或者放到其他地方等,异常你得让人家知道

文件路径 url 的应该是:URL+bucket+filename

image-20210919182104928

7、单元测试

https://start.aliyun.com/

阿里云 Java 工程脚手架

image-20210919182547439

image-20210919182727628

拿到 Maven 坐标

image-20210919182821139

为了方便直接放在 service 下的 pom.xml 中:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

MinioTest

新建 com.atguigu.gmall.product.MinioTest

@SpringBootTest
public class MinioTest {

    @Autowired
    MinioService minioService;

    @DisplayName("测试上传")
    @Test
    void upload() throws Exception {
        FileInputStream fileInputStream = new FileInputStream("D:\\TempFile\\LOqWKCZ5OcVfbrw.txt");
        String upload = minioService.upload(fileInputStream, null, "LOqWKCZ5OcVfbrw.txt");
        System.out.println("upload = " + upload);
    }
}

八、文件上传

文档

接口http://api.gmall.com/admin/product/fileUpload
请求参数file
请求方式post
返回值{
“code”:200,
“message”:“成功”,
“data”:”http://192.168.200.128:8080/group1/M00/00/01/wKjIgF9zVUOEAQ_2AAAAAPzxDAI115.png”,
“ok”:true
}

1、FileUploadController

新建 com.atguigu.gmall.product.controller.FileUploadController

@RequestMapping("/admin/product")
@RestController
public class FileUploadController {

    @Autowired
    MinioService minioService;

    @PostMapping("/fileUpload")
    public Result<String> uploadFile(@RequestPart("file") MultipartFile file) {

        try {
            InputStream inputStream = file.getInputStream();

            String originalFilename = file.getOriginalFilename();

            String upload = minioService.upload(inputStream, null, originalFilename);
            return Result.ok(upload);
        } catch (Exception e) {
            return Result.fail("网络异常");
        }
    }
}

2、@RequestPart

image-20210919184009502

image-20210919184204523

3、例子

① test.html
<form method="post" enctype="multipart/form-data">
 头像:     <input type="file" name="header">
 生活照:   <input type="file" name="shz">
 相亲照:   <input type="file" name="xqz">
 身份证正面:<input type="file" name="idcard-zm">
    身份证号:<input type="text" name="idnum">
</form>

getOriginalFilename 是获取的文件的名字,getName 是获取 name 属性后的值,例:header、shz、xqz …

image-20210919185547690

对应的 Java 文件:

② test.java
@PostMapping("/formupload")
public Result<String> upload(
			@RequestPart("header") MultipartFile header,
            @RequestPart("shz") MultipartFile shz,
		    @RequestParam("idnum") String idnum,
			@RequestPart("sfz") MultipartFile[] sfz){

    return Result.ok("");
}

运行

image-20210919190218950 image-20210919190343080
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值