前后端分离,使用vue3整合SpringSecurity加JWT实现登录校验

前段时间写了一篇spring security的详细入门,但是没有联系实际。

所以这次在真实的项目中来演示一下怎样使用springsecurity来实现我们最常用的登录校验。本次演示使用现在市面上最常见的开发方式,前后端分离开发。前端使用vue3进行构建,用到了element-plus组件库、axios封装、pinia状态管理、Router路由跳转等技术。后端还是spring boot整合springsecurity+JWT来实现登录校验。

本文适合有一定基础的人来看,如果你对springsecurity安全框架还不是很了解,建议你先去看一下我之前写过的spring security框架的快速入门:

springboot3整合SpringSecurity实现登录校验与权限认证(万字超详细讲解)_springboot3 + springsecurity6 校验密码-CSDN博客

技术栈版本:vue3.3.11、springboot3.1.5、spring security6.x

业务流程:

可以看到整个业务的流程还是比较简单的,那么接下来就基于这个业务流程来进行我们具体代码的编写和实现;

前端:

新建一个vue项目,并引入一些具体的依赖;我们本次项目用到的有:element-plus、axios、pinia状态管理、Router路由跳转(注意我们在项目中使用到的pinia要引入持久化插件)

在vue项目中新建两个组件:Login.vue(登录组件,负责登录页面的展示)、Layout.vue(布局页面,负责整体项目的布局,登录成功之后就是跳转到这个页面)

路由的定义:在router文件夹下新建index.ts文件

import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'login',
      component: () => import('@/components/Login.vue')
    },
    {
      path: '/layout',
      name: 'layout',
      component: () => import('@/components/Layout.vue')
    }
  ]
})

export default router

定义Login登录组件为默认的组件,并定义Layout组件;

useToken的状态封装:在stoers文件夹下新建useToken.ts

import { defineStore } from 'pinia'
import { ref } from 'vue'
const useTokenStore = defineStore('token', ()=>{
const token=ref()


const removeToken=()=>{
    token.value=''
}

return {token,removeToken}
},
{persist: true}
)

export default useTokenStore

axios的封装:在utils文件夹在新建request.ts文件

import axios from "axios";
import  useTokenStore from '@/stores/useToken'
import { ElMessage } from 'element-plus';
// 先建一个api
const api = axios.create({
    baseURL: "http://localhost:8888",
    timeout: 5000
});
// 发送请求前拦截
api.interceptors.request.use(
    config =>{
const useToken = useTokenStore();
if(useToken.token){
    console.log("请求头toekn=====>", useToken.token);
    // 设置请求头
    // config.headers['token'] = useToken.token;
    config.headers.token = useToken.token;
}
        return config;

},
error =>{

    return Promise.reject(error);
}
)

// 响应前拦截
api.interceptors.response.use(
    response =>{
        console.log("响应数据", response);
if(response.data.code !=200){
    ElMessage.error(response.data.message);
}

        return response;
},
error =>{
    return Promise.reject(error);
}
)

export default api;

在请求前拦截,主要是为了在请求头中新增token。在request.ts中引入了useToken,并判断如果token不为空,那么在请求头中新增token。

在响应前也进行了一次拦截,如果后端返回的状态码不为200,那么就打印出错误信息;

接下来就可以在Login.vue中进行我们的登录逻辑的具体编写了(我直接将组件内容进行复制了,也不是什么太难的东西,主要还是element-plus的表单):

<template>
    <div class="background" style="font-family:kaiti" >

<!-- 注册表单 -->
<el-dialog v-model="isRegister" title="用户注册" width="30%">
    <el-form label-width="120px" v-model="registerForm">
        <el-form-item label="用户名">
            <el-input type="text"   v-model="registerForm.username"   >
              <template #prefix>
                <el-icon><Avatar /></el-icon>
              </template>
            </el-input>
        </el-form-item>
        <el-form-item label="密码">
            <el-input  type="password" v-model="registerForm.password" >
              <template #prefix>
        <el-icon><Lock /></el-icon>
        </template>
            </el-input>
        </el-form-item>
        <el-form-item>
            <el-button type="primary" @click="registerAdd" >提交</el-button>
            <el-button @click="isRegister = false">取消</el-button>
        </el-form-item>
    </el-form>
</el-dialog>

