vue+springboot实现JWT登录验证

目录

  • 前言
    • 概念
    • 实际演示
        • 路由信息
        • 初始访问登录界面
        • 登录验证
        • 验证过期
    • vue实现
      • 依赖引入
      • main.js
      • 获取和设置token工具类
      • 登录方法
        • 实体
        • 登录方法
        • axios请求
      • router配置
    • springboot实现
      • 依赖引入
      • JWT工具类
      • 忽视jwt验证注解
      • 拦截器逻辑
      • 跨域&调用拦截器配置
      • 登录接口&验证token接口
  • 结语

前言

最近在研究SSO(单点登录)系统,对于内部是如何实现登录验证的产生了兴趣,而现在终于研究出它是如何验证成功的,接下来我将讲解如何通过vue和springboot实现Jwt验证登录
🌺🌹🥀🌺🥀🌹🌺🌹🥀🌺🥀🌹

概念

在正式开始之前,我同样会讲解一下概念
单点登录:

单点登录(Single Sign-On, SSO)是一种身份认证授权机制,允许用户在多个应用程序或系统中进行登录,并在登录后可以无需重新输入凭据就能访问其他应用程序。通过SSO,用户只需登录一次,即可获得对多个相关但独立的软件系统或资源的访问权限。

那么这篇文章,只会讲解如何实现身份认证,并不会讲解如何实现SSO

🟠🟡🔴🟠🟣🔵🟡🟠🟣


JWT:

JWT全称为JSON Web Token,是一种开放标准(RFC 7519),定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息。它通常用于在用户和服务之间传递身份认证信息和声明。JWT通常被用作实现身份验证和授权的标准方法。

JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改

🌺🌹🥀🌺🥀🌹🌺🌹🥀🌺🥀🌹

实际演示

路由信息

在我的项目中,我的router页面有这些:

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/main',
    component: () => import('@/views/main'),
    children: [
      {
        path: '',
        name: 'dashBorad',
        component: () => import('@/views/dashBorad')
      },
      {
        path: '/menuPage',
        name: 'menuPage',
        component: () => import('@/views/menuPage')
      },
      {
        path: '/userDataManage',
        name: 'userDataManage',
        component: () => import('@/views/user/userDataManage')
      }
    ]
  },
  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  // 404 page must be placed at the end !!!
  {path: '*', redirect: '/404', hidden: true}
]
初始访问登录界面

初始我访问界面,会到登录界面
在这里插入图片描述
假如我在未登录的情况下想访问其他路由,会禁止:
在这里插入图片描述
自动会跳转到登录界面
在这里插入图片描述

登录验证

假如输入账号密码登录之后,才能进入到系统内:
在这里插入图片描述
这个时候能够切换到不同的界面:
在这里插入图片描述
并且能够调用后端接口查询数据:
在这里插入图片描述

验证过期

但是一旦jwt验证过期,为演示方便,这边将手动把token修改错误

此时再跳转界面和查数据都会,报错,且跳转到登录页:
在这里插入图片描述
在这里插入图片描述

🧡💚💛🧡💜🧡🧡💚💛🧡💜🧡
🌺🌷🌻🌼🌷🌺🌷🌻🌼🌷🌻🌼~~~~~~~~
🧡💚💛🧡💜🧡🧡💚💛🧡💜🧡

vue实现

🌴🌳🍀🌲🥀🍁

依赖引入

在我的项目中,涉及相关JWT验证的有如下:

// axios
npm i axios@1.5.0
// elementui
npm i element-ui -S
// router
npm i vue-router
// js-cookie
npm i js-cookie

🧡🧡🧡🧡🧡🧡🧡🧡🧡🧡🧡🧡

main.js

这时main.js的代码如下:

import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip = false

Vue.use(ElementUI)
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

💛💛💛💛💛💛💛💛💛💛💛💛

获取和设置token工具类

这里写一个工具类专门获取和设置工具类
建一个token.js

