Shiro安全框架(上)

目录

第一章 权限概述

1、什么是权限

2、身份认证概念-Authentication

【1】什么是认证

【2】对象

2、用户授权概念-Authorization

【1】什么是授权

【2】授权流程

第二章 Shiro概述

1、Shiro简介

【1】什么是Shiro?

【2】Shiro 的特点

2、核心组件

第三章 Shiro入门

1、身份认证

【1】基本流程

【2】案例演示

【2.1】需求

【2.2】实现

【2.2.1】新建项目

【2.2.3】编写shiro.ini

【2.2.4】测试类验证测试

【2.3】小结

2、Realm(重要)

【1】Realm接口

【2】自定义Realm

【2.1】需求

【2.2】实现

【2.2.1】创建项目

【2.2.2】定义SecurityService

【2.2.3】定义MyRealm

【2.2.4】编辑shiro.ini

3、散列算法-SimpleHash

4、Realm使用散列算法

【1】新建项目

【2】创建密文密码

【3】修改SecurityService

【4】指定密码匹配方式

5、用户授权

【1】基本流程

【2】案例演示

【2.1】需求

【2.2】实现

【2.2.1】创建项目

【2.2.2】编写SecurityService(测试功能使用的是假数据)

【2.2.3】编写MyRealm

【2.2.4】编写App

【3】小结


第一章 权限概述

1、什么是权限

权限管理,一般指根据系统设置的安全策略或者安全规则,用户可以访问而且只能访问自己被授权的资源,不多不少。权限管理几乎出现在任何系统里面,只要有用户和密码的系统。

权限管理在系统中一般分为:

  • 访问权限

    一般表示你能做什么样的操作,或者能够访问那些资源。例如:给张三赋予“人事主管”角色,“人事主管”具有“查询员工”、“添加员工”、“修改员工”和“删除员工”权限。此时张三能够进入系统,则可以进行这些操作
  • 数据权限

    一般表示某些数据你是否属于你,或者属于你可以操作范围。例如:李四是"项目经理"角色,他可以看他的员工所有的任务分解,他的员工只能看自己负责的任务分解

2、身份认证概念-Authentication

【1】什么是认证

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和密码,看其是否与系统中存储的该用户的用户名和密码一致,来判断用户身份是否正确。例如:密码登录,手机短信验证、三方授权等

【2】对象

Subject:主体:访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体,简单理解就是当前用户

Principal:身份信息是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal),简单理解就是当前账号

credential:凭证信息:是只有主体自己知道的安全信息,如密码、证书等,简单理解就是密码

2、用户授权概念-Authorization

【1】什么是授权

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后,系统会为其分配对应的权限,当访问资

源时,会校验其是否有访问此资源的权限。

这里首先理解4个对象。

用户对象subject:当前操作的用户、程序,例如:zhangSan,liSi,admin1。

资源对象resource:当前被访问的对象,如系统菜单、页面、按钮、方法、系统商品信息等

访问类型:商品菜单,订单菜单、分销商菜单

数据类型:我的商品,我的订单,我的评价

角色对象role :一组 "权限操作许可权" 的集合。

权限对象permission:权限操作许可权

我的商品(资源)===>访问我的商品(权限许可)

分销商菜单(资源)===》访问分销商列表(权限许可)

【2】授权流程

第二章 Shiro概述

1、Shiro简介

【1】什么是Shiro?

Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

【2】Shiro 的特点

Shiro 是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。如下是它所具有的特点:

· 易于理解的 Java Security API;

· 简单的身份认证(登录),支持多种数据源(LDAP,JDBC 等);

· 对角色的简单的签权(访问控制),也支持细粒度的鉴权;

· 支持一级缓存,以提升应用程序的性能;

· 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;

· 异构客户端会话访问;

· 非常简单的加密 API;

· 不跟任何的框架或者容器捆绑,可以独立运行。

2、核心组件

  • Shiro架构图

  • Subject

Subject主体,外部应用与subject进行交互,subject将用户作为当前操作的主体,这个主体:可以是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
  • SecurityManager

SecurityManager权限管理器,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口
  • Authenticator

Authenticator即认证器,对用户登录时进行身份认证
  • Authorizer

Authorizer授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
  • Realm(数据库读取+认证功能+授权功能实现)

Realm领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据
比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。 
  • SessionManager

