13.后端标准化项目构建

1、数据库设计

1.1、准备工作

1、新建一个数据库 icoding_mall
2、导入素材中的icoding_mall.sql文件
3、分析数据库中表的关系

1.2、数据库设计规约

注意:数据库设计规约并不是数据库设计的严格规范,根据不同团队的不同要求设计。

以下规约只针对本模块,更全面的文档参考《阿里巴巴Java开发手册》

  1. 库名与应用名称尽量一致
  2. 表名、字段名必须使用小写字母或数字,禁止出现数字开头
  3. 表名不使用复数名词
  4. 表的命名最好是加上“业务名称_表的作用”。如,edu_teacher
  5. 表必备三字段:id, gmt_create, gmt_modified
其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。
(如果使用分库分表集群部署,则id类型为varchar,非自增,业务中使用分布式id生成器)
gmt_create, gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被动更新。
  1. 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
    说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
  2. 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0表示否)。
说明:任何字段如果为非负数,必须是 unsigned。
注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀。
数据库表示是与否的值,使用 tinyint类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。
正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除
  1. 小数类型为 decimal,禁止使用 float 和 double。
    说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
  2. 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
  3. varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
  4. 唯一索引名为 uk字段名;普通索引名则为 idx字段名。
    说明:uk_ 即 unique key;idx_ 即 index 的简称
  5. 不得使用外键与级联,一切外键概念必须在应用层解决。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。

2、基础项目创建

2.1、创建项目

1、创建项目,在工作目录下新建一个文件夹 icoding_mall
2、在idea中选择 File - Open,选择这个文件夹
3、配置JDK

2.2、创建父工程imall-parent