import Cookies from 'js-cookie'
// 获取token的key,需要和后端一致
const TokenKey = 'Authorization'
// 获取token
export function getToken () {
  return Cookies.get(TokenKey)
}
// 设置token
export function setToken (token) {
  return Cookies.set(TokenKey, token)
}
// 移除token
export function removeToken () {
  return Cookies.remove(TokenKey)
}

💙💙💙💙💙💙💙💙💙💙💙💙

登录方法

因为我的登录方法存在别的逻辑,如验证码,记住我等等,因此,这里演示只给出最纯粹的登录逻辑

实体
// 设置用户名和密码登录
      loginForm: {
        username: 'admin',
        password: 'admin',
      },

登录方法涉及到引入

// 注意,这里文件的位置请根据自己实际项目文件位置进行修改
import { getToken, setToken } from '@/utils/token'
import {login} from '../../api/login'

🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸

登录方法

点击按钮调用登录方法

    submitLogin () {
      if (!this.loginForm.username) {
        this.$message.error('用户名不能为空!')
        return
      }
      if (!this.loginForm.password) {
        this.$message.error('密码不能为空!')
        return
      }
      login(this.loginForm.username, this.loginForm.password).then(res => {
        if (res.header.code !== 0) {
          this.$message.error(res.header.message)
          return
        }
        // 设置token
        setToken(res.value.token)
        // 根据自己实际项目跳转主界面
        this.$router.push('/main')
      })
    },

当这里登录之后会在cookie位置新增数据(F12):
在这里插入图片描述
💐💐💐💐💐💐💐💐💐💐💐💐💐

axios请求

新增axios的工具类,进行封装,在这里会在调用之前确认是否验证过期
request.js: 新建js代码,代码如下:

import axios from 'axios'
import { Message, MessageBox, Notification } from 'element-ui'
import { getToken } from '@/utils/token'
import errorCode from '@/utils/errorCode'
import router from '../router/index'
import {removeToken} from './token'

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'

// 创建axios实例
const service = axios.create({
  // 配置后端请求路径,根据自己实际项目修改
  baseURL: process.env.VUE_APP_BASE_API,
  // 请求超时时间 withCredentials: true,
  timeout: 30000
})

// 请求统一拦截处理
service.interceptors.request.use(config => {
  // 是否需要设置 token
  if (getToken()) {
    config.headers['Authorization'] = getToken() // 请求均需携带自定义token
  }
  return config
},
error => {
  // 请求失败
  console.log(error) // for debug
  // return Promise.reject(error)
  return Promise.reject(error)
}
)

// 响应拦截器
service.interceptors.response.use(res => {
  console.log('res.data', res.data)
  // 未设置状态码则默认成功状态
  const code = res.data.header.code || 200
  // 获取错误信息
  const msg = errorCode[code] || res.data.header.message || errorCode['default']
  if (code === 401) {
    return new Promise((resolve, reject) => {
      MessageBox.confirm('登录状态已过期,请重新登录', '系统提示', {
        confirmButtonText: '重新登录',
        showCancelButton: false,
        type: 'warning'
      }).then(() => {
        removeToken()
        router.push('/login')
        resolve() // 手动解决 Promise,避免重复导航
      })
    })
  } else if (code === 500) {
    Message({
      message: msg,
      type: 'error'
    })
    return Promise.reject(new Error(msg))
  } else if (code !== 0 && code !== 200) {
    Notification.error({
      title: msg
    })
    // eslint-disable-next-line prefer-promise-reject-errors
    return Promise.reject('error')
  } else {
    return res.data
  }
}, error => {
  let { message } = error
  if (message === 'Network Error') {
    message = '服务端连接异常'
  } else if (message.includes('timeout')) {
    message = '系统接口请求超时'
  } else if (message.includes('Request failed with status code')) {
    message = '系统接口' + message.substr(message.length - 3) + '异常'
  }
  Message({
    message: message,
    type: 'error',
    duration: 5 * 1000
  })
  return Promise.reject(error)
}
)

