前端技术选型
技术名称 | 说明 |
---|---|
Vue.js | 是一套用于构建用户界面的渐进式JavaScript框架 |
Element UI库 | element-ui 是饿了么前端出品的基于 Vue.js的 后台组件库,方便程序员进行页面快速布局和构建 |
node.js | 简单的说 Node.js 就是运行在服务端的 JavaScript 运行环境 |
axios | 对ajax的封装, 简单来说就是ajax技术实现了局部数据的刷新,axios实现了对ajax的封装 |
微信登录
vue项目安装
- 微信官方提供的生成二维码的js
npm install vue-wxlogin
如果不是vue的项目,可以直接引用官方提供的js文件,来生成二维码
页面引入
<div id="loginForm">
<!-- 登录表单 -->
<el-form>
<!-- 登录按钮 -->
<el-button/>
<!-- 微信登录图标 -->
<img/>
</div>
<!-- 二维码 -->
<wxlogin id="wxLoginForm" style="display:none"
:appid="appid" :scope="scope" :redirect_uri="redirect_uri">
</wxlogin>
<script>
import wxlogin from 'vue-wxlogin'; // 引入
export default {
name: "Header",
components: {
wxlogin // 声明
},
data() {
return {
appid:"wxd99431bbff8305a0", // 应用唯一标识,在微信开放平台提交应用审核通过后获得
scope:"snsapi_login", // 应用授权作用域,网页应用目前仅填写snsapi_login即可
redirect_uri:"http://www.pinzhi365.com/wxlogin", //重定向地址,(回调地址)
};
},
methods: {
goToLoginWX() {
document.getElementById("loginForm").style.display = "none";
document.getElementById("wxLoginForm").style.display = "block";
}
}
}
</script>
修改hosts文件
文件位置:C:\Windows\System32\drivers\etc\hosts
回调默认指定的是80端口,别忘记将tomcat的8003端口修改成80
127.0.0.1 www.pinzhi365.com
依赖
<!-- 需要使用HttpServletRequest获得参数 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<!-- 需要使用HttpClient发出请求 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.12</version>
</dependency>
封装HttpClient
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;
/**
* @BelongsProject: lagou-edu-web
* @Author: GuoAn.Sun
* @CreateTime: 2020-09-22 11:30
* @Description: httpclient的封装工具类
*/
public class HttpClientUtil {
public static String doGet(String url) {
return doGet(url,null);
}
/**
* get请求,支持request请求方式,不支持restfull方式
*
* @param url 请求地址
* @param param 参数
* @return 响应的字符串
*/
public static String doGet(String url, Map<String, String> param) {
// 创建httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response =null;
try {
// 创建url
URIBuilder builder = new URIBuilder(url);
if (param != null) {
// 在url后面拼接请求参数
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 创建http get请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpClient.execute(httpGet);
// 从响应对象中获取状态码(成功或失败的状态)
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("响应的状态 = " + statusCode);
// 200表示响应成功
if (statusCode == 200) {
// 响应的内容字符串
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
// 释放资源
try {
if (response != null) {
response.close();
}
httpClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return resultString;
}
}
定义从微信返回的数据对象
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 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是唯一的。
}
回调函数controller
@RestController
public class TestWX {
@RequestMapping("wxlogin")
public Object wxlogin(HttpServletRequest request) throws IOException {
// 1.通过扫码去微信服务器请求,返回的code值
String code = request.getParameter("code");
System.out.println("code = " + code);
// 2.通过code获取access_token,定义获取access_token的url
String getTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wxd99431bbff8305a0&secret=60f78681d063590a469f1b297feff3c4&code="+code+"&grant_type=authorization_code";
// 3.发请求并获得access_token
String access_token = HttpClientUtil.doGet(getTokenUrl);
System.out.println("access_token = "+access_token);
// 4.将返回的json格式字符串转换成对象
Token token = JSON.parseObject(access_token, Token.class);
// 5.通过access_token获取获取微信用户个人信息
String getUserUrl = "https://api.weixin.qq.com/sns/userinfo?access_token="+token.getAccess_token()+"&openid="+token.getOpenid();
// 6.发请求并获取用户信息
String user_token = HttpClientUtil.doGet(getUserUrl);
// 7.将返回的json格式字符串转换成对象
User user = JSON.parseObject(user_token, User.class);
// 8.自定义网页逻辑
System.out.println("昵称 = " + user.getNickname());
return user;
}
}
如果下面的错误:是因为谷歌浏览器有bug,待修复!切换别的浏览器即可
web服务的端口号必须是80!
后续
WxLoginController
@RestController
@RequestMapping("user")
public class WxLoginController {
@Reference // 远程消费
private UserService userService;
private UserDTO dto = null; // 是否用微信登录成功,dto为null,则尚未登录
@GetMapping("wxlogin")
public String wxlogin(HttpServletRequest request, HttpServletResponseresponse) throws ServletException, IOException {
// 1. 微信官方发给我们一个临时凭证
String code = request.getParameter("code");
System.out.println("【临时凭证】code = " + code);
// 2. 通过code,去微信官方申请一个正式的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 = " + tokenString);
// 将json格式的token字符串转换成实体对象,方便存和取
Token token = JSON.parseObject(tokenString, Token.class);
// 3. 通过token,去微信官方获取用户的信息
String getUserByToken_url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + token.getAccess_token() + "&openid=" + token.getOpenid();
String userinfoString = HttpClientUtil.doGet(getUserByToken_url);
System.out.println("userinfoString = " + userinfoString);
// 将json格式的user字符串转换成实体对象,方便存和取
WxUser wxUser = JSON.parseObject(userinfoString, WxUser.class);
System.out.println("微信昵称 = " + wxUser.getNickname());
System.out.println("微信头像 = " + wxUser.getHeadimgurl());
// 拉勾的业务流程! 需要 手机号(wxUser.getUnionid())和密码(wxUser.getUnionid()),头像和昵称
User user = null;
dto = new UserDTO();
// 检测手机号是否注册
Integer i = userService.checkPhone(wxUser.getUnionid());
if(i == 0){
// 未注册,自动注册并登录
userService.register(wxUser.getUnionid(),
wxUser.getUnionid(),wxUser.getNickname(),wxUser.getHeadimgurl());
dto.setMessage("手机号尚未注册,系统已帮您自动注册,请牢记密码!");
user = userService.login(wxUser.getUnionid(), wxUser.getUnionid());
}else{
user = userService.login(wxUser.getUnionid(), wxUser.getUnionid());
if(user == null){
dto.setState(300); //300表示失败
dto.setMessage("帐号密码不匹配,登录失败!");
}else{
dto.setState(200); //200表示成功
dto.setMessage("登录成功!");
}
}
dto.setContent(user);
response.sendRedirect("http://localhost:8080");
return null;
}
@GetMapping("checkWxStatus")
public UserDTO checkWxStatus(){
return this.dto;
}
@GetMapping("logout")
public Object logout(){
this.dto = null;
return null;
}
}
Header.vue
<script>
created(){
// 当刷新页面,组件创建成功之后,立刻检测本地储存中是否存在用户对象
this.userDTO = JSON.parse( localStorage.getItem("user") );
if(this.userDTO != null){
this.isLogin = true; // 已登录
}else{
// 去检测微信是否登录过
this.axios
.get("http://localhost:80/user/checkWxStatus")
.then( (result)=>{
this.userDTO = result.data;
this.phone = this.userDTO.content.phone;
this.password = this.userDTO.content.password;
this.login();// 走普通登录
})
.catch( (error)=>{
//this.$message.error("登录失败!");
});
}
}
//登出
logout(){
localStorage.setItem("user", null); // 将登录成功的对象信息保存到本地储存中
this.isLogin = false; // 更新登录状态
alert('谢谢使用,再见!');
// 去检测微信是否登录过
this.axios.get("http://localhost:80/user/logout")
.then( (result)=>{
})
.catch( (error)=>{
//this.$message.error("登录失败!");
});
}
</script>
解决二维码在谷歌浏览器的bug
谷歌浏览器调试的时候,iframe标签跨域问题导致无法跳转的bug
如果iframe未添加sandbox属性,或者sandbox属性不赋值,就代表采用默认的安全策略
即:iframe的页面将会被当做一个独立的源,并且不能提交表单,不能执行javascript脚本,也不能让包含iframe的父页面导航到其他地方,所有的插件,如flash等也全部不能起作用
简单来说iframe就只剩下一个展示数据的功能,正如他的名字一样,所有的内容都被放进了一个“单独的沙盒”
- sandbox包含的属性及作用:
属性值 | 说明 |
---|---|
allow-scripts | 允许运行执行脚本 |
allow-top-navigation | 允许iframe能够主导window.top进行页面跳转 |
allow-same-origin | 允许同域请求,比如ajax,storage |
allow-forms | 允许进行提交表单 |
allow-popups | 允许iframe中弹出新窗口,比如,window.open,target=”_blank” |
allow-pointer-lock | 在iframe中可以锁定鼠标,主要和鼠标锁定有关 |
- 加上 sandbox=“allow-scripts allow-top-navigation allow-same-origin” 属性,即可解决
- 官方js:res.wx.qq.com/connect/zh_…
- 无法修改微信服务器上的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);
}
Course.vue
<div id="wxLoginForm"></div>
methods: {
// 微信登录
goToLoginWX() {
// 普通的登录表单隐藏
document.getElementById("loginForm").style.display = "none";
// 显示二维码的容器
document.getElementById("wxLoginForm").style.display = "block";
// 去生成二维码
this.$nextTick(function(){
this.createCode(); // 直接调用会报错:TypeError: Cannot read property'appendChild' of null
});
},
// 生成二维码
createCode(){
var obj = new WxLogin({
id:"wxLoginForm", // 显示二维码的容器
appid: "wxd99431bbff8305a0", // 应用唯一标识,在微信开放平台提交应用审核通过后获得
scope: "snsapi_login", // 应用授权作用域,网页应用目前仅填写snsapi_login即可
redirect_uri: "http://www.pinzhi365.com/wxlogin", //重定向地址,(回调地址)
href: "data:text/css;base64,加密后的样式"
});
},
}
.impowerBox .qrcode {width: 200px;}
.impowerBox .title {display: none;}
.impowerBox .info {width: 200px;}
.status_icon {display: none}cs
.impowerBox .status {text-align: center;}
我们用站长工具对样式代码进行base64加密:tool.chinaz.com/Tools/Base6…
微信支付
创建二维码
1、安装 qrcodejs2 (注意:安装的是qrcodejs2,不要安装qrcode ---> 会报错)
npm install qrcodejs2 --save
2、页面中引入
<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>
<script>
import QRCode from 'qrcodejs2'; // 引入qrcodejs
export default {
name: "Index",
components: {
Header,
Footer,
QRCode // 声明组件
},
data() {
return {
dialogFormVisible: false, // 是否显示登录框,true:显示,false:隐藏
};
},
methods: {
// 购买课程
buy(courseid) {
//alert("购买第【" + courseid + "】门课程成功,加油!");
this.dialogFormVisible = true; //显示提示框
// 待dom更新之后再用二维码渲染其内容
this.$nextTick(function(){
this.createCode(); // 直接调用会报错:TypeError: Cannot read property'appendChild' of null
});
},
// 生成二维码
createCode(){
// QRCode(存放二维码的dom元素id,二维码的属性参数)
let qrcode = new QRCode('qrcode',{
width:200, // 二维码的宽度
height:200, // 二维码的高度
text:"我爱你中国" // 二维码中包含的信息
});
},
},
};
</script>
名词介绍
参数 | 说明 |
---|---|
appid | 微信公众帐号或开放平台APP的唯一标识 |
partner | 商户号(配置文件中的partner:账户) |
partnerkey | 商户密钥(密码) |
sign | 数字签名,根据微信官方提供的密钥和一套算法生成的一个加密信息,就是为了保证交易安全 |
- 如果获得这些信息?
- 需要注册认证公众号,费用300元/次
查看自己的公众号的参数
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";
}
SDK
pay.weixin.qq.com/wiki/doc/ap…
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
主要使用sdk中的三个功能:
1、获取随机字符串(生成订单编号)
WXPayUtil.generateNonceStr();
2、将map转换成xml字符串(自动添加签名))
WXPayUtil.generateSignedXml(map,partnerKey);
3、将xml字符串转换整map
WXPayUtil.xmlToMap(result);
JFinal 框架
- JFinal 是基于Java 语言的极速 web 开发框架,其核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展
- 取代HttpClient
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal</artifactId>
<version>3.5</version>
</dependency>
构建二维码
- Course.vue
<script>
// 生成二维码
createCode () {
this.axios
.get("http://localhost:80/order/createCode", {
params: {
courseid: this.course.id, // 课程编号
courseid: this.course.courseName, // 课程名称
price: this.course.discounts, // 优惠价,非原价
},
})
.then((result) => {
console.log(result);
let qrcode = new QRCode('qrcode',{
width:200,
height:200,
text:result.data.code_url // 将支付连接嵌入到二维码中
});
})
.catch((error) => {
this.$message.error("二维码生成失败!");
});
}
</script>
- 支付配置
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";
}
- createCodeController
import com.alibaba.fastjson.JSON;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("order")
public class PayAction {
@GetMapping("createCode")
public Object createCode(String courseid,String coursename, String price) throws Exception {
//1.编写商户信息
Map<String,String> mm = new HashMap();
mm.put("appid",PayConfig.appid); //公众账号ID
mm.put("mch_id",PayConfig.mchid);//商户号
mm.put("nonce_str",WXPayUtil.generateNonceStr());//随机字符串
mm.put("body",coursename); //商品名称
String orderId = WXPayUtil.generateNonceStr();
System.out.println("订单编号 = "+orderId);
mm.put("out_trade_no",orderId); //商户订单号
mm.put("total_fee",price+""); //订单金额,单位分
mm.put("spbill_create_ip","127.0.0.1"); //终端IP
mm.put("notify_url",PayConfig.notifyurl); //通知地址
mm.put("trade_type","NATIVE"); //交易类型
System.out.println("发送的map = "+mm.toString());
//2.生成数字签名,并把上面的map转换成xml格式
String xml = WXPayUtil.generateSignedXml(mm,PayConfig.partnerKey);
System.out.println("转换后的xml = "+xml);
//3.将数据发送给微信后台,并得到微信后台返回的数据
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String result = HttpKit.post(url,xml);
System.out.println("返回的xml = "+result); //如果报错:<![CDATA[签名错误]]>商户四要素的原因,重置商户API密钥。
//4.后台返回的xml格式,转成map,并添加两个参数
Map<String,String> resultMap = WXPayUtil.xmlToMap(result);
resultMap.put("orderId",orderId);
resultMap.put("money",price+"");
//5.将map返回给浏览器
return resultMap;
}
}
- 解决乱码
coursename = new String(coursename.getBytes("ISO-8859-1"),"UTF-8");
- 检查支付状态
- 保存订单并更新状态