小于博客 小于博客
首页
  • Java学习笔记
  • Docker专区
  • 实战教程
  • Shell
  • 内存数据库
  • Vue学习笔记
  • Nginx
  • Php
  • CentOS
  • Docker
  • Gitlab
  • GitHub
  • MySql
  • MongoDB
  • OpenVPN
  • 配置文件详解
  • Other
  • ELK
  • K8S
  • Nexus
  • Jenkins
  • 随写编年
  • 电影音乐
  • 效率工具
  • 博客相关
  • 最佳实践
  • 迎刃而解
  • 学习周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • 网站状态 (opens new window)
    • json2go (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)

小于博客

行者常至,为者常成
首页
  • Java学习笔记
  • Docker专区
  • 实战教程
  • Shell
  • 内存数据库
  • Vue学习笔记
  • Nginx
  • Php
  • CentOS
  • Docker
  • Gitlab
  • GitHub
  • MySql
  • MongoDB
  • OpenVPN
  • 配置文件详解
  • Other
  • ELK
  • K8S
  • Nexus
  • Jenkins
  • 随写编年
  • 电影音乐
  • 效率工具
  • 博客相关
  • 最佳实践
  • 迎刃而解
  • 学习周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • 网站状态 (opens new window)
    • json2go (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)
  • Java学习笔记

    • RequestBody和RequestParam区别全面详细
    • BigDecimal用法
    • Java笔试易错点记录
    • jsencrypt.js前端参数RSA加密
    • SpringBoot调用http请求的6种方式
    • vue实现点击不同按钮展示不同内容
    • 精髓代码随手笔记
    • 经典代码汇总
    • 项目实战问题笔记
      • 1.项目实体类属性不存入数据库排除方式
      • 2.阿里EasyExcel大数据导入用法
      • 3.两个实体类互相copy
      • 4.实体Entity转Map
      • 5.常量的定义
      • 6.SpringBoot使用RequestBodyAdvice进行统一参数处理
      • 7.vue中将后台返回的数字转换成对应的文字
        • 7.1、对于列表循环el-table-column采用如下方式:
        • 7.2、对于详情或者修改页面采用如下方式:
      • 8.实体类属性copy
      • 9.el-table-column宽度自适应
      • 10.el-table表格中单元格内容过多显示省略号
      • 11.mybatis-plus的LambdaQueryWrapper自定义sql用法
      • 12.mybatis-plus的@Select注解用法
      • 13.vue调用子组件作为弹窗时只执行一次created问题
      • 14.Java字符串前后补零的几种方法
      • 15.springboot集成https
      • 16.项目防刷控制(自定义注解)
        • 创建一个自定义注解
        • 2.创建一个拦截器 (用于拦截请求,更新当前用户访问的次数,如果访问受限,则返回超时的状态码)
        • 3.注册拦截器
        • 4.OK , 下面我们就可以在需要进行现在访问次数的controller中的方法使用该注解了
      • 17.string转json
      • 18.SpringBoot项目业务操作日志记录
        • 1、依赖配置
        • 2、表结构设计
        • 3、定义业务日志注解@BusLog,可以作用在控制器或其他业务类上,用于描述当前类的功能;也可以用于方法上,用于描述当前方法的作用
        • 4、把业务操作日志注解BusLog标记在PersonController类和方法上
        • 5、编写切面类BusLogAop,并使用@BusLog定义切入点,在环绕通知内执行过目标方法后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述, 把方法的参数报文写入到文件中,最后保存业务操作日志信息
      • 19.jackson返回前端的字符串中引号被自动加上反斜杆
      • 结尾。
    • CentOS7下安装mysql5.7
    • SpringBoot 快速实现 api 加密!
  • Docker专区

  • Shell编程

  • 实战教程

  • 内存数据库

  • Vue学习笔记

  • 编程世界
  • Java学习笔记
小于博客
2024-01-18
目录

项目实战问题笔记

项目实战问题笔记

场景:基于Spring Boot使用Java调用http请求的6种方式。服务端发布一个POST请求和2个GET请求。使用6种方式实现的客户端都调用服务端发布的这3个方法。可以直观感受和比对6种http请求的客户端。

