initial commit

This commit is contained in:
2025-12-23 19:47:02 +08:00
commit 7e439d0bed
79 changed files with 5120 additions and 0 deletions

203
web-server/pom.xml Normal file
View File

@@ -0,0 +1,203 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.yelink.example</groupId>
<artifactId>rainyhon-xzt</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>web-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.yelink.example</groupId>
<artifactId>yelink-redis-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yelink.example</groupId>
<artifactId>yelink-doc-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yelink.example</groupId>
<artifactId>yelink-security-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yelink.example</groupId>
<artifactId>yelink-minio-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.19</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!--打包jar-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<!--不打包资源文件,exclude的目录不是src下面的是以编译结果classes为根目录计算-->
<excludes>
<exclude>*.properties</exclude>
<exclude>*.yml</exclude>
<exclude>*.xml</exclude>
</excludes>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<!--MANIFEST.MF 中 Class-Path 加入前缀-->
<classpathPrefix>lib/</classpathPrefix>
<!--jar包不包含唯一版本标识-->
<useUniqueVersions>false</useUniqueVersions>
<!--指定入口类-->
<mainClass>com.rainyhon.swput3.web.WebServerApplication</mainClass>
</manifest>
<manifestEntries>
<!--MANIFEST.MF 中 Class-Path 加入资源文件目录-->
<Class-Path>./resources/</Class-Path>
</manifestEntries>
</archive>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</plugin>
<!--拷贝依赖 copy-dependencies-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/lib/
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!--拷贝资源文件 copy-resources-->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.directory}/resources</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!--spring boot repackage依赖 maven-jar-plugin 打包的jar包 重新打包成 spring boot 的jar包-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--重写包含依赖包含不存在的依赖jar里没有pom里的依赖-->
<includes>
<include>
<groupId>null</groupId>
<artifactId>null</artifactId>
</include>
</includes>
<layout>ZIP</layout>
<!--使用外部配置文件jar包里没有资源文件-->
<addResources>true</addResources>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<!--配置jar包特殊标识 配置后,保留原文件,生成新文件 *-run.jar -->
<!--配置jar包特殊标识 不配置,原文件命名为 *.jar.original生成新文件 *.jar -->
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<repository>${project.artifactId}</repository>
<buildArgs>
<JAR_FILE>target/</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>
</project>

View File

@@ -0,0 +1,19 @@
package com.rainyhon.swput3.web;
import com.yelink.security.annotation.AppEnableResourceServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author cwp
* @date 2024-08-21 15:31
*/
@AppEnableResourceServer
@SpringBootApplication
@EnableFeignClients(basePackages = "com.rainyhon.swput3.web.client")
public class WebServerApplication {
public static void main(String[] args) {
SpringApplication.run(WebServerApplication.class, args);
}
}

View File

@@ -0,0 +1,31 @@
package com.rainyhon.swput3.web.client;
import com.rainyhon.swput3.web.dto.Result;
import com.rainyhon.swput3.web.openapi.openfeign.EdgeFeignConfig;
import com.rainyhon.swput3.web.client.factory.DfsClientFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author cwp
*/
@FeignClient(
name = "dfsClient",
url = "${openapi.edge-gateway-url}",
// url = "https://192.168.102.180:8301",
path = "/openApi/dfs",
fallbackFactory = DfsClientFallbackFactory.class,
configuration = EdgeFeignConfig.class)
public interface DfsClient {
/**
* 查询设备的历史数据
*
* @param page 页数
* @param size 每页大小
* @return
*/
@GetMapping("/api/v1/open/sensors/records")
Result querySensorsRecords(@RequestParam(value = "current") Integer page, @RequestParam(value = "size") Integer size);
}

View File

@@ -0,0 +1,20 @@
package com.rainyhon.swput3.web.client.factory;
import com.rainyhon.swput3.web.client.DfsClient;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author cwp
* @date 2024-08-22 15:28
*/
@Component
@Slf4j
public class DfsClientFallbackFactory implements FallbackFactory<DfsClient> {
@Override
public DfsClient create(Throwable throwable) {
log.error("DFS服务调用失败:{}", throwable.getMessage());
return null;
}
}

View File

@@ -0,0 +1,100 @@
package com.rainyhon.swput3.web.controller;
import com.rainyhon.swput3.web.dto.Result;
import com.rainyhon.swput3.web.dto.req.SaveDemoModelReq;
import com.rainyhon.swput3.web.dto.req.SendKafkaMessageReq;
import com.rainyhon.swput3.web.dto.req.UploadFileReq;
import com.rainyhon.swput3.web.service.IDemoService;
import com.rainyhon.swput3.web.utils.ResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* @author cwp
* @date 2024-08-21 15:41
*/
@RestController
@RequestMapping()
@RequiredArgsConstructor
@Validated
@Api(tags = "样例控制器")
public class DemoController {
private final IDemoService demoService;
@PostMapping("/kafka/send")
@ApiOperation(
value = "发送kafka消息"
)
public Result send(@Valid @RequestBody SendKafkaMessageReq req) {
demoService.sendKafka(req);
return ResultUtil.success();
}
@PostMapping("/mysql/save")
@ApiOperation(
value = "mysql保存数据样例"
)
public Result mysqlSaveDemo(@Valid @RequestBody SaveDemoModelReq req) {
demoService.mysqlSaveDemo(req);
return ResultUtil.success();
}
@PostMapping("/redis/save")
@ApiOperation(
value = "redis保存数据样例"
)
public Result redisSave(@Valid @RequestBody SaveDemoModelReq req) {
demoService.redisSave(req);
return ResultUtil.success();
}
@PostMapping("/redis/test/single")
@ApiOperation(
value = "redis压测"
)
public Result redisTestSingle(@RequestParam(value = "count") int count) {
demoService.redistTestSingle(count);
return ResultUtil.success();
}
@PostMapping("/redis/test/batch")
@ApiOperation(
value = "redis压测"
)
public Result redisTestBatch(@RequestParam(value = "count") int count) {
demoService.redisTestBatch(count);
return ResultUtil.success();
}
@PostMapping("/minio/upload")
@ApiOperation(
value = "minio上传文件样例",
notes = "返回的是minio文件的相对路径。\n" +
"为了支持多网络访问推荐用法是前端使用该相对路径地址请求后端的nginx已经默认配置路由规则到文件系统服务。"
)
@ApiImplicitParams({
@ApiImplicitParam(name = "file", value = "上传文件", paramType = "form", dataType = "__file", required = true),
})
public Result<String> minioUpload(@Valid UploadFileReq req) {
return ResultUtil.successJsonStr(demoService.minioUpload(req));
}
@PostMapping("/call/service/api")
@ApiOperation(
value = "调用其他服务接口样例",
notes = "调用其他服务需要经过网关。\n" +
"调用此接口如果携带了token的请求头,则采用token的认证方式调用其他服务,否则采用AK/SK的认证方式。"
)
public Result callServiceApi() {
Result res = demoService.callServiceApi();
return res;
}
}

