谷粒商城后端项目开发
1.三级分类-逻辑删除(需要mybatis-plus支持)
---->对项目进行配置(yml举例)
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1,全局)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0,全局)
---->实体类字段上加上@TableLogic注解
//value:默认逻辑未删除值(局部,该值可无、会自动获取全局配置)
//delval:默认逻辑删除值(局部,该值可无、会自动获取全局配置)
@TableLogic(value = "1",delval = "0")
Integer show_status;
2.商品服务-品牌管理
---->页面新增品牌管理菜单
3.Aliyun Spring Boot OSS文件上传
---->导入pom文件
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
---->access-key,secret-key获取(创建完成后记得先保存)
---->配置application.properties
spring.cloud.alicloud.access-key= (自己的key)
spring.cloud.alicloud.secret-key= (自己的key)
spring.cloud.alicloud.oss.endpoint= (自己的endpoint)
---->在测试类中进行测试
@Autowired
private OSSClient ossClient;
@Test
public void saveFile() throws FileNotFoundException {
// download file to local
FileInputStream fileInputStream = new FileInputStream("E:\\0d40c24b264aa511.jpg");
ossClient.putObject("gulimall","0d40c24b264aa511.jpg",fileInputStream);
ossClient.shutdown();
System.out.println("上传成功");
}
---->在页面上确认是否上传成功
4.OSS获取服务端签名
---->创建gulimall-third-party微服务
---->导入common的pom并排除mybatis-plus
<dependency>
<groupId>com.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
---->在nacos-config中新增一个命名空间gulimall-third-party并新增oss.properties
spring.cloud.alicloud.access-key=xxxx
spring.cloud.alicloud.secret-key= xxxx
spring.cloud.alicloud.oss.endpoint= xxxx
---->创建OssController
@RestController
public class OssController {
@Autowired
OSS ossClient;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
@RequestMapping("/oss/policy")
public Map<String,String> policy() throws Exception {
Map<String, String> respMap= null;
String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
// String callbackUrl = "http://88.88.88.88:8888";
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format + "/"; // 用户上传文件时指定的前缀。
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
// PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap = new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
return respMap;
}
}
---->添加yml配置文件
spring:
cloud:
nacos:
discovery:
server-addr: xxxx:8848
alicloud:
access-key: xxxx
secret-key: xxxx
oss:
endpoint: oss-cn-beijing.aliyuncs.com
bucket: xxxx
application:
name: gulimall-third-party
server:
port: 20000
---->修改网关配置文件
spring:
cloud:
gateway:
routes:
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
---->访问测试
localhost:88/api/thirdparty/oss/policy
5.实现文件上传
---->编写前端页面
---->参考官方文档配置并修改CORS跨域权限
https://help.aliyun.com/document_detail/31926.html?spm=a2c4g.11174283.6.1568.5fb67da2s0gRxy
---->gulimall-third-party的OssController里的policy方法以R类型返回josn串
@RequestMapping("/oss/policy")
public R policy() throws Exception {
String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
// String callbackUrl = "http://88.88.88.88:8888";
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format + "/"; // 用户上传文件时指定的前缀。
Map<String, String> respMap= null;
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
// PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap = new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
return R.ok().put("data",respMap);
}
---->修改前端upload下的路径
–>src/components/upload下的multiUpload,singleUpload,policy.js的
Bucket路径为自己的路径
---->上传测试
6.JSR303后端校验
---->给Bean添加校验注解
javax.validation.constraints,并定义自己的message提示,举例:
/**
检索首字母
*/
@NotEmpty
@Pattern(regexp = "/^[a-zA-Z]$/]",message = "检索首字母必须为字母")
private String firstLetter;
---->开启校验功能
在controller需要校验的参数前加入@Valid
效果:校验参数会有默认响应
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){}
---->给校验的bean后紧跟一个BindingResult就可以获取到校验的结果
//获取校验的错误信息
result.getFieldErrors().forEach((item)->{
//获取错误提示
String message = item.getDefaultMessage();
//获取错误的属性名字
String field = item.getField();
});
---->在postman访问测试
localhost:88/api/product/brand/save
7.统一异常处理
---->编写exception.GulimallExceptionControllerAdvice
@Slf4j
@RestControllerAdvice(basePackages = "接收异常的地址全类名")
public class GulimallExceptionControllerAdvice {
//value:异常类型
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleException(MethodArgumentNotValidException ex){
BindingResult result = ex.getBindingResult();
HashMap<String, String> map = new HashMap<>();
result.getFieldErrors().forEach((fieldError)->{
map.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(),BizCodeEnum.VAILD_EXCEPTION.getMsg()).put("data",map);
}
}
---->在common下创建公用枚举异常类BizCodeEnum
–>自定义所有的异常消息,异常编码。
public enum BizCodeEnum {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验异常");
private int code;
private String msg;
BizCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
BizCodeEnum() { }
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMsg() { return msg; }
public void setMsg(String msg) { this.msg = msg; }}
---->测试
8.分组新增&级联选择器
---->在AttrGroupController里新增list方法
@RequestMapping("/list/{catelogId}")
public R list(@RequestParam Map<String, Object> params,@PathVariable("catelogId") Long catelogId){
PageUtils page = attrGroupService.queryPage(params,catelogId);
return R.ok().put("page", page);
}
---->创建queryPage的接口与实现类impl
@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
if (catelogId == 0){
IPage<AttrGroupEntity> page = this.page(
new Query<AttrGroupEntity>().getPage(params),
new QueryWrapper<AttrGroupEntity>()
);
return new PageUtils(page);
}else {
String key = (String) params.get("key");
QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId);
if (!StringUtils.isEmpty(key)){
wrapper.and((obj)->{
obj.eq("attr_group_id","key").or().like("attr_group_name",key);
});
}
IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),
wrapper);
return new PageUtils(page);
}
}
---->修改前端页面
---->在CategoryEntity增加新注解
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@TableField(exist = false)
private List<CategoryEntity> children;
9.品牌分类-修改(路径回显)
---->修改Controller
@RequestMapping("/info/{attrGroupId}")
//@RequiresPermissions("product:attrgroup:info")
public R info(@PathVariable("attrGroupId") Long attrGroupId){
AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);
Long catelogId = attrGroup.getCatelogId();
Long[] catelogPath = categoryService.findCatelogPath(catelogId);
attrGroup.setCatelogPath(catelogPath);
return R.ok().put("attrGroup", attrGroup);
}
---->新增findCatelogPath接口并添加实现类
@Override
public Long[] findCatelogPath(Long catelogId) {
List<Long> paths = new ArrayList<>();
List<Long> parentPath = findParentPath(catelogId, paths);
Collections.reverse(parentPath);
return (Long[]) parentPath.toArray(new Long[parentPath.size()]);
}
private List<Long> findParentPath(Long catelogId,List<Long> paths){
paths.add(catelogId);
CategoryEntity byId = this.getById(catelogId);
if (byId.getParentCid()!=0){
findParentPath(byId.getParentCid(),paths);
}
return paths;
}
---->测试
@Test
public void testFindPath(){
Long[] catelogPath = categoryService.findCatelogPath(225L);
log.info("完整路径:"+ Arrays.asList(catelogPath));
}