Spring Boot电商项目(二)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Spring Boot电商项目

我们接下来。再对application.properties这文件进行下基础的配置:


spring.datasource.name=shop
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shop?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=zc20020106
server.port=8083
mybatis.mapper-locations=classpath:mappers/*.xml


下面我们做一个简单的查询来测试一下我们的项目和数据库方面是否初始化成功。


我们之前生成的dao包里的mappers并没有自动生成spring注解,我们可以手动给他们都加上@Repository注解。我们还要给我们应用的启动类添加一个@MapperScan(basePackages = “dao包名”)的注解。这个注解用于扫描mapper映射,指向我们的dao包。


package com.haiexijun.mall;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.haiexijun.mall.model.dao")
public class MallApplication {
    public static void main(String[] args) {
        SpringApplication.run(MallApplication.class, args);
    }
}


下面就创建好controller包、service包,并编写如下的类和接口:

service包下创建UserService接口,这个接口不用加注解:


package com.haiexijun.mall.service;
import com.haiexijun.mall.model.pojo.User;
public interface UserService {
    public User getUser();
}


在service包下面再创建一个impl包,用于存放接口的实现类,下面是UserServiceImpl类,我们在这个接口里面添加@Service注解,并在类里面用@Autowired通过类型引入UserMapper,我们通过UserMapper的selectByPrimaryKey方法通过主键查询数据:


package com.haiexijun.mall.service.impl;
import com.haiexijun.mall.model.dao.UserMapper;
import com.haiexijun.mall.model.pojo.User;
import com.haiexijun.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    UserMapper userMapper;
    @Override
    public User getUser() {
        return userMapper.selectByPrimaryKey(4);
    }
}


最后我们在controller包下面创建好UserController,并在里面引入UserService,配置好请求参数。


package com.haiexijun.mall.controller;
import com.haiexijun.mall.model.pojo.User;
import com.haiexijun.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
 * 描述:用户控制器
 */
@Controller
public class UserController {
    @Autowired
    UserService userService;
    @GetMapping("/test")
    @ResponseBody
    public User personalPage(){
        return userService.getUser();
    }
}


之后,就可以运行项目了。浏览器中运行结果如下,成功查询到数据:


aa3805c7d6734d4abc1df77def880c80.png


3.配置log4j2日志组件


log4j2日志组件是我们开发中必不可少的一个关键组成部分。日志的级别(从高到低)有:error、warn、info、debug、trace。

我们在安装log4j2日志组件前,先要排除之前的日志组件,先添加如下的配置:


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


还要再引入log4j2的依赖:


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>


这个依赖不用设置版本号,因为它会工具我们spring-boot的版本自动匹配版本号。

下面我们还要在resources目录下创建一个log4j2的配置文件log4j2.xml,如下:


<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="fatal">
  <Properties>
    <!--日志文件的存放位置,要在linux中创建,在windows里面对应C盘用户目录下的logs目录,value可自己更改-->
    <Property name="baseDir" value="${sys:user.home}/logs"/>
  </Properties>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
      <ThresholdFilter level="info" onMatch="ACCEPT"
        onMismatch="DENY"/>
      <PatternLayout
        pattern="[%d{MM:dd HH:mm:ss.SSS}] [%level] [%logger{36}] - %msg%n"/>
    </Console>
    <!--debug级别日志文件输出-->
    <RollingFile name="debug_appender" fileName="${baseDir}/debug.log"
      filePattern="${baseDir}/debug_%i.log.%d{yyyy-MM-dd}">
      <!-- 过滤器 -->
      <Filters>
        <!-- 限制日志级别在debug及以上在info以下 -->
        <ThresholdFilter level="debug"/>
        <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
      </Filters>
      <!-- 日志格式 -->
      <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
      <!-- 策略 -->
      <Policies>
        <!-- 每隔一天转存 -->
        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
        <!-- 文件大小 -->
        <SizeBasedTriggeringPolicy size="100 MB"/>
      </Policies>
    </RollingFile>
    <!-- info级别日志文件输出 -->
    <RollingFile name="info_appender" fileName="${baseDir}/info.log"
      filePattern="${baseDir}/info_%i.log.%d{yyyy-MM-dd}">
      <!-- 过滤器 -->
      <Filters>
        <!-- 限制日志级别在info及以上在error以下 -->
        <ThresholdFilter level="info"/>
        <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
      </Filters>
      <!-- 日志格式 -->
      <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
      <!-- 策略 -->
      <Policies>
        <!-- 每隔一天转存 -->
        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
        <!-- 文件大小 -->
        <SizeBasedTriggeringPolicy size="100 MB"/>
      </Policies>
    </RollingFile>
    <!-- error级别日志文件输出 -->
    <RollingFile name="error_appender" fileName="${baseDir}/error.log"
      filePattern="${baseDir}/error_%i.log.%d{yyyy-MM-dd}">
      <!-- 过滤器 -->
      <Filters>
        <!-- 限制日志级别在error及以上 -->
        <ThresholdFilter level="error"/>
      </Filters>
      <!-- 日志格式 -->
      <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
      <Policies>
        <!-- 每隔一天转存 -->
        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
        <!-- 文件大小 -->
        <SizeBasedTriggeringPolicy size="100 MB"/>
      </Policies>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="debug">
      <AppenderRef ref="Console"/>
      <AppenderRef ref="debug_appender"/>
      <AppenderRef ref="info_appender"/>
      <AppenderRef ref="error_appender"/>
    </Root>
  </Loggers>