View File

@@ -0,0 +1,64 @@
package com.rainyhon.swput3.web.controller;
import com.rainyhon.swput3.web.dto.Result;
import com.rainyhon.swput3.web.dto.req.ProcessesTaskModelReq;
import com.rainyhon.swput3.web.service.IProcessesTaskService;
import com.rainyhon.swput3.web.utils.ResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* @author leim
* @date 2024-08-21 15:41
*/
@RestController
@RequestMapping("/task")
@RequiredArgsConstructor
@Validated
@Api(tags = "工序任务")
public class ProcessesTaskController {
private final IProcessesTaskService taskService;
@PostMapping("/add")
@ApiOperation(value = "新增任务")
public Result add(@Valid @RequestBody ProcessesTaskModelReq data) {
taskService.add(data);
return ResultUtil.success();
}
@PutMapping("/edit")
@ApiOperation(value = "修改任务")
public Result edit(@Valid @RequestBody ProcessesTaskModelReq data) {
taskService.edit(data);
return ResultUtil.success();
}
@DeleteMapping("/del")
@ApiOperation(value = "删除任务")
public Result del(Long taskId) {
taskService.deletes(taskId);
return ResultUtil.success();
}
@GetMapping("/detail")
@ApiOperation(value = "详情任务")
public Result detail(Long taskId) {
return ResultUtil.success(taskService.detail(taskId));
}
@GetMapping("/list")
@ApiOperation(value = "任务列表")
public Result list(@RequestParam(required = false) Long proId,
@RequestParam(required = false) String taskName,
@RequestParam(required = false) String productCode) {
return ResultUtil.success(taskService.list(proId, taskName,productCode));
}
}

View File

@@ -0,0 +1,14 @@
package com.rainyhon.swput3.web.dto;
import lombok.Data;
/**
* @author cwp
* @date 2024-01-09 12:20
*/
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
}

View File