# 1.项目实体类属性不存入数据库排除方式

  1. @Transient 该注解只适用于hibernate框架,在实体类(pojo)属性上使用、表示数据库表中没有这个字段就忽略;
  2. @TableField 该注解只适用于mybatis-plus框架: @TableField(exist = false):表示该属性不为数据库表字段,但又是必须使用的。 @TableField(exist = true):表示该属性为数据库表字段。

# 2.阿里EasyExcel大数据导入用法

pom文件依赖

<!--阿里巴巴EasyExcel依赖-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.5</version>
</dependency>
1
2
3
4
5
6

导入(读)数据–第一步创建一个监听器ExcelDataListener.java继承AnalysisEventListener类

// 1.编写一个监听器 ExcelDataListener.java继承AnalysisEventListener类
//    重写invoke方法
package com.xiaogui.log.utils;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.Cell;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.fastjson.JSON;
import com.xiaogui.log.mapper.LogMapper;
import com.xiaogui.log.vo.resp.RespLogInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.*;

@Component
@Slf4j
public class ExcelDataListener extends AnalysisEventListener<RespLogInfo> {
/**
* 每隔10条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 10;
List<与Excel表头对应的Entity> list = new ArrayList<>();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。
* 当然如果不用存储这个对象没用。
*/
private final RespLogInfoService respLogInfoService;

    /**
     * 如果使用了spring,请使用这个构造方法。
     * 每次创建Listener的时候需要把spring管理的类传进来
     */
    public ExcelDataListener(RespLogInfoService respLogInfoService) {
        this.respLogInfoService = respLogInfoService;
    }

    /**
     * 这个每一条数据解析都会来调用
     */
    @Override
    public void invoke(与Excel表头对应的Entity data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            respLogInfoService.saveOrUpdateBatch(list);
            // 存储完成清理 list
            list.clear();
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        respLogInfoService.saveOrUpdateBatch(list);
        log.info("所有数据解析完成!");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

实体类

public class RespLogInfo implements Serializable {
    @ExcelProperty(index = 0)//对应excel第几列
    private String type;//开支类型 信用卡等
1
2
3

用法

package easyExcel;

import com.alibaba.excel.EasyExcel;
import com.easyexcel.listener.EasyExcelOrderListener;
import com.easyexcel.pojo.ExcelOrder;
import org.junit.Test;
/***
* easyExcel测试类
  ***/
  public class ExcelReadTest {
  @Test
  public void excelRead(){
  //String fileName = "文件路径/订单表.xlsx";//文件路径
  //默认读取第一个sheet
  //EasyExcel.read(fileName, ExcelOrder.class,new EasyExcelOrderListener()).sheet().doRead();
  //或者如下方法
  EasyExcel.read(file.getInputStream, RespLogInfo.class,new FileListener(this)).sheet().doRead();
  }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3.两个实体类互相copy

BeanUtils.copyProperties(被copyEntity,targetEntity);
1

# 4.实体Entity转Map

Map<String,String> oMapper = new ObjectMapper().convertValue(clerk, HashMap.class);
1

# 5.常量的定义

public static final String MONDAY = "test";
  public static final Map map = new HashMap();
  static {
  map.put("key1", "value1");
  map.put("key2", "value2");
  }
  //或者
  public static final Map<String, String> map = new HashMap<>() {
  {
  put("key1", "value1");
  put("key2", "value2");
  }
  };
  //List常量定义方式
  public static final List<String> list = Arrays.asList("88","99","100");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 6.SpringBoot使用RequestBodyAdvice进行统一参数处理

请求处理====在实际项目中 , 往往需要对请求参数做一些统一的操作 , 例如参数的过滤 , 字符的编码 , 第三方的解密等等 , Spring提供了RequestBodyAdvice一个全局的解决方案 , 免去了我们在Controller处理的繁琐 .RequestBodyAdvice仅对使用了@RqestBody注解的生效 , 因为它原理上还是AOP , 所以GET方法是不会操作的

package com.xbz.common.web;
  import org.springframework.core.MethodParameter;
  import org.springframework.http.HttpHeaders;
  import org.springframework.http.HttpInputMessage;
  import org.springframework.http.converter.HttpMessageConverter;
  import org.springframework.web.bind.annotation.ControllerAdvice;
  import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
  import java.io.IOException;
  import java.io.InputStream;
  import java.lang.reflect.Type;

/**
* @title 全局请求参数处理类
* @author Xingbz
* @createDate 2019-8-2
  */
  @Component
  //@ControllerAdvice(basePackages = "com.xbz.controller")//此处设置需要当前Advice执行的域 , 省略默认全局生效
  @ControllerAdvice(assignableTypes = {TestController.class})//也可以用这种方式 让单个controller生效
  public class GlobalRequestBodyAdvice implements RequestBodyAdvice {
  private final Logger log = LoggerFactory.getLogger(getClass());

  /** 此处如果返回false , 则不执行当前Advice的业务 */
  @Override
  public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
  //        return methodParameter.getMethod().isAnnotationPresent(XXApiReq.class);
  return true;
  }

  /**
    * @title 读取参数前执行
    * @description 在此做些编码 / 解密 / 封装参数为对象的操作
    *
    *  */
       @Override
       public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
       //return new XHttpInputMessage(inputMessage, "UTF-8");
       return inputMessage;
       }

  /**
    * @title 读取参数后执行  解密等的操作区域
    * @author Xingbz
      */
      @Override
      public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
      log.info("第三方请求加密数据:"+body)
      //解密 todo
      String aaa = body;
      return aaa;
      }

  /**
    * @title 无请求时的处理
      */
      @Override
      public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
      return body;
      }
      }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