</Configuration>


4. AOP统一处理web请求日志


这是对于系统健壮性的一种保证,在真实的项目中,基本上都会有这样的功能,我们会用filter,把每个请求都给打印出来,这样的良好习惯可以提高我们开发和调试的效率。我们这一节的目的就是创建filter,把我们的请求信息和返回信息给打印出来。


下面就来实操演示,究竟应该如何配置这个功能:


要想引入AOP的功能,我们首先要引入一个依赖:


<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>


有了这个依赖之后,我们先要去建立一个过滤器,来对我们的请求来做一个拦截和打印。我们新建一个filter包,里面用来存放过滤器。

我们在里面创建一个WebLogAspect类:


package com.haiexijun.mall.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
 * 描述:打印请求和响应信息
 */
@Aspect
@Component
public class WebLogAspect {
    private final Logger log = LoggerFactory.getLogger(WebLogAspect.class);
    @Pointcut("execution(public * com.haiexijun.mall.controller.*.*(..))")
    public void webLog(){
    }
    //捕获,并打印请求的相关信息
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint){
        //收到请求,记录请求内容
        ServletRequestAttributes attributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request=attributes.getRequest();
        //打印请求的url
        log.info("URL : " + request.getRequestURL().toString());
        //打印请求方法
        log.info("HTTP_METHOD : " +request.getMethod());
        //打印请求的Ip地址
        log.info("IP : " + request.getRemoteAddr());
        //打印请求处理类的一些信息
        log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName());
        //打印请求的参数
        log.info("ARGS : "+ Arrays.toString(joinPoint.getArgs()));
    }
    //捕获,并打印响应相关的信息
    @AfterReturning(returning = "res",pointcut = "webLog()")
    public void doAfterReturning(Object res) throws JsonProcessingException {
        //处理完请求,返回内容
        log.info("RESPONSE : "+new ObjectMapper().writeValueAsString(res));
    }
}


浏览器运行,访问一下/test接口,控制台打印了请求和响应的相关信息。


c7c9174ee89b44f1887124dd084bc227.png


三.用户模块的开发


1.用户模块整体介绍


c2c8f2c9e56a47698b2f26b1799e047b.png


这个模块会有登录,注册,注册会有重名校验,用户名不允许重复。同时,为了更加安全,我们还会对密码进行加密存储。在登录的时候我们是用session,可以把用户登录的信息给保存下来。还会进行越权校验,比如说没有登陆的情况下,它不允许修改,以及只能修改自己用户的一些信息等。之后我们还会学习统一响应对象的概念,这个概念非常的重要,他会贯穿我们整个项目。而在构建这个对象的时候,我们也会对异常进行处理,会构建java异常体系。之后会对postman进行一些使用上的讲解。以及统一异常处理的功能,因为异常我们如果不对其进行处理的话会有安全风险。所以我们用过滤器对其统一处理。


接口设计,我们会用到一个接口文档,我们可以先用这个文档来开发,后面会学习如何自己生成接口文档。


2.API统一返回对象


我们要创建一个包叫common包,这个包的作用就是用来存储一些统一的、通用的类,里面创建一个ApiRestResponse类,这个类就是我们的统一响应对象。


package com.haiexijun.mall.common;
import com.haiexijun.mall.exception.MallExceptionEnum;
/**
 * 描述:通用返回对象
 */
