1.创建SpringBoot项目必须选择Java8为版本
2.选择需要的依赖项

  • 使用 Maven 创建
  1. 在maven中pom.xml配置SpringBoot
  2. 构建包结构1,application.yml文件 在项目结构中,spring设置中点击右侧spring小图标添加当前yml
  3. 创建启动类,来启动SpringBoot
<project>
    ...
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
  
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

    </dependencies>
...
</project>
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class helloWorldApplication {
    public static void main(String[] args) {
        SpringApplication.run(helloWorldApplication.class, args);
    }
}
  • 使用 Spring Initializr 创建
  1. 选择SpringWeb依赖,和其他相关依赖
  2. 删掉不相关文件和文件夹如 .mvn和HELP.md
  3. 将application.properties改为application.yml
  4. 创建包结构 1

3.添加其他依赖项

  1. mybatis-plus-boot-starter
  2. lombok
  3. fastjson
  4. commons-lang
  5. mysql-connector-java
  6. druid-spring-boot-starter
<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</version>
        </dependency>

4.添加maven插件
spring-boot-maven-plugin

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

5.若resources中所有资源放在static中则不用静态资源映射,否则在config包下编写静态资源映射类

@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
  @Override
  protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    log.info("开始进行静态资源映射");
    registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
    ///backend/**表示backend包下的所有资源,必须加classpath:,否则可能找不到该目录
    registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
  }
}

6.在yml中配置SpringBoot相关配置

#如果三个文件同时存在,运行优先级:properties>yml>yaml,建议用yml,数据前面必须有空格
server:
  port: 80
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/javaee?useSSL=false&useServerPrepStmts=true #SpringBoot2.4.3之前需要加 ?serverTimezone=UTC
      username: root
      password: root
  main:
    banner-mode: off #关闭springboot的logo,直接输入banner有提示,在资源中加一个空壳logback取消运行时日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true #在映射实体或者数据库时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
  global-config:
    db-config:
      id-type: assign_id #id自增策略
    banner: false #关闭mybatis的logo
waimai:
  path: F:/images/
email:
  from_address: ...
  password: ...

7.编写实体类与数据库表一一对应

SetmealDish实体类

/**
 * 套餐
 */
@Data
public class Setmeal implements Serializable {

  private static final long serialVersionUID = 1L;

  private Long id;


  //分类id
  private Long categoryId;


  //套餐名称
  private String name;


  //套餐价格
  private BigDecimal price;


  //状态 0:停用 1:启用
  private Integer status;


  //编码
  private String code;


  //描述信息
  private String description;


  //图片
  private String image;


  @TableField(fill = FieldFill.INSERT)
  private LocalDateTime createTime;


  @TableField(fill = FieldFill.INSERT_UPDATE)
  private LocalDateTime updateTime;


  @TableField(fill = FieldFill.INSERT)
  private Long createUser;


  @TableField(fill = FieldFill.INSERT_UPDATE)
  private Long updateUser;


  //是否删除
  private Integer isDeleted;
}

需要扩展实体属性时,写在DTO中

举个栗子,这里SetmealDTO中新增了分类名称

@Data
public class SetmealDTO extends Setmeal {

    private List<SetmealDish> setmealDishes;//新增一个套餐清单,将套餐清单的信息放入集合,因为Setmeal中没有该属性,前端需要展示该属性

    private String categoryName;//新增一个分类名称,因为Setmeal中没有该属性,前端需要展示该属性
}

8.编写service和mapper包中所有的接口和实现类框架
mapper接口:

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

service接口:

如果业务太复杂可以封装成方法写在接口中,在实现类中实现

也可以将业务全部写在service中使controller中的代码简洁

public interface UserService extends IService<User> {
}

service实现类:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

9.编写common中的业务处理

ThreadLocal封装的工具类,用于保存和获取当前登录用户id

