文章目录

  • XOrder
    • 前言
    • 环境
      • 微信开发者工具
      • Java开发环境
      • Vue开发环境
    • 成果展示
    • 小程序端
      • 小程序前端
        • 扫码点餐
        • 文件树
        • 技术选型
        • 鸣谢
      • 小程序后端
        • 文件树
        • 技术选型
        • 项目总结
      • 未完成的任务
    • 管理系统端
      • 管理系统前端
        • 文件树
        • 技术选型
      • 管理系统后端
        • 文件树
        • 技术选型
        • 项目总结
        • 未完成的任务
    • 总结
      • 未完成的任务
    • 总结

XOrder

前言

源码仓库地址
源码仓库地址-国内镜像加速地址

https://github.com/happysnaker/HXOrder.git
https://hub.fastgit.org/happysnaker/HXOrder.git

该项目分为小程序前后端和后台管理系统前后端,无论小程序端还是管理系统均为 SpringBoot + Vue / Wx 前后端分离项目,项目的定位是一个 Java 入门项目,在开始做此项目之前,我没有任何的前端基础,也没有使用过任何的 Java 开发框架,学习与这个项目是同步开始的,这个项目从头到尾都是一个人完成的,也没有看网课提前学习(导致了很多架构不合理,无奈重构),也算是边学边实践了。

这个项目并未涉及到分布式相关,我的下一个学习计划就是学习分布式相关,学长建议我实现 RPC 框架,或许到时候回来重构项目,希望你我一起努力。

  • 小程序前端项目
  • 小程序后端项目
  • 管理系统前端项目
  • 管理系统后端项目
  • SQL文件(不包含建库语句,项目数据库为:restaurant)

环境

微信开发者工具

下载开发者工具直接导入项目即可。

Java开发环境

IDEA2021.2

JDK11

Vue开发环境

不知道,前往介绍 | vue-element-admin (panjiachen.github.io)自行了解。

成果展示

后台管理系统:XOrder后台管理系统

小程序端由于未申请企业账号不允许上线,可自行下载代码体验,部分照片:

小程序端

小程序前端

扫码点餐

在小程序首页点击扫码点餐,前往草料二维码生成器 (cli.im)生成,文本内容为 storeId=XXX&table=XXXX,例如:

文件树

├─component                  # 自定义的组件
├─dist                      # 引用的组件库
├─images                    # 存放静态图片
└─pages                     # 主页面├─address              # 收货地址页面├─cancel-reason         # 退货原因页面 ├─collection           # 收藏菜品页面├─dish-detail           # 订单详情├─dish-order          # 点餐主页面├─index                  # 首页├─integral              # 会员积分(待实现)├─member             # 会员(待实现)├─message              # 我的消息├─order                   # 订单页面├─order-detail            # 订单详情├─order-meal          # 点餐页面├─order-ok                # 订单已确认├─order-sure         # 确认订单└─user                    # 我的
├─app.json                  # 配置 tarbar,引入 dist 组件库
├─app.js                    # 配置全局遍历
├─app.wxss                  # 配置全局样式

技术选型

  • Html
  • Css
  • JavaScript

鸣谢

VanApp 轻量、可靠的小程序 UI 组件库

小程序后端

这算是我的第一个 SpringBoot 项目了,很青涩,甚至参数还得从 httpServletRequest 取

文件树

├─logs
├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─happysnaker
│  │  │          ├─api          # 引入百度地图 API 进行经纬度解析
│  │  │          ├─config       # 配置文件
│  │  │          ├─controller   #
│  │  │          │  └─base      #
│  │  │          ├─exception    # 自定义异常
│  │  │          ├─filter       # 自定义过滤器
│  │  │          ├─mapper       # dao 层
│  │  │          ├─Observer     # 观察者
│  │  │          │  └─impl
│  │  │          ├─pojo         # POJO
│  │  │          ├─service      # 业务层
│  │  │          │  └─impl
│  │  │          └─utils        # 实用类
│  │  └─resources
│  │      ├─com
│  │      │  └─happysnaker
│  │      │      └─mapper
│  │      ├─static
│  │      └─templates
│  └─test
│      └─java
│          └─com
│              └─happysnaker

技术选型

