代码示例:快速找到示例代码

1、图表设计器

数据可视化能加载到的接口,方法上需要加 category = FunctionCategoryEnum.QUERY_PAGE

@Model.model(ExpensesIncome.MODEL_MODEL)
@Component
@Slf4j
public class ExpensesIncomeAction {

    @Function.Advanced(type = FunctionTypeEnum.QUERY,category = FunctionCategoryEnum.QUERY_PAGE)
    @Function.fun(FunctionConstants.queryPage)
    @Function(openLevel = {FunctionOpenEnum.API})
    public Pagination<ExpensesIncome> queryPage(Pagination<ExpensesIncome> page, IWrapper<ExpensesIncome> queryWrapper) {
        page = new ExpensesIncome().queryPage(page, queryWrapper);

        if (page!=null && CollectionUtils.isNotEmpty(page.getContent())) {
            page.getContent().forEach(a->{
                if (a.getBudgetInCome()!=null && a.getBudgetInCome().compareTo(new BigDecimal("0.0"))!=0) {
                    if (a.getRetailAmount()!=null) {
                        a.setInComeRate(a.getRetailAmount().divide(a.getBudgetInCome(),2));
                    }
                }
            });
        }

        return page;
    }
}

2、事务支持

1)对于单个系统(InJvm)/模型内部采用强事务的方式。比如:在库存转移时,库存日志和库存数量的变化在一个事务中,保证两个表的数据同时成功或者失败。
2)强事务管理采用编码式,Oinone事务管理兼容Spring的事务管理方式;有注解的方式和代码块的方式。
a) 使用注解的方式:

    @Override
    @Transactional
    public void awardOut(AwardBookOutEmpRecordDetail detail) {
        List<OutEmpRecordSubjectDetail> subjectDetails = detail.getSubjectDetails();
        ……
    }

重要说明
基于注解的事务,Bean和加注解的方法必须能被Spring切面到。

b) 使用编码方式:

  //组装数据/获取数据/校验
  Tx.build(new TxConfig()).executeWithoutResult(status -> {
      // 1、保存部门数据
      deliDepartmentService.createOrUpdateBatch(departments);

      //2、如果父进行了下级关联设置,处理下级关联设置的逻辑
      for (DeliDepartment department : departments) {
          doDepartSubLink(department);
      }
  });
  …………

3)对于分布式事务采用最终数据一致性,借助【可靠消息】或者【Job】的方式来实现。

3、平台工具类使用

3.1 FetchUtil

pro.shushi.pamirs.core.common.FetchUtil,重点关注

# 根据Id查询列表
pro.shushi.pamirs.core.common.FetchUtil#fetchMapByIds

# 根据Id查询,返回Map
pro.shushi.pamirs.core.common.FetchUtil#fetchListByIds

# 根据Codes查询列表
pro.shushi.pamirs.core.common.FetchUtil#fetchMapByCodes

# 根据Code查询,返回Map
pro.shushi.pamirs.core.common.FetchUtil#fetchListByCodes

# 根据Entity查询
pro.shushi.pamirs.core.common.FetchUtil#fetchOne

其他更多方法参考这个类的源代码

3.2 ListUtils

pro.shushi.pamirs.meta.common.util.ListUtils

# 把输入的List,按给定的长度进行分组
pro.shushi.pamirs.meta.common.util.ListUtils#fixedGrouping

# 从给定的List中返回特定字段的数组,忽略Null并去重
pro.shushi.pamirs.meta.common.util.ListUtils#transform

4、枚举获取

根据枚举的name获取value
Integer value = ArchivesConfigTypeEnum.getValueByName(ArchivesConfigTypeEnum.class,“status1”);
根据枚举的name获取枚举项
ArchivesConfigTypeEnum typeEnum = TtypeEnum.getEnum(ArchivesConfigTypeEnum.class, “status1”);

5、Map类型的数据怎么定义,即ttype 为map的字段写法

@Field(displayName = "上下文", store = NullableBoolEnum.TRUE, serialize = JSON)
@Field.Advanced(columnDefinition = "TEXT")
private Map<String, Object> context;