public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    public static void setCurrentId(Long id){
        threadLocal.set(id);//将当前用户放入线程中,因为浏览器处理一个用户应用是用一个线程,所以不会因为线程多而出现数据混乱的情况
    }
    public static Long getCurrentId(){
       return threadLocal.get();//取出线程中的用户
    }
}

自定义的业务异常

public class CustomException extends RuntimeException{
    public CustomException(String message){
        super(message);
    }
}

全局异常处理,防止出错后直接抛异常给虚拟机,这样导致程序无法继续运行,服务崩溃

@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 异常处理方法,处理SQLIntegrityConstraintViolationException
     * @param ex
     * @return
     */
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex) {
        log.error(ex.getMessage());
        if (ex.getMessage().contains("Duplicate entry")) {
            String[] s = ex.getMessage().split(" ");//用空格分隔
            String s1 = s[2] + "已存在";
            EmailSender.sendToDeveloper(ex);//给开发者发送邮件
            return R.error(s1);
        }
        return R.error("未知错误");
    }
    /**
     * 异常处理方法,处理自定义异常
     * @param ex
     * @return
     */
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler(CustomException ex) {
        log.error(ex.getMessage());
        EmailSender.sendToDeveloper(ex);//给开发者发送邮件
        return R.error(ex.getMessage());
    }
}

对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象

/**
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

自定义的元数据对象处理器

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    /**
     * 插入操作自动填充插入时间,插入者
     *
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("公共字段自动填充[insert]");
        log.info(metaObject.toString());
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("createUser", BaseContext.getCurrentId());
        metaObject.setValue("updateUser", BaseContext.getCurrentId());
    }

    /**
     * 更新操作自动填充
     *
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共字段自动填充[update]");
        log.info(metaObject.toString());
        log.info("线程id:{}", Thread.currentThread().getId());
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("updateUser", BaseContext.getCurrentId());
    }
}

与前端交互时的规定格式

@Data
public class R<T> {

    private Integer code; //编码:1成功,0和其它数字为失败

    private String msg; //错误信息

    private T data; //数据

    private Map map = new HashMap(); //动态数据

    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }

    public static <T> R<T> error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}

10.编写config中的配置

配置MP2

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //在MybatisPlusInterceptor(MybatisPlus 拦截器)中添加分页拦截器
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

配置WebMvc

@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始进行静态资源映射");
        //添加资源处理器,添加资源位置
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }

    /**
     * 扩展mvc消息转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展自己的消息转换器");
        //创建消息转换器
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //设置对象转换器,底层使用Jackson将java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //将上面的消息转换器对象追加到mvc框架的转换器容器集合中中
        converters.add(0,messageConverter);//注意:加索引优先使用自己的转换器
    }

}

11.编写utils工具类

EmailSender用来给程序员发送电子邮箱,提醒程序服务出错了

public class EmailSender {
    public static void sendToDeveloper(Exception ex) {
        long l = System.currentTimeMillis();
        SimpleDateFormat smf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Boolean s = EmailUtils.sendEmail("发送者邮箱", "密码", "程序员邮箱", "亲,你的代码在" + smf.format(l) + "时,出现了严重错误:", ex.getMessage()+"请尽快及时修复哦!!!");
    }
}

EmailUtils邮件发送工具,这里采用的是谷歌邮箱,大家可以用QQ邮箱,应该比这个快一些,另外其实可以将EmailSender与EmailUtils放在一起,只需修改common中的全局异常捕获

public class EmailUtils {
    public static Boolean sendEmail(String from, String pass, String to_address, String title, String msg) {

        String host = "smtp.gmail.com";

        // Get system properties
        Properties properties = System.getProperties();
        // Setup mail server
        properties.put("mail.smtp.starttls.enable", "true");
        properties.put("mail.smtp.host", host);
        properties.put("mail.smtp.user", from);
        properties.put("mail.smtp.password", pass);
        properties.put("mail.smtp.port", "587");
        properties.put("mail.smtp.auth", "true");

        // Get the default Session object.
        Session session = Session.getDefaultInstance(properties);

        try {
            // Create a default MimeMessage object.
            MimeMessage message = new MimeMessage(session);

            // Set From: header field of the header.
            message.setFrom(new InternetAddress(from));

            // Set To: header field of the header.
            message.addRecipient(Message.RecipientType.TO,
                    new InternetAddress(to_address));

            // Set Subject: header field
            message.setSubject(title);

            // Now set the actual message
            message.setText(msg);

            // Send message
            Transport transport = session.getTransport("smtp");
            transport.connect(host, from, pass);
            transport.sendMessage(message, message.getAllRecipients());
            transport.close();
            System.out.println("Sent message successfully....");
        } catch (Exception mex) {
            mex.printStackTrace();
            return false;
        }
        return true;
    }
}

随机生成验证码工具类

/**
 * 随机生成验证码工具类
 */
