在工作中如何写出高效优雅的Java代码:实战技巧与代码示例

本文介绍了如何通过选择合适的数据结构(如HashMap代替List)、消除重复代码、利用Java8新特性(如Lambda和StreamAPI)、处理并发编程和使用全局异常类等方法,来提高代码效率。同时,还涉及工具类的使用、字符串操作优化以及并发编程的最佳实践。

1. 使用合适的数据结构

选择合适的数据结构对提高代码效率至关重要。

示例:使用HashMap优化查找

// 不推荐的做法:使用List进行查找  List 查找元素,是通过索引慢
List<String> list = new ArrayList<>(Arrays.asList("apple", "banana", "cherry"));
if (list.contains("banana")) {
    // 处理逻辑
}

// 推荐的做法:使用HashMap提高查找效率  HashSet 的底层是 HashMap ,直接相等与
Set<String> set = new HashSet<>(Arrays.asList("apple", "banana", "cherry"));
if (set.contains("banana")) {          
    // 处理逻辑 
}

set.contains("banana") == map.get("banana")

2. 避免重复代码

重复代码会使得程序难以维护,使用方法或类重构可以有效解决这一问题。

示例:提取重复代码到方法

// 重复代码示例
public void processUser(User user) {
    if (user != null && user.isActive()) {
        // 处理逻辑
    }
}

// 优化后
public void processUser(User user) {
    if (isValidUser(user)) {
        // 处理逻辑
    }
}

private boolean isValidUser(User user) {
    return user != null && user.isActive();
}

3. 利用Java 8特性

Java 8引入了许多新特性,如Lambda表达式和Stream API,它们可以使代码更加简洁高效。

示例:使用Stream API处理集合

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> filteredNames = names.stream()
    .filter(name -> name.startsWith("A"))
    .collect(Collectors.toList());

4. 理解并发编程

在多线程和并发编程中,正确管理资源和线程是提高性能的关键。

示例:可以使用Java 8 的  CompletableFuture

     // 异步把上个版本的数据 批量插入到 历史表
            CompletableFuture.runAsync(() -> {
                MyBatisBatchExecutorUtil.batchCommit(PzryMapper.class, INSERT_HISTORY, historyList);
                MyBatisBatchExecutorUtil.batchCommit(PzryMapper.class, BATCH_HISTORY_INSERT_LD, historyLdData);
            });


     // 异步调用一些方法(记录操作日志)
        CompletableFuture.runAsync(() -> logService.addLog(LogOperationYeMianEnum.getContent(2), logType, builder, isSucess, userId));


 5.使用全局异常类

可以使用全局异常处理类来替换写不完的 try catch

示例: @RestControllerAdvice

原本:
   @GetMapping("/add2")
    public String add2() {
        String result = "成功";
        try {
            int a = 10 / 0;
        } catch (Exception e) {
            result = "数据异常";
        }
        return result;
    }


使用后:
   @GetMapping("/add2")
    public String add2() {
        String result = "成功";
        int a = 10 / 0;
        return result;
    }



@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        if (e instanceof ArithmeticException) {
            return "数据异常";
        }
        if (e != null) {
            return "服务器内部异常";
        }
        return null;
    }
}

 6. 多使用工具类方法

可以多使用一些工具类方法来判断集合,字符串,对象是否为空

示例: Objects,Strings,StringUtils, CollectionUtils,ObjectUtils

        Objects.nonNull(new Ld());
        Objects.isNull(new Ld());
        Objects.equals(new Ld(),new Ld());
        Strings.isNullOrEmpty("");
        StringUtils.isEmpty();
        CollectionUtils.isEmpty(new HashMap<>());
        CollectionUtils.isEmpty(new ArrayList<>());
        CollectionUtils.isEmpty(new HashSet<>());
        
        List<Object> list = new ArrayList<>();
        list.add("111");
        System.out.println(ObjectUtils.isEmpty(list));

 7. 字符串拼接

替换传统的 + “” +

示例:String.format

String.format("%s(%s)", name.getName(), name.getNetId())

 8. 优化一些码值的转换

替换传统的if else 判断存值

示例:使用 map 枚举

  //如果是 例行= 0 非例行= 1 OA申请=2
   Map<String, String> map= new HashMap<>(16);
   map.put("例行", "0");
   map.put("非例行", "1");
   map.put("OA申请", "2");

   yxThingsType.setIsRoutine(map.getOrDefault(yxThingsType.getIsRoutine(), ""));

9. 使用静态常量替换魔法值,字符串

 private static final String INSERT_HISTORY = "insertHistory";

 private static final Integer MAX_DRAFT_COUNT = 5;

10. 批量插入几百条,几千条数据库时使用工具类

穿透的批量插入,当数据量过多的时候批量插入的时间多长

示例:使用底层  sqlSessionFactory 的批量插入

工具类1:

package com.ly.cloud.util;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import com.google.common.collect.Lists;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 * @Author 
 * @Date Created in  2023/12/25 15:57
 * @DESCRIPTION: mybatis快速批量插入 数据 工具类
 * @Version V1.0
 */
@Component
public class MyBatisBatchExecutorUtil {
    private static final Logger logger = LoggerFactory.getLogger(MyBatisBatchExecutorUtil.class);

    @Resource
    private SqlSessionFactory sqlSessionFactory;
    private static MyBatisBatchExecutorUtil utils;

    @PostConstruct
    public void init() {
        utils = this;
        utils.sqlSessionFactory = this.sqlSessionFactory;
    }

