这是我第一次写的练习项目,是一个在线购物商城的后端。因为是练习项目没有使用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不支持多主键的表格,当然联合主键也是可以通过范式分解成几个单主键的表,不过若是分解太细致会导致查询时经常用到连接查询,而连接查询则会影响查询效率,需要根据实际情况确定是否要继续进行范式分解(在数据库设计的时候)。