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

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

mybatis和mybatis-plus设置值为null不起作用问题及解决

《mybatis和mybatis-plus设置值为null不起作用问题及解决》Mybatis-Plus的FieldStrategy主要用于控制新增、更新和查询时对空值的处理策略,通过配置不同的策略类型... 目录MyBATis-plusFieldStrategy作用FieldStrategy类型每种策略的作

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本