购物车系统记录

项目概述

项目git地址

后端:https://gitee.com/gxw220412/shopcar-springBoot.git

前端:https://gitee.com/gxw220412/shopcar-vue.git

后端

springBoot框架完成项目构建,自动配置,快速搭建项目。

mybatis进行与MySQL数据库交互

redis存放购物车信息

jwt完成登录校验

MD5对密码进行加密,校验处理

定时任务实时访问订单状态

调用公司网关,进行支付宝微信支付

前端

vue+ElementUI搭建前端页面

配置axios进行接口调用获取数据,配置请求,响应拦截器

获取后端时间,编写倒计时

vueRouter进行路由跳转

项目结构

springBoot项目结构

目录内容
config用来存放springBoot的相关配置类,主要包括跨域,拦截器,redisTemplate,定时器的配置类
controller对api进行分类,对同一实体的api放到同一个controller中
exceptionHandle统一异常处理,存放自定义异常类,以及配置统一异常处理类
interceptor登录拦截类 校验token
mapperDAO层,映射resources中mappers的对应xml,执行对应的sql语句
pojo实体类,映射数据库实体类,也可自定义数据用于返回数据的实体
req封装api接收的参数 为对象
res存放res,返回结果类,以及分页返回结果类
service业务逻辑层,调用mapper层方法,进行逻辑处理,完成对应功能,返回类型Result
utilsjwt,MD5,uuid工具类
resourcesmappers目录存放mapper对应
webapp用于打包时识别,也可以在pom.xml中配置false
pom.xml项目相关依赖

vue项目结构

项目实现

登录拦截

​ 实现效果:用户可直接访问首页展示商品列表,和登录页面,当用户访问其他页面或者触发请求,拦截,让用户先登录后在重新执行操作。

1.后端拦截器设置

​ 获取请求头中token,无token则拦截,校验token成功在放行,同时放行登录请求和首页查询请求

 @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURL = String.valueOf(request.getRequestURL());
        String method = request.getMethod();

        if (method.equals("OPTIONS")){
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }
        
            String authorization = request.getHeader("Authorization");
            if (authorization!=null){
                boolean verify = JwtUtil.verifyToken(authorization);
                if (verify){
                    return true;
                }else {
                    throw new LoginException("token校验失败,请重新登录");
                }
            }else {
                throw new LoginException("无token,请重新登录");
            }
        }

springBoot中配置拦截路径以及放行路径

@Component
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthorizationInterceptor())
                .addPathPatterns("/**")//拦截所有的路径
                .excludePathPatterns("/customer/login")
                .excludePathPatterns("/good/selectGoodsByPageAndKeyWord")
                .excludePathPatterns("/alipay/notify")
                .excludePathPatterns("/alipay/notify2")
                .excludePathPatterns("/alipay/notify3");
    }
}

2.前端拦截

配置路由守卫,判断localstorage中是否还有token信息

router.beforeEach((to, from, next) => {
  let token = localStorage.getItem("Authorization")

  if (!token && to.name == "login" || !token && to.path == "/") {
    router.app.$options.store.commit('setToRouterName', from)
    next()
  } else if (!token && to.name != "login" || !token && to.path != "/") {
    alert('请先登录!!');
    router.app.$options.store.commit('setToRouterName', to)
    next("/login")
  } else {
    next()
  }
})

配置axios响应拦截,未登录访问后端请求时,统一异常处理后返回状态码402,判断状态码跳转登录页

axios.interceptors .response.use(res=>{

    const code = res.data.code

    if(code == "402"){
        alert("请先登录")
        router.push("/login")
    }

    //返回配置,不然拿不到
    return res.data;
})

调用支付宝接口实现支付

支付

AliPayReq2

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AliPayReq2 {
    private String subject;
    private String out_trade_no;
    private BigDecimal total_amount;
    private String return_url = "";
    private String notify_url = "";
}

@Override
//支付调用方法
    public Result aliPay2(AliPayReq2 aliPayReq2) {
        //调用网关的地址
        String url = "http://m.jnbat.com:8081//PayGateway/alipay/pay";
        //修改订单状态的接口 支付成功后回调的api
        String notifyUrl="http://m.jnbat.com/api/alipay/notify2";
        //支付成功后前端将要跳转的界面,将订单号传给前端页面 数据会拼接到路径后面
        String returnUrl="http://m.jnbat.com/paying";
        aliPayReq2.setNotify_url(notifyUrl);
        aliPayReq2.setReturn_url(returnUrl);
       	转换为请求体
        String bizContent = JSON.toJSONString(aliPayReq2);
        System.out.println(bizContent);

		//设置请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> requestEntity = new HttpEntity<>(bizContent, headers);

		//使用restTemplate调用接口
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, requestEntity, String.class);
        String str = responseEntity.getBody();
        DataRes dataRes = JSONObject.parseObject(str, DataRes.class);
        System.out.println(str);
        return Result.success("success",dataRes.getData());

    }

