maven环境配置
settings配置文件
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
<profiles>
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>centeral</mirrorOf>
<name>Nexus aliyun</name>
<url>https://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
git提交代码排除无用文件
在总项目的.gitignore目录配置
项目地址
https://gitee.com/lds-pjy/gulimall.git
nacos
1注册中心步骤
1.导入依赖
<!--总文件进行版本控制,后续导入的组件无需再引入版本号-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--服务注册/发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.在.properties中配置nacos地址和服务名称
spring.application.name=renren-fast
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
3.在启动类使用@EnableDiscoveryClient注解开启服务注册与发现
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallCouponApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallCouponApplication.class, args);
}
}
4.启动nacos客户端(双击starup.cmd)
页面输入 :http://127.0.0.1:8848/nacos(默认账号密码:nacos,nacos)
5.给服务起名字
spring:
application:
name: gulimall-coupon
服务列表注册成功
2.配置中心
1.引入依赖
<!--配置中心来做配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2.编写bootstrap.properties文件(这个文件会比.properties文件先加载)
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
3.配置![](https://i-blog.csdnimg.cn/blog_migrate/28c6f94078a5c5be0098e3882a8cde17.png)
4.测试成功![](https://i-blog.csdnimg.cn/blog_migrate/4066507eeced6e69fbdedff099dfb343.png)
5.配置动态获取并刷新配置(controller类添加@RefreshScope注解)
6.如果配置中心和当前应用文件都配置了相同的项,优先使用配置中心的配置
3.细节
命名空间:用来做配置隔离的
默认是public空间,所有新增的配置默认都是在public空间
应用场景:环境隔离
应用场景:微服务相互隔离:每个服务使用自己的命名空间
可以直接克隆配置到不同的命名空间
配置集
一组相关或者不相关的集合叫做配置集:比如说yml文件有超多的配置,合起来就叫做配置集
配置集ID
对于idea来说,配置文件名称就是配置id
对于nacos来说,Data ID就是配置集id
配置分组
默认所有的配置集都属于:DEFAULT_GROUP
例子:双十一用1111组,618 用618组
修改bootstrap文件使用1111在
同时加载多个配置集
微服务任何配置信息、配置文件都可以放在nacos中
只需要在bootrap.properties说明加载配置中心哪些配置文件即可
配置中心有的优先使用配置中心的
步骤
openfeign
1.引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.编写一个接口,告诉SpringCloud这个接口需要调用远程服务
@FeignClient("gulimall-coupon"):远程接口所属的服务名称
@RequestMapping("/coupon/coupon/member/list") 别调用方法的全路径名
public R membercoupons(); 被调用的方法
3.启动类添加@EnableFeignClients注解![](https://i-blog.csdnimg.cn/blog_migrate/5bd330c59661c0c0cd1376c4a7afba03.png)
basePackages = "com.atguigu.gulimall.member.feign":自动扫描这个包下标注了@FeignClient注解的接口
4.调用成功
被调用方法
//controller上还有个@RequestMapping("coupon/coupon")路径
@RequestMapping("/member/list")
public R membercoupons(){
CouponEntity couponEntity = new CouponEntity();
couponEntity.setCouponName("满100减10");
return R.ok().put("coupons",Arrays.asList(couponEntity));
}
调用方法
@Autowired
CouponFeignService couponFeignService;
@RequestMapping("/coupons")
public R test(){
MemberEntity memberEntity = new MemberEntity();
memberEntity.setNickname("张三");
R membercoupons = couponFeignService.membercoupons();
return R.ok().put("member",memberEntity).put("coupons",membercoupons.get("coupons"));
}
成功调用
网关(Gateway)
简介
核心规则
当请求到达网关,网关先通过断言(Predicate)判断请求是不是符合某个路由(Route)规则,符合了就按照这个路由规则把它路由到指定的地方,但要去这些地方就要经过一系列的过滤器(filter)过滤
网关不需要数据源相关(排除数据源)
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
yml路由规则配置(小试牛刀)
spring:
cloud:
gateway:
routes:
- id: baidu_route #唯一id
uri: https://www.baidu.com #需要去访问的url
predicates: #断言规则
- Query=url,baidu
# 当请求的url满足断言(predicates)规则: url路径包含baidu的时候。就路由去访问uri后面的地址
- id: qq_route
uri: https://www.qq.com
predicates:
- Query=url,qq
# 当请求的url满足断言(predicates)规则: url路径包含qq的时候。就路由去访问uri后面的地址
三级分类![](https://i-blog.csdnimg.cn/blog_migrate/2bf7da7ce4e88bb4f91d511ece209381.png)
网关把所有请求都路由给renren-fast处理
spring: cloud: gateway: routes: - id: admin_route uri: lb://renren-fast #lb:负载均衡 满足断言规则就负载均衡到renren-fast服务 predicates: - Path:/api/** #前端发起的所有请求,只要带有api前缀都默认发送到renren-fast服务 filters: #过滤器 - RewritePath=/api/{?<segment>.*},/renren-fast/$\{segment} #把/api/{?<segment>.*}重写成/renren-fast/$\{segment}路径 # 当请求的url满足断言(predicates)规则: 请求带api/**。就路由去uri的地址,并且把地址重写成 /renren-fast/$\{segment}
跨域
OPTIONS预检请求,还未正式的进行登录
跨域流程![](https://i-blog.csdnimg.cn/blog_migrate/445ccf0a75e1efa738d9929a2105f3ed.png)
解决跨域
方案一![](https://i-blog.csdnimg.cn/blog_migrate/7b56476fca6ddfe1701f0506d8e09902.png)
把前端项目和后端项目都部署到nginx服务器,静态请求就直接代理给前端项目。后端 请求代理给网关,同意访问nginx的域名。实际项目中用这种方案有点麻烦。
方案二![](https://i-blog.csdnimg.cn/blog_migrate/2c2395d1cd782765fc3565c05fde3bc7.png)
因为路径都是由网关统一转发的,所以配置到网关里面
使用springboot的CorsWebFilter
@Configuration
public class GulimallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//配置跨域
//允许哪些头进行跨域
corsConfiguration.addAllowedHeader("*");
//允许哪些请求方式进行跨域:GET,POST...
corsConfiguration.addAllowedMethod("*");
//允许哪些请求来源跨域
corsConfiguration.addAllowedOrigin("*");
//是否允许携带cookie
corsConfiguration.setAllowCredentials(true);
//所有路径都需要跨域请求
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
mybatis-plus逻辑删除
第一步:配置全局的逻辑删除规则(3.1.1后可省略)
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
第二步:配置逻辑删除的组件bean(3.1.1后可省略)
第三步:给Bean加上逻辑删除注解
/**
* 是否显示[0-不显示,1显示]
*/
@TableLogic(value = "1",delval = "0")
private Integer showStatus;
JSR303数据校验
实体字段校验 @NotNull、@NotEmpty、@NotBlank(区别)
@NotNull
不能为 null,但可以为 empty,一般用在 Integer 类型的基本数据类型的非空校验上,而且被其标注的字段可以使用 @size、@Max、@Min 对字段数值进行大小的控制
.@NotEmpty
不能为 null,且长度必须大于 0,一般用在集合类上或者数组上
@NotBlank
只能作用在接收的 String 类型上,注意是只能,不能为 null,而且调用 trim() 后,长度必须大于 0即:必须有实际字符
注意:
注意在使用 @NotBlank 等注解时,一定要和 @valid 一起使用,否则 @NotBlank 不起作用。
@NotBlank
private String name;@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/*,BindingResult result*/){
一个 BigDecimal 的字段使用字段校验标签应该为 @NotNull。
在使用 @Length 一般用在 String 类型上可对字段数值进行最大长度限制的控制。
在使用 @Range 一般用在 Integer 类型上可对字段数值进行大小范围的控制。
例子:
1.String name = null;
@NotNull: false
@NotEmpty:false
@NotBlank:false2.String name = "";
@NotNull:true
@NotEmpty: false
@NotBlank: false3.String name = "(空格)";
@NotNull: true
@NotEmpty: true
@NotBlank: false4.String name = "Hello World!";
@NotNull: true
@NotEmpty:true
@NotBlank:true
使用步骤
1)、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
@NotBlank(message = "品牌名必须提交")
2)、Controller开启校验功能@Valid
3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand,BindingResult result){
if(result.hasErrors()){
Map<String,String> map = new HashMap<>();
//1、获取校验的错误结果
result.getFieldErrors().forEach((item)->{
//FieldError 获取到错误提示
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();
}
统一异常处理 (@RestControllerAdvice)
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
/*
能精确捕捉到的异常就走这个方法捕获,不能精确的走下面大方法捕获
*/
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleVaildException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError)->{
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("错误:",throwable);
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
错误码定义
枚举类定义
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
*/
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败");
private final int code;
private final String msg;
BizCodeEnume(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
spu与sku
spu:比如小米八
sku:小米八的64G,128G,黑色,白色等具体维度
同一个spu下的多个sku:共享商品介绍、规格与包装
表设计
属性表
属性分组表
属性分组中间表
商品属性值表
spu信息表
总览
@JsonInclude注解
@JsonInclude(JsonInclude.Include.NON_EMPTY):为空的时候不返回给前端
@TableField注解
@TableField(exist = false):标注该字段非数据库字段
mybatis-puls分页插件
@Configuration
@EnableTransactionManagement //开启事务
@MapperScan("com.atguigu.gulimall.product.dao")
public class MyBatisConfig {
//引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}
分页代码
@Override
public PageUtils queryPage(Map<String, Object> params) {
String key = params.get("key").toString();
QueryWrapper<BrandEntity> wrapper = new QueryWrapper<>();
if (!StringUtils.isEmpty(key)){
wrapper.like("name",key).or().eq("bran_id",key);
}
IPage<BrandEntity> page = this.page(
new Query<BrandEntity>().getPage(params),
wrapper
);
return new PageUtils(page);
}
@PathVariable和@RequestParam区别
总的来说:
@pathvariable 接收url路径上的参数
@RequestParam 接收参数请求的params