Skip to content
项目
群组
代码片段
帮助
正在加载...
登录
切换导航
X
XXL-JOB
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
靳帅
XXL-JOB
Commits
32a84353
提交
32a84353
authored
2月 21, 2019
作者:
xuxueli
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
任务支持更换绑定执行器,方便任务分组转移和管理;
上级
9cf966c4
隐藏空白字符变更
内嵌
并排
正在显示
7 个修改的文件
包含
41 行增加
和
41 行删除
+41
-41
XXL-JOB官方文档.md
doc/XXL-JOB官方文档.md
+1
-1
XxlJobDynamicScheduler.java
...m/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
+18
-25
XxlJobLogDao.java
...min/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java
+2
-1
XxlJobServiceImpl.java
...ava/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
+16
-11
XxlJobInfoMapper.xml
...in/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
+1
-0
XxlJobLogMapper.xml
...min/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml
+2
-2
jobinfo.index.ftl
...in/src/main/resources/templates/jobinfo/jobinfo.index.ftl
+1
-1
没有找到文件。
doc/XXL-JOB官方文档.md
浏览文件 @
32a84353
...
@@ -1423,7 +1423,7 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段
...
@@ -1423,7 +1423,7 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段
-
9、任务暂停、删除优化,避免quartz delete不完整导致任务脏数据;
-
9、任务暂停、删除优化,避免quartz delete不完整导致任务脏数据;
-
10、任务回调、心跳注册成功日志优化,非核心常规日志调整为debug级别,降低冗余日志输出;
-
10、任务回调、心跳注册成功日志优化,非核心常规日志调整为debug级别,降低冗余日志输出;
-
11、执行器回调日志落盘方案复用RPC序列化方案,并移除Jackson依赖;
-
11、执行器回调日志落盘方案复用RPC序列化方案,并移除Jackson依赖;
-
[
迭代中
]
任务支持更换绑定执行器
;
-
12、任务支持更换绑定执行器,方便任务分组转移和管理
;
-
[
迭代中
]
注册中心优化,实时性注册发现:心跳注册间隔10s,refresh失败则首次注册并立即更新注册信息,心跳类似;30s过期销毁;
-
[
迭代中
]
注册中心优化,实时性注册发现:心跳注册间隔10s,refresh失败则首次注册并立即更新注册信息,心跳类似;30s过期销毁;
-
[
迭代中
]
脚本任务,支持数据参数,新版本仅支持单参数不支持需要兼容;
-
[
迭代中
]
脚本任务,支持数据参数,新版本仅支持单参数不支持需要兼容;
-
[
迭代中
]
提供执行器Docker镜像;
-
[
迭代中
]
提供执行器Docker镜像;
...
...
xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
浏览文件 @
32a84353
...
@@ -164,11 +164,10 @@ public final class XxlJobDynamicScheduler {
...
@@ -164,11 +164,10 @@ public final class XxlJobDynamicScheduler {
*/
*/
public
static
void
fillJobInfo
(
XxlJobInfo
jobInfo
)
{
public
static
void
fillJobInfo
(
XxlJobInfo
jobInfo
)
{
String
group
=
String
.
valueOf
(
jobInfo
.
getJobGroup
());
String
name
=
String
.
valueOf
(
jobInfo
.
getId
());
String
name
=
String
.
valueOf
(
jobInfo
.
getId
());
// trigger key
// trigger key
TriggerKey
triggerKey
=
TriggerKey
.
triggerKey
(
name
,
group
);
TriggerKey
triggerKey
=
TriggerKey
.
triggerKey
(
name
);
try
{
try
{
// trigger cron
// trigger cron
...
@@ -198,15 +197,14 @@ public final class XxlJobDynamicScheduler {
...
@@ -198,15 +197,14 @@ public final class XxlJobDynamicScheduler {
* add trigger + job
* add trigger + job
*
*
* @param jobName
* @param jobName
* @param jobGroup
* @param cronExpression
* @param cronExpression
* @return
* @return
* @throws SchedulerException
* @throws SchedulerException
*/
*/
public
static
boolean
addJob
(
String
jobName
,
String
jobGroup
,
String
cronExpression
)
throws
SchedulerException
{
public
static
boolean
addJob
(
String
jobName
,
String
cronExpression
)
throws
SchedulerException
{
// 1、job key
// 1、job key
TriggerKey
triggerKey
=
TriggerKey
.
triggerKey
(
jobName
,
jobGroup
);
TriggerKey
triggerKey
=
TriggerKey
.
triggerKey
(
jobName
);
JobKey
jobKey
=
new
JobKey
(
jobName
,
jobGroup
);
JobKey
jobKey
=
new
JobKey
(
jobName
);
// 2、valid
// 2、valid
if
(
scheduler
.
checkExists
(
triggerKey
))
{
if
(
scheduler
.
checkExists
(
triggerKey
))
{
...
@@ -239,16 +237,15 @@ public final class XxlJobDynamicScheduler {
...
@@ -239,16 +237,15 @@ public final class XxlJobDynamicScheduler {
* remove trigger + job
* remove trigger + job
*
*
* @param jobName
* @param jobName
* @param jobGroup
* @return
* @return
* @throws SchedulerException
* @throws SchedulerException
*/
*/
public
static
boolean
removeJob
(
String
jobName
,
String
jobGroup
)
throws
SchedulerException
{
public
static
boolean
removeJob
(
String
jobName
)
throws
SchedulerException
{
JobKey
jobKey
=
new
JobKey
(
jobName
,
jobGroup
);
JobKey
jobKey
=
new
JobKey
(
jobName
);
scheduler
.
deleteJob
(
jobKey
);
scheduler
.
deleteJob
(
jobKey
);
/*TriggerKey triggerKey = TriggerKey.triggerKey(jobName
, jobGroup
);
/*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
if (scheduler.checkExists(triggerKey)) {
if (scheduler.checkExists(triggerKey)) {
scheduler.unscheduleJob(triggerKey); // trigger + job
scheduler.unscheduleJob(triggerKey); // trigger + job
}*/
}*/
...
@@ -261,16 +258,15 @@ public final class XxlJobDynamicScheduler {
...
@@ -261,16 +258,15 @@ public final class XxlJobDynamicScheduler {
/**
/**
* updateJobCron
* updateJobCron
*
*
* @param jobGroup
* @param jobName
* @param jobName
* @param cronExpression
* @param cronExpression
* @return
* @return
* @throws SchedulerException
* @throws SchedulerException
*/
*/
public
static
boolean
updateJobCron
(
String
job
Group
,
String
job
Name
,
String
cronExpression
)
throws
SchedulerException
{
public
static
boolean
updateJobCron
(
String
jobName
,
String
cronExpression
)
throws
SchedulerException
{
// 1、job key
// 1、job key
TriggerKey
triggerKey
=
TriggerKey
.
triggerKey
(
jobName
,
jobGroup
);
TriggerKey
triggerKey
=
TriggerKey
.
triggerKey
(
jobName
);
// 2、valid
// 2、valid
if
(!
scheduler
.
checkExists
(
triggerKey
))
{
if
(!
scheduler
.
checkExists
(
triggerKey
))
{
...
@@ -293,7 +289,7 @@ public final class XxlJobDynamicScheduler {
...
@@ -293,7 +289,7 @@ public final class XxlJobDynamicScheduler {
scheduler
.
rescheduleJob
(
triggerKey
,
oldTrigger
);
scheduler
.
rescheduleJob
(
triggerKey
,
oldTrigger
);
/*
/*
JobKey jobKey = new JobKey(jobName
, jobGroup
);
JobKey jobKey = new JobKey(jobName);
// old job detail
// old job detail
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
...
@@ -304,7 +300,7 @@ public final class XxlJobDynamicScheduler {
...
@@ -304,7 +300,7 @@ public final class XxlJobDynamicScheduler {
// cover trigger of job detail
// cover trigger of job detail
scheduler.scheduleJob(jobDetail, triggerSet, true);*/
scheduler.scheduleJob(jobDetail, triggerSet, true);*/
logger
.
info
(
">>>>>>>>>>> resumeJob success, Job
Group:{}, JobName:{}"
,
jobGroup
,
jobName
);
logger
.
info
(
">>>>>>>>>>> resumeJob success, Job
Name:{}"
,
jobName
);
return
true
;
return
true
;
}
}
...
@@ -313,13 +309,12 @@ public final class XxlJobDynamicScheduler {
...
@@ -313,13 +309,12 @@ public final class XxlJobDynamicScheduler {
* pause
* pause
*
*
* @param jobName
* @param jobName
* @param jobGroup
* @return
* @return
* @throws SchedulerException
* @throws SchedulerException
*/
*/
/*public static boolean pauseJob(String jobName
, String jobGroup
) throws SchedulerException {
/*public static boolean pauseJob(String jobName) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName
, jobGroup
);
TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
boolean result = false;
boolean result = false;
if (scheduler.checkExists(triggerKey)) {
if (scheduler.checkExists(triggerKey)) {
...
@@ -336,13 +331,12 @@ public final class XxlJobDynamicScheduler {
...
@@ -336,13 +331,12 @@ public final class XxlJobDynamicScheduler {
* resume
* resume
*
*
* @param jobName
* @param jobName
* @param jobGroup
* @return
* @return
* @throws SchedulerException
* @throws SchedulerException
*/
*/
/*public static boolean resumeJob(String jobName
, String jobGroup
) throws SchedulerException {
/*public static boolean resumeJob(String jobName) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName
, jobGroup
);
TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
boolean result = false;
boolean result = false;
if (scheduler.checkExists(triggerKey)) {
if (scheduler.checkExists(triggerKey)) {
...
@@ -359,14 +353,13 @@ public final class XxlJobDynamicScheduler {
...
@@ -359,14 +353,13 @@ public final class XxlJobDynamicScheduler {
* run
* run
*
*
* @param jobName
* @param jobName
* @param jobGroup
* @return
* @return
* @throws SchedulerException
* @throws SchedulerException
*/
*/
/*public static boolean triggerJob(String jobName
, String jobGroup
) throws SchedulerException {
/*public static boolean triggerJob(String jobName) throws SchedulerException {
// TriggerKey : name + group
// TriggerKey : name + group
JobKey jobKey = new JobKey(jobName
, jobGroup
);
JobKey jobKey = new JobKey(jobName);
TriggerKey triggerKey = TriggerKey.triggerKey(jobName
, jobGroup
);
TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
boolean result = false;
boolean result = false;
if (scheduler.checkExists(triggerKey)) {
if (scheduler.checkExists(triggerKey)) {
...
...
xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java
浏览文件 @
32a84353
...
@@ -14,7 +14,8 @@ import java.util.Map;
...
@@ -14,7 +14,8 @@ import java.util.Map;
*/
*/
@Mapper
@Mapper
public
interface
XxlJobLogDao
{
public
interface
XxlJobLogDao
{
// exist jobId not use jobGroup, not exist use jobGroup
public
List
<
XxlJobLog
>
pageList
(
@Param
(
"offset"
)
int
offset
,
public
List
<
XxlJobLog
>
pageList
(
@Param
(
"offset"
)
int
offset
,
@Param
(
"pagesize"
)
int
pagesize
,
@Param
(
"pagesize"
)
int
pagesize
,
@Param
(
"jobGroup"
)
int
jobGroup
,
@Param
(
"jobGroup"
)
int
jobGroup
,
...
...
xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
浏览文件 @
32a84353
...
@@ -164,13 +164,19 @@ public class XxlJobServiceImpl implements XxlJobService {
...
@@ -164,13 +164,19 @@ public class XxlJobServiceImpl implements XxlJobService {
jobInfo
.
setChildJobId
(
StringUtils
.
join
(
childJobIds
,
","
));
jobInfo
.
setChildJobId
(
StringUtils
.
join
(
childJobIds
,
","
));
}
}
// group valid
XxlJobGroup
jobGroup
=
xxlJobGroupDao
.
load
(
jobInfo
.
getJobGroup
());
if
(
jobGroup
==
null
)
{
return
new
ReturnT
<
String
>(
ReturnT
.
FAIL_CODE
,
(
I18nUtil
.
getString
(
"jobinfo_field_jobgroup"
)+
I18nUtil
.
getString
(
"system_unvalid"
))
);
}
// stage job info
// stage job info
XxlJobInfo
exists_jobInfo
=
xxlJobInfoDao
.
loadById
(
jobInfo
.
getId
());
XxlJobInfo
exists_jobInfo
=
xxlJobInfoDao
.
loadById
(
jobInfo
.
getId
());
if
(
exists_jobInfo
==
null
)
{
if
(
exists_jobInfo
==
null
)
{
return
new
ReturnT
<
String
>(
ReturnT
.
FAIL_CODE
,
(
I18nUtil
.
getString
(
"jobinfo_field_id"
)+
I18nUtil
.
getString
(
"system_not_found"
))
);
return
new
ReturnT
<
String
>(
ReturnT
.
FAIL_CODE
,
(
I18nUtil
.
getString
(
"jobinfo_field_id"
)+
I18nUtil
.
getString
(
"system_not_found"
))
);
}
}
//String old_cron = exists_jobInfo.getJobCron();
exists_jobInfo
.
setJobGroup
(
jobInfo
.
getJobGroup
());
exists_jobInfo
.
setJobCron
(
jobInfo
.
getJobCron
());
exists_jobInfo
.
setJobCron
(
jobInfo
.
getJobCron
());
exists_jobInfo
.
setJobDesc
(
jobInfo
.
getJobDesc
());
exists_jobInfo
.
setJobDesc
(
jobInfo
.
getJobDesc
());
exists_jobInfo
.
setAuthor
(
jobInfo
.
getAuthor
());
exists_jobInfo
.
setAuthor
(
jobInfo
.
getAuthor
());
...
@@ -186,10 +192,9 @@ public class XxlJobServiceImpl implements XxlJobService {
...
@@ -186,10 +192,9 @@ public class XxlJobServiceImpl implements XxlJobService {
// update quartz-cron if started
// update quartz-cron if started
String
qz_group
=
String
.
valueOf
(
exists_jobInfo
.
getJobGroup
());
String
qz_name
=
String
.
valueOf
(
exists_jobInfo
.
getId
());
try
{
try
{
XxlJobDynamicScheduler
.
updateJobCron
(
qz_group
,
qz_name
,
exists_jobInfo
.
getJobCron
());
String
qz_name
=
String
.
valueOf
(
exists_jobInfo
.
getId
());
XxlJobDynamicScheduler
.
updateJobCron
(
qz_name
,
exists_jobInfo
.
getJobCron
());
}
catch
(
SchedulerException
e
)
{
}
catch
(
SchedulerException
e
)
{
logger
.
error
(
e
.
getMessage
(),
e
);
logger
.
error
(
e
.
getMessage
(),
e
);
return
ReturnT
.
FAIL
;
return
ReturnT
.
FAIL
;
...
@@ -201,12 +206,14 @@ public class XxlJobServiceImpl implements XxlJobService {
...
@@ -201,12 +206,14 @@ public class XxlJobServiceImpl implements XxlJobService {
@Override
@Override
public
ReturnT
<
String
>
remove
(
int
id
)
{
public
ReturnT
<
String
>
remove
(
int
id
)
{
XxlJobInfo
xxlJobInfo
=
xxlJobInfoDao
.
loadById
(
id
);
XxlJobInfo
xxlJobInfo
=
xxlJobInfoDao
.
loadById
(
id
);
String
group
=
String
.
valueOf
(
xxlJobInfo
.
getJobGroup
());
if
(
xxlJobInfo
==
null
)
{
String
name
=
String
.
valueOf
(
xxlJobInfo
.
getId
());
return
ReturnT
.
SUCCESS
;
}
String
name
=
String
.
valueOf
(
xxlJobInfo
.
getId
());
try
{
try
{
// unbind quartz
// unbind quartz
XxlJobDynamicScheduler
.
removeJob
(
name
,
group
);
XxlJobDynamicScheduler
.
removeJob
(
name
);
xxlJobInfoDao
.
delete
(
id
);
xxlJobInfoDao
.
delete
(
id
);
xxlJobLogDao
.
delete
(
id
);
xxlJobLogDao
.
delete
(
id
);
...
@@ -222,12 +229,11 @@ public class XxlJobServiceImpl implements XxlJobService {
...
@@ -222,12 +229,11 @@ public class XxlJobServiceImpl implements XxlJobService {
@Override
@Override
public
ReturnT
<
String
>
start
(
int
id
)
{
public
ReturnT
<
String
>
start
(
int
id
)
{
XxlJobInfo
xxlJobInfo
=
xxlJobInfoDao
.
loadById
(
id
);
XxlJobInfo
xxlJobInfo
=
xxlJobInfoDao
.
loadById
(
id
);
String
group
=
String
.
valueOf
(
xxlJobInfo
.
getJobGroup
());
String
name
=
String
.
valueOf
(
xxlJobInfo
.
getId
());
String
name
=
String
.
valueOf
(
xxlJobInfo
.
getId
());
String
cronExpression
=
xxlJobInfo
.
getJobCron
();
String
cronExpression
=
xxlJobInfo
.
getJobCron
();
try
{
try
{
boolean
ret
=
XxlJobDynamicScheduler
.
addJob
(
name
,
group
,
cronExpression
);
boolean
ret
=
XxlJobDynamicScheduler
.
addJob
(
name
,
cronExpression
);
return
ret
?
ReturnT
.
SUCCESS
:
ReturnT
.
FAIL
;
return
ret
?
ReturnT
.
SUCCESS
:
ReturnT
.
FAIL
;
}
catch
(
SchedulerException
e
)
{
}
catch
(
SchedulerException
e
)
{
logger
.
error
(
e
.
getMessage
(),
e
);
logger
.
error
(
e
.
getMessage
(),
e
);
...
@@ -238,12 +244,11 @@ public class XxlJobServiceImpl implements XxlJobService {
...
@@ -238,12 +244,11 @@ public class XxlJobServiceImpl implements XxlJobService {
@Override
@Override
public
ReturnT
<
String
>
stop
(
int
id
)
{
public
ReturnT
<
String
>
stop
(
int
id
)
{
XxlJobInfo
xxlJobInfo
=
xxlJobInfoDao
.
loadById
(
id
);
XxlJobInfo
xxlJobInfo
=
xxlJobInfoDao
.
loadById
(
id
);
String
group
=
String
.
valueOf
(
xxlJobInfo
.
getJobGroup
());
String
name
=
String
.
valueOf
(
xxlJobInfo
.
getId
());
String
name
=
String
.
valueOf
(
xxlJobInfo
.
getId
());
try
{
try
{
// bind quartz
// bind quartz
boolean
ret
=
XxlJobDynamicScheduler
.
removeJob
(
name
,
group
);
boolean
ret
=
XxlJobDynamicScheduler
.
removeJob
(
name
);
return
ret
?
ReturnT
.
SUCCESS
:
ReturnT
.
FAIL
;
return
ret
?
ReturnT
.
SUCCESS
:
ReturnT
.
FAIL
;
}
catch
(
SchedulerException
e
)
{
}
catch
(
SchedulerException
e
)
{
logger
.
error
(
e
.
getMessage
(),
e
);
logger
.
error
(
e
.
getMessage
(),
e
);
...
...
xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
浏览文件 @
32a84353
...
@@ -142,6 +142,7 @@
...
@@ -142,6 +142,7 @@
<update
id=
"update"
parameterType=
"com.xxl.job.admin.core.model.XxlJobInfo"
>
<update
id=
"update"
parameterType=
"com.xxl.job.admin.core.model.XxlJobInfo"
>
UPDATE XXL_JOB_QRTZ_TRIGGER_INFO
UPDATE XXL_JOB_QRTZ_TRIGGER_INFO
SET
SET
job_group = #{jobGroup},
job_cron = #{jobCron},
job_cron = #{jobCron},
job_desc = #{jobDesc},
job_desc = #{jobDesc},
update_time = NOW(),
update_time = NOW(),
...
...
xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml
浏览文件 @
32a84353
...
@@ -48,7 +48,7 @@
...
@@ -48,7 +48,7 @@
SELECT
<include
refid=
"Base_Column_List"
/>
SELECT
<include
refid=
"Base_Column_List"
/>
FROM XXL_JOB_QRTZ_TRIGGER_LOG AS t
FROM XXL_JOB_QRTZ_TRIGGER_LOG AS t
<trim
prefix=
"WHERE"
prefixOverrides=
"AND | OR"
>
<trim
prefix=
"WHERE"
prefixOverrides=
"AND | OR"
>
<if
test=
"jobGroup gt 0"
>
<if
test=
"job
Id==0 and job
Group gt 0"
>
AND t.job_group = #{jobGroup}
AND t.job_group = #{jobGroup}
</if>
</if>
<if
test=
"jobId gt 0"
>
<if
test=
"jobId gt 0"
>
...
@@ -82,7 +82,7 @@
...
@@ -82,7 +82,7 @@
SELECT count(1)
SELECT count(1)
FROM XXL_JOB_QRTZ_TRIGGER_LOG AS t
FROM XXL_JOB_QRTZ_TRIGGER_LOG AS t
<trim
prefix=
"WHERE"
prefixOverrides=
"AND | OR"
>
<trim
prefix=
"WHERE"
prefixOverrides=
"AND | OR"
>
<if
test=
"jobGroup gt 0"
>
<if
test=
"job
Id==0 and job
Group gt 0"
>
AND t.job_group = #{jobGroup}
AND t.job_group = #{jobGroup}
</if>
</if>
<if
test=
"jobId gt 0"
>
<if
test=
"jobId gt 0"
>
...
...
xxl-job-admin/src/main/resources/templates/jobinfo/jobinfo.index.ftl
浏览文件 @
32a84353
...
@@ -301,7 +301,7 @@ exit 0
...
@@ -301,7 +301,7 @@ exit 0
<div
class=
"form-group"
>
<div
class=
"form-group"
>
<label
for=
"firstname"
class=
"col-sm-2 control-label"
>
${I18n.jobinfo_field_jobgroup}
<font
color=
"red"
>
*
</font></label>
<label
for=
"firstname"
class=
"col-sm-2 control-label"
>
${I18n.jobinfo_field_jobgroup}
<font
color=
"red"
>
*
</font></label>
<div
class=
"col-sm-4"
>
<div
class=
"col-sm-4"
>
<select
class=
"form-control"
name=
"jobGroup"
disabled
>
<select
class=
"form-control"
name=
"jobGroup"
>
<
#
list
JobGroupList
as
group
>
<
#
list
JobGroupList
as
group
>
<option
value=
"${group.id}"
>
${group.title}
</option>
<option
value=
"${group.id}"
>
${group.title}
</option>
</
#
list>
</
#
list>
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论