- 前端环境准备
1、Node.js下载
node.js下载地址
下载方式可参考博客
Node.js最新最详细安装教程(2020)
2、安装vue-cli
参考文档
npm install -g @vue/cli
安装成功后,运行cmd输入vue -V查看版本,此时有可能提示无法使用该命令,可参考该博客
3、以上全部环境安装后,即可创建vue项目
以管理员身份运行cmd,
输入命令vue create “项目名称”
然后按照下图方式进行选择
然后根据指定操作运行即可 npm run serve
环境准备完毕,即可开始写代码
用idea打开vue项目
添加如下操作,即可用idea运行vue项目
- 前端编写
项目环境准备完后开始写前端代码。
可参考Element Plus编写前端样式。
首先引入element-plus样式
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).use(ElementPlus)
以组件的形式拼接页面
导入request.js文件,和后端进行数据交互使用
import axios from 'axios'
const request = axios.create({
timeout: 5000
})
// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
// config.headers['token'] = user.token; // 设置请求头
return config
}, error => {
return Promise.reject(error)
});
// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
response => {
let res = response.data;
// 如果是返回的文件
if (response.config.responseType === 'blob') {
return res
}
// 兼容服务端返回的字符串数据
if (typeof res === 'string') {
res = res ? JSON.parse(res) : res
}
return res;
},
error => {
console.log('err' + error) // for debug
return Promise.reject(error)
}
)
export default request
//在需要与后端交互的操作中使用request,这里request必须与导入request.js一致,否则报错。
//这里url为后端接口,也是整个前后端项目的核心所在,前后端交互只需使用相应的接口即可
save(){
request.post("http://localhost:9091/user",this.form).then(res =>{
console.log(res)
})
this.dialogVisible=false
}
后:
1、技术选型
vue+springboot+ElementUI+shiro
及支付宝支付接入
2、功能概述
1、登录及注册功能:
2、信息管理
可实现增删改查操作及分页功能
3、书籍管理
可进行增删改查操作及书籍购买功能
4、我的商品
记录已购买商品,可删除购买记录。
3、各种坑的记录
3.1、支付宝接入
@Controller
@CrossOrigin
@RequestMapping("/pay")
public class PayController {
@ResponseBody
@GetMapping
public String pay(@RequestParam String OutTradeNo,@RequestParam String TotalAmount,@RequestParam String SubjectQx) throws AlipayApiException {
AlipayClient alipayClient = new
DefaultAlipayClient("https://openapi.alipaydev.com/gateway.do", "2021000118622319", "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDjV5nkoMfeh6ZW6f3DvuIYnQLfkGEBpi7ZMkuLgZ5MVeAdybEJLSuB2m5cvS8Ba2b90p6OiUv4apCoeaU/JeaYzXV6JVRWtK2RkZ/YrdwyTcSE3QIk8OsnkeuTbtiS71pE9gKDxPvKSflrl5qwdyBxjTZMCmbnZX9NYIahbSuFrSAzWMa4yRM5aTzyWAgIDFn/Bs8LDzCQb9Xa1XZH2UM5VnM5Itw8OErsNDHGA4tuMLgtQYQD4pvDx+EujzOECFCSutlTJdEOsLhMlzwX/NPgFNuv1ipamghCnlfE0lKGuCTWLtmpDgJLM98PjX7gGmhXcveYpX5AemxDGLn6FClfAgMBAAECggEBAIs9HQE9RAzUEhnLaGQaJgojKB5bJD7JVIosVr6T3astMX2tCSPwqWP3r35XVFHPQR4WgIwWJRR52C2Hi6D4ipptxBTShiwwrtmoUjl4ipO4s7MYVXbgo3Xx1lHPIxAjTqtL7VBdNkW4Nu4hxVltLM6/SVEbUdlJfcWbZ/m4jl5o+fmXVw+lrnmfb7Fn/CAouT4csneEIjPfHsFyHbEIbok0v60GaehXk/b7zzWRDcBwJcmZbIHmh2hkWq1fZJwaqxAVtpGn4hjpy5n6Uep1GxL6/bSFkhOIT8yoctjfYgzsIIOBCB6sEbMHKWHzCJfGvOSqL/Oqy3HN+b+OV1KEMEkCgYEA8vjmUNz662VHs0mmeDzGovtyzyIWlBl4u8zdcCRFwG/Z7tz9aA7sCgv0T3+JWeahY248TiagyFxFP0BQHotumxJYhHR+Jll0GYWgXuLpegjf8642x4Ty8W2dPREdPBwbNsmX3Skcn5zLNkgIPjpxuTUXpTqDJPRorjVqLjP47P0CgYEA74gotzZfSfK4PNUhSqhI0yDnX5hqTEUOfd7YBOTzkfs7UTEnGYfCDNRJQhFpe0RWj3s1uDTBI2IG4GWdEnyofEFvsF0kye9loF3iMbcP6O7ppLeF7APLOvMuwpCJ2+xhuL07tysPH3o2p4j1KTWzRF3chsM312b8y0cWkbzwLIsCgYB80ENwErSLK9HzVchAR+g6XTUTFo3GsncE8i5mpxt1yX+IZtl+FPxIBPARzpA+MG2DaGNuOgWqJ0wjO+RsbG/7FSeHk1giVx1+5tgFJkeNuvVgdoQJZlBumUKG1j3QXRfVwyVSJQZhL+hsEAC8BsXYwF9PL4mi+DK2GxZaebRL+QKBgHEhZOGLW5wIeD27B9tD+glFjCHZUbMuFncajfpZzDRRbSWeynHDbcIzbVo38RLYJu3jam7S87lrbgYD2BjIKaduHHs2ss5xNBV24gNLC8efN1ilmSnsbMst2hLow0PMtCbVsWOEgcMDLNcZ13icNUv/7Mz1XOVubczwiVdR3jmbAoGBAJ/grdLCK32p3rRykJ7ozDt0H/USZWBcarNI3+5pONBqyzASP0Y4mXhscsFczxGZhC2QZaJalWo5zhSOAvbTirwWZxihG347AIrpG/yPBKsBEKBozO4FzSYmPkGaNeqTPncWYD/hsodSGvYvOe8/yVeIWwPZnMVwIXGMKZPvdfDS", "json", "utf-8",
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkA452+NCGejZNdcBT0T1mSIgYTkdNKvR4NbI0Dfv5fFKK" +
"wJzu5uTQMUVWtHIWVbin7HvHWte8c5sJitPzGlUhLMaasnYCTxLW1m5sOcjbKfWg5tPqcOzMFcM5ayfu6eb1QKjc2NJuhB58FP/es4UHGW1W" +
"kxNP/BkMvVVB0GU9D63uBa2ZPKvg5iwd9FMlvMOVZOLhAf6Yb1zFa+mYYnSlBTSiI4TjaXPfE0mEoCfgpjX4w1Uopvf1rmQCfm3jY3MFIkwo" +
"K0TEsGapxEJucmRT9IXD1djJ+rqoflGy77Ff232M9ga57d/VD4EXFI/wz2FEffuBfWWY6zqBIFbimpBJQIDAQAB", "RSA2");
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(OutTradeNo);//流水号
model.setSubject(SubjectQx);//订单标题
model.setTotalAmount(TotalAmount);//订单总金额
model.setQuitUrl("http://localhost:8080/myproduct");
request.setNotifyUrl("http://9g73zd.natappfree.cc/notify");
request.setReturnUrl("http://localhost:8080/myproduct");
request.setBizModel(model);
String form = alipayClient.pageExecute(request).getBody();
return form;
}
}
这里request.setNotifyUrl所请求的url必须为外网所能能够访问到的,采用内网穿透实现
具体操作可借鉴NATAPP
PayComfirmController
用于接收支付结果,将支付结果等数据插入数据库
package com.example.springboot.controller;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.springboot.common.Result;
import com.example.springboot.mapper.TradeMessageMapper;
import com.example.springboot.pojo.TradeMessage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Controller
@CrossOrigin
public class PayConfirmController {
@Resource
TradeMessageMapper tradeMessageMapper;
@ResponseBody
@RequestMapping("/notify")
public void notfiyPay (HttpServletRequest request) throws AlipayApiException {
Map<String,String> stringStringMap = convertRequestParamsToMap(request) ;
boolean signVerified = AlipaySignature.rsaCheckV1(stringStringMap,"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkA452+NCGejZNdcBT0T1mSIgYTkdNKvR4NbI0Dfv5fFKKwJzu5uTQMUVWtHIWVbin7HvHWte8c5sJitPzGlUhLMaasnYCTxLW1m5sOcjbKfWg5tPqcOzMFcM5ayfu6eb1QKjc2NJuhB58FP/es4UHGW1WkxNP/BkMvVVB0GU9D63uBa2ZPKvg5iwd9FMlvMOVZOLhAf6Yb1zFa+mYYnSlBTSiI4TjaXPfE0mEoCfgpjX4w1Uopvf1rmQCfm3jY3MFIkwoK0TEsGapxEJucmRT9IXD1djJ+rqoflGy77Ff232M9ga57d/VD4EXFI/wz2FEffuBfWWY6zqBIFbimpBJQIDAQAB","utf-8","RSA2");
if(signVerified)
{
String trade_no=stringStringMap.get("trade_no");
String date=stringStringMap.get("gmt_payment");
String trade_status=stringStringMap.get("trade_status");
String out_trade_no=stringStringMap.get("out_trade_no");
TradeMessage tradeMessage=new TradeMessage(trade_no,date,trade_status,out_trade_no);
tradeMessageMapper.insert(tradeMessage);
// System.out.println(stringStringMap);
}
}
@ResponseBody
@GetMapping("/confirmTrade/{date}")
public Result<?> confirmTrade(@PathVariable String date)
{
LambdaQueryWrapper<TradeMessage> wrapper = Wrappers.lambdaQuery();
wrapper.eq(TradeMessage::getOutTradeNo,date);
TradeMessage tradeMessage=tradeMessageMapper.selectOne(wrapper);
//System.out.println(tradeMessage);
return Result.success(tradeMessage);
}
//将request中请求转变为map
private static Map<String,String> convertRequestParamsToMap(HttpServletRequest request)
{
Map<String,String> retMap = new HashMap<String,String>();
Set<Map.Entry<String,String[]>> entrySet = request.getParameterMap().entrySet();
for(Map.Entry<String,String[ ]> entry : entrySet)
{
String name = entry.getKey();
String[ ] values = entry.getValue( );
int valLen = values.length;
if (valLen == 1)
{
retMap.put(name,values[0]);
} else if (valLen > 1)
{
StringBuilder sb = new StringBuilder();
for(String val : values)
{
sb.append("," ).append (val);
}
retMap.put(name,sb.toString().substring(1));
}
else {
retMap.put(name,"");
}
}
return retMap;
}
}
整个支付逻辑:
1、确定按钮:调用接口“/pay”,传入前端参数params: { OutTradeNo: date, TotalAmount: this.formBook.totalPrice, SubjectQx: this.formBook.productName }
2、支付成功后,进入异步请求,用于接收支付结果状态成功或失败 “/notify”,将数据插入数据库中以便后续取出作为判断依据。
3、接下来请求 “/confirmTrade/{date}”,用于取出支付状态,判断支付是否成功,成功则进入下一步,否则失败
4、若第三步成功,则可请求“/purchase”,将所购买的书籍插入到数据库中,至此,整个支付流程才算成功
注意前端逻辑代码编写:
saveBook(){
//购买中的确定按钮
//在此添加支付宝确认功能,返回参数若为支付成功,即可进入下一步操作,插入数据库
/*注意,这里的request请求后台之后,后台确实可以返回数据,但是不会自动跳转到指定的url
* ,其作用就是单纯的对数据进行处理。比如后台通过数据处理后,想要把数据呈现到前端时,必须让它
* 主动跳转
* */
//const date=1632847324588
const date=new Date().getTime()
console.log(date)
request.get("http://localhost:9091/pay",
{
params: {
OutTradeNo: date,
TotalAmount: this.formBook.totalPrice,
SubjectQx: this.formBook.productName
}
}).then(
//这里url采用拼接的方式进行请求
console.log(date),
window.location.href='http://localhost:9091/pay?OutTradeNo='+date+'&TotalAmount='+this.formBook.totalPrice+'&SubjectQx='+this.formBook.productName
)
//嵌套request无法请求问题
request.get("http://localhost:9091/confirmTrade/"+date).then(res =>{
console.log(res)
this.flagOfPay=res.data.tradeStatus
})
if(this.flagOfPay === "TRADE_SUCCESS")
{
console.log("进入成功")
request.post("http://localhost:9091/purchase",this.formBook).then(res => {
console.log("方法执行成功")
if(res.code === '0'){
this.$message({
type: "success",
message: "购买成功"
})
}
else{
this.$message({
type: "error",
message: res.msg
})
}
})
}
else
{
this.$message({
type:"error",
message:"支付失败"
})
}
this.dialogVisibleBook=false
this.formBook={}//不能使用this.form 否则会出现第二次点击购买时无法弹出页面
}
3.2 shiro框架接入
报302错误,屏蔽options请求、
可参考博客解决权限验证提示没有认证(Authentation)的问题
前后端分离时后端shiro权限认证
注解不产生作用可能没有引入aop,可参考前后端分离Shiro未执行授权方法解决办法