public class ValidateCodeUtils {
    /**
     * 随机生成数字验证码
     * @param length 长度为4位或者6位
     * @return
     */
    public static Integer generateValidateCode(int length){
        Integer code =null;
        if(length == 4){
            code = new Random().nextInt(9999);//生成随机数,最大为9999
            if(code < 1000){
                code = code + 1000;//保证随机数为4位数字
            }
        }else if(length == 6){
            code = new Random().nextInt(999999);//生成随机数,最大为999999
            if(code < 100000){
                code = code + 100000;//保证随机数为6位数字
            }
        }else{
            throw new RuntimeException("只能生成4位或6位数字验证码");
        }
        return code;
    }

    /**
     * 随机生成指定长度字符串验证码
     * @param length 长度
     * @return
     */
    public static String generateValidateCode4String(int length){
        Random rdm = new Random();
        String hash1 = Integer.toHexString(rdm.nextInt());
        String capstr = hash1.substring(0, length);
        return capstr;
    }
}

12.controller控制业务

这里举个例子,需要自行分析完成

/**
 * 套餐分类查询
 */
@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
  @Autowired
  private SetmealService setmealService;
  @Autowired
  private SetmealDishService setmealDishService;
  @Autowired
  private CategoryService categoryService;

  /**
   * 新增套餐
   *
   * @param setmealDto
   * @return
   */
  @PostMapping
  public R<String> save(@RequestBody SetmealDTO setmealDto) {
    log.info("套餐信息:{}", setmealDto);
    setmealService.saveWithDish(setmealDto);
    return R.success("新增套餐成功");
  }

  /**
   * 套餐分页查询
   *
   * @param page
   * @param pageSize
   * @param name
   * @return
   */
  @GetMapping("/page")
  public R<Page> page(int page, int pageSize, String name) {
    //分页构造器对象
    Page<Setmeal> page1 = new Page<>(page, pageSize);
    Page<SetmealDTO> page2 = new Page<>();
    LambdaQueryWrapper<Setmeal> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //添加查询条件,根据name进行like模糊查询
    lambdaQueryWrapper.like(name != null, Setmeal::getName, name);
    //添加排序条件,根据更新时间进行降序排序
    lambdaQueryWrapper.orderByDesc(Setmeal::getUpdateTime);
    setmealService.page(page1, lambdaQueryWrapper);
    //对象拷贝
    BeanUtils.copyProperties(page1, page2, "records");
    List<Setmeal> records = page1.getRecords();
    List<SetmealDTO> list = records.stream().map((item) -> {
      SetmealDTO setmealDto = new SetmealDTO();
      //对象拷贝
      BeanUtils.copyProperties(item, setmealDto);
      //分类id
      Long categoryId = item.getCategoryId();
      //根据分类id查询分类对象
      Category category = categoryService.getById(categoryId);
      if (category != null) {
        //分类名称
        String categoryName = category.getName();
        setmealDto.setCategoryName(categoryName);
      }
      return setmealDto;
    }).collect(Collectors.toList());
    page2.setRecords(list);
    return R.success(page2);
  }

  /**
   * 停售菜品
   *
   * @param ids
   * @return
   */
  @PostMapping("/status/{status}")
  public R<String> change(@PathVariable int status, @RequestParam List<Long> ids) {
    log.info("停售套餐信息id:{}", ids);
    for (Long id : ids) {
      Setmeal service = setmealService.getById(id);
      service.setStatus(status);
      setmealService.updateById(service);
    }
    return R.success("套餐修改成功");
  }

  /**
   * 修改套餐
   * @param
   * @return
   */
  @PutMapping
  public R<String> change( @RequestBody Setmeal setmeal) {
    log.info("停售套餐信息id:{}", setmeal);
    setmealService.updateById(setmeal);
    return R.success("修改成功");
  }
  /**
   * 套餐删除
   * @param ids
   * @return
   */
  @DeleteMapping
  public R<String> delete(@RequestParam List<Long> ids) {
    log.info("ids:{}", ids);
    setmealService.removeWithDish(ids);
    return R.success("套餐数据删除成功");
  }

  /**
   * 根据条件查询套餐
   * @param setmeal
   * @return
   */
  @GetMapping("/list")
  public R<List<Setmeal>> list(Setmeal setmeal){
    LambdaQueryWrapper<Setmeal> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(setmeal.getCategoryId() != null, Setmeal::getCategoryId, setmeal.getCategoryId());
    lambdaQueryWrapper.eq(setmeal.getStatus() != null, Setmeal::getStatus, setmeal.getStatus());
    lambdaQueryWrapper.orderByDesc(Setmeal::getUpdateTime);
    List<Setmeal> list = setmealService.list(lambdaQueryWrapper);
    return R.success(list);
  }
}