<!-- 登陆框 -->
<div class="login-box">
<el-form
    label-width="100px"
    :model="loginFrom"
    style="max-width: 460px"
    :rules="Loginrules"
    ref="ruleFormRef"
  >
    <el-form-item label="用户名"  prop="username">
      <el-input v-model="loginFrom.username"  clearable  >
        <template #prefix>
                <el-icon><Avatar /></el-icon>
              </template>
        </el-input>
    </el-form-item>
    <el-form-item label="密码" prop="password">

      <el-input v-model="loginFrom.password"   show-password   clearable  type="password" >
        <template #prefix>
        <el-icon><Lock /></el-icon>
        </template>
      </el-input>
    </el-form-item>

    <el-form-item label="验证码"  prop="codeValue">
      <el-input v-model="loginFrom.codeValue"  style="width: 100px;"  clearable  >
      </el-input>
      <img :src="codeImage" @click="getCode" style="transform: scale(0.9);"/>
    </el-form-item>

    <el-button type="success" @click="getLogin(ruleFormRef)"  style="transform: translateX(50px)"  class="my-button">登录</el-button>
    <el-button type="primary" @click="isRegister=true" class="my-button">注册</el-button>
  </el-form>

</div>

    </div>
</template>

<script lang="ts" setup>
import { ref,onMounted,reactive } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import useTokenStore  from '@/stores/useToken'
import  api  from '@/utils/request'
import type { FormInstance, FormRules } from 'element-plus'
const ruleFormRef =  ref<FormInstance>()

const loginFrom=ref({
username:'',
password:'',
codeKey:'',
codeValue:''
})

const Loginrules=reactive({

  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur'}
  ],
  codeValue: [
    { required: true, message: '请输入验证码', trigger: 'blur' }
  ]

})

const registerForm=ref({
  username:'',
  password:''
})

const codeImage=ref('')

const isRegister=ref(false)

const tokenStore = useTokenStore();


const router = useRouter()



const getLogin = async(formEl: FormInstance | undefined) => {

  if (!formEl)  return

  await formEl.validate((valid, fields) => {
    if (valid) {
      console.log('submit!')
      
    } else {
      ElMessage('请输入完整信息')
      return;
    }
  })

  let {data}=await api.post('/user/login',loginFrom.value)

if(data.code==200){
  ElMessage('登录成功')
  console.log(data);
  tokenStore.token=data.data
  router.replace({name:'layout'})
}else{

  ElMessage('登录失败')
}


}


const getCode=async()=>{
  let {data}=await api.get('/getCaptcha')
  loginFrom.value.codeKey=data.data.codeKey
  codeImage.value=data.data.codeValue

}

const registerAdd=async()=>{
let {data}=await api.post('/user/register',registerForm.value)

if(data.code==200){
  ElMessage('注册成功')
  isRegister.value=false
}else{

  ElMessage('注册失败')
  isRegister.value=false
  }

}

// 页面加载完成获取验证码

onMounted(()=>{
getCode()

})


</script>

这个页面中,我还加入了一个图形验证码。还有一个注册的表单。其他的就和普通的登录一样了;

这个页面的最终效果如图:

Layout.vue页面中,我们只进行两个方法的测试;一个是获取当前用户的具体信息,一个是退出登录的按钮;

<template>
    <div class="common-layout">
    <el-container>
          <el-header height="100px">
            头部
            <el-button type="primary" @click="getUserInfo">获取用户信息</el-button>
            <el-button type="success" @click="Logout">退出登录</el-button>
        </el-header>
    
    <el-container>
    <el-aside width="200px">
        菜单栏
    </el-aside>
    <el-main>
        展示区
    </el-main>
    </el-container>
    </el-container>
  </div>


</template>

<script lang="ts" setup name="Layout">
import { ref } from 'vue'
import api from '@/utils/request'
import {ElMessage} from 'element-plus'
import { useRouter } from 'vue-router'
import  useToeknStore from '@/stores/useToken'
const router = useRouter()

const Logout =async () => {
 let data= api.get("/user/logout")
if(data.data.code==200){
ElMessage.success('退出成功')
// 清除token
useToeknStore().removeToken
router.replace({name:'login'})
}
else{
  ElMessage.error('退出失败')
}

}

const getUserInfo = async() => {

let data=await api.get("/user/info")

console.log('@',data);

}


</script>

数据库:

我新建一个数据表,用于登录校验:

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    expired BOOLEAN NOT NULL
);

这张表中只有简单的用户名,密码,和用户是否过期等字段;

后端:
 

新建一个spring boot项目,并导入以下的依赖:

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>


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

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.18</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.21</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

后端使用MybatisPlus做用户的增、删、改、查等。基础的controller、service、mapper,我就不再这里进行赘述了;

新建一个类MyTUserDetail ,继承UserDetail:

@Data
public class MyTUserDetail implements Serializable , UserDetails {

    private static final long serialVersionUID = 1L;

private User User;

@JsonIgnore  //json忽略
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
 
        return null;
    }
 
 
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return true;
    }
}

新建一个类MyUserDetailServerImpl,实现MyUserDetailServer接口的loadUserByUsername方法

@Service
public class MyUserDetailServerImpl implements MyUserDetailServer {
    @Autowired
    UserMapper userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User tUser = userService.selectOne(new LambdaQueryWrapper<User>().
                eq(username != null, TUser::getUsername, username));
        if (tUser == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }

        MyTUserDetail myTUserDetail=new MyTUserDetail();
myTUserDetail.setTUser(tUser);
        return myTUserDetail;
    }
}

新建一个JwtUtils的工具类,来生成token;

@Component
public class JwtUtil {

    private final String secret="zhangqiao";

    private final Long expiration=36000000L;

    public String generateToken(Integer id) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);

        Algorithm algorithm = Algorithm.HMAC256(secret);
        return JWT.create()
                .withSubject(String.valueOf(id))
                .withIssuedAt(now)
                .withExpiresAt(expiryDate)
                .sign(algorithm);
    }

    public Integer getUsernameFromToken(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return Integer.valueOf(jwt.getSubject());
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /*
    * 判断token是否过期
    * */
    public boolean isTokenValid(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWT.require(algorithm).build().verify(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /*
    * 刷新token
    * */

    public String refreshToken(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            String username = jwt.getSubject();
            Algorithm algorithm = Algorithm.HMAC256(secret);

            Date now = new Date();
            Date expiryDate = new Date(now.getTime() + expiration);

            return JWT.create()
                    .withSubject(username)
                    .withIssuedAt(now)
                    .withExpiresAt(expiryDate)
                    .sign(algorithm);
        } catch (JWTDecodeException e) {
            return null;
        }
    }
}

新建一个Jwt的拦截类,继承一个OncePerRequestFilter类,用来在每次请求前拦截请求,并从中获取token,并判断这个token是否是我们用户表中的token;

如果是,那么将用户信息存储到security中,这样后面的过滤器就可以获取到用户信息了,如果不是,那么直接放行。我们会将这个拦截器加入到UsernamePasswordAuthenticationFilter过滤器之前。

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取请求头中的token
        String token = request.getHeader("token");
        System.out.println("前端的token信息=======>"+token);
        //如果token为空直接放行,由于用户信息没有存放在SecurityContextHolder.getContext()中所以后面的过滤器依旧认证失败符合要求
        if(!StringUtils.hasText(token)){
            filterChain.doFilter(request,response);
            return;
        }

//        解析Jwt中的用户id
        Integer userId = jwtUtil.getUsernameFromToken(token);
        //从redis中获取用户信息
        String redisUser = redisTemplate.opsForValue().get(String.valueOf(userId));
        if(!StringUtils.hasText(redisUser)){
            filterChain.doFilter(request,response);
            return;
        }

        MyTUserDetail myTUserDetail= JSON.parseObject(redisUser, MyTUserDetail.class);

        //将用户信息存放在SecurityContextHolder.getContext(),后面的过滤器就可以获得用户信息了。
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(myTUserDetail,null,null);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        filterChain.doFilter(request,response);
    }
}

security配置类的设置:

@Configuration
@EnableWebSecurity
public class MyServiceConfig {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    /*
    * security的过滤器链
    * */
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)throws Exception {
http.csrf(AbstractHttpConfigurer::disable);

http.authorizeHttpRequests((auth) ->
    auth
            .requestMatchers("/getCaptcha","user/login","user/register").permitAll()
            .anyRequest().authenticated()
);
http.cors(cors->{
    cors.configurationSource(corsConfigurationSource());
        });
//自定义过滤器放在UsernamePasswordAuthenticationFilter过滤器之前
    http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);


    return http.build();
}