@@ -0,0 +1,28 @@
package com.rainyhon.swput3.web.dto.req;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.persistence.*;
/**
* @author leim
* @date 2025-06-26 17:08
*/
@Data
public class ProcessesTaskModelReq {
@ApiModelProperty(value = "产品编码")
private String productCode;
@ApiModelProperty(value = "任务id")
private Long taskId;
@ApiModelProperty(value = "任务名称")
private String taskName;
@ApiModelProperty(value = "任务内容")
private String taskContent;
@ApiModelProperty(value = "工序id")
private Long proId;
@ApiModelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,22 @@
package com.rainyhon.swput3.web.dto.req;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Pattern;
/**
* @author cwp
*/
@Data
public class SaveDemoModelReq {
@Pattern(
regexp = "^[A-Za-z0-9][A-Za-z0-9_.]{1,18}[A-Za-z0-9]$",
message = "用户名长度必须为3-20个字符以字母或数字开头和结尾可以包含字母、数字、下划线和句点"
)
@ApiModelProperty(value = "用户名称", notes = "用户名长度必须为3-20个字符以字母或数字开头和结尾可以包含字母、数字、下划线和句点")
private String username;
@ApiModelProperty(value = "用户地址")
private String address;
}

View File

@@ -0,0 +1,23 @@
package com.rainyhon.swput3.web.dto.req;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Pattern;
/**
* @author cwp
* @date 2024-01-09 12:14
*/
@Data
public class SendKafkaMessageReq {
@Pattern(regexp = "^[a-z0-9]+(_[a-z0-9]+)*(_v[0-9]+)?$", message = "主题名称必须以小写字母或数字开始,主题名称可以包含多个由下划线分隔的部分,每个部分由小写字母或数字组成。")
@ApiModelProperty(value = "主题名称", notes = "主题名称必须以小写字母或数字开始,主题名称可以包含多个由下划线分隔的部分,每个部分由小写字母或数字组成。")
private String topic;
@ApiModelProperty(value = "主题主键")
private String key;
@ApiModelProperty(value = "消息内容")
private String message;
}

View File

@@ -0,0 +1,17 @@
package com.rainyhon.swput3.web.dto.req;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotNull;
/**
* @author cwp
* @date 2024-08-22 11:51
*/
@Data
public class UploadFileReq {
@NotNull
private MultipartFile file;
}

View File

@@ -0,0 +1,100 @@
package com.rainyhon.swput3.web.handler;
import com.rainyhon.swput3.web.dto.Result;
import com.rainyhon.swput3.web.utils.ResultUtil;
import com.rainyhon.swput3.web.handler.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.nio.file.AccessDeniedException;
import java.util.List;
/**
* @Description 全局异常处理
* @auther cwp
* @create 2019-07-15 13:35
*/
@Slf4j
public class BaseExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public void handleException(Exception e) {
log.error("系统内部异常,异常信息", e);
}
@ExceptionHandler(value = BusinessException.class)
@ResponseStatus(HttpStatus.OK)
public Result handleFebsException(BusinessException e) {
log.error("BusinessException {} ", e.getSnapshot());
return ResultUtil.error(e.getCode(), e.getSnapshot());
}
/**
* 统一处理请求参数校验(实体对象传参)
*
* @param e BindException
* @return FebsResponse
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleBindException(BindException e) {
StringBuilder message = new StringBuilder();
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
for (FieldError error : fieldErrors) {
message.append(error.getField()).append(error.getDefaultMessage()).append(",");
}
message = new StringBuilder(message.substring(0, message.length() - 1));
return ResultUtil.error(40001, message.toString());
}
/**
* 统一处理请求参数校验(json)
*
* @param e ConstraintViolationException
* @return FebsResponse
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handlerMethodArgumentNotValidException(MethodArgumentNotValidException e) {
StringBuilder message = new StringBuilder();
for (FieldError error : e.getBindingResult().getFieldErrors()) {
message.append(error.getField()).append(error.getDefaultMessage()).append(",");
}
message = new StringBuilder(message.substring(0, message.length() - 1));
log.error(message.toString(), e);
return ResultUtil.error(40003, message.toString());
}
@ExceptionHandler(value = AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public Result handleAccessDeniedException() {
return ResultUtil.error(10407, "没有权限访问该资源");
}
@ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
String msg = "该方法不支持" + StringUtils.substringBetween(e.getMessage(), "'", "'") + "媒体类型";
return ResultUtil.error(100801, msg);
}
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
String msg = "该方法不支持" + StringUtils.substringBetween(e.getMessage(), "'", "'") + "请求方法";
return ResultUtil.error(100802, msg);
}
}

View File

@@ -0,0 +1,16 @@
package com.rainyhon.swput3.web.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @author cwp
*/
@RestControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class GlobalExceptionHandler extends BaseExceptionHandler {
}

View File

@@ -0,0 +1,42 @@
package com.rainyhon.swput3.web.handler.exception;
import org.slf4j.helpers.MessageFormatter;
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 9215327422367136022L;
private int code;
private String snapshot;
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.snapshot = errorCode.getMessage();
}
public BusinessException(ErrorCode errorCode, String snapshotFormat, Object... argArray) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.snapshot = errorCode.getMessage() + MessageFormatter.arrayFormat(snapshotFormat, argArray).getMessage();
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
this.snapshot = message;
}
public BusinessException(int code, String message, String snapshotFormat, Object... argArray) {
super(message);
this.code = code;
this.snapshot = MessageFormatter.arrayFormat(snapshotFormat, argArray).getMessage();
}
public int getCode() {
return code;
}
public String getSnapshot() {
return snapshot;
}
}

View File

@@ -0,0 +1,34 @@
package com.rainyhon.swput3.web.handler.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* @author cwp
* @date 2024-03-14 11:39
*/
@AllArgsConstructor
@Getter
@ToString
public enum ErrorCode {
/**
* code 错误码
* message 详细的错误信息
*/
KAFKA_SEND_FAIL(10000, "kafka消息发送异常"),
MINIO_UPLOAD_FAIL(10001, "上传文件失败"),
END(99999,"END");
/**
* 错误码
*/
private final int code;
/**
* 详细的错误信息
*/
private final String message;
}

View File

@@ -0,0 +1,23 @@
package com.rainyhon.swput3.web.kafka;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
/**
* @author cwp
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class KafkaListenerDemo {
public static final String MIDDLE_PRIORITY_POST = "middle_priority_post";
@KafkaListener(topics = {MIDDLE_PRIORITY_POST})
public void kafkaRealTimeMsgListener(ConsumerRecord<?, ?> record) {
String msg = record.value().toString();
log.info("接收到kafka消息 {}", msg);
}
}

View File

@@ -0,0 +1,23 @@
package com.rainyhon.swput3.web.model;
import com.rainyhon.swput3.web.model.audit.DateAudit;
import lombok.Data;
import javax.persistence.*;
/**
* @author cwp
* @date 2024-08-21 17:08
*/
@Entity
@Table(name = "t_demo")
@Data
public class DemoModel extends DateAudit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String address;
}

View File

@@ -0,0 +1,45 @@
package com.rainyhon.swput3.web.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import javax.persistence.*;
import java.time.Instant;
import java.util.Date;
/**
* @author leim
* @date 2025-06-26 17:08
*/
@Entity
@Table(name = "processes_task")
@Data
public class ProcessesTaskModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long taskId;
private String productCode;
private String taskName;
private String taskContent;
private Long proId;
private String remark;
private String createBy;
private String updateBy;
@CreatedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

View File

@@ -0,0 +1,27 @@
package com.rainyhon.swput3.web.model.audit;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.time.Instant;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public abstract class DateAudit implements Serializable {
@CreatedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Instant createdAt;
@LastModifiedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Instant updatedAt;
}

View File

@@ -0,0 +1,24 @@
package com.rainyhon.swput3.web.openapi;
import lombok.Data;
import java.time.Instant;
/**
* @author cwp
*/
@Data
public class OpenApiParams {
public static final String SIGNATURE_KEY = "Signature";
public static final String SIGNATURE_NONCE_KEY = "SignatureNonce";
public static final String TIMESTAMP_KEY = "Timestamp";
public static final String ACCESSKEY_ID_KEY = "AccessKeyId";
public static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private String signature;
private String signatureMethod;
private String signatureNonce;
private Instant timestamp;
private String accessKeyId;
}

View File