export default service

上述会捕获后端返回的code,做不同的事情,是否可调用接口

针对上述的errorCode ,为新建的封装报错代码js,根据自己需要可做可不做
errorCode.js: 代码如下:

export default {
  '401': '认证失败,无法访问系统资源',
  '403': '当前操作没有权限',
  '404': '访问资源不存在',
  'default': '系统未知错误,请反馈给管理员'
}

上述登录方法涉及到的axios的api如下
login.js

import request from '@/utils/request'

// 登录方法
export function login (account, password) {
  const data = {
    account,
    password
  }
  return request({
    url: '/idle/login',
    method: 'post',
    data: data
  })
}

// 验证token是否有效
export function verify (token) {
  let data = {
    token
  }
  return request({
    url: '/idle/verify',
    method: 'get',
    params: data
  })
}

router配置

跳转路由,拦截请求是否已经过期
新建router文件夹,在其中建立index.js
代码如下:

import Vue from 'vue'
import Router from 'vue-router'
// eslint-disable-next-line standard/object-curly-even-spacing
import {getToken, removeToken } from '@/utils/token'
import {verify} from '@/api/login'
import { Message } from 'element-ui'

Vue.use(Router)

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/main',
    component: () => import('@/views/main'),
    children: [
      {
        path: '',
        name: 'dashBorad',
        component: () => import('@/views/dashBorad')
      },
      {
        path: '/menuPage',
        name: 'menuPage',
        component: () => import('@/views/menuPage')
      },
      {
        path: '/userDataManage',
        name: 'userDataManage',
        component: () => import('@/views/user/userDataManage')
      }
    ]
  },
  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  // 404 page must be placed at the end !!!
  {path: '*', redirect: '/404', hidden: true}
]

const createRouter = () => {
  const router = new Router({
    mode: 'hash',
    scrollBehavior: () => ({y: 0}),
    routes: constantRoutes
  })

  router.beforeEach((to, from, next) => {
    let token = getToken()
    if (!token) {
      // 如果未登录并且不是去往登录页,则跳转到登录页
      if (to.path !== '/login') {
        next('/login')
      } else {
        next() // 如果是去往登录页,则直接放行
      }
    } else {
      // 已登录状态
      verify(token).then(res => {
        let isVerify = res.value
        // 判断是否token验证成功,验证成功则跳转要去的路由,否则报错,跳回登录界面
        if (isVerify) {
          next()
        } else {
          removeToken()
          setTimeout(() => { next('/login') }, 1500)
        }
      }).catch(() => {
        next('/login') // 异步操作失败后再手动重定向
      })
    }
  })

  return router
}

const router = createRouter()

