87-分布式前端微信操作

微信登录和微信支付

在上一章我们初步的完成了前端的编写,接下来我们来操作微信的登录和微信的支付
微信开放平台(针对开发者和公司):
对应的微信官方文档:
https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
登录用,对应的AppID和AppSecret需要申请才可操作,需要在管理中心创建申请对应的网站,一般是必须要上线的网站:
https://open.weixin.qq.com/
后面我给出了对应的信息(用来测试用),这样就不用你自己申请了
准备工作:
网站应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统
在进行微信OAuth2.0授权登录接入之前,在微信开放平台注册开发者帐号
并拥有一个已审核通过的网站应用(或者其他应用,这里就以网站即网页为例子,后面的都说明网站)
并获得相应的AppID和AppSecret,申请微信登录且通过审核后,可开始接入流程
注册帐号和申请应用都是免费的,必须要有一个线上的网站,才能审核通过(过程还是挺麻烦的)
就可以使用微信的登录了,但是如果想使用微信支付的功能,就必须认证开发者资质(认证一次300块人民币)
名词解释:
OAuth2.0协议

87-分布式前端微信操作_第1张图片

玩抖音,发视频,抖音需要访问你相册的授权,话筒的授权,地理位置的授权等等
一句话,我不想帐号密码给第三方应用,但我还想用他们的功能,而他们的功能需要我的部分数据来协助
ok,咱玩令牌,令牌与密码的作用都可以进入系统,但是有三点差异:
1:令牌是短期的,到期会自动失效,用户自己无法修改,密码一般长期有效,用户不修改,就不会发生变化
2:令牌可以被数据所有者撤销,会立即失效,以上例而言,屋主可以随时取消快递员的令牌,密码一般不允许被他人撤销
3:令牌有权限范围,比如只能进小区的二号门,对于网络服务来说,只读令牌就比读写令牌更安全,密码一般是完整权限
上面这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全
OAuth的四种授权模式:
1:授权码模式(功能最完整,流程最严密的授权模式)
说白了,授权码模式,不再client和user之间商量授权,而是client想要被授权
所以client去找了一个和事佬大妈,大妈将client和user叫到了一起(认证服务器)
给大妈个面子,这事就这么定了,就是这样的一个过程,全程中认证服务器会发布一个认证码贯穿始终
下面的可以百度了解即可
2:密码模式
一般通过账号密码就可以访问,他们可以通过你的账号密码进行访问(保存在他们的数据库里)
3:简化模式
授权码模式的减低版,没有code授权码
4:客户端模式
最不安全的模式,基本上不需要什么操作就可以访问,他们保存了你的令牌(你给的),保存在数据库里
安全性:授权码模式 > 简化模式 > 密码模式 > 客户端模式
那么具体的信任程度,一般是:客户端模式>密码模式>简化模式>授权码模式
因为对应的第三方必须信任要高,才会放心的给出最简便,但最不安全的模式,但最好还是使用授权码模式
因为在不安全的情况下,黑客也更加容易得到你的信息
由于授权码模式总体来说是最好的,所以我们操作授权码模式
AppID:应用ID,唯一标识(身份证号)
AppSecret :应用的密钥(密码)
code:授权的临时凭证(例如:临时身份证)
access_token :接口调用凭证(例如:真正的身份证,虎符,令牌)
登录授权时序图 :

87-分布式前端微信操作_第2张图片

上面的二维码一般保存对应的地址,你可以进行测试,在百度上搜索"草料二维码生成器",进行官方网站
输入"中华人民共和国"这个内容,点击生成二维码,用微信扫一扫,就会出现该内容
当然多次生成一样的,对应的码基本都相同
当然你也可以输入网站,如http://www.baidu.com,那么会自动的进行跳转,那么为什么不会直接显示内容,而是跳转呢
主要是观察是否有": //“,其中单独的”//“,若前面没有值,那么默认”//"后面是一个网站,否则就是内容
而": //"代表整体是一个网站了(第一个开始,分割线)
具体可以自己进行测试,一般由于登录是用户自身来选择扫描的,所有基本上开发人员调用接口或者对应的地址即可,并不需要申请一些复杂东西,而不会像支付那样,需要一些申请,因为支付数额由开发人员调(可以在前端显示少的,但是底层支付了很多),比较不安全
接下来我们继续说明一下对应的模式
其中授权码模式,上面的图片中,就是一个授权码模式
code就是授权码,token就是令牌,AppID和AppSecret 是网站的对应唯一值,这样该网站可以通过令牌得到用户的信息了
而简化模式,就是没有code这个授权码
而密码模式,一般输入的密码是对应的第三方的,第三方直接通过你的密码进行授权并操作
而客户端模式,自己通过自己的信息得到令牌,然后将令牌给第三方
第三方可以说是我们要访问的客户端,或者网站等等,通过上面的描述,可以知道
的确客户端模式是最不安全的,因为他能直接操作最终的令牌
开发步骤:
vue项目安装
微信官方提供的生成二维码的js,有对应的组件,包含了对应的js
npm install vue-wxlogin 
如果不是vue的项目,可以直接引用官方提供的js文件,来生成二维码
http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js
页面引入:
在对应的前端找到Header.vue(上一章的前端项目)
这里只会给出部分代码,需要自己去进行对比修改,由于是部分,对应的代码可能不是全的
<script>