响应处理=====实现ResponseBodyAdvice接口,其实是对加了@RestController(也就是@Controller+@ResponseBody)注解的处理器将要返回的值进行增强处理。 其实也就是采用了AOP的思想,对返回值进行一次修改。

 //此接口说明对添加了@Controller的类织入一个通知(增强功能)
      @ControllerAdvice(assignableTypes = {TestController.class})
      @Component
      public class MyResponseBodyAdvice implements ResponseBodyAdvice {
      private final Logger log = LoggerFactory.getLogger(getClass());

  @Override
  public boolean supports(MethodParameter returnType, Class converterType) {
  // 开关处于开启状态  是get请求  
  //使用MethodParameter参数判断注解信息
  //符合此Get请求才进行织入通知
  //return enable && returnType.hasMethodAnnotation(GetMapping.class)
  return true;
  }

  /**
  *@param body:原controller要返回的内容
  加密操作等
  */
  @Override
  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
  log.info("响应第三方数据加密处理开始:"+body)
  //加密处理 todo
  String sendXml = body;
  return sendXml;
  }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 7.vue中将后台返回的数字转换成对应的文字

# 7.1、对于列表循环el-table-column采用如下方式:

  第一种方案
<el-table-column prop="status" :show-overflow-tooltip="true" label="状态" width="60" :formatter="statusFormatter">
</el-table-column>ji

        methods:{
        statusFormatter(row, column){
        }
        }
        第二种方案
<el-table-column prop="type" label="类型" align="center">
<template v-slot="{ row }">
<span v-show="row.type == 1">普通用户</span>
<span v-show="row.type == 2">管理员</span>
<span v-show="row.type == 3">项目经理</span>
</template>
</el-table-column>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 7.2、对于详情或者修改页面采用如下方式:

在Vue中,created和mounted的区别是created用来初始化属性值,mounted用来操作属性值。下面小编举例讲解Vue中created和mounted的区别是什么。
created是用来初始化页面的值的
mounted是修改页面的值的
created() {
console.log(this.dataMsg);
console.log(this.propMsg);
},
mounted() {
console.log(this.dataMsg);
this.submit()
console.log(this.propMsg);
}
上面方法mounted有可能只触发一次,就是说详情页面关闭后再打开mounted中的方法不会再次执行。
完美方案v-model绑定计算属性:
<el-form-item>
<span slot="label">
<i class="el-icon-edit"></i>
活动名称
</span>
<el-input v-model="formatValue"></el-input>
</el-form-item>
计算属性定义
computed: {
formatValue() {
if(this.inputvalue === '0'){
return "中国";
}
}
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 8.实体类属性copy

BeanUtils.copyProperties(source,target);
1

# 9.el-table-column宽度自适应

如下这种方式会把每列宽度设置为10px
<el-table-column
prop="sort"
label="Sort"
width="10%">
如下是百分比的写法
<el-table-column prop="sort" label="Sort" min-width="50%">
<el-table-column prop="sort" label="Sort" min-width="50%">
1
2
3
4
5
6
7
8

# 10.el-table表格中单元格内容过多显示省略号

el-table表格中内容超出单元格的宽度会自动换行,会使整个表格看起来显得不太美观,
此时可以使用el-table-column 自带的 show-overflow-tooltip="true"  属性来设置,可以使超出单元格宽度的内容变成省略号,
而且鼠标放上去会提示单元格中原本有的全部的内容
<el-table-column
prop="address"
label="地址"
show-overflow-tooltip="true"
min-width="100">

</el-table-column>
1
2
3
4
5
6
7
8
9
10

# 11.mybatis-plus的LambdaQueryWrapper自定义sql用法

# 如果排序的字段需要先转换类型呢
# 那么就需要 sql自由拼接方法 (wrapper.apply)
    @Override
    public List<SysRoleEntity> selectListByTypeCode(String typeCode) {
        LambdaQueryWrapper<SysRoleEntity> wrapper = new LambdaQueryWrapper<SysRoleEntity>()
                .eq(SysRoleEntity::getTypeCode,typeCode)

                .apply("ORDER BY TO_NUMBER(SEQUENCEVALUE) ASC");
    }
或者
apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2020-10-08")
1
2
3
4
5
6
7
8
9
10
11

# 12.mybatis-plus的@Select注解用法

//没有查询条件
@Select("select BLOCK_ID,BLOCK_NAME,PARENT_BLOCK_ID,BLOCK_LEVEL,ORDER_NUM from XY_DIC_BLOCK_T where block_level=1 " )
public List<Block> sqlMany();
//有查询条件
@Select("select BLOCK_ID,BLOCK_NAME,PARENT_BLOCK_ID,BLOCK_LEVEL,ORDER_NUM from XY_DIC_BLOCK_T where block_level=#{level}"    )
public List<Block> sqlManyParm(String level);
//sql中有条件判断
@Select("<script> select BLOCK_ID,BLOCK_NAME,PARENT_BLOCK_ID,BLOCK_LEVEL,ORDER_NUM from XY_DIC_BLOCK_T where 1=1 " +
"<if test='level != null'>" + " and block_level=#{level} " + "</if>" +
"</script>")
public List<Block> sqlManyParmNull(String level);
//sql中有条件判断并且传递参数时个对象的情况
@Select("<script> select BLOCK_ID,BLOCK_NAME,PARENT_BLOCK_ID,BLOCK_LEVEL,ORDER_NUM from XY_DIC_BLOCK_T where 1=1 " +
"<if test='item.blockLevel != null'>" + " and block_level=#{item.blockLevel} " + "</if>" +
"</script>")
public List<Block> sqlManyObject(@Param("item") Block block);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 13.vue调用子组件作为弹窗时只执行一次created问题

解决办法:用v-if将子组件包裹起来,因为v-if=false时可以将子组件销毁掉,再次调用时重新渲染 补充知识:vue如何每次打开子组件弹窗都进行初始化 :visible.sync 与 v-if同时使用即可

# 14.Java字符串前后补零的几种方法

数字类型前补 0

String.format("%08d", 123);    // 00000123
1

字符串类型前补 0

String.format("%8s", "abc").replace(" ", "0");
// 00000abc
1
2

也可以先在前面补 8 位的 0,再截取:

String str = "00000000" + "abc";
str.substring(str.length() - 8);
// 00000abc
1
2
3

后补 0 对于后补 0,都可以使用一种方式来做,就是在后面加上 00000...,之后截取:

(123 + "00000000").substring(0, 8);
// 12300000
1
2

这种方式通用任何类型

# 15.springboot集成https

生成证书
keytool -genkey -alias tomcat -dname "CN=Andy,OU=kfit,O=kfit,L=HaiDian,ST=BeiJing,C=CN" -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 365
说明:"CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=单位的两字母国家代码"
1
2
3

输入后会提示输入密码,这个密码在下面配置文件有用到。 生成后,在家目录找到证书文件,复制到SpringBoot应用的src/main/resources下

application.yml配置如下信息
server:
ssl:
# 证书路径
key-store: classpath:keystore.p12
# 与申请时输入一致
key-alias: tomcat
enabled: true
key-store-type: PKCS12
#与申请时输入一致
key-store-password: 123456
# 浏览器默认端口 和 80 类似,https默认的端口号为443
port: 443
说明:端口443可以改成任意值
1
2
3
4
5
6
7
8
9
10
11
12
13
14

此时启动SpringBoot应用,发现可以通过https访问了====快去打开浏览器访问试试😉。 想要http同时访问就看下面,否则跳过

在yml配置文件中,添加http端口号定义
server
http:
port: 8888
创建配置类
@Value("${server.http.port}")
private Integer httpPort;

@Bean
public ServletWebServerFactory servletContainer(){
final Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(httpPort);
final TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();

    tomcat.addAdditionalTomcatConnectors(connector);
    return tomcat;
}
启动项目时,我们会发现如下日志,Tomcat绑定了两个端口号,其中https绑定在8080,http绑定在8888。
恭喜你通关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 16.项目防刷控制(自定义注解)

# 创建一个自定义注解

package com.example.annotation;
import java.lang.annotation.*;

@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
int seconds() default 1;
int maxCount() default 1;
}
1
2
3
4
5
6
7
8
9
10
11