    /**
     * 批量提交数据
     *
     * @param mapperClass     Mapper 类
     * @param dataList        要提交的数据列表
     */
    public static <T> void batchCommit(Class<?> mapperClass, String mybatisSqlId, List<T> dataList) {
        if (dataList == null || dataList.isEmpty()) {
            return;
        }

        SqlSession session = null;
        int commitCountEveryTime = 500;
        try {
            long startTime = System.currentTimeMillis();
            session = utils.sqlSessionFactory.openSession(ExecutorType.BATCH, false);
            List<List<T>> groupList = Lists.partition(dataList, commitCountEveryTime);
            for (List<T> tempList : groupList) {
                session.insert(mapperClass.getName() + "." + mybatisSqlId, tempList);
            }
            session.commit();
            session.clearCache();  // 移动到循环结束后执行
            long endTime = System.currentTimeMillis();
            logger.info("批量插入数据耗时:" + (endTime - startTime) + "毫秒");
        } catch (Exception e) {
            logger.error("batchCommit error!", e);
            if (session != null) {
                session.rollback();
            }
        } finally {
            if (session != null) {
                session.close();
            }
        }
    }

}




使用示例:
INSERT_HISTORY  === 》  对应的mapper 类的方法名 (mybatis 的id )
currentData     === 》  对应的数据集合
MyBatisBatchExecutorUtil.batchCommit(NewYxThingsTypeMapper.class, INSERT_HISTORY, currentData);

工具类2:

package com.example.juc.utils.dataUtils;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.function.BiFunction;

/**
 * @author 12926
 * @CreatTime: 2022/7/27 10:53
 */
@Component
public class MybatisBatchUtils {
    /**
     * 每次处理1000条
     */
    private static final int BATCH_SIZE = 1000;

    @Resource
    private SqlSessionFactory sqlSessionFactory;
    private static MybatisBatchUtils utils;
    @PostConstruct
    public void init() {
        utils = this;
        utils.sqlSessionFactory = this.sqlSessionFactory;
    }

    /**
     * 批量处理修改或者插入
     *
     * @param data     需要被处理的数据
     * @param mapperClass  Mybatis的Mapper类
     * @param function 自定义处理逻辑
     * @return int 影响的总行数
     */
    public static <T,U,R> int batchUpdateOrInsert(List<T> data, Class<U> mapperClass, BiFunction<T, U, R> function) {
        int i = 1;
        SqlSession batchSqlSession = utils.sqlSessionFactory.openSession();
        batchSqlSession.getConfiguration().setDefaultExecutorType(ExecutorType.BATCH);
        try {
            U mapper = batchSqlSession.getMapper(mapperClass);
            int size = data.size();
            for (T element : data) {
                function.apply(element,mapper);
                if ((i % BATCH_SIZE == 0) || i == size) {
                    System.out.println(batchSqlSession.flushStatements());
                }
                i++;
            }
            // 非事务环境下强制commit,事务情况下该commit相当于无效
            batchSqlSession.commit(!TransactionSynchronizationManager.isSynchronizationActive());
        } catch (Exception e) {
            batchSqlSession.rollback();
            throw new RuntimeException(e);
        } finally {
            batchSqlSession.close();
        }
        return i - 1;
    }
}


//使用方法
       MybatisBatchUtils.batchUpdateOrInsert(list, UserMapper.class,
                (user, userMapper) -> userMapper.insert(user));


@Mapper
public interface UserMapper {
    List<User> getUsersList();

    int insert(@Param("user") User user);
}

11. 获取接口的请求头信息

使用工具类获取,每次在只需要一行代码

示例:

原来每个请求:@RequestHeader("loginUserId") String loginUserId

    @ApiOperation("查询流程[类型]列表(本地数据源)")
    @PostMapping("/listInstancesTypeByLocal")
    public WebResult<List<InstancesStatusVo>> listInstancesTypeByLocal(@RequestBody FlowGetInstanceByTypeDto flowGetInstanceByTypeDto,
                                                                      @RequestHeader("loginUserId") String loginUserId) {
            return WebResult.ok(yxMyThingsService.listInstancesTypeByLocal(flowGetInstanceByTypeDto, loginUserId));
       


工具类:
public class LoginUserUtil {
    public static String getLoginUserId() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = null;
        if (requestAttributes != null) {
            request = ((ServletRequestAttributes) requestAttributes).getRequest();
        }
        // 子线程共享
        RequestContextHolder.setRequestAttributes(requestAttributes, true);
        if (request != null) {
            return request.getHeader("loginUserId");
        }
        return null;
    }
}

//调用:
LoginUserUtil.getLoginUserId

12. 遍历Map 的时候,使用 EntrySet 方法

 使用 EntrySet 方法,可以直接返回 set 对象,直接拿来用即可;而使用 KeySet 方法,获得的是key 的集合,需要再进行一次 get 操作,多了一个操作步骤,所以更推荐使用 EntrySet 方式遍历 Map。

Set<Map.Entry<String, String>> entryseSet = nmap.entrySet();
for (Map.Entry<String, String> entry : entryseSet) {
    System.out.println(entry.getKey()+","+entry.getValue());
}

 13.为集合对象中的每个元素赋值(序号)

IntStream.rangeClosed(1, departmentList.size())
                    .forEach(i -> 
departmentList.get(i - 1).setMyNumber(String.valueOf(i)));

 14. 减少集合循环次数

在开发比较俩个集合的案例很多

示例:

反例
for(User user: userList) {
   for(Role role: roleList) {
      if(user.getRoleId().equals(role.getId())) {
         user.setRoleName(role.getName());
      }
   }
}

正例
Map<Long, List<Role>> roleMap = roleList.stream().collect(Collectors.groupingBy(Role::getId));