返回结果

{"code":"200","msg":"成功","data":"<form name=\"punchout_form\" method=\"post\" action=\"https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.wap.pay&sign=YnOYyuYUmrc6DcOjt5QpiCHNdJOIw7eHEyIoNeKqOVYc6LxMmd7OUR%2BA%2Be%2F1DLTKchkUPHZFySBoJUvgjiHMJJGyUxOy%2FiDp5JSA5aeXAbzuHmi3r3BjhM07diCvaVvA3JS%2BoyU5to0Ox8kDfJKvVKt2b%2FTHxRxIp83rGLNjKTRCNrAlS99eZob4WwY%2B7S2nnIflj%2B%2FBi4ZTIOAWZqbCRMia%2BBLuX1K1dJnULxnCvyVlePywrg2wfvyf42ouQdaT56XU2yGCXdJ0PXeEmQAV1FQ5MJgBA27JGBeXMGzeHDaq4vb3W3doZO51tuW6D4TweogqmfJtOcLo%2FK4dVwg8Zw%3D%3D&return_url=http%3A%2F%2Fm.jnbat.com%2Fpaying&notify_url=http%3A%2F%2Fm.jnbat.com%3A8081%2FPayGateway%2Falipay%2Fnotify&version=1.0&app_id=2021000122664646&sign_type=RSA2&timestamp=2023-09-19+11%3A45%3A01&alipay_sdk=alipay-sdk-java-dynamicVersionNo&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;16172591394320230919&quot;,&quot;total_amount&quot;:198,&quot;subject&quot;:&quot;榴芒一刻 榴莲冰皮月饼 爆浆广式流心月饼 中秋节礼盒100g*6枚企业团购&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>"}

前端获取后

document.write(res.data)

支付成功后回调接口

Result

Result 	
private String code;
    private String msg;
    private T data;

AlipayNotifyDTO

public class AlipayNotifyDTO {
    private String out_trade_no;//订单编号
    private BigDecimal receipt_amount;//实付金额
    private Date gmt_payment;//支付时间
    public String getOut_trade_no() {
        return out_trade_no;
    }
    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }
    public BigDecimal getReceipt_amount() {
        return receipt_amount;
    }
    public void setReceipt_amount(BigDecimal receipt_amount) {
        this.receipt_amount = receipt_amount;
    }
    public Date getGmt_payment() {
        return gmt_payment;
    }
    public void setGmt_payment(Date gmt_payment) {
        this.gmt_payment = gmt_payment;
    }
}
    @RequestMapping("notify2")
    public String alipayMoney2(@RequestBody Result<AlipayNotifyDTO> res) throws Exception {

        System.out.println("result=============="+res);
        AlipayNotifyDTO alipayNotifyReq =  res.getData();
        String order_no = alipayNotifyReq.getOut_trade_no();
        Order order = new Order();
        order.setPay_way("aliPay");
        order.setPay_time(alipayNotifyReq.getGmt_payment());
        order.setOrder_no(order_no);

        Result result = orderService.updateStatus(order);
        return "success";

    }

支付成功后跳转页面

http://m.jnbat.com/paying

该页面定时器周期获取订单状态,订单状态变为已支付时跳转购买成功页面。

<template>
    <div class="waitSuccess">
        <span>正在修改订单状态....</span>
        <el-steps :active="1" style="width: 40%;" align-center finish-status="success">
            <el-step title="支付成功"></el-step>
            <el-step title="修改订单状态"></el-step>
            <el-step title="购买成功"></el-step>
        </el-steps>
    </div>
</template>

<script>
import axios from "../utils/axios.js"
export default {
    data() {
        return {
            order_no: ""
        }
    },
    created() {
        this.order_no = this.$route.query.out_trade_no
        let timer = setInterval(()=>{
            this.fun(timer)
        },2000)

       
    },
    methods:{
        fun(timer){
            setTimeout(()=> {
            axios({
                method: "POST",
                url: "/order/selectOrderStatus/" + this.order_no
            }).then(res => {
                console.log(res);
                if (res.data == "1") {
                    clearInterval(timer)
                    this.$message({
                        message: "订单修改成功",
                        type: "success"
                    })
                    
                    this.$router.push({ path: "/paySuccess", query: { order_no: this.order_no } })  
                } 
            })
        }, 0)
        }
    }
}
</script>

<style>
.waitSuccess {
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    align-items: center;
    height: 400px;
}
</style>

调用微信接口支付

调用支付

WeChatPayReq

