Camunda入门(四) - 流程应用(支付流程)入门示例
参考文章:Camunda入门(四) - 流程应用(支付流程)入门示例_罗小爬EX的博客-CSDN博客_camunda入门
1. 支付流程 - 场景设定
为了快速上手Camunda,结合Camunda官方QuickStart示例,
本文设计了如下支付请求实例:
- 由用户发起支付请求(商品名称、金额、支付用户ID)
- 根据商品判断对应的折扣
- 若折扣后实际支付金额 < 1000则直接 调用支付接口(JavaDelegate)
- 若折扣后实际支付金额 >= 1000则需要征求用户同意
- 若用户同意后,才可继续调用支付接口,否则支付失败
如上案例需创建以下4个模型:
功能 | 对应文件 | 类型 |
---|---|---|
支付流程模型 | PaymentProcess.bpmn | BPMN |
商品折扣决策模型 | ProductDiscountDecision.dmn | DMN |
支付请求表单模型 | PaymentInitForm.form | Form |
用户确认表单模型 | PaymentConfirmForm.form | Form |
2. 业务DB设计
该流程比较简单,且各个活动间数据相差不大,
所以采用整个流程对应一个业务对象的设计。
注: 采用流程实例的BusinessKey来存储对应业务数据的ID数据库采用Mysql 5.7,
首先新建Camunda流程引擎的对应的数据库Camunda_716(该名称可根据业务需求适当调整),
然后在此库中新建业务数据表biz_payment_process_info,该表具体定义如下:
CREATE TABLE `biz_payment_process_info` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '业务主键',`process_instance_id` varchar(36) DEFAULT NULL COMMENT '流程实例ID',`product_name` varchar(64) DEFAULT NULL COMMENT '商品名称',`product_price` decimal(10,2) DEFAULT NULL COMMENT '商品金额',`product_discount` decimal(3,2) DEFAULT NULL COMMENT '商品折扣',`product_discount_price` decimal(10,2) DEFAULT NULL COMMENT '商品折扣后金额',`payment_assignee` varchar(64) DEFAULT NULL COMMENT '支付用户ID',`approval_result` tinyint(3) unsigned DEFAULT '0' COMMENT '用户是否同意付款(0:未确认,1:同意, 2:不同意)',`approval_time` datetime DEFAULT NULL COMMENT '用户确认时间',`payment_result` tinyint(3) unsigned DEFAULT '0' COMMENT '支付结果(0:未支付,1:成功, 2:失败)',`payment_time` datetime DEFAULT NULL COMMENT '支付时间',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付流程 - 业务数据';
注: Camunda流程引擎相关的其他表可在Camunda流程应用启动时连接同一数据库后自动创建。
3. 流程模型设计
3.1 新建BPMN - 支付流程定义
注: 一定记得勾选流程的Excutable属性
,否则流程不可执行。
- Start Event - 支付请求
- Form - 关联支付请求表单
- End Event - 支付成功
- End Event - 用户拒绝支付
其中Task任务包括:
- Business Rule Task - 商品折扣规则
- User Task - 用户确认付款
- Form - 关联用户确认表单
- Service Task - 调用支付服务
其中Gateway网关包括:
- Exclusive Gateway - 是否需要用户确认?
- Exclusive Gateway - 用户是否同意支付?
3.1.1 任务Task相关设置
新建Task后,如下图选择扳手图标即可设置不同的Task类型。
Business Rule Task - 商品折扣规则
Business Rule Task - 商品折扣规则任务,主要需要定义如下内容:
- Id、Name属性
- DMN绑定 - 设置任务绑定DMN(引用DMN文件中的具体Decision.id),通过单独DMN文件定义决策规则,即定义商品折扣规则。
- 设置规则输出结果变量 - 设置对Decision中规则表格的输出结果的引用变量名称及类型
注: 关于Business Rule Task中的输出变量类型,包括:
- single Result(Map<String, Object) - 单行规则多个output,且仅命中唯一规则
- singleEntry(TypedValue) - 单行规则一个output,且仅命中唯一规则
- collecEntries(List<Object) - 单行规则一个output,且允许同时命中多条规则
- resultList(List<Map<String, Object>>) - 单行规则多个output,且允许同时命中多条规则
User Task - 用户确认付款
User Task - 用户确认付款任务,主要需要定义如下内容:
- Id、Name属性
- 设置用户任务的处理人 - 通过流程变量paymentAssignee(可任意调整变量名)设置需要处理该任务的用户ID。
- 绑定用户确认的form表单 - 即通过form-key的方式来绑定单独的FORM定义,在后续执行到此处用户任务时,即可在Camunda管理平台TaskList中展示对应的表单。
注:
不建议在BPMN中定义内嵌及关联表单,可由业务应用动态设置流程变量,增加灵活性
,
此示例中的相关FORM定义皆是为了学习、测试使用。
Service Task - 调用支付服务
关于Service Task(调用服务任务)调用服务的方式有多种:
- 调用Java代码
- External Task
- 调用web服务(REST、SOAP)
本示例使用调用Java代码方式,即自定义JavaDelegate实现。
Service Task - 调用支付服务任务,主要定义内容如下:
- Id、Name属性
- 设置对应Java Delegate实现 - 即执行到当前Service Task时会调用指定的JavaDelegate实现。
3.1.2 网关Gateway相关设置
本示例中使用的皆是排他网关Exclusive Gateway。
3.2 新建DMN - 商品折扣规则定义
点击商品折扣规则的左上角蓝色表格图标后,即可进入规则表格编辑界面,
3.3 新建FORM - 支付请求表单、用户确认表单
4. 启动流程应用
Camunda Process Engine是一个Java框架,持久层采用Mybatis,可以
- 内嵌集成到Java应用、
- SpringBooot应用中,
- 也可以独立运行(通过REST API提供服务,支持非Java语言)。
SpringBoot集成Camunda流程应用具体代码参见:
camunda-demo: 工作流引擎 Camunda示例
构建参考:Get started with Camunda and BPMN 2.0 | docs.camunda.org
4.1 流程定义自动部署
如前文描述的BPMN、DMN、FORM文件皆可以放在应用代码resource目录下,
支持嵌套文件夹,如resources/bpmn、resources/payment,如下图:
同时在resources/META-INF下添加processes.xml文件,
该文件用于描述流程应用关于流程自动部署的相关设置,
<process-applicationxmlns="http://www.camunda.org/schema/1.0/ProcessApplication"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><process-archive name="PaymentProcess"><process-engine>default</process-engine><!-- 默认会扫描resource(及其子目录下)的*.bpmn, *.cmmn, *.dmn文件 --><!-- 可通过<resource/>指定其他的需要一同部署的资源文件--><!-- 注:formKey中路径需和resource路径一致,如:camunda-forms:deployment:bpmn/payment_confirm.form --><!-- 注:通过Modeler作为附件一起部署,formKey格式为:camunda-forms:deployment:payment_confirm.form --><resource>bpmn/PaymentInitForm.form</resource><resource>bpmn/PaymentConfirmForm.form</resource><properties><property name="isDeleteUponUndeploy">false</property><property name="isScanForProcessDefinitions">true</property></properties></process-archive>
</process-application>
添加如上设置后,在流程应用启动后,即可自动将resources下的流程定义部署到Camunda仓库(数据库)中。
//启动流程 - 如开启支付请求
String businessKey = "111";
ProcessInstance processInstance = this.runtimeService.startProcessInstanceByKey("PaymentProcess", businessKey);Map processVariablesMap = CamundaUtils.convertProcessVariablesFromEntity(processVariables);
ProcessInstance processInstance = this.runtimeService.startProcessInstanceByKey("PaymentProcess", businessKey, processVariablesMap);//查询待处理任务 - 如获取指定用户待确认的支付请求
List<Task> taskList = this.taskService.createTaskQuery().taskAssignee("luo").processDefinitionKey("PaymentProcess").taskDefinitionKey("Task_UserConfirmPayment").orderByTaskCreateTime().listPage(0, 10);//完成任务 - 如确认支付
Map processVariablesMap = CamundaUtils.convertProcessVariablesFromEntity(processVariables);
this.taskService.complete(taskId, processVariablesMap);//查看历史数据
List<HistoricTaskInstance> historicTaskInstanceList = this.historyService.createHistoricTaskInstanceQuery().taskAssignee("luo").processDefinitionKey("PaymentProcess").taskDefinitionKey("Task_UserConfirmPayment").finished().orderByHistoricActivityInstanceStartTime().listPage(0, 10);
由于使用了自定义业务数据表biz_payment_process_info记录流程数据,
所以需要在流程执行过程中将流程变量中的相关数据同步到biz_payment_process_info中,
- 启动流程时 - 初始biz_payment_process_info数据,设置process_instance_id
- 启动流程时 - 需设置流程实例的businessKey为对应biz_payment_process_info的记录id
- 用户确认时 - 同步userApproval用户确认结果及折扣金额 到biz_payment_process_info中
- 调用支付服务时 - 同步paymentResult支付结果及支付时间 到biz_payment_process_info中
关于流程应用的编码及数据同步,可参见:
camunda-process-application/payment-application/…/BizPaymentProcessInfoServiceImpl.java
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luo.camunda.app.constants.PaymentProcessConstants;
import com.luo.camunda.app.enums.ConfirmResultEnum;
import com.luo.camunda.app.enums.PaymentResultEnum;
import com.luo.camunda.app.mapper.BizPaymentProcessInfoMapper;
import com.luo.camunda.app.model.entity.BizPaymentProcessInfo;
import com.luo.camunda.app.model.param.PaymentConfirmParam;
import com.luo.camunda.app.model.param.PaymentQueryParam;
import com.luo.camunda.app.model.param.PaymentRequestParam;
import com.luo.camunda.app.model.wrapper.PaymentProcessVariablesWrapper;
import com.luo.camunda.app.service.IBizPaymentProcessInfoService;
import com.luo.camunda.app.utils.CommonUtils;
import com.luo.camunda.common.model.param.ProcessVariablesQueryParam;
import com.luo.camunda.common.model.param.TaskQueryParam;
import com.luo.camunda.common.model.vo.TaskVo;
import com.luo.camunda.common.servcie.CamundaCommonService;
import com.luo.camunda.common.utils.CamundaUtils;
import com.luo.demo.sc.base.model.result.RespResult;
import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.history.HistoricTaskInstance;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Map;/*** 支付流程 - 业务数据 服务实现类** @author luohq* @since 2022-01-30*/
@Service
@Slf4j
public class BizPaymentProcessInfoServiceImpl extends ServiceImpl<BizPaymentProcessInfoMapper, BizPaymentProcessInfo> implements IBizPaymentProcessInfoService {@Resourceprivate CamundaCommonService camundaCommonService;@Override@Transactional(rollbackFor = Exception.class)public RespResult<String> startPaymentProcess(PaymentRequestParam paymentRequestParam) {/** 插入支付流程业务数据 */BizPaymentProcessInfo bizPaymentProcessInfo = new BizPaymentProcessInfo();//拷贝参数中的属性:productName, productPrice, paymentAssigneeBeanUtils.copyProperties(paymentRequestParam, bizPaymentProcessInfo);//设置业务数据默认状态bizPaymentProcessInfo.setPaymentResult(PaymentResultEnum.NOT_PAY.getCode());bizPaymentProcessInfo.setApprovalResult(ConfirmResultEnum.NOT_CONFIRM.getCode());log.info("保存支付流程业务数据,参数:{}", paymentRequestParam);Boolean result = this.save(bizPaymentProcessInfo);log.info("保存支付流程业务数据,结果:{}", result);/** 启动支付流程 */log.info("开启支付流程处理,processKey:{}, businessKey:{}", PaymentProcessConstants.PAYMENT_PROCESS_ID, bizPaymentProcessInfo.getId());ProcessInstance processInstance = this.camundaCommonService.startProcessInstance(PaymentProcessConstants.PAYMENT_PROCESS_ID,String.valueOf(bizPaymentProcessInfo.getId()),paymentRequestParam);log.info("开启支付流程处理,结果:{}", processInstance);/** 更新业务数据中的流程实例ID */result = this.updateById(BizPaymentProcessInfo.builder().processInstanceId(processInstance.getId()).id(bizPaymentProcessInfo.getId()).build());log.info("更新支付流程业务数据 - 流程实例ID,结果:{}", result);return RespResult.successData(bizPaymentProcessInfo.getProcessInstanceId());}@Overridepublic RespResult<TaskVo<BizPaymentProcessInfo>> queryTasks(TaskQueryParam taskQueryParam) {log.info("查询待处理任务列表,参数:{}", taskQueryParam);RespResult<TaskVo<BizPaymentProcessInfo>> respResult = this.camundaCommonService.queryRuntimeTasks(taskQueryParam, (bizKey) -> {return this.getById(Long.valueOf(bizKey));});log.info("查询待处理任务列表,结果:{}", respResult);return respResult;}@Overridepublic RespResult<HistoricTaskInstance> queryHistoryTasks(TaskQueryParam taskQueryParam) {return this.camundaCommonService.queryHistoryTasks(taskQueryParam);}@Override@Transactional(rollbackFor = Exception.class)public RespResult confirmPayment(PaymentConfirmParam paymentConfirmParam) {log.info("用户确认支付, 参数:{}", paymentConfirmParam);/** 查询当前流程实例已经存在的流程变量 */ProcessVariablesQueryParam processVariablesQueryParam = ProcessVariablesQueryParam.builder()//.taskId(paymentConfirmParam.getTaskId()).processInstanceId(paymentConfirmParam.getProcessInstanceId()).build();Map<String, Object> existProcessVariables = this.camundaCommonService.getRuntimeProcessVariables(processVariablesQueryParam);log.info("获取当前已存在流程变量,结果:{}", existProcessVariables);//使用流程变量包装器(便于获取属性)PaymentProcessVariablesWrapper existPaymentProcessVariablesWrapper = new PaymentProcessVariablesWrapper(existProcessVariables);/** 更新业务流程数据(更新范围:之前流程添加的 + 当前操作添加的 流程变量 ) */BizPaymentProcessInfo bizPaymentProcessInfo = BizPaymentProcessInfo.builder().productDiscount(existPaymentProcessVariablesWrapper.getProductDiscount()).productDiscountPrice(existPaymentProcessVariablesWrapper.getProductDiscountPrice()).approvalResult(paymentConfirmParam.getApprovalResult()).approvalTime(LocalDateTime.now()).id(Long.valueOf(paymentConfirmParam.getBizKey())).build();log.info("更新用户确认信息,参数:{}", bizPaymentProcessInfo);Boolean result = this.updateById(bizPaymentProcessInfo);log.info("更新用户确认信息,结果:{}", result);/** 更新流程引擎(完成用户确认任务 + 添加新的流程变量(用于后续网关判断)) */Map<String, Integer> processVariables = CamundaUtils.convertProcessVariablesFromPair(PaymentProcessConstants.PAYMENT_APPROVAL_RESULT_VAR_NAME, paymentConfirmParam.getApprovalResult());this.camundaCommonService.completeTask(paymentConfirmParam.getTaskId(), processVariables);return RespResult.success();}@Overridepublic RespResult<BizPaymentProcessInfo> queryPayments(PaymentQueryParam paymentQueryParam) {IPage<BizPaymentProcessInfo> pageResult = this.page(CommonUtils.convertPage(paymentQueryParam),Wrappers.<BizPaymentProcessInfo>lambdaQuery().eq(null != paymentQueryParam.getId(), BizPaymentProcessInfo::getId, paymentQueryParam.getId()).eq(null != paymentQueryParam.getProcessInstanceId(), BizPaymentProcessInfo::getProcessInstanceId, paymentQueryParam.getProcessInstanceId()).like(null != paymentQueryParam.getProductName(), BizPaymentProcessInfo::getProductName, paymentQueryParam.getProductName()).ge(null != paymentQueryParam.getProductPriceStart(), BizPaymentProcessInfo::getProductPrice, paymentQueryParam.getProductPriceStart()).le(null != paymentQueryParam.getProductPriceEnd(), BizPaymentProcessInfo::getProductPrice, paymentQueryParam.getProductPriceEnd()).ge(null != paymentQueryParam.getProductDiscountStart(), BizPaymentProcessInfo::getProductDiscount, paymentQueryParam.getProductDiscountStart()).le(null != paymentQueryParam.getProductDiscountEnd(), BizPaymentProcessInfo::getProductDiscount, paymentQueryParam.getProductDiscountEnd()).ge(null != paymentQueryParam.getProductDiscountPriceStart(), BizPaymentProcessInfo::getProductDiscountPrice, paymentQueryParam.getProductDiscountPriceStart()).le(null != paymentQueryParam.getProductDiscountPriceEnd(), BizPaymentProcessInfo::getProductDiscountPrice, paymentQueryParam.getProductDiscountPriceEnd()).eq(null != paymentQueryParam.getApprovalResult(), BizPaymentProcessInfo::getApprovalResult, paymentQueryParam.getApprovalResult()).eq(null != paymentQueryParam.getPaymentResult(), BizPaymentProcessInfo::getPaymentResult, paymentQueryParam.getPaymentResult()).eq(null != paymentQueryParam.getPaymentAssignee(), BizPaymentProcessInfo::getPaymentAssignee, paymentQueryParam.getPaymentAssignee()).ge(null != paymentQueryParam.getCreateTimeStart(), BizPaymentProcessInfo::getCreateTime, paymentQueryParam.getCreateTimeStart()).le(null != paymentQueryParam.getCreateTimeEnd(), BizPaymentProcessInfo::getCreateTime, paymentQueryParam.getCreateTimeEnd()).ge(null != paymentQueryParam.getApprovalTimeStart(), BizPaymentProcessInfo::getApprovalTime, paymentQueryParam.getApprovalTimeStart()).le(null != paymentQueryParam.getApprovalTimeEnd(), BizPaymentProcessInfo::getApprovalTime, paymentQueryParam.getApprovalTimeEnd()).ge(null != paymentQueryParam.getPaymentTimeStart(), BizPaymentProcessInfo::getPaymentTime, paymentQueryParam.getPaymentTimeStart()).le(null != paymentQueryParam.getPaymentTimeEnd(), BizPaymentProcessInfo::getPaymentTime, paymentQueryParam.getPaymentTimeEnd()));return CommonUtils.convertPageResult(pageResult);}}
4.4 调用支付服务 - JavaDelegate实现
import com.luo.camunda.app.enums.PaymentResultEnum;
import com.luo.camunda.app.model.wrapper.PaymentProcessVariablesWrapper;
import com.luo.camunda.app.model.entity.BizPaymentProcessInfo;
import com.luo.camunda.app.service.IBizPaymentProcessInfoService;
import com.luo.demo.sc.base.execption.MsgRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.time.LocalDateTime;/*** 支付活动 - Java代理** @author luohq* @date 2022-01-31*/
@Component
@Slf4j
public class PaymentDelegate implements JavaDelegate {@Resourceprivate IBizPaymentProcessInfoService bizPaymentProcessInfoService;@Override@Transactional(rollbackFor = Exception.class)public void execute(DelegateExecution execution) {/** 获取流程相关信息 */String bizKey = execution.getProcessBusinessKey();String processInstanceId = execution.getProcessInstanceId();log.info("调用支付服务,processInstanceId: {}, bizKey: {}", processInstanceId, bizKey);PaymentProcessVariablesWrapper paymentProcessVariablesWrapper = new PaymentProcessVariablesWrapper(execution.getVariables());log.info("RPC调用支付服务, 流程变量:{}", paymentProcessVariablesWrapper);/** 更新业务DB */BizPaymentProcessInfo bizPaymentProcessInfo = BizPaymentProcessInfo.builder().id(Long.valueOf(bizKey)).paymentResult(PaymentResultEnum.PAY_SUCCESS.getCode()).paymentTime(LocalDateTime.now()).build();log.info("更新支付结果, 参数:{}", bizPaymentProcessInfo);Boolean result = this.bizPaymentProcessInfoService.updateById(bizPaymentProcessInfo);log.info("更新支付结果, 结果:{}", result);//抛出异常,则此task执行失败,回退到上一步//throw new MsgRuntimeException("支付异常");}}
4.5 使用业务数据查询代替流程引擎查询
- 查询所有支付请求记录
- 查询指定用户的支付请求记录(payment_assignee=‘luo’)
- 查询用户不同意支付的请求(approval_result = 2 )
- 查询支付成功的支付请求(payment_result=1)
- …可根据自身需求调整业务数据设计
4.6 测试用例
- 首先用户开启支付流程 -
test01_startPayment
- 然后用户获取待处理任务 - 待用户确认的支付请求 -
test02_getTasks
- 提交用户确认请求 -
test03_confirmPayment
- 流程应用调用支付服务 -
PaymentDelegate
- 查询流程引擎历史任务 -
test04_getHistoryTasks
- 查询支付数据 -
test05_getPayments_all, test06_getPayments_not_confirm, test07_getPayments_with_condition
Camunda入门(四) - 流程应用(支付流程)入门示例相关推荐
- 微信小程序获取用户信息、登录流程、支付流程
1.获取用户信息 第一种: 通过 open-data 获取 <open-data type="userAvatarUrl"></open-data> 需要注 ...
- 微信小程序2--登录流程与支付流程简写
1.获取用户头像user.js opendata //user.js data: {flag:true, // 是否可以获取opendatacanIUseOpenData: wx.canIUse('o ...
- 小程序对接停车场支付流程思考
前言 最近一直在做公司的小程序对接停车场的需求.结果遇到了并发问题,出现一笔订单,用户支付两次的情况.现在对整个支付流程进行梳理.下面讨论的A用户和B用户都是对于同一订单处理的场景,对于不同订单,则没 ...
- 从支付宝SDK的支付流程理解什么是公钥和私钥,什么是加密和数字签名
2019独角兽企业重金招聘Python工程师标准>>> 名词解释 什么是公钥和私钥 首先要明白公钥和私钥只是一个相对概念,就是说我们不能单纯的去称呼一对密钥中的一个为公钥,另一个为私 ...
- 小程序下单账号与支付账号不一致不让支付_微信小程序支付流程
微信支付之小程序支付 微信的支付方式有以下几种,不同的支付方式适用于不同的支付场景,而今天要给大家讲的就是 小程序支付 方式 说到支付功能就要涉及到金钱交易,必定是有比较严格的规范及流程,如要求小程序 ...
- 微擎支付返回商户单号_微信小程序支付流程
微信支付之小程序支付 微信的支付方式有以下几种,不同的支付方式适用于不同的支付场景,而今天要给大家讲的就是 小程序支付 方式 说到支付功能就要涉及到金钱交易,必定是有比较严格的规范及流程,如要求小程序 ...
- 第三方支付系统-支付流程
为什么80%的码农都做不了架构师?>>> 目前来说,越来越多的行业互联网化,也掀起了互联网金融的浪潮,第三方支付的开发也越来越广泛,一般大型的第三方支付系统包括,前置系统,支付 ...
- 第三方支付系统--支付流程
目前来说,越来越多的行业互联网化,也掀起了互联网金融的浪潮,第三方支付的开发也越来越广泛,一般大型的第三方支付系统包括,前置系统,支付系统,渠道系统,账务系统,清结算系统,运营与维护管理平台.下面我们 ...
- php第三方支付系统--支付流程--及代码
目前来说,越来越多的行业互联网化,也掀起了互联网金融的浪潮,第三方支付的开发也越来越广泛,一般大型的第三方支付系统包括,前置系统,支付系统,渠道系统,账务系统,清结算系统,运营与维护管理平台.下面我们 ...
最新文章
- Fedora 17安装NVIDIA显卡驱动
- 【心得】Ctrl+Z、\n、\0、eof的区别和用法
- Linux下配置DNS
- Java应用服务器Tomcat
- Web前端——JavaScript(基本语法)
- python字典数据类型笔记_python笔记2-数据类型:元组、字典常用操作
- 易语言超文本ctrl c,易语言超文本浏览框处理键盘消息源码
- 关于caffe-ssd训练时smooth_L1到底参与运算与否的问题
- 在百度上搜不到的资源是在哪找的?就在这些强大的资源搜索网站呀
- 全国所有机场 sql表 (截至2021.1.25)绝对最全!!
- Matlab-数值计算方法作业
- 重新认识JavaScript面向对象: 从ES5到ES6
- Dem地形数据转换为cass支持的dat格式教程
- dell商务计算机主机闪烁黄灯,戴尔电脑开不了机 已经几天了 主机灯橙色 闪闪的...
- 杭州马开始坐不住,深圳腾窃喜,小龙信为何露出淫淫的笑容
- shell 99乘法口诀表
- 在线JSON转TSV工具
- 数据结构笔记:选择排序
- CDA数据分析师视频教程全套零基础入门excel考试教学课程2022
- 市场营销学【最精典】案例分析
热门文章
- BIM+9大技术,你知多少?
- Django----做一个简单网页的教程(适合初学者)
- vps文件服务器,vps搭建媒体文件服务器
- PHP解析错误 PHP Parse error: syntax error, unexpected '[' in
- xe android 摄像头,玩就要出彩 佳明VIRB XE运动摄像机评测
- Matlab算法之优化计算1
- FreeSwitch配置开启转码功能及安装G729语音编码
- 黑马程序员—C#多线程
- html页面解析 成dom树,将网页解析成dom树的几种方法
- 导航网站合集|高效工作学习