@@ -0,0 +1,20 @@
package com.rainyhon.swput3.web.openapi.openfeign;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author cwp
*/
@Component
@ConfigurationProperties(prefix = "app.security")
@Data
public class AccessKeyProperties {
@JsonProperty("access-key-id")
private String accessKeyId;
@JsonProperty("access-key-secret")
private String accessKeySecret;
}

View File

@@ -0,0 +1,42 @@
package com.rainyhon.swput3.web.openapi.openfeign;
import com.rainyhon.swput3.web.utils.RequestInterceptorUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import com.rainyhon.swput3.web.utils.SecurityUtils;
import java.util.Collection;
/**
* @author cwp
*/
@AllArgsConstructor
@NoArgsConstructor
public class AccessKeyRequestInterceptor implements RequestInterceptor {
private AccessKeyProperties properties;
@Override
public void apply(RequestTemplate template) {
try {
String token = SecurityUtils.getCurrentTokenValue();
if (StringUtils.isNotEmpty(token)) {
template.header("Authorization", "Bearer " + token);
return;
}
} catch (Exception e) {
// do nothing
}
Collection<String> authorization = template.headers().get("Authorization");
if (ObjectUtils.isNotEmpty(authorization)) {
return;
}
// 生成ak请求头
RequestInterceptorUtil.generateAccessKeyHeader(properties, template);
}
}

View File

@@ -0,0 +1,73 @@
package com.rainyhon.swput3.web.openapi.openfeign;
import feign.Client;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.HttpMessageConverter;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.stream.Collectors;
/**
* @author cwp
*/
@RequiredArgsConstructor
public class EdgeFeignConfig {
private final AccessKeyProperties properties;
/**
* 自定义feign拦截器
*
* @return
*/
@Bean
public AccessKeyRequestInterceptor customFeignInterceptor() {
return new AccessKeyRequestInterceptor(properties);
}
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
@Bean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) throws NoSuchAlgorithmException, KeyManagementException {
SSLContext ctx = SSLContext.getInstance("SSL");
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
//do nothing
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
//do nothing
}
@Override
public X509Certificate[] getAcceptedIssuers() {
//如果这里后续报空指针就return new X509Certificate[0]
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
return new LoadBalancerFeignClient(new Client.Default(ctx.getSocketFactory(),
(hostname, session) -> true),
cachingFactory, clientFactory);
}
}

View File

@@ -0,0 +1,14 @@
package com.rainyhon.swput3.web.repository;
import com.rainyhon.swput3.web.model.DemoModel;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* @author cwp
* @date 2024-08-21 17:11
*/
@Repository
public interface DemoModelRepository extends JpaRepository<DemoModel, Long> {
}

View File

@@ -0,0 +1,14 @@
package com.rainyhon.swput3.web.repository;
import com.rainyhon.swput3.web.model.ProcessesTaskModel;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* @author leim
* @date 2024-08-21 17:11
*/
@Repository
public interface ProcessesTaskRepository extends JpaRepository<ProcessesTaskModel, Long> {
}

View File

@@ -0,0 +1,177 @@
package com.rainyhon.swput3.web.service;
import com.rainyhon.swput3.web.dto.Result;
import com.rainyhon.swput3.web.dto.req.SaveDemoModelReq;
import com.rainyhon.swput3.web.dto.req.SendKafkaMessageReq;
import com.rainyhon.swput3.web.dto.req.UploadFileReq;
import com.rainyhon.swput3.web.model.DemoModel;
import com.yelink.common.redis.service.RedisService;
import com.rainyhon.swput3.web.client.DfsClient;
import com.rainyhon.swput3.web.handler.exception.BusinessException;
import com.rainyhon.swput3.web.handler.exception.ErrorCode;
import com.rainyhon.swput3.web.repository.DemoModelRepository;
import com.rainyhon.swput3.web.utils.RetryTimerTask;
import com.yelink.minio.service.MinioService;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author cwp
* @date 2024-08-21 17:30
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class DemoServiceImpl implements IDemoService {
private Timer failTimer = null;
private final KafkaTemplate<String, String> kafkaTemplate;
private final DemoModelRepository demoModelRepository;
private final RedisService redisService;
private final RedissonClient redissonClient;
private final MinioService minioService;
private final DfsClient dfsClient;
private final RedisTemplate<String, Object> redisTemplate;
private static final String LOCK_KEY = "REDIS_LOCK";
@Override
public void sendKafka(SendKafkaMessageReq req) {
try {
SendResult<String, String> result = kafkaTemplate.send(req.getTopic(), req.getKey(), req.getMessage()).get(5, TimeUnit.SECONDS);
log.info(result.toString());
} catch (ExecutionException | TimeoutException e) {
throw new BusinessException(ErrorCode.KAFKA_SEND_FAIL);
} catch (InterruptedException e) {
log.error("发送消息失败 InterruptedException", e);
Thread.currentThread().interrupt();
throw new BusinessException(ErrorCode.KAFKA_SEND_FAIL);
}
}
@Override
public void mysqlSaveDemo(SaveDemoModelReq req) {
DemoModel demoModel = new DemoModel();
BeanUtils.copyProperties(req, demoModel);
demoModelRepository.save(demoModel);
}
@Override
public void redisSave(SaveDemoModelReq req) {
RLock rLock = redissonClient.getLock(LOCK_KEY);
try {
boolean isLock = rLock.tryLock(10, TimeUnit.MINUTES);
if (isLock) {
try {
doInvoke(req);
} catch (Throwable e) {
log.error("调用 doInvoke 方法失败5s 后将进入后台的自动重试,异常信息: ", e);
addFailed(() -> doInvoke(req));
}
} else {
log.error("未拿到锁");
}
} catch (InterruptedException e) {
log.error("InterruptedException", e);
Thread.currentThread().interrupt();
} finally {
rLock.unlock();
}
}
@Override
public String minioUpload(UploadFileReq req) {
try {
return minioService.upload(req.getFile(), UUID.randomUUID().toString());
} catch (Exception e) {
throw new BusinessException(ErrorCode.MINIO_UPLOAD_FAIL);
}
}
@Override
public Result callServiceApi() {
return dfsClient.querySensorsRecords(1, 10);
}
@Override
public void redistTestSingle(int count) {
for (int i = 0; i < count; i++) {
redisService.set("test" + i, i);
}
}
@Override
public void redisTestBatch(int count) {
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
ValueOperations<String, Object> valueOperations = (ValueOperations<String, Object>) operations.opsForValue();
for (int i = 0; i < count; i++) {
valueOperations.set("batch-test" + i, i);
}
// 返回null即可因为返回值会被管道的返回值覆盖外层取不到这里的返回值
return null;
}
});
}
private void addFailed(Runnable task) {
getInstance();
// 每30s重试一次
RetryTimerTask retryTimerTask = new RetryTimerTask(task, 30, 3);
try {
// 5s 后执行第一次重试
failTimer.newTimeout(retryTimerTask, 5, TimeUnit.SECONDS);
} catch (Throwable e) {
log.error("提交定时任务失败exception: ", e);
}
}
private void doInvoke(SaveDemoModelReq req) {
Boolean res = redisService.set(req.getUsername(), req.getAddress());
if (res) {
log.info("redis数据保存成功");
} else {
log.error("redis数据保存失败");
}
}
private synchronized void getInstance() {
if (failTimer == null) {
failTimer = new HashedWheelTimer();
}
}
public static void main(String[] args) {
List<String> authOrganizationIds = Arrays.asList("1", "2", "3","5");
// List<Long> authOrganizationIds = Arrays.asList(1L, 2L, 3L);
List<String> organizationIds = Arrays.asList("1", "2", "3","4");
boolean match = authOrganizationIds.stream().anyMatch(v -> !organizationIds.stream().anyMatch(v1 -> Objects.equals(v, v1)));
authOrganizationIds.stream().anyMatch(v -> organizationIds.stream().anyMatch(v1 -> !Objects.equals(v, v1)));
System.out.println("=====");
System.out.println(match);
}
}