export function resetRouter () {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

通过以上router.jsrequest.js,即可在跳转页面以及访问后端接口的时候进行拦截验证

🌼🌼🌼🌼🌻🌻🌻🌻🌻🌷🌷🌷🌷🌷🌷🌷🌼🌼🌼🌼🌻🌻🌻🌻🌻

springboot实现

依赖引入

同样,为了实现JWT,我们后端也需要做一些引入,注意:本次引入只涉及到JWT相关,其他自己项目相关请额外进行引入

    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.4.0</version>
    </dependency>

🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵

JWT工具类

新建工具类,命名JwtTokenUtil:
代码如下:

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import java.util.Date;

public class JwtTokenUtil {


  //定义token返回头部
  public static final String AUTH_HEADER_KEY = "Authorization";


  //token前缀
  public static final String TOKEN_PREFIX = "Bearer ";


  //签名密钥
  public static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";

  //有效期默认为 2hour
  public static final Long EXPIRATION_TIME = 1000L * 60 * 60 * 2;


  /**
   * 创建TOKEN
   */
  public static String createToken(String content) {
    return TOKEN_PREFIX + JWT.create()
        .withSubject(content)
        .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
        .sign(Algorithm.HMAC512(KEY));
  }


  /**
   * 验证token
   */
  public static String verifyToken(String token) throws Exception {
    try {
      return JWT.require(Algorithm.HMAC512(KEY))
          .build()
          .verify(token.replace(TOKEN_PREFIX, ""))
          .getSubject();
    } catch (TokenExpiredException e) {
      throw new Exception("token已失效,请重新登录", e);
    } catch (JWTVerificationException e) {
      throw new Exception("token验证失败!", e);
    }
  }

  public static Boolean verify(String token) throws Exception {
    try {
      JWT.require(Algorithm.HMAC512(KEY))
          .build()
          .verify(token.replace(TOKEN_PREFIX, ""))
          .getSubject();
      return true;
    } catch (Exception e) {
      return false;
    }
  }
}

忽视jwt验证注解

新建一个注解,用于忽视验证,比如登录,注册方法

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JwtIgnore {
  boolean value() default true;
}

🌱🌱🌱🌱🌱🌱🌱🌱🌱🌱🌱🌱🌱🌱🌱

拦截器逻辑

import cn.hutool.json.JSONObject;
import com.pearl.Interface.JwtIgnore;
import com.pearl.utils.JwtTokenUtil;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

@Slf4j
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json; charset=utf-8");
    // 从http请求头中取出token
    final String token = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);
    //如果不是映射到方法,直接通过
    if (!(handler instanceof HandlerMethod)) {
      return true;
    }
    //如果方法有JwtIgnore注解,直接通过
    HandlerMethod handlerMethod = (HandlerMethod) handler;
    Method method = handlerMethod.getMethod();
    if (method.isAnnotationPresent(JwtIgnore.class)) {
      JwtIgnore jwtIgnore = method.getAnnotation(JwtIgnore.class);
      if (jwtIgnore.value()) {
        return true;
      }
    }
    // 执行认证
    if (StringUtils.isEmpty(token)) {
      JSONObject res = new JSONObject();
      res.put("code", 401);
      res.put("msg", "无token,请重新登录");
      res.put("data", false);
      PrintWriter out = response.getWriter();
      out.append(res.toString());
      return false;
    }
    if (!JwtTokenUtil.verify(token)) {
      JSONObject res = new JSONObject();
      res.put("code", 401);
      res.put("msg", "token验证失败,请重新登录");
      res.put("data", false);
      PrintWriter out = response.getWriter();
      out.append(res.toString());
      return false;
    }
    return true;
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
      Object handler, Exception ex) throws Exception {

  }
}

🌳🌳🌳🌳🌳🌳🌳🌳🌳🌳🌳🌳🌳🌳🌳🌳

跨域&调用拦截器配置

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class GlobalWebMvcConfig implements WebMvcConfigurer {

  /**
   * 重写父类提供的跨域请求处理的接口
   */
  @Override
  public void addCorsMappings(CorsRegistry registry) {
    // 添加映射路径
    registry.addMapping("/**")
        .allowedOriginPatterns("*")  // 允许所有来源
        .allowCredentials(true)      // 允许发送身份验证凭据
        .allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")
        .allowedHeaders("*")
        .exposedHeaders("Server", "Content-Length", "Authorization", "Access-Token",
            "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials");
  }

  // 添加拦截器,我的项目的基础路径为sso,登录接口路径为/sso/idle/login
  // addPathPatterns是拦截所有路径,excludePathPatterns是排除需要拦截的路径
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new AuthenticationInterceptor())
        .addPathPatterns("/**")
        .excludePathPatterns("/sso/idle/login");
  }
}

🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴

登录接口&验证token接口


import com.pearl.Interface.JwtIgnore;
import com.pearl.entitys.beans.UserLoginData;
import com.pearl.entitys.dataBaseTable.User;
import com.pearl.responseEntity.Response;
import com.pearl.service.LoginService;
import com.pearl.utils.db.PrimeDB;
import java.sql.Connection;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/idle")
public class LoginController {
// 我的数据库连接类
  @Autowired
  private PrimeDB primeDB;
// service层,
  @Resource
  private LoginService loginService;