for (User user : userList) {
    List<Role> roles = roleMap.get(user.getRoleId());
    if(CollectionUtils.isNotEmpty(roles)) {
        user.setRoleName(roles.get(0).getName());
    }
}

15. 使用BigDecimal 进行小数点的加减

    private String covertString(String pzrySmallBbh) {
        if (!StringUtils.isEmpty(pzrySmallBbh)) {
            // 将字符串转换为 BigDecimal 类型
            BigDecimal number = new BigDecimal(pzrySmallBbh);
            // 每次加 0.01
            number = number.add(new BigDecimal("0.01"));
            // 将结果转换回字符串
            return number.toString();
        }
        return null;
    }

16. 多个线程往同一个集合中写数据时

使用:CopyOnWriteArrayList

List<Object> list = new CopyOnWriteArrayList<>();

Set<Object> list  = new CopyOnWriteArraySet<>();

Map<String,String> map2 = new ConcurrentHashMap<>()

17. 集合复制时修改复制后的集合

第一种简单的:

   
反例: 这样会把 yxAdminList 这个集合的 setYxThingNo 也值为空;
List<YxAdmin> yxAdminListTwo = new ArrayList<>(yxAdminList);

yxAdminListTwo.forEach(vo -> vo.setYxThingNo("")); 




正例:
        List<ListCopy> addALL = new ArrayList<>();
        addALL.addAll(list);
        addALL.forEach(vo -> vo
                .setName(""));

或者使用for循环 
    List<YxAdmin> yxAdminListTwo = new ArrayList<>();
            for (YxAdmin vo : yxAdminList) {
                YxAdmin copiedAdmin = new YxAdmin();
                copiedAdmin.setName(vo.getName());
                copiedAdmin.setDepartment(vo.getDepartment());
                copiedAdmin.setYxThingNo("");
                yxAdminListTwo.add(copiedAdmin);
            }

第二种工具类库

package com.example.juc.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;

/**
 * @Author 
 * @Date Created in  2024/4/2 16:48
 * @DESCRIPTION: 对象赋值工具类
 * @Version V1.0
 */

public class BeanCopyUtil {
    private static final Logger logger = LoggerFactory.getLogger(BeanCopyUtil.class);
    /**
     * 相同对象合并,将原对象的非空属性的值赋值给目标对象
     *
     * @param origin      源对象
     * @param destination 目标对象
     * @param <T>         对象的类型
     */
    public static <T> void merge(T origin, T destination) {
        if (origin == null || destination == null) {
            return;
        }
        if (!origin.getClass().equals(destination.getClass())) {
            return;
        }
        Field[] fields = origin.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Object value = field.get(origin);
                if (null != value) {
                    field.set(destination, value);
                }
            } catch (IllegalAccessException e) {
                logger.error("访问对象异常", e);
            }
            field.setAccessible(false);
        }
    }
}



 ListCopy listCopy = new ListCopy();
        listCopy.setName("qqqqqqq");
        listCopy.setAge(18);

        ListCopy listCopy2 = new ListCopy();
        BeanCopyUtil.merge(listCopy, listCopy2);

        listCopy2.setName("2222222");
        System.out.println(listCopy);
        System.out.println(listCopy2);

18. 将list<String> 转成字符串

List<String> objects = new ArrayList<>();

String name = String.join(",", objects);

19. 学会使用三目运算符

反例
String title;
if (isMember(phone)) {
    title = "会员";
} else {
    title = "游客";
} 

正例

String title = isMember(phone) ? "会员" : "游客";

20. 使用增强for 替换普通for 循环

    for (JsonElement element : dataArray) {
            JsonObject itemObject = element.getAsJsonObject();
            String title = itemObject.get("Title").getAsString();
            System.out.println("Title:----" + title);
        }

21. 利用 Map 的 computeIfAbsent 方法

利用 Map 的 computeIfAbsent 方法,可以保证获取到的对象非空,从而避免了不必要的空判断和重新设置值。

普通写法:
Map<Long, List<UserDO>> roleUserMap = new HashMap<>();
for (UserDO userDO : userDOList) {
    Long roleId = userDO.getRoleId();
    List<UserDO> userList = roleUserMap.get(roleId);
    if (Objects.isNull(userList)) {
        userList = new ArrayList<>();
        roleUserMap.put(roleId, userList);
    }
    userList.add(userDO);
}


使用利用 Map 的 computeIfAbsent 方法

Map<Long, List<UserDO>> roleUserMap = new HashMap<>();
for (UserDO userDO : userDOList) {
    roleUserMap.computeIfAbsent(userDO.getRoleId(), key -> new ArrayList<>())
        .add(userDO);
}

22.stream 并行流适时使用

    // 使用stream 并行流处理查询结果,检查是否有重复数据 提高查询效率
if (admins.parallelStream().anyMatch(vo -
> !CollectionUtils.isEmpty(
pzPeopleMapper.isExitYxAdmin(vo.getName(), vo.getDepartment())))) {
// 只要匹配到有一条重复数据,直接返回 false
 return false;
}

