Java 定时任务JOB创建过程

1.创建QuartzManager管理类。
如果需要可根据自身的需要对此类的方法进行重载,以符合自身业务

import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import java.util.Map;public class QuartzManager {private static String JOB_GROUP_NAME = "EXTJWEB_JOBGROUP_NAME";private static String TRIGGER_GROUP_NAME = "EXTJWEB_TRIGGERGROUP_NAME";/*** @Description: 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名** @param sched*            调度器** @param jobName*            任务名* @param cls*            任务* @param time*            时间设置,参考quartz说明文档** @Title: QuartzManager.java*/public static void addJob(Scheduler sched, String jobName, @SuppressWarnings("rawtypes") Class cls, String time) {try {JobDetail jobDetail = new JobDetail(jobName, JOB_GROUP_NAME, cls);// 任务名,任务组,任务执行类// 触发器CronTrigger trigger = new CronTrigger(jobName, TRIGGER_GROUP_NAME);// 触发器名,触发器组trigger.setCronExpression(time);// 触发器时间设定sched.scheduleJob(jobDetail, trigger);// 启动if (!sched.isShutdown()) {sched.start();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Description: 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名** @param sched*            调度器** @param jobName*            任务名* @param cls*            任务* @param map*            描述* @param time*            时间设置,参考quartz说明文档** @Title: QuartzManager.java*/public static void addJob(Scheduler sched, String jobName, Map map, @SuppressWarnings("rawtypes") Class cls, String time) {try {JobDetail jobDetail = new JobDetail(jobName, JOB_GROUP_NAME, cls);// 任务名,任务组,任务执行类jobDetail.setJobDataMap(new JobDataMap(map));//jobDetail.setDescription(description);// 触发器CronTrigger trigger = new CronTrigger(jobName, TRIGGER_GROUP_NAME);// 触发器名,触发器组trigger.setCronExpression(time);// 触发器时间设定sched.scheduleJob(jobDetail, trigger);// 启动if (!sched.isShutdown()) {sched.start();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Description: 添加一个定时任务** @param sched*            调度器** @param jobName*            任务名* @param jobGroupName*            任务组名* @param triggerName*            触发器名* @param triggerGroupName*            触发器组名* @param jobClass*            任务* @param time*            时间设置,参考quartz说明文档** @Title: QuartzManager.java*/public static void addJob(Scheduler sched, String jobName, String jobGroupName, String triggerName, String triggerGroupName, @SuppressWarnings("rawtypes") Class jobClass, String time) {try {JobDetail jobDetail = new JobDetail(jobName, jobGroupName, jobClass);// 任务名,任务组,任务执行类// 触发器CronTrigger trigger = new CronTrigger(triggerName, triggerGroupName);// 触发器名,触发器组trigger.setCronExpression(time);// 触发器时间设定sched.scheduleJob(jobDetail, trigger);} catch (Exception e) {throw new RuntimeException(e);}}/*** @Description: 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)** @param sched*            调度器* @param jobName* @param time** @Title: QuartzManager.java*/@SuppressWarnings("rawtypes")public static void modifyJobTime(Scheduler sched, String jobName, String time) {try {CronTrigger trigger = (CronTrigger) sched.getTrigger(jobName, TRIGGER_GROUP_NAME);if (trigger == null) {return;}String oldTime = trigger.getCronExpression();if (!oldTime.equalsIgnoreCase(time)) {JobDetail jobDetail = sched.getJobDetail(jobName, JOB_GROUP_NAME);Class objJobClass = jobDetail.getJobClass();removeJob(sched, jobName);addJob(sched, jobName, objJobClass, time);}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Description: 修改一个任务的触发时间** @param sched*            调度器 ** @param sched*            调度器* @param triggerName* @param triggerGroupName* @param time** @Title: QuartzManager.java*/public static void modifyJobTime(Scheduler sched, String triggerName, String triggerGroupName, String time) {try {CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerName, triggerGroupName);if (trigger == null) {return;}String oldTime = trigger.getCronExpression();if (!oldTime.equalsIgnoreCase(time)) {CronTrigger ct = (CronTrigger) trigger;// 修改时间ct.setCronExpression(time);// 重启触发器sched.resumeTrigger(triggerName, triggerGroupName);}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Description: 移除一个任务(使用默认的任务组名,触发器名,触发器组名)** @param sched*            调度器* @param jobName** @Title: QuartzManager.java*/public static void removeJob(Scheduler sched, String jobName) {try {sched.pauseTrigger(jobName, TRIGGER_GROUP_NAME);// 停止触发器sched.unscheduleJob(jobName, TRIGGER_GROUP_NAME);// 移除触发器sched.deleteJob(jobName, JOB_GROUP_NAME);// 删除任务} catch (Exception e) {throw new RuntimeException(e);}}/*** @Description: 移除一个任务** @param sched*            调度器* @param jobName* @param jobGroupName* @param triggerName* @param triggerGroupName** @Title: QuartzManager.java*/public static void removeJob(Scheduler sched, String jobName, String jobGroupName, String triggerName, String triggerGroupName) {try {sched.pauseTrigger(triggerName, triggerGroupName);// 停止触发器sched.unscheduleJob(triggerName, triggerGroupName);// 移除触发器sched.deleteJob(jobName, jobGroupName);// 删除任务} catch (Exception e) {throw new RuntimeException(e);}}/*** @Description:启动所有定时任务** @param sched*            调度器** @Title: QuartzManager.java*/public static void startJobs(Scheduler sched) {try {sched.start();} catch (Exception e) {throw new RuntimeException(e);}}/*** @Description:关闭所有定时任务*** @param sched*            调度器*** @Title: QuartzManager.java*/public static void shutdownJobs(Scheduler sched) {try {if (!sched.isShutdown()) {sched.shutdown();}} catch (Exception e) {throw new RuntimeException(e);}}
}

2.创建定时任务实现类

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class QuartzJob implements Job{@Overridepublic void execute(JobExecutionContext push) throws JobExecutionException {//System.out.println("我只是一个定时任务,你们看不见我");//TODO业务代码省略}
}

3.如何使用,创建以上两个类之后,我们就该调用了

//
public void test(){SchedulerFactory schedulerFactory  = new StdSchedulerFactory();Scheduler sche = schedulerFactory.getScheduler();String taskID = "7879879798797";Map map = new HashMap();map.put("test1","我是业务参数1");map.put("test2","我是业务参数2");QuartzManager.addJob(sche, taskID,map, QuartzJob.class,  CornUtil.getCron(DateUtil.stringToDate("2030-07-19 12:12:12","yyyy-MM-dd HH:mm:ss")));
}

附CornUtil类(此类是时间转core表达式)

import java.text.SimpleDateFormat;
import java.util.Date;public class CornUtil {/***** @param date* @param dateFormat : e.g:yyyy-MM-dd HH:mm:ss* @return*/public static String formatDateByPattern(Date date, String dateFormat) {SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);String formatTimeStr = null;if (date != null) {formatTimeStr = sdf.format(date);}return formatTimeStr;}/**** convert Date to cron ,eg.  "0 07 10 15 1 ? 2016"* @param date  : 时间点* @return*/public static String getCron(Date date) {String dateFormat = "ss mm HH dd MM ? yyyy";return formatDateByPattern(date, dateFormat);}}

附时间转化类DateUtil

import java.text.;
import java.util.
;

public class DateUtil {

public static final String ISO_DATE_FORMAT = "yyyyMMdd";public static final String ISO_EXPANDED_DATE_FORMAT = "yyyy-MM-dd";/*** yyyy-MM-dd hh:mm:ss*/
public static String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static String DATE_PATTERN = "yyyyMMddHHmmss";private static boolean LENIENT_DATE = false;private static Random random = new java.util.Random();
private static final int ID_BYTES = 10;public synchronized static String generateId() {StringBuffer result = new StringBuffer();result = result.append(System.currentTimeMillis());for (int i = 0; i < ID_BYTES; i++) {result = result.append(random.nextInt(10));}return result.toString();
}protected static final float normalizedJulian(float JD) {float f = Math.round(JD + 0.5f) - 0.5f;return f;
}/*** Returns the Date from a julian. The Julian date will be converted to noon GMT,* such that it matches the nearest half-integer (i.e., a julian date of 1.4 gets* changed to 1.5, and 0.9 gets changed to 0.5.)** @param JD the Julian date* @return the Gregorian date*/
public static final Date toDate(float JD) {/* To convert a Julian Day Number to a Gregorian date, assume that it is for 0 hours, Greenwich time (so* that it ends in 0.5). Do the following calculations, again dropping the fractional part of all* multiplicatons and divisions. Note: This method will not give dates accurately on the* Gregorian Proleptic Calendar, i.e., the calendar you get by extending the Gregorian* calendar backwards to years earlier than 1582. using the Gregorian leap year* rules. In particular, the method fails if Y<400. */float Z = (normalizedJulian(JD)) + 0.5f;float W = (int) ((Z - 1867216.25f) / 36524.25f);float X = (int) (W / 4f);float A = Z + 1 + W - X;float B = A + 1524;float C = (int) ((B - 122.1) / 365.25);float D = (int) (365.25f * C);float E = (int) ((B - D) / 30.6001);float F = (int) (30.6001f * E);int day = (int) (B - D - F);int month = (int) (E - 1);if (month > 12) {month = month - 12;}int year = (int) (C - 4715); //(if Month is January or February) or C-4716 (otherwise)if (month > 2) {year--;}Calendar c = Calendar.getInstance();c.set(Calendar.YEAR, year);c.set(Calendar.MONTH, month - 1); // damn 0 offsetsc.set(Calendar.DATE, day);return c.getTime();
}/*** Returns the days between two dates. Positive values indicate that* the second date is after the first, and negative values indicate, well,* the opposite. Relying on specific times is problematic.** @param early the "first date"* @param late the "second date"* @return the days between the two dates*/
public static final int daysBetween(Date early, Date late) {Calendar c1 = Calendar.getInstance();Calendar c2 = Calendar.getInstance();c1.setTime(early);c2.setTime(late);return daysBetween(c1, c2);
}/*** Returns the days between two dates. Positive values indicate that* the second date is after the first, and negative values indicate, well,* the opposite.** @param early* @param late* @return the days between two dates.*/
public static final int daysBetween(Calendar early, Calendar late) {return (int) (toJulian(late) - toJulian(early));
}/*** Return a Julian date based on the input parameter. This is* based from calculations found at* <a href="http://quasar.as.utexas.edu/BillInfo/JulianDatesG.html">Julian Day Calculations* (Gregorian Calendar)</a>, provided by Bill Jeffrys.* @param c a calendar instance* @return the julian day number*/
public static final float toJulian(Calendar c) {int Y = c.get(Calendar.YEAR);int M = c.get(Calendar.MONTH);int D = c.get(Calendar.DATE);int A = Y / 100;int B = A / 4;int C = 2 - A + B;float E = (int) (365.25f * (Y + 4716));float F = (int) (30.6001f * (M + 1));float JD = C + D + E + F - 1524.5f;return JD;
}/*** Return a Julian date based on the input parameter. This is* based from calculations found at* <a href="http://quasar.as.utexas.edu/BillInfo/JulianDatesG.html">Julian Day Calculations* (Gregorian Calendar)</a>, provided by Bill Jeffrys.* @param date* @return the julian day number*/
public static final float toJulian(Date date) {Calendar c = Calendar.getInstance();c.setTime(date);return toJulian(c);
}/*** @param isoString* @param fmt* @param field   Calendar.YEAR/Calendar.MONTH/Calendar.DATE* @param amount* @return* @throws ParseException*/
public static final String dateIncrease(String isoString, String fmt,int field, int amount) {try {Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));cal.setTime(stringToDate(isoString, fmt, true));cal.add(field, amount);return dateToString(cal.getTime(), fmt);} catch (Exception ex) {return null;}
}/*** Time Field Rolling function.* Rolls (up/down) a single unit of time on the given time field.** @param isoString* @param field the time field.* @param up Indicates if rolling up or rolling down the field value.* @param expanded use formating char's* @exception ParseException if an unknown field value is given.*/
public static final String roll(String isoString, String fmt, int field,boolean up) throws ParseException {Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));cal.setTime(stringToDate(isoString, fmt));cal.roll(field, up);return dateToString(cal.getTime(), fmt);
}/*** Time Field Rolling function.* Rolls (up/down) a single unit of time on the given time field.** @param isoString* @param field the time field.* @param up Indicates if rolling up or rolling down the field value.* @exception ParseException if an unknown field value is given.*/
public static final String roll(String isoString, int field, boolean up) throwsParseException {return roll(isoString, DATETIME_PATTERN, field, up);
}/***  java.util.Date* @param dateText* @param format* @param lenient* @return*/
public static Date stringToDate(String dateText, String format,boolean lenient) {if (dateText == null) {return null;}DateFormat df = null;try {if (format == null) {df = new SimpleDateFormat();} else {df = new SimpleDateFormat(format);}// setLenient avoids allowing dates like 9/32/2001// which would otherwise parse to 10/2/2001df.setLenient(false);return df.parse(dateText);} catch (ParseException e) {return null;}
}/*** @return Timestamp*/
public static java.sql.Timestamp getCurrentTimestamp() {return new java.sql.Timestamp(new java.util.Date().getTime());
}/** java.util.Date* @param dateText* @param format* @return*/
public static Date stringToDate(String dateString, String format) {return stringToDate(dateString, format, LENIENT_DATE);
}/*** java.util.Date* @param dateText*/
public static Date stringToDate(String dateString) {return stringToDate(dateString, ISO_EXPANDED_DATE_FORMAT, LENIENT_DATE);
}/*** @return* @param pattern* @param date*/
public static String dateToString(Date date, String pattern) {if (date == null) {return null;}try {SimpleDateFormat sfDate = new SimpleDateFormat(pattern);sfDate.setLenient(false);return sfDate.format(date);} catch (Exception e) {return null;}
}/*** yyyy-MM-dd* @param date* @return*/
public static String dateToString(Date date) {return dateToString(date, ISO_EXPANDED_DATE_FORMAT);
}/*** @return*/
public static Date getCurrentDateTime() {java.util.Calendar calNow = java.util.Calendar.getInstance();java.util.Date dtNow = calNow.getTime();return dtNow;
}/**** @param pattern* @return*/
public static String getCurrentDateString(String pattern) {return dateToString(getCurrentDateTime(), pattern);
}/***   yyyy-MM-dd* @return*/
public static String getCurrentDateString() {return dateToString(getCurrentDateTime(), ISO_EXPANDED_DATE_FORMAT);
}/*** 返回固定格式的当前时间*   yyyy-MM-dd hh:mm:ss* @param date* @return*/
public static String dateToStringWithTime( ) {return dateToString(new java.util.Date(), DATETIME_PATTERN);
}/***   yyyy-MM-dd hh:mm:ss* @param date* @return*/
public static String dateToStringWithTime(Date date) {return dateToString(date, DATETIME_PATTERN);
}/**** @param date* @param days* @return java.util.Date*/
public static Date dateIncreaseByDay(Date date, int days) {Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));cal.setTime(date);cal.add(Calendar.DATE, days);return cal.getTime();
}/**** @param date* @param days* @return java.util.Date*/
public static Date dateIncreaseByMonth(Date date, int mnt) {Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));cal.setTime(date);cal.add(Calendar.MONTH, mnt);return cal.getTime();
}/**** @param date* @param mnt* @return java.util.Date*/
public static Date dateIncreaseByYear(Date date, int mnt) {Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));cal.setTime(date);cal.add(Calendar.YEAR, mnt);return cal.getTime();
}/**** @param date   yyyy-MM-dd* @param days* @return  yyyy-MM-dd*/
public static String dateIncreaseByDay(String date, int days) {return dateIncreaseByDay(date, ISO_DATE_FORMAT, days);
}/*** @param date* @param fmt* @param days* @return*/
public static String dateIncreaseByDay(String date, String fmt, int days) {return dateIncrease(date, fmt, Calendar.DATE, days);
}/**** @param src* @param srcfmt* @param desfmt* @return*/
public static String stringToString(String src, String srcfmt,String desfmt) {return dateToString(stringToDate(src, srcfmt), desfmt);
}/**** @param date* @return string*/
public static String getYear(Date date) {java.text.SimpleDateFormat formater = new java.text.SimpleDateFormat("yyyy");String cur_year = formater.format(date);return cur_year;
}/**** @param date* @return string*/
public static String getMonth(Date date) {java.text.SimpleDateFormat formater = new java.text.SimpleDateFormat("MM");String cur_month = formater.format(date);return cur_month;
}/*** @param date* @return string*/
public static String getDay(Date date) {java.text.SimpleDateFormat formater = new java.text.SimpleDateFormat("dd");String cur_day = formater.format(date);return cur_day;
}/*** @param date* @return string*/
public static String getHour(Date date) {java.text.SimpleDateFormat formater = new java.text.SimpleDateFormat("HH");String cur_day = formater.format(date);return cur_day;
}public static int getMinsFromDate(java.util.Date dt) {GregorianCalendar cal = new GregorianCalendar();cal.setTime(dt);int hour = cal.get(Calendar.HOUR_OF_DAY);int min = cal.get(Calendar.MINUTE);return ((hour * 60) + min);
}/*** Function to convert String to Date Object. If invalid input then current or next day date* is returned (Added by Ali Naqvi on 2006-5-16).* @param str String input in YYYY-MM-DD HH:MM[:SS] format.* @param isExpiry boolean if set and input string is invalid then next day date is returned* @return Date*/
public static java.util.Date convertToDate(String str, boolean isExpiry) {SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm");java.util.Date dt = null;try {dt = fmt.parse(str);} catch (ParseException ex) {Calendar cal = Calendar.getInstance();if (isExpiry) {cal.add(Calendar.DAY_OF_MONTH, 1);cal.set(Calendar.HOUR_OF_DAY, 23);cal.set(Calendar.MINUTE, 59);} else {cal.set(Calendar.HOUR_OF_DAY, 0);cal.set(Calendar.MINUTE, 0);}dt = cal.getTime();}return dt;
}public static java.util.Date convertToDate(String str) {SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd hh:mm");java.util.Date dt = null;try {dt = fmt.parse(str);} catch (ParseException ex) {dt = new java.util.Date();}return dt;
}public static String dateFromat(Date date, int minute) {String dateFormat = null;int year = Integer.parseInt(getYear(date));int month = Integer.parseInt(getMonth(date));int day = Integer.parseInt(getDay(date));int hour = minute / 60;int min = minute % 60;dateFormat = String.valueOf(year)+(month > 9 ? String.valueOf(month) :"0" + String.valueOf(month))+(day > 9 ? String.valueOf(day) : "0" + String.valueOf(day))+ " "+(hour > 9 ? String.valueOf(hour) : "0" + String.valueOf(hour))+(min > 9 ? String.valueOf(min) : "0" + String.valueOf(min))+ "00";return dateFormat;
}
public static String sDateFormat() {return new SimpleDateFormat(DATE_PATTERN).format(Calendar.getInstance().getTime());
}
public static Long getCurrentTimestampLong() {return new java.util.Date().getTime();
}
public static void main(String[] args)
{String timeDir=DateUtil.dateToString(new Date(),DateUtil.ISO_EXPANDED_DATE_FORMAT);System.out.println(timeDir);
}

}

