Cookie、Token和Session区别
这里写一篇文章
hello,我是索奇~
精心写了一篇Cookie、Session和Token的 vivid 文章,并分享给大家
我们可以把Cookie、Token和Session看作是三个好基友,它们都是用来跟踪用户的身份和状态的,但是它们之间有一些区别和使用场景。
Cookie
Cookie:曲奇饼,小甜饼;……样的人;(浏览网页后存储在计算机的)缓存文件;<苏格兰>淡面包;漂亮的女孩
啊,不是让你翻译~ 是让你介绍计算机中Cookie~(不过也学会了一个单词)
Cookie就像是你的小秘书,它的主要作用是保存用户的偏好和浏览历史。比如说,你在网上买了一件衣服,但是还没决定是否买下,这时候你可以把这件衣服放进购物车,Cookie就会帮你记住这个购物车里有什么。等到你下次再来这个网站的时候,Cookie就会帮你把购物车里的东西显示出来,让你可以继续购物。
Cookie的数据存储在客户端的浏览器中,不会占用服务器的资源
在浏览器的控制台中,可以直接输入:document.Cookie来查看Cookie。Cookie是一个由键值对构成的字符串,出于安全考虑
httponly类型的获取不到的,不要找半天httponly发现找不到
又多一个名词,再探索一下?
httponly又是什么啊?
HttpOnly就是一个设置在HTTP响应头中的标志,它可以防止某些类型的客户端脚本(如JavaScript)访问cookie。当服务器向客户端发送带有HttpOnly标志的cookie时,客户端的JavaScript代码将无法通过document.cookie访问该cookie,这可以有效地提高Web应用程序的安全性。
如果给某个 cookie 设置了 httponly 属性,则无法通过 JS 脚本
读取到该 cookie 的信息,但还是能通过Application 中手动修改 cookie, 所以只是在一定程度上可以防止 XSS 攻击,不是绝对的安全
Cookie主要用于跟踪用户的偏好和行为,以便提供个性化的体验。例如,在网站上保存用户的登录状态、购物车信息等。
啊,平时刷视频、逛tb、个性化广告等等的信息居然就是这样被页面记录、推送的
还有一个大家都在讨论(众说纷纭)的话题就是-我们平时的浏览记录等信息会被记录吗?
答案是不确定(不保证一定不被记录,不保证一定被记录)
Cookie本身是存储在客户端的,而不是服务器端,所以服务器不需要把
Cookie记录保存到数据库中
但至于记录个人的爱好、浏览记录等信息是否被记录到数据库,如何被记录到数据库,这取决于具体的软件、网站、隐私政策和数据收集方式..
Session
Session就像是你的个人档案,它的主要作用是保存用户的状态和权限。比如说,你在网站上登录之后,服务器就会为你创建一个Session,里面保存了你的登录状态和购物车信息等等。这样,当你在浏览网站的时候,服务器就会根据Session来提供个性化的体验,比如显示你的购物车里有什么,或者显示你最近浏览过的商品。
也可以理解为是一个比较特殊的map ,除了可以像其它map一样存取数据,它还有过期时间、唯一的id区分不同session,
创建该session时,会同时创建一个Cookie,Cookie的key为JSESSIONID,而Cookie的value是该session的id。
又遇到不懂的了吗?Cookie的key是啥东西?
JSESSIONID是一种用于在客户端和服务器端之间传递会话信息的Cookie名称。当用户在浏览器中访问一个需要登录的网站时,服务器会
在后台创建一个会话,并生成一个唯一的Session ID,将其存储在服务器端的Session中,同时,服务器会将Session ID通过Cookie的方式发送给客户端,通常使用的Cookie名称就是OBSESSION
Session的数据信息存放在服务器上,Session的数据只能被服务器访问,因此相对来说比较安全,但是需要占用服务器的资源,
Session主要用于跟踪用户的状态和权限,以便提供个性化的体验。例如,你搜索的内容、在网站上保存用户的登录状态、购物车信息等。
对于Session并没有上限,但出于对服务器端的性能考虑,Session内不要存放过多的东西
Token
Token就像是你的身份证,它的主要作用是用于身份验证和授权。比如说,你在使用某个APP的时候,需要登录才能使用一些功能,这时候APP就会颁发给你一个Token(令牌),你需要在每个请求中携带这个Token,服务器会通过验证Token来确定你的身份和权限,以确保你只能访问你有权访问的内容。
比如用户已经登录了系统, 我给他发一个token, 里边包含了这个用户的 user id, 下一次这个用户再次通过Http 请求访问我的时候, 把这个token 通过Http header 带过来就可以了。
但是这时候感觉和session没区别啊,万一有人伪造做假攻击呢?于是就用算法对数据做了签名,用签名+数据 = token ,签名不知道,也就无法伪造token了
这个token 不保存, 当用户把这个token 给我发过来的时候,我再用同样的算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道用户已经登录过了,并且可以直接取到用户的user id , 如果不相同, 数据部分肯定被人篡改过, 就知道这个人是冒充货,返给它没有认证的信息
Token是一种无状态的身份验证机制,意味着服务器不需要保存Token的状态(这不是大大的减轻了服务器的压力~),前后端分离架构中前端无法直接访问后端的Session。但是,前后端分离架构中依然可以使用Session来存储应用程序的其他状态信息,例如购物车数据等,只是不能用来保存用户的登录状态。
既可以保存在服务器也可以在客户端
Token是一种无状态的身份验证机制,它可以在多个服务器之间共享,而Session则需要在每个服务器上都保存一份。使用Token可以避免Session共享和Session过期等问题,同时也可以降低服务器的负担。
Token 中的数据是明文保存的, 还是可以被别人看到的, 所以我不能在其中保存像密码这样的敏感信息
基于Token的身份验证是无状态的,我们不将用户信息存在服务器或Session中。
大多数使用Web API的互联网公司中,它是Tokens多用户下处理认证的最佳方式
被攻击是不是很烦恼! Token通常用于API身份验证等场景,可以有效避免跨站请求伪造(CSRF)等攻击~
拓展一下Token的身份验证过程
用户在客户端进行登录操作,将用户名和密码发送到服务器端。
服务器端通过验证用户名和密码的正确性,生成一个Token,并将Token返回给客户端。
客户端将Token保存在本地,例如在浏览器的Cookie或localStorage中。
客户端在后续的请求中,将Token发送给服务器端进行身份验证。
服务器端接收到请求后,从请求中获取Token,并对Token进行解密和验证。
如果Token验证通过,服务器端将响应请求并返回所需的数据,否则返回身份验证失败的错误信息。
在身份验证过程中,服务器端通常会对Token进行解密、验证签名、检查Token是否过期等操作,以确保Token的有效性和安全性
栩栩如生、通俗易懂~ 重点讲完了!
简单记一些知识
看完了没,啥也没懂?好吧,无奈,简单记一下区别吧,面试时候不能哑口无言吧
Session和Token是在服务器端保存数据的机制,而Cookie是在客户端保存数据的机制
通常情况单个Cookie保存的数据在4KB以内(面试官:这都知道,给你offer!欣喜若狂的自己:太好了!)
Session和Token通常用于身份验证和状态管理,而Cookie通常用于跟踪用户的偏好和行为
Session和Token通常用于敏感数据的存储和传输,而Cookie通常用于非敏感数据的存储和传输。
Session和Token需要服务器端进行管理和维护,而Cookie可以由客户端自行管理和维护。
Token可以跨域使用,而Session通常只能在同一个域名下使用;Token可以在分布式系统中使用,而Session通常只能在单一服务器上使用。
(可以忽略)写着写着又想要拓展了,哈哈哈,想要探索的伙伴们,一定想要知道单个站点可以存储的Cookie数量,
这里有疑惑?
国际互联网标准是每个网站可以存储的 Cookie 数量不得超过 300 个,具体还是根据不同的浏览器来定,
发现部分博主说单个站点最多保存20个Cookie,这是不合理的,也有近100点赞
网上一连串的信息是复制的,有时我们不能轻易的相信,要学会自己去探索,去验证!不然就误人耳目了
这里是仅仅是为了说明下Cookie的数量,帮助更多伙伴学会探索知识,对原博主没有任何恶意哈
4. 登录相关接口
4.1 登录
登录的信息放到Redis中
接口属性 值
url /user/login
method post
请求参数 username password
返回参数
controller
/** * * @param user * SpringMVC默认情况下,请求体中的数据是以 JSON 格式传输的。如果你不使用 @RequestBody 注解, * SpringMVC 在处理请求时就不会将请求体中的数据解析成 JSON 对象,也就无法将请求体中的数据转化为 User 对象。 * @return */ @PostMapping("/login") public Result<Map<String,Object>> login(@RequestBody User user){ // 根据用户名和密码在数据库中遍历,如果存在表示信息正确,具体的登录逻辑在UserServiceImpl业务层中实现 Map<String,Object> data = userService.login(user); if (data!= null){ return Result.success(data); } // 可以自行拓展为枚举类 return Result.fail(20002,"用户名和密码错误"); }
service
@Autowired private RedisTemplate redisTemplate; /** * @param user * @return * @description 根据用户名和密码查询 */ @Override public Map<String, Object> login(User user) { // 结果不为空,生成token,为空,则将信息写入Redis LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getUsername, user.getUsername()); wrapper.eq(User::getPassword, user.getPassword()); User loginUser = this.baseMapper.selectOne(wrapper); if (loginUser != null) { // 简单项目用UUID,可以改为更好的jwt方案 String key = "user:" + UUID.randomUUID(); // 存入redis // 防止密码存入redis中 loginUser.setPassword(null); /* redisTemplate.opsForValue()是RedisTemplate提供的一个操作字符串类型数据的方法 它返回一个ValueOperations对象, 可以用来对Redis中的字符串类型数据进行操作可以使用Redis中的set、get、delete等操作字符串类型数据的命令。 */ redisTemplate.opsForValue().set(key, loginUser, 30, TimeUnit.MINUTES); //返回数据 Map<String, Object> data = new HashMap<>(); data.put("token", key); return data; } return null; } }
测试的时候用post方法,由于浏览器发送的是get请求,会报错
Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported]
我们需要用postman这个工具进行post请求
整合redis
整合redis需要用启动redis服务
这里只是简单的redis功能,具体看其它项目实现,也可以系统的学习redis
pom
<!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
yml
spring: redis: host: localhost port: 6379
配置类
代码中的StringRedisSerializer 是 RedisTemplate 默认的 key 和 value 的序列化器。在使用 StringRedisSerializer 进行序列化时,它会将字符串对象转换为字节数组,并将其存储到 Redis 中。在读取数据时,它会将字节数组反序列化为字符串对象。
Jackson2JsonRedisSerializer可以将 Java 对象序列化为 JSON 格式的字符串,并将其存储到 Redis 中。在读取数据时,它可以将 JSON 格式的字符串反序列化为 Java 对象;
@Configuration public class MyRedisConfig { //用于创建Redis连接的接口,不需要关心底层 Redis 连接实现的细节,就直接可以使用 Redis 进行数据存储和缓存。 @Resource private RedisConnectionFactory factory; @Bean public RedisTemplate redisTemplate(){ RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); //改变序列化器 redisTemplate.setKeySerializer(new StringRedisSerializer()); //设置为RedisTemplate的连接工厂可以让这个对象与Redis服务器进行交互 redisTemplate.setConnectionFactory(connectionFactory); // 类型不确定所以用Object Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); redisTemplate.setValueSerializer(serializer); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); om.setTimeZone(TimeZone.getDefault()); om.configure(MapperFeature.USE_ANNOTATIONS, false); om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); om.setSerializationInclusion(JsonInclude.Include.NON_NULL); serializer.setObjectMapper(om); return redisTemplate; } }
4.2 获取用户信息
接口属性 值
url /user/info?token=xxx
method get
请求参数 token
返回参数
其中的roles角色表我们在下一节才会讲到
avator(化身):头像的地址
name:登录的用户名
controller
返回值 Result<Map<String,Object>> 在这里写具体的或者?都可以
@GetMapping("/info") public Result<Map<String,Object>> getUserInfo(@RequestParam("token") String token){ // 根据token获取用户信息 Map<String,Object> data = userService.getUserInfo(token); if (data!= null){ return Result.success(data); } // 可以自行拓展为枚举类 return Result.fail(20003,"用户信息无效,请重新登陆"); }
service
@Override public Map<String, Object> getUserInfo(String token) { // 根据token获取用户信息,redis Object obj = redisTemplate.opsForValue().get(token); // 在 redisConfig中已经做了序列化,所以需要用抽象类JSON反序列化取出来,转换成User对象(也可以用其它的实现) if(obj!=null){ User loginUser = JSON.parseObject(JSON.toJSONString(obj), User.class); Map<String,Object> data = new HashMap<>(); data.put("name",loginUser.getUsername()); data.put("avatar",loginUser.getAvatar()); List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId()); //角色,一个人可能有多个角色 data.put("roles", roleList); return data; } return null; }
视频项目中用的是SQL来进行多表联查
UserMapper
public interface UserMapper extends BaseMapper<User> { public List<String> getRoleNameByUserId(Integer userId); }
UserMapper.xml
<select id="getRoleNamesByUserId" parameterType="Integer" resultType="String"> SELECT b.role_name FROM x_user_role a,x_role b WHERE a.`user_id` = #{userId} AND a.`role_id` = b.`role_id` </select>
用postman测试时候先http://localhost:9999/user/login请求token,复制token新建get请求http://localhost:9999/user/info?token写入token成功证明你没错~
4.3 注销
接口属性 值
url /user/logout
method post
请求参数
返回参数 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DwV5GD4c-1675952251553)(C:\Users\dacai\AppData\Roaming\Typora\typora-user-images\image-20230203171855151.png)]
controller
前面是把token保存到了Redis中,把它清除掉即可
前端设置的token名叫x-token
@PostMapping("/logout") public Result<?> logout(@RequestHeader("X-Token") String token){ userService.logout(token); return Result.success("注销成功"); } service public void logout(String token) { redisTemplate.delete(token); }
前端中有这一个代码
before: require('./mock/mock-server.js')
前后端对接的时候应该被移除,或者注释掉
这段代码是在Vue项目中使用mock数据的一种方式。mock数据是指在前端开发过程中,模拟后端接口返回的数据,用于前端开发和调试。在这段代码中,require('./mock/mock-server.js')表示引入mock-server.js文件,该文件中定义了mock数据的生成规则和接口拦截规则。在开发环境中,通过这种方式可以使用mock数据来替代后端接口,方便前端开发和测试。在生产环境中,这段代码应该被移除,以避免不必要的性能损耗。
6. 跨域处理
跨域是指在浏览器中,当前网页所在的域名与当前请求所访问的域名不同,即跨域请求。
举个栗子:如果当前网页的URL为https://www.example.com,则同源策略要求发送请求的URL也必https://www.example.com,否则就会被拦截。
Access-Control-Allow-Origin
这里设置的全局跨域处理,不建议使用注解局部方式
@Configuration public class MyCorsConfig { // 当前跨域请求最大有效时长,这里默认1天 // private static final long MAX_AGE = 24 * 60 * 60; @Bean public CorsFilter corsFilter() { //1.添加CORS配置信息 CorsConfiguration config = new CorsConfiguration(); //1) 允许的域,不要写*,否则Cookie就无法使用了 //这里填写请求的前端服务器 config.addAllowedOrigin("http://localhost:8888"); //2) 是否发送Cookie信息 config.setAllowCredentials(true); //3) 允许的请求方式 config.addAllowedMethod("*"); // config.setMaxAge(MAX_AGE); // 4)允许的所有的请求头 config.addAllowedHeader("*"); //2.添加映射路径,我们拦截一切请求 UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource(); // 接收的是接收CorsConfiguration类型的参数, urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", config); //3.返回新的CorsFilter. return new CorsFilter(urlBasedCorsConfigurationSource); } }
CorsFilter源码大致如下
public CorsFilter(CorsConfigurationSource configSource, CorsProcessor processor) { Assert.notNull(configSource, "CorsConfigurationSource must not be null"); Assert.notNull(processor, "CorsProcessor must not be null"); this.configSource = configSource; this.processor = processor; }
注意:
如果使用的是 Spring Boot 2.4 及以上版本,您还需要在 application.properties 或 application.yml 文件中添加以下配置项,以允许跨域请求携带 Cookie:
spring: mvc: cors: allow-credentials: true
这个配置项会告诉 Spring Boot 在跨域请求中允许携带 Cookie。
提示:脚手架版本用的是:2.13.2 ,所以建议把element更改为2.13.2版本,避免bug
如果分页面是英文,可在main.js下面更改为中文zh-CN
7. 用户管理接口
接口 说明
查询用户列表 分页查询
新增用户
根据id查询用户
修改用户
删除用户 逻辑删除
7.1 查询用户列表
1.controller
@GetMapping("/list") public Result<?> getUserListPage(@RequestParam(value = "username", required = false) String username, @RequestParam(value = "phone", required = false) String phone, @RequestParam("pageNo") Long pageNo, @RequestParam("pageSize") Long pageSize) { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper(); wrapper.eq(username != null, User::getUsername, username); wrapper.eq(phone != null, User::getPhone, phone); Page<User> page = new Page<>(pageNo, pageSize); userService.page(page, wrapper); Map<String, Object> data = new HashMap<>(); data.put("total", page.getTotal()); data.put("rows", page.getRecords()); return Result.success(data); }
IService源码
default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) { return this.getBaseMapper().selectPage(page, queryWrapper); }
2.分页拦截器配置
复制过来的别忘了把new PaginationInnerInterceptor(DbType.MYSQL) 这里改为我们的MYSQL数据库
@Configuration public class MpConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
测试
对接前后端
更改api文件中进行对接后端
7.2 新增用户
密码不能是明文,需要加密处理,用BCryptPasswordEncoder,涉及登录逻辑改动
@PostMapping public Result<?> addUser(@RequestBody User user){ user.setPassword(passwordEncoder.encode(user.getPassword())); userService.save(user); return Result.success("新增用户成功"); }
7.3 修改用户
此处不提供密码更新,大家自行扩展,可以去实现前端右上角菜单的个人信息功能
修改展示
注意这里response.data不加括号,它不是方法!这点容易忽略
saveUser() { // 触发表单验证 this.$refs.userFormRef.validate((valid) => { if (valid) { // 提交给后台 userApi.saveUser(this.userForm).then(response => { // 成功提示 this.$message({ message: response.message, type: 'success' }) // 关闭对话框 this.dialogFormVisible = false // 刷新表格 this.getUserList() }) } else { console.log('error submit!!') return false } }) },
saveUser(user) { if (user.id == null || user.id === undefined) { return this.addUser(user) } return this.updateUser(user) } getUserById(id) { return request({ url: `/user/'+${id}`, method: 'get', data: user }) }
7.4 删除用户
利用MyBatisPlus做逻辑删除处理(MybatisPlus官网上有配置也可以复制哈)
yml(别忘记重启项目)
mybatis-plus: global-config: db-config: logic-delete-field: delted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
Controller
@DeleteMapping("/{id}") public Result<User> deleteUserById(@PathVariable("id") Integer id) { userService.removeById(id); return Result.success("删除成功"); }
我们要学会善于查阅文档,不能局限于笔记、视频中的说明,也要结合别人的理解,善于查阅官方文档,这样才能够进一步的打通自己的任通二脉,找到自己的路~ 加油,未来可期
补充
便于大家快速查阅,这里留存一些整个类的文档
Entity
User
@TableName("x_user") public class User implements Serializable { private static final long serialVersionUID = 1L; //主键字段名为id,主键生成策略为自增长。 @TableId(value = "id", type = IdType.AUTO) private Integer id; private String username; private String password; private String email; private String phone; private Integer status; private String avatar; private Integer deleted; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public String getAvatar() { return avatar; } public void setAvatar(String avatar) { this.avatar = avatar; } public Integer getDeleted() { return deleted; } public void setDeleted(Integer deleted) { this.deleted = deleted; } @Override public String toString() { return "User{" + "id=" + id + ", username=" + username + ", password=" + password + ", email=" + email + ", phone=" + phone + ", status=" + status + ", avatar=" + avatar + ", deleted=" + deleted + "}"; } }
UserRole
@TableName("x_user_role") public class UserRole implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; private Integer userId; private Integer roleId; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } @Override public String toString() { return "UserRole{" + "id=" + id + ", userId=" + userId + ", roleId=" + roleId + "}"; } }
Controller
UserController
@RestController @RequestMapping("/user") public class UserController { @Autowired private IUserService userService; @Autowired private PasswordEncoder passwordEncoder; @GetMapping("/all") public Result<List<User>> getAllUser() { List<User> list = userService.list(); return Result.success(list, "查询成功"); } /** * @param user SpringMVC默认情况下,请求体中的数据是以 JSON 格式传输的。如果你不使用 @RequestBody 注解, * SpringMVC 在处理请求时就不会将请求体中的数据解析成 JSON 对象,也就无法将请求体中的数据转化为 User 对象。 * @return */ @PostMapping("/login") public Result<Map<String, Object>> login(@RequestBody User user) { // 根据用户名和密码在数据库中遍历,如果存在表示信息正确,具体的登录逻辑在UserServiceImpl业务层中实现 Map<String, Object> data = userService.login(user); if (data != null) { return Result.success(data); } // 可以自行拓展为枚举类 return Result.fail(20002, "用户名和密码错误"); } /** * @param token * @return * @description 将名为 "token" 的 HTTP 请求参数绑定到方法参数 token 上。 */ @GetMapping("/info") public Result<Map<String, Object>> getUserInfo(@RequestParam("token") String token) { // 根据token获取用户信息 Map<String, Object> data = userService.getUserInfo(token); if (data != null) { return Result.success(data); } // 可以自行拓展为枚举类 return Result.fail(20003, "用户信息无效,请重新登陆"); } @PostMapping("logout") public Result<?> logout(@RequestHeader("X-Token") String token) { userService.logout(token); return Result.success(); } @GetMapping("/list") public Result<Map<String, Object>> getUserList (@RequestParam(value = "username", required = false) String username, @RequestParam(value = "phone", required = false) String phone, @RequestParam("pageNo") Long pageNo, @RequestParam("pageSize") Long pageSize) { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasLength(username),User::getUsername, username); wrapper.eq(StringUtils.hasLength(phone),User::getPhone, phone); wrapper.orderByDesc(User::getId); Page<User> page = new Page<>(pageNo,pageSize); userService.page(page,wrapper); Map<String,Object> data = new HashMap<>(); data.put("total",page.getTotal()); data.put("rows",page.getRecords()); return Result.success(data); } /** * 新增用户 * @return */ @PostMapping public Result<?> addUser(@RequestBody User user){ user.setPassword(passwordEncoder.encode(user.getPassword())); userService.save(user); return Result.success("新增用户成功"); } @PutMapping public Result<?> updateUser(@RequestBody User user){ user.setPassword(null); userService.updateById(user); return Result.success("修改用户成功"); } @GetMapping("/{id}") public Result<User> getUserById(@PathVariable("id") Integer id) { User user = userService.getById(id); return Result.success(user); } @DeleteMapping("/{id}") public Result<User> deleteUserById(@PathVariable("id") Integer id) { userService.removeById(id); return Result.success("删除成功"); } }
Service
IUserService
IUserService
public interface IUserService extends IService<User> { Map<String, Object> login(User user); Map<String, Object> getUserInfo(String token); void logout(String token); }
UserServiceImpl
UserServiceImpl
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Autowired private RedisTemplate redisTemplate; @Autowired private PasswordEncoder passwordEncoder; /** * @param user * @return * @description 根据用户名查询,加密后处理 */ @Override public Map<String, Object> login(User user) { // 结果不为空并且匹配传入的密码,生成token,为空,则将信息写入Redis LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getUsername, user.getUsername()); User loginUser = this.baseMapper.selectOne(wrapper); if (loginUser != null && passwordEncoder.matches(user.getPassword(),loginUser.getPassword())) { // 简单项目用UUID,可以改为更好的jwt方案 String key = "user:" + UUID.randomUUID(); // 存入redis2 // 防止密码存入redis中 loginUser.setPassword(null); // redisTemplate.opsForValue()是RedisTemplate提供的一个操作字符串类型数据的方法 // 它返回一个ValueOperations对象, // 可以用来对Redis中的字符串类型数据进行操作可以使用Redis中的set、get、delete等操作字符串类型数据的命令。 redisTemplate.opsForValue().set(key, loginUser, 30, TimeUnit.MINUTES); //返回数据 Map<String, Object> data = new HashMap<>(); data.put("token", key); return data; } return null; } // /** // * @param user // * @return // * @description 根据用户名和密码查询 // */ // @Override // public Map<String, Object> login(User user) { // // 结果不为空,生成token,为空,则将信息写入Redis // LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); // wrapper.eq(User::getUsername, user.getUsername()); // wrapper.eq(User::getPassword, user.getPassword()); // User loginUser = this.baseMapper.selectOne(wrapper); // if (loginUser != null) { // // 简单项目用UUID,可以改为更好的jwt方案 // String key = "user:" + UUID.randomUUID(); // // 存入redis2 // // 防止密码存入redis中 // loginUser.setPassword(null); // // /*redisTemplate.opsForValue()是RedisTemplate提供的一个操作字符串类型数据的方法 // 它返回一个ValueOperations对象, // 可以用来对Redis中的字符串类型数据进行操作可以使用Redis中的set、get、delete等操作字符串类型数据的命令。*/ // // redisTemplate.opsForValue().set(key, loginUser, 30, TimeUnit.MINUTES); // //返回数据 // Map<String, Object> data = new HashMap<>(); // data.put("token", key); // return data; // } // // return null; // } @Override public Map<String, Object> getUserInfo(String token) { // 根据token获取用户信息,redis Object obj = redisTemplate.opsForValue().get(token); // 在 redisConfig中已经做了序列化,所以需要用抽象类JSON反序列化取出来,转换成User对象(也可以用其它的实现) if (obj != null) { //将一个Java对象转换为JSON字符串,然后再将JSON字符串转换回Java对象可以将数据格式标准化 // JSON.parseObject第一个参数是要转换的JSON字符串,第二个参数是要转换成的Java对象类型。 User loginUser = JSON.parseObject(JSON.toJSONString(obj), User.class); Map<String, Object> data = new HashMap<>(); data.put("name", loginUser.getUsername()); data.put("avatar", loginUser.getAvatar()); List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId()); //角色,一个人可能有多个角色 data.put("roles", roleList); return data; } return null; } @Override public void logout(String token) { redisTemplate.delete(token); } }
Result
Result
@Data @NoArgsConstructor @AllArgsConstructor public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(){ return new Result<>(20000,"success",null); } public static<T> Result<T> success(T data){ return new Result<>(20000,"success",data); } public static<T> Result<T> success(T data, String message){ return new Result<>(20000,message,data); } public static<T> Result<T> success(String message){ return new Result<>(20000,message,null); } public static<T> Result<T> fail(){ return new Result<>(20001,"fail",null); } public static<T> Result<T> fail(Integer code){ return new Result<>(code,"fail",null); } public static<T> Result<T> fail(Integer code, String message){ return new Result<>(code,message,null); } public static<T> Result<T> fail( String message){ return new Result<>(20001,message,null); } }
config
MpConfig
@Configuration public class MpConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
MyCorsConfig
@Configuration public class MyCorsConfig { // 当前跨域请求最大有效时长,这里默认1天 // private static final long MAX_AGE = 24 * 60 * 60; @Bean public CorsFilter corsFilter() { //1.添加CORS配置信息 CorsConfiguration config = new CorsConfiguration(); //1) 允许的域,不要写*,否则Cookie就无法使用了 //这里填写请求的前端服务器 config.addAllowedOrigin("http://localhost:8888"); //2) 是否发送Cookie信息 config.setAllowCredentials(true); //3) 允许的请求方式 config.addAllowedMethod("*"); // config.setMaxAge(MAX_AGE); // 4)允许的所有的请求头 config.addAllowedHeader("*"); //2.添加映射路径,我们拦截一切请求 UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource(); // 接收的是接收CorsConfiguration类型的参数, urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", config); //3.返回新的CorsFilter. return new CorsFilter(urlBasedCorsConfigurationSource); } }
MyRedisConfig
@Configuration public class MyRedisConfig { @Resource private RedisConnectionFactory factory; @Bean public RedisTemplate redisTemplate(){ RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); redisTemplate.setKeySerializer(new StringRedisSerializer()); Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); redisTemplate.setValueSerializer(serializer); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); om.setTimeZone(TimeZone.getDefault()); om.configure(MapperFeature.USE_ANNOTATIONS, false); om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); om.setSerializationInclusion(JsonInclude.Include.NON_NULL); serializer.setObjectMapper(om); return redisTemplate; } }
yml
yml
server: port: 9999 spring: datasource: username: root password: 123456 url: jdbc:mysql:///xdb redis: port: 6379 host: localhost logging: level: com.suoqi: debug mybatis-plus: global-config: db-config: logic-delete-field: delted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
mapper
UserMapper
public interface UserMapper extends BaseMapper<User> { public List<String> getRoleNameByUserId(Integer userId); }
xml
UserMapper.xml
<?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.suoqi.sys.mapper.UserMapper"> <select id="getRoleNameByUserId" parameterType="Integer" resultType="String"> SELECT b.role_name FROM x_user_role a, x_role b WHERE a.`user_id` = #{userId} AND a.`role_id` = b.`role_id` </select> </mapper>