准备:
springboot 2.5.5
jdk 1.8
没有操作刷新token功能,也没有放redis做缓存

1.先贴代码

2.后讲一下验证逻辑

1.导入依赖

        <!--shiro--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.7.1</version></dependency><!--集成jwt实现token认证--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.2.0</version></dependency>

2.创建JWTUtil工具类

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.ronsafe.wlw.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;public class JWTUtil {// 过期时间 2 小时private static final long EXPIRE_TIME = 2 * 60 * 60 * 1000;// 密钥private static final String SECRET = "jwt+shiro";@Autowiredprivate UserMapper userMapper;/*** 生成 token*/public static String createToken(String username) {try {Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);Algorithm algorithm = Algorithm.HMAC256(SECRET);//jwt的header部分Map<String ,Object> map=new HashMap<>();map.put("alg","HS256");map.put("typ","JWT");// 附带username信息return JWT.create().withHeader(map)//jwt的header部分.withClaim("username", username)//私有声明.withExpiresAt(date)//过期时间.withIssuedAt(new Date())//签发时间.sign(algorithm);//签名} catch (Exception e) {return null;}}/*** 校验 token 是否正确*///校验token的有效性,1、token的header和payload是否没改过;2、没有过期public static boolean verify(String token) {try {//解密JWTVerifier verifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
//            System.out.println("5555555->error1111111111");verifier.verify(token);return true;}catch (Exception e){return false;}}/*** 获得token中的信息,无需secret解密也能获得*/public static String getUsername(String token) {try {DecodedJWT jwt = JWT.decode(token);return jwt.getClaim("username").asString();} catch (JWTDecodeException e) {return null;}}public static String getCurrentUsername(HttpServletRequest request){String accessToken = request.getHeader("Jmt-token");return getUsername(accessToken);}
}

3.创建类JwtToken

import org.apache.shiro.authc.AuthenticationToken;public class JwtToken implements AuthenticationToken {private String token;public JwtToken(String token) {this.token = token;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}}

4.创建ShiroRealm类

import com.ronsafe.wlw.util.JWTUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;@Component
@Slf4j
public class ShiroRealm extends AuthorizingRealm {/*** 根据token判断此Authenticator是否使用该realm* 必须重写此方法,不然会报错*/@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}/*** 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//        System.out.println("7777777777777777");String token = (String) authenticationToken.getCredentials();// 解密获得username,用于和数据库进行对比String username = null;try {username= JWTUtil.getUsername(token);}catch (Exception e){throw new AuthenticationException("token非法,不是规范的token,可能被篡改了,或者过期了");}if (username == null || !JWTUtil.verify(token)) {
//            System.out.println("5555555->error2222222222");throw new AuthenticationException("token认证失效,token错误或者过期,重新登陆");}
//        System.out.println("8888888888888888888888");return new SimpleAuthenticationInfo(token,token,"ShiroRealm");}/*** 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return  null;}
}

5.创建类JwtFilter

import com.alibaba.fastjson.JSON;
import com.ronsafe.wlw.util.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {private boolean allowOrigin = true;public JwtFilter(){}public JwtFilter(boolean allowOrigin){this.allowOrigin = allowOrigin;}/*** 如果带有 token,则对 token 进行检查,否则直接通过*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
//        System.out.println("555555555555555555");try {executeLogin(request, response);} catch (Exception e) {
//            System.out.println("5555555->error333333333333");//token 错误responseError(response);}
//        System.out.println("1010101010");return true;}/*** 判断用户是否想要登入。* 检测 header 里面是否包含 token 字段*/@Overrideprotected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {HttpServletRequest req = (HttpServletRequest) request;String token = req.getHeader("Jmt-token");return token != null;}/*** 执行登陆操作*/@Overrideprotected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
//        System.out.println("6666666666666");HttpServletRequest httpServletRequest = (HttpServletRequest) request;String token = httpServletRequest.getHeader("Jmt-token");JwtToken jwtToken = new JwtToken(token);// 提交给realm进行登入,如果错误它会抛出异常并被捕获getSubject(request, response).login(jwtToken);// 如果没有抛出异常则代表登入成功,返回true
//        System.out.println("9999999999999999999");return true;}/*** 对跨域提供支持*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;HttpServletResponse httpServletResponse = (HttpServletResponse) response;httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));//前后端分离,shiro过滤器配置引起的跨域问题// 是否允许发送Cookie,默认Cookie不包括在CORS请求之中。设为true时,表示服务器允许Cookie包含在请求中。httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");//前后端分离,shiro过滤器配置引起的跨域问题// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {httpServletResponse.setStatus(HttpStatus.OK.value());return false;}return super.preHandle(request, response);}/*** 非法请求返回401,前端拦截到登录页*/private void responseError(ServletResponse response) {HttpServletResponse httpServletResponse = WebUtils.toHttp(response);httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());httpServletResponse.setCharacterEncoding("UTF-8");httpServletResponse.setContentType("application/json; charset=utf-8");try (ServletOutputStream out = httpServletResponse.getOutputStream()) {
//            System.out.println("5555555->error444444444444444");out.write(JSON.toJSONString(Result.fail(401,"身份验证失败,请重新登陆!")).getBytes("utf-8"));} catch (IOException e) {throw new AuthenticationException("直接返回Response信息出现IOException异常:" + e.getMessage());}}
}

