EIP开放接口使用MD5验签发起请求(v5.x)

验签工具类

PS:该验签方法仅在pamirs-core的5.0.16版本以上可正常使用

public class EipSignUtils {

    public static final String SIGN_METHOD_MD5 = "md5";

    private static final String SIGN_METHOD_HMAC = "hmac";

    private static final String SECRET_KEY_ALGORITHM = "HmacMD5";

    private static final String MESSAGE_DIGEST_MD5 = "MD5";

    public static String signTopRequest(Map<String, String> params, String secret, String signMethod) throws IOException {
        // 第一步:检查参数是否已经排序
        String[] keys = params.keySet().toArray(new String[0]);
        Arrays.sort(keys);

        // 第二步:把所有参数名和参数值串在一起
        StringBuilder query = new StringBuilder();
        if (SIGN_METHOD_MD5.equals(signMethod)) {
            query.append(secret);
        }
        for (String key : keys) {
            String value = params.get(key);
            if (StringUtils.isNoneBlank(key, value)) {
                query.append(key).append(value);
            }
        }

        // 第三步:使用MD5/HMAC加密
        byte[] bytes;
        if (SIGN_METHOD_HMAC.equals(signMethod)) {
            bytes = encryptHMAC(query.toString(), secret);
        } else {
            query.append(secret);
            bytes = encryptMD5(query.toString());
        }

        // 第四步:把二进制转化为大写的十六进制(正确签名应该为32大写字符串,此方法需要时使用)
        return byte2hex(bytes);
    }

    private static byte[] encryptHMAC(String data, String secret) throws IOException {
        byte[] bytes;
        try {
            SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), SECRET_KEY_ALGORITHM);
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            bytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        } catch (GeneralSecurityException e) {
            throw new IOException(e.toString(), e);
        }
        return bytes;
    }

    private static byte[] encryptMD5(String data) throws IOException {
        return encryptMD5(data.getBytes(StandardCharsets.UTF_8));
    }

    private static byte[] encryptMD5(byte[] data) throws IOException {
        try {
            MessageDigest md = MessageDigest.getInstance(MESSAGE_DIGEST_MD5);
            return md.digest(data);
        } catch (NoSuchAlgorithmException e) {
            throw new IOException(e.toString(), e);
        }
    }

    private static String byte2hex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();
        for (byte aByte : bytes) {
            String hex = Integer.toHexString(aByte & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toUpperCase());
        }
        return sign.toString();
    }
}

Oinone社区 作者:张博昊原创文章,如若转载,请注明出处:https://doc.oinone.top/backend/14224.html

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

Like (0)
张博昊's avatar张博昊数式管理员
Previous 2024年6月28日 am10:41
Next 2024年7月1日 pm1:58

