首页
归档
留言
友链
广告合作
壁纸
更多
美女主播
Search
1
博瑞GE车机升级/降级
5,608 阅读
2
Mac打印机设置黑白打印
4,937 阅读
3
修改elementUI中el-table树形结构图标
4,894 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,673 阅读
5
intelliJ Idea 2022.2.X破解
4,354 阅读
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Vue
Spring Cloud
Mac
MyBatis
WordPress
MacOS
asp.net
Element UI
Nacos
.Net
Spring Cloud Alibaba
MySQL
Mybatis-Plus
Typecho
jQuery
Java Script
IntelliJ IDEA
微信小程序
Laughing
累计撰写
627
篇文章
累计收到
1,421
条评论
首页
栏目
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
友链
广告合作
壁纸
美女主播
搜索到
627
篇与
的结果
2021-06-11
Spring Boot 使用mybatis-plus逻辑删除选装件
试想一下我们的系统,我们所有的表都有五个字段,分别是创建者(create_by)、创建时间(create_time)、修改者(update_by)、修改时间(update_time)、删除标志(del_flag), 同时通过mybatis拦截器,自动注入创建人、创建时间、修改人、修改时间。目前在数据库新增,能自动注入创建人、创建时间,修改时,能自动注入修改人、 修改时间。如果我们调用mybatis-plus提供的删除方法(deleteById),既然是逻辑删除,我们设想的肯定是此时能够自动注入修改人、修改时间,但是,实际测试你会发现,此时sql并没有注入修改人、修改时间。为了解决这个问题,mybatis-plus提供了一个LogicDeleteByIdWithFill的逻辑删除组装件。通过此方法,我们在进行逻辑删除时,便能自动注入修改人、修改时间。继承DefaultSqlInjector/** * Description:官方逻辑删除选装件 * * @author : laughing * DateTime: 2021-06-11 15:21 */ @Component public class MySqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); //2.官方选装件,逻辑删除时,自动填充其他字段(例如逻辑删除时,自动填充删除人是谁,什么时候删除的) methodList.add(new LogicDeleteByIdWithFill()); return methodList; } }配置逻辑删除插件/** * Mybatis-Plus配置 * * @author laughing */ @Configuration @EnableTransactionManagement public class MyBatisPlusConfig { @javax.annotation.Resource private Environment env; static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; private final Logger logger = LoggerFactory.getLogger(MyBatisPlusConfig.class); @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false // interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); interceptor.addInnerInterceptor(new TenantLineInnerInterceptor( new TenantLineHandler() { // manager_id = 1088248166370832385 // 获取租户 ID 值表达式,只支持单个 ID 值 @Override public Expression getTenantId() { return new StringValue(SecurityUtils.getOrgCode()); } // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件, // 这里设置 role表不需要该条件 @Override public boolean ignoreTable(String tableName) { logger.info("orgCode=======" + SecurityUtils.getOrgCode()); return StringUtils.isEmpty(SecurityUtils.getOrgCode()) || WisdomAgricultureConfig.getFilterTableList().stream().anyMatch(e -> e.equalsIgnoreCase(tableName)); } @Override public String getTenantIdColumn() { return "org_code"; } })); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } @Bean public ConfigurationCustomizer configurationCustomizer() { return configuration -> configuration.setUseDeprecatedExecutor(false); } public static String setTypeAliasesPackage(String typeAliasesPackage) { ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver(); MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); List<String> allResult = new ArrayList<String>(); try { for (String aliasesPackage : typeAliasesPackage.split(",")) { List<String> result = new ArrayList<String>(); aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN; Resource[] resources = resolver.getResources(aliasesPackage); if (resources != null && resources.length > 0) { MetadataReader metadataReader = null; for (Resource resource : resources) { if (resource.isReadable()) { metadataReader = metadataReaderFactory.getMetadataReader(resource); try { result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } if (result.size() > 0) { HashSet<String> hashResult = new HashSet<String>(result); allResult.addAll(hashResult); } } if (allResult.size() > 0) { typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); } else { throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); } } catch (IOException e) { e.printStackTrace(); } return typeAliasesPackage; } public Resource[] resolveMapperLocations(String[] mapperLocations) { ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); List<Resource> resources = new ArrayList<Resource>(); if (mapperLocations != null) { for (String mapperLocation : mapperLocations) { try { Resource[] mappers = resourceResolver.getResources(mapperLocation); resources.addAll(Arrays.asList(mappers)); } catch (IOException e) { // ignore } } } return resources.toArray(new Resource[resources.size()]); } @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { String typeAliasesPackage = env.getProperty("mybatis-plus.typeAliasesPackage"); String mapperLocations = env.getProperty("mybatis-plus.mapperLocations"); String configLocation = env.getProperty("mybatis-plus.configLocation"); typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); VFS.addImplClass(SpringBootVFS.class); final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); sessionFactory.setTypeAliasesPackage(typeAliasesPackage); sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ","))); sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); sessionFactory.setPlugins(mybatisSqlInterceptor(), mybatisPlusInterceptor()); sessionFactory.setGlobalConfig(globalConfig()); return sessionFactory.getObject(); } @Bean public MybatisSqlInterceptor mybatisSqlInterceptor() { MybatisSqlInterceptor mybatisSqlInterceptor = new MybatisSqlInterceptor(); Properties properties = new Properties(); mybatisSqlInterceptor.setProperties(properties); return mybatisSqlInterceptor; } /** * 逻辑删除插件 */ @Bean public GlobalConfig globalConfig() { GlobalConfig globalConfig = new GlobalConfig(); GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig(); dbConfig.setLogicDeleteValue("Y"); dbConfig.setLogicNotDeleteValue("N"); globalConfig.setDbConfig(dbConfig); globalConfig.setSqlInjector(sqlInjector()); return globalConfig; } /** * 自定义Sql注入器 */ @Bean public MySqlInjector sqlInjector() { return new MySqlInjector(); } }扩展BaseMapper/** * Description: * * @author : laughing * DateTime: 2021-06-11 15:23 */ public interface MyBaseMapper<T> extends BaseMapper<T> { /** * 逻辑删除,并且带自动填充 */ int deleteByIdWithFill(T entity); }修改Mapper接口修改Mapper接口,将原来继承BaseMapper改成我们自定义的MyBaseMapper/** * Description:红外测温 * * @author : laughing * DateTime: 2021-05-18 10:28 */ public interface CulturePigTemperatureMapper extends MyBaseMapper<CulturePigTemperature> { /** * 红外测温列表查询 * @param pig 查询条件 * @return 结果 */ List<CulturePigTemperature> selectCulturePigTemperatureList(CulturePig pig); }修改删除方法将原来的删除方法deleteById改成deleteByIdWithFill再次测试,可以看到修改人、修改时间字段能够在逻辑删除方法时自动注入。
2021年06月11日
1,729 阅读
0 评论
0 点赞
2021-06-11
Spring Boot解决fastjson序列化丢失小数点后零的问题
项目上使用数值类型(金额)时,比如价格,我们可能会将数值格式化成两位小数。比如12.34或者12.00。如果项目上使用FastJson进行序列化,你会发现如果我们金额是整数,比如12.00,FastJson序列化之后,返回到前端的是12而不是12.00。查阅FastJson文档,我们发现,在fastjson 1.2.16版本之后,JSONField支持新的定制化配置serializeUsing,可以单独对某一个类的某个属性定制序列化。实现ObjectSerializer接口第一步,我们实现ObjectSerializer,提供我们自己序列化的规则,如下/** * Description:FastJson金额序列化格式化成两位 * * @author : laughing * DateTime: 2021-06-11 11:49 */ public class Keep2Format implements ObjectSerializer { private DecimalFormat df = new DecimalFormat("0.00"); @Override public void write(JSONSerializer jsonSerializer, Object object, Object fieldName, Type type, int i) throws IOException { jsonSerializer.write(df.format(object)); } }@JSONField注解字段第二步,通过@JSONField设置我们自定义的序列化规则。@JSONField(serializeUsing = Keep2Format.class)再次查看数据,可以发现price字段已经格式化成两位小数。{message type="warning" content="注意:此功能需要fastjson 1.2.16及之后的版本"/}
2021年06月11日
1,900 阅读
0 评论
0 点赞
2021-06-09
Spring Boot Session共享
正常情况下,HttpSession是通过Servlet容器创建并进行管理的。创建成功之后都是保存在内存中的。如果开发者需要对项目进行横向扩展搭建集群,那么可以利用一些硬件或者软件工具来做负载均衡,此时,来自同一个用户的HTTP请求就有可能被分发到不同的实例上去,如何保证各个实例之间Session的同步就成为了一个必须要解决的问题。Spring Boot提供了自动化的Session共享配置,它结合Redis可以非常方便的解决这个问题。使用Redis解决Session共享问题的原理非常简单,就是把原本存储在不同服务器上的Session拿出来放到一个独立的服务器上。添加依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>配置redis修改配置文件,增加redis配置spring.redis.database=0 spring.redis.host=localhost spring.redis.port=6379创建controller测试@RestController public class RedidSessionController { @Value("${server.port}") public String port; @PostMapping("/save") public String saveName(String name, HttpSession session) { session.setAttribute("name", name); return "hello," + name + ":" + port; } @PostMapping("/get") public String getName(HttpSession session) { session.getAttribute("name"); return "hello," + session.getAttribute("name") + ":" + port; } }打包jar包通过mvn clean package打包成jar包。分别执行java -jar demo-0.0.1-SNAPSHOT.jar --server.port=8080 java -jar demo-0.0.1-SNAPSHOT.jar --server.port=8081通过8080、8081两个端口运行配置nginx负载均衡主要配置内容如下upstream test.com{ server localhost:8080 weight=1; server localhost:8081 weight=1; } server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://test.com; root html; index index.html index.htm; } } 配置文件中首先配置上游服务器,即两个real server,权重都是1,也就是说,请求平均分配到8080及8081端口。测试我们通过postman,调用save方法,保存session信息。然后再次通过postman,调用get方法,可以看到,系统正确的获取到8080端口保存的session信息。再次打开redis,查看session信息可以看到,session信息正确保存到了我们配置的redis数据库中。
2021年06月09日
1,051 阅读
0 评论
1 点赞
2021-06-09
Spring Boot @WebServlet、@WebFilter、@WebListener的使用
三个注解都必须在启动类增加@ServletComponentScan才能够被扫描到。@WebServlet可用于根据不同条件,重定向请求至不同URL。示例代码@WebServlet(name = "/my") @Slf4j public class MyWebServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { log.info("进入get方法"); log.info(req.getParameter("name")); doPost(req,resp); } @Override protected void doHead(HttpServletRequest req, HttpServletResponse resp) { log.info("进入get方法"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) { log.info("进入post方法"); log.info(req.getParameter("name")); } @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) { log.info("进入put方法"); } @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) { log.info("进入delete方法"); } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) { log.info("进入option方法"); } @Override public void destroy() { log.info("servlet>>>destroy"); } @Override public void init() { log.info("servlet>>>init"); } } 使用postman分别通过get、post、delete等进行请求http://localhost:8080/my?name=张三,可以查看输出结果@WebFilter可用于拦截/过滤请求,提前对请求的request进行判断处理,在doFilter后过滤器放行,调用实际请求URL路径。示例代码@WebFilter("/my") @Slf4j public class MyWebFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { log.info("filter>>>init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { log.info("进入doFilter方法"); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { log.info("filter>>>destroy"); } }再次请求,查看输出结果{message type="error" content="注意看输出顺序"/}@WebListener在spring启动之前监听执行初始化操作,可用于提前加载缓存等。@WebListener @Slf4j public class MyWebListener implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent sre) { log.info("requestDestroyed"); } @Override public void requestInitialized(ServletRequestEvent sre) { log.info("requestInitialized"); } }
2021年06月09日
903 阅读
0 评论
0 点赞
2021-06-08
Spring Boot通过ApplicationRunner实现系统启动任务
在Spring Boot通过CommandLineRunner实现系统启动任务中我们介绍了通过CommandLineRunner实现启动任务。但是CommandLineRunner实现的启动任务,我们在传递入口参数时,只能传递基本类型,入法通过键值对的形式传递参数。ApplicationRunner的实现原理跟CommandLineRunner基本类似,我们终点说一下ApplicationRunner的用法。ApplicationRunner实现类中run()方法参数为ApplicationArguments,ApplicationArguments说明如下:通过getNonOptionArgs()获取基本参数,这个与CommandLineRunner的参数是一致的。通过getOptionNames()获取键。通过getOptionValues获取值。演示代码@Component @Order(1) @Slf4j public class MyApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) { List<String> nonOptionArgs = args.getNonOptionArgs(); Set<String> optionNames = args.getOptionNames(); List<String> optionValues = args.getOptionValues("name"); log.info("MyApplicationRunner>>>nonOptionArgs>>>" + nonOptionArgs.toString()); log.info("MyApplicationRunner>>>optionNames>>>" + optionNames.toString()); log.info("MyApplicationRunner>>>optionValues>>>" + optionValues.toString()); } }修改入口参数--name代表键,=后面是值,注意是--不是-
2021年06月08日
1,055 阅读
0 评论
0 点赞
2021-06-08
Spring Boot通过CommandLineRunner实现系统启动任务
所谓系统启动任务,是在系统启动时执行的一些操作,一般而言只在系统启动之时执行,并且一般只执行一次。比如加载配置文件、数据库初始化操作等。Spring Boot针对系统启动任务,提供了两种解决方案:一是实现CommandLineRunner接口另一种方式是实现ApplicationRunner接口。本文针对CommandLineRunner方式进行说明。Spring Boot项目在启动时,会遍历所有CommandLineRunner实现类并调用其中的run()方法。如果系统中有多个CommandLineRunner实现类,可以通过@Order()注解指定实现类的调用顺序,数值越小越先执行。{mtitle title="演示代码"/}MyCommandLineRunner1@Component @Order(1) @Slf4j public class MyCommandLineRunner1 implements CommandLineRunner { @Override public void run(String... args) throws Exception { log.info("Runner1>>>" + Arrays.toString(args)); } }MyCommandLineRunner2@Component @Order(1) @Slf4j public class MyCommandLineRunner2 implements CommandLineRunner { @Override public void run(String... args) { log.info("Runner2>>>" + Arrays.toString(args)); } }idea配置入口参数@Order(1)用来描述CommandLineRunner的执行顺序,值越小越先执行。run()方法的参数是系统启动时传入的参数。
2021年06月08日
1,041 阅读
0 评论
0 点赞
2021-06-08
vue-baidu-map通过路书实现轨迹回放
网上实现轨迹回放的代码很多,但是很少有vue实现的。本文通过vue-baidu-map对vue轨迹回放功能进行说明。安装依赖npm install vue-baidu-map --save修改main.jsmain.js增加以下内容,注意ak需要替换成自己的。import BaiduMap from 'vue-baidu-map' Vue.use(BaiduMap, { ak: '替换成自己的ak' })前端页面<template> <div class="app-container"> <el-form :model="queryParams" ref="queryForm" :inline="true"> <el-row> <el-col :span="6"> <el-form-item label="车辆" prop="vehicleLicence"> <el-select filterable v-model="queryParams.vehicleLicence" placeholder="请选择车辆" clearable size="small" style="width: 100%" > <el-option v-for="vehicle in vehicleOptions" :key="vehicle.licence" :label="vehicle.licence" :value="vehicle.licence" /> </el-select> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="查询时间" prop="dateRange"> <el-date-picker v-model="dateRange" size="small" style="width: 100%" value-format="yyyy-MM-dd HH:mm:ss" type="datetimerange" range-separator="-" start-placeholder="请选择开始时间" end-placeholder="请选择结束时间" :picker-options="pickerOptions" ></el-date-picker> </el-form-item> </el-col> <el-col :span="5"> <el-form-item label="轨迹速度"> <el-slider v-model="speed" style="width: 200px" :step="1"></el-slider> </el-form-item> </el-col> <el-col :span="5"> <el-form-item style="float: right"> <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> </el-form-item> </el-col> </el-row> </el-form> <baidu-map style="height:47.1rem;width:100%" :center="center" :zoom="15" :scroll-wheel-zoom="true"> <bm-polyline :path="path" stroke-color="blue" :stroke-opacity="0.5" :stroke-weight="3" :editing="false" ></bm-polyline> <bm-marker :position="{lng: startMark.lng, lat: startMark.lat}"></bm-marker> <bm-marker :position="{lng: endMark.lng, lat: endMark.lat}"></bm-marker> <bml-lushu @stop="reset" :path="path" :icon="icon" :play="play" :rotation="true" :speed="speed * 10" :infoWindow="true" :content="content" > </bml-lushu> </baidu-map> </div> </template> <script> //百度地图 import BaiduMap from 'vue-baidu-map/components/map/Map.vue' import BmScale from 'vue-baidu-map/components/controls/Scale' import BmNavigation from 'vue-baidu-map/components/controls/Navigation' import BmMarkerClusterer from 'vue-baidu-map/components/extra/MarkerClusterer' import BmMarker from 'vue-baidu-map/components/overlays/Marker' import BmInfoWindow from 'vue-baidu-map/components/overlays/InfoWindow' import { BmlLushu } from 'vue-baidu-map' import drugMarkerIcon from '@/assets/icons/map_marker_check.png' import { selectGpsOrbitList } from '@/api/dmp/industry/gps/orbit' import { listAllVehicle } from '@/api/basic/vehicle' export default { components: { BaiduMap, BmScale, BmNavigation, BmMarkerClusterer, BmMarker, BmInfoWindow, BmlLushu }, props: {}, data() { return { // 查询参数 queryParams: { vehicleLicence: undefined }, // 日期范围 dateRange: [new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate() + ' 00:00:00', new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate() + ' 23:59:59'], play: false, path: [], center: { lng: 116.984646, lat: 36.659686 }, startMark: {}, endMark: {}, icon: { url: drugMarkerIcon, size: { width: 32, height: 32 }, opts: { anchor: { width: 27, height: 13 } } }, content: undefined, speed: 20, pickerOptions: { disabledDate(time) { return time.getTime() > Date.now() - 8.64e6 } }, vehicleOptions: [] } }, methods: { reset() { this.play = false }, handleSearchComplete(res) { this.path = res.getPlan(0).getRoute(0).getPath() }, handleQuery() { const search = this.addDateRange(this.queryParams, this.dateRange) if (this.queryParams.vehicleLicence === undefined) { this.msgError('请选择车辆') return } if (search.beginTime === undefined || search.beginTime === '' || search.endTime === undefined || search.endTime === '') { this.msgError('请选择查询时间') return } selectGpsOrbitList(this.addDateRange(this.queryParams, this.dateRange)).then(response => { if (response.data.length <= 0) { this.msgError('未查询到该车辆的运行数据') return } let length = response.data.length let middle = -1 if (length % 2 === 0) { middle = length / 2 + 1 } else { middle = (length + 1) / 2 } response.data.forEach(item => { let obj = { lng: item.longitude, lat: item.latitude } this.path.push(obj) }) this.center = this.path[middle] this.startMark = this.path[0] this.endMark = this.path[this.path.length - 1] this.content = this.queryParams.vehicleLicence this.play = true }).catch(error => { }) }, /** 重置按钮操作 */ resetQuery() { this.resetForm('queryForm') this.dateRange = [new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate() + ' 00:00:00', new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate() + ' 23:59:59'] } }, mounted() { listAllVehicle().then(response => { this.vehicleOptions = response.data }) } } </script> <style> </style> data关键参数说明:play:true或false,为true时,开始播放轨迹。path:数组,格式为[{lng: 116.984646,lat: 36.659686}]center:地图中心点,格式{lng: 116.984646,lat: 36.659686}startMark:轨迹开始经纬度,格式{lng: 116.984646,lat: 36.659686}endMark:轨迹终点经纬度,格式{lng: 116.984646,lat: 36.659686}icon:轨迹图标,如下演示的小车图标content:轨迹显示内容,如下按时的车牌speed:轨迹绘制的速度,这里通过el-slider实现滑动调整速度bm-polyline组件属性说明path:绘制曲线的经纬度数组bml-lushuplay:true或false,为true时,开始播放轨迹。infoWindow:true或false,为true时,显示窗口,及content内容。
2021年06月08日
3,086 阅读
4 评论
2 点赞
2021-06-07
Spring Boot注册拦截器Interceptor
Spring MVC提供了AOP风格的拦截器,拥有更加精细的拦截器处理能力。Spring Boot中拦截器的注册更加方便,步骤如下:spring-boot-starter-web创建拦截器,实现HandlerInterceptor配置拦截器,定义配置类进行拦截器的配置增加依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>实现HandlerInterceptor代码如下:public class MyInterceptor implements HandlerInterceptor { private final Logger logger = LoggerFactory.getLogger(MyInterceptor.class); /** * 方法执行前运行 * 必须返回true,后面的方法才能执行 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("我先执行"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { logger.info("我在controller方法之后执行"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { logger.info("我最后执行"); } }配置拦截器@Configuration public class MyWebMvcConfig implements WebMvcConfigurer { /** * 拦截器 * * @param registry registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()) //配置拦截的路由 .addPathPatterns("/interceptor/**") //配置不拦截的路由 .excludePathPatterns("/demo/**") //配置顺序 .order(1); } }测试定义controller测试我们的拦截器@RestController @RequestMapping("interceptor") public class InterceptorController { private final Logger logger = LoggerFactory.getLogger(InterceptorController.class); @GetMapping("/test") public String testInterceptor(){ logger.info("我是controller方法"); return "success"; } }通过调用http://localhost:8080/interceptor/test查看输出日志。温馨提示拦截器按照preHandle→Controller→postHandle→afterHandle的顺序执行。只有preHandle方法返回true时,后面的方法才会执行。当拦截器链内存在多个拦截器时,postHandle在拦截器链内所有拦截器返回成功时才会调用。当拦截器链内存在多个拦截器时,afterHandle在拦截器链内所有拦截器返回true时才会调用。当拦截器链内存在多个拦截器时,如果第一个拦截器的preHandle方法返回false,则后面的方法都不会执行。调用controller方法时,只要配置了拦截的路由,哪怕前端请求404,仍然会调用preHandle、postHandle及afterHandle的方法。如果我们设置了order,代码如下:/** * 拦截器 * * @param registry registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor2()) //配置拦截的路由 .addPathPatterns("/interceptor/**") //配置不拦截的路由 .excludePathPatterns("/demo/**") //配置顺序 .order(200); registry.addInterceptor(new MyInterceptor1()) //配置拦截的路由 .addPathPatterns("/interceptor/**") //配置不拦截的路由 .excludePathPatterns("/demo/**") //配置顺序 .order(300); }
2021年06月07日
1,175 阅读
1 评论
1 点赞
2021-06-07
springboot 通过 DefaultErrorAttributes自定义错误信息
自定义error数据就是对返回的数据进行自定义。Spring Boot返回的Error信息一共有5条,分别是timestamp、status、error、path。在BasicErrorController的errorHtml()方法和error()方法,都是通过getErrorAttributes()方法获取Error信息的,该方法最终会调用DefaultErrorAttributes类的getErrorAttributes()方法,而DefaultErrorAttributes类是在ErrorMvcAutoConfiguration中默认提供的。当系统没有提供 errorAttributes 时才会采 DefaultErrorAttributes,因此自定义错误提示时,只需要自己提供一个ErrorAttributes即可,而DefaultErrorAttributes是ErrorAttributes的子类,因此只需要继承 DefaultErrorAttributes 即可。@Component public class MyErrorAttribute extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { Map<String, Object> map = super.getErrorAttributes(webRequest, options); map.put("errorMsg", "出错了"); return map; } }
2021年06月07日
2,093 阅读
0 评论
1 点赞
2021-06-06
SpringBoot 使用Gson
添加依赖需要排除web自带的jackson<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency>增加配置文件@Configuration public class GsonConfig { @Bean @ConditionalOnMissingBean public GsonHttpMessageConverter gsonHttpMessageConverter() { GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter(); gsonHttpMessageConverter.setDefaultCharset(StandardCharsets.UTF_8); GsonBuilder gsonBuilder = new GsonBuilder(); //设置日期格式 gsonBuilder.setDateFormat("yyyy-MM-dd"); //设置忽略的字段 gsonBuilder.excludeFieldsWithModifiers(Modifier.PROTECTED); Gson gson = gsonBuilder.create(); gsonHttpMessageConverter.setGson(gson); return gsonHttpMessageConverter; } }增加测试类@Data public class Person { protected Integer age; private String name; private Date birthday; }测试 @GetMapping("test") public Person test() { Person person = new Person(); person.setAge(30); person.setBirthday(new Date()); person.setName("张三"); return person; }
2021年06月06日
1,146 阅读
0 评论
0 点赞
2021-06-06
Spring Boot使用profile
开发者在项目发布之前,一般需要频繁的在开发环境、测试环境及生产环境之间进行切换,这个时候大量的配置需要频繁更改,例如数据库配置、 redis配置、mongodb 配置等。频繁修改带来巨大工作量,Spring对此提供了解决方案(@Profile注解), Spring Boot则更进一步提供了更加简洁的解决方案, Spring Boot中约定的不同环境下配置文件名称规则为application-{profile} .properties, profile占位符表示当前环境的名称,具体配置步骤如下。创建配置文件在resources中创建两个配置文件,分别是application-dev.properties和application-prod.properties,分别代表开发环境及正式环境的配置。application-dev.propertiesdev指定端口为8080server.port=8080 server.servlet.context-path=/study server.tomcat.basedir=logapplication-prod.propertiesprod指定端口为8081server.port=8081 server.servlet.context-path=/study server.tomcat.basedir=log在application.properties中配置spring.profiles.active=prod在代码中配置在启动类中设置 public static void main(String[] args) { // SpringApplication.run(DemoApplication.class, args); SpringApplicationBuilder builder = new SpringApplicationBuilder(DemoApplication.class); builder.bannerMode(Banner.Mode.OFF); builder.application().setAdditionalProfiles("prod"); builder.run(args); }在项目启动时配置java -jar demo-0.0.1-SNAPSHOT.jar --spring.profile.active=prod
2021年06月06日
971 阅读
0 评论
0 点赞
2021-06-06
java多线程之ThreadLocal类
ThreadLocal是⼀个本地线程副本变量⼯具类。内部是⼀个弱引⽤的Map来维护。这⾥不详细介绍它的原理,⽽是只是介绍它的使⽤,以后有独⽴章节来介绍ThreadLocal类的原理。有些朋友称ThreadLocal为线程本地变量或线程本地存储。严格来说,ThreadLocal类并不属于多线程间的通信,⽽是让每个线程有⾃⼰”独⽴“的变量,线程之间互不影响。它为每个线程都创建⼀个副本,每个线程可以访问⾃⼰内部的副本变量。ThreadLocal类最常⽤的就是set⽅法和get⽅法。package com.company; public class ThreadLocalDemo { static class ThreadA implements Runnable{ private final ThreadLocal<String> threadLocal; public ThreadA(ThreadLocal<String> threadLocal){ this.threadLocal=threadLocal; } @Override public void run() { threadLocal.set("A"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("ThreadA输出:" + threadLocal.get()); } } static class ThreadB implements Runnable{ private final ThreadLocal<String> threadLocal; public ThreadB(ThreadLocal<String> threadLocal){ this.threadLocal=threadLocal; } @Override public void run() { threadLocal.set("B"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("ThreadB输出:" + threadLocal.get()); } } public static void main(String[] args){ ThreadLocal<String> threadLocal = new ThreadLocal<>(); new Thread(new ThreadA(threadLocal)).start(); new Thread(new ThreadB(threadLocal)).start(); } }
2021年06月06日
826 阅读
0 评论
0 点赞
1
...
24
25
26
...
53