Java工具类
本文最后更新于:2025年4月29日 下午
ChatGPT 流式输出
调用 ChatGPT 接口,采用流式输出:
先在控制台进行流式输出,下面是效果图:
本次使用个人 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,这是一种允许服务器向客户端推送事件的技术
下面使用java与vue3进行演示
全局异常类
整体结构:
- 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 文件夹,如下图
- 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,,首先我们要先分析文件上传有几步
- 文件校验(包括但不限于,图片的大小、图片的类型、图片是否为空、上传的是否是文件等)
- 将图片重命名,图片重命名又可分为以下几步
- 获取原来文件的后缀名,可以使用
file.getOriginalFilename()
获取原来的文件名 - 生成一个随机的新文件名,这里可以使用
UUID.randomUUID()
- 把新名称和原后缀名拼接起来作为新的文件名
- 获取原来文件的后缀名,可以使用
- 把图片上传的指定的目录下,我们这里讲的是 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 对象存储
创建 Bucket
- 名称:随便起
- 地域:想你的服务器在哪就选哪个(也是随便选)
- 存储类型:标准存储
- 冗余存储:关闭
- 版本控制:关闭
- 读写权限:公共读写
代码
- 导入依赖
<!-- 阿里云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文件中配置
:keyid与keysecret可以在阿里云的账号的 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插件
,并开启:
然后需要在IDEA
中连接数据库,在需要生成代码的表右键,选择MybatisX-Generator
:
在Generate Options
中,双击module path
选择我们要生成的项目模块,填写信息,点击Next:
选择Mybatis-Plus 3
,在注释选项选择: Comment、Lombok,选择模板,点击Finish
,在对应位置查找生成的代码:
分页插件
创建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 模块中。所有我们需要引入Hutool
和javax.mail
使用
- 引入依赖
<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>
- 邮件服务器配置
在 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 用户测试使用。
发送邮件
- 发送普通文本邮件,最后一个参数可选是否添加多个附件:
// 格式:
MailUtil.send(tos, subject, content, isHtml, files);
MailUtil.send("hutool@foxmail.com", "测试", "邮件来自Hutool测试", false);
- 发送 HTML 格式的邮件并附带附件,最后一个参数可选是否添加多个附件:
MailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true, FileUtil.file("d:/aaa.xml"));
- 群发邮件,可选 HTML 或普通文本,可选多个附件:
ArrayList<String> tos = CollUtil.newArrayList(
"person1@bbb.com",
"person2@bbb.com",
"person3@bbb.com",
"person4@bbb.com");
MailUtil.send(tos, "测试", "邮件来自Hutool群发测试", false);
发送邮件非常简单,只需一个方法即可搞定其中按照参数顺序说明如下:
- tos: 对方的邮箱地址,可以是单个,也可以是多个(Collection 表示)
- subject:标题
- content:邮件正文,可以是文本,也可以是 HTML 内容
- isHtml: 是否为 HTML,如果是,那参数 3 识别为 HTML 内容
- files: 可选:附件,可以为多个或没有,将 File 对象加在最后一个可变参数中即可
其他
- 自定义邮件服务器
除了使用配置文件定义全局账号以外,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);
- 使用 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
- 针对 QQ 邮箱和 Foxmail 邮箱的说明
(1) QQ 邮箱中 SMTP 密码是单独生成的授权码,而非你的 QQ 密码,至于怎么生成,见腾讯的帮助说明:什么是授权码,它又是如何设置?(opens new window)
使用帮助引导生成授权码后,配置如下即可:
pass = 你生成的授权码
(2) Foxmail 邮箱本质上也是 QQ 邮箱的一种别名,你可以在你的 QQ 邮箱中设置一个 foxmail 邮箱,不过配置上有所区别。在 Hutool 中user
属性默认提取你邮箱@前面的部分,但是对于 foxmail 邮箱是无效的,需要单独配置为与之绑定的 qq 号码或者XXXX@qq.com
的XXXX
。即:
host = smtp.qq.com
from = XXXX@foxmail.com
user = foxmail邮箱对应的QQ号码或者qq邮箱@前面部分
...
(3) 阿里云邮箱的user
是邮箱的完整地址,即xxx@aliyun.com
- 针对 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);
}
}
}