View File

@@ -0,0 +1,33 @@
package com.rainyhon.swput3.web.service;
import com.rainyhon.swput3.web.dto.Result;
import com.rainyhon.swput3.web.dto.req.SaveDemoModelReq;
import com.rainyhon.swput3.web.dto.req.SendKafkaMessageReq;
import com.rainyhon.swput3.web.dto.req.UploadFileReq;
/**
* @author cwp
* @date 2024-08-21 17:30
*/
public interface IDemoService {
/**
* 发送kafka消息
*
* @param req
*/
void sendKafka(SendKafkaMessageReq req);
void mysqlSaveDemo(SaveDemoModelReq req);
void redisSave(SaveDemoModelReq req);
String minioUpload(UploadFileReq req);
Result callServiceApi();
void redistTestSingle(int count);
void redisTestBatch(int count);
}

View File

@@ -0,0 +1,66 @@
package com.rainyhon.swput3.web.service;
import com.rainyhon.swput3.web.dto.req.ProcessesTaskModelReq;
import com.rainyhon.swput3.web.model.ProcessesTaskModel;
import java.util.List;
/**
* @author leim
* @date 2025-06-26 17:30
*/
public interface IProcessesTaskService {
/**
* 新增任务
*
* @param data
* @return null
* @Date 2025/6/26
*/
void add(ProcessesTaskModelReq data);
/**
* 修改任务
*
* @param data
* @return null
* @Date 2025/6/26
*/
void edit(ProcessesTaskModelReq data);
/**
* 删除任务
*
* @param taskIds
* @return null
* @Date 2025/6/26
*/
void deletes(Long taskIds);
/**
* 详情
*
* @param taskId
* @return null
* @Date 2025/6/26
*/
ProcessesTaskModel detail(Long taskId);
/**
* 列表
*
* @param proId
* @param taskName
* @param productCode 产品编号
* @return null
* @Date 2025/6/26
*/
List<ProcessesTaskModel> list(Long proId, String taskName,String productCode);
}

View File

