一、RESTFUL
Post:新增
Put:全量修改
Patch:修改某个值
Delete: 删除
Get:查询
删除接口也可以用POST请求
url注意:
url中不要带有敏感词(用户id等)
url中的名词用复数形式
url设计:
api.xxx.com/courses
二、状态码和提示信息
code:状态码
message:success等
消息:真正的消息体
这样做可以省去解析消息体拿到状态码
三、分页
/api/courses?page=1&size=10
/api/courses?max_id=100&min_id=90
第一种缺点:不适合频繁有插入的情况(第一页的数据,可能在看第二页的时候被挤到第二页了,看到了重复数据)
第二种缺点:不合适看指定的页数(得要看了前一页之后,才能知道下一页的max_id和min_id)
四、接口设计工具
APIFOX
五、dependencies和dependencyManager
dependencyManager在父项目中只声明依赖,并不实现引用;子项目需要显示声明依赖;有利于版本统一,父项目声明后,子项目中可以不写版本号
dependencies:在父项目中写了依赖后,子项目自动继承父项目的依赖,可以直接使用
创建项目:
groupId:公司名
artifactId:项目名
六、创建项目
验证项目
项目创建完成,可以通过package打包的方式,验证项目是否创建完成
父项目中packaging为pom
<packaging>pom</packaging>
3.创建子项目
new module
创建完子项目,父项目的pom文件中会多:
<modules>
<module>子项目名 </module>
<modules>
4. relativePath
relativePath用于找父项目的pom文件位置,默认就是../pom.xml
<relativePath>../pom.xml<relativePath>
5. 子项目调用
同一个父项目中的各个子项目,如果需要相互调用,可以在父项目中加对应的项目依赖,也可以在调用的项目中添加依赖
七、nacos
nacos只是一个应用服务,下载了以后,直接启动运行
nacos启动时,路径带中文会报错
windows上可以使用启动脚本来启动nacos,把启动命令放在bat文件中,双击bat文件就可以启动了
把服务注册到nacos中
在服务中添加nacos依赖
添加配置
在启动类中添加注解
@EnableDiscoveryClient
八、链式调用@Accessors
实体类上可以加@Accessors(chain=true)注解
用于链式调用,set完一个属性后可以直接再设置另一个属性,会返回对象本身
user.setName("").setAge();
九、springboot、spring cloud、spring cloud alibaba版本统一问题
spring cloud alibaba中有说明版本统一问题,可以通过dependcyManager在管理整个项目的版本
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
在父项目中注明spring cloud alibab的版本号
十、openFeign
子项目之间调用可以使用openFegin
添加openFeign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类需要添加注解
@EnableFeignClients
使用openFegin需要引用loadBalance依赖
使用openFeign进行远程调用
使用feign进行服务调用会出现的问题:
GET请求中,如果参数放在body中,使用@RequestBody进行参数传递,GET请求会被转成POST请求。
解决方式一:需要修改成路径参数 @PathVariable
解决方式二:添加依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
十一、mysql8的数据库连接
需要写时区serverTimezone=GMT%2B8
十二、JWT
用于生成解析token
JWT:Json Web Token
官网:https://jwt.io/
第一部分和第二部分都可以被解析出来,所以token里面不要放很重要的信息
第三部分中有一个256位的密码,可以增加密码的安全性
项目中引入JWT
添加依赖
生成token
// 生成token
public static String generatorToken(String phone , String identity , String tokenType) {
Map<String,String> map = new HashMap<>();
map.put(JWT_KEY_PHONE,phone);
map.put(JWT_KEY_IDENTITY, identity);
map.put(JWT_TOKEN_TYPE, tokenType);
// 防止每次生成的token一样。
map.put(JWT_TOKEN_TIME, Calendar.getInstance().getTime().toString());
JWTCreator.Builder builder = JWT.create();
// 整合map
map.forEach(
(k,v) -> {
builder.withClaim(k,v);
}
);
// 整合过期时间
// builder.withExpiresAt(date);
// 生成 token
String sign = builder.sign(Algorithm.HMAC256(SIGN));
return sign;
}
解析token
// 解析token
public static TokenResult parseToken(String token){
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
String phone = verify.getClaim(JWT_KEY_PHONE).asString();
String identity = verify.getClaim(JWT_KEY_IDENTITY).asString();
TokenResult tokenResult = new TokenResult();
tokenResult.setPhone(phone);
tokenResult.setIdentity(identity);
return tokenResult;
}
十三、添加拦截器
实现HandlerInterceptor
重写preHandle方法
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean result = true;
String resutltString = "";
String token = request.getHeader("Authorization");
// 解析token
TokenResult tokenResult = JwtUtils.checkToken(token);
if (tokenResult == null){
resutltString = "access token invalid";
result = false;
}else{
// 拼接key
String phone = tokenResult.getPhone();
String identity = tokenResult.getIdentity();
String tokenKey = RedisPrefixUtils.generatorTokenKey(phone,identity, TokenConstants.ACCESS_TOKEN_TYPE);
// 从redis中取出token
String tokenRedis = stringRedisTemplate.opsForValue().get(tokenKey);
if ((StringUtils.isBlank(tokenRedis)) || (!token.trim().equals(tokenRedis.trim()))){
resutltString = "access token invalid";
result = false;
}
}
if (!result){
PrintWriter out = response.getWriter();
out.print(JSONObject.fromObject(ResponseResult.fail(resutltString)).toString());
}
return result;
}
}
把拦截器注入到spring容器中
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public JwtInterceptor jwtInterceptor(){
return new JwtInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
// 拦截的路径
.addPathPatterns("/**")
// 不拦截的路径
.excludePathPatterns("/noauth")
.excludePathPatterns("/verification-code")
.excludePathPatterns("/verification-code-check")
;
}
}
拦截器是在bean实例化前初始化的,所以拦截器中使用bean需要在拦截器配置类中手动注入要使用的bean
十四、token
token失效
可以通过token本身设置过期时间
也可以通过存储在redis中设置过期时间
双token
accessToken、refreshToken
accessToken是正常的访问token,refreshToken是刷新token,refreshToken的过期时间一般要比accessToken长。当accessToken快要过期了,但是用户正在访问,此时突然过期,对用户体验很不友好。此时,使用refreshToken访问,来更新accessToken和refreshToken。提高用户体验
可以在浏览器中安装JSON-handle插件,这样浏览器中的数据会展示成json格式
十五、读取yml中配置的参数
@Value
2. 配置文件
@Data
@Configuration
@ConfigurationProperties(prefix = "xxx.xxx.xxx")
public class ApiAuthProperties {
private String url;
private String checkToken;
}
十六、Ribbon
Ribbon用法:
在启动类中添加RestTemplate
通过RestTemplate进行远程调用
openFeign和Ribbon的区别
openFeign内置了Ribbon,调用的是服务注册中心(Nacos)的服务
Ribbon可以调用第三方接口,需要手动调用
url中的参数是复杂json,包含数组,对象
需要对一些符号进行编码:[ ] " : ,
使用ribbon进行接口调用时,url需要使用URI.create(url)
十七、根据数据库表生成对应实体类
引入jar包
编写自动生成工具类
FastAutoGenerator.create("jdbc:mysql://localhost:3306/service-driver-user?characterEncoding=utf-8&serverTimezone=GMT%2B8",
"root","root")
.globalConfig(builder -> {
builder.author("作者名").fileOverride().outputDir("导出文件路径");
})
.packageConfig(builder -> {
builder.parent("com.mashibing.serviceDriverUser").pathInfo(Collections.singletonMap(OutputFile.mapperXml,
"xxx\\service-driver-user\\src\\main\\java\\com\\mashibing\\serviceDriverUser\\mapper"));
})
.strategyConfig(builder -> {
builder.addInclude("driver_user_work_status");
})
.templateEngine(new FreemarkerTemplateEngine())
.execute();
加sql相关的日志
logging:
level:
com:
baomidou:
mybatisplus: debug
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
十八、请求参数
@RequestParam
写在params中的参数,接收时使用@RequestParam
@RequestBody
在body中的,接收时使用@RequestBody
@PathVariable
在路径中的,接收时使用@PathVariable
Header
header中的参数,接收时使用HttpServletRequest,httpServletRequest.getHeader("xxx");
十九、日期
实体类的中Date不推荐,推荐使用LocalDate
时间转成时间戳:
localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli()
使用@JsonFormat注解,需要以下依赖
二十、请求错误码
4开头的是客户端的错误
5开头的是服务端的错误
400:参数有误,比如说日期格式有误
401:权限有误
404:地址有误
二十一、idea运行xml中的sql注意事项
勾选bulid project automatically
按住clt+shift+A,选择Register,勾选compiler.xxx
二十二、把mapper文件放在resource下,需要配置
二十三、数字与字符串
json格式中的字符串使用getLong获取,可能会导致精度丢失
可以getString后,通过Long.parseLong转换
小数用double存储,还是用字符串存储
如果小数用于计算,用double
如果不用于计算,只用于展示,可以用字符串
二十四、设置提交忽略目录
在项目的跟目录下找到.gitignore文件
写项目名/子项目名/target/,这样可以忽略target下所有文件
二十五、并发测试
工具:Jmeter
https://jmeter.apache.org/
需要配置环境变量JMETER_HOME
设置需要的参数
二十六、并发问题
单节点可以使用synchronized加锁
synchronized锁long类型的数据,可以使用:
synchronized(( longNum + "").intern())
当调用intern()方法时,如果常量池中已经有这个字符串(根据equals判断),则直接返回
如果常量池中没有,把这个字符串添加进常量池并返回
但是synchronized是jvm级别的,解决不了集群之间的并发问题
2. 集群可以使用redisson
RLock lock = redissonClient.getLock(lockKey);
lock.lock(); // 加锁
lock.unlock(); // 解锁
一个项目启动多次(测试集群部署)
添加多个端口号
复制两个项目启动
二十七、修改项目名
rename
修改包名
修改pom文件
二十八、其他
数据库名可以与服务名保持一致
JSONObject中是否有某个字段,可以用has
复制对象属性:
可以使用BeanUtils.copyProperties(src, dest);
微服务开发时,如果切换网络,可能会导致服务间调用不通了
前后端数据交互,可以使用:SseEmitter
路径中的下划线_和中划线-的区别:
搜索引擎关于爬虫的优化,建议在路径中使用中划线-
计算机通过外网被访问,需要提供一个外网的ip地址,可以使用花生壳产生一个外网ip
接入支付宝支付参考:https://opendocs.alipay.com/common/02ncur