SpringBoot整合Shiro实现登录认证,鉴权授权

news2024/12/23 0:30:38

文章目录

  • 前言
  • 一、shiro简介
  • 二、环境搭建
    • 2.1.数据库
      • 2.1.1user用户表
      • 2.1.2user_role用户角色关系表
      • 2.1.3role角色表
      • 2.1.4role_permission角色权限关系表
      • 2.1.5permission权限表
    • 2.2导坐标
    • 2.3实体类
      • 2.3.1User
      • 2.3.2Role
      • 2.3.3Permission
    • 2.4MVC三层
      • 2.4.1User
        • 2.4.1.1mapper层
        • 2.4.1.2service层
        • 2.4.1.3controller层
      • 2.4.2Role
        • 2.4.2.1mapper层
        • 2.4.2.2service层
        • 2.4.2.3controller层
      • 2.3.3Permission
        • 2.4.3.1mapper层
        • 2.4.3.2service层
        • 2.4.3.3controller层
    • 2.5.加密工具类
    • 2.6.全局异常处理器
    • 2.7自定义Realm
    • 2.8shiro配置类
  • 三、案例演示
    • 3.1 登录认证
    • 3.2 权限认证
  • 总结


前言

近期对Springboot框架的学习中,为了更好的学习理解Springsecurity中间件,先学习了一下“老派”的shiro安全框架,本文章将通过注解的方式实现基础的用户认证和角色授权案例

一、shiro简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

二、环境搭建

2.1.数据库

2.1.1user用户表

在这里插入图片描述

2.1.2user_role用户角色关系表

在这里插入图片描述

2.1.3role角色表

在这里插入图片描述

2.1.4role_permission角色权限关系表

在这里插入图片描述

2.1.5permission权限表

在这里插入图片描述

2.2导坐标

<properties>
        <java.version>1.8</java.version>
        <!--shiro-->
        <shiro.version>1.3.2</shiro.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>

        <!-- SECURITY begin -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <!-- SECURITY end -->
    </dependencies>

2.3实体类

2.3.1User

@TableName("pe_user")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableId(value = "id",type = IdType.AUTO)
    private int id;
    private String username;
    private String password;
    private String salt;
    @TableField(exist = false)
    private Set<Role> roles;
}

2.3.2Role

@TableName("pe_role")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
    @TableId(value = "id",type = IdType.AUTO)
    private int id;
    private String name;
    private String code;
    private String description;
    @TableField(exist = false)
    private Set<Permission> permissions;
}

2.3.3Permission

@TableName("pe_permission")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Permission {
    @TableId(value = "id",type = IdType.AUTO)
    private int id;
    private String name;
    private String code;
    private String description;
}

2.4MVC三层

2.4.1User

2.4.1.1mapper层

public interface UserMapper extends BaseMapper<User> {

    @Select("select * from pe_user where username = #{name}")
    User findUserByName(String name);
		//级联查询
    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "username",property = "username"),
            @Result(column = "password",property = "password"),
            @Result(column = "salt",property = "salt"),
            @Result(column = "id",property = "roles",
                    many=@Many(select = "com.apesource.shirostudy_demo_05.dao.RoleMapper.findRoleIdsByUserId"))

    })
    @Select("select * from pe_user where id = #{id}")
    User findUserDetailById(@Param("id") int id);
}

2.4.1.2service层

public interface IUserService extends IService<User> {
    User findUserByName(String name);

    User findUserDetailById(int id);
}

@Service
@SuppressWarnings("all")
public class UserServiceImp extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    UserMapper userMapper;
    @Autowired
    IRoleService roleService;

    @Override
    public User findUserByName(String name) {
        User user = userMapper.findUserByName(name);
        return user;
    }

    @Override
    public User findUserDetailById(int id) {
        //根据userid查询user
        User user = userMapper.findUserDetailById(id);
        //返回
        return user;
    }
}

2.4.1.3controller层

@RestController
public class UserController {
    @Autowired
    private IUserService userService;

    //个人主页
    @RequiresPermissions("user-home")
    @RequestMapping(value = "/user/home")
    public String home() {
        return "访问个人主页成功";
    }

