02 秒杀系统的整体设计(初步)


在这里插入图片描述

  • 前端采用 Metronic模板
  • 在接入层进行一些异常处理,并定义前后端分离数据交互的一个格式
  • 业务层进行业务的处理,并进行后端数据的组合交给前端
  • 数据层:使用事务、数据Dao进行持久化
  • 数据库:MySQL, redis


通用工具的设计

1、后端传给前端的数据

为了实现前后端分离,后端传给前端的数据,需要封装成一个统一的格式,之后前端拿到后端传过来的数据,对数据进行解析,然后对页面进行渲染。

定义一个类,对数据格式进行统一的封装

  • status: 状态,数据是否处理成功
  • data:后端传给前端的数据,前端对这个数据进行解析,若status为success则返回正常的数据,若status为fail则返回错误的异常信息
public class CommonReturnType {

    //表明对应请求的返回的处理结果 “success"或"fail"
    private String status;

    //若status=success则data内返回前端需要的数据
    //若status=fail,则data内使用通用的错误吗格式
    private Object data;

后端返回给前端的数据,只有这一种格式, status+data,如下所示

status状态位success

{
	"status": "success",
	"data": {
		"id": 3,
		"title": "剑指Offer树部分解析",
		"price": 123.00,
		"stock": 4,
		"description": "测试",
		"sales": 0,
		"imgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590575581472&di=b9767ddbccb08a760b3a913d09a72a21&imgtype=0&src=http%3A%2F%2Fimg1.imgtn.bdimg.com%2Fit%2Fu%3D3175508956%2C2902264390%26fm%3D214%26gp%3D0.jpg",
		"promoPrice": null,
		"startDate": null,
		"promoId": null,
		"promoStatus": 0
	}
}

status状态位fail


{
	"status": "fail",
	"data": {
		"errCode": 20001,
		"errMsg": "用户不存在"
	}
}

封装错误信息枚举类

在后端返回给前端数据时,当返回错误信息时,需要返回错误信息,那么我们可以将错误信息进行集中管理这样就可以方便管理

在这里插入图片描述

当发生错误信息的时候,将异常抛出throw BusineessException(EmBusinessError),可以对这个异常进行拦截,通过对信息进行封装,解析数据,返回CommonReturnType.create()

CommonError

//错误接口
public interface CommonError {

    //接口中定义errMsg errCode等方法
    public  int getErrCode();
    public String getErrMsg();

    //自定义错误消息
    public CommonError setErrMsg(String errMsg);

}


错误异常枚举类 EmBusinessError

public enum EmBusinessError implements CommonError {

    //通用错误类型 00001
    PARAMETER_VALIDATION_ERROR(10001,"参数不合法"),
    UNKWON_ERROR(10002,"未知错误"),
    //2000开头为用户信息相关错误定义
    USER_NOT_EXIST(20001,"用户不存在"),
    USER_NOT_LOGIN(20003,"用户还未登录"),
    USER_LOGIN_FAIL(20002,"用户或手机号密码不正确"),

    ITEM_NOT_EXIST(30001,"商品不存在"),

    //4000开头为交易信息错误
    STOCK_NOT_ENOUGH(40001,"库存不足");




    private int errCode;
    private String errMsg;

    EmBusinessError(int errCode, String errMsg) {
        this.errCode = errCode;
        this.errMsg = errMsg;
    }

    @Override
    public int getErrCode() {
        return this.errCode;
    }

    @Override
    public String getErrMsg() {
        return this.errMsg;
    }

    @Override
    public CommonError setErrMsg(String errMsg) {
        this.errMsg=errMsg;
        return this;
    }
}


业务异常类实现

public class BusinessException extends Exception implements CommonError{

   private CommonError commonError;

   //直接接受EmBusinessError的传参用于构造业务异常
    public BusinessException(CommonError commonError) {
        super();//继承异常类中的消息
        this.commonError = commonError;
    }

    //接受自定义errMsg的方式构造业务异常
    public BusinessException(CommonError commonError,String errorMsg) {
        super();
        this.commonError = commonError;
        this.commonError.setErrMsg(errorMsg);
    }

    @Override
    public int getErrCode() {
        return this.commonError.getErrCode();
    }

