Java工具类

本文最后更新于:2025年4月29日 下午

ChatGPT 流式输出

调用 ChatGPT 接口,采用流式输出:

先在控制台进行流式输出,下面是效果图:

video

本次使用个人 chagpt 接口,可换乘 openai:

引入okhttp3

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>

输出到控制台:

@GetMapping("/fast")
public Result fastChat(String msg) {
    OkHttpClient client = new OkHttpClient();
    MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    String jsonInputString = "{" +
        "\"model\": \"chatglm2-6b\"," +
        "\"messages\": [{" +
        "\"role\": \"user\"," +
        "\"content\": \""+msg+"\"" +
        "}]," +
        "\"temperature\": 0.7," +
        "\"n\": 1," +
        "\"max_tokens\": 1024," +
        "\"stop\": []," +
        "\"stream\": true," +
        "\"presence_penalty\": 0," +
        "\"frequency_penalty\": 0" +
        "}";
    RequestBody body = RequestBody.create(JSON, jsonInputString);
    Request request = new Request.Builder()
        .url("http://192.168.123.170:2421/chat/fastchat")
        .post(body)
        .build();
    try (Response response = client.newCall(request).execute()) {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

        // 使用 InputStreamReader 和 BufferedReader 来处理UTF-8字符流
        try (InputStreamReader isr = new InputStreamReader(response.body().byteStream(), StandardCharsets.UTF_8);
             BufferedReader reader = new BufferedReader(isr)) {
            int intChar;
            while ((intChar = reader.read()) != -1) {
                char ch = (char) intChar;
                System.out.print(ch);
                Thread.sleep(50); // 延迟,以便一个接一个地输出字符
            }
        }
        return Result.ok("Streamed successfully!");
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.fail("fail");
}

SSE 与前端进行流式输出

可以看出 ChatGPT 的回答方式采用的是event-stream,及Server-Sent Events, SSE,这是一种允许服务器向客户端推送事件的技术

image-20230920172711829

下面使用javavue3进行演示

SSE-Video

全局异常类

整体结构:

- exception
	- GlobalExceptionHandler
	- GlobalException
- result
	- Result
	- ResultCodeEnum
  • 创建全局异常枚举类ResultCodeEnum
@Getter
public enum ResultCodeEnum {

	SUCCESS(200, "成功"),
	FAIL(201, "失败"),
	SERVICE_ERROR(2012, "服务异常"),
	DATA_ERROR(204, "数据异常"),
	ILLEGAL_REQUEST(205, "非法请求"),
	REPEAT_SUBMIT(206, "重复提交"),

	LOGIN_AUTH(208, "未登陆"),
	PERMISSION(209, "没有权限");

	private final Integer code;

	private final String message;

	private ResultCodeEnum(Integer code, String message) {
		this.code = code;
		this.message = message;
	}
}
  • 创建Result
@Data
public class Result<T> {

	// 状态码
	private Integer code;

	// 信息
	private String message;

	// 数据
	private T data;

	// 构造私有化
	private Result() {
	}

	// 设置数据,返回方法
	public static <T> Result<T> build(T data, Integer code, String message) {
		// 创建Result对象,设置值,返回对象
		Result<T> result = new Result<>();
		// 判断data是否为空
		if (data != null) {
			// 设置数据到result对象中
			result.setData(data);
		}
		// 设置其他值
		result.setCode(code);
		result.setMessage(message);
		// 返回结果
		return result;
	}

	// 成功
	public static <T> Result<T> ok(T data) {
		return build(data, ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS.getMessage());
	}

	// 失败
	public static <T> Result<T> fail(T data) {
		return build(data, ResultCodeEnum.FAIL.getCode(), ResultCodeEnum.FAIL.getMessage());
	}
}
  • 创建统一异常处理类GlobalExceptionHandler
// AOP面向切面
@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(Exception.class)
	@ResponseBody
	public Result error(Exception e){
		e.printStackTrace();
		return Result.fail(null);
	}

	/**
	 * 自定义异常处理方法
	 * @param e
	 * @return
	 */
	@ExceptionHandler(GlobalException.class)
	@ResponseBody
	public Result error(GlobalException e){
		return Result.build(null,e.getCode(),e.getMessage());
	}
}
  • 创建GlobalException
@Data
public class GlobalException extends RuntimeException{

	//异常状态码
	private Integer code;

