- 课程:黑马程序员Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+黑马点评实战项目_哔哩哔哩_bilibili
Mybatis与MybatisPlus:
参考springboot,需要额外写mapper.class,在方法上+@Select等
在ssm中,需要这样实现mybatis:
配置 sql mapper映射文件
sqlsession实现与数据库连接
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(configuration); SqlSession sqlSession = sessionFactory.openSession(); User user = sqlSession.selectOne("com.demo.dao.UserMapper.getUserById", 1); sqlSession.close();
MybatisPlus:自带分页 ,更便捷的 CRUD (create,read,update,delete)操作:
MyBatis-Plus 提供了一个
BaseMapper
接口,它封装了所有常见的 CRUD 操作方法,开发者无需手动编写 SQL 语句,框架会自动根据实体类和方法名生成对应的 SQL 并执行。这就是“更便捷的 CRUD 操作”的含义。
1.Redis下载与安装
1.基于Linux系统来安装Redis,利用docker,使用redis,用linux而不是windows:redis在设计之初是没有官方windows版的
此处选择的Linux版本为CentOS 7
2.docter和linux的关系:
Docker 让 Linux 更方便:Docker 让你在Linux上运行程序变得非常简单。你可以把程序打包成一个容器,然后在任何一台Linux电脑上运行,不用担心环境配置的问题。
3.下载顺序:
linux->VMware虚拟机(linux centos7版本需和vmware一起用)->docker->redis
参考文章:
vm虚拟机下载:VMware Workstation Pro 17下载安装和虚拟机创建-CSDN博客
centos7下载:
Linux发行版Centos7系统官网下载教程,Centos7系统如何搭配VMware虚拟机使用?_centos7下载-CSDN博客
虚拟机安装Docker:点终端即可进行更新
虚拟机Linux系统安装docker_linux虚拟机安装docker-CSDN博客
虚拟机安装redis:安装目录:/home/?/
Redis安装教程(vmware虚拟机上)_vmware虚拟机怎么安装redis-CSDN博客
有点问题没解决:?????????????????
安装时遇到的问题:
1.无法创建新虚拟机: 无法打开配置文件 :
修改:使用管理员身份运行。
2.
修改:挂载点选择biosboot:
3.挂起后重新启动:
修改:以管理员身份运行该程序
参考文章:
VMware开启虚拟机问题:开启此虚拟机需要用到此文件。如果移动了此文件,请提供它的新位置。_开启此虚拟机需要用到此文件。如果移动了此文件,请提供它的新位置。-CSDN博客
其他解决方法:
Vmware虚拟机找不到.vmdk文件,不能开机_虚拟机打不开vmdk文件,并提示找不到指定的文件-CSDN博客
4..yaml配置的修改:
获取host,port:vm终端输入:
redis-cli
ping
config get port
//得到port
config get bind
//得到host此处:
再输入:ifconfig
5.启动
后端启动:
添加spb启动模块:
成功运行:
前端启动:
启动失败:
修改:
1.重新创建nginx中的temp目录和temp/client_body_temp
2.以管理员打开cmd,启动nginx,会出现提示框“是否允许Nginx使用网络”,再次登录url,启动成功(每次启动该程序都要进行的一步)
2.基于Session的登录
BUG:手机号与验证码需一一匹配
1.保存验证码到Session?
当你在网站上看到验证码图片时,网站不仅把验证码显示给你,还会把验证码的正确答案“偷偷”保存在你的Session里。这样做的目的是:
验证你输入的是否正确: 当你输入验证码并提交表单时,网站会去Session里查看保存的正确答案,然后和你输入的内容对比。如果一致,说明你输入正确;如果不一致,说明你输入错误或者可能是机器人。
防止作弊: 如果验证码不保存在Session里,而是直接显示在网页上,那么恶意用户可能会通过技术手段直接读取验证码,从而绕过验证。保存在Session里可以让验证码更安全。
2.登录和更新密码使用Token的原因
无状态和分布式支持:Token(如JWT)是无状态的,适合分布式系统和微服务架构。每次请求携带Token,服务器通过解析Token验证用户身份,无需依赖服务器端的Session存储。
跨域支持:Token可以通过请求头传递,天然支持跨域通信,适合前后端分离的架构。
性能优势:Token机制无需在服务器端存储用户状态,减轻了服务器的内存压力,适合高并发场景。
安全性:Token可以通过签名和加密确保安全性,并且可以设置过期时间,进一步增强安全性。
3. 验证码使用Session的原因
临时性:验证码是临时数据,仅在用户提交表单时使用一次。Session适合存储这种临时且需要快速验证的数据。
安全性:Session数据存储在服务器端,客户端无法直接访问或篡改,更适合存储验证码这种敏感信息。
简单性:使用Session存储验证码的实现方式简单,不需要额外的Token生成、解析和验证逻辑。
Token适用于需要无状态、支持分布式和跨域的场景,如登录认证和API保护。
Session适用于需要临时存储且对安全性要求较高的场景,如验证码。
4.为什么 Token 需要返回凭证?
无状态性:Token 是无状态的,服务器不会存储用户的会话信息。每次请求都需要通过 Token 来验证用户身份,因此 Token 本身就是一个“凭证”,客户端必须在每次请求中携带它。
跨域和分布式支持:Token 通常用于前后端分离的架构或分布式系统中。由于客户端和服务器可能不在同一个域下,Token 需要作为凭证在每次请求中传递,服务器通过解析 Token 来确认用户的身份和权限。
安全性:Token 包含签名,可以防止被篡改。服务器通过验证签名来确保 Token 的合法性,因此 Token 本身就是一个安全的凭证。
5.为什么 Session 不需要返回凭证?
//保存用户信息到sessionsession.setAttribute("user",user);return Result.ok();
Session 是一种服务器端存储机制,服务器在用户登录时会创建一个 Session,并将用户的状态信息存储在服务器端。服务器会生成一个 Session ID,并将其返回给客户端(通常存储在 Cookie 中)。客户端在后续请求中携带 Session ID,服务器通过 Session ID 查找对应的 Session 来验证用户身份。
服务器端存储:Session 的核心数据(如用户身份、权限等)存储在服务器端,客户端只需要携带一个 Session ID(通常是一个随机字符串)。服务器通过 Session ID 找到对应的 Session,从而验证用户身份。
有状态性:Session 是有状态的,服务器会维护用户的会话状态。客户端不需要携带完整的用户信息或权限信息,只需要携带 Session ID 即可。
安全性:Session ID 是由服务器生成的随机字符串,很难被预测或伪造。服务器通过 Session ID 来管理用户的会话状态,客户端不需要关心 Session 的具体内容。
Cookie和Session: 想象一下,你去一个餐厅吃饭,服务员给你一个小纸条,上面写了一些信息,比如你点的菜、座位号等。每次你去柜台拿东西的时候,服务员都会看看这个小纸条,然后根据上面的信息给你服务。这个小纸条就是 Cookie。服务员给你一个小纸条(Cookie),但真正记录你点的菜和座位号的地方是服务员的笔记本(Session)。这个笔记本放在厨房里,服务员通过小纸条上的编号(Session ID)来找到你在笔记本里的记录
6.图示
发送验证码
图示:POST/GET/PATCHE
@RequestParam:它可以帮助你从 HTTP 请求中提取数据,并将其传递给方法中的变量。
代码展示:
Controller:
@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// TODO 发送短信验证码并保存验证码return userService.sendCode(phone,session);}
serviceImpl:
@Overridepublic Result sendCode(String phone, HttpSession session) {//校验if(RegexUtils.isPhoneInvalid(phone)){//校验不通过return Result.fail("手机号格式错误");}//校验通过,生成验证码String code=RandomUtil.randomNumbers(6);//验证码保存到sessionsession.setAttribute("code",code);//发送验证码log.debug("验证码:{}已发送",code);return Result.ok();}
运行:
登录注册
图示:请求参数见Payload
@RequestBody:前端到后台接受JSON格式数据,就用这个
ServiceImpl类创建新方法快捷键:crtl+alt+m
代码展示:
Controller:
@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){// TODO 实现登录功能return userService.login(loginForm,session);}
ServiceImpl:
//登录@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//验证手机号//每一个事务进行完整的校验if(RegexUtils.isPhoneInvalid(loginForm.getPhone())){return Result.fail("手机号格式错误") ;}//验证验证码Object cachecode=session.getAttribute("code");String code=loginForm.getCode();if(cachecode==null||!cachecode.toString().equals(code)){//不一致//反向校验,避免if嵌套return Result.fail("验证码有问题");}//一致//手机号-》用户//mybatisplus select * from user where phone=#{phone};User user=query().eq("phone",loginForm.getPhone()).one();//用户是否存在if(user==null){//用户不存在,根据phone创建新用户user= CreateUserWithPhone(loginForm);}//保存用户信息到sessionsession.setAttribute("user",user);return Result.ok();}private User CreateUserWithPhone(LoginFormDTO loginForm) {User user=new User();user.setPhone(loginForm.getPhone());user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));save(user);return user;}
mybatisplus:
save()
方法的作用是将一个新创建的对象(在这个例子中是User
对象)保存到数据库中。具体来说,它会执行一个 插入(INSERT) 操作,将对象的数据写入数据库表中。
运行:
检验登录
用到拦截器,参考springboot速成(八),未登录不能访问页面内容,也用到了拦截器:
定义拦截器(类上+@Configuration)>配置拦截器
Implenments HandleInterceptor 快捷键:ctrl+i
if(user==null) 快捷键:user.null+enter
LoginFormDTO有phone,code,password,UserDTO有id,nickname,icon,User有id,phone,password,nickName,icon,createTime,updaTime。为什么要创建LoginFormDTO,UserDTO:
1.创建不同的 DTO(Data Transfer Object,数据传输对象)和实体(Entity)类是为了满足不同的需求和目的。
2.
LoginFormDTO
:安全性:只发送必要的信息(如电话号码和密码),而不是用户的全部信息。
简化处理:在处理登录逻辑时,只需要关注
LoginFormDTO
中的字段,简化代码逻辑。前后端分离:在前后端分离的架构中,
LoginFormDTO
用于定义前端发送的数据格式。3.
UserDTO
:(在此处,为了不显示额外不必要用户信息,返回UserDTO)数据封装:封装需要传输的用户信息,而不是直接使用实体类。
灵活性:可以根据需要选择性地包含或排除某些字段,而不需要修改实体类。
解耦:将数据传输逻辑与业务逻辑分离,提高代码的可维护性和可读性。
代码展示:
创建拦截器:
package com.hmdp.utils;import com.hmdp.dto.UserDTO;import com.hmdp.entity.User;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@Configuration
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//获取session//获取session中的用户HttpSession session=request.getSession();Object user=session.getAttribute("user");//判断用户是否存在if (user == null) {//不存在,拦截response.setStatus(401);return false;}//存在,保存UserHolder.saveUser((UserDTO) user);//放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}
配置拦截器:
package com.hmdp.config;import com.hmdp.utils.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/shop-type/**","/upload/**","/voucher/**","/blog/hot","/user/code","/user/login");}
}
Controller:
@GetMapping("/me")public Result me(){// TODO 获取当前登录的用户并返回UserDTO user= UserHolder.getUser();return Result.ok(user);}
运行: