使用Springboot创建简易的商城后端

这是我第一次写的练习项目,是一个在线购物商城的后端。因为是练习项目没有使用Log4J2日志插件.

springboot启动配置

启动时勾选,springWeb项目,jdbc,mysql-connect,mybatis-framework

pom.xml引入依赖

       <dependency>  <!--mybatis-plus插件-->
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency><!--JWT插件-->
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency><!--pageHelper插件-->
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.3.0</version>
        </dependency>
        <dependency><!--fastJson插件-->
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>
        <dependency><!--Swagger依赖-->
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency><!--SwaggerUI插件-->
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency><!--SwaggerUI美化插件-->
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.9</version>
        </dependency>
        <dependency> <!-- 参数验证依赖-->
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.2.3.Final</version>
            <scope>compile</scope>
        </dependency>        
        <!-- 连表查询插件-->
        <dependency>
            <groupId>com.github.yulichang</groupId>
            <artifactId>mybatis-plus-join</artifactId>
            <version>1.2.4</version>
        </dependency>

在src的resources下创建mapper目录,存放Mybatis的xml文件映射

配置application.yml


#服务器相关设置,设置端口号
server:
  port: 8080

#spring设置,设置数据源,连接池使用默认的hikari连接池
#mvc:pathmatch:matching-strategy:这里的设置是为了能让Swagger能运行
spring:
  datasource:
    username: 数据库账户
    password: 数据库账户的密码
    url: jdbc:mysql://localhost:数据库端口号/数据库名称?userSSL=false;serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

#设置mybatis的mapper映射位置,就是刚才resources下的mapper目录,
# type-aliases-package可以不设置,是用来指定实体类存放位置的,这样resultType就只要直接写类名就可以了
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.mistore.entity

#配置PageHelper,一个mybatis分页查询插件
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params: count=countsql

#配置mybatis-plus的日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    

配置设置类
创建一个config包,用于放置配置类

Swagger配置类

@Configuration
@EnableSwagger2
@EnableKnife4j
public class SwaggerConfig {
    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.mistore.controller"))//扫描的包路径
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("小米商城")//文档标题
                .version("1.0.0")//文档版本说明
                .build();
    }
}

Mybatis配置类
开启mybatis-plus的事务注解

@EnableTransactionManagement
@Configuration
public class MybatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

启动类配置
在原有的启动类上添加mapper注解,扫描数据层

@SpringBootApplication
@MapperScan("com.example.mistore.mapper")
public class MistoreApplication {

    public static void main(String[] args) {
        SpringApplication.run(MistoreApplication.class, args);
    }

}

到这里前期的配置工作基本都做好了。

开始写实体类

建立entity包用于存放实体类,实体类包内再建立tableEntity包(用于存放数据库表对应的实体类),vo包(返回前端数据交互的实体类),param包(接受前端参数的实体类),tmp包(处理业务逻辑时的中间用到的实体类)
以tableEntity包中的一个实体类为例(getter,setter,构造方法请自己生成)
若要使用主键自增可以再@TableId注解添加type = IdType.AUTO,这样当你插入时的数据实体类主键对应属性为null时,写入数据库时就会自动生成主键

@ApiModel(description = "category表的对应实体类")
@TableName("category")
public class CategoryEntity {

    @ApiModelProperty(value = "类别Id",name = "categoryId",dataType = "Integer")
    @TableId
    private Integer categoryId;

    @ApiModelProperty(value = "类别名称",name = "categoryName",dataType = "String")
    @TableField("category_name")
    private String categoryName;

    @ApiModelProperty(value = "类别图片1",name = "categoryPicture1",dataType = "String")
    @TableField("category_picture1")
    private String categoryPicture1;

    @ApiModelProperty(value = "类别图片2",name = "categoryPicture2",dataType = "String")
    @TableField("category_picture2")
    private String categoryPicture2;

mapper层

建立mapper包存放mapper层的接口,以其中一个mapper类为例,记得添加注解
该接口继承MPJBaseMapper<表格对应的实体类>

/**
 * 购物车表的mapper接口
 * */
@Repository
public interface IShoppingCartMapper extends MPJBaseMapper<ShoppingCartTableEntity> {
}

Service层

service层包中建立两个包,一个放自定义的Service接口,一个放实现Service接口的实现类

自定义Service接口,继承MPJBaseService<表对应的实体类>

public interface IShoppingCartService extends MPJBaseService<ShoppingCartTableEntity> {

    List<ShoppingCartProductsInfo> getShoppingCartProductsList(Integer userId);

    boolean addShoppingCartProduct(Integer userId,Integer productId,Integer num);

    boolean updateShoppingCartProductNum(Integer userId,Integer productId,Integer num);

