Skip to content

Commit

Permalink
update 开发
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeaaaaaa committed Sep 18, 2024
1 parent 05e5763 commit 03bbbdc
Show file tree
Hide file tree
Showing 3 changed files with 372 additions and 3 deletions.
314 changes: 311 additions & 3 deletions _posts/2024-09-14-苍穹外卖后端开发.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,96 @@ public class SecurityConfig {

JWT(Json Web Token)是目前最主流的无状态认证机制,当你最开始登录成功后,服务端会给客户端返回一个JWT(包含加密后的用户信息和国企时间),之后的每次请求前端都需要将JWT放置到请求头当中,服务器通过请求头中的JWT来鉴别用户身份;

我们整合登录过程,我们将整个流程具体化:

![前后端身份验证_前端后台模板身份验证-CSDN博客](https://raw.githubusercontent.com/mikeaaaaaa/cloudimg/main/img/2a16584aaf86f24840c3c50370d3665b.png)

其实这里大部分都是说的后端服务器干的事情,那么前端vue服务器都需要做些什么呢?

1. 登录成功后,将后端服务器返回的token信息存储到浏览器![image-20240916173834306](https://raw.githubusercontent.com/mikeaaaaaa/cloudimg/main/img/image-20240916173834306.png)

2. 定义request.js (1)统一处理发送的请求:为其加上token(2)统一处理响应:若为验证未成功,则让用户到登录页面重新登陆

```java
import axios from 'axios'
import router from "@/router";

const request = axios.create({
baseURL: 'http://localhost:8081', // 注意!! 这里是全局统一加上了 '/api' 前缀,也就是说所有接口都会加上'/api'前缀在,页面里面写接口的时候就不要加 '/api'了,否则会出现2个'/api',类似 '/api/api/user'这样的报错,切记!!!
timeout: 5000
})

// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
//从前端拿到user对象 登陆时进行了存储
let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null
if (user) {
config.headers['token'] = user.token; // 设置请求头
}
return config
}, error => {
return Promise.reject(error)
});

// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
response => {
let res = response.data;
// 如果是返回的文件
if (response.config.responseType === 'blob') {
return res
}
// 兼容服务端返回的字符串数据
if (typeof res === 'string') {
res = res ? JSON.parse(res) : res
}

// 当权限验证不通过的时候给出提示
if(res.code === '401'){
router.push("/login")
}

return res;
},
error => {
console.log('err' + error) // for debug
return Promise.reject(error)
}
)


export default request
```





已知Token是存储在客户端本地的,那么已知常用的存储方法有四种,分别是:

cookie、local storage、session storage、session

下面我们对其进行对比分析

| 特性 | Cookie | Local Storage | Session Storage | Session |
| ---------------- | -------------------------------------------- | -------------------------------------- | ---------------------------------- | ---------------------------------- |
| 数据存储位置 | 客户端(浏览器) | 客户端(浏览器) | 客户端(浏览器) | 服务器 |
| 数据容量限制 | 约 4KB |5-10MB |5-10MB | 无明确限制 |
| 生命周期 | 可以设置过期时间,默认在浏览器关闭后仍可保留 | 除非手动删除,否则**永久保留** | 会话结束(关闭页面或浏览器)时删除 | 在会话结束或超时后失效 |
| 跨页面访问 | 可以**跨多个页面共享** | 可以**跨多个页面共享** | **仅限于同一页面会话** | 可以在服务器端**跨多个页面共享** |
| 是否发送到服务器 | 每次 HTTP 请求都会自动发送到服务器 | 不会自动发送 | 不会自动发送 | 服务器端管理,不需要通过网络传输 |
| 安全性 | 不安全,可能会被劫持或伪造 | 相对安全,不会随请求发送 | 相对安全,不会随请求发送 | 服务器端控制,更安全 |
| 用途 | 主要用于保存用户会话、偏好设置等 | 用于持久化数据(用户偏好、本地缓存等) | 用于短期数据存储(如表单数据) | 用于存储服务器端的会话信息 |
| 访问方式 | `document.cookie` | `localStorage` | `sessionStorage` | 通过后端语言(如 JavaPHP)等访问 |
| 与服务器的关系 | 客户端与服务器间传递数据 | 仅客户端使用,不与服务器交互 | 仅客户端使用,不与服务器交互 | 仅服务器端维护,客户端无法直接访问 |
| 示例 | 登录状态、跟踪用户行为 | 持久化登录状态、主题颜色等 | 临时保存用户输入 | 用户登录会话管理 |

上述表格的补充:上述四种存储方式都支持 key-value形式存储数据;已知cookie会每次随着request的发送附加到请求同当中,因此cookie不宜过大, 不然会浪费带宽,因此需要本地存储 localStrorage与session Storage两个配合,并且localStorage可永久存储,而Session Storage页面关闭就没了(不支持跨标签页)。

依赖:

```xml
Expand Down Expand Up @@ -402,7 +492,9 @@ public class AuthController {



### 数据库时间存储
### 时间配置

#### 基本使用

java8之前的时间相关操作大多使用Date类或者Calendar类,比如:

Expand Down Expand Up @@ -469,7 +561,7 @@ System.out.println(dateTime);



Java格式化:
#### Java格式化:

```java
import com.fasterxml.jackson.annotation.JsonFormat;
Expand All @@ -486,11 +578,72 @@ public class Event {

这样此Java格式化为Json字符串时,就会按照指定格式;通过 `@JsonFormat` 注解可以控制时间格式的序列化和反序列化,确保传输到前端时是正确的格式

数据库映射:
#### **重点**(使用消息转换器):

像这样一条一调加注解太麻烦了,我们可以在 `WebMvcConfiguration`中扩展啊Spring MVC 的消息转换器,同意对日期类型进行格式化处理;消息转换器的作用就是同意对 后端返回给前端的数据做处理;我们在`webmvcconfig`类中重写一个方法即可:

```java
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 创建一个消息转换器对象
MappingJackson2CborHttpMessageConverter converter = new MappingJackson2CborHttpMessageConverter();
// 需要为消息转换器设置一个对象转换器,对象转换器是用来将对象转换为 JSON 格式的
converter.setObjectMapper(new JacksonObjectMapper());
// 将消息转换器添加到 Spring MVC 中,通常需要
converters.add(0,converter);

}
```

自己写的数据转换器如下:

```java
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {

public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}

```



#### 数据库映射:

+ 在数据库中,通常使用 `DATETIME``TIMESTAMP` 字段类型存储日期时间信息。
+ `LocalDateTime` 可以很好地映射到这两种类型,无需额外的转换。





### 测试

有时候测试,不能仅仅依靠前后端联调,实际开发中前后端可能开发进步不同;因此测试主要依靠 **接口文档测试**
Expand Down Expand Up @@ -557,3 +710,158 @@ Swagger 是一种用于设计、构建、记录和测试 **RESTful API** 的开



### ThreadLocal

根据之前学习的JWT知识,我们通过解析JWT我们是能够解析出用户ID的,但是我们将解析过程制作成了 `Interceptor`,我们无法在Service中获取到用户ID。

还有我们需要知道,客户端每一个发送的请求都对应着一个线程 `Thread`,我们在 Interceptor、handler、service中打印 `Thread.currentThrad.getId()`,会得到相同的结果。

ThreadLocal为每个线程提供一个单独的存储空间,具有线程隔离的效果,因此我们可以在线程空间中存储用户ID,为此我们创建了一个 `BaseContext`的方法类:

```java
package com.sky.context;

public class BaseContext {

public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

public static void setCurrentId(Long id) {
threadLocal.set(id);
}

public static Long getCurrentId() {
return threadLocal.get();
}

public static void removeCurrentId() {
threadLocal.remove();
}

}
```

通过上述代码我们可以猜到,ThreadLoca只能存储一个数值,但是好在其允许存储任意一个对象,当数值较多的时候,我们可以将各个数值封装到一个对象当中即可。

### 分页查询

1. 根据页码展示员工信息
2. 每页展示10条数据
3. 分页查询时,可根据员工姓名进一步筛选
4. 每条信息都支持 修改、禁用、删除操作

插播一条小知识:![image-20240916191019718](https://raw.githubusercontent.com/mikeaaaaaa/cloudimg/main/img/image-20240916191019718.png),这个按钮其实非常好用,可以快速在当前文件的位置!!!



对于翻页查询,本质上是基于 底层mysql的分页查询:

```mysql
select * from employee limit 0,10 // 表示从第0条开始,查询10
```

我们要做的就是将这两个数值计算出来填一下就好了,但是需要我们自己计算的话,就太麻烦了,我们有专门的插件来完成:

`mybatis`提供了一个非常好的插件 page-helper,依赖如下:

```xml
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.5</version>
</dependency>
```

插件的原理就是mybatis底层的拦截器,之后进行mysql语句的拼接,哈哈看了看源码就会发现 他也是通过ThreadLocal将页码与每页记录数传递给mysql拦截器!!

之后代码层面就非常简单了:

```java
public PageResult<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
// 开始分页查询,首先设置页码和每页的记录数
PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
// 查询员工列表
Page<Employee> employeePage = employeeMapper.pageQuery(employeePageQueryDTO);
// 封装分页结果
PageResult<Employee> pageResult = new PageResult<>();
pageResult.setTotal(employeePage.getTotal());
pageResult.setRecords(employeePage.getResult());
return pageResult;
}
```

还有,为了实现模糊查询,我们首先了解一下sql的模糊查询:

like关键字:搭配常见的以下通配符:

+ 百分号%:表示0或多个字符(最常用
+ 下划线_:表示单个字符
+ 方括号[]:表示字符集中任意一个字符
+ 排除字符集[^]:表示排除字符集中任意一个数值

举例如下:

```mysql
select * from employee where name like '%hao[12]'
```





此外还需要使用一项技术,那就是动态sql语句:

如下:

```xml
<mapper namespace="com.sky.mapper.EmployeeMapper">

<select id="pageQuery" resultType="Employee">
select * from employee
<where>
<if test="name != null and name != ''">
and name like concat('%', #{name}, '%')
</if>
</where>
order by create_time desc
</select>
</mapper>
```

有几项需要注意的点:

1. 前面说了,Page是ListArray的子类,所以ResultType只需要写泛型类就行
2. 使用了pagehelper就不需要再使用limit进行sql语句拼接
3. 想要实现mapper类与xml文件快速跳转,只需要下载一个idea插件 `mubatisx`即可

最后,我们可以通过设置mapper接口文件的日志输出设置为debug,我们就能看到pagehelper为我们设置好的 sql 语句输出!!!



### 各种坑

1. 大驼峰映射在动态sql语句编写的时候作用到底是什么

回答:在 SQL 查询结果返回时,`user_id` 字段会自动映射到 `userId` 属性,`user_name` 字段会自动映射到 `userName` 属性,无需额外配置,只有这么一个作用,就是我们可以不再写 **别名**了!!!

2. 为什么

```
<if test="status != null">
status = #{status},
</if>
```
可以但是
```
<if test="status != null and status!=''">
status = #{status},
</if>
```
不行!!!,这个很正常,因为status是基本数据类型,不能和空字符串做比较,会报错的!!!
3. 关于@RequestBody的使用,我们只需要在post请求时使用这个注解,当使用的是get请求时,我们写了这个注解反而会报错,只需要什么都不屑就能够实现对象的映射;
4.
49 changes: 49 additions & 0 deletions _posts/2024-09-16-奇怪的知识又增加了.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---

title: 奇怪的知识又增加了
date: 2024-09-16 11:00:00 +0800
categories: [Mixed Knowledge]
tags: [Mixed Knowledge]

---

## Typora配置

最主要的就是配置typora图床,我在网上看了很多免费的图床,但是感觉懒得搞了,不如自己的github仓库来的方便(就是访问的时候得挂代理这一点麻烦点),但是无所谓了,总感觉自己的才是最好的。

首先就是把图像保存的设置搞一下,这样每次在typora中插入图片就会自动上传到GitHub仓库并更换图片路径;

![image-20240916192544767](https://raw.githubusercontent.com/mikeaaaaaa/cloudimg/main/img/image-20240916192544767.png)

当然如图我们使用了`pig-go`这个技术,因此我们还需要进行相应的配置才行,我个人的配置文件如下:

```json
{
"picBed": {
"github": {
"repo": "mikeaaaaaa/cloudimg",
"token": "自己在github中生成",
"path": "img/",
"customUrl": "",
"branch": "main"
},
"current": "github",
"uploader": "github"
},
"picgoPlugins": {
"picgo-plugin-super-prefix": true
},
"picgo-plugin-super-prefix": {
"prefixFormat": "YYYY/MM/DD/",
"fileFormat": "YYYYMMDD-HHmmss"
}
}

```

看到我还学着网上使用了一个插件,这个插件需要使用nodejs的npm进行安装 `npm install picgo-plugin-super-prefix`

当然还有最后一件事情,每次图片上传都会进行一次图片暂存,过一段时间我们最好将图片手动删除掉,路径如下:

> `C:\Users\xxx\AppData\Roaming\Typora\typora-user-images`
Loading

0 comments on commit 03bbbdc

Please sign in to comment.