Merge pull request 'dev' (#10) from dev into master

Reviewed-on: #10
This commit is contained in:
bunny 2024-08-22 10:11:56 +08:00
commit b1f45b098f
34 changed files with 513 additions and 482 deletions

412
ReadMe.md Normal file
View File

@ -0,0 +1,412 @@
# Spring微服务模板
每个服务下都有`Dockerfile`文件,几乎是写好的模板,如果要添加在这基础上即可。
- 基础包有
- 微服务基础功能openFein
- 邮件发送
- WebSocket
- Minio
- Redis
- rabbitMq
- velocity
- IP地址查询
- knife4j
- 数据库多源配置
- 启动类一共有两个,网关是必不可少的
1. web端
2. admin端
3. gateway网关
## 基础配置
### 微服务模块
微服务请求其它模块无法从线程中获取到值将用户Token放在请求头中这样从请求头中获取Token即可
`common/common-service/src/main/java/cn/bunny/common/service/interceptor/UserTokenFeignInterceptor.java`
```java
/**
* * 微服务请求其它模块找不到Token无法从线程中获取值
* 传递请求头,在微服务中
*/
public class UserTokenFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert requestAttributes != null;
HttpServletRequest request = requestAttributes.getRequest();
String token = request.getHeader("token");
requestTemplate.header("token", token);
}
}
```
### 配置文件详情
### 打包命令
命令解释:清理之前内容,打包,使用生产环境,跳过测试
```shell
mvn clean package -Pprod -DskipTests
```
#### SpringBoot配置文件
在开发中需要使用到开发环境、上线需要生产环境,在环境中设置`@profiles.active@`可以根据不同环境切换
```yaml
spring:
profiles:
active: @profiles.active@
application:
name: service-admin
```
只需要在IDE中勾选相关环境即可
![image-20240822093552802](./images/image-20240822093552802.png)
> 注意!!!
>
> 因为Java每次启动都需要生成target有缓存在里面很有可能明明选择了配置但是没有生效的情况。
>
> 解决办法就是,每次改变环境执行`mvn clean`或者点击IDE中`mvn clean`
>
> ![image-20240822093803078](./images/image-20240822093803078.png)
### Dockerfile配置
如果需要访问宿主机文件目录这个是Docker内部地址
```dockerfile
# 程序内部挂在目录
VOLUME /home/server/uploads
```
#### IDE中配置
![image-20240822094021339](./images/image-20240822094021339.png)
![image-20240822094000542](./images/image-20240822094000542.png)
### 整体返回响应
整体返回响应如下
```java
// 状态码
private Integer code;
// 返回消息
private String message;
// 返回数据
private T data;
```
![image-20240822092441020](./images/image-20240822092441020.png)
和分页返回
```java
/**
* 封装分页查询结果
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ResultPage<T> implements Serializable {
// 当前页
private Integer pageNo;
// 每页记录数
private Integer pageSize;
// 总记录数
private long total;
// 当前页数据集合
private List<T> list;
}
```
以及常用的枚举状态码(展示部分)
![image-20240822092510151](./images/image-20240822092510151.png)
### 多数据库源配置
开发中有时会使用到多个数据库源这个配置也是来自MybatisPlus官方推荐的库
```xml
<!-- 多数据库源插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.3.1</version>
</dependency>
```
#### 配置简介
如果不需要多数据库,移除包之后将注释的部分放开,删除`dynamic`节点以下内容
```java
datasource:
# type: com.zaxxer.hikari.HikariDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://${bunny.datasource.host}:${bunny.datasource.port}/${bunny.datasource.sqlData}?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true
# username: ${bunny.datasource.username}
# password: ${bunny.datasource.password}
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
grace-destroy: false #是否优雅关闭数据源默认为false设置为true时关闭数据源时如果数据源中还存在活跃连接至多等待10s后强制关闭
datasource:
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${bunny.datasource.host}:${bunny.datasource.port}/${bunny.datasource.sqlData}?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true
username: ${bunny.datasource.username}
password: ${bunny.datasource.password}
aop:
enabled: true
```
## 中间件配置
### mybatis-plus
#### 配置详情
配置乐观锁、防止全表删除、最大分页100页
`common/service-utils/src/main/java/cn/bunny/common/service/config/MybatisPlusConfig.java`
```java
/**
* Mybatis-Plus配置类
*/
@EnableTransactionManagement
@Configuration
@Slf4j
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(100L);// 设置最大分页为100
interceptor.addInnerInterceptor(paginationInnerInterceptor);
// 乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防止全表删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
```
如果如要插入和修改时,自定义时间或者其它可以在这设置
`common/service-utils/src/main/java/cn/bunny/common/service/config/MyBatisPlusFieldConfig.java`
```java
/**
* 配置MP在修改和新增时的操作
*/
@Component
public class MyBatisPlusFieldConfig implements MetaObjectHandler {
/**
* 使用mp做添加操作时候这个方法执行
*/
@Override
public void insertFill(MetaObject metaObject) {
// 设置属性值
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("deleteStatus", 1, metaObject);
if (BaseContext.getUsername() != null) {
this.setFieldValByName("createBy", BaseContext.getUsername(), metaObject);
this.setFieldValByName("updateBy", BaseContext.getUsername(), metaObject);
}
}
/**
* 使用mp做修改操作时候这个方法执行
*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("updateBy", BaseContext.getUsername(), metaObject);
}
}
```
### Redis
#### 配置详情
分别设置了过期30天、1小时、3分钟
`common/service-utils/src/main/java/cn/bunny/common/service/config/RedisConfiguration.java`
```java
/**
* * 配置Redis过期时间30天
* 解决cache(@Cacheable)把数据缓存到redis中的value是乱码问题
*/
@Bean
@Primary
@SuppressWarnings("all")
public CacheManager cacheManagerWithMouth(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofDays(30));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
/**
* * 配置redis过期时间3分钟
*
* @param factory
* @return
*/
@Bean
@SuppressWarnings("all")
public CacheManager cacheManagerWithMinutes(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(3));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
/**
* * 配置Redis过期时间1小时
*
* @param factory
* @return
*/
@Bean
@SuppressWarnings("all")
public CacheManager cacheManagerWithHours(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofHours(1));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
```
#### 使用详情
如果需要指定Redis配置`cacheManager="Redis配置中方法名"`
使用springCache只需要在方法上加上下面代码
```java
@Cacheable(value = "TaskStatistics", key = "'ByDepartment::'+#departmentName",
cacheManager = "cacheManagerWithMinutes")
```
### Minio
#### 配置详情
Minio没有给出SpringBoot的配置文件下面是自定义实现
`module/module-minio/src/main/java/cn/bunny/module/minio/properties/MinioProperties.java`
在配置文件中有这4个配置字段
```java
@Configuration
@ConfigurationProperties(prefix = "bunny.minio")
@ConditionalOnProperty(name = "bunny.minio.bucket-name")// 当属性有值时这个配置才生效
@Data
@Slf4j
public class MinioProperties {
private String endpointUrl;
private String accessKey;
private String secretKey;
private String bucketName;
@Bean
public MinioClient minioClient() {
log.info("注册MinioClient...");
return MinioClient.builder().endpoint(endpointUrl).credentials(accessKey, secretKey).build();
}
}
```
在项目中加入了Minio常用工具方法对Minio二次封装
![image-20240822091720866](./images/image-20240822091720866.png)
### 邮箱发送
邮箱发送配置的是动态邮件,发件人是动态的不是写死在配置文件中
![image-20240822091810597](./images/image-20240822091810597.png)
#### 配置文件
如果不需要动态配置可以在`SpringBoot`配置文件中加入下面的配置
```properties
mail:
host: smtp.qq.com # 邮箱地址
port: 465 # 邮箱端口号
username: xxx@qq.com # 设置发送邮箱
password: xx # 如果是纯数字要加引号
default-encoding: UTF-8 # 设置编码格式
protocol: smtps
properties:
mail:
debug: true # 是否开启debug模式发送邮件
smtp:
auth: true
connectionTimeout: 5000 # 设置连接延迟
timeout: 5000 # 延迟时间
writeTimeout: 5000 # 写入邮箱延迟
allow8BitMime: true
sendPartial: true
ssl:
enabled: true # 是否开启SSL连接
socketFactory:
class: javax.net.ssl.SSLSocketFactory # 必要设置!!!
```
### SpringSecurity
因为项目做的是开发模板在admin模板中集成了安全框架
`module/spring-security/src/main/java/cn/bunny/security/config/WebSecurityConfig.java`
在这个文件最下面是排除路径不需要Security检测的路径根据自己需求进行修改因为整合了knife4j在测试时需要放开swagger配置响应请求等。
```java
/**
* * 排出鉴定路径
*
* @return WebSecurityCustomizer
*/
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
String[] annotations = {"/", "/test/**", "/diagram-viewer/**", "/editor-app/**", "/*.html",
"/*/*/noAuth/**", "/*/noAuth/**", "/favicon.ico", "/swagger-resources/**", "/webjars/**",
"/v3/**", "/swagger-ui.html/**", "/doc.html"};
return web -> web.ignoring().requestMatchers(annotations);
}
```

View File

@ -7,6 +7,7 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* * 微服务请求其它模块找不到Token无法从线程中获取值
* 传递请求头在微服务中
*/
public class UserTokenFeignInterceptor implements RequestInterceptor {

View File

@ -19,12 +19,10 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
log.info("MybatisPlusInterceptor===>注入Mybatis-Plus配置...");
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(100L);// ? 设置最大分页为100
paginationInnerInterceptor.setMaxLimit(100L);// 设置最大分页为100
interceptor.addInnerInterceptor(paginationInnerInterceptor);
// 乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

View File

@ -13,6 +13,7 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@ -55,13 +56,13 @@ public class RedisConfiguration {
}
/**
* * 配置Redis过期时间30天
* 解决cache(@Cacheable)把数据缓存到redis中的value是乱码问题
*/
@Bean
@Primary
@SuppressWarnings("all")
public CacheManager cacheManager(RedisConnectionFactory factory) {
log.info("RedisConfiguration===>解决cache(@Cacheable)把数据缓存到redis中的value是乱码问题");
public CacheManager cacheManagerWithMouth(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
@ -71,6 +72,43 @@ public class RedisConfiguration {
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
/**
* * 配置redis过期时间3分钟
*
* @param factory
* @return
*/
@Bean
@SuppressWarnings("all")
public CacheManager cacheManagerWithMinutes(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(3));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
/**
* * 配置Redis过期时间1小时
*
* @param factory
* @return
*/
@Bean
@SuppressWarnings("all")
public CacheManager cacheManagerWithHours(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofHours(1));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
/**
* 指定的日期模式
*/

View File

@ -1,25 +0,0 @@
package cn.bunny.common.service.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "bunny.snowflake")
@Data
public class SnowflakeProperties {
// 数据中心id
private Long datacenterId;
// 数据中心id位数
private Long datacenterBits;
// 机器id
private Long workerId;
// 机器id位数
private Long workerBits;
// 序列id所占位数
private Long sequenceBits;
// 时间戳起始点毫秒
private Long twepoch;
// 单次批量生成id的最大数量
private Integer maxBatchCount;
}

View File

@ -1,206 +0,0 @@
package cn.bunny.common.service.utils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpUtil {
public static HttpResponse doGet(String host, String path, String method, Map<String, String> headers, Map<String, String> querys) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpGet request = new HttpGet(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, Map<String, String> bodys) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (bodys != null) {
List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
for (String key : bodys.keySet()) {
nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
request.setEntity(formEntity);
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, String body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, byte[] body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, String body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, byte[] body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
public static HttpResponse doDelete(String host, String path, String method, Map<String, String> headers, Map<String, String> querys) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
StringBuilder sbUrl = new StringBuilder();
sbUrl.append(host);
if (!StringUtils.isBlank(path)) {
sbUrl.append(path);
}
if (null != querys) {
StringBuilder sbQuery = new StringBuilder();
for (Map.Entry<String, String> query : querys.entrySet()) {
if (!sbQuery.isEmpty()) {
sbQuery.append("&");
}
if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
sbQuery.append(query.getValue());
}
if (!StringUtils.isBlank(query.getKey())) {
sbQuery.append(query.getKey());
if (!StringUtils.isBlank(query.getValue())) {
sbQuery.append("=");
sbQuery.append(URLEncoder.encode(query.getValue(), StandardCharsets.UTF_8));
}
}
}
if (!sbQuery.isEmpty()) {
sbUrl.append("?").append(sbQuery);
}
}
return sbUrl.toString();
}
private static HttpClient wrapClient(String host) {
HttpClient httpClient = new DefaultHttpClient();
if (host.startsWith("https://")) {
sslClient(httpClient);
}
return httpClient;
}
private static void sslClient(HttpClient httpClient) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] xcs, String str) {
}
public void checkServerTrusted(X509Certificate[] xcs, String str) {
}
};
ctx.init(null, new TrustManager[]{tm}, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = httpClient.getConnectionManager();
SchemeRegistry registry = ccm.getSchemeRegistry();
registry.register(new Scheme("https", 443, ssf));
} catch (Exception ex) {
throw new RuntimeException();
}
}
}

