Day3 权限管理

news2024/11/15 17:58:39

Day3 权限管理

这里会总结构建项目过程中遇到的问题,以及一些个人思考!!
学习方法:
1 github源码 + 文档 + 官网
2 内容复现 ,实际操作

项目源码同步更新到github 欢迎大家star~ 后期会更新并上传前端项目

创建管理员服务模块

提供对角色和权限的管理— 操作数据库

  1. pom文件(父子关系、 依赖注入)
  2. 配置文件修改 nacos注册
  3. 服务和common接口

新增管理员


/**
 * 管理员实现类
 *
 * @author bootsCoder
 * @date created on 2024/4/15
 */
@DubboService
@Transactional
public class AdminServiceImpl implements AdminService {
    @Autowired
    private AdminMapper adminMapper;
    @Override
    public void add(Admin admin) {
        adminMapper.insert(admin);

    }

    @Override
    public void update(Admin admin) {

    }

    @Override
    public void delete(Long id) {

    }

    @Override
    public Admin findById(Long id) {
        return null;
    }

    @Override
    public Page<Admin> search(int page, int size) {
        return null;
    }

    @Override
    public void updateRoleToAdmin(Long aid, Long[] rids) {

    }
}


/**
 * 管理员服务
 *
 * @author bootsCoder
 * @date created on 2024/4/15
 */
public interface AdminService {
    /**
     * 新增管理员
     */
    void add(Admin admin);
    /**
     * 更新管理员
     */
    void update(Admin admin);
    /**
     * 删除管理员
     */
    void delete(Long id);
    /**
     * 查找管理员
     */
    Admin findById(Long id);
    /**
     * 新增管理员
     */
    Page<Admin> search(int page, int size);
    /**
     * 更新角色
     */
    void updateRoleToAdmin(Long aid, Long[] rids);
}


/**
 * 管理员api
 *
 * @author bootsCoder
 * @date created on 2024/4/15
 */
@RestController
@RequestMapping("/admin")
public class AdminController {
    @DubboReference
    private AdminService adminService;

    @PostMapping("/add")
    public BaseResult add(@RequestBody Admin admin) {
        adminService.add(admin);
        return BaseResult.ok();
    }
}
image-20240415194505264

删除管理员

删除管理员时需要删除对应的角色


/**
 * 管理员的数据库映射类
 *
 * @author bootsCoder
 * @date created on 2024/4/15
 */
public interface AdminMapper extends BaseMapper<Admin> {

    /**
     * Deletes all roles associated with the specified administrator ID.
     *
     * @param aid the administrator ID
     */
    @Delete("DELETE FROM boots_admin_role WHERE aid = #{aid}")
    void deleteAdminAllRole(@Param("aid") Long aid);
}

查询管理员 – 编写多表联查