23:使用map 优化 if else 语法

        List<String> changeRecords = new ArrayList<>();
        Map<String, String[]> changes = new HashMap<>();
        changes.put("讲座名称", new String[]{changeFontName, changeEndName});
        changes.put("讲座副标题", new String[]{lectureMessage.getLectureSubtitle(), vo.getLectureSubtitle()});
        changes.put("讲座类型", new String[]{lectureMessage.getLectureType(), vo.getLectureType()});
        changes.put("讲座详情", new String[]{lectureMessage.getLectureDetail(), vo.getLectureDetail()});
        changes.put("举办地点", new String[]{lectureMessage.getHoldPlace(), vo.getHoldPlace()});
        changes.put("举办开始时间", new String[]{String.valueOf(lectureMessage.getHoldStartTime()), String.valueOf(vo.getHoldStartTime())});
        changes.put("举办结束时间", new String[]{String.valueOf(lectureMessage.getHoldEndTime()), String.valueOf(vo.getHoldEndTime())});
        changes.put("主讲人", new String[]{isForeign(lectureMessage.getForeignRelations()), isForeign(vo.getForeignRelations())});


        for (Map.Entry<String, String[]> entry : changes.entrySet()) {
            String key = entry.getKey();
            String[] values = entry.getValue();
            if (!values[0].equals(values[1])) {
                changeRecords.add(formatChangeRecord(key, values[0], values[1]));
            }
        }

24:优化Map 的put方法 

        ImmutableMap<String, String> paramMap = ImmutableMap.of(
                "year", place.substring(0, 4),
                "month", place.substring(5, 10),
                "day", place.substring(11, 24),
                "weekday", chineseDayOfWeek
        );

25:如何把集合数据根据不同的条件分割

 注: 根据不同的条件true,false 来分割 mainSpeaker.getForeignSchool() 是否 为0 的数据
 public List<MainSpeaker> partitioningBy(List<MainSpeaker> list, Boolean isTrue) {
        Map<Boolean, List<MainSpeaker>> booleanListMap = list.stream()
                .collect(Collectors.partitioningBy(mainSpeaker -> "0".equals(mainSpeaker.getForeignSchool())));
        return booleanListMap.get(isTrue);
    }

26:枚举算法

列举出来所有的 数据结果

package com.example.juc.factory;

import com.example.juc.entity.ListCopy;
import com.example.juc.utils.BeanCopyUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

public class test{

    // 枚举算法
    public List<String> permute(String str) {
        List<String> result = new ArrayList<>();
        backtrack(result, "", str);
        return result;
    }

    private void backtrack(List<String> result, String current, String remaining) {
        if (remaining.isEmpty()) {
            result.add(current);
            return;
        }
        for (int i = 0; i < remaining.length(); i++) {
            String next = current + remaining.charAt(i);
            String left = remaining.substring(0, i) + remaining.substring(i + 1);
            backtrack(result, next, left);
        }
    }

    public static void main(String[] args) {
        test permutations = new test();
        List<String> results = permutations.permute("16");
        System.out.println(results);
    }
}

27:二分查找~迭代

public class BinarySearch {
    public static int binarySearchIterative(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2; // 防止溢出

            if (arr[mid] == target) {
                return mid; // 找到目标,返回索引
            } else if (arr[mid] < target) {
                left = mid + 1; // 在右半部分继续搜索
            } else {
                right = mid - 1; // 在左半部分继续搜索
            }
        }
        return -1; // 没有找到目标
    }

    public static void main(String[] args) {
        int[] sortedArray = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        int target = 5;
        
        int result = binarySearchIterative(sortedArray, target);
        if (result != -1) {
            System.out.println("Element found at index: " + result);
        } else {
            System.out.println("Element not found.");
        }
    }
}

 28:二分查找 - 递归实现

public class BinarySearch {
    public static int binarySearchRecursive(int[] arr, int target, int left, int right) {
        if (left > right) {
            return -1; // 基本情况:没有找到目标
        }

        int mid = left + (right - left) / 2; // 防止溢出

        if (arr[mid] == target) {
            return mid; // 找到目标,返回索引
        } else if (arr[mid] < target) {
            return binarySearchRecursive(arr, target, mid + 1, right); // 在右半部分继续搜索
        } else {
            return binarySearchRecursive(arr, target, left, mid - 1); // 在左半部分继续搜索
        }
    }

    public static void main(String[] args) {
        int[] sortedArray = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        int target = 5;

        int result = binarySearchRecursive(sortedArray, target, 0, sortedArray.length - 1);
        if (result != -1) {
            System.out.println("Element found at index: " + result);
        } else {
            System.out.println("Element not found.");
        }
    }
}

28: multipartFile --> fileInputStream

    public static FileInputStream convertMultipartFileToInputStream(MultipartFile multipartFile) throws IOException {
        // 创建临时文件
        File tempFile = File.createTempFile("temp", null);

        // 将MultipartFile的数据写入临时文件
        try (FileOutputStream fos = new FileOutputStream(tempFile)) {
            fos.write(multipartFile.getBytes());
        }

        // 将临时文件转换为FileInputStream
        FileInputStream fileInputStream = new FileInputStream(tempFile);

        // 删除临时文件
        tempFile.delete();

        return fileInputStream;
    }

