shiro 再次通过源码谈谈登录的流程,之前理解的不是很清楚!

2024-08-21 00:32

本文主要是介绍shiro 再次通过源码谈谈登录的流程,之前理解的不是很清楚!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

PrincipalCollection这个可以理解为当事人的信息!昨天在授权信息检查的时候,一直在传递这个信息,当时不是很理解,所以今天继续说说这个设计的意思到底是什么回事。以及登录流程之前疏忽的一些重要的信息,都统统的补齐。

subject.login(token);这个是今天的主要的角色,刚刚断点跟踪了一会才理解了到时是在做什么。

protected void login(String configFile, String username, String password) {//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManagerFactory<org.apache.shiro.mgt.SecurityManager> factory =new IniSecurityManagerFactory(configFile);//2、得到SecurityManager实例 并绑定给SecurityUtilsorg.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken(username, password);subject.login(token);}

(1)
DelegatingSubject这个是Subject的实现类,主要的成员函数如下,一个是当前的管家的引用,一个是PrincipalCollection 当事人的信息,具体代表什么意思细细的道来。

    protected PrincipalCollection principals;protected boolean authenticated;protected String host;protected Session session;protected boolean sessionCreationEnabled;protected transient SecurityManager securityManager;

进入到了Subject的实现类的login方法中,因为我们知道这些验证的信息都是来至于我们的管家的验证。

DelegatingSubject->
public void login(AuthenticationToken token)

这里写图片描述

(2)从这里开始,又开始交给管家去处理了,所以管家的继承图不能少啊!
这里写图片描述

DefaultSecurityManager.login

 public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {AuthenticationInfo info;try {//这里就是给继承图的上面的处理认证信息info = authenticate(token);} catch (AuthenticationException ae) {try {onFailedLogin(token, ae, subject);} catch (Exception e) {if (log.isInfoEnabled()) {}}throw ae;}//根据当前的信息,创造一个Subject信息。Subject loggedIn = createSubject(token, info, subject);onSuccessfulLogin(token, info, loggedIn);return loggedIn;}

AuthenticatingSecurityManager
//成员变量里面的认证的门面哦~
private Authenticator authenticator;
//构造函数里面创建
this.authenticator = new ModularRealmAuthenticator();

AuthenticationInfo 作为返回信息,这里面试啥呢?还是不给力投路,一会看图片就知道了。认证之后,返回呢一个认证信息

 public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {return this.authenticator.authenticate(token);}

ModularRealmAuthenticator的上面呢,还继承了一个主要处理验证成功和失败的监听通知的传送!

 ModularRealmAuthenticator->单个的验证realm在我们处理的时候其实这些信息已经全部的在了。通过配置读取的时候全部都设置好了。reaml处理数据的信息哦,无论是验证还是权限的信息都需要这个的处理哦!这里只配置了原始的默认的,只有一个Realm protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {assertRealmsConfigured();Collection<Realm> realms = getRealms();if (realms.size() == 1) {return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);} else {return doMultiRealmAuthentication(realms, authenticationToken);}}

这里继续,发现是realm.getAuthenticationInfo(token); 这里返回的才是当前处理的重点,就是当前客户的信息。我们自己定义的时候都必须返回这AuthenticationInfo 的实现类才可以哦。

protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {if (!realm.supports(token)) {throw new UnsupportedTokenException(msg);}AuthenticationInfo info = realm.getAuthenticationInfo(token);if (info == null) {throw new UnknownAccountException(msg);}return info;}

realm又一次进入到了realm的处理的世界,我们知道real的世界中呢,又是一个非常擅长使用template方法,继承结构的。所以还是再次来看看继承图,看这里有认证realm有授权的realm分工的很清楚啊,还有管理缓存的realm哦。这样的设计你说好不好呢?
这里写图片描述

AuthenticatingRealm认证的realm哦,进入到了这里,注意到这个是final方法,必须在这里验证哦~看看有没有缓存,没有缓存直接在进行下一步,查询信息

 public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {AuthenticationInfo info = getCachedAuthenticationInfo(token);if (info == null) {//otherwise not cached, perform the lookup:info = doGetAuthenticationInfo(token);log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);if (token != null && info != null) {cacheAuthenticationInfoIfPossible(token, info);}} else {log.debug("Using cached authentication info [{}] to perform credentials matching.", info);}if (info != null) {assertCredentialsMatch(token, info);} else {log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);}return info;}

我们使用的是默认的IniRealm进行处理哦~,这个不能理解错误了。进入到了SimpleAccountRealm这个是IniRealm的父类的信息哦,这里的信息都是保存在内存中的,有一个users和roles的读写锁哦。具体的users是什么和roles这个Map里面的信息是什么一个是username->SimpleAccount 一个是roleName->SimpleRole


public class SimpleAccountRealm extends AuthorizingRealm {//TODO - complete JavaDocprotected final Map<String, SimpleAccount> users; //username-to-SimpleAccountprotected final Map<String, SimpleRole> roles; //roleName-to-SimpleRoleprotected final ReadWriteLock USERS_LOCK;protected final ReadWriteLock ROLES_LOCK;public SimpleAccountRealm() {this.users = new LinkedHashMap<String, SimpleAccount>();this.roles = new LinkedHashMap<String, SimpleRole>();USERS_LOCK = new ReentrantReadWriteLock();ROLES_LOCK = new ReentrantReadWriteLock();//SimpleAccountRealms are memory-only realms - no need for an additional cache mechanism since we're//already as memory-efficient as we can be:setCachingEnabled(false);}public SimpleAccountRealm(String name) {this();setName(name);}protected SimpleAccount getUser(String username) {USERS_LOCK.readLock().lock();try {return this.users.get(username);} finally {USERS_LOCK.readLock().unlock();}}public void addAccount(String username, String password) {addAccount(username, password, (String[]) null);}public void addAccount(String username, String password, String... roles) {Set<String> roleNames = CollectionUtils.asSet(roles);SimpleAccount account = new SimpleAccount(username, password, getName(), roleNames, null);add(account);}protected String getUsername(SimpleAccount account) {return getUsername(account.getPrincipals());}protected String getUsername(PrincipalCollection principals) {return getAvailablePrincipal(principals).toString();}protected void add(SimpleAccount account) {String username = getUsername(account);USERS_LOCK.writeLock().lock();try {this.users.put(username, account);} finally {USERS_LOCK.writeLock().unlock();}}protected SimpleRole getRole(String rolename) {ROLES_LOCK.readLock().lock();try {return roles.get(rolename);} finally {ROLES_LOCK.readLock().unlock();}}public boolean roleExists(String name) {return getRole(name) != null;}public void addRole(String name) {add(new SimpleRole(name));}protected void add(SimpleRole role) {ROLES_LOCK.writeLock().lock();try {roles.put(role.getName(), role);} finally {ROLES_LOCK.writeLock().unlock();}}protected static Set<String> toSet(String delimited, String delimiter) {if (delimited == null || delimited.trim().equals("")) {return null;}Set<String> values = new HashSet<String>();String[] rolenamesArray = delimited.split(delimiter);for (String s : rolenamesArray) {String trimmed = s.trim();if (trimmed.length() > 0) {values.add(trimmed);}}return values;}protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {UsernamePasswordToken upToken = (UsernamePasswordToken) token;SimpleAccount account = getUser(upToken.getUsername());if (account != null) {if (account.isLocked()) {throw new LockedAccountException("Account [" + account + "] is locked.");}if (account.isCredentialsExpired()) {String msg = "The credentials for account [" + account + "] are expired";throw new ExpiredCredentialsException(msg);}}return account;}protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String username = getUsername(principals);USERS_LOCK.readLock().lock();try {return this.users.get(username);} finally {USERS_LOCK.readLock().unlock();}}
}

这里先别管之前的验证了,先看看当前的user和roles到底是什么东西
这里写图片描述

这里的信息都是之前重Ini文件中获取过来,然后转换的哦!这个过程还是需要了解清楚的。
在来详细的看看user
这里写图片描述
在来详细看看roles对于昨天的学习,这个分段的处理信息Permission这个很好理解了哦~,当前角色对于的权限嘛是不是,这里看起来比user里面的要简单了很多啊,所以想看看roles怎么定义的!
这里写图片描述

SimpleRole 一个角色对应的所有的权限~Permission 这个Permission可以自己去处理,也不一定是系统默认定义的哦,原来那种是分段处理的哈哈,就像看到的这种样子。

/*** A simple representation of a security role that* has a name and a collection of permissions.  This object can be* used internally by Realms to maintain authorization state.*这个是被realms内部使用的,维护状态* @since 0.2*/
public class SimpleRole implements Serializable {protected String name = null;protected Set<Permission> permissions;public SimpleRole() {}public SimpleRole(String name) {setName(name);}public SimpleRole(String name, Set<Permission> permissions) {setName(name);setPermissions(permissions);}public String getName() {return name;}public void setName(String name) {this.name = name;}public Set<Permission> getPermissions() {return permissions;}public void setPermissions(Set<Permission> permissions) {this.permissions = permissions;}public void add(Permission permission) {Set<Permission> permissions = getPermissions();if (permissions == null) {permissions = new LinkedHashSet<Permission>();setPermissions(permissions);}permissions.add(permission);}public void addAll(Collection<Permission> perms) {if (perms != null && !perms.isEmpty()) {Set<Permission> permissions = getPermissions();if (permissions == null) {permissions = new LinkedHashSet<Permission>(perms.size());setPermissions(permissions);}permissions.addAll(perms);}}public boolean isPermitted(Permission p) {Collection<Permission> perms = getPermissions();if (perms != null && !perms.isEmpty()) {for (Permission perm : perms) {if (perm.implies(p)) {return true;}}}return false;}public int hashCode() {return (getName() != null ? getName().hashCode() : 0);}public boolean equals(Object o) {if (o == this) {return true;}if (o instanceof SimpleRole) {SimpleRole sr = (SimpleRole) o;//only check name, since role names should be unique across an entire application:return (getName() != null ? getName().equals(sr.getName()) : sr.getName() == null);}return false;}public String toString() {return getName();}
}

SimpleAccount这个包含了很多的当前账号的信息,包含了用户名,凭证(密码)权限信息,我们看哈英文怎么说的。
contains principal (可以理解为账号)and credential (可以理解为密码)and authorization information(授权的信息) (roles and permissions) as instance variables。这个就是对于账号的信息哦
这里写图片描述

这个SimpleAccount还是比较大复杂的,保存了账号的凭证,密码,还有权限信息,其实这些在接口中都有体现,我们先看看继承的接口的信息把。
AuthenticationInfo 认证信息

public interface AuthenticationInfo extends Serializable {/*** 返回所有的principals和当前的Subject相关的,Each principal is an identifying piece of(唯一的标识)* information useful to the application such as a username, or user id, a given name, etc - anything useful* to the application to identify the current Subject.可以识别当前的Subject的* The returned PrincipalCollection should not contain any credentials used to verify principals, such* as passwords, private keys, etc.  Those should be instead returned by {@link #getCredentials() getCredentials()}.*不应该包含秘密的信息,也就是说Authentication(认证信息)在Subject为啥只有PrincipalCollection 而没得密码*/PrincipalCollection getPrincipals();/*** Returns the credentials associated with the corresponding Subject.  A credential verifies one or more of the* {@link #getPrincipals() principals} associated with the Subject, such as a password or private key.  Credentials* are used by Shiro particularly during the authentication process to ensure that submitted credentials* during a login attempt match exactly the credentials here in the <code>AuthenticationInfo</code> instance.*这个就是密码啦!*/Object getCredentials();}

SaltedAuthenticationInfo 这个是加盐操作的接口,一般不直接通过密码就行比较,增加处理,盐通常由安全的伪随机数生成器生成,因此它们是有效的,盐值应安全存储在账户信息侧,以确保它与帐户的凭据一起维护。

public interface SaltedAuthenticationInfo extends AuthenticationInfo {/*** Returns the salt used to salt the account's credentials or {@code null} if no salt was used.** @return the salt used to salt the account's credentials or {@code null} if no salt was used.*/ByteSource getCredentialsSalt();
}

MergableAuthenticationInfo合并认证信息~一般不会用到吧!

public interface MergableAuthenticationInfo extends AuthenticationInfo {/*** Merges the given {@link AuthenticationInfo} into this instance.  The specific way* that the merge occurs is up to the implementation, but typically it involves combining* the principals and credentials together in this instance.  The <code>info</code> argument should* not be modified in any way.** @param info the info that should be merged into this instance.*/void merge(AuthenticationInfo info);}

Account账号的信息包含认证的信息和授权的信息

public interface Account extends AuthenticationInfo, AuthorizationInfo {
一个是认证的信息,一个是授权的信息~哈哈写不写都可以
}

AuthorizationInfo 授权信息的集合

//授权信息
public interface AuthorizationInfo extends Serializable {/*** Returns the names of all roles assigned to a corresponding Subject.*/Collection<String> getRoles();Collection<String> getStringPermissions();/*** Returns all type-safe {@link Permission Permission}s assigned to the corresponding Subject.  The permissions* returned from this method plus any returned from {@link #getStringPermissions() getStringPermissions()}* represent the total set of permissions.  The aggregate set is used to perform a permission authorization check.* 返回所有的权限的信息当前的用户拥有的* @return all type-safe {@link Permission Permission}s assigned to the corresponding Subject.*/Collection<Permission> getObjectPermissions();
}

SimpleAccount好复杂啊!
下面的两个成员变量主要的实现了认证和授权这两个接口哦~,代理过去处理

 /*** The authentication information (principals and credentials) for this account.*/private SimpleAuthenticationInfo authcInfo;/*** The authorization information for this account.*/private SimpleAuthorizationInfo authzInfo;

SimpleAuthorizationInfo 这个比较简单,包含了角色信息和权限咯~~

public class SimpleAuthorizationInfo implements AuthorizationInfo {/*** The internal roles collection.*/protected Set<String> roles;/*** Collection of all string-based permissions associated with the account.*/protected Set<String> stringPermissions;/*** Collection of all object-based permissions associaed with the account.*/protected Set<Permission> objectPermissions;}

SimpleAuthenticationInfo 权限验证这个比较复杂啊~但是和还是一样的身份的,盐,密码。 PrincipalCollection 这个身份是一个重点,因为每一个Subject的成员变量中也拥有一个这样的变量。这里面包含了验证的realm,对应的用户的信息!
这里写图片描述

public class SimpleAuthenticationInfo implements MergableAuthenticationInfo, SaltedAuthenticationInfo {/*** The principals identifying the account associated with this AuthenticationInfo instance.*/protected PrincipalCollection principals;/*** The credentials verifying the account principals.*/protected Object credentials;/*** Any salt used in hashing the credentials.** @since 1.1*/protected ByteSource credentialsSalt;/*** Default no-argument constructor.*/public SimpleAuthenticationInfo() {}/*** Constructor that takes in a single 'primary' principal of the account and its corresponding credentials,* associated with the specified realm.* <p/>* This is a convenience constructor and will construct a {@link PrincipalCollection PrincipalCollection} based* on the {@code principal} and {@code realmName} argument.** @param principal   the 'primary' principal associated with the specified realm.* @param credentials the credentials that verify the given principal.* @param realmName   the realm from where the principal and credentials were acquired.*/public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {this.principals = new SimplePrincipalCollection(principal, realmName);this.credentials = credentials;}/*** Constructor that takes in a single 'primary' principal of the account, its corresponding hashed credentials,* the salt used to hash the credentials, and the name of the realm to associate with the principals.* <p/>* This is a convenience constructor and will construct a {@link PrincipalCollection PrincipalCollection} based* on the <code>principal</code> and <code>realmName</code> argument.** @param principal         the 'primary' principal associated with the specified realm.* @param hashedCredentials the hashed credentials that verify the given principal.* @param credentialsSalt   the salt used when hashing the given hashedCredentials* @param realmName         the realm from where the principal and credentials were acquired.* @see org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher* @since 1.1*/public SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) {this.principals = new SimplePrincipalCollection(principal, realmName);this.credentials = hashedCredentials;this.credentialsSalt = credentialsSalt;}/*** Constructor that takes in an account's identifying principal(s) and its corresponding credentials that verify* the principals.** @param principals  a Realm's account's identifying principal(s)* @param credentials the accounts corresponding principals that verify the principals.*/public SimpleAuthenticationInfo(PrincipalCollection principals, Object credentials) {this.principals = new SimplePrincipalCollection(principals);this.credentials = credentials;}/*** Constructor that takes in an account's identifying principal(s), hashed credentials used to verify the* principals, and the salt used when hashing the credentials.** @param principals        a Realm's account's identifying principal(s)* @param hashedCredentials the hashed credentials that verify the principals.* @param credentialsSalt   the salt used when hashing the hashedCredentials.* @see org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher* @since 1.1*/public SimpleAuthenticationInfo(PrincipalCollection principals, Object hashedCredentials, ByteSource credentialsSalt) {this.principals = new SimplePrincipalCollection(principals);this.credentials = hashedCredentials;this.credentialsSalt = credentialsSalt;}public String toString() {return principals.toString();}}

SimplePrincipalCollection继承了 PrincipalCollection
这里写图片描述

因为可能一个用户有很多的唯一标识符比如电话,邮箱等等~处理这个信息的时候做了个心眼SimplePrincipalCollection
SimplePrincipalCollection

  private Map<String, Set> realmPrincipals;一个是realm的名字,一个是对应当前的用户标识的集合。

SimpleAccountRealm主要的都知道了,那么我们返回来啦,继续讲解验证。
这里通过用户的名字获取到SimpleAccountRealm中 protected final Map< String, SimpleAccount> users; 保存的用户的信息,得到SimpleAccount的信息。
这里写图片描述

 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {UsernamePasswordToken upToken = (UsernamePasswordToken) token;SimpleAccount account = getUser(upToken.getUsername());if (account != null) {if (account.isLocked()) {throw new LockedAccountException("Account [" + account + "] is locked.");}if (account.isCredentialsExpired()) {String msg = "The credentials for account [" + account + "] are expired";throw new ExpiredCredentialsException(msg);}}return account;}

得到了这个信息之后,在返回验证realm中去检查当前的的AuthenticationToken 中的密码 和这个获取出来信息的密码一样不~一样就成功了。

AuthenticatingRealm

 public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {AuthenticationInfo info = getCachedAuthenticationInfo(token);if (info == null) {//otherwise not cached, perform the lookup:info = doGetAuthenticationInfo(token);log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);if (token != null && info != null) {cacheAuthenticationInfoIfPossible(token, info);}} else {log.debug("Using cached authentication info [{}] to perform credentials matching.", info);}if (info != null) {//这里就是去检验啦~~看看密码信息一样不!assertCredentialsMatch(token, info);} else {log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);}return info;}

具体的密码的比较~的设计明天再说~~

这篇关于shiro 再次通过源码谈谈登录的流程,之前理解的不是很清楚!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1091611

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【测试】输入正确用户名和密码,点击登录没有响应的可能性原因

目录 一、前端问题 1. 界面交互问题 2. 输入数据校验问题 二、网络问题 1. 网络连接中断 2. 代理设置问题 三、后端问题 1. 服务器故障 2. 数据库问题 3. 权限问题: 四、其他问题 1. 缓存问题 2. 第三方服务问题 3. 配置问题 一、前端问题 1. 界面交互问题 登录按钮的点击事件未正确绑定,导致点击后无法触发登录操作。 页面可能存在

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、