xml 格式,有时间研究一下 @Select 的方法

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bootscoder.shopping_admin_service.mapper.AdminMapper">

    <resultMap id="adminMapper" type="com.bootscoder.shopping_common.pojo.Admin">
        <id property="aid" column="aid"></id>
        <result property="username" column="username"></result>
        <collection property="roles" column="aid" ofType="com.bootscoder.shopping_common.pojo.Role">
            <id property="rid" column="rid"></id>
            <result property="roleName" column="roleName"></result>
            <result property="roleDesc" column="roleDesc"></result>
            <collection property="permissions" column="rid" ofType="com.bootscoder.shopping_common.pojo.Permission">
                <id property="pid" column="pid"></id>
                <result property="permissionName" column="permissionName"></result>
                <result property="url" column="url"></result>
            </collection>
        </collection>
    </resultMap>
    <select id="findById" parameterType="long" resultMap="adminMapper">
        SELECT * FROM boots_admin
                          LEFT JOIN boots_admin_role on boots_admin.aid = boots_admin_role.aid
                          LEFT JOIN boots_role on boots_admin_role.rid = boots_role.rid
                          LEFT JOIN boots_role_permission on boots_role.rid = boots_role_permission.rid
                          LEFT JOIN boots_permission on boots_role_permission.pid = boots_permission.pid
        WHERE boots_admin.aid = #{aid}
    </select>

    <insert id="addRoleToAdmin">
        INSERT INTO boots_admin_role values (#{aid},#{rid});
    </insert>

    <select id="findAllPermission" parameterType="string" resultType="com.bootscoder.shopping_common.pojo.Permission">
        SELECT DISTINCT boots_permission.*
        FROM
            boots_admin
                LEFT JOIN boots_admin_role ON boots_admin.aid = boots_admin_role.aid
                LEFT JOIN boots_role on boots_admin_role.rid = boots_role.rid
                LEFT JOIN boots_role_permission on boots_role.rid = boots_role_permission.rid
                LEFT JOIN boots_permission on boots_role_permission.pid = boots_permission.pid
        WHERE boots_admin.username = #{username}
    </select>
</mapper>

这个真的是有一手了,我感觉得好好学,不开玩笑

分页查询管理员

 @GetMapping("/search")
    public BaseResult<Page<Admin>> search(int page, int size) {
        Page<Admin> adminPage = adminService.search(page, size);
        return BaseResult.ok(adminPage);
    }
    
     @Override
    public Page<Admin> search(int page, int size) {
        return adminMapper.selectPage(new Page<>(page, size), null);
    }
    
    //在启动类配置分页
 @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
image-20240415204655342

修改管理员角色

修改管理员的角色,即修改boots_admin_role表中的记录。修改管理员角色时,先将管理员的所有角色删除,再将其新角色添加到boots_admin_role表中。

/**
 * 管理员实现类
 *
 * @author bootsCoder
 * @date created on 2024/4/15
 */
@DubboService
@Transactional //配置事务
public class AdminServiceImpl implements AdminService {
    @Override
    @Transactional
    public void updateRoleToAdmin(Long aid, Long[] rids) {
        if (aid == null || aid <= 0) {
            //这个异常我想让我的异常捕获器捕捉到该怎么办呢?
            throw new IllegalArgumentException("Invalid administrator ID");
        }
        if (rids == null || rids.length == 0) {
            throw new IllegalArgumentException("Roles array must not be empty");
        }

        // 删除用户的所有角色
        adminMapper.deleteAdminAllRole(aid);
        // 重新添加管理员角色
        for (Long rid : rids) {
            adminMapper.addRoleToAdmin(aid, rid);
        }
    }
}

提问:

  1. 方法【updateRoleToAdmin】需要在Transactional注解指定rollbackFor或者在方法中显式的rollback。 why
  2. 类的@Transaction 注释和 方法的有什么不同
  3. how to add this exception in my own exception handler?

认真比对了一下参数,成功了

生成接口文档 – easyYAPi

连接前端工程 测试

image-20240416181600159

image-20240416182038506

修改角色? – > 如果用户本来没有角色需要添加怎么办?

修改逻辑错误

 @Override
    public void updateRoleToAdmin(Long aid, Long[] rids) {
        if (aid == null || aid <= 0) {
            //这个异常我想让我的异常捕获器捕捉到该怎么办呢?
            throw new IllegalArgumentException("Invalid administrator ID");
        }
        // 删除用户的所有角色
        adminMapper.deleteAdminAllRole(aid);
        if (rids != null || rids.length > 0) {
            for (Long rid : rids) {
                // 重新添加管理员角色
                adminMapper.addRoleToAdmin(aid, rid);
            }
        }
    }

测试:
删除成功~
JDBC Connection [HikariProxyConnection@1234983148 wrapping com.mysql.cj.jdbc.ConnectionImpl@6fb35126] will be managed by Spring
= => Preparing: DELETE FROM boots_admin_role WHERE aid = ?
= = > Parameters: 33(Long)
<== Updates: 1

编写角色相关功能

和admin 类似 还是一样 最难的部分是xml中sql的编写和结果的封装

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bootscoder.shopping_admin_service.mapper.PermissionMapper">
    <delete id="deletePermissionAllRole" parameterType="long">
        DELETE FROM boots_role_permission WHERE pid = #{pid}
    </delete>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bootscoder.shopping_admin_service.mapper.RoleMapper">
    <resultMap id="roleMapper" type="com.bootscoder.shopping_common.pojo.Role">
        <id property="rid" column="rid"></id>
        <result property="roleName" column="roleName"></result>
        <result property="roleDesc" column="roleDesc"></result>
        <collection property="permissions" column="rid" ofType="com.bootscoder.shopping_common.pojo.Permission">
            <id property="pid" column="pid"></id>
            <result property="permissionName" column="permissionName"></result>
            <result property="url" column="url"></result>
        </collection>
    </resultMap>
    <select id="findById" parameterType="long" resultMap="roleMapper">
        SELECT * FROM boots_role
          LEFT JOIN boots_role_permission on boots_role.rid = boots_role_permission.rid
          LEFT JOIN boots_permission on boots_role_permission.pid = boots_permission.pid
        WHERE boots_role.rid = #{rid}
    </select>
    <delete id="deleteRoleAllPermission" parameterType="long">
        DELETE FROM boots_role_permission where rid = #{rid}
    </delete>
    <delete id="deleteRoleAllAdmin" parameterType="long">
        DELETE FROM boots_admin_role where rid = #{rid}
    </delete>
    <insert id="addPermissionToRole">
        INSERT INTO boots_role_permission values (#{rid},#{pid});
    </insert>

</mapper>

编写角色相关功能

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bootscoder.shopping_admin_service.mapper.PermissionMapper">
    <delete id="deletePermissionAllRole" parameterType="long">
        DELETE FROM boots_role_permission WHERE pid = #{pid}
    </delete>
</mapper>

修改角色权限时报错

image-20240416193015801

 @Override
    public void updatePermissionToRole(Long rid, Long[] pids) {

        // 删除角色的所有权限
        roleMapper.deleteRoleAllPermission(rid);
        if (pids != null && pids.length > 0){
            // 给角色添加权限
            for (Long pid : pids) {
                roleMapper.addPermissionToRole(rid,pid);
            }	
        }
    }

删除所有权限 写成所有admin了

Spring Security 核心组件

在Spring Security 3.0中,spring-security-core jar的内容被剥离到最低限度。它不再包含与web相关的任何代码 - 应用程序安全性,LDAP或命名空间配置。我们将在这里看一下您在核心模块中可以找到的一些Java类型。它们代表了框架的构建块,因此如果您需要超越简单的命名空间配置,那么即使您实际上不需要直接与它们进行交互,您也必须了解它们是什么。

SecurityContextHolder,SecurityContext和Authentication Objects

最基本的对象是SecurityContextHolder。这是我们存储应用程序当前安全上下文的详细信息的地方,其中包括当前使用该应用程序的主体的详细信息。默认情况下,SecurityContextHolder使用ThreadLocal来存储这些详细信息,这意味着安全上下文始终可用于同一执行线程中的方法,即使安全上下文未作为参数显式传递那些方法。如果在处理当前委托人的请求之后小心地清除线程,以这种方式使用ThreadLocal是非常安全的。当然,Spring Security会自动为您解决这个问题,因此无需担心。

某些应用程序并不完全适合使用ThreadLocal,因为它们使用线程的特定方式。例如,Swing客户端可能希望Java虚拟机中的所有线程都使用相同的安全上下文。SecurityContextHolder可以在启动时配置策略,以指定您希望如何存储上下文。对于独立应用程序,您将使用SecurityContextHolder.MODE_GLOBAL策略。其他应用程序可能希望安全线程生成的线程也采用相同的安全标识。这是通过使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL来实现的。您可以通过两种方式从默认SecurityContextHolder.MODE_THREADLOCAL更改模式。第一个是设置系统属性,第二个是在SecurityContextHolder上调用静态方法。大多数应用程序不需要更改默认值,但如果这样做,请查看JavaDoc for SecurityContextHolder以了解更多信息。

获取有关当前用户的信息

SecurityContextHolder内,我们存储了当前与应用程序交互的主体的详细信息。Spring Security使用Authentication对象来表示此信息。您通常不需要自己创建Authentication对象,但用户查询Authentication对象是相当常见的。您可以使用以下代码块(从应用程序的任何位置)获取当前经过身份验证的用户的名称,例如:

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}

调用getContext()返回的对象是SecurityContext接口的实例。这是保存在线程本地存储中的对象。正如我们将在下面看到的,Spring Security中的大多数认证机制都返回UserDetails的实例作为主体。

UserDetailsService

上面代码片段中需要注意的另一个问题是,您可以从Authentication对象获取主体。校长只是Object。大多数情况下,这可以转换为UserDetails对象。UserDetails是Spring Security中的核心界面。它代表一个主体,但是以可扩展和特定于应用程序的方式。可以将UserDetails视为您自己的用户数据库与SecurityContextHolder内Spring Security所需的适配器之间的适配器。作为来自您自己的用户数据库的东西的表示,您经常会将UserDetails转换为您的应用程序提供的原始对象,因此您可以调用特定于业务的方法(如getEmail()getEmployeeNumber()和等等)。

到现在为止你可能想知道,所以我什么时候提供UserDetails对象?我怎么做?我以为你说这个东西是声明性的,我不需要编写任何Java代码 - 是什么给出的?简短的回答是有一个名为UserDetailsService的特殊界面。此接口上唯一的方法接受基于String的用户名参数并返回UserDetails

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

这是在Spring Security内为用户加载信息的最常用方法,只要需要有关用户的信息,您就会看到它在整个框架中使用。

上成功的认证,UserDetails被用来建立存储在SecurityContextHolder(关于这一点的Authentication对象下面)。好消息是我们提供了许多UserDetailsService实现,包括一个使用内存映射(InMemoryDaoImpl)和另一个使用JDBC(JdbcDaoImpl)的实现。但是,大多数用户倾向于自己编写,他们的实现通常只是位于代表其员工,客户或应用程序其他用户的现有数据访问对象(DAO)之上。记住使用上面的代码片段始终可以从SecurityContextHolder获得UserDetailsService返回的优点。

hello springSecurity!!!
UserDetailsService经常有些混乱。它纯粹是用户数据的DAO,除了将数据提供给框架内的其他组件之外,不执行任何其他功能。特别是,它不会对用户进行身份验证,这是由AuthenticationManager完成的。在许多情况下,如果您需要自定义身份验证过程,直接实现AuthenticationProvider会更有意义。
一个GrantedAuthority

除了校长之外,Authentication提供的另一个重要方法是getAuthorities()。此方法提供GrantedAuthority个对象的数组。毫不奇怪,GrantedAuthority是授予校长的权力。这些权力通常是“角色”,例如ROLE_ADMINISTRATORROLE_HR_SUPERVISOR。稍后将为web授权,方法授权和域对象授权配置这些角色。Spring Security的其他部分能够解释这些权威,并期望它们存在。GrantedAuthority对象通常由UserDetailsService加载。

通常GrantedAuthority对象是应用程序范围的权限。它们不是特定于给定的域对象。因此,你不可能有一个GrantedAuthority代表Employee对象编号54的权限,因为如果有数千个这样的权限,你很快就会耗尽内存(或者,至少,因为应用程序需要很长时间来验证用户身份)。当然,Spring Security专门用于处理这个常见要求,但您可以使用项目的域对象安全功能来实现此目的。

摘要

Spring Security的主要构建块是:

  • SecurityContextHolder,提供SecurityContext的访问权限。
  • SecurityContext,保存Authentication和可能的特定于请求的安全信息。
  • Authentication,以特定于Spring Security的方式代表校长。
  • GrantedAuthority,以反映授予主体的应用程序范围的权限。
  • UserDetails,提供从应用程序的DAO或其他安全数据源构建Authentication对象所需的信息。
  • UserDetailsService,在基于String的用户名(或证书ID等)中传递时创建UserDetails

既然您已经了解了这些重复使用的组件,那么让我们仔细看看身份验证过程。

Spring Security的整体原理

让我们考虑一个每个人都熟悉的标准身份验证方案。

  1. 提示用户使用用户名和密码登录。
  2. 系统(成功)验证用户名的密码是否正确。
  3. 获取该用户的上下文信息(他们的角色列表等)。
  4. 为用户建立安全上下文
  5. 用户继续进行,可能执行一些可能受访问控制机制保护的操作,该访问控制机制针对当前安全上下文信息检查操作所需的许可。

前三项构成了身份验证过程,因此我们将在Spring Security内查看这些过程是如何发生的。

  1. 获取用户名和密码并将其合并到UsernamePasswordAuthenticationToken的实例中(我们之前看到的Authentication接口的实例)。
  2. 令牌被传递给AuthenticationManager的实例以进行验证。
  3. AuthenticationManager在成功验证后返回完全填充的Authentication实例。
  4. 通过调用SecurityContextHolder.getContext().setAuthentication(…)建立安全上下文,传入返回的身份验证对象。

编写Security处理器

中文教程

接下来我们使用Spring Security编写管理员认证和授权功能。Spring Security在访问接口时进行认证和授权,所以Spring Security的相关代码编写在管理员API模块。

在springboot中使用Spring Security时,登录后会配置跳转页面。但boots商城是前后端分离项目,所有认证和授权的结果,只是返回json字符串让前端去处理。所以我们要创建认证成功处理器认证失败处理器未登录处理器权限不足处理器登出成功处理器处理不同的结果,Spring Security通过实现接口编写结果处理器。

主要解决两个问题:

  • 认证(Authentication)

解决你是谁的问题,具体表现为注册与登录

  • 授权(Authorization)

解决你能干什么的问题,你登录后有哪些权限。

引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
/**
 *  登出成功处理器
 *
 * @author bootsCoder
 * @date created on 2024/4/18
 */
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException {
        BaseResult result = new BaseResult(200, "注销成功", null);
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSON.toJSONString(result));
    }
}


