-
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 为定时任务中每次执行的时间
此代码也可以找到每月的最后一天,也就是每月最后一天执行,包含特殊月份的处理
更多相关内容 -
Java定时器(实现每月1号、每日、每15分钟自动执行任务)
2017-03-20 14:48:39在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即可自动执行。
-
微信公众号开发之使用服务号的模板消息实现每日提醒通知功能
2021-02-18 21:47:44用户访问给定的页面,进行 授权登录 ,然后确认自己的身份(本科生或研究生),并 开启通知 ,即可每天在约定时间收到微信服务号的消息提醒。当然,用户可以自定义每日通知的时间,也可以随时开启或关闭每日通知。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是进行其他操作,如发送消息模板等的必备参数。3、配置域名
配置域名的页面访问路径是:
公众号后台 → 设置 → 公众号设置 → 功能设置
。需要配置的域名主要有三个:
① 业务域名
设置业务域名后,在微信内访问该域名下页面时,不会被重新排版。用户在该域名上进行输入时,不出现安全提示。比如在微信内打开网页,输入表单的时候,不会出现“请勿输入QQ密码”等安全提示。
② JS接口安全域名
设置JS接口安全域名后,公众号开发者可在该域名下调用微信开放的JS接口。比如前文中的access_token的获取,需要使用此域名。
③ 网页授权域名
用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠。
4、添加开发者
只有已添加的开发者,才可以使用
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_in access_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_token
和openid
拉取用户信息了。请求链接如下(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 否 模板内容字体颜色,不填默认为黑色 请求成功后,返回的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参数。如下图所示:我们暂时用到的是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>'; }
输出的信息如下:
5.4 获取用户信息
上面拿到了
access_token
和OpenID
,接下来可以使用这两个参数来获取用户的基本信息。继续完善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×tamp=$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时,要用这些信息。然后定义一个函数,主要功能是实现发送数据的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
以上。
-
iOS设置工作日 本地通知提醒(周一到周五重复通知)
2018-12-27 16:27:383 如果用户设置不提醒,则永不提醒 实现: 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提醒事项加入日历怎么不提醒我 到时
2020-12-29 11:26:10iphone6提醒事项加入日历怎么不提醒我 到时234游戏网友 提出于 2019-07-14 21:02:21请问:iPhone6s plus如何添加提醒事项1、苹果手机提醒事项不提醒,可以查看是不是没有开启提醒事项的通知设置,声音是不是静音,... -
iOS本地推送通知的简单封装(定时提醒、重复提醒)
2018-12-30 23:25:23iOS本地通知的简单封装(定时提醒、重复提醒)iOS10及以上注册通知创建通知添加通知重复提醒取消通知iOS10以下创建通知重复提醒取消通知 实现快捷创建简单的定时提醒推送功能。 iOS10及以上 注册通知 iOS10及... -
天然气阶梯不是明年1月1号开始么?怎么现在充气就限量了
2020-12-23 14:12:46尊敬的天然气用户您好,我司目前按照苏州市物价文件(苏价工字〔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:401。我的电脑-----右键‘管理’-----任务计划程序 2。创建任务.如下图: 注意:起始于那块必须填,且必须如图那样的格式(到根目录即可) 条件,什么都不选即可 设置也是什么都不选 最后弹出... -
Crontab 每隔整点1小时2小时执行一次任务
2020-07-01 12:00:08第4列月1~12 第5列星期0~6(0表示星期天) 第6列要运行的命令 文件中是通过 5 个“*”来确定命令或任务的执行时间的,这 5 个“*”的具体含义如表 2 所示。 在时间表示中,还有一些特殊符号需要学习,如表 3 所... -
9月20号NetSuite培训杂记
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都会遇到在客户、公司员工生日的时候,送个祝福,送个小礼物等增进感情的事情,但是人数多了以后,光靠纸和脑子、表格,是不容易记录下来的,最好的办法是能够设置生日提醒,这样就可以防患于未然,... -
超级桌面提醒助手1.5(原创)
2011-03-17 09:36:04按日提醒:每几日提醒一次,可以排除指定的每月几号不提醒。 按周提醒:每几周提醒一次,可以指定的每周星期几提醒,可多选。 按月提醒:方式一, 指定月(可多选)的第几天(可多选)提醒;方式二,指定月(可多选... -
如何高效的使用苹果产品的备忘录、提醒事项、日历?
2020-11-04 16:06:36首先说一下,iOS和macOS里面的备忘录、提醒事项、日历是我每天都会用到的三个苹果原生app。接下来我会分点概述这三个app的应用,并且会举出平时使用的实例。 前提条件 如果你有两台以上苹果设备,这三个app合作... -
御用导航提示提醒页面_PowerBI 个性化定制你的报告导航
2020-12-04 15:32:37“页导航”是PowerBI在2020年5月的更新中一个非常关键的功能。我也写过一篇文章,如何在书签和页导航中进行选择:PowerBI中的书签和导航页,如何选择呢?而通过页导航的自定义参数链接可以实现给最终用户提供个性化... -
Quartz 定时任务设置某个时间区间每隔一定时间触发的cron表达式
2017-06-21 11:41:15Cron表达式 Quartz使用类似于Linux下的Cron表达式定义时间规则,Cron表达式由6或7个由空格分隔的时间字段组成。 Cron表达式时间字段(从...1 秒 0-59 , - * / 2 分钟 0-59 , - * / 3 小时 0-23 -
消息提醒系统:设计模式与实现方案 (公告(通告)、消息、提醒等基本功能数据库表设计与实现)
2019-10-31 17:51:52参考地址: https://blog.csdn.net/DamonREN/article/details/83339636 ... 案例一: 多种消息提醒系统的设计模式、实现方案(附功能截图+表结构) 2018-09-25 08:28:59黑夜的风阅读数 982... -
2021年总结(2021年1月1日至2021年12月26日)
2021-12-26 20:25:26时间飞快,转眼就到了年底,2021年就这么过去了,今年也是正式工作的第一年,记得好像是2020年7月13号进的公司,进入社会,9月7号转正,正式成为一名公司员工,到现在2021年12月26号大概有一年半的时间了。... -
向系统日历添加日程提醒的规则
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 语言... -
听说你还在纠结自己没访问量?成不了“博客专家”?
2019-12-18 16:29:19一、提高浏览量的技巧 相信很多人都这么想过:“我文章写的这么好,怎么就没人看呢?”; 或者这样想过:“这文章写得明明比我烂很多,凭什么这么多浏览量?...1-1、一个博眼球的标题 俗话说得... -
不花钱就可以给企业微信做个提醒机器人
2019-07-22 18:40:00到了新公司之后,发现居然也是用企业微信。但可惜的是,外部的企业微信居然没有机器人。这对以前在鹅厂里习惯用企业微信做提醒的我觉得很不方便。终于,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:21SQLServer:定时作业的设置方法 如果在SQL Server 里需要定时或者每隔一段时间执行某个存储过程或3200字符以内的SQL语句时,可以用管理-SQL Server代理-作业来实现 1、管理-SQL Server代理-作业(按... -
C/C++/Java/Go/Rust,Python喊你来打擂:3秒钟内统计出小于1亿的素数个数
2019-11-04 10:24:20前几天,有个非计算机专业的同学问我,如何快速找出1亿之内的孪生素数——所谓孪生素数,就是差值为2的两个素数。原本以为这是一个很简单的问题,随便用python写了一个方法,没想到却要跑17分钟左右。改用C++试试,... -
公众号开发小技能:解决模板消息(业务通知)的日调用上限问题
2022-03-11 14:36:53引言 ...每个帐号每月共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,如:设置...