# 2.创建一个拦截器 (用于拦截请求,更新当前用户访问的次数,如果访问受限,则返回超时的状态码)

package com.example.interceptor;

import com.example.annotation.AccessLimit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

@Component
public class FangshuaInterceptor extends HandlerInterceptorAdapter {
@Resource
RedisTemplate<String, Object> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handler1 = (HandlerMethod) handler;
//       3. 获取方法中的注解,看是否有该注解
AccessLimit accessLimit = handler1.getMethodAnnotation(AccessLimit.class);
if (accessLimit != null) {
//                3.2 : 判断请求是否受限制
if (isLimit(request, accessLimit)) {
render(response, "{\"code\":\"30001\",\"message\":\"请求过快\"}");
return false;
}
}
}
return true;
}

    //判断请求是否受限
    public boolean isLimit(HttpServletRequest request, AccessLimit accessLimit) {
        // 受限的redis 缓存key ,因为这里用浏览器做测试,我就用sessionid 来做唯一key,如果是app ,可以使用 用户ID 之类的唯一标识。
        String limitKey = request.getServletPath() + request.getSession().getId();
        // 从缓存中获取,当前这个请求访问了几次
        Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey);
        if (redisCount == null) {
            //初始 次数
            redisTemplate.opsForValue().set(limitKey, 1, accessLimit.seconds(), TimeUnit.SECONDS);
            System.out.println("写入redis --");
        } else {
            System.out.println("intValue-->" + redisCount.intValue());
            if (redisCount.intValue() >= accessLimit.maxCount()) {
                return true;
            }
            // 次数自增
            redisTemplate.opsForValue().increment(limitKey);
        }
        return false;
    }

    private void render(HttpServletResponse response, String cm) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        out.write(cm.getBytes("UTF-8"));
        out.flush();
        out.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

# 3.注册拦截器

package com.example.interceptor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.io.Serializable;

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
private FangshuaInterceptor interceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor);
}
//如果redisTemplate.opsForValue().increment(limitKey);的increment方法报错添加这个方法
@Bean
public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
//创建 redisTemplate 模版
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
//设置 value 的转化格式和 key 的转化格式
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//关联 redisConnectionFactory
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 4.OK , 下面我们就可以在需要进行现在访问次数的controller中的方法使用该注解了

@RestController
@RequestMapping("/test")
public class Test {
@GetMapping("/test1")
//    指定此接口同一个用户在20秒内只能访问2次
@AccessLimit(seconds = 20, maxCount = 2)
public String test1() {
return "我是test1";
}
}
1
2
3
4
5
6
7
8
9
10

# 17.string转json

JSONObject  json=new  JSONObject(“”);
String  key  =  json.getString(key);
1
2

# 18.SpringBoot项目业务操作日志记录

# 1、依赖配置

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1
2
3
4

# 2、表结构设计

create table if not exists bus_log(
id bigint auto_increment comment '自增id'
primary key,
bus_name varchar(100) null comment '业务名称',
bus_descrip varchar(255) null comment '业务操作描述',
oper_person varchar(100) null comment '操作人',
oper_time datetime null comment '操作时间',
ip_from varchar(50) null comment '操作来源ip',
param_file varchar(255) null comment '操作参数报文文件'
)comment '业务操作日志' default charset ='utf8';
1
2
3
4
5
6
7
8
9
10

# 3、定义业务日志注解@BusLog,可以作用在控制器或其他业务类上,用于描述当前类的功能;也可以用于方法上,用于描述当前方法的作用

