Spring Security实战实用_涛濤的博客-程序员资料

技术标签: Spring  

以前也没有接触过Spring Security,最近公司要重构一个安全登录控制,顺便学习了一下此框架,我会将我在学习过程中遇到的疑惑一一告诉大家,希望对大家有一些帮助,废话不多说直接上代码才是大家最关心的:

1.第一步得搭建Spring Security环境噻;

我使用的开发环境是IDEA:

springSecurity='3.2.9.RELEASE'
springMobile='1.1.5.RELEASE'

"org.springframework.security:spring-security-web:$springSecurity",
"org.springframework.security:spring-security-config:$springSecurity",
"org.springframework.security:spring-security-remoting:$springSecurity",
"org.springframework.security:spring-security-acl:$springSecurity",
"org.springframework.security:spring-security-aspects:$springSecurity",
"org.springframework.security:spring-security-crypto:$springSecurity",
"org.springframework.security:spring-security-ldap:$springSecurity",
"org.springframework.security:spring-security-taglibs:$springSecurity",
"org.springframework.mobile:spring-mobile-device:$springMobile",
当然也许要Spring的基础jar,这基础环境就大家自己去搭建了

2.编写Spring-security.xml文件,这个是关键,直接上代码

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">

       <!--WAP登录表单地址认证切入点-->
       <security:http entry-point-ref="loginUrlAuthenticationEntryPoint" use-expressions="true">
              <!--配置访问地址必须具备的角色,"/succ/*"拦截所有成功地址-->
              <security:intercept-url pattern="/succ/*" access="hasRole('ROLE_MOBILE_CUSTOMER')"/>



              <!--session管理-->
              <security:session-management session-fixation-protection="newSession"/>
              <!--
              <security:logout logout-url="/logout" logout-success-url="/login/randomcode"/>
              -->
              <!--配置过滤器-->
              <security:custom-filter ref="simpleAuthenticationFilter" position="PRE_AUTH_FILTER" />

       </security:http>
       <bean id="loginUrlAuthenticationEntryPoint" class="com.ct10000.sc.sctelwap.wap.sso.entrypoint.WAPLoginUrlAuthenticationEntryPoint">
              <!--构造方法参数-->
              <constructor-arg index="0"  value="/succ/ssosuccessed"/>
       </bean>
       <!--SSO过滤器-->
       <bean id="simpleAuthenticationFilter" class="com.ct10000.sc.sctelwap.wap.sso.filter.SimpleAuthenticationFilter">
              <property name="authenticationManager" ref="authenticationManager"/>
              <property name="authenticationSuccessHandler" ref="authenticationSuccessHandler"/>
              <property name="authenticationFailureHandler" ref="authenticationFailureHandler"/>
              <property name="authenticationDetailsSource" ref="authenticationDetailsSource"/>
       </bean>
       <!--验证成功处理器-->
       <bean id="authenticationSuccessHandler" class="com.ct10000.sc.sctelwap.wap.sso.handler.WAPAuthenticationSuccessHandler"/>
       <!--验证失败处理器-->
       <bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
              <!--sso认证失败跳转页面-->
              <property name="defaultFailureUrl" value="/error/ssofailed"/>
       </bean>
       <!--验证细节Provider-->
       <bean id="simpleAuthenticationProvider" class="com.ct10000.sc.sctelwap.wap.sso.provider.SimpleAuthenticationProvider"/>
        <!--WAP 认证细节源对象-->
       <bean id="authenticationDetailsSource" class="com.ct10000.sc.sctelwap.wap.sso.source.WAPAuthenticationDetailsSource"/>


       <security:authentication-manager alias="authenticationManager">
              <security:authentication-provider ref="simpleAuthenticationProvider"/>
       </security:authentication-manager>
</beans>
3.web.xml配置

<!--源设备过滤器-->
<filter>
    <filter-name>deviceResolverRequestFilter</filter-name>
    <filter-class>org.springframework.mobile.device.DeviceResolverRequestFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>deviceResolverRequestFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<!--Spring Security-->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
4此处贴上Spring security的运行机制;


SimpleAuthenticationProvider细节验证:

 

执行SimpleAuthenticationUserDetailsService  方法查询客户资料

 

loadUserDetails()中执行逻辑:

1.POST请求认证接口,返回数据包括用户号码,号码类型,渠道ID;

2.根据用户号码,号码类型,渠道ID查询客户资料,返回MobileUser;

验证成功之后Spring Security会自动把用户信息保存到上下文中,获取用户信息

user = (MobileUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal();     

5.关键代码来了

Filter:

/**
 * Created by leitao on 2016/12/21.
 * 单点登陆过滤器
 */
public class SimpleAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    /**
     * 日志对象
     */
    private Logger log = LogManager.getLogger(getClass());
    /**
     * 单点登陆过滤url
     */
    public static final String SSO_AUTHENTICATION_FILTER_PROCESSES_URL = "/ssologin/index.html";

    /**
     * 如果参数传递渠道为空,则默认此渠道
     */
    public static final String DEFAULT_CHANNEL = "xyzd";

    /**
     * 创建新的实例
     */
    public SimpleAuthenticationFilter() {
        this(SSO_AUTHENTICATION_FILTER_PROCESSES_URL);
    }

    protected SimpleAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        log.info("进入SimpleAuthenticationFilter");
        //参数验证
        String params = request.getParameter("params");//入参
        String channel = request.getParameter("channel");//渠道
        log.info("入参params:" + params + ",渠道channel:" + channel);
        UsernamePasswordAuthenticationToken token = null;
        Authentication authentication=null;
        if (StringUtils.isEmpty(params)) {
            redirectRequest(request, response);
        }else {
            //判断渠道是否为空
            if (StringUtils.isEmpty(channel)) {
                channel = DEFAULT_CHANNEL;
            }
            token = new UsernamePasswordAuthenticationToken(params, channel);
            //设置其他认证信息
            setDetail(request, token);
            //进入provider处理链,进行用户信息详细认证
            authentication = this.getAuthenticationManager().authenticate(token);
            //验证之后的用户对象
            MobileUser mobiuser= (MobileUser) authentication.getPrincipal();
            //跳转到要单点的url
            String url = mobiuser.getUrl();
            if(!StringUtils.isEmpty(url)){
                log.info("将单点跳转地址存入session:"+url);
                redirectUrl(request,url);
            }
        }
        return authentication;
    }

    /**
     * 登录参数错误重定向请求
     *
     * @param request  请求对象
     * @param response 响应对象
     * @throws IOException
     */
    private void redirectRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        RedirectUrlBuilder builder = new RedirectUrlBuilder();
        builder.setScheme("http");
        builder.setServerName(request.getServerName());
        builder.setPort(request.getServerPort());
        builder.setContextPath(request.getContextPath());
        builder.setPathInfo("/login/error");
        log.info("request url:" + builder.getUrl());
        response.sendRedirect(builder.getUrl());
    }

    /**
     * 将要单点的地址存入session
     * @param request
     * @param url
     */
    private void redirectUrl(HttpServletRequest request, String url) {
        RedirectUrlBuilder builder = new RedirectUrlBuilder();
        builder.setScheme("http");
        builder.setServerName(request.getServerName());
        builder.setPort(request.getServerPort());
        builder.setContextPath(request.getContextPath());
        builder.setPathInfo(url);
        log.info("request url:" + builder.getUrl());
        //将需要跳转的url存入session,SpringSecurity验证成功后SuccessHandler从Session中取出url跳转
        request.getSession().setAttribute("returnUrl", builder.getUrl());
    }

    /**
     * 设置额外的认证信息,如ip,设备来源...等
     *
     * @param request
     * @param authRequest
     */
    private void setDetail(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

}
Provider:

/**
 * Created by leitao on 2016/12/21.
 * SSO认证.
 */
public class SimpleAuthenticationProvider implements AuthenticationProvider, InitializingBean, Ordered {

    /**
     * 日志对象
     */
    private Logger log = LogManager.getLogger(getClass());


    @Autowired
    private AuthenticationUserDetailsService<UsernamePasswordAuthenticationToken> simpleAuthenticationUserDetailsService;