  /**
   * 登录
   */

  @JwtIgnore
  @PostMapping("/login")
  public Response<Map<String, Object>> login(@RequestBody UserLoginData userDto,
      HttpServletResponse response)
      throws Exception {
    try (Connection conn = primeDB.create()) {
      Map<String, Object> map = loginService.login(conn, userDto, response);
      return new Response<>(0, map, "登录成功");
    } catch (Exception e) {
      return new Response<>(1, e.getMessage());
    }
  }

  @JwtIgnore
  @GetMapping("/verify")
  public Response<Boolean> verify(@RequestParam("token") String token) {
    try {
      return new Response<>(0, loginService.verify(token), "验证成功!");
    } catch (Exception e) {
      return new Response<>(1, false, "验证失败");
    }
  }
}

🌾🌾🌾🌾🌾🌾🌾🌾🌾🌾🌾🌾🌾🌾🌾
其中,验证接口的逻辑很简单,单纯调用JWT工具类进行判断即可,而登录方法根据不同的项目,可能各有区别,因此登录逻辑给出来只有参考意义.如下是loginService代码:

import com.alibaba.fastjson.JSONObject;
import com.pearl.db.UserDao;
import com.pearl.entitys.beans.UserLoginData;
import com.pearl.entitys.beans.UserToken;
import com.pearl.entitys.dataBaseTable.User;
import com.pearl.utils.AesUtil;
import com.pearl.utils.AssertUtils;
import com.pearl.utils.JwtTokenUtil;
import java.sql.Connection;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

@Service
public class LoginService {

  public Map<String, Object> login(Connection conn, UserLoginData userLoginData) throws Exception {
    try {
      Map<String, Object> map = new HashMap<>();
      /**
       * 校验账号
       * */
      UserDao userDao = new UserDao(conn);
      AssertUtils.notNull(userLoginData, "请求参数不能为空!");
      AssertUtils.isError(StringUtils.isEmpty(userLoginData.getAccount()), "账号不能为空!");
      AssertUtils.isError(StringUtils.isEmpty(userLoginData.getPassword()), "密码不能为空!");
      User user = userDao.selectbyUserId(userLoginData.getAccount());
      AssertUtils.notNull(user, "该账号不存在!");
      // 判断账号是否失效
      AssertUtils.isError(user.getStatus() != 1, "账号:" + user.getUserId() + "已失效!请联系管理员恢复!");
      // 验证账密
      Boolean isTruePass = new AuthService()
          .checkPassword(userLoginData.getPassword(), user.getPassword(), user.getSalt());
      AssertUtils.isError(!isTruePass, "用户名或密码错误!");
      //TODO 获取用户权限
      
      UserToken userToken = new UserToken();
      BeanUtils.copyProperties(user, userToken);
      String token = JwtTokenUtil.createToken(JSONObject.toJSONString(userToken));
      map.put("user", user);
      map.put("token", token);
      return map;
    } catch (Exception e) {
      throw new Exception(e.getMessage());
    }
  }

  public Boolean verify(String token) throws Exception {
    try {
      return JwtTokenUtil.verify(token);
    } catch (Exception e) {
      throw new Exception(e.getMessage());
    }
  }
}

🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
以上我有封装响应实体,我的响应实体代码如下:

public class Response<T> {

  public Header header;

  public T value;

  public Response() {
  }

  public Response(T value) {
    this.header = new Header();
    this.value = value;
  }

  public Response(int code, Exception ex) {
    if (ex.getMessage() == null) {
      this.header = new Header(code, ex.toString());
    } else {
      this.header = new Header(code, ex.getMessage());
    }
    this.value = null;
  }

  public Response(int code, String message) {
    this.header = new Header(code, message);
    this.value = null;
  }

  public Response(int code, T value, Exception ex) {
    if (ex.getMessage() == null) {
      this.header = new Header(code, ex.toString());
    } else {
      this.header = new Header(code, ex.getMessage());
    }
    this.value = value;
  }

