点餐小程序开发C端

一、项目简要介绍

1、技术介绍

  • 前端:微信小程序
  • 后端:Java、SpringBoot、MyBatisPlus、RabbitMQ
  • 数据库:Redis、MySQL
  • 运维:Docker、Rancher容器编排工具

2、项目功能

  1. 基础点单功能
  2. JWT登陆校验
  3. 优惠劵功能(无门槛优惠劵、满减劵、新用户自动发放新人优惠劵)
  4. 积分功能(积分兑换、等级提升)
  5. 订单后端校验+优惠劵使用
  6. 自动生成递增的取餐号(加锁预防高并发问题)
  7. 15分钟未支付自动取消订单+回滚商品库存和优惠劵数据

3、页面展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、封装后端请求

为了代码的可读性和可维护性,可以将请求接口进行封装;需要在根目录下创建一个 http 文件夹,然后在里面创建 request.jsapi.js 两个文件(名字都可以随意);

1、request.js

reqeust.js 对 wx.request() 进行二次封装

module.exports = {
  request: (url, method,token,data = {}) => {
    // 拼凑url
    let _url = `http://localhost:8093/${url}`
    
    // 发送请求
    return new Promise((resolve, reject) => {
      // 显示加载界面
      wx.showLoading({
        title: '正在加载',
      })
      // 拼装请求
      wx.request({
        url: _url,
        data: data,
        method: method,
        header: {
          'content-type': 'application/json',
          'token': token // 将token放在请求头中
        },
        success: (res) => {
          let {code} = res.data
          // 请求成功
          if (code == 0) {
            // 结束加载界面
            wx.hideLoading()
            // 返回后端数据
            resolve(res.data)
          } 
        }
      })
    })
  }
}

2、api.js

请求的接口

// 引入二次封装后的request
const {request} = require('./request')

module.exports = {
  /**
   * 用户登陆:POST请求
   */
  userLogin: (e,token) => {
    return request('tea/api/v1/user/login', 'POST', token,{code: e})
  },

  /**
   * 获取商品列表信息:GET请求
   */
  getProductList: () => {
    return request('tea/api/v1/product/list', 'GET');
  },
}

三、组件库的安装使用

这里采用的是 Lin UI 微信小程序原生语法 实现的组件库

1、组件库安装

直接通过git下载 Lin UI 源代码,并将 dist 目录(Lin-UI 组件库)拷贝到自己的项目中。

git clone https://github.com/TaleLin/lin-ui.git

在这里插入图片描述

2、组件库的使用

这里采用按需加载组件,在需要使用组件的页面的json配置文件中进行配置

在这里插入图片描述

三、JWT登陆校验

JWT是一个开放标准,简单来说就是通过一定的规范生成token,然后可以通过解密算法逆向解密token,尽而获取用户信息;由于生成的 token 是存储在客户端,所以并不占用服务端的内存资源;
微信为每个小程序的用户准备了一套 openId ,是该用户在当前小程序中的唯一标识,后端可以通过 openId 来确定当前操作的用户。前端将登陆凭证 code 发送给后端,后端携带 code 请求微信服务器获取 openId,然后后端生成 token 返回给前端;

1、前端:获取登陆凭证

通过调用 wx.login() 接口获取登陆凭证(code),登陆凭证有效期五分钟;将后端返回的 token 保存到本地storage中

userLogin(){
  wx.login({
    success(res) {
      if (res.code) {
        // 将 code 发送给后端
        userLogin(res.code).then(res => {
          if (res.code == 0) {
            // 将token保存到缓存中
            wx.setStorageSync('token', res.data.token)
          }
        })
      } else {
        console.log('登录失败!' + res.errMsg)
      }
    }
  })
},

/**
* 页面渲染时触发
*/
onLoad(){
  this.userLogin()
},

2、后端:获取 openid

(1)调用 auth.code2Session 换取 openid

