微信精选文章
更多文章请微信搜索“灯塔量化”公众号,跟我一起学习投资理财知识!
1 | <startEvent id="startevent1" name="Start"> |
1 | FormService formService = engine.getFormService(); |
1 | //这样也会执行任务到下一个任务节点 |
activiti.cfg.xml配置文件添加配置:
添加一个bean实现ActivitiEventListener接口
根据ActivitiEventType枚举判断监听的类型
程序添加runtimeService.addEventListener(new MyEventListenner());
runtimeService.dispatchEvent(new ActivitiEventImpl(ActivitiEventType.CUSTOM));
利用多实例,实现多个审核通过
taskAssignee 优先 taskCandidateGroup
delegate > setAssignee > claim 后两者都是设置Assignee(代理人),设置claim时,存在就会抛出异常不会设置成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- spring模式加载的class不同 -->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db_activiti" />
<!-- 数据库配置也可以直接用数据源dataSource注入 -->
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="root" />
<!-- false:关闭;true:当表不存在时自动创建;create-drop/drop-create -->
<property name="databaseSchemaUpdate" value="true" />
<!-- 监控流程作业,默认false -->
<property name="jobExecutorActivate" value="false" />
<!--
none: 跳过所有的历史数据。性能最优,但是没有历史数据。
activity: 保存所有的流程实例和活动信息。只保留最后信息,没有明细信息。
audit: 默认级别。保存所有的流程实例,任务、活动、表单属性等信息。
full: 最高级别,最完整的的历史信息。除了audit中的信息外,还包含详细信息。
-->
<property name="history" value="audit" />
<!-- 打开异步处理Service任务 -->
<property name="asyncExecutorActivate" value="true"></property>
<!-- 发布流程不生成流程图 -->
<property name="createDiagramOnDeploy" value="false" />
<!-- 字体 -->
<property name="activityFontName" value="宋体" />
<property name="labelFontName" value="宋体" />
<!-- 不保存流程图片 -->
<property name="createDiagramOnDeploy" value="false" />
<property name="DbIdentityUsed" value="true" />
<property name="DbHistoryUsed" value="true" />
<property name="databaseTablePrefix" value="T_" />
<property name="databaseType" value="mysql" />
</bean>
</beans>
RepositoryService
流程仓库Service,用于管理流程仓库,如部署、删除、读取流程资源。
IdentifyService
身份Service,管理和查询用户、组之间的关系。
RuntimeService
运行时Service,管理所有正在运行状态的流程实例、任务等。
TaskService
任务Service,用于管理、查询任务,如签收、办理、指派等。
FormService
表单Service,用于读取和流程、任务相关的表单数据。
HistoryService
历史Service,可以查询所有历史数据。
ManagementService
引擎管理Service,和具体业务无关,主要是可以查询引擎配置、数据库、作业等。
7个service都是通过ProcessEngine对象来获取的
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
定时器事件定义
可以用在开始事件、中间事件、边界事件。可选指定时间触发、循环触发。支持cron表达式
错误事件定义
可以用在开始事件、结束事件、边界事件。捕获/抛出错误。
信号事件定义
通过一个信号控制多个流程实例,可以用在边界事件、中间事件。
消息事件定义
一个消息控制一个流程实例
取消事件定义
用在事务子流程模型中,有取消边界事件和取消结束事件
补偿事件定义
补偿事件
其他事件
定时器开始事件
必须在配置文件打开异步处理Service任务,可以设置定时器触发方式
消息开始事件
通过消息名称启动流程:
ProcessInstance pi = runService.startProcessInstanceByMessage(“msgName”);
流程配置文件添加<message id="msgA" name="msgName"></message>
流程中抛出异常,在事件子流程开始错误事件中捕获异常
流程配置文件添加<error id="countError" errorCode="abc"></error>
处理类抛出throw new org.activiti.engine.delegate.BpmnError("abc");
无指定结束事件
错误结束事件
流程以错误事件结束,然后事务子流程中就会捕获,从而执行子流程。或者触发边界错误捕获流程。
取消结束事件
在事务子流程中,触发取消结束事件后,就会触发所有补偿边界事件,然后先执行补偿边界事件,再判断整个事务子流程中是否有边界事件,有就执行事务子流程中的。
1
2
3
4 <endEvent id="cancelendevent1" name="CancelEnd">
<cancelEventDefinition></cancelEventDefinition>
</endEvent>
>
终止结束事件
当一个流程实例中,有多个任务都可以执行(并行网关),有一条执行到了终止结束事件,那么整个流程实例都终止了。
定时器边界事件
当触发定时器时,执行定时器指定的任务
错误边界事件
捕获异常:
throw new org.activiti.engine.delegate.BpmnError("abc");
信号边界事件
,当执行到某个节点的时候触发信号或者消息时,执行另一个任务。申请–>审批–>审批不通过触发事件–>回到申请
取消边界事件
当事务子流程中,取消结束事件被触发后,补偿边界事件触发,然后子流程取消边界事件也会触发
补偿边界事件
1
2
3
4 <boundaryEvent id="boundarycompensation1" name="Compensate" attachedToRef="usertask1" cancelActivity="true">
<compensateEventDefinition></compensateEventDefinition>
</boundaryEvent>
>
必须等待定时器触发后才能执行后面的节点任务(下个节点延迟拿到执行)
1
2
3
4
5
6 <intermediateCatchEvent id="timerintermediatecatchevent1"
name="TimerCatchEvent">
<timerEventDefinition>
<timeDuration>PT5S</timeDuration>
</timerEventDefinition>
</intermediateCatchEvent>
>
信号中间事件(捕获和抛出)
比如并行网关里,多条执行任务都是捕获信号中间事件,然后其中一条任务执行完成后,抛出信号中间事件,其他有捕获信号中间时间的执行任务才能往下走。(中间信号事件是自动触发的)
1
2
3
4
5
6
7
8
9
10
11 <signal id="finishPay" name="finishPay"></signal>
<!-- 捕获 -->
<intermediateThrowEvent id="signalintermediatethrowevent1" name="SignalThrowEvent">
<signalEventDefinition signalRef="finishPay"></signalEventDefinition>
</intermediateThrowEvent>
<!-- 抛出 -->
<intermediateCatchEvent id="signalintermediatecatchevent2"
name="SignalCatchEvent">
<signalEventDefinition signalRef="finishPay"></signalEventDefinition>
</intermediateCatchEvent>
>
消息中间事件(捕获)
1
2
3
4 Execution run = runService.createExecutionQuery().messageEventSubscriptionName("message").singleResult();
System.out.println(run.getId());
runService.messageEventReceived("message", run.getId());
>
1
2
3
4
5
6
7
8
9 // 启动流程
ProcessInstance pi = runService.startProcessInstanceById(pd.getId());
// 查当前的子执行流(只有一个)
Execution exe = runService.createExecutionQuery()
.processInstanceId(pi.getId()).onlyChildExecutions()
.singleResult();
System.out.println(pi.getId() + ", 当前节点:" + exe.getActivityId());
runService.signalEventReceived("testSignal");
>
- 事务子流程有取消结束事件就会执行子流程中补偿边界事件
- 触发补偿中间事件==>触发边界补偿事件
触发补偿中间事件就会触发所有之前任务的边界补偿事件
分配任务的候选人
- 方式1:使用taskService的addCandidateGroup和addCandidateUser方法
- 方式2:使用XML配置
id
1
2
3
4
5
6
7 <potentialOwner>
<resourceAssignmentExpression>
<formalExpression>user(angus), group(management), boss
</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
分配任务代理人
一个任务可以分配多个候选人,但是只能有一个代理人
- 方式1:使用task对象的setAssignee方法设置代理人
- 方式2:使用XML配置
id
1
2
3
4
5
6 <humanPerformer>
<resourceAssignmentExpression>
<formalExpression>user</formalExpression>
</resourceAssignmentExpression>
</humanPerformer>
> </userTask>
权限分配扩展
1
2
3
4 <userTask id="usertask1" name="Task A" activiti:assignee="user1"
activiti:candidateUsers="angus,angus1" activiti:candidateGroups="boss,management">
</userTask>
>
当设置代理人的时候,该任务只属于该代理人
使用任务监听器进行权限分配
见监听器
使用JUEL分配权限
1
2
3
4 <userTask id="usertask1" name="My Task" activiti:candidateUsers="${authService.getCandidateUsers()}"
activiti:candidateGroups="${authService.getCandidateGroups()}" activiti:assignee="${authService.getAssignee()}"
></userTask>
>
启动流程:
1
2
3
4 Map<String, Object> vars = new HashMap<String, Object>();
vars.put("authService", new AuthService());
ProcessInstance pi = runService.startProcessInstanceById(pd.getId(), vars);
>AuthService类中方法返回
List<String>
获取String
类型即可
Java Service Task
必须写死类路径
activiti:delegateExpression
activiti:delegateExpression="${myDelegate}"
1
2
3
4
5
6
7 //MyDelegate实现JavaDelegate和Serializable
MyDelegate de = new MyDelegate();
Map<String ,Object> vars = new HashMap<String, Object>();
vars.put("myDelegate", de);
// 启动流程
ProcessInstance pi = runService.startProcessInstanceById(pd.getId(), vars);
>
activiti:expression
activiti:expression="${myBean.print(execution)}"
activiti:expression="${execution.setVariable('myName', myBean.name)}"
1
2
3
4
5
6 Map<String ,Object> vars = new HashMap<String, Object>();
//print方法传入Execution exe,可以获取当前的任务
vars.put("myBean", new MyBean());//实现Serializable接口,并且有name成员变量,getName方法
// 启动流程
ProcessInstance pi = runService.startProcessInstanceById(pd.getId(), vars);
>
JavaDelegate 会自动执行,ActivityBehavior 会等待
1
2
3
4
5
6
7
8
9
10 <serviceTask id="servicetask1" name="Service Task" activiti:type="shell">
<extensionElements>
<activiti:field name="command" stringValue="cmd"/>
<activiti:field name="arg1" stringValue="/c"/>
<activiti:field name="arg2" stringValue="echo"/>
<activiti:field name="arg3" stringValue="%JAVA_HOME%"/>
<activiti:field name="outputVariable" stringValue="javaHome"/>
</extensionElements>
</serviceTask>
>
获取参数值
runService.getVariable(pi.getId(), "javaHome")
- Receive Task
1
2
3
4
5
6
7
8
9
10 // 启动流程
ProcessInstance pi = runService.startProcessInstanceById(pd.getId());
// 查当前的子执行流(只有一个)
Execution exe = runService.createExecutionQuery()
.processInstanceId(pi.getId()).onlyChildExecutions()
.singleResult();
System.out.println(pi.getId() + ", 当前节点:" + exe.getActivityId());
// 让它往前走
runService.trigger(exe.getId());
>
Web Service Task
用不上
3、其他任务与流程监听器
其他任务
手工任务
自己会执行,会记录历史数据
自动发送邮件
任务监听器
同Service Task有三种配置方式:
监听器触发:
create
流程创建时
assignment
流程任务分配
complete
任务完成
1 | <userTask id="usertask1" name="User Task" activiti:assignee="crazyit"> |
监听器也可以获取流程中设置的参数
监听器实现TaskListener接口1
2
3
4
5
6
7
8
9
10
11
12
13
14public class MyTaskListener implements TaskListener {
private FixedValue userName;
public void setUserName(FixedValue userName) {
this.userName = userName;
}
@Override
public void notify(DelegateTask arg0) {
System.out.println("这是自定义任务监听器, " + userName.getExpressionText());
}
}
流程监听器
监听器触发:
start
流程开始
take
流程经过
end
流程完成
1 | <extensionElements> |
监听器实现类是ExecutionListener
子流程与外部任务交互必须经过边界事件
调用式子流程
调用外部流程
添加callActivity节点,指定外部流程ID:SubProcess
<callActivity id="callactivity1" name="调用其他的流程" calledElement="SubProcess"></callActivity>
获取外部流程的实例:ProcessInstance piSub = runService.createProcessInstanceQuery(). superProcessInstanceId(pi.getId()).singleResult();
传递参数:
1
2
3
4
5 <extensionElements>
<activiti:in source="days" target="newDays"/>
<activiti:out source="myDays" target="resultDays"/>
</extensionElements>
>
事件子流程
可以由错误、信号、消息、定时器事件触发
在事务子流程中出现异常,子流程边界错误事件会捕捉到。事务子流程中出现取消结束流程,会触发之前的流程任务的边界补偿事务,同时触发子流程取消边界事务
6.0 新增,在该子流程中,不存在执行顺序,由执行时决定
条件顺序流1
2
3
4<sequenceFlow id="flow3" sourceRef="exclusivegateway1" targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${days <= 3 || day == 5}]]></conditionExpression>
</sequenceFlow>
如果是条件网关的话,不满足条件会报错。多条满足,会默认走第一个
普通顺序流
一个任务不能出现多条普通顺序流吧
网关
- 单向网关
与条件顺序流一起使用,只能有一个条件通过并行网关
兼容网关
单向网关和并行网关结合体- 事件网关
和中间事务结合使用,多个任务的时候,哪个任务先触发就执行哪条任务- 组合网关
不支持
多实例流程活动
创建多个任务实例
1
2
3
4
5
6
7 <serviceTask id="servicetask1" name="Service Task" activiti:class="org.crazyit.act.c10.ForeachDelegate">
<multiInstanceLoopCharacteristics
isSequential="false">
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
</serviceTask>
>
设置循环数据(6.0版本)
1
2
3
4 Map<String, Object> vars = new HashMap<String, Object>();
vars.put("datas1", datas1); //List<String>格式
ProcessInstance pi = runService.startProcessInstanceById(pd.getId(), vars);
>
1
2
3
4
5
6 <serviceTask id="servicetask1" name="Service Task" activiti:class="org.crazyit.act.c10.ForeachDelegate">
<multiInstanceLoopCharacteristics isSequential="false" activiti:elementVariable="data">
<loopDataInputRef>datas1</loopDataInputRef>
</multiInstanceLoopCharacteristics>
</serviceTask>
>
1
2
3
4
5
6
7
8 //处理类,List集合有多少个元素就执行几次
public class ForeachDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("执行服务任务: " + execution.getVariable("data"));
}
}
>设置了循环了数据,会有内置参数,例如nrOfCompletedInstances是当前执行的实例数
添加<completionCondition>${nrOfCompletedInstances >= 2}</completionCondition>
设置条件执行流程
查询所有
通过service获取,各个service查询类似。service.createGroupQuery().groupName("Group_1").groupType("TYPE_1").list();
分页查询service.createGroupQuery().listPage(1, 5)
查询单个service.createGroupQuery().groupName("Group_0").singleResult();
排序service.createGroupQuery().orderByGroupId().desc().orderByGroupName().asc().list()
自定义查询service.createNativeGroupQuery().
sql("SELECT * FROM ACT_ID_GROUP where NAME_ = #{name}").
parameter("name", "Group_2").list();
保存service.newGroup(id);
//新增一个执行ID值
###2、流程部署
ZIP1
2
3
4FileInputStream fis = new FileInputStream(new File("resource/datas.zip"));
ZipInputStream zis = new ZipInputStream(fis);
builder.addZipInputStream(zis);
builder.deploy();
代码生成流程模型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public static void main(String[] args) {
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 存储服务
RepositoryService rs = engine.getRepositoryService();
RuntimeService run = engine.getRuntimeService();
DeploymentBuilder builder = rs.createDeployment();
builder.addBpmnModel("My Process", createProcessModel());
Deployment dep = builder.deploy();
}
private static BpmnModel createProcessModel() {
// 创建BPMN模型对象
BpmnModel model = new BpmnModel();
// 创建一个流程定义
org.activiti.bpmn.model.Process process = new org.activiti.bpmn.model.Process();
model.addProcess(process);
process.setId("myProcess");
process.setName("My Process");
// 开始事件
StartEvent startEvent = new StartEvent();
startEvent.setId("startEvent");
process.addFlowElement(startEvent);
// 用户任务
UserTask userTask = new UserTask();
userTask.setName("User Task");
userTask.setId("userTask");
process.addFlowElement(userTask);
// 结束事件
EndEvent endEvent = new EndEvent();
endEvent.setId("endEvent");
process.addFlowElement(endEvent);
// 添加流程顺序
process.addFlowElement(new SequenceFlow("startEvent", "userTask"));
process.addFlowElement(new SequenceFlow("userTask", "endEvent"));
return model;
}
关闭流程部署验证1
2
3
4DeploymentBuilder builder = rs.createDeployment();
builder.addClasspathResource("error/schema_error.bpmn");
builder.disableSchemaValidation(); //关闭
builder.deploy();
查询文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 存储服务
RepositoryService rs = engine.getRepositoryService();
DeploymentBuilder builder = rs.createDeployment();
builder.addClasspathResource("my_text.txt");
Deployment dep = builder.deploy();
// 数据查询
InputStream is = rs.getResourceAsStream(dep.getId(), "my_text.txt");
int count = is.available();
byte[] contents = new byte[count];
is.read(contents);
String result = new String(contents);
//输入结果
System.out.println(result);
查询流程文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
// 部署一份流程文件
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("gen.bpmn").deploy();
// 查询流程定义
//查询流程定义实体
ProcessDefinition def = repositoryService.createProcessDefinitionQuery()
.deploymentId(dep.getId()).singleResult();
// 查询资源文件
InputStream is = repositoryService.getProcessModel(def.getId());
// 读取输入流
int count = is.available();
byte[] contents = new byte[count];
is.read(contents);
String result = new String(contents);
//输入输出结果
System.out.println(result);
查询流程图片1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
// 部署一份流程文件与相应的流程图文件
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("gen.bpmn").deploy();
// 查询流程定义
ProcessDefinition def = repositoryService.createProcessDefinitionQuery()
.deploymentId(dep.getId()).singleResult();
// 查询资源文件
InputStream is = repositoryService.getProcessDiagram(def.getId());
// 将输入流转换为图片对象
BufferedImage image = ImageIO.read(is);
// 保存为图片文件
File file = new File("resource/result.png");
if (!file.exists()) file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
ImageIO.write(image, "png", fos);
fos.close();
is.close();
删除流程部署1
2repositoryService.deleteDeployment(dep.getId());
repositoryService.deleteDeployment(dep.getId(), true); //级联删除(会删除运行实例和历史数据)
自己指定流程图片1
2
3
4
5
6
7ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 存储服务
RepositoryService rs = engine.getRepositoryService();
DeploymentBuilder builder = rs.createDeployment();
builder.addClasspathResource("test2.bpmn").addClasspathResource("test2.png");
builder.deploy();
流程中止
流程被中止后是不能被重新启动的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 存储服务
RepositoryService rs = engine.getRepositoryService();
DeploymentBuilder builder = rs.createDeployment();
builder.addClasspathResource("test3.bpmn");
Deployment dep = builder.deploy();
ProcessDefinition def = rs.createProcessDefinitionQuery()
.deploymentId(dep.getId()).singleResult();
System.out.println("id: " + def.getId());
rs.suspendProcessDefinitionByKey(def.getKey());
// 将会抛出异常,因为流程定义被中止了
RuntimeService runService = engine.getRuntimeService();
runService.startProcessInstanceByKey(def.getKey());
启动流程任务权限1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 存储服务
RepositoryService rs = engine.getRepositoryService();
IdentityService is = engine.getIdentityService();
TaskService ts = engine.getTaskService();
User user = is.newUser(UUID.randomUUID().toString());
user.setFirstName("Angus");
is.saveUser(user);
DeploymentBuilder builder = rs.createDeployment();
builder.addClasspathResource("test3.bpmn");
Deployment dep = builder.deploy();
ProcessDefinition def = rs.createProcessDefinitionQuery()
.deploymentId(dep.getId()).singleResult();
rs.addCandidateStarterUser(def.getId(), user.getId());
//查询该用户有多少个流程可以启动
List<ProcessDefinition> defs = rs.createProcessDefinitionQuery().startableByUser(user.getId()).list();
for(ProcessDefinition de : defs) {
System.out.println(de.getId());
}
###4、流程任务
任务候选人(组)
多少个用户或组能看见这个任务1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService ts = engine.getTaskService();
IdentityService is = engine.getIdentityService();
// 创建任务
String taskId = UUID.randomUUID().toString();
Task task = ts.newTask(taskId);
task.setName("test");
ts.saveTask(task);
// 创建用户
String userId = UUID.randomUUID().toString();
User user = is.newUser(userId);
user.setFirstName("angus");
is.saveUser(user);
// 设置任务的候选用户组
ts.addCandidateUser(taskId, userId);
/*添加用户到组*/
//is.createMembership(userId, groupId);
List<Task> tasks = ts.createTaskQuery().taskCandidateUser(userId).list();
System.out.println(userId + " 这个用户有权限处理的任务有:");
for(Task t : tasks) {
System.out.println(t.getName());
}
任务持有人
当前处理该任务用户,一个任务只有一个持有人1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService ts = engine.getTaskService();
IdentityService is = engine.getIdentityService();
// 创建任务
String taskId = UUID.randomUUID().toString();
Task task = ts.newTask(taskId);
task.setName("test1");
ts.saveTask(task);
// 创建用户
String userId = UUID.randomUUID().toString();
User user = is.newUser(userId);
user.setFirstName("angus1");
is.saveUser(user);
// 设置任务持有人
ts.setOwner(taskId, userId);
// ts.setOwner(taskId, "e110a0a5-1314-4c0c-9ba9-142447b11dea");
// 根据用户来查询他所持有的任务
List<Task> tasks = ts.createTaskQuery().taskOwner(userId).list();
for(Task t : tasks) {
System.out.println(t.getName());
}
engine.close();
任务代理人1
2
3
4
5//任务被认领之后,其他人认领就会报错
ts.claim(taskId, user.getId());
//任务指定代理人
ts.setAssignee(taskId, user.getId());
任务完成taskService.complete(task.getId());
设置参数
a、流程配置文件设置
1 | <!-- process标签里面 --> |
b、启动流程设置
1 | Map<String,Object> map = new HashMap<>(); |
c、服务设置
1 | runService.setVariable(execution.getId(), "key",value); |
d、设置本地参数
setVariable变成setVariableLocal
获取参数值1
2runService.getVariable(execution.getId(), "key");
taskService.getVariable(task.getId(), "key");
附件
1 | //通过ID启动并设置business_key参数 |
task流程taskService.complete(task.getId());
Receive Task流程
该task不会产生task数据,只有execution1
2
3
4
5
6
7
8
9// 启动流程
ProcessInstance pi = runService.startProcessInstanceById(pd.getId());
// 查当前的子执行流(只有一个)
Execution exe = runService.createExecutionQuery()
.processInstanceId(pi.getId()).onlyChildExecutions()
.singleResult();
System.out.println(pi.getId() + ", 当前节点:" + exe.getActivityId());
// 让它往前走
runService.trigger(exe.getId());
手动执行任务(act_run_job)1
2
3
4Job job = managementService.createJobQuery().singleResult();
//设置重试次数
managementService.setJobRetries(job.getId(), 1);
managementService.executeJob(job.getId());
6.0版本支持activiti:candidateGroups=”${prd_add}”
5.X版本只能activiti:candidateGroups=”#{prd_add}”
1 | <div ng-app=""> |
1 | <div ng-app="myApp" ng-controller="myCtrl"> |
1 | <div ng-app="" ng-init="person={firstName:'John',lastName:'Doe'}"> |
1 | <div ng-app="" ng-init="points=[1,15,19,2,40]"> |
1 | <div ng-app="" ng-init="names=['Jani','Hege','Kai']"> |
1 | <body ng-app="myApp"> |
你可以通过以下方式来调用指令:
<runoob-directive></runoob-directive>
<div runoob-directive></div>
restrict 值可以是以下几种:
1 | <div ng-app="myApp" ng-controller="myCtrl"> |
1 | <form ng-app="" name="myForm"> |
1 | <form ng-app="" name="myForm" ng-init="myText = 'test@runoob.co"> |
1 | <style> |
ng-model 指令根据表单域的状态添加/移除以下类:
1 | <div ng-app="myApp" ng-controller="myCtrl"> |
1 | <div ng-app="myApp" ng-controller="myCtrl"> |
1 | <div ng-app="myApp" ng-controller="personCtrl"> |
currency 格式化数字为货币格式。
filter 从数组项中选择一个子集。
lowercase 格式化字符串为小写。
orderBy 根据某个表达式排列数组。
uppercase 格式化字符串为大写。
1 | <div ng-app="myApp" ng-controller="personCtrl"> |
1 | var app = angular.module('myApp', []); |
返回当前页面的 URL 地址。
1
2
3
4 var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $location) {
$scope.myUrl = $location.absUrl();
});
向服务器发送Http请求
1
2
3
4
5
6 var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $http) {
$http.get("welcome.htm").then(function (response) {
$scope.myWelcome = response.data;
});
});
$timeout 服务对应了 JS window.setTimeout 函数。
1
2
3
4
5
6
7 var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $timeout) {
$scope.myHeader = "Hello World!";
$timeout(function () {
$scope.myHeader = "How are you today?";
}, 2000);
});
$interval 服务对应了 JS window.setInterval 函数。
1
2
3
4
5
6 app.controller('myCtrl', function($scope, $interval) {
$scope.theTime = new Date().toLocaleTimeString();
$interval(function () {
$scope.theTime = new Date().toLocaleTimeString();
}, 1000);
});
1 | var app = angular.module('myApp', []); |
1 | <div ng-app="myApp"> |
1 | // 简单的 GET 请求,可以改为 POST |
1 | <div ng-app="myApp" ng-controller="siteCtrl"> |
1 | <div ng-app="myApp" ng-controller="myCtrl"> |
1 | <div ng-app="" ng-init="mySwitch=false"> |
1 | <div ng-app="" ng-init="hour=13"> |
1 | <p ng-hide="true">我是不可见的。</p> |
1 | <button ng-click="count = count + 1">点我!</button> |
1 | <div ng-app="myApp" ng-controller="personCtrl"> |
http://www.runoob.com/angularjs/angularjs-forms.html
数据绑定
<input type="text" ng-model="firstname">
Checkbox(复选框)
单选框使用同一个 ng-model
全局 API 函数使用 angular 对象进行访问。
1 | <p ng-switch = "x3"> |
ng-include="'runoob.htm'"
###
ng-bind 和花括号的区别就是 启动加载的时候显示不同
1 | const name = 'lux'; |
1 | function action(num = 200) { |
1 | x => x * x |
1 | function people(name, age) { |
1 | //对象 |
Object.assign()
可以把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象。第一参数即为目标对象。在实际项目中,我们为了不改变源对象。一般会把目标对象传为{}
1 | //数组 |
1 | //全部导入 |
作者:陈嘻嘻啊
链接:https://www.jianshu.com/p/287e0bb867ae
來源:简书
1 | def add(x, y, f): |
map()函数
1 | def f(x): |
reduce()函数
1 | def f(x, y): |
filter()函数
1 | def is_odd(x): |
sorted()函数
1 | sorted([36, 5, 12, 9, 21]) # 排序 |
装饰者模式
1 | from functools import reduce |
偏函数
1 | import functools |
他是一个服务注册中心,服务消费者会注册到服务中心,服务实例会通过心跳方式定时通知服务中心(定时向注册中心更新实例。默认30S),Eureka Server收到心跳后,会通知集群里的其它Eureka Server更新此实例的状态。
在Service Provider服务shutdown的时候,主动通知Eureka Server把自己剔除,从而避免客户端调用已经下线的服务。
Eureka Server会定时,进行检查,如果发现实例在在一定时间,内没有收到心跳,则会注销此实例。(间隔值是eureka.server.eviction-interval-timer-in-ms,默认值为60)
保护模式:则会触发自我保护模式,此时Eureka Server此时会认为这是网络问题,它不会注销任何过期的实例。
其他服务注册中心也有Consul和Zookeeper
1 | <!-- eureka server --> |
server1
2
3
4
5
6
7
8
9
10
11
12
13server.port=9990
#服务名称
spring.application.name=eureka-server
#服务地址
eureka.instance.hostname=127.0.0.1
#是否注册eureka(当高可用模式下才会启用) 是否向服务注册中心注册自己
eureka.client.register-with-eureka=false
#是否启用获取服务注册信息
eureka.client.fetch-registry=false
#注册和查询都需依赖该地址,多个以逗号分隔
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
#留存的服务实例低于多少比列进入保护模式 默认是85%
eureka.server.renewal-percent-threshold=0.5
client1
2
3
4
5
6
7
8server.port=8083
spring.application.name=comment
#eureka server地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:9990/eureka/
# 心跳间隔 5s 默认30s
eureka.instance.lease-renewal-interval-in-seconds=5
# 服务失效时间: 如果多久没有收到请求,则可以删除服务 默认90s
eureka.instance.lease-expiration-duration-in-seconds=10
eureka server在启动类添加注解@EnableEurekaServer
eureka client在启动类添加注解@EnableEurekaClient
@EnableEurekaClient和@EnableEurekaClient功能类似,但是@EnableEurekaClient只用于eureka。
我这里的例子是web服务,所有采用的是springBoot作为网关。如果是应用服务可以用spring Cloud zuul
所有的接口都是通过网关提供出去的,所以身份认证、路由服务、流量控制、日志统计代码编写都是在网关服务。
1 | server.port=8090 |
zuul主要就是路由转发和过滤器
1 | <dependency> |
1 | server.port=8769 |
启动类添加@EnableZuulProxy
注解
两种通信方式:restTemplate+httpclient和feign
1 | @Bean |
post1
int result = restTemplate.postForObject("http://comment/insert", comment, Integer.class);
get1
2
3Map<String, Integer> map = new HashMap<>();
map.put("nid", nid);
List<CommentUser> response = restTemplate.getForObject("http://comment/list?nid={nid}", List.class,map);
其中comment就是服务配置文件中的
spring.application.name=comment
feign
依赖
1
2
3
4 <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
类似于controller编写1
2
3
4
5
6
7
8
9@FeignClient(value = "comment")
public interface NewsFeign {
@RequestMapping(value="insert",method=RequestMethod.POST)
public int insert(@RequestBody Comment comment);
@RequestMapping(value="list",method=RequestMethod.GET)
public List<CommentUser> queryAll(int nid);
}
1 | <dependency> |
1 | @Bean |
feign默认集成了ribbon
服务隔离(每个服务有单独线程池)、熔断(也可以称为断路)、降级等手段控制依赖服务的延迟与失败。
防止单个服务的故障,从而影响整个系统的性能,避免分布式环境里大量级联失败。使用快速失败策略,当调用一个服务超时,或者,线程池满了的情况,会立即拒绝服务而不会排队等待,还有服务降级,当调用失败的时候,就调用备用的服务。
1 | <dependency> |
1 | ###########hystrix配置################ |
可以使用注解自定义配置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37@Repository
@DefaultProperties(groupKey="newsDao",
commandProperties={@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="2000")},
threadPoolProperties={@HystrixProperty(name="coreSize",value="10")
,@HystrixProperty(name="maxQueueSize",value="1000")},
threadPoolKey="newsDao"
)
public class NewsDao {
@Autowired
private RestTemplate restTemplate;
public int insert(News news){
return restTemplate.postForObject("http://news/news/insert", news, Integer.class);
}
@HystrixCommand
public UserNews queryOne(int id){
Map<String,Integer> map = new HashMap<>();
map.put("id", id);
UserNews response = restTemplate.getForObject("http://news/news/query?id={id}", UserNews.class, map);
return response;
}
@HystrixCommand(fallbackMethod = "errFallback")
public List<News> queryAll(){
List<News> response = restTemplate.getForObject("http://news/news/list", List.class);
return response;
}
/**
* 有降级方法,但是用户线程池用完了的话,还是会启动短路器。并且一定的时间段调用还是会拒绝所有请求,当这个请求时间过了的时候,等下一次请求正常访问,断路器才会关闭
* @return
*/
public List<News> errFallback(){
return null;
}
}
在启动类添加注解@EnableCircuitBreaker
分布式服务跟踪系统,微服务中,分布式服务架构,业务的调用链越来越复杂。分布式服务跟踪是整个分布式系统中跟踪一个用户请求的过程(包括数据采集、数据传输、数据存储、数据分析、数据可视化)提供链路追踪,故障快速定位,可视化各个阶段耗时,进行性能分析。理清服务依赖关系
1 | <dependency> |
1 | #############zipkin配置############### |
通过引入spring-cloud-starter-zipkin依赖和设置spring.zipkin.base-url就可以了。
健康检测,监控服务的堆栈,线程、GC、请求等信息
a. 创建一个admin服务,添加依赖。设置端口99921
2
3
4<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
b. 添加启动类注解@EnableAdminServer
c. 子服务添加依赖和admin服务地址配置1
2
3
4
5
6
7
8
9<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>1.5.1</version>
</dependency>
1 | ###########spring-admin配置########### |
监控信息包括请求成功,失败(客户端抛出的异常),超时和线程拒绝。如果访问依赖服务的错误百分比超过阈值,断路器会跳闸,此时服务会在一段时间内停止对特定服务的所有请求
a. 创建hystrix-dashboard服务,添加依赖,端口为99911
2
3
4
5
6
7
8
9
10
11
12<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
b. 启动类添加注解@EnableHystrixDashboard
服务调用在整个链路中的状态可视化界面。sleuth收集数据,然后发送到zipkin存储展示。(默认存储到内存中,也可以存储到redis/mysql)
a. 创建zipkin服务,添加依赖,端口为99931
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
b. 添加启动依赖注解@EnableZipkinServer
服务产生的日志文件后,logstash去收集,然后解析,输出到ElasticSearch中,最后由kibana展示出来。
ElasticSearch是一个基于 Lucene 的搜索服务器。
安装:Elasticsearch 基础入门
常见坑:elasticsearch启动常见错误
数据可视化平台,安装最简单,安装完后指向ES地址即可。
Logstash 基础入门
Logstash 是一个开源的数据收集引擎,它具有备实时数据传输能力。
难点就是编写配置文件中的过滤器部分:grok 插件
grok 插件用于过滤杂乱的内容,将其结构化,增加可读性。
1 | input{ |
ELK整体搭建:搭建ELK日志分析平台
SpringCloud Config:全局配置,比如git,svn,githup
SpringCloud Bus: +RabbitMQ 可以实现配置更新通知所有服务
log4j2:多个线程会把日志放入到一个无锁化环形队列,然后由一个异步队列去拉取队列的数据打印到日志里面去
资产=负债+股东权益
简介:银行存款、现金、准备金、库存现金(保险柜里的钱),存银行的采购,专款专用的钱,同业(冻结和被质押的存款)
影响:
- 过大的表资金运用能力较弱,过小,在偿债能力不足。
- 货币资金远小于负债,说明随时有偿债危机爆发,货币充裕,但基本都是高利贷。
- 其他货币资金巨大,但没有合理解释。
应收票据
简介:银行承兑汇票和商业承兑汇票,急用钱,就会把银行承兑汇票打折出售给银行,这个折扣就叫做银行贴现。银行转给其他银行叫转贴现,还有直接给了央行,拿到现金,这就叫做再贴现。商业承兑汇票是企业开出来的。
应收账款
简介:大部分都是赊账拿货,然后卖完了再把钱给你。
影响:应收账款占收入比例较大,且长期没有还,就说明有问题。
应收账款的时间,如果拖的越久,变成烂账的可能性就越大。而坏账准备就会计提越多。(计提就是预先计入某些已经发生但未实际支付的费用;)
也就是多年未还,就计资产减值损失。熊市好多企业大量的隐藏收入就是这个,然后牛市又把利润释放出来,从而达到套现或者增发的目的。举例:一些公司,会以客户的名义打入一笔钱,用来冲销数额巨大的期限过长的应收账款,然后再把这笔钱,通过其他应收款和预付款,购货款的名义转出去,于是就不用减值太多,所以资产就增加了。还有利用关联方,先将应收账款收回,然后通过在建工程和无形资产长期投资这些项目把资金流出,这样就能光明正大的折旧了。
简介:先交钱再拿货,如果预付款过多的话,那么也就意味着这个公司没什么地位,总是处于资金被压榨的地位。
影响:还有长期挂账的预付款,这很可能就是一种资金挪用,通过预付款把钱拿走不知道干什么去了。给投资者出个招,就是要把历年的预付款占应收的比例都算出来,如果这个比例大幅度波动,就要找原因,如果没有原因,就有可能是个雷了。
其他应收款
简介:这就是个垃圾筐,无关营业的应收款,都会放到这里面。比如借钱给别人。优秀的工作一般都是0
长期应收款
简介:主要是融资租赁和递延分期付款,比如公司卖的房,别人分期还款。
存货
简介:就是企业在生产过程中的原材料。基本上劳务福利,折旧费和修理费,办公水电耗材全都放在这里了。
影响:基本上劳务福利,折旧费和修理费,办公水电耗材全都放在这里了。利用存货造假的手法很多,比如为了虚增利润,故意不对已经贬值的存货计提跌价准备,减少费用就会让利润增多。还有,很多公司还通过让存货大量增长,从而掩护大量现金流出,然后再以关联公司虚构订单方式回来。还有用增加存货的方式来增加毛利率,美化报表。
例子:所以你看到毛利率大幅提升的时候,应该赶紧去看看存货是不是也大幅增加了,如果是的话,那么这个报表就是做出来的。
其他
固定资产
简介:包括房屋建筑物,机器,运输车辆等,按折旧计算。随时间要做减值准备
影响:短期内折旧很快,说明公司在着眼于未来发展,他们希望把更多的业绩留到以后。
在建工程和工程物资
简介:在建工程是不需要折旧的,因为还没建好
影响:如果你发现有一个在建工程很长时间都不转入固定资产,那么有两个可能,一是公司避免折旧,不愿意减少利润。二一个其实我们之前讲过,是上市公司通过在建工程向关联供应商输送资金,然后再采购商品,增加订单,流入公司。最后这种在建工程,往往会通过大比例折旧,或者意外损毁的名义毁尸灭迹。
无形资产
简介:知识产权一类或者是品牌。这其中注意,包括了土地使用权。所以如果买地产股,一定注意这个科目。
影响:无形资产也是有折旧的,但他叫做摊销。这其中有个叫做研发费用的项目,也是用来调整报表的。低收入企业通常把研发费用计算到无形资产里面,而不进入公司费用,也是为了粉饰报表,显得资产多一点,利润高一点。
商誉
简介:说白了就是你花10块钱,愿意买7块钱的东西,3块钱的附加值就是商誉。但只有公司收购之后才会体现,一般公司的报表里都是0。
长期待摊费用
简介:是企业已经支出的,但持续期1年以上的费用。比如装修房子,修理机器等等,这些需要进入当期费用,然后从利润里扣除。比如预交了一年的房租。
简介:会计算的所得税和税务算的不一样,但一定要按照税务算的交,于是如果算的少了,多交了钱,回去就把多交的钱,写在递延所得税资产里面。相反就是负债
交易性金融资产
简介:交易性金融资产主要是指企业为了近期内出售而持有的金融资产,如企业以赚取差价为目的从二级市场购入的股票、债券、基金等。
可供出售金融资产
简介:随时可以卖了,牛市里这块就是低估的金矿。而熊市里这就是地雷。
长期股权投资
简介:指的是上市公司持有其他公司股份,一般是那种战略投资。有这么几个关系,持股大于50%叫做控制,持股在20%-50%之间,叫做联营,持股100%,叫做全资子公司。还有一种合营,持股随便约定。这种长期投资不计入利润,只要不卖,股权价值变动对于公司没影响,如果持有公司分红,还会被计入到利润。
投资性房地产
简介:这里面有一个巨大的陷阱,那就是很多人看房产股的时候发现市盈率和市净率都很低啊,其实主要都是公允价值计量所造成的,每年房价上涨,净利润和净资产都会很多,表现出来就是获利能力强,资产庞大,但事实上公允价值都是财富幻觉,这个很多普通人买房是一样的,总觉得自己很有钱,因为房子值上千万,但其实这都没什么卵用,好比你打高一分钱,买入中石油100股,大概只需要700块钱,但你却能瞬间提高中石油的市值将近20亿,所以懂了吧,这东西都是乘数乘出来的,其实根本就没这么多钱。大量的房地产公司,其实就是用这些公允价值玩出了把戏,而实际上一旦房价不涨了,其实根本就无法变现。
主要也是为了方便债权人看他到底有没有偿债能力。如果一个公司分红,就会大幅增加分配性负债,从资产的未分配利润科目划入到负债的应付股利科目,如果你发现公司净资产大幅下降,先不要慌,看看是不是因为分红了。很多公司分红并不是真心的,而是出于再融资的考虑,大比例分红后,净资产下降,但净利润却不变,所以净资产收益率就大幅提升了,他就可以符合要求,进行再融资了。所以你要翻翻这个公司的历史,看有没有这个传统,分红之后再融资。如果有千万别踩雷。
注意房地产:
【在会计准则里,房企预收账款按负债计,但预收款本质上是尚待确认的营业收入,不需要支付利息,和通常语境下的负债不是一回事。预收款多,说明房企房子卖得多,经营状况好。】
简介:假如老总年薪百万,会计如果把你的工资计入管理费用,就要从利润里面减掉,公司营业利润就少了上百万,如果想释放这百万的利润出来,就把你同时任命为车间主任,待遇不变。然后你的工资就进入了生产成本,变成了存货。
薪资是不会减少的,除了裁员
简介:总股数
盈余公积金
简介:是利润留下来的,是继续扩大再生产的钱,法定盈余公积是政府强制股东留下来的,一般10%。
未分配利润
简介:企业赚的钱提取了盈余公积金之后,剩下的就是未分配利润了,企业可以利用盈余公积金和未分配利润送红股,前提是有利润,送股后盈余公积金不得低于注册资本的25%。
注意:但未分配利润可不一定都是现金,公司手里的钱就是货币资金里面的现金和现金等价物,未分配利润可能已经被公司挪用出去投资了,比如变成了固定资产或者是土地厂房什么的。
资本公积金
简介:叫做股本溢价或者资本溢价,计入资本公积。换句话说,资本公积金,就是放置股东投入的地方。
库存股
简介:公司将已经发行出去的股票,从市场中买回,存放于公司,而尚未再出售或是注销
归属于母公司股东权益合计
少数股东权益
股东权益合计
简介:净资产
所有者权益合计=母公司股东权益合计母+少数股东权益合计
负债和股东权益总计
简介:公司将已经发行出去的股票,从市场中买回,存放于公司,而尚未再出售或是注销
也就是所有带应收两字的总和,减去应收票据里的银行承兑汇票金额,然后除以总资产。如果这个比例过大, 比如超过了3成,说明资产质量不佳。欠的钱很可能拿不回来,第二看是否有大的变动,总资产基本上变动不大,应收如果变动很大的话,那么一定是销售出了问题,可能就是公司变坏的苗头。
货币资金除以有息负债,看你抵御危机的能力,特别是在经济逆周期,比如现在,看看有多少现金和现金等价物,是你投资一个公司的根本保证,未来现金会越来越值钱,而如果负债过多,会在加息周期,步伐越来越沉重。
非主业资产比上总资产,无疑就是看公司业务是否聚焦,如果这个比例过大,那么也就意味着他觉得自己的主业已经不赚钱了
利润=收入-支出
营业收入(营业总收入)
描述:企业卖出的产品所获得的收益就是营业收入。一个是销售商品,一个提供劳务,另外一个就是让渡资产使用权
营业成本
描述:卖出去的产品就是营业成本.主要包括两部分,一个是产品成本,以及劳务成本。
营业总成本
描述:营业成本,营业税金及附加,营业费用,营业费用又包括销售费用,管理费用和财务费用。俗称三费。
- 销售费用是销售产品所产生的费用,比如广告费,促销费保险运输费,装卸费,销售人员的返点等等。销售费用和销售收入应该是同涨共跌的,毕竟多列销售费用的目的就是促进销。
- 管理费用包含管理者的工资福利,工会经费,职工教育培训,行政开支。比如员工乱开发票。管理费用太大说明管理不行
- 财务费用主要就是利息和手续费
毛利润
描述:主营收入(营业收入)减去主营成本就是毛利润,而毛利润占主营收入的比例就是毛利率,毛利低的行业,竞争很激烈,毛利高的行业才有护城
营业利润
描述:收入和成本相减
营业利润率
描述:营业利润除以营业总收入。然后跟同行比较一下,如果这个指标高,说明这个公司在行业里的竞争地位很强。
净利润
描述:营业利润加上其他的营业外的收支净额,交完所得税,剩下的钱。
扣非净利润
描述:由于能为公司带来利润的方法很多,比如:股权投资,打新股,财补贴,债务重组…等,这些东西跟公司的主营业务无关,都是一次性的,不可持续的
资产减值损失
描述:一般投资亏了,或者坏账了,才会提这个减值损失。
公允价值变动
描述:主要是交易性金融资产,比如买了很多的房子,房价涨了,这块也就跟着涨而来。
投资收益
描述:是股票分红,债券利息,主要指那些付息的东西,在还本付息之前,拿到的利息收入。
汇兑收益
描述:就是企业持有外汇资产或者负债期间,所造成的汇率变化。
营业外收入
描述:比如卖一栋房,专业投资者上来就会把这些营业外收入给剔除掉,因为这东西根本不可持续。
营业外支出
描述:包括的项目有固定资产盘亏、处置固定资产净损失、处置无形资产净损失、债务重组损失、计提的无形资产减值准备、计提的固定资产减值准备、计提的在建工程减值准备、罚款支出、捐赠支出、非常损失等。
营业税金及附加
描述:营业税、消费税以及城建税、教育费附加、地方教育附加。
所得税
描述:
其他综合收益
描述:可供出售金融资产的公允价值变动、减值及处置导致的其他综合收益的增加或减少。
综合收益总额
描述:综合收益总额是企业净利润搜索与其他综合收益的合计金额。其中净利润好理解,而其他综合收益是指直接计入所有者权益的利得或损失扣除所得税之后的余额,它们都属于利润表大类项目。
净利润必须结合现金一起看,脱离了现金你会被骗的怀疑人生的。很简单打白条,把货拉走,权益发生了,净利润增加了,但钱却压根没见着。看似净利润大增,过几天人家告诉你应收账款全都坏账了,企业分分钟变成巨亏。这都是套路。套的就是那些只看利润不看现金的股市上懒惰的投资者。
潜在需求增长
市场整个份额扩大了。
市场份额扩大
抢了别人的份额,,很可能会带来价格战和报复。
价格提升
牺牲客户利益为大家。那就看会不会让客户减少了。还要看替代产品强不强。
营业增长速度 还要跟同行比较
高毛利率就说明很强的竞争优势,我建议大家毛利率40%以下的就可以不看了。因为替代性太强的企业,
三费占营业总收入的比例,这个指标反映的是公司的管理水平。要跟同行比较。销售费用管理费用,财务费用加在一起除以毛利润,比例小于30%,这个企业就算不错
营业利润除以营业收入;跟同行比较
经营现金流净额,除以净利润。这个比值越大越好大于1就是好企业了
经营活动,投资活动和筹资活动
投资活动 就是收购
筹资活动,公司回购股票,向股东分红,借债,发行股票再融资等等。
经营活动现金流入
描述: 主要是销售商品或者提供劳务产生现金,收到税费返还的钱,还有收到其他与企业经营有关的钱,比如出租什么的。
经营活动现金流出
描述:购买商品,接受劳务,付出的钱,支付应付款,减去本期退货收到的现金。支付给职工的工资,奖金以及各种津贴。支付各种税费,还比如支付各种租金。
重点就是要得到流量净额这个数据,要从净利润反推经营活动现金收入净额,拢共分三步,第一步就是把净利润里扣除的费用,比如什么资产减值准备和折旧摊销全部都给他加回去,第二步是把净利润跟经营活动无关的收入,全部减去,比如什么投资收益,募集资金收入,非经常性的损益。第三步,把经营活动中没有包含在成本里的现金支出减去,比如多交了得税,这个在递延所得税资产里,增加了的存货,以及增加了的营收和支付了的应付
销售商品和提供劳务收到的现金,这个数字可以和营收对比,因为前者包含了增值税,所以两个数字按照理论来说应该前者是后者的1.17倍,前面的数字比后面大就对了,如果销售商品提供劳务还没有营收多,那就有问题了。证明大量款项被作为应收账款,打了白条。这笔钱能不能收回,就很难说了。
如果一个企业总是花钱投资,属于扩张阶段,如果一个企业投资活动现金流是往回流的,是正数,那么说明这个企业已经扩张放缓,属于收缩阶段。
妖怪型:经营现金流,投资现金流筹资现金流都是正向流入的企业,企业本身赚钱,但是有不投资,而且还向别人借钱。
这类企业现实中还真存在,属于妖怪型,他们可能随时要开展一场大规模的投资活动,要不就是在借着上市公司的壳搞钱,搞到钱之后在给关联方使用。反正这样的企业相当可疑。估计是没憋好屁。
老母鸡型,经营流入,投资流入,筹资流出,很赚钱,而且还能拿钱去赚钱,这都很好。
筹资活动现金流出有两种可能,要么在还债,要么是回报分红,如果是分红,这个企业还不错。应该是价值型的公司。
企业疯牛型,属于经营流入,投资流出,融资流入。企业能够赚到钱,然后赚到的钱,都愿意拿出去扩张,说明很看好未来的发展。
另外还能借到钱,说明外面的人也看好这个企业的项目。这时候两种可能,一个是高增长持续,但也很容易出现资金断流。太多的企业都是死在了冲锋的路上,所以投资这块要谨慎看待,特别是那种打出多元化国际化口号的。务必小心。
最佳类型:经营流入,投资流出,融资也流出,企业能赚钱,然后也能投资,还能够偿债或者回报股东,如果可以持续的话,这应该是最佳的类型了。
他经营的钱能够覆盖对外投资和分红,这就属于那种现金奶牛,比如茅台和格力。增长不见得很快,但每年都会有钱拿。巴菲特最喜欢的企业类型。这时候你要在算算,经营现金减去投资再减去融资,是不是仍然为正,如果仍然为正,这个奶牛的成色非常足。如果减完了得到了负数,那么很可能很快就变成了上面那种企业。
骗吃骗喝型,经营现金流为负值,投资现金流净额为正的,筹资为正的是骗吃骗喝型
筹资活动现金流出有两种可能,要么在还债,要么是回报分红,如果是分红,这个企业还不错。应该是价值型的公司。
混吃等死型,经营现金流为负值,筹资为负的就是是混吃等死型
赌徒型,经营现金流为负值,投资活动为负的就更糟糕,能融到钱的属于赌徒型
大出血型,经营现金流为负值,融不到钱还要还债的属于大出血型
关注以下5组数据就够了。第一就是经营活动现金流量净额大于净利润,并且大于零。第二销售商品提供劳动收入现金大于营业收入,第三投资活动的现金流净流量小于零,且主要是投入的新项目。第四现金及现金等价物净增加额大于零,或者排除分红因素该科目仍然大于零,第五期末现金及现金等价物余额大于有息负债。
要结合历史报表和同行业报表来看
有一个指标你总是要记住的,那就是现金及现金等价物,一定要大于有息负债。
教科书一般认为,流动比率2,速动比率在1是一个比较理想的状态
营业利润率等于营业收入减去营业成本再减去三费,然后用这个结果再除以营业收入,用以反应收入到底多少能转化成利润。
这里面还能体现杠杆的因素,比如只有1成的钱,借来了9成的钱,然后赚了1成,他就收益100%了,但其实他的生意利润也就10%,所以如果两家公司净资产收益率一样,而总资产收益率更强的那家,显然盈利能力更强,承担的风险更小。
营业增长率和利润增长率是必须要看的指标,我们常用的PEG估值方法,那个G就是增长率的情况,通常保守的价值型企业用利润增长率来算,成长型企业则用营业增长率来计算,增长率20%,顶多给20倍市盈率的估值。成长率为10%,最好买入的市盈率别超过10倍。至于增长率100%,理论上可以给100倍市盈率的估值,但问题是这100%的增长能维持多久?
PEG,是用公司的市盈率(PE)除以公司未来3或5年的每股收益复合增长率。
应收账款周转率,存货周转率和固定资产周转率,说白了就是营运能力,周转率就是周转的次数,如果固定周期中周转的次数越多,说明公司越高效,毕竟存货周期短的话,计提减值就少。
净资产收益率=净利润/销售收入 X 销售收入/平均总资产 X 平均总资产/净资产。
分别是净利润除以销售收入,这其实就是产品净利润率,销售收入除以平均总资产,这是总资产周转率,而平均总资产除以净资产,就是杠杆系数。这确实是高人研究出来的,因为这三个部分,刚好表明了企业的有且只有的三种发展模式,也就是茅台模式,沃尔玛模式和银行模式,所谓茅台模式,就是第一个净利润比上销售收入,是产品净利率极高的发展模式。属于现金奶牛,属于超级大白马,属于超高产品附加值。
而沃尔玛模式,就是总资产周转率高的企业,也就是第二个公式销售收入比上平均总资产,说明这个企业的利润并不高,周转效率很高,靠的是效率赚大钱。
第三种是银行模式,利润不高,也没什么周转率,但人家靠的是高杠杆,把本来很低的利润给放大了。所以通过杜邦公式你一眼就能看出这是一个怎样的企业,他的净资产收益率到底高在哪了。所以这个公式是你必须牢记的,4个指标也是你要率先从报表上找出来的。重复一遍,四个指标分别是净利润,销售收入,平均总资产和净资产,杜邦公司的净资产收益率公式是,净资产收益率=净利润/销售收入X销售收入/平均总资产X平均总资产/净资产。
利润等于收入减去费用,所以只有2个手法,要么虚构收入,要么减少费用。其中虚构收入是比较常见的,比如某上市公司,成立了35家秘密控制的空壳公司,拿出其中一家当低价买农村山地,然后以100倍的价格卖给上市公司,这样上市公司就虚增资产1亿多,而那家卖地的公司,就从上市公司掏出来上亿的现金,接着以类似的手法分转给其他的公司,然后再由那些公司,从这个上市公司采购产品,虚增营业收入。
还有的公司,把产品单价提高,然后以高价格卖给关联公司,最后再通过在建工程款,原料采购等名义,把钱返还出去,这样他就有很高的收入。甚至还有的不要脸的公司,会把政策的补助,给化妆成合同采购,假装成销售收入流入公司。另外,我们之前也讲过,还有很多公司借助一次性的卖资产而来的钱,扩大收入。还有通过收购一个公司,多付出一些钱,然后被收购对象再把钱返回来购买产品,虚增收入。
还有些公司会将亏损打包进一个公司和业务部门,高价出售,然后掩盖亏损。然后上市公司会通过,借款,担保,项目合作,并购或者咨询费用再把这些钱补偿给接盘公司。最无耻的还有互换贸易,就是跟一个公司约定好,我高价采购你的产品,你也高价采购我的产品。这样双方的营业收入就都提升了。
就是跟一个公司约定好,我高价采购你的产品,你也高价采购我的产品。这样双方的营业收入就都提升了。
经常会把本期费用推迟到未来,来掩盖本期的业绩问题。还有虚增费用,然后给业绩洗澡,把几年的费用一年爆出来,大比例的注销资产或者存货,大额计提减值准备,集中亏一次,总比持续的小亏要强的多。有个上市公司,少记原材料成本1个亿,结果虚增利润7000万。这是比较常见的手法,然后还有的公司不计入对外拆借资金的利息,减少财务费用的。这样的事很多。
有一个苦肉计大家要注意,就是公司有时候会恍然大悟,然后承认错误,说我之前漏算了很多费用,现在把这些费用放回去。这就好比,我先打你一嘴巴,然后说对不起一样。您的业绩都披露完好久了,股价也涨了,现在要把之前的业绩改回来,道歉有用吗?
这是为了专业投资者准备的坑,知道你们老盯着现金流,所以他们就必须要在现金流上面下手脚。例如把投资或者筹资的 现金流入,变成经营活动流入,这个手法有,投资收购一家公司,然后跟对方商量好,你们让客户慢点付款,多保留些应付款,等于我投资下去,收购了很多的应收款,这就把钱变成了经营现金流入。而且有的时候收购未必要出很多钱。所以投资活动现金也不用怎么流出,就能换回经营现金流入。
还有公司会出售子公司,把出售款分成首付和未来收入分账两部分,这样收入分账部分,就变成了这家公司的未来经营现金流入。这就把投资现金流入变成了经营现金流入。有的公司还以存货抵押借款,然后签合同的时候说,咱们改一种说法,叫做我先卖给你,然后约定未来再回购从而创造本期经营活动现金流入。还有的上市公司替客户担保,然后让客户大比例购买他的产品,其实也是投资现金流出,而经营现金流入。
当然有流入就有流出,上市公司会尽量避免经营现金流出,能放到投资流出的绝不放到经营现金流流出。
第一重点关注高毛利率的业务,这块非常多的用来造假,因为空间大,不容易被发现。如果某个产品毛利率显著高于同行,或者大幅波动,那么就要小心了,除非是特别知名的产品,否则很可能就是个坑。
第二是其他业务收入,一般也是玩文字游戏的地方。而报表是可以不披露其他业务收入明细的,所以你很难进一步知道这个东西到底是什么玩意。这就为造假提供可能。
第三是销售费用和管理费用,这是藏污纳垢的重灾区,一定要跟同行对比。
第四是营业外支出和一次性费用,二话不说先把一次费用排除掉。带着其他字眼的东西,最好都给他加回去。对于有经验的投资者来说,其他两个字,就跟标红差不多。那意思就是这里该有故事了。
第五是资产减值损失,这里是业绩洗澡的地方,很多东西会一次性减值注销。
第七是其他应收款,这就更可疑了,好公司这块绝对很小的。
第八预付账款,这个大幅增加很可能是在作假,通过预付款流出资金,然后通过营收再流回来,虚增收入。
用未分配利润或者盈余公积金送股叫做派送红股
如果用资本公积金送股叫做转增多少股。转增股票是不用交税的。
1 | ## if条件判断 |
迭代器有两个基本的方法:iter()
和 next()
。1
2
3
4
5
6
7
8
9
10list=[1,2,3,4]
it = iter(list) # 创建迭代器对象
for x in it:
print (x, end=" ")
while True:
try:
print (next(it))
except StopIteration:
sys.exit()
使用了 yield
的函数被称为生成器(generator),作用是函数执行时遇到这个关键字的时候就会停止执行,知道调用了next之后才能继续执行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import sys
def fibonacci(n): # 生成器函数 - 斐波那契
a, b, counter = 0, 1, 0
while True:
if (counter > n):
return
yield a
a, b = b, a + b
counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
while True:
try:
print (next(f), end=" ")
except StopIteration:
sys.exit()
1 | def 函数名(参数列表): |
调用时:
printinfo( age=50, name="light" );
定义时:
def printinfo( name, age = 35 ):
定义时:
def printinfo( name, *str ):
类似与java的...
定义时:
def printinfo( name, **args):
2. 匿名函数
python 使用 lambda 来创建匿名函数。
1
2 sum = lambda arg1, arg2: arg1 + arg2;
print ("相加后的值为 : ", sum( 10, 20 ))
global局部变量修改全局变量的值1
2
3
4
5
6
7num = 1
def fun1():
global num # 需要使用 global 关键字声明
num = 123
print(num)
fun1()
print(num)
nonlocal关键字1
2
3
4
5
6
7
8def fun():
j = 0
def fun1():
nonlocal j
j = 5
print(j)
fun1()
print(j)
1 | # 堆栈:先进后出,后进先出 |
1 | vec = [2, 4, 6] |
1 | from modname import * # 把一个模块的所有内容全都导入到当前的命名空间,但是由单一下划线(_)开头的名字不在此例。 |
每个模块都有一个__name__
属性,当其值是'__main__'
时,表明该模块自身在运行,否则是被引入。
1 | import fibo |
1 | f = open(filename, mode) # 打开文件 |
1 |
|
关键词 with 语句就可以保证诸如文件之类的对象在使用完之后一定会正确的执行他的清理方法1
2
3with open("myfile.txt") as f:
for line in f:
print(line, end="")
1 | class MyError(Exception): |
1 | class A: |
os模块提供了不少与操作系统相关联的函数。1
2
3
4
5
6
7
8import os
os.getcwd() # 返回当前的工作目录
os.chdir('/server/accesslogs') # 修改当前的工作目录
os.system('mkdir today') # 执行系统命令 mkdir
import shutil
shutil.copyfile('data.db', 'archive.db')
shutil.move('/build/executables', 'installdir')
建议使用 “import os” 风格而非 “from os import *”。这样可以保证随操作系统不同而有所变化的 os.open() 不会覆盖内置函数 open()。
1 | import glob |
1 | import sys |
1 | import re |
import math
import random
1 | ## URL访问 |
1 | from datetime import date |
1 | import zlib |
1 | from timeit import Timer |
1 | # 待补充...... |