Java 定时任务JOB相关推荐

  1. java定时任务,每天定时执行任务

    java定时任务,每天定时执行任务.以下是这个例子的全部代码. public class TimerManager {//时间间隔private static final long PERIOD_DA ...

  2. java定时任务框架elasticjob详解

    这篇文章主要介绍了java定时任务框架elasticjob详解,Elastic-Job是ddframe中dd-job的作业模块中分离出来的分布式弹性作业框架.该项目基于成熟的开源产品Quartz和Zo ...

  3. Springboot2 Quartz实现JAVA定时任务的动态配置

    动态配置Quartz.没接触过定时任务的同学可以先看下此篇:JAVA定时任务实现的几种方式 文章目录 一.需求背景 1. 问题现象 2. 问题分析 3. 解决方案 二.需求背景 2.1. maven依 ...

  4. Java定时任务解决方案

    Java定时任务解决方案 参考文章: (1)Java定时任务解决方案 (2)https://www.cnblogs.com/zhixiang-org-cn/p/9490877.html (3)http ...

  5. Java定时任务(一) Timer及TimerTask的案例解析及源码分析

    Java定时任务(一)  Timer及TimerTask的案例解析及源码分析 一.概述: 定时任务这个概念在Java的学习以及项目的开发中并不陌生,应用场景也是多种多样.比如我们会注意到12306网站 ...

  6. JAVA定时任务的简单实现

    Java定时任务的简单实现 2011-01-02 18:34:43|  分类: 软件开发 |  标签:void  timer  import  param  dateutil   |字号大中小 订阅 ...

  7. Java定时任务技术分析

    <从零打造项目>系列文章 工具 比MyBatis Generator更强大的代码生成器 ORM框架选型 SpringBoot项目基础设施搭建 SpringBoot集成Mybatis项目实操 ...

  8. Java 定时任务详解

    文章目录 单机定时任务技术选型 Timer ScheduledExecutorService Spring Task 时间轮 分布式定时任务技术选型 Quartz Elastic-Job XXL-JO ...

  9. Java定时任务手工触发-使用Arthas

    1. 前言 在测试环境经常需要手工触发Java应用中的定时任务,如果定时任务没有使用Quartz,Java应用中也没有提供其他方法手工触发定时任务,可以使用Arthas快速实现以上目的. 以下使用Ar ...

  10. JAVA定时任务时间配置

    JAVA定时任务时间配置 每个位置配置解释 参数说明 通配符说明 常用示例 每个位置配置解释 秒 分 时 日 月 周 年* * * * * * * 参数说明 序号 说明 是否必填 允许填写的值 允许的 ...

最新文章

  1. WCF一个运行环境,一个服务逻辑人,一个客户
  2. 第5周实践项目2 链栈的算法库建立
  3. 计划将项目中使用entity framework的要点记录到改栏目下
  4. python_异常处理
  5. Cs231n课堂内容记录-Lecture 5 卷积神经网络介绍
  6. Java线程的不同状态
  7. Hadoop学习笔记—15.HBase框架学习(基础知识篇)
  8. daily scrum 11.27
  9. 面试题: 找出二叉树上任意两个结点的最近共同父结点。
  10. 4999元起!三星在中国正式发布Galaxy S22系列
  11. SAP License:PO中“最终发票”的应用与理解
  12. 流浪汉转型程序员,年薪超 70 万人民币!
  13. Moss网站在不同服务器的迁移
  14. 快到而立之年了,可是能撑得起而立吗?
  15. ​香农与信息论三大定律
  16. 5类6类7类网线对比_五类/超五类/六类/超六类/七类等多类网线的比较
  17. Python 机器学习实战 —— 监督学习(上)
  18. 阿里P9:做了6年架构设计,这次聊聊微服务与分布式事务细节
  19. 关于一款心理辅导机器人的调研(Woebot)
  20. 电子商务B2C的新动态 - 个性化、专业化定制网站已经平民化

热门文章

  1. Edge浏览器支持IE内核 / 增加Edge兼容性
  2. ITIL与DevOps
  3. Windows聚焦壁纸
  4. 某条微博评论数据爬取
  5. 如何利用Python爬虫获取网络小说
  6. Ubuntu 9.04下安装飞信LibFetion V1.1版
  7. linux ubuntu软件中心,Ubuntu 20.04 将Ubuntu软件中心切换到Snap商店
  8. roseha for linux,ROSEHA for linux安装与操作文档
  9. “Master”围棋对战50胜1和,人工智能身份欲揭?
  10. 从零开始用Kotlin结合Jetpack写一个五子棋