  public Response(int code, T value, String message) {
    this.header = new Header(code, message);
    this.value = value;
  }

  // 请求头,包含响应码和响应提醒信息
  public static class Header {

    public int code;

    public String message;

    public Header() {
      this.code = 0;
      this.message = "";
    }

    public Header(int code, String message) {
      this.code = code;
      this.message = message;
    }
  }
}

如上我的调用登录数据结构如下:

{
    "header": {
        "code": 0,
        "message": "登录成功"
    },
    "value": {
        "user": {
            "userId": "admin",
            "avatar": null,
            "userName": "超级管理员",
            "password": "t3zluLHlyip9A8TcXrR05Q==",
            "email": null,
            "phone": null,
            "sex": null,
            "age": 0,
            "status": 1,
            "createTime": "2024-04-07 08:11:43",
            "updateTime": "2024-04-07 08:11:43"
        },
        "token": "Bearer xxx"
    }
}

因此前端可获取token数据,进行赋值设置
🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲

结语

以上,为vue+springboot实现JWT登录验证过程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/528738.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

初识SpringMVC

一、什么是MVC MVC是一种软件架构模式&#xff08;是一种软件架构设计思想&#xff0c;不止Java开发中用到&#xff0c;其它语言也需要用到&#xff09;&#xff0c;它将应用分为三块&#xff1a; M&#xff1a;Model&#xff08;模型&#xff09;V&#xff1a;View&#xff08…

自定义类型:结构体,位端

结构体内存对齐 结构体的对齐规则&#xff1a; 1. 第一个成员在与结构体变量偏移量为0的地址处。 2. 其他成员变量要对齐到某个数字&#xff08;对齐数&#xff09;的整数倍的地址处。 对齐数 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8 Linux中没有默…

【Shell】各种条件语句的使用——test语句、if语句、case语句

Shell条件语句的使用 条件语句 Shell条件语句的使用条件测试的语法字符串测试表达式整数二元比较操作符逻辑操作符 if的条件语句的语法if的嵌套case语句语法 条件测试的语法 语法1&#xff1a;test <测试表达式> 利用test命令进行条件测试表达式的方法。test命令与<测…

外包干了25天,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入杭州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

深入浅出 -- 系统架构之微服务标准组件及职责

我们来认识一下微服务架构在Java体系中依托哪些组件实现的。 相对于单体架构的简单粗暴&#xff0c;微服务的核心是将应用打散&#xff0c;形成多个独立提供的微服务&#xff0c;虽然从管理与逻辑上更符合业务需要。但微服务架构也带来了很多急需解决的核心问题&#xff1a; 1…

从“危”到“机”:HubSpot如何助企业转化出海营销CRM风险?

在全球化的大背景下&#xff0c;越来越多的企业选择出海拓展业务&#xff0c;以寻求更大的发展空间。然而&#xff0c;随着市场的扩大&#xff0c;企业在出海营销过程中也面临着各种风险。为了有效规避这些风险&#xff0c;许多企业选择借助HubSpot这样的专业营销软件。今天运营…

软文写作技巧,媒介盒子揭秘

数字化时代,想要获取用户的注意力难上加难&#xff0c;只有紧跟互联网的创作节奏&#xff0c;在软文写作中,根据用户的浏览偏好进行适当调整,让软文具有更高的审美性、易读性和启示性,才能有效地吸引当下受众的注意力。今天媒介盒子就来和大家聊聊软文写作技巧。 一、文章选题 …

C语言之自定义类型联合和枚举

目录 前言 一&#xff1a;联合体&#xff08;共用体&#xff09;union 1.联合体类型的声明 2.联合体的特点 3.联合体大小的计算 4.联合体判断机器的大小端 二&#xff1a;枚举enum 1.概念 2.枚举的优点 3.枚举的使用 接下来的日子会顺顺利利&#xff0c;万事胜意…

