热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Activiti基础02:手把手带你来创建一个Activiti工作流

更多Java从0-1的笔记均在专栏中~Activiti基础01:从认识Activiti7开始,到环境配置一、Activiti入门在本章

更多Java从0-1的笔记均在专栏中~
Activiti基础01:从认识Activiti7开始,到环境配置

一、Activiti入门

在本章内容中,我们来创建一个Activiti工作流,并启动这个流程。

创建Activiti工作流主要包含以下几步:

1、定义流程,按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来

2、部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据

3、启动流程,使用java代码来操作数据库表中的内容

1.1 流程符号

BPMN 2.0是业务流程建模符号2.0的缩写。

它由Business Process Management Initiative这个非营利协会创建并不断发展。作为一种标识,BPMN 2.0是使用一些符号来明确业务流程设计流程图的一整套符号规范,它能增进业务建模时的沟通效率。

目前BPMN2.0是最新的版本,它用于在BPM上下文中进行布局和可视化的沟通。

接下来我们先来了解在流程设计中常见的 符号。

BPMN2.0的基本符合主要包含:

事件 Event

在这里插入图片描述

活动 Activity

活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程; 其次,你还可以为活动指定不同的类型。常见活动如下:

在这里插入图片描述

网关 GateWay

网关用来处理决策,有几种常用网关需要了解:

在这里插入图片描述

排他网关 (x)

——只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继续执行当前网关的输出流;

​ 如果多条线路计算结果都是 true,则会执行第一个值为 true 的线路。如果所有网关计算结果没有true,则引擎会抛出异常。

​ 排他网关需要和条件顺序流结合使用,default 属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。

并行网关 (+)

——所有路径会被同时选择

​ 拆分 —— 并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路。

​ 合并 —— 所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。

包容网关 (+)

—— 可以同时执行多条线路,也可以在网关上设置条件

​ 拆分 —— 计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行

​ 合并 —— 所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。

事件网关 (+)

—— 专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。

流向 Flow

流是连接两个流程节点的连线。常见的流向包含以下几种:

在这里插入图片描述

1.2 流程设计器使用


Activiti-Designer使用


Palette(画板)

在idea中安装插件即可使用,画板中包括以下结点:

Connection—连接

Event—事件

Task—任务

Gateway—网关

Container—容器

Boundary event—边界事件

Intermediate event- -中间事件

流程图设计完毕保存生成.bpmn文件

新建流程(IDEA工具)

首先选中存放图形的目录(选择resources下的bpmn目录),点击菜单:New -> BpmnFile,如图:

在这里插入图片描述

弹出如下图所示框,输入evection 表示 出差审批流程:

在这里插入图片描述

起完名字evection后(默认扩展名为bpmn),就可以看到流程设计页面,如图所示:

在这里插入图片描述

左侧区域是绘图区,右侧区域是palette画板区域

鼠标先点击画板的元素即可在左侧绘图

绘制流程

使用滑板来绘制流程,通过从右侧把图标拖拽到左侧的画板,最终效果如下:

在这里插入图片描述

指定流程定义Key

流程定义key即流程定义的标识,通过properties视图查看流程的key

在这里插入图片描述

指定任务负责人

在properties视图指定每个任务结点的负责人,如:填写出差申请的负责人为 zhangsan

在这里插入图片描述

经理审批负责人为 jerry

总经理审批负责人为 jack

财务审批负责人为 rose

二、流程操作

2.1 流程定义


概述

流程定义是线下按照bpmn2.0标准去描述 业务流程,通常使用idea中的插件对业务流程进行建模。

使用idea下的designer设计器绘制流程,并会生成两个文件:.bpmn和.png

.bpmn文件

使用activiti-desinger设计业务流程,会生成.bpmn文件,上面我们已经创建好了bpmn文件

