zoukankan      html  css  js  c++  java
  • sm整合shiro权限控制

    CasUser:

    /**
     * Copyright (c) 2020, All Rights Reserved.
     *
    */
    
    package com.micropattern.urp.domain.entity.cas;
    
    import java.util.HashSet;
    import java.util.Set;
    
    import javax.persistence.Entity;
    import javax.persistence.JoinColumn;
    import javax.persistence.JoinTable;
    import javax.persistence.ManyToMany;
    import javax.persistence.Table;
    import javax.persistence.Transient;
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import com.micropattern.urp.domain.entity.base.BaseIdAndTime;
    import com.micropattern.urp.domain.entity.role.Role;
    
    /**
     * cas集成用户<br/>
     *
     * @author zuo
     * @Date 2020年4月20日 上午11:11:05
     * @since 1.0.0
     *  
     */
    @SuppressWarnings("serial")
    @Table(name = "t_cas_user")
    @Entity
    public class CasUser extends BaseIdAndTime{
        
        private String userName;
        private String role;
        @Transient
        private String searchKey;
        private String salt;
        private String password;
        private String remark;
        
        /**
         * 用户角色关系表
         */
        @JsonIgnore
        @ManyToMany
        @JoinTable(name="t_cas_user_role", joinColumns={@JoinColumn(name="user_id",referencedColumnName="id")},
                   inverseJoinColumns={@JoinColumn(name="role_id",referencedColumnName="id")})
        private Set<Role> roles = new HashSet<>();
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getRole() {
            return role;
        }
    
        public void setRole(String role) {
            this.role = role;
        }
    
        public String getSearchKey() {
            return searchKey;
        }
    
        public void setSearchKey(String searchKey) {
            this.searchKey = searchKey;
        }
    
        public Set<Role> getRoles() {
            return roles;
        }
    
        public void setRoles(Set<Role> roles) {
            this.roles = roles;
        }
    
        public String getSalt() {
            return salt;
        }
    
        public void setSalt(String salt) {
            this.salt = salt;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getRemark() {
            return remark;
        }
    
        public void setRemark(String remark) {
            this.remark = remark;
        }
        
    }
    View Code

    Role:

    /**
     * Copyright (c) 2020, All Rights Reserved.
     *
    */
    
    package com.micropattern.urp.domain.entity.role;
    
    import java.util.HashSet;
    import java.util.Set;
    
    import javax.persistence.Entity;
    import javax.persistence.JoinColumn;
    import javax.persistence.JoinTable;
    import javax.persistence.ManyToMany;
    import javax.persistence.Table;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import com.micropattern.urp.domain.entity.base.BaseIdAndTime;
    import com.micropattern.urp.domain.entity.cas.CasUser;
    import com.micropattern.urp.domain.entity.permission.Permission;
    
    /**
     * 此处应有类说明<br/>
     *
     * @author why
     * @Date 2020年4月13日 下午3:36:58
     * @since 1.0.0
     *  
     */
    @SuppressWarnings("serial")
    @Table(name = "t_cas_role")
    @Entity
    public class Role extends BaseIdAndTime{
        /**
         * 角色名
         */
        private String name;
        
        private Boolean delFlag;
        
        private String remark;
        
        /**
         * 角色,用户,多对多
         */
        @JsonIgnore
        @ManyToMany(mappedBy="roles")
        private Set<CasUser> users=new HashSet<>();
        
        @JsonIgnore
        @ManyToMany
        @JoinTable(name="t_cas_role_permission", joinColumns={@JoinColumn(name="role_id",referencedColumnName="id")},
                   inverseJoinColumns={@JoinColumn(name="permission_id",referencedColumnName="id")})
        private Set<Permission> permissions = new HashSet<>();
    
        public Set<Permission> getPermissions() {
            return permissions;
        }
    
        public void setPermissions(Set<Permission> permissions) {
            this.permissions = permissions;
        }
    
        public Boolean getDelFlag() {
            return delFlag;
        }
    
        public void setDelFlag(Boolean delFlag) {
            this.delFlag = delFlag;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Set<CasUser> getUsers() {
            return users;
        }
    
        public void setUsers(Set<CasUser> users) {
            this.users = users;
        }
    
        public String getRemark() {
            return remark;
        }
    
        public void setRemark(String remark) {
            this.remark = remark;
        }
        
    }
    View Code

    Permission:

    /**
     * Copyright (c) 2020, All Rights Reserved.
     *
    */
    
    package com.micropattern.urp.domain.entity.permission;
    
    import javax.persistence.Entity;
    import javax.persistence.Table;
    import javax.persistence.Transient;
    
    import com.micropattern.urp.domain.entity.base.BaseIdAndTime;
    
    /**
     * 此处应有类说明<br/>
     *
     * @author why
     * @Date 2020年4月13日 下午3:50:53
     * @since 1.0.0
     *  
     */
    @SuppressWarnings("serial")
    @Table(name = "t_cas_permission")
    @Entity
    public class Permission extends BaseIdAndTime{
        
        /**
         * 权限名称
         */
        private String name;
        /**
         * 权限类型
         */
        private Integer type;
        /**
         * 资源路径
         */
        private String url;
        
        private Boolean delFlag;
        
        /**
         * 是否勾选
         * true : 勾选
         * false : 未勾选
         */
        @Transient
        private Boolean checked;
        
        /**
         * 排序
         */
        private Integer sort;
        
        /**
         * 菜单等级
         * 1:父节点
         * 2:子节点
         */
        private Integer level;
        
        /**
         * 父id
         */
        private String parentId;
        
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Integer getType() {
            return type;
        }
        public void setType(Integer type) {
            this.type = type;
        }
        public String getUrl() {
            return url;
        }
        public void setUrl(String url) {
            this.url = url;
        }
        public Boolean getDelFlag() {
            return delFlag;
        }
        public void setDelFlag(Boolean delFlag) {
            this.delFlag = delFlag;
        }
        public Boolean getChecked() {
            return checked;
        }
        public void setChecked(Boolean checked) {
            this.checked = checked;
        }
        public Integer getSort() {
            return sort;
        }
        public void setSort(Integer sort) {
            this.sort = sort;
        }
        public Integer getLevel() {
            return level;
        }
        public void setLevel(Integer level) {
            this.level = level;
        }
        public String getParentId() {
            return parentId;
        }
        public void setParentId(String parentId) {
            this.parentId = parentId;
        }
       
    }
    View Code

    建表sql:

    CREATE TABLE `t_cas_user` (
      `id` varchar(255) NOT NULL COMMENT '用户id',
      `password` varchar(255) DEFAULT NULL COMMENT '密码',
      `user_name` varchar(64) DEFAULT NULL COMMENT '用户名',
      `role` varchar(64) DEFAULT NULL COMMENT '角色',
      `salt` varchar(64) DEFAULT NULL COMMENT '密码盐',
      `create_time` datetime DEFAULT NULL COMMENT '创建时间',
      `create_user` varchar(64) DEFAULT NULL COMMENT '创建用户',
      `update_time` datetime DEFAULT NULL COMMENT '更新时间',
      `update_user` varchar(64) DEFAULT NULL COMMENT '更新用户',
      `version` int(11) DEFAULT '0',
      `remark` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `t_cas_user_role` (
      `user_id` varchar(255) NOT NULL,
      `role_id` varchar(255) NOT NULL,
      PRIMARY KEY (`user_id`,`role_id`),
      KEY `FK_h6p8coxk4oxl6txq1hd7jim82` (`role_id`),
      CONSTRAINT `FK_h6p8coxk4oxl6txq1hd7jim82` FOREIGN KEY (`role_id`) REFERENCES `t_cas_role` (`id`),
      CONSTRAINT `FK_oajhcq0g0st27cocwugnq47mf` FOREIGN KEY (`user_id`) REFERENCES `t_cas_user` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    
    CREATE TABLE `t_cas_role` (
      `id` varchar(255) NOT NULL,
      `name` varchar(64) DEFAULT NULL COMMENT '权限名称',
      `create_time` datetime DEFAULT NULL COMMENT '创建时间',
      `create_user` varchar(255) DEFAULT NULL COMMENT '创建用户',
      `update_time` datetime DEFAULT NULL COMMENT '更新时间',
      `update_user` varchar(255) DEFAULT NULL COMMENT '更新用户',
      `version` int(11) DEFAULT '0' COMMENT '版本号',
      `del_flag` bit(1) DEFAULT NULL COMMENT '是否删除,0:未删除',
      `remark` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `name` (`name`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';
    
    
    CREATE TABLE `t_cas_role_permission` (
      `role_id` varchar(255) NOT NULL,
      `permission_id` varchar(255) NOT NULL,
      PRIMARY KEY (`role_id`,`permission_id`),
      KEY `FK_ssde3tsrhwnl5s15n0cys16p0` (`permission_id`),
      CONSTRAINT `FK_13au68ku0hwd8b01mtpdp49mo` FOREIGN KEY (`role_id`) REFERENCES `t_cas_role` (`id`),
      CONSTRAINT `FK_ssde3tsrhwnl5s15n0cys16p0` FOREIGN KEY (`permission_id`) REFERENCES `t_cas_permission` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    
    CREATE TABLE `t_cas_permission` (
      `id` varchar(255) NOT NULL COMMENT '主键',
      `name` varchar(100) DEFAULT NULL COMMENT '权限名称',
      `type` int(11) DEFAULT NULL COMMENT '权限类型,1:菜单 2:按钮 3:API',
      `url` varchar(200) DEFAULT NULL COMMENT '资源路径',
      `create_time` datetime DEFAULT NULL COMMENT '创建时间',
      `create_user` varchar(255) DEFAULT NULL COMMENT '创建用户',
      `update_time` datetime DEFAULT NULL COMMENT '更新时间',
      `update_user` varchar(255) DEFAULT NULL COMMENT '更新用户',
      `version` int(11) DEFAULT '0' COMMENT '版本号',
      `del_flag` bit(1) DEFAULT NULL COMMENT '是否删除,0:未删除',
      `sort` int(11) DEFAULT '0' COMMENT '排序',
      `parent_id` varchar(64) DEFAULT NULL COMMENT '父id',
      `level` int(11) DEFAULT NULL COMMENT '权限等级,1:父节点,2:子节点',
      PRIMARY KEY (`id`),
      UNIQUE KEY `name` (`name`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='资源表';
    View Code

    spring-shiro.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"  
        xsi:schemaLocation="http://www.springframework.org/schema/beans   
                            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" 
        default-lazy-init="true">
      
        <description>Shiro Configuration</description>  
      
        <!-- 安全管理器 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="userRealm" />
            <property name="cacheManager" ref="cacheManager" />
            <!--设置会话管理器-->
            <!-- <property name="sessionManager" ref="sessionManager"></property> -->
        </bean>  
      
        <!-- 項目自定义的Realm -->  
        <bean id="userRealm" class="com.micropattern.urp.common.shiro.UserRealm">  
            <property name="cacheManager" ref="cacheManager" />
            <!-- <property name="credentialsMatcher" ref="credentialsMatcher"></property> -->
            <property name="credentialsMatcher" ref="customCredentialsMatcher"></property>
        </bean>  
      
        <!-- Shiro Filter -->  
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
            <property name="securityManager" ref="securityManager" />  
            <!-- 用户登录地址 -->
            <property name="loginUrl" value="/login"/>  
            <!-- 登录成功 -->
            <property name="successUrl" value="/login"/>  
            <!-- 未授权的失败页 -->
            <property name="unauthorizedUrl" value="/error"/>
            <property name="filterChainDefinitions">
                <value>  
                     <!-- 设置访问用户list页面需要授权操作 -->
                    /** = anon
                    /manage = anon
                    
                </value>  
            </property>  
        </bean>  
        
        <!--配置会话管理器-->
        <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
            <!--设置session的超时时间20min-->
            <property name="globalSessionTimeout" value="1200000"></property>
            <!--删除失效session-->
            <property name="deleteInvalidSessions" value="true"></property>
        </bean>
      
       <!-- 给予shior的内存缓存系统 -->
        <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>  
      
        <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->  
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />  
      
        <!-- <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
            <property name="hashAlgorithmName" value="SHA-512"></property>
            <property name="hashIterations" value="2"></property>
        </bean> -->
        
        <bean id="customCredentialsMatcher" class="com.micropattern.urp.common.shiro.CustomCredentialsMatcher"/>
    
        
    </beans>
    View Code

    UserRealm:

    /**
     * Copyright (c) 2020, All Rights Reserved.
     *
     */
    
    package com.micropattern.urp.common.shiro;
    
    import java.util.HashSet;
    import java.util.Set;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authc.credential.CredentialsMatcher;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    import org.springframework.beans.factory.annotation.Autowired;
    import com.micropattern.urp.common.constant.ErrorCode.SystemUserError;
    import com.micropattern.urp.common.enums.SystemUserStatus;
    import com.micropattern.urp.common.exception.BusinessException;
    import com.micropattern.urp.domain.entity.permission.Permission;
    import com.micropattern.urp.domain.entity.role.Role;
    import com.micropattern.urp.domain.entity.system.User;
    import com.micropattern.urp.domain.service.system.UserService;
    
    /**
     * shiro管理<br/>
     *
     * @author zuo
     * @Date 2020年4月13日 下午2:10:29
     * @since 1.0.0
     * 
     */
    public class UserRealm extends AuthorizingRealm {
    
        @Autowired
        private UserService userService;
    
        //授权认证
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            System.out.println("=============执行授权逻辑================");
            Set<String> set = new HashSet<>();// 权限集合
            // 给资源进行授权
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            User tokenUser = (User) principals.getPrimaryPrincipal();
            User user = userService.findByUserName(tokenUser.getUserName());
            Set<Role> roles = user.getRoles();
            for (Role role : roles) {
                Set<Permission> permissions = role.getPermissions();
                for (Permission permission : permissions) {
                    set.add(permission.getUrl());
                }
            }
            simpleAuthorizationInfo.addStringPermissions(set);
            return simpleAuthorizationInfo;
        }
    
        //登录认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            System.out.println("=============执行认证逻辑================");
            UsernamePasswordToken tokens = (UsernamePasswordToken) token;
            User user = userService.findByUserName(tokens.getUsername());
            if (user == null) {
                throw new BusinessException(SystemUserError.USER_NAME_NOTEXISTS);
            }
            if (user.getDelFlag()) {
                throw new BusinessException(SystemUserError.USER_NAME_NOTEXISTS);
            } else if (SystemUserStatus.ENABLE != user.getStatus()) {
                throw new BusinessException(SystemUserError.LOGIN_FORBID);
            }
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(),
                    ByteSource.Util.bytes(user.getSalt()), getName());
            return simpleAuthenticationInfo;
        }
    
        //自定义密码校验
        @Override
        public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
            super.setCredentialsMatcher(new CustomCredentialsMatcher());
        }
    
        //清空权限缓存 
        public void clearCachedAuthorization(){
            clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
        }
        
    }
    View Code

    shiro清空缓存:

    /**
     * Copyright (c) 2020, All Rights Reserved.
     *
    */
    
    package com.micropattern.urp.common.shiro;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.mgt.RealmSecurityManager;
    
    /**
     * shiro清除缓存<br/>
     *
     * @author zuo
     * @Date 2020年4月17日 上午9:25:50
     * @since 1.0.0
     *  
     */
    public class ShiroUtils {
        
        public static void clearShiroCache(){
            RealmSecurityManager rsm = (RealmSecurityManager)SecurityUtils.getSecurityManager();
            UserRealm realm = (UserRealm)rsm.getRealms().iterator().next();
            realm.clearCachedAuthorization();
        }
        
    }
    View Code

    自定义加密类:

    /**
     * Copyright (c) 2018, All Rights Reserved.
     * 
     */
    
    package com.micropattern.urp.common.utils;
    
    import java.security.MessageDigest;
    
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    
    import org.apache.commons.lang3.RandomStringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * 签名加密工具类<br/>
     * Date: 2018年1月29日 下午2:15:59 <br/>
     * 
     * @author xin.zhou
     * @version
     * @since JDK 1.7
     * @see
     */
    public class SignUtils {
        private static final Logger LOG = LoggerFactory.getLogger(SignUtils.class);
        private static final String DEFAULT_CHARSET = "UTF-8";
        private static final char[] DIGITS;
    
        public static String hmacSha256(String key, String data) {
            try {
                Mac mac = Mac.getInstance("HmacSHA256");
                SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), mac.getAlgorithm());
                mac.init(signingKey);
                return encodeHex(mac.doFinal(data.getBytes()));
            } catch (Exception e) {
                LOG.error("execute hmacSHA256 error", e);
            }
    
            return null;
        }
    
        private static String encrypt(String algorithm, String data, String charset) {
            try {
                byte[] msg = data.getBytes(charset);
                MessageDigest md = MessageDigest.getInstance(algorithm);
                return encodeHex(md.digest(msg));
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
            }
            return null;
        }
    
        public static String md5(String data, String charset) {
            return encrypt("MD5", data, charset);
        }
    
        public static String sha1(String data, String charset) {
            return encrypt("SHA1", data, charset);
        }
    
        public static String sha1(String data) {
            return sha1(data, DEFAULT_CHARSET);
        }
    
        public static String sha256(String data, String charset) {
            return encrypt("SHA-256", data, charset);
        }
    
        public static String sha256(String data) {
            return sha256(data, DEFAULT_CHARSET);
        }
    
        public static String sha512(String data, String charset) {
            return encrypt("SHA-512", data, charset);
        }
    
        public static String sha512(String data) {
            return sha512(data, DEFAULT_CHARSET);
        }
    
        private static String encodeHex(byte[] data) {
            int l = data.length;
            char[] out = new char[l << 1];
            int i = 0;
    
            for (int j = 0; i < l; ++i) {
                out[j++] = DIGITS[(240 & data[i]) >>> 4];
                out[j++] = DIGITS[15 & data[i]];
            }
    
            return new String(out);
        }
    
        static {
            DIGITS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
        }
        
        public static void main(String[] args) {
            //UPJW1u
            //1d0c7526881640dfb2a7018c9e2328a3b520b6236bf53378b42059578cc603ffa81c159384119c5bcf280d16503abe32d747104e7c409d53c90ced1e7136fe10
            String salt=RandomStringUtils.randomAlphanumeric(6);
            System.out.println(salt+" "+sha512("123456"+salt));
        }
    }
    View Code

    shiro加密校验:

    /**
     * Copyright (c) 2020, All Rights Reserved.
     *
    */
    
    package com.micropattern.urp.common.shiro;
    
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
    import org.apache.shiro.subject.PrincipalCollection;
    import com.micropattern.urp.common.utils.SignUtils;
    import com.micropattern.urp.domain.entity.system.User;
    
    /**
     * 自定义shiro密码校验<br/>
     *
     * @author zuo
     * @Date 2020年4月14日 下午5:30:44
     * @since 1.0.0
     *  
     */
    public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
        
        @Override
        public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
            UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
            PrincipalCollection principals = info.getPrincipals();
            User user = (User) principals.getPrimaryPrincipal();
            String salt = user.getSalt();
            String password = String.valueOf(token.getPassword());
            Object tokenCredentials = encrypt(password, salt);
            Object accountCredentials = getCredentials(info);
            //将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false
            return equals(tokenCredentials, accountCredentials);
        }
    
        //密码加密方法
        private String encrypt(String password, String salt) {
            return SignUtils.sha512(password + salt);
        }
    
        
    }
    View Code

    页面按钮控制:

    <@shiro.hasPermission name="sysuser:add">
    <button class="btn btn-primary btn-xs" id="addBtn"><i class="ace-icon glyphicon glyphicon-plus"></i>新增</button>
    </@shiro.hasPermission>

  • 相关阅读:
    20140630 科技脉搏-互联网精神之“我不是为了输赢,我就是认真”
    iOS 获取本地视频的缩略图
    iOS App与iTunes文件传输的方法和对iOS App文件结构的说明
    罗振宇自媒体品牌“罗辑思维”估值1亿背后:媒体通往社群之路
    20140622 科技脉搏 -互联网思维之“一群人团结起来占其他人便宜”
    20140616 科技脉搏 -最大颠覆来自创业公司与边缘产业
    关于流媒体(m3u8)的下载与播放
    20140608 科技脉搏 -下半身需求是人类共同需求,有多少人就有多大市场
    IOS遍历未知对象属性、函数
    iOS中使用 Reachability 检测网络
  • 原文地址:https://www.cnblogs.com/chong-zuo3322/p/12777786.html
Copyright © 2011-2022 走看看