private String getOpenId(String code) throws IOException {
    // 构造查询url
    String url = "https://api.weixin.qq.com/sns/jscode2session";
    url += "?appid=微信开发工具右上角点击详情可见";  // Appid
    url += "&secret=登陆小程序管理网页可见";// AppSecret
    url += "&js_code=" + code; // 前端传入的登陆凭证
    url += "&grant_type=authorization_code";
    url += "&connect_redirect=1";
    
    // 构造请求
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    // DefaultHttpClient();
    HttpGet httpget = new HttpGet(url);    //GET方式
    CloseableHttpResponse response = null;
    
    // 配置信息
    RequestConfig requestConfig = RequestConfig.custom()
        .setConnectTimeout(5000) // 设置连接超时时间(单位毫秒)
        .setConnectionRequestTimeout(5000)  // 设置请求超时时间(单位毫秒)
        .setSocketTimeout(5000) // socket读写超时时间(单位毫秒)
        .setRedirectsEnabled(false) // 设置是否允许重定向(默认为true)
        .build();
    httpget.setConfig(requestConfig);  // 由客户端执行(发送)Get请求
    
    // 获取响应实体
    String res = null;
    response = httpClient.execute(httpget); // 从响应模型中获取响应实体
    HttpEntity responseEntity = response.getEntity();
    if (responseEntity != null) {
        res = EntityUtils.toString(responseEntity);
    }
    
    // 释放资源
    if (httpClient != null) {
        httpClient.close();
    }
    if (response != null) {
        response.close();
    }
    
    // 解析响应对象 res,获取 JsonId
    JSONObject jo = JSON.parseObject(res);
    String openid = jo.getString("openid");

    return openid;
}

(2)将用户信息保存到数据库中
可以暂时只创建这三个字段:id、openId、createTime

3、后端:token生成和校验

(1)添加依赖

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.7.0</version>
</dependency>

(2)生成token

private static final String SUBJECT = "luomiaosen"; // JWT主题
private static final long EXPIRE = 1000*60*60*24; // Token 过期时间 24 小时
private static final String SECRET = "skjdlasjdljsajddsad"; // 密钥(!超级重要,不可以泄漏)
private static final String TOKEN_PREFIX = "luomiaosen:"; // 令牌前缀

public static String geneJsonWebToken(UserDO userDO, HttpServletRequest request){
    // 生成 token
    String token = Jwts.builder().setSubject(SUBJECT)
        .claim("id",userDO.getId()) // 进行加密判断的信息1
        .claim("agent",request.getHeader("user-agent")) // 进行加密判断的信息2
        .setIssuedAt(new Date()) // 设置发布时间
        .setExpiration(new Date(System.currentTimeMillis()+EXPIRE)) // 设置过去时间
        .signWith(SignatureAlgorithm.HS256, SECRET) // 设置签名方式
        .compact(); // 生成token
    // 加上前缀
    token = TOKEN_PREFIX + token;
    
    return token;
}

(2)校验token

private static final String SECRET = "skjdlasjdljsajddsad"; // 密钥(!超级重要,不可以泄漏)
private static final String TOKEN_PREFIX = "luomiaosen:"; // 令牌前缀

public static Claims checkJWT(String token){
	try{
    	final Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();
    	return claims;
	}catch (Exception e){
    	log.info("解密失败");
    	return null;
	}
}

4、后端:登陆拦截器

(1)登陆拦截器

/**
* 登陆拦截器
*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    // 定义线程局部变量
    public static ThreadLocal<LoginUser>threadLocal = new ThreadLocal<>();

    // 登陆拦截器处理-前处理
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler){
        // 1.获取请求传入的token(token可能在请求头,也可能在请求参数上)
        String token = request.getHeader("token");

        // 2.若token问空,返回false
        if(token==null){
            // 返回未登陆信息给客户端
            CommonUtils.sentJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
            return false;
        }

        // 3.若token不为空,校验token
        Claims claims = JWTUtils.checkJWT(token);
        // 3.1 校验失败
        if(claims == null){
            CommonUtils.sentJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
            return false;
        }
        // 3.2 校验成功(获取token中的用户信息)
        String idInfo = claims.get("id").toString();
        Integer id = Integer.valueOf(idInfo);
        // 3.3 传递用户信息
        LoginUser loginUser = new LoginUser();
        loginUser.setId(id);
        threadLocal.set(loginUser);

        return true;
    }

}

LoginUser.class

/**
 * 登陆用户信息
 */