@Autowired
private MyUserDetailServerImpl myUserDetailsService;



/*
* 验证管理器
* */
    @Bean
    public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder){
        DaoAuthenticationProvider provider=new DaoAuthenticationProvider();
//将编写的UserDetailsService注入进来
        provider.setUserDetailsService(myUserDetailsService);
//将使用的密码编译器加入进来
        provider.setPasswordEncoder(passwordEncoder);
//将provider放置到AuthenticationManager 中
        ProviderManager providerManager=new ProviderManager(provider);
        return providerManager;
    }


//跨域配置
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    /*
    * 密码加密器*/
@Bean
    public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

}

在security的配置类中,设置了跨域问题、拦截器链的配置(并将一些需要放行的接口放行,将我们自定义的Jwt拦截器加入了security拦截链)、密码编译器、AuthenticationManager 验证管理等等一系列配置;

Usercontroller控制器:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostMapping("/login")
    public Result<String> login(@RequestBody DtoLogin dtoLogin) {
        System.out.println(dtoLogin);
        String token = userService.login(dtoLogin);
        return Result.successData(token);
    }

    @PostMapping("/register")
    public Result register(@RequestBody DtoLogin dtoLogin) {
        System.out.println(dtoLogin);
        TUser user = new TUser();
        user.setLoginAct(dtoLogin.getUsername());
        user.setLoginPwd(passwordEncoder.encode(dtoLogin.getPassword()));
        userService.save(user);
        return Result.success();

    }
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Autowired
    private JwtUtil jwtUtil;

    @GetMapping("/logout")
    public Result logout(@RequestHeader("token")String token){
        Integer id = jwtUtil.getUsernameFromToken(token);
        redisTemplate.delete(String.valueOf(id));

        return Result.success();
    }

    @GetMapping("/info")
    public Result info(@RequestHeader("token")String token){
        System.out.println("controller层获取到的token=======>"+token);
        Integer id = jwtUtil.getUsernameFromToken(token);
        String redisUser = redisTemplate.opsForValue().get(String.valueOf(id));
        MyTUserDetail myTUserDetail = JSON.parseObject(redisUser, MyTUserDetail.class);
        return Result.successData(myTUserDetail);

    }

}

在UserController控制器中,由于登录方法比较复杂,我将登录方法重新在service中重写了,剩下的获取用户信息、用户注册、退出登录都直接在UseController中实现了;

service中重写的登录方法:
 

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    private JwtUtil jwtUtil;


    @Override
    public String login(DtoLogin dtoLogin) {
        String codeRedis = redisTemplate.opsForValue().get(dtoLogin.getCodeKey());
        if (!dtoLogin.getCodeValue().equals(codeRedis)){
            throw new ResultException(400,"验证码错误");
        }
        // 验证码正确,删除redis中的验证码
        redisTemplate.delete(dtoLogin.getCodeKey());

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(dtoLogin.getUsername(),dtoLogin.getPassword());

        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        if(authenticate==null){
            throw new  ResultException(400,"用户名或密码错误");
        }
//        获取返回的用户信息
        Object principal = authenticate.getPrincipal();

        MyTUserDetail myTUserDetail=(MyTUserDetail) principal;
        System.out.println(myTUserDetail);
//        使用Jwt生成token,并将用户的id传入
        String token = jwtUtil.generateToken(myTUserDetail.getTUser().getId());
        redisTemplate.opsForValue().
                set(String.valueOf(myTUserDetail.getTUser().getId()), JSON.toJSONString(myTUserDetail),1, TimeUnit.DAYS);


        return token;
    }

}

由于我们还是用了验证码,所以在这个登录方法中先判断了验证码、如果验证码正确。那么在判断传回来的用户名和密码。如果都正确,那么用Jwt返回一个token,token中携带的是用户的id;

至此,我们所有的前后端代码都已经写完了。那么,让我们具体的实验一下;

运行:

由于我刚创建的表,还没有添加数据,那么我现在前端点击注册,写入几条用户信息;

写入信息之后,我使用刚注册过的用户来登录一下:

注册成功之后,就会进入到我们自定义个Layout.vue组件内:

现在,我点击“获取用户信息”按钮,因为这个路径我们并没有放行,那么他访问时就会被我们自定义的Jwt拦截器拦截,并验证它请求头中携带的token是否正确。如果正确,则放行。如果不正确,那么就会放行到登录拦截器中。

