微信订餐系统项目回顾

项目地址:https://github.com/wanger61/Springboot-

1.系统流程

该项目分为买家端和卖家端两部分:

  1. 买家端为微信端,可以在买家端查看商品,创建订单/查询订单和支付订单
  2. 卖家端为网页端,可以在买家端管理商品,查询订单/接收订单
  3. 买家端和卖家端通过消息进行通信

2.数据库表设计

该项目共有5张数据表,分别为:
商品详情表(product_info),商品类目表(product_category),订单主表(order_master),订单详情表(order_detail),卖家信息表(seller_info)

其中的注意点:

  1. 表名和字段名都应采用xx_xx的格式,对应JavaBean中一一对应为xxXx格式,(如数据表中字段名为order_id,JavaBean中属性名为orderId) 使用Mybatis时可以开启驼峰命名进行映射
  2. id字段,如果在记录较少的情况下可以使用int和auto_increment, 而在记录较多的情况下应该使用varchar类型(因为auto_increment是有上限的)
  3. 对于金钱相关的字段需使用decimal类型,否则会有精度上的差异
  4. 创建数据表时最好添加创建时间和更新时间字段
  5. 最好给字段添加注释
  6. 对于图片等数据通常在表中记录其链接地址
  7. 状态属性字段在数据库中应设置为tinyint类型,并添加注释什么数字对应什么状态(如订单的支付状态,订单状态)
  update_time TIMESTAMP NOT NULL DEFAULT current_timestamp ON UPDATE current_timestamp COMMENT '更新时间',`
  create_time TIMESTAMP NOT NULL DEFAULT current_timestamp COMMENT '创建时间',
  

在订单表的设计上,把订单分为订单主表订单详情表
其中订单主表记录了总金额,买家信息等;
订单详情表记录了购买了什么商品,商品数量等;
订单主表的一条记录对应着多条订单详情表记录,订单详情表中有order_id字段,记录其所属的订单主表记录

这么设计,在进行支付等操作时只需要查订单主表即可,而在查询订单详情时再根据order_id去查询到底买了什么商品,对表进行了合理的拆分

3.JavaBean对象的映射

针对数据库中的5张表,需要创建与之相对应的JavaBean对象,这里可以使用Lombok插件,只需在对象类上添加@Data注解,就会自动生成get/set和toString方法,非常方便

注意点:

  1. 金额字段必须使用BigDecimal类型
  2. 对于状态属性最好为其创建枚举类型,再从枚举中获得状态数字与数据表中的字段相对应:
  3. 最好对状态属性设置默认值

以订单状态为例:

//订单状态枚举类
@Getter
public enum OrderStatusEnum implements CodeEnum {
    NEW(0,"新订单"),
    FINISHED(1,"完结"),
    CANCEL(2,"已取消"),
    ;

    private Integer code;

    private String message;

    OrderStatusEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

对象属性:

    /** 订单状态,默认为0新下单. */
    private Integer orderStatus = OrderStatusEnum.NEW.getCode();

    /** 支付状态,默认为0未支付. */
    private Integer payStatus = PayStatusEnum.WAIT.getCode();

4.Mapper层开发

这里采用的是Mybatis的注解版进行开发,同样也是为每张表创建一个Mapper,对其进行CRUD操作

注意点:

  1. 对于增删改操作,方法的返回类型最好设置为int型(返回修改的记录行数),这样在后续方法调用时就可以根据该值进行判断是否操作成功,没成功及时捕获
  2. 对于根据多个字段值查询记录的情况,最好写在一个方法里,查询的字段值通过List传入,然后用动态Sql进行遍历。这样一来避免了多次查询数据库。
  3. 对于插入操作,有自增主键需使用@Options开启自增主键
  4. 写完后一定要进行单元测试,因为SQL语句很容易写错…

如根据多个类目类型查询类目:

@Select({"<script> ",
            "SELECT * FROM product_category ",
            "WHERE category_type IN ",
            "<foreach collection = 'types' separator = ',' open = '(' close = ')' item = 'category'>  ",
            "#{category} ",
            "</foreach> ",
            "</script>"})
    public List<ProductCategory> findProductCategoriesByTypes(@Param("types") List<Integer> types);

自增主键的插入:

@Options(useGeneratedKeys = true,keyProperty = "categoryId")
    @Insert("insert into product_category(category_name, category_type) values(#{categoryName}, #{categoryType})")
    public int insertNewProductCategory(ProductCategory productCategory);

4.Service层基础功能开发

对于基本的增删改查操作,注入对应Mapper调用其方法即可
对于查询多条记录的操作,在记录很多时需进行分页,这里使用的Mybatis的PageHelper插件。使用时非常简单,只需在调用Mapper方法前调用静态方法PageHelper.startPage(page,size)即可;
如:

 @Override
    public List<ProductInfo> findAll(Integer page, Integer size) {
        PageHelper.startPage(page,size);
        List<ProductInfo> allProductInfos = productInfoMapper.findAllProductInfos();
        return allProductInfos;
    }

对于订单的查询,在给前端传数据时,想通过查询直接查出该订单与其对应的订单详情,但没有与之对应的JavaBean,怎么办?

这里重新创建了OrderDTO对象(Data Transport Object)用于数据传输,在OrderMaster的基础上添加List<OrderDetail*> orderDetailList属性用于对应订单详情,在OrderService中通过OrderDTO进行相关操作

4.1 订单创建

  • 生成订单id时,需生成一个唯一的主键,这里通过 时间 + 随机数的方式,编写一个KeyUtil类生成主键,并使用synchronized保证时间戳唯一

  • 将前端传来的OrderDTO(含订单详情)转换为OrderMaster和OrderDetail对象时,可以使用BeanUtils进行属性的拷贝。但在拷贝前一定要注意属性是否拷贝完全。

  • 在进行订单总价的计算时,商品的价格一定要从数据库中查出,而不能从前端传来(防止篡改价格)

  • 订单的创建包括查询商品,计算总价,写入订单数据库,扣库存等操作;整个操作应定义为一个事务,因此需要在方法上添加 @Transactional 注解!

  • 在进行扣库存操作时,这里设计了一个购物车对象CartDTO,包括商品id和商品数量, 在扣库存方法中传入,这样就不用在扣库存时再遍历OrderDTO

4.2 订单查询

其实就是根据订单id从数据库中查找订单,或者查询一个用户的所有订单,组装成一个OrderDTO传给前端,这里要注意的查不到订单的处理方式:

这里采用自定义异常的方式,如果查不到就抛出对应的异常,不过在异常的设计上这里编写一个通用的异常,然后在抛出时为其注入对应的异常原因(枚举)

异常的设计:
继承自运行时异常,在构造方法中传入枚举状态赋予错误码(code)

@Getter
public class SellException extends RuntimeException {

    private Integer code;

    public SellException(ResultEnum resultEnum) {
        super(resultEnum.getMessage());
        this.code = resultEnum.getCode();
    }


    public SellException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}

枚举的设计(这里把状态都列出来了)

@Getter
public enum ResultEnum {

    SUCCESS(0,"成功"),
    PARAM_ERROR(1,"参数不正确"),
    PRODUCT_NOT_EXIST(10,"商品不存在"),
    PRODUCT_STOCK_ERROR(11,"商品库存不正确"),
    ORDER_NOT_EXIST(12,"订单不存在"),
    ORDERDETAIL_NOT_EXIST(13,"订单详情不存在"),
    ORDER_STATUS_ERROR(14,"订单取消状态不正确"),
    ORDER_UPDATE_FAIL(15,"订单取消失败"),
    ORDER_DETAIL_EMPTY(16,"取消订单中无商品详情"),
    ORDER_PAY_STATUS_ERROR(17,"订单支付状态不正确"),
    CART_EMPTY(18,"购物车为空"),
    ORDER_OWNER_ERROR(19,"该订单不属于你"),
    ORDER_CANCEL_SUCCESS(20,"订单取消成功"),
    ORDER_FINISH_SUCCESS(21,"订单完结成功"),
    PRODUCT_STATUS_ERROR(22,"商品状态不正确"),
    PRODUCT_UPDATE_ERROR(23,"商品更新失败"),
    WECHAT_MP_ERROR(3,"微信公众号方面错误"),
    WXPAY_NOTIFY_MONEY_VERITY_ERROR(24,"微信支付异步通知金额校验不通过"),
    LOGIN_FAIL(25,"登录失败"),
    LOGOUT_SUCCESS(26,"登出成功")
    ;

    private Integer code;

    private String message;

    ResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

4.3 订单取消/完结/支付

  • 其实都是去修改订单主表中的订单状态或支付状态,逻辑都很类似,不过需要注意的是,在修改状态前必须先对原本的状态进行校验,只有当原本的状态正确时才能进行修改,不正确要及时抛异常

  • 这些方法都应该声明为事务

  • 加减库存时应该考虑多线程的场景,(防止发生超卖等问题)

4.4 商品和商品类目的Service

这部分比较简单,详情见代码

5.Controller层基础功能开发

首先是商品功能,需要根据类目把数据库中的商品传出来发给前端。由于这是一个前后端分离项目,因此服务端主要做的就是提供API,并根据开发文档规定的Json格式向前端传数据

不过要注意的是,向前端传送的Json格式跟原本的JavaBean是有出入的,因此需要根据文档规定的Json格式创建对应的VO对象,然后将原本的JavaBean组装成相应VO对象再传输

在Json传输时的注意点:

  1. 如果属性名和Json的字段名不同,相要在传输Json时将属性名转成对应的字段名,可以在属性上添加@JsonProperty注解,如:
    @JsonProperty("name") private String productName;
    在传输Json时就会把productName转为name;
  2. 如果在传输Json时不想传输为null的属性值,则在对象上添加注解:
    @JsonInclude(JsonInclude.Include.NON_NULL)
    或添加全局配置 spring.jackson.default-property-inclusion=non_null
  3. 如果想在传输Json时忽略某些字段或方法,则在字段或方法上添加
    @JsonIgnore注解
  4. 如果在传输Json时想对属性进行格式化,规定传输时的格式,则在该属性上添加 @JsonSerialize注解, 并传入相应的JsonSerializer

如传输时想对时间属性进行格式化

/** 创建时间. */
    @JsonSerialize(using = Date2LongSerializer.class)
    private Date createTime;

在JsonSerializer中重新定义转换格式:

public class Date2LongSerializer extends JsonSerializer<Date> {

    @Override
    public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeNumber(date.getTime()/1000);
    }
}

5.1 表单对象的处理

前端通过表单传输数据时,应当为每个表单都单独创建相应的Form对象,并在对象的字段上添加JSR303校验注解对表单字段进行规范

Controller在接受表单字段时,有固定的格式:

@PostMapping("create")
    public ResultVO<Map<String,String>> create(@Valid OrderForm orderForm,
                                               BindingResult bindingResult){
        if (bindingResult.hasErrors()){
            log.error("创建订单参数不正确,orderForm = {}",orderForm);
            throw new SellException(ResultEnum.PARAM_ERROR.getCode(),bindingResult.getFieldError().getDefaultMessage());
        }

而从前端传来的Json数据为字符串,要想把字符串重新转换为Java对象,这里用到了Gson:

Gson gson = new Gson();
List<OrderDetail> orderDetailList = new ArrayList<>();
        try {
            orderDetailList = gson.fromJson(orderForm.getItems(), new TypeToken<List<OrderDetail>>(){}.getType());
        } catch (Exception e){
            log.error("对象转换错误, string={}",orderForm.getItems());
            throw new SellException(ResultEnum.PARAM_ERROR);
        }

orderDTO.setOrderDetailList(orderDetailList);

5.2 卖家端Controller

卖家端管理平台对页面美观没有太大的需求,这里采用Freemarker模板技术,前端代码可以从ibootstrap下载

其中Freemarker通过${ }的方式取值,迭代格式如下:

<#list categoryList as category>
  <tr>
       <td>${category.categoryId}</td>
       <td>${category.categoryName}</td>
       <td>${category.categoryType}</td>
       <td>${category.createTime?string('dd.MM.yyyy HH:mm:ss')}</td>
       <td>
           <a href="/sell/seller/category/index?categoryId=${category.categoryId}">修改</a>
       </td>
  </tr>
</#list>

注意,使用Freemarker需配置 spring.freemarker.suffix=.ftl

在修改和更新操作后,一般先跳到成功或失败页面,几秒后再跳转,跳转的前端代码为:

<script>
    setTimeout('location.href="${url}"',3000);
</script>

5.3 登录/登出功能

因为该项目后期可能会改为分布式项目,因此单个服务器上的Session在其他服务器上会失效,所以采用Redis实现分布式Session:

1.登录时先去找数据库里有没有匹配的用户信息,有则把token设置在Redis和Cookie中

        //2.设置token至redis
        String token = UUID.randomUUID().toString();
        Integer expire = RedisConstant.EXPIRE;
        redisTemplate.opsForValue().set(String.format(RedisConstant.TOKEN_PREFIX, token), openid, expire, TimeUnit.SECONDS);

        //3.设置token至Cookie
        Cookie cookie = new Cookie("token",token);
        cookie.setPath("/");
        cookie.setMaxAge(expire);
        httpServletResponse.addCookie(cookie);
  1. 登出时把该token从Redis和Cookie中清除
       Cookie[] cookies = request.getCookies();
       if (cookies != null){
            for (Cookie cookie: cookies){
                if (cookie.getName() == "token"){
                    //2.清除Redis
                    redisTemplate.opsForValue().getOperations().delete(String.format(RedisConstant.TOKEN_PREFIX,cookie.getValue()));
                    //3.清除Cookie
                    cookie.setMaxAge(0); //设置时间为0使其失效
                }
            }
        }
  1. 身份验证采用AOP的方式,在卖家端进行操作时,需要先进行身份验证:
    先看看Cookie中有没有Token,如果有再去Redis中找有没有,如果找得到说明已经登录过了;没有则需重新跳转到登录Controller

5.4 微信相关开发

关于微信相关功能的开发,主要为微信授权和微信支付以及微信模板推送,都根据微信官方文档和第三方sdk文档进行开发

微信授权的主要流程为:
用户点击生成的特定链接——>会向微信平台发起请求——>微信平台收到请求后再转发到服务器对应的Controller,并传来Code参数——>服务器根据传来的Code参数再向微信平台发送请求以获取access_token ——>如果采用SNSAPI_BASE模式,在传来的access_token中即可得到用户的微信openid

微信支付的主要流程为:
用户点击支付——>服务器调用接口生成用户订单——>服务器通过微信平台的统一下单API向微信平台发送请求——>微信平台收到请求后返回预付单信息——>服务器根据返回的预付单信息生成前端界面给用户——>用户点击支付后微信平台向用户返回支付成功信息——>同时异步地通知服务器支付结果——>服务器收到支付结果后进行金额的比对,比对正确后再修改订单的支付状态——>然后再告知微信处理结果

6. 消息通信

采用Websocket进行买家端和卖家端的消息通信

7.Redis缓存

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值