29:在调用第三方接口时如何设置超时时间

    private CloseableHttpClient buildHttpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        // 创建 SSL 上下文,跳过证书认证
        SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial((chain, authType) -> true).build();
        // 创建 SSL 连接套接字工厂
        SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);

        // 创建RequestConfig并设置读取超时时间为10秒
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(30000) // 读取超时
                .setConnectTimeout(30000) // 连接超时
                .setConnectionRequestTimeout(30000) // 从连接池获取连接的超时
                .build();

        // 创建CloseableHttpClient并应用RequestConfig
        return HttpClients.custom().setSSLSocketFactory(sslSocketFactory).setDefaultRequestConfig(requestConfig).build();
    }

 30:如何比较俩个新旧实体类的区别

   List<String> changeRecords = new ArrayList<>();
        Map<String, String[]> changes = new HashMap<>();
        changes.put("讲座名称", new String[]{changeFontName, changeEndName});
        changes.put("讲座副标题", new String[]{lectureMessage.getLectureSubtitle(), vo.getLectureSubtitle()});
        changes.put("讲座类型", new String[]{lectureMessage.getLectureType(), vo.getLectureType()});
        changes.put("讲座详情", new String[]{lectureMessage.getLectureDetail(), vo.getLectureDetail()});
        changes.put("举办地点", new String[]{lectureMessage.getHoldPlace(), vo.getHoldPlace()});
        changes.put("举办开始时间", new String[]{String.valueOf(lectureMessage.getHoldStartTime()), String.valueOf(vo.getHoldStartTime())});
        changes.put("举办结束时间", new String[]{String.valueOf(lectureMessage.getHoldEndTime()), String.valueOf(vo.getHoldEndTime())});
        changes.put("主讲人", new String[]{isForeign(lectureMessage.getForeignRelations()), isForeign(vo.getForeignRelations())});


        for (Map.Entry<String, String[]> entry : changes.entrySet()) {
            String key = entry.getKey();
            String[] values = entry.getValue();
            if (!values[0].equals(values[1])) {
                changeRecords.add(formatChangeRecord(key, values[0], values[1]));
            }
        }



   private String formatChangeRecord(String oldValue, String newValue, String label) {
        if (!oldValue.equalsIgnoreCase(newValue)) {
            return String.format("【%s由:%s 改为 %s】", label, oldValue, newValue);
        }
        return "";
    }

31:FileUtils常用的一些方法

package com.ly.cloud.util;

import cn.hutool.core.util.IdUtil;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;

/**
 * &#064;Author  
 * &#064;Date  Created in  2024/4/11 上午11:13
 * &#064;DESCRIPTION:   将 multipartFile --> file
 * &#064;Version  V1.0
 */

public class FileUtil {

    private final static Log logger = LogFactory.getLog(FileUtil.class);

    /**
     * 将输入流输出到页面
     */
    public static void writeFile(HttpServletResponse resp, InputStream inputStream) {
        OutputStream out = null;
        try {
            out = resp.getOutputStream();
            int len = 0;
            byte[] b = new byte[1024];
            while ((len = inputStream.read(b)) != -1) {
                out.write(b, 0, len);
            }
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * BufferedImage --->  InputStream
     */
    public static InputStream convertBufferedImageToInputStream(BufferedImage poster) throws IOException {
        // 创建一个字节输出流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ImageIO.write(poster, "png", byteArrayOutputStream);

        // 从字节输出流中获取字节数组
        byte[] imageData = byteArrayOutputStream.toByteArray();
        // 创建一个 ByteArrayInputStream 来提供 InputStream
        return new ByteArrayInputStream(imageData);
    }
    public static BufferedImage convert(String temp , BufferedImage backgroundImage) throws IOException {
        // 创建输出文件
        File outputFile = new File(temp); // 设置文件路径和文件名

        // 将 BufferedImage 写入文件
        ImageIO.write(backgroundImage, "jpg", outputFile);


        File file = new File(temp);
        FileInputStream payload = new FileInputStream(file);
        // step2: 压缩图片
        byte[] imageBytes = compressImage(payload);
        outputFile.delete();
        return byteArrayToBufferedImage(imageBytes);
    }

    // 将字节数组转换回 BufferedImage
    public static BufferedImage byteArrayToBufferedImage(byte[] imageData) throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(imageData);
        return ImageIO.read(byteArrayInputStream);
    }

    public static byte[] compressImage(InputStream payload) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] bytes;
        try {
            // scale:设置缩略图的缩放系数,大于0.0
            // outputQuality:设置将缩略图写入外部目标(如文件或输出流)时用于压缩缩略图的压缩算法的输出质量,该值是一个介于0.0f和1.0f之间的浮点数,其中0.0f表示最低质量,1.0f表示压缩编解码器应该使用的最大质量设置。
            Thumbnails.of(payload).scale(0.2f).outputQuality(0.2f).toOutputStream(outputStream);
            bytes = outputStream.toByteArray();
        } catch (IOException e) {
            bytes = new byte[]{};
        } finally {
            try {
                outputStream.close();
            } catch (IOException e) {
            }
        }

        return bytes;
    }

    /**
     * 将 multipartFile -->  fileInputStream
     */
    public static FileInputStream convertMultipartFileToInputStream(MultipartFile multipartFile) throws IOException {
        // 创建临时文件
        File tempFile = File.createTempFile("temp", null);

        // 将MultipartFile的数据写入临时文件
        try (FileOutputStream fos = new FileOutputStream(tempFile)) {
            fos.write(multipartFile.getBytes());
        }

        // 将临时文件转换为FileInputStream
        FileInputStream fileInputStream = new FileInputStream(tempFile);

        // 删除临时文件
        tempFile.delete();

        return fileInputStream;
    }

    /**
     * 将上传的 logo图片保存在本地;
     */
    public static File convertInputStreamToFile(InputStream inputStream, String fileName) throws IOException {
        String imagesPath = System.getProperty("user.dir") + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator + "static" + File.separator + "imagesLogo" + File.separator;
        logger.info("我的路径是:{}" + imagesPath);
        File directory = new File(imagesPath);
        // 如果目标文件夹不存在,则创建它
        if (!directory.exists()) {
            directory.mkdirs();
        }
        File file = new File(directory, fileName);
        try (OutputStream outputStream = Files.newOutputStream(file.toPath())) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
        }