可以看到,在控制台中打印出了用户的信息。这是肯定的,因为它这次请求携带的token是正确的,那么如果我们修改一下token的值,他还能正常访问到用户信息这个接口吗?

我修改了请求头中的token信息,可以看到立马这个请求就被拦截了。并爆出了403错误;

现在,我点击“退出登录”按钮,它应该删除useToken中的token值,并且跳转到登录页面。后端也会删除redsi中存储的用户数据;

现在,我们所有的任务都已经完成了。

我再整体理一下具体的思路:

前端发送请求后端,如果是登录请求,那么直接走登录接口即可,我将登录接口进行了方行,任何人都可以访问到登录接口,并且执行登录接口的逻辑;如果登录成功,会返回一个token,前后会将这个token存到useToken中,并且再以后的每次请求中都携带token;如果登录失败,返回一个报错信息即可。

如果前端发送的不是登录接口,但是前端携带可正确的token,那么会被我们自定义的Jwt拦截器拦截,并从中读取用户信息,放到security中共后续的拦截器使用;如果没有携带token,或者token不正确,那么后端会直接返回403的状态码提示;

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

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

相关文章

vue生命周期图示

详见&#xff1a;官网介绍

梳理一下若依框架的权限过滤系统

梳理一下若依框架的权限过滤系统 首先&#xff0c;我们直入主题&#xff0c;且看这段代码 /*** 获取用户列表*/ PreAuthorize("ss.hasPermi(system:user:list)") GetMapping("/list") public TableDataInfo list(SysUser user) {startPage();List<SysU…

OpenHarmony当前进展和未来趋势

操作系统自20世纪50年代诞生&#xff0c;经历了从专用操作系统到通用操作系统的转变。整体可以将操作系统的发展历史分为3个阶段&#xff1a;PC时代、移动互联网时代、万物互联时代。 PC时代主要以计算机为主&#xff0c;用户规模从1970年的10亿增长到1990年的30亿。这一时代诞…

QComboBox 下拉框

文章目录 1、简介2、functions3、Signal QT 官方文档参考地址&#xff1a;https://doc.qt.io/qt-5/qcombobox.html 1、简介 QComboBox 是下拉列表框组件类&#xff0c;它提供一个下拉列表供用户选择&#xff0c;也可以直接当作一个 QLineEdit 用作输入。 2、functions 1、voi…

供应商导添加预扣税字段

文章目录 1 Introduction2 Code3 Summary 1 Introduction I only think I can assign value to them and I implement it by the following code . 2 Code LOOP AT gt_bukrs INTO gs_bukrs WHERE lifnr gs_alv1-lifnr.CLEAR:ls_company.ls_company-task M.ls_company-data…

mybatis----动态Sql

1.if标签 通过if标签构建动态条件&#xff0c;通过其test属性的true或false来判断该添加语句是否执行。 mapper接口 public interface AccountMapper {List<Account> selectAllByCondition(Account account); } 映射文件 <select id"selectAllByCondition&q…

具有运动模糊的大规模场景的混合神经绘制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;具有运动模糊的大规模场景的混合神经绘制1、研究背景2、方法提出3、视点依赖归一化方法4、训练方法5、试验细节及对比 YOLO模型1、…

.NET高速开发分层架构,ASPNETCore,IOC开发

分层架构,ASPNETCore,IOC开发 大家好,我是行不更名,坐不改姓的宋晓刚。今天我将带领大家深入了解分层架构、ASP.NET Core、IoC 开发,以及共同开发的魅力。我们将一同探索如何利用这些先进技术构建高效、可维护的数据库应用程序。 家人们,如果有什么不懂,可以留言,或者…

【JS逆向学习】国家加密系列-SM算法实例

SM系列 1、国家加密算法介绍 事实上从 2010 年开始&#xff0c;我国国家密码管理局就已经开始陆续发布了一系列国产加密算法&#xff0c;其中SM1、SM4、SM7、祖冲之密码&#xff08;ZUC&#xff09;是对称算法&#xff1b;SM2、SM9是非对称算法&#xff1b;SM3是哈希算法。目…

大数据导论(4)---大数据应用

