Liferay使用第三方权限系统控制Portlet权限问题记录

本文主要是介绍Liferay使用第三方权限系统控制Portlet权限问题记录,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

更多最新文章欢迎大家访问我的个人博客😄:豆腐别馆

Liferay使用第三方权限系统控制Portlet权限问题记录

前言:在无法彻底摸透一个框架或技术之前,相关环境、框架等请务必选择与其相对应的版本!!!版本对不上号可能会出现各种本不该出现的异常,徒劳伤神。
在Liferay6.2 CE版中,如使用集成了Liferay ide7版的Eclipse,即使JDK、JRE都为7,也都将无法正常生成自定义外部接口的WebService客户端jar!

本文概要

  1. 使用第三方权限系统控制Portlet权限
  2. 调用对外接口时报Authenticated access required身份验证异常
  3. 服务端与客户端未在同一机器上调用对外接口出现的WebService 403问题

一、第三方权限系统控制Portlet权限

主要涉及到的技术点有:

  • 如何发布使用Liferay自带对外接口
  • 当自带对外接口不够用时如何发布自定义的对外接口
  • Liferay如何使用原生SQL语句查询
  • 调用权限相关接口时注意actionids与bitwisevalue之间的位与关系运算

1、发布调用Liferay自带对外接口,这个网上有相关资料,不做过多描述,基本流程为:下载Liferay客户端jar,下其对应版本的客户端jar即可下载链接,有如下jar包:
这里写图片描述

2、当自带对外接口不够用时,可编写自定义对外接口,可参考该篇博文:
Liferay自定义对外接口,但里面内容不知是版本问题还是其它,6.25版来说,里面的内容有误,需:
(1)将文章里导包的说明替换成上面链接下载下来的jar
(2)当执行到build-client时,若正常执行完毕,在docroot/WEB-INF下应生成client文件夹,包含xxx-portlet-client.jar和namespace-mapping.properties文件。而非portal-client.jar,复制xxx-portlet-client.jar到第三方系统,与调用Liferay自带外部接口一样调用即可。

3、这里主要是涉及到Liferay资源权限表(Resourcepermission)里的actionids与资源动作表(resourceaction)里的bitwisevalue的位与关系的判断与控制。

参考这篇博客里对于Liferay权限体系的介绍Liferay权限体系简介,

以获取对某一Portlet拥有查看权限的角色ID为例,自定义SQL查询
(1)在xxx.service.persistence里面新建xxxFinderImpl方法,继承自BasePersistenceImpl类。此处的命名必须是xxxFinderImpl(xxx为实体)
(2)执行service builder,此时会在service包的xxx.service.persistence下面生成xxxFinder的接口类和对应的xxxFinderUtil类。
(3)让xxxFinderImpl继承xxxFinder类,在此类中编写代码,如下:

public class ExternalPrivilegeFinderImpl extends BasePersistenceImpl<ResourcePermission> implements
ExternalPrivilegeFinder {@SuppressWarnings("unchecked")public List<String> getRoleIdHaveViewPermission(String name, int scope,String primkey) {List<String> list = new ArrayList<String>();StringBuffer sql = new StringBuffer("SELECT r.roleId FROM resourcepermission r WHERE r.name = '"+ name + "' ");if (scope != 0) {sql.append("AND r.scope = " + scope + " ");}if (primkey != null) {sql.append("AND r.primkey = '" + primkey + "' ");}sql.append("AND r.actionIds&1 = 1 GROUP BY r.roleId");Session session = null;try {List<BigInteger> blist = openSession().createSQLQuery(sql.toString()).list();for (BigInteger b : blist) {list.add(b.toString());}} catch (Exception e) {e.printStackTrace();} finally {closeSession(session);}return list;}
}

(4)执行ServiceBuilder,现在会在xxxFinderUtil里面生成相应的接口,但是我们不能直接调用xxxFinderUtil方法,需要将我们的这个方法添加到xxxLocalServiceImpl里面。我们在xxxLocalServiceImpl里面添加相应的方法,在xxxLocalServiceImpl里面使用xxxFinder.xxx()进行调用。
(5)再次执行build-service,现在就可以通过xxxLocalServiceUtil类调用自定义的查询类了,至此自定义查询完毕