    private int order = -1;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        log.info("进入SimpleAuthenticationProvider:"+authentication);
        if (!supports(authentication.getClass())){
            return null;
        }
        if (!(authentication.getPrincipal() instanceof String)){
            return  null;
        }
        //加载用户详细信息
        UserDetails userDetails = simpleAuthenticationUserDetailsService.loadUserDetails((UsernamePasswordAuthenticationToken) authentication);
        log.info("userDetails:"+userDetails);
        UsernamePasswordAuthenticationToken resultToken = new UsernamePasswordAuthenticationToken(userDetails,userDetails.getPassword(),userDetails.getAuthorities());
        resultToken.setDetails(authentication.getDetails());
        return resultToken;
    }

    /**
     * 判断传入的对象是否是UsernamePasswordAuthenticationToken,是就执行authenticate方法,不是就执行下一个Provider
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(simpleAuthenticationUserDetailsService,
                "An AuthenticationUserDetailsService must be set");
    }

    @Override
    public int getOrder() {
        return order;
    }
}
SimpleAuthenticationUserDetailsService:
/**
 * Created by leitao on 2016/12/21.
 * SSO用户信息认证
 */
@Service
public class SimpleAuthenticationUserDetailsService implements AuthenticationUserDetailsService<UsernamePasswordAuthenticationToken> {

    /**
     * 日志对象
     */
    Logger log = LogManager.getLogger(getClass());

    /**
     * SSO 请求认证
     */
    @Autowired
    private ISimpleAuthService simpleAuthService;

    /**
     * SSO 日志
     */
    @Autowired
    private ISsoLogService ssoLogService;

    @Override
    public UserDetails loadUserDetails(UsernamePasswordAuthenticationToken token) throws UsernameNotFoundException {
        log.info("进入SimpleAuthenticationUserDetailsService:"+token);
        MobileUser user = null;
        try {
            String channelAlias = token.getCredentials().toString();
            log.info("渠道别名channelAlias:"+channelAlias);
            //单点认证
            Map<String,String> map =simpleAuthService.verificationRequest(channelAlias);
            if (null ==map || map.size()==0){
                log.info("单点请求认证失败,可能没有此渠道");
                throw  new VerificationRequestErrorException();
            }
            String number = map.get("Number");//电话号码
            String acrType = map.get("NumberType");//号码类型
            String code = map.get("Citycode");//区号
            String channel_id = map.get("ChannelID");//渠道ID
            String url = map.get("Url");//单点访问地址
            log.info("单点认证出参电话号码Number:"+number+",号码类型NumberType:"+acrType+",区号Citycode:"+code+",单点访问地址:"+url);
            //通过登录号码从CRM查询用户注册信息,acrType 号码类型(9手机,固话1,宽带2)
            log.info("根据号码从CRM查询客户资料.");
            CustInfo custInfo = CrmTool.qryCustInfo(number, acrType, code);
            //如果没有查到相应的用户信息则抛此异常
            if (null == custInfo){
                log.info("没有找到此用户:"+number);
                throw new RegisterDataNotFoundException();
            }else {
                log.info("用户姓名:" + custInfo.getCustName());
                CustomerDetail customerDetail = new CustomerDetail();
                customerDetail.setCustInfo(custInfo);
                user = new MobileUser(number, token.getCredentials().toString(),
                        MobileUserAuthority.getAuthorities(customerDetail), customerDetail, channel_id,url);
                ssoLogService.saveSsoLog(number,acrType,code,channel_id,url,((WAPAuthenticationDetails) token.getDetails()).getRemoteAddress());
            }
        }catch (VerificationRequestErrorException e){
            throw new UsernameNotFoundException(e.getMessage(), e.getCause());
        }catch (RegisterDataNotFoundException e){
            throw new UsernameNotFoundException(e.getMessage(), e.getCause());
        }
        return user;
    }
}