技术 说明 官网
SpringBoot 容器+MVC框架 https://spring.io/projects/spring-boot
MyBatis ORM框架 http://www.mybatis.org/mybatis-3/zh/index.html
RabbitMQ 消息队列 https://www.rabbitmq.com/
Redis 分布式缓存 https://redis.io/
Lombok 简化对象封装工具 https://github.com/rzwitserloot/lombok
Swagger-UI 文档生成工具 https://github.com/swagger-api/swagger-ui

项目总结

  • 锻炼了项目开发、排错能力重构能力,提高了逻辑思维能力,提升了原本菜的抠脚 SQL 水平,这是最基本也是最重要的内力提升了。
  • Wx官方定义用户openid为敏感数据不应该在网上传输,所以我对原先的项目架构进行了重构,我没有去更改原先的架构,而是采用利用了 token 思想,让小程序带着 token 访问,定义过滤器辅以责任链模式,在控制层之前进行拦截验证,透明的设置参数,使得原先的代码无须任何改变。

​ 在此,我学到了如何去生成(手动) token,jwt 形式的 token 是由 头部、载荷和签名构成的,头部是一些标识信息,如 jwt 形式、签名算法等;载荷是要承载的数据,如用户ID、过期时间、发行人等等;头部和载荷采用 base64 进行 ascii 编码,它们仍然是明文的,因此不应承载敏感数据;签名就是利用私匙对头部和载荷进行加密,这就是计网里面的 MAC(报文完整性)加密,通过对头部和载荷加密我们确定这个 token 有没有被篡改,因为私匙只有我们知道,token 生成在 utils 包下的 token 实用类生成,此外我还定义了 TokenBuilder 类,以建造者模式去生成 token。

​ 抽象过滤器如下所示,子类继承抽象类并实现抽象方法即可,具体的 doFilter 方法由抽象类完成,子类做自己要做的即可,这利用了模板方法的设计模式。

public abstract class AbstractFilterChain implements Filter {AbstractFilterChain myFilterChain;@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {if (isRequired((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse)) {if (!doFilter((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse)) {return;}}if (myFilterChain != null) {myFilterChain.doFilter(servletRequest, servletResponse, filterChain);return;}filterChain.doFilter(servletRequest, servletResponse);}public abstract boolean isRequired(HttpServletRequest request, HttpServletResponse response);public abstract boolean doFilter(HttpServletRequest request, HttpServletResponse response);
}
  • 静态热数据利用 redis 缓存,如首页的菜品信息、店铺信息等。
  • 频繁变更数据利用 Redis 缓存,例如用户对菜品的喜欢、收藏等,Redis 初始化时读取全部 ID,利用 bitmap 做一个简单的布隆过滤器,防止缓存穿透。在本项目中,我对访问的每一个用户都维护了一个 bitmap,当用户数量增多时,必须要考虑内存问题。从理论上说,菜品的喜欢数量理应使用 redis 缓存,然后读取时以 redis 同步,但我并没有这么做,理由很简单,在我想到这一点时业务层已经完成了,我不想在去改业务层代码,我想到可以利用代理模式进行控制,但最终我选择了在控制层实现,每个控制层维护菜品的喜欢数量,当增减达到一定阈值时,通知观察者将它们写入。

我尝试利用 jdk 动态代理生成Service代理,但这样做会一直报错(xxx proxy$104 无法转化 xx)

  • 下单时处理是整个项目最繁琐的地方,在这个地方,我一共重构了五次:

    • 第一次架构很朴素,符合我的思想,这里一定要注意 SELECT 要 FOR UPDATE,不能读版本快照:

    • 第一次的性能不好,因为每个是串行化的,第二次我尝试将锁细化,即对菜品 ID 上锁而不是对整个方法上锁,利用 redis setnx可以很容易的实现一个分布式锁,将锁细化后性能明显提升。

    PS: 这里如果面试官问还可以怎么优化分布式锁,除了锁住商品ID细化之外,我们还可以将库存表分区,对不同区使用不同的锁,这样又可以将锁细化。

    • 第三次我在 SQL 层做了乐观锁,怎么做呢?其实就是在执行SQL时判断库存是否大于需要下单的数目,就像下面这样:

      UPDATE `dish` SET stock = stock + #{val} WHERE id = #{id} AND stock > #{val};
      

      当然是乐观锁就会存在 ABA 问题,不过版本对这里没有影响。SQL返回的是影响的行数,我们可以根据影响的行数判断是否插入成功,然后让SpringBoot帮我们回滚。现在的架构是:

    • 好吧,明眼人都能看出来,插入订单、增加积分、添加等待队列、减少折扣次数等等可以异步去做,所以我的第四次重构就是将这些任务用线程池去异步完成。这个时候去测试网站(多线程疯狂提交)发现效率比第一次提高了 40%,已经很优秀了,经过模拟,确实也没啥问题,不过并发量太小了,测试也没啥意义。

    • 上面的架构的问题是如果用户下单多个商品,一些库存足,一些不足,那么足的肯定扣了库存,不足的又会抛出异常,读写都是 update 操作,会锁定数据库,效率比较低。因此考虑引入缓存进行提前判断,先判断缓存是否充足,充足的话减缓存,最后利用数据库乐观锁重新判断一次,同时,这个时候我把线程池换成了消息队列,线程池和消息队列都可以实现异步,但消息队列可以实现完全解耦,更适用于分布式,现在的架构是:

​ 现在它已经足够优秀了,我的重构之路就到此结束了,但我们还有一个问题没有解决:**Redis中没有 key 怎么办?**这个问题没有那么简单,主要是两个问题:

  1. 考虑 A,B 同时察觉到没有缓存,都尝试更新,假设我们已经对更新操作上锁,那么可能会出现 A 更新了缓存为数据库中的 XX,然后立马扣减了缓存,此时 redis 为 XX1,这个时候 B 才姗姗来迟,B又将缓存更新数据库中的 XX,造成不可重复读。
  2. 即使只有一个更改者 A 想要更新缓存,但是此时可能 MQ 中有消费者 B 正准备扣减缓存,A先入,更新为 XX,此时 redis 为 XX,而 B 马上对数据库更改了,数据库变成了 XX1,数据不一致。

其实 1 和 2 本质上是一个问题,解决1可以加一个乐观锁,在更新方法中重新判断 key 是否存在,令整个方法成为原子方法(或利用分布式锁)。

解决方法 2 则要保证更新缓存时,没有消费者,所以我们需要在 1 的原子方法内去等待消费队列为空。

  • 利用MQ延迟性质与死信队列完成定时取消未支付订单,设置消息延迟时间,消息过期后被投递到死信队列,让死信消费者去取消订单。

  • 利用 AOP 打印 request 与 response,方便调试。

未完成的任务

优惠券、会员没有实现,设计数据库都考虑到了。

emmm,写着写着才发现,购物车这玩意,应该让后端去处理数据,我全在前端做了,一大坨,包括判断折扣,处理精度问题,整麻了…如果在前端做,那么后端必须对发过来的订单进行重复的验证,麻了,写不动了,好吧我觉得前后端都要做…

没有完成真正的支付功能,个人小程序不让接入支付,涉及到金额相关的实现的没有那么严谨,甚至可能被攻击…

如果我还能记得起来这个项目的话,我会进行又一次重构。

管理系统端

管理系统前端

项目基于mall-admin-web是一个电商后台管理系统的前端项目,基于Vue+Element实现二次开发,对于整体的架构与部署,可以参考:介绍 | vue-element-admin (panjiachen.github.io),我基本上只修改了 src 里和 api 文件下的内容。

遇到所有部署问题,基本上都可以在介绍 | vue-element-admin (panjiachen.github.io)找到解决方法。

文件树

├─src├──views├─authority-ms          # 权限控制│  ├─admin│  └─role├─dish-ms              # 菜品模块│  ├─combo│  │  └─components│  └─dish│      └─components├─home                # 首页├─layout                # 整体路由布局│  ├─components│  │  └─Sidebar│  └─mixin├─login             # 登录├─marketing-ms      # 营销│  ├─coupon│  │  └─components│  ├─discount│  ├─new│  └─recommend├─order-ms          # 订单模块│  └─order└─store-ms          # 店铺模块└─store └─component

技术选型

技术 说明 官网
Vue 前端框架 https://vuejs.org/
Vue-router 路由框架 https://router.vuejs.org/
Vuex 全局状态管理框架 https://vuex.vuejs.org/
Element 前端UI框架 https://element.eleme.io/
Axios 前端HTTP框架 https://github.com/axios/axios
v-charts 基于Echarts的图表框架 https://v-charts.js.org/
Js-cookie cookie管理工具 https://github.com/js-cookie/js-cookie
nprogress 进度条控件 https://github.com/rstacruz/nprogress
vue-element-admin 项目脚手架参考 https://github.com/PanJiaChen/vue-element-admin

管理系统后端

文件树

├─java
│  └─com
│      └─happysnaker
│          ├─configuration  # 配置包
│          │  ├─inspecter   # 拦截器包
│          │  └─security    # SpringSecurity 相关
│          ├─controller     # 控制层
│          │  └─base
│          ├─exception      # 异常类
│          ├─mapper         # dao 层
│          ├─pojo           # 实体类层
│          ├─service        # 服务层
│          │  ├─base
│          │  ├─impl
│          │  ├─proxy       # 服务类代理
│          │  └─security    # SpringSecurity 相关
│          └─utils          # 实用类
└─resources├─com.happysnaker.mapper├─static└─templates

技术选型

技术 说明 官网
SpringBoot 容器+MVC框架 https://spring.io/projects/spring-boot
SpringSecurity 认证和授权框架 https://spring.io/projects/spring-security
JWT JWT登录支持 https://github.com/jwtk/jjwt
MyBatis ORM框架 http://www.mybatis.org/mybatis-3/zh/index.html
RabbitMQ 消息队列 https://www.rabbitmq.com/
Redis 分布式缓存 https://redis.io/
Lombok 简化对象封装工具 https://github.com/rzwitserloot/lombok
Swagger-UI 文档生成工具 https://github.com/swagger-api/swagger-ui
COS 对象存储

项目总结