13.filter过滤器

为了防止用户不登陆就可以操作数据

@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
  //路径匹配器,支持通配符
  public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
    log.info("拦截到请求:{}", httpServletRequest.getRequestURI());
    //不需要处理的页面
    String[] urls = new String[]{
            "/employee/login",
            "/employee/logout",
            "/backend/**",//静态页面,看了没事,拦截的是数据请求
            "/front/**",
            "/common/**",
            "/user/sendMsg",
            "/user/login"
    };
    boolean check = check(urls, httpServletRequest.getRequestURI());
    if (check) {
      log.info("本次请求{}不需要处理", httpServletRequest.getRequestURI());
      filterChain.doFilter(httpServletRequest, httpServletResponse);
      return;
    }
    //如果pc端用户已登录,则放行
    if (httpServletRequest.getSession().getAttribute("employee") != null) {
      log.info("用户已登录,用户id为:{}", httpServletRequest.getSession().getAttribute("employee"));
      log.info("线程id:{}", Thread.currentThread().getId());
      BaseContext.setCurrentId((Long) httpServletRequest.getSession().getAttribute("employee"));
      filterChain.doFilter(httpServletRequest, httpServletResponse);
      return;
    }
    //如果移动端端用户已登录,则放行
    if (httpServletRequest.getSession().getAttribute("user") != null) {
      log.info("用户已登录,用户id为:{}", httpServletRequest.getSession().getAttribute("user"));
      log.info("线程id:{}", Thread.currentThread().getId());
      BaseContext.setCurrentId((Long) httpServletRequest.getSession().getAttribute("user"));
      filterChain.doFilter(httpServletRequest, httpServletResponse);
      return;
    }
    log.info("用户未登录");
    //如果未登录则返回未登录结果,通过输出流的方式,向客户端页面响应数据
    httpServletResponse.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
    return;
  }

  /**
   * 路径匹配,检查本次请求是否需要放行
   *
   * @param urls
   * @param requestUrl
   * @return
   */
  public boolean check(String[] urls, String requestUrl) {
    for (String url : urls) {
      boolean match = PATH_MATCHER.match(url, requestUrl);
      if (match) {
        return true;
      }
    }
    return false;
  }
}

当你完成你的controller时,整个SpringBoot就完成了。

springboot 页面开发 springboot开发过程_spring


  1. common(业务包),config,controller,dto,entity,filter,mapper,service,utils ↩︎ ↩︎
  2. MyBatisPlus ↩︎