@@ -0,0 +1,83 @@
package com.rainyhon.swput3.web.service;
import com.rainyhon.swput3.web.dto.req.ProcessesTaskModelReq;
import com.rainyhon.swput3.web.model.ProcessesTaskModel;
import com.rainyhon.swput3.web.repository.ProcessesTaskRepository;
import com.rainyhon.swput3.web.utils.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.util.*;
/**
* @author leim
* @date 2025-06-26 17:30
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class ProcessesTaskServiceImpl implements IProcessesTaskService {
private final ProcessesTaskRepository taskRepository;
@Override
public void add(ProcessesTaskModelReq data) {
ProcessesTaskModel processesTaskModel = new ProcessesTaskModel();
BeanUtils.copyProperties(data, processesTaskModel);
processesTaskModel.setCreateBy(SecurityUtils.getUserName());
processesTaskModel.setCreateTime(new Date());
taskRepository.save(processesTaskModel);
}
@Override
public void edit(ProcessesTaskModelReq data) {
ProcessesTaskModel taskModel = taskRepository.findById(data.getTaskId()).orElseThrow(() -> new RuntimeException("任务数据不存在!"));
taskModel.setTaskName(data.getTaskName());
taskModel.setProductCode(data.getProductCode());
taskModel.setTaskContent(data.getTaskContent());
taskModel.setRemark(data.getRemark());
taskModel.setProId(data.getProId());
taskModel.setUpdateBy(SecurityUtils.getUserName());
taskModel.setUpdateTime(new Date());
// save方法在数据存在时更新
taskRepository.save(taskModel);
}
@Override
public void deletes(Long taskId) {
taskRepository.deleteById(taskId);
}
@Override
public ProcessesTaskModel detail(Long taskId) {
Optional<ProcessesTaskModel> taskModel = taskRepository.findById(taskId);
return taskModel.orElse(null);
}
@Override
public List<ProcessesTaskModel> list(Long proId, String taskName,String productCode) {
ProcessesTaskModel probe = new ProcessesTaskModel();
if (!ObjectUtils.isEmpty(taskName)) {
probe.setTaskName(taskName);
}
if (!ObjectUtils.isEmpty(proId)) {
probe.setProId(proId);
}
if(!ObjectUtils.isEmpty(productCode)){
probe.setProductCode(productCode);
}
// 2. 创建匹配器 - 关键配置
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("taskName", match ->
match.contains() // 使用包含匹配 (LIKE %value%)
.ignoreCase() // 忽略大小写
).withIgnoreNullValues(); // 忽略null值
Example<ProcessesTaskModel> example = Example.of(probe, matcher);
return taskRepository.findAll(example);
}
}

View File

@@ -0,0 +1,98 @@
package com.rainyhon.swput3.web.utils;
import cn.hutool.core.util.RandomUtil;
import com.rainyhon.swput3.web.openapi.openfeign.AccessKeyProperties;
import com.rainyhon.swput3.web.openapi.OpenApiParams;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @author cwp
*/
@Slf4j
public class RequestInterceptorUtil {
public static void generateAccessKeyHeader(AccessKeyProperties properties, RequestTemplate template) {
Map<String, String> queries = initQueryParams(template);
int randomInt = RandomUtil.randomInt(1000, 1000000);
Map<String, String> headerMap = new HashMap<>();
// 公共参数
headerMap.put(OpenApiParams.ACCESSKEY_ID_KEY, properties.getAccessKeyId());
headerMap.put(OpenApiParams.TIMESTAMP_KEY, getTimestamp());
headerMap.put(OpenApiParams.SIGNATURE_NONCE_KEY, String.valueOf(randomInt));
// 请求参数
HashMap<String, String> map = new HashMap<>(headerMap);
map.putAll(queries);
String signature;
try {
signature = SignatureUtils.generate(template.method(), map, properties.getAccessKeySecret());
headerMap.put(OpenApiParams.SIGNATURE_KEY, signature);
} catch (Exception e) {
e.printStackTrace();
}
Map<String, Collection<String>> headers = toCollectionMap(headerMap);
template.headers(headers);
}
public static String getTimestamp() {
// 获取当前时间
OffsetDateTime utcTime = OffsetDateTime.now(ZoneOffset.UTC);
// 将时间格式化为指定格式的字符串
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(OpenApiParams.TIMESTAMP_FORMAT);
return utcTime.format(formatter);
}
public static Map<String, Collection<String>> toCollectionMap(Map<String, String> map) {
Map<String, Collection<String>> result = new HashMap<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
result.put(entry.getKey(), Collections.singletonList(entry.getValue()));
}
return result;
}
public static Map<String, String> toMap(Map<String, Collection<String>> map) {
Map<String, String> result = new HashMap<>();
for (Map.Entry<String, Collection<String>> entry : map.entrySet()) {
result.put(entry.getKey(), entry.getValue().iterator().next());
}
return result;
}
private static Map<String, String> initQueryParams(RequestTemplate template) {
Map<String, Collection<String>> queries = template.queries();
Map<String, String> decodedQueryParams = new HashMap<>();
for (Map.Entry<String, Collection<String>> entry : queries.entrySet()) {
String encodedKey = entry.getKey();
for (String encodedValue : entry.getValue()) {
String decodedKey = decodeQueryParam(encodedKey);
String decodedValue = decodeQueryParam(encodedValue);
decodedQueryParams.put(decodedKey, decodedValue);
}
}
return decodedQueryParams;
}
private static String decodeQueryParam(String value) {
try {
return URLDecoder.decode(value, "UTF-8");
} catch (UnsupportedEncodingException ex) {
log.warn("Could not decode query value [" + value + "] as 'UTF-8'. " +
"Falling back on default encoding: " + ex.getMessage());
return URLDecoder.decode(value);
}
}
}

View File

