本文很多的东西都来自这位大佬的文档
我的仓库地址,里面有很多好东西
我使用的vagrant是我自己打包好的,可观看该博客自定义box,或者在博客最下面找到云盘地址
创建模块gulimall-coupon、gulimall-member、gulimall-order、gulimall-product、gulimall-ware
创建虚拟机(建议使用vagrant),在里面安装docker。并使用docker 运行mysql、redis、nacos。
vagrant up
就会搭建好环境。clone 人人开源,完成前后端的搭建,以及逆向生成代码。
git clone [email protected]:renrenio/renren-generator.git
git clone [email protected]:renrenio/renren-fast-vue.git
git clone [email protected]:renrenio/renren-fast.git
初始化数据库。
bash db_init.sh
就会初始化好数据库环境。逆向工程生成 coupon、member、order、product、ware模块的代码。
给coupon、member、order、product、ware模块 配置数据库连接信息、nacos discovery 、 nacos config。
创建gateway 模块。
idea 的配置工具,能一次启动我们配置好的项目。
运行renren-fast
使用vscode 运行renren-fast-vue 项目。
需要先安装node.js 。这个软件会替我们安装npm 这个包管理工具。
安装运行步骤
# 首先把项目文件夹下的package.json里面的node-sass4.9.0改成4.9.2
$ pwd
/Users/haitao/Desktop/gulimall/renren-fast-vue
$ npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
$ npm install -f
$ npm run dev
关掉权限检查。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2XvM18wF-1592837435384)(./http://haitaoss-markdown.oss-cn-shenzhen.aliyuncs.com/2020/06/22/image20200622171248088.png)]
js同源策略
解决:在网关添加一个filter
@Configuration
public class GulimallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1、配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
renren-fast 也配置了同源策略,我们将它删除
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iYlVrTK7-1592837435389)(./http://haitaoss-markdown.oss-cn-shenzhen.aliyuncs.com/2020/06/22/image20200622171933143.png)]
终于顺利登陆了
@RestController
public class TestFeignController {
@Autowired
CouponFeignService couponFeignService;
@GetMapping("/test")
public R test() {
Map<String, Object> params = new HashMap<>();
R list = couponFeignService.list(params);
return R.ok().put("data", list);
}
}
//@FeignClient(value = "gulimall-coupon")
// 添加到IOC 容器中,如果当前包与主启动类同包或者子包下。就能被扫描然后加入IOC 容器中
// 为了以防万一,我们可以在主启动类上特定指定一下feign接口的包路径
@FeignClient(value = "gulimall-gateway")
public interface CouponFeignService {
/**
* 我们给方法传递的参数会转换为json 封装到请求体中。
* 目标服务想获取我们传递的数据得从请求体中获取,所以得使用 @RequestBody 注解
*/
@RequestMapping("/api/coupon/coupon/list")
public R list(@RequestBody Map<String, Object> params);
}
Springcloud 版本选择
https://spring.io/projects/spring-cloud-alibaba
Spring Cloud Version | Spring Cloud Alibaba Version | Spring Boot Version |
---|---|---|
-------- | -------- | -------- |
Spring Cloud Greenwich | 2.1.x.RELEASE | 2.1.x.RELEASE |
Spring Cloud Finchley | 2.0.x.RELEASE | 2.0.x.RELEASE |
Spring Cloud Edgware | 1.5.x.RELEASE | 1.5.x.RELEASE |
https://spring.io/projects/spring-cloud
Release Train | Boot Version |
---|---|
Hoxton | 2.2.x |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
$ docker run --name nacos01 -d \
-p 8848:8848 \
--privileged=true \
--restart=always \
-e JVM_XMS=512m \
-e JVM_XMX=2048m \
-e MODE=standalone \
-e PREFER_HOST_MODE=hostname \
nacos/nacos-server:1.1.4
引入依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
编写配置文件将服务注册到nacos中
spring:
# nacos
cloud:
nacos:
discovery:
server-addr: 192.168.1.10:8848
# 不指定名字无法注册进nacos
application:
name: gulimall-coupon
在启动类上加上这个注解开启服务发现的功能
@SpringBootApplication
@EnableDiscoveryClient
public class GulimallCouponApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallCouponApplication.class, args);
}
}
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
首先,修改 pom.xml 文件,引入 Nacos Config Starter。
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
在应用的 /src/main/resources/bootstrap.properties 配置文件中配置 Nacos Config 元数据
spring.application.name=nacos-config-example
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
完成上述两步后,应用会从 Nacos Config 中获取相应的配置,并添加在 Spring Environment 的 PropertySources 中。这里我们使用 @Value 注解来将对应的配置注入到 SampleController 的 userName 和 age 字段,并添加 @RefreshScope 打开动态刷新功能。配置的加载:如果配置中心和当前应用的配置文件中都配置了相同的项,优先使用配置中心的配置。
@RefreshScope
class SampleController {
@Value("${user.name}")
String userName;
@Value("${user.age}")
int age;
}
# 这么配置默认找的是 public->DEFAULT->appName.properties 文件(命名空间、组、文件名)
spring.application.name=appName
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=192.168.1.10:8848
spring.cloud.nacos.config.namespace=b4e817d4-8bdd-4e13-a917-14c8abef6296
# 这个是默认配置文件的加载,默认的DEFAULT ,加载的是组里面的 ${spring.application.name}.properties 文件
spring.cloud.nacos.config.group=prod
# 扩展配置
# 读取 b4e817d4-8bdd-4e13-a917-14c8abef6296->prood->datasource.yml
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=prod
spring.cloud.nacos.config.ext-config[0].refresh=true
# 读取 b4e817d4-8bdd-4e13-a917-14c8abef6296->prood->mybatis.yml
spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=prod
spring.cloud.nacos.config.ext-config[1].refresh=true
# 读取 b4e817d4-8bdd-4e13-a917-14c8abef6296->prood->other.yml
spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=prod
spring.cloud.nacos.config.ext-config[2].refresh=true
2、细节
* 1)、命名空间:配置隔离;
* 默认:public(保留空间);默认新增的所有配置都在public空间。
* 1、开发,测试,生产:利用命名空间来做环境隔离。
* 注意:在bootstrap.properties;配置上,需要使用哪个命名空间下的配置,
* spring.cloud.nacos.config.namespace=9de62e44-cd2a-4a82-bf5c-95878bd5e871
* 2、每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置
*
* 2)、配置集:所有的配置的集合,说白了就是我们在nacos里面写个每一个配置文件。
*
* 3)、配置集ID:类似文件名。
* Data ID:类似文件名
*
* 4)、配置分组:
* 默认所有的配置集都属于:DEFAULT_GROUP;
* 1111,618,1212
*
* 项目中的使用:每个微服务创建自己的命名空间,使用配置分组区分环境,dev,test,prod
*
* 3、同时加载多个配置集
* 1)、微服务任何配置信息,任何配置文件都可以放在配置中心中
* 2)、只需要在bootstrap.properties说明加载配置中心中哪些配置文件即可
* 3)、@Value,@ConfigurationProperties。。。
* 以前SpringBoot任何方法从配置文件中获取值,都能使用。
* 配置中心有的优先使用配置中心中的,
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.1.10:8848
application:
name: gulimall-member
@FeignClient(name = "gulimall-coupon" )
public interface CouponFeignService {
@GetMapping("/coupon/coupon/member/list" )
public R memberCoupons();
}
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign" )
public class GulimallMemberApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallMemberApplication.class, args);
}
}
定义一个接口,这个接口必须添加到IOC容器中。
使用@FeignClient 注解申明这是一个feign 接口
如果当前接口所在的包与主启动类同包或者子包下。就能被扫描然后加入IOC 容器中
但是为了省事,我们最好是在主启动类上特定指定一下feign接口的包路径@EnableFeignClients(basePackages = {"com.atguigu.gulimall.product.feign"})
feign 接口远程调用服务过程中,是将数据放到请求体中,所以服务的提供放获取参数的使用必须使用@RequestBody 注解,表示从请求体中获取数据。
网关的作用:鉴权、限流、日志输出、隐藏微服务
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/
ServerWebExchange
. This lets you match on anything from the HTTP request, such as headers or parameters.GatewayFilter
that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request.断言的编写
filter的编写
gateway:route、predicate、filter
流程:请求到达网关,网关通过断言来判断我们的请求是否符合某个路由规则。如果符合了就按照路由规则路由到指定的地方。到指定地方的过程中我们可以进行过滤,请求的响应结果也会经过过滤。在过滤环节我们可以添加、修改请求体或者响应体的信息。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
spring:
cloud:
# 网关的配置
gateway:
routes:
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?>/?.*), /renren-fast/$\{
segment}
SpringBoot 已经为我们提供了跨域请求的filter,我们拿来用即可。
@Configuration
public class GulimallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1、配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
文档地址
:data -> v-bind:data // array类型,就是tree数据
:props="defaultProps" // array 里面的每一个元素,主要是确定元素的children 和 节点显示的名字
node-key="catId" // 唯一表示,设置主键字段即可
:expand-on-click-node="false" // 点击的时候不展开,只有点击展开按钮才展开
show-checkbox // 显示复选框
:draggable="draggable" // 是否可以拖拽,这里绑定一个属性
:allow-drop="allowDrop" // 拖拽时判定目标节点能否被放置
@node-drop="handleDrop" // 拖拽成功触发的函数
ref="menuTree" // 获取tree 里面选中的节点的时候,需要这个tree 的引用
<el-tree
:data="menu"
:props="defaultProps"
node-key="catId"
:expand-on-click-node="false"
:default-expanded-keys="expandedKey"
show-checkbox
:draggable="draggable"
:allow-drop="allowDrop"
@node-drop="handleDrop"
ref="menuTree"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{
{ node.label }}span>
<span>
<el-button v-if="node.level <=2 " type="text" size="mini" @click="() => append(data)">Appendel-button>
<el-button
v-if="data.children.length==0"
type="text"
size="mini"
@click="() => remove(node, data)"
>Deleteel-button>
span>
span>
el-tree>
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
data | 展示数据 | array | — | — |
node-key | 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 | String | — | — |
props | 配置选项,具体看下表 | object | — | — |
show-checkbox | 节点是否可被选择 | boolean | — | false |
default-checked-keys | 默认勾选的节点的 key 的数组 | array | — | — |
draggable | 是否开启拖拽节点功能 | boolean | — | false |
allow-drop | 拖拽时判定目标节点能否被放置。type 参数有三种情况:‘prev’、‘inner’ 和 ‘next’,分别表示放置在目标节点前、插入至目标节点和放置在目标节点后 |
Function(draggingNode, dropNode, type) | — | — |
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
label | 指定节点标签为节点对象的某个属性值 | string, function(data, node) | — | — |
children | 指定子树为节点对象的某个属性值 | string | — | — |
Tree
内部使用了 Node 类型的对象来包装用户传入的数据,用来保存目前节点的状态。 Tree
拥有如下方法:
方法名 | 说明 | 参数 |
---|---|---|
getCheckedKeys | 若节点可被选择(即 show-checkbox 为 true ),则返回目前被选中的节点的 key 所组成的数组 |
(leafOnly) 接收一个 boolean 类型的参数,若为 true 则仅返回被选中的叶子节点的 keys,默认值为 false |
event
node-drop | 拖拽成功完成时触发的事件 | 共四个参数,依次为:被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置(before、after、inner)、event |
---|---|---|
Element ui 确认消息弹框https://element.eleme.cn/#/zh-CN/component/message-box
消息提示https://element.eleme.cn/#/zh-CN/component/message
对话框(添加的时候,里面可以放表单)https://element.eleme.cn/#/zh-CN/component/dialog
开关https://element.eleme.cn/#/zh-CN/component/switch
el-switch>
开关触发Events
事件名称 | 说明 | 回调参数 |
---|---|---|
change | switch 状态发生变化时的回调函数 | 新状态的值 |
node-click | 节点被点击时的回调 | 共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 |
按钮https://element.eleme.cn/#/zh-CN/component/button
表格自定义列模板https://element.eleme.cn/#/zh-CN/component/table
<el-table-column prop="showStatus" header-align="center" align="center" label="显示状态">
<template slot-scope="scope"> // 就是定义变量名字
<el-switch v-model="scope.row.showStatus" active-color="#13ce66" inactive-color="#ff4949">el-switch> // 通过变量取值
template>
el-table-column>
form 表单自定义校验规则
form 表单级联选择器
文件上传https://element.eleme.cn/#/zh-CN/component/upload
导入elementui
页面布局
阿里云 oss object storage service (OSS) 对象存储服务
java sdk 参考
简单上传demo
alibaba-oss
endpoint的取值:
accessKeyId和accessKeySecret需要创建一个RAM账号:
创建用户完毕后,会得到一个“AccessKey ID”和“AccessKeySecret”,然后复制这两个值到代码的“AccessKey ID”和“AccessKeySecret”。
另外还需要添加访问控制权限:
oss 跨域请求
在Maven项目中加入依赖项(推荐方式)
在 Maven 工程中使用 OSS Java SDK,只需在 pom.xml 中加入相应依赖即可。以 3.8.0 版本为例,在 内加入如下内容:
<dependency>
<groupId>com.aliyun.ossgroupId>
<artifactId>aliyun-sdk-ossartifactId>
<version>3.8.0version>
dependency>
以下代码用于上传文件流:
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
// 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
String accessKeyId = "" ;
String accessKeySecret = "" ;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上传文件流。
InputStream inputStream = new FileInputStream("" );
ossClient.putObject("" , "" , inputStream);
// 关闭OSSClient。
ossClient.shutdown();
更为简单的使用方式,是使用SpringCloud Alibaba
详细使用方法,见: https://help.aliyun.com/knowledge_detail/108650.html
(1)添加依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alicloud-ossartifactId>
<version>2.2.0.RELEASEversion>
dependency>
(2)创建“AccessKey ID”和“AccessKeySecret”
(3)配置key,secret和endpoint相关信息
access-key: LTAI4G4W1RA4JXz2QhoDwHhi
secret-key: R99lmDOJumF2x43ZBKT259Qpe70Oxw
oss:
endpoint: oss-cn-shanghai.aliyuncs.com
(4)注入OSSClient并进行文件上传下载等操作
但是这样来做还是比较麻烦,如果以后的上传任务都交给gulimall-product来完成,显然耦合度高。最好单独新建一个Module来完成文件上传任务。
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alicloud-ossartifactId>
<version>2.2.0.RELEASEversion>
dependency>
<dependency>
<groupId>com.bigdata.gulimallgroupId>
<artifactId>gulimall-commonartifactId>
<version>1.0-SNAPSHOTversion>
<exclusions>
<exclusion>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
exclusion>
exclusions>
dependency>
另外也需要在“pom.xml”文件中,添加如下的依赖管理
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.1.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
@EnableDiscoveryClient
采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。因此,OSS提供了服务端签名后直传的方案。
服务端签名后直传的原理如下:
阿里云对象存储一服务端签名后直传
在Java中提供了一系列的校验方式,它这些校验方式在“javax.validation.constraints”包中,提供了如@Email,@NotNull等注解。
在非空处理方式上提供了@NotNull,@Blank和@
(1)@NotNull
The annotated element must not be null. Accepts any type.
注解元素禁止为null,能够接收任何类型
(2)@NotEmpty
the annotated element must not be null nor empty.
该注解修饰的字段不能为null或""
Supported types are:
支持以下几种类型
CharSequence (length of character sequence is evaluated)
字符序列(字符序列长度的计算)
Collection (collection size is evaluated)
集合长度的计算
Map (map size is evaluated)
map长度的计算
Array (array length is evaluated)
数组长度的计算
(3)@NotBlank
The annotated element must not be null and must contain at least one non-whitespace character. Accepts CharSequence.
该注解不能为null,并且至少包含一个非空白字符。接收字符序列。
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
想要自定义错误消息,可以覆盖默认的错误提示信息,如@NotBlank的默认message是
public @interface NotBlank {
String message() default "{javax.validation.constraints.NotBlank.message}";
可以在添加注解的时候,修改message:
@NotBlank(message = "品牌名必须非空")
private String name;
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
if( result.hasErrors()){
Map<String,String> map=new HashMap<>();
//1.获取错误的校验结果
result.getFieldErrors().forEach((item)->{
//获取发生错误时的message
String message = item.getDefaultMessage();
//获取发生错误的字段
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交的数据不合法").put("data",map);
}else {
}
brandService.save(brand);
return R.ok();
}
这种是针对于该请求设置了一个内容校验,如果针对于每个请求都单独进行配置,显然不是太合适,实际上可以统一的对于异常进行处理。
可以使用SpringMvc所提供的@ControllerAdvice,通过“basePackages”能够说明处理哪些路径下的异常。
(1)抽取一个异常处理类
package com.bigdata.gulimall.product.exception;
import com.bigdata.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 集中处理所有异常
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.bigdata.gulimall.product.controller")
public class GulimallExceptionAdvice {
@ExceptionHandler(value = Exception.class)
public R handleValidException(MethodArgumentNotValidException exception){
Map<String,String> map=new HashMap<>();
BindingResult bindingResult = exception.getBindingResult();
bindingResult.getFieldErrors().forEach(fieldError -> {
String message = fieldError.getDefaultMessage();
String field = fieldError.getField();
map.put(field,message);
});
log.error("数据校验出现问题{},异常类型{}",exception.getMessage(),exception.getClass());
return R.error(400,"数据校验出现问题").put("data",map);
}
}
(2)默认异常处理
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("未知异常{},异常类型{}",throwable.getMessage(),throwable.getClass());
return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),BizCodeEnum.UNKNOW_EXEPTION.getMsg());
}
如:指定在更新和添加的时候,都需要进行校验
@NotEmpty
@NotBlank(message = "品牌名必须非空",groups = {
UpdateGroup.class,AddGroup.class})
private String name;
在这种情况下,没有指定分组的校验注解,默认是不起作用的。想要起作用就必须要加groups。
@Validated的value方法:
Specify one or more validation groups to apply to the validation step kicked off by this annotation.
指定一个或多个验证组以应用于此注释启动的验证步骤。
JSR-303 defines validation groups as custom annotations which an application declares for the sole purpose of using
them as type-safe group arguments, as implemented in SpringValidatorAdapter.
JSR-303 将验证组定义为自定义注释,应用程序声明的唯一目的是将它们用作类型安全组参数,如 SpringValidatorAdapter 中实现的那样。
Other SmartValidator implementations may support class arguments in other ways as well.
其他SmartValidator 实现也可以以其他方式支持类参数。
@Documented
@Constraint(validatedBy = {
ListValueConstraintValidator.class})
@Target({
METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.bigdata.common.valid.ListValue.message}";
Class<?>[] groups() default {
};
Class<? extends Payload>[] payload() default {
};
int[] value() default {
};
}
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set=new HashSet<>();
@Override
public void initialize(ListValue constraintAnnotation) {
int[] value = constraintAnnotation.value();
for (int i : value) {
set.add(i);
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
@Constraint(validatedBy = {
ListValueConstraintValidator.class})
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(value = {
0,1},groups ={
AddGroup.class})
private Integer showStatus;
例子
iphoneX是SPU、MI8是SPU
iphoneX 64G黑曜石是 SKU
MI8 8+64G+黑色是 SKU
每个分类下的商品共享规格参数,与销售属性。只是有些商品不一定要用这个分类下全部的
属性;SPU 决定规格参数,SKU 决定销售属性
能完全决定库存与售价的叫sku(销售属性)。描述商品的是spu(规格参数).
同一个分类下的商品,spu的名字是相同的,只不过对应的值不同,有些商品可以没有值。
sku 包含 spu
spu 不包含 sku,而是所属与 sku
# 商品的分类与品牌(多对多)
pms_category
pms_brand
pms_category_brand_relation
# 属性:基本属性,销售属性,既是基本属性又是销售属性
# 属性组 (属性和属性组是多对多关系)
# 属性值
pms_attr (根据attr_type 区分是什么属性,里面定义了一个属性可能的所有值)
pms_attr_group
pms_attr_attrgroup_relation
pms_product_attr_value(因为每个产品的属性值不一样,所以单独抽出一个商品属性值表。)
# 商品的规格参数
pms_spu_info
pms_spu_info_desc
pms_spu_images
pms_spu_comment
# 真正到手的商品,sku 是包含spu的。
pms_sku_info
pms_sku_images
pms_sku_sale_attr_value
pms_comment_replay
@JsonInclude(JsonInclude.Include.NON_EMPTY) // 不为 "" 或者 null 才会转换为json
private List<CategoryEntity> children;
关于pubsub、publish报错,无法发送查询品牌信息的请求:
1、npm install --save pubsub-js
2、在src下的main.js中引用:
① import PubSub from ‘pubsub-js’
② Vue.prototype.PubSub = PubSub
P100点击规格找不到页面,解决:在数据库gulimall_admin执行以下sql再刷新页面即可:【INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num) VALUES (76, 37, ‘规格维护’, ‘product/attrupdate’, ‘’, 2, ‘log’, 0);】
1、分布式基础概念
2、基础开发
3、环境
4、开发规范
数据校验JSR303、全局异常处理、全局统一返回、全局跨域处理
数据为null 就不封装成json,发给客户端。
@JsonInclude(JsonInclude.Include.NON_EMPTY) // 不为 "" 或者 null 才会转换为json
private List<CategoryEntity> children;
枚举状态、业务状态码、VO与TO与PO划分、逻辑删除
Lombok:@Data、@Slf4j