**

security整体流程

**

1.点击login按钮,会被UsernamePasswordAuthenticationFilter拦截。获取用户输入的账号和密码
2.UsernamePasswordAuthenticationToken会验证账号和密码
3.验证通过this.setDetails(request, authRequest),把信息存入
4.此时可以注册session
5.实现UserDetailsService
6.验证账号信息(是不是被锁定等)
7.存入账号信息和角色信息

8.自定义的用户认证成功处理器SavedRequestAwareAuthenticationSuccessHandler。可以自定义一些自己的信息,方便开发的时候使用
9.自定义权限拦截器

以下代码已经实现基本功能,后续有待完善

一、跳转界面

package com.htky.exchange.controller.system;import com.htky.exchange.model.system.ResourcesEntity;
import com.htky.exchange.service.system.ResourcesService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;import java.util.List;/**** @author liyd* @date 2021/08/19*/
@Controller
@Api(tags = "IndexController", description = "管理")
public class IndexController extends BaseController {@Autowiredprivate ResourcesService resourcesService;/*** 登录界面* @return*/@ApiOperation("跳转登录页面")@GetMapping("/login")public ModelAndView login () {ModelAndView mv = new ModelAndView("/login");return mv;}/*** 首页** @return*/@ApiOperation("跳转到首页")@RequestMapping(value = "/index", method = {RequestMethod.GET})public ModelAndView index() {ModelAndView mv = new ModelAndView("/index");List<ResourcesEntity> list = resourcesService.queryUserResources(getUsers().getUserCode(), "菜单资源", "", "智能交换系统");mv.addObject("menus", list);mv.addObject("userType", getUsers().getUserType());return mv;}
}

二、SpringSecurity核心配置文件

package com.htky.exchange.config;import com.htky.exchange.security.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;/*** @author liyd* security配置类* @EnableWebSecurity 开启security安全框架* @EnableGlobalMethodSecurity(prePostEnabled = true) 开启方法级安全验证*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredCustomUserDetailsService userDetailsServiceImpl;@AutowiredDynamicAccessDecisionManager dynamicAccessDecisionManager; //决策拦截器,用户是否有该权限@AutowiredDynamicSecurityMetadataSource dynamicSecurityMetadataSource; //获取权限列表@AutowiredDynamicSecurityFilter dynamicSecurityFilter;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(passwordEncoder());}/*** 释放静态资源 js,css等* @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/static/**");}@Beanpublic PasswordEncoder passwordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}@Beanpublic SessionRegistry sessionRegistry(){return new SessionRegistryImpl();}@Beanpublic AuthenticationSuccessHandler authenticationSuccessHandler() {CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler = new CustomAuthenticationSuccessHandler();customAuthenticationSuccessHandler.setSuccessUrl("/index");customAuthenticationSuccessHandler.setDefaultTargetUrl("/index");customAuthenticationSuccessHandler.setAlwaysUseDefaultTargetUrl(true);return customAuthenticationSuccessHandler;}@Beanpublic AuthenticationFailureHandler authenticationFailureHandler() {CustomAuthenticationFailureHandler customAuthenticationFailureHandler = new CustomAuthenticationFailureHandler();customAuthenticationFailureHandler.setDefaultFailureUrl("/login");//customAuthenticationFailureHandler.setAllowSessionCreation(true);return customAuthenticationFailureHandler;}/*** 自定义认证过滤器* @return* @throws Exception*/@Beanpublic LoginFilter loginFilter() throws Exception {LoginFilter loginFilter = new LoginFilter();//认证成功。。。loginFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());//认证失败loginFilter.setAuthenticationFailureHandler(authenticationFailureHandler());loginFilter.setAuthenticationManager(authenticationManagerBean());loginFilter.setFilterProcessesUrl("/userLogin");ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());sessionStrategy.setMaximumSessions(1);loginFilter.setSessionAuthenticationStrategy(sessionStrategy);return loginFilter;}/*** 重写configure配置,编写权限校验规则* @param http* @throws Exception'*/@Overrideprotected  void configure(HttpSecurity http) throws Exception {http.formLogin().loginPage("/login").permitAll() //登录页面跳转请求.loginProcessingUrl("/login").permitAll() //登录发起的post请求方法.and().headers().frameOptions().sameOrigin();http.authorizeRequests() // 认证配置.antMatchers("/", "/userLogin", "/login").permitAll()//设置哪些路径可以直接访问,不需要认证.anyRequest() // 任何请求.authenticated() // 都需要身份验证.and().csrf().ignoringAntMatchers("/userLogin") //忽略userLogin请求进行csrf验证.and().csrf().disable(); //关闭corshttp.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>(){@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setAccessDecisionManager(dynamicAccessDecisionManager);//决策管理器object.setSecurityMetadataSource(dynamicSecurityMetadataSource);//数据源return object;}});//自定义用户认证过滤器http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);//有动态权限配置时添加动态权限校验过滤器http.addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class);}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}
}