相关推荐

  • 字段类型之关系描述的特殊场景(常量关联)

    场景概述 【字段类型之关系与引用】一文中已经描述了各种关系字段的常规写法,还有一些特殊场景如:关系映射中存在常量,或者M2M中间表是大于两个字段构成。 场景描述 1、PetTalent模型增加talentType字段2、PetItem与PetTalent的多对多关系增加talentType(达人类型),3、PetItemRelPetTalent中间表维护petItemId、petTalentId以及talentType,PetDogItem和PetCatItem分别重写petTalents字段,关系中增加常量描述。示意图如下: 实际操作步骤 Step1 新增 TalentTypeEnum package pro.shushi.pamirs.demo.api.enumeration; import pro.shushi.pamirs.meta.annotation.Dict; import pro.shushi.pamirs.meta.common.enmu.BaseEnum; @Dict(dictionary = TalentTypeEnum.DICTIONARY,displayName = "达人类型") public class TalentTypeEnum extends BaseEnum<TalentTypeEnum,Integer> { public static final String DICTIONARY ="demo.TalentTypeEnum"; public final static TalentTypeEnum DOG =create("DOG",1,"狗达人","狗达人"); public final static TalentTypeEnum CAT =create("CAT",2,"猫达人","猫达人"); } Step2 PetTalent模型增加talentType字段 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.demo.api.enumeration.TalentTypeEnum; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; @Model.model(PetTalent.MODEL_MODEL) @Model(displayName = "宠物达人",summary="宠物达人",labelFields ={"name"}) public class PetTalent extends AbstractDemoIdModel{ public static final String MODEL_MODEL="demo.PetTalent"; @Field(displayName = "达人") private String name; @Field(displayName = "达人类型") private TalentTypeEnum talentType; } Step3 修改PetItem的petTalents字段,在关系描述中增加talentType(达人类型) @Field.many2many(relationFields = {"petItemId"},referenceFields = {"petTalentId","talentType"},through = PetItemRelPetTalent.MODEL_MODEL ) @Field.Relation(relationFields = {"id"}, referenceFields = {"id","talentType"}) @Field(displayName = "推荐达人",summary = "推荐该商品的达人们") private List<PetTalent> petTalents; Step4 PetDogItem增加petTalents字段,重写父类PetItem的关系描述 talentType配置为常量,填入枚举的值 增加domain描述用户页面选择的时候自动过滤出特定类型的达人,RSQL用枚举的name @Field(displayName = "推荐达人") @Field.many2many( through = "PetItemRelPetTalent", relationFields = {"petItemId"}, referenceFields = {"petTalentId","talentType"} ) @Field.Relation(relationFields = {"id"}, referenceFields = {"id", "#1#"}, domain = " talentType == DOG") private List<PetTalent> petTalents; Step5 PetCatItem增加petTalents字段,重写父类PetItem的关系描述 talentType配置为常量,填入枚举的值 增加domain描述用户页面选择的时候自动过滤出特定类型的达人,RSQL用枚举的name @Field(displayName = "推荐达人") @Field.many2many( through = "PetItemRelPetTalent", relationFields = {"petItemId"}, referenceFields = {"petTalentId","talentType"} ) @Field.Relation(relationFields = {"id"}, referenceFields = {"id", "#2#"}, domain = " talentType == CAT") private List<PetTalent> petTalents; Step6 PetCatItem增加petTalents字段,many2one关系示例 talentType配置为常量,填入枚举的值 增加domain描述用户页面选择的时候自动过滤出特定类型的达人,RSQL用枚举的name @Model.model(PetPet.MODEL_MODEL) @Model(displayName…

    2024年5月25日
    2.2K00
  • 树形下拉组件数据查询覆写

    一、背景与问题 Oinone 的树形下拉组件(TreeSelect)和树视图(Tree)在查询数据时,最终调用的是树节点所配置模型的数据管理器函数: 树接口 调用的数据管理器函数 场景 fetchAll queryListByWrapper 一次性全量加载 fetchChildren queryPage 按层级懒加载(默认) queryKeywords4Tree queryListByWrapper 关键字搜索 reverselyQuery queryListByWrapper 已选数据回填 官方文档原文:「所有树查询接口都会调用模型对应的数据管理器函数,可以通过重写的方式修改其查询结果。」 直接覆写的风险 如果直接在原模型(如 business.PamirsDepartment)上覆写 queryPage: // ⚠️ 危险写法:会影响所有使用 PamirsDepartment.queryPage 的场景 @Component @Model.model(PamirsDepartment.MODEL_MODEL) public class DeptQueryAction { @Function.Advanced(type = FunctionTypeEnum.QUERY, category = FunctionCategoryEnum.QUERY_PAGE) @Function.fun(FunctionConstants.queryPage) @Function(openLevel = {FunctionOpenEnum.LOCAL, FunctionOpenEnum.REMOTE, FunctionOpenEnum.API}) public Pagination<PamirsDepartment> queryPage(Pagination<PamirsDepartment> page, IWrapper<PamirsDepartment> queryWrapper) { // 追加过滤条件… return new PamirsDepartment().queryPage(page, queryWrapper); } } 该覆写会全局生效——不仅是树形下拉,所有调用 PamirsDepartment 的 queryPage 的地方(组织架构页面、人员管理列表、权限分配、框架内部关联查询等)都会受到影响,容易引发难以排查的副作用。 二、推荐方案:代理模型(PROXY)隔离 核心思路 创建一个继承原模型的代理模型(ModelTypeEnum.PROXY),拥有独立的模型命名空间 在代理模型中重新声明自关联字段(如 parent),将关联目标指向代理模型自身 在代理模型的 Action 中覆写数据管理器函数,追加树形场景专属的过滤逻辑 树形下拉的视图 DSL 中,<node> 的 model 指向代理模型 代理模型与原模型共享同一张数据表,但函数注册在各自独立的命名空间下,互不干扰。 框架官方示例 Oinone 框架自身就采用了这一模式:ResourceRegionProxyModel 继承 ResourceRegion,用于地区页面的代理展示,其扩展点 ResourceRegionProxyModelExtPoint 中的 queryPageAfter 只影响代理模型自身的查询。 三、完整实现步骤(以 PamirsDepartment 为例) 场景假设 业务需求:某个表单页面的「部门」树形下拉只需要展示当前公司(companyCode == 当前用户公司)且启用状态(dataStatus == ENABLED)的部门,而系统中其他使用 PamirsDepartment 的地方(组织架构、权限管理等)不受影响。 步骤 1:定义代理模型 package com.example.demo.model; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.enmu.ModelTypeEnum; import pro.shushi.pamirs.business.api.model.PamirsDepartment; import java.util.List; /** * 部门树形下拉专用代理模型 * <p> * 继承 PamirsDepartment,共享同一张数据表, * 但拥有独立的函数命名空间,覆写 queryPage 不影响原模型。 */ @Model.model(DeptTreeProxy.MODEL_MODEL) @Model.Advanced(type = ModelTypeEnum.PROXY) @Model(displayName = "部门(树选择)", labelFields = "name") public class DeptTreeProxy extends PamirsDepartment { public static final String MODEL_MODEL = "demo.DeptTreeProxy"; /** * 【必须重新声明】上级部门(自关联字段) * <p> * 基类 PamirsDepartment 中 parent 的类型是 PamirsDepartment, * 代理模型必须将其覆盖为指向自身(DeptTreeProxy), * 否则设计器/树组件无法在代理模型命名空间内构成完整的自关联关系, * 导致树形下拉无法正确展开层级。 */ @Field.many2one @Field.Relation(relationFields = {"parentCode"}, referenceFields = {"code"}) @Field(displayName = "上级部门")…

    后端 2026年7月27日
    7400
  • 工作流-流程代办等页面自定义

    1. 审批/填写节点的视图页面 在界面设计器中创建对应模型的表单视图,可根据业务场景需要自定义所需流程待办的审批页面 2. 在审批/填写节点中选择刚创建的视图 在工作流待办数据权限可在节点数据权限中可对字段设置查看、编辑、隐藏

    2024年5月14日
    2.5K00
  • 工作流用户待办过滤站内信

    工作流用户待办过滤站内信 全局过滤 启动工程application.yml中配置: pamirs: workflow: notify: false 个性化过滤 实现pro.shushi.pamirs.workflow.app.api.service.WorkflowMailFilterApi接口 返回true表示需要发送站内信 返回false表示不需要发送站内信 示例: import org.apache.commons.lang3.StringUtils; import pro.shushi.pamirs.message.model.PamirsMessage; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.user.api.model.PamirsUser; import pro.shushi.pamirs.workflow.app.api.model.WorkflowUserTask; import pro.shushi.pamirs.workflow.app.api.service.WorkflowMailFilterApi; /** * MyWorkflowMailFilterImpl * * @author yakir on 2025/02/24 16:28. */ @Fun(WorkflowMailFilterApi.FUN_NAMESPACE) public class MyWorkflowMailFilterImpl implements WorkflowMailFilterApi { @Override @Function public Boolean filter(WorkflowUserTask workflowUserTask, PamirsUser user, PamirsMessage message) { // 按用户待办过滤 workflowUserTask if (10000L == workflowUserTask.getInitiatorUid()){ return true; } // 按用户过滤 user if (1000L == user.getId()){ return true; } // 按站内信消息过滤 message if (StringUtils.contains(message.getBody(), "你好")) { return true; } return false; } }

    2025年2月24日
    1.3K00
  • 如何发送邮箱、手机短信以及设置

    1.邮件发送 1.1 邮件服务设置 1.1.1 方法一:通过yaml文件配置 pamirs: email: smtp: smtpHost: smtp.exmail.qq.com smtpUser: xxx@xxx.com smtpPassword: xxxxxx smtpPort: 465 smtpSecurity: SSL #邮件模块可后续后台自行添加 templates: – name: 邮箱注册邮件 title: '${code}是你此次注册的验证码' body: '<div>Hi ${realname},</div><div>你正在使用验证码注册。</div>' 1.1.2 方法二:工程启动加入初始化设置方法 /** * 初始化邮件模板 */ private void initEmailTemplate(){ EmailSenderSource emailSenderSource = new EmailSenderSource(); emailSenderSource.setName("邮件发送服务"); emailSenderSource.setType(MessageEngineTypeEnum.EMAIL_SEND); //优先级 emailSenderSource.setSequence(10); //发送账号 emailSenderSource.setSmtpUser("xxx@xxx.com"); //发送密码 emailSenderSource.setSmtpPassword("xxxxxx"); //发送服务器地址和端口 emailSenderSource.setSmtpHost("smtp.exmail.qq.com"); emailSenderSource.setSmtpPort(465); //" None: SMTP 对话用明文完成。" + //" TLS (STARTTLS): SMTP对话的开始时要求TLS 加密 (建议)" + //" SSL/TLS: SMTP对话通过专用端口用 SSL/TLS 加密 (默认是: 465)") emailSenderSource.setSmtpSecurity(EmailSendSecurityEnum.SSL); emailSenderSource.setActive(true); emailSenderSource.createOrUpdate(); List<EmailTemplate> templates = new ArrayList<>(); templates.add(new EmailTemplate().setName("重置密码邮件模板").setTitle("请确认你的密码修改请求").setBody("<div>Hi ${realname},</div><div>你正在使用验证码注册。</div>").setModel(PamirsUser.MODEL_MODEL).setEmailSenderSource(emailSenderSource)); new EmailTemplate().createOrUpdateBatch(templates); } 1.2 调用邮件发送组件发送邮件 /** * 代码中使用消息组件发送Email */ public void sendEmailByTemplate(){ try { EmailSender emailSender = (EmailSender) MessageEngine.get(MessageEngineTypeEnum.EMAIL_SEND).get(null);; EmailTemplate template = new EmailTemplate().setName("邮件模版名称").queryOne(); //标题:${name} //内容:${fieldInt}次数 String sendTo = "xxx@xxx.com"; String copyTo = "yyy@yyy.com"; Map<String, Object> objectData = new HashMap<>(); objectData.put("name","张三"); objectData.put("fieldInt",999); Boolean aBoolean = emailSender.send(template, objectData, sendTo, copyTo); if (null == aBoolean || !aBoolean) { log.error("发送邮件失败"); } } catch (Exception e) { log.error("发送确认邮件失败:,异常:{}", e); } } 2.发送短信 2.1 短信通道设置 2.1.1 方法一:通过yaml文件配置 pamirs: sms: aliyun: signatureMethod: HMAC-SHA1 endpoint: https://dysmsapi.aliyuncs.com version: '2017-05-25' accessKeyId: xxxxxxxxxxxxx signatureVersion: '1.0' accessKeySecret: xxxxxxxxxxxx regionId: cn-hangzhou timeZone: GMT signName: xxxxxx 2.1.2 方法二:工程启动加入初始化设置方法 private void…

    后端 2023年11月6日
    2.1K00

Leave a Reply

Please Login to Comment