@@ -0,0 +1,48 @@
package com.rainyhon.swput3.web.utils;
import com.rainyhon.swput3.web.dto.Result;
/**
* @author cwp
* @create 2020-04-15 17:21
*/
public class ResultUtil {
public static Result success() {
return success("");
}
public static Result success(String msg) {
Result result = new Result();
result.setCode(0);
result.setMessage(msg);
return result;
}
public static Result success(Object data) {
Result result = new Result();
result.setCode(0);
result.setData(data);
return result;
}
public static Result successJsonStr(String data) {
Result result = new Result();
result.setCode(0);
result.setData(data);
return result;
}
public static Result error(int code, String message) {
return error(code, message, null);
}
public static Result error(int code, String message, Object data) {
Result result = new Result();
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
}

View File

@@ -0,0 +1,65 @@
package com.rainyhon.swput3.web.utils;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
/**
* @author cwp
* @date 2024-01-24 10:56
*/
@Slf4j
public class RetryTimerTask implements TimerTask {
/**
* 每隔几秒执行一次
*/
private final long tick;
/**
* 最大重试次数
*/
private final int retries;
private int retryTimes = 0;
private final Runnable task;
public RetryTimerTask(Runnable task, long tick, int retries) {
this.tick = tick;
this.retries = retries;
this.task = task;
}
@Override
public void run(Timeout timeout) {
try {
task.run();
} catch (Throwable e) {
if ((++retryTimes) >= retries) {
// 重试次数超过了设置的值
log.error("失败重试次数超过阈值: {},不再重试", retries);
} else {
log.error("重试失败,继续重试");
rePut(timeout);
}
}
}
/**
* 通过 timeout 拿到 timer 实例,重新提交一个定时任务
* @param timeout
*/
private void rePut(Timeout timeout) {
if (timeout == null) {
return;
}
Timer timer = timeout.timer();
if (timeout.isCancelled()) {
return;
}
timer.newTimeout(timeout.task(), tick, TimeUnit.SECONDS);
}
}

View File

@@ -0,0 +1,83 @@
package com.rainyhon.swput3.web.utils;
import lombok.extern.slf4j.Slf4j;
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Set;
@Slf4j
public class SecurityUtils {
private SecurityUtils() {
}
/**
* Get the token of the current user.
*
* @return the login of the current user organizationId.
*/
public static KeycloakAuthenticationToken getPrincipe() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return (KeycloakAuthenticationToken) authentication;
}
/**
* Get the token of the current user Id.
*
* @return the login of the current user organizationId.
*/
public static String getUserId() {
SimpleKeycloakAccount account = getAccount();
return account.getKeycloakSecurityContext().getToken().getSubject();
}
public static String getUserName() {
SimpleKeycloakAccount account = getAccount();
return account.getKeycloakSecurityContext().getToken().getPreferredUsername();
}
public static SimpleKeycloakAccount getAccount() {
KeycloakAuthenticationToken principe = getPrincipe();
return (SimpleKeycloakAccount) principe.getDetails();
}
public static boolean isAdmin() {
return getAccount().getRoles().contains("admin");
}
public static String getClient() {
SimpleKeycloakAccount account = getAccount();
String client = account.getKeycloakSecurityContext().getToken().getIssuedFor();
return client;
}
public static Set<String> getRealmRoles() {
SimpleKeycloakAccount account = getAccount();
Set<String> roles = account.getKeycloakSecurityContext().getToken().getRealmAccess().getRoles();
return roles;
}
/**
* 获取当前令牌内容
*
* @return String 令牌内容
*/
public static String getCurrentTokenValue() {
String token = null;
try {
token = getAccount().getKeycloakSecurityContext().getTokenString();
} catch (Exception e) {
log.warn("can not get OAuth2AuthenticationDetails;{}", e.getMessage());
}
return token;
}
}

View File

@@ -0,0 +1,99 @@
package com.rainyhon.swput3.web.utils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Map;
import java.util.TreeMap;
/**
* 服务端API签名
*
* @author cwp
*/
public class SignatureUtils {
private final static String CHARSET_UTF8 = "utf8";
private final static String ALGORITHM = "HmacSHA1";
private final static String SEPARATOR = "&";
public static Map<String, String> splitQueryString(String url)
throws URISyntaxException, UnsupportedEncodingException {
URI uri = new URI(url);
String query = uri.getQuery();
final String[] pairs = query.split("&");
TreeMap<String, String> queryMap = new TreeMap<String, String>();
for (String pair : pairs) {
final int idx = pair.indexOf("=");
final String key = idx > 0 ? pair.substring(0, idx) : pair;
if (!queryMap.containsKey(key)) {
queryMap.put(key, URLDecoder.decode(pair.substring(idx + 1), CHARSET_UTF8));
}
}
return queryMap;
}
public static String generate(String method, Map<String, String> parameter, String accessKeySecret)
throws Exception {
String signString = generateSignString(method, parameter);
byte[] signBytes = hmacSHA1Signature(accessKeySecret + "&", signString);
String signature = newStringByBase64(signBytes);
if ("POST".equals(method)) {
return signature;
}
return URLEncoder.encode(signature, "UTF-8");
}
public static String generateSignString(String httpMethod, Map<String, String> parameter) throws IOException {
TreeMap<String, String> sortParameter = new TreeMap<String, String>();
sortParameter.putAll(parameter);
String canonicalizedQueryString = UrlUtil.generateQueryString(sortParameter, true);
if (null == httpMethod) {
throw new RuntimeException("httpMethod can not be empty");
}
StringBuilder stringToSign = new StringBuilder();
stringToSign.append(httpMethod).append(SEPARATOR);
stringToSign.append(percentEncode("/")).append(SEPARATOR);
stringToSign.append(percentEncode(canonicalizedQueryString));
return stringToSign.toString();
}
public static String percentEncode(String value) {
try {
return value == null ? null
: URLEncoder.encode(value, CHARSET_UTF8).replace("+", "%20").replace("*", "%2A").replace("%7E",
"~");
} catch (Exception e) {
}
return "";
}
public static byte[] hmacSHA1Signature(String secret, String baseString) throws Exception {
if (StringUtils.isEmpty(secret)) {
throw new IOException("secret can not be empty");
}
if (StringUtils.isEmpty(baseString)) {
return null;
}
Mac mac = Mac.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(CHARSET_UTF8), ALGORITHM);
mac.init(keySpec);
return mac.doFinal(baseString.getBytes(CHARSET_UTF8));
}
public static String newStringByBase64(byte[] bytes) throws UnsupportedEncodingException {
if (bytes == null || bytes.length == 0) {
return null;
}
return new String(Base64.encodeBase64(bytes, false), CHARSET_UTF8);
}
}

View File