View File

@ -1,6 +1,7 @@
package cn.bunny.common.service.utils;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;
@ -9,6 +10,7 @@ import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class IpUtil {
private static Searcher searcher;
@ -32,7 +34,7 @@ public class IpUtil {
byte[] bytes = FileCopyUtils.copyToByteArray(inputStream);
searcher = Searcher.newWithBuffer(bytes);
} catch (Exception exception) {
exception.printStackTrace();
log.error(exception.getMessage());
}
}
@ -66,8 +68,8 @@ public class IpUtil {
return splitIpInfo[0];
}
}
} catch (Exception e) {
e.printStackTrace();
} catch (Exception exception) {
log.error(exception.getMessage());
}
return "";
} else {

View File

@ -1,149 +0,0 @@
package cn.bunny.common.service.utils;
import cn.bunny.common.service.properties.SnowflakeProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class SnowflakeIdGenerator {
// 数据中心id
private final long datacenterId;
// 数据中心id位数
private final long datacenterBits;
// 机器id
private final long workerId;
// 机器id位数
private final long workerBits;
// 序列id所占位数
private final long sequenceBits;
// 时间戳起始点毫秒
private final long twepoch;
// 数据中心最大id
private final long maxDatacenterId;
// 机器最大id
private final long maxWorkerId;
// 最大序列号
private final long maxSequence;
// 机器id左移位数
private final long workerIdShift;
// 数据中心id左移位数
private final long datacenterIdShift;
// 毫秒数左移位数
private final long timestampLeftShift;
// 单次批量生成id的最大数量
private final int maxBatchCount;
// 序列号
private long sequence = 0L;
// 上一次时间戳
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(SnowflakeProperties properties) {
// 数据中心id
this.datacenterId = properties.getDatacenterId();
// 数据中心id位数
this.datacenterBits = properties.getDatacenterBits();
// 机器id
this.workerId = properties.getWorkerId();
// 机器id位数
this.workerBits = properties.getWorkerBits();
// 序列id所占位数
this.sequenceBits = properties.getSequenceBits();
// 时间戳起始点毫秒
this.twepoch = properties.getTwepoch();
// 数据中心最大id
this.maxDatacenterId = -1L ^ (-1L << properties.getDatacenterBits());
// 机器最大id
this.maxWorkerId = -1L ^ (-1L << properties.getWorkerBits());
// 最大序列号
this.maxSequence = -1L ^ (-1L << properties.getSequenceBits());
this.workerIdShift = properties.getSequenceBits();
// 数据中心id左移位数
this.datacenterIdShift = properties.getSequenceBits() + properties.getWorkerBits();
// 毫秒数左移位数
this.timestampLeftShift = properties.getSequenceBits() + properties.getWorkerBits() + properties.getSequenceBits();
// 单次批量生成id的最大数量
this.maxBatchCount = properties.getMaxBatchCount();
// 校验datacenterId和workerId是否超出最大值
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("数据中心Id不能大于%d或小于0", maxDatacenterId));
}
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("机器Id不能大于%d或小于0", maxWorkerId));
}
}
/**
* id生成方法(单个)
*/
public synchronized long nextId() {
// 获取当前时间的毫秒数
long timestamp = currentTime();
// 判断时钟是否回拨
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("时钟回拨,回拨毫秒数:%d", lastTimestamp - timestamp));
}
// 设置序列号
if (lastTimestamp == timestamp) {
// 设置序列号递增如果当前毫秒内序列号已经达到最大值则直到下一毫秒在重新从0开始计算序列号
sequence = (sequence + 1) & maxSequence;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// 计算id
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
/**
* id生成方法(批量)
*/
public synchronized List<Long> nextIds(int count) {
if (count > maxBatchCount || count < 0) {
throw new IllegalArgumentException(String.format("批量生成id的数量不能大于%d或小于0", maxBatchCount));
}
List<Long> ids = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ids.add(nextId());
}
return ids;
}
/**
* 循环等待直至获取到新的毫秒时间戳
* 确保生成的时间戳总是向前移动的即使在相同的毫秒内请求多个ID时也能保持唯一性
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = currentTime();
// 循环等待直至获取到新的毫秒时间戳
while (timestamp <= lastTimestamp) {
timestamp = currentTime();
}
return timestamp;
}
/**
* 获取当前时间的毫秒数
*/
private long currentTime() {
return System.currentTimeMillis();
}
}