/** * 业务日志注解 * 可以作用在控制器或其他业务类上,用于描述当前类的功能; * 也可以用于方法上,用于描述当前方法的作用; */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BusLog {  
/**     * 功能名称     * @return     */
String name() default "";
/**     * 功能描述     * @return     */
String descrip() default "";
}
1
2
3
4
5
6
7
8
9

# 4、把业务操作日志注解BusLog标记在PersonController类和方法上

@RestController
@Slf4j
@BusLog(name = "人员管理")
@RequestMapping("/person")
public class PersonController {
@Autowired
private IPersonService personService;
private Integer maxCount=100;

    @PostMapping
    @NeedEncrypt
    @BusLog(descrip = "添加单条人员信息")
    public Person add(@RequestBody Person person) {
        Person result = this.personService.registe(person);
        log.info("//增加person执行完成");
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 5、编写切面类BusLogAop,并使用@BusLog定义切入点,在环绕通知内执行过目标方法后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述, 把方法的参数报文写入到文件中,最后保存业务操作日志信息

@Component
@Aspect
@Slf4j
public class BusLogAop implements Ordered {
@Autowired
private BusLogDao busLogDao;

    /**     * 定义BusLogAop的切入点为标记@BusLog注解的方法     */
    @Pointcut(value = "@annotation(com.fanfu.anno.BusLog)")
    public void pointcut() {
    }
 
    /**     * 业务操作环绕通知     *     * @param proceedingJoinPoint     * @retur     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) {
        log.info("----BusAop 环绕通知 start");
        //执行目标方法
        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        //目标方法执行完成后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述
        Object target = proceedingJoinPoint.getTarget();
        Object[] args = proceedingJoinPoint.getArgs();
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        BusLog anno1 = target.getClass().getAnnotation(BusLog.class);
        BusLog anno2 = signature.getMethod().getAnnotation(BusLog.class);
        BusLogBean busLogBean = new BusLogBean();
        String logName = anno1.name();
        String logDescrip = anno2.descrip();
        busLogBean.setBusName(logName);
        busLogBean.setBusDescrip(logDescrip);
        busLogBean.setOperPerson("fanfu");
        busLogBean.setOperTime(new Date());
        JsonMapper jsonMapper = new JsonMapper();
        String json = null;
        try {
            json = jsonMapper.writeValueAsString(args);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        //把参数报文写入到文件中
        OutputStream outputStream = null;
        try {
            String paramFilePath = System.getProperty("user.dir") + File.separator + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN) + ".log";
            outputStream = new FileOutputStream(paramFilePath);
            outputStream.write(json.getBytes(StandardCharsets.UTF_8));
            busLogBean.setParamFile(paramFilePath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } 
            }
        }
        //保存业务操作日志信息
        this.busLogDao.insert(busLogBean);
        log.info("----BusAop 环绕通知 end");
        return result;
    }
 
    @Override
    public int getOrder() {
        return 1;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

# 19.jackson返回前端的字符串中引号被自动加上反斜杆

  • 对象中有字符串是返回的对象中有反斜杆
{\"networkNumber\": 1}
1

解决方法: 使用JsonNode

    class DtoNew {
      JsonNode data;
    }
    ObjectMapper mapper = new ObjectMapper();
    try {
        dtoNew.data =  mapper.readTree(dto.data ));
    } catch (IOException e) {
        e.printStackTrace();
    }
1
2
3
4
5
6
7
8
9

实际使用方法

@JsonIgnore
private String preserveList;

private JsonNode preserve_list;

public JsonNode getPreserve_list(){
        try {
            return new ObjectMapper().readTree(this getPreserveList());
        }catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 对象中字段转换json输出名字
@JsonProperty("door_no")
private String doorNo;
1
2
  • 忽略字段的输出
@JsonIgnore
private String doorNo;
1
2

# 结尾。

上次更新: 2024/02/03, 13:17:04

← 经典代码汇总 CentOS7下安装mysql5.7→

最近更新
01
SpringBoot 快速实现 api 加密!
03-21
02
SpringBoot整合SQLite
03-07
03
SpringBoot配置使用H2数据库的简单教程
02-21
更多文章>
Theme by Vdoing | Copyright © 2017-2024 | 点击查看十年之约 | 豫ICP备2022014539号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式