//        deleteImage(imagesPath,fileName);
        directory.delete();
        return file;
    }

    /**
     * 删除图片
     */
    public static void deleteImage(String directoryPath, String imageName) {
        File directory = new File(directoryPath);
        if (!directory.exists() || !directory.isDirectory()) {
            return;
        }
        File imageFile = new File(directory, imageName);
        if (imageFile.exists() && imageFile.isFile()) {
            if (imageFile.delete()) {
                logger.info("删除图片成功!");
            } else {
                logger.error("删除图片失败!");
            }
        } else {
            System.out.println("Image file " + imageName + " does not exist.");
        }
    }

    /**
     * 修改图片的宽高 等比例扩大 海报背景图和 logo
     */
    public static byte[] resizeImage(byte[] imageData, int width, int height) throws IOException {
        ByteArrayInputStream bis = new ByteArrayInputStream(imageData);
        BufferedImage image = ImageIO.read(bis);

        Image resizedImage = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);

        BufferedImage bufferedResizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        bufferedResizedImage.getGraphics().drawImage(resizedImage, 0, 0, null);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(bufferedResizedImage, "jpg", bos);
        byte[] resizedImageData = bos.toByteArray();

        bis.close();
        bos.close();

        return resizedImageData;
    }

    public static void main(String[] args) throws IOException {
            System.out.println(IdUtil.simpleUUID());
            String sql = "SELECT COUNT(1) FROM ( SELECT A.BH AS id, D.ZJRXM AS lectureTeacher, A.JZLX AS lectureType, A.JZZT AS lectureStatus, A.JZMC AS lectureName, A.JTDD AS place, A.JBDD AS holdPlace, A.JBKSSJ AS holdStartTime, A.JBJSSJ AS holdEndTime, TO_CHAR(A.JBKSSJ, 'YYYY-MM-DD') AS lectureDate, CASE WHEN (A.JBJSSJ - A.JBKSSJ) < INTERVAL '1' DAY THEN TO_CHAR(A.JBKSSJ, 'YYYY-MM-DD HH24:MI') || ' - ' || TO_CHAR(A.JBJSSJ, 'HH24:MI') ELSE TO_CHAR(A.JBKSSJ, 'YYYY-MM-DD HH24:MI') || ' - ' || TO_CHAR(A.JBJSSJ, 'YYYY-MM-DD HH24:MI') END AS holdTime, A.ZBDW AS DWMC, B.BMMC AS organizer, A.JZBH AS lectureNumber, TO_CHAR(A.CZSJ,'YYYY-MM-DD HH:SS:MM') AS operationTime, C.YHMC AS publishPeople, A.WDSJBS AS wedaDataId, A.LSH AS serialNumber, A.ZBDZ AS liveBroadcastLink , A.HYLJ AS meetingLink FROM LY_SJS_WDJZ_JZXJ A LEFT JOIN LY_XTGL_BM B ON A.ZBDW = B.BMBH LEFT JOIN LY_XTGL_YH C ON A.FBRY = C.YHBH LEFT JOIN ( SELECT A.BH, LISTAGG(C.ZJRXM, ',') WITHIN GROUP (ORDER BY B.SFDYZJR, C.ZJRXM) AS ZJRXM FROM LY_SJS_WDJZ_JZXJ A LEFT JOIN LY_SJS_WDJZ_JZXJ_ZJRXX B ON A.BH = B.JZBH LEFT JOIN LY_SJS_WDJZ_ZJRGL C ON B.ZJRBH = C.BH WHERE A.SFSC = '1' AND B.SFSC = '1' AND C.SFSC = '1' AND C.SFYX = '1' GROUP BY A.BH ) D ON A.BH = D.BH WHERE A.SFSC = '1' AND A.LSH IN ( ? , ? , ? ) ORDER BY A.CZSJ DESC ) TOTAL";
            String params = "1846773565878845441(String), 1846778466331672577(String), 1847168643654893569(String)";
        System.out.println(concatSQL(sql,params));


    }
    public static String concatSQL(String sql,String params){
        String[] p = params.split(", ");
        System.out.println();
        for (String s : p) {
            System.out.println(s);
            String param = s.substring(0,s.lastIndexOf('('));
            sql = sql.replaceFirst("[?]","'"+param+"'");
        }
        return sql;
    }
}

32:AES加密工具类

package com.ly.cloud.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

import cn.hutool.core.io.resource.ClassPathResource;
import com.alibaba.druid.util.Base64;
import org.apache.commons.codec.binary.Hex;

public class AESUtils {

    private static final String AES = "AES";
    private static final String UTF8 = "UTF-8";
    private static final String CIPHERALGORITHM = "AES/ECB/PKCS5Padding";
    private static final String Key = "9!#95hsup*&$1zq7";