    boolean deleteShoppingCartProduct(Integer userId,Integer productId);
}

Service实现类,继承MPJBaseServiceImpl<对应表的mapper层接口, 对应表的实体类> implements 自定义Service接口,记得加注解

@Service
public class ShoppingCartServiceImpl extends MPJBaseServiceImpl<IShoppingCartMapper, ShoppingCartTableEntity> implements IShoppingCartService {

    @Autowired
    private IShoppingCartMapper iShoppingCartMapper;


    /**
     * 根据用户Id查询用户购物车内购买的商品
     * @return List<ShoppingCartProductsInfo>
     * */
    @Override
    public List<ShoppingCartProductsInfo> getShoppingCartProductsList(Integer userId) {
        List<ShoppingCartProductsInfo> list = iShoppingCartMapper.selectJoinList(ShoppingCartProductsInfo.class,
                new MPJLambdaWrapper<ShoppingCartTableEntity>()
                .select(ShoppingCartTableEntity::getShoppingCartId,ShoppingCartTableEntity::getNum)
                .select(Product::getProductId,Product::getProductName,Product::getCategoryId,Product::getProductTitle,Product::getProductIntro,Product::getProductPicture,Product::getProductPrice,Product::getProductSellingPrice)
                .leftJoin(Product.class,Product::getProductId,ShoppingCartTableEntity::getProductId)
                .eq(ShoppingCartTableEntity::getUserId,userId)
        );
        return list;
    }

    /**
     * 用户向购物车内添加商品
     * @return boolean,表示添加成功或者失败
     * */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean addShoppingCartProduct(Integer userId, Integer productId, Integer num) {
        QueryWrapper<ShoppingCartTableEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("product_id",productId).eq("user_id",userId);
        if ( iShoppingCartMapper.selectOne(queryWrapper) != null)
            return false;

        ShoppingCartTableEntity shoppingCartTableEntity = new ShoppingCartTableEntity();
        shoppingCartTableEntity.setProductId(productId);
        shoppingCartTableEntity.setUserId(userId);
        shoppingCartTableEntity.setNum(num);
        iShoppingCartMapper.insert(shoppingCartTableEntity);
        return true;
    }


    /**
     * 用户修改购物车内某件商品的数量
     * @return boolean,表示修改成功或者失败
     * */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateShoppingCartProductNum(Integer userId, Integer productId, Integer num) {
        QueryWrapper<ShoppingCartTableEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("product_id",productId).eq("user_id",userId);
        ShoppingCartTableEntity x = iShoppingCartMapper.selectOne(queryWrapper);
        if ( x != null) {
            x.setNum(num);
            iShoppingCartMapper.updateById(x);
            return true;
        }
        else
            return false;
    }


    /**
     * 用户删除购物车内的某件商品
     * @return boolean,表示删除成功或者失败
     * */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteShoppingCartProduct(Integer userId, Integer productId) {
        QueryWrapper<ShoppingCartTableEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("product_id",productId).eq("user_id",userId);
        ShoppingCartTableEntity x = iShoppingCartMapper.selectOne(queryWrapper);
        if ( x != null) {
            iShoppingCartMapper.delete(queryWrapper);
            return true;
        }
        else
            return false;
    }
}

controller层

用于与前端进行数据交互的层,通过调用Service实现类完成业务逻辑

@Api(tags = "购物车模块")
@RestController
@RequestMapping("/shoppingCart")
public class ShoppingCartController {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private ShoppingCartServiceImpl shoppingCartService;

    @ApiOperation("查询当前用户购物车内的内容")
    @ApiResponses( {
            @ApiResponse(code = 200,message = "查询当前用户购物车内容成功")    }
    )
    @GetMapping
    public AjaxResult<List<ShoppingCartProductsInfo>> getShoppingCartProductByUser() {
        String token = request.getHeader("token");
        Integer userId = TokenUtill.getUserIdFromToken(token);
        List<ShoppingCartProductsInfo> list = shoppingCartService.getShoppingCartProductsList(userId);
        if (list == null) return AjaxResult.success("当前用户购物车内容为空");
        return AjaxResult.success("查询当前用户购物车内容成功",list);
    }


    @ApiOperation("查询当前用户购物车内商品的数量")
    @ApiResponses( {
            @ApiResponse(code = 200,message = "查询当前用户购物车内商品的数量成功")    }
    )
    @GetMapping("/count")
    public AjaxResult<Integer> getShoppingCartProductCounts() {
        String token = request.getHeader("token");
        Integer userId = TokenUtill.getUserIdFromToken(token);
        List<ShoppingCartProductsInfo> list = shoppingCartService.getShoppingCartProductsList(userId);
        if (list == null) return AjaxResult.success("当前用户购物车内容为空",0);
        return AjaxResult.success("查询当前用户购物车内商品数量成功",list.size());
    }