编写配置类

/**
 * Spring Security配置
 *
 * @author bootsCoder
 * @date created on 2024/4/18
 */
@Configuration
public class SecurityConfig {
    //
    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        /**
         *         自定义表单登录
         */
        http.formLogin(
                form -> {
                    form.usernameParameter("username") // 用户名项
                            .passwordParameter("password") // 密码项
                            .loginProcessingUrl("/admin/login") // 登录提交路径
                            .successHandler(new LoginSuccessHandler()) // 登录成功处理器
                            .failureHandler(new LoginFailHandler()); // 登录失败处理器
                }
        );


        // 权限拦截配置
        http.authorizeHttpRequests(
                resp -> {
                    resp.requestMatchers("/login", "/admin/login").permitAll(); // 登录请求不需要认证
                    resp.anyRequest().authenticated();// 其余请求都需要认证
                }
        );


        /**
         *  退出登录配置
         */
        http.logout(
                logout -> {
                    logout.logoutUrl("/admin/logout") // 注销的路径
                            .logoutSuccessHandler(new MyLogoutSuccessHandler()) // 登出成功处理器
                            .clearAuthentication(true) // 清除认证数据
                            .invalidateHttpSession(true);// 清除session
                }
        );


        // 异常处理
        http.exceptionHandling(
                exception -> {
                    exception.authenticationEntryPoint(new MyAuthenticationEntryPoint())// 未登录处理器
                            .accessDeniedHandler(new MyAccessDeniedHandler()); // 权限不足处理器
                }
        );