1、创建父工程,在项目 icoding_mall 下创建模块:使用 Spring Initializr 快速初始化一个 Spring Boot模块
在这里插入图片描述
2、删除多余的文件
3、配置 pom.xml
4、修改版本为:2.0.9.RELEASE
5、< artifactId > 节点后面添加`

<packaging>pom</packaging>

6、配置< properties >

<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	<java.version>1.8</java.version>
	
	<!--Dubbo-zookeeper-->
	<dubbo-starter.version>2.7.3</dubbo-starter.version>
	<zkclient.version>0.11</zkclient.version>
	<curator-framework.version>2.12.0</curator-framework.version>
	<curator-recipes.version>2.12.0</curator-recipes.version>
	<zookeeper.version>3.4.14</zookeeper.version>
	
	<!--mybatis-plus-swagger-->
	<mybatis-plus.version>3.0.5</mybatis-plus.version>
	<velocity.version>2.0</velocity.version>
	<swagger.version>2.7.0</swagger.version>
</properties>

7、配置 < dependencyManagement > 锁定依赖的版本,管理版本号,不下载对应的包

<dependencyManagement>
	<dependencies>
		<!--Dubbo-zookeeper-->
		<dependency>
			<groupId>com.101tec</groupId>
			<artifactId>zkclient</artifactId>
			<version>${zkclient.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo-spring-boot-starter</artifactId>
			<version>${dubbo-starter.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-framework</artifactId>
			<version>${curator-framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
			<version>${curator-recipes.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.zookeeper</groupId>
			<artifactId>zookeeper</artifactId>
			<version>${zookeeper.version}</version>
		</dependency>
		
		<!--mybatis-plus-swagger-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>${mybatis-plus.version}</version>
		</dependency>

		<!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
		<dependency>
			<groupId>org.apache.velocity</groupId>
			<artifactId>velocity-engine-core</artifactId>
			<version>${velocity.version}</version>
		</dependency>

		<!--swagger-->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>${swagger.version}</version>
		</dependency>

		<!--swagger ui-->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>${swagger.version}</version>
		</dependency>
	</dependencies>
</dependencyManagement>

2.3、创建common模块

1、创建一个普通maven模块 imall-common,父项目Parent:imall-parent
2、配置pom.xml


<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-json</artifactId><optional>true</optional>
	</dependency>

	<!--swagger-->
	<dependency>
		<groupId>io.springfox</groupId>
		<artifactId>springfox-swagger2</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>io.springfox</groupId>
		<artifactId>springfox-swagger-ui</artifactId>
		<optional>true</optional>
	</dependency>

	<!--lombok用来简化实体类:需要安装lombok插件-->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>
	
	<!--mybatis-plus-->
	<dependency>
		<groupId>com.baomidou</groupId>
		<artifactId>mybatis-plus-boot-starter</artifactId>
		<optional>true</optional>
	</dependency>
</dependencies>
	

2.4、创建product模块

1、创建一个普通maven模块 imall-product,父项目Parent:imall-parent
2、配置pom.xml

<dependencies>
	<dependency>
		<groupId>com.coding</groupId>
		<artifactId>imall-common</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
		
	<!--mybatis-plus-->
	<dependency>
		<groupId>com.baomidou</groupId>
		<artifactId>mybatis-plus-boot-starter</artifactId>
	</dependency>

	<!--mysql-->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>

	<!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
	<dependency>
		<groupId>org.apache.velocity</groupId>
		<artifactId>velocity-engine-core</artifactId>
	</dependency>
	
	<!--swagger-->
	<dependency>
		<groupId>io.springfox</groupId>
		<artifactId>springfox-swagger2</artifactId>
	</dependency>
	<dependency>
		<groupId>io.springfox</groupId>
		<artifactId>springfox-swagger-ui</artifactId>
	</dependency>
	
	<!--lombok用来简化实体类:需要安装lombok插件-->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
	</dependency>
	
	<!--开发者工具-->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-devtools</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

3、配置application.properties文件,resources目录下创建文件 application.properties

# 服务端口
server.port=8110
# 服务名
spring.application.name=imall-product
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/icoding_mall?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
# Hikari是Spring Boot 2.0之后默认整合的数据库连接池,比druid更快的数据库连接池# 数据源类型
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
# 连接池名称,默认HikariPool-1
spring.datasource.hikari.pool-name=CodingHikariPool
# 最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值
spring.datasource.hikari.maximum-pool-size=12
# 连接超时时间:毫秒,小于250毫秒,否则被重置为默认值30秒
spring.datasource.hikari.connection-timeout=60000
# 最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size
spring.datasource.hikari.minimum-idle=10
# 空闲连接超时时间,默认值600000(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且小于10秒,会被重置为10秒。
# 只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放
spring.datasource.hikari.idle-timeout=500000
# 连接最大存活时间.不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
spring.datasource.hikari.max-lifetime=540000
#连接测试查询
spring.datasource.hikari.connection-test-query=SELECT 1
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4、创建mybatis-plus代码生成器
在test/java目录下创建代码生成器:CodeGenerator.java


import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;
import java.util.ArrayList;
public class CodeGenerator {
	@Test
	public void genCode() {
	//数据库名称 icoding_mall
	//模块名 mall
	String moduleName = "mall";
	
	// 1、创建代码生成器
	AutoGenerator mpg = new AutoGenerator();
	
	// 2、全局配置
	GlobalConfig gc = new GlobalConfig();
	String projectPath = System.getProperty("user.dir");
	gc.setOutputDir(projectPath + "/src/main/java");
	gc.setAuthor("Coding");
	gc.setOpen(false); //生成后是否打开资源管理器
	gc.setFileOverride(false); //重新生成时文件是否覆盖
	gc.setServiceName("%sService"); //去掉Service接口的首字母I
	gc.setIdType(IdType.ID_WORKER_STR); //主键策略
	gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
	gc.setSwagger2(true);//开启Swagger2模式
	mpg.setGlobalConfig(gc);
	
	// 3、数据源配置
	DataSourceConfig dsc = new DataSourceConfig();
	dsc.setUrl("jdbc:mysql://localhost:3306/icoding_" + moduleName+"?
	characterEncoding=utf-8&useSSL=false");
	dsc.setDriverName("com.mysql.jdbc.Driver");
	dsc.setUsername("root");
	dsc.setPassword("123456");

	dsc.setDbType(DbType.MYSQL);
	mpg.setDataSource(dsc);
	
	// 4、包配置
	PackageConfig pc = new PackageConfig();
	pc.setModuleName(moduleName); //模块名
	pc.setParent("com.coding");
	pc.setController("controller");
	pc.setEntity("entity");
	pc.setService("service");
	pc.setMapper("mapper");
	mpg.setPackageInfo(pc);
	
	// 5、策略配置
	StrategyConfig strategy = new StrategyConfig();
	strategy.setInclude("im_\\w*"); //设置要映射的表名
	strategy.setNaming(NamingStrategy.underline_to_camel); //数据库表映射到实体的命名策略
	strategy.setTablePrefix("im_"); //设置表前缀不生成
	strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
	strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain= true) setter链式操作
	strategy.setLogicDeleteFieldName("is_delete");//逻辑删除字段名
	strategy.setEntityBooleanColumnRemoveIsPrefix(true);//去掉布尔值的is_前缀
	
	//自动填充
	TableFill gmtCreate = new TableFill("create_time",FieldFill.INSERT);
	TableFill gmtModified = new TableFill("update_time",FieldFill.INSERT_UPDATE);
	ArrayList<TableFill> tableFills = new ArrayList<>();
	tableFills.add(gmtCreate);
	tableFills.add(gmtModified);
	strategy.setTableFillList(tableFills);
	strategy.setVersionFieldName("version");//乐观锁列
	strategy.setRestControllerStyle(true); //restful api风格控制器
	strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
	mpg.setStrategy(strategy);
	
	// 6、执行
	mpg.execute();
	}
}

执行代码生成器方法
XxxServiceImpl 继承了 ServiceImpl 类,并且mybatis-plus为我们注入了 XxxMapper
这样可以使用 service 层默认为我们提供的很多方法,当然也可以调用我们自己在 dao 层编写的方法。
说明,如果出错了,查看一些IDEA项目结构,是否有包未导入

2.5、产品列表查询

1、在controller包下创建admin包,创建ProductAdminController.java

package com.coding.mall.controller.admin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/admin/mall/product")
public class ProductAdminController {
}

2、RestFul 获取所有产品列表

@Autowired
private ProductService productService;
@GetMapping
public List<Product> list(){
	return productService.list(null);
}

3、在mall包下创建config包,创建MyBatisPlusConfig.java

package com.coding.mall.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@MapperScan("com.coding.mall.mapper")
public class MyBatisPlusConfig {
}

4、配置相关插件

//SQL 执行性能分析,插件开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
	PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
	performanceInterceptor.setMaxTime(1000);//ms,超过此处设置的ms则sql不执行

	performanceInterceptor.setFormat(true);
	return performanceInterceptor;
}

//逻辑删除插件
@Bean
public ISqlInjector sqlInjector() {
	return new LogicSqlInjector();
}

//分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
	return new PaginationInterceptor();
}

5、创建SpringBoot启动类,在mall包下创建ProductApplication.java

package com.coding.mall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProductApplication {
	public static void main(String[] args) {
		SpringApplication.run(ProductApplication.class, args);
	}
}

6、启动测试:访问 http://localhost:8110/admin/mall/product 得到json数据

2.6、配置跨域

什么是跨域:浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是
跨域 。
前后端分离开发中,需要考虑ajax跨域的问题。这里我们可以从服务端解决这个问题!

配置:在Controller类上添加注解

@RestController
@RequestMapping("/admin/mall/product")
@CrossOrigin //跨域
public class ProductAdminController {
//......
}

3、配置Swagger

3.1、Swagger介绍

前后端分离开发模式中,api文档是最好的沟通方式。
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。

  1. 及时性 (接口变更后,能够及时准确地通知相关前后端开发人员)
  2. 规范性 (并且保证接口的规范性,如接口的地址,请求方式,参数及响应格式和错误信息)
  3. 一致性 (接口信息一致,不会出现因开发人员拿到的文档版本不一致,而出现分歧)
  4. 可测性 (直接在接口文档上进行测试,以方便理解业务)

3.2、配置Swagger

1、pom相关依赖

<!--swagger-->
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<version>2.7.0</version>
</dependency>
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger-ui</artifactId>
	<version>2.7.0</version>
</dependency>

2、创建Swagger2配置文件

package com.coding.mall.config;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class Swagger2Config {
	@Bean
	public Docket webApiConfig(){
	//过滤掉 admin 下的请求
	return new Docket(DocumentationType.SWAGGER_2)
			.groupName("webApi")
			.apiInfo(webApiInfo())
			.select()
			.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
			.paths(Predicates.not(PathSelectors.regex("/error.*")))
			.build();
	}
	
	@Bean
	public Docket adminApiConfig(){
	//只选取 /admin 下的请求
	return new Docket(DocumentationType.SWAGGER_2)
	.groupName("adminApi")
	.apiInfo(adminApiInfo())
	.select()
	.paths(Predicates.and(PathSelectors.regex("/admin/.*")))
	.build();
	}
	
	private ApiInfo webApiInfo(){
		return new ApiInfoBuilder()
			.title("艾编程商城-产品中心API文档")
			.description("本文档描述了产品中心微服务接口定义")
			.version("1.0")
			.contact(new Contact("Coding", "http://icodingedu.com","24736743@qq.com"))
			.build();
	}
	
	private ApiInfo adminApiInfo(){
		return new ApiInfoBuilder()
			.title("艾编程商城-后台管理系统")
			.description("本文档描述了后台管理系统产品中心微服务接口定义")
			.version("1.0")
			.contact(new Contact("Coding", "http://icodingedu.com","24736743@qq.com"))
			.build();
	}
}

3、重启服务器查看接口
http://localhost:8110/swagger-ui.html

3.3、API模型

1、mybatis-plus的代码生成器已经在entity的实体类中生成了模型的定义

@ApiModel(value="Buycart对象", description="购物车")
@ApiModelProperty(value = "...")

2、可以添加一些自定义设置,例如:定义样例数据

@ApiModelProperty(value = "创建时间",example = "0123456789")
@TableField(fill = FieldFill.INSERT)
private Integer createTime;

@ApiModelProperty(value = "更新时间",example = "0123456789")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Integer updateTime;

3、定义接口说明和参数说明,在Controller上写
定义在类上:@Api
定义在方法上:@ApiOperation
定义在参数上:@ApiParam

package com.coding.mall.controller.admin;
import com.coding.mall.entity.Product;
import com.coding.mall.service.ProductService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(description="产品后台管理接口")
@RestController
@RequestMapping("/admin/mall/product")
@CrossOrigin //跨域
public class ProductAdminController {
	@Autowired
	private ProductService productService;
	
	@ApiOperation(value = "所有产品列表")
	@GetMapping
	public List<Product> list(){
		return productService.list(null);
	}
	
	@ApiOperation(value = "根据ID删除产品")
	@DeleteMapping("{id}")
	public boolean removeById(@ApiParam(name = "id", value = "产品ID", required = true)
								@PathVariable int id){
		return productService.removeById(id);
	}
}

4、重启测试,查看效果
在这里插入图片描述

4、统一返回结果对

4.1、统一返回数据格式

项目中我们会将响应封装成json返回,一般我们会将所有接口的数据格式统一, 使前端(iOS Android,Web)对数据的操作更一致、轻松。
一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以。但是一般会包含状态码、返回消息、数据这几部分内容

例如,我们的系统要求返回的基本数据格式如下:
列表:

{
	"success": true,
	"code": 20000,
	"message": "成功",
	"data": {
		"items": [
			{
				"id": "1",
				"name": "coding",
				"intro": "coding老师有点帅"
			}
		]
	}
}

分页:

{
	"success": true,
	"code": 20000,
	"message": "成功",
	"data": {
		"total": 17,
		"rows": [
			{
				"id": "1",
				"name": "coding",
				"intro": "coding老师有点帅"
			}
		]
	}
}

没有返回数据:

{
	"success": true,
	"code": 20000,
	"message": "成功",
	"data": {}
}

失败:

{
	"success": false,
	"code": 20001,
	"message": "失败",
	"data": {}
}

因此,我们定义统一结果

{
	"success": 布尔, //响应是否成功
	"code": 数字, //响应码
	"message": 字符串, //返回消息
	"data": HashMap //返回数据,放在键值对中
}

4.2、定义统一返回结果

1、创建返回码定义枚举类
在 imall-common 中创建一个 com.coding.common.constants,创建枚举类ResultCodeEnum.java

package com.coding.common.constants;
import lombok.Getter;
@Getter
public enum ResultCodeEnum {

	SUCCESS(true, 20000,"成功"),
	UNKNOWN_REASON(false, 20001, "未知错误"),
	BAD_SQL_GRAMMAR(false, 21001, "sql语法错误"),
	JSON_PARSE_ERROR(false, 21002, "json解析异常"),
	PARAM_ERROR(false, 21003, "参数不正确");
	
	private Boolean success;
	private Integer code;
	private String message;
	
	private ResultCodeEnum(Boolean success, Integer code, String message) {
		this.success = success;
		this.code = code;
		this.message = message;
	}
}

2、创建结果类 , 创建包com.coding.common.vo,创建类 R.java

package com.coding.common.vo;
import com.coding.common.constants.ResultCodeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
@ApiModel(value = "全局统一返回结果")
public class R {

	@ApiModelProperty(value = "是否成功")
	private Boolean success;
	
	@ApiModelProperty(value = "返回码")
	private Integer code;
	
	@ApiModelProperty(value = "返回消息")
	private String message;
	
	@ApiModelProperty(value = "返回数据")
	private Map<String, Object> data = new HashMap<String, Object>();
	
	private R(){
	}
	
	public static R ok(){
		R r = new R();
		r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
		r.setCode(ResultCodeEnum.SUCCESS.getCode());
		r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
		return r;
	}
	
	public static R error(){
		R r = new R();
		r.setSuccess(ResultCodeEnum.UNKNOWN_REASON.getSuccess());
		r.setCode(ResultCodeEnum.UNKNOWN_REASON.getCode());
		r.setMessage(ResultCodeEnum.UNKNOWN_REASON.getMessage());
		return r;
	}
	
	public static R setResult(ResultCodeEnum resultCodeEnum){
		R r = new R();
		r.setSuccess(resultCodeEnum.getSuccess());
		r.setCode(resultCodeEnum.getCode());
		r.setMessage(resultCodeEnum.getMessage());
		return r;
	}
	
	//链式编程
	public R success(Boolean success){
		this.setSuccess(success);
		return this;
	}
	
	public R message(String message){
		this.setMessage(message);
		return this;
	}
	
	public R code(Integer code){
		this.setCode(code);
		return this;
	}
	
	public R data(String key, Object value){
		this.data.put(key, value);
		return this;
	}
	
	public R data(Map<String, Object> map){
		this.setData(map);
		return this;
	}
}

4.3、统一返回结果

1、修改Controller中的返回结果

修改 imall-product 的 ProductAdminController

列表

@ApiOperation(value = "所有产品列表")
@GetMapping
public R list(){
	List<Product> list = productService.list(null);
	return R.ok().data("items",list);
}

删除

@ApiOperation(value = "根据ID删除产品")
@DeleteMapping("{id}")
public R removeById(@ApiParam(name = "id", value = "产品ID", required = true)
					@PathVariable int id){
	productService.removeById(id);
	return R.ok();
}

5、分页及条件查询

5.1、分页

ProductAdminController 中添加分页方法

@ApiOperation(value = "分页产品列表")
@GetMapping("{page}/{limit}")
public R pageList(
	@ApiParam(name = "page", value = "当前页码", required = true)@PathVariable Long page,
	@ApiParam(name = "limit", value = "每页记录数", required = true)@PathVariable Long limit){

	Page<Product> pageParam = new Page<>(page, limit);
	productService.page(pageParam, null);
	List<Product> records = pageParam.getRecords(); //查询数据列表
	long total = pageParam.getTotal(); //总数
	return R.ok().data("total", total).data("rows", records);
}

5.2、条件查询

根据产品名称 product_title,上架还是下架 status、产品发布时间(时间段)查询
1、创建查询对象
创建com.coding.mall.query包,创建ProductQuery.java查询对象

package com.coding.mall.query;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@ApiModel(value = "Product查询对象", description = "产品查询对象封装")
@Data
public class ProductQuery implements Serializable {

	private static final long serialVersionUID = 1L;
	
	@ApiModelProperty(value = "产品标题")
	private String productTitle;
	
	@ApiModelProperty(value = "产品状态:0-下架,1-上架")
	private Integer status;
	
	@ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
	private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换
	
	@ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
	private String end;
}

2、service层增加接口方法和实现

接口

package com.coding.mall.service;
public interface ProductService extends IService<Product> {
	void pageQuery(Page<Product> pageParam, ProductQuery productQuery);
}

实现类

package com.coding.mall.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.coding.mall.entity.Product;
import com.coding.mall.mapper.ProductMapper;
import com.coding.mall.query.ProductQuery;
import com.coding.mall.service.ProductService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {

	//参数说明: pageParam分页, productQuery条件查询
	@Override
	public void pageQuery(Page<Product> pageParam, ProductQueryproductQuery) {
		QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
		queryWrapper.orderByAsc("id"); //唯一id排序
		if (productQuery == null){
			baseMapper.selectPage(pageParam, queryWrapper);
			return;
		}
	
	String productTitle = productQuery.getProductTitle();
	Integer status = productQuery.getStatus();
	String begin = productQuery.getBegin();
	String end = productQuery.getEnd();
	
	if (!StringUtils.isEmpty(productTitle)) {
		queryWrapper.like("product_title", productTitle);
	}
	if (!StringUtils.isEmpty(status) ) {
		queryWrapper.eq("status", status);
	}
	if (!StringUtils.isEmpty(begin)) {
		queryWrapper.ge("create_time", begin);
	}
	if (!StringUtils.isEmpty(end)) {
		queryWrapper.le("update_time", end);
	}
	
	baseMapper.selectPage(pageParam, queryWrapper);
	}
}

Controller

ProductAdminController 中修改 pageList方法:

  1. 增加参数TeacherQuery teacherQuery,非必选
  2. teacherService.page 修改成 teacherService.pageQuery
@ApiOperation(value = "分页产品列表")
@GetMapping("{page}/{limit}")
public R pageList(
					@ApiParam(name = "page", value = "当前页码", required = true)@PathVariable Long page,
					@ApiParam(name = "limit", value = "每页记录数", required = true)@PathVariable Long limit,
					@ApiParam(name = "productQuery", value = "查询对象", required = false)
					
	ProductQuery productQuery){
		Page<Product> pageParam = new Page<>(page, limit);
		productService.pageQuery(pageParam, productQuery);
		List<Product> records = pageParam.getRecords(); //查询数据列表
		long total = pageParam.getTotal(); //总数
		return R.ok().data("total", total).data("rows", records);
}

Swagger中测试

在这里插入图片描述

6、产品的新增和修改

6.1、自动填充配置

1、配置mybatis-plus自动填充处理器

imall-common中创建com.coding.common.handler包
创建自动填充处理类CommonMetaObjectHandler.java

package com.coding.common.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Slf4j
public class CommonMetaObjectHandler implements MetaObjectHandler {
	
	@Override
	public void insertFill(MetaObject metaObject) {
	log.info("start insert fill ....");
	// 注意插入对象的格式问题
	this.setFieldValByName("createTime", (int)(new
	Date().getTime()/1000), metaObject);
	this.setFieldValByName("updateTime", (int)(new
	Date().getTime()/1000), metaObject);
	}
	
	@Override
	public void updateFill(MetaObject metaObject) {
	log.info("start update fill ....");
	this.setFieldValByName("updateTime", (int)(new
	Date().getTime()/1000), metaObject);
	}
}

2、扫描自动填充处理器

在ProductApplication.java上添加注解@ComponentScan

package com.coding.mall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages={"com.coding.mall","com.coding.common"})
public class ProductApplication {
	public static void main(String[] args) {
		SpringApplication.run(ProductApplication.class, args);
	}
}

6.2、新增产品

1、 新增产品Controller

@ApiOperation(value = "新增产品")
@PostMapping
public R save(@ApiParam(name = "product", value = "产品对象", required = true)
				@RequestBody Product product){
	
	productService.save(product);
	return R.ok();
}

2、根据ID查询产品

@ApiOperation(value = "根据ID查询产品")
@GetMapping("{id}")
public R getById(@ApiParam(name = "id", value = "产品ID", required = true)
				@PathVariable int id){
				
	Product product = productService.getById(id);
	return R.ok().data("item", product);
}

3、根据id修改产品

@ApiOperation(value = "根据ID修改产品")
@PutMapping("{id}")
public R updateById(@ApiParam(name = "id", value = "产品ID", required = true)@PathVariable int id,
					@ApiParam(name = "product", value = "产品对象", required = true)
					@RequestBody Product product){
		
		product.setId(id);
		productService.updateById(product);
		return R.ok();
}

7、异常处理

7.1统一异常处理

我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要统一异
常处理

1、创建统一异常处理器
imall-common中的com.coding.common.handler包中
创建统一异常处理类GlobalExceptionHandler.java

package com.coding.common.handler;
import com.coding.common.vo.R;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 统一异常处理类
*/
@ControllerAdvice //增强
public class GlobalExceptionHandler {

	@ExceptionHandler(Exception.class)
	@ResponseBody
	public R error(Exception e){
		e.printStackTrace();
		return R.error();
	}
}

2、扫描异常处理器
确认ProductApplication.java上添加了注解@ComponentScan

@SpringBootApplication
@ComponentScan(basePackages={"com.coding.mall","com.coding.common"})
public class ProductApplication {
}

7.2处理特定异常

1、添加异常处理方法(GlobalExceptionHandler.java中添加)

@ExceptionHandler(BadSqlGrammarException.class)
@ResponseBody
public R error(BadSqlGrammarException e){
	e.printStackTrace();
	return R.setResult(ResultCodeEnum.BAD_SQL_GRAMMAR);
}
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseBody
public R error(JsonParseException e){
	e.printStackTrace();
	return R.setResult(ResultCodeEnum.JSON_PARSE_ERROR);
}

7.3、自定义异常

1、创建自定义异常类
common模块中创建com.coding.common.exception包
创建CodingException.java通用异常类 继承 RuntimeException
RuntimeException 对代码没有侵入性

package com.coding.common.exception;
import com.coding.common.constants.ResultCodeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "全局异常")
public class CodingException extends RuntimeException {

	@ApiModelProperty(value = "状态码")
	private Integer code;
	
	/**
	* 接受状态码和消息
	* @param code
	* @param message
	*/
	public CodingException(Integer code, String message) {
		super(message);
		this.code=code;
	}
	
	/**
	* 接收枚举类型
	* @param resultCodeEnum
	*/
	public CodingException(ResultCodeEnum resultCodeEnum) {
		super(resultCodeEnum.getMessage());
		this.code = resultCodeEnum.getCode();
	}
}

2、业务中需要的位置抛出CodingException

产品controller中分页查询方法中判断参数是否合法

public R pageList(......){
	if(page <= 0 || limit <= 0){
		//throw new CodingException(21003, "参数不正确1");
		throw new CodingException(ResultCodeEnum.PARAM_ERROR);
	}
......
}

3、添加异常处理方法
GlobalExceptionHandler.java中添加

@ExceptionHandler(CodingException.class)
@ResponseBody
public R error(CodingException e){
	e.printStackTrace();
	return R.error().message(e.getMessage()).code(e.getCode());
}

8、统一日志处理

8.1、日志

配置日志级别
日志记录器(Logger)的行为是分等级的。
分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别
可配置日志级别

# 设置日志级别
logging.level.root=WARN

这种方式只能将日志打印在控制台上

8.2、Logback日志

Spring boot内部使用Logback作为日志实现的框架。
配置:
1、删除application.properties中的日志配置
2、安装idea彩色日志插件:grep-console
github地址:
https://github.com/krasa/GrepConsole/releases
在这里插入图片描述

3、resources 中创建 logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
	<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
	<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true-->
	<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
	<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
	<contextName>logback</contextName>
	<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
	<property name="log.path" value="E:/资料/项目/CodingEdu/代码/coding_edu/edu" />
	
	<!-- 彩色日志 -->
	<!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
	<!-- magenta:洋红 -->
	<!-- boldMagenta:粗红-->
	<!-- cyan:青色 -->
	<!-- white:白色 -->
	<!-- magenta:洋红 -->
	<property name="CONSOLE_LOG_PATTERN"	
			value="%yellow(%date{yyyy-MM-dd HH:mm:ss})|%highlight(%-5level) |%blue(%thread)|%blue(%file:%line) |%green(%logger)|%cyan(%msg%n)"/>

	<!--输出到控制台-->
	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
		<!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 -->
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>INFO</level>
		</filter>
		<encoder>
			<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
			<!-- 设置字符集 -->
			<charset>UTF-8</charset>
		</encoder>
	</appender>

	<!--输出到文件-->
	<!-- 时间滚动输出 level为 INFO 日志 -->
	<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
	
		<!-- 正在记录的日志文件的路径及文件名 -->
		<file>${log.path}/log_info.log</file>
		
		<!--日志文件输出格式-->
		<encoder>
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level%logger{50} - %msg%n</pattern>
			<charset>UTF-8</charset>
		</encoder>
		
		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- 每天日志归档路径以及格式 -->
			<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MMdd}.%i.log</fileNamePattern>
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
				<maxFileSize>100MB</maxFileSize>
			</timeBasedFileNamingAndTriggeringPolicy>
		
			<!--日志文件保留天数-->
			<maxHistory>15</maxHistory>
		</rollingPolicy>

		<!-- 此日志文件只记录info级别的 -->
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
			<level>INFO</level>
			<onMatch>ACCEPT</onMatch>
			<onMismatch>DENY</onMismatch>
		</filter>
	</appender>


	<!-- 时间滚动输出 level为 WARN 日志 -->
	<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<!-- 正在记录的日志文件的路径及文件名 -->
		<file>${log.path}/log_warn.log</file>
		<!--日志文件输出格式-->
		<encoder>
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level%logger{50} - %msg%n</pattern>
			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
		</encoder>
		
		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MMdd}.%i.log</fileNamePattern>
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
				<maxFileSize>100MB</maxFileSize>
			</timeBasedFileNamingAndTriggeringPolicy>
		
		<!--日志文件保留天数-->
			<maxHistory>15</maxHistory>
		</rollingPolicy>


	<!-- 此日志文件只记录warn级别的 -->
	<filter class="ch.qos.logback.classic.filter.LevelFilter">
		<level>warn</level>
		<onMatch>ACCEPT</onMatch>
		<onMismatch>DENY</onMismatch>
	</filter>
	</appender>


		<!-- 时间滚动输出 level为 ERROR 日志 -->
	<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<!-- 正在记录的日志文件的路径及文件名 -->
		<file>${log.path}/log_error.log</file>
		<!--日志文件输出格式-->
		<encoder>
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level%logger{50} - %msg%n</pattern>
			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
		</encoder>
		
		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MMdd}.%i.log</fileNamePattern>
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
				<maxFileSize>100MB</maxFileSize>
			</timeBasedFileNamingAndTriggeringPolicy>
			<!--日志文件保留天数-->
			<maxHistory>15</maxHistory>
		</rollingPolicy>


	<!-- 此日志文件只记录ERROR级别的 -->
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
			<level>ERROR</level>
			<onMatch>ACCEPT</onMatch>
			<onMismatch>DENY</onMismatch>
		</filter>
	</appender>
<!--
	<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
	<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。
		name:用来指定受此logger约束的某一个包或者具体的某一个类。
		level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL和 OFF,
		如果未设置此属性,那么当前logger将会继承上级的级别。
-->

<!--
	使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
	第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
	第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:
-->
	

	<!--开发环境:打印控制台-->
	<springProfile name="dev">

		<!--可以输出项目中的debug日志,包括mybatis的sql日志-->
		<logger name="com.coding" level="INFO" />
		<!--root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
		level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR,ALL 和 OFF,
		默认是DEBUG可以包含零个或多个appender元素。-->
		<root level="INFO">
			<appender-ref ref="CONSOLE" />
			<appender-ref ref="INFO_FILE" />
			<appender-ref ref="WARN_FILE" />
			<appender-ref ref="ERROR_FILE" />
		</root>
	</springProfile>
		

	<!--生产环境:输出到文件-->
	<springProfile name="pro">
		<!--可以输出项目中的debug日志,包括mybatis的sql日志-->
		<logger name="com.coding" level="WARN" />
		<root level="INFO">
			<appender-ref ref="ERROR_FILE" />
			<appender-ref ref="WARN_FILE" />
		</root>
	</springProfile>
</configuration>

4、将错误日志输出到文件
GlobalExceptionHandler.java 中,类上添加注解 @Slf4j ,修改异常输出语句

//e.printStackTrace();
log.error(e.getMessage());

将日志堆栈信息输出到文件
为了保证日志的堆栈信息能够被输出,我们需要定义工具类!
imall-common下创建util包,创建ExceptionUtil.java工具类

package com.coding.common.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
//打印异常堆栈信息
public class ExceptionUtil {

	public static String getMessage(Exception e) {
	
		StringWriter sw = null;
		PrintWriter pw = null;
		try {
			sw = new StringWriter();
			pw = new PrintWriter(sw);
			// 将出错的栈信息输出到printWriter中
			e.printStackTrace(pw);
			pw.flush();
			sw.flush();
		} finally {
			if (sw != null) {
				try {
					sw.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
			
			if (pw != null) {
				pw.close();
			}
		}
		return sw.toString();
	}
}

修改异常输出语句

//e.printStackTrace();
//log.error(e.getMessage());
log.error(ExceptionUtil.getMessage(e));

CodingException中创建toString方法

@Override
public String toString() {
	return "CodingException{" +"message=" + this.getMessage() +", code=" + code +'}';
}

9、Rest风格

REST:即 Representational State Transfer,(资源)表现层状态转化。
是目前最流行的一种互联网软件架构。

具体说,就是 HTTP 协议里面,四个表示操作方式的动词:
GET、POST、PUT、DELETE。

它们分别对应四种基本操作:

  1. GET 用来获 取资源
  2. POST 用来新建资源
  3. PUT 用来更新资源
  4. DELETE 用来删除资源

幂等性

HTTP幂等方法,是指无论调用多少次都不会有不同结果的 HTTP 方法。不管你调用一次,还是调用一百次,一千次,结果都是相同的。

HTTP GET方法

HTTP GET方法,用于获取资源,不管调用多少次接口,结果都不会改变,所以是幂等的。
只是查询数据,不会影响到资源的变化,因此我们认为它幂等。
幂等性指的是作用于结果而非资源本身。例如,这个HTTP GET方法可能会每次得到不同的返回内容,但并不影响资源。

HTTP POST方法

HTTP POST方法是一个非幂等方法,因为调用多次,都将产生新的资源。
因为它会对资源本身产生影响,每次调用都会有新的资源产生,因此不满足幂等性。

HTTP PUT方法

HTTP PUT方法直接把实体部分的数据替换到服务器的资源,我们多次调用它,只会产生一次影响,但是有相同结果的HTTP 方法,所以满足幂等性。

HTTP PATCH方法

HTTP PATCH方法是非幂等的。
服务端对方法的处理是,当调用一次方法,更新部分字段,将这条ticket记录的操作记录加一,每次调用的资源变了,所以它是有可能是非幂等的操作。

HTTP DELETE方法
HTTP DELETE方法用于删除资源,会将资源删除。
调用一次和多次对资源产生影响是相同的,所以也满足幂等性。

HTTP GET方法 vs HTTP POST方法
HTTP请求的GET与POST方式有什么区别?
GET方式通过URL提交数据,数据在URL中可以看到;
POST方式,数据放置在HTML HEADER内提交。

从RESTful的资源角度来看待问题,HTTP GET方法是幂等的,所以它适合作为查询操作,HTTP POST方法是非幂等的,所以用来表示新增操作。

但是,也有例外,我们有的时候可能需要把查询方法改造成HTTP POST方法。比如,超长(1k)的GET URL使用POST方法来替代,因为GET受到URL长度的限制。虽然,它不符合幂等性,但是它是一种折中的方案。

HTTP POST方法 vs HTTP PUT方法

对于HTTP POST方法和HTTP PUT方法,我们一般的理解是POST表示创建资源,PUT表示更新资源。当然,这个是正确的理解。

但是,实际上,两个方法都用于创建资源,更为本质的差别是在幂等性。
HTTP POST方法是非幂等,所以用来表示创建资源,
HTTP PUT方法是幂等的,因此表示更新资源更加贴切。

HTTP PUT方法 vs HTTP PATCH方法

HTTP PUT方法和HTTP PATCH方法,都是用来表述更新资源,
PUT表示更新全部资源,PATCH表示更新部分资源。
首先,这个是我们遵守的第一准则。根据上面的描述,PATCH方法是非幂等的,因此我们在设计我们服务端的RESTfulAPI的时候,也需要考虑。如果,我们想要明确的告诉调用者我们的资源是幂等的,我的设计更倾向于使用HTTP PUT方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值