    @Override
    public String getErrMsg() {
        return this.commonError.getErrMsg();
    }

    @Override
    public CommonError setErrMsg(String errMsg) {
       this.commonError.setErrMsg(errMsg);
       return this;
    }
}

BaseCOntroller对业务报错的信息进行统一的解析返回给前端数据

通过对业务错误异常信息进行统一处理,从而对返回给前端json统一的格式
@ExceptionHandler(Exception.class):注解表明该类用于处理异常
@ResponseStatus(HttpStatus.OK):
@ResponseBosy:在响应体中返回数据:Marks a method or exception class with the status {@link #code} and

@Controller
public class BaseController {


    //定义ajax请求中的contentType
    public static final String CONTENT_TYPE_FORMED = "application/x-www-form-urlencoded";

    /*
   定义一个exceptionhandler解决没有被controller层吸收的异常
    */
    @ExceptionHandler(Exception.class)  //用于处理错误
    @ResponseStatus(HttpStatus.OK) //状态码为200
    @ResponseBody
    public Object handlerException(HttpServletRequest request, Exception ex) {
    //定义返回的数据
        Map<String, Object> responseData = new HashMap<>();
			
		//对业务异常进行解析
        if (ex instanceof BusinessException) {
            //ex抛出的信息太多,建议把Exception转化为自定义的BusinessException
            BusinessException businessException = (BusinessException) ex;
            responseData.put("errCode", businessException.getErrCode());
            responseData.put("errMsg", businessException.getErrMsg());
        } else {
            responseData.put("errCode", EmBusinessError.UNKWON_ERROR.getErrCode());
            responseData.put("errMsg",  EmBusinessError.UNKWON_ERROR.getErrMsg());
        }
        //!!!!
        //返回异常,返回给前端数据
        return CommonReturnType.create(responseData,"fail");
    }
}

在这里插入图片描述

跨域实现请求 @CrossOrigin

在调试的过程中,为了实现跨域的服务,可以加上@CrossOrigin这个注解

@CrossOrigin(origins = {"*"},allowCredentials = "true")//允许跨域请求
//origins:所有支持域的集合,
//allowCredentials:是否允许cookie随请求发送,使用时必须指定具体的域

同时,前端也需要进行配置


② jquery需要再每次使用ajax时增加如下配置
xhrFields:{
    withCredentials:true 
}

自动生成mapper

我们可以根据数据库中的表,使用一定的配置,生成基础的Mapper,然后在其基础上进行改进

参考内容>>>

pom依赖

		<!--配置mybatis自动生成器-->
				<plugin>
					<groupId>org.mybatis.generator</groupId>
					<artifactId>mybatis-generator-maven-plugin</artifactId>
					<version>1.3.5</version>
					<dependencies>
						<dependency>
							<groupId>org.mybatis.generator</groupId>
							<artifactId>mybatis-generator-core</artifactId>
							<version>1.3.5</version>
						</dependency>

                        <dependency>
                            <groupId>mysql</groupId>
                            <artifactId>mysql-connector-java</artifactId>
                            <version>5.1.41</version>
                        </dependency>

                    </dependencies>
					<executions>
						<execution>
							<id>mybatis.generator</id>
							<phase>package</phase>
							<goals>
								<goal>generate</goal>
							</goals>
						</execution>
					</executions>
					<configuration>
						<!--允许移动生成的文件-->
						<verbose>true</verbose>
						<!--允许自动覆盖文件-->
						<!--慎用-->
						<overwrite>false</overwrite>
						<configurationFile>
							src/main/resources/mybatis-generator.xml
						</configurationFile>
					</configuration>
				</plugin>

配置文件 mybatis-generator.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!--数据库地址账号密码-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
        <!--连接数据的名字-->
                        connectionURL="jdbc:mysql://47.98.144.17:3306/miaosha"
                        userId="root"
                        password="123456">
        </jdbcConnection>


        <javaTypeResolver >
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>
        <!--生成DataObkect类存放地址-->
        <javaModelGenerator targetPackage="com.zj.miaoshaproject.dataObject" targetProject="src/main/java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>


        <!--生成映射文件存放地址-->
        <sqlMapGenerator targetPackage="mapping"  targetProject="src/main/resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>

        <!--生成Dao类存放位置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.zj.miaoshaproject.dao"  targetProject="src/main/java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>

        <!--生成对应表及类名-->

       <table tableName="user_info" domainObjectName="UserDO" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false"></table>
        <table tableName="user_password" domainObjectName="UserPasswordDO" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false"></table>

        <table tableName="item" domainObjectName="ItemDO" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false"></table>
        <table tableName="item_stock" domainObjectName="ItemStockDO" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false"></table>
        <table tableName="order_info" domainObjectName="OrderDO" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false"></table>
        <table tableName="sequence_info" domainObjectName="SequenceDO" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false"></table>
        <table tableName="promo" domainObjectName="PromoDO" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false"></table>

    </context>
</generatorConfiguration>

各层Model(以及各个Model转换)

在开发中,每层都有其对应的模型,这样是根据功能来的,每层的数据有 不同作用
在这里插入图片描述

  • 前端层次VO:是给用户可以显示的数据(需要根据业务单独抽象出来)
  • 后端层次Model:业务层次,需要根据业务对模型进行聚合,方便操作
  • 数据库层次DO:数据库层次,这个就是为了方便对数据库中的字段进行操作

对于不同数据的组合,需要对数据进行转换,为了方便进行转换,可以使用BeanUtils.copy(source,desitination)注意各个字段要匹配,否则会出现赋值为空的情况

对字段进行校验

为了方便对各个字段进行校验,可以使用javax.validation.Validation提供的校验功能
校验器

package com.zj.miaoshaproject.validator;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;

@Component
public class ValidatorImpl implements InitializingBean {
    private Validator validator;

    /**
     *
     * @param bean 需要校验的bean
     * @return
     */
    public ValidationResult validate(Object bean) {


        //构造校验的结果
        ValidationResult validationResult = new ValidationResult();


        //对bean进行校验
        Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(bean);


        //集合大于0 说明该校验的字段有错
        if (constraintViolationSet.size() > 0) {

            validationResult.setHasErroes(true);

            //将map集合中的错误遍历
            constraintViolationSet.forEach(objectConstraintViolation -> {
                String propertyName = objectConstraintViolation.getPropertyPath().toString();
                String errMsg = objectConstraintViolation.getMessage();
                validationResult.getErrorMsgMap().put(propertyName, errMsg);
            });
        }
        //返回校验的结果
        return validationResult;
    }


    //实例化validator
    @Override
    public void afterPropertiesSet() throws Exception {
        //通过工厂的方式使得validator实例化
        this.validator = Validation.buildDefaultValidatorFactory().getValidator();

    }



}


校验结果的封装


package com.zj.miaoshaproject.validator;

import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;

//校验的结果
public class ValidationResult {


    //校验结果是否有错
    private boolean hasErroes = false;

    //存放错误的信息
    private Map<String, String> errorMsgMap = new HashMap<>();

    //实现通用的格式化字符串星系获取错误
    public String getErrMsg() {
        return StringUtils.join(errorMsgMap.values().toArray(), ",");
    }

    //
    public boolean isHasErroes() {
        return hasErroes;
    }

    public void setHasErroes(boolean hasErroes) {
        this.hasErroes = hasErroes;
    }

    public Map<String, String> getErrorMsgMap() {
        return errorMsgMap;
    }

    public void setErrorMsgMap(Map<String, String> errorMsgMap) {
        this.errorMsgMap = errorMsgMap;
    }
}

可以在服务层Model上加上注解进行校验


public class ItemModel  {


    private Integer id;

    //商品名称
    @NotBlank(message = "商品名称不能为空")
    private String title;


    //商品价格
    @NotNull(message = "商品价格不能为了拍卖行")
    @Min(value = 0,message = "商品价格必须大于0")
    private BigDecimal price;

    //商品库存
    @NotNull(message = "库存不能不填")
    private Integer stock;

在需要的地方进行校验

 @Override
    @Transactional
    public ItemModel createItem(ItemModel itemModel) throws BusinessException {

        ValidationResult validationResult = validator.validate(itemModel);

        //校验入参
        if(validationResult.isHasErroes()){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,validationResult.getErrMsg());
        }

        //转换ItemModel--->dataObject
        ItemDO itemDO = convertItemDOFromItemModel(itemModel);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值