        // 跨域访问
        http.cors();

        // 关闭csrf防护
        http.csrf(csrf ->{
            csrf.disable();
        });


        return http.build();
    }


    /**
     *     加密工具
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

image-20240418145958970

编写认证授权相关的服务方法

  <select id="findAllPermission" resultType="com.bootscoder.shopping_common.pojo.Permission" parameterType="string">
        SELECT
            DISTINCT boots_permission.*
        FROM
            boots_admin
                LEFT JOIN boots_admin_role
                          ON boots_admin.aid = boots_admin_role.aid
                LEFT JOIN boots_role
                          ON boots_admin_role.rid = boots_role.rid
                LEFT JOIN boots_role_permission
                          ON boots_role.rid = boots_role_permission.rid
                LEFT JOIN boots_permission
                          ON boots_role_permission.pid = boots_permission.pid
        WHERE boots_admin.username = #{username}
    </select>

编写认证授权逻辑

/**
 * 认证授权逻辑
 *
 * @author bootsCoder
 * @date created on 2024/4/18
 */
@Service
public class MyUserDetailService implements UserDetailsService {
    @DubboReference
    private AdminService adminService;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.认证
        Admin admin = adminService.findByAdminName(username);
        if(admin == null){
            throw new UsernameNotFoundException("用户不存在");
        }