三、UsernamePasswordAuthenticationFilter拦截器

package com.htky.exchange.security;import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class LoginFilter extends UsernamePasswordAuthenticationFilter {@AutowiredSessionRegistry sessionRegistry;private boolean postOnly = true;private static final String METHOD_POST = "POST";@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {//必须为post请求if (this.postOnly && !METHOD_POST.equals(request.getMethod())) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());} else {//获取Login的账号及密码String userName = this.obtainUsername(request);if (StringUtils.isEmpty(userName)) {userName = "";}String passWord = this.obtainPassword(request);if (StringUtils.isEmpty(userName)) {passWord = "";}//验证账号和密码//todo security底层比较密码的时候,为什么拿123加密后和没有加密的123去比较,而不是和数据库密码去比较???UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, passWord);// 请求和Token存起来this.setDetails(request, authRequest);// 用户名密码验证通过后,注册sessionsessionRegistry.registerNewSession(request.getSession().getId(), authRequest.getPrincipal());// 过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证return this.getAuthenticationManager().authenticate(authRequest);}}
}

四:实现UserDetailsService

package com.htky.exchange.security;import com.htky.exchange.model.system.UsersEntity;
import com.htky.exchange.service.impl.system.UsersServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.ArrayList;
import java.util.List;/*** @author liyd* @description 自定义的UserDetailsService* @create 2021/9/6*/
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UsersServiceImpl usersService;@Overridepublic UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {List<GrantedAuthority> authorities = new ArrayList<>();//获取用户信息、角色UsersEntity etyUsers = usersService.doLogin(userName, authorities);System.out.println(etyUsers.getPassword());if (null == etyUsers){throw new UsernameNotFoundException("用户名不存在");}if ("1".equals(etyUsers.getUserState()) || "2".equals(etyUsers.getUserState())) {// 返回UserDetails实现类,因为数据库存入的是密文,所以密码不需要加密return new CustomUserDetails(etyUsers);} else {throw new UsernameNotFoundException("用户被禁用");}}
}

五:实现UserDetails

package com.htky.exchange.security;import com.htky.exchange.model.system.UsersEntity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;/*** @author  liyd* @description 赋于用户对应的角色* @create 2021/9/6*/
public class CustomUserDetails implements UserDetails {private UsersEntity user;public CustomUserDetails(UsersEntity etyUsers) {this.user = etyUsers;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.user.getAuthorityInfos();}@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;}public UsersEntity getUser() {return user;}
}

六、账号密码等信息认证成功触发SavedRequestAwareAuthenticationSuccessHandler