深度学习500问——Chapter06: 循环神经网络(RNN)(2)

文章目录 6.4 CNN和RNN的区别 6.5 RNNs与FNNs有什么区别 6.6 RNNs训练和传统ANN训练异同点 6.7 为什么RNN训练的时候Loss波动很大 6.8 标准RNN前向输出流程 6.9 BPTT算法推导 6.9 RNN中为什么会出现梯度消失 6.10 如何解决RNN中的梯度消失问题 6.4 CNN和RNN的区别 类别特点描述…

2014最新AIGC创作系统ChatGPT网站源码+AI绘画网站源码+GPT4-All联网搜索模型

一、文章前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持…

【超简单】基于PaddleSpeech搭建个人语音听写服务

一、【超简单】之基于PaddleSpeech搭建个人语音听写服务 1.需求分析 亲们,你们要写会议纪要嘛?亲们,你们要写会议纪要嘛?亲们,你们要写会议纪要嘛?当您面对成吨的会议录音,着急写会议纪要而不得不愚公移山、人海战术?听的头晕眼花,听的漏洞百出,听的怀疑人生,那么你…

电脑网卡无法连接网络?三招教你解决问题

在现代生活中&#xff0c;电脑网卡扮演着连接互联网的关键角色。无论是有线网卡还是无线网卡&#xff0c;都是电脑与外部网络通信的重要途径。然而&#xff0c;有时候我们可能会遇到电脑网卡无法连接网络的问题&#xff0c;这会严重影响我们的工作和娱乐。本文将介绍三种常见的…

【Qt】文件与音视频

目录 一、输入输出设备类 二、文件读写类 三、文件和目录信息类 四、音视频 4.1 音频 4.2 视频 文件操作是应用程序必不可少的部分。Qt作为一个通用开发库&#xff0c;提供了跨平台的文件操作能力。Qt提供了很多关于文件的类&#xff0c;通过这些类能够对文件系统进行操作…

用Python实现输入点云索引绘制该点云法向量

import open3d as o3d# 读取pcd文件 pcd o3d.io.read_point_cloud(r"D:\PythonProjects\Codes\paper_images\back_point\voxel.pcd")# 计算法向量 pcd.estimate_normals(search_paramo3d.geometry.KDTreeSearchParamHybrid(radius0.1, max_nn30))# 选择要绘制法向量…

四级作文模板——议论文——现象解释

议论文类型 现象解释 第一句 with the rapid development of society / economy / education / technology / culture / medical / service(任选) , it is of great necessity for youngster / students to improve our speaking ability.随着社会/经济/教育/科技/文化/医疗…

腾讯、阿里、字节….等大厂都更喜欢什么样的简历?

我985毕业&#xff0c;为什么筛选简历时输给了一个普通一本&#xff1f; 我投了20份简历&#xff0c;为什么没有一个大厂回我&#xff1f; 每次HR收到简历就没下文了&#xff0c;是我的简历有问题吗&#xff1f; 诚然&#xff0c;在求职时&#xff0c;简历往往就是我们给予H…

AI爆款文案 巧用AI大模型让文案变现插上翅膀

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

【C++】 详解 lower_bound 和 upper_bound 函数(看不懂来捶我!!!)

目录 一、前言 二、函数详解 &#x1f95d; lower_bound &#x1f34d;upper_bound 三、常考面试题 四、共勉 一、前言 这两个函数是我在 LeetCode 上做题见到&#xff0c;看到不熟悉的函数 lower_bound 和 upper_bound让我感觉很难受&#xff0c;于是在 C 官网去学习&…

Java入门基础知识第七课(超基础,超详细)——数组

前面二白讲了选择结构和循环结构&#xff0c;动手的同学会发现已经有了一定的难度&#xff0c;后面二白会专门收集一些经典的题目&#xff0c;训练多了才能让记忆更加深刻&#xff0c;这次咱们讲一下数组。 一、数组的定义 什么是数组呢&#xff0c;我们都知道变量是存储数据的…