    /**
     * AES加密+Base64转码
     *
     * @param data 明文(16进制)
     * @param key  密钥
     * @return
     */
    public static String encrypt(String data, String key) {
        byte[] keyb = null;
        keyb = Base64.base64ToByteArray(key);
        SecretKeySpec sKeySpec = new SecretKeySpec(keyb, "AES");
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            e.printStackTrace();
        }
        try {
            cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        byte[] bjiamihou = null;
        String miwen = "";
        try {
            bjiamihou = cipher.doFinal(data.getBytes("utf-8"));
            // byte加密后
            miwen = Base64.byteArrayToBase64(bjiamihou);// 密文用base64加密
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return miwen;
    }

    /**
     * Base64解码 + AES解码
     *
     * @param data 密文 (16进制)
     * @param key  密钥
     * @return
     */
    public static String decrypt(String data, String key) {
        byte[] keyb = null;
        keyb = Base64.base64ToByteArray(key);
        byte[] miwen = Base64.base64ToByteArray(data);
        SecretKeySpec sKeySpec = new SecretKeySpec(keyb, "AES");
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            e.printStackTrace();
        }
        try {
            cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        byte[] bjiemihou = null;
        String mingwen = "";
        try {
            bjiemihou = cipher.doFinal(miwen);
            // byte加密后
            mingwen = new String(bjiemihou, "utf-8");
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return mingwen;
    }

    public static void main(String[] args) throws IOException {
//		String name = "{\"userId\":\"179135\",\"keyword\":\"讲座副标题\",\"pageNum\":\"1\",\"pageSize\":\"10\"}";

        String name = "{\"pageNum\":\"1\",\"pageSize\":\"10\"}";
		String encrypt = encrypt(name, "");
//		String name = "{\"userId\":\"179135\",\"pageNum\":\"1\",\"pageSize\":\"20\"}";

//		String name1 = "{\"userId\":\"179135\",\"organizerId\":\"02020\"}";
//		String name1 = "{\"pageNum\":\"1\",\"pageSize\":\"20\"}";

//        String encrypt = encrypt(name, "");
        System.out.println(encrypt);
//        System.out.println("---------");
//        System.out.println(decrypt("", ""));


    }


    /**
     * 生成base 64 作为AES加密后的密钥
     */
    public static String encrypts(String content) {
        try {
            byte[] encodeFormat = Key.getBytes();
            SecretKeySpec key = new SecretKeySpec(encodeFormat, AES);
            // Cipher对象实际完成加密操作
            Cipher cipher = Cipher.getInstance(CIPHERALGORITHM);
            // 加密内容进行编码
            byte[] byteContent = content.getBytes(UTF8);
            // 用密匙初始化Cipher对象
            cipher.init(Cipher.ENCRYPT_MODE, key);
            // 正式执行加密操作
            byte[] result = cipher.doFinal(byteContent);
            return Hex.encodeHexString(result);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    public static String dataToAES(String password, String data) {
        String hexStr = encrypt(data, password);
        return hexStr;
    }

    public static String AESToData(String password, String data) {
        return decrypt(data, password);
    }

}

33:ThreadLocal 的简单使用方法

    private static ThreadLocal<Integer> LECTURE_SPEAKER_INDEX = ThreadLocal.withInitial(() -> 1); // 默认值为 1



        int i = LECTURE_SPEAKER_INDEX.get();

        LECTURE_SPEAKER_INDEX.set(i+1);

  if (LECTURE_SPEAKER_INDEX.get().equals(2)) {
                LECTURE_SPEAKER_INDEX.set(1);
                return combiner.getCombinedImageStream();
            }

34:Iterator 遍历移除某个元素

   List<PosterSettings> posterSettingsList = lectureMessageMapper.getPosterSettings(posterId);
            // 获取绑定的海报设置的海报信息
            Iterator<PosterSettings> iterator = posterSettingsList.iterator();
            while (iterator.hasNext()) {
                PosterSettings vo = iterator.next();
                String lectureNumber = vo.getLectureNumber();
                LectureMessageVO lecture = lectureMessageMapper.getISHasLectureOver(lectureNumber);
                if ("5".equals(lecture.getLectureStatus())) {
                    // 移除 
                    iterator.remove();
                }
            }

35:如何获取今天周几的方法

 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd");
 LocalDate date = LocalDate.parse(place.substring(0, 10), formatter);
 // 获取星期几
 DayOfWeek dayOfWeek = date.getDayOfWeek();
 // 将 DayOfWeek 转换为中文
 String[] weekDays = {"", "周一", "周二", "周三", "周四", "周五", "周六", "周日"};
 String chineseDayOfWeek = weekDays[dayOfWeek.getValue()];

36:批量下载zip压缩包附件

    @Override
    public void downloadAttachmentsZip(BankCardCriteria criteria, HttpServletResponse response) throws IOException, InterruptedException {
        criteria.unPaged();
        criteria.setBankCardType(true);
        List<BankCardDTO> pageList = listPaged(criteria).getPage();

        List<CustomerBankCardExcelDTO> excelList = myBankCardMapper.dtoToCustomerExcel(pageList);
        List<CustomerBankCardExcelDTO> ordered = excelList.stream()
                .filter(excel -> excel != null && excel.getCredentialAttachmentId() != null)
                .collect(Collectors.toList());

        if (CollUtil.isEmpty(ordered)) {
            throw new CustomException("");
        }


        // ---------- 队列初始化 ----------
        BlockingQueue<ZipItem> queue = new LinkedBlockingQueue<>(20); // 可调节缓冲区大小
        int cores = Runtime.getRuntime().availableProcessors();

        // 生产者线程池(I/O密集型:从 数据库 读取附件)
        ThreadPoolExecutor producerPool = new ThreadPoolExecutor(
                cores * 2,
                cores * 4,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(200),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        CountDownLatch latch = new CountDownLatch(ordered.size());
        AtomicBoolean hasError = new AtomicBoolean(false);

        // ---------- 启动生产者(并发下载附件) ----------
        for (int i = 0; i < ordered.size(); i++) {
            final int index = i + 1;
            producerPool.submit(() -> {
                try {
                    if (hasError.get()) return; // 有错误则不再继续生产

                    CustomerBankCardExcelDTO dto = ordered.get(index - 1);
                    Long id = dto.getCredentialAttachmentId();
                    AttachmentDTO attachment = attachmentExternalClient.get(id);
                    if (attachment == null) {
                        log.warn("附件[id:{}]不存在", id);
                        return;
                    }

                    byte[] encoded = attachment.getContent();
                    if (encoded == null || encoded.length == 0) return;

                    String ext = getFileExtensionOrDefault(attachment.getName());
                    String rawName = String.join("_",
                            Optional.ofNullable(dto.getPartyCode()).orElse(""),
                            Optional.ofNullable(dto.getName()).orElse(""),
                            Optional.ofNullable(dto.getBankAccount()).orElse("")
                    ) + ext;

                    String fileNameWithIndex = String.format("%02d_", index) + sanitizeFileName(rawName);

                    // 放入队列
                    queue.put(new ZipItem(fileNameWithIndex, encoded));

                } catch (Exception e) {
                    log.error("附件下载失败", e);
                    hasError.set(true);
                } finally {
                    latch.countDown();
                }
            });
        }

        // ---------- 设置响应头 ----------
        String zipName = "客户卡片凭证附件.zip";
        FileUtils.setDownloadFileResponse(response, zipName);
        // ---------- 启动消费者(边写边出队) ----------
        try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()))) {
            Set<String> usedNames = new HashSet<>();

            while (true) {
                // 取出队列中的数据(阻塞等待)
                ZipItem item = queue.poll(1, TimeUnit.SECONDS);

                // 若所有生产者结束且队列为空,则跳出
                if (item == null && latch.getCount() == 0) break;
                if (item == null) continue;

                String finalName = makeUniqueFileName(item.fileName, usedNames);
                zos.putNextEntry(new ZipEntry(finalName));

                try (InputStream in = new ByteArrayInputStream(item.bytes)) {
                    byte[] buffer = new byte[8192];
                    int len;
                    while ((len = in.read(buffer)) != -1) {
                        zos.write(buffer, 0, len);
                    }
                }
                zos.closeEntry();
                response.flushBuffer();
            }
            zos.finish();
            log.info("ZIP 文件写入完成,共写入 {} 个文件", usedNames.size());
        } catch (Exception e) {
            hasError.set(true);
            log.error("ZIP 压缩或写入出错", e);
            throw new CustomException("附件打包下载失败");
        } finally {
            // 等待所有任务结束
            latch.await();
            producerPool.shutdownNow();
        }
    }

 

    @Override
    public void downloadAttachmentsZip(BankCardCriteria criteria, HttpServletResponse response) throws IOException {
        criteria.unPaged();
        criteria.setBankCardType(true);
        List<BankCardDTO> pageList = listPaged(criteria).getPage();

        // 并发拉取附件,按读取顺序写入 ZIP
        List<CustomerBankCardExcelDTO> excelList = myBankCardMapper.dtoToCustomerExcel(pageList);
        List<CustomerBankCardExcelDTO> ordered = excelList.stream()
                .filter(excel -> excel != null && excel.getCredentialAttachmentId() != null)
                .collect(Collectors.toList());

        if (CollUtil.isEmpty(ordered)) {
            throw new CustomException("暂无可下载的银行凭证附件");
        }
        log.info("开始下载银行凭证附件,数量:{}", ordered.size());

        // 使用 IntStream 来遍历索引和内容,并加上顺序编号
        List<CompletableFuture<ZipItem>> futures = new ArrayList<>(ordered.size());
        for (int i = 0; i < ordered.size(); i++) {
            final int index = i + 1;
            CompletableFuture<ZipItem> future = CompletableFuture.supplyAsync(() -> {
                CustomerBankCardExcelDTO dto = ordered.get(index - 1); // 获取对应的 DTO
                Long id = dto.getCredentialAttachmentId();
                AttachmentDTO attachment = attachmentExternalClient.get(id);
                if (attachment == null) {
                    log.warn("附件[id:{}]不存在", id);
                    return null;
                }
                byte[] encoded = attachment.getContent();
                String ext = getFileExtensionOrDefault(attachment.getName());
                String rawName = String.join("_",
                        Optional.ofNullable(dto.getPartyCode()).orElse(""),
                        Optional.ofNullable(dto.getName()).orElse(""),
                        Optional.ofNullable(dto.getBankAccount()).orElse("")
                ) + ext;
                // 在文件名前加上顺序编号,例如 "01_", "02_" 等
                String fileNameWithIndex = String.format("%02d_", index) + sanitizeFileName(rawName);
                return new ZipItem(fileNameWithIndex, encoded);
            }, commonPoolExecutor).exceptionally(throwable -> {
                logger.error("附件下载失败:", throwable);
                throw new CustomException("附件下载失败");
            });
            futures.add(future);
        }
        // 设置文件名
        String zipName = "客户卡片凭证附件.zip";
        FileUtils.setDownloadFileResponse(response, zipName);
        try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()))) {
            Set<String> usedNames = new HashSet<>();

            for (CompletableFuture<ZipItem> future : futures) {
                try {
                    ZipItem item = future.join();
                    if (item == null || item.bytes == null || item.bytes.length == 0) continue;

                    String finalName = makeUniqueFileName(item.fileName, usedNames);
                    zos.putNextEntry(new ZipEntry(finalName));

                    try (InputStream in = new ByteArrayInputStream(item.bytes)) {
                        byte[] buffer = new byte[8192];
                        int len;
                        while ((len = in.read(buffer)) != -1) {
                            zos.write(buffer, 0, len);
                        }
                    }

                    zos.closeEntry();
                } catch (Exception e) {
                    throw new CustomException("附件下载失败");
                }
            }
            zos.finish();
            response.flushBuffer();
        }
    }

后续。。。。持续更新

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大大怪~将军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值