	/**
	 * 通过状态码和错误消息创建异常对象
	 * @param message
	 * @param code
	 */
	public GlobalException(String message, Integer code) {
		super(message);
		this.code = code;
	}

	/**
	 * 接收枚举类型对象
	 * @param resultCodeEnum
	 */
	public GlobalException(ResultCodeEnum resultCodeEnum) {
		super(resultCodeEnum.getMessage());
		this.code = resultCodeEnum.getCode();
	}

	@Override
	public String toString() {
		return "GlobalException{" +
				"code=" + code +
				", message=" + this.getMessage() +
				'}';
	}
}

RedisUtil.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }


    // ============================String=============================

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 普通缓存放入并设置时间
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 递增
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }


    /**
     * 递减
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }


    // ================================Map=================================

    /**
     * HashGet
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * HashSet 并设置时间
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }


    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }


    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }


    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */

    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将list放入缓存
     * 从头部插入leftPush
     * @param key   键
     * @param value 值
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().leftPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将list放入缓存
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */

    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */

    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

上传图片

本地上传

本地路径方式

  • 在 Resource 目录下有一个 upload.html 文件,和一个 static 文件夹,static 下又有一个 images 文件夹,如下图

image-20230626133928623

  • upload.html

这里主要就是一个 form 表单,用来提交数据,但是要注意的是我这个表单用了 post 和 enctype=”multipart/form-data”,以及 input 的类型是 file

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>上传图片</title>
  </head>
  <body>
    <form action="/uplaod" method="post" enctype="multipart/form-data">
      <input type="file" name="file" value="请选择你要上传的图片" />
      <input type="submit" value="上传" />
    </form>
  </body>
</html>
  • UploadController

为了方便演示,我把业务都放在 controller,,首先我们要先分析文件上传有几步

  1. 文件校验(包括但不限于,图片的大小、图片的类型、图片是否为空、上传的是否是文件等)
  2. 将图片重命名,图片重命名又可分为以下几步
    • 获取原来文件的后缀名,可以使用file.getOriginalFilename()获取原来的文件名
    • 生成一个随机的新文件名,这里可以使用UUID.randomUUID()
    • 把新名称和原后缀名拼接起来作为新的文件名
  3. 把图片上传的指定的目录下,我们这里讲的是 Resource,就以 Resource 为例
    • new ApplicationHome(this.getClass())可以获取当前程序运行的主页
    • 我们知道 Java 程序都是运行的.class 字节码文件,所以getDir()获取文件夹位置其实是.class 字节码文件的位置,需要使用getParentFile()两次回到项目的主目录
    • 获取到主目录的绝对路径拼接上从这里到 Resource 下的 images
    • 最后通过file.transferTo(new File(path));把文件上传到 Resource 下的 images 目录,并且返回一个 url 地址
package com.wang.upload.controller;

import org.springframework.boot.system.ApplicationHome;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

/**
 * @version: java version 1.8
 * @Author: WWJ
 * @description:
 * @date: 2023-02-17 23:41
 */
@RestController
public class UploadController {
    @PostMapping("/upload")
    public String UploadFile(MultipartFile file) {
        if (file.isEmpty()) {
            return "上传文件为空";
        }
        String originalFilename = file.getOriginalFilename();
        //获取原来的文件名和后缀
        String ext = "." + originalFilename.split("\\.")[1];
        //生成一个新的文件名(以防有重复的名字存在导致被覆盖)
        String uuid = UUID.randomUUID().toString().replace("-", "");
        String newName = uuid + ext;
        ApplicationHome applicationHome = new ApplicationHome(this.getClass());
        String pre = applicationHome.getDir().getParentFile().getParentFile().getAbsolutePath() +
                "\\src\\main\\resources\\static\\images\\";
        String path = pre + newName;
        try {
            file.transferTo(new File(path));
        } catch (IOException e) {
            e.printStackTrace();
            return "上传失败";
        }
        return path;
    }
}

云服务器上传

OSS 对象存储

对象存储 OSS

  1. 创建 Bucket

    • 名称:随便起
    • 地域:想你的服务器在哪就选哪个(也是随便选)
    • 存储类型:标准存储
    • 冗余存储:关闭
    • 版本控制:关闭
    • 读写权限:公共读写
  2. 代码

    • 导入依赖
    <!-- 阿里云oss依赖 -->
    <dependency>
    	<groupId>com.aliyun.oss</groupId>
    	<artifactId>aliyun-sdk-oss</artifactId>
    	<version>3.9.1</version>
    </dependency>
    <!-- 日期工具栏依赖 -->
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
        <version>2.10.1</version>
    </dependency>

    applcation.yml文件中配置keyidkeysecret可以在阿里云的账号的 AccessKey 管理中,进行创建

    aliyun:
      endpoint: oss-cn-shanghai.aliyuncs.com
      keyid: xxxxxxxx
      keysecret: xxxxxxxxxxxxxxxx
      bucketname: xxxxxxx

    如果创建的是子用户,一定要赋予该角色 OSS 权限控制

    FileUploadController

    @Api(tags = "文件上传接口")
    @RestController
    @RequestMapping("/upload")
    public class FileUploadController {
    	@Autowired
    	private FileUploadService fileUploadService;
    
    	@ApiOperation("图片上传")
    	@PostMapping("/fileUpload")
    	public Result fileUpload(MultipartFile file) {
    		String url = fileUploadService.uploadFile(file);
    		return Result.ok(url);
    	}
    }

    FileUploadServiceImpl

    @Service
    public class FileUploadServiceImpl implements FileUploadService {
    	@Value("${aliyun.endpoint}")
    	private String endPoint;
    	@Value("${aliyun.keyid}")
    	private String accessId;
    	@Value("${aliyun.keysecret}")
    	private String secreKey;
    	@Value("${aliyun.bucketname}")
    	private String bucketName;
    
    	@Override
    	public String uploadFile(MultipartFile file) {
    		OSS ossClient = new OSSClientBuilder().build(endPoint, accessId, secreKey);
    		try {
    			// 上传文件流。
    			InputStream inputStream = file.getInputStream();
    			String fileName = file.getOriginalFilename();
    			//生成随机唯一值,使用uuid,添加到文件名称里面
    			String uuid = UUID.randomUUID().toString().replaceAll("-","");
    			fileName = uuid+fileName;
    			//按照当前日期,创建文件夹,上传到创建文件夹里面
    			//  2023/02/02/01.jpg
    			String timeUrl = new DateTime().toString("yyyy/MM/dd");
    			fileName = timeUrl+"/"+fileName;
    			//调用方法实现上传
    			ossClient.putObject(bucketName, fileName, inputStream);
    			// 关闭OSSClient。
    			ossClient.shutdown();
    			//上传之后文件路径
    			// https://wjwang.oss-cn-shanghai.aliyuncs.com/01.jpg
    			return "https://"+bucketName+"."+endPoint+"/"+fileName;
    		} catch (IOException e) {
    			e.printStackTrace();
    			return null;
    		}
    	}
    }

文件大小报错异常

在 SpringBoot 的 application.yml 中配置

spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 1000MB

MyBatis Plus

代码生成器

使用MyBatis Plus,安装依赖

<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>

<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.0</version>
</dependency>

CodeGet.java

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

/**
 * @author Wjwang
 * @description 代码生成工具
 * @date 2023/9/23 16:55:01
 */
public class CodeGet {

	public static void main(String[] args) {

		// 1、创建代码生成器
		AutoGenerator mpg = new AutoGenerator();

		// 2、全局配置
		// 全局配置
		GlobalConfig gc = new GlobalConfig();
		gc.setOutputDir("D:\\Project\\code\\test\\service\\service-admin\\src\\main\\java");

		gc.setServiceName("%sService");    //去掉Service接口的首字母I
		gc.setAuthor("Wjwang");
		gc.setOpen(false);
		mpg.setGlobalConfig(gc);

		// 3、数据源配置
		DataSourceConfig dsc = new DataSourceConfig();
		dsc.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useSSL=false");
		dsc.setDriverName("com.mysql.cj.jdbc.Driver");
		dsc.setUsername("root");
		dsc.setPassword("123456");
		dsc.setDbType(DbType.MYSQL);
		mpg.setDataSource(dsc);

		// 4、包配置
		PackageConfig pc = new PackageConfig();
		pc.setParent("com.wjwang");
		pc.setModuleName("admin"); //模块名
		pc.setController("controller");
		pc.setService("service");
		pc.setMapper("mapper");
		mpg.setPackageInfo(pc);

		// 5、策略配置
		StrategyConfig strategy = new StrategyConfig();

		strategy.setInclude("user_info","admin_info");

		strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略

		strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
		strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

		strategy.setRestControllerStyle(true); //restful api风格控制器
		strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

		mpg.setStrategy(strategy);

		// 6、执行
		mpg.execute();
	}
}