ssoLogService
@Override
public Map<String, String> verificationRequest(String channelAlias) {
    Map<String,String> outputParamMap = null;
    String endpointAddress="";
    //根据渠道别名查询渠道信息
    List<Map<String, Object>> maps = ssoChannelInfoDao.selectSSOChannelInfoByChannelAlias(channelAlias);
    if (null!=maps && maps.size()>0){
        SSOChannelInfo ssoChannelInfo = convertMap2SSOChannelInfo(maps.get(0));
        if (null!=ssoChannelInfo){
            endpointAddress = ssoChannelInfo.getEndpointAddress();
            log.info("终端验证地址endpointAddress:"+endpointAddress);
            //输入参数
            String inputParam = ParseSsoParam.getInputParams(ssoChannelInfo);
            //调用单点验证接口输出参数
            String outputParam = HttpRequest.sendPost(endpointAddress,inputParam);
            log.info("调用单点验证接口输出参数:"+outputParam);
            outputParamMap=ParseSsoParam.getOutputParams(ssoChannelInfo,outputParam);
            log.info("调用单点验证接口输出Map解析参数"+outputParamMap);
        }
    }
    return outputParamMap;
}
WAPAuthenticationDetails
/**
 * Created by leitao on 2016/12/22.
 * WAP认证细节对象
 */
public class WAPAuthenticationDetails implements Serializable {

    private static final int HASH_CODE = 7654;
    private static final int SEVEN = 7;

    private String remoteAddress;
    private String sessionId;
    /**
     * 设备来源
     */
    private DeviceOrigin deviceOrigin;

    /**
     *
     * @return IP地址
     */
    public String getRemoteAddress() {
        return remoteAddress;
    }

    public void setRemoteAddress(String remoteAddress) {
        this.remoteAddress = remoteAddress;
    }