参考博文:ServiceBuilder自定义SQL查询

(6)接下来要在第三方系统使用该接口,就跟上面提到的使用Liferay自带对外接口方法一致:build-wsdd -> deploy -> 启动Tomcat -> build-client
(7)上面的几个步骤都没问题之后,拿到生成的客户端xxx-portlet-client.jar导入第三方系统即可。

依旧以获取对某一Portlet拥有查看权限为例,由于调用相关权限接口时其scope、primKey等参数将需要动态赋值,特别当某个Portlet被多次拖拽到相同或不同页面上显示之后,其primkey将会出现变化,即在ResourcePermission表中将会出现多条不同primKey的数据,因此需要获取ResourcePermission下拥有查看权限的集合后进行遍历调用。集合获取代码如下:

/*** 获取资源查看权限列表* * @param name* @return*/
@SuppressWarnings({ "unchecked", "static-access" })
public List<ResourcePermission> getResourcePermission(String name, Long roleId) {List<ResourcePermission> list = new ArrayList<ResourcePermission>();StringBuffer sql = new StringBuffer("SELECT r.* FROM ResourcePermission r WHERE r.name = '" + name + "' ");if(roleId != null) {sql.append("AND r.roleId = " + roleId + " ");}sql.append("AND r.actionIds&1 = 1");Session session = null;try {List<Object> oList = openSession().createSQLQuery(sql.toString()).list();for(int x = 0; x < oList.size(); x ++) {Object[] obj = (Object[]) oList.get(x);BigInteger bResourcePermissionId = (BigInteger)obj[0];BigInteger bCompanyId = (BigInteger) obj[1];BigInteger bRoleId = (BigInteger) obj[5];BigInteger bOwnerId = (BigInteger) obj[6];BigInteger bActionIds = (BigInteger) obj[7];ResourcePermission r = new ResourcePermissionUtil().create(bResourcePermissionId.longValue());r.setCompanyId(bCompanyId.longValue());r.setName(obj[2].toString());r.setScope((Integer) obj[3]);r.setPrimKey(obj[4].toString());r.setRoleId(bRoleId.longValue());r.setOwnerId(bOwnerId.longValue());r.setActionIds(bActionIds.longValue());list.add(r);}} catch (Exception e) {e.printStackTrace();} finally {closeSession(session);}return list;
}

此处只需要注意下面两个点:
(1)Hibernat执行含有查询条件的原生SQL返回列表时,返回的是一个List<Object>
(2)注意代码中部分参数为long类型但数据库为BigInteger类型之间的转换。

也有疑问:在此无法使用Hibernate的addEntity()方法自动进行对象的封装,提示找不到ResourcePermission实体,因Liferay中对Hibernate进行了封装,暂未去找其映射文件/注解,因此直接采用暴力添加进List<ResourcePermission> ,如有更好解决方法者,请告诉我,O(∩_∩)O谢谢。

第三方权限系统调用测试代码(此处原返回的List<xxx>将会默认被封装成返回一个xxx实体类型的数组):

/*** 获取资源权限角色* * @param name* @return*/
public void getResourcePermission(String name, Long roleId) {try {ExternalPrivilegeServiceSoapServiceLocator privilegeLocaltor = new ExternalPrivilegeServiceSoapServiceLocator();ExternalPrivilegeServiceSoap service = privilegeLocaltor.getPlugin_t_ExternalPrivilegeService(APIUtil.getPortalURLForAddress(privilegeLocaltor.getPlugin_t_ExternalPrivilegeServiceAddress()));((Stub) service)._setProperty(Call.USERNAME_PROPERTY, USERNAME);((Stub) service)._setProperty(Call.PASSWORD_PROPERTY, PASSWORD);ResourcePermissionSoap[] resourcePermissionSoaps = service.getResourcePermission(name, roleId);for (ResourcePermissionSoap r : resourcePermissionSoaps) {System.out.println(r.getName());}} catch (ServiceException | RemoteException e) {e.printStackTrace();}
}

