概述
- 说明:本文讲述基于公众号实现的签到程序的全部开发过程。开发环境:PHP+MySQL。
- 源码下载地址:敬请期待
- 关注微信公众号【知行校园汇】可免费下载全部源码。
- >>点击查看WUTer计算机专业实验汇总
- 谨记:纸上得来终觉浅,绝知此事要躬行。
0 成果展示
本文较长,为了减少读者的时间,先展示本项目的成果图,以便读者快速确实这篇文章是不是正在寻找的文章。
这里有必要说明一下,项目中的前端页面配色配图等参考自网络公开的CSS样式表。
关于项目的详情,请见后文。
0.1 签到主页面:
用户点击公众号的菜单【早起签到】后,即实现自动微信登录,进入签到主页面。
主页面展示如下:
主页面分为三个展示页面。未到签到时间(0:00到05:50期间)显示图如上左图所示。到签到时间时,按钮状态可点击,如上中图。签到完成后,将显示该用户在本公众号的当日签到名次。
0.2 签到记录页
用户签到后,可以查看本用户自己的签到记录。详情如下图所示:
在本项目中,用户签到后,可以在本日或者次日的06:50至08:30到指定地点领取奖励。超过这个时间,本条签到记录即作废。三种状态的签到记录如上图所示。
0.3 后台兑换页
下图为后台兑换的测试页面。输入签到ID即可查询该签到记录。
如上图。如果前来兑换的用户的签到记录已过期,则显示如上左图所示。如果用户的签到记录已兑换,则显示如上中图所示。如果用户的签到记录可兑换,则显示如上右图所示。
点击“立即兑换”按钮,则显示“兑换成功”弹窗,如下图所示。
看到这里,如果这不是你想要的项目,那么你可以关闭本篇博客了。
如果这是你想要的项目,或者本项目和你当前项目接近,或者想学习这个项目的编写,或者……
请继续往下看。下面正式开始^_^
1 项目背景与需求分析
1.1 项目背景
关于此需求分析部分,先从项目的背景说起吧。
起因是这样的。项目组要在公众号内举办一个活动。这个活动简单易懂,就是“早起签到领奖励”。每天早上指定时间开启签到系统,然后用户点击菜单栏的“早起签到”按钮,即可通过微信登录后,进入签到系统。
用户签到完成后,用户可以凭签到记录到指定地点领取早餐一份。同时每日的早餐限量,所以先到先得。同时签到记录也有兑换期限,本项目组指定的计划是本日或者次日的8:30前均可领取。所以今天签到,后天早上就领不到早餐了^_^
1.2 需求分析
通过对项目背景的分析,本项目的需求有如下几部分:
1、本项目为公众号项目,微信网页开发;
2、实现用户的微信登录授权,以确定用户身份,并实现单日只能签到一次;
3、用户签到后,记录签到时间以及当日签到名次;
4、显示用户签到记录,以及签到记录的状态(可兑换、已兑换、已过期);
5、后台管理员进行兑换确定。
2 概要设计
2.1 开发技术分析
既然是微信网页开发,并且要实现用户的登录,那么就需要学习一下微信的公众号网页授权机制。
微信开放平台中有关于这方面的使用说明的介绍,可以通过开发者文档自行学习。链接如下:
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
具体而言,网页授权流程分为四步:
1、引导用户进入授权页面同意授权,获取code
2、通过code换取网页授权access_token(与基础支持中的access_token不同)
3、如果需要,开发者可以刷新网页授权access_token,避免过期
4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)
只有通过微信认证的服务号可以使用“网页授权”接口。
如果想用来进行开发测试,可以申请测试账号。申请链接如下:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
关于微信网页授权登录的详细过程,将在实践代码中(后边编码实现过程)进行讲解。
————————————————————
然后需要对开发语言进行分析确定。
微信网页可以采用任意Web开发语言实现。
但是结合本项目来说,需要对数据库的数据进行增删改查,所以最终采用的编程语言是PHP。
(在此项目前,笔者未接触过PHP开发o(╥﹏╥)o,笔者熟悉的Web开发语言是Java Web。所以编码实现过程。如有不妥之处,多多包涵(*^▽^*))
最后数据库采用MySQL。
2.2 数据库设计
这个项目本身并不大,所以设计的数据库表单也很简单。
首先是用户表。
在本项目中,只需要获取用户对此公众号产生的唯一标识OpenID即可,并不需要获取用户的昵称、城市、头像等其他开放信息。如果需要用户的这些信息,可以对user表进行扩展。
用户表user的字段只有2列。如下图所示:
各个字段含义:
- userid:用户在本签到系统中的用户ID,即项目中的唯一标识。主键。int型。
- openid:通过微信授权接口获取的用户在此公众号所产生的唯一标识。int型。
然后是用户签到记录表。signin表单详情如下:
各个字段含义:
- signid:用户签到记录的唯一标识。主键。int型。
- userid:用户ID,为表user的外键。int型。
- signdata:用户签到的时间戳,单位为ms,含义为1970年至今的时间秒数。int型。
- signmon:签到的月份。int型。
- signday:签到记录为本月的第几日。int型
- signhour:签到记录的时。int
- signmin:签到记录的分。int
- mingci:本日签到名次(这里请忽略使用拼音表示Ծ‸Ծ主要是因为排名(rank)为SQL语言的关键字)int
- isused:本签到记录是否已兑换奖品。是:1,否:0。
- usetime:兑换时间,同为时间戳。(上图中此列拼写错误,可忽略。)
如果你的项目还需要其他表单,可自行设计。
3 开发环境配置
工欲善其事,必先利其器。进行开发之前,首先需要对开发环境等进行配置。
3.1 编程环境:
本项目使用PHP语言。所以编译程序使用的是JetBrains PhpStorm。
PHPStorm为付费软件。如果你是学生用户,拥有教育邮箱,可以从JetBrains官网授权获取免费版。
3.2 编译环境:
项目编写过程,难免要进行调试。所以这里用到的PHP网页运行环境为phpStudy。
此软件为免费软件。具体如何,可从phpStudy官网查询相关文档。
3.3 MySQL数据库可视化:
调试过程,难免需要对数据库中的数据进行核实检查。
笔者使用的MySQL数据库可视化工具为:Navicat for MySQL。
此软件为付费软件。
3.4 网页调试:
如何对公众号网页进行调试?微信公众平台的开发者工具栏就给出了Web开发者工具:微信web开发者工具
如上图,点击【web开发者工具】,跳转到绑定开发者微信号页面。这里将开发者的微信号进行绑定。
然后前往:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html 下载微信web开发者工具电脑客户端。
下载安装后,运行后,需要开发者扫描登录才能运行。运行页面如下:
3.5 微信公众后台配置
如果直接将编写完成的项目配置到公众号的菜单栏,微信是不认可你这个项目的。
不过,你也不可能在不配置公众号后台的情况下编写出来这个程序O(∩_∩)O
所谓对公众号后台进行配置,就是获取公众号的秘钥,以及将域名添加到公众号接口白名单的过程。
具体如下:
3.5.1 配置域名
项目最终是需要使用域名进行访问的。并且域名必须启用SSL证书(HTTPS协议)
至于如何在PHPStudy中配置SSL证书,笔者前面写过,点击此处查看。
这里讲述的是将域名加入到公众号的“白名单”中。
首先进入公众号后台,点击【设置】>>【公众号设置】>>【功能设置】,如下图所示:
点击上图红框中的两个模块的设置,即可将域名添加授权。
为了核验你对这个域名拥有所有权,需要将指定的文件上传到域名服务器的目录中进行校验。如下图所示:
具体配置过程,根据上图提示进行配置即可。
微信公众号支持配置2个网页授权域名,和3个JS接口安全域名。
3.5.2 配置IP白名单
本项目需要用到access_token接口,所以需要将最终部署网站的服务器的IP地址配置到IP白名单中!
也就是将域名所解析到的IP地址配置到公众号IP白名单中(具体在域名解析列表中查看)。
点击微信公众平台后台的【开发】>>【基本配置】,即可进行IP白名单修改配置。
点击上图下面红框中的【查看】,即可对IP白名单进行查看与修改。
3.5.3 获取开发者密码
开发者密码是校验公众号开发者身份的密码,具有极高的安全性。
点击微信公众平台后台的【开发】>>【基本配置】,即可对开发者密码进行重置获取。
重要:这里需要将开发者ID(AppID)和开发者密码(AppSecret)记录下来!
重要:这里需要将开发者ID(AppID)和开发者密码(AppSecret)记录下来!
重要:这里需要将开发者ID(AppID)和开发者密码(AppSecret)记录下来!
4 编码实现
都说PHP是一门松散的语言。笔者也是第一次接触PHP,并使用PHP来实现这个项目……总体感觉,PHP挺好用(^o^)/~
项目本身就不大,客户端一共2个页面,所以也就不采用什么框架还是MVC开发模式了(下面模仿MVC开发模式)
4.1 模型(Model)层DAO类代码:
首先需要新建一个Dao类,这个类实现的是构造Dao类,以实现与数据库中的数据进行打交道。
通俗的将,就是实现了对数据库的增删改查,所以的SQL语句均在这里执行。
文件名:dao.php
详细代码以及注释如下所示:
<?php
//模型层,实现与数据库的数据交换
//设置时区
date_default_timezone_set('Asia/Chongqing');
//忽略错误警告
error_reporting(0);
Class Dao{
/**
* constructor:构造函数
*/
function __construct()
{
//这里换成你自己的数据库信息,如服务器地址127.0.0.1,登录名root,密码123456,数据库名qiandao,端口号3306
$this->conn=mysqli_connect("127.0.0.1","root","123456","qiandao","3306");
mysqli_query($this->conn,"SET NAMES gbk");
}
/**
* __destruct:析构函数
*/
function __destruct()
{
// TODO: Implement __destruct() method.
mysqli_close($this->conn);
}
/**
* 根据用户的OpenID查询该用户是否已登录注册
* 输入:用户OpenID
* 返回值:如果注册,返回用户userID,否则,返回0
*/
public function getUser($openid){
$sql="SELECT userid FROM user WHERE openid='".$openid."'";
$results = $this->conn->query($sql);
if($row = $results->fetch_row()){
return $row[0];//返回用户userID
}else{
return 0;//返回0
}
}
/**
* 查询当前数据库中的userID最大值
* 返回:最后注册的用户的userID
*/
public function getMaxUserID(){
$sql="SELECT MAX(userid) FROM user";
$results = $this->conn->query($sql);
if($row = $results->fetch_row()){
return $row[0];//返回用户userID
}else{
return 10000;//返回初始值10000
}
}
/**
* 为该用户创建新用户userID
* 输入:用户OpenID
* 返回:成功true 或者失败 false
*/
public function createID($userid,$openid){
$sql="INSERT INTO user(userid,openid) VALUES('".$userid."','".$openid."')";
$this->conn->query($sql);
}
/**
* 获取目前签到日(signday)中的排名最大值(rank)
* 如果查询为空,说明当日没有签到的用户,返回:0
* 返回:0 或者最大值
*/
public function getMaxRank(){
$now=getdate(date("U"));
$day = $now[mday];
$sql="SELECT MAX(mingci) FROM signin where signday='".$day."'";
$results = $this->conn->query($sql);
if($row = $results->fetch_row()){
//print $row[0];
return $row[0];//返回最大值
}else{
return 0;//返回0
}
}
/**
* 获取当前签到表中签到ID最大值
* 返回:最大签到ID signid
*/
public function getMaxSignID(){
$sql="SELECT MAX(signid) FROM signin";
$results = $this->conn->query($sql);
if($row = $results->fetch_row()){
return $row[0];//返回最大值
}else{
return 10000;//返回0
}
}
/**
* 将签到记录保存至数据库
* 输入:signid userid 签到时间signdata 签到日signday 签到排名rank isuserd默认为0
*/
public function saveSign($signid,$userid,$signdata,$signmon,$signday,$signhour,$signmin,$mingci,$isused,$usertime){
$sql="INSERT INTO signin(signid,userid,signdata,signmon,signday,signhour,signmin,mingci,isused,usertime) VALUES('".$signid."','".$userid."','".$signdata."','".$signmon."','".$signday."','".$signhour."','".$signmin."','".$mingci."','".$isused."','".$usertime."')";
try{
$this->conn->query($sql);
}catch (Exception $exception){
print $exception->getMessage();
return false;
}
return true;
}
/**
* 查询指定用户的当日签到记录
*/
public function judgesign($userid){
$now=getdate(date("U"));
$day = $now[mday];
$sql="SELECT mingci FROM signin WHERE userid='".$userid."' AND signday='".$day."'";
$results = $this->conn->query($sql);
if($row = $results->fetch_row()){
return $row[0];//返回本日的签到名次
}else{
return 0;//返回0,表示名次为0,即未签到
}
}
/**
* 查询所有的签到记录
* 返回:结果集
*/
public function getSign($userid){
$sql = "SELECT signid,signdata,mingci,isused,usertime,signday,signhour,signmin FROM signin WHERE userid='".$userid."' ORDER BY signdata DESC";
$results = $this->conn->query($sql);
return $results;//返回结果集
}
/**
* 查询指定签到ID的签到信息
*/
public function getUserSignInfo($usersignid){
$sql = "SELECT signid,signdata,mingci,isused,usertime,signday,signhour,signmin FROM signin WHERE signid='".$usersignid."'";
$results = $this->conn->query($sql);
return $results;
}
/**
* 更新指定签到ID的签到信息(兑换、兑换时间等)
*/
public function updateSignInfo($usetime,$signid){
$sql = "UPDATE signin SET isused='1',usertime='".$usetime."' WHERE signid='".$signid."'";
try{
$this->conn->query($sql);
}catch (Exception $exception){
print $exception->getMessage();
return false;
}
return true;
}
}
上面代码中,细节方面就不纠结了,保证正确的情况下,能实现功能即可。其实应该对数据库操作相关的语句进行Try-Catch判断……
4.2 控制(Control)层control类代码:
这个类文件中实现的是从前端获取用户的数据,如时间等信息,控制签到程序,然后将数据发送到模型层,对前端和数据库操作进行控制,起到中介的作用。
文件名:control.php
详细代码及注释如下所示:
<?php
include ('dao.php');
session_start();
class control{
/**
* 用户登录时执行的操作
* 新用户:分配ID
* 旧用户:直接获取ID
*/
public static function login($openid){
if(empty($openid))
return 0;
$dao = new Dao();
$results = $dao->getUser($openid); //新用户:返回0,老用户:返回userID
//新用户
if($results === 0){
$userid = $dao->getMaxUserID() + 1 ; //计算新用户ID
$dao->createID($userid,$openid); //创建新用户
return $userid; //返回用户userID
}
//老用户
else{
return $results; //返回用户userID
}
}
/**
* 获取当前用户当日是否已经签到,即用户的名次
*/
public static function issign(){
$dao = new Dao();
$mingci = $dao->judgesign($_SESSION["userid"]);
return $mingci;
}
/**
* 获取当前用户签到记录
*/
public static function getMySign(){
$dao = new Dao();
$results = $dao->getSign($_SESSION["userid"]);
return $results;
}
/**
* 获取当前指定签到ID的签到信息
*/
public static function getSignInfo(){
$dao = new Dao();
$signid = $_SESSION["usersignid"];
$results = $dao->getUserSignInfo($signid);
return $results;
}
/**
* 进行兑换
*/
public static function duiHuan(){
$usedata = microtime(true);//时间戳
$usersignid = $_SESSION["usersignid"];
$dao = new Dao();
$success = $dao->updateSignInfo($usedata,$usersignid);
return $success;
}
}
4.3 授权接口调用weixin类
下面这篇开发者文档给出了获取用户OpenID,以及用户个人信息的几个步骤:
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
建议读者认真阅读开发者文档中的这篇文章,然后再往下看。
4.3.1 获取(或者说是创建)用户授权链接
用户点击公众号菜单栏后,如果用户未登录,需要引导用户前往确认授权页面。
用户点击这个链接,打开的授权页面是这样的:
这里的关键代码如下所示:
/**
* 第1步: 获取用户授权code url
* @param string $scope 授权作用域:snsapi_base or snsapi_userinfo,这里选择base
* @param string $state 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值
* @param string $redirect_url 重定向URL
* @return string
*/
public static function createCodeUrl($scope,$state,$redirect_url){
$open_url = 'https://open.weixin.qq.com';
$redirect_url = urlencode($redirect_url);//这里必须对链接进行处理,即连接中的斜线转换成字符
//参考链接示例:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
$url = $open_url.'/connect/oauth2/authorize?appid='.APPID.'&redirect_uri='.$redirect_url.'&response_type=code&scope='.$scope.'&state='.$state.'#wechat_redirect';
return $url;
}
return语句前面一行的字符串中的几个变量值说明一下:
- APPID:这是开发者ID,本文前面3.3.5节记录的就是这个,也是公众号的唯一标识;
- redirect_uri:这是用户允许公众号获取信息后,继续跳转到的页面;
- scope:这是公众号应用授权的作用域。这个变量只有两个值:①值为snsapi_base时,只获取用户的OpenID,不获取用户的昵称、头像等信息,如果scope=snsapi_base,则不会出现上图的授权页面,即实现的是静默授权,最多用户的屏幕会显示“正在登录……”几个字。②值为snsapi_userinfo 时,弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注公众号的情况下,只要用户授权,也能获取其信息。
- state:重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节。这个参数根据项目实际进行赋值。
如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
在开发者工具中可以清晰的看到这些信息:
其中code的值为换取access_token的票据。每次用户授权后,code值均不同,code只能使用一次,有效期5分钟。
4.3.2 通过code换取网页授权access_token,从Token中读取用户OpenID
这部分的关键代码如下:
/**
* 第2步: 获取用户授权access_token
* @param type $code 授权时获得code值
* @return type
*/
public static function getAuthToken($code){
//参考链接示例:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
$url = self::API_URL.'/sns/oauth2/access_token?appid='.APPID.'&secret='.APPSECRET.'&code='.$code.'&grant_type=authorization_code';
$content = curl_get( $url );
$ret = json_decode($content, true );
return self::getResult( $ret ) ? $ret : null;
}
这里链接中用到的三个变量的说明如下:
- APPID:公众号的唯一标识,同上;
- secret:公众号的appsecret,前面3.5.3节让记下的一串字符;
- code:授权时跳转的链接中带的参数。
如果函数执行正确,返回的数据为一个JSON数据包,可以简单理解为C++中的结构体。开发者文档的解释如下:
这里边就包含用户的唯一标识:OpenID。
在本项目中,进行到这里就结束了。
4.3.3 完整代码
如果你还想继续获取用户的昵称、头像等公开信息,请看完整代码。
各个过程就是请求链接、获取JSON包的过程。
文件名:weixin.class.php
<?php
//下面是全局变量的几个值
define('APPID','这里换成你自己的APPID');//
define('APPSECRET','这里换成你自己的APPSECRET');
//define('open_url','https://open.weixin.qq.com');
class weixin extends wxcommon{
/**
* 第1步: 获取用户授权code url
* @param string $scope 授权作用域:snsapi_base or snsapi_userinfo,这里选择base
* @param string $state 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值
* @param string $redirect_url 重定向URL
* @return string
*/
public static function createCodeUrl($scope,$state,$redirect_url){
$open_url = 'https://open.weixin.qq.com';
$redirect_url = urlencode($redirect_url);
//参考链接示例:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
$url = $open_url.'/connect/oauth2/authorize?appid='.APPID.'&redirect_uri='.$redirect_url.'&response_type=code&scope='.$scope.'&state='.$state.'#wechat_redirect';
return $url;
}
/**
* 第2步: 获取用户授权access_token
* @param type $code 授权时获得code值
* @return type
*/
public static function getAuthToken($code){
//参考链接示例:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
$url = self::API_URL.'/sns/oauth2/access_token?appid='.APPID.'&secret='.APPSECRET.'&code='.$code.'&grant_type=authorization_code';
$content = curl_get( $url );
$ret = json_decode($content, true );
return self::getResult( $ret ) ? $ret : null;
}
/**
* 第3步:刷新用户授权access_token
* @param type $refresh_token 用户刷新access_token
* @return type
*/
public static function refershAuthToken($refresh_token){
//参考链接示例:https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
$url = self::API_URL.'/sns/oauth2/refresh_token?appid='.APPID.'&grant_type=refresh_token&refresh_token='.$refresh_token;
$content = curl_get( $url );
$ret = json_decode($content, true );
return self::getResult( $ret ) ? $ret : null;
}
/**
* 第4步: 获取用户基本信息
* @access_token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
* @param type $openid 普通用户的标识,对当前公众号唯一
* @param string $lang 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语
* @return type
*/
public static function getUserInfoByID( $access_token, $openid, $lang='zh_CN' ){
if( !$lang ) $lang = 'zh_CN';
//$access_token = self::getToken();
//参考链接示例:https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
$url = self::API_URL . "/sns/userinfo?access_token={$access_token}&openid={$openid}&lang={$lang}";
$ret = json_decode(curl_get( $url ), true );
return self::getResult( $ret ) ? $ret : null;
}
}
/**
* GET方式获取服务器响应
* @param {string} $url
* @return {string|boolen} 成功时返回服务器响应内容,失败则返回false
*/
function curl_get( $url ){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);;
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
if(!curl_exec($ch)){
error_log( curl_error ( $ch ));
$data = '';
} else {
$data = curl_multi_getcontent($ch);
}
curl_close($ch);
return $data;
}
/**
*微信通用接口
*/
class wxcommon{
const API_URL = 'https://api.weixin.qq.com';
private static $access_token;
private static $expries_time = 0;
/**
* 用于获取AccessToken。如成功返回AccessToken,失败返回false
*/
public static function getToken(){
if(isset(self::$access_token) && time() < self::$expries_time){
return self::$access_token;
}
$url = self::API_URL."/cgi-bin/token?grant_type=client_credential&appid=".APPID."&secret=".APPSECRET;
$content=curl_get($url);
$ret=json_decode($content,true);//{"access_token":"ACCESS_TOKEN","expires_in":7200}
if(array_key_exists('errcode',$ret) && $ret['errcode'] != 0){
return false;
}else{
self::$access_token = $ret['access_token'];
self::$expries_time = time() + intval($ret['expires_in']);
return self::$access_token;
}
}
public static function getResult($ret) {
if(!is_array($ret) || !array_key_exists('errcode',$ret)){
return $ret;
}
$errcode = intval($ret['errcode']);
if(in_array($errcode, self::$ERRCODE_MAP)){
if($errcode == 0){
return true;
}
return array('errcode' => $errcode, 'errinfo' => self::$ERRCODE_MAP[$errcode]);
}
return array('errcode'=>'-2','errinfo'=>'未知错误');
}
static $ERRCODE_MAP = array(
'-1' => '系统繁忙',
'0' => '请求成功',
'40001' => '获取access_token时AppSecret错误,或者access_token无效',
'40002' => '不合法的凭证类型',
'40003' => '不合法的OpenID',
'40004' => '不合法的媒体文件类型',
'40005' => '不合法的文件类型',
'40006' => '不合法的文件大小',
'40007' => '不合法的媒体文件id',
'40008' => '不合法的消息类型',
'40009' => '不合法的图片文件大小',
'40010' => '不合法的语音文件大小',
'40011' => '不合法的视频文件大小',
'40012' => '不合法的缩略图文件大小',
'40013' => '不合法的APPID',
'40014' => '不合法的access_token',
'40015' => '不合法的菜单类型',
'40016' => '不合法的按钮个数',
'40017' => '不合法的按钮个数',
'40018' => '不合法的按钮名字长度',
'40019' => '不合法的按钮KEY长度',
'40020' => '不合法的按钮URL长度',
'40021' => '不合法的菜单版本号',
'40022' => '不合法的子菜单级数',
'40023' => '不合法的子菜单按钮个数',
'40024' => '不合法的子菜单按钮类型',
'40025' => '不合法的子菜单按钮名字长度',
'40026' => '不合法的子菜单按钮KEY长度',
'40027' => '不合法的子菜单按钮URL长度',
'40028' => '不合法的自定义菜单使用用户',
'40029' => '不合法的oauth_code',
'40030' => '不合法的refresh_token',
'40031' => '不合法的openid列表',
'40032' => '不合法的openid列表长度',
'40033' => '不合法的请求字符,不能包含uxxxx格式的字符',
'40035' => '不合法的参数',
'40038' => '不合法的请求格式',
'40039' => '不合法的URL长度',
'40050' => '不合法的分组id',
'40051' => '分组名字不合法',
'41001' => '缺少access_token参数',
'41002' => '缺少appid参数',
'41003' => '缺少refresh_token参数',
'41004' => '缺少secret参数',
'41005' => '缺少多媒体文件数据',
'41006' => '缺少media_id参数',
'41007' => '缺少子菜单数据',
'41008' => '缺少oauth code',
'41009' => '缺少openid',
'42001' => 'access_token超时',
'42002' => 'refresh_token超时',
'42003' => 'oauth_code超时',
'43001' => '需要GET请求',
'43002' => '需要POST请求',
'43003' => '需要HTTPS请求',
'43004' => '需要接收者关注',
'43005' => '需要好友关系',
'44001' => '多媒体文件为空',
'44002' => 'POST的数据包为空',
'44003' => '图文消息内容为空',
'44004' => '文本消息内容为空',
'45001' => '多媒体文件大小超过限制',
'45002' => '消息内容超过限制',
'45003' => '标题字段超过限制',
'45004' => '描述字段超过限制',
'45005' => '链接字段超过限制',
'45006' => '图片链接字段超过限制',
'45007' => '语音播放时间超过限制',
'45008' => '图文消息超过限制',
'45009' => '接口调用超过限制',
'45010' => '创建菜单个数超过限制',
'45015' => '回复时间超过限制',
'45016' => '系统分组,不允许修改',
'45017' => '分组名字过长',
'45018' => '分组数量超过上限',
'46001' => '不存在媒体数据',
'46002' => '不存在的菜单版本',
'46003' => '不存在的菜单数据',
'46004' => '不存在的用户',
'47001' => '解析JSON/XML内容错误',
'48001' => 'api功能未授权',
'50001' => '用户未授权该api',
);
}
上面这个代码是笔者从一本参考书中摘抄的,可以直接拿来使用。
但是需要将前两行的APPID和APPSECRET补充完整。
4.4 签到主页面设计
主页面主要是HTML代码,以及加载HTML代码前执行的几段PHP代码。
这里我们要想清楚几个问题:
用户真正能够授权,后台并且能够拿到code的链接长这样:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
//注:这是微信开发者文档给出的实例链接
但是这样的链接,太长,看起来就不舒服。
我们正常理解的,我们能接受的公众号网页链接,应该是这样的:
https://wx.yourdomain.com/index.php
所以这就需要在index.php中进行控制。
这里笔者简单说一下笔者的思路:
if(没有创建客户端到服务端的session["userid"]){ //说明是第一次访问,session中没有userid这个字段
if(GET到了链接中的state值){ //说明已经拿到code
//通过code获取Token
//解析Token获取openid
//将openid与数据库即有数据对比,获取用户userid
//获取其他信息
}
else{
//跳转到授权链接,就是前面那个很长的链接
}
else{ //反之就是已经创建包含userid的session,用于已经登录,则可以获取网页中需要的信息
//获取其他信息
}
所以这里的关键代码,笔者是这样写的:
/**
* 首次访问进行登录
*/
//从登录链接返回的两个参数:code和state,其中state用来获取用户的OpenID,state用来判断是否是首次打开页面
//如果没有设置全局session变量userID,执行if内函数
if(!isset($_SESSION["userid"])){
if(isset($_GET["state"])){
$token=weixin::getAuthToken($_GET['code']); //根据请求链接获取返回的Token,其中包含access_token
$_SESSION["userid"] = control::login($token['openid']); //根据返回的JSON包,将用户的OpenID进行登录验证,并拿到用于的userID,保存到session中。
$_SESSION["issign"] = control::issign(); //拿到用户的登签到名次,未签到为0
}else{
$url = weixin::createCodeUrl("snsapi_base","123","https://wx.XXX.com/index.php");
header("location:$url");
}
}
else{
$_SESSION["issign"] = control::issign(); //拿到用户的登签到名次issign,未签到为0
}
完整代码,以及代码详解如下:
文件名:index.php
<?php
//设置时区
date_default_timezone_set('Asia/Chongqing');
session_start();
/**
* 本项目中使用session全局数组保存用户名,以及用户的签到名次
* 当然也可以改用cookie
* */
//加载几个引用的文件
require 'lib/weixin.class.php';
require 'lib/control.php';
/**
* 首次访问进行登录
*/
//从登录链接返回的两个参数:code和state,其中state用来获取用户的OpenID,state用来判断是否是首次打开页面
//如果没有设置全局session变量userID,执行if内函数
if(!isset($_SESSION["userid"])){
if(isset($_GET["state"])){
$token=weixin::getAuthToken($_GET['code']); //根据请求链接获取返回的Token,其中包含access_token
$_SESSION["userid"] = control::login($token['openid']); //根据返回的JSON包,将用户的OpenID进行登录验证,并拿到用于的userID,保存到session中。
$_SESSION["issign"] = control::issign(); //拿到用户的登签到名次,未签到为0
}else{
$url = weixin::createCodeUrl("snsapi_base","123","https://wx.XXX.com/index.php");
header("location:$url");
}
}
else{
$_SESSION["issign"] = control::issign(); //拿到用户的登签到名次,未签到为0
}
//下面是获取用户的信息,没用到……
//$userinfo=weixin::getUserInfoByID($token['access_token'],$token['openid'],'zh_CN');//根据access_Token获取用户的OpenID
//$nickname = $userinfo["nickname"];
//$usersex = $userinfo["sex"];
//………………
?>
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0,viewport-fit=cover">
<title>早起签到</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="banner2"><img src="images/banner.jpg" class="img-responsive"></div>
<?php
$now=getdate(date("U")); //当前时间
$hour = $now[hours]; //签到时
$min = $now[minutes]; //签到分
//如果未到签到时间,则不能签到!
if($hour< 6 || ($hour==6 && $min<50) ){
?>
<form name="sign" action="sign.php">
<div id="fromBox" class="fromBox">
<button id="btn-qiandao" class="btn3" disabled="true">5:50开启今日签到</button>
</div>
</form>
<?php
}
//如果用户未签到,显示可签到
else if($_SESSION["issign"]==0) {
//if($issign===0) {
?>
<form name="sign" action="sign.php">
<div id="fromBox" class="fromBox">
<button id="btn-qiandao" class="btn3">立即签到</button>
</div>
</form>
<?php
}
//用户已经签到,显示签到名次
else{
?>
<form name="sign">
<div id="fromBox" class="fromBox">
<button id="btn-qiandao" class="btn3" disabled="true">今日签到名次:<?php echo $_SESSION["issign"]; ?> </button>
</div>
</form>
<?php
}
?>
<p align="right"><a href="mysign.php" style="text-decoration: none">>>我的签到记录 </a></p>
<div class="pt10lr10 mt10">
<div class="pline"></div>
<div class="prizebox">
<div class="ptitle"><strong>活动详情</strong></div>
<div class="" id="demo" style="">
<table style="font-size: 13px;color: #b25d06">
<tr>
<td width="6PX" valign="top" align="right">1、</td><td>每日05:50至23:59开启早起签到。</td>
</tr>
<tr>
<td valign="top" align="right">2、</td><td>其他内容……</td>
</tr>
<tr>
<td valign="top" align="right">3、</td><td>其他内容……</td>
</tr>
</table>
<br>
</div>
</div>
</div>
<div align="center">
<a style="font-size: 10px;color: #ff820b;text-decoration: none" href="https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzIwODkwOTg5Nw==&scene=124#wechat_redirect">技术支持:@拾年之璐</a>
</div>
</body>
这个主页面用到的CSS文件,来自网络,篇幅较长,将在文末展示。
这里注意上面代码中的“立即签到”按钮所在的form表单中,action是跳转到sign.php文件,这里要注意!
所以需要对sign.php文件进行编写。这个文件,就是个控制页面,以及跳转页面。详细代码如下:
文件名:sign.php
<?php
header("Content-type:text/html;charset=UTF-8");
error_reporting(0);
include('lib/dao.php');
//下面代码完全可以放在control.php文件中实现,然后此处调用!
session_start();
$dao = new Dao();
//获取签到的基本信息
$signid = $dao->getMaxSignID()+1;//签到ID
$userid=$_SESSION["userid"];//用户ID
$signdata = microtime(true);//时间戳
$now=getdate(date("U")); //当前时间
$signmon = $now[mon]; //签到月份
$signday = $now[mday]; //签到日
$signhour = $now[hours]; //签到时
$signmin = $now[minutes]; //签到分
$mingci = $dao->getMaxRank()+1;//签到名次
$isused = 0; //是否兑换
$usertime = 0; //兑换时间
$success = $dao->saveSign($signid,$userid,$signdata,$signmon,$signday,$signhour,$signmin,$mingci,$isused,$usertime);
//上面代码完全可以放在control.php文件中实现,然后此处调用!
//签到名次保存到session中!
//setcookie("issign",$mingci,time()+60*60*24); //有效期24个小时,弃用cookie
$_SESSION["issign"] = $mingci;
if($success === true) {
echo "<script charset='UTF-8' language='javascript' type='text/javascript'> { window.alert('签到成功!')}; setTimeout( window.parent.location.href='index.php',2000); </script>";
//echo "<script> {window.alert('签到成功!')} </script>";
}
else{
echo "<script charset='UTF-8'language='javascript' type='text/javascript'> {window.alert('签到失败,请联系管理员!')} ;window.parent.location.href='index.php'</script>";
}
4.5 我的签到记录页面
前面index.php代码中,有一个“我的签到记录”按钮,是个超链接的形式,所跳转的页面是mysign.php。详细代码如下:
文件名:mysign.php
<?php
require 'lib/control.php';
$results = control::getMySign();//得到签到记录集合
$exist = false;//是否存在签到记录
//时间戳转换成标准时间格式
function get_microtime_format($time)
{
if(strstr($time,'.')){
sprintf("%01.3f",$time); //小数点。不足三位补0
list($usec, $sec) = explode(".",$time);
$sec = str_pad($sec,3,"0",STR_PAD_RIGHT); //不足3位。右边补0
}else{
$usec = $time;
$sec = "000";
}
$date = date("Y-m-d H:i:s.x",$usec);
return str_replace('x', $sec, $date);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>我的签到记录</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="wrap">
<div class="banner2"><img src="images/banner.jpg"/></div>
<div class="tabsbox tabsbox2">
<div class="title1">我的签到记录</div>
<?php
while($row = $results->fetch_row()) {
$exist = true;
?>
<div class="Prize">
<p>签到ID:<?php echo $row[0]; ?> 当日签到名次:<?php echo $row[2]; ?></p>
<div>签到时间:<?php echo get_microtime_format($row[1]);
$now=getdate(date("U")); //当前时间
$nowday = $now[mday]; //当前日
$nowhour = $now[hours]; //当前时
$nowmin = $now[minutes]; //当前分
//1、昨天5.5.到24.00签到的用户,今天8.30前可兑换
//2、今天5.50到8.30之间签到的用户,可在今天8.30前兑换,或者明天兑换
if(($row[3]==0 && $row[5] == ($nowday-1) && $nowhour<=8 && $nowmin<=30) || ($row[3]==0 && $row[5] == $nowday) ){
?><a style="font-weight: bold;color:green"> 可兑换</a></div><?php
}
else if($row[3]==1 ){
?><a style="font-weight: bold;color:red"> 已兑换</a></div>
<div>兑换时间:<?php echo get_microtime_format($row[4]); ?></div>
<?php
}
else{
?><a style="font-weight: bold;color:orange"> 已过期</a></div><?php
}
?>
</div>
<?php
}
if($exist === false){?>
<div class="Prize">
<div align="center">您还没有签到记录,快去签到吧^_^</div>
</div>
<?php
}
?>
</div>
<a href="index.php" class="btn4" style="text-decoration: none">返回</a>
</div>
</body>
</html>
4.6 后台兑换页面
后台兑换页面就是一个很简单的HTML页面。其中主要由两个PHP文件组成:
主页面文件名:search.php
完整代码:
<?php
require 'lib/control.php';
//测试用
//$_SESSION["usersignid"] = 10005;
//↑ 测试用
$exist = false;
if(isset($_REQUEST["usersignid"])){
$usersignid2 = $_REQUEST["usersignid"];
$_SESSION["usersignid"] = $usersignid2;
}
if(isset($_SESSION["usersignid"])){
$result = control::getSignInfo();
}
else{
$result = 0;
}
//时间戳转换
function get_microtime_format($time)
{
if(strstr($time,'.')){
sprintf("%01.3f",$time); //小数点。不足三位补0
list($usec, $sec) = explode(".",$time);
$sec = str_pad($sec,3,"0",STR_PAD_RIGHT); //不足3位。右边补0
}else{
$usec = $time;
$sec = "000";
}
$date = date("Y-m-d H:i:s.x",$usec);
return str_replace('x', $sec, $date);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>后台查询系统</title>
<link rel="stylesheet" href="css/style.css">
<style type="text/css">
input{
border: 1px solid #ccc;
padding: 10px 0px;
border-radius: 5px; /*css3属性IE不支持*/
padding-left:10px;
width: 150px;
}
.btn5{ display:block;
border:0px;
margin:0rem auto 0% auto;
width: 94%;
background-color: #ef2122;
text-align: center;
font-weight: bold;
font-size:17px ;
color: #fff3f0;
border-radius: 10px;
}
</style>
<script type="text/javascript">
function doAction() {
var usersignid = document.getElementById("usersignid");
window.location.href = "a.php?usersignid="+ usersignid.value;
}
</script>
</head>
<body>
<div>
<br><br><br>
</div>
<form action="a.php" method="post">
<table align="center" width="85%">
<tr>
<td width="40%">
<input placeholder="请输入签到ID" type="text" tabindex="1" name="usersignid" id="usersignid" required autofocus autocomplete="on">
</td>
<td width="40%">
<input class="btn5" type="button" value="立即查询" onclick="doAction()">
</td>
</tr>
</table>
</form>
<div>
<br><br><br><br>
</div>
<div id="wrap">
<div class="tabsbox tabsbox2">
<div class="title1">用户签到记录</div>
<?php
if(isset($_SESSION["usersignid"])){
while($row = $result->fetch_row()) {
$exist = true;
?>
<div class="Prize">
<p>签到ID:<?php echo $row[0]; ?> 签到名次:<?php echo $row[2]; ?></p>
<div>签到时间:<?php echo get_microtime_format($row[1]);
$now=getdate(date("U")); //当前时间
$nowday = $now[mday]; //当前日
$nowhour = $now[hours]; //当前时
$nowmin = $now[minutes]; //当前分
//1、昨天5.5.到24.00签到的用户,今天8.30前可兑换
//2、今天5.50到8.30之间签到的用户,可在今天8.30前兑换,或者明天兑换
if(($row[3]==0 && $row[5] == ($nowday-1) && $nowhour<=8 && $nowmin<=30) || ($row[3]==0 && $row[5] == $nowday) ){
?><a style="font-weight: bold;color:green"> 可兑换</a>
<br>
<br>
<a href="duihuan.php" class="btn4" style="text-decoration: none">立即兑换</a>
<?php
}
else if($row[3]==1){
?><a style="font-weight: bold;color:red"> 已兑换</a>
<div>兑换时间:<?php echo get_microtime_format($row[4]); ?></div>
<?php
}
else{
?><a style="font-weight: bold;color:orangered"> 已过期</a><?php
}?>
</div>
<?php
}
}
if($exist === false){?>
<div class="Prize">
<div align="center">未查询到该签到记录!</div>
</div>
<?php
}
?>
</div>
</div>
</body>
</html>
兑换按钮文件名:submit.php
完整代码:
<?php
header("Content-type:text/html;charset=UTF-8");
require 'lib/control.php';
$success = control::duiHuan();
if($success === true) {
echo "<script charset='UTF-8' language='javascript' type='text/javascript'> { window.alert('兑换成功!')}; setTimeout( window.parent.location.href='a.php',2000); </script>";
//echo "<script> {window.alert('签到成功!')} </script>";
}
else{
echo "<script charset='UTF-8'language='javascript' type='text/javascript'> {window.alert('兑换失败,请联系管理员!')} ;window.parent.location.href='a.php'</script>";
}
至此,本项目的关键代码展示完毕。
本文结束。
5 参考文献:
[1] 软件开发技术联盟编著.PHP+MySQL开发实战[M].北京:清华大学出版社.2013. |
[2] 刘乃琦,李忠主编.PHP和MySQL Web应用开发[M].北京:人民邮电出版社.2013. |
[3] 于荷云编著.PHP+MySQL网站开发全程实例[M].北京:清华大学出版社.2012. |
[4] 易伟著.微信公众平台服务号开发 揭秘九大高级接口[M].北京:机械工业出版社.2014. |
[5] 席新亮编著.微信公众平台JSSDK开发实战 公众号与HTML5混合模式揭秘[M].北京:电子工业出版社.2015. |
[6] 闫小坤,周涛.微信公众平台应用开发实践[M].北京:清华大学出版社.2017. |
[7] 闫小坤,周涛著.微信公众平台应用开发从入门到精通[M].北京:清华大学出版社.2015. |
[8] 张暑军主编.基于HTML 5的APP开发教程[M].北京:北京理工大学出版社.2016. |
6 附:style.css文件代码
注:此文件来自网络!
文件名:style.css
完整代码:
@charset "UTF-8";
h1,h2,h3,h4,h5,h6,span,p,a,.btn,input,select,textarea,div{ font-weight: normal; font-family: "Microsoft YaHei",微软雅黑,"MicrosoftJhengHei",华文细黑,STHeiti,MingLiu,Helvetica Neue, Helvetica, Arial, sans-serif ;}
ul,li{list-style: none;margin:0;padding:0;}
a{ color: #323232;}
a:hover{ color: #323232; text-decoration: none;}
.w100{width:100%;}
body{background-color:#faca34}
/*背景颜色*/
.bodybg{background:#f7f7f7;}
.bgbody{background:#eeeff3;}
.bg-white{background:#fff;}
.bg-red{background:#e3393a;}
.bgmainy{background-color:#ffa15c;}/*crm商品管理,页面主题黄色*/
.bgea{background:#EA6846;}
.bgf1{background:#F1F2F4;}
.bgf7{background:#f7f7f7;}
.bgfb{background:#fbfbfb;}
.bgf36{background:#ff3366;}
.bgf2f1{background:#f2f1f1;}
.bgef1e3b{background:#ef1e3b;}
/*字体大小*/
.font10{font-size:10px;}
.font12{font-size:12px;}
.font13{font-size:13px;}
.font14{font-size:14px;}
.font15{font-size:15px;}
.font16{font-size:16px;}
.font18{font-size:18px;}
.font20{font-size:20px;}
.font42{font-size:42px;}
.font2m{font-size: 2em;}
.font3m{font-size: 3em;}
/*字体颜色*/
.text-red{color:#df493b;}
.text-white{color:#fff;}
.text-grey{color:#ccc;}
.text-grey1{color:#9e9ea1;}
.text1{color:#50d2c2;}
.text-f36{color:#ff3366;}
.colorb5{color:#b5b5b5;}
.colorstar {color:#ff4444;}
.color29{color:#292929;}
.text-orange{color:#ffa15c;}
.text-blue{color:#41a4e3;}
.text-jiangjiu{color:#da5141;}/* 广东酱酒专家 */
.texthidden{text-overflow:ellipsis;white-space: nowrap;overflow: hidden;}/* 1行 */
.rows2{display:-webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical; overflow: hidden;}/* 2行 */
/*高度和行高*/
.h30{height:30px;}
.h35{height:35px;}
.h43{height:43px;line-height: 43px;}
.h48{height:48px;line-height: 48px;}
.h50{height:50px;line-height: 50px;}
.h70{height:70px;}
.lh25{line-height: 25px;}
.lh33{line-height: 33px;}
.lh34{line-height: 34px;}
.lh47{line-height: 47px;}
/*边框样式*/
.bordernone{border:none;}
.border0{border:none;}
.border{border: 1px solid #e8e9eb;}
.bordert{border-top: 1px solid #e8e9eb;}
.borderr{border-right: 1px solid #e8e9eb;}
.borderb{border-bottom: 1px solid #e8e9eb;}
.borderl{border-left:1px solid #e8e9eb;}
.bordertb{border-top:1px solid #e8e9eb;border-bottom:1px solid #e8e9eb;}
.btn-simple{width:100%;border-radius:0;border:none;}
.bradius3{border-radius:3px;}
.bradius20{border-top-left-radius:20px;border-bottom-left-radius:20px;border-top-right-radius:20px;border-bottom-right-radius:20px;}
.bg-success1{background: #66c300;}
/*delete*/
.bordertop{border-top: 1px solid #e8e9eb;}
.borderright{border-right: 1px solid #e8e9eb;}
.borderbottom{border-bottom: 1px solid #e8e9eb;}
/* 内外边距 */
.clearMargin{margin:0;}
.clearPadding{padding:0;}
.clearPtb{padding-top:0px;padding-bottom:0;}
.clearMb{margin-bottom:0;}
.clearPb{padding-bottom:0;}
/* 内边距 */
.padding5{padding:5px;}
.padding10{padding:10px; background-color:#FFF; width:90%; margin:0px auto 10px auto}
.padding15{padding:15px;}
.p15{padding:15px;}
.pt10b3{padding-top:10px;padding-bottom:3px;}
.ptb5{padding-top: 5px;padding-bottom: 5px;}
.ptb6{padding-top: 6px;padding-bottom: 6px;}
.ptb10{padding-top:10px;padding-bottom: 10px;}
.ptb15{padding-top: 15px;padding-bottom: 15px;}
.ptb20{padding-top: 20px;padding-bottom: 20px;}
.ptb30{padding-top:30px;padding-bottom:30px;}
.plr0{padding-left:0px;padding-right:0px;}
.plr10{padding-left:10px;padding-right:10px;}
.plr15{padding-left:15px;padding-right:15px;}
.plr25{padding-left: 25px;padding-right: 25px;}
.pb5{padding-bottom: 5px}
.pb10{padding-bottom: 10px}
.pb13{padding-bottom: 13px}
.pb15{padding-bottom: 15px}
.pb20{padding-bottom: 20px}
.pb55{padding-bottom: 55px}
.pt5{padding-top:5px;}
.pt10{padding-top: 10px;}
.pt12{padding-top: 12px}
.pt15{padding-top: 15px;}
.pt20{padding-top: 20px;}
.pt25{padding-top: 25px;}
.pt42{padding-top: 42px;}
.pt5p{padding-top: 5%;}
.pr0{padding-right: 0;}
.pr5{padding-right: 5px;}
.pl0{padding-left:0;}
.pl2{padding-left: 2px;}
.pl5{padding-left:5px;}
.pl10{padding-left:10px;}
.pl15{padding-left: 15px;}
.pl60{padding-left: 60px;}
.-pl20{padding-left:-20px;}
.pl12p{padding-left: 12%;}
/* 外边距 */
.margin15{margin:15px;}
.margin30{margin:30px;}
.m30{margin: 30px;}
.mt0{margin-top: 0;}
.mt1{margin-top: 1px;}
.mt5 {margin-top: 5px;}
.mt9{margin-top: 9px;}
.mt10{margin-top: 10px;}
.mt15{margin-top: 15px;}
.mt20 {margin-top: 20px;}
.mt30{margin-top: 30px;}
.mt-1{margin-top:-1px;}
.mb0{margin-bottom: 0;}
.mb5{margin-bottom: 5px;}
.mb10{margin-bottom: 10px;}
.mb15{margin-bottom: 15px;}
.mb46{margin-bottom:46px;}
.mb60{margin-bottom: 60px;}
.mr6{margin-right:6px;}
.mr20{margin-right:20px;}
.ml20{margin-left:20px;}
.mtb5{margin-top:5px;margin-bottom:5px;}
.mtb10{margin-top: 10px;margin-bottom: 10px;}
.mtb15{margin-top: 15px;margin-bottom: 15px;}
.mtb20{margin-top: 20px;margin-bottom: 20px;}
.mt20b50{margin-top: 20px;margin-bottom: 50px;}
.mlr3{margin-right: 3px;margin-left: 3px;}
.mlr10{margin-right:10px;margin-left:10px;}
.mlr15{margin-left: 15px;margin-right: 15px;}
/*签到*/
.maskbox{width:100%;height:100%;background:rgba(0,0,0,0.7);display: none;position: absolute;z-index:1000;top:0;left:0;}
.calendar{background:#faca34;padding:0px 15px 0;}
.libaolist .bg-red{background:#e60012;}
.libaolist .pt2{padding-top:2px;}
.libaolist .pt3{padding-top:3px;}
.libaolist .btn-lingqu{width:70px;text-align:center;background:#e60012;color:#fff;}
.libaolist .btn-disable{width:70px;text-align:center;background:#c9c9c9;color:#fff;}
.btn-qiandao{width:160px;height:50px;background:#e60012;border:5px solid #faca34;color:#fff;font-size:18px;font-weight:bolder;border-radius:25px;text-align:center;position:relative;bottom:-20px;}
.qdbox{display:none;padding:15px 0;width:250px;border:3px solid #f82729;border-radius:10px;background:#fff;position:fixed;z-index:1001;top:50%;left:50%;margin-top:-113px;margin-left:-120px;}
.qdbox .text-green{color:#e60012;}
.btn-lottery{width:120px;text-align:center;color:#fff;background:#e60012;font-size:16px;}
.calenbox{width:100%;margin:0 auto;background:#faca34;}
.calenbox .date{width:14%;text-align:center;background:#fff;border-radius:7px;color:#6a3906;font-weight:bolder;font-size:18px;padding:10px 0;float:left;border-right:1px solid #faca34;border-bottom:1px solid #faca34;}
.singer_r_img{display:block;width:114px;height:52px;line-height:45px;background:url(images/sing_week.gif) right 2px no-repeat;vertical-align:middle;*margin-bottom:-10px;text-decoration:none;}
.singer_r_img:hover{background-position:right -53px;text-decoration:none;}
.singer_r_img span{margin-left:14px;font-size:16px;font-family:'Hiragino Sans GB','Microsoft YaHei',sans-serif !important;font-weight:700;color:#165379;}
.singer_r_img.current{background:url(images/sing_sing.gif) no-repeat 0 2px;border:0;text-decoration:none;}
.sign table{width:100%;border-collapse: collapse;border-spacing: 0;color: #a46626;font-weight: bold;font-size:20px;}
.sign th,.sign td {width: 30px;height: 40px;text-align: center;line-height: 40px;border:1px solid #faca34;border-radius:6px;background:#fff;}
.sign th {font-size: 16px;border-radius:6px;background:#fff;}
.sign td {color: #404040;vertical-align: middle;border-radius:6px;background:#fff;color: #a46626;}
.sign .on {background-color:#f0bc1a;}
.calendar_month_next,.calendar_month_prev{width: 34px;height: 40px;cursor: pointer;background:url(images/sign_arrow.png) no-repeat;}
.calendar_month_next {float:right;line-height:40px;}
.calendar_month_span {display:inline;line-height: 40px;font-size: 16px;color: #a46626;letter-spacing: 2px;font-weight: bold;}
.calendar_month_prev {float:left;line-height:40px;}
.sign_succ_calendar_title {text-align: center;border-left:1px solid #faca34;border-right:1px solid #faca34;background:#faca34;}
.sign_main{border-top:1px solid #faca34;font-family: "Microsoft YaHei",SimHei;}
/* 大转盘样式 */
.turbg{background:#e60012;}
.banner{display:block;width:90%;margin:-60px auto 0;}
.banner .turnplate{display:block;width:100%;position:relative;}
.banner .turnplate canvas.item{width:100%;}
.banner .turnplate img.pointer{position:absolute;width:31.5%;height:42.5%;left:34.6%;top:23%;}
.prizebox{background:#ffffff;margin:-3px 15px 10px;box-shadow:#d9d9d9 0 5px 20px 3px;color:#6a3906;}
.pline{height:12px;border-radius:10px;background:#ef2122;}
.ptitle{color:#6a3906;font-size:16px;padding:10px 15px;font-size:16px;}
.prizebox .ptitle .text-yellow{color:#6a3906;}
.prizebox .ptitle .text-red{color:#ff0000;}
.prizebox .prizelist{padding:0 15px;}
.prizebox .prizelistwrap{height:auto;overflow:scroll;}
.pt10lr10{padding:10px 10px 0;}
.turRule{padding:0 15px;color:#7d0000;margin-bottom:20px;}
.turRule .text-brown{color:#7d0000;}
.turRule .line{height:3px;background:#7d0000;margin-top:10px;}
.turRule .ball{display:inline-block;width:10px;height:10px;border-radius:5px;position:absolute;background:#7d0000;top:7px;}
.turRule .ball1{left:0;}
.turRule .ball2{right:0;}
.turRule dl{margin-bottom:10px;}
.turRule dl dt{margin-bottom:5px;}
.turRule dl dt strong{font-size:16px;}
.turRule dl span{display:inline-block;width:18px;height:18px;border-radius:9px;background:#faca34;text-align:center;margin-right:5px;}
.banner2{ width:100%}
.banner2 img{ width:100%}
.from{ width:90%; margin:0px auto;}
.from input {width: 100%; border: none; font-size:15px; background-color: #fff; border-radius: 0.5rem; padding: 15px 10px; margin-bottom: 5%;}
.from > div input {width: 50%; float: left;}
.from > div button {width: 42%; float: right; padding: 15px 0px;display: inline-block;}
.sorrytext{ display:none; text-align:center; color:#FFF; margin-bottom:10px}
.btn2 {border-radius: 0.5rem; background-color: #ffe335; border: none; padding: 15px 0px; color: #d31427; }
.btn3{
display:block;
border:0px;
margin:0rem auto;
width: 90%;
padding: 17px 0px;
background-color: #ef2122;
text-align: center;
font-weight: bold;
font-size:18px;
color: #ffffff;
border-radius: 0.26666667rem;
}
a.btn-lingqu2{background:#e60012;color:#fff;}
.btn-disable3{background:#c9c9c9;color:#fff;}
.banner2{ width:100%; margin:0px auto 5% auto;}
.banner2 img{ width:100%;}
.tabsbox { width:94%; margin:0px auto 5% auto;font-size: 80%; color: #fff; background-color:#FFF; padding:11% 0% 5% 0%; position:relative; }
.tabsbox2{padding:11% 0% 0% 0%;}
.tabsbox .title1 {width:160px;height:44px; line-height:44px;background:#e60012;border:5px solid #faca34;color:#fff;font-size:18px;font-weight:bolder;border-radius:25px;text-align:center;position:absolute; left:50%;top:-27px; margin-left:-85px;}
.tabsbox p { width:90%; margin:0px auto; line-height:26px; color:#a46626}
.btn4{ display:block;
border:0px;
margin:0rem auto 5% auto;
width: 94%;
height: 44px;
line-height: 44px;
background-color: #ef2122;
text-align: center;
font-weight: bold;
font-size:18px ;
color: #fff3f0;
border-radius: 4px;}
.Prize { border-bottom: solid 1px #faca34; padding:3% 2% 3% 2%; }
.Prize > div { color: #000; width:90%; margin:0 auto; line-height:40px}
最后
以上就是忧心蜜蜂为你收集整理的【微信开发】基于微信公众号的早起签到程序0 成果展示1 项目背景与需求分析2 概要设计3 开发环境配置4 编码实现5 参考文献:6 附:style.css文件代码 的全部内容,希望文章能够帮你解决【微信开发】基于微信公众号的早起签到程序0 成果展示1 项目背景与需求分析2 概要设计3 开发环境配置4 编码实现5 参考文献:6 附:style.css文件代码 所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复