    public String getSessionId() {
        return sessionId;
    }

    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }

    public DeviceOrigin getDeviceOrigin() {
        return deviceOrigin;
    }

    public void setDeviceOrigin(DeviceOrigin deviceOrigin) {
        this.deviceOrigin = deviceOrigin;
    }

    public WAPAuthenticationDetails(){

    }
    /**
     * 创建新实例
     * @param request
     */
    public WAPAuthenticationDetails(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        this.sessionId = (session != null) ? session.getId() : null;
        obtainRemoteAddress(request);
        obtainDeviceOrigin(request);
    }

    private void obtainRemoteAddress(HttpServletRequest request) {
        remoteAddress = request.getHeader("x-forwarded-for");
        if (remoteAddress == null || remoteAddress.length() == 0 || "unknown".equalsIgnoreCase(remoteAddress)) {
            remoteAddress = request.getHeader("Proxy-Client-IP");
        }
        if (remoteAddress == null || remoteAddress.length() == 0 || "unknown".equalsIgnoreCase(remoteAddress)) {
            remoteAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (remoteAddress == null || remoteAddress.length() == 0 || "unknown".equalsIgnoreCase(remoteAddress)) {
            remoteAddress = IPGetTool.getIpAddr(request);
        }
    }

    /**
     * 获取设备来源.
     * 客户端设备识别:识别结果只有3种类型:NORMAL(非手机设备)、MOBILE(手机设备)、TABLET(平板电脑)。在系统里可以通过以下代码获取设备识别结果:
     * 网站偏好设置:Spring 通过设备识别的结果来设置当前网站是NORMAL还是MOBILE。
     * 最后 Spring Mobile会将信息同时放入cookie和request attribute里面。
     * 网站自动切换:可根据不同的访问设备切换到对应的页面
     * 此处需要在web.xml中配置DeviceResolverRequestFilter过滤器
     * @param request 请求对象
     */
    private void obtainDeviceOrigin(HttpServletRequest request) {
        Device currentDevice = DeviceUtils.getCurrentDevice(request);
        if (currentDevice.isMobile()) {
            this.deviceOrigin = DeviceOrigin.MOBILE;
        }
        if (currentDevice.isNormal()) {
            this.deviceOrigin = DeviceOrigin.NORMAL;
        }
        if (currentDevice.isTablet()) {
            this.deviceOrigin = DeviceOrigin.TABLET;
        }
    }

    /**
     * 比较对象的IP地址.
     *
     * @param rhs 被比较的对象
     * @return 布尔值
     */
    public boolean isEqualsRemoteAddress(WAPAuthenticationDetails rhs) {
        if ((remoteAddress == null) && (rhs.getRemoteAddress() != null)) {
            return false;
        }
        if ((remoteAddress != null) && (rhs.getRemoteAddress() == null)) {
            return false;
        }
        if (remoteAddress != null && !remoteAddress.equals(rhs.getRemoteAddress())) {
            return false;
        }
        return true;
    }

    /**
     * 比较对象的会话ID.
     *
     * @param rhs 被比较的对象
     * @return 布尔值
     */
    public boolean isEqualsSessionId(WAPAuthenticationDetails rhs) {
        if ((sessionId == null) && (rhs.getSessionId() != null)) {
            return false;
        }
        if ((sessionId != null) && (rhs.getSessionId() == null)) {
            return false;
        }
        if (sessionId != null && !sessionId.equals(rhs.getSessionId())) {
            return false;
        }
        return true;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof WAPAuthenticationDetails) {
            WAPAuthenticationDetails rhs = (WAPAuthenticationDetails) obj;
            if (isEqualsRemoteAddress(rhs) && isEqualsSessionId(rhs)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int hashCode() {
        int code = HASH_CODE;
        if (this.remoteAddress != null) {
            code = code * (this.remoteAddress.hashCode() % SEVEN);
        }
        if (this.sessionId != null) {
            code = code * (this.sessionId.hashCode() % SEVEN);
        }
        return code;
    }
}
WAPAuthenticationDetailsSource
/**
 * Created by leitao on 2016/12/22.
 * WAP 认证细节源对象
 */
public class WAPAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WAPAuthenticationDetails> {
    @Override
    public WAPAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new WAPAuthenticationDetails(context);
    }
}

登录成功后获取用户资料:

/**
 * Created by leitao on 2016/12/28.
 * Wap上下文工具
 */
public class WapContextUtils {

    /**
     * 获取WAP登录用户(也就是当前会话中的用户).
     * @return 用户信息
     */
    public static MobileUser getMobileUser() {
        MobileUser user = null;
        if (SecurityContextHolder.getContext().getAuthentication().getPrincipal() instanceof MobileUser) {
            user = (MobileUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        }
        return user;
    }
}
特别提醒用户bean一定要集成Spring Security提供的User类,这样Spring Security才会帮我们管理该对象;

切入点:

/**
 * Created by leitao on 2016/12/21.
 * WAP登录表单地址认证切入点
 */
public class WAPLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {

    /**
     * 创建新的实例
     * @param loginFormUrl 登录表单地址
     */
    public WAPLoginUrlAuthenticationEntryPoint(String loginFormUrl){
        super(loginFormUrl);
    }
}
单点登陆成功后:

/**
 * Created by leitao on 2016/12/21.
 * SSO WAP认证成功处理器
 */
public class WAPAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    /**
     * 日志对象
     */
    Logger log  = LogManager.getLogger(getClass());

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("进入WAP认证成功处理器");
        RedirectUrlBuilder builder = new RedirectUrlBuilder();
        builder.setScheme("http");
        builder.setServerName(request.getServerName());
        builder.setPort(request.getServerPort());
        builder.setContextPath(request.getContextPath());
        String returnUrl = builder.getUrl();
        Object object = request.getSession().getAttribute("returnUrl");
        if (!StringUtils.isEmpty(object)){
            returnUrl = object.toString();
            request.getSession().removeAttribute("returnUrl");
        }
        if (!StringUtils.isEmpty(returnUrl)){
            log.info("认证成功后跳转地址returnUrl:"+returnUrl);
            response.sendRedirect(returnUrl);
            return;
        }else {
            request.getRequestDispatcher("/").forward(request,response);
        }
    }
}
跳转到相应的地址;

此时Spring Security会把SPRING_SECURITY_CONTEXT 存入Session中,也可以通过上面我提供的方法的从上下文中获取用户资料




版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/admin1973/article/details/53908751

智能推荐

python自动化办公之 第1章 安装部署国产化系统和wps软件并python安装_随行之旅的博客-程序员资料

使用国产化平台和软件(这里略过)统信UOS操作系统 wps国产化软件安装python内容安装好统信操作系统后,安装python编译器命令如下: sudo apt-get install python(如安装则不用安装) sudo apt-get install python3(如安装则不用安装)注意:统信系统必须开启开发者模式来进行安装。安装好python软件后查看python版本号注意:在统信操作系统中有2个版本:1) 2.7.16 2) 3.7....

Video Station搜刮器thetvdb注册以及一键修改host_dnpao的博客-程序员资料_tvdb

1、API注册https://www.themoviedb.org/,打开改中文语言,然后注册—验证邮箱—登录2、API申请 应用简介,样板:Data is at the heart of every industry’s transformation, and this is where Synology has a profoundly important role to play. At its very core, our mission is to manage a.

