微信登录和微信支付
在上一章我们初步的完成了前端的编写,接下来我们来操作微信的登录和微信的支付
微信开放平台(针对开发者和公司):
对应的微信官方文档:
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协议

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

上面的二维码一般保存对应的地址,你可以进行测试,在百度上搜索"草料二维码生成器",进行官方网站
输入"中华人民共和国"这个内容,点击生成二维码,用微信扫一扫,就会出现该内容
当然多次生成一样的,对应的码基本都相同
当然你也可以输入网站,如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,
userDTO:null,
isHasNewMessage: false,
dialogFormVisible: false,
phone: "",
password: "",
appid:"wxd99431bbff8305a0",
scope:"snsapi_login",
redirect_uri:"http://www.pinzhi365.com/wxlogin",
};
},
goToLoginWX() {
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;
}
public static String doGet(String url, Map<String,String> param){
CloseableHttpClient aDefault = HttpClients.createDefault();
String s = null;
CloseableHttpResponse response = null;
try {
URIBuilder uriBuilder = new URIBuilder(url);
if(param!=null){
for(String key:param.keySet()){
uriBuilder.addParameter(key,param.get(key));
}
}
URI uri = uriBuilder.build();
HttpGet httpGet = new HttpGet(uri);
response = aDefault.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
System.out.println(response);
System.out.println("响应的状态:" +response.getStatusLine());
System.out.println("响应的状态:" +statusCode);
if(statusCode==200){
System.out.println(response.getEntity());
System.out.println("---");
s = EntityUtils.toString(response.getEntity(),"UTF-8");
System.out.println(s);
System.out.println("---");
System.out.println(1);
}
}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);
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"
}
查看对应的微信官方文档中,是否是正确的格式:

对比发现,的确是是正确的,至此得到对应的token令牌成功(字符串有令牌参数及其值,简称为token字符串),即操作成功
接下来我们通过token字符串来获得微信用户的信息
在这之前我们需要先创建一个类:
在java资源文件夹下,创建entity包,并在里面创建Token类:
package entity;
public class Token {
private String access_token;
private String expires_in;
private String refresh_token;
private String openid;
private String scope;
private String unionid;
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;
private String province;
private String city;
private String country;
private String headimgurl;
private String privilege;
private String 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);
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);
Token token = JSON.parseObject(tokenString, Token.class);
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);
User user = JSON.parseObject(UserString, User.class);
System.out.println("微信的用户昵称 = " + user.getNickname());
System.out.println("微信的用户头像 = " + user.getHeadimgurl());
return null;
}
至此微信用户的信息就得到了,比如说"nickname":“king”,其中king就是我微信的名称
当然一些值是0或者是""的,则是我们微信并没有设置的,或者是默认的,也有可能需要其他方式才可获得,但这里并不需要理会
接下来我们修改对应的注册逻辑:
dao层:
Integer register(@Param("phone") String phone, @Param("password") String
Password,@Param("nickname") String nickname,@Param("headimg") String headimg);
service层:
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层:
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);
userDTO.setMessage("登录成功");
} else {
userDTO.setState(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);
}
若数据库有对应的数据,则修改成功
对应的部分前端代码(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
public class WxLoginController {
@Reference
private UserService userService;
private UserDTO userDTO = null;
@GetMapping("wxlogin")
public UserDTO wxlogin(HttpServletRequest request, HttpServletResponse response) throws
IOException {
String code = request.getParameter("code");
System.out.println("【临时凭证】code=" + code);
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);
Token token = JSON.parseObject(tokenString, Token.class);
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);
User user = JSON.parseObject(UserString, User.class);
System.out.println("微信的用户昵称 = " + user.getNickname());
System.out.println("微信的用户头像 = " + user.getHeadimgurl());
System.out.println("微信的unionid参数值:" + user.getUnionid());
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);
userDTO.setMessage("登录成功");
} else {
userDTO.setState(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() {
userDTO = null;
return null;
}
}
对应的前端Header.vue:
created(){
console.log(JSON.parse(localStorage.getItem("user")));
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
if(this.userDTO!=""){
this.phone = this.userDTO.content.phone
this.password = this.userDTO.content.password
this.login()
}
}).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标签下的,但是一般新版本的谷歌可能并没有设置跨域问题
旧版的谷歌却可以(但不一定)也就是如下:

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