    @ApiOperation("向购物车内添加商品")
    @ApiResponses( {
            @ApiResponse(code = 200,message = "添加购物车成功"),
            @ApiResponse(code = 500,message = "添加购物车失败,可能是购物车内已经有这件商品"),
            @ApiResponse(code = 500,message = "参数:商品数量不能为空")
    })
    @PostMapping
    public AjaxResult addShoppingCartProduct(@RequestBody AddShoppingCartProducrParam addShoppingCartProducrParam) {

        if (addShoppingCartProducrParam.getNum() == null) return AjaxResult.error("参数:数量不能为空");
        String token = request.getHeader("token");
        Integer userId = TokenUtill.getUserIdFromToken(token);
        if ( shoppingCartService.addShoppingCartProduct(userId,addShoppingCartProducrParam.getProductId(),addShoppingCartProducrParam.getNum()) )
            return AjaxResult.success("添加成功");
        else
            return AjaxResult.error("添加失败,可能是购物车内已经有这件商品了");
    }


    @ApiOperation("修改购物车内某件商品的数量")
    @ApiResponses( {
            @ApiResponse(code = 200,message = "修改购物车内某件商品的数量成功"),
            @ApiResponse(code = 500,message = "修改失败,可能是购物车内没有这件商品"),
            @ApiResponse(code = 500,message = "参数:商品数量不能为空")
    })
    @PutMapping
    public AjaxResult updateShoppingCartProductNum(@RequestBody AddShoppingCartProducrParam addShoppingCartProducrParam) {
        if (addShoppingCartProducrParam.getNum() == null) return AjaxResult.error("参数:数量不能为空");
        String token = request.getHeader("token");
        Integer userId = TokenUtill.getUserIdFromToken(token);
        if ( shoppingCartService.updateShoppingCartProductNum(userId,addShoppingCartProducrParam.getProductId(),addShoppingCartProducrParam.getNum()) )
            return AjaxResult.success("修改成功");
        else
            return AjaxResult.error("修改失败,可能是购物车内没有这件商品");
    }


    @ApiOperation("删除购物车内的某件商品")
    @ApiResponses( {
            @ApiResponse(code = 200,message = "删除成功"),
            @ApiResponse(code = 500,message = "删除失败,可能是购物车内没有这件商品"),
            @ApiResponse(code = 500,message = "参数:商品数量不能为空")
    })
    @DeleteMapping("/{productId}")
    public AjaxResult deleteShoppingCartProduct(@PathVariable Integer productId) {
        if (productId == null) return AjaxResult.error("参数:商品数量不能为空");
        String token = request.getHeader("token");
        Integer userId = TokenUtill.getUserIdFromToken(token);
        if ( shoppingCartService.deleteShoppingCartProduct(userId,productId) )
            return AjaxResult.success("删除成功");
        else
            return AjaxResult.error("删除失败,可能是购物车内没有这件商品");
    }

}

工具层

用于存放一些封装好的工具方法,例如返回前端数据的AjaxResult类,返回前端分页查询的TableDataInfo类,从token中解密数据的TokenUtill类,实现雪花算法的SnowFlakeUtil类。

拦截器层和过滤器层

用于实现拦截器和过滤器,本项目中没有使用到复杂的拦截器和过滤器,只有一个用于拦截请求是否有token的拦截器。但是一般在其它项目中拦截器和过滤器都十分的重要。
写好拦截器和过滤器后要在config层里实现拦截器和过滤器相关的配置类。

该项目所有接口君经过postman测试完成

总结

该练习项目没有很复杂的点,但是对于第一次完成项目的我来说还是遇到了很多问题,主要是在业务逻辑实现这一块,要善于利用Mybatis-plus和mybatis-plus-join两个插件。其中的Wapper可以很方便的设置sql语句,特别是MPJLambdaWrapper,用好lambda表达式的Wapper可以节省精力,更快捷的写好sql的CRUD,在本项目中我没有用到mapper.xml的映射文件来写sql语句,所有resources/mapper目录(不是mapper层)里面是空的。

通过该项目主要是更加熟悉了springboot以及相关插件的使用,练习了项目用到的CRUD的实现。

不过在项目中也遇到了一些困难,比如Mybatis-plus不支持多主键的表格,当然联合主键也是可以通过范式分解成几个单主键的表,不过若是分解太细致会导致查询时经常用到连接查询,而连接查询则会影响查询效率,需要根据实际情况确定是否要继续进行范式分解(在数据库设计的时候)。


版权声明:本文为m0_52985643原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。