当前位置: 首页 > news >正文

springsecurity的学习(四):实现授权

简介

springsecurity的授权,自定义授权失败的处理,跨域的处理和自定义权限校验方法的介绍

授权

权限系统作用

在后台进行用户权限的判断,判断当前用户是否有相应的权限,必须具有所需的权限才能进行相应的操作,以此达到不同的用户可以使用不同的功能。

流程

会使用springsecurity默认的FilterSecurityInterceptor来进行权限校验,会从SecurityContextHolder获取其中的Authentication,从Authentication中获取权限的信息,判断当前用户是否拥有当前资源所需的权限。

实现方式

springsecurity提供了基于注解的权限控制方案,使用注解去指定访问对应的资源所需的权限。
需要在配置类中添加注解@EnableGlobalMethodSecurity 注解开启相关的配置
开启后,即可在controller的接口上添加使用springsecurity的权限相关的注解。如
@PreAuthorize("hasAuthority('权限字符串')"):可以判断当前访问接口的用户是否有这个权限

数据库查询权限

rabc权限模型

基于角色的权限控制。

创建表

需要5张表
在这里插入图片描述
创建语句:

CREATE TABLE `sys_menu` (`id` bigint NOT NULL AUTO_INCREMENT,`menu_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '菜单名',`path` varchar(200) DEFAULT NULL COMMENT '路由地址',`component` varchar(255) DEFAULT NULL COMMENT '组件路径',`visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',`status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',`perms` varchar(100) DEFAULT NULL COMMENT '权限标识',`icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',`create_by` bigint DEFAULT NULL,`create_time` datetime DEFAULT NULL,`update_by` bigint DEFAULT NULL,`update_time` datetime DEFAULT NULL,`del_flag` int DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',`remark` varchar(500) DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='权限表';#################################
CREATE TABLE `sys_role` (`id` bigint NOT NULL AUTO_INCREMENT,`name` varchar(128) DEFAULT NULL,`role_key` varchar(100) DEFAULT NULL COMMENT '角色权限字符串',`status` char(1) DEFAULT '0' COMMENT '角色状态(0正常 1停用)',`del_flag` int DEFAULT '0' COMMENT 'del_flag',`create_by` bigint DEFAULT NULL,`create_time` datetime DEFAULT NULL,`update_by` bigint DEFAULT NULL,`update_time` datetime DEFAULT NULL,`remark` varchar(500) DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色表';
#################################
CREATE TABLE `sys_role_menu` (`role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',`menu_id` bigint NOT NULL DEFAULT '0' COMMENT '菜单id',PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
#################################
CREATE TABLE `sys_user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',`user_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',`nick_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称',`password` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',`status` char(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)',`email` varchar(64) DEFAULT NULL COMMENT '邮箱',`phonenumber` varchar(32) DEFAULT NULL COMMENT '手机号',`sex` char(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',`avatar` varchar(128) DEFAULT NULL COMMENT '头像',`user_type` char(1) NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',`create_by` bigint DEFAULT NULL COMMENT '创建人的用户id',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`update_by` bigint DEFAULT NULL COMMENT '更新人',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`del_flag` int DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
#################################
CREATE TABLE `sys_user_role` (`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户id',`role_id` bigint NOT NULL DEFAULT '0' COMMENT '角色id',PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
根据userid查询权限
select distinct m.`perms`
fromsys_user_role urleft join `sys_role` r on ur.`role_id` = r.`id`left join `sys_role_menu` rm on ur.`role_id` = rm.`role_id`left join `sys_menu` m on m.`id` = rm.`menu_id`
where user_id= 用户idand r.`status` = 0and m.`status` = 0

在这里插入图片描述

Menu 类
package com.springSecurityTest.common;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.Date;@TableName(value="sys_menu") //指定表名,避免等下mybatisplus的影响
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
//Serializable是官方提供的,作用是将对象转化为字节序列
public class Menu implements Serializable {private static final long serialVersionUID = -54979041104113736L;@TableIdprivate Long id;/*** 菜单名*/private String menuName;/*** 路由地址*/private String path;/*** 组件路径*/private String component;/*** 菜单状态(0显示 1隐藏)*/private String visible;/*** 菜单状态(0正常 1停用)*/private String status;/*** 权限标识*/private String perms;/*** 菜单图标*/private String icon;private Long createBy;private Date createTime;private Long updateBy;private Date updateTime;/*** 是否删除(0未删除 1已删除)*/private Integer delFlag;/*** 备注*/private String remark;
}
MenuMapper类
package com.springSecurityTest.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.springSecurityTest.common.Menu;
import org.apache.ibatis.annotations.Mapper;import java.util.List;
@Mapper
public interface MenuMapper extends BaseMapper<Menu> {List<String> selectMemusByUserId(Long userId);
}
menu.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.springSecurityTest.mapper.MenuMapper"><select id="selectMemusByUserId" resultType="java.lang.String">select distinct sys_menu.perms from sys_user_role
left join sys_role
on sys_user_role.role_id = sys_role.id
left join sys_role_menu
on sys_user_role.role_id = sys_role_menu.role_id
left join sys_menu
on sys_menu.id = sys_role_menu.menu_id
where user_id = #{userid}
and sys_role.`status` = 0</select>
</mapper>

springsecurity授权

UserDetailsServiceImpl类

把权限信息放入到loginuser中

package com.springSecurityTest.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.springSecurityTest.mapper.MenuMapper;
import com.springSecurityTest.mapper.UserMapper;
import com.springSecurityTest.common.LoginUser;
import com.springSecurityTest.common.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@ResourceUserMapper userMapper;@Resourceprivate MenuMapper menuMapper;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(User::getUserName,s);User user = userMapper.selectOne(lambdaQueryWrapper);if (Objects.isNull(user)){throw new RuntimeException("用户名或密码错误");}//查询权限List<String> list = menuMapper.selectMemusByUserId(user.getId());return new LoginUser(user,list);}
}
LoginUser 类

添加权限的属性,重写getAuthorities方法。把permissions中的权限信息封装成simpleGrantauthority对象

package com.springSecurityTest.common;import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;private List<String> permissions;public LoginUser(User user,List<String> permissions){this.user = user;this.permissions = permissions;}@JSONField(serialize = false)private List<GrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if(authorities!= null){return authorities;}authorities = new ArrayList<>();for(String permission:permissions){SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);authorities.add(simpleGrantedAuthority);}return authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
JwtAuthenticationTokenFilter 类

给usernamePasswordAuthenticationToken 对象添加权限

package com.springSecurityTest.filter;import com.springSecurityTest.common.LoginUser;
import com.springSecurityTest.utils.JwtUtil;
import com.springSecurityTest.utils.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import sun.plugin.liveconnect.SecurityContextHelper;import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Resourceprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {String token = httpServletRequest.getHeader("token");if (!StringUtils.hasText(token)) {filterChain.doFilter(httpServletRequest, httpServletResponse);return;}String userId;try {System.out.println(JwtUtil.parseJWT(token));Claims claims = JwtUtil.parseJWT(token);userId = claims.getSubject();} catch (Exception e) {e.printStackTrace();throw new RuntimeException("token非法");}String redisKey = "token:" + userId;LoginUser loginUser = redisCache.getCacheObject(redisKey);if(Objects.isNull(loginUser)){throw new RuntimeException("用户未登录");}UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null, loginUser.getAuthorities());SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);filterChain.doFilter(httpServletRequest,httpServletResponse);}}
controller类
package com.springSecurityTest.controller;import com.springSecurityTest.common.User;
import com.springSecurityTest.mapper.UserMapper;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.List;@RestController
public class dark {
@RequestMapping("/dark")
@PreAuthorize("@hasAuthority('system:dept:list')")public String dark(){return "it's too dark!";
}}

自定义失败

在springsecurity中,如果在认证或者授权的过程中出现了异常,会被ExceptionTranslationFilter捕获,然后调用如下对象的方法处理异常:

  • AuthenticationEntryPoint对象的方法会对认证过程中出现的异常进行处理
  • AccessDeniedHandler对象的方法会对授权过程中出现的异常进行处理。

要自定义异常处理,只需要自定义AuthenticationEntryPoint和AccessDeniedHandler,然后配置给springsecurity。

AccessDeniedHandlerImpl类
package com.springSecurityTest.handler;import com.alibaba.fastjson.JSON;
import com.springSecurityTest.common.ResponseResult;
import com.springSecurityTest.utils.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Service;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Service
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "权限不足");String json = JSON.toJSONString(result);WebUtils.renderString(httpServletResponse,json);}
}
AuthenticationEntryPointImpl类
package com.springSecurityTest.handler;import com.alibaba.fastjson.JSON;
import com.springSecurityTest.common.ResponseResult;
import com.springSecurityTest.utils.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Service;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Service
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "用户认证失败请重新登录");String json = JSON.toJSONString(result);WebUtils.renderString(httpServletResponse,json);}
}
WebUtils

上面两个类中用到的工具类

package com.springSecurityTest.utils;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class WebUtils {/*** 将字符串渲染到客户端** @param response 渲染对象* @param string 待渲染的字符串* @return null*/public static String renderString(HttpServletResponse response, String string) {try{response.setStatus(200);response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().print(string);}catch (IOException e){e.printStackTrace();}return null;}
}
SecurityConfig

添加两个异常处理器

package com.springSecurityTest.config;import com.springSecurityTest.filter.JwtAuthenticationTokenFilter;
import com.springSecurityTest.handler.AuthenticationEntryPointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.annotation.Resource;
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Resourceprivate AuthenticationEntryPointImpl authenticationEntryPoint;@Resourceprivate AccessDeniedHandler accessDeniedHandler;@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
//testgit@Overrideprotected void configure(HttpSecurity http) throws Exception {http//由于是前后端分离项目,所以要关闭csrf.csrf().disable()//由于是前后端分离项目,所以session是失效的,我们就不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()//指定让spring security放行登录接口的规则.authorizeRequests()// 对于登录接口 anonymous表示允许匿名访问.antMatchers("/user/login").anonymous().antMatchers("/dark").hasAuthority("system:test:list")// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);//配置异常处理器http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);
http.cors();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}
}

跨域问题

同源策略:协议,域名,端口号要一致
在应用中启用了Spring Security,它默认会对所有的请求进行拦截和验证。这意味着,即使Spring Boot配置允许了CORS,Spring Security的默认配置也可能阻止跨域请求,因为它会检查每一个请求是否带有有效的认证信息。
为了使Spring Security与CORS协同工作,通常需要在Spring Security的配置中显式地允许CORS请求。

springboot配置
package com.springSecurityTest.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Override//重写spring提供的WebMvcConfigurer接口的addCorsMappings方法public void addCorsMappings(CorsRegistry registry) {// 设置允许跨域的路径registry.addMapping("/**")// 设置允许跨域请求的域名.allowedOriginPatterns("*")// 是否允许cookie.allowCredentials(true)// 设置允许的请求方式.allowedMethods("GET", "POST", "DELETE", "PUT")// 设置允许的header属性.allowedHeaders("*")// 跨域允许时间.maxAge(3600);}
}
springsecurity配置

在springsecurity的配置类SecurityConfig中,重写configure方法,加上http.cors();

自定义权限校验方法

package com.springSecurityTest.expression;import com.springSecurityTest.common.LoginUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;import java.util.List;@Component("MyExpressionRoot ")
public class MyExpressionRoot {public boolean hasAuthority(String authority){Authentication authentication = SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser  = (LoginUser) authentication.getPrincipal();List<String> permissions = loginUser.getPermissions();return permissions.contains(authority);}
}
使用自定义权限校验方法
package com.springSecurityTest.controller;import com.springSecurityTest.common.User;
import com.springSecurityTest.mapper.UserMapper;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.List;@RestController
public class dark {@ResourceUserMapper userMapper;
@RequestMapping("/dark")
@PreAuthorize("@MyExpressionRoot .hasAuthority('system:dept:list')")public String dark(){return "it's too dark!";
}
@GetMapping("/getUser")public List<User> usertest(){List<User> users = userMapper.selectList(null);System.out.println(users);return users;
}
}

CSRF

跨站请求伪造,是web常见攻击之一,依靠的是cookie中携带的认证信息,使用token可以不用担心csrf攻击,因为token不存储在cookie中,而且前端把token设置到请求头中访问网站资源。

扩展

如果登录页面还有验证码,那还可以在UsernamePasswordAuthenticationFilter之前再写一个验证码的过滤器,组成过滤器链。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 如何使用Wireshake解密Wi-Fi QoS Data报文?
  • 通过python搭建文件传输服务器;支持多台电脑之间互相传输文件(支持局域网或广域网)(应该也能用于虚拟机和宿主机之间)
  • Linux中的信号
  • LinuxC高级day05(函数指针、条件编译)
  • Python酷库之旅-第三方库Pandas(078)
  • Python知识点:如何使用Arcade进行简易游戏开发
  • 手机电量消耗分析工具 Battery Historian 指南
  • matlab求解方程
  • redis面试(十四)公平锁可重入
  • 【Linux入门】root密码忘记了怎么办?
  • 乳制品企业怎么防止信息泄露?使用加密软件保障数据安全
  • laravel 11 使用jw-auth进行API 登录
  • vs2022 启动之后崩溃解决方案
  • 学习嵌入式入门(十)高级定时器简介及实验(下)
  • 关于MariaDB
  • PAT A1017 优先队列
  • python大佬养成计划----difflib模块
  • 多线程事务回滚
  • 构建二叉树进行数值数组的去重及优化
  • 基于Volley网络库实现加载多种网络图片(包括GIF动态图片、圆形图片、普通图片)...
  • 前端存储 - localStorage
  • 我从编程教室毕业
  • Nginx实现动静分离
  • 如何在招聘中考核.NET架构师
  • ‌移动管家手机智能控制汽车系统
  • !!java web学习笔记(一到五)
  • # windows 运行框输入mrt提示错误:Windows 找不到文件‘mrt‘。请确定文件名是否正确后,再试一次
  • # 利刃出鞘_Tomcat 核心原理解析(七)
  • #### go map 底层结构 ####
  • #我与Java虚拟机的故事#连载08:书读百遍其义自见
  • (2024)docker-compose实战 (8)部署LAMP项目(最终版)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第13章第1节 (全局数据、栈和堆)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第7章第3节(封装和窗体)
  • (Java入门)学生管理系统
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • (二) 初入MySQL 【数据库管理】
  • (二)PySpark3:SparkSQL编程
  • (二)springcloud实战之config配置中心
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (汇总)os模块以及shutil模块对文件的操作
  • (蓝桥杯每日一题)love
  • (十六)视图变换 正交投影 透视投影
  • (一)Neo4j下载安装以及初次使用
  • (原)Matlab的svmtrain和svmclassify
  • (转)用.Net的File控件上传文件的解决方案
  • .net core webapi 部署iis_一键部署VS插件:让.NET开发者更幸福
  • .Net Core缓存组件(MemoryCache)源码解析
  • .NET 使用 XPath 来读写 XML 文件
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)
  • .Net+SQL Server企业应用性能优化笔记4——精确查找瓶颈
  • .NET8 动态添加定时任务(CRON Expression, Whatever)
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证
  • @data注解_SpringBoot 使用WebSocket打造在线聊天室(基于注解)