6.创建类ShiroConfig

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;@Slf4j
@Configuration
public class ShiroConfig {/*** 先经过token过滤器,如果检测到请求头存在 token,则用 token 去 login,接着走 Realm 去验证*/@Beanpublic ShiroFilterFactoryBean factory(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
//        System.out.println("1111111111111");ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();factoryBean.setSecurityManager(securityManager);Map<String, Filter> filterMap = new LinkedHashMap<>();// 添加自己的过滤器并且取名为jwtfilterMap.put("jwt", new JwtFilter());factoryBean.setFilters(filterMap);// 设置无权限时跳转的 url;factoryBean.setUnauthorizedUrl("/unauthorized/relogin");Map<String, String> filterRuleMap = new HashMap<>();//添加不需要拦截的urlfilterRuleMap.put("/unauthorized/**","anon");
//        //登录不需要拦截filterRuleMap.put("/login","anon");
//        //处理swagger不能访问问题filterRuleMap.put("/swagger-ui.html", "anon");filterRuleMap.put("/swagger**/**", "anon");filterRuleMap.put("/webjars/**", "anon");filterRuleMap.put("/v2/**", "anon");//这个需要放到最下面// 所有请求通过我们自己的JWT FilterfilterRuleMap.put("/**", "jwt");factoryBean.setFilterChainDefinitionMap(filterRuleMap);
//        System.out.println("2222222222222222222222");return factoryBean;}/*** 注入 securityManager*/@Bean(name = "securityManager")public DefaultWebSecurityManager securityManager(ShiroRealm customRealm) {
//        System.out.println("333333333333333");DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//设置自定义realm.securityManager.setRealm(customRealm);//关闭shiro自带的sessionDefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();defaultSessionStorageEvaluator.setSessionStorageEnabled(false);subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);securityManager.setSubjectDAO(subjectDAO);
//        System.out.println("444444444444444");return securityManager;}/*** 添加注解支持*/@Beanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();// 强制使用cglib,防止重复代理和可能引起代理出错的问题defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);return defaultAdvisorAutoProxyCreator;}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}
}

7.创建类LoginController

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ronsafe.wlw.entity.SysUser;
import com.ronsafe.wlw.service.UserService;
import com.ronsafe.wlw.util.JWTUtil;
import com.ronsafe.wlw.util.PasswordUtil;
import com.ronsafe.wlw.util.Result;
import com.ronsafe.wlw.util.StatusCode;
import com.ronsafe.wlw.vo.UserVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.HashMap;/*** @Author R0137 csy* @Date 2021/11/9 14:46*/
@RestController
@Api(tags = "系统管理")
public class LoginController {@Autowiredprivate UserService userService;@CrossOrigin@PostMapping("/login")@ApiOperation("登录")public Result login(String username,String password){QueryWrapper<SysUser> wrapper=new QueryWrapper<>();wrapper.eq("username",username);SysUser user = userService.getOne(wrapper);if (user==null) return Result.fail(StatusCode.LOGINERROR,"用户不存在!");password = PasswordUtil.encrypt(username,password,user.getSalt());if(!user.getPassword().equals(password)) return Result.fail(StatusCode.LOGINERROR,"密码错误!");String token = JWTUtil.createToken(username);HashMap<String, Object> result = new HashMap<>();UserVO userVO = new UserVO();BeanUtils.copyProperties(user,userVO);result.put("token",token);result.put("user",userVO);return Result.success(result);}
}

至此所有集成代码都粘贴完毕,下面粘一下工具类

public class Result {//是否成功private boolean flag;//返回的状态码private Integer code;//返回信息private String message;//返回数据private Object data;//全参构造方法public Result(boolean flag, Integer code, String message, Object data) {//super();this.flag = flag;this.code = code;this.message = message;this.data = data;}//无参构造方法public Result() {}//没有返回数据的方法public Result(boolean flag, Integer code, String message) {super();this.flag = flag;this.code = code;this.message = message;}// 通用的成功  无返回结果public static Result success() {return new Result(true, StatusCode.OK, "OK", null);}// 通用的成功  有返回结果public static Result success(Object data) {return new Result(true, StatusCode.OK, "OK", data);}// 通用的失败创建接口  没有返回结果public static Result fail(int statusCode, String message) {return new Result(false, statusCode, message, null);}// 通用的失败创建接口  有返回结果public static Result fail(int statusCode, String message, Object data) {return new Result(false, statusCode, message, data);}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}
}
package com.ronsafe.wlw.util;import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import java.security.Key;
import java.security.SecureRandom;
import java.util.Random;public class PasswordUtil {/*** 随机数* @param place 定义随机数的位数*/public static String randomGen(int place) {String base = "qwertyuioplkjhgfdsazxcvbnmQAZWSXEDCRFVTGBYHNUJMIKLOP0123456789";StringBuffer sb = new StringBuffer();Random rd = new Random();for(int i=0;i<place;i++) {sb.append(base.charAt(rd.nextInt(base.length())));}return sb.toString();}/*** JAVA6支持以下任意一种算法 PBEWITHMD5ANDDES PBEWITHMD5ANDTRIPLEDES* PBEWITHSHAANDDESEDE PBEWITHSHA1ANDRC2_40 PBKDF2WITHHMACSHA1* *//*** 定义使用的算法为:PBEWITHMD5andDES算法*/public static final String ALGORITHM = "PBEWithMD5AndDES";//加密算法public static final String Salt = "63293188";//密钥/*** 定义迭代次数为1000次*/private static final int ITERATIONCOUNT = 1000;/*** 获取加密算法中使用的盐值,解密中使用的盐值必须与加密中使用的相同才能完成操作. 盐长度必须为8字节* * @return byte[] 盐值* */public static byte[] getSalt() throws Exception {// 实例化安全随机数SecureRandom random = new SecureRandom();// 产出盐return random.generateSeed(8);}public static byte[] getStaticSalt() {// 产出盐return Salt.getBytes();}/*** 根据PBE密码生成一把密钥* * @param password*            生成密钥时所使用的密码* @return Key PBE算法密钥* */private static Key getPBEKey(String password) {// 实例化使用的算法SecretKeyFactory keyFactory;SecretKey secretKey = null;try {keyFactory = SecretKeyFactory.getInstance(ALGORITHM);// 设置PBE密钥参数PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());// 生成密钥secretKey = keyFactory.generateSecret(keySpec);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}return secretKey;}/*** 加密明文字符串* * @param plaintext*            待加密的明文字符串* @param password*            生成密钥时所使用的密码* @param salt*            盐值* @return 加密后的密文字符串* @throws Exception*/public static String encrypt(String plaintext, String password, String salt) {Key key = getPBEKey(password);byte[] encipheredData = null;PBEParameterSpec parameterSpec = new PBEParameterSpec(salt.getBytes(), ITERATIONCOUNT);try {Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);//update-begin-author:sccott date:20180815 for:中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7encipheredData = cipher.doFinal(plaintext.getBytes("utf-8"));//update-end-author:sccott date:20180815 for:中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7} catch (Exception e) {}return bytesToHexString(encipheredData);}/*** 解密密文字符串* * @param ciphertext*            待解密的密文字符串* @param password*            生成密钥时所使用的密码(如需解密,该参数需要与加密时使用的一致)* @param salt*            盐值(如需解密,该参数需要与加密时使用的一致)* @return 解密后的明文字符串* @throws Exception*/public static String decrypt(String ciphertext, String password, String salt) {Key key = getPBEKey(password);byte[] passDec = null;PBEParameterSpec parameterSpec = new PBEParameterSpec(salt.getBytes(), ITERATIONCOUNT);try {Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);passDec = cipher.doFinal(hexStringToBytes(ciphertext));}catch (Exception e) {// TODO: handle exception}return new String(passDec);}/*** 将字节数组转换为十六进制字符串* * @param src*            字节数组* @return*/public static String bytesToHexString(byte[] src) {StringBuilder stringBuilder = new StringBuilder("");if (src == null || src.length <= 0) {return null;}for (int i = 0; i < src.length; i++) {int v = src[i] & 0xFF;String hv = Integer.toHexString(v);if (hv.length() < 2) {stringBuilder.append(0);}stringBuilder.append(hv);}return stringBuilder.toString();}/*** 将十六进制字符串转换为字节数组* * @param hexString*            十六进制字符串* @return*/public static byte[] hexStringToBytes(String hexString) {if (hexString == null || hexString.equals("")) {return null;}hexString = hexString.toUpperCase();int length = hexString.length() / 2;char[] hexChars = hexString.toCharArray();byte[] d = new byte[length];for (int i = 0; i < length; i++) {int pos = i * 2;d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));}return d;}private static byte charToByte(char c) {return (byte) "0123456789ABCDEF".indexOf(c);}}

代码粘完了,讲一下流程






一些问题
1.没有登出?
目前没有结合redis存token,也不存在token在线操作刷新的问题,所以后端不需要做什么,如果用户主动登出,前端删除用户信息,回到登录界面即可,如果是token过期的话,用户带着过期的token过来会给前端返回401,前端拦截,再执行退出操作即可
2.获取当前登录用户
通过jwtUtil工具类中的getCurrentUsername方法拿到用户名,即可以拿到用户

springboot+shiro+jwt实现token认证登录相关推荐

  1. springboot+vue jwt校验token 单点登录

    SSO(Single Sign On)模式 CAS单点登录.OAuth2 分布式,SSO(single sign on)模式:单点登录英文全称Single Sign On,简称就是SSO.它的解释是: ...

  2. SpringBoot集成JWT实现token验证

    Jwt全称是:json web token,以JSON对象的形式安全的传递信息.它将用户信息加密到token里,服务器不保存任何用户信息.服务器通过使用保存的密钥验证token的正确性,只要正确即通过 ...

  3. jwt重放攻击_【干货分享】基于JWT的Token认证机制及安全问题

    一步一步教你基于JWT的Token认证机制实现,以及如何防范XSS攻击.Replay攻击和中间人攻击. 文章目录 一.几种常用的认证机制 1.1 HTTP Basic Auth HTTP Basic ...

  4. SpringSecurity - 整合JWT使用 Token 认证授权

    一.SpringSecurity 前面讲解了SpringSecurity的动态认证和动态权限角色,我们都知道在现在大多都是微服务前后端分离的模式开发,前面讲的还是基于Session的,本篇我们整合JW ...

  5. 基于JWT的Token认证机制实现

    一.基于JWT的Token认证机制实现 1.什么是JWT JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息. 2.JWT组成 ...

  6. SpringBoot 整合 JWT 实现 Token 验证

    前言 在Spring Security整合oauth2实现认证token也不满足实际生产需求的时候,可以整合Jwt实现token认证,完全手写获取token,认证token的方法. Maven依赖包 ...

  7. 前后端分离 springboot shiro+jwt token认证 权限校验

    项目源码 国涛/springboot-shiro-jwthttps://gitee.com/dugt/springboot-shiro-jwt GitHub - dugt-1998/springboo ...

  8. springboot+shiro+jwt实现登录+权限验证

    目录 一.简介: JWT优点: JWT缺点: shiro: JWT: 1.JWT头 2.有效载荷 3.签名哈希 4.Base64URL算法 二.实现 1.引入maven依赖 2.编写shiro配置类 ...

  9. SpringBoot集成JWT实现Token登录验证

    目录 1.1 JWT是什么? 1.2 JWT主要使用场景 1.3 JWT请求流程 1.4 JWT结构 二,SpringBoot集成JWT具体实现过程 2.1添加相关依赖 2.2自定义跳出拦截器的注解 ...

最新文章

  1. RecyclerView滑动到指定位置,并置顶
  2. WP7之题样式与数据绑定
  3. oracle迁移postsql的,osdba's blog : Oracle迁移PostgreSQL系列文章之二:merge语句
  4. 【OpenCV3】图像翻转——cv::flip()详解
  5. mysql分库分表风险_数据库分库分表存在的问题及解决方案
  6. 一份完整的机房建设方案
  7. centos7.9更改root账号密码
  8. ubuntu16.04卡在了’SMBus Host Controller not enabled‘
  9. docker 简版教程
  10. java做图形界面计算n_n皇后问题回溯法---java图形界面实现回溯过程
  11. Java实战之管家婆记账系统(24)——项目总结
  12. 网速魔法师 v1.8 官方安装版
  13. webpack 的plugin简单实现 customize-cra
  14. shap 解释理赔时效模型特征
  15. 苹果要求部分员工佩戴警用级随身摄像头
  16. windows搭建redis java简易访问客户端
  17. html标签图片填充背景色快捷键,ps中填充颜色的快捷键是什么?
  18. CSS3的:nth选择器
  19. 2023年1月伊凡梳理如何快速安装git并且配置本地gitee账号权限方便拉代码
  20. mysql temporary_MySQL中临时表(TEMPORARY)

热门文章

  1. 零基础带你学习MySQL—数学函数(十四)
  2. JavaScript学习(六)—location对象常用的属性和方法
  3. FCFS,SJF,HRRN调度算法
  4. 力扣 两个数组的交集
  5. 职场人如何做好「公开表达」,提升个人影响力?
  6. 电脑黑屏的原因有哪些
  7. erp系统是什么东西
  8. 为什么有人说“穷人玩股票一般都会赔”?
  9. ATM机为什么不能存100张?
  10. 成年人的世界里,赚钱是保护自己和身边人最高效的手段