SpringSecurity 入门
SpringSecurity 入门
一步一步来。
集各家之所长,师从 尚硅谷、狂神、三更草堂;如果文中发现跟他们有类似的,不用想,就是他们那里拿来的
事先声明,本人很菜,其中有说的误人子弟的,请大家指出来。
环境:
idea2020.1 SpringBoot2.6.3 MyBatisPlus Maven3.6.3 SpringSecurity Lombok MySQL8.0.23…
总述:
先根据三家总的说一下,大致可以划分为两类: **1.**前后端不分离 **2.**前后端分离
SpringSecurity概述
这一部分就是简述它的。
它是一款重量级安全框架,说起安全框架就立马想起两个关键词“认证”和“授权”。
一般来说,Web 应用的安全性包括**用户认证(Authentication)和用户授权(Authorization)**两个部分,这两点也是 Spring Security 重要核心功能。 英语单词务必给我记住
(1)用户认证指的是:就是系统认为用户是否能登录,账号密码匹配的问题
(2)用户授权指的是:就是系统判断用户是否有权限去做某些事情,比如vip权限可以干啥,普通用户却不行
例子:老婆与卧室
老婆在卧室里面睡觉,
情况一:这时响起了敲门声,通过猫眼看是你(老公),认证通过,授权老公身份,允许进入卧室,嘿嘿嘿;
情况二:通过猫眼看是隔壁老王(熟人),认证通过,没有权限跟老婆一起进卧室,老王黯然神伤;
情况三:陌生人,客厅门都进不来
同台竞争对手:Shiro(大致学过,估计忘记了,轻量级)
技术选型推荐:SSM + Shiro SpringBoot + SpringSecurity
-前后端不分离
下面代码块中标记有项目代码的,就可作为练手的,即写进项目自行测试的(记号为 @@txf)
–入门案例
新建一个SpringBoot项目
目录结构如图:
需要引入的依赖:@@txf
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--先把这个注释掉-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-security</artifactId>-->
<!-- </dependency>--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency></dependencies>
HelloController.java @@txf
@Controller
public class HelloController {@RequestMapping("/hello")public String HelloPage() {return "hello";}
}
hello.html @@txf(templates下面)
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>hello</title>
</head>
<body>这是hello界面
</body>
</html>
启动项目,浏览器运行 localhost:8080/hello
会直接跳转到templates下面的hello.html界面
然后将pom.xml里面的security依赖加上,刷新maven,重新运行项目,浏览器输入 localhost:8080/hello
出现如下界面:
我们发现地址栏自动发生了变化,界面也不是hello了,在控制台我们甚至可以看到这么一出
这就说明,SpringSecurity拦截到了我们的/hello请求,发现没有进行认证(登录),就给我们默认处理,跳转到了这个界面(默认的)。
此时,username输入user, password复制控制到输出的密码,就可以进行登录了。以上是Security默认生成了密码,为第一种方式;第二种方式:在配置文件中自己配置username和password。如下
application.properties或者application.yml @@txf
server.port=8080 #这是我自己配置的端口号,爱写不写,还是养成写的好习惯#在配置文件中配置账号密码
spring.security.user.name=txf
spring.security.user.password=123456
配置完重新启动项目,发现控制台不会自动生成密码了,此时localhost:8080/hello跳转去/login时用我们在配置文件中的账号密码登录即可。登录完成就可以看到hello了。
你如果账号密码输入的不正确,肯定是通过不了的,自己动手实践去。
如果这样写账号密码的话,是固定的,实际场景肯定是从数据库中查到的!!!!!!!!
–加入数据库
引入依赖 @@txf
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.23</version>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version>
</dependency>
创建数据库test,创建表customer: @@txf(数据库建表)
DROP TABLE IF EXISTS `customer`;
CREATE TABLE `customer` (`id` int NOT NULL AUTO_INCREMENT,`realName` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '真实姓名',`username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账号',`password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',`age` int NULL DEFAULT 20 COMMENT '年龄',`adress` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '家庭住址',`gender` int NULL DEFAULT 1 COMMENT '性别',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of customer
-- ----------------------------
INSERT INTO `customer` VALUES (1, '田小锋', 'txfnbnbnb', '123456', 20, '湖北省荆门市京山市', 1);
里面加入了一条数据,那就是我了。。。
配置文件中肯定是要修改的,加入下面这些 @@txf
#数据库
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=GMT%2B8&useUnicode=true&allowMultiQueries=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Drivermybatis-plus.type-aliases-package=com.feng.securitydemo01.pojo
mybatis-plus.mapper-locations=classpath:/mapper/*.xml
实体类pojo包 @@txf
package com.feng.securitydemo01.pojo;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("customer")
public class Customer {@TableIdprivate Integer id;private String realname;private String username;private String password;private Integer age;private String adress;private Integer gender;
}
Mapper接口 @@txf
package com.feng.securitydemo01.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.feng.securitydemo01.pojo.Customer;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;@Mapper
@Repository
public interface CustomerMapper extends BaseMapper<Customer> {}
–原理初探
页面提交方式必须为 post 请求
先放一张别人的图,SpringSecurity本质是一个过滤器链。
大致流程:
各个过滤器及其顺序:
上图只需要明白流程,不要记!大致是从顶部先一步一步调用走到底部,然后从底部返回值一步一步回到顶部
我们在它默认的登录页面输入的username和password,经过上图一系列过程,最终到UserDetailsService中。这个UserDetailsService是一个接口。
package com.feng.securitydemo01.service.Impl;@Service //这个就不用多说了吧,交给Spring容器来管理,替换掉默认的
public class MyUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//在这个方法里面,根据前端页面传过来的username,我们去数据库做一系列查询操作,//返回值是一个UserDetails,}
}//==================下面是具体实现===================== @@txf
package com.feng.securitydemo01.service.Impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.feng.securitydemo01.mapper.CustomerMapper;
import com.feng.securitydemo01.pojo.Customer;
import com.feng.securitydemo01.pojo.LoginCustomer;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.Objects;@Service
public class MyUserDetailsService implements UserDetailsService {@Autowiredprivate CustomerMapper customerMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {QueryWrapper<Customer> wrapper = new QueryWrapper<>();wrapper.eq("username",username);Customer customer = customerMapper.selectOne(wrapper);if (Objects.isNull(customer) ) { //空的,说明查不到throw new UsernameNotFoundException("用户名不存在!");}//查到了,注意,这里现在我们不需要密码校验,直接封装为UserDetails返回就行了//UserDetail我们自己实现return new LoginCustomer(customer);}
}
UserDetails
//点进源码发现这也是一个接口,是接口我们就可以去实现它,来完成自定义(虽然它已经有实现类了)
public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled();
}
//===============================源码===========================================
//LoginCustomer.java 自定义实现UserDetails接口
//=====================具体实现=========== @@txf
package com.feng.securitydemo01.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;@Data
@AllArgsConstructor
public class LoginCustomer implements UserDetails {private Customer customer; //存入我们根据username查到的customer@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() { //获取权限,先不看return null;}@Overridepublic String getPassword() {return customer.getPassword();}@Overridepublic String getUsername() {return customer.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() { //能使用return true;}
}
–流程分析
根据上面的流程图文字解析:
根据传过来的username和password构造UsernamePasswordAuthenticationToken;(UsernamePasswordAuthenticationToken继承自AbstractAuthenticationToken,抽象类AbstractAuthenticationToken实现Authentication接口。)
调用方法ProviderManager.authenticate(参数类型是Authentication) ,所以可以传进去上面构造的User…Token。
public Authentication authenticate(Authentication authentication) throws AuthenticationException {..........................Iterator var9 = this.getProviders().iterator();while(var9.hasNext()) {.......//这里就是AbstractUserDetailsAuthenticationProvider的authenticate,result = provider.authenticate(authentication);.......}抽象类AbstractUserDetailsAuthenticationProvider的authenticate,
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
- AbstractUserDetailsAuthenticationProvider的子类DaoAuthenticationProvider.retrieveUser()
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {.................//这里用上了我们的UserDetailsService!!!!!!!!!!!//是不是就返回了UserDetails!!!UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);...................}//看看这个方法是干什么的???????????
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {if (authentication.getCredentials() == null) {this.logger.debug("Failed to authenticate since no credentials provided");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));} else {String presentedPassword = authentication.getCredentials().toString();//=========================// this.passwordEncoder 密码校验,知道为什么不用你自己写了吧。自定义也行//============================if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {this.logger.debug("Failed to authenticate since password does not match stored value");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}}}
–加密
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”
还有一种是什么not like 什么什么的,
//密码加密
//假如数据库泄露了,密码如果是明文存储,那还搞个鬼子!!!!!!!!!所以,安全框架嘛,必然考虑得如此周到
/*
我们存进数据库里面的密码,那就肯定是要加密的了,即存进去的是密文 @@txf
*/
package com.feng.securitydemo01.config; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();}
}
//加密测试
@Testpublic void test() {String s = "123456";String s1 = encoder.encode(s);String s2 = encoder.encode(s);System.out.println(s1 +" " + s2);}
//输出结果,居然不一样!!!!这我们就不深究了。。。。反正都是123456
$2a$10$ipQhvDpdCZrpAc.JR7og/.l.3EpPovD2NlMcpUnYH4xv8inMemhI6 $2a$10$LDPVG9vvJvjBHsVfgLv0RO.2sq5LIscT01WBaybTf7bpQ.XHFOASa//密码匹配测试@Testpublic void test() {String s = "123456";boolean matches = encoder.matches(s,"$2a$10$ipQhvDpdCZrpAc.JR7og/.l.3EpPovD2NlMcpUnYH4xv8inMemhI6");System.out.println(matches); //true}
所以数据库中的123456要改咯,改成密文。将配置文件中自定义的username和password注掉,再来启动项目测试。这时候输入的就是数据库中的了。
数据中:
1 田小锋 txf $2a$10$ipQhvDpdCZrpAc.JR7og/.l.3EpPovD2NlMcpUnYH4xv8inMemhI6 20 湖北省荆门市京山市 1
启动项目:localhost:8080/hello 这时候输入自己的账号密码即可。
仔细看上面两个流程图,流程一定得明白,源码才勉强看得懂
–小项目
在上面的基础之上,改造页面,权限访问那些
授权基本流程
在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。
所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。
然后设置我们的资源所需要的权限即可
数据添加:(后面两个的密码是txf123)
用户拥有某一种角色,角色对应有哪些权限。上面这些表的意思。(走后门、查找、浏览)三种权限,admin所有权限都有,vip只有两种,user只有一种。四张表的名字分别是customer,cusrole,cusauth,cusroleauth。
有了权限,我们需要从数据库里面根据customer的id查询该用户所拥有的权限名称。
SELECT authname
FROM cusauth
WHERE authid in (SELECT authid FROM cusroleauthWHERE roleid = (SELECT roleid FROM customerWHERE id = 1)
)#这里可不可以请大佬帮我换一种方式查出来。。。本人着实有点愚钝
Mapper接口里面 @@txf
@Mapper
@Repository
public interface CustomerMapper extends BaseMapper<Customer> {List<String> getAuthorizaById(Integer id);
}
Mapper.xml ( resourses下的mapper目录里面CustomerMapper.xml) @@txf
<?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.feng.securitydemo01.mapper.CustomerMapper"><select id="getAuthorizaById" resultType="string" parameterType="int">SELECT authnameFROM cusauthWHERE authid in (SELECT authidFROM cusroleauthWHERE roleid = (SELECT roleidFROM customerWHERE id = #{id}))</select></mapper>
修改
MyUserDetailsService和loginCustomer @@txf
//DetailsService
//查询权限封装进去
List<String> authoriza = customerMapper.getAuthorizaById(customer.getId());
//查到了,注意,这里现在我们不需要密码校验,直接封装为UserDetails返回就行了
//UserDetail我们自己实现
return new LoginCustomer(customer,authoriza);//LoginUserDetailsService.java
private Customer customer;
private List<String> myAuthorities;
//存储SpringSecurity所需要的权限信息的集合
private List<GrantedAuthority> authorities;public LoginCustomer(Customer customer, List<String> myAuthorities) {this.customer = customer;this.myAuthorities = myAuthorities;}@Override
public Collection<? extends GrantedAuthority> getAuthorities() {if(authorities!=null){return authorities;}//myAuthoritiesauthorities = myAuthorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return authorities;
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
//主启动类上添加此注解,先开启相关配置。
HelloController @@txf
package com.feng.securitydemo01.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
public class HelloController {@RequestMapping({"/","/index"})public String index() {return "index";}@RequestMapping("/toLogin")public String toLogin() {return "login";}@GetMapping("/noauth")public String accessDenyPage(){return "noauth"; }@RequestMapping("/houmen")@PreAuthorize("hasAuthority('zouhoumen')") //看登录的用户是否有此权限。public String houmen() {return "admin/houmen";}@RequestMapping("/find")@PreAuthorize("hasAuthority('find')")public String find() {return "vip/find";}@RequestMapping("/hello")@PreAuthorize("hasAuthority('look')")public String HelloPage() {return "hello";}
}
此外,我们希望对首页,不需要登录就能访问怎么做呢? @@txf
@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();}//================这中间配置即可@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable() //关闭csrf.formLogin()
// .usernameParameter("自己根据情况写") 自定义表单的username,password
// .passwordParameter("自己情况写")//还有rememberme、logout功能,极其简单,这里就不说了.loginProcessingUrl("/login") //登录的url.successForwardUrl("/index") //登录成功后走/index.and().authorizeRequests().antMatchers("/","/index","/toLogin").permitAll() //"/","/index"放行.anyRequest().authenticated(); //所有请求都需要认证//配置403处理器,修改response中的内容,//http.exceptionHandling().accessDeniedHandler(myAccessFail);//自定义403页面,前后端不分离http.exceptionHandling().accessDeniedPage("/noauth");}//==================
}
403处理器
package com.feng.securitydemo01.config;import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;@Component
public class MyAccessFail implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {//设置响应状态码response.setStatus(HttpServletResponse.SC_FORBIDDEN);//设置响应数据格式response.setContentType("application/json;charset=utf-8");//输入响应内容PrintWriter writer = response.getWriter();String json="{\"status\":\"403\",\"msg\":\"无权访问\"}";writer.write(json);writer.flush();}
}
package com.feng.securitydemo01.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredMyAccessFail myAccessFail;@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable() //关闭csrf.formLogin()
// .usernameParameter("自己根据情况写") 自定义表单的username,password
// .passwordParameter("自己情况写")//还有rememberme、logout功能,极其简单,这里就不说了.loginProcessingUrl("/login") //登录的url.successForwardUrl("/index") //登录成功后走/index.and().authorizeRequests().antMatchers("/","/index","/toLogin").permitAll() //"/","/index"放行.anyRequest().authenticated(); //所有请求都需要认证//配置403处理器,修改response中的内容,前后端分离,是根据响应体来具体判断的。//http.exceptionHandling().accessDeniedHandler(myAccessFail);//自定义403页面,前后端不分离http.exceptionHandling().accessDeniedPage("/noauth");}
}
源码
https://download.csdn.net/download/okok__TXF/80962384
-前后端分离
如果前文误人子弟太多了,就不写了,这篇也删了。。。根据情况来写吧,
SpringSecurity 入门相关推荐
- SpringSecurity入门(SSM版)
1. 简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Sp ...
- SpringSecurity入门
Spring Security介绍: Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架. (https://project ...
- SpringSecurity入门到入土教程_2 Oauth教程
https://gitee.com/fakerlove/spring-security 文章目录 SpringOauth 教程 1. 简介 1.1 oauth2 概念 架构图 验证流程 spring ...
- SpringSecurity入门到入土教程_1
https://gitee.com/fakerlove/spring-security 文章目录 SpringSecurity 教程 1. 简介 1.1 概念 1.2 入门案例 1.3 自定义登录逻辑 ...
- 普歌 - SpringSecurity入门(1)
构建SpringSecurity模块化工程 前言 一.项目结构 二.使用maven创建项目 1.创建项目 parent的pom文件: base的pom文件: core的pom文件: web的pom文件 ...
- SpringSecurity入门01(含源码)
环境:SpringBoot 2.1 + Mybatis + Spring Security 5.0 一.导入依赖 导入 spring-boot-starter-security 依赖,在 Spring ...
- SpringSecurity - 基础篇
文章目录 一.SpringSecurity能做什么 二.SpringSecurity替代方案 三.权限管理中的相关概念 四.SpringSecurity 入门案例 前言:通常我们写http接口是不会用 ...
- .anonymous springsecurity需要登陆嘛_springSecurity之java配置篇
一 前言 本篇是springSecurity知识的入门第二篇,主要内容是如何使用java配置的方式进行配置springSeciruty,然后通过一个简单的示例自定义登陆页面,覆盖原有springSec ...
- 学习SpringSecurity这一篇就够了
目录 一.SpringSecurity 框架简介 1.1.概要 1.2.Spring Security到底能干什么? 1.3.常用术语 1.4.历史 1.5.同款产品对比 1.6.模块划分 二.Spr ...
最新文章
- 【CSS】【13】文字的排版
- java final
- 纯C#代码的Excel读取器(不需要Office Excel Com组件)
- redhat 6 配置 yum 源
- python字符串可以保存在变量中吗_在python中可以从字符串变量实例化类吗?
- 肺部胸片图像掩膜和伪彩色处理matlab
- (十一)深入浅出TCPIP之TCP粘包问题
- linux 64位内存划分,linux 64位内存空间大小?(linux 64 bits memory space size? [closed])
- css 矩形两边挖半圆
- Log4j2日志框架集成Slf4j日志门面
- (组合数学笔记)格点路径问题分析求解
- mysql linux 用户_Linux mysql添加用户,删除用户,以及用户权限
- oracle 主键自增
- 大学生计算机考试系统软件,我爱C”《大学计算机基础》考试系统学生端软件使用说明.doc...
- Android 动画 - TranslateAnimation位移动画
- 如何在word中的框中打钩、打叉
- 13丨性能测试场景:如何进行场景设计
- 1.计算机发展阶段 计算机发展历史 机械式计算机 机电式计算机 电子计算机 逻辑电路与计算机 二极管 电子管 晶体管 硅 门电路 计算机 电磁学计算机二进制...
- Monash call (莫纳什来电) -开篇
- 激光雷达:点云语义分割算法
热门文章
- GraphTCN: Spatio-Temporal Interaction Modeling for Human Trajectory Prediction
- 计算机数据库服务器名,附加数据库 对于 服务器“服务器计算机名”失败。 (Microsoft.SqlServer.Smo)...
- android 三星拍照,据说卖断货?我替大伙试试三星S10的拍照功能好不好用
- 手写最大堆(Java实现)
- 我们做互联网的产品的时候,面对不同的细分市场
- wordpress主题GIT,多功能高级WordPress主题
- C#中用“橡皮条”法绘图和重绘
- linux系统var日志磁盘满了,Linux 系统(Centos版本)磁盘空间占满的解决办法。
- 《动物园之星》给模拟经营游戏带来了什么
- 回车、换行、空格的ASCII码值—(附ASCII码表)