    //根据名字查询用户基本信息
    @RequiresPermissions("user-find")
    @RequestMapping(value = "/user/userByName/{name}")
    public String findUserByName(@PathVariable String name) {
        System.out.println(name);
        User user = userService.findUserByName(name);
        return user.toString();
    }


    //更新
    @RequiresPermissions("user-update")
    @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
    public String update(@PathVariable String id) {
        return "更新用户成功";
    }


    //删除
    @RequiresPermissions("user-delete")
    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable String id) {
        return "删除用户成功";
    }


    //根据id查询用户详细信息
    @RequiresPermissions("user-find")
    @RequestMapping(value = "/user/userById/{id}")
    public String findUserDetailById(@PathVariable int id) {
        return userService.findUserDetailById(id).toString();
    }

    //登录页面
    @RequestMapping(value = "/login")
    public String login(User user) {
        try {
            //1.构造登录令牌
            UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
            //2.//2.获取subject
            Subject subject = SecurityUtils.getSubject();
            //3.调用subject进行登录
            subject.login(token);
            return "登录成功!!!";
        } catch (AuthenticationException e) {
            return "用户名或密码错误!!!";
        }


    }

    //未登陆与未授权页面
    @RequestMapping(value = "/autherror")
    public String autherror() {
        return "未登录";
    }

}

2.4.2Role

2.4.2.1mapper层

public interface RoleMapper extends BaseMapper<Role> {
    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "name",property = "name"),
            @Result(column = "code",property = "code"),
            @Result(column = "description",property = "description"),
            @Result(column = "id",property = "permissions",
                    many =@Many(select = "com.apesource.shirostudy_demo_05.dao.PermissionMapper.findPermissionIdsByRoleId"))
    })
    @Select("SELECT * FROM pe_role WHERE id IN (SELECT role_id FROM pe_user_role WHERE user_id = #{id})")
    Set<Role> findRoleIdsByUserId(int id);
}

2.4.2.2service层

public interface IRoleService extends IService<Role> {
    Set<Role> findRoleIdsByUserId(int id);
}
@Service
@SuppressWarnings("all")
public class RoleServiceImp extends ServiceImpl<RoleMapper, Role> implements IRoleService {
    @Autowired
    RoleMapper roleMapper;
    @Autowired
    IPermissionService permissionService;

    @Override
    public Set<Role> findRoleIdsByUserId(int id) {
        Set<Role> roles = roleMapper.findRoleIdsByUserId(id);
        return roles;
    }
}

2.4.2.3controller层

由于不演示,无。

2.3.3Permission

2.4.3.1mapper层

public interface PermissionMapper extends BaseMapper<Permission> {


    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "name",property = "name"),
            @Result(column = "code",property = "code"),
            @Result(column = "description",property = "description"),
    })
    @Select("SELECT * FROM pe_permission WHERE id IN (SELECT permission_id FROM pe_role_permission WHERE role_id = #{id} ) ")
    Set<Permission> findPermissionIdsByRoleId(int id);
}

2.4.3.2service层

public interface IPermissionService extends IService<Permission> {
    Set<Permission> findPermissionIdsByRoleId(int id);
}
@Service
public class PermissionServiceImp extends ServiceImpl<PermissionMapper, Permission> implements IPermissionService {
    @Autowired(required = false)
    PermissionMapper permissionMapper;

    @Override
    public Set<Permission> findPermissionIdsByRoleId(int id) {
        Set<Permission> permissionIds = permissionMapper.findPermissionIdsByRoleId(id);
        return permissionIds;
    }
}

2.4.3.3controller层

由于不演示,无。

2.5.加密工具类

public class DigestsUtil {

    public static final String SHA1 = "SHA-1";

    public static final Integer COUNTS =369;

    /**
     * @Description sha1方法
     * @param input 需要散列字符串
     * @param salt 盐字符串
     * @return
     */
    public static String show(String input, String salt) {
       return new SimpleHash(SHA1, input, salt,COUNTS).toString();
    }

    /**
     * @Description 随机获得salt字符串
     * @return
     */
    public static String generateSalt(){
        SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
        return randomNumberGenerator.nextBytes().toHex();
    }


