本文章记录我在写SpringBoot为后端,Vue为前端的项目时,使用到的技巧,碰到的问题,本文持续更新
前端 项目运行时浏览器自动打开在package.json文件中的scripts配置项:
"scripts": {
"serve": "vue-cli-service serve --open", // 在启动项后面添加--open
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
关闭eslint校验工具(不建议关闭,建议修改规则)
在Vue.config.js配置文件中
module.exports = {
// 关闭eslint
lintOnSave:false
}
Vue3使用路由
首先添加路由的依赖
yarn add vue-router
与Vue2的自动新建不同,vue-router 4.x似乎不会自动新建router.js文件,所以需要手动新建
import { createRouter, createWebHistory } from "vue-router";
import Main from "../view/Main.vue";
const routes = [
{
path: "/",
name: "Main",
component: Main
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
新版的Vue-router不再是通过new Router新建路由类,而是同过createRouter函数
其他的改动见官方文档:https://router.vuejs.org/zh/guide/migration/index.html
新建了路由文件后,在入口文件main.js中,使用use的方法挂载
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/router";
const app = createApp(App);
app.use(router);
app.mount("#app");
控制路由跳转
由于在Vue3的setup中无法像Vue2那样通过this访问$router,Vue-Router官方提供了代替的useRouter函数
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
function pushWithQuery(query) {
router.push({
name: 'search',
query: {
...route.query,
},
})
}
},
}
使用vite创建的项目读取.env配置文件配置项注:在模板中依然可以访问到$router和$route,所以在setup中无需抛出router和route
之前的process方式已失效,使用全新的import.meta.env读取配置,且配置前缀必须为VITE
例:
.env文件:
VITE_SERVICE_URL = 'http://127.0.0.1:8000'
读取:
console.log(import.meta.env.VITE_SERVICE_URL)
其他详细配置:https://vitejs.cn/guide/env-and-mode.html
后端 修改时间格式为标准格式将:2019-10-30T06:18:46.000+00:00修改为标准格式yyyy-MM-dd HH:mm:ss的时间
SpringBoot中在application配置文件中设置
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
逻辑删除
1.首先配置好MybatisPlus的逻辑删除插件,插件配置在编写好的MyBatisPlus的配置类中即可
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
2.为实体类中需要进行逻辑删除的字段添加上@TableLogic注解
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除") @TableLogic private Boolean isDeleted;新版本变化
以Mybatis-Plus 3.4.2为例
不需要再配置逻辑删除插件,在application配置文件中配置以下配置即可
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
在实体类上添加@TableLogic注解的步骤不变
独立的工具模块如果编写的配置类与业务模块不处于同一个模块中,例如以下结构:
如果要在service模块中读取到common的配置类或者工具类,首先第一步,在service模块的pom.xml中添加上common某配置类的节点坐标
com.xxx service_base 0.0.1-SNAPSHOT
然后再在service的子模块的启动类上,添加上@ComponentScan注解启动扫描
@SpringBootApplication
@ComponentScan(basePackages = {"com.guli"})
public class EduApplication {
public static void main(String[] args) {
SpringApplication.run(EduApplication.class,args);
}
}
统一返回数据格式
假设要求的返回的数据格式如下
{
"success":布尔 // 响应的结果
"code":数字 // 响应码
"message":字符串 // 返回消息
"data":HashMap // 返回数据
}
1.创建接口,定义数据返回状态码
public interface ResultCode {
public static Integer SUCCESS = 2000; // 成功状态码
public static Integer ERROR = 2001; // 失败状态码
}
2.定义统一返回结果类
首先按设定定义好返回的数据格式的所有属性字段
private Boolean success; private Integer code; private String message; private HashMapdata = new HashMap ();
然后再把构造方法私有化使该类无法new对象
private Result(){
}
之后再分别定义好成功和失败返回方法
public static Result OK(){
Result result = new Result();
result.setSuccess(true);
result.setCode(ResultCode.SUCCESS);
result.setMessage("访问成功");
return result;
}
public static Result Error(){
Result result = new Result();
result.setSuccess(false);
result.setCode(ResultCode.ERROR);
result.setMessage("访问失败");
return result;
}
最后使用上链式编程
public Result success(Boolean success){
this.setSuccess(success);
return this;
}
public Result message(String message){
this.setMessage(message);
return this;
}
public Result code(Integer code){
this.setCode(code);
return this;
}
public Result data(String key, Object value){
this.data.put(key, value);
return this;
}
public Result data(Map map){
this.setData(map);
return this;
}
链式编程可以在类调用一个方法后继续使用.再进行调用下一个方法
Mybatis-plus分页1.以Mybatis-Plus-3.4.2版本为例,首先在Mybatis-Plus的配置类中添加分页插件
@Configuration
@MapperScan("com.xxx.xxx.mapper") // 指向Mapper层的类路径
public class EduConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false); // 该属性已被启用,但3.4.2中不会报错
}
}
2、编写分页的Controller方法
@GetMapping("/pageList/{current}/{size}")
public Result pageTeacher(@PathVariable Integer current,@PathVariable Integer size){
// 创建Page对象
Page eduTeacherPage = new Page<>(current,size);
// 调用Service层的分页方法
teacherService.page(eduTeacherPage);
return Result.OK().data("pageList",eduTeacherPage);
}
条件分页查询current参数为页码,size为查询条数
service层的分页方法page有两个重载:
1.参数为Page对象的重载2.参数为Page对象和Wrapper对象的重载
第二种重载带了Mybatis-Plus的查询对象即可实现条件分页查询
使用QueryWrapper构建条件对象
@PostMapping("/pageList/search/{current}/{size}")
public Result pageTeacherListSearch(
@PathVariable Integer current, @PathVariable Integer size,
@RequestBody(required = false) TeacherQuery teacherQuery // TeacherQuery为条件实体类,包含名称,等级等数据属性,新建时需要添加get和set方法
) {
System.out.println(teacherQuery);
// 创建Page对象
Page eduTeacherPage = new Page<>(current,size);
// 构建条件
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.like("name",teacherQuery.getName());
wrapper.eq("level",teacherQuery.getLevel());
wrapper.ge("gmt_create",teacherQuery.getBegin());
wrapper.le("gmt_create",teacherQuery.getEnd());
// 调用Service层的分页方法
teacherService.page(eduTeacherPage,wrapper);
return Result.OK().data("getList",eduTeacherPage);
}
Mybatis-Plus实体类自动装填
@TableField注解标注一个属性为数据库字段,fill属性可以设置自动装填
@TableField(fill = FieldFill.INSERT) private Date gmtCreate; @TableField(fill = FieldFill.INSERT_UPDATE) private Date gmtModified;
以上面两个属性为例,FieldFill.INSERT表示新建时的装填,FieldFill.INSERT_UPDATE表示装填时装填
然后再新建一个配置类
@Component
public class MyMetObjectHandler implements metaObjectHandler {
@Override
public void insertFill(metaObject metaObject) {
this.setFieldValByName("gmtCreate", new Date(), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
@Override
public void updateFill(metaObject metaObject) {
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
}
统一异常处理这里设置的是自动装填的内容,这里的gmtCreate和gmtModified不是数据库中的字段名称,而是实体类中的属性名称
且注意,类路径要相同,否则注入无效
为了处理项目运行过程中出现的某些异常,使其返回更人性化的提示给用户,这里需要用到统一异常处理
全局异常注:以下演示代码中的Result类为自定义统一返回数据格式类
新建一个类
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody // 使该方法能够返回数据
public Result error(Exception e){
e.printStackTrace();
return Result.Error().message(e.getMessage());
}
}
@ControllerAdvice学名为Controller增强器,作用是给Controller控制器添加统一的操作或者处理
@ExceptionHandler注解可以设置出现哪些异常执行被注解的方法,这里Exception.class表示所有异常都执行
以上就定义了一个全局的包含了所有的异常的处理方法
特殊异常按照@ExceptionHandler的设定,可以指定异常并设置不同的处理方法
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Result error(ArithmeticException e){
e.printStackTrace();
return Result.Error().message("计算出错了");
}
该示例指定了一个算术异常
若有多个异常方法,例如分别定义了一个Exception异常的处理和一个ArithmeticException异常的处理,优先调用ArithmeticException的异常处理
自定义异常处理1.创建一个自定义异常类,该类需要继承RuntimeException
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GuLiException extends RuntimeException{
private Integer code; // 状态码
private String message; // 异常信息
}
2.在统一异常处理中添加使用该异常的方法
@ExceptionHandler(GuLiException.class)
@ResponseBody
public Result error(GuLiException e){
e.printStackTrace();
return Result.Error().code(e.getCode()).message(e.getMessage());
}
3.使自定义异常执行
由于是自定义异常,该异常不会被自动捕获,需要我们手动抛出
try {
int i = 10 / 0;
}catch (Exception e){
throw new GuLiException(-1,"自定义异常处理");
}
统一日志处理
在运行项目时,在控制台上打印出来信息就是日志,它可以帮助我们查看服务器的信息,运行状态,报错信息等等
日志记录器(Logger的行为是分等级的):
OFFFATALERRORWARNINFODEBUGALL 可能已失效
每种基本都会显示不同的信息,默认为INFO,如果需要更换日志级别,则需要在配置文件中添加相应的配置
logging:
level:
root: INFO
Logback日志工具
SpringBoot内部使用Logback作为日志实现的框架
logback相对于log4j的一些优点:https://blog.csdn.net/caisini_vc/article/details/48551287
配置Logback日志工具首先,将配置文件中的所有的日志的配置去掉
然后在resources文件夹中创建logback-spring.xml(固定写法)
注:复制勿忘该输出文件夹名称,该属性在第10行
将错误日志输出到文件logback INFO ${CONSOLE_LOG_PATTERN} UTF-8 ${log.path}/log_info.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log 100MB 15 INFO ACCEPT DENY ${log.path}/log_warn.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log 100MB 15 warn ACCEPT DENY ${log.path}/log_error.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log 100MB 15 ERROR ACCEPT DENY
该功能需要有统一异常处理
在统一异常处理类上添加上@Slf4j注解,同时在异常处理方法中添加上日志打印语句
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
log.error(e.getMessage()); // 将异常信息打印出来
return Result.Error().message("服务器出错啦");
}
}
这种方式打印出来的异常信息非常简单,如果想要打印更加详细的异常日志,可以自定义一个工具类获取详细的异常信息
public class ExceptionUtil {
public static String getMessage(Exception e) {
StringWriter sw = null;
PrintWriter pw = null;
try {
sw = new StringWriter();
pw = new PrintWriter(sw);
// 将出错的栈信息输出到printWriter中
e.printStackTrace(pw);
pw.flush();
sw.flush();
} finally {
if (sw != null) {
try {
sw.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (pw != null) {
pw.close();
}
}
return sw.toString();
}
}
在统一异常处理中调用
@ExceptionHandler(Exception.class) // 指定出现哪些异常执行该方法
@ResponseBody // 使该方法能够返回数据
public Result error(Exception e){
e.printStackTrace();
log.error(ExceptionUtil.getMessage(e)); // 将异常打印出来
return Result.Error().message("服务器出错啦");
}
开启跨域
配置跨域配置类
@Configuration
public class GlobalWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//允许所有域名进行跨域调用
config.addAllowedOriginPattern("*");
//允许跨越发送cookie
config.setAllowCredentials(true);
//放行全部原始头信息
config.addAllowedHeader("*");
//允许所有请求方法跨域调用
config.addAllowedMethod("*");
UrlbasedCorsConfigurationSource source = new UrlbasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}