public class ApiRestResponse<T> {
    // 状态码
    private Integer status;
    // 响应信息
    private String msg;
    // 响应的数据data
    private T data;
    //响应成功为10000。这个状态数是我们自己定义的。
    private static final int OK_CODE =10000;
    private static final String OK_MSG="SUCCESS";
    public ApiRestResponse() {
        //无参构造方法为默认的响应信息
        this(OK_CODE,OK_MSG);
    }
    public ApiRestResponse(Integer status, String msg, T data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }
    public ApiRestResponse(Integer status, String msg) {
        this.status = status;
        this.msg = msg;
    }
    public Integer getStatus() {
        return status;
    }
    public void setStatus(Integer status) {
        this.status = status;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
    // 为了方便调试,打印出来,故重写toString方法
    @Override
    public String toString() {
        return "ApiRestResponse{" +
                "status=" + status +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
    //请求成功时调用,无返回结果
    public static <T> ApiRestResponse<T> success(){
        //成功就调用无参的构造方法就行
        return new ApiRestResponse<T>();
    }
    // 请求成功调用的重载,有返回结果
    public static <T> ApiRestResponse<T> success(T result){
        ApiRestResponse<T> response=new ApiRestResponse<T>();
        response.setData(result);
        return response;
    }
    public static <T> ApiRestResponse<T> error(MallExceptionEnum ex){
        return new ApiRestResponse<T>(ex.getCode(), ex.getMsg());
    }
    public static <T> ApiRestResponse<T> error(Integer code,String msg){
        return new ApiRestResponse<T>(code,msg);
    }
}


我们还要考虑到用户输入了错误的信息之后,会返回错误信息和异常。所以我们要创建一个异常处理类,创建exception包,先在里面创建一个异常枚举类,里面有一些异常的枚举。


package com.haiexijun.mall.exception;
/**
 * 描述类:异常枚举类
 */
public enum MallExceptionEnum {
    NEED_USER_NAME(10001,"用户名不能为空"),
    PASSWORD_TOO_SHORT(10003,"密码长度不能小于八位"),
    NEED_USER_PASSWORD(10002,"用户密码不能为空"),
    NAME_EXISTED(10004,"用户名已存在,注册失败"),
    INSERT_FAILED(10005,"插入失败,请重试"),
    SYSTEM_ERROR(20000,"系统异常");
    //异常码
    Integer code;
    //异常信息
    String msg;
    MallExceptionEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
}


然后还要在里面创建一个异常类:


package com.haiexijun.mall.exception;
/**
 * 描述:  统一异常描述类
 */
public class MallException extends Exception{
    private final Integer code;
    private final String message;
    public MallException(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
    public MallException(MallExceptionEnum exceptionEnum){
        this(exceptionEnum.code, exceptionEnum.msg);
    }
    public Integer getCode() {
        return code;
    }
    @Override
    public String getMessage() {
        return message;
    }
}


关于异常的响应,我们系统接受到请求后,会对进行一系列操作,其中就可能会有异常产生,并返回,我们要对异常进行处理一下在返回给用户。这就是统一处理异常的类GlobalExceptionHandler。


package com.haiexijun.mall.exception;
import com.haiexijun.mall.common.ApiRestResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
 * 描述 : 用于统一处理异常的类
 */
//@ControllerAdvice的作用就是拦截异常的
@ControllerAdvice
public class GlobalExceptionHandler {
    private final Logger log= LoggerFactory.getLogger(GlobalExceptionHandler.class);
    //系统错误的异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object handleException(Exception e){
        log.error("Default Exception : " +e);
        return ApiRestResponse.error(MallExceptionEnum.SYSTEM_ERROR);
    }
    //处理自定义的异常
    @ExceptionHandler(MallException.class)
    @ResponseBody
    public Object MallException(MallException e){
        log.error("MallException : " +e);
        return ApiRestResponse.error(e.getCode(),e.getMessage());
    }
}


3.注册接口的开发


我们注册就要进行插入操作,在UserService类中添加一个注册的方法:


void register(String userName,String password) throws MallException;


并在他的实现类UserServiceImpl中对其进行实现:


    @Override
    public void register(String userName, String password) throws MallException {
        //查询用户名是否存在,不允许重名
        User result=userMapper.selectByName(userName);
        if (result !=null){
            //如果重名就抛出异常,把它作为异常进行抛出去
            throw new MallException(MallExceptionEnum.NAME_EXISTED);
        }
        //如果没有重名,就把注册的用户信息写到数据库中,注册成功
        User user=new User();
        user.setUsername(userName);
        user.setPassword(password);
        //userMapper的insertSelective方法会先判断插入数据的字段是否为空,如果不是空,才对他进行插入。insert方法则全部插入
        //方法返回插入成功的条数
        int count= userMapper.insertSelective(user);
        // 下面判断一下是否插入成功
        if (count==0){
            throw new MallException(MallExceptionEnum.INSERT_FAILED);
        }
    }


我们注册涉及到要对比用户名是否有重复,所以,我们要在UserMapper类中添加一个新的查询方法,并在对应的xml文件进行配置:


User selectByName(String userName);


  <select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from mall_user
    where username=#{userName,jdbcType=VARCHAR}
  </select>


最后就可以在UserController中编写注册的接口了:


package com.haiexijun.mall.controller;
import com.haiexijun.mall.common.ApiRestResponse;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.exception.MallExceptionEnum;
import com.haiexijun.mall.model.pojo.User;
import com.haiexijun.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
 * 描述:用户控制器
 */
@Controller
public class UserController {
    @Autowired
    UserService userService;
    @GetMapping("/test")
    @ResponseBody
    public User personalPage(){
        return userService.getUser();
    }
    @PostMapping("/register")
    @ResponseBody
    public ApiRestResponse register(@RequestParam("userName") String userName, @RequestParam("password") String password) throws MallException {
        //注册校验
        //StringUtils是springboot提供的字符串判断类
        if(StringUtils.isEmpty(userName)){
            //如果用户名为空
            return ApiRestResponse.error(MallExceptionEnum.NEED_USER_NAME) ;
        }
        if (StringUtils.isEmpty(password)){
            //如果密码为空
            return ApiRestResponse.error(MallExceptionEnum.NEED_USER_PASSWORD);
        }
        if (password.length()<8){
            //如果密码长度太小,也要报异常,这里为不小于八位
            return ApiRestResponse.error(MallExceptionEnum.PASSWORD_TOO_SHORT);
        }
        //如果密码不小于八位,而且一切正常的话
        userService.register(userName,password);
        return ApiRestResponse.success();
    }
}
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
11 2
|
29天前
|
设计模式 前端开发 Java
Spring MVC——项目创建和建立请求连接
MVC是一种软件架构设计模式,将应用分为模型、视图和控制器三部分。Spring MVC是基于MVC模式的Web框架,通过`@RequestMapping`等注解实现URL路由映射,支持GET和POST请求,并可传递参数。创建Spring MVC项目与Spring Boot类似,使用`@RestController`注解标记控制器类。
34 1
Spring MVC——项目创建和建立请求连接
|
29天前
|
Java 关系型数据库 MySQL
Maven——创建 Spring Boot项目
Maven 是一个项目管理工具,通过配置 `pom.xml` 文件自动获取所需的 jar 包,简化了项目的构建和管理过程。其核心功能包括项目构建和依赖管理,支持创建、编译、测试、打包和发布项目。Maven 仓库分为本地仓库和远程仓库,远程仓库包括中央仓库、私服和其他公共库。此外,文档还介绍了如何创建第一个 SpringBoot 项目并实现简单的 HTTP 请求响应。
113 1
Maven——创建 Spring Boot项目
|
1月前
|
Java 关系型数据库 MySQL
如何使用 maven 创建一个 Spring Boot项目
Maven 是一个强大的项目管理工具,通过配置 `pom.xml` 文件自动获取所需的 jar 包,提高开发效率。其核心功能包括项目构建和依赖管理。项目构建支持编译、测试、打包和发布等流程,而依赖管理则通过中央仓库、本地仓库和私有服务器获取和管理项目依赖。示例中展示了如何创建第一个 SpringBoot 项目并实现简单接口。
22 1
如何使用 maven 创建一个 Spring Boot项目
|
1月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
52 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
1月前
|
Java 应用服务中间件 Android开发
Eclipse创建Spring项目
本文介绍了在Eclipse中创建Spring项目的步骤,包括如何配置Tomcat服务器、创建项目、部署项目到Tomcat以及添加Spring框架所需的JAR包。
49 1
Eclipse创建Spring项目
|
29天前
|
Java Apache Maven
Java/Spring项目的包开头为什么是com?
本文介绍了 Maven 项目的初始结构,并详细解释了 Java 包命名惯例中的域名反转规则。通过域名反转(如 `com.example`),可以确保包名的唯一性,避免命名冲突,提高代码的可读性和逻辑分层。文章还讨论了域名反转的好处,包括避免命名冲突、全球唯一性、提高代码可读性和逻辑分层。最后,作者提出了一个关于包名的问题,引发读者思考。
Java/Spring项目的包开头为什么是com?
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
53 2
|
1月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
160 2
|
1月前
|
前端开发 安全 Java
【Spring】Spring Boot项目创建和目录介绍
【Spring】Spring Boot项目创建和目录介绍
83 2