• shiro 框架使用实例


    博文目录

    1. 权限的简单描述
    2. 实例表结构及内容及POJO
    3. Shiro-pom.xml
    4. Shiro-web.xml
    5. Shiro-MyShiro-权限认证,登录认证层
    6. Shiro-applicationContext-shiro.xml
    7. HomeController
    8. 三个JSP文件

    什么是权限呢?举个简单的例子:

    我有一个论坛,注册的用户分为normal用户,manager用户。
    对论坛的帖子的操作有这些:
    添加,删除,更新,查看,回复
    我们规定:
    normal用户只能:添加,查看,回复
    manager用户可以:删除,更新

    normal,manager对应的是角色(role)
    添加,删除,更新等对应的是权限(permission)

    我们采用下面的逻辑创建权限表结构(不是绝对的,根据需要修改)

    一个用户可以有多种角色(normal,manager,admin等等)
    一个角色可以有多个用户(user1,user2,user3等等)
    一个角色可以有多个权限(save,update,delete,query等等)
    一个权限只属于一个角色(delete只属于manager角色)

    我们创建四张表:
    t_user用户表:设置了3个用户

    -------------------------------

    id + username+ password

    —±---------------±---------

    1+tom +000000

    2+jack +000000

    3+rose +000000

    ---------------------------------
    t_role角色表:设置3个角色
    --------------

    id + rolename

    —±---------

    1+ admin

    2+ manager

    3+ normal

    --------------

    t_user_role用户角色表:tom是admin和normal角色,jack是manager和normal角色,rose是normal角色
    ---------------------

    user_id+role_id

    -----------±----------

    1 + 1

    1 + 3

    2 + 2

    2 + 3

    3 + 3

    ---------------------

    t_permission权限表:admin角色可以删除,manager角色可以添加和更新,normal角色可以查看

    -----------------------------------

    id+permissionname+role_id

    ----±-----------------------±----------

    1+add + 2

    2+del + 1

    3+update + 2

    4+query + 3

    -----------------------------------

    建立对应的POJO:

    Java代码 [这里是图片002]

    1.  package com.cn.pojo;  
         
       import java.util.HashSet;  
       import java.util.List;  
       import java.util.Set;  
         
       import javax.persistence.Entity;  
       import javax.persistence.GeneratedValue;  
       import javax.persistence.GenerationType;  
       import javax.persistence.Id;  
       import javax.persistence.JoinColumn;  
       import javax.persistence.JoinTable;  
       import javax.persistence.ManyToMany;  
       import javax.persistence.Table;  
       import javax.persistence.Transient;  
         
       import org.hibernate.validator.constraints.NotEmpty;  
         
       @Entity  
       @Table(name="t_user")  
       public class User {  
         
           private Integer id;  
           @NotEmpty(message="用户名不能为空")  
           private String username;  
           @NotEmpty(message="密码不能为空")  
           private String password;  
           private List roleList;//一个用户具有多个角色  
             
           @Id  
           @GeneratedValue(strategy=GenerationType.IDENTITY)  
           public Integer getId() {  
               return id;  
           }  
           public void setId(Integer id) {  
               this.id = id;  
           }  
           public String getUsername() {  
               return username;  
           }  
           public void setUsername(String username) {  
               this.username = username;  
           }  
           public String getPassword() {  
               return password;  
           }  
           public void setPassword(String password) {  
               this.password = password;  
           }  
           @ManyToMany  
           @JoinTable(name="t_user_role",joinColumns={@JoinColumn(name="user_id")},inverseJoinColumns={@JoinColumn(name="role_id")})  
           public List getRoleList() {  
               return roleList;  
           }  
           public void setRoleList(List roleList) {  
               this.roleList = roleList;  
           }  
             
           @Transient  
           public Set getRolesName(){  
               List roles=getRoleList();  
               Set set=new HashSet();  
               for (Role role : roles) {  
                   set.add(role.getRolename());  
               }  
               return set;  
           }  
             
       }  
        
       Java代码  收藏代码
       package com.cn.pojo;  
         
       import java.util.ArrayList;  
       import java.util.List;  
         
       import javax.persistence.Entity;  
       import javax.persistence.GeneratedValue;  
       import javax.persistence.GenerationType;  
       import javax.persistence.Id;  
       import javax.persistence.JoinColumn;  
       import javax.persistence.JoinTable;  
       import javax.persistence.ManyToMany;  
       import javax.persistence.OneToMany;  
       import javax.persistence.Table;  
       import javax.persistence.Transient;  
         
       @Entity  
       @Table(name="t_role")  
       public class Role {  
         
           private Integer id;  
           private String rolename;  
           private List permissionList;//一个角色对应多个权限  
           private List userList;//一个角色对应多个用户  
             
           @Id  
           @GeneratedValue(strategy=GenerationType.IDENTITY)  
           public Integer getId() {  
               return id;  
           }  
           public void setId(Integer id) {  
               this.id = id;  
           }  
           public String getRolename() {  
               return rolename;  
           }  
           public void setRolename(String rolename) {  
               this.rolename = rolename;  
           }  
           @OneToMany(mappedBy="role")  
           public List getPermissionList() {  
               return permissionList;  
           }  
           public void setPermissionList(List permissionList) {  
               this.permissionList = permissionList;  
           }  
           @ManyToMany  
           @JoinTable(name="t_user_role",joinColumns={@JoinColumn(name="role_id")},inverseJoinColumns={@JoinColumn(name="user_id")})  
           public List getUserList() {  
               return userList;  
           }  
           public void setUserList(List userList) {  
               this.userList = userList;  
           }  
             
           @Transient  
           public List getPermissionsName(){  
               List list=new ArrayList();  
               List perlist=getPermissionList();  
               for (Permission per : perlist) {  
                   list.add(per.getPermissionname());  
               }  
               return list;  
           }  
       }  
        
       Java代码  收藏代码
       package com.cn.pojo;  
         
       import javax.persistence.Entity;  
       import javax.persistence.GeneratedValue;  
       import javax.persistence.GenerationType;  
       import javax.persistence.Id;  
       import javax.persistence.JoinColumn;  
       import javax.persistence.ManyToOne;  
       import javax.persistence.Table;  
         
       @Entity  
       @Table(name="t_permission")  
       public class Permission {  
         
           private Integer id;  
           private String permissionname;  
           private Role role;//一个权限对应一个角色  
             
           @Id  
           @GeneratedValue(strategy=GenerationType.IDENTITY)  
           public Integer getId() {  
               return id;  
           }  
           public void setId(Integer id) {  
               this.id = id;  
           }  
           public String getPermissionname() {  
               return permissionname;  
           }  
           public void setPermissionname(String permissionname) {  
               this.permissionname = permissionname;  
           }  
           @ManyToOne  
           @JoinColumn(name="role_id")  
           public Role getRole() {  
               return role;  
           }  
           public void setRole(Role role) {  
               this.role = role;  
           }  
             
       }  
        
        使用SHIRO的步骤:
       1,导入jar
       2,配置web.xml
       3,建立dbRelm
       4,在Spring中配置
       
       pom.xml中配置如下:
       Xml代码  收藏代码
         
         4.0.0  
         com.hyx  
         springmvc  
         war  
         0.0.1-SNAPSHOT  
         springmvc Maven Webapp  
         http://maven.apache.org  
           
             
             junit  
             junit  
             3.8.1  
             test  
             
             
             
               org.springframework  
               spring-webmvc  
               3.2.4.RELEASE  
             
             
             
               org.springframework  
               spring-jdbc  
               3.2.4.RELEASE  
             
             
               org.springframework  
               spring-orm  
               3.2.4.RELEASE  
             
             
             
             
               org.hibernate  
               hibernate-core  
               4.2.5.Final  
             
             
               org.hibernate  
               hibernate-ehcache  
               4.2.5.Final  
             
             
               net.sf.ehcache  
               ehcache  
               2.7.2  
             
             
               commons-dbcp  
               commons-dbcp  
               1.4  
             
             
               mysql  
               mysql-connector-java  
               5.1.26  
             
             
             
               javax.inject  
               javax.inject  
               1  
                     
             
             
             
             
               org.hibernate  
               hibernate-validator  
               5.0.1.Final  
             
             
             
               org.codehaus.jackson  
               jackson-mapper-asl  
               1.9.13  
                     
             
             
               javax.servlet  
               jstl  
               1.2  
             
             
             
             javax.servlet  
             servlet-api  
             2.5  
             
             
               
               
             org.apache.shiro    
             shiro-core    
             1.2.2    
               
               
             org.apache.shiro    
             shiro-web    
             1.2.2    
               
               
             org.apache.shiro    
             shiro-spring    
             1.2.2    
              
           
           
           
           springmvc  
             
             
                 
                 org.mortbay.jetty  
                 jetty-maven-plugin  
                   
                   10  
                     
                     /  
                     
                     
                     
                        
                         80  
                         60000  
                        
                     
                   
                 
             
           
         
        
        web.xml中的配置:
       Xml代码  收藏代码
         
         
         Archetype Created Web Application  
           
           
           
           opensessioninview  
           org.springframework.orm.hibernate4.support.OpenSessionInViewFilter  
           
           
           opensessioninview  
           /*  
           
           
           
           
           springmvc  
           org.springframework.web.servlet.DispatcherServlet  
           1  
           
           
           springmvc  
             
           /  
           
           
           
           
           contextConfigLocation  
           classpath:applicationContext*.xml  
           
           
             
               org.springframework.web.context.ContextLoaderListener  
             
           
           
             
             
           shiroFilter    
           org.springframework.web.filter.DelegatingFilterProxy    
             
             
           shiroFilter    
           /*    
           
           
         
        
        
       Java代码  收藏代码
       package com.cn.service;  
         
       import java.util.List;  
         
       import javax.inject.Inject;  
         
       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.authz.AuthorizationInfo;  
       import org.apache.shiro.authz.SimpleAuthorizationInfo;  
       import org.apache.shiro.realm.AuthorizingRealm;  
       import org.apache.shiro.subject.PrincipalCollection;  
       import org.springframework.stereotype.Service;  
       import org.springframework.transaction.annotation.Transactional;  
         
       import com.cn.pojo.Role;  
       import com.cn.pojo.User;  
         
       @Service  
       @Transactional  
       public class MyShiro extends AuthorizingRealm{  
         
           @Inject  
           private UserService userService;  
           /** 
            * 权限认证 
            */  
           @Override  
           protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {  
               //获取登录时输入的用户名  
               String loginName=(String) principalCollection.fromRealm(getName()).iterator().next();  
               //到数据库查是否有此对象  
               User user=userService.findByName(loginName);  
               if(user!=null){  
                   //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)  
                   SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();  
                   //用户的角色集合  
                   info.setRoles(user.getRolesName());  
                   //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的四行可以不要  
                   List roleList=user.getRoleList();  
                   for (Role role : roleList) {  
                       info.addStringPermissions(role.getPermissionsName());  
                   }  
                   return info;  
               }  
               return null;  
           }  
         
           /** 
            * 登录认证; 
            */  
           @Override  
           protected AuthenticationInfo doGetAuthenticationInfo(  
                   AuthenticationToken authenticationToken) throws AuthenticationException {  
               //UsernamePasswordToken对象用来存放提交的登录信息  
               UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;  
               //查出是否有此用户  
               User user=userService.findByName(token.getUsername());  
               if(user!=null){  
                   //若存在,将此用户存放到登录认证info中  
                   return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());  
               }  
               return null;  
           }  
         
       }  
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117
      • 118
      • 119
      • 120
      • 121
      • 122
      • 123
      • 124
      • 125
      • 126
      • 127
      • 128
      • 129
      • 130
      • 131
      • 132
      • 133
      • 134
      • 135
      • 136
      • 137
      • 138
      • 139
      • 140
      • 141
      • 142
      • 143
      • 144
      • 145
      • 146
      • 147
      • 148
      • 149
      • 150
      • 151
      • 152
      • 153
      • 154
      • 155
      • 156
      • 157
      • 158
      • 159
      • 160
      • 161
      • 162
      • 163
      • 164
      • 165
      • 166
      • 167
      • 168
      • 169
      • 170
      • 171
      • 172
      • 173
      • 174
      • 175
      • 176
      • 177
      • 178
      • 179
      • 180
      • 181
      • 182
      • 183
      • 184
      • 185
      • 186
      • 187
      • 188
      • 189
      • 190
      • 191
      • 192
      • 193
      • 194
      • 195
      • 196
      • 197
      • 198
      • 199
      • 200
      • 201
      • 202
      • 203
      • 204
      • 205
      • 206
      • 207
      • 208
      • 209
      • 210
      • 211
      • 212
      • 213
      • 214
      • 215
      • 216
      • 217
      • 218
      • 219
      • 220
      • 221
      • 222
      • 223
      • 224
      • 225
      • 226
      • 227
      • 228
      • 229
      • 230
      • 231
      • 232
      • 233
      • 234
      • 235
      • 236
      • 237
      • 238
      • 239
      • 240
      • 241
      • 242
      • 243
      • 244
      • 245
      • 246
      • 247
      • 248
      • 249
      • 250
      • 251
      • 252
      • 253
      • 254
      • 255
      • 256
      • 257
      • 258
      • 259
      • 260
      • 261
      • 262
      • 263
      • 264
      • 265
      • 266
      • 267
      • 268
      • 269
      • 270
      • 271
      • 272
      • 273
      • 274
      • 275
      • 276
      • 277
      • 278
      • 279
      • 280
      • 281
      • 282
      • 283
      • 284
      • 285
      • 286
      • 287
      • 288
      • 289
      • 290
      • 291
      • 292
      • 293
      • 294
      • 295
      • 296
      • 297
      • 298
      • 299
      • 300
      • 301
      • 302
      • 303
      • 304
      • 305
      • 306
      • 307
      • 308
      • 309
      • 310
      • 311
      • 312
      • 313
      • 314
      • 315
      • 316
      • 317
      • 318
      • 319
      • 320
      • 321
      • 322
      • 323
      • 324
      • 325
      • 326
      • 327
      • 328
      • 329
      • 330
      • 331
      • 332
      • 333
      • 334
      • 335
      • 336
      • 337
      • 338
      • 339
      • 340
      • 341
      • 342
      • 343
      • 344
      • 345
      • 346
      • 347
      • 348
      • 349
      • 350
      • 351
      • 352
      • 353
      • 354
      • 355
      • 356
      • 357
      • 358
      • 359
      • 360
      • 361
      • 362
      • 363
      • 364
      • 365
      • 366
      • 367
      • 368
      • 369
      • 370
      • 371
      • 372
      • 373
      • 374
      • 375
      • 376
      • 377
      • 378
      • 379
      • 380
      • 381
      • 382
      • 383
      • 384
      • 385
      • 386
      • 387
      • 388
      • 389
      • 390
      • 391
      • 392
      • 393
      • 394
      • 395
      • 396
      • 397
      • 398
      • 399
      • 400
      • 401
      • 402
      • 403
      • 404
      • 405
      • 406
      • 407
      • 408
      • 409
      • 410
      • 411
      • 412
      • 413
      • 414
      • 415
      • 416
      • 417
      • 418
      • 419
      • 420
      • 421
      • 422
      • 423
      • 424
      • 425
      • 426
      • 427
      • 428
      • 429
      • 430
      • 431
      • 432
      • 433
      • 434
      • 435
      • 436
      • 437
      • 438
      • 439
      • 440
      • 441
      • 442
      • 443
      • 444
      • 445
      • 446
      • 447
      • 448
      • 449
      • 450
      • 451

    在spring的配置文件中配置,为了区别spring原配置和shiro我们将shiro的配置独立出来。

    applicationContext-shiro.xml

      
      
      
          
            
              
                
              
                
          
          
          
           
               
               
               
                
              
                
              
                
              
                
                    
                      
                    /static/**=anon  
                      
                    /user=perms[user:query]  
                      
                    /user/add=roles[manager]  
                    /user/del/**=roles[admin]  
                    /user/edit/**=roles[manager]  
                        
                    /** = authc  
                    
                
          
          
          
            
           
          
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    用于登录,登出,权限跳转的控制:

    Java代码 [这里是图片003]

    1.  package com.cn.controller;  
         
       import javax.validation.Valid;  
         
       import org.apache.shiro.SecurityUtils;  
       import org.apache.shiro.authc.AuthenticationException;  
       import org.apache.shiro.authc.UsernamePasswordToken;  
       import org.springframework.stereotype.Controller;  
       import org.springframework.ui.Model;  
       import org.springframework.validation.BindingResult;  
       import org.springframework.web.bind.annotation.RequestMapping;  
       import org.springframework.web.bind.annotation.RequestMethod;  
       import org.springframework.web.servlet.mvc.support.RedirectAttributes;  
         
       import com.cn.pojo.User;  
         
       @Controller  
       public class HomeController {  
         
           @RequestMapping(value="/login",method=RequestMethod.GET)  
           public String loginForm(Model model){  
               model.addAttribute("user", new User());  
               return "/login";  
           }  
             
           @RequestMapping(value="/login",method=RequestMethod.POST)  
           public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){  
               try {  
                   if(bindingResult.hasErrors()){  
                       return "/login";  
                   }  
                   //使用权限工具进行用户登录,登录成功后跳到shiro配置的successUrl中,与下面的return没什么关系!  
                   SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));  
                   return "redirect:/user";  
               } catch (AuthenticationException e) {  
                   redirectAttributes.addFlashAttribute("message","用户名或密码错误");  
                   return "redirect:/login";  
               }  
           }  
             
           @RequestMapping(value="/logout",method=RequestMethod.GET)    
           public String logout(RedirectAttributes redirectAttributes ){   
               //使用权限管理工具进行用户的退出,跳出登录,给出提示信息  
               SecurityUtils.getSubject().logout();    
               redirectAttributes.addFlashAttribute("message", "您已安全退出");    
               return "redirect:/login";  
           }   
             
           @RequestMapping("/403")  
           public String unauthorizedRole(){  
               return "/403";  
           }  
       }  
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53

    三个主要的JSP:
    login.jsp:

    Html代码 [这里是图片004]

    1.  <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
       <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>  
         
         
           
           My JSP 'MyJsp.jsp' starting page  
           
           
           
           

      登录页面----${message }

      用户名:
      密 ??码:
      submit
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

    user.jsp:

    Html代码 [这里是图片005]

    1.  <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
       <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>  
       <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>  
         
         
           
           用户列表  
           
           
           

      ${message }

      用户列表--添加用户---退出登录

      权限列表

      用户已经登录显示此内容 manager角色登录显示此内容 admin角色登录显示此内容 normal角色登录显示此内容 **manager or admin 角色用户登录显示此内容** -显示当前登录用户名 add权限用户显示此内容 query权限用户显示此内容 不具有user:del权限的用户显示此内容
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45

    403.jsp:

    Html代码 [这里是图片006]

    1.  <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
       <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>  
         
         
           
           权限错误  
           
           
           
           

      对不起,您没有权限请求此连接!

      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      本文转载至http://www.360doc.com/content/14/0529/10/11298474_381933566.shtml

    shiro demo https://github.com/haois/shiro-rbac-demo.git (spring springmvc mybatis maven)

  • 相关阅读:
    SAP MD04 MRP元素显示中文(TCODE:OMD5)
    Python与Excel的完美结合:操作技巧与自动化应用
    基于LSCF和LSFD算法在频域中识别快速实现的MIMO研究(Matlab代码实现)
    Spark新特性与核心概念
    Couchdb-权限绕过--命令执行--(CVE-2017-12635)&&(CVE-2017-12636)--H2database命令执行--(CVE-2022-23221)
    记一次 .NET 某企业OA后端服务 卡死分析
    C/C++程序的断点调试
    管件注塑过程中采用串级PID控制法实现高压压力精密控制的解决方案
    深度剖析堆及代码实现
    ArcGIS导出Excel中文乱码及shp添加字段3个字被截断
  • 原文地址:https://blog.csdn.net/m0_66557301/article/details/126496146