MybatisX 代码生成

首先我们需要在IDEA中安装MyBatisX插件,并开启:

image-202407271847988

然后需要在IDEA中连接数据库,在需要生成代码的表右键,选择MybatisX-Generator

image-20240727190235627

Generate Options中,双击module path选择我们要生成的项目模块,填写信息,点击Next

image-20240727191146269

选择Mybatis-Plus 3,在注释选项选择: Comment、Lombok,选择模板,点击Finish,在对应位置查找生成的代码:

image-20240727191633995

分页插件

创建config.MybatisPlusConfig,编写MybatisPlusConfig.java

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * MybatisPlus配置类
 */
@EnableTransactionManagement
@Configuration
@MapperScan("com.wjwang.*.mapper") // 根据项目结构引入
public class MybatisPlusConfig {

	/**
	 * mp插件
	 */
	@Bean
	public MybatisPlusInterceptor optimisticLockerInnerInterceptor() {
		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
		//向Mybatis过滤器链中添加分页拦截器
		interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
		return interceptor;
	}
}

在 Json 格式中插入当前时间

// payload 为JSON格式数据
String payload = message.getPayload().toString();

获取当前时间
Date date = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = formatter.format(date);

JSONObject jsonObject = JSON.parseObject(payload);JSON中插入k,v对
jsonObject.put("time", time);
// 存入Redis缓存数据库中
redisUtil.lSet("sensor", jsonObject.toString());

解析 JSON 格式数据

JSON 格式数据,通过 key,获取 value

Object massage = redisUtil.lGetIndex("sensor", 0);
JSONObject jsonObject = JSON.parseObject(massage.toString());
String sn = jsonObject.getString("SN");
BigDecimal temp = jsonObject.getBigDecimal("Temp");
BigDecimal humidity = jsonObject.getBigDecimal("Humidity");
String time = jsonObject.getString("time");

启动 banner