BPMN 2.0根节点是definitions节点。 这个元素中,可以定义多个流程定义(不过我们建议每个文件只包含一个流程定义, 可以简化开发过程中的维护难度)。 注意,definitions元素 最少也要包含xmlns 和 targetNamespace的声明。 targetNamespace可以是任意值,它用来对流程实例进行分类。

流程定义部分:定义了流程每个结点的描述及结点之间的流程流转。

流程布局定义:定义流程每个结点在流程图上的位置坐标等信息。

生成.png图片文件

IDEA工具中的操作方式

1、修改文件后缀为xml

首先将evection.bpmn文件改名为evection.xml,如下图:

在这里插入图片描述

evection.xml修改前的bpmn文件,效果如下:

2、使用designer设计器打开.xml文件

在evection.xml文件上面,点右键并选择Diagrams菜单,再选择Show BPMN2.0 Designer…

在这里插入图片描述

3、查看打开的文件

打开后,却出现乱码,如图:

在这里插入图片描述

4、解决中文乱码

1、打开Settings,找到File Encodings,把encoding的选项都选择UTF-8

在这里插入图片描述

2、打开IDEA安装路径,找到如下的安装目录

在这里插入图片描述

根据自己所安装的版本来决定,我使用的是64位的idea,所以在idea64.exe.vmoptions文件的最后一行追加一条命令: -Dfile.encoding=UTF-8

如下所示:

在这里插入图片描述

一定注意,不要有空格,否则重启IDEA时会打不开,然后 重启IDEA。

如果以上方法已经做完,还出现乱码,就再修改一个文件,并在文件的末尾添加: -Dfile.encoding=UTF-8,然后重启idea,如图:

在这里插入图片描述

最后重新在evection.xml文件上面,点右键并选择Diagrams菜单,再选择Show BPMN2.0 Designer…,看到生成图片,如图:

在这里插入图片描述

到此,解决乱码问题

5、导出为图片文件

点击Export To File的小图标,打开如下窗口,注意填写文件名及扩展名,选择好保存图片的位置:

在这里插入图片描述

然后,我们把png文件拷贝到resources下的bpmn目录,并且把evection.xml改名为evection.bpmn。

2.2 流程定义部署


概述

将上面在设计器中定义的流程部署到activiti数据库中,就是流程定义部署。

通过调用activiti的api将流程定义的bpmn和png两个文件一个一个添加部署到activiti中,也可以将两个文件打成zip包进行部署。

单个文件部署方式

分别将bpmn文件和png图片文件部署。

