微信点餐系统技术总结
1.项目开发步骤
1.项目设计
1.角色划分
买家端(手机端) 卖家端(PC端)
2.功能模块划分
详细设计个各个功能模块
买家 卖家
商品: 商品列表数据
订单: 订单创建 订单查询 订单取消 ...
类目: 订单管理 商品管理 类目管理 ...
2.架构和基础框架
SSH 框架的了解和使用
Mybatis 的两种配置方法
Redis 的缓存作用 分布式锁
Aop 身份验证
等等
3.数据库设计
设计表的成员 表与表之间的关系 确定每个表的主键 设计属性之间的关联...
4开发日志的使用
这里用Slf4j
1.导入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2.加上注解
@Slf4j
3..使用
log.error("【创建订单】购物车不能为空");
Home("【微信支付】发起支付request={}",JsonUtil.toJson(payRequest));
5. Logback的配置
需求:区分info 和error 日志 ,每天产生一个日志文件.
1.application.xml文件的配置
logging:
pattern:
console: "%d - %msg%n"
path: /var/log/tomcat/
file: /var/log/tomcat/sell.log
level:
com.imooc.LoggerTest: debug
path 和 file 二选一即可
Path:产生文件的位置
File:产生文件的位置和名字
Level: 指定某个类的日志级别
2.Logback-spring.xml配置文件
Resouces目录下建立 Logback-spring.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%d - %msg%n
</pattern>
</layout>
</appender>
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<!--<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>-->
</filter>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径 -->
<fileNamePattern>/home/hk/log/tomcat/info.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径 -->
<fileNamePattern>/home/hk/log/tomcat/error.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<root level="info">
<appender-ref ref="consoleLog"/>
<appender-ref ref="fileInfoLog"/>
<appender-ref ref="fileErrorLog"/>
</root>
</configuration>
简单的日志需求选择第一种即可,复杂的需要选择第二种配置
注意: 这里写配置文件时提示不是很友好,不要打错!
6. 买家类目
1.买家类目-dao
1.数据库的连接:
添加依赖
Mysql的驱动
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
Jap的驱动
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
配置数据库的信息:
在application.yml配置文件中`配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 741623
url: jdbc:mysql://127.0.0.1:3306/food?characterEncoding=utf8&useSSL=false&zeroDateTimeBehavior=convertToNull
jpa:
show-sql: true
2.建立和数据表映射过来对应的对象
对象类需要加入
@Entity 注解
导入包 import javax.persistence.Entity
默认下类名和表明一致
类名中除了第一个字母,其他字母的大写在表中会自动用_加对应的小写字母替代
表名: product_category
类名:ProductCategory
也可以加注解来添加映射关系
@Table(name = “ product_category”)
对象里面的属性名也必须和数据表里面的属性名对应
否则需要在属性之前添加注解@Column(name = “属性名”)
对象的主键用 @Id 标识 导入下面的包
import javax.persistence.Id;
对象自增用 @GeneratedValue 表示
import javax.persistence.GeneratedValue;
若类名和属性名都与数据表对应, 则这个类就和 product_category数据表建立映射关系.
注意: 建议用默认值
类名前面添加 @Data
导入包import lombok.Data;
依赖文件和之前日志的依赖一样
这个注解可以省去 get 和 set 方法
DAO 层的编写
与对象做关联
public interface ProductCategoryRepository extends JpaRepository<ProductCategory, Integer>
继承接口 JpaRepository<> 第一个参数表明对象类的名称 , 第二个参数表明对象的主键
2.买家类目-service
1.先建立CategoryService接口
在接口中列出可能需要用的方法
再建立CategoryServiceImpl类实现接口 CategoryService
在 CategoryServiceImpl 需要加入注解 @Service
导入包import org.springframework.stereotype.Service;
3.买家类目-controller
由于类目不存在单独给买家端的接口,所以不存在 controller
7.买家商品
基本步骤和上面买家类目实现一样
注重Service的编写
Dao ---> Service ---> Controller
------------------技术技巧------------------------------------------------
1.分页查询
查询的时候传入 Pageable pageable对象可以分页查询
Page<ProductInfo> findAll(Pageable pageable);
导入包 import org.springframework.data.domain.Pageable;
传入Pageable pageable对象 返回的时一个 Page<>对象
2.枚举类的使用
当用数字表示一些商品的情况或者订单的情况时,一旦数目过多就容易混淆,
这时候可以用到枚举类 Enum, 可以建立一个单独的包来管理各个枚举类
枚举类可以将对应的数字信息和和文字信息结合起来,可以更加方便的了解数字对应的信息
package com.imooc.enums;
import lombok.Getter;
/**
* 商品状态
*/
@Getter
public enum ProductStatusEnum implements CodeEnum {
UP(0, "在架"),
DOWN(1, "下架")
;
private Integer code;
private String message;
ProductStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
3.封装返回结果
返回给前端的类目信息分三层结构
1. 最外层(ResultVO<T>): 包含code(错误码) , msg(提示信息) , 泛形 T(包含返回的具体信息)
package com.imooc.VO;
import lombok.Data;
/**
* http请求返回的最外层对象
*/
@Data
public class ResultVO<T> {
/** 错误码. */
private Integer code;
/** 提示信息. */
private String msg;
/** 具体内容. */
private T data;
}
2.中间层(ProductVO): 商品类目信息,包含 类目名字 , 类目编号 , 商品信息 List (里面包含属于这个类目的商品信息).
package com.imooc.VO;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* 商品(包含类目)
*/
@Data
public class ProductVO {
@JsonProperty("name")
private String categoryName;
@JsonProperty("type")
private Integer categoryType;
@JsonProperty("foods")
private List<ProductInfoVO> productInfoVOList;
}
3.最里层(ProductInfoVO): 商品详情( 包含用户能看到的商品信息 )
package com.imooc.VO;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* 商品详情
*/
@Data
public class ProductInfoVO {
@JsonProperty("id")
private String productId;
@JsonProperty("name")
private String productName;
@JsonProperty("price")
private BigDecimal productPrice;
@JsonProperty("description")
private String productDescription;
@JsonProperty("icon")
private String productIcon;
}
4.对查询请求的反应和相应的操作
1.查询所有上架商品
2.查询类目(一次性查询)
这里用到一个 lambda 表达式(从商品对象中获得类目属性)
List<Integer> categoryTypeList = productInfoList.stream()
.map(e -> e.getCategoryType())
.collect(Collectors.toList());
作用是从 对象集合productInfoList中抽取一个属性集合 categoryTypeList
作用等价于:
List<Integer> categoryTypeList = new ArrayList<>();
传统方法
for (ProductInfo productInfo : productInfoList) {
categoryTypeList.add(productInfo.getCategoryType());
}
根据获得的类目type属性集合来得到类目对象集合
List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypeList);
3.数据拼装(将对应的商品对象添加到对应的类目List下去)
新建一个ProductVO(商品类目信息: 包含 类目名字 , 类目编号 , 商品信息 List )的 List
List<ProductVO> productVOList = new ArrayList<>();
对类目对象集合循环遍历
for (ProductCategory productCategory: productCategoryList) {
新建一个ProductVO对象
设置CategoryType和CategoryName 属性值
ProductVO productVO = new ProductVO();
productVO.setCategoryType(productCategory.getCategoryType());
productVO.setCategoryName(productCategory.getCategoryName());
新建一个 ProductInfoVO 的List --->roductInfoVOList
List<ProductInfoVO> productInfoVOList = new ArrayList<>();
在类目循环体内部对商品信息金额和循环遍历
for (ProductInfo productInfo: productInfoList) {
If 判断 : 若满足商品对象的类目属性与当前类目对象的类目属性相同
if (productInfo.getCategoryType().equals(productCategory.getCategoryType())) {
满足上述 if 判断则新建一个 productInfoVO 对象
运用工具类BeanUtils中的copyProperties()方法将productInfo中的属性值拷贝到productInfoVO对象中(拷贝的值包括两个对象中名字和类型一样的属性)
ProductInfoVO productInfoVO = new ProductInfoVO();
BeanUtils.copyProperties(productInfo, productInfoVO);
productInfoVOList.add(productInfoVO);
设置 productVO 的 ProductInfoVOList 的值为 productInfoVOList
productVO.setProductInfoVOList(productInfoVOList);
将 productVO 添加到 productVOList
productVOList.add(productVO);
8.买家订单
Dao --- service --- controller
都是相关逻辑的处理 不一一列举了
涉及两张数据表(订单用户信息表 和订单商品详情表)
DAO层
订单表接口的方法
Page<OrderMaster> findByBuyerOpenid(String buyerOpenid, Pageable pageable);
订单详情表接口的方法
List<OrderDetail> findByOrderId(String orderId);
买家订单的service接口的方法
/** 创建订单. */
OrderDTO create(OrderDTO orderDTO);
/** 查询单个订单. */
OrderDTO findOne(String orderId);
/** 查询订单列表. */
Page<OrderDTO> findList(String buyerOpenid, Pageable pageable);
/** 取消订单. */
OrderDTO cancel(OrderDTO orderDTO);
/** 完结订单. */
OrderDTO finish(OrderDTO orderDTO);
/** 支付订单. */
OrderDTO paid(OrderDTO orderDTO);
/** 查询订单列表. */
Page<OrderDTO> findList(Pageable pageable);
JSON格式转化为 List
导入依赖
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
Json格式的 orderForm.getItems() 转化为 List<OrderDetail>
Gson gson = new Gson();
orderDetailList = gson.fromJson(orderForm.getItems(),
new TypeToken<List<OrderDetail>>() {
}.getType());
9.获得微信授权
注: 必须时服务号才能进行微信相关接口开发,订阅号不行
1.手动造轮子
1.设置域名
2.获取 code(code作为换取access_token的票据,每次用户授权带上的code不一样,code只能使用一次,5分钟未使用自动过期).
3.通过code 获得网页受权access_token
2.微信网页授权的第三方SDK
这里是可能是目前最好最全的微信Java开发工具包(SDK)
包括微信支付、开放平台、公众号、企业微信、企业号、小程序等
https://github.com/Wechat-Group/weixin-java-tools
添加Maven引用
注意:以下为最新正式版,最新测试版本号为
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>(不同模块参考下文)</artifactId>
<version>2.9.0</version>
</dependency>
· 各模块的artifactId:
o 微信小程序:weixin-java-miniapp
o 微信支付:weixin-java-pay
o 微信开放平台:weixin-java-open
o 公众号:weixin-java-mp
o 企业号/企业微信:weixin-java-cp
8.微信支付
注:由于没有服务号,这一块没有研究
1.微信发起支付(后端)
2.在网页发起支付
3.动态注入参数发起支付
4.微信异步通知
5.微信退款
9.卖家订单
--->service ----->controller
由于之前买家端写了很多代码了,其中与数据交互的dao层在卖家端不用写了
1.Service层(重用买家订单的service层接口)
/** 创建订单. */
OrderDTO create(OrderDTO orderDTO);
/** 查询单个订单. */
OrderDTO findOne(String orderId);
/** 查询订单列表. */
Page<OrderDTO> findList(String buyerOpenid, Pageable pageable);
/** 取消订单. */
OrderDTO cancel(OrderDTO orderDTO);
/** 完结订单. */
OrderDTO finish(OrderDTO orderDTO);
/** 支付订单. */
OrderDTO paid(OrderDTO orderDTO);
/** 查询订单列表. */
Page<OrderDTO> findList(Pageable pageable);
添加一个查询所有用户的订单方法(分页查询)
2.Controller层
(不予具体说明)
10.卖家商品
1. 新增和修改页面
2.修改表单体提交
3.新增功能
4.卖家类目功能开发
11.登录登出
1.分布式下的session
1.什么叫分布式系统
旨在支持应用程序和服务的开发,可以利用物理架构由多个自治的处理元素,不共享主内存,但是通过网络发送消息合作.
三个特点: 1.多节点
2.消息通信(http通信)
3.不共享内存
三个概念: 1.分布式系统
2.集群
3.分布式计算
2.AOP实现身份验证
1.先选取有身份验证的位置做切面
@Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" +
"&& !execution(public * com.imooc.controller.SellerUserController.*(..))")
public void verify() {}
2.在需要验证的方法需要做什么
@Before("verify()")
public void doVerify() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//查询cookie
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if (cookie == null) {
log.warn("【登录校验】Cookie中查不到token");
throw new SellerAuthorizeException();
}
//去redis里查询
String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
if (StringUtils.isEmpty(tokenValue)) {
log.warn("【登录校验】Redis中查不到token");
throw new SellerAuthorizeException();
}
}
3.拦截登录异常,不正常访问将会跳转到登录界面
//拦截登录异常
//http://sell.natapp4.cc/sell/wechat/qrAuthorize?returnUrl=http://sell.natapp4.cc/sell/seller/login
@ExceptionHandler(value = SellerAuthorizeException.class)
public ModelAndView handlerAuthorizeException() {
return new ModelAndView("redirect:"
.concat(projectUrlConfig.getWechatOpenAuthorize())
.concat("/sell/wechat/qrAuthorize")
.concat("?returnUrl=")
.concat(projectUrlConfig.getSell())
.concat("/sell/seller/login"));
}
12.项目相关其他技术
1.mybatis的使用
1.注解使用方式(sql语句写在注解里面)
导入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
创建一个mapper 包
包下建立对应实体类的sql语句操作类
1.通过map的方式插入 sql 语句(一般不用)
//通过 map 的方式
@Insert("insert into product_category(category_name, category_type) values (#{category_name, jdbcType=VARCHAR}, #{category_type, jdbcType=INTEGER})")
int insertByMap(Map<String,Object>map);
2.通过对象的方式插入 sql 语句
//通过对象的方式
//插入
@Insert("insert into product_category(category_name, category_type) values (#{categoryName, jdbcType=VARCHAR}, #{categoryType, jdbcType=INTEGER})")
int insertByObject(ProductCategory productCatrgory);
3.通过类型查询
//通过类型查询
@Select("select *from product_category where category_type = #{categoryType}")
@Results({
@Result(column = "category_type" , property = "categoryType"),
@Result(column = "category_id" , property = "categoryId"),
@Result(column = "category_name" , property = "categoryName")
})
ProductCategory findByCategoryType(Integer categoryType);
4.通过名字查询
//通过名字查询
@Select("select *from product_category where category_name = #{categoryName}")
@Results({
@Result(column = "category_type" , property = "categoryType"),
@Result(column = "category_id" , property = "categoryId"),
@Result(column = "category_name" , property = "categoryName")
})
List<ProductCategory> findByCategoryName(String categoryName);
5.更新姓名
(传入姓名和type)
//更新姓名
@Update("update product_category set category_name = #{categoryName} where category_type = #{categoryType}")
int updateByCategoryType(@Param("categoryName") String categoryName, @Param("categoryType") Integer categoryType);
(传入对象)
@Update("update product_category set category_name = #{categoryName} where category_type = #{categoryType}")
int updateByObject(ProductCategory productCategory);
6.通过categoryType 删除
@Delete("delete from product_category where category_type = #{categoryType}")
int deleteByCategoryType(Integer categoryType);
2.xml方式的使用(sql语句写在xml文件里面)
Namespace : 接口的位置
<mapper namespace="com.cyg.dataobject.mapper.ProductCategoryMapper">
Type: 操作的实体类的位置
<id 实体类的属性 />
<resultMap id="BaseResultMap" type="com.cyg.dataobject.ProductCategory">
<id column="category_id" property="categoryId" jdbcType="INTEGER" />
<id column="category_name" property="categoryName" jdbcType="VARCHAR" />
<id column="category_type" property="categoryType " jdbcType="INTEGER" />
</resultMap>
Id : 方法名
resuletMap: 上面定义的 resuletMap标签
parameterType : 写入参的类型 如果入参时个对象,就写入参的路径
<select id="selectByCategoryType" resultMap="BaseResultMap" parameterType="java.lang.Integer">
SELECT category_id,category_name,category_type
FROM product_category
WHERE category_type = #{category_type , jdbcType = INTEGER}
</select>
完整xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cyg.dataobject.mapper.ProductCategoryMapper">
<resultMap id="BaseResultMap" type="com.cyg.dataobject.ProductCategory">
<id column="category_id" property="categoryId" jdbcType="INTEGER" />
<id column="category_name" property="categoryName" jdbcType="VARCHAR" />
<id column="category_type" property="categoryType" jdbcType="INTEGER" />
</resultMap>
<select id="selectByCategoryType" resultMap="BaseResultMap" parameterType="java.lang.Integer">
SELECT category_id,category_name,category_type
FROM product_category
WHERE category_type = #{category_type,jdbcType = INTEGER}
</select>
</mapper>为
接下来在配置文件里面配置
mybatis:
mapper-locations: classpath:mapper/*.xml
在启动类中配置配置文件的位置
@MapperScan(basePackages = "com.cyg.dataobject.mapper")
13.redis的使用
推荐一个网站
redis中文官方网站
1.redis分布式锁
package com.cyg.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* @author cyg
* @date 18-3-17 下午5:14
**/
@Component
@Slf4j
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean lock(String key , String value){
if (redisTemplate.opsForValue().setIfAbsent(key,value)){
return true;
}
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期
if(!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()){
//获取上一个锁的时间
String oldValue = redisTemplate.opsForValue().getAndSet(key , value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(System.currentTimeMillis())){
return true;
}
}
return false;
}
public void unlock(String key , String value){
try {
String currentValue = redisTemplate.opsForValue(.get(key);
if (StringUtils.isEmpty(currentValue) && currentValue.equals(value)){
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e){
log.error("[redis分布式锁] 解锁异常");
}
}
}
2.redis缓存的使用
14.项目部署
15.总结
微信点餐系统有两种,一种是微信公众号点餐系统,另一种是微信小程序点餐系统。这两种点餐系统都可以实现微信扫码点餐,也是各有优势。公众号点餐系统能更好的聚集粉丝、与粉丝互动以及文章推送,而小程序点餐系统能更好的线上推广拓客,可以倚靠微信12亿流量红利,将线上流量引导到店内消费。
基于微信小程序点餐系统具体功能都有哪些?
①扫桌面二维码点餐,“到店顾客”就坐后,使用微信,扫描桌面二维码,选择餐品并在线付款,等待服务员上菜。
②自助点餐,预约堂食/自提,消费者通过餐饮店点餐小程序自助下单,预约好进餐时间;餐饮门店加工、顾客到点再堂食/自提。
③点外卖,等送餐上门,通过餐饮店点餐小程序,用户选好喜欢的菜品之后,用户直接下单、支付,还可以预约送达时间,查看订单状态和订单详情。
微信点餐系统有哪些推广的渠道
①微信公众号点餐系统推广:二维码是最直接的线上入口,如餐厅海报、菜单广告上印上二维码,本地论坛发帖带上二维码等,公众号名片分享也是很常见的一种方式,如好友分享、群分享等等。
②公众号文章分享这种一般用会选择优惠活动来做文章。比如什么优惠活动,转发到朋友圈集齐多少个赞,免费送一个特色菜。这种免费的方式吸引力还是有的,尤其是学生,格外喜欢这种方式。现在餐厅一般都是提供免费wifi的,微信公众号开通门店功能后,可以设置关注公众号连wifi等。
③通过位置标注,打开-发现-小程序,你会看到在附近的小程序能看到,首页会显示自己所在位置附近5公里以内的商家,顶部菜单侧显示了各行各业的小程序,餐桌上的小程序码,是最直接的线上入口,顾客扫一下就成了小程序的会员。附近5km的人群均可以在微信附近小程序列表中看到餐厅的小程序;分享转发同公众号类似,但是小程序会永久留存在“聊天小程序”栏目中。智铺子科技在外卖点餐系统开发方面有着丰富的经验,既可以定制开发又可以模板开发。网上搜索智铺子即可!