  • 这个项目基本上是一个落地的 CRUD 了,因为要做分页,所以对SQL的优化查询体会更深了点,使用 MySql 的 limit 语句可以很容易实现分页查询:
SELECT * FROM `table` LIMIT offset, size;
# offset 是偏移,size 是大小,这个语句表示选取从 offset 开始的 size 条记录

但是这条语句效率低下,例如 offset = 100000, size = 1,我们只想选取 1 条语句,但是 Mysql 却会顺序查询 1到 100001 条记录,然后抛弃掉前面的记录,只选取一条,这样效率十分低下,如果可以知道 offset = 100000 的主键id就好了,这样就可以利用主键的排序性快速定位:

SELECT * FROM `table` WHERE id >= 100000 LIMIT 0, 1;

但可惜 Mysql 的主键一般不会严格连续,即使你设置成自增也没用,因此,另一个优化是不要选取那么多列,少一点IO:

SELECT * FROM `table` WHERE id >= (SELECT id FROM `table` LIMIT 100000, 1
)
LIMIT 0, 1;
  • 利用 SpringSecurity + JWT + BCR + VueRouter 做权限管理,具体思想是维护 menu、api、role、user 四张表,用户与角色关联,角色与菜单关联,菜单与api(还可以有很多,例如Get or Post)资源(控制层uri)关联,用户登陆后我们返回菜单树给前端,前端只显示这些菜单而隐蔽其他菜单,同时用户发起的访问 API 必须要动态鉴权,看看是否有权限访问…JWT框架相较于我们自己写的生成token,无非就是更严谨了些,携带参数更丰富了些,我们可以直接在 token 上设置过期时间、用户ID,从而无须使用 redis,这就是去中心化的思想,不过缺点是无法改变过期时间等信息(貌似我们自己也能实现…)。
  • 利用 AOP 实现日志打印。利用AOP维护与小程序端的缓存一致性。
  • 利用 观察者模式 实现对订单类变更的消息通知,观察者观察订单状态,对更改状态的订单发送消息通知用户,发送消息利用了线程池去异步处理。

嗯,没啥了,剩下的就是磨练基本功了。

未完成的任务

嗯,还有很多细节没有实现,很多也偷懒了,直接在数据库写死了…

问题不大,毕竟主要目的是学习。

总结

这一个多月下来,基本上对 Java 开发、项目开发有了个清晰的认识,对主流的开发框架也有了更进一步的理解,项目中基本上都用上了,用过一番之后,接下来就得去看看它们的底层实现了,磨练完基本功之后,来年开学再迈入分布式的学习,希望明年暑假能找一个好点的厂实习吧,卑微…

,观察者观察订单状态,对更改状态的订单发送消息通知用户,发送消息利用了线程池去异步处理。

嗯,没啥了,剩下的就是磨练基本功了。

未完成的任务

嗯,还有很多细节没有实现,很多也偷懒了,直接在数据库写死了…

问题不大,毕竟主要目的是学习。

总结

这一个多月下来,基本上对 Java 开发、项目开发有了个清晰的认识,对主流的开发框架也有了更进一步的理解,项目中基本上都用上了,用过一番之后,接下来就得去看看它们的底层实现了,磨练完基本功之后,来年开学再迈入分布式的学习,希望明年暑假能找一个好点的厂实习吧,卑微…

Java设计-微信小程序线上点餐/外卖项目+后台管理系统相关推荐