@Data
@AllArgsConstructor
@NoArgsConstructor
public class WeChatPayReq {
    private String body; //描述
    private String out_trade_no; //订单编号
    private Integer total_fee;  //总金额
    private String redirect_url; //成功后跳转
    private String notify_url;

}
 @Override
    public Result weChatPay(WeChatPayReq weChatPayReq) {
        String url = "http://m.jnbat.com:8081/PayGateway/wxpay/pay";
        String notifyUrl = "http://m.jnbat.com/api/alipay/notify3";
        String returnUrl = "http://m.jnbat.com/paying";



        weChatPayReq.setNotify_url(notifyUrl);
        weChatPayReq.setRedirect_url(returnUrl);

        String content = JSON.toJSONString(weChatPayReq);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<String> requestEntity = new HttpEntity<>(content, httpHeaders);
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, requestEntity, String.class);
        String body = responseEntity.getBody();

        Result result = JSONObject.parseObject(body, Result.class);
        return result;
    }

返回结果

{
"code":200,
"msg":"success",
"data":"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?
prepay_id=wx2016121516420242444321ca0631331346&package=1405458241&redirect_url=http
://m.jnbat.com/home"
}

前端接收后

 window.open(res.data) 打开页面

拿到data中的支付链接后,前端使用window.open或href重定向即可,前端的域名必须是公司域名,否则会出现商家未配置的问题,因为微信支付只能在指定域名下才能使用

回调接口

@RequestMapping("notify3")
    public String weChatMoney(@RequestBody Result<WXPayVO> res) throws Exception {

        System.out.println("result=============="+res);
        WXPayVO wxPayVO =  res.getData();
        String order_no = wxPayVO.getOut_trade_no();
        Order order = new Order();
        order.setPay_way("wxPay");

        order.setPay_time(new Date(System.currentTimeMillis()));
        order.setOrder_no(order_no);

        Result result = orderService.updateStatus(order);
        return "success";
    }

WXPayVO

@Data
@AllArgsConstructor
@NoArgsConstructor
public class WXPayVO {
    private String out_trade_no;//订单号
    private String result_code;//支付是否成功
    private Integer settlement_total_fee;//实付金额
    private String time_end;//支付时间
}

支付成功后同上

订单状态监听器

​ 实现效果:当订单创建后,默认状态为0,监听器每隔一段时间监听数据库订单状态,如果订单超过创建时间15分钟后,状态仍未修改为已支付1,则修改订单状态为超时未支付2。前端在待支付页面,显示剩余时长倒计时。

配置定时器

在启动类上加上@EnableScheduling开启定时器支持

@SpringBootApplication
@MapperScan("com.gxw.shopcarsb.mapper")
@EnableScheduling
public class ShopcarsbApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShopcarsbApplication.class, args);
    }

}

编写定时器

@Component
@EnableScheduling
public class ScheduledOrderConfig implements InitializingBean {
    @Resource
    OrderMapper orderMapper;
	
    //定时器执行周期
    @Scheduled(fixedRate=5*60*1000)
    @Transactional //事务
    public void timer(){
        System.out.println("定时器启动");
        List<Order> allStatus0 = orderMapper.findAllStatus0();
        allStatus0.forEach(order -> {
            System.out.println("遍历循环订单");
            Date create_time = order.getCreate_time();
            //剩余时间
            Long time=System.currentTimeMillis()-create_time.getTime();
            System.out.println(time);
            //时间超过15分钟后,修改订单状态 2
            if(time>=15*60*1000){
                order.setStatus(2);
                int i = orderMapper.updateStatus(order);
                if (i != 1) {
                    throw new NormalException("修改订单状态失败");
                }
            }
        });
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        this.timer();
    }
}

前端倒计时

 count: "",
 seconds: 900
 

 axios({
            method: "POST",
            url: "/order/showOrderDetail/" + this.order_no
        }).then(res => {
            console.log(res);
            let now = new Date().getTime();
            let create_time = new Date(res.data.create_time).getTime();
             //获取剩余时间
            this.seconds = parseInt(create_time + (900 * 1000) - now);
            this.order_detail = res.data
            res.data.order_details.forEach(element => {
                this.goodsdesc = element.good_detail + this.goodsdesc
            });
        })
mounted() {
        this.Time() //调用定时器
    },
// 天 时 分 秒 格式化函数
        countDown() {
            let m = parseInt(this.seconds / 1000 / 60 % 60);
            m = m < 10 ? "0" + m : m
            let s = parseInt(this.seconds / 1000 % 60);
            s = s < 10 ? "0" + s : s
            this.count = m + '分' + s + '秒'
        },
        Time() {
            setInterval(() => {
                this.seconds -= 1000
                this.countDown()
            }, 1000)
        },
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值