View File

@ -1,4 +1,4 @@
package cn.bunny.vo.page;
package cn.bunny.pojo.result;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -15,7 +15,7 @@ import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PageResult<T> implements Serializable {
public class ResultPage<T> implements Serializable {
// 当前页
private Integer pageNo;
// 每页记录数

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -1,5 +1,3 @@
Configuration example
mail:
host: smtp.qq.com # 邮箱地址
port: 465 # 邮箱端口号

View File

@ -21,7 +21,6 @@ public class MinioProperties {
@Bean
public MinioClient minioClient() {
log.info("注册MinioClient...");
return MinioClient.builder().endpoint(endpointUrl).credentials(accessKey, secretKey).build();
}
}

View File

@ -1,23 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>module-task</artifactId>
<packaging>jar</packaging>
<name>module-task</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
</dependencies>
</project>

View File

@ -1,27 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>module-websocket</artifactId>
<packaging>jar</packaging>
<name>module-websocket</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,13 +0,0 @@
package cn.bunny.module.websocket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter endpointExporter() {
return new ServerEndpointExporter();
}
}

View File

@ -16,9 +16,7 @@
<module>module-minio</module>
<module>module-mail</module>
<module>module-rabbitMQ</module>
<module>module-websocket</module>
<module>spring-security</module>
<module>module-task</module>
</modules>
<properties>
@ -31,6 +29,11 @@
<artifactId>service-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.27.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@ -85,7 +85,11 @@ public class WebSecurityConfig {
return new SessionRegistryImpl();
}
// 排出鉴定路径
/**
* * 排出鉴定路径
*
* @return WebSecurityCustomizer
*/
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
String[] annotations = {"/", "/test/**", "/diagram-viewer/**", "/editor-app/**", "/*.html",

View File

@ -1,4 +1,4 @@
FROM openjdk:17
FROM openjdk:21
MAINTAINER bunny
#系统编码
@ -17,5 +17,7 @@ COPY target/*.jar /home/bunny/app.jar
#启动容器时的进程
ENTRYPOINT ["java","-jar","/home/bunny/app.jar"]
#暴露 8080 端口
EXPOSE 8500
#暴露 8800 端口
EXPOSE 8080
# maven 打包mvn clean package -Pprod -DskipTests

View File

@ -18,4 +18,6 @@ COPY target/*.jar /home/bunny/app.jar
ENTRYPOINT ["java","-jar","/home/bunny/app.jar"]
#暴露 8800 端口
EXPOSE 8800
EXPOSE 8080
# maven 打包mvn clean package -Pprod -DskipTests

View File

@ -1,3 +1,8 @@
# 线上禁用文档
knife4j:
enable: true
production: true
bunny:
datasource:
host: 192.168.3.100

View File

@ -18,4 +18,6 @@ COPY target/*.jar /home/bunny/app.jar
ENTRYPOINT ["java","-jar","/home/bunny/app.jar"]
#暴露 8800 端口
EXPOSE 8800
EXPOSE 8080
# maven 打包mvn clean package -Pprod -DskipTests

View File

@ -31,11 +31,11 @@
<artifactId>module-minio</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- <dependency> -->
<!-- <groupId>cn.bunny</groupId> -->
<!-- <artifactId>service-admin-client</artifactId> -->
<!-- <version>0.0.1-SNAPSHOT</version> -->
<!-- </dependency> -->
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -3,10 +3,12 @@ package cn.bunny.service.web.config;
import cn.bunny.service.web.interceptor.UserTokenInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
@Slf4j
@ -14,6 +16,16 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private UserTokenInterceptor userTokenInterceptor;
/**
* * 配置WebSocket
*
* @return ServerEndpointExporter
*/
@Bean
public ServerEndpointExporter endpointExporter() {
return new ServerEndpointExporter();
}
/**
* 跨域配置
*

View File

@ -1,4 +1,4 @@
package cn.bunny.module.task;
package cn.bunny.service.web.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;

View File

@ -1,4 +1,4 @@
package cn.bunny.module.websocket;
package cn.bunny.service.web.ws;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnMessage;

View File

@ -1,3 +1,8 @@
# 线上禁用文档
knife4j:
enable: true
production: true
bunny:
datasource:
host: 192.168.3.100

View File

@ -88,12 +88,3 @@ bunny:
accessKey: ${bunny.minio.accessKey}
secretKey: ${bunny.minio.secretKey}
bucket-name: ${bunny.minio.bucket-name}
snowflake:
datacenterBits: 5 # 数据中心id位数
workerBits: 5 # 机器id位数
sequenceBits: 12 # 序列id所占位数
datacenterId: 1 # 数据中心id,范围0-2^5-1
workerId: 1 # 机器id,范围0-2^5-1
twepoch: 1704038400000 # 时间戳起始点2024-01-01 00::00:00 的毫秒数)
maxBatchCount: 100000 #单次批量生成id的最大数量 默认10万