关键词:接口开发,异步接口,MQ
一、需求
openAPI目前是不支持通过配置做成异步接口开发的,这就需要我们通过开发平台的自定义接口来自己实现的。
二、思路与方案
关于如何开发 自定义的接口可以参考下这个文章:
MQ的案例也可以参阅这个文章:
https://vip.kingdee.com/article/316882666815440896?productLineId=29&isKnowledge=2
我们这次以通过接口生成学生信息为例,给大家演示下如何创建。
三、实现过程
1 创建单据元数据
新建一个学生信息单,在单据头加上名称和年级。
在分录上加上,学科编码,学科名称,成绩
2 编写接口controller
@ApiController(value="Student",desc="保存学生信息") public class SaveStudentApiController { @ApiPostMapping(value="saveStudent") public CustomApiResult<String> saveStudentApi(@Valid @ApiParam("学生信息") SaveStudentReqModel data) { MessagePublisher mp = MQFactory.get().createSimplePublisher("bidt", "savestudent_queue"); try{ mp.publish(data); return CustomApiResult.success("调用接收成功!"); }catch(Exception e) { return CustomApiResult.fail("500", "异步处理失败!"); }finally{ mp.close(); } } }
3 编写api请求model
import java.io.Serializable; import java.util.List; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import kd.bos.openapi.common.custom.annotation.ApiModel; import kd.bos.openapi.common.custom.annotation.ApiParam; @ApiModel public class SaveStudentReqModel implements Serializable{ /** * */ private static final long serialVersionUID = 1L; @ApiParam("number") private String number; @ApiParam("name") private String name; @ApiParam("grade") private String grade; @ApiParam("分录") public List<Subject > subList; @ApiModel public static class Subject implements Serializable { @ApiParam("学科编码") private String subjectNum; @ApiParam("学科名称") @NotNull private String subjectName; @Max(100) @Min(0) @ApiParam("成绩") private int score; public String getSubjectNum() { return subjectNum; } public void setSubjectNum(String subjectNum) { this.subjectNum = subjectNum; } public String getSubjectName() { return subjectName; } public void setSubjectName(String subjectName) { this.subjectName = subjectName; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGrade() { return grade; } public void setGrade(String grade) { this.grade = grade; } public List<Subject> getSubList() { return subList; } public void setSubList(List<Subject> subList) { this.subList = subList; } }
4 在开放平台配置接口
第一步在api管理新增接口
第二步
在类名处把刚才写好全类名controller 粘贴过去,系统就会自动加载类里面的属性和方法了,不再需要我们手动填入了。
完善名称就可以保存了(在这里你也可以稍微改动下一些备注说明)
5 编写消费类
import kd.bos.dataentity.entity.DynamicObject; import kd.bos.dataentity.entity.DynamicObjectCollection; import kd.bos.logging.Log; import kd.bos.logging.LogFactory; import kd.bos.mq.MessageAcker; import kd.bos.mq.MessageConsumer; import kd.bos.servicehelper.BusinessDataServiceHelper; import kd.bos.servicehelper.operation.SaveServiceHelper; import test.api.SaveStudentReqModel.Subject; public class SaveStudentConsumer implements MessageConsumer { Log log = LogFactory.getLog(getClass()); @Override public void onMessage(Object message, String messageId, boolean resend, MessageAcker acker) { log.info("自定义DemoConsumer开始消费"); try { SaveStudentReqModel student = (SaveStudentReqModel)message; //组装数据 DynamicObject newStudent =BusinessDataServiceHelper.newDynamicObject("bidt_student_object"); newStudent.set("billno",student.getNumber()); newStudent.set("bidt_name", student.getName()); newStudent.set("billstatus", "B"); newStudent.set("bidt_grade",student.getGrade()); for (Subject sub:student.getSubList()) { DynamicObjectCollection subEntryCols = newStudent.getDynamicObjectCollection("entryentity"); DynamicObject subEntryCol = new DynamicObject(subEntryCols.getDynamicObjectType()); subEntryCol.set("bidt_subject_num", sub.getSubjectNum()); subEntryCol.set("bidt_subject_name", sub.getSubjectName()); subEntryCol.set("bidt_score", sub.getScore()); subEntryCols.add(subEntryCol); } SaveServiceHelper.saveOperate("bidt_student_object",new DynamicObject[] {newStudent}); } catch (Throwable e) { boolean discard = false; //是否废弃这条消息,根据具体场景判断 if (discard){ acker.discard(messageId);//废弃 // 记录废弃原因,并写业务日志 } else{ acker.deny(messageId);//告诉mq重发这条消息 // 记录异常原因,并写业务日志 } } } }
6 编写mqconfig文件
在main目录下的resources文件里面创建一个xml文件
文件里面代码如下:
<?xml version="1.0" encoding="UTF-8"?> <root> <region name="bidt"> <queue name="savestudent_queue" appid="bidt_yanzheng"> <consumer class="test.api.SaveStudentConsumer"></consumer> </queue> </region> </root>
文件里面写的region 对应controller类代码里的第一个参数,queue对应controller类代码里的第二个参数。
MessagePublisher mp = MQFactory.get().createSimplePublisher("bidt", "savestudent_queue");
7 在添加启动参数
这里有两种方式添加,一种是启动类DebugServer.java里面添加,这种比较简单,一般是轻量级里面都是这样加的
System.setProperty("dubbo.registry.register", "true"); System.setProperty("mq.consumer.register", "true"); //定义一个唯一的标识别和其他人重复 System.setProperty("mq.debug.queue.tag", "sharkv"); System.setProperty("lightweightdeploy","false"); //队列发送方、消费方均需要该配置项,包括具体的配置文件,生产环境可配在服务节点的启动参数中 System.setProperty("mqConfigFiles.config","testmqconfig.xml");
还有一种是在mc里面添加,协同及生产环境都可以在这里添加
MC:公共配置方案,/root/config/mservice/prop中新增mqConfigFiles.config参数
注:如果有多个xml文件,换行即可
这里也要提一点,经常有人会出现 rabbitmq config error: queue not configured for xxx/xxx的问题
这里就要查看下是否是路径问题,打包的时候是否把xml编译到其他文件目录下的,否则也会在启动的时候扫描不到xml文件,导致找不到queue的问题
其他的排查思路可以参考下
https://vip.kingdee.com/article/230271851040272896?productLineId=29
8 重新启动服务
启动后通过mq的管理服务器就可以看到我们刚才配置队列已经启动好了
四、效果图
1 通过开发平台自带测试开始测试
2 这里已经看到进入了消费类进行消费了。
3 跳过调试,跑完后,进入单据查看,发现已经正确生成了。
五、开发环境版本
COSMICV5.0.003.0
六、参考资料
java及xml.rar(2.35KB)
bidt_devtset-bidt_yanzheng-202 …(7.51KB)
推荐阅读