6、后端获取前端请求中的variables

PamirsRequestVariables requestVariables = PamirsSession.getRequestVariables();

7、为什么有时候调用服务走远程了

1、假设:
A模型(modelA)所在的模块(ModuleA)
B模型(modelB)所在的模块(ModuleB)

部署方式决定调用方式:
1)部署方式1:
ModuleA 和 ModuleB 部署在一个jvm中,此时:
modelA的服务/页面去调用 modelB,因为部署在一起,直接走inJvm

2、部署方式2:
ModuleA 和 ModuleB 部署在不同的jvm中;
modelA的服务/页面去调用 modelB,则会走远程(RPC);此时需要:
a)ModuleB 需要开启dubbo服务,且ModuleA 和 ModuleB注册中心相同
b)ModuleB 对应的方法需要暴露dubbo接口,自定义service需要增加@Function注解

8、流程拒绝和回退调用自定义函数

1)、工作流撤销的自定义函数

@Function
public XXX recall(String data) {    
    return new XXX();
} 

2)、撤销回退调用的自定义函数
调用入口:
pro/shushi/pamirs/workflow/app/core/service/operator/ApprovalFallbackOperatorService.java:181

@Function
public XXX fallBack(String data) {
    return new XXX();
}

3)、工作流拒绝之后回调钩子

@Function
public XXX reject((String data) {    
    return new XXX();
} 

9、启动后访问页面报枚举项不存在

现象:枚举项不存在, model:base.ViewAction, name:target,value:ROUTER

pro.shushi.pamirs.meta.common.exception.PamirsException: 枚举项不存在, model:base.ViewAction, name:target,value:ROUTER
    at pro.shushi.pamirs.meta.common.exception.PamirsException$Builder.errThrow(PamirsException.java:173) ~[pamirs-meta-common-4.6.6.jar:na]
    at pro.shushi.pamirs.framework.orm.converter.entity.handler.EnumNamedHandler.lambda$dealEnum$4(EnumNamedHandler.java:149) ~[pamirs-framework-orm-4.6.8.jar:na]
    at pro.shushi.pamirs.framework.orm.converter.multi.MultiValueStrategy.submit(MultiValueStrategy.java:59) ~[pamirs-framework-orm-4.6.8.jar:na]
    at pro.shushi.pamirs.framework.orm.converter.multi.MultiValueStrategy.submit(MultiValueStrategy.java:23) ~[pamirs-framework-orm-4.6.8.jar:na]
    at pro.shushi.pamirs.framework.orm.converter.entity.handler.EnumNamedHandler.dealEnum(EnumNamedHandler.java:86) ~[pamirs-framework-orm-4.6.8.jar:na]
    at pro.shushi.pamirs.framework.orm.converter.entity.handler.EnumNamedHandler.stringify(EnumNamedHandler.java:66) ~[pamirs-framework-orm-4.6.8.jar:na]
    at pro.shushi.pamirs.framework.orm.converter.entity.handler.EnumHandler.stringify(EnumHandler.java:57) ~[pamirs-framework-orm-4.6.8.jar:na]
    at pro.shushi.pamirs.framework.orm.client.converter.entity.ClientEnumConverter.out(ClientEnumConverter.java:30) ~[pamirs-framework-orm-client-4.6.8.jar:na]
    at pro.shushi.pamirs.framework.orm.client.converter.processor.ClientTypeProcessor.lambda$out$6(ClientTypeProcessor.java:71) ~[pamirs-framework-orm-client-4.6.8.jar:na]
    at pro.shushi.pamirs.meta.common.enmu.BaseEnum.switchConsume(BaseEnum.java:717) ~[pamirs-meta-common-4.6.6.jar:na]
    at pro.shushi.pamirs.meta.common.enmu.BaseEnum.switch0(BaseEnum.java:697) ~[pamirs-meta-common-4.6.6.jar:na]
    at pro.shushi.pamirs.meta.common.enmu.BaseEnum.switches(BaseEnum.java:638) ~[pamirs-meta-common-4.6.6.jar:na]

问题原因:
Oinone重写了GraphQL的枚举转换类,报错是没有加载到这个自定义类

解决方法
在boot工程的pom的最前面引用如下的jar,重新发布即可

<dependency>
  <groupId>pro.shushi.pamirs</groupId>
  <artifactId>a</artifactId>
</dependency>

10、配置日志文件

现在启动输出到out.log应该是启动脚本中定向输出了(>>)

不定向输出,采用自己配置的方式,与标准的SpringBoot工程配置日志一样
两种方式(都是Spring提供的方式):
1、bootstrap.yml 里面可以按profiles指定logback的配置文件,例如:
logging:
config: classpath:logback-pre.xml
具体文件名和文件输入在logback里面进行配置,跟通用的logback配置一致

2、resources的根目录,直接配置 logback-spring.xml, 启动会自动加载

11、自定义controller如何获取用户信息

    private PamirsUser fetchUserByRequest() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String sessionId = CookieUtil.getValue(request, pro.shushi.pamirs.framework.common.utils.CookieUtil.USER_SESSION_ID);
        PamirsUserDTO pamirsUserDTO = UserCache.get(sessionId);
        if (pamirsUserDTO!=null) {
            // 根据用户ID获取完整的用户信息
           return UserInfoCache.queryUserById(pamirsUserDTO.getUserId()); 
        } else {
            throw PamirsException.construct(BaseExpEnumerate.BASE_USER_NOT_LOGIN_ERROR).errThrow();
        }
    }

