精华内容
下载资源
问答
  • python3 按月执行定时任务,保证每月都有执行任务(2月,30天,31天做了处理)
    千次阅读
    2018-08-20 22:25:25
    def mouthRange(beginDate, endDate, shu_day, schedule_time):
        dates = []
        new_mouths = []
        dt = datetime.datetime.strptime(beginDate, "%Y-%m-%d %H:%M")
        date = beginDate[:]
        while date <= endDate:
            dates.append(date)
            dt = dt + datetime.timedelta(1)
            date = dt.strftime("%Y-%m-%d %H:%M")
        da_mouth = []
        for i in dates:
            if i[:7] not in da_mouth:
                da_mouth.append(i[:7])
        # schedule_time = "17:20"
        # shu_day = 1
        # current_time = datetime.datetime.now()
        # current_time = current_time.strftime("%Y-%m-%d %H:%M")
        # current_time = datetime.datetime.strptime(current_time, "%Y-%m-%d %H:%M")
        # big_time = datetime.datetime.strptime(beginDate, "%Y-%m-%d %H:%M")
        # print(current_time)
        # print(big_time)
        # if current_time > dt:
        #     print("12jpkp")
        #     da_mouth = da_mouth[1:]
        first_mouth = int(beginDate[8:10])
        print(beginDate[8:10])
        if first_mouth <= shu_day:
            new_mouths.append(da_mouth[0] + "-" +str(shu_day) + " "+ schedule_time)
        for i in da_mouth[1:]:
            days = calendar.monthrange(int(i[:4]), int(i[5:7]))[1]
            if shu_day < 29:
                new_mouths.append(i+"-"+str(shu_day).zfill(2) +" "+schedule_time)
            if shu_day == 29:
                if days >= 29:
                    new_mouths.append(i+"-"+str(shu_day) +" "+schedule_time)
                else:
                    new_mouths.append(i+"-"+"28" +" "+schedule_time)
            elif shu_day == 30:
                if days == 28:
                    new_mouths.append(i+"-"+str(days) +" "+schedule_time)
                elif days== 29 :
                    new_mouths.append(i+"-"+"29" +" "+schedule_time)
                elif days >= 30:
                    new_mouths.append(i+"-"+"30" +" "+schedule_time)
            elif shu_day == 31:
                if days == 28:
                    new_mouths.append(i+"-"+str(days) +" "+schedule_time)
                elif days== 29 :
                    new_mouths.append(i+"-"+"29" +" "+schedule_time)
                elif days == 30:
                    new_mouths.append(i+"-"+"30" +" "+schedule_time)
                elif days == 31:
                    new_mouths.append(i+"-"+"31" +" "+schedule_time)
            else:
                pass
        last_time = datetime.datetime.strptime(new_mouths[-1], "%Y-%m-%d %H:%M")
        endDate_time = datetime.datetime.strptime(endDate, "%Y-%m-%d %H:%M")
        if last_time > endDate_time:
            new_mouths =  new_mouths[:-1]
        return new_mouths
    print(mouthRange('2018-08-15 11:00', '2018-09-15 12:30', 15, "12:00"))

    输出结果:

    
    

     

    解释说明:

    beginDate 为 定时任务 开始执行日期

    endDate   为定时任务  结束执行日期

    shu_day  为 每月几号 执行定时任务

    schedule_time  为定时任务中每次执行的时间

    此代码也可以找到每月的最后一天,也就是每月最后一天执行,包含特殊月份的处理

    更多相关内容
  • 在tomcat中,每月一号、每日、每分钟自动执行指定的任务。 开发环境: java1.7+ tomcat 实现思路: 在tomcat中,添加监听器,在监听器中设置定时任务。 1.监听: 新建监听类implents ServletContextListener,...

    需求:

    在tomcat中,每月一号、每日、每分钟自动执行指定的任务。

    开发环境:

    java1.7 + tomcat

    实现思路:

    在tomcat中,添加监听器,在监听器中设置定时任务。

    1.监听:

    新建监听类implents  ServletContextListener,实现其中的方法即可。

    讲解:

    1.1:创建一个基准时间defaultdate(每日8点),用于参照,在此时间以后的多长周期内执行操作。

    1.2 :schedule(task, firstTime, period); 方法参数介绍:

    task:TimerTask任务,用内部匿名类的方式新建一个即可(当然也可以在外部类中建一个类,用于写任务,写法麻烦点),实现run()方法,在Run中写你要执行操作即可。

    firstTime:任务首次执行时间。当系统时间大于firstTime,会立即执行一次任务。当系统时间小于firstTime,则等到时间等于firstTime时才执行。所以用schedule实现定时任务,最重要的的就是控制这个firstTime。

    period:执行周期,单位:毫秒。一天写法:24 * 60 * 60 * 1000

    1.3 : 如何判断是每月1号?用Calendar的Calendar.DAY_OF_MONTH,每月首天 返回值为1。每天都执行下判断,到每月一号时,即可实现每月执行一次

    Java监听器代码:

    import java.util.Calendar;
    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    
    
    public class SendWsListener implements ServletContextListener {
    
    	@Override
    	public void contextDestroyed(ServletContextEvent arg0) {
    		System.out.println("定时发送Xml信息监听--已关闭!");
    	}
    
    	@Override
    	public void contextInitialized(ServletContextEvent arg0) {
    		Calendar calendar = Calendar.getInstance();
    		int year = calendar.get(Calendar.YEAR);
    		int month = calendar.get(Calendar.MONTH);
    		int day = calendar.get(Calendar.DAY_OF_MONTH);
    		calendar.set(year, month, day, 8, 00, 00);
    		// 当天8点(默认执行时间)
    		Date defaultdate = calendar.getTime();
    		Date sendDate = null;
    		if (defaultdate.before(new Date())) {
    			// 若当前时间超过了defaultdate时间,当天不再执行,则将执行时间sendDate改为明天8点
    			calendar.add(Calendar.DATE, 1);
    			sendDate = calendar.getTime();
    		}else {
    			// 若当前时间没有超过defaultdate时间,则将执行时间sendDate改为defaultdate
    			sendDate = defaultdate;
    		}
    
    		/**
    		 * ----------------每刻任务 ---------------- 
    		 * 启动服务器后,若此时时间没过8点,等待。到了8点自动执行一次,15分钟后再执行一次,周而复始
    		 * 启动服务器后,若此时时间超过8点,会立刻执行一次,等到15分钟后再次执行一次,周而复始 到了第二天,不会再判断是否是8点,这个开始时间,只会判断一次而已
    		 */
    		Timer qTimer = new Timer();
    		qTimer.schedule(new TimerTask() {
    
    			@Override
    			public void run() {
    				System.out.println("每刻任务已执行");
    				// TODO 写你的逻辑
    			}
    		}, defaultdate, 15 * 60 * 1000);// 定时每15分钟
    		System.out.println("每刻定时发送Xml信息监听--已启动!");
    
    		/**
    		 * ----------------每日任务 ---------------- 
    		 * 启动服务器后,若此时时间没过8点,等待。到了8点自动执行一次,24小时后(第二天8点)再执行一次,周而复始
    		 * 启动服务器后,若此时时间已经超过8点,则等到24小时后(第二天8点)才执行一次,周而复始
    		 */
    		Timer dTimer = new Timer();
    		dTimer.schedule(new TimerTask() {
    
    			@Override
    			public void run() {
    				System.out.println("每日任务已经执行");
    				// TODO 写你的逻辑
    			}
    		}, sendDate, 24 * 60 * 60 * 1000);// 定时24小时:24 * 60 * 60 * 1000
    		System.out.println("每日定时发送Xml信息监听--已启动!");
    
    		/**
    		 * ----------------每月任务 ---------------- 
    		 * 启动服务器后,若此时时间没过8点,等待。到了8点自动执行判断是否是当前月份的1号,若是则执行一次,
    		 * 24小时后(第二天8点)再执行一次判断(每月1号以后后的29天或30天后才会是下月1号,再执行一次),周而复始 启动服务器后,若此时时间已经超过8点,会立刻执行一次,等到下个月1号再次执行一次,周而复始
    		 */
    		Timer mTimer = new Timer();
    		mTimer.schedule(new TimerTask() {
    
    			@Override
    			public void run() {
    				Calendar c = Calendar.getInstance();
    				int day = c.get(Calendar.DAY_OF_MONTH);
    				System.out.println("月任务 判断中");
    				if (day == 1) {
    					// 每天执行,若为每月1号才执行
    					System.out.println("月任务执行已执行");
    					// TODO 写你的逻辑
    				}
    
    			}
    		}, sendDate, 24 * 60 * 60 * 1000);// 每天执行一次检查
    
    		System.out.println("每月定时发送Xml信息监听--已启动!");
    
    	}
    
    }
    

     

     

    2.在web.xml中设置监听文件:

    <web-app>  
      <listener>
        <listener-class>com.today.ems.listener.SendWsListener</listener-class>
      </listener>
    </web-app>

    3.重新部署项目,启动tomcat即可自动执行。

     

    展开全文
  • 用户访问给定的页面,进行 授权登录 ,然后确认自己的身份(本科生或研究生),并 开启通知 ,即可每天在约定时间收到微信服务的消息提醒。当然,用户可以自定义每日通知的时间,也可以随时开启或关闭每日通知。

    1. 项目简介

    灵感来源于学校的 每日健康日报,要求使用微信小程序进行每日健康打卡。所以此项目的功能类似于 QQ群机器人,或者是 每日闹钟

    功能描述:

    用户访问给定的页面,进行 授权登录 ,然后确认自己的身份(本科生或研究生),并 开启通知 ,即可每天在约定时间收到微信服务号的消息提醒。当然,用户可以自定义每日通知的时间,也可以随时开启或关闭每日通知。

    特点对比
    • 和QQ群机器人、闹钟相比,该功能的实现,方便用户一键直达小程序
    • 但是该方法需关注服务号,同时开发门槛较高。

    2. 项目截图

    图:项目演示截图

    左图:用户订阅的页面,中图:用户修改通知时间的页面,右图:用户收到的消息提醒

    3. 开发准备

    3.1 开发工具

    1、编码工具:用于项目开发。如果使用 Java 实现,可使用 IntelliJ IDEA,如果使用 PHP 开发,可使用 JetBrains PhpStorm

    2、运行工具:用于本地测试。如果是PHP开发,可选择phpstudy_pro作为PHP项目的运行部署环境;

    3、测试工具:只能使用 微信开发者工具 ,开发模式选择 公众号网页调试

    微信开发者工具下载地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html

    因为涉及到微信授权登录功能,所以测试工具必须使用 微信开发者工具,而普通浏览器无法实现该功能。

    微信开发者工具的使用和调试方法,与普通浏览器一致

    图:微信开发者工具截图

    3.2 环境支持

    1、任意域名一枚,需要开启SSL(即https访问);

    2、微信服务号一个,必须通过认证;

    3、具有外网IP的服务器一台。

    如果仅仅是学习技术,满足前两项即可。如果项目需要上线,上述三者必不可少。

    对于第2条,没有服务号,也可以在微信开放平台 中创建网站应用。不过微信开放平台同样需要企业认证。

    在上述环境均满足的情况下,接下来需要对微信服务号进行一些配置。主要有:

    1、获取公众号开发信息:

    公众号的开发信息主要是 开发者ID(AppID)开发者密码(AppSecret)

    某些接口必须使用开发者ID和开发者密码才能进行调用请求。

    获取路径是:微信公众号后台 → 开发 → 基本配置

    2、设置IP白名单

    只有IP白名单中的IP地址才可以调用 获取access_token接口,其中access_token是进行其他操作,如发送消息模板等的必备参数。

    图:获取开发信息与设置IP

    图:查看与修改IP白名单

    3、配置域名

    配置域名的页面访问路径是:公众号后台 → 设置 → 公众号设置 → 功能设置

    需要配置的域名主要有三个:

    ① 业务域名

    设置业务域名后,在微信内访问该域名下页面时,不会被重新排版。用户在该域名上进行输入时,不出现安全提示。比如在微信内打开网页,输入表单的时候,不会出现“请勿输入QQ密码”等安全提示。

    ② JS接口安全域名

    设置JS接口安全域名后,公众号开发者可在该域名下调用微信开放的JS接口。比如前文中的access_token的获取,需要使用此域名。

    ③ 网页授权域名

    用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠。

    图:域名配置页面

    4、添加开发者

    只有已添加的开发者,才可以使用web开发者工具进行对应公众号的开发和调试。

    添加开发者页面的访问路径是:微信公众号后台 → 开发 → 开发者工具 → web开发者工具

    图:添加web开发者

    5、添加模板

    添加功能插件模块中,添加模板消息功能

    图:开启模板消息

    然后在模板库中,搜索合适的模板,进行添加:

    图:添加模板消息

    其中模板ID是开发用到的参数。

    4. 官方参考文档解读

    关于微信公众号的开发技术,及其实现路线,在官网中均有介绍。

    官方文档:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html

    打开官方开发文档,第一单元标题为开发前必读,开发者规范和全局返回码说明章节还是有必要读一读的

    开发时,不能违反微信的相关规范,否则容易被封号,然后全局返回码可以帮助快速定位问题所在。

    图:开发者文档

    4.1 网页授权登录解读

    打开官方开发文档,第5章微信网页开发中,第2节即为网页授权的参考文档。

    直达链接:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html

    该章节的目录之前,有一段关于网页授权的一些说明。

    第一点关于网页授权回调域名的说明,即前文中关于域名的配置。

    第二点关于网页授权的两种scope的区别说明,主要说明的是两种授权模式,一种是静默登录授权形式,这种形式只能获取到用户的OpenID,另一种是获取用户基本信息的形式,这种形式会弹窗,需要用户手动点击登录。两种实现方式,主要是修改登录链接中的scope参数的值。后文详述。

    第三点关于网页授权access_token和普通access_token的区别,可参考文档。其实主要说明的是网页授权access_token的安全性更高,必须通过code来获取access_token,而且不同code获取的access_token不同,每一个access_token有效期2小时(7200秒),每个access_token在这2小时内,只对同一个用户有效。而普通access_token(即基础支持中的“获取access_token”接口获取到的)可以直接获取,不同场景可重复使用,有效期也为2小时,过时需要刷新。

    具体而言,网页授权流程分为四步:

    1、引导用户进入授权页面同意授权,获取code

    2、通过code换取网页授权access_token(与基础支持中的access_token不同)

    3、如果需要,开发者可以刷新网页授权access_token,避免过期

    4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)

    1. 获取code

    首先需要使用户访问如下链接,才能进行登录:

    https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

    该链接中,大写字母标识的字段,需要进行替换(下同)。

    上述链接中的各个参数详细说明如下:

    参数是否必须说明
    appid公众号的唯一标识
    redirect_uri授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理
    response_type返回类型,只能填写code
    scope应用授权作用域,只能为snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid)或snsapi_userinfo(弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
    state重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
    #wechat_redirect无论直接打开还是做页面302重定向时候,必须带此参数

    用户确定登录后,即可在授权后重定向的回调链接地址中获取到code,格式如下:

    redirect_uri/?code=CODE&state=STATE

    这里的CODE是接下来换取access_token的必须参数,而STATE是开发者自定义的其他信息。

    2. 换取access_token

    上面获取到了code,接下来需要使用这个code来换取access_token。

    请求链接如下(GET方式):

    https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

    上述链接中的各个参数详细说明如下:

    参数是否必须说明
    appid公众号的唯一标识、开发者ID
    secret公众号的appsecret、开发者密码
    code填写第一步获取的code参数
    grant_type只能为:authorization_code

    请求成功后,返回的JSON数据包格式如下:

    {
      "access_token":"ACCESS_TOKEN",
      "expires_in":7200,
      "refresh_token":"REFRESH_TOKEN",
      "openid":"OPENID",
      "scope":"SCOPE" 
    }
    

    关于该JSON数据包的各个字段说明如下:

    参数说明
    access_token网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
    expires_inaccess_token接口调用凭证超时时间,单位(秒)
    refresh_token用户刷新access_token,有效期30天。当access_token超时后,可以使用refresh_token进行刷新。
    openid用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID
    scope用户授权的作用域,使用逗号(,)分隔

    下面是可选操作,一般用不到:

    使用refresh_token获取access_token的链接如下(GET方式):

    https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN

    上述链接中的各个参数详细说明如下:

    参数是否必须说明
    appid公众号的唯一标识
    grant_type填写为refresh_token
    refresh_token填写通过access_token获取到的refresh_token参数

    正确返回的JSON格式和上面的一致。

    一些说明:

    执行到这里,我们已经拿到了用户的OpenID。如果仅仅做用户唯一性校验,至此结束即可。同样,对于发送模板消息,也只需要用户的OpenID。

    3. 获取用户信息

    如果网页授权作用域为 snsapi_userinfo ,则此时开发者可以通过 access_tokenopenid 拉取用户信息了。

    请求链接如下(GET方式、需使用https协议):

    https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

    上述链接的参数说明如下:

    参数描述
    access_token网页授权接口调用凭证,即前面步骤获取到的access_token。
    openid用户的唯一标识
    lang返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语

    请求成功后返回的JSON数据包格式如下:

    {   
      "openid":" OPENID",
      "nickname": NICKNAME,
      "sex":"1",
      "province":"PROVINCE",
      "city":"CITY",
      "country":"COUNTRY",
      "headimgurl":       "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
      "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
      "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
    }
    

    上述JSON数据包的参数详细说明如下:

    参数描述
    openid用户的唯一标识
    nickname用户昵称
    sex用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
    province用户个人资料填写的省份
    city普通用户个人资料填写的城市
    country国家,如中国为CN
    headimgurl用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。
    privilege用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)
    unionid只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。

    附:检验授权凭证(access_token)是否有效

    请求链接(GET方法、需使用https协议):

    https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID

    上述链接的参数说明如下:

    参数描述
    access_token网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
    openid用户的唯一标识

    正确的JSON数据包返回结果如下:

    { "errcode":0,"errmsg":"ok"}
    

    错误时的JSON返回示例:

    { "errcode":40003,"errmsg":"invalid openid"}
    

    使用该方法可以检测access_token是否过期,以保证项目的高可用性。

    4.2 模板消息推送解读

    打开官方开发文档,第4章 消息管理 中,第7节即为 模板消息接口 的参考文档。

    直达链接:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html

    在官方文档中,该模块很大篇幅所叙述的内容,是针对第三方微信服务商,所以有一些是个人开发者用不到的。

    总结模板消息推送的实际过程,主要流程如下:

    1. 获取基础access_token

    请求链接如下(GET方式):

    https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

    该链接中的参数详细说明如下:

    参数是否必须说明
    grant_type获取access_token填写client_credential
    appid第三方用户唯一凭证
    secret第三方用户唯一凭证密钥,即appsecret

    请求成功返回的JSON数据包格式如下:

    {"access_token":"ACCESS_TOKEN","expires_in":7200}
    

    JSON数据包参数说明:

    参数说明
    access_token获取到的凭证
    expires_in凭证有效时间,单位:秒
    2. 发送消息模板

    请求链接如下(POST方式):

    https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN

    该链接中只需要 access_token 一个参数,即第一步获取到的 access_token

    POST数据包格式为JSON格式,示例如下:

     {
               "touser":"OPENID",
               "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
               "url":"http://weixin.qq.com/download",  
               "miniprogram":{
                 "appid":"xiaochengxuappid12345",
                 "pagepath":"pages/index?foo=bar"
               },          
               "data":{
                       "first": {
                           "value":"恭喜你购买成功!",
                           "color":"#173177"
                       },
                       "keyword1":{
                           "value":"巧克力",
                           "color":"#173177"
                       },
                       "keyword2": {
                           "value":"39.8元",
                           "color":"#173177"
                       },
                       "keyword3": {
                           "value":"2014年9月22日",
                           "color":"#173177"
                       },
                       "remark":{
                           "value":"欢迎再次购买!",
                           "color":"#173177"
                       }
               }
           }
    

    该JSON数据包中的各个参数详细说明如下:

    参数是否必填说明
    touser接收者openid
    template_id模板ID,前文配置中添加模板消息后获得的一串字符
    url模板跳转链接(海外帐号没有跳转能力)
    miniprogram跳小程序所需数据,不需跳小程序可不用传该数据。当链接和小程序都存在时,优先跳转小程序。
    appid所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)
    pagepath所需跳转到小程序的具体页面路径,支持带参数,(示例pages/index?foo=bar),要求该小程序已发布,暂不支持小游戏
    data模板数据。这里的数据个数,需要与消息模板中的参数对应,如下图所示。
    color模板内容字体颜色,不填默认为黑色

    data字段个数示例

    请求成功后,返回的JSON数据包格式如下所示:

     {
        "errcode":0,
         "errmsg":"ok",
         "msgid":200228332
      }
    

    这时,用户手机微信将收到对应的提醒。

    行文至此,讲述的主要是授权登录和发送模板消息的大致实现流程。

    下文,将结合实际的项目,对上述功能的实现流程,以项目源码的形式,进行更加详细的演示。

    5. 网页授权登录实现(PHP为例)

    5.1 工具类GetWX

    该工具类的主要功能是根据微信授权登录的实现流程,进行链接请求和数据获取。

    <?php
    
    /**
     * 获取微信用户信息
     */
    class GetWX
    {
        //公众号的开发信息
        private $appid = 'APPID';
        private $appsecret = 'APPSECERT';
    
        /**
         * 功能:用户授权并获取code
         *
         * @param $callback
         */
        public function get_code($callback)
        {
            $appid = $this->appid;
            $scope = 'snsapi_userinfo';
            $state = md5(uniqid(rand(), TRUE));//唯一ID标识符绝对不会重复
            $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' . $appid . '&redirect_uri=' . urlencode($callback) . '&response_type=code&scope=' . $scope . '&state=' . $state . '#wechat_redirect';
            header("Location:$url");
        }
    
        /**
         * 功能:通过code获取access_token
         *
         * @param $code
         * @return mixed
         */
        public function get_access_token($code)
        {
            $appid = $this->appid;
            $appsecret = $this->appsecret;
            $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' . $appid . '&secret=' . $appsecret . '&code=' . $code . '&grant_type=authorization_code';
            $data = json_decode(file_get_contents($url));//返回的json数组转换成array数组
            return $data;
        }
    
        /**
         * 功能:使用access_token获取用户信息
         *
         * @param $access_token
         * @param $openid
         * @return mixed
         */
        public function get_user_info($access_token, $openid)
        {
            $url = 'https://api.weixin.qq.com/sns/userinfo?access_token=' . $access_token . '&openid=' . $openid . '&lang=zh_CN';
            $data = json_decode(file_get_contents($url));//返回的json数组转换成array数组
            return $data;
        }
    }
    
    ?>
    

    关于该工具类的具体实现方法不唯一。后文中有第二种完整的示例。

    5.2 获取code

    登录回调页面(login.php)的源码如下:

    <?php
    include 'getWX.php';
    
    $getWX = new GetWX();
    
    //如果没有GET到code,则执行登录操作
    if (!isset($_GET['code'])) {
        //微信服务器回调url,这里是本页url
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $callback = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
        //获取code
        $getWX->get_code($callback);
    } //GET到了code,可以执行后续操作
    else {
    
    }
    

    如上源码所示,这时访问https://XXXX.cn/test/login.php,执行到if语句处,判断链接中没有code这个参数,所以获取了当前链接的URL地址(callback参数),并将其交给get_code方法,该方法将构造登录链接,并前往该页面,然后弹出登录授权页面,如下所示:

    登录授权页面

    用户点击 同意 之后,返回到$callback链接。这时,链接中携带code参数,以及开发者自定义的state参数。如下图所示:

    image-20200905222754206

    我们暂时用到的是code参数。

    5.3 换取access_token

    接下来,使用该 code换取access_token,继续完善login.php代码,具体如下:

    <?php
    include 'getWX.php';
    
    $getWX = new GetWX();
    
    //如果没有GET到code,则执行登录操作
    if (!isset($_GET['code'])) {
        //微信服务器回调url,这里是本页url
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $callback = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
        //获取code
        $getWX->get_code($callback);
    } //GET到了code,可以执行后续操作
    else {
        //从链接中拿code
        $code = $_GET['code'];
        //获取网页授权access_token和用户openid
        $data = $getWX->get_access_token($code);
        //测试输出
        echo 'access_token:' . $data->access_token . '<br>';
        echo 'expires_in:' . $data->expires_in . '<br>';
        echo 'refresh_token:' . $data->refresh_token . '<br>';
        echo 'openid:' . $data->openid . '<br>';
        echo 'scope:' . $data->scope . '<br>';
    }
    
    

    输出的信息如下:

    获取access_token输出结果

    5.4 获取用户信息

    上面拿到了 access_tokenOpenID,接下来可以使用这两个参数来获取用户的基本信息。

    继续完善login.php,如下:

    <?php
    include 'getWX.php';
    
    $getWX = new GetWX();
    
    //如果没有GET到code,则执行登录操作
    if (!isset($_GET['code'])) {
        //微信服务器回调url,这里是本页url
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $callback = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
        //获取code
        $getWX->get_code($callback);
    } //GET到了code,可以执行后续操作
    else {
        //从链接中拿code
        $code = $_GET['code'];
        //获取网页授权access_token和用户openid
        $data = $getWX->get_access_token($code);
        //获取微信用户信息
        $userInfo = $getWX->get_user_info($data->access_token, $data->openid);
        //测试输出:
        echo 'openid:' . $userInfo->openid . '<br>';
        echo 'nickname:' . $userInfo->nickname . '<br>';
        echo 'sex:' . $userInfo->sex . '<br>';
        echo 'province:' . $userInfo->province . '<br>';
        echo 'city:' . $userInfo->city . '<br>';
        echo 'country:' . $userInfo->country . '<br>';
        echo 'headimgurl:' . $userInfo->headimgurl . '<br>';
    }
    
    

    执行结果如下图所示:

    获取用户信息结果图

    这时,我们就拿到了用户的基本信息,之后就可以将这些基本信息保存到数据库,用户ID等可以保存到session或者cookie中,等等,进行各种其他操作。

    5.5 另一种完整的示例

    工具类:

    <?php
    
    /**
     * 获取微信用户信息
     * 本源码来自互联网
     */
    class GetWxUser
    {
        private $appid = '';
        private $appsecret = '';
    
        /**
         * 1、获取微信用户信息,判断有没有code,有使用code换取access_token,没有去获取code。
         * @return array 微信用户信息数组
         */
        public function get_user_all()
        {
            if (!isset($_GET['code'])) {//没有code,去微信接口获取code码
                //微信服务器回调url,这里是本页url
                $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
                $callback = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
                //获取code
                $this->get_code($callback);
            } else {//获取code后跳转回来到这里了
                $code = $_GET['code'];
                $data = $this->get_access_token($code);//获取网页授权access_token和用户openid
                $userInfo = $this->get_user_info($data['access_token'], $data['openid']);//获取微信用户信息
                return $userInfo;
            }
        }
    
        /**
         * 2、用户授权并获取code
         * @param string $callback 微信服务器回调链接url
         */
        public function get_code($callback)
        {
            $appid = $this->appid;
            $scope = 'snsapi_userinfo';
            $state = md5(uniqid(rand(), TRUE));//唯一ID标识符绝对不会重复
            $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' . $appid . '&redirect_uri=' . urlencode($callback) . '&response_type=code&scope=' . $scope . '&state=' . $state . '#wechat_redirect';
            header("Location:$url");
        }
    
        /**
         * 3、使用code换取access_token
         * @param string 用于换取access_token的code,微信提供
         * @return array access_token和用户openid数组
         */
        public function get_access_token($code)
        {
            $appid = $this->appid;
            $appsecret = $this->appsecret;
            $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' . $appid . '&secret=' . $appsecret . '&code=' . $code . '&grant_type=authorization_code';
            $user = json_decode(file_get_contents($url));
            if (isset($user->errcode)) {
                echo 'error:' . $user->errcode . '<hr>msg  :' . $user->errmsg;
                exit;
            }
            $data = json_decode(json_encode($user), true);//返回的json数组转换成array数组
            return $data;
        }
    
        /**
         * 4、使用access_token获取用户信息
         * @param string access_token
         * @param string 用户的openid
         * @return array 用户信息数组
         */
        public function get_user_info($access_token, $openid)
        {
            $url = 'https://api.weixin.qq.com/sns/userinfo?access_token=' . $access_token . '&openid=' . $openid . '&lang=zh_CN';
            $user = json_decode(file_get_contents($url));
            if (isset($user->errcode)) {
                echo 'error:' . $user->errcode . '<hr>msg  :' . $user->errmsg;
                exit;
            }
            $data = json_decode(json_encode($user), true);//返回的json数组转换成array数组
            return $data;
        }
    }
    
    ?>
    

    登录页login.php:

    <?php
    header("Content-Type: text/html;charset=utf-8");
    //设置时区
    date_default_timezone_set('Asia/Chongqing');
    
    include 'getWXUser.php';
    
    $getWxUser = new GetWxUser();
    
    //主页的URL
    $homeUrl = 'https://mzxy.cxhit.cn';
    
    //没有code,去微信接口获取code码
    if (!isset($_GET['code'])) {
        //微信服务器回调url,这里是本页url
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $callback = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
    
        $getWxUser->get_code($callback);
    } //获取code后跳转回来到这里了
    else {
        $code = $_GET['code'];
        //获取网页授权access_token和用户openid
        $data = $getWxUser->get_access_token($code);
        //获取微信用户信息(数组)
        $userInfo = $getWxUser->get_user_info($data['access_token'], $data['openid']);
    
        //保存到数据库
        if ($userInfo['openid'] != '') {
            saveUserInfo($userInfo['openid'], $userInfo['nickname'], $userInfo['sex'], $userInfo['province'], $userInfo['city'], $userInfo['country'], $userInfo['headimgurl']);
        }
    
        //保存到cookie里
        setcookie('openid', $userInfo['openid'], time() + 3600 * 24 * 30, "/");
    
        //返回主页
        header("Location:" . $homeUrl);
    }
    ?>
    

    6. 模板消息推送实现(PHP为例)

    6.1 官方JSSDK文档(PHP)

    该SDK实现了各种参数的获取,源码如下:

    <?php
    
    class JSSDK
    {
        private $appId;
        private $appSecret;
    
        public function __construct($appId, $appSecret)
        {
            $this->appId = $appId;
            $this->appSecret = $appSecret;
        }
    
        public function getSignPackage()
        {
            $jsapiTicket = $this->getJsApiTicket();
    
            // 注意 URL 一定要动态获取,不能 hardcode.
            $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
            $url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
    
            $timestamp = time();
            $nonceStr = $this->createNonceStr();
    
            // 这里参数的顺序要按照 key 值 ASCII 码升序排序
            $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";
    
            $signature = sha1($string);
    
            $signPackage = array(
                "appId" => $this->appId,
                "nonceStr" => $nonceStr,
                "timestamp" => $timestamp,
                "url" => $url,
                "signature" => $signature,
                "rawString" => $string
            );
            return $signPackage;
        }
    
        private function createNonceStr($length = 16)
        {
            $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            $str = "";
            for ($i = 0; $i < $length; $i++) {
                $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
            }
            return $str;
        }
    
        private function getJsApiTicket()
        {
            // jsapi_ticket 应该全局存储与更新,以下代码以写入到文件中做示例
            $data = json_decode($this->get_php_file("jsapi_ticket.php"));
            if ($data->expire_time < time()) {
                $accessToken = $this->getAccessToken();
                // 如果是企业号用以下 URL 获取 ticket
                // $url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=$accessToken";
                $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken";
                $res = json_decode($this->httpGet($url));
                $ticket = $res->ticket;
                if ($ticket) {
                    $data->expire_time = time() + 7000;
                    $data->jsapi_ticket = $ticket;
                    $this->set_php_file("jsapi_ticket.php", json_encode($data));
                }
            } else {
                $ticket = $data->jsapi_ticket;
            }
    
            return $ticket;
        }
    
        public function getAccessToken()
        {
            // access_token 应该全局存储与更新,以下代码以写入到文件中做示例
            $data = json_decode($this->get_php_file("access_token.php"));
            if ($data->expire_time < time()) {
                // 如果是企业号用以下URL获取access_token
                // $url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=$this->appId&corpsecret=$this->appSecret";
                $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->appId&secret=$this->appSecret";
                $res = json_decode($this->httpGet($url));
                $access_token = $res->access_token;
                if ($access_token) {
                    $data->expire_time = time() + 7000;
                    $data->access_token = $access_token;
                    $this->set_php_file("access_token.php", json_encode($data));
                }
            } else {
                $access_token = $data->access_token;
            }
            return $access_token;
        }
    
        private function httpGet($url)
        {
            $curl = curl_init();
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($curl, CURLOPT_TIMEOUT, 500);
            // 为保证第三方服务器与微信服务器之间数据传输的安全性,所有微信接口采用https方式调用,必须使用下面2行代码打开ssl安全校验。
            // 如果在部署过程中代码在此处验证失败,请到 http://curl.haxx.se/ca/cacert.pem 下载新的证书判别文件。
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, true);
            curl_setopt($curl, CURLOPT_URL, $url);
    
            $res = curl_exec($curl);
            curl_close($curl);
    
            return $res;
        }
    
        private function get_php_file($filename)
        {
            return trim(substr(file_get_contents($filename), 15));
        }
    
        private function set_php_file($filename, $content)
        {
            $fp = fopen($filename, "w");
            fwrite($fp, "<?php exit();?>" . $content);
            fclose($fp);
        }
    }
    

    6.2 模板消息JSON化

    这里以下面这个模板消息为实例,进行演示。模板 详细内容 处的内容需要牢记,因为在构造JSON时,要用这些信息。

    image-20200905225255928

    然后定义一个函数,主要功能是实现发送数据的JSON格式化

    函数源码:

    //模板消息JSON格式化
    function json_tempalte($openid, $appid, $nickname, $time)
    {
        //模板消息
        $template = array(
            'touser' => $openid,  //用户openid
            'template_id' => 'XXXX', //在公众号下配置的模板id
            'url' => 'https://XXXX.cn', //点击模板消息会跳转的链接
            'miniprogram' => array(
                'appid' => $appid,
                'pagepath' => 'pages/main/login/login?foo=bar'
            ),
            'data' => array(
                'first' => array('value' => '完成今日健康日报了吗?未完成,请尽快哦~', 'color' => "#FF0000"),
                'keyword1' => array('value' => $nickname, 'color' => '#173177'),  //keyword需要与配置的模板消息对应
                'keyword2' => array('value' => $time, 'color' => '#173177'),
                'remark' => array('value' => '点击此处前往填报。本通知由您订阅产生,切换身份、更改通知时间及退订管理,请访问菜单栏『学习助手』模块。', 'color' => '#FF0000'),
            )
        );
        $json_template = json_encode($template);
        return $json_template;
    }
    

    这个函数返回的结果是JSON数据包。

    6.3 发送模板消息

    关键源码如下:

    include("jssdk.php");
    $jssdk = new JSSDK("APPID", "APPSECRET");
    //定义消息模板JSON
    $json_template = json_tempalte('APPID', 'wxa0738e54aae84423', '拾年之璐' . '(本科生)', date("Y-m-d"));
    //获取AccessToken
    $accessToken = $jssdk->getAccessToken();
    //POST 的链接
    $url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" . $accessToken;
    //提交
    list($returnCode, $returnContent) = http_post_json($url, $json_template);
    //打印日志(JSON格式)
    echo '发送结果如下:' . $returnContent . '\n';
    

    其中,上面代码中的 http_post_json 函数如下:

    //发送POST请求
    function http_post_json($url, $jsonStr)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonStr);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json; charset=utf-8',
                'Content-Length: ' . strlen($jsonStr)
            )
        );
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
    
        return array($httpCode, $response);
    }
    

    该函数是自己封装的进行POST请求。当然各种

    执行完成后,用户即可收到消息模板。

    7. 后记与源码

    行文至此,关于微信授权登录和微信模板消息推送的主要使用方法讲解完毕。

    前文中虽然有提到过每日提醒的模板消息推送功能,但这里将不再论述。

    源码下载:https://mianbaoduo.com/o/bread/YZuVk5dp

    以上。

    展开全文
  • 3 如果用户设置提醒,则永不提醒 实现: 1 关于本地通知移步这里 2 实现代码 static NSNotificationName const _Nullable kCheckinNotification = @"CheckinNotification"; static NSSt...

    需求:
    1 每周周一到周五的九点半,通过本地通知提醒签到;
    2 如果用户当天九点半之前已经签到,不再提醒;
    3 如果用户设置不提醒,则永不提醒

    实现:

    1 关于本地通知移步这里
    2 实现代码

    static NSNotificationName const _Nullable kCheckinNotification = @"CheckinNotification";
    static NSString *const _Nullable kCheckinNotificationSwitchKey  = @"kCheckinNotificationSwitchKey";
    
    /**  设置签到通知*/
    - (void)resetCheckinNotifications{
    
        // 用户首次使用 默认开启通知(相当于NSUserDefaults)
        if ([NSUtil getValueForKey:kCheckinNotificationSwitchKey] == nil) {
            [NSUtil saveValue:@(YES) forKey:kCheckinNotificationSwitchKey];
        }
    
        // 如果用户关闭通知,直接返回
        if([[NSUtil getValueForKey:kCheckinNotificationSwitchKey] isEqual: @(NO)]) return;
    
        // 取消以前的通知
        [self cancelAllNotification];
    
        // weekday 1:周日  2:周一, 3:周二 4:周三 5:周四 6:周五 7:周六
        NSCalendar *calendar = [NSCalendar currentCalendar];
        NSDateComponents *dateComps = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitWeekday fromDate:[NSDate date]];
        NSDate *zeroDate = [calendar dateFromComponents:dateComps] ;
        NSDate *notifyDate = [zeroDate dateByAddingTimeInterval:9.5*60*60]; // 每天9:30分通知
    
        // 未来一周 创建5个通知:星期一到星期五 (过滤掉周末 并按周重复)
        for (int i=0;i<7;i++) {
    
            // 从明天开始
            NSInteger notifyWeekday = [dateComps weekday]+i+1;
    
            // 如果是明天到下周了,矫正weekday
            if(notifyWeekday >= 8){
                notifyWeekday = notifyWeekday-7;
            }
            // 明天是周六或周日 过滤掉
            if(notifyWeekday == 7 || notifyWeekday == 1) continue;
    
            // 计算未来通知的时间
            NSDate *date= [notifyDate dateByAddingTimeInterval:(i+1)*60*60*24];
            // 只是一个时间的格式化,将日期作为通知的identifier
            NSString *identifier = [NSUtil stringWithDate:date format:@"yyyy-MM-dd"];
    
            if (@available(iOS 10.0, *)) {
                // weekday 2:周一, 3:周二 4:周三 5:周四 6:周五
                UNNotificationRequest *request = [self createNotificationRequestWithWeekday:notifyWeekday identifier:identifier];
                // 把通知加到UNUserNotificationCenter, 到指定触发点会被触发
                [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil];
            }
            else{
                UILocalNotification *notification = [self createLocalNotificationWithDate:date identifier:identifier];
                [kApplication scheduleLocalNotification:notification];
            }
        }
    
    }
    
    /**  取消一个特定的通知*/
    - (void)cancelNotificationWithIdentifier:(NSString *)identifier{
        if (@available(iOS 10.0, *)) {
            [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[identifier]];
        }
        else{
    
            // 获取当前所有的本地通知
            NSArray *notificaitons = [[UIApplication sharedApplication] scheduledLocalNotifications];
            if (!notificaitons || notificaitons.count <= 0) { return; }
            for (UILocalNotification *notify in notificaitons) {
                if ([[notify.userInfo objectForKey:@"identifier"] isEqualToString:identifier]) {
                    [[UIApplication sharedApplication] cancelLocalNotification:notify];
                    break;
                }
            }
        }
    
    }
    - (void)cancelAllNotification{
        if (@available(iOS 10.0, *)) {
            [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
            [[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
        }
        else{
             [[UIApplication sharedApplication] cancelAllLocalNotifications];
        }
    }
    
    /**  取消已经推过的通知*/
    - (void)removeAllDeliveredNotifications __IOS_AVAILABLE(10.0){
        if (@available(iOS 10.0, *)) {
            [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
        }
    }
    
    
    
    #pragma mark   -  私有方法
    
    /**  设置指定时间通知,每周重复*/
    - (UILocalNotification *)createLocalNotificationWithDate:(NSDate *)date identifier:(NSString *)identifier{
    
        UILocalNotification *localNotification = [[UILocalNotification alloc] init];
    
        // 1.设置触发时间(如果要立即触发,无需设置)
        localNotification.timeZone = [NSTimeZone defaultTimeZone];
        localNotification.fireDate = date;
        localNotification.repeatInterval = NSCalendarUnitWeekday;
    
        // 2.设置通知标题
        localNotification.alertBody = self.title.length ? self.title:@"签到通知";
        localNotification.alertAction = self.content.length ? self.content:@"同学,今天签到没?千万不要忘了哦!";
        // localNotification.applicationIconBadgeNumber = ++kApplication.applicationIconBadgeNumber;
    
        // 3.设置通知的 传递的userInfo
        localNotification.userInfo = @{@"kLocalNotificationID":kCheckinNotification,@"identifier":identifier};
    
        return localNotification;
    }
    
    
    /**  每个星期的星期几 9点30通知*/
    - (UNNotificationRequest *)createNotificationRequestWithWeekday:(NSInteger)weekday identifier:(NSString *)identifier {
    
        // 1.创建通知内容
        UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
        content.sound = [UNNotificationSound defaultSound];
        content.title = self.title.length ? self.title:@"签到提醒";
        content.body = self.content.length ? self.content:@"同学,今天签到没?千万不要忘了哦!";
        //content.badge = @(++kApplication.applicationIconBadgeNumber); // 不显示角标
        content.userInfo = @{@"kLocalNotificationID":kCheckinNotification,@"identifier":identifier};
    
        // 2.触发模式 9点30 每周重复 ()
        NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
        dateComponents.hour = 9;  // 九点
        dateComponents.minute = 30;
        dateComponents.weekday = weekday;
        UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:dateComponents repeats:YES];
    
        // 4.设置UNNotificationRequest
        UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
    
        return request;
    }
    @end

    3 使用
    首次使用调用一次resetCheckinNotifications 即可
    在签到成功的方法里判断如果签到的时间在今天九点半以前,调用一次resetCheckinNotifications(目的取消今天的,设置以后的通知)
    注意 不能直接拿当天的日期作为identifier去取消通知(通知中心里保留的identifier是设置通知时的那一周的日期)

    4 需要再AppDelegate里面,注册通知,并处理接收通知的回调

    5 不足:未过滤调节假日,所以节假日还会提醒

    如果有什么不对的地方,或者有什么更好的实现方法,欢迎评论区留言指正

    展开全文
  • iphone6提醒事项加入日历怎么不提醒我 到时234游戏网友 提出于 2019-07-14 21:02:21请问:iPhone6s plus如何添加提醒事项1、苹果手机提醒事项不提醒,可以查看是不是没有开启提醒事项的通知设置,声音是不是静音,...
  • iOS本地通知的简单封装(定时提醒、重复提醒)iOS10及以上注册通知创建通知添加通知重复提醒取消通知iOS10以下创建通知重复提醒取消通知 实现快捷创建简单的定时提醒推送功能。 iOS10及以上 注册通知 iOS10及...
  • 尊敬的天然气用户您好,我司目前按照苏州市物价文件(苏价工字〔2013〕79)对经营区域内居民生活用管道天然气实行阶梯式(二级)气价政策:1.居民用户每户年用气量600立方米以内(含600立方米)为第一档,销售价格为...
  • C#获取下一个1号的日期

    千次阅读 2014-03-31 10:38:57
    在系统中用到了获取下一1号,开始写的代码如下: DateTime.Now.AddMonths(1).AddDays(-DateTime.Now.Day+1).AddHours(-DateTime.Now.Hour+1).AddMinutes(-DateTime.Now.Minute).AddSeconds(-DateTime.Now....
  • windows系统定时任务设置

    千次阅读 2019-05-09 13:33:40
    1。我的电脑-----右键‘管理’-----任务计划程序 2。创建任务.如下图: 注意:起始于那块必须填,且必须如图那样的格式(到根目录即可) 条件,什么都不选即可 设置也是什么都不选 最后弹出...
  • 第4列月1~12 第5列星期0~6(0表示星期天) 第6列要运行的命令 文件中是通过 5 个“*”来确定命令或任务的执行时间的,这 5 个“*”的具体含义如表 2 所示。 在时间表示中,还有一些特殊符号需要学习,如表 3 所...
  • 920NetSuite培训杂记

    千次阅读 2018-11-18 09:34:28
    参与培训有许多partner,大概看了下,有Partner: SUNOAN/Douples/TCIC/SZ INTech/文思海辉/盈诺德/佳邦等。...11/19,北京,7000$,售前培训*3人。 数据无法wipe out,delete后可以restore,无法delete for good.
  • 企业员工生日提醒短信怎么发送

    千次阅读 2019-11-07 08:22:01
    老板、销售、HR都会遇到在客户、公司员工生日的时候,送个祝福,送个小礼物等增进感情的事情,但是人数多了以后,光靠纸和脑子、表格,是不容易记录下来的,最好的办法是能够设置生日提醒,这样就可以防患于未然,...
  • 按日提醒:每几日提醒一次,可以排除指定的每月提醒。 按周提醒:每几周提醒一次,可以指定的每周星期几提醒,可多选。 按月提醒:方式一, 指定月(可多选)的第几天(可多选)提醒;方式二,指定月(可多选...
  • 首先说一下,iOS和macOS里面的备忘录、提醒事项、日历是我每天都会用到的三个苹果原生app。接下来我会分点概述这三个app的应用,并且会举出平时使用的实例。 前提条件 如果你有两台以上苹果设备,这三个app合作...
  • “页导航”是PowerBI在2020年5的更新中一个非常关键的功能。我也写过一篇文章,如何在书签和页导航中进行选择:PowerBI中的书签和导航页,如何选择呢?而通过页导航的自定义参数链接可以实现给最终用户提供个性化...
  • Cron表达式 Quartz使用类似于Linux下的Cron表达式定义时间规则,Cron表达式由6或7个由空格分隔的时间字段组成。 Cron表达式时间字段(从...1 秒 0-59 , - * / 2 分钟 0-59 , - * / 3 小时 0-23
  • 参考地址: https://blog.csdn.net/DamonREN/article/details/83339636 ... 案例一: 多种消息提醒系统的设计模式、实现方案(附功能截图+表结构) 2018-09-25 08:28:59黑夜的风阅读数 982...
  • 时间飞快,转眼就到了年底,2021年就这么过去了,今年也是正式工作的第一年,记得好像是2020年713进的公司,进入社会,97转正,正式成为一名公司员工,到现在2021年1226大概有一年半的时间了。...
  • 向系统日历添加日程提醒的规则

    千次阅读 2017-09-25 15:51:49
    个18个1号至15号每天发生一次,共发生10次: RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15 2个的所有周二每天发生一次: RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU ...
  • 牛逼!Java 从入门到精通,超全汇总版

    万次阅读 多人点赞 2021-05-06 19:40:33
    初学 C 语言周期大概是3 - 6 个,学编程的捷径就是每天敲代码,比如 C primer plus 上面就有很多代码示例,你要对着敲,课后练习要跟着做,坚持 3 - 6 个,你会感谢你自己的坚持。 学到这里,你就可以说 C 语言...
  • 一、提高浏览量的技巧 相信很多人都这么想过:“我文章写的这么好,怎么就没人看呢?”; 或者这样想过:“这文章写得明明比我烂很多,凭什么这么多浏览量?...1-1、一个博眼球的标题 俗话说得...
  • 到了新公司之后,发现居然也是用企业微信。但可惜的是,外部的企业微信居然没有机器人。这对以前在鹅厂里习惯用企业微信做提醒的我觉得很不方便。终于,7开始企业微信终于上线机器...
  • python里百分

    千次阅读 2020-11-23 13:15:38
    广告关闭腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器... 第一种格式化方式,和 c 语言一样,python 也使用百分 % 来格式化代码。 print(im %s, my age is %d %(keinye, 29))上面的代码将输出 im keiny...
  • 万字干货:教新手从0到1搭建完整的增长数据体系

    万次阅读 多人点赞 2020-07-14 01:00:57
    第五步:了解用户的基本活跃度 发现: 超过80%的活跃用户每月登录少于4次 但每月登录4次以上的用户,有接近80%的用户为18-25岁的大学生用户群体 总结: 新用户下载到活跃的转化率尚可: 老用户活跃度低: 人均阅读...
  • SQLServer:定时作业的设置方法

    千次阅读 2015-11-18 13:04:21
    SQLServer:定时作业的设置方法 如果在SQL Server 里需要定时或者隔一段时间执行某个存储过程或3200字符以内的SQL语句时,可以用管理-SQL Server代理-作业来实现   1、管理-SQL Server代理-作业(按...
  • 前几天,有个非计算机专业的同学问我,如何快速找出1亿之内的孪生素数——所谓孪生素数,就是差值为2的两个素数。原本以为这是一个很简单的问题,随便用python写了一个方法,没想到却要跑17分钟左右。改用C++试试,...
  • 引言 ...每个帐号每月共10次清零操作机会,清零生效一次即用掉一次机会(10次包括了平台上的清零和调用接口API的清零)。 公众号后台-开发-接口权限,可以看到每个接口的调用量 1.2 申请临时提额
  • android安卓源码海量项目合集打包-1

    万次阅读 多人点赞 2019-06-11 16:16:24
    │ │ EditText输入电话号码、银行卡自动添加空格分割.zip │ │ editText限制输入的4种方法.rar │ │ FloatingLabel会移动提示的edittext.rar │ │ material-code-input material风格的代码输入框.rar │ ...
  • 延迟消息的五种实现方案

    万次阅读 多人点赞 2021-01-12 12:09:06
    设置消息延迟级别大于等于1并且小于等于18时,消息延迟特定时间,如:设置消息延迟级别等于1,则延迟1s;设置消息延迟级别等于2,则延迟5s,以此类推。 设置消息延迟级别大于18时,则该消息延迟级别为18,如:设置...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 43,139
精华内容 17,255
关键字:

如何设置每月1号提醒