1.创建SpringBoot项目必须选择Java8为版本
2.选择需要的依赖项
- 使用 Maven 创建
- 在maven中pom.xml配置SpringBoot
- 构建包结构1,application.yml文件 在项目结构中,spring设置中点击右侧spring小图标添加当前yml
- 创建启动类,来启动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 创建
- 选择SpringWeb依赖,和其他相关依赖
- 删掉不相关文件和文件夹如 .mvn和HELP.md
- 将application.properties改为application.yml
- 创建包结构 1
3.添加其他依赖项
- mybatis-plus-boot-starter
- lombok
- fastjson
- commons-lang
- mysql-connector-java
- 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就完成了。
- common(业务包),config,controller,dto,entity,filter,mapper,service,utils ↩︎ ↩︎
- MyBatisPlus ↩︎