12、查询设置不排序

查询的page和Wrapper可设置setSortable(Boolean.FALSE),来实现查询不需要排序的场景

示例1:page中设置不排序

page.setSortable(Boolean.FALSE);
page = new PetShop().queryPage(page, queryWrapper);

示例2:Wrapper中设置不排序

IWrapper wrapper = Pops.lambdaQuery()
    .from(PetShop.MODEL_MODEL).setBatchSize(-1).setSortable(Boolean.FALSE);
List petShops4 = new PetShop().queryList(wrapper);

持续补充中。。。。。

Oinone社区 作者:shao原创文章,如若转载,请注明出处:https://doc.oinone.top/dai-ma-shi-jian/5613.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(1)
shao的头像shao数式管理员
上一篇 2024年2月20日 pm6:52
下一篇 2024年2月20日 pm7:15

相关推荐

  • 标品实施:从标品构建到定制(扩展)包的开发

    总体描述 Oinone有一个非常重要的特性:通过平台承载标准化产品(标品)。针对不同客户的个性化需求,不再直接修改标准产品代码,而是以扩展包的形式进行扩展和定制化,通过继承和重写标准产品的能力来满足客户需求。 本文讲解述怎么通过标品构建扩展工程的过程。 构建标品 按照Oinone的规范构建标品工程 构建扩展包 在定制模块中指定上游模块 上游依赖模块upstreams,模块定义如下: @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Module { // 显示名称 @AliasFor("displayName") String value() default ""; // 依赖模块名列表 String[] dependencies() default ModuleConstants.MODULE_BASE; // 上游模块名列表 String[] upstreams() default {}; …… 扩展模块示例 @Component @Module( name = SecondModule.MODULE_NAME, displayName = "DEMO扩展", version = "1.0.0", // 指定上游模块(标品模块,可以为多个) upstreams = DemoModule.MODULE_MODULE, priority = 1, dependencies = {ModuleConstants.MODULE_BASE, CommonModule.MODULE_MODULE, UserModule.MODULE_MODULE, AuthModule.MODULE_MODULE, BusinessModule.MODULE_MODULE, // 上游模块(标品模块,可以为多个) DemoModule.MODULE_MODULE, } ) @Module.module(SecondModule.MODULE_MODULE) @Module.Advanced(selfBuilt = true, application = true) @UxHomepage(@UxRoute(model = WorkRecord.MODEL_MODEL)) public class SecondModule implements PamirsModule { public static final String MODULE_MODULE = "demo_core_ext"; public static final String MODULE_NAME = "DemoCoreExt"; @Override public String[] packagePrefix() { return new String[]{ "pro.shushi.pamirs.second" }; } } application.yml配置文件 pamirs: boot: modules: ….. – demo_core // 加标准工程 – demo_core_ext maven配置 父工程依赖 <dependencyManagement> <dependencies> ….. <dependency> <groupId>pro.shushi.pamirs.demo</groupId> <artifactId>pamirs-demo-api</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>pro.shushi.pamirs.demo</groupId> <artifactId>pamirs-demo-core</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> ….. </dependencies> </dependencyManagement> api子工程加入依赖 <dependency> <groupId>pro.shushi.pamirs.demo</groupId> <artifactId>pamirs-demo-api</artifactId> </dependency> boot子工程加入依赖 <dependency> <groupId>pro.shushi.pamirs.demo</groupId> <artifactId>pamirs-demo-core</artifactId> </dependency> 数据库设置 base数据库要跟标品工程一致 注意事项 标品工程的第三方依赖,在扩展工程都要有,否则启动会报错 扩展模块功能开发 菜单扩展 1、可以按需隐藏标品的菜单; 2、可以根据扩展包的实际情况增加菜单; 模型扩展 1、扩展包可继承标品已有模型; 新增字段、覆盖字段,不继承 2、扩展包可根据实际情况新增自有模型; 函数扩展 1、扩展包可根据实际情况【覆写】标品中的函数; 2、扩展包可根据实际情况【新增】自有函数; 3、扩展包可通过Hook机制实现业务的个性化; 4、扩展包可根据自身业务情况实现标品中的扩展点; 5、……

    2024年6月1日
    1.9K00
  • 推送自定义消息

    项目中添加消息依赖 pro.shushi.pamirs.core pamirs-message-api 调用pro.shushi.pamirs.message.engine.message.MessageSender#sendSystemMail发送系统消息示例: @Action(displayName = "发送消息") public Student sendMessage(Student data){ MessageSender mailSender = (MessageSender) MessageEngine.get(MessageEngineTypeEnum.MAIL_SEND).get(null); String content = "发送自定义消息"; String subject = null; List<Long> userIds = new ArrayList<>(); userIds.add(PamirsSession.getUserId()); PamirsMessage message = new PamirsMessage() .setName(subject) .setSubject(subject) .setBody(content) .setMessageType(MessageTypeEnum.NOTIFICATION); List<PamirsMessage> messages = new ArrayList<>(); messages.add(message); SystemMessage systemMessage = new SystemMessage(); systemMessage.setPartners(userIds.stream().map(i -> (PamirsUser) new PamirsUser().setId(i)).collect(Collectors.toList())) .setType(MessageGroupTypeEnum.SYSTEM_MAIL) .setMessages(messages); mailSender.sendSystemMail(systemMessage); return data; }

    2024年8月19日
    1.2K00
  • 如何扩展行为权限

    注意:5.2.8 以上版本适用 需求 我们的权限控制需要在页面上有交互才可以在管理中心控制该动作权限,在没有页面交互的动作是不能进行授权操作的。所以本文章将介绍如何将页面上没有交互的动作接入到系统权限中管理。 一、扩展系统权限的菜单页面 实现 步骤: 创建授权节点实现权限节点扩展接口:pro.shushi.pamirs.auth.api.extend.load.PermissionNodeLoadExtendApi#buildRootPermissions @Component @Order(88) public class MyTestNodeLoadExtend implements PermissionNodeLoadExtendApi { public static final String MODEL = AuthTest.MODEL_MODEL; public static final String MODULE = TopModule.MODULE_MODULE; public static final String FUN = "dataStatus"; @Override public List<PermissionNode> buildRootPermissions(PermissionLoadContext loadContext, List<PermissionNode> nodes) { //创建授权根节点 PermissionNode root = createMyNode(); List<PermissionNode> newNodes = new ArrayList<>(); //从缓存中读取需要授权的Action Action cacheAction = PamirsSession.getContext().getExtendCache(ActionCacheApi.class).get(MODEL, FUN); if (cacheAction != null) { //将该Action放入权限树 //权限鉴权的path路径是根据【cacheAction.getModel() + cacheAction.getName()】拼接的。和MODULE没有关系,这里MODULE可以自定义。 AuthNodeHelper.addNode(newNodes, root, AuthNodeHelper.createActionNode(MODULE, cacheAction, root)); } nodes.add(0, root); return newNodes; } private PermissionNode createMyNode() { return AuthNodeHelper.createNodeWithTranslate("MyNode", "自定义节点"); } } 在管理中心中我们可以看到代码里创建的授权节点。 给角色分配该动作的权限,调用我们配置的AuthTest模型的dataStatus动作看效果。 二、扩展菜单下的动作权限 实现 步骤: 创建viewAction用于作为权限菜单"permissionExtension"是自定义的viewAction的name,用于下面拼path路径鉴权。因为这里只需要在系统权限那边利用这个viewAction创建出授权节点。所以”权限扩展form“可以随意定义名字,系统会拿默认视图。 @Model.model(AuthTest.MODEL_MODEL) @Component @UxRouteButton( action = @UxAction(name = "permissionExtension", displayName = "权限扩展", label = "权限扩展", contextType = ActionContextTypeEnum.CONTEXT_FREE), value = @UxRoute(model = AuthTest.MODEL_MODEL, viewName = "权限扩展form", openType = ActionTargetEnum.ROUTER)) public class AuthTestAction { @Action(displayName = "启用", contextType = ActionContextTypeEnum.SINGLE) public AuthTest dataStatus(AuthTest data) { data.setOrgName("给个值"); return data; } } 创建授权节点实现权限节点扩展接口:pro.shushi.pamirs.auth.api.extend.load.PermissionNodeLoadExtendApi#buildRootPermissions @Component @Order(88) public class MyTestNodeLoadExtend implements PermissionNodeLoadExtendApi { //创建授权根节点 @Override public List<PermissionNode> buildRootPermissions(PermissionLoadContext loadContext, List<PermissionNode> nodes) { PermissionNode root = AuthNodeHelper.createNodeWithTranslate("CustomNode", "自定义节点"); List<PermissionNode> newNodes = new ArrayList<>(); newNodes.add(root); ViewAction viewAction = new ViewAction().setModel(AuthTest.MODEL_MODEL).setName("permissionExtension").queryOne(); //将该Action放入权限树 //权限鉴权的path路径是根据【viewAction.getModel(),…

    2024年11月1日
    79200
  • Oinone登录扩展:对接SSO(适应于4.7.8及之后的版本)

    适配版本 4.7.8及其之后的版本 概述 在企业内部,对于已有一套完整的登录系统(SSO)的情况下,通常会要求把所有的系统都对接到SSO中;本文主要讲解用Oinone开发的项目对接SSO的具体实现。 对接步骤 1、项目自定义实现UserCookieLogin,可参考示例说明: pro.shushi.pamirs.user.api.login.UserCookieLoginFree 2、对接SSO示例 对接流程说明: 1)【必须】从请求头Header或者Query中获取到token; 2)【必须】去SSO服务端验证token的有效性; 3)【可选】根据token去服务端获取用户信息;如果token可以直接反解析出用户信息,则该步骤忽略; 4)【可选】根据实际情况用户信息是否进行DB的存储; 5)【必须】验证token有效后,生成Session和Cookie(即token换cookie); 注意超时时间需要 <= SSO服务端token失效时间。 package pro.shushi.pamirs.demo.core.sso; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import pro.shushi.pamirs.demo.core.sso.constant.HttpConstant; import pro.shushi.pamirs.demo.core.sso.constant.SessionUserTypeEnum; import pro.shushi.pamirs.demo.core.sso.model.ApiCommonTransient; import pro.shushi.pamirs.demo.core.sso.model.PermissionInfoResp; import pro.shushi.pamirs.demo.core.sso.utils.AuthenticateUtils; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.dto.model.PamirsUserDTO; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.common.exception.PamirsException; import pro.shushi.pamirs.meta.common.spring.BeanDefinitionUtils; import pro.shushi.pamirs.resource.api.enmu.UserSignUpType; import pro.shushi.pamirs.user.api.cache.UserCache; import pro.shushi.pamirs.user.api.constants.UserConstant; import pro.shushi.pamirs.user.api.enmu.UserExpEnumerate; import pro.shushi.pamirs.user.api.enmu.UserLoginTypeEnum; import pro.shushi.pamirs.user.api.login.IUserLoginChecker; import pro.shushi.pamirs.user.api.login.UserCookieLogin; import pro.shushi.pamirs.user.api.login.UserCookieLoginSimple; import pro.shushi.pamirs.user.api.model.PamirsUser; import pro.shushi.pamirs.user.api.model.tmodel.PamirsUserTransient; import pro.shushi.pamirs.user.api.service.UserService; import pro.shushi.pamirs.user.api.utils.CookieUtil; import javax.servlet.http.HttpServletResponse; /** * * @author shushi * * 完全自定义login的过程 * 需要实现登陆部分login 以及拦截部分fetchUserIdByReq * 如果fetchUserIdByReq返回值为null的时候 将会被拦截 */ @Slf4j @Order(0) @Component public class DemoUserSSOCookieLogin extends UserCookieLogin<PamirsUser> { //刷新令牌 private static String REFRESH_TOKEN = "refreshToken"; //系统id private static String CLIENT_ID = "client-id"; //访问令牌 private static String AUTHORIZATION = "Authorization"; private IUserLoginChecker checker; @Autowired private UserService userService; @Autowired private RedisTemplate<String, String> redisTemplate; @Override public String type() { return UserLoginTypeEnum.COOKIE.value(); } @Override public PamirsUser resolveAndVerification(PamirsUserTransient user) { if (checker == null) { checker = BeanDefinitionUtils.getBean(IUserLoginChecker.class); } return checker.check4login(user); } /** * 重写登录拦截功能 * 该函数主要作用,通过三方权限校验. * @return */ // 版本升级需要修改 @Override public PamirsUserDTO fetchUserIdByReq() { String sessionId =…

    2024年4月2日
    1.9K00
  • mybatis拦截器的使用

    场景:自定义拦截器做数据的加解密。 注册自定义拦截器 @Configuration public class MyBatisConfig { // TODO: 注册自定义拦截器 @Bean @Order(999) public EncryptionInterceptor encryptionInterceptor() { return new EncryptionInterceptor(); } } 使用mybatis拦截器拦截查询。 @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class EncryptionInterceptor implements Interceptor { @Autowired private EncryptionConfig encryptionConfig; @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameter = args[1]; // 判断操作类型是insert, update 或 delete if (ms.getSqlCommandType().equals(SqlCommandType.INSERT) || ms.getSqlCommandType().equals(SqlCommandType.UPDATE) || ms.getSqlCommandType().equals(SqlCommandType.DELETE)) { // TODO: 加密字段 encryptFields(parameter); } else if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) { // TODO: 查询操作,在执行后需要对结果进行解密 Object result = invocation.proceed(); List<EncryptionConfig.Models> models = encryptionConfig.getModels(); for (EncryptionConfig.Models model : models) { if (judgmentModel(parameter, model)) { decryptFields(result); } } return result; } return invocation.proceed(); } private Boolean judgmentModel(Object parameter, EncryptionConfig.Models model) { MetaObject metaObject = SystemMetaObject.forObject(parameter); if (metaObject.getOriginalObject() instanceof MapperMethod.ParamMap) { if (metaObject.hasGetter("ew")) { Object param1 = metaObject.getValue("ew"); if (param1 != null) { Object originalObject = SystemMetaObject.forObject(param1).getOriginalObject(); if (originalObject instanceof QueryWrapper) { DataMap entity = (DataMap) ((QueryWrapper<?>) originalObject).getEntity(); if (entity != null) { Object modelFieldName = entity.get(FieldConstants._d_modelFieldName);…

    2024年12月2日
    95800

Leave a Reply

登录后才能评论