  1. 微信小程序线上点餐问题及解决

    2019.5.22 1.当我们遍历出数据库中菜单的菜品时,需要为其单个产品添加数量的问题. 解决:在表中加入 购买数量(buycount)的字段 然后,在js文件中,控制当前对象中的buycount ...

  2. 微信小程序线上预约管理系统

    微信小程序线上预约管理系统 核心代码展示 /** * 人员列表 * @param {Object} $table 表名 * @param {Object} $pageindex 分页page * @p ...

  3. 从零开始的微信小程序线上商城开发(一)

    今天开始制作一款微信小程序,一方面是巩固所学,一方面也在查漏补缺,准备尽可能的将所有电商功能模块都做出来,首先是准备工作. 这是微信小程序开发文档,下载微信开发者工具,我的开发者工具版本为1.03.2 ...

  4. 基于微信小程序线上生鲜选购平台

  5. 微信小程序商城搭建二手交易网站购物+后台管理系统|前后分离VUE.js

  6. 微信小程序毕业设计论文医院挂号预约系统+后台管理系统项目源代码

  7. 微信小程序实现上传视频功能(后端代码是java)

    微信小程序实现上传视频功能(后端代码是java) 1.前端 ​ wxml文件 <image bindtap="uploadVideo" length="3" ...

  8. 基于SpringBoot和微信小程序的餐馆点餐系统的设计和实现

    作者主页:Designer 小郑 作者简介:Java全栈软件工程师一枚,来自浙江宁波,负责开发管理公司OA项目,专注软件前后端开发(Vue.SpringBoot和微信小程序).系统定制.远程技术指导. ...

  9. java与微信小程序通讯_java与微信小程序实现websocket长连接

    本文实例为大家分享了java与微信小程序实现websocket长连接的具体代码,供大家参考,具体内容如下 背景: 需要在小程序实现地图固定坐标下实时查看消息 java环境 :tomcat7 jdk1. ...

最新文章

  1. 云端卫士架构师讲DDoS攻击的智能防御之道
  2. Redis分布式锁的实现原理
  3. 两个异常:a circular reference was detected while serializing及 Maximum length exceeded
  4. 狐狸抓老鼠,为何东北向才会成功
  5. keil实验c语言汇编实验一实验报告,KeilC51集成开发环境的使用实验报告
  6. 硅基生命之漫谈-1:天马行空
  7. server r2 系统更新文件清理
  8. 标准成本还是实际成本 成本核算标准选择
  9. FS\OFS\RS\ORS的使用
  10. java中的steam流
  11. 谋时而动 顺势而为 “软件定义智能新时代”
  12. Android Studio实现简单计算器
  13. 搜索引擎的小技巧【可以组合起来使用】:
  14. Python 分组处理
  15. python里的demo是什么意思_软件中的“DEMO” 是什么意思?游戏中的“DEMO呢?
  16. 【转载】成本中心费用分配与分摊的区别
  17. 2017 黑马 C++ 教学视频
  18. MATLAB中乘*和点乘.*的区别
  19. MATLAB 快速绘制曲线图的形状,粗细,颜色
  20. 毕马威误删,14.5万个账号清零,微软确认数据不可恢复

热门文章

  1. MSK的CPM调制和I-Q调制
  2. 我的世界mysql插件_MySQL Inventory Bridge — 跨服背包[1.7-1.15]【Bukkit】
  3. Authorization
  4. iphone4安装使用微信
  5. 用计算机来解锁密码,电脑忘记开机密码怎么办?如何解锁?
  6. 【JDK编译】Centos7编译OpenJDK7
  7. 移动互联网应用的十项设计原则和小提示
  8. CF 678F Lena and Queries
  9. 职业+英语思维导图模板
  10. springboot:redis报错io.lettuce.core.RedisCommandExecutionException: ERR Client sent AUTH, but no passw