        // 2.授权
        List<Permission> permissions = adminService.findAllPermission(username);
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        if (permissions.get(0) != null){
            for (Permission permission : permissions) {
                grantedAuthorities.add(new SimpleGrantedAuthority(permission.getUrl()));
            }
        }


        // 3.封装为UserDetails对象
        UserDetails userDetails = User.withUsername(admin.getUsername())
                .password(admin.getPassword())
                .authorities(grantedAuthorities)
                .build();


        // 4.返回封装好的UserDetails对象
        return userDetails;
    }
}

修改新增修改管理员方法-- 添加加密

 @Override
    public void update(Admin admin) {
        // 如果前端传来空密码,则密码还是原来的密码
        if(!StringUtils.hasText(admin.getPassword())){
            // 查询原来的密码
            String password = adminMapper.selectById(admin.getAid()).getPassword();
            admin.setPassword(password);
        }
        adminMapper.updateById(admin);
    }
    
     @PutMapping("/update")
    public BaseResult update(@RequestBody Admin admin) {
        String password = admin.getPassword();
        // 密码不为空加密
        if (StringUtils.hasText(password)){
            password = encoder.encode(password);
            admin.setPassword(password);
        }
        adminService.update(admin);
        return BaseResult.ok();
    }

权限管理_获取登录管理员名&接口鉴权配置

 /**
     * 分页查询角色
     * @param page 页码
     * @param size 每页条数
     * @return 查询结果
     */
    @GetMapping("/search")
    @PreAuthorize("hasAnyAuthority('/role/search')")
    public BaseResult<Page<Role>> search(int page, int size){
        Page<Role> rolePage = roleService.search(page, size);
        return BaseResult.ok(rolePage);
    }