@@ -0,0 +1,55 @@
package com.rainyhon.swput3.web.utils;
import org.apache.commons.lang3.StringUtils;
import java.net.URLEncoder;
import java.util.Map;
/**
* URL处理类
*
* @author cwp
*/
public class UrlUtil {
private final static String CHARSET_UTF8 = "utf8";
public static String urlEncode(String url) {
if (!StringUtils.isEmpty(url)) {
try {
url = URLEncoder.encode(url, "UTF-8");
} catch (Exception e) {
System.out.println("Url encode error:" + e.getMessage());
}
}
return url;
}
public static String generateQueryString(Map<String, String> params, boolean isEncodeKV) {
StringBuilder canonicalizedQueryString = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (isEncodeKV) {
canonicalizedQueryString.append(percentEncode(entry.getKey())).append("=")
.append(percentEncode(entry.getValue())).append("&");
} else {
canonicalizedQueryString.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
if (canonicalizedQueryString.length() > 1) {
canonicalizedQueryString.setLength(canonicalizedQueryString.length() - 1);
}
return canonicalizedQueryString.toString();
}
public static String percentEncode(String value) {
try {
// 使用URLEncoder.encode编码后将"+","*","%7E"做替换即满足API规定的编码规范。
return value == null ? null
: URLEncoder.encode(value, CHARSET_UTF8).replace("+", "%20").replace("*", "%2A").replace("%7E",
"~");
} catch (Exception e) {
}
return "";
}
}

View File

@@ -0,0 +1,123 @@
server:
port: 38104
spring:
datasource:
url: jdbc:mysql://${MYSQL_URL:172.23.255.62:3306}/rainyhon?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false
username: ${MYSQL_USER:root}
password: ${MYSQL_PASSWORD:yelink123}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
max-lifetime: 600000
flyway:
enabled: true
locations: classpath:db/migration
out-of-order: true
redis:
database: 12
host: ${REDIS_URL:172.23.255.62}
port: 6379
jedis:
pool:
min-idle: 8
max-idle: 500
max-active: 2000
max-wait: 10000
timeout: 5000
main:
allow-bean-definition-overriding: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
jpa:
open-in-view: true
hibernate:
ddl-auto:jpa: validate
kafka:
listener:
concurrency: 1
missing-topics-fatal: false
# 设置批量消费
# type=batch
bootstrap-servers: ${KAFKACLOUD_URL:172.23.255.62:9092}
producer:
retries: 3
# 应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
acks: 1
# 批量大小
batch-size: 16384
# 提交延时
properties:
linger:
ms: 0
# 当生产端积累的消息达到batch-size或接收到消息linger.ms后,生产者就会将消息提交给kafka
# linger.ms为0表示每接收到一条消息就提交给kafka,这时候batch-size其实就没用了
# 生产端缓冲区大小
buffer-memory: 33554432
# Kafka提供的序列化和反序列化类
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
###########【初始化消费者配置】###########
# 默认的消费组ID
consumer:
# 是否自动提交offset
enable-auto-commit: true
# 提交offset延时(接收到消息后多久提交offset)
auto-commit-interval: 1000
auto-offset-reset: latest
properties:
group.id: consumer-group-example
# 消费会话超时时间(超过这个时间consumer没有发送心跳,就会触发rebalance操作)
session:
timeout.ms: 120000
# 消费请求超时时间
request:
timeout.ms: 180000
# Kafka提供的序列化和反序列化类
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# 批量消费每次最多消费多少条消息
# max-poll-records=50
doc:
enable: true
title: ${spring.application.name}文档
base-package: com.rainyhon.swput3.web.controller
description: swput3 doc
name: swput3
email: swput3@example.com
url: https://example.com
version: 1.0-SNAPSHOT
app:
security:
enable: true
anon-uris: /v2/api-docs-ext,/actuator/**,/swagger-ui.html,/webjars/**,/swagger-resources/**,/v2/api-docs/**,/call/service/api
access-key-id: ${ACCESS_KEY:DJPE3QJ07OXPAFFJGORO}
access-key-secret: ${ACCESS_KEY_SECRET:sFjISWRruGQjJLCZYiEwomqEpBCNiE3HBaYixZFl}
keycloak:
auth-server-url: ${AUTH_URL:http://172.23.255.62:8411/auth}
realm: ${KEYCLOAK_REALM:edge}
principal-attribute: preferred_username
use-resource-role-mappings: false
ssl-required: none
enable-basic-auth: true
resource: ${CLIENT_ID:iot}
credentials:
secret: ${CLIENT_SECRET:de512cc3-1208-4c3c-a389-8637774eee56}
enabled: ${app.security.enable}
minio:
bucket: ${MINIO_BUCKET:oss-public}
access-key: ${MINIO_ACCESS_KEY:miniouser}
secret-key: ${MINIO_SECRET_KEY:yelink123}
endpoint: ${MINIO_ENDPOINT:http://172.23.255.62:28301}
application-name: ${MINIO_PREFIX_NAME:example}
openapi:
edge-gateway-url: ${EDGE_GATEWAY_URL:https://172.23.255.62:8301}

View File

@@ -0,0 +1,15 @@
spring:
application:
name: factory-digital-app-swput3
cloud:
nacos:
discovery:
server-addr: ${NACOS_URL:172.23.255.62:8001}
metadata:
version: ${VERSION}
ip: ${LOCAL_IP:172.23.226.250}
port: ${server.port}
logging:
level:
com.alibaba.nacos: warn

View File

@@ -0,0 +1,10 @@
create table t_demo
(
id int auto_increment,
username varchar(64) not null,
address varchar(128) null,
created_at timestamp default CURRENT_TIMESTAMP null,
updated_at timestamp default CURRENT_TIMESTAMP null,
constraint t_demo_pk primary key (id)
);

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--设置存储路径变量-->
<property name="LOG_HOME" value="./logs/"/>
<!-- ConsoleAppender把日志输出到控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--设置输出格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} %line - %msg%n</pattern>
<!--设置编码-->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--文件输出,时间窗口滚动-->
<appender name="timeFileOutput" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志名,指定最新的文件名其他文件名使用FileNamePattern -->
<File>${LOG_HOME}/rainyhon-xzt.log</File>
<append>true</append>
<!--文件滚动模式-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名,可设置文件类型为gz,开启文件压缩-->
<FileNamePattern>${LOG_HOME}/timeFile/rainyhon-xzt.%d{yyyy-MM-dd}.%i.gz</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
<!--按大小分割同一天的-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<totalSizeCap>2048MB</totalSizeCap>
</rollingPolicy>
<!--输出格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!--设置编码-->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 控制台输出日志级别 -->
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="timeFileOutput"/>
</root>
</configuration>