MinIO
- 启动服务:minio server xxx路径
- 配置服务配置文件
MinIO核心概念
- Object 对象
- Bucket 存储桶
- Endpoint 端点
- Access Key 和 Secret key
使用步骤
- 登录:
- 注意,防火墙是否关闭,端口是否放开
- 登录密码在我们的EnvironmentFile配置文件中
- 创建存储桶
- 上传图片
- 找到目标桶
- 上传图片
- 访问图片
- 图片url就是
MinIO地址+Endpoint+存储路径 - 懂得都懂,默认权限就是private,肯定访问不到
- 可以自定义访问权限Custom
- 这个Custom自定义权限我是真的不懂不会写
MinIO Java SDK
``` xml
- 图片url就是
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
### 开发
#### 逻辑删除功能
使用`配置`或者`@TableLogic`注解
* 注意,这个功能只对我们Mybatis-plus有用,如果自己定义的sql,那就没用了
* 只要有`@TableLogic` remove操作就是逻辑删除
#### @JsonIgnore
* 删去不需要的信息,比如创建时间,is_delete
* jackon提供的
#### 自动填充
* 加注解@TableField(fill = FieldFill.INSERT )
* 填充内容
``` java
@Component
public class MybatisMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}
```
#### 流程中所有的类型转换
* 一旦涉及到枚举,就会多次进行类型转换
* WebDataBinder : Enum entity -> 前端数据(要code而不是_SUCCESS_)
* TypeHandler : Enum entity -> 数据库要查的属性code
* HTTPMessageConverter:前端数据->Enum entity
* SpringMVC提供了String->Enum的转换器,但是只能通过实例名称转换,不能通过Enum的code
* 所以我们只能自己转换
``` java
@Component
public class StringToItemTypeConverter implements Converter<String, ItemType> {
@Override
public ItemType convert(String code) {
for (ItemType value : ItemType.values()) {
if (value.getCode().equals(Integer.valueOf(code))) {
return value;
}
}
throw new IllegalArgumentException("code非法");
}
}
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private StringToItemTypeConverter stringToItemTypeConverter;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(this.stringToItemTypeConverter);
}
}
/**
* 在 Spring Boot 中,只要你的 Converter 被注册成 Bean(如 @Component),就会被自动加入 ConversionService,根本不需要手写 WebMvcConfigurer。
*/
```
##### ConverterFactory
``` java
@Component
public class StringToBaseTypeConveter implements ConverterFactory<String, BaseEnum> {
@Override
public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
return new Converter<String, T>() {
@Override
public T convert(String source) {
T[] enumConstants = targetType.getEnumConstants();
for (T enumConstant : enumConstants) {
if (enumConstant.getCode().equals(Integer.valueOf(source))) {
return enumConstant;
}
}
throw new IllegalArgumentException();
}
};
}
}
TypeHandler (MP)
@EnumValue: 可实现枚举对象和枚举属性值的转换
HTTPMessageConveter
- 依赖于jackson
@JsonValue
枚举类属性映射
1
2
3
4
5
6
7
8
9
10
11
12
@Data
@ConfigurationProperties(prefix="minio")
public class MinIOProperties{
private String endpoint;
}
//在配置类上写这个注解注册我们的配置信息类
@ConfigurationPropertiesScan("包名")
@EnableConfigurationProperties(类名)
//二选一就行
MinIO上传文件
- 熟悉MinIO上传文件的流程
-
理解ContentType的作用
这里的代码cv就行
- MultipartFile
1
2
3
4
5
//可以获取很多文件信息
System.out.println(file.getName());
System.out.println(file.getOriginalFilename());
System.out.println(file.getContentType());
System.out.println(file.getSize());
全局异常处理
1
2
3
4
5
6
7
8
9
10
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public Result exceptionHandler(Exception e){
e.printStackTrace();
return Result.fail();
}
}
saveOrupdate多张表
- 执行insert语句后会触发主键回显
- 补充,就算对于mp中的saveOrupdate操作,也并不是都能触发主键回显的,只有当走的是save也就是insert操作的时候才会触发主键回显,对于update语句则不会
- stream流操作
- map适用于类型转换:类型转换,list->newList
- foreach适用于副操作:打印等等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//判断要更新还是纯添加
boolean is_update = apartmentSubmitVo.getId() != null;
//注意下面这一步会触发主键回显
//注意,只有执行insert语句才可能触发主键回显
//也就是说这句话如果走的是update语句,也照样不会触发主键回显
this.saveOrUpdate(apartmentSubmitVo);
// 删除图片
LambdaQueryWrapper<GraphInfo> graphInfoLambdaQueryWrapper = new LambdaQueryWrapper<>();
graphInfoLambdaQueryWrapper.eq(GraphInfo::getId, apartmentSubmitVo.getId())
.eq(GraphInfo::getItemType,ItemType.APARTMENT);
graphInfoService.remove(graphInfoLambdaQueryWrapper);
//添加图片
List<GraphVo> graphVoList = apartmentSubmitVo.getGraphVoList();
if (!CollectionUtils.isEmpty(graphVoList)) {
List<GraphInfo> graphInfoList = graphVoList.stream().map(graphVo -> {
GraphInfo graphInfo = new GraphInfo();
graphInfo.setName(graphVo.getName());
graphInfo.setUrl(graphVo.getUrl());
graphInfo.setItemType(ItemType.APARTMENT);
graphInfo.setItemId(apartmentSubmitVo.getId());
return graphInfo;
}).collect(Collectors.toList());
graphInfoService.saveBatch(graphInfoList);
}
分页查询
- 分页插件配置类配置
- 编写较为复杂的sql语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.lease.web.admin.mapper.ApartmentInfoMapper">
<select id="pageItem" resultType="com.atguigu.lease.web.admin.vo.apartment.ApartmentItemVo">
select
ai.id,
ai.name,
ai.introduction,
ai.district_id,
ai.district_name,
ai.city_id,
ai.city_name,
ai.province_id,
ai.province_name,
ai.address_detail,
ai.latitude,
ai.longitude,
ai.phone,
ai.is_release,
ai.create_time,
ai.update_time,
ai.is_deleted,
ifnull(ri.cnn,0) total_room_count,
ifnull(ri.cnn,0)-ifnull(lg.cnn,0) free_room_count
from
(select id,
name,
introduction,
district_id,
district_name,
city_id,
city_name,
province_id,
province_name,
address_detail,
latitude,
longitude,
phone,
is_release,
create_time,
update_time,
is_deleted
from apartment_info
<where>
is_deleted = 0
<if test="queryVo.provinceId != null">
and apartment_info.province_id = #{queryVo.provinceId}
</if>
<if test="queryVo.cityId != null">
and apartment_info.city_id = #{queryVo.cityId}
</if>
<if test="queryVo.districtId != null">
and apartment_info.district_id = #{queryVo.districtId}
</if>
</where>)
ai
left join
(select apartment_id, count(*) cnn
from lease_agreement
where is_deleted = 0
and status in (2, 5)
group by apartment_id)
lg on ai.id = lg.apartment_id
left join
(select apartment_id, count(*) cnn
from room_info
where is_deleted = 0
and is_release = 1
group by apartment_id)
ri on ai.id = ri.apartment_id
</select>
</mapper>
参数展平配置
1
2
springdoc:
default-flat-param-object: true
多表查询拼接
在我们一个service需要对多张表进行操作,比如十几张表,我们如果还是选择使用sql进行拼接,这其实有点复杂
我们可以使用多次查询,然后再封装一下各个数据,再返回给前端。
多次查询 or Sql连接
- 譬如我们上面的分页操作,我们需要对一页里每一条数据进行查对应的其他表中的数据,如果我们自己手动查,需要一个循环慢慢查,很复杂,所以这种情况下我们选择复杂sql,在数据库方面查询
- 但是对于一个根据id查这个商品在其他各种表的信息,我们就可以使用多次sql,然后进行数据拼接,因为这时候我们永远有id这个条件,方便我们进行查询
所以对于不用的需求,我们应该选择不同的方式进行封装数据,不要一昧凭感觉
- 注意:在上面第二条中,我们可能会遇到这种情况,我们得到的数据是多表的一个VO,我们自定义sql语句查询比较好,而不是查多次再手动拼接
删除数据
- 如果删除一个大类,我们在后台应该判断这个大类的子类是否为null
- 如果不是null,我们就throw一个自定义异常,并return 一个枚举状态码
- 这样方便我们对各种异常情况进行处理
时间格式与时区
ViewAppointment实体类中的appointmentTime字段为Date类型,Date类型的字段在序列化成JSON字符串时,需要考虑两个点,分别是格式和时区。本项目使用JSON序列化框架为Jackson,具体配置如下
-
格式
格式可按照字段单独配置,也可全局配置,下面分别介绍
-
单独配置
在指定字段增加
@JsonFormat注解,如下1 2
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date appointmentTime;
-
全局配置
在
application.yml中增加如下内容1 2 3
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss
-
-
时区
时区同样可按照字段单独配置,也可全局配置,下面分别介绍
-
单独配置
在指定字段增加
@JsonFormat注解,如下1 2
@JsonFormat(timezone = "GMT+8") private Date appointmentTime;
-
全局配置
1 2 3
spring: jackson: time-zone: GMT+8
-
推荐格式按照字段单独配置,时区全局配置。
定时任务
本节内容是通过定时任务定时检查租约是否到期。SpringBoot内置了定时任务,具体实现如下。
-
启用Spring Boot定时任务
在SpringBoot启动类上增加
@EnableScheduling注解,如下1 2 3 4 5 6 7
@SpringBootApplication @EnableScheduling public class AdminWebApplication { public static void main(String[] args) { SpringApplication.run(AdminWebApplication.class, args); } }
-
编写定时逻辑
在web-admin模块下创建
com.atguigu.lease.web.admin.schedule.ScheduledTasks类,内容如下1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
@Component public class ScheduledTasks { @Autowired private LeaseAgreementService leaseAgreementService; @Scheduled(cron = "0 0 0 * * *") public void checkLeaseStatus() { LambdaUpdateWrapper<LeaseAgreement> updateWrapper = new LambdaUpdateWrapper<>(); Date now = new Date(); updateWrapper.le(LeaseAgreement::getLeaseEndDate, now); updateWrapper.eq(LeaseAgreement::getStatus, LeaseStatus.SIGNED); updateWrapper.in(LeaseAgreement::getStatus, LeaseStatus.SIGNED, LeaseStatus.WITHDRAWING); leaseAgreementService.update(updateWrapper); } }
知识点:
SpringBoot中的cron表达式语法如下
1 2 3 4 5 6 7 8 9
┌───────────── second (0-59) │ ┌───────────── minute (0 - 59) │ │ ┌───────────── hour (0 - 23) │ │ │ ┌───────────── day of the month (1 - 31) │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC) │ │ │ │ │ ┌───────────── day of the week (0 - 7) │ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN) │ │ │ │ │ │ * * * * * *
隐藏字段
- 对于有些时候,我们希望隐藏掉
UserInfo里面的password字段 - 但是我们又懒得去做不同的vo
- 所以我们直接选择偷懒
- MP提供有现成的字段
@TableField(value = "password", select = false) - 这里其实还有另一个比较好用的东西
@IgnoreJson记得区分一下使用情景
密码处理
常用于处理密码的单向函数(算法)有MD5、SHA-256等,Apache Commons提供了一个工具类DigestUtils,其中就包含上述算法的实现
1
2
3
4
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
Mybatis-Plus update strategy
使用Mybatis-Plus提供的更新方法时,若实体中的字段为null,默认情况下,最终生成的update语句中,不会包含该字段。若想改变默认行为,可做以下配置。
- 全局配置
在application.yml中配置如下参数
1
2
3
4
mybatis-plus:
global-config:
db-config:
update-strategy: <strategy>
注:上述<strategy>可选值有:ignore、not_null、not_empty、never,默认值为not_null
ignore:忽略空值判断,不管字段是否为空,都会进行更新-
not_null:进行非空判断,字段非空才会进行判断 -
not_empty:进行非空判断,并进行非空串(”“)判断,主要针对字符串类型 -
never:从不进行更新,不管该字段为何值,都不更新 -
局部配置
在实体类中的具体字段通过
@TableField注解进行配置,如下:1 2 3
@Schema(description = "密码") @TableField(value = "password", updateStrategy = FieldStrategy.NOT_EMPTY) private String password;
用户登录
- Session
- JWT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
验证码生成工具
1
2
3
4
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
</dependency>
用户登录
- 使用Interceptor进行请求拦截
- 特别注意要放行我们的swagger
- 放行登录的所有接口
- 使用localthread存储用户信息,减少token解析次数
@Conditional
@ConditionalOnProperty(name = "minio.endpoint)
异步操作
- 在 Spring Boot 主应用程序类上添加
@EnableAsync注解 - 在要进行异步处理的方法上添加
@Async注解
部署
代理
- 正向代理
- 反向代理
block
- main
- user nginx;
- worker_process auto; //会将cpu核数作为进程数
- error_log 配置全局错误日志文件路径
- events block 比较难
- http block
- access_log: 指定访问日志的路径
- log_formart:
- include 引入conf文件
- server block //被http block引入
- listen: 端口号
- server_name: 域名或ip
- location /路径
- root /var/ 从本地拿资源
- proxy_pass http: 转发