后端¶
一、新增标签表¶
1、需求分析¶
建议用标签,不要用分类,更灵活。
- 性别:男、女
- 方向:Java、C++、Go、前端
- 正在学:Spring
- 目标:考研、春招、秋招、社招、考公、竞赛(蓝桥杯)、转行、跳槽
- 段位:初级、中级、高级、王者
- 身份:小学、初中、高中、大一、大二、大三、大四、学生、待业、已就业、研一、研二、研三
- 状态:乐观、有点丧、一般、单身、已婚、有对象
2、字段¶
- id, int, 主键
- 标签名, varchar, 非空(必须唯一,唯一索引)
- 上传标签的用户, userId, int(如果要根据 userId 查已上传标签的话,最好加上,普通索引)
- 父标签 id ,parentId,int(分类)
- 是否为父标签 isParent,tinyint(0 不是父标签、1 - 父标签)
- 创建时间 createTime,datetime
- 更新时间 updateTime,datetime
- 是否删除 isDelete, tinyint(0、1)
3、使用分析¶
怎么查询所有标签,并且把标签分好组?
按父标签 id 分组,能实现 ✅
根据父标签查询子标签?
根据 id 查询,能实现 ✅
根据标签查询用户?
①直接在用户表补充 tags 字段,存 json 字符串 ✅
- 优点:查询方便、不用新建关联表,标签是用户的固有属性(除了该系统、其他系统可能要用到,标签是用户的固有属性)节省开发成本
- 查询用户列表,查关系表拿到这 100 个用户有的所有标签 id,再根据标签 id 去查标签表。哪怕性能低,可以用缓存。
- 缺点:用户表多一列
②加一个关联表,记录用户和标签的关系关联表的应用场景 💬
- 查询灵活,可以正查反查
- 缺点:要多建一个表、多维护一个表
- 重点:企业大项目开发中尽量减少关联查询,很影响扩展性,而且会影响查询性能
4、SQL语言分类¶
SQL:Structured Query Language 结构化查询语言
- DDL define 建表、操作表
- DML manage 管理,更新删除数据,影响实际表里的内容
- DCL control 控制,权限
- DQL query 查询,select
二、根据标签搜索用户¶
1、逻辑实现¶
- SQL 查询(实现简单,可以通过拆分查询进一步优化)
- 内存查询(灵活,可以通过并发进一步优化)
- 如果参数可以分析:根据用户的参数去选择查询方式,比如标签数
- 如果参数不可分析:并且数据库连接足够、内存空间足够,可以并发同时查询,谁先返回用谁。
- 还可以 SQL 查询与内存计算相结合,比如先用 SQL 过滤掉部分 tag,再去内存过滤。
2、解析JSON字符串¶
序列化:java对象转成 json
反序列化:把 json 转为 java 对象
java json 序列化库:
- gson(google 的)
- fastjson alibaba(阿里出品,快,但是漏洞太多)
- jackson
- kryo
三、整合接口文档¶
1、接口文档意义¶
- 有个书面内容(背书或者归档),便于大家参考和查阅,便于 沉淀和维护 ,拒绝口口相传
- 接口文档便于前端和后端开发对接,前后端联调的 介质 。后端 => 接口文档 <= 前端
- 好的接口文档支持在线调试、在线测试,可以作为工具提高我们的开发测试效率
2、Swagger¶
① 引入依赖(Swagger 或 Knife4j)
<!-- swagger 接口文档 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
② 自定义 Swagger 配置类,定义需要生成接口文档的代码位置(Controller)
// 配置类
@Configuration
// 开启 swagger2 的自动配置
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket() {
// 创建一个 swagger 的 bean 实例
return new Docket(DocumentationType.SWAGGER_2)
// 配置接口信息
.select() // 设置扫描接口
// 配置如何扫描接口
// 扫描指定包下的接口
.apis(RequestHandlerSelectors
.basePackage("com.yupi.usercenter.controller"))
.paths(PathSelectors
.any() // 满足条件的路径,该断言总为true
//.none() // 不满足条件的路径,该断言总为false(可用于生成环境屏蔽 swagger)
//.ant("/user/**") // 满足字符串表达式路径
//.regex("") // 符合正则的路径
)
.build();
}
// 基本信息设置
private ApiInfo apiInfo() {
Contact contact = new Contact(
"shayu", // 作者姓名
"shayuyu.cn", // 作者网址
"shayu-yusha@qq.com"); // 作者邮箱
return new ApiInfoBuilder()
.title("鱼泡伙伴匹配系统-接口文档") // 标题
.description("众里寻他千百度,慕然回首那人却在灯火阑珊处") // 描述
.termsOfServiceUrl("https://www.baidu.com") // 跳转连接
.version("1.0") // 版本
.license("Swagger-的使用(详细教程)")
.licenseUrl("https://xxxx")
.contact(contact)
.build();
}
}
线上环境不要把接口暴露出去!!!
- 可以通过在 SwaggerConfig 配置文件开头加上
@Profile({"dev", "test"})
限定配置仅在部分环境开启 - 可以通过在 controller 方法上添加注解来自定义生成的接口描述信息
如果 springboot version >= 2.6,需要添加如下配置:
③ 启动:http://localhost:8080/api/swagger-ui.html
3、Knife4j¶
① 引入依赖
<!-- knife4j 接口文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.7</version>
</dependency>
② 创建SwaggerConfig文件
@Configuration
@EnableSwagger2WebMvc
//版本控制访问
@Profile({"dev", "test"})
public class SwaggerConfig {
@Bean(value = "defaultApi2")
public Docket defaultApi2() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// 这里一定要标注你控制器的位置
.apis(RequestHandlerSelectors
.basePackage("com.yupi.usercenter.controller"))
.paths(PathSelectors.any())
.build();
}
/**
* api 信息
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("鱼皮用户中心")
.description("鱼皮用户中心接口文档")
.termsOfServiceUrl("https://github.com/liyupi")
.build();
}
}
如果 springboot version >= 2.6,需要添加如下配置:
③ 启动:http://localhost:8080/api/doc.html
四、EasyExcel¶
1、概述¶
EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel (alibaba.com)
两种读对象的方式:
- 确定表头:建立对象,和表头形成映射关系
- 不确定表头:每一行数据映射为 Map
两种读取模式:
- 监听器
- 先创建监听器、在读取文件时绑定监听器。
- 单独抽离处理逻辑,代码清晰易于维护;
- 一条一条处理,适用于数据量大的场景。
- 同步读
- 无需创建监听器,一次性获取完整数据。
- 方便简单,但是数据量大时会有等待时间,也可能内存溢出。
2、使用流程¶
1)导入依赖¶
<!-- easy Excel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
2)创建数据接收对象¶
@Data
public class User {
@ExcelProperty("姓名")
private String name;
@ExcelProperty("学号")
private String id;
}
3)方式一:监听器读取¶
① 新建监听器
public class UserListener implements ReadListener<User> {
// 这个每一条数据解析都会来调用
@Override
public void invoke(User data, AnalysisContext context) {
System.out.println(data);
}
// 所有数据解析完成后调用
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("已解析完成");
}
}
② 编写读取脚本
public class App {
public static void main(String[] args) {
String FileName = "/xxxx/test.xlsx";
try {
EasyExcel.read(FileName, User.class,
new UserListener()).sheet().doRead();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4)方式二:同步读¶
public static void main(String[] args) {
String FileName = "/xxxx/test.xlsx";
List<User> result = EasyExcel.read(FileName).head(User.class)
.sheet().doReadSync();
for (User user : result) {
System.out.println(user);
}
}
五、banner.txt¶
在resource文件夹下新建banner.txt,即可替换Springboot加载的打印信息
Spring Boot banner在线生成工具,制作下载英文banner.txt,修改替换banner.txt文字实现自定义,个性化启动banner-bootschool.net
_ _ _ _
| | (_) | | (_)
__ __ _ __ ___ | |__ __ __ _ | | __ _ ___ _ __
\ \ /\ / / | '_ ` _ \ | '_ \ \ \ /\ / / | | | |/ / | | / __| | '_ \
\ V V / | | | | | | | | | | \ V V / | | | < | | _ | (__ | | | |
\_/\_/ |_| |_| |_| |_| |_| \_/\_/ |_| |_|\_\ |_| (_) \___| |_| |_|
六、Redis¶
1、Docker安装Redis¶
使用宝塔面板演示
一定要选择「自定义部署」,才能设置Redis密码
可以使用「QuickRedis」可视化工具连接Redis数据库
QuickRedis:https://quick123.net/
2、Session共享(单点登录)¶
整合Springboot,将登录状态的Session信息,存入Redis数据库。
① 引入Redis依赖,注意版本需要和Springboot版本一致(不写版本默认与父版本一致)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
② 引入 spring-session 和 redis 的整合,使得自动将 session 存储到 redis 中
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
③ 进行application.yml的配置(Springboot3.1.5)
# redis配置
spring:
data:
redis:
port: 6379
host: localhost
# password: 1234
database: 0
timeout: 100000
# redis与session整合配置
spring:
session:
redis:
# 缓存刷新模式:保存时才刷新
flush-mode: on_save
# 缓存保存模式:读写操作都进行缓存
save-mode: always
补充Springboot2的配置
3、缓存¶
数据查询慢怎么办 → 提前把数据取出来保存好(通常保存到读写更快的介质,比如内存)
Spring Data:通用的数据访问框架,定义了一组 增删改查 的接口mysql、redis、jpa
① 引入Redis整合依赖,注意版本需要和Springboot版本一致(不写版本默认与父版本一致)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
② 引入 spring-data-redis
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
③ 进行application.yml的配置(Springboot3.1.5)
# redis配置
spring:
data:
redis:
port: 6379
host: localhost
# password: 1234
database: 0
timeout: 100000
④创建自定义序列化
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
// 创建RedisTemplate对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置连接工厂
redisTemplate.setConnectionFactory(connectionFactory);
// 设置Key的序列化
redisTemplate.setKeySerializer(RedisSerializer.string());
// 创建Json序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置Value的序列化
redisTemplate.setValueSerializer(jsonRedisSerializer);
return redisTemplate;
}
}
⑤在Service中添加Redis缓存
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public BaseResponse<List<Download>> getDownloadList(Integer typeId, HttpServletRequest httpServletRequest) {
// 鉴权
Admin admin = (Admin) httpServletRequest.getSession().getAttribute("admin");
String redisKey = admin == null ? "hpan:list:user" : "hpan:list:admin";
// 查询redis
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
List<Download> list = (List<Download>) valueOperations.get(redisKey);
// 存在缓存 => 返回
if (list != null) {
System.out.println("Redis Get Data");
return ResultUtils.success(list);
}
// 不存在缓存 => 查询
QueryWrapper<Download> queryWrapper = new QueryWrapper<>();
if (typeId != null) queryWrapper.eq("type", typeId);
if (admin == null) queryWrapper.eq("is_enable", 1);
list = this.list(queryWrapper);
// 存入redis
valueOperations.set(redisKey, list, 30 * 1000, TimeUnit.MILLISECONDS);
// 返回数据
System.out.println("MySQL Get Data");
return ResultUtils.success(list);
}
通过QuickRedis查看redis数据:
运行结果:
补充:spring-data-redis默认使用了lettuce连接池,提高了连接的复用性。
4、缓存预热¶
优点:
- 解决了第一个加载很慢的问题
- 保护数据库
缺点:
- 增加开发成本(额外的开发、设计)
- 预热的时机和时间如果错了,可能你缓存的数据不对或者太老
- 需要占用额外空间(避免内存溢出)
实现 → 使用定时任务:
- Spring Scheduler(spring boot 默认整合)
- Quartz(独立于 Spring 存在的定时任务框架)
- XXL-Job 分布式任务调度平台(通过界面 + sdk)
使用Spring Scheduler实现定时任务:
① 主类开启 @EnableScheduling
② 给需要定时的方法开启@Scheduled
注解,指定 cron 表达式或者执行频率