第三方系统系统调用:

/*** * @Title: bindbing   * @Description: TODO(绑定门户角色权限)   * @param: @param model* @param: @param portletId* @param: @param roleId* @param: @return      * @return: String      * @throws*/
@RequestMapping(value="/binding",method=RequestMethod.POST )
@ResponseBodypublic JsonVo  bindbing(String portletId,String ident,long companyId,String roleAlias){JsonVo vo = new JsonVo();try {LiferayUitl liferayUitl = new LiferayUitl();Setting setting = SystemUtils.getSetting();RoleSoap soap = liferayUitl.getRoleId(roleAlias, companyId);ResourcePermissionSoap[] privilege = liferayUitl.getPrivilege(portletId, soap.getRoleId());if (ident.equals("add")) {liferayUitl.addResourcePermission(liferayUitl.GROUPID, companyId,portletId, setting.getScope(), setting.getPrimKey(),soap.getRoleId(), liferayUitl.ACTIONID);} else {for (ResourcePermissionSoap resourcePermissionSoap : privilege) {liferayUitl.removeResourcePermission(liferayUitl.GROUPID, resourcePermissionSoap.getCompanyId(),portletId, resourcePermissionSoap.getScope(),soap.getRoleId(), liferayUitl.ACTIONID);}}vo.setSuccess(true);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();vo.setSuccess(false);}return vo;}

二、调用对外接口时报Authenticated access required身份验证异常

异常如下:

AxisFaultfaultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userExceptionfaultSubcode: faultString: java.rmi.RemoteException: Authenticated access requiredfaultActor: faultNode: faultDetail: {http://xml.apache.org/axis/}hostname:PC-201606131336java.rmi.RemoteException: Authenticated access requiredat org.apache.axis.message.SOAPFaultBuilder.createFault(SOAPFaultBuilder.java:222)at org.apache.axis.message.SOAPFaultBuilder.endElement(SOAPFaultBuilder.java:129)at org.apache.axis.encoding.DeserializationContext.endElement(DeserializationContext.java:1087)at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:609)at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1782)at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2973)at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606)at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:117)at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510)at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848)at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777)at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213)at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:648)at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.parse(SAXParserImpl.java:332)at org.apache.axis.encoding.DeserializationContext.parse(DeserializationContext.java:227)at org.apache.axis.SOAPPart.getAsSOAPEnvelope(SOAPPart.java:696)at org.apache.axis.Message.getSOAPEnvelope(Message.java:435)at org.apache.axis.handlers.soap.MustUnderstandChecker.invoke(MustUnderstandChecker.java:62)at org.apache.axis.client.AxisClient.invoke(AxisClient.java:206)at org.apache.axis.client.Call.invokeEngine(Call.java:2784)at org.apache.axis.client.Call.invoke(Call.java:2767)at org.apache.axis.client.Call.invoke(Call.java:2443)at org.apache.axis.client.Call.invoke(Call.java:2366)at org.apache.axis.client.Call.invoke(Call.java:1812)at com.liferay.client.soap.portal.service.http.Portal_ResourcePermissionServiceSoapBindingStub.removeResourcePermission(Portal_ResourcePermissionServiceSoapBindingStub.java:250)at com.test.Test.removeResourcePermission(Test.java:112)at com.test.Test.main(Test.java:376)

出现该异常的主要原因是Liferay本身对外接口对以邮箱地址及密码的验证方式的支持不友好(不知7版该问题是否得到解决),解决该问题的方法有二:

1、修改Liferay默认认证方式,将用户认证方式更改为除邮件地址外的方式,同时调用代码处传入相对应的参数即可解决。具体流程:管理 -> 控制面板 -> Portal设置 -> 认证:

这里写图片描述

如图所示设置为通过屏幕名称认证,但若使用此种方式将会导致无法跟原先一样以邮箱登录,即将会更改为与所选认证方式一致的参数作为登录认证账号。这显然不是我想要的。因此未采用此种方式,而是使用方式2。

2、在方法调用之前提前进行身份验证:

((Stub) service)._setProperty(Call.USERNAME_PROPERTY, USERNAME);
((Stub) service)._setProperty(Call.PASSWORD_PROPERTY, PASSWORD);

Liferay的对外接口其底层其实就是WebService,Java调用WebService时,服务端如需要client客户端进行授权验证,那么这时只需要在client端提供用户名和密码即可,其实这个异常就是WebService的401异常(unauthorized未授权错误。)
调用代码示例:

/*** 添加权限* * @param groupId* @param companyId* @param name* @param scope* @param primKey* @param roleId* @param actionId* @return*/
public boolean addResourcePermission(long groupId, long companyId,String name, int scope, String primKey, long roleId, String actionId) {try {ResourcePermissionServiceSoapService localtor = new ResourcePermissionServiceSoapServiceLocator();ResourcePermissionServiceSoap service = localtor.getPortal_ResourcePermissionService(APIUtil.getPortalURLForAddress(localtor.getPortal_ResourcePermissionServiceAddress()));((Stub) service)._setProperty(Call.USERNAME_PROPERTY, USERNAME);((Stub) service)._setProperty(Call.PASSWORD_PROPERTY, PASSWORD);service.addResourcePermission(groupId, companyId, name, scope, primKey, roleId, actionId);System.out.println("添加成功");return true;} catch (ServiceException | RemoteException e) {e.printStackTrace();}return false;
}

三、服务端与客户端未在同一机器上调用对外接口时出现的WebService 403问题

一开始的服务端与客户端调用都是在我本机子上完成,并未有该异常出现,但是当客户端不在我本机上时,这异常就出来了,如下:

AxisFaultfaultCode: {http://xml.apache.org/axis/}HTTPfaultSubcode: faultString: (403)ForbiddenfaultActor: faultNode: faultDetail: {}:return code:  403
&lt;html&gt;&lt;head&gt;&lt;title&gt;Apache Tomcat/7.0.62 - Error report&lt;/title&gt;&lt;style&gt;&lt;!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--&gt;&lt;/style&gt; &lt;/head&gt;&lt;body&gt;&lt;h1&gt;HTTP Status 403 - Access denied for 192.168.2.117&lt;/h1&gt;&lt;HR size=&quot;1&quot; noshade=&quot;noshade&quot;&gt;&lt;p&gt;&lt;b&gt;type&lt;/b&gt; Status report&lt;/p&gt;&lt;p&gt;&lt;b&gt;message&lt;/b&gt; &lt;u&gt;Access denied for 192.168.2.117&lt;/u&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;description&lt;/b&gt; &lt;u&gt;Access to the specified resource has been forbidden.&lt;/u&gt;&lt;/p&gt;&lt;HR size=&quot;1&quot; noshade=&quot;noshade&quot;&gt;&lt;h3&gt;Apache Tomcat/7.0.62&lt;/h3&gt;&lt;/body&gt;&lt;/html&gt;{http://xml.apache.org/axis/}HttpErrorCode:403(403)Forbiddenat org.apache.axis.transport.http.HTTPSender.readFromSocket(HTTPSender.java:744)at org.apache.axis.transport.http.HTTPSender.invoke(HTTPSender.java:144)at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:32)at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:118)at org.apache.axis.SimpleChain.invoke(SimpleChain.java:83)at org.apache.axis.client.AxisClient.invoke(AxisClient.java:165)at org.apache.axis.client.Call.invokeEngine(Call.java:2784)at org.apache.axis.client.Call.invoke(Call.java:2767)at org.apache.axis.client.Call.invoke(Call.java:2443)at org.apache.axis.client.Call.invoke(Call.java:2366)at org.apache.axis.client.Call.invoke(Call.java:1812)at com.liferay.client.soap.portal.service.http.Portal_ResourcePermissionServiceSoapBindingStub.addResourcePermission(Portal_ResourcePermissionServiceSoapBindingStub.java:226)at com.test.Test.addResourcePermission(Test.java:92)at com.test.Test.main(Test.java:382)

解决过程稍有点绕,先上解决方法:修改Liferay portal-tomcat下portal-setup-wizard.properties文件,在里面加上相应配置即可:

axis.servlet.hosts.allowed = 允许访问的客户端IP(以逗号分隔,为空则全部允许)
axis.servlet.https.required = false

解决思路:
上网一查,发现还真有人遇到类似的,但网上所提供的方法都是在tomcat\webapps\ROOT\WEB-INF\classes\portal-ext.properties文件中设置axis.servlet.hosts.allowed的值,一开始还有点小确幸这么快找到解决方法,但翻开我的tomcat一看,醉了,压根没有portal-ext.properties这个配置文件。总不能我新建一个上去吧(咳咳,我还真新建加上去了,但是没用- -),不知道是因为这网上复制来复制去的解决方法没经过验证还是我姿势不对,总之都不是一个好消息,那我只能回到异常本身慢慢看。
此处报的异常虽是在调用Liferay对外接口时所报,但实则依旧是WebService的范畴。而403异常,我们都知道是服务端拒绝了客户端访问,即客户端缺少相应访问权限,由此得出必定是服务端做了相应权限控制,而之前的一番查找也不是一无所获,至少我知道了在Liferay对外接口中有这么一个参数在控制着客户端权限,翻开Liferay源码一看,果然,在每一个对外的接口的xxxServiceSoap中都有这么一个注释:

You can see a list of services at http://localhost:8080/api/axis.
Set the property <b>axis.servlet.hosts.allowed</b> in portal.properties to configure security.

源码注释已经明白地说明我可以在portal.properties文件中配置axis.servlet.hosts.allowed参数来控制安全权限,还是找源码,发现在源码src下的portal.properties文件有这么一个配置:

##
## Axis Servlet
#### See the properties "main.servlet.hosts.allowed" and# "main.servlet.https.required" on how to protect this servlet.#axis.servlet.hosts.allowed=127.0.0.1,SERVER_IPaxis.servlet.https.required=false

二话不说就上Tomcat里面去找这个portal.properties配置文件去了,嘿,一找同名的文件倒不少,慢慢排除掉一些Tomcat里面portlet下的、编译后生成的、缓存下的等等文件夹下的properties,最后排除只剩下三个都在Tomcat下,文件名一致、内容一致的properties,由于不确定那只能一个个试了,但是,试了个遍,包括全部添上去,还是不行,在清除完Tomcat缓存依旧不行后无奈下就再到网上去翻找了,少有的几个帖子里(基本都一样内容的帖子- -)依旧都提及到了Tomcat下portal-ext.properties这个配置文件,啊,几个帖子总不能都空穴来风,想想我的Tomcat没有,源码上可能会有,一找源码,发现在源码src\tools\db-upgrade文件夹下还真有这么个properties,
内容如下:

jdbc.default.jndi.name=jdbc.default.driverClassName=com.mysql.jdbc.Driver
jdbc.default.url=jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
jdbc.default.username=
jdbc.default.password=

嘿,JDBC配置!这不是我portal-tomcat下的portal-setup-wizard.properties文件里该有的内容嘛,OK,往里加上客户端配置,一试果然异常没有了。

时间与资历有限,难免有局限之处,请谅解指导。在此感谢IT人生录里相关资料对我的帮助及博主的答疑。

这篇关于Liferay使用第三方权限系统控制Portlet权限问题记录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

springboot中使用okhttp3的小结

《springboot中使用okhttp3的小结》OkHttp3是一个JavaHTTP客户端,可以处理各种请求类型,比如GET、POST、PUT等,并且支持高效的HTTP连接池、请求和响应缓存、以及异... 在 Spring Boot 项目中使用 OkHttp3 进行 HTTP 请求是一个高效且流行的方式。

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1

Vue3绑定props默认值问题

《Vue3绑定props默认值问题》使用Vue3的defineProps配合TypeScript的interface定义props类型,并通过withDefaults设置默认值,使组件能安全访问传入的... 目录前言步骤步骤1:使用 defineProps 定义 Props步骤2:设置默认值总结前言使用T

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有