1.背景:想用定时任务执行某个脚本然后实现定时发送钉钉机器人消息;
2.构思:楼主是做Android开发的,由于不太懂phyon脚本,便想着用java来构建jar包并用shell脚本执行,然后用系统自带的定时任务执行shell脚本
小插曲:查阅了资料,发现mac下并没有像Windows那样直接创建定时任务的功能,不过还是有解决方案的,接下里主角launchctl登场,launchctl是一个统一的服务管理框架,可以启动、停止和管理守护进程、应用程序、进程和脚本等。
launchctl是通过配置文件来指定执行周期和任务的。mac下可把任务信息配置在plist上由launchctl读取
1.shell脚本编写实现
Shell 脚本(shell script),是一种为 shell 编写的脚本程序。
Shell 编程跟 java、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
1.1创建tapd_task.jar用来给钉钉群发送机器人定时消息
具体代码:
//测试钉钉群
// public static String WEBHOOK_TOKEN = "https://oapi.dingtalk.com/robot/send?access_token=a9b02dba00b5ad77e6cffbb45c07b95602f7d9d72f5c11370f130787f93ae08a";
public static String MESSAGE = "{\n" +
"\t\"msgtype\": \"link\",\n" +
"\t\"link\": {\n" +
"\t\t\"text\": \"当天的工作即将结束,请各位同学更新tapd任务花费及状态等信息(点击此妹纸可直达tapd任务链接 Y(^_^)Y \",\n" +
"\t\t\"title\": \"更新tapd任务提示\",\n" +
"\t\t\"picUrl\": \"https://ws1.sinaimg.cn/large/0065oQSqly1g0ajj4h6ndj30sg11xdmj.jpg\",\n" +
"\t\t\"messageUrl\": \"https://www.tapd.cn/",\n" +
"\t\t\"at\": {\n" +
"\t\t\t\"atMobiles\": [\n" +
"\t\t\t\t\"156xxxx8827\",\n" +
"\t\t\t\t\"189xxxx8325\"\n" +
"\t\t\t],\n" +
"\t\t\t\"isAtAll\": true\n" +
"\t\t}\n" +
"\t}\n" +
"}";
public static String MESSAGE2 = "{\n" +
"\t\"msgtype\": \"text\",\n" +
"\t\"text\": {\n" +
"\t\t\"content\": \"请各位同学及时处理!!!\"\n" +
"\t},\n" +
"\t\"at\": {\n" +
"\t\t\"atMobiles\": [\n" +
"\t\t\t\"156xxxx8827\",\n" +
"\t\t\t\"189xxxx8325\"\n" +
"\t\t],\n" +
"\t\t\"isAtAll\": true\n" +
"\t}\n" +
"}";
public static void main(String args[]) throws Exception {
// HttpClient httpclient = HttpClients.createDefault();
HttpClient httpclient = createSSLClientDefault();
HttpPost httppost = new HttpPost(WEBHOOK_TOKEN);
httppost.addHeader("Content-Type", "application/json; charset=utf-8");
StringEntity se = new StringEntity(MESSAGE, "utf-8");
httppost.setEntity(se);
HttpResponse response = httpclient.execute(httppost);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
String result = EntityUtils.toString(response.getEntity(), "utf-8");
System.out.println(result);
}
HttpClient httpclient2 = HttpClients.createDefault();
HttpPost httppost2 = new HttpPost(WEBHOOK_TOKEN);
httppost2.addHeader("Content-Type", "application/json; charset=utf-8");
StringEntity se2 = new StringEntity(MESSAGE2, "utf-8");
httppost2.setEntity(se2);
httpclient2.execute(httppost2);
}
/**
* 忽略https证书,1.6的jdk在mac环境下用命令行运行jar包对https的证书兼容有问题
*
* @return
*/
public static CloseableHttpClient createSSLClientDefault() {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
// 信任所有
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
return HttpClients.createDefault();
}
生成jar包的gradle配置:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
task deleteOldJar(type: Delete) {
delete 'build/outputs/test.jar'//架包输出路径
}
task exportJar(type: Copy) {
from('build/intermediates/intermediate-jars/release/')//3.0之前的版本是(build/intermediates/bundles/release/)之后发生了变化
into('build/libs/')//输出路径,自定义
include('classes.jar')
rename ('classes.jar', 'tapd_task.jar')//后面的是jar包名,自定义
}
exportJar.dependsOn(deleteOldJar, build)
sourceCompatibility = "6"
targetCompatibility = "6"
在Android studio terminal里执行gradle exportJar即可在对应的文件夹里生成shell脚本执行的jar包
这里的值得一说的就是这个配置
sourceCompatibility =6
原先配置里面是
sourceCompatibility =7
但是在后面执行tapd.plist时发现系统提示“您需要安装JDK才能使用java命令工具”,之后我在终端里打印
java -verison
发现系统已经安装了jdk并且已经配置了环境变量,但是jdk版本为最新版本,但为什么会提示这个异常呢,最后发现java官网的这个说明:
具体链接:https://www.java.com/zh_CN/download/faq/yosemite_java.xml
随后我便装了更低版本的jdk1.6,运行后发现1.6的jdk对https的证书兼容不太好,也就有了后续我忽略https证书的处理;
1.2创建run.sh脚本
进入 run.sh放置的问价目录cd /Users/xiaozhuzhu/Library/LaunchAgents
创建run.sh脚本vi run.sh
#!/bin/bash
echo “tapdTask”
#PWD=`pwd`
PWD='/Users/xiaozhuzhu/Library/LaunchAgents'
echo $PWD
for i in $PWD/lib/*;
do
CLASSPATH=$CLASSPATH:$i
done
export CLASSPATH=$CLASSPATH:$PWD/tapd_task.jar
echo $CLASSPATH
className='com.huanshou.shopapp.tapd_task.MyClass'
java -Dfile.encoding=utf-8 -Xmx1024M $className
#> $PWD/tapdtask.log 2>&1
点击esc退出编辑模式,然后输入:wq
保存退出
注意,脚本要改成可执行的权限chmod 777 run.sh
2 .编写plist文件
launchctl 将根据plist文件的信息来启动任务。
plist脚本一般存放在以下目录:
-
/Library/LaunchDaemons
-->只要系统启动了,哪怕用户不登陆系统也会被执行 -
/Library/LaunchAgents
-->当用户登陆系统后才会被执行
更多的plist存放目录:
~/Library/LaunchAgents 由用户自己定义的任务项
/Library/LaunchAgents 由管理员为用户定义的任务项
/Library/LaunchDaemons 由管理员定义的守护进程任务项
/System/Library/LaunchAgents 由Mac OS X为用户定义的任务项
/System/Library/LaunchDaemons 由Mac OS X定义的守护进程任务项
进入~/Library/LaunchAgents
,创建一个plist文件com.demo.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Label唯一的标识 -->
<key>Label</key>
<string>com.demo.plist</string>
<!-- 指定要运行的脚本 -->
<key>ProgramArguments</key>
<array>
<string>/Users/xiaozhuzhu/Library/LaunchAgents/run.sh</string>
</array>
<!-- 指定要运行的时间 -->
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>15</integer>
<key>Minute</key>
<integer>30</integer>
<key>Weekday</key>
<integer>1</integer>
</dict>
<dict>
<key>Hour</key>
<integer>15</integer>
<key>Minute</key>
<integer>30</integer>
<key>Weekday</key>
<integer>2</integer>
</dict>
<dict>
<key>Hour</key>
<integer>15</integer>
<key>Minute</key>
<integer>30</integer>
<key>Weekday</key>
<integer>3</integer>
</dict>
<dict>
<key>Hour</key>
<integer>15</integer>
<key>Minute</key>
<integer>30</integer>
<key>Weekday</key>
<integer>4</integer>
</dict>
<dict>
<key>Hour</key>
<integer>15</integer>
<key>Minute</key>
<integer>30</integer>
<key>Weekday</key>
<integer>5</integer>
</dict>
</array>
<!-- 标准输出文件 -->
<key>StandardOutPath</key>
<string>/Users/xiaozhuzhu/Library/LaunchAgents/run.log</string>
<!-- 标准错误输出文件,错误日志 -->
<key>StandardErrorPath</key>
<string>/Users/xiaozhuzhu/Library/LaunchAgents/run.err</string>
</dict>
</plist>
2.1 加载命令
launchctl load -w com.demo.plist
这样任务就加载成功了。
更多的命令:
# 加载任务, -w选项会将plist文件中无效的key覆盖掉,建议加上
$ launchctl load -w com.demo.plist
# 删除任务
$ launchctl unload -w com.demo.plist
# 查看任务列表, 使用 grep '任务部分名字' 过滤
$ launchctl list | grep 'com.demo'
# 开始任务
$ launchctl start com.demo.plist
# 结束任务
$ launchctl stop com.demo.plist
如果任务呗修改了,那么必须先unload,然后重新load
start可以测试任务,这个是立即执行,不管时间到了没有
执行start和unload前,任务必须先load过,否则报错
stop可以停止任务
2.3番外篇
plist支持两种方式配置执行时间:
StartInterval: 指定脚本每间隔多长时间(单位:秒)执行一次;
StartCalendarInterval: 可以指定脚本在多少分钟、小时、天、星期几、月时间上执行,类似如crontab的中的设置,包含下面的 key:
Minute <integer>
The minute on which this job will be run.
Hour <integer>
The hour on which this job will be run.
Day <integer>
The day on which this job will be run.
Weekday <integer>
The weekday on which this job will be run (0 and 7 are Sunday).
Month <integer>
The month on which this job will be run.
其中Weekday里0,7表示星期天,1-5分别表示周一到周五;
plist部分参数说明:
Label:对应的需要保证全局唯一性;
Program:要运行的程序;
ProgramArguments:命令语句
StartCalendarInterval:运行的时间,单个时间点使用dict,多个时间点使用 array <dict>
StartInterval:时间间隔,与StartCalendarInterval使用其一,单位为秒
StandardInPath、StandardOutPath、StandardErrorPath:标准的输入输出错误文件,这里建议不要使用 .log 作为后缀,会打不开里面的信息。
定时启动任务时,如果涉及到网络,但是电脑处于睡眠状态,是执行不了的,这个时候,可以定时的启动屏幕就好了。
更多的参数参见:mac官方文档
3总结:
由于对mac系统不是很熟悉,导致在调试的时候出各种问题,好在有前人各种采坑,再次谢过给本文提供帮助的大神
参考文章:
https://www.jianshu.com/p/4addd9b455f2
https://blog.csdn.net/u012390519/article/details/74542042