    /**
     * @Description 生成密码字符密文和salt密文
     * @param
     * @return
     */
    public static Map<String,String> entryptPassword(String passwordPlain) {
       Map<String,String> map = new HashMap<>();
       String salt = generateSalt();
       String password =show(passwordPlain,salt);
       map.put("salt", salt);
       map.put("password", password);
       return map;
    }
}

2.6.全局异常处理器

@ControllerAdvice
public class BaseExceptionHandler {
    @ExceptionHandler(value = AuthorizationException.class)
    @ResponseBody
    public String error(HttpServletRequest request, HttpServletResponse response, AuthorizationException e){
        return "未授权!!!";
    }
}

2.7自定义Realm

public class MyRealm extends AuthorizingRealm {
    @Autowired
    IUserService userService;



    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        int userId = (int) principalCollection.getPrimaryPrincipal();
        User user = userService.findUserDetailById(userId);
        Set<String> roles = new HashSet<>();
        Set<String> permissions = new HashSet<>();
        user.getRoles().stream().forEach(role -> {
            roles.add(role.getCode());
            role.getPermissions().stream().forEach(permission -> {
                permissions.add(permission.getCode());
            });
        });
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(roles);
        info.addStringPermissions(permissions);
        return info;
    }

    /**
     * 认证方法
     *  参数:传递的用户名密码
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1.获取登录的用户名密码(token)
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        //2.根据用户名查询数据库
        //mybatis情景下:user对象中包含ID,name,pwd(匿名)
        User user = userService.findUserByName(username);
        if(user != null){
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getId(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),"myRealm");
            return info;
        }
        return null;
    }


    /**
     * @Description 自定义密码比较器
     * @param
     * @return
     * bean标签 init-method属性
     */
    @PostConstruct
    public void initCredentialsMatcher() {
        //指定密码算法
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(DigestsUtil.SHA1);
        //指定迭代次数
        hashedCredentialsMatcher.setHashIterations(DigestsUtil.COUNTS);
        //生效密码比较器
        setCredentialsMatcher(hashedCredentialsMatcher);
    }
}

2.8shiro配置类

@Configuration
public class ShiroConfiguration {

    /**
     * 1.创建shiro自带cookie对象
     */
    @Bean
    public SimpleCookie sessionIdCookie(){
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setName("ShiroSession");
        return simpleCookie;
    }

    //2.创建realm
    @Bean
    public MyRealm getRealm(){
        return new MyRealm();
    }