${AnsiColor.BRIGHT_YELLOW}${AnsiStyle.BOLD}
                    _ooOoo_
                   o8888888o
                   88" . "88
                   (| ^_^ |)
                   O\  =  /O
                ____/`---'\____
              .'  \\|     |//  `.
             /  \\|||  :  |||//  \
            /  _||||| -:- |||||-  \
            |   | \\\  -  /// |   |
            | \_|  ''\---/''  |   |
            \  .-\__  `-`  ___/-. /
          ___`. .'  /--.--\  `. . ___
        ."" '<  `.___\_<|>_/___.'  >'"".
      | | :  `- \`.;`\ _ /`;.`/ - ` : | |
      \  \ `-.   \_ __\ /__ _/   .-` /  /
========`-.____`-.___\_____/___.-`____.-'========
                     `=---='
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       佛祖保佑          永无BUG         永不修改
${AnsiColor.CYAN}${AnsiStyle.BOLD}
::  Java:${java.version}
::  Spring Boot Version: ${spring-boot.version}
::  Application Version: ${application.version}
${AnsiStyle.NORMAL}

邮件工具

在 Java 中发送邮件主要依靠 javax.mail 包,但是由于使用比较繁琐,因此我们使用Hutool,针对其做了封装。由于依赖第三方包,因此将此工具类归类到 extra 模块中。所有我们需要引入Hutooljavax.mail

使用

  1. 引入依赖
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.26</version>
</dependency>
<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.6.2</version>
</dependency>
  1. 邮件服务器配置

在 classpath(在标准 Maven 项目中为src/main/resources)的 config 目录下新建mail.setting文件,最小配置内容如下,在此配置下,smtp 服务器和用户名都将通过from参数识别:

# 发件人(必须正确,否则发送失败)
from = hutool@yeah.net
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
pass = q1w2e3

有时候一些非标准邮箱服务器(例如企业邮箱服务器)的 smtp 地址等信息并不与发件人后缀一致,端口也可能不同,此时 Hutool 可以提供完整的配置文件:

# 邮件服务器的SMTP地址,可选,默认为smtp.<发件人邮箱后缀>
host = smtp.yeah.net
# 邮件服务器的SMTP端口,可选,默认25
port = 25
# 发件人(必须正确,否则发送失败)
from = hutool@yeah.net
# 用户名,默认为发件人邮箱前缀
user = hutool
# 密码(注意,某些邮箱需要为SMTP服务单独设置授权码,详情查看相关帮助)
pass = q1w2e3

注意
邮件服务器必须支持并打开 SMTP 协议,详细请查看相关帮助说明
配置文件的样例中提供的是我专门为测试邮件功能注册的 yeah.net 邮箱,帐号密码公开,供 Hutool 用户测试使用。

发送邮件

  1. 发送普通文本邮件,最后一个参数可选是否添加多个附件:
// 格式:
MailUtil.send(tos, subject, content, isHtml, files);
MailUtil.send("hutool@foxmail.com", "测试", "邮件来自Hutool测试", false);
  1. 发送 HTML 格式的邮件并附带附件,最后一个参数可选是否添加多个附件:
MailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true, FileUtil.file("d:/aaa.xml"));
  1. 群发邮件,可选 HTML 或普通文本,可选多个附件:
ArrayList<String> tos = CollUtil.newArrayList(
	"person1@bbb.com",
	"person2@bbb.com",
	"person3@bbb.com",
	"person4@bbb.com");

MailUtil.send(tos, "测试", "邮件来自Hutool群发测试", false);

发送邮件非常简单,只需一个方法即可搞定其中按照参数顺序说明如下:

  1. tos: 对方的邮箱地址,可以是单个,也可以是多个(Collection 表示)
  2. subject:标题
  3. content:邮件正文,可以是文本,也可以是 HTML 内容
  4. isHtml: 是否为 HTML,如果是,那参数 3 识别为 HTML 内容
  5. files: 可选:附件,可以为多个或没有,将 File 对象加在最后一个可变参数中即可

其他

  1. 自定义邮件服务器

除了使用配置文件定义全局账号以外,MailUtil.send方法同时提供重载方法可以传入一个MailAccount对象,这个对象为一个普通 Bean,记录了邮件服务器信息。

MailAccount account = new MailAccount();
account.setHost("smtp.yeah.net");
account.setPort("25");
account.setAuth(true);
account.setFrom("hutool@yeah.net");
account.setUser("hutool");
account.setPass("q1w2e3");

MailUtil.send(account, CollUtil.newArrayList("hutool@foxmail.com"), "测试", "邮件来自Hutool测试", false);
  1. 使用 SSL 加密方式发送邮件 在使用 QQ 或 Gmail 邮箱时,需要强制开启 SSL 支持,此时我们只需修改配置文件即可:
# 发件人(必须正确,否则发送失败),“小磊”可以任意变更,<>内的地址必须唯一,以下方式也对
# from = hutool@yeah.net
from = 小磊<hutool@yeah.net>
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
pass = q1w2e3
# 使用SSL安全连接
sslEnable = true

在原先极简配置下只需加入sslEnable即可完成 SSL 连接,当然,这是最简单的配置,很多参数根据已有参数已设置为默认。完整的配置文件如下:

# 邮件服务器的SMTP地址
host = smtp.yeah.net
# 邮件服务器的SMTP端口
port = 465
# 发件人(必须正确,否则发送失败)
from = hutool@yeah.net
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
user = hutool
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
pass = q1w2e3
#使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
starttlsEnable = true

# 使用SSL安全连接
sslEnable = true
# 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
socketFactoryClass = javax.net.ssl.SSLSocketFactory
# 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
socketFactoryFallback = true
# 指定的端口连接到使用的套接字工厂。如果没有设置,将使用默认端口465
socketFactoryPort = 465

# SMTP超时时长,单位毫秒,缺省值不超时
timeout = 0
# Socket连接超时值,单位毫秒,缺省值不超时
connectionTimeout = 0
  1. 针对 QQ 邮箱和 Foxmail 邮箱的说明

(1) QQ 邮箱中 SMTP 密码是单独生成的授权码,而非你的 QQ 密码,至于怎么生成,见腾讯的帮助说明:什么是授权码,它又是如何设置?(opens new window)

使用帮助引导生成授权码后,配置如下即可:

pass = 你生成的授权码

(2) Foxmail 邮箱本质上也是 QQ 邮箱的一种别名,你可以在你的 QQ 邮箱中设置一个 foxmail 邮箱,不过配置上有所区别。在 Hutool 中user属性默认提取你邮箱@前面的部分,但是对于 foxmail 邮箱是无效的,需要单独配置为与之绑定的 qq 号码或者XXXX@qq.comXXXX。即:

host = smtp.qq.com
from = XXXX@foxmail.com
user = foxmail邮箱对应的QQ号码或者qq邮箱@前面部分
...

(3) 阿里云邮箱的user是邮箱的完整地址,即xxx@aliyun.com

  1. 针对 QQ 邮箱(foxmail)PKIX path building failed 错误(since 5.6.4)

部分用户反馈发送邮件时会遇到错误:

cn.hutool.extra.mail.MailException: MessagingException: Could not connect to SMTP host: smtp.qq.com, port: 465
...
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

这个错误可能是需要 SSL 验证造成的,我们可以手动跳过这个验证:

MailAccount mailAccount = new MailAccount();
mailAccount.setAuth(true);
mailAccount.setSslEnable(true);
...
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);
mailAccount.setCustomProperty("mail.smtp.ssl.socketFactory", sf);

Mail mail = Mail.create(mailAccount)
    .setTos("xx@xx.com")
	.setTitle("邮箱验证")
	.setContent("您的验证码是:<h3>2333</h3>")
	.setHtml(true)
	.send();

WebSocket

1.在 SpringBoot 的 pom.xml 中添加依赖:

<!-- websocket -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2.在配置中加入WebSocketConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

    /**
     * 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

3.新建类WebSocketServer.java

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @author websocket服务
 */
@ServerEndpoint(value = "/imserver/{username}")
@Component
public class WebSocketServer {

    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * 记录当前在线连接数
     */
    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        sessionMap.put(username, session);
        log.info("有新用户加入,username={}, 当前在线人数为:{}", username, sessionMap.size());
        JSONObject result = new JSONObject();
        JSONArray array = new JSONArray();
        result.set("users", array);
        for (Object key : sessionMap.keySet()) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.set("username", key);
            // {"username", "zhang", "username": "admin"}
            array.add(jsonObject);
        }
//        {"users": [{"username": "zhang"},{ "username": "admin"}]}
        sendAllMessage(JSONUtil.toJsonStr(result));  // 后台发送消息给所有的客户端
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session, @PathParam("username") String username) {
        sessionMap.remove(username);
        log.info("有一连接关闭,移除username={}的用户session, 当前在线人数为:{}", username, sessionMap.size());
    }

    /**
     * 收到客户端消息后调用的方法
     * 后台收到客户端发送过来的消息
     * onMessage 是一个消息的中转站
     * 接受 浏览器端 socket.send 发送过来的 json数据
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("username") String username) {
        log.info("服务端收到用户username={}的消息:{}", username, message);
        JSONObject obj = JSONUtil.parseObj(message);
        String toUsername = obj.getStr("to"); // to表示发送给哪个用户,比如 admin
        String text = obj.getStr("text"); // 发送的消息文本  hello
        // {"to": "admin", "text": "聊天文本"}
        Session toSession = sessionMap.get(toUsername); // 根据 to用户名来获取 session,再通过session发送消息文本
        if (toSession != null) {
            // 服务器端 再把消息组装一下,组装后的消息包含发送人和发送的文本内容
            // {"from": "zhang", "text": "hello"}
            JSONObject jsonObject = new JSONObject();
            jsonObject.set("from", username);  // from 是 zhang
            jsonObject.set("text", text);  // text 同上面的text
            this.sendMessage(jsonObject.toString(), toSession);
            log.info("发送给用户username={},消息:{}", toUsername, jsonObject.toString());
        } else {
            log.info("发送失败,未找到用户username={}的session", toUsername);
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    /**
     * 服务端发送消息给客户端
     */
    private void sendMessage(String message, Session toSession) {
        try {
            log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }

    /**
     * 服务端发送消息给所有客户端
     */
    private void sendAllMessage(String message) {
        try {
            for (Session session : sessionMap.values()) {
                log.info("服务端给客户端[{}]发送消息{}", session.getId(), message);
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);

        }
    }
}

Java工具类
https://junyyds.top/2023/05/17/Java工具类/
作者
Phils
发布于
2023年5月17日
更新于
2025年4月29日
许可协议