package com.htky.exchange.security;import com.htky.exchange.common.util.Constants;
import com.htky.exchange.dao.system.DeptInMapper;
import com.htky.exchange.model.system.DeptInEntity;
import com.htky.exchange.model.system.UsersEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;/*** 自定义的用户认证成功处理器* @author liyd* @date 2021/9/4* AuthenticationSuccessHandler接口是Security提供的认证成功处理器接口,我们只需要去实现它即可。* 但是通常来说,我们不会直接去实现AuthenticationSuccessHandler接口,而* 是继承SavedRequestAwareAuthenticationSuccessHandler 类,这个类会记住用户上一次请求的资源路径,* 比如:用户请求books.html,没有登陆所以被拦截到了登录页,当你万成登陆之后会自动跳转到books.html,而不是主页面。*/
@Component
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {@Autowiredprivate DeptInMapper daoDeptIn;@Value("${spring.security.loginType}")private String loginType;private String successUrl;public CustomAuthenticationSuccessHandler() {}@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException, ServletException {//这里可以自定义一些实现逻辑//比如往session中存入用户基本信息等super.onAuthenticationSuccess(request, response, authentication);}public void setSuccessUrl(String successUrl) {this.successUrl = successUrl;}}

七、自定义权限拦截器

package com.htky.exchange.security;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Service;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;/*** @author liyd* @Description: 权限拦截器* @date 2021/09/09*/
@Service
public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter {@Autowiredprivate DynamicSecurityMetadataSource dynamicSecurityMetadataSource;@Autowiredpublic void setMyAccessDecisionManager(DynamicAccessDecisionManager dynamicAccessDecisionManager) {super.setAccessDecisionManager(dynamicAccessDecisionManager);}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);//OPTIONS请求直接放行if(request.getMethod().equals(HttpMethod.OPTIONS.toString())){fi.getChain().doFilter(fi.getRequest(), fi.getResponse());return;}//此处会调用AccessDecisionManager中的decide方法进行鉴权操作InterceptorStatusToken token = super.beforeInvocation(fi);try {fi.getChain().doFilter(fi.getRequest(), fi.getResponse());} finally {super.afterInvocation(token, null);}}@Overridepublic void destroy() {}@Overridepublic Class<?> getSecureObjectClass() {return FilterInvocation.class;}@Overridepublic SecurityMetadataSource obtainSecurityMetadataSource() {return dynamicSecurityMetadataSource;}}

八、获取权限信息

package com.htky.exchange.security;import cn.hutool.core.util.URLUtil;
import com.htky.exchange.model.system.ResourcesEntity;
import com.htky.exchange.model.system.RoleEntity;
import com.htky.exchange.service.system.ResourcesService;
import com.htky.exchange.service.system.RoleService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;import java.util.*;
import java.util.concurrent.ConcurrentHashMap;/*** @author liyd* @Description 获取权限列表* @date 2021/09/09*/
@Component
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {private static Map<String, ConfigAttribute> configAttributeMap = new ConcurrentHashMap<String, ConfigAttribute>();@Autowiredprivate ResourcesService resourcesService;@Autowiredprivate RoleService roleService;public void clearDataSource() {configAttributeMap.clear();configAttributeMap = null;}public void loadResources(){//todo 先这样写,功能全部实现,再去优化。。   感觉可以抽出来,启动项目的时候加载权限等列表//获取角色List<RoleEntity> listRole = roleService.queryAll();for (RoleEntity role : listRole) {if (StringUtils.isEmpty(role.getRoleName())) {continue;}//根据角色查询处对应的菜单List<ResourcesEntity> listMenu = resourcesService.queryRoleMenu(role.getRoleId());for (ResourcesEntity menu : listMenu) {if (StringUtils.isEmpty(menu.getResourceUrl())) {continue;}SecurityConfig config = new SecurityConfig(menu.getResourceName());configAttributeMap.put(menu.getResourceUrl(), config);}}}@Overridepublic Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {if (configAttributeMap.isEmpty()) {this.loadResources();}List<ConfigAttribute>  configAttributes = new ArrayList<>();//获取当前访问的路径String url = ((FilterInvocation) o).getRequestUrl();String path = URLUtil.getPath(url);PathMatcher pathMatcher = new AntPathMatcher();Iterator<String> iterator = configAttributeMap.keySet().iterator();//获取访问该路径所需资源while (iterator.hasNext()) {String pattern = iterator.next();pattern = "/" + pattern;if (pathMatcher.match(pattern, path)) {System.out.println(configAttributeMap.get(pattern));configAttributes.add(configAttributeMap.get(pattern));}}// 未设置操作请求权限,返回空集合return configAttributes;}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> aClass) {return true;}}

九、动态权限决策管理器,用于判断用户是否有访问权限

package com.htky.exchange.security;import cn.hutool.core.collection.CollUtil;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;import java.util.Collection;
import java.util.Iterator;/*** @author  liyd* @description 动态权限决策管理器,用于判断用户是否有访问权限* @create 2021/09/09*/
@Component
public class DynamicAccessDecisionManager implements AccessDecisionManager {@Overridepublic void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {// 当接口未被配置资源时直接放行if (CollUtil.isEmpty(configAttributes)) {return;}Iterator<ConfigAttribute> iterator = configAttributes.iterator();while (iterator.hasNext()) {ConfigAttribute configAttribute = iterator.next();//将访问所需资源或用户拥有资源进行比对String needAuthority = configAttribute.getAttribute();for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {if (needAuthority.trim().equals(grantedAuthority.getAuthority())) {return;}}}throw new AccessDeniedException("抱歉,您没有访问权限");}@Overridepublic boolean supports(ConfigAttribute configAttribute) {return true;}@Overridepublic boolean supports(Class<?> aClass) {return true;}}

可参考其它链接,几篇不错的springsecurity集成方案
SpringSecurity学习
Spring Boot中使用使用Spring Security和JWT
Springboot + Spring Security 实现前后端分离登录认证及权限控制

SpringBoot 集成SpringSecurity整体流程相关推荐

  1. SpringBoot集成SpringSecurity(二) 个性化登录配置(remember-me mongodb)

    前言 本文件所记录的是使用SpringSecurity实现remember me功能,有兴趣的朋友可以继续阅读,有何不足之处还请各位指出(本文未对用户 -  角色 - 权限三者的关系进行详细介绍详情见 ...

  2. springboot集成springSecurity,jwt实现前后端分离

    ** ## springboot集成springSecurity,jwt实现授权,查看权限,获取用户信息:] 简单的springsecurity授权登录校验我就暂时不写了,博客太多了: 第一步:还是导 ...

  3. springboot md5加密_SpringSecurity入门-SpringBoot集成SpringSecurity

    前言 至今Java能够如此的火爆Spring做出了很大的贡献,它的出现让Java程序的编写更为简单灵活,而Spring如今也形成了自己的生态圈,今天咱们探讨的是Spring旗下的一个款认证工具:Spr ...

  4. SpringBoot集成Editor.md 流程详细

    接上一篇:SpringBoot整合Editor.md实现Markdown编辑器 https://blog.csdn.net/weixin_40816738/article/details/103160 ...

  5. Springboot集成SpringSecurity(获取当前登录人)

    简言 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架.它是用于保护基于Spring的应用程序的实际标准. Spring Security是一个框架,致力于为Java应 ...

  6. SpringBoot:集成SpringSecurity

    在 Web 开发中,安全一直是非常重要的一个方面.安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来.如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严 ...

  7. springboot集成springsecurity 使用OAUTH2做权限管理

    Spring Security OAuth2 主要配置,注意application.yml最后的配置resource filter顺序配置,不然会能获取token但是访问一直 没有权限 WebSecu ...

  8. java.securti_springboot集成springsecurity 使用OAUTH2做权限管理的教程

    Spring Security OAuth2 主要配置,注意application.yml最后的配置resource filter顺序配置,不然会能获取token但是访问一直 没有权限 WebSecu ...

  9. 手把手带你在集成SpringSecurity的SpringBoot应用中添加短信验证码登录认证功能

    本文目录 前言 1 自定义AuthenticationToken类 2 自定义AuthenticationProvider类 3 自定义MobilePhoneAuthenticationFilter ...

  10. springboot整个cas_SpringBoot集成SpringSecurity+CAS

    1 简介 本文主要讲述如何通过SpringSecurity+CAS在springboot项目中实现单点登录和单点注销的功能. 2 项目依赖 主要依赖如下 org.springframework.boo ...

最新文章

  1. python ord()与chr()用法以及区别
  2. python连接oracle视频教程_python怎么连接oracle
  3. SpringMvc Intercetor
  4. 保姆级!!前端必会Taro入门级教学!!
  5. linux 修改ldap密码,Linux-ldap密码修改程序,如何加密ssha
  6. oracle唯一性索引的用法,sql – Oracle:基于函数的索引选择唯一性
  7. Mybatis 一对多
  8. Intelij IDEA中web项目抛org.apache.ibatis.binding.BindingException: Invalid bound statement(not found):异常
  9. 20. PHP 表单验证 - 验证 E-mail 和 URL
  10. UIKit 框架之UIAlertController
  11. 阵列信号处理学习小结
  12. 深度学习(二),终于理解了深度学习原理--SPGD(SGD)优化算法的实现原理
  13. 单片机中断程序,如何被中断?
  14. Apple Pay 详解
  15. 不想996的程序员不是好老板(上)
  16. 游戏主机系统,破旧老电脑秒变高性能儿时复古游戏主机
  17. Android 自定义圆角TextView控件 带边框 非shape
  18. wdb_2018_3rd_soEasy
  19. 【优化求解】基于matlab禁忌搜索算法求解函数极值问题【含Matlab源码 1204期】
  20. (转)coures包下载和安装 可解决报错ImportError: No module named '_curses'

热门文章

  1. ntp的同步方式slew step的区别
  2. JavaScript 每日一题 #10
  3. 打开Beyond Compare报This license key has been revoked:
  4. 秀米排版,从入门到入土(一)
  5. HBuilder的mui登录模板修改登录页为非入口页面的方法
  6. PCB后续以及泪滴、敷铜和标识
  7. iMazing中IPA文件的介绍与管理
  8. 20165219王彦博第一周学习总结
  9. android 多个按键精灵,Android 一种通用的按键精灵的实现思路
  10. 基于STM32_HAL库GY-30(BH1750FLV)驱动