@Data
public class LoginUser {
    private Integer id;
    // 可以添加其他
}

CommonUtils.sentJsonMessage 方法

public class CommonUtils {
    /**
	* 返回Json数据
	*/
    public static void sentJsonMessage(HttpServletResponse response, Object obj) {
        // 提供一些功能将转换成Java对象匹配JSON结构
        ObjectMapper objectMapper = new ObjectMapper();
        // 设置响应类型
        response.setContentType("application/json;charset=utf-8");
        try {
            PrintWriter writer = response.getWriter();
            // 将传入对象写入response
            writer.print(objectMapper.writeValueAsString(obj));
            writer.close();
            // 刷新response缓存
            response.flushBuffer();
        } catch (IOException e) {
            log.info("响应Json数据给前端异常:{}",e);
        }
    }
}

(2)拦截器配置

/**
 * 登陆拦截器和放行路径开发配置
 */
@Configuration
@Slf4j
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry){
        // 登陆拦截器
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/user/**","/product/**","/order/**")
                .excludePathPatterns("/user/login","/product/get/*","/product/list")
                .order(0);
    }
}

四、发放新人优惠劵

后端通过 openId 来判断用户是否是新用户,若为新用户则发放新人优惠劵,并除了返回token外,还返回 couponMsg 优惠劵信息 以及 show=true,供前端判定是否需要新人优惠劵弹窗;
发放优惠劵采用 RabbitMQ 消息队列的方式,实现高性能、高可用,防止优惠劵方法失败导致用户登陆失败;

1、前端:新人优惠劵弹窗

l-popup 是 Lin UI 的弹窗组件

<l-popup show="{{show}}">
  <view>新人优惠劵弹窗内容:自定义</view>
</l-popup>

首页渲染时就触发用户登陆函数,后端返回 show 判断是否需要展示弹窗

/**
* 用户登陆函数
*/
userLogin(){
  var that = this
  wx.login({
    success(res) {
      if (res.code) {
        userLogin(res.code).then(res => {
          if (res.code == 0) {
            wx.setStorageSync('token', res.data.token)
            
            // 新增:后端传回 show ,判断是否需要弹窗
            if(res.data.show=="true"){
              that.data.show = true
            }else if(res.data.show=="false"){
              that.data.show = false
            }
            that.setData({
              coupon: res.data.couponMsg,
              show: that.data.show
            })
            
          }
        })
      } else {
        console.log('登录失败!' + res.errMsg)
      }
    }
  })
},

/**
* 页面渲染时触发
*/
onLoad(){
	this.userLogin()
},

2、后端:用户登陆判断

@Override
@Transactional
public JsonData getUserInfo(String code, HttpServletRequest request) throws IOException {

    HashMap<String, Object> map = new HashMap<>();

    // 获取用户openId
    String openId = getOpenId(code);
    // 判断数据库中是否存在该用户
    UserDO userDO = userMapper.selectOne(new LambdaQueryWrapper<UserDO>().eq(UserDO::getOpenId, openId));
    if(userDO==null){
        // 插入新用户
        UserDO newUserDO = new UserDO();
        newUserDO.setOpenId(openId);
        newUserDO.setCreateTime(new Date());
        newUserDO.setBonus(0);
        userMapper.insert(newUserDO);
        // 自动领取新人优惠劵(消息队列)
        CouponMQMessage couponMQMessage = new CouponMQMessage();
        couponMQMessage.setCouponId(1);
        couponMQMessage.setUserId(newUserDO.getId());
        rabbitTemplate.convertAndSend(rabbitMQConfig.getCouponEventExchange(),rabbitMQConfig.getCouponRoutingKey(),couponMQMessage);
        // 返回token
        String token = JWTUtils.geneJsonWebToken(newUserDO, request);
        map.put("token",token);
        // 获取新人优惠劵信息
        List<CouponVO> couponVOS = getCouponMsg();
        map.put("couponMsg",couponVOS);
        map.put("show","true");

        return JsonData.buildSuccess(map);
    }
    // 存在,返回token信息
    String token = JWTUtils.geneJsonWebToken(userDO, request);
    map.put("token",token);
    map.put("show","false");
    return JsonData.buildSuccess(map);
}

3、后端:MQ发放优惠劵

(1)配置MQ

@Configuration
@Data
public class RabbitMQConfig {

    /**
     * Json方式序列化
     */
    @Bean
    public MessageConverter jsonMessageConverter(){
        return new Jackson2JsonMessageConverter();
    }

    // 名称
    @Value("${mqconfig.coupon_event_exchange}")
    private String couponEventExchange;
    @Value("${mqconfig.coupon_queue}")
    private String couponQueue;
    @Value("${mqconfig.coupon_routing_key}")
    private String couponRoutingKey;

    /**
     * 交换机
     */
    @Bean
    public Exchange couponEventExchange(){
        return new TopicExchange(couponEventExchange,true,false);
    }

    /**
     * 队列
     */
    @Bean
    public Queue couponQueue(){
        return new Queue(couponQueue,true,false,false);
    }

    /**
     * 队列与交换机绑定
     */
    @Bean
    public Binding couponBinding(){
        return new Binding(couponQueue,Binding.DestinationType.QUEUE,couponEventExchange,couponRoutingKey,null);
    }
}

(2)发送消息

@Resource
private RabbitMQConfig rabbitMQConfig;
@Resource
private RabbitTemplate rabbitTemplate;

// 参数:交换机名称、routingKey、发送的消息
rabbitTemplate.convertAndSend(rabbitMQConfig.getCouponEventExchange(),rabbitMQConfig.getCouponRoutingKey(),couponMQMessage);

(3)接收处理消息

@Component
@RabbitListener(queues = "${mqconfig.coupon_queue}")
public class CouponMQListener {

    @Resource
    private CouponServiceImpl couponServiceImpl;

    /**
     * 自动发放优惠劵:注意 发送的消息类型 和 接受消息的类型要一致,我这里是一个类 CouponMQMessage
     */
    @RabbitHandler
    @Transactional
    public void orderHandler(CouponMQMessage couponMQMessage, Message message, Channel channel) throws IOException {
        // msgTag消息投递序号
        long msgTag = message.getMessageProperties().getDeliveryTag();
        // 发放优惠劵
        couponServiceImpl.addCouponRecord(couponMQMessage.getCouponId(),couponMQMessage.getUserId());
        // 消费成功
        channel.basicAck(msgTag, false);
    }

}

五、订单后端校验

待补充

六、十五分钟未支付自动取消订单

待补充

七、回滚商品库存和优惠劵数据

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
微信点餐小程序是基于微信的点餐小程序,它采用了基于Web服务模式,可以在Internet环境下使用,用户不受时间和地点的限制。\[1\]在开发微信点餐小程序时,需要进行一些项目配置和安装依赖。首先,在vite.config.ts配置文件中添加相应的配置,包括设置服务器主机、口号、开启热更新和启动浏览器等。\[3\]其次,需要安装路由依赖,并创建路由文件,然后在main.ts中引入路由,并修改App.vue文件以适配路由。\[2\] 以上是微信点餐小程序开发的一些基本步骤和配置要点。具体的开发教程和源码可以参考相关的视频讲解教程和文档。 #### 引用[.reference_title] - *1* [node.js毕业设计基于微信的点餐小程序(源码+程序+LW+部署)](https://blog.csdn.net/sheji1056/article/details/128567191)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [vue3+uniapp+springboot开发校园点餐系统/微信点餐小程序03-项目路由安装](https://blog.csdn.net/weixin_47741691/article/details/130312400)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值