文章目录 1. 在互联网中的应用1.1 推荐系统1.2 长尾理论1.3 推荐方法与模型1.4 推荐系统应用 2. 在其他领域的应用2.1 企业营销2.2 智慧交通 1. 在互联网中的应用 1.1 推荐系统 1. 推荐系统产生&#xff1a;  (1) 互联网的飞速发展使我们进入了信息过载的时代&#xff0c;搜索…

二叉树题目:二叉树的序列化与反序列化

文章目录 题目标题和出处难度题目描述要求示例数据范围 前言解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;二叉树的序列化与反序列化 出处&#xff1a;297. 二叉树的序列化与反序列化 难度 8 级 题目描述 要求 序列…

数据结构:堆与堆排序

目录 堆的定义&#xff1a; 堆的实现&#xff1a; 堆的元素插入&#xff1a; 堆元素删除&#xff1a; 堆初始化与销毁&#xff1a; 堆排序&#xff1a; 堆的定义&#xff1a; 堆是一种完全二叉树&#xff0c;完全二叉树定义如下&#xff1a; 一棵深度为k的有n个结点的二…

微信小程序的nodejs+vue课堂在线学习系统教学辅助平台PHP设计与实现

小程序主要实现功能&#xff1a;一、用户的登录与实现 二、课程页面。学生们可以观看课程视频【课程视频有章程】&#xff0c;搜索课程&#xff0c;课程签到&#xff0c;评论课程&#xff0c;课后答题&#xff08;课后成绩&#xff09;&#xff0c;课程互动&#xff08;在视频下…

【深度学习】手把手教你使用 Auto DL 远程服务器连接 PyCharm

前言 文章性质&#xff1a;实操记录 &#x1f4bb; 主要内容&#xff1a;主要记录了如何租用 Auto DL 服务器&#xff0c;以及如何在 PyCharm 中连接远程服务器。 相关文档&#xff1a;如何使用 Auto DL 远程服务器连接 PyCharm 运行代码 - 知乎 冷知识1&#xff1a;小伙伴们不…

c++:string相关的oj题(把字符串转换成整数、344.反转字符串、387. 字符串中的第一个唯一字符、917. 仅仅反转字母)

文章目录 1.把字符串转换成整数题目详情代码思路 2. 344.反转字符串题目详情代码1思路1代码2思路 3. 387. 字符串中的第一个唯一字符题目详情代码思路 4. 917. 仅仅反转字母题目详情代码思路 1.把字符串转换成整数 传送门 题目详情 代码 class Solution { public:int StrToI…

提升用户体验的利器——TTS语音合成软件盘点

提升用户体验的利器——TTS语音合成软件盘点 在当今信息爆炸的时代&#xff0c;人们每天都要处理大量的文本信息。因此&#xff0c;将文本信息转化为语音信息&#xff0c;使得信息能够以更自然、更方便的方式传达给人们&#xff0c;就显得尤为重要。这就是TTS&#xff08;Text…

【C++修行之道】竞赛常用库函数(sort,min和max函数,min_element和max_element、nth_element)

目录 一、sort 1.1sort简介 语法 参数 功能 适用容器 1.2sort的用法 1.3自定义比较函数 示例 1265蓝桥题 —— 排序 二、min和max函数 三、min_element和max_element 497蓝桥题 —— 成绩分析 四、nth_element 一、sort 1.1sort简介 sort函数包含在头文件<a…

手机软件的测试主要有哪些方面去测试,性能测试用什么去测试好?

手机App软件与Web软件系统的架构是不一样的&#xff0c;手机是基于CS架构&#xff0c;而Web系统是基于BS架构的&#xff0c;所以测试手机App软件那么要考虑的东西会更多一些。 分析题主的问题包含两块&#xff1a; 1、手机软件(App)测试主要有哪些方面&#xff1f; 2、手机软件…

【C/C++】C/C++编程——为什么学习 C++?

当提到C的时候&#xff0c;很多人会觉得语法复杂、学习曲线陡峭&#xff0c;并且好像与C语言还有点"纠缠不清"。尽管如此&#xff0c;C仍然是当今世界上最受欢迎和最有影响力的编程语言之一。特别是在当今快速发展的人工智能&#xff08;AI&#xff09;领域&#xff…

java数据结构与算法刷题-----LeetCode645. 错误的集合(位运算解法需要重点掌握)

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 法一&#xff1a;桶排序思想法二&#xff1a;位运算 法一&#x…