package com.itheima.test;import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.junit.Test;public class ActivitiDemo {/*** 部署流程定义*/@Testpublic void testDeployment(){
// 1、创建ProcessEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、得到RepositoryService实例RepositoryService repositoryService = processEngine.getRepositoryService();
// 3、使用RepositoryService进行部署Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/evection.bpmn") // 添加bpmn资源.addClasspathResource("bpmn/evection.png") // 添加png资源.name("出差申请流程").deploy();
// 4、输出部署信息System.out.println("流程部署id:" + deployment.getId());System.out.println("流程部署名称:" + deployment.getName());}
}

执行此操作后activiti会将上边代码中指定的bpm文件和图片文件保存在activiti数据库。

压缩包部署方式

将evection.bpmn和evection.png压缩成zip包。

@Testpublic void deployProcessByZip() {// 定义zip输入流InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("bpmn/evection.zip");ZipInputStream zipInputStream = new ZipInputStream(inputStream);// 获取repositoryServiceRepositoryService repositoryService = processEngine.getRepositoryService();// 流程部署Deployment deployment = repositoryService.createDeployment().addZipInputStream(zipInputStream).deploy();System.out.println("流程部署id:" + deployment.getId());System.out.println("流程部署名称:" + deployment.getName());}

执行此操作后activiti会将上边代码中指定的bpm文件和图片文件保存在activiti数据库。

操作数据表

流程定义部署后操作activiti的3张表如下:

act_re_deployment 流程定义部署表,每部署一次增加一条记录

act_re_procdef 流程定义表,部署每个新的流程定义都会在这张表中增加一条记录

act_ge_bytearray 流程资源表

接下来我们来看看,写入了什么数据:

SELECT * FROM act_re_deployment #流程定义部署表,记录流程部署信息

结果:

在这里插入图片描述

SELECT * FROM act_re_procdef #流程定义表,记录流程定义信息

结果:

注意,KEY 这个字段是用来唯一识别不同流程的关键字

在这里插入图片描述

SELECT * FROM act_ge_bytearray #资源表

结果:

在这里插入图片描述

注意:

act_re_deployment和act_re_procdef一对多关系,一次部署在流程部署表生成一条记录,但一次部署可以部署多个流程定义,每个流程定义在流程定义表生成一条记录。每一个流程定义在act_ge_bytearray会存在两个资源记录,bpmn和png。

建议:一次部署一个流程,这样部署表和流程定义表是一对一有关系,方便读取流程部署及流程定义信息。

2.3 启动流程实例

流程定义部署在activiti后就可以通过工作流管理业务流程了,也就是说上边部署的出差申请流程可以使用了。

针对该流程,启动一个流程表示发起一个新的出差申请单,这就相当于java类与java对象的关系,类定义好后需要new创建一个对象使用,当然可以new多个对象。对于请出差申请流程,张三发起一个出差申请单需要启动一个流程实例,出差申请单发起一个出差单也需要启动一个流程实例。

代码如下:

/*** 启动流程实例*/@Testpublic void testStartProcess(){
// 1、创建ProcessEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取RunTimeServiceRuntimeService runtimeService = processEngine.getRuntimeService();
// 3、根据流程定义Id启动流程ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myEvection");
// 输出内容System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());System.out.println("流程实例id:" + processInstance.getId());System.out.println("当前活动Id:" + processInstance.getActivityId());}

输出内容如下:

在这里插入图片描述

操作数据表

act_hi_actinst 流程实例执行历史

act_hi_identitylink 流程的参与用户历史信息

act_hi_procinst 流程实例历史信息

act_hi_taskinst 流程任务历史信息

act_ru_execution 流程执行信息

act_ru_identitylink 流程的参与用户信息

act_ru_task 任务信息

2.4 任务查询

流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。

/*** 查询当前个人待执行的任务*/@Testpublic void testFindPersonalTaskList() {
// 任务负责人String assignee = "zhangsan";ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskServiceTaskService taskService = processEngine.getTaskService();
// 根据流程key 和 任务负责人 查询任务List<Task> list &#61; taskService.createTaskQuery().processDefinitionKey("myEvection") //流程Key.taskAssignee(assignee)//只查询该任务负责人的任务.list();for (Task task : list) {System.out.println("流程实例id&#xff1a;" &#43; task.getProcessInstanceId());System.out.println("任务id&#xff1a;" &#43; task.getId());System.out.println("任务负责人&#xff1a;" &#43; task.getAssignee());System.out.println("任务名称&#xff1a;" &#43; task.getName());}}

输出结果如下&#xff1a;

流程实例id&#xff1a;2501
任务id&#xff1a;2505
任务负责人&#xff1a;zhangsan
任务名称&#xff1a;创建出差申请

2.5 流程任务处理

任务负责人查询待办任务&#xff0c;选择任务进行处理&#xff0c;完成任务。

// 完成任务&#64;Testpublic void completTask(){
// 获取引擎ProcessEngine processEngine &#61; ProcessEngines.getDefaultProcessEngine();
// 获取taskServiceTaskService taskService &#61; processEngine.getTaskService();// 根据流程key 和 任务的负责人 查询任务
// 返回一个任务对象Task task &#61; taskService.createTaskQuery().processDefinitionKey("myEvection") //流程Key.taskAssignee("zhangsan") //要查询的负责人.singleResult();// 完成任务,参数&#xff1a;任务idtaskService.complete(task.getId());}

2.6 流程定义信息查询

查询流程相关信息&#xff0c;包含流程定义&#xff0c;流程部署&#xff0c;流程定义版本

/*** 查询流程定义*/&#64;Testpublic void queryProcessDefinition(){// 获取引擎ProcessEngine processEngine &#61; ProcessEngines.getDefaultProcessEngine();
// repositoryServiceRepositoryService repositoryService &#61; processEngine.getRepositoryService();
// 得到ProcessDefinitionQuery 对象ProcessDefinitionQuery processDefinitionQuery &#61; repositoryService.createProcessDefinitionQuery();
// 查询出当前所有的流程定义
// 条件&#xff1a;processDefinitionKey &#61;evection
// orderByProcessDefinitionVersion 按照版本排序
// desc倒叙
// list 返回集合List<ProcessDefinition> definitionList &#61; processDefinitionQuery.processDefinitionKey("myEvection").orderByProcessDefinitionVersion().desc().list();
// 输出流程定义信息for (ProcessDefinition processDefinition : definitionList) {System.out.println("流程定义 id&#61;"&#43;processDefinition.getId());System.out.println("流程定义 name&#61;"&#43;processDefinition.getName());System.out.println("流程定义 key&#61;"&#43;processDefinition.getKey());System.out.println("流程定义 Version&#61;"&#43;processDefinition.getVersion());System.out.println("流程部署ID &#61;"&#43;processDefinition.getDeploymentId());}}

输出结果&#xff1a;

流程定义id&#xff1a;myEvection:1:4
流程定义名称&#xff1a;出差申请单
流程定义key&#xff1a;myEvection
流程定义版本&#xff1a;1

2.7 流程删除

public void deleteDeployment() {// 流程部署idString deploymentId &#61; "1";ProcessEngine processEngine &#61; ProcessEngines.getDefaultProcessEngine();// 通过流程引擎获取repositoryServiceRepositoryService repositoryService &#61; processEngine.getRepositoryService();//删除流程定义&#xff0c;如果该流程定义已有流程实例启动则删除时出错repositoryService.deleteDeployment(deploymentId);//设置true 级联删除流程定义&#xff0c;即使该流程有流程实例启动也可以删除&#xff0c;设置为false非级别删除方式&#xff0c;如果流程//repositoryService.deleteDeployment(deploymentId, true);}

说明&#xff1a;

  1. 使用repositoryService删除流程定义&#xff0c;历史表信息不会被删除

  2. 如果该流程定义下没有正在运行的流程&#xff0c;则可以用普通删除。

如果该流程定义下存在已经运行的流程&#xff0c;使用普通删除报错&#xff0c;可用级联删除方法将流程及相关记录全部删除。

先删除没有完成流程节点&#xff0c;最后就可以完全删除流程定义信息

项目开发中级联删除操作一般只开放给超级管理员使用.

2.8 流程资源下载

现在我们的流程资源文件已经上传到数据库了&#xff0c;如果其他用户想要查看这些资源文件&#xff0c;可以从数据库中把资源文件下载到本地。

解决方案有&#xff1a;

1、jdbc对blob类型&#xff0c;clob类型数据读取出来&#xff0c;保存到文件目录

2、使用activiti的api来实现

使用commons-io.jar 解决IO的操作

引入commons-io依赖包

<dependency><groupId>commons-iogroupId><artifactId>commons-ioartifactId><version>2.6version>
dependency>

通过流程定义对象获取流程定义资源&#xff0c;获取bpmn和png

import org.apache.commons.io.IOUtils;&#64;Testpublic void deleteDeployment(){
// 获取引擎ProcessEngine processEngine &#61; ProcessEngines.getDefaultProcessEngine();
// 获取repositoryServiceRepositoryService repositoryService &#61; processEngine.getRepositoryService();
// 根据部署id 删除部署信息,如果想要级联删除&#xff0c;可以添加第二个参数&#xff0c;truerepositoryService.deleteDeployment("1");}public void queryBpmnFile() throws IOException {
// 1、得到引擎ProcessEngine processEngine &#61; ProcessEngines.getDefaultProcessEngine();
// 2、获取repositoryServiceRepositoryService repositoryService &#61; processEngine.getRepositoryService();
// 3、得到查询器&#xff1a;ProcessDefinitionQuery&#xff0c;设置查询条件,得到想要的流程定义ProcessDefinition processDefinition &#61; repositoryService.createProcessDefinitionQuery().processDefinitionKey("myEvection").singleResult();
// 4、通过流程定义信息&#xff0c;得到部署IDString deploymentId &#61; processDefinition.getDeploymentId();
// 5、通过repositoryService的方法&#xff0c;实现读取图片信息和bpmn信息
// png图片的流InputStream pngInput &#61; repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
// bpmn文件的流InputStream bpmnInput &#61; repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
// 6、构造OutputStream流File file_png &#61; new File("d:/evectionflow01.png");File file_bpmn &#61; new File("d:/evectionflow01.bpmn");FileOutputStream bpmnOut &#61; new FileOutputStream(file_bpmn);FileOutputStream pngOut &#61; new FileOutputStream(file_png);
// 7、输入流&#xff0c;输出流的转换IOUtils.copy(pngInput,pngOut);IOUtils.copy(bpmnInput,bpmnOut);
// 8、关闭流pngOut.close();bpmnOut.close();pngInput.close();bpmnInput.close();}

说明&#xff1a;

  1. deploymentId为流程部署ID

  2. resource_name为act_ge_bytearray表中NAME_列的值

  3. 使用repositoryService的getDeploymentResourceNames方法可以获取指定部署下得所有文件的名称

  4. 使用repositoryService的getResourceAsStream方法传入部署ID和资源图片名称可以获取部署下指定名称文件的输入流

最后的将输入流中的图片资源进行输出。

2.9 流程历史信息的查看

即使流程定义已经删除了&#xff0c;流程执行的历史信息通过前面的分析&#xff0c;依然保存在activiti的act_hi_*相关的表中。所以我们还是可以查询流程执行的历史信息&#xff0c;可以通过HistoryService来查看相关的历史记录。

/*** 查看历史信息*/&#64;Testpublic void findHistoryInfo(){
// 获取引擎ProcessEngine processEngine &#61; ProcessEngines.getDefaultProcessEngine();
// 获取HistoryServiceHistoryService historyService &#61; processEngine.getHistoryService();
// 获取 actinst表的查询对象HistoricActivityInstanceQuery instanceQuery &#61; historyService.createHistoricActivityInstanceQuery();
// 查询 actinst表&#xff0c;条件&#xff1a;根据 InstanceId 查询
// instanceQuery.processInstanceId("2501");
// 查询 actinst表&#xff0c;条件&#xff1a;根据 DefinitionId 查询instanceQuery.processDefinitionId("myEvection:1:4");
// 增加排序操作,orderByHistoricActivityInstanceStartTime 根据开始时间排序 asc 升序instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
// 查询所有内容List<HistoricActivityInstance> activityInstanceList &#61; instanceQuery.list();
// 输出for (HistoricActivityInstance hi : activityInstanceList) {System.out.println(hi.getActivityId());System.out.println(hi.getActivityName());System.out.println(hi.getProcessDefinitionId());System.out.println(hi.getProcessInstanceId());System.out.println("<&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;>");}}


推荐阅读
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文讨论了在Spring 3.1中,数据源未能自动连接到@Configuration类的错误原因,并提供了解决方法。作者发现了错误的原因,并在代码中手动定义了PersistenceAnnotationBeanPostProcessor。作者删除了该定义后,问题得到解决。此外,作者还指出了默认的PersistenceAnnotationBeanPostProcessor的注册方式,并提供了自定义该bean定义的方法。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
author-avatar
平凡小店88
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有