SessionManager会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
  • SessionDAO

SessionDAO即会话dao,是对session会话操作的一套接口
比如:可以通过jdbc将会话存储到数据库也可以把session存储到缓存服务器
  • CacheManager

CacheManager缓存管理,将用户权限数据存储在缓存,这样可以提高性能
  • Cryptography

Cryptography密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能

第三章 Shiro入门

1、身份认证

【1】基本流程

流程如下:

1、Shiro把用户的数据封装成标识token,token一般封装着用户名,密码等信息

2、使用Subject门面获取到封装着用户的数据的标识token

3、Subject把标识token交给SecurityManager,在SecurityManager安全中心中,SecurityManager把标识token委托给认证器Authenticator进行身份验证。认证器的作用一般是用来指定如何验证,它规定本次认证用到哪些Realm

4、认证器Authenticator将传入的标识token,与数据源Realm对比,验证token是否合法

【2】案例演示
【2.1】需求
1、使用shiro完成一个用户的登录
【2.2】实现
 <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency>
【2.2.1】新建项目

shiro

【2.2.3】编写shiro.ini
[users]
zhanSan=123
liSi=456
【2.2.4】测试类验证测试
@Testvoid contextLoads() {Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);Subject subject = SecurityUtils.getSubject();log.info("是否登录成功"+String.valueOf(subject.isAuthenticated()));subject.login(new UsernamePasswordToken("zhangSan","admin"));log.info("是否登录成功"+String.valueOf(subject.isAuthenticated()));}

【2.2.4】测试

当账号密码正确的时候代码可以继续执行
当错误的时候,会抛出异常
UnknownAccountException 账号不存在异常
IncorrectCredentialsException 认证失败异常
​
​
tips:
Incorrect 不正确
Credentials 认证
【2.3】小结
1、权限定义:ini文件
2、加载过程:导入权限ini文件构建权限工厂工厂构建安全管理器使用SecurityUtils工具生效安全管理器使用SecurityUtils工具获得主体使构建账号token用SecurityUtils工具获得主体构建账号token登录操作----------抛出异常

2、Realm(重要)

【1】Realm接口

所以,一般在真实的项目中,我们不会直接实现Realm接口,我们一般的情况就是直接继承AuthorizingRealm,能够继承到认证与授权功能。它需要强制重写两个方法

【2】自定义Realm
【2.1】需求
1、自定义Realm,取得密码用于比较
【2.2】实现
【2.2.1】创建项目

shiro01

【2.2.2】定义SecurityService

SecurityService 

package com.itqq.service;
​
public interface SecurityService {String findPasswordByLoginName(String loginName);
}

SecurityServiceImpl

package com.itqq.shiro.service.impl;
​
import com.itheima.shiro.service.SecurityService;
​
/*** @Description:权限服务层*/
public class SecurityServiceImpl implements SecurityService {
​@Overridepublic String findPasswordByLoginName(String loginName) {return "123";}
}
【2.2.3】定义MyRealm
package com.itqq.realm;
​
import com.haogu.service.SecurityService;
import com.haogu.service.impl.SecurityServiceImpl;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
​
public class MyRealm extends AuthorizingRealm {// 授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}
​// 认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String username = (String) token.getPrincipal();SecurityService securityService = new SecurityServiceImpl();String password = securityService.findPasswordByUsername(username);if("".equals(password) || password == null){throw new UnknownAccountException("账号不存在");}return new SimpleAuthenticationInfo(username,password,getName());}
}
​
【2.2.4】编辑shiro.ini
#声明自定义的realm,且为安全管理器指定realms
[main]
myRealm=com.itqq.realm.MyRealm
securityManager.realms=$myRealm
​
##[users]
##zhangSan=123

3、散列算法-SimpleHash

散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“hello123”,产生的散列值是“be3ee20eac72da4ef6b233814bb61e67afab7af1”,可以到一些md5解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如salt(即盐);这样散列的对象是“密码+salt”,这样生成的散列值相对来说更难破解。

shiro支持的散列算法:

Md2Hash、Md5Hash、Sha1Hash、Sha256Hash、Sha384Hash、Sha512Hash

  @Testpublic void m1() {// algorithmName 加密方式:md5,md2,sha-1等// source 加密内容// salt 盐// hashIterations 加密次数String password = new SimpleHash("md5","hello123","dvfdhj",512).toHex();System.out.println(password);}

4、Realm使用散列算法

上面我们了解散列算法,那么在realm中怎么使用?在realm02中我们使用的密码是明文的校验方式,也就是SecurityServiceImpl中findPasswordByUsername返回的是明文123的密码

【1】新建项目

shiro01/MD503

【2】创建密文密码
password:c8bb39206310191b78d22680243afc42
salt:dvfdhj
原密码:hello123
【3】修改SecurityService

SecurityService修改成返回salt和password的map

package com.itqq.service.impl;
​
import com.itqq.service.SecurityService;
​
public class SecurityServiceImpl implements SecurityService {@Overridepublic String findPasswordByUsername(String username) {return "c8bb39206310191b78d22680243afc42";}
}
【4】指定密码匹配方式

为MyRealm类添加构造方法如下:

public MyRealm(){//指定密码匹配方式为md5HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("md5");//指定密码迭代次数matcher.setHashIterations(512);//使用父亲方法使匹配方式生效setCredentialsMatcher(matcher);}

修改MyRealm类的认证doGetAuthenticationInfo方法如下

 // 认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String username = (String) token.getPrincipal();SecurityService securityService = new SecurityServiceImpl();String password = securityService.findPasswordByUsername(username);if("".equals(password) || password == null){throw new UnknownAccountException("账号不存在");}// 第1个参数:账号// 第2个参数:密码// 第3个参数:盐// 第4个参数:real的名字return new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes("dvfdhj"),getName());}
//    认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {UsernamePasswordToken token1 = (UsernamePasswordToken) token;String username = token1.getUsername();if(!StringUtils.hasText(username)){throw new AuthenticationException("用户名不能为空");}String password = new String(token1.getPassword());if(!StringUtils.hasText(password)){throw new AuthenticationException("密码不能为空");}User user = securityService.findByUsername(username);if (user == null){return null;}// 密码判断:shiro框架自行判断--与用户输入的账号和密码进行对比,验证token是否合法// user.getId(),是shiro框架提供的可以改变的值,一般是id//        原本写是username/phone --------> idreturn new SimpleAuthenticationInfo(user.getId(),user.getPassword(), ByteSource.Util.bytes("adagag"),getName());}

5、用户授权

【1】基本流程

1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager。

2、SecurityManager接着会委托给内部组件Authorizer;

3、Authorizer再将其请求委托给我们的Realm去做;Realm才是真正干活的

4、Realm将用户请求的参数封装成权限对象。再从我们重写的doGetAuthorizationInfo方法中获取从数据库中查询到的权限集合。

5、Realm将用户传入的权限对象,与从数据库中查出来的权限对象,进行一一对比。如果用户传入的权限对象在从数据库中查出来的权限对象中,则返回true,否则返回false。

进行授权操作的前提:用户必须通过认证。

在真实的项目中,角色与权限都存放在数据库中。

好了,接下来,我们要重写我们本小节的核心方法了。在MyRealm中找到下列方法:

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;
}

此方法的传入的参数PrincipalCollection principals,是一个包装对象,它表示"用户认证凭证信息"。包装的是谁呢?没错,就是认证doGetAuthenticationInfo()方法的返回值的第一个参数username。你可以通过这个包装对象的getPrimaryPrincipal()方法拿到此值,然后再从数据库中拿到对应的角色和资源,构建SimpleAuthorizationInfo。

/*** @Description 授权方法*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {//拿到用户认证凭证信息String loginName = (String) principals.getPrimaryPrincipal();//从数据库中查询对应的角色和资源SecurityService securityService = new SecurityServiceImpl();List<String> roles = securityService.findRoleByloginName(loginName);List<String> permissions = securityService.findPermissionByloginName(loginName);//构建资源校验SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();authorizationInfo.addRoles(roles);authorizationInfo.addStringPermissions(permissions);return authorizationInfo;
}
【2】案例演示
【2.1】需求
1、实现doGetAuthorizationInfo方法实现鉴权
2、使用subject类实现权限的校验
【2.2】实现
【2.2.1】创建项目

shiro01/authentication-realm04

【2.2.2】编写SecurityService(测试功能使用的是假数据)

在SecurityService中添加

package com.itqq.service;
​
import java.util.List;
​
public interface UserService {String findPasswordByUsername(String username);List<String> findRolesByUsername(String username);List<String> findPermissionByUsername(String username);
}
​

SecurityServiceImpl添加实现

package com.itqq.service.impl;
​
import com.itqq.service.UserService;
​
import java.util.ArrayList;
import java.util.List;
​
public class UserServiceImpl implements UserService {
​
​
​@Overridepublic String findPasswordByUsername(String username) {if ("admin".equals(username)){return "4d5233a24fd625a06437778c4ed76969"; // 123}else if("zhangSan".equals(username)){return "a7b19b47e46ce6ca9860c326db041859"; // 1234}else if("liSi".equals(username)){return "a7b19b47e46ce6ca9860c326db041859"; // 1234}return null;}
​@Overridepublic List<String> findRolesByUsername(String username) {List<String> roles = new ArrayList<>();roles.add("普通用户");if("admin".equals(username)){roles.add("管理员");roles.add("VIP用户");}else if("liSi".equals(username)){roles.add("VIP用户");}return roles;}
​@Overridepublic List<String> findPermissionByUsername(String username) {List<String> permissions = new ArrayList<>();permissions.add("comment:select"); // 评论表的查询权限permissions.add("comment:insert"); // 评论表的添加权限if("admin".equals(username)){
//            permissions.add("user:select");
//            permissions.add("user:insert");
//            permissions.add("user:update");
//            permissions.add("user:delete");permissions.add("*:*"); // 所有表的所有权限}if("liSi".equals(username) ){
//            permissions.add("comment:update");
//            permissions.add("comment:delete");permissions.add("comment:*"); // 评论表的所有权限}return permissions;}
}
【2.2.3】编写MyRealm

在MyRealm中修改doGetAuthorizationInfo方法如下

package com.itqq.realm;
​
import com.itqq.service.SecurityService;
import com.itqq.service.impl.SecurityServiceImpl;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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 java.util.List;
​
public class MyRealm extends AuthorizingRealm {
​
​public MyRealm(){//指定密码匹配方式为md5HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("md5");//指定密码迭代次数matcher.setHashIterations(512);//使用父亲方法使匹配方式生效setCredentialsMatcher(matcher);}
​// 授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {//拿到账号String username = (String) principals.getPrimaryPrincipal();// SecurityServiceImpl()不在ioc容器中所以要new一个对象,不能注入SecurityService securityService = new SecurityServiceImpl();// 获取角色List<String> roles = securityService.findRoleByUsername(username);// 获取权限List<String> permissions = securityService.findPermissionByUsername(username);SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();authorizationInfo.addRoles(roles);authorizationInfo.addStringPermissions(permissions);return authorizationInfo;}
​// 认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String username = (String) token.getPrincipal();SecurityService securityService = new SecurityServiceImpl();String password = securityService.findPasswordByUsername(username);if("".equals(password) || password == null){throw new UnknownAccountException("账号不存在");}return new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes("dvfdhj"),getName());}
}
【2.2.4】编写App
package com.itqq;
​
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
​
​
public class App 
{public static void main( String[] args ) {Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);Subject subject = SecurityUtils.getSubject();try {subject.login(new UsernamePasswordToken("zhangSan","1234"));}catch (IncorrectCredentialsException e){System.out.println("密码错误");}catch (UnknownAccountException e){System.out.println("账号不存在");}System.out.println(subject.isAuthenticated());System.out.println("管理员:"+ subject.hasRole("管理员"));System.out.println("普通用户:"+ subject.hasRole("普通用户"));System.out.println("VIP用户:"+ subject.hasRole("VIP用户"));// has是返回boolean值,check抛出异常
//        subject.checkRole("管理员"); // UnauthenticatedExceptionSystem.out.println("用户表添加:"+subject.isPermitted("user:insert"));System.out.println("评论表的查询:"+subject.isPermitted("comment:select"));// UnauthorizedException: Subject does not have permission [comment:update]subject.checkPermission("comment:update");}
}
​
【3】小结

1、鉴权需要实现doGetAuthorizationInfo方法
2、鉴权使用门面subject中方法进行鉴权
    以check开头的会抛出异常
    以is和has开头会返回布尔值

总结:

Shiro框架的几个核心组件:

  1. Subject - 这是Shiro的安全主体,代表了应用程序用户。它封装了所有与安全操作相关的方法,如登录、登出、权限检查等。

  2. Realms - Realm是Shiro与应用数据源(如数据库)交互的模块,用于获取用户信息和角色权限。你可以定义多个Realm来从不同的数据源获取信息。

  3. SecurityManager - 它是Shiro的核心,负责管理Subject、Realms和其他安全相关的组件。它是Shiro的“大脑”,协调所有的安全操作。

  4. Session Manager - 用于会话管理和状态跟踪,可以控制用户的会话生命周期,并提供跨域会话管理能力。

  5. Caching - Shiro提供了缓存机制,可以提高安全性操作的性能,如缓存认证和授权结果。

  6. Cryptography - 提供加密和散列功能,用于保护敏感数据。

Shiro的设计使得它可以轻松集成到任何Java应用中,无论是Web应用还是独立的Java应用。它的灵活性和可扩展性使得它成为企业级应用安全解决方案的首选。

 


总结结束!!! 

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

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

相关文章

HormonyOs之 路由简单跳转

Navigation路由相关的操作都是基于页面栈NavPathStack提供的方法进行&#xff0c;每个Navigation都需要创建并传入一个NavPathStack对象&#xff0c;用于管理页面。主要涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能。 Entry Component struct Index …

八月超市营销活动规划

八月骄阳似火&#xff0c;尽管多地仍笼罩在炎热的夏意之中&#xff0c;但随着立秋节气的到来&#xff0c;以及七夕节、中元节等传统节日的相继而至&#xff0c;为商家们提供了极佳的营销契机。巧妙地根据这些节日节气规划每一周期的营销活动&#xff0c;不仅能营造浓厚的节日氛…

Java面试篇类加载过程详解(吊打面试官)

类加载过程详解 文章目录 类加载过程详解类的生命周期类加载过程1.加载2.验证3.准备4.解析5.初始化6.类卸载 类的生命周期 类从被加载到虚拟机内存中开始到卸载内存为止&#xff0c;它的整个生命周期简单概括加载-验证-准备-解析-初始化-使用-卸载 验证、准备、解析这三个阶段…

使用ESP32搭建简单的WiFi控制LED的Web服务器

在这篇博客中&#xff0c;我将展示如何使用ESP32或ESP8266开发板通过WiFi搭建一个简单的Web服务器&#xff0c;并使用它来控制一个LED的开关。首先确保esp32与自己的手机或者电脑在同一WiFi环境下 效果展示 led 项目准备 在开始之前&#xff0c;请确保你已经准备好以下材料&a…

【HarmonyOS】关于鸿蒙消息推送的心得体会(二)

【HarmonyOS】关于鸿蒙消息推送的心得体会&#xff08;二&#xff09; 前言 推送功能的开发与传统功能开发还是有很大区别。首先最大的区别点就在于需要多部门之间的协同&#xff0c;作为鸿蒙客户端开发&#xff0c;你需要和产品&#xff0c;运营&#xff0c;以及后台开发一起…

golang语言 .go文件版本条件编译,xxx.go文件指定go的编译版本必须大于等于xxx才生效的方法, 同一个项目多个go版本文件共存方法

在go语言中&#xff0c;我们不关是可以在编译时指定版本&#xff0c; 在我们的xxx.go文件中也可以指定go的运行版本&#xff0c;即 忽略go.mod中的版本&#xff0c;而是当当前的go运行版本达到指定条件后才生效的xxx.go文件。 方法如下&#xff1a; 我们通过在xxx.go文件的头部…

04 HTML CSS JavaScript

文章目录 HTML1、HTML介绍2、快速入门3、基础标签4、图片、音频、视频标签5、超链接标签6、列表标签7、表格标签8、布局标签9、 表单标签 CSS1、 概述2、 css 导入方式3、 css 选择器4、 css 属性 JavaScript1、JavaScript简介2、JavaScript引入方式3、JavaScript基础语法4、Ja…

Potree在web端显示大型点云模型文件

一、克隆项目代码&#xff08;准备好上网工具&#xff0c;得先有node.js npm 环境&#xff09; git clone https://github.com/potree/potree.git二、依赖安装&#xff08;换淘宝镜像能快一些&#xff09; cd potree npm install三、运行 npm start四、使用样例 打开浏览器…

【Spring】SpringRetry重试机制和Spring异步任务发送操作结合应用场景实操,通俗易懂

平时调用一些第三方接口或者内部接口&#xff0c;可能出现处理异常或者超时或者意外因素&#xff0c;我们可以使用重试机制来为用户提高体验。 1.引用依赖 <dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</a…

单片机学习(18)--红外遥控器

红外遥控器 17.1红外遥控的基础知识1.红外遥控简介2.硬件电路3.基本发送和接收4.NEC编码5.遥控器键码6.51单片机的外部中断7.外部中断寄存器 17.2红外遥控的程序代码1.红外遥控&#xff08;1&#xff09;工程目录&#xff08;2&#xff09;main.c函数&#xff08;3&#xff09;…

vue 实战 tab标签页+el-card+流式布局+异步接口调用

<template><div><!-- 布局按钮 --><el-button click"dialogVisible true">布局配置查看</el-button><!-- 布局配置对话框 --><el-dialog :visible.sync"dialogVisible" title"布局配置查看" width"…

Invalid bound statement (not found)

Invalid bound statement (not found) 首先申明的是这个错误一般是使用mybatis方法没有找到或者参数不匹配等原因造成的&#xff01; 原本项目是使用eclipse运行&#xff0c;导入到idea之后&#xff0c;项目启动就报错 …Invalid bound statement (not found)… 解决办法&#…

Python 爬虫(爬取百度翻译的数据)

前言 要保证爬虫的合法性&#xff0c;可以从以下几个方面着手&#xff1a; 遵守网站的使用条款和服务协议&#xff1a;在爬取数据之前&#xff0c;仔细阅读目标网站的相关规定。许多网站会在其 robots.txt 文件中明确说明哪些部分可以爬取&#xff0c;哪些不可以。 例如&…

中电金信:AI数据服务

01 方案简介 AI数据服务解决方案为泛娱乐、电子商务、交通出行等行业提供数据处理、数据分析、AI模型训练等服务&#xff0c;通过自主研发的IDSC自动化数据服务平台与客户业务流程无缝衔接&#xff0c;实现超低延时的实时数据处理支持。 02 应用场景 智能医疗&#xff1a; 通…

深入浅出mediasoup—通信框架

libuv 是一个跨平台的异步事件驱动库&#xff0c;用于构建高性能和可扩展的网络应用程序。mediasoup 基于 libuv 构建了包括管道、信号和 socket 在内的一整套通信框架&#xff0c;具有单线程、事件驱动和异步的典型特征&#xff0c;是构建高性能 WebRTC 流媒体服务器的重要基础…

《javaEE篇》--单例模式详解

目录 单例模式 饿汉模式 懒汉模式 懒汉模式(优化) 指令重排序 总结 单例模式 单例模式属于一种设计模式&#xff0c;设计模式就好比是一种固定代码套路类似于棋谱&#xff0c;是由前人总结并且记录下来我们可以直接使用的代码设计思路。 单例模式就是&#xff0c;在有…

升级python版本

参考 https://blog.51cto.com/u_15579956/10397535 python3 main.py

聚焦保险行业客户经营现状,概述神策数据 CJO 解决方案

触点红利时代&#xff0c;企业的经营需求从「深度的用户行为分析」转变为「个性化、全渠道一致的客户体验」。客户旅程编排&#xff08;Customer Journey Orchestration&#xff0c;简称 CJO&#xff09;从体验出发&#xff0c;关注客户需求、感受和满意度&#xff0c;能够帮助…

HarmonyOS Next系列之地图组件(Map Kit)使用(九)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现&#xff08;一&#xff09; HarmonyOS Next 系列之验证码输入组件实现&#xff08;二&#xff09; HarmonyOS Next 系列之底部标签栏TabBar实现&#xff08;三&#xff09; HarmonyOS Next 系列之HTTP请求封装和Token…

「运费速查神器」精明买家必备!一键查询1688供应商发货费用

对于从事跨境买家还是国内电商买家&#xff0c;在选品时&#xff0c;需要全面考虑商品成本&#xff0c;发货费用是供应链成本的重要组成部分。 原来如果我们在1688选品看供应商发货运费&#xff0c;需要一个个单独点击到商品的详情页去查看&#xff0c;再选择具体的收货地、再…