JS--3d轮播图_玩会手机就睡觉的博客-程序员资料_3d轮播图js实现

前言这个是我要介绍的第三种轮播图—3d轮播图,这种轮播图在一些app里面会用得比较多,视觉效果很不错!!代码&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;title&gt;网易云音乐轮播图实现&lt;/title&gt; &lt;link rel="stylesheet" href="./css/iconfont.css"&gt; &lt;link r

解决:ImportError: No module named '_tkinter', please install the python3-tk package_躺鸡小能手的博客-程序员资料

调用 matplotlib 模块时,出现:ImportError: No module named '_tkinter', please install the python3-tk package意思是没有该模块,找了一些博客,大多都是建议直接pip3 install python3-tk,在pycharm 终端 pip3 install python3-tk,出现以下问题:解决办法...

MonkeyRunner的使用_DaiLM的博客-程序员资料

要使用MonkeyRunner,就要学习使用Python,哎先抄一段官方doc里的代码作用是启动一个程序(应该是启动程序默认的Activity),然后按MENU键,并截屏[code=&quot;python&quot;]# Imports the monkeyrunner modules used by this programfrom com.android.monkeyrunner imp...

WPS 配置MathType_wps mathtype_all~的博客-程序员资料

注:先找到 自己安装的 MathType 和 WPS 的安装目录最简单的方式就找到快捷方式查看 目标。以我为例我的安装软件是32位1、进入 MathType 目录在 MathPage 和 Office Support目录下找到对应 位数的文件:MathPage.wll 和 MathType Commands 2013.dotm  尤其是在找 MathType Commands 2013.dotm 文件要注意文件格式,因为在该目录下由于其同名文件2、配置wps  将步骤一中提取

随便推点

Run a Command as Administrator_gfire2008的博客-程序员资料

Run a Command as Administrator from the Windows 7 / Vista Run box<br />If you are a command line junkie like me, and have been testing out Windows 7 or Vista… one of the first things you’ll notice is that there is no way to run a command from the run box i

HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别_光着脚丫数星星的博客-程序员资料_hashset和hashmap

①HashMap的工作原理HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会

umijs有什么好处_UMI.js使用方法_蒋二笑的博客-程序员资料

umi.js使用方法node环境安装在官网下载与系统相应的node版本,node.js版本&gt;=8.10编辑器推荐使用Visual Studio Code 安装方法安装uminpm install -g umi推荐使用 yarn 代替 npm 来安装 umi , yarn 会针对部分场景做一些缓存以节省时间,你可以输入以下命令来全局安装 yarn,使用yarn后项目中尽量避免再使用npm,不然...

linux上wps能云同步吗,Linux版WPS管理云端文档_weixin_39895481的博客-程序员资料

WPS for Linux的主界面的右上角,有3个非常、非常、非常小的图标,这里藏有玄机!1在ElementaryOS上使用WPS for Linux的时间不长,但是体验还是相当不错的。客户端本身体积比较小,功能也很齐全,而且即使是免费账户也默认拥有1个GB的云端文档存储空间。这个1个GB的云端文档存储空间,相当于是一块文档同步网盘,它可以即时地将对云端文档的修改保存在云端,并同步到其它平台的客户..._1671465600

oracle rac报错:PRVF-9652 : Cluster Time Synchronization Services check failed_东城绝神的博客-程序员资料

报错如下:/u01/app/11.2.0/grid/bin/clscfg.bin: error while loading shared libraries: libcap.so.1: cannot open shared object file: No such file or directory[[email protected] ~]# /u01/app/oraInventory

linux top 命令可视化_Shell 命令执行可视化和告警工具_风车下站立少年的博客-程序员资料

Sampler是一个用于shell命令执行,可视化和告警的工具。其配置使用的是一个简单的YAML文件。1、为什么我需要它?你可以直接从终端对任意动态进程进行采样 – 观察数据库中的更改,监控MQ动态消息(in-flight messages),触发部署脚本并在完成后获取通知。如果有一种方法可以使用shell命令获取指标(metric),那么可以使用Sampler立即对其进行可视化。2、安装macO...

推荐文章

热门文章

相关标签