Day03 后台跨域与文件上传
根据接口文档完成各个接口
文档位置: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](https://i-blog.csdnimg.cn/blog_migrate/ea101af09ec0014f50bfd1ff880572a7.png)
改进后:
![image-20210919101722437](https://i-blog.csdnimg.cn/blog_migrate/a6c4e0908e4aa4edfb1387e9f96088e5.png)
6、BaseAttrInfo
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、效果
二、添加属性名和值功能
1、分析
(1)前端页面+base_attr_info
(2)数据表
(3)前端页面+base_attr_value
2、跨域的问题
点击保存的时候
我们之前明明已经设置过跨域了为什么还会有跨域的问题呢?
之前我们配置的网关
(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](https://i-blog.csdnimg.cn/blog_migrate/63af01c09668a370771e53ea05b787dd.png)
![image-20210919122033954](https://i-blog.csdnimg.cn/blog_migrate/391e94d18a4bbd65b2e839c9c29e7b83.png)
(2)跨域
https://developer.mozilla.org/zh-CN/docs/Glossary/CORS
(3)同源安全策略
跨域只是限制 JavaScript 写的 ajax 请求,无论前端的啥 JS 框架,底层都是 JavaScript ,你要发请求都是 ajax,一句话,前端框架发送的请求全部都会被跨域限制,为什么呢?这是浏览器做的事情,有一个东西叫同源安全策略
举一个生活中的例子:比如我和吴某凡都是同一个公司的人,这就属于同源,我给他拍照就是被允许的;但如果是狗仔队想要拍照就不被允许了,因为不是同一个公司的,不属于同源。
如果都是在同一个源服务器的,大家都是在一个网址下的,我给网址下的另外一个地方发请求跟我给网址下的其他地方发请求就相当于大家都在一个公司(同一个网址代表共一个公司)无所谓随便发没有限制, 但如果不是公司内部的,比如狗仔队拍照就会被限制,不是吴某凡的限制,(吴某凡 ≈ 服务端),而我们是从浏览器发送请求,我们得出公司的门,我们出公司门的这一刻就被卡住了,这就是同源安全策略。
那如果狗仔队想要拍吴某凡怎么办呢?
有两种办法:
- 自己写一个浏览器(想 peach)
- 先写申请打报告,获得对方服务器允许才行
CORS 头:
![image-20210919124910961](https://i-blog.csdnimg.cn/blog_migrate/fd57f4cfaf4af3ff061dc7db2e4ad710.png)
总体了解
Cross-Origin Resource Sharing (CORS)
![image-20210919125331021](https://i-blog.csdnimg.cn/blog_migrate/349d9b5e155ef5efa78798e4a289510f.png)
如果 domain-a.com 请求 domain-a.com 的资源是同源请求,但是如果 domain-a.com 请求 domain-b.com 就是跨域请求了,此时的 Web document 就相当于是用户代理,作为 domain-a.com 的代理去请求 domain-b.com
User-Agent
我们接着往下看
(4)简单请求
![image-20210919130031498](https://i-blog.csdnimg.cn/blog_migrate/7e478b381dfa37584e60a9695e25b443.png)
简单请求我们通过原来的在 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](https://i-blog.csdnimg.cn/blog_migrate/1b70de4c31895c05775e35ad2720cc02.png)
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
- url路径部分第一个/往前,所有任何地方不一样都是不同源。以下都是不同源
(2)简单请求与复杂请求
简单请求直接发给服务器,如果服务器允许就直接执行
复杂请求先发给服务器一个
预检请求
,服务器同意跨域以后,浏览器再发真实请求详细看:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
-
什么简单请求:请求违背下面任何一个都不是简单请求
- 请求方式以下的某种
- 请求头是以下几种
Accept
Accept-Language
Content-Language
Content-Type
the additional requirements below)Content-Type
必须是以下几种application/x-www-form-urlencoded
multipart/form-data
text/plain
我们的 Content-Type 是 json,不属于简单请求中 content-type 指定的类型
点进来发现 request method 竟然是 option,可我们发的明明是 post,其实是因为这个请求是预检请求,说明此请求是复杂请求
![image-20211208000540229](https://i-blog.csdnimg.cn/blog_migrate/5e01cbbb17f3e130f9cb882f865177a8.png)
复杂请求先 preflight ;发送预检请求。
https://developer.mozilla.org/zh-CN/docs/Glossary/CORS
![image-20210802103623104](https://i-blog.csdnimg.cn/blog_migrate/a006026cf5e0c68efecf5f30a41e6941.png)
所以我们需要额外设置一个 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-20211208000952770](https://i-blog.csdnimg.cn/blog_migrate/005bf7631712845ff7156f020f707323.png)
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](https://i-blog.csdnimg.cn/blog_migrate/5a65b7258e13b41a136b1635f415e8e6.png)
看一下它的 view source 数据
![image-20210919135703078](https://i-blog.csdnimg.cn/blog_migrate/9e3d7b80ee2fe5de012fc67f35963363.png)
对比着看
(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 +
'}';
}
}
先启动测试一下
前端提交的数据过来了
(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> {
}
测试看一下效果
保存成功
数据库
base_attr_info
![image-20210919142939873](https://i-blog.csdnimg.cn/blog_migrate/eb6f626176f1c8158bec7aa69a17dff2.png)
base_attr_value
![image-20210919143019507](https://i-blog.csdnimg.cn/blog_migrate/6f9c7628dcfde39376397778bec38975.png)
三、为什么Mapper下的xml能被自动扫描
因为在 service-product 的 application.yaml 文件中有一个默认配置
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
这个配置是默认的,不配置都行,表示在任意类路径下的 mapper 下的 .xml 文件
![image-20210919143707011](https://i-blog.csdnimg.cn/blog_migrate/6f6046b8c816de33c0fc927534ba994d.png)
我们把 XXXMapper.xml 放在 resources 下的 mapper 文件中就可以被扫描到,如果把 XXXMapper.xml 配置在 com.atguigu.gmall.product.mapper 下还要额外配置一些其他的东西,更加麻烦一些
还要在 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](https://i-blog.csdnimg.cn/blog_migrate/997d5b5164b4729906631d710b077b20.png)
五、修改平台属性
文档
接口 | 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> {
}
运行效果:
控制台 zipkin 报错
先把 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
//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;
}
}
运行效果
七、MinIo
http://docs.minio.org.cn/docs/master/java-client-quickstart-guide
![image-20210919165044123](https://i-blog.csdnimg.cn/blog_migrate/6b218191de4ca1bff146467cdf83787e.png)
测试 demo
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](https://i-blog.csdnimg.cn/blog_migrate/1cbf804151e4dc9385afebb33975ce77.png)
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、如何把配置和配置类的属性绑定起来呢?
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](https://i-blog.csdnimg.cn/blog_migrate/4b10a4bf8d0bc1b6b620a15f1de110ae.png)
7、单元测试
https://start.aliyun.com/
阿里云 Java 工程脚手架
拿到 Maven 坐标
![image-20210919182821139](https://i-blog.csdnimg.cn/blog_migrate/5aae407f945efde5497900c82a1d716e.png)
为了方便直接放在 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
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](https://i-blog.csdnimg.cn/blog_migrate/0e7ad9064bd2440efaab05d9abafcb7c.png)
对应的 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](https://i-blog.csdnimg.cn/blog_migrate/eb19605a6120631db9e07a13b8c24c23.png)
![image-20210919190343080](https://i-blog.csdnimg.cn/blog_migrate/e070e5bcea74dce34c38b3fad9d81753.png)