加上 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(){
!(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"),
(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,而不使用官方的
将对应的引入的注释:
组件也不同声明了:
components:{
},
对应的html也注释掉:
实际上对应的组件操作也是封装了二维码js的执行,我们可以直接自己定义来保存二维码:
<div id="wxLoginForm">div>
接下来我们找到对应的方法,因为组件操作了对应二维码的生成,和一些属性
但我们自己定义的,需要自己进行操作,这里当然是默认没有的,因为什么都没有操作,看如下代码:
goToLoginWX() {
document.getElementById("loginForm").style.display="none"
this.$nextTick(function(){
this.createCode();
});
},
createCode(){
var obj = new WxLogin({
id:"wxLoginForm",
appid:"wxd99431bbff8305a0",
scope:"snsapi_login",
redirect_uri:"http://www.pinzhi365.com/wxlogin",
})
},
至此,浏览器的问题就解决了,我们发现,对应的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;}
"
那么如何使得可以操作对应的样式呢,我们可以在对应的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="
})
这时我们再次看看二维码,发现二维码变小的,至此样式也操作成功
我们操作完了微信的登录,接下来我们操作微信的支付:
创建二维码:
安装 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,
isBuy:false,
user:null,
myCourseList:[],
dialogFormVisible:false
};
<script>
import Header from "./Header/Header";
import Footer from "./Footer/index";
import QRCode from 'qrcodejs2';
export default {
name: "Course",
components: {
Header,
Footer,
QRCode
},
这里有个问题,前面的操作问题
if(this.user != null){
this.isLogin = true
if(this.course!=null){
this.getMyCourseList()
}
this.getComment();
}
buy(courseid) {
this.dialogFormVisible = true;
this.$nextTick(function(){
this.createCode()
});
},
createCode(){
document.getElementById("qrcode").innerHTML = "";
let qrcode = new QRCode("qrcode",{
width:200,
height:200,
text:"我爱你中国"
});
},
至此,点击立即购买,发现,对应的二维码出来了,用微信扫描,就会出现设置的信息"我爱你中国",至此二维码显示操作完毕
准备工作:
名词介绍:

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

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

如果不是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 = "";
this.axios.get("http://localhost:80/createCode",{
params:{
courseid:this.course.id,
coursename: this.course.courseName,
price:this.course.discounts
}
}).then(res => {
console.log(res)
let qrcode = new QRCode("qrcode",{
width:200,
height:200,
text:res
});
}).catch(err =>{
this.$message.error("生成二维码失败")
})
},
在web层项目下的commons包下创建实体类PayConfig:
package commons;
public class PayConfig {
public static String appid = "wx8397f8696b538317";
public static String partner = "1473426802";
public static String partnerKey = "8A627A4578ACE384017C997F12D68B23";
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<String,String> map = new HashMap<>();
map.put("appid", PayConfig.appid);
map.put("mch_id",PayConfig.partner);
map.put("nonce_str", WXPayUtil.generateNonceStr());
map.put("body",coursename);
map.put("out_trade_no",WXPayUtil.generateNonceStr());
map.put("total_fee",price);
map.put("spbill_create_ip","127.0.0.1");
map.put("notify_url",PayConfig.notifyurl);
map.put("trade_type","NATIVE");
System.out.println("商户信息:" +map);
String xml = WXPayUtil.generateSignedXml(map, PayConfig.partnerKey);
System.out.println("商户的xml信息:" +xml);
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String post = HttpKit.post(url, xml);
System.out.println(post);
Map<String, String> map1 = WXPayUtil.xmlToMap(post);
System.out.println(map1);
return map1;
}
}
对应的Course.vue:
let qrcode = new QRCode("qrcode",{
width:200,
height:200,
text:res.data.code_url
});
这时打开你的手机,扫描对应的二维码,我的出现如下:

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

至此中文乱码解决
检查支付状态:
在这之前,我们需要修改一下对应后端的代码:
Map<String, String> map1 = WXPayUtil.xmlToMap(post);
map1.put("orderId",map.get("out_trade_no"));
对应的前端(Course.vue):
createCode(){
document.getElementById("qrcode").innerHTML = "";
this.axios.get("http://localhost:80/createCode",{
params:{
courseid:this.course.id,
coursename: this.course.courseName,
price:this.course.discounts
}
}).then(res => {
let qrcode = new QRCode("qrcode",{
width:200,
height:200,
text:res.data.code_url
});
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 {
Map<String,String> map = new HashMap<>();
map.put("appid", PayConfig.appid);
map.put("mch_id",PayConfig.partner);
map.put("out_trade_no ", orderId);
map.put("nonce_str",WXPayUtil.generateNonceStr());
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);
if(map1.get("trade_state").equalsIgnoreCase("SUCCESS")) {
return map1;
}
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,
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) {
orderService.saveOrder(orderNo,userid,courseid,acid,stype);
return orderNo;
}
给data加上对应的订单属性(这里就不全部写出来了):
orderNo:"",
let qrcode = new QRCode("qrcode",{
width:200,
height:200,
text:res.data.code_url
});
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;
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 => {
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)
}
至此可以进行测试,每次的购买后,去已购那里看看发现的确在那里了,且对应都是播放
但是这里有一个问题,如果是未支付的情况下,可能是有相同的保存,那么会报错
通过修改后端代码可以解决问题(在文章最后面)
若不修改后端代码,如何解决:
在查询支付状态里面可以解决此问题(位置没改变之前,就是这样,只是回来了而已)
因为和状态的设置是一起的(且是支付成功后的操作)
所以为了解决此问题,外面还是将对应的保存订单放回到对应的判断里面:
if(res.data.trade_state=="SUCCESS"){
document.getElementById("statusText").innerHTML=
" 支付成功"
this.saveOrder();
}
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>
对应的接口:
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) {
console.log("markdown内容: " + markdown);
console.log("html内容:" + markdown);
},
saveDoc(markdown, html) {
console.log("markdown内容:" + markdown);
console.log("html内容:" + html);
}
}
}
script>
<style>
style>
至此操作完成,简单吧