image-20240418154747346

mybatis 封装的时候 为空时判list长度 1

 // 2.授权
        List<Permission> permissions = adminService.findAllPermission(username);
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        if (permissions.get(0) != null){
            for (Permission permission : permissions) {
                grantedAuthorities.add(new SimpleGrantedAuthority(permission.getUrl()));
            }
        }

image-20240418155141020

测试认证功能成功

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

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

相关文章

技术周刊的转变:如何平衡热爱与现实?

大家好&#xff0c;我是那个自己打脸自己的猫哥&#xff0c;本来说周刊不做订阅制的&#xff0c;现在却推出了订阅专栏。今天想为自己辩护一下&#xff0c;同时聊聊技术周刊今后的发展计划。 首先回顾一下我过去的想法吧&#xff0c;然后再解释为什么会突然出现转变。 出于对…

Stable Diffusion 3 API 发布!超越Midjourney v6和DALL-E 3

Stable Diffusion 3 于 2 月首次宣布作为预览版发布。而今天&#xff0c;StabilityAI 正式推出了 Stable Diffusion 3 和 Stable Diffusion 3 Turbo API 的API接口服务。 Stability AI 称仍在持续改进该模型&#xff0c;并没有说明发布日期。模型还没发布&#xff0c;但API先来…

Python | Leetcode Python题解之第28题找出字符串中的第一个匹配项的下标

题目&#xff1a; 题解&#xff1a; class Solution:def strStr(self, haystack: str, needle: str) -> int:# Func: 计算偏移表def calShiftMat(st):dic {}for i in range(len(st)-1,-1,-1):if not dic.get(st[i]):dic[st[i]] len(st)-idic["ot"] len(st)1re…

JS-39-underscore01-初识underscore

一、underscore简介 前面我们已经讲过了&#xff0c;JavaScript是函数式编程语言&#xff0c;支持高阶函数和闭包。 函数式编程非常强大&#xff0c;可以写出非常简洁的代码。例如Array的map()和filter()方法&#xff1a; use strict; var a1 [1, 4, 9, 16]; var a2 a1.ma…

数据结构书后习题

p17 1&#xff0c; 个人解答&#xff1a; int DeleteMinElem(SqList &L,int &min) {int j 0;if (L.length 0){printf("error!");return 0;}int min L.data[0];for (int i 1; i < L.length; i){if (L.data[i] < min){min L.data[i];j i;}}L.dat…

电工与电子技术选择题填空题计算题复习题含参考答案

答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 电工与电子技术复习题 一 . 单选题&#xff08;共 33 题&a…

[渗透测试学习] TwoMillion-HackTheBox

TwoMillion-HackTheBox 信息搜集 nmap扫描一下 nmap -sV -v 10.10.11.221扫描结果 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0) 80/tcp open http nginx 3851/tcp f…

SAP 技巧:如何查询TCODE的节点路径。

目录 SAP 技巧&#xff1a;如何查询TCODE的节点路径。 步骤一&#xff1a;在命令栏里输入&#xff1a;Search_SAP_Menu 会弹出以上页面&#xff0c;然后输入你想查询的T-code。回车。 步骤二&#xff1a;找到正确路径&#xff0c; SAP 技巧&#xff1a;如何查询TCODE的节点路…

0418WeCross搭建 + Caliper测试TPS

1. 基本信息 虚拟机名称&#xff1a;Pure-Ununtu18.04 WeCross位置&#xff1a;/root/wecross-demo 2. 搭建并启动WeCross 参考官方指导文档 https://wecross.readthedocs.io/zh-cn/v1.2.0/docs/tutorial/demo/demo.html 访问WeCross网页管理平台 http://localhost:8250/s/…

变频超声波驱鸟器,变电站驱鸟