    /**
     * 3.创建会话管理器
     */
    @Bean
    public DefaultWebSessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(false);
        sessionManager.setSessionIdCookieEnabled(true);
        sessionManager.setSessionIdCookie(sessionIdCookie());
        sessionManager.setGlobalSessionTimeout(3600000);
        return sessionManager;
    }

    //4.创建安全管理器
    @Bean
    public SecurityManager defaultWebSecurityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(getRealm());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }



    /**
     * 5.保证实现了Shiro内部lifecycle函数的bean执行
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 6.开启对shiro注解的支持
     *   AOP式方法级权限检查
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    /**
     * 7.配合DefaultAdvisorAutoProxyCreator事项注解权限校验
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager());
        return authorizationAttributeSourceAdvisor;
    }




    //8.配置shiro的过滤器工厂再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
    @Bean
    public ShiroFilterFactoryBean shiroFilter() {
        //1.创建过滤器工厂
        ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
        //2.设置安全管理器
        filterFactory.setSecurityManager(defaultWebSecurityManager());
        //3.通用配置(跳转登录页面,为授权跳转的页面)
        filterFactory.setLoginUrl("/autherror");//跳转url地址
        //4.设置过滤器集合
        //key = 拦截的url地址
        //value = 过滤器类型
        Map<String,String> filterMap = new LinkedHashMap<>();
        //key:请求规则   value:过滤器名称
        filterMap.put("/login","anon");//当前请求地址可以匿名访问
        filterMap.put("/user/**","authc");//当前请求地址必须认证之后可以访问
        //在过滤器工程内设置系统过滤器
        filterFactory.setFilterChainDefinitionMap(filterMap);

        return filterFactory;
    }
    
}

三、案例演示

用户密码均是123456加密后保存至数据库

3.1 登录认证

成功登录:
在这里插入图片描述
失败登录:
在这里插入图片描述

3.2 权限认证

由于刚刚登录的信息有管理员和员工的身份,所以其所有功能都能使用。
有权限时:在这里插入图片描述
无权限时:在这里插入图片描述
无权限时会被全局异常处理器拦截,并作出响应的响应。


总结

通过对shrio的学习,更好的理解了安全认证以及鉴权授权的流程,大概了解了安全认证的机制,对我之后学习理解Springsecurity有更好的帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/891596.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python使用xlwt时,报ValueError: More than 4094 XFs (styles)

在写表格时&#xff0c;遇到如下报错 一、报错原因 xlwt最多只能有4094个样式&#xff0c;超出这个样式数量就报错了。 二、解决办法 &#xff08;1&#xff09;去掉样式的要求

Windows权限维持—自启动映像劫持粘滞键辅助屏保后门WinLogon

Windows权限维持—自启动&映像劫持&粘滞键&辅助屏保后门&WinLogon 1. 前置2. 自启动2.1. 路径加载2.1.1. 放置文件2.1.2. 重启主机 2.2. 服务加载2.2.1. 创建服务2.2.2. 查看服务2.2.3. 重启主机 2.3. 注册表加载2.3.1. 添加启动项2.3.2. 查看注册表2.3.3. 重启…

如何使用Asp.net Core实现定时任务,轻松解决任务调度问题!

一、前言 Asp.net core作为一种高效、跨平台的web框架&#xff0c;在开发过程中&#xff0c;我们常常需要在后台执行定时任务&#xff0c;例如清理无用文件、生成报告、发送邮件等任务。对于这种需求&#xff0c;我们可以使用第三方库&#xff08;如Hangfire或Quartz.NET&…

VIOOVI:什么是精益生产改善?如何做好精益生产改善?

现代化企业经营&#xff0c;更要注重科学化管理、精准化布局&#xff0c;才能为长线稳健运营奠定基础。当下&#xff0c;精益生产改善是各行各业都在探索的话题。那什么是精益生产改善&#xff1f;精益生产&#xff0c;也被称之为精益生产方式&#xff0c;它是基于生产组织、管…

Datawhale Django后端开发入门 TASK03 QuerySet和Instance、APIVIew

一、QuerySet QuerySet 是 Django 中的一个查询集合&#xff0c;它是由 Model.objects 方法返回的&#xff0c;并且可以用于生成数据库中所有满足一定条件的对象的列表。 QuerySet 在 Django 中表示从数据库中获取的对象集合,它是一个可迭代的、类似列表的对象集合。主要特点…

重发布 路由策略

[r4]ip ip_prefix 15 permit 192.168.3.0 24. 根据序号插入规则 [r4]undo ip-prefix aa index 15. 删除规则 [r4]ip ip-prefix aa permit 192.168.3.0 24 less- equal 28 抓取目标网段为3.0掩码长度为24到28的路由 [r4]ip ip-prefix aa permit 192.168.3.0 24 greate…

记录一下基于jeecg-boot3.0的待办消息移植记录

因为之前没有记录&#xff0c;所以还要看代码进行寻找&#xff0c;比较费劲&#xff0c;所以今天记录一下&#xff1a; 1、后端 SysAnnouncementController 下面函数增加待办的几个显示内容给前端用 具体代码如下&#xff1a; /*** 功能&#xff1a;补充用户数据&#xff0c…

Google play应用成功上架要点——如何防止封号、拒审、下架?

Google Play是全球最大的移动应用商店之一&#xff0c;它是运行Android操作系统的设备的官方应用商店。它提供各种数字内容&#xff0c;包括应用程序&#xff08;应用&#xff09;、游戏、音乐、书籍等&#xff0c;包括免费和付费选项。这也为许多游戏/APP出海的企业或开发者提…

spring源码分析bean的生命周期(上)

bean扫描生成BeanDefinition的过程&#xff1a; 创建非懒加载的单例bean的过程&#xff1a; spring容器初始化好之后&#xff0c;首先要进行bean的扫描&#xff0c;然后再进行bean的创建和管理 一、扫描生成BeanDefinition public int scan(String... basePackages) {// 扫描…

[Go版]算法通关村第十关黄金——归并排序

目录 归并排序&#xff08;mergeSort&#xff09;思路分析&#xff1a;二分分割 合并两个数组 递归遍历时处理元素的过程图&#xff1a;递归遍历时栈内的数据图&#xff1a;复杂度&#xff1a;时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)、空间复杂度 O ( n ) O(n) O(n)Go…

innodb的锁

一致性锁定读和一致性非锁定读 Read Committed和Repetable Read级别下采用MVCC 实现非锁定读 但在一些情况下&#xff0c;要使用加锁来保障数据的逻辑一致性 自增列 锁的算法 唯一值 MySQL 中关于gap lock / next-key lock 的一个问题_呜呜呜啦啦啦的博客-CSDN博客 RR可以通过…

为什么一些程序员很傲慢?懂点编程了不起吗?

Perl 语言之父 Larry Wall 说过&#xff0c;好的程序员有 3 种美德&#xff1a;懒惰、急躁和傲慢&#xff08;Laziness, Impatience and hubris&#xff09;。 在日常工作中&#xff0c;程序员的傲慢可以说是被吐槽的最多的&#xff0c;之前还有人特地开了帖子&#xff0c;发…

九耶丨阁瑞钛伦特-HashCode是什么

HashCode是一种用于快速查找和比较对象的方法。它是一个整数值&#xff0c;由对象的内容计算得出。HashCode通常用于数据结构中的散列函数&#xff0c;如哈希表、散列表等。 HashCode的作用有以下几点&#xff1a; 在哈希表中快速查找对象&#xff1a;哈希表根据对象的HashCod…

SQL Server数据库无法连接

问题如下&#xff1a; 原因&#xff1a;sql server服务器未开启 解决方法&#xff1a;以管理员身份打开cmd&#xff0c;输入&#xff1a;net start mssqlserver。

[Go版]算法通关村第十二关白银——字符串经典基础面试题

目录 反转专题题目&#xff1a;反转字符串思路分析&#xff1a;左右双指针 对向交换复杂度&#xff1a;时间复杂度 O ( n ) O(n) O(n)、空间复杂度 O ( 1 ) O(1) O(1)Go代码 题目&#xff1a;反转字符串 II思路分析&#xff1a;K个一组反转思想&#xff08;找到每组的首尾索引…

实在没货,简历(软件测试)咋写?

简历咋写&#xff0c;这是很多没有【软件测试实际工作经验】的同学们非常头疼的事情。 简历咋写&#xff1f;首先你要知道简历的作用。 简历的作用是啥呢&#xff1f;一句话就是&#xff1a;让HR小姐姐约你。 如何让HR看你一眼&#xff0c;便相中你的简历&#xff0c;实现在众…

AcrelEMS-IDC数据中心综合能效管理系统解决方案-安科瑞黄安南

1 概述 安科瑞电气紧跟数据中心发展形式&#xff0c;推出AcrelEMS-IDC数据中心综合能效管理解决方案&#xff0c;包含有电力监控、动环监控、消防监控、能耗统计分析、智能照明控制以及新能源监测几个子系统。集成了变配电监测、电源备自投、电气接点测温、智能照明控制、电能…

vue 实现图片懒加载

一&#xff1a;懒加载的目的 有些页面可能展示的是大量的图片&#xff0c;如果我们一次性加载所有图片就会浪费性能&#xff0c;影响用户体验&#xff0c;所以我们就会懒加载这些图片。即可视区域之外的图片不加载&#xff0c;随着页面的滚动&#xff0c;图片进入可视区域&…

小程序swiper一个轮播显示一个半内容且实现无缝滚动

效果图&#xff1a; wxml&#xff08;无缝滚动&#xff1a;circular"true"&#xff09;&#xff1a; <!--components/tool_version/tool_version.wxml--> <view class"tool-version"><swiper class"tool-version-swiper" circul…

基于IMX6ULLmini的linux裸机开发系列二:使用C语言和SDK点亮LED

引入sdk头文件 sudo chown -R gec /opt 用这条命令给gec赋权限&#xff0c;否则访问权限不够&#xff0c;无法读取&#xff0c;如下图成功 目的&#xff1a;解决寄存器地址难查难设置 devices/MCIMX6Y2/MCIMX6Y2.h 记录外设寄存器及其相关操作 devices/MCIMX6Y2/drivers/fsl_…