//引入
import wxlogin from 'vue-wxlogin'; 
export default {
  name: "Header",
  components:{
    wxlogin //声明引用的组件,可以使用他的标签来操作,也就是直接操作该组件
  },

 
    <el-dialog
      style="width:800px;margin:0px auto;"
      title=""
      :visible.sync="dialogFormVisible">
      <div id="loginForm">
      <el-form>
        <el-form-item>
          <h1 style="font-size:30px;color:#00B38A">拉勾h1>
        el-form-item>
        <el-form-item>
          <el-input v-model="phone" placeholder="请输入常用手机号...">el-input>
        el-form-item>
        <el-form-item>
          <el-input v-model="password" placeholder="请输入密码...">el-input>
        el-form-item>
      el-form>
      <el-button
        style="width:100%;margin:0px auto;background-color: #00B38A;font-size:20px"
        type="primary"
        @click="login">确 定el-button>
      <p>p>
      
      <img
        @click="goToLoginWX"
src="http://www.lgstatic.com/lg-passport-fed/static/pc/modules/common/img/icon-wechat@2x_68c86d1.png"
        alt=""
      />
      div>


<wxlogin id="wxLoginForm" style="display:none"  
:appid="appid" :scope="scope" :redirect_uri="redirect_uri">wxlogin>


    el-dialog>
    
data() {
    return {
      isLogin: false, // 登录状态,true:已登录,false:未登录
      userDTO:null, // 用来保存登录的用户信息
      isHasNewMessage: false, // 是否有新的推送消息
      dialogFormVisible: false, // 是否显示登录框,true:显示,false:隐藏
      phone: "", // 双向绑定表单 手机号
      password: "", // 双向绑定表单 密码
      appid:"wxd99431bbff8305a0", 
        // 应用唯一标识,在微信开放平台提交应用审核通过后获得,这里就使用我申请好的
        //注释,并不是必须写在代码上方,也是可以写在下方的,只是一般习惯于写在代码上方
      scope:"snsapi_login", // 应用授权作用域,网页应用目前仅填写snsapi_login即可,该值基本固定死的
      redirect_uri:"http://www.pinzhi365.com/wxlogin",  //重定向地址,(回调地址)
      //该地址一般保存对应的第三方地址,或者说微信服务地址(一般需要自己写)
        //他只能检测申请后的值是否正确,但可不可以访问成功却不会检测,即只要可以访问,就说明是正确的值
        //一般是我们自己编写后端使得去接受code的值
        
        //也就是说,我们扫描二维码后,我们会得到微信官方的地址,并去微信官方发送申请
        //微信官方给出提示,在你手机上显示是否确认允许登录,当你确认后
        //微信官方就会将对应的参数给你的上线的网站,由于你的网站是上线的
       //那么当前的前端项目可以访问该网站(但通常是本身,而不是这里的其他项目,并且这个参数是第一次扫描得到的,而那个地址是重定向的(虽然说是回调,但是是重定向地址,只是回调一般代表是对方造成的,而这个造成是不是由对方自身处理则无关系,这里是我们处理(重定向),而不是对方自身处理),所以就算再本地处理这个登录,也是可以的,也就说明,如果以后的回调是由服务器来访问的,那么就不能处理本地,这在大多数的其他公司提供的情况都是如此,只是这个微信登录还并没有,当然,微信支付一般也可以,以后不确定了(微信登录也是))
        //从而得到对应的参数值(申请后,会有的方法调用),在根据对应的地址(redirect_uri)使用get请求来操作(一般来说他只识别域名,否则通常会出现错误,不会给出二维码)
          //当然,对应的参数值就如我们自己访问返回的结果是一样的,所以自然可以得到,要不然,你浏览器访问url的页面数据是怎么来的呢
       //总体来说,就是允许后,相当于请求对应的地址,并给出对应的参数,我们的服务器接收后,再次自动的访问这个地址,由于是再次的访问,所以也称为重定向地址(一般这个再次访问的过程由对应的函数处理,即二维码的那个函数,当然也可以认为是重定向的操作(与转发对应的那个,并且这个时候重定向也可以认为存在参数操作,是可以给重定向操作参数的,浏览器存在这样的开关接口(一般是二维码的那个最终获取的),当然,其他关于这方面的也会存在,百度就知道了,如mvc的FlashMapManager组件))
		//从而当前前端对应的后端就得到了对应的code参数的值以及其他参数的值(一般是state参数的值)
        //所以code的得到,也是由对应根据微信的上线网站来得到的,所以基本三个参数必须要正确才可
 			//appid基本是唯一,用它来表示不同的网站,从而得到对应的报错信息是否正确(而不是每个网站得到)
        //后面的就是网站的不同信息了
        
        //上面三个参数,一般申请后,基本都会给出对应的值
        
      //第三方在一些情况下,是客户与服务之间统一操作的一方,如这里
        
        
        //这里你可能会有疑问,为什么需要code,而不直接给令牌呢
        //从浏览器出发,如果给令牌,必然会暴露,我们从上知道,我们的前端将数据给后端时是get请求
        //这样的请求必然是通过浏览器的,所以我们需要对应的临时凭证
        //实际上你允许后,页面的确发生跳转,并可以在url上看到对应的参数信息(这样容易被拦截知道信息)
        //所以为了不在浏览器上操作,一般我们会直接在程序里进行请求(如java),而不会在浏览器上进行
        //而正是因为初始数据的传递基本必须要经过浏览器,所以令牌也基本在java程序里面进行操作
        //也为了防止code给拦截,所以发送的get请求中,不止是有code
        //还有当前网站的信息,一般对应的唯一id不会暴露出去
        //若你能破解网站的对应的唯一值
        //那么也必定可以得到对应的微信用户的私隐信息
        //但账号密码基本不会得到,因为单独的授权并不能得到账号和密码
        //实际上程序的get请求虽然也会被拦截,但是相对于安全一些,因为基本是使用类似于https来操作的
        //实际上就算被拦截,但对应的账号和密码也基本上得不到的
        //那为什么不一直使用https呢(一般前端使用的是http的),既然更加安全,那么对应的操作也就会越多
        //特别是大型多次的操作,使用https会大量的使用资源,所以不会一直用
        //虽然post不那么明显,但也是明文,所以这样,就使用更加高效的get了
    };
  },
 goToLoginWX() {
      //alert("微信登录");
      //普通的登录表单隐藏
      document.getElementById("loginForm").style.display="none"

      //显示二维码
       document.getElementById("wxLoginForm").style.display="block"


    },
接下来点击对应的微信图标,那么就会出现二维码
假设:如果对应的网站Scope权限没有开通或者scope的值不正确,那么会提示对应错误,这是该网站的问题
具体可以百度,这里并没有问题
当然对应的appid的值不正确或者redirect_uri的值不正确,也会出现对应的提示错误,他们都会去验证的
所以申请时也一般只能是上线的网站或者其他应用,否则申请基本会失败,你可以自己试验一下(故意修改参数值)
总体来说,若二维码生成失败,那么就是上面三个参数的问题,只要都正确,基本才会生成出二维码
修改hosts文件(若有自己的远程服务器可以不用这样操作):
文件位置:C:\Windows\System32\drivers\etc\hosts
127.0.0.1 www.pinzhi365.com
回调默认指定的是80端口(如果设置的参数中,指定了端口,一般会报错,即redirect_uri的值不正确,即redirect_uri参数错误)
所以也只能是80端口,即别忘记将对应的tomcat的端口修改成80后,再次启动该项目(web层项目的那个服务器)
可能80端口被占用,且杀死不了,主要是因为对应的服务是系统的服务,所以我们需要以管理员的方式进入命令行窗口(cmd)
执行net stop http,选择y,等待关闭,注意需要管理员方式,否则可能操作不了
这时80端口没有占用了(对应关闭的服务并不是特别需要)
但可能重启还是会继续占用,则继续执行sc config http start= disabled,使得重启不占用
来到后端的web层项目:
引入依赖:

<dependency>
    <groupId>javax.servletgroupId>
    <artifactId>servlet-apiartifactId>
    <version>2.4version>
    <scope>providedscope>dependency>

<dependency>
    <groupId>org.apache.httpcomponentsgroupId>
    <artifactId>httpclientartifactId>
    <version>4.5.12version>
dependency>
在lagou包下,创建wx包,并在里面创建WxLoginController类:
package com.lagou.wx;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;

/**
 *
 */
@RestController
public class WxLoginController {

        @GetMapping("wxlogin")
        public Object wxlogin(HttpServletRequest request){
            String code = request.getParameter("code");
            System.out.println("【临时凭证】code="+code);
            return null;
        }

}

记得观察对应扫描包时,是否包括这个包,否则相当于没有写(不会跳转)

    <dubbo:annotation package="com.lagou.controller,com.lagou.wx"/>

这时我们进行扫描二维码(点击登录的框框中的微信图形,用手机扫描一下二维码,点击允许
在后端观看是否得到code的数据,若得到,则操作成功
注意:对应的不同的浏览器可能会有对应的问题(如谷歌,若出现问题,可以换一个浏览器)
特别是新的版本,现在一般会有,具体的问题在后面说明
至此我们最后来操作code这个临时数据(临时授权码或者临时凭证):
在程序上进行发送请求(这里是java):
在对应的web层项目上的java资源文件下,创建包commons,并在里面创建HttpClientUtil类:
package commons;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.net.URI;
import java.util.Map;

/**
 *
 */
public class HttpClientUtil {

    public static String doGet(String url) {
        String s = doGet(url,null);
        return s;

    }

    /**
     * get请求 支持request请求方式,不支持restfull方式(了解即可)
     * @param url 请求地址
     * @param param 参数
     * @return 响应字符串
     */
    public static String doGet(String url, Map<String,String> param){
        //创建httpclient对象
        CloseableHttpClient aDefault = HttpClients.createDefault();

        String s = null;
        CloseableHttpResponse response = null;
        try {
            //创建url
            URIBuilder uriBuilder = new URIBuilder(url);

            if(param!=null){
                //在url后面拼接请求参数
                //map本身并不能操作迭代器,需要先变成set集合来操作,这里是操作key当set
                for(String key:param.keySet()){
                    //当前的set,假设对应key的value值,实现拼接
                    uriBuilder.addParameter(key,param.get(key));
                }
            }
            //对应的请求总地址,上面只是操作url的值,如加上http://等等
            URI uri = uriBuilder.build();

            //创建http get请求,并将总地址给他进行请求,使得是get方式
            HttpGet httpGet = new HttpGet(uri);

            //执行请求,并得到响应的结果
            response = aDefault.execute(httpGet);

            //获取响应结果中的状态码
            int statusCode = response.getStatusLine().getStatusCode();

            System.out.println(response); 
            //我这里是:HttpResponseProxy{HTTP/1.1 200 OK [Connection: keep-alive, Content-Type: text/plain, Date: Tue, 26 Jul 2022 02:09:12 GMT, Content-Length: 79] ResponseEntityProxy{[Content-Type: text/plain,Content-Length: 79,Chunked: false]}}
            System.out.println("响应的状态:" +response.getStatusLine()); 
            //响应的状态:HTTP/1.1 200 OK
            System.out.println("响应的状态:" +statusCode); //响应的状态:200

            //200表示响应成功
            if(statusCode==200){
                //响应的内容字符串
                System.out.println(response.getEntity());
                //ResponseEntityProxy{[Content-Type: text/plain,Content-Length: 79,Chunked: false]}
                System.out.println("---");
                //得到有令牌的json字符串,只能获得一次
                s = EntityUtils.toString(response.getEntity(),"UTF-8");
                System.out.println(s);
                //我这里是:{"access_token":"59_Fvw_sDU0rnO6tuTiWrP4kONaE41_Fh9oJ5GyNLvrggiINj4WrZyIddJiwSgoaM2dnk1Ds9vshnTP-Wae3UaVzIz3V1giTrbkjU041nXjhRg","expires_in":7200,"refresh_token":"59_4V4PgKjvJ3JAGgRAH46VwqelRNNNgQ2-6FU48jtR98DgTWFWnVuFCMWgN0v5bI1DZ-VCL7a7IVP36dVddV-jkFlZVpLXPj3IW_Zys5P5Z6k","openid":"od4PTw7JYdV2XYGkJPPqF4nZvlfA","scope":"snsapi_login","unionid":"oEg8VuHZxBTgpzcrTi3rAEvwsU88"}
//若出现类似于这样的:{"errcode":40125,"errmsg":"invalid appsecret, rid: 62df4cc8-00ff426a-331a0ac7"}
                //则代表对应的secret不正确,前端写的是授权作用域,并不是对应的值
                System.out.println("---");
                System.out.println(1);
                //System.out.println(EntityUtils.toString(response.getEntity()));
                //{"access_token":"59_Fvw_sDU0rnO6tuTiWrP4kONaE41_Fh9oJ5GyNLvrggiINj4WrZyIddJiwSgoaM2dnk1Ds9vshnTP-Wae3UaVzIz3V1giTrbkjU041nXjhRg","expires_in":7200,"refresh_token":"59_4V4PgKjvJ3JAGgRAH46VwqelRNNNgQ2-6FU48jtR98DgTWFWnVuFCMWgN0v5bI1DZ-VCL7a7IVP36dVddV-jkFlZVpLXPj3IW_Zys5P5Z6k","openid":"od4PTw7JYdV2XYGkJPPqF4nZvlfA","scope":"snsapi_login","unionid":"oEg8VuHZxBTgpzcrTi3rAEvwsU88"}
                // 虽然UTF-8并没有起作用(好像的默认的),但也要防止默认识别不了中文
//注意EntityUtils.toString(response.getEntity())中EntityUtils.toString()方法只能获得一次,如果再次获得
                //就会报错,自然后面的代码就不会执行,当然也包括他的赋值,即得不到值
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                if (response != null) {
                    response.close(); //关闭对应的对象,总得也让其他人也能执行请求吧
                }
                aDefault.close(); //关闭对应的对象
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        return s;
    }
}

对应后端代码:
 @GetMapping("wxlogin")
        public Object wxlogin(HttpServletRequest request){
            //微信官方给我们的临时凭证
            String code = request.getParameter("code");
            System.out.println("【临时凭证】code="+code);

            //通过code去微信官方申请一个正式的令牌(token)
            //发出一个get请求,httpclient-封装好的操作
            //微信官方文档上获得token令牌的请求

  String getTokenByCode_url = 
      "https://api.weixin.qq.com/sns/oauth2/access_token?
      appid=wxd99431bbff8305a0&secret=60f78681d063590a469f1b297feff3c4&code="+
      code+"&grant_type=authorization_code";


            
            String tokenString = HttpClientUtil.doGet(getTokenByCode_url);

            System.out.println(tokenString);
            return null;
        }
扫描二维码允许后,就得到对应的json字符串
对应格式化之后是如下:
{
    "access_token": "59_Fvw_sDU0rnO6tuTiWrP4kONaE41_Fh9oJ5GyNLvrggiINj4WrZyIddJiwSgoaM2dnk1Ds9vshnTP-Wae3UaVzIz3V1giTrbkjU041nXjhRg", 
    "expires_in": 7200, 
    "refresh_token": "59_4V4PgKjvJ3JAGgRAH46VwqelRNNNgQ2-6FU48jtR98DgTWFWnVuFCMWgN0v5bI1DZ-VCL7a7IVP36dVddV-jkFlZVpLXPj3IW_Zys5P5Z6k", 
    "openid": "od4PTw7JYdV2XYGkJPPqF4nZvlfA", 
    "scope": "snsapi_login", 
    "unionid": "oEg8VuHZxBTgpzcrTi3rAEvwsU88"
}
查看对应的微信官方文档中,是否是正确的格式:

87-分布式前端微信操作_第3张图片

对比发现,的确是是正确的,至此得到对应的token令牌成功(字符串有令牌参数及其值,简称为token字符串),即操作成功
接下来我们通过token字符串来获得微信用户的信息
在这之前我们需要先创建一个类:
在java资源文件夹下,创建entity包,并在里面创建Token类:
package entity;

/**
 *
 */
public class Token {
      private String access_token;//接口调用凭证
      private String expires_in; //access_token接口调用凭证超时时间,单位(秒)
      private String refresh_token;//用户刷新access_token
      private String openid; //授权用户唯一标识
      private String scope; //用户授权的作用域,使用逗号(,)分隔
      private String unionid; //当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段

      public Token() {
      }

      public Token(String access_token, String expires_in, String refresh_token, String openid, 
                   String scope, String unionid) {
            this.access_token = access_token;
            this.expires_in = expires_in;
            this.refresh_token = refresh_token;
            this.openid = openid;
            this.scope = scope;
            this.unionid = unionid;
      }

      @Override
      public String toString() {
            return "Token{" +
                    "xxx'" + access_token + '\'' +
                    ", expires_in='" + expires_in + '\'' +
                    ", refresh_token='" + refresh_token + '\'' +
                    ", openid='" + openid + '\'' +
                    ", scope='" + scope + '\'' +
                    ", unionid='" + unionid + '\'' +
                    '}';
      }

      public String getAccess_token() {
            return access_token;
      }

      public void setAccess_token(String access_token) {
            this.access_token = access_token;
      }

      public String getExpires_in() {
            return expires_in;
      }

      public void setExpires_in(String expires_in) {
            this.expires_in = expires_in;
      }

      public String getRefresh_token() {
            return refresh_token;
      }

      public void setRefresh_token(String refresh_token) {
            this.refresh_token = refresh_token;
      }

      public String getOpenid() {
            return openid;
      }

      public void setOpenid(String openid) {
            this.openid = openid;
      }

      public String getScope() {
            return scope;
      }

      public void setScope(String scope) {
            this.scope = scope;
      }

      public String getUnionid() {
            return unionid;
      }

      public void setUnionid(String unionid) {
            this.unionid = unionid;
      }
}

创建User类:
package entity;

/**
 *
 */
public class User {
      private String openid;//普通用户的标识,对当前开发者帐号唯一
      private String nickname;//普通用户昵称
      private String sex;//普通用户性别,1为男性,2为女性
      private String province;//普通用户个人资料填写的省份
      private String city;//普通用户个人资料填写的城市
      private String country;//国家,如中国为CN
      private String headimgurl;//用户头像
    //最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像)
    //用户没有头像时该项为空
      private String privilege;//用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
      private String unionid;//用户统一标识,针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的

    public User() {
    }

    public User(String openid, String nickname, String sex, String province, String city, String 
                country, String headimgurl, String privilege, String unionid) {
        this.openid = openid;
        this.nickname = nickname;
        this.sex = sex;
        this.province = province;
        this.city = city;
        this.country = country;
        this.headimgurl = headimgurl;
        this.privilege = privilege;
        this.unionid = unionid;
    }

    @Override
    public String toString() {
        return "User{" +
                "openid='" + openid + '\'' +
                ", nickname='" + nickname + '\'' +
                ", sex='" + sex + '\'' +
                ", province='" + province + '\'' +
                ", city='" + city + '\'' +
                ", country='" + country + '\'' +
                ", headimgurl='" + headimgurl + '\'' +
                ", privilege='" + privilege + '\'' +
                ", unionid='" + unionid + '\'' +
                '}';
    }

    public String getOpenid() {
        return openid;
    }

    public void setOpenid(String openid) {
        this.openid = openid;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getHeadimgurl() {
        return headimgurl;
    }

    public void setHeadimgurl(String headimgurl) {
        this.headimgurl = headimgurl;
    }

    public String getPrivilege() {
        return privilege;
    }

    public void setPrivilege(String privilege) {
        this.privilege = privilege;
    }

    public String getUnionid() {
        return unionid;
    }

    public void setUnionid(String unionid) {
        this.unionid = unionid;
    }
}

接下来继续修改对应的后端:
   @GetMapping("wxlogin")
        public Object wxlogin(HttpServletRequest request){
            //微信官方给我们的临时凭证
            String code = request.getParameter("code");
            System.out.println("【临时凭证】code="+code);

            //通过code去微信官方申请一个正式的令牌(token)
            //发出一个get请求,httpclient-封装好的操作
            //微信官方文档上获得token令牌的请求

            String getTokenByCode_url = 
                "https://api.weixin.qq.com/sns/oauth2/access_token?
                appid=wxd99431bbff8305a0&secret=60f78681d063590a469f1b297feff3c4&code="+
                code+"&grant_type=authorization_code";

            String tokenString = HttpClientUtil.doGet(getTokenByCode_url);

            System.out.println(tokenString);

            //将json格式的该token字符串转换成实体对象,方便存和取
            Token token = JSON.parseObject(tokenString, Token.class);

            //通过token,去微信官方获取用户的信息
String getUserByToken =
    "https://api.weixin.qq.com/sns/userinfo?access_token="+token.getAccess_token()+
    "&openid="+token.getOpenid();

            System.out.println("----------------------");
            String UserString = HttpClientUtil.doGet(getUserByToken);
            System.out.println("UserString = " + UserString);
            //UserString = {"openid":"od4PTw7JYdV2XYGkJPPqF4nZvlfA","nickname":"king","sex":0,"language":"","city":"","province":"","country":"","headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/YmfJ8sPH0ibZ6s8gl7p4ZOz4H6us5w3cwTNVBQ84YicdLt24NHbxc1vwFJZuePz7nicAKhQicQZuOSMI8Piah73MIxA\/132","privilege":[],"unionid":"oEg8VuHZxBTgpzcrTi3rAEvwsU88"}
//发先对应的值不同了,因为请求不同,虽然具体的显示信息打印是相同的
            //但对应的信息还是需要EntityUtils.toString()来根据参数获得信息
            //该json字符串包含微信的信息,简称用户字符串
            
            //将json格式的用户字符串转换成实体对象,方便存和取
            //记得User类的对应的包是对应的
            User user = JSON.parseObject(UserString, User.class);
            System.out.println("微信的用户昵称 = " + user.getNickname());
            System.out.println("微信的用户头像 = " + user.getHeadimgurl());
            //项目流程
            //由于我们登录需要手机号和密码
            //所以我们需要将用户字符串中的数据当作手机号和密码进行登录(没有会自动注册的)
            //但要注意,一般需要是唯一的标识,我们通过用户字符串中,可以发现unionid参数一般是用户的唯一
            //你可以继续扫描,发现无论扫描多少次,该值一直不变,换个手机后(不同的微信用户)
            //该值就改变了,也就是说一个微信用户对应的该值是唯一的
            //那么就可以使用他来当作手机号和密码(两者都是这个)
            //为什么密码也要是这个呢,因为微信登录绑定的是微信
            //我们必须在有限的数据下进行登录且要安全,那么一般都会操作这个unionid参数
            //这样也可以防止,扫描码时,不会出现登录失败的情况
            //因为该值在就可以登录,也不会出现密码不正确的情况
               //主要是因为基本不是用户来输入的,所以如果操作我们自己生成的,与用户习惯不好,导致体验不好
            //至此通过分析可知,我们的手机号和密码用unionid参数值
            //但是一般我们需要对应的微信用户昵称和微信用户头像
            //这些在对应的登录后端中并没有操作(注册的操作),所以我们需要修改对应的注册逻辑(后面操作)
            return null;
        }
至此微信用户的信息就得到了,比如说"nickname":“king”,其中king就是我微信的名称
当然一些值是0或者是""的,则是我们微信并没有设置的,或者是默认的,也有可能需要其他方式才可获得,但这里并不需要理会
接下来我们修改对应的注册逻辑:
dao层:
  /**
     * 用户注册
     *
     * @param phone 手机号
     * @param password 密码
     * @param nickname 昵称
     * @param headimg 头像
     * @return 受影响的行数,一般是1
     */
    Integer register(@Param("phone") String phone, @Param("password") String 
                     Password,@Param("nickname") String nickname,@Param("headimg") String headimg);

service层:
  /**
     * 用户注册
     *
     * @param phone 手机号
     * @param password 密码
     * @param nickname 昵称
     * @param headimg 头像
     * @return 受影响的行数,一般是1
     */
    Integer register(String phone, String Password,String nickname,String headimg);

  @Override
    public Integer register(String phone, String Password,String nickname,String headimg) {
        Integer register = userDao.register(phone, Password,nickname,headimg);

        return register;
    }
web层:
   /**
     * 用户注册
     *
     * @param phone 手机号
     * @param password 密码
     * @param nickname 昵称
     * @param headimg 头像
     * @return 受影响的行数,一般是1
     */
    Intege
对应的UserController类的ogin方法:
 @GetMapping("login")
//参数也进行了修改
    public UserDTO login(String phone, String password,String nickname,String headimg) {
        UserDTO userDTO = new UserDTO<>();
        System.out.println(phone);
        System.out.println(password);
        System.out.println(nickname);
        System.out.println(headimg);

        User login = null;
        //检测手机号是否注册
        Integer integer = userService.checkPhone(phone);
        if (integer == 0) {
            //未注册,注册并登录
            userService.register(phone, password,nickname,headimg); //这里进行了修改
            userDTO.setMessage("手机号尚未注册,系统已帮您自动注册,请牢记密码!");
            login = userService.login(phone, password);
        } else {
            login = userService.login(phone, password);
            if (login != null) {
                userDTO.setState(200); //规定200表示成功
                userDTO.setMessage("登录成功");
            } else {
                userDTO.setState(300); //规定300表示失败
                userDTO.setMessage("登录失败");
            }

        }
        userDTO.setContent(login);

        System.out.println(login);
        return userDTO;

    }
改变后,对应的xml配置文件也进行改变:
<insert id="register" parameterType="string">
        insert into user
            (name,portrait,phone,password,create_time,update_time)
        values
            (#{nickname},#{headimg},#{phone},#{password},sysdate(),sysdate())

     
    insert>
至此我们运行对应的测试类TestUser里的register方法:
  @Test
    public void register(){
        Integer integer = userDao.register("11544","123123","1","2");
        System.out.println(integer); //0:未注册,大于0(通常为1):已注册
    }
若数据库有对应的数据,则修改成功
对应的部分前端代码(Header.vue组件里面的代码):
 return this.axios.get("http://localhost:8002/user/login",{
        params:{
          phone:this.phone,
          password:this.password,
          nickname:"",
          headimg:"",
        }
      }).then(res =>{
        
至此测试登录,随便写一个没有的,登录后,会自动注册,并登录,然后去数据库看看是否有对应数据,若有,则操作成功
我们在对应的wxlogin方法里添加如下代码:
  System.out.println("微信的unionid参数值:" + user.getUnionid());
            //跳转到当前项目开始的这个路径,也就能找到了
return "user/login?phone="+user.getUnionid()+"&password="
    +user.getUnionid()+"&nickname="+user.getNickname()+"&headimg="+user.getHeadimgurl();
     //记得在最后添加
至此启动对应的项目,并在前端扫描二维码进行登录,若返回了对应的数据,且数据库里也有对应数据,则登录成功
且你会发现,对应的url那里前端进行了访问,但后端的对应的请求并没有显示出来
也就是不依靠浏览器的显示了,安全性的确提高
但是对应的使用return会到其他的方法里面去,实际上我们操作该方法后,需要跳转到原来的前端页面
至此对应的wxlogin方法要进行改变,但对应的数据并不会得到,所以我们需要修改WxLoginController类:
package com.lagou.wx;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.lagou.entity.UserDTO;
import com.lagou.service.UserService;
import commons.HttpClientUtil;
import entity.Token;
import entity.User;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *
 */
@RestController
//@Controller
public class WxLoginController {

    @Reference //远程调用
    private UserService userService;

    private UserDTO userDTO = null; //是否用微信登录成功,若是null,则表示尚未登录


    @GetMapping("wxlogin")
    public UserDTO wxlogin(HttpServletRequest request, HttpServletResponse response) throws 
        IOException {
        //微信官方给我们的临时凭证
        String code = request.getParameter("code");
        System.out.println("【临时凭证】code=" + code);

        //通过code去微信官方申请一个正式的令牌(token)
        //发出一个get请求,httpclient-封装好的操作
        //微信官方文档上获得token令牌的请求

        String getTokenByCode_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wxd99431bbff8305a0&secret=60f78681d063590a469f1b297feff3c4&code=" + code + "&grant_type=authorization_code";

        String tokenString = HttpClientUtil.doGet(getTokenByCode_url);

        System.out.println(tokenString);

        //将json格式的该token字符串转换成实体对象,方便存和取
        Token token = JSON.parseObject(tokenString, Token.class);

        //通过token,去微信官方获取用户的信息
        String getUserByToken = "https://api.weixin.qq.com/sns/userinfo?access_token=" + 
            token.getAccess_token() + "&openid=" + token.getOpenid();

        System.out.println("----------------------");
        String UserString = HttpClientUtil.doGet(getUserByToken);
        System.out.println("UserString = " + UserString);
        //UserString = {"openid":"od4PTw7JYdV2XYGkJPPqF4nZvlfA","nickname":"king","sex":0,"language":"","city":"","province":"","country":"","headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/YmfJ8sPH0ibZ6s8gl7p4ZOz4H6us5w3cwTNVBQ84YicdLt24NHbxc1vwFJZuePz7nicAKhQicQZuOSMI8Piah73MIxA\/132","privilege":[],"unionid":"oEg8VuHZxBTgpzcrTi3rAEvwsU88"}
        //发先对应的值不同了,因为请求不同,虽然具体的显示信息打印是相同的
        //但对应的信息还是需要EntityUtils.toString()来根据参数获得信息
        //该json字符串包含微信的信息,简称用户字符串


        //将json格式的用户字符串转换成实体对象,方便存和取
        //记得User类的对应的包是对应的
        User user = JSON.parseObject(UserString, User.class);
        System.out.println("微信的用户昵称 = " + user.getNickname());
        System.out.println("微信的用户头像 = " + user.getHeadimgurl());

        //项目流程
        //由于我们登录需要手机号和密码
        //所以我们需要将用户字符串中的数据当作手机号和密码进行登录(没有会自动注册的)
        //但要注意,一般需要是唯一的标识,我们通过用户字符串中,可以发现unionid参数一般是用户的唯一
        //你可以继续扫描,发现无论扫描多少次,该值一直不变,换个手机后(不同的微信用户)
        //该值就改变了,也就是说一个微信用户对应的该值是唯一的
        //那么就可以使用他来当作手机号和密码(两者都是这个)
        //为什么密码也要是这个呢,因为微信登录绑定的是微信
        //我们必须在有限的数据下进行登录且要安全,那么一般都会操作这个unionid参数
        //这样也可以防止,扫描码时,不会出现登录失败的情况,因为该值在就可以登录,而不会出现密码不正确的情况
        //至此通过分析可知,我们的手机号和密码用unionid参数值
        //但是一般我们需要对应的微信用户昵称和微信用户头像
        //这些在对应的登录后端中并没有操作(注册的操作),所以我们需要修改对应的注册逻辑

        System.out.println("微信的unionid参数值:" + user.getUnionid());
        //跳转到当前项目开始的这个路径,也就能找到了
        /*
        return "user/login?phone="+user.getUnionid()+"
        &password="+user.getUnionid()+"
            &nickname="+user.getNickname()+"&headimg="+user.getHeadimgurl();
            */

        userDTO = new UserDTO<>(); //不要自己声明,因为优先使用局部变量,就近原则

        com.lagou.entity.User login = null;
        //检测手机号是否注册
        Integer integer = userService.checkPhone(user.getUnionid());
        if (integer == 0) {
            //未注册,注册并登录
            userService.register(user.getUnionid(), user.getUnionid(), user.getNickname(), 
                                 user.getHeadimgurl());
            userDTO.setMessage("手机号尚未注册,系统已帮您自动注册,请牢记密码!");
            login = userService.login(user.getUnionid(), user.getUnionid());
        } else {
            login = userService.login(user.getUnionid(), user.getUnionid());
            if (login != null) {
                userDTO.setState(200); //规定200表示成功
                userDTO.setMessage("登录成功");
            } else {
                userDTO.setState(300); //规定300表示失败
                userDTO.setMessage("登录失败");
            }

        }
        userDTO.setContent(login);


        response.sendRedirect("http://localhost:8080/");
        //这样我们又回到的前端那个页面,但是由于是重定向,即对应的返回的数据并没有返回
        //不会经过其他操作,转发也是如此
        //即对应的数据并没有过去
        return null;
    }

    @GetMapping("checkWXStatus")
    public UserDTO checkWXStatus() {
        return userDTO;
    }
    
      @GetMapping("logout")
    public Object logout() { //若返回类型为void,则默认是空的值,相当于null(单独的,而不是字符串)和""
        userDTO = null;
        return null;
    }

}

对应的前端Header.vue:
  created(){
    
    //当刷新页面,组件创建成功之后,立刻检测本地储存中是否存在用户对象
    console.log(JSON.parse(localStorage.getItem("user")));
    //将得到的字符串变成对应的对象,也可以说是JSON对象,使得可以操作,即"对象.key"的获取值
     this.userDTO =JSON.parse(localStorage.getItem("user")); 
     console.log(this.userDTO)
     if( this.userDTO != null){
         this.isLogin = true //更新登录状态,表示已登录
     }else{
       //检测微信是否登录过
        return this.axios.get("http://localhost:80/checkWXStatus").then(res =>{
          console.log(22)
       console.log(res)
        this.userDTO = res.data 
        //这里我们得到了对应扫描二维码的数据,对应的操作基本在login方法里面,这里我们直接重新调用即可
        //反正已经注册了
        if(this.userDTO!=""){
        this.phone = this.userDTO.content.phone
        this.password = this.userDTO.content.password
        //有了账号密码,就直接走登录
        this.login()
            //若不执行上面的this.login(),则需要执行下面的代码,很明显
            //代码很多(实际上就算对应方法里面的操作),但也减少了一次后端的访问
            //有时候代码虽多,但性能更好,在好的情况下,可以牺牲性能来更好的维护
            //if(this.userDTO.content!=null){
        // this.isLogin = true //更新登录状态
        // //将登录成功的信息进行保存(本地),使得刷新界面时,可以继续得到信息,基本只与浏览器有关
        // localStorage.setItem("user",JSON.stringify(this.userDTO)) //将字符串数据变过去
            //}
        // //初始化
        // this.phone = "";
        // this.password="";
      
        // this.dialogFormVisible = false; // 不显示登录框

        // this.$router.push("/" );
        // window.location.reload()
        }
        //虽然可以不走登录,复制登录里面的操作即可,因为this.userDTO有值了
      }).catch(err =>{
        this.$message.error("登录失败")
      })
     }
  },
 logout(){//登出
localStorage.setItem("user",null)
 this.isLogin = false

  return this.axios.get("http://localhost:80/logout").then(res =>{
       this.$router.push("/");
        window.location.reload()
 alert("已登出")
      }).catch(err =>{
        this.$message.error("登出失败")
      })
      alert(1)

 
    }
我们可以发现,对应的端口都是80(虽然也可以是其他端口),但最好相同,因为他们都是得到和设置值
需要在一个服务器里面操作(可以说,一个端口代表一个服务器的运行)
至此我们扫描二维码进行操作,若有对应的信息(登录那里),则微信登录完成
在前面应该说过,微信的扫描二维码并允许后,可能会出现浏览器的问题(一般是谷歌)
解决二维码在谷歌浏览器的bug :
通过官方js:http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js
可以知道,对应的二维码是放在iframe标签下的,但是一般新版本的谷歌可能并没有设置跨域问题
旧版的谷歌却可以(但不一定)也就是如下:

87-分布式前端微信操作_第4张图片

为什么不能让他跨域呢,因为是安全问题,iframe可以内嵌其他网页,而对应的网页我们却无法控制
也就是说,若该网页是恶意的网站,那么对应用户来说是非常危险的,所以一般会让他不能跨域
sandbox包含的属性及作用:

87-分布式前端微信操作_第5张图片

加上 sandbox="allow-scripts allow-top-navigation allow-same-origin"属性
即可解决官方js:http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js
由于基本无法修改微信服务器上的js文件,所以我们将js代码放在本地并进行修改(复制上面官方的js):
created(){
    //下面的代码记得放在最前面,防止对应的return使得他没有操作
!(function (a, b, c) {
        function d(a) {
          var c = "default";
          a.self_redirect === !0
            ? (c = "true")
            : a.self_redirect === !1 && (c = "false");
          var d = b.createElement("iframe"),
            e =
              "https://open.weixin.qq.com/connect/qrconnect?appid=" +
              a.appid +
              "&scope=" +
              a.scope +
              "&redirect_uri=" +
              a.redirect_uri +
              "&state=" +
              a.state +
              "&login_type=jssdk&self_redirect=" +
              c +
              "&styletype=" +
              (a.styletype || "") +
              "&sizetype=" +
              (a.sizetype || "") +
              "&bgcolor=" +
              (a.bgcolor || "") +
              "&rst=" +
              (a.rst || "");
          (e += a.style ? "&style=" + a.style : ""),
            (e += a.href ? "&href=" + a.href : ""),
            (d.src = e),
            (d.frameBorder = "0"),
            (d.allowTransparency = "true"),
            // 允许多种请求,下面的就是我加上的,用来解决浏览器中对应的跨域(iframe)问题
            (d.sandbox = "allow-scripts allow-top-navigation allow-same-origin"), 
            (d.scrolling = "no"),
            (d.width = "300px"),
            (d.height = "400px");
          var f = b.getElementById(a.id);
          (f.innerHTML = ""), f.appendChild(d);
        }
        a.WxLogin = d;
      })(window, document);
}
我们直接将该代码放到对应的created()的钩子函数里面,使得操作我们自己的js,而不使用官方的
将对应的引入的注释:
//引入
//import wxlogin from 'vue-wxlogin'; 
组件也不同声明了:
components:{
    //wxlogin
  },
对应的html也注释掉:


实际上对应的组件操作也是封装了二维码js的执行,我们可以直接自己定义来保存二维码:
<div id="wxLoginForm">div> 
接下来我们找到对应的方法,因为组件操作了对应二维码的生成,和一些属性
但我们自己定义的,需要自己进行操作,这里当然是默认没有的,因为什么都没有操作,看如下代码:
 goToLoginWX() {
      //alert("微信登录");
      //普通的登录表单隐藏
  
      document.getElementById("loginForm").style.display="none"

      //显示二维码,这一步可以不需要,因为对应的并没有设置为none
      //  document.getElementById("wxLoginForm").style.display="block"

      //生成二维码
      //待dom更新后再用二维码渲染内容
      this.$nextTick(function(){ 
        //好像这个this.$nextTick并不需要写,直接执行 this.createCode()也可以
        //微信登录可以不用,但微信支付一般要用,因为容易先出来
        //主要是为了使得二维码后出来,而不会抢先出来,使得出现问题(即会报错)
        //若报错,则写上
        this.createCode(); 
        // 所以直接调用可能会报错(先出来):TypeError: Cannot read property 'appendChild' of null
        //这时就需要对应的this.$nextTick
     
     
  });
    },
    //生成二维码
  createCode(){
    
 
     //由于对应的官方js已经操作其函数,也就是使得WxLogin=对应的自己写的函数
     //所以我们直接创建其对象赋值即可,在创建对象的时候是会执行他的内容的,也就是执行方法进行赋值而已
     //其中返回的是基本是对应的方法的变量
     //所以说,在创建对象时,就执行的对应的方法
    var obj = new WxLogin({
      id:"wxLoginForm",//挂载点,二维码的容器
      appid:"wxd99431bbff8305a0", 
      scope:"snsapi_login", 
      redirect_uri:"http://www.pinzhi365.com/wxlogin", 
      })

      //所以总体来说,就是要使得对应的参数,如上面的
      /*
      appid:"wxd99431bbff8305a0", 
      scope:"snsapi_login", 
      redirect_uri:"http://www.pinzhi365.com/wxlogin", 
      */
     //给对应的方法里面,这时只要你进行扫描
    //就会发送二维码里面的请求(其中对应 function d(a)方法里面有一个请求)
     //即https://open.weixin.qq.com/connect/qrconnect?appid=,后面省略
    //他就是对应二维码的显示
    //因为d.src = e,e就是对应的请求地址,不同的e对应的二维码显示可能不同,如有对应的样式操作
    //而扫描的就是二维码自带的请求
     //而我们操作组件时,里面封装了对应js,和对应的参数,一般需要我们去绑定,然后扫描也会去请求
    //所以是一样的操作,只是方式不同而已,只是组件帮我们封装好了的
    
  },
至此,浏览器的问题就解决了,我们发现,对应的js实际上就是直接设置iframe的属性
可以得知,浏览器可能会给该标签默认设置其他不跨域的属性,所以需要我们手动设置,使得不操作默认
若我们需要修改二维码的样式,可以添加如下代码:
.impowerBox .qrcode {width: 200px;}
.impowerBox .title {display: none;}
.impowerBox .info {width: 200px;}
.status_icon {display: none}cs
.impowerBox .status {text-align: center;}
但是实际上要操作该样式,在对应的参数下面基本不能这样的写,因为{}不能对json格式不对,如:
href:"
.impowerBox .qrcode {width: 200px;} //二维码的宽度
.impowerBox .title {display: none;}
.impowerBox .info {width: 200px;} //二维码的高度
.status_icon {display: none}cs
.impowerBox .status {text-align: center;}
"
//你将他放在前端json里面,会发现,全部看成一个字符串,并没有css的操作
那么如何使得可以操作对应的样式呢,我们可以在对应的WxLogin对象中(方法里的操作)
直接传递href值,由于该值会被请求的地址服务器解析(二维码的显示的请求)
使得进行对应的操作显示,而该解析我们需要对应的加密,这是规定的,因为对应地址服务的解析时,会解密进行操作
我们用站长工具对样式代码进行base64加密:http://tool.chinaz.com/Tools/Base64.aspx
//加密后的数据是
LmltcG93ZXJCb3ggLnFyY29kZSB7d2lkdGg6IDIwMHB4O30NCi5pbXBvd2VyQm94IC50aXRsZSB7ZGlzcGxheTogbm9uZTt9DQouaW1wb3dlckJveCAuaW5mbyB7d2lkdGg6IDIwMHB4O30NCi5zdGF0dXNfaWNvbiB7ZGlzcGxheTogbm9uZX1jcw0KLmltcG93ZXJCb3ggLnN0YXR1cyB7dGV4dC1hbGlnbjogY2VudGVyO30=
这时,对应的js代码是:
 var obj = new WxLogin({
      id:"wxLoginForm",//挂载点,二维码的容器
      appid:"wxd99431bbff8305a0", 
      scope:"snsapi_login", 
      redirect_uri:"http://www.pinzhi365.com/wxlogin", 
      href: "data:text/css;base64,LmltcG93ZXJCb3ggLnFyY29kZSB7d2lkdGg6IDIwMHB4O30NCi5pbXBvd2VyQm94IC50aXRsZSB7ZGlzcGxheTogbm9uZTt9DQouaW1wb3dlckJveCAuaW5mbyB7d2lkdGg6IDIwMHB4O30NCi5zdGF0dXNfaWNvbiB7ZGlzcGxheTogbm9uZX1jcw0KLmltcG93ZXJCb3ggLnN0YXR1cyB7dGV4dC1hbGlnbjogY2VudGVyO30=" //加载修改二维码的css样式
      })
 
 
 //其中data:text/css;base64规定了对应的解密方式,基本不能改变
 
   //LmltcG93ZXJCb3ggLnFyY29kZSB7d2lkdGg6IDMwMHB4O30NCi5pbXBvd2VyQm94IC50aXRsZSB7ZGlzcGxheTogbm9uZTt9DQouaW1wb3dlckJveCAuaW5mbyB7d2lkdGg6IDMwMHB4O30NCi5zdGF0dXNfaWNvbiB7ZGlzcGxheTogbm9uZX1jcw0KLmltcG93ZXJCb3ggLnN0YXR1cyB7dGV4dC1hbGlnbjogY2VudGVyO30= 宽300,高300
     //LmltcG93ZXJCb3ggLnFyY29kZSB7d2lkdGg6IDIwMHB4O30NCi5pbXBvd2VyQm94IC50aXRsZSB7ZGlzcGxheTogbm9uZTt9DQouaW1wb3dlckJveCAuaW5mbyB7d2lkdGg6IDIwMHB4O30NCi5zdGF0dXNfaWNvbiB7ZGlzcGxheTogbm9uZX1jcw0KLmltcG93ZXJCb3ggLnN0YXR1cyB7dGV4dC1hbGlnbjogY2VudGVyO30= 宽200,高200
//LmltcG93ZXJCb3ggLnFyY29kZSB7d2lkdGg6ID,至此后面的只有部分不同,即Mw和Iw(其余基本相同)
 //防止你以为他是一样的,所以这里给出了不同
这时我们再次看看二维码,发现二维码变小的,至此样式也操作成功
我们操作完了微信的登录,接下来我们操作微信的支付:
创建二维码:
安装 qrcodejs2 (注意:安装的是qrcodejs2,不要安装qrcode —> 会报错),主要报错原因是
可能是因为二维码生成的错误,即生成不了二维码,即后面的new QRCode这个地方报错,类似于(相当于)某些语法错误一样
比如 alert(u),这个u不存在,也会使得报错,而这样的报错,自然会到catch里面执行
即如果操作了qrcode ,那么后面的代码就会执行this.$message.error(“生成二维码失败!”);
当然,你安装后不使用也可以,具体可以自己测试,只要是导入了qrcodejs2即可
即import QRCode from ‘qrcodejs2’;即可,所以qrcode 可以安装,但没有必要,因为这里并没有使用他
npm install qrcodejs2 --save #之前的是操作登录的,这里是操作支付
在Course.vue组件的页面中引入:


<el-dialog :visible.sync="dialogFormVisible" style="width:800px;margin:0px auto;">
  <h1 style="font-size:30px;color:#00B38A">微信扫一扫支付h1>
  <div id="qrcode" style="width:210px;margin:20px auto;">div>
el-dialog>
 data() {
    return {
      comment:null, //待发表的留言内容
      activeName: "intro",
      course:null,
      totalLessons:0, //本门课程的总节数
      commentList:null, //所有留言
      isLogin:false, //false,未登录
      isBuy:false,//未购买
      user:null, //当前用户,{}对象可以","结尾
      myCourseList:[], //当前用户购买过的所有课程
      dialogFormVisible:false //这里操作是否显示弹出的框框
    };

<script>
import Header from "./Header/Header"; //顶部登录条
import Footer from "./Footer/index"; //顶部登录条
import QRCode from 'qrcodejs2'; // 引入qrcodejs
export default {

  name: "Course",
  components: {
    Header,
    Footer,
    QRCode //声明组件
  },
这里有个问题,前面的操作问题
if(this.user != null){
  this.isLogin = true //已登陆

if(this.course!=null){ 
  this.getMyCourseList() //查询登录用户购买的所有课程
}
this.getComment(); 
    //这里放在这个判断里面,因为其中的操作会用到user,若user没有值,那么可能会使得当前页面操作不了
}
 buy(courseid) {
  
      //alert("购买第【" + courseid + "】门课程成功,加油!");
      this.dialogFormVisible = true; //显示提示框框
              //一般不能有对应的当前所在标签的报错,如上面的user没有值,那么可能会使得不会显示出来
     
     //待dom更新后再用二维码渲染内容(使得二维码后出来,而不是先出来)
      this.$nextTick(function(){ 
      this.createCode()  
          // 直接调用可能会报错(先出来):TypeError: Cannot read property 'appendChild' of null
      });
    },
    //生成二维码
    createCode(){
       document.getElementById("qrcode").innerHTML = ""; 
        //防止多次点击时,对应的前面的二维码一直存在,使得出现多个二维码
        //写在最前面,使得最先删除对应的信息
      //QRCode(dom元素的id,二维码的属性参数)
let qrcode = new QRCode("qrcode",{
  width:200, //记得不能加上px,因为他默认给你加上了,否则会报错,因为没有200pxpx
  height:200,
  text:"我爱你中国" //二维码中包含的信息
}); 
                //qrcode就是当前有id="qrcode"的标签
    },
至此,点击立即购买,发现,对应的二维码出来了,用微信扫描,就会出现设置的信息"我爱你中国",至此二维码显示操作完毕
准备工作:
名词介绍:

87-分布式前端微信操作_第6张图片

如果获得这些信息,需要注册认证公众号,费用300元/次
获取认证的流程 :
注册公众号(类型:服务号) ,或者微信开发平台操作的网站(如企业),这里就操作公众号了
根据营业执照类型选择以下主体注册:个体工商户 | 企业/公司 | 政府 | 媒体 | 其他类型
认证公众号:
公众号认证后才基本可以去申请微信支付:300元/次
提交材料申请微信支付 :
登录公众平台,左侧菜单【微信支付】,开始填写资料等待审核,审核时间1~5工作日这里需要提交的资料有营业执照
开户成功,登录商户平台进行验证:
资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证
在线签署协议:
本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效
查看自己的公众号的参数 :
测试的数据,一般不能操作,下面只是用来看的,让你知道需要哪些数据
即当你使用下面的信息时,一个返回的数据中会说明签名错误,因为没有对应数据(如appid,企业一般是corpid,对应于appid)
他们一般是微信公众号和企业微信,都是微信,因为使用微信支付总得是微信吧
所以到这里,后面的可以进行了解,当你有对应企业的这些数据时或者微信公众号的这些数据时(自己进行申请)
可以回到这里再次查看进行操作:
public class PayConfig {    
    //企业公众号ID
    public static String appid = "wx8397f8696b538317";    
    // 财付通平台的商户帐号
    public static String partner = "1473426802";    
    // 财付通平台的商户密钥
    public static String partnerKey = "8A627A4578ACE384017C997F12D68B23";    
    // 回调URL
    public static String notifyurl = "http://a31ef7db.ngrok.io/WeChatPay/WeChatPayNotify";
}
支付流程:

87-分布式前端微信操作_第7张图片

工具介绍:
SDK(软开开发工具包,也有对应的其他介绍,如API列表):
https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1

87-分布式前端微信操作_第8张图片

如果不是maven项目,一般需要下载对应的jar包,若是maven项目,则引入依赖即可
对应的依赖:
<dependency>
    
    <groupId>com.github.wxpaygroupId>
    <artifactId>wxpay-sdkartifactId>
    <version>0.0.3version>
    dependency>
主要使用sdk中的三个功能:
获取随机字符串(生成订单编号):
WXPayUtil.generateNonceStr();
将map转换成xml字符串(自动添加签名,对应给微信支付的信息一般需要是xml信息):
WXPayUtil.generateSignedXml(map,partnerKey);
将xml字符串转换整map:
WXPayUtil.xmlToMap(result);
JFinal 框架:
JFinal 是基于Java 语言的极速 web 开发框架
其核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展
基本可以取代HttpClient(拥有后面的特殊的请求,比如加上了对应的xml格式的字符串,该字符串通常是用来作为参数的,所以用法多点),所以可以不使用HttpClient,而只使用JFinal (前面的就可以进行改变,当然这里就不改变了)
对应的依赖:
<dependency>
    
    <groupId>com.jfinalgroupId>
    <artifactId>jfinalartifactId>
    <version>3.5version>
    dependency>
构建二维码 :
外面需要将对应的支付连接放在二维码里面
在Course.vue组件里面:
//生成二维码
    createCode(){

         document.getElementById("qrcode").innerHTML = ""; 
        //防止多次点击时,对应的前面的二维码一直存在,使得出现多个二维码
        //写在最前面,使得最先删除对应的信息,不要放在对应的axios里面
        //防止,他请求失败或者缓慢时,对应的信息没有及时删除,而出现二维码
        
//去获取支付连接
 this.axios.get("http://localhost:80/createCode",{
   params:{
     courseid:this.course.id,
     coursename:  this.course.courseName,//总不能显示对应id的数据吧,用这个来显示
     price:this.course.discounts 
       //测试的时候,最好设置为1,代表1分钱,总不能测试是使用真实数据吧
       //除非你是土豪,或者就是你的公众号和企业
   }
 }).then(res => {
console.log(res)
    
   //QRCode(dom元素的id,二维码的属性参数)
  let qrcode = new QRCode("qrcode",{
  width:200,
  height:200,
  text:res  //将返回的数据放入到二维码中
      //一般直接扫描得到的对象数据,手机上基本是显示不了的,但我们并不是操作显示
  //二维码中包含的信息
}); //qrcode就是当前有id="qrcode"的标签
    }).catch(err =>{
      this.$message.error("生成二维码失败")
    })

   
    },
在web层项目下的commons包下创建实体类PayConfig:
package commons;

/**
 *
 */
public class PayConfig {
//下面的数据是错误的,因为并不会给出真实的数据,我操作时,使用了正确的数据
    
    //企业公众号ID
    public static String appid = "wx8397f8696b538317";
    // 财付通平台的商户帐号
    public static String partner = "1473426802";
    // 财付通平台的商户密钥
    public static String partnerKey = "8A627A4578ACE384017C997F12D68B23";
    // 回调URL
    public static String notifyurl = "http://a31ef7db.ngrok.io/WeChatPay/WeChatPayNotify";

}

该类先放在这里,后面有可能会再次进行操作(如修改数值,虽然并不会)
在对应的web层项目中的wx包下,创建WxPayController类:
package com.lagou.wx;

import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayUtil;
import com.jfinal.kit.HttpKit;
import commons.PayConfig;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 *
 */
@RestController
public class WxPayController {

    @GetMapping("createCode")
    public Object createCode(String courseid,String coursename,String price) throws Exception {

        //注意:一般对应map的value值不能是null,否则报错,其他的map值也是如此,当然""可以,但最好不要
        //因为信息最好按照规定来,否则可能容易会被盗取或者发生错误等等
        
        //编写商户信息写入到map中
 //在这之前,查看对应的https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1地址
        //看看参数的必填项,并操作必填项
        Map<String,String> map = new HashMap<>();
        map.put("appid", PayConfig.appid); //公众账号ID,微信分配的公众账号ID(企业号corpid即为此appid)
        map.put("mch_id",PayConfig.partner); //商户号,微信支付分配的商户号
        map.put("nonce_str", WXPayUtil.generateNonceStr()); //随机字符串,不长于32位
        //WXPayUtil.generateNonceStr()在底层实际上是UUID的操作,且不长于32位
        //即return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
        //replaceAll("-", "")作用是将-替换成""
        //substring(0, 32)作用是取得0开始到32的字符串,包括0,但不包括32
        //一般UUID.randomUUID().toString()的值,除了-外
        //正好是32个字母(即32个数),但加上substring(0, 32)是为了以防万一
        //对应的(数字)签名(sign)虽然是必填项,但并不需要写,因为会自动帮我们生成
        map.put("body",coursename); //商品的描述,这里就传递对应的课程名称
        map.put("out_trade_no",WXPayUtil.generateNonceStr()); //随机生成的商户订单号
        //商户系统内部订单号,要求32个字符内(最少6个字符)
        //只能是数字、大小写字母,_,-,|,*,且在同一个商户号下唯一,所以这里也随机生成字符串
        map.put("total_fee",price); //订单金额,说明:订单总金额,单位为分,只能为整数
        map.put("spbill_create_ip","127.0.0.1");
        //支持IPV4和IPV6两种格式的IP地址,调用微信支付API的机器IP,一般是当前服务器的ip
        //付款码也是自动生成的,所以也不用写
        //这里有几个可能对应的介绍并没有给出说明,如下面的map:
        map.put("notify_url",PayConfig.notifyurl); //通知地址(回调URL)
        map.put("trade_type","NATIVE"); //交易类型

        System.out.println("商户信息:" +map);

        //生成数字签名,并把商户信息转换成xml格式
        String xml = WXPayUtil.generateSignedXml(map, PayConfig.partnerKey);

        System.out.println("商户的xml信息:" +xml);
        /*
        返回如下:


    674572d7a645449492f570b99a73aaf6
    d2359dec6bec4961bdc2bd6958aa7bd5
    wx8397f8696b538317
    100
    08FB22C809DAEA9E6748B352E73B3358 这个签名的确进行了自动添加,一般根据对应的密钥值来操作
    NATIVE
    1473426802
    文案高手的18项修炼
    http://a31ef7db.ngrok.io/WeChatPay/WeChatPayNotify
    127.0.0.1

我们发现付款码并没有添加,实际上他由前端来进行添加了,因为对应的码在前端,这里看不到
         */
        //上面的信息总体来说我们称为数字签名
        //实际上该签名由当前的map集合数据以及时间戳(一般没有)
        //根据MD5算法来操作(一般操作有随机数,且不会相同,如UUID,或者密钥),来生成的
        //正是因为随机数和时间戳(一般没有),所以基本不会一样

        //将xml数据(包含了对应的商户信息和对应企业公众号id,微信的)发送给微信支付平台
        //从而使得可以生成订单,因为有对应的id,那么对应的支付对象也就是该企业了
        //而商户信息则代表合法的商品,因为不能随便填写的,至此,商家合法,企业合法
        //即可以给出支付的链接(虽然也有其他信息,包括该链接,通常称该总的信息为支付链接)
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //发送请求,并返回一个xml格式的字符串
        String post = HttpKit.post(url, xml); 
        //将对应的xml信息给微信支付平台(因为一般他们通常操作xml信息)

        System.out.println(post);
        //到这里,由于是测试数据,那么基本会显示
        /*
        



这是因为对应的密钥错误,进行解密签名时出错的,密钥不可用
更有可能是对应的id和账号,又或者是格式的原因(比如规定需要是整数,因为1分就是0.01元,在日常生活中,我们操作微信支付时,最小也是0.01元,大概就是这里的规定吧),由于是测试的,所以你将他看成是成功的即可
         */

        //微信支付平台返回xml格式的数据,将其转换成map格式并返回给前端
        Map<String, String> map1 = WXPayUtil.xmlToMap(post);

        System.out.println(map1);
        //{return_msg=签名错误,请检查后再试, return_code=FAIL}
        
        //正确的数据中,有对应的code_url,这个就是支付连接(链接),用来进行支付的,一般有过期时间
        //我们需要将该地址,给对应的二维码里面,然后手机扫描后,经过对应的操作(后面会进行编写)就会到该地址
        //进行支付操作,使得我们会给对应的注册的商家进行支付,然后返回支付的状态

        //当然若到了过期时间,那么再次的扫描自然也是没有作用的
        return map1;
        
    }
}

对应的Course.vue:
  //QRCode(dom元素的id,二维码的属性参数)
  let qrcode = new QRCode("qrcode",{
  width:200,
  height:200,
  text:res.data.code_url   //将该地址放在该二维码里面,就会进行跳转
 
});
这时打开你的手机,扫描对应的二维码,我的出现如下:

87-分布式前端微信操作_第9张图片

100分也就是1.00元
至此若出现如上的操作,那么说明链接正确,即操作完成
但是我们会发现有对应的乱码,对应解决方法:
coursename = new String(coursename.getBytes("ISO-8859-1"),"UTF-8");
与前面的操作(好像是上一章的操作)一样,操作完成后,那么应该会出现如下(我这里是):

87-分布式前端微信操作_第10张图片

至此中文乱码解决
检查支付状态:
在这之前,我们需要修改一下对应后端的代码:
  Map<String, String> map1 = WXPayUtil.xmlToMap(post);
        map1.put("orderId",map.get("out_trade_no"));
//需要在返回之前,传递订单编号,因为对应微信支付平台,返回的xml数据中,并没有订单编号,虽然你传递了过去
//为什么需要订单编号呢,这是为了使得查询状态
//由于该订单编号(基本唯一,操作的UUID)和一系列信息使得得到了对应的支付链接
//实际上在高并发下UUID也可能会重复
//这时一般需要用户的具体方位或者结合小锁(有条件的锁)来实现解决重复问题,当然还有其他方法,这里就不做说明
//只要我们扫描支付链接并支付成功,那么对应的该订单编号对应的状态在微信支付平台那里有所改变
//这时我们可以通过该编号去对于的微信支付平台获取状态(一般需要做循环)
//其中扫描支付和该获得状态是互不影响的,实际上都是操作微信支付平台对应的状态


对应的前端(Course.vue):
 //生成二维码
    createCode(){

//去获取支付连接
  document.getElementById("qrcode").innerHTML = ""; 
        //防止多次点击时,对应的前面的二维码一直存在,使得出现多个二维码
        //写在最前面,使得最先删除对应的信息,不要放在对应的axios里面
        //防止,他请求失败或者缓慢时,对应的信息没有及时删除,而出现二维码
        
 this.axios.get("http://localhost:80/createCode",{
   params:{
     courseid:this.course.id,
     coursename:  this.course.courseName,//总不能显示对应id的数据吧,用这个来显示
     price:this.course.discounts
   }
 }).then(res => {


   //QRCode(dom元素的id,二维码的属性参数)
  let qrcode = new QRCode("qrcode",{
  width:200,
  height:200,
  text:res.data.code_url  //将返回的数据放入到二维码中
  //二维码中包含的信息
}); //qrcode就是当前有id="qrcode"的标签


      //检查支付状态
      this.axios.get("http://localhost:80/checkOrderStatus",{
        params:{
         orderId:res.data.orderId //传递 订单编号 进行查询
        }
      }).then(res => {
      //检查支付状态
          }).catch(err =>{
            this.$message.error("查询订单状态失败")
          })
     
     //发现这里二维码生成后,也就是支付链接放到二维码里面后,然后再次进行检查支付状态


          }).catch(err =>{
            this.$message.error("生成二维码失败")
          })

   
    },
对应的后端:
  @GetMapping("checkOrderStatus")
    public Object createCode(String orderId) throws Exception {

        //编写商户信息
        //在该https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_02地址下
        //看看参数的必填项,并操作必填项
        Map<String,String> map = new HashMap<>();
        map.put("appid", PayConfig.appid); //公众账号ID,微信分配的公众账号ID(企业号corpid即为此appid)
        map.put("mch_id",PayConfig.partner); //商户号,微信支付分配的商户号
        map.put("out_trade_no ", orderId); //商户订单号
        //商户系统内部订单号,要求32个字符内(最少6个字符),只能是数字、大小写字母,_,-,|,*
        //且在同一个商户号下唯一,即值为orderId(因为他是通过前面的UUID操作来的,搞好符合要求)
        map.put("nonce_str",WXPayUtil.generateNonceStr()); //随机字符串,不长于32位

        ///对应的(数字)签名(sign)虽然是必填项,但并不需要写,因为会自动帮我们生成

        //生成数字签名
        String xml = WXPayUtil.generateSignedXml(map, PayConfig.partnerKey);

        //发送查询请求给微信支付平台
        String url = "https://api.mch.weixin.qq.com/pay/orderquery";

        String post = HttpKit.post(url, xml);

        //对微信支付平台返回的查询结果进行处理
        Map<String, String> map1 = WXPayUtil.xmlToMap(post);
        return map1;
    }
至此,我们点击二维码,不需要扫描,执行对应的方法
在对应的map集合中可以获得对应的状态(一般是订单未支付,一般保存在trade_state_desc字段)
对于状态的判断,我们一般需要在后端进行如下的编写,前面分析过
我们的支付成功与否,对应修改对应的编号的状态,所以我们需要循环获取:
 //发送查询请求给微信支付平台
        String url = "https://api.mch.weixin.qq.com/pay/orderquery";

        //设置订单状态的开始时间点
        long l = System.currentTimeMillis();

        //不停的去微信支付平台询问是否支付成功
        while(true) {
            String post = HttpKit.post(url, xml);

            //对微信支付平台返回的查询结果进行处理
            Map<String, String> map1 = WXPayUtil.xmlToMap(post);

            //已经支付成功,我们的企业得到钱,微信支付平台,修改数据,得到支付成功的数据,就可以返回
            //trade_state就是对应的状态信息,用符号表示
            //但并没有trade_state_desc对应的"订单未支付"这个信息
            //只是用符号表示
            //所以他们是互不影响的
            /*
            对应的trade_state的可能值
            SUCCESS--支付成功
            REFUND--转入退款
            NOTPAY--未支付
            CLOSED--已关闭
            REVOKED--已撤销(相当于我们直接的刷卡支付,而不操作订单了,或者认为是直接的使用现金交易)
            USERPAYING--用户支付中
            PAYERROR--支付失败(其他原因,如银行返回失败)
            ACCEPT--已接收,等待扣款
             */
            if(map1.get("trade_state").equalsIgnoreCase("SUCCESS")) {
                return map1;
            }

            //超过30秒未支付就停止询问,因为不可能一直循环浪费资源
            if(System.currentTimeMillis() - l>30*1000){
                return map1;
            }
             Thread.sleep(3000); //每隔三秒访问一次,总不能一直访问吧(太浪费空间)
            
        }
上面如果在30秒内支付成功,那对应前端界面会显示出来你已经支付
但是如果30秒内没有支付或者没有支付成功,前端界面会显示未支付
一般的我们会重新生成二维码,或者对应过期时间设置为二维码的过期时间
防止在前端返回未支付的情况下,可以支付,操作对应数据的不合理,甚至会购买不上
对应的前端:

<el-dialog :visible.sync="dialogFormVisible" style="width:800px;margin:0px auto;" >
 <h1 style="font-size:30px;color:#00B38A">微信扫一扫支付h1>
 <div id="qrcode" style="width:210px;margin:20px auto;">div>
 <h2 id="statusText">h2>
 el-dialog>
 //检查支付状态
      this.axios.get("http://localhost:80/checkOrderStatus",{
        params:{
         orderId:res.data.orderId
        }
      }).then(res => {
      //检查支付状态
      console.log(res)
      if(res.data.trade_state=="SUCCESS"){
          document.getElementById("statusText").innerHTML=
              " 支付成功"
      }
          }).catch(err =>{
            this.$message.error("查询订单状态失败")
          })
至此可以扫描二维码,进行支付,发现,出现对应的样式,显示支付成功
当你再次扫描时一般会提示已经操作(因为一个已经支付的二维码一般是会销毁的,可能有些情况下会继续支付)
至此,微信支付完成
接下来操作关闭二维码:

<el-dialog :visible.sync="dialogFormVisible" style="width:800px;margin:0px auto;" >
 <h1 style="font-size:30px;color:#00B38A">微信扫一扫支付h1>
 <div id="qrcode" style="width:210px;margin:20px auto;">div>
 <h2 id="statusText">h2>
 <p id="closeTest">p>
 el-dialog>
 data() {
    return {
      comment:null, //待发表的留言内容
      activeName: "intro",
      course:null,
      totalLessons:0, //本门课程的总节数
      commentList:null, //所有留言
      isLogin:false, //false,未登录
      isBuy:false,//未购买
      user:null, //当前用户,{}对象可以","结尾
      myCourseList:[], //当前用户购买过的所有课程
      dialogFormVisible:false,
      time:null, //计时对象,这是新加的
    };
  },
   //检查支付状态
      console.log(res)
      if(res.data.trade_state=="SUCCESS"){
          document.getElementById("statusText").innerHTML=
              " 支付成功"
             //真实的修改数据库,保存订单
       this.saveOrder();
      let s = 3; //倒计时的秒数
      this.closeQRForm(s) //关闭二维码窗口
       
      }
修改web层项目的对应的OrderController类的saveOrder方法:
 @GetMapping("saveOrder/{userid}/{courseid}/{acid}/{stype}")
    public String saveOrder(String orderNo,@PathVariable("userid") String userid,
                            @PathVariable("courseid") String courseid,
                            @PathVariable("acid") String acid,@PathVariable("stype") String stype) {

        //String orderNo = UUID.randomUUID().toString();
        orderService.saveOrder(orderNo,userid,courseid,acid,stype);
        return orderNo;

    }
给data加上对应的订单属性(这里就不全部写出来了):
     orderNo:"",//订单编号
         //在java里点击重新启动,实际上是根据当前数据启动,这时若端口改变,则使用改变的端口
         //而不是以前的端口
   //QRCode(dom元素的id,二维码的属性参数)
  let qrcode = new QRCode("qrcode",{
  width:200,
  height:200,
  text:res.data.code_url  //将返回的数据放入到二维码中
  //二维码中包含的信息
}); //qrcode就是当前有id="qrcode"的标签

this.orderNo = res.data.orderId; //这里进行赋值
      //检查支付状态
      this.axios.get("http://localhost:80/checkOrderStatus",{
        params:{
         orderId:this.orderNo //这里修改一下
        }
//保存订单
    saveOrder(){

      this.axios.get("http://localhost:8002/order/saveOrder/"+this.user.content.id+
                     "/"+this.course.id+"/"+this.course.id+"/1",{
        params:{
         orderNo:this.orderNo
        }
      }).then(res => {
     console.log(res)
          }).catch(err =>{
            this.$message.error("保存订单失败")
          })

    },
    //倒计时关闭二维码窗口
    closeQRForm(s){
     let that = this; //用来关闭下面的循环,因为是不同的this了
      that.time = setInterval(function(){
    
document.getElementById("closeTest").innerHTML = "("+ s-- +")秒后关闭本窗口"
if(s==0){
  clearInterval(that.time) //停止计时器
  that.dialogFormVisible = false //二维码窗口隐藏
  that.isBuy = true; //修改购买状态(已购买),对应的视频就会变成播放

}
      },1000)
    },
但是我们发现,对应的数据不对,因为订单中的status并不是20,使得还是试看,而不是播放
若还是播放,那么应该是设置了购买,或者已经购买
已经购买的话,一般保存订单会失败,即后端返回异常信息,使得前端报错
所以我们需要进行修改:
   //保存订单
    saveOrder(){

      this.axios.get("http://localhost:8002/order/saveOrder/"+this.user.content.id+
                     "/"+this.course.id+"/"+this.course.id+"/1",{
        params:{
         orderNo:this.orderNo
        }
      }).then(res => {

          //这里进行修改成20,表示已支付,这样就会得到已经购买的课程了,对应的视频也基本都是播放了
          //实际上我们只需要将保存的代码中,对应的status值从0修改成20即可
          //而不用写下面的代码,但是为了以后,我们对应的保存订单也最好设置为0,默认创建
          //因为总不能只要有个订单(无论是否支付),就相当于是已支付吧,这不符合实际情况
          //我们在生活中可以知道,订单有时是存在的,需要我们去支付,否则一般会显示待支付
          //所以一般我们设置为0,表示以创建,而不是默认支付
this.axios.get("http://localhost:8002/order/updateOrder/"+this.orderNo+"/20").then(res => {
     console.log(res)
          }).catch(err =>{
            this.$message.error("设置订单状态失败")
          })


          }).catch(err =>{
            this.$message.error("保存订单失败")
          })

    },
实际上对应的保存订单在查询支付状态里面,假如查询不了怎么办,那么订单也就不会生成,所以说
对应的订单应该在状态之前,而不会受任何影响,且是创建的订单
this.orderNo = res.data.orderId;

     //真实的修改数据库,保存订单
       this.saveOrder(); //一般购买后,就基本到不了这里,也就不会报错
      //检查支付状态
      this.axios.get("http://localhost:80/checkOrderStatus",{
 if(res.data.trade_state=="SUCCESS"){
          document.getElementById("statusText").innerHTML=
              " 支付成功"

       
      }
//无论是否支付成功,对应的窗口都需要进行关闭,自然也要等待后端访问得到返回数据
       let s = 3; //倒计时的秒数
      this.closeQRForm(s) //关闭二维码窗口
那么对应的保存订单中的设置20,就要进行位置变化了:
 //检查支付状态
      console.log(res)
      if(res.data.trade_state=="SUCCESS"){
          document.getElementById("statusText").innerHTML=
              " 支付成功"
        this.axios.get("http://localhost:8002/order/updateOrder/"+this.orderNo+"/20").then(res => {
     console.log(res)
          }).catch(err =>{
            this.$message.error("设置订单状态失败")
          })
       
      }
我们可以将对应的更新状态封装成一个函数:
updateOrder(ss){
 this.axios.get("http://localhost:8002/order/updateOrder/"+this.orderNo+"/"+ss).then(res => {
     console.log(res)
          }).catch(err =>{
            this.$message.error("设置订单状态失败")
          })
那么原来的可以这样写:
 //检查支付状态
      console.log(res)
      if(res.data.trade_state=="SUCCESS"){
          document.getElementById("statusText").innerHTML=
              " 支付成功"
       //支付成功
       this.updateOrder(20)
              //进行判断等等
      //  }else if(res.data.trade_state=="NOTPAY"){
      //    document.getElementById("statusText").innerHTML=" 未支付"
      // 未支付
      // this.updateOrder(10)
      //  }
       
      }
至此可以进行测试,每次的购买后,去已购那里看看发现的确在那里了,且对应都是播放
但是这里有一个问题,如果是未支付的情况下,可能是有相同的保存,那么会报错
通过修改后端代码可以解决问题(在文章最后面)
若不修改后端代码,如何解决:
在查询支付状态里面可以解决此问题(位置没改变之前,就是这样,只是回来了而已)
因为和状态的设置是一起的(且是支付成功后的操作)
所以为了解决此问题,外面还是将对应的保存订单放回到对应的判断里面:
 if(res.data.trade_state=="SUCCESS"){
          document.getElementById("statusText").innerHTML=
              " 支付成功"
        //真实的修改数据库,保存订单
       this.saveOrder(); //放回来,里面执行状态修改
   
      //  }else if(res.data.trade_state=="NOTPAY"){
      //    document.getElementById("statusText").innerHTML=" 未支付"
      // //未支付
      // this.updateOrder(10)
      //  }
       
      }

//实际上外面应该在后端进行判断是否有订单,而不是放在该判断里面
//因为以后该判断会非常的多,那么该代码也就是多了起来
//这里就只是一个测试,所以放在该判断里面了
  //保存订单
    saveOrder(){
      this.axios.get("http://localhost:8002/order/saveOrder/"
                     +this.user.content.id+"/"+this.course.id+"/"+this.course.id+"/1",{
        params:{
         orderNo:this.orderNo
        }
      }).then(res => {

     this.updateOrder(20) //这里执行


          }).catch(err =>{
            this.$message.error("保存订单失败")
          })

    },
注意:这只是测试,实际情况下,外面需要修改对应的后端代码使得进行判断,而不是修改前端
因为该保存的订单也基本固定了20的状态,或者也可以将对应的方法加上参数,如:
 this.saveOrder(20);
 
  //保存订单
    saveOrder(ss){
      this.axios.get("http://localhost:8002/order/saveOrder/"
                     +this.user.content.id+"/"+this.course.id+"/"+this.course.id+"/1",{
        params:{
         orderNo:this.orderNo
        }
      }).then(res => {

     this.updateOrder(ss)


          }).catch(err =>{
            this.$message.error("保存订单失败")
          })

    },
        //这样也可,虽然代码非常多(操作后端即可)
        //后端思路:根据传递的参数查询表里面是否有数据,有则不执行保存订单
至此微信的操作完成
后端代码(修改后,就可以将保存方法放在查询状态外面,状态修改方法放在查询状态里面,他们都是单独的):
添加部分对应的OrderDap.xml:
<select id="getOrder" resultMap="UserCourseOrderMap">
        select * from user_course_order where user_id = #{user_id}
                                          and course_id = #{course_id}
                                          and source_type = #{source_type}
    select>

对应的接口:
    /**
     *
     * @param user_id 用户id
     * @param course_id 课程id
     * @param source_type '订单来源类型: 1 用户下单购买 2 后台添加专栏',一般是1
     * @return
     */
    UserCourseOrder getOrder(@Param("user_id") String user_id,
                             @Param("course_id") String course_id,
                             @Param("source_type") String source_type);

对应的service层的OrderServiceImpl实现类的saveOrder方法:
 @Override
    public void saveOrder(String orderNo, String user_id, 
                          String course_id, String activity_course_id, String source_type) {

        //只要没有订单才能保存,否则会报错的
            //因为通常对应的字段值会相同,由于表的索引设置对应的唯一,所以会报错
        
        UserCourseOrder order = orderDao.getOrder(user_id, course_id,source_type);
        System.out.println(order);
        if(order == null) {
            orderDao.saveOrder(orderNo, user_id, course_id, activity_course_id, source_type);
        }


    }
通过后端代码的编写,使得对应操作完成
这时候的前端可以是:
  this.saveOrder();
      //检查支付状态
      this.axios.get("http://localhost:80/checkOrderStatus",{
        params:{
         orderId:this.orderNo
        }
      }).then(res => {
      //检查支付状态
      console.log(res)
      if(res.data.trade_state=="SUCCESS"){
          document.getElementById("statusText").innerHTML=
              " 支付成功"
        //真实的修改数据库,保存订单
       this.updateOrder(20)
至此,微信的登录和微信的支付都操作完毕
但是要注意:除了在课程里购买,其他的地方的购买代码基本是类似的,即他们都是重复的代码
至此,并没有给出操作,可以自己进行尝试,还有些细节问题,可以自己进行查看,比如buy()方法,好像固定是7的参数
注意:当你的localhost被设置其他域名,虽然前端会执行,但还是默认本机,并不会执行对应ip
但是一些情况下,并不会显示,如富文本编辑器和对应的检查(有元素,网络,等等的)
说到富文本编辑器,接下来说明一下,如何在vue里操作吧:
很简单的步骤:
#首先安装对应的组件
npm install mavon-editor --save
<template>
  <div>
      
        <mavon-editor
            @save="saveDoc"
            @change="updateDoc"
            ref="editor"
            v-model="doc">
         mavon-editor>
         <button @click="s">sssbutton>
  div>
template>

<script>
import {mavonEditor} from "mavon-editor";
import "mavon-editor/dist/css/index.css";
export default {
    name:"jj",
components: {mavonEditor},
data() {
        return {
            doc: "1" //绑定的值
        };
    },
    methods: {
        s(){
            alert(1)
        },
        updateDoc(markdown, html) {
            // 此时会自动将 markdown 和 html 传递到这个方法中
            console.log("markdown内容: " + markdown);
            console.log("html内容:" + markdown);
        },
        saveDoc(markdown, html) {
            // 此时会自动将 markdown 和 html 传递到这个方法中
            console.log("markdown内容:" + markdown);
            console.log("html内容:" + html);
        }
    }

}
script>

<style>

style>
至此操作完成,简单吧

你可能感兴趣的:(笔记,微信,前端,分布式,微信登录和微信支付)