随着春夏季来临&#xff0c;各种鸟类活动也愈发频繁。这一时期&#xff0c;变电站内有很多中大型鸟类&#xff0c;选择在户外架空高压裸导线龙门架、主变进线支撑架等重要设备上筑巢停留&#xff0c;它们筑巢所用的枝干和各类杂物&#xff0c;时常会掉落&#xff0c;引发设备短…

SQL Server Management Studio 显示行号

前言 在使用 SQL Server Management Studio (SSMS) 进行数据库管理和查询时&#xff0c;能够看到代码的行号是非常有用的。这可以帮助您更容易地定位代码错误、讨论特定的代码行&#xff0c;或者在执行长查询时快速找到特定行。在本文中&#xff0c;我将向您展示如何在 SSMS 中…

怎么你出的MES方案像屎一样?

最近在一个群里面&#xff0c;大家普遍感受到制定MES技术方案变得越来越困难&#xff0c;客户也变得越来越挑剔&#xff0c;方案的复杂度也在不断增加。在竞标过程中&#xff0c;各方技术水平的差距变得越来越小&#xff0c;这让人们感到相当困扰。考虑到这一问题&#xff0c;我…

OpenHarmony多媒体-ohos_videocompressor

介绍 videoCompressor是一款ohos高性能视频压缩器。 目前实现的能力&#xff1a; 支持视频压缩 使用本工程 有两种方式可以下载本工程&#xff1a; 开发者如果想要使用本工程,可以使用git命令 git clone https://gitee.com/openharmony-sig/ohos_videocompressor.git --…

U盘文件突然消失?别急,这里有数据恢复的终极攻略!

在日常的工作和生活中&#xff0c;U盘几乎成了我们随身携带的“数据小仓库”&#xff0c;存放着各种重要的文件。然而&#xff0c;就在某一天&#xff0c;你突然发现U盘中的文件神秘失踪&#xff0c;仿佛从未存在过一般。这种突如其来的U盘文件消失&#xff0c;无疑让人措手不及…

docker拉取镜像速度慢

解决办法是配置阿里云镜像加速 在docker desktop的docker engine里添加 "registry-mirrors": ["https://owzy8hoh.mirror.aliyuncs.com"] 修改以后重启docker 参考&#xff1a; 【docker】Windows10系统下安装并配置阿里云镜像加速_docker desktop 配置…

MySQL中InnoDB的行级锁

InnoDB 实现了以下两种类型的行锁。 共享锁&#xff08;S&#xff09;&#xff1a;又称为读锁&#xff0c;简称S锁&#xff0c;共享锁就是多个事务对于同一数据可以共享一把锁&#xff0c;都能访问到数据&#xff0c;但是只能读不能修改。 排他锁&#xff08;X&#xff09;&am…

仿真科普|从设计到研发,CAE仿真技术为汽车智造保驾护航

2024年3月28日&#xff0c;对于汽车产业来说&#xff0c;是历史性的一天&#xff0c;作为近年汽车行业发布会流量最大的一次&#xff0c;小米SU7的发布让整个汽车圈为之沸腾&#xff0c;成功收割全平台热搜。时至今日&#xff0c;小米汽车依然热度不减。 随着“蔚、小、理、特…

利用米家通断器与双控开关改造排风扇,或手动或定时运行

改造前的排风扇接线图如下&#xff1a; 改造要求&#xff1a;实现手动与智能运行两种控制方式&#xff0c;互不影响。 改造后的米家通断器与双控开关接线图如下&#xff1a; 默认米家通断器与双控开关都是关闭状态。

Leetcode 4.18

Leetcode 1.无重复字符的最长子串2.最长回文子串3.整数反转4.字符串转换整数 (atoi)5.正则表达式匹配 1.无重复字符的最长子串 无重复字符的最长子串 滑动窗口&#xff0c;先让右指针右移&#xff0c;如果发现这个子串有元素和右指针当前元素重复。 则&#xff1a; 左指针右移…

OpenHarmony南向开发案例:【智能照相机】

样例简介 本Demo是基于Hi3516开发板&#xff0c;使用OpenHarmony3.0-LTS开发的应用。通过获取摄像头数据&#xff0c;实现预览拍照以及路视频等功能。并且通过后台AI服务识别唤醒词来进行语音控制拍照及录制视频。 应用运行效果图&#xff1a; 此为相机的预览界面。 样例原理…