diff --git a/m2pool-api/api-system/api-system.iml b/m2pool-api/api-system/api-system.iml new file mode 100644 index 0000000..6c3b0b7 --- /dev/null +++ b/m2pool-api/api-system/api-system.iml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-api/api-system/pom.xml b/m2pool-api/api-system/pom.xml new file mode 100644 index 0000000..8de82ed --- /dev/null +++ b/m2pool-api/api-system/pom.xml @@ -0,0 +1,36 @@ + + + + m2pool-api + com.m2pool + 3.5.0 + + 4.0.0 + + api-system + + + api-system系统接口模块 + + + + + + + + com.m2pool + common-core + + + + + org.projectlombok + lombok-maven-plugin + provided + + + + + diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteFileService.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteFileService.java new file mode 100644 index 0000000..b5ec445 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteFileService.java @@ -0,0 +1,39 @@ +package com.m2pool.system.api; + +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.constant.ServiceNameConstants; +import com.m2pool.system.api.entity.SysFile; +import com.m2pool.system.api.factory.RemoteFileFallbackFactory; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件服务 + * + * @author m2pool + */ +@FeignClient(contextId = "remoteFileService", value = ServiceNameConstants.FILE_SERVICE, fallbackFactory = RemoteFileFallbackFactory.class) +public interface RemoteFileService +{ + /** + * 上传文件 + * + * @param file 文件信息 + * @return 结果 + */ + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R upload(@RequestPart(value = "file") MultipartFile file); + + /** + * windMiner上传文件 + * + * @param file 文件信息 + * @return 结果 + */ + @PostMapping(value = "/windMinerUploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R windMinerUploadFile(@RequestPart(value = "file") MultipartFile file); +} + diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteLogService.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteLogService.java new file mode 100644 index 0000000..50af8f2 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteLogService.java @@ -0,0 +1,43 @@ +package com.m2pool.system.api; + +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.constant.ServiceNameConstants; +import com.m2pool.system.api.entity.SysLogininfor; +import com.m2pool.system.api.entity.SysOperLog; +import com.m2pool.system.api.factory.RemoteLogFallbackFactory; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; + +/** + * @Description 日志服务 + * @Date 2024/6/11 11:46 + * @Author dy + */ +@FeignClient(contextId = "remoteLogService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteLogFallbackFactory.class) +public interface RemoteLogService { + + /** + * 保存系统日志 + * + * @param sysOperLog 日志实体 + * @param source 请求来源 + * @return 结果 + */ + @PostMapping("/operlog") + public R saveLog(@RequestBody SysOperLog sysOperLog, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); + + /** + * 保存访问记录 + * + * @param sysLogininfor 访问实体 + * @param source 请求来源 + * @return 结果 + */ + @PostMapping("/logininfor") + public R saveLogininfor(@RequestBody SysLogininfor sysLogininfor, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); + + +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteMailService.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteMailService.java new file mode 100644 index 0000000..a30e9b1 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteMailService.java @@ -0,0 +1,36 @@ +package com.m2pool.system.api; + +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.constant.ServiceNameConstants; +import com.m2pool.system.api.entity.EmailEntity; +import com.m2pool.system.api.entity.GetEmailCodeEntity; +import com.m2pool.system.api.factory.RemoteMailFallbackFactory; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * 邮件服务 + * + * @author m2pool + */ +@FeignClient(contextId = "remoteMailService", value = ServiceNameConstants.AUTH_SERVICE, fallbackFactory = RemoteMailFallbackFactory.class) +public interface RemoteMailService +{ + /** + * 发送提现的邮箱验证码 + * + * @return 结果 + */ + @PostMapping("/addCreditEmailCode") + public R sendAddCreditEmailCode(@RequestBody GetEmailCodeEntity entity); + + /** + * 发送文本邮件 + * + * @return 结果 + */ + @PostMapping("/sendTextMail") + public R sendTextMail(@RequestBody EmailEntity entity); +} + diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteUserService.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteUserService.java new file mode 100644 index 0000000..7bda21f --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/RemoteUserService.java @@ -0,0 +1,95 @@ +package com.m2pool.system.api; + +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.constant.ServiceNameConstants; +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.api.entity.SysUserLeveDate; +import com.m2pool.system.api.factory.RemoteUserFallbackFactory; +import com.m2pool.system.api.model.LoginUser; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @Description 用户服务 + * @Date 2024/6/12 15:46 + * @Author dy + */ +@FeignClient(contextId = "remoteUserService",value = ServiceNameConstants.SYSTEM_SERVICE,fallbackFactory = RemoteUserFallbackFactory.class) +public interface RemoteUserService { + + /** + * 通过用户名/邮箱查询用户信息 + * + * @param account 用户名/邮箱 + * @param source 请求来源 + * @return 结果 + */ + @GetMapping("/user/info/{account}") + public R getUserInfo(@PathVariable("account") String account, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); + + /** + * 修改用户最后一次登录的ip和登录次数 + * + * @param sysUser 用户信息 + * @return 结果 + */ + @PostMapping("/user/update") + public R updateUserInfo(@RequestBody SysUser sysUser); + + /** + * 注册用户信息 + * + * + * @param sysUser 用户信息 + * @return 结果 + */ + @PostMapping("/user/register") + public R registerUserInfo(@RequestBody SysUser sysUser); + + /** + * 重置用户密码 + * + * @param sysUser 用户信息 + * @return 结果 + */ + @PostMapping("/user/profile/resetPwd") + public R resetPwdByEmail(@RequestBody SysUser sysUser); + + /** + * 查看会员有效期情况 + * @return 结果 + */ + @PostMapping("/user/profile/userLevel") + public R userLevel(@RequestBody Long userId); + + /** + * 升级用户并记录用户会员有效期 + * + * @param user 用户信息 + * @return 结果 + */ + @PostMapping("/user/profile/Levelup") + public R userLevelup(@RequestBody SysUserLeveDate user, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); + + /** + * 用户谷歌验证器验证 + * + * @param code 用户信息 + * @return 结果 + */ + @PostMapping("/user/profile/checkGoogleCode") + public R checkGoogleCode(@RequestBody Long code, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); + + /** + * 获取客服列表 + * + * @return 结果 + */ + @PostMapping("/user/getCSList") + public List getCSList(); + +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/EmailCodeEntity.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/EmailCodeEntity.java new file mode 100644 index 0000000..736dd5e --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/EmailCodeEntity.java @@ -0,0 +1,28 @@ +package com.m2pool.system.api.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @Description 用户登录对象 + * @Date 2024/6/11 14:13 + * @Author dy + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EmailCodeEntity implements Serializable { + + /** 用户名或邮箱 */ + private String userName; + + /** 邮箱 */ + private String email; + + private String emailCode; + + private int times; +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/EmailEntity.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/EmailEntity.java new file mode 100644 index 0000000..410b1f9 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/EmailEntity.java @@ -0,0 +1,25 @@ +package com.m2pool.system.api.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @Description 用户登录对象 + * @Date 2024/6/12 16:13 + * @Author dy + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EmailEntity implements Serializable { + + /** 邮箱 */ + private String email; + + private String subject; + + private String text; +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/GetEmailCodeEntity.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/GetEmailCodeEntity.java new file mode 100644 index 0000000..21a911d --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/GetEmailCodeEntity.java @@ -0,0 +1,33 @@ +package com.m2pool.system.api.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; + +/** + * @Description 用户登录对象 + * @Date 2024/6/12 16:13 + * @Author dy + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GetEmailCodeEntity { + + /** 用户名或邮箱 */ + private String userName; + + /** 邮箱 */ + @NotNull(message = "用户邮箱不能为空") + @Email(message = "邮箱格式错误") + private String email; + + //@NotNull(message = "用户密码不能为空") + //@Size(min=6, max=15,message="密码长度必须在 5 ~ 15 字符之间!") + ////@Pattern(regexp="^[a-zA-Z0-9|_]+$",message="密码必须由字母、数字、下划线组成!") + //private String password; + +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysDept.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysDept.java new file mode 100644 index 0000000..3345677 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysDept.java @@ -0,0 +1,85 @@ +package com.m2pool.system.api.entity; + +import com.m2pool.common.core.web.entity.BaseEntity; +import lombok.Data; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.List; + +/** + * @Description 部门表实体 + * @Date 2024/6/12 11:19 + * @Author dy + */ +@Data +public class SysDept extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** 部门ID */ + private Long deptId; + + /** 父部门ID */ + private Long parentId; + + /** 祖级列表 */ + private String ancestors; + + /** 部门名称 */ + private String deptName; + + /** 显示顺序 */ + private Integer orderNum; + + /** 负责人 */ + private String leader; + + /** 联系电话 */ + private String phone; + + /** 邮箱 */ + private String email; + + /** 部门状态:0正常,1停用 */ + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 父部门名称 */ + private String parentName; + + /** 子部门 */ + private List children = new ArrayList(); + + @NotBlank(message = "部门名称不能为空") + @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符") + public String getDeptName() + { + return deptName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符") + public String getPhone() + { + return phone; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysFile.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysFile.java new file mode 100644 index 0000000..f05c641 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysFile.java @@ -0,0 +1,67 @@ +package com.m2pool.system.api.entity; + +import com.m2pool.common.core.web.entity.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 文件信息 + * + * @author dy + */ +public class SysFile extends BaseEntity +{ + /** + * 文件表主键 + */ + private Long id; + + /** + * 文件名称 + */ + private String name; + + /** + * 文件地址 + */ + private String url; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getUrl() + { + return url; + } + + public void setUrl(String url) + { + this.url = url; + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("name", getName()) + .append("id", getId()) + .append("url", getUrl()) + .toString(); + } +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysLogininfor.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysLogininfor.java new file mode 100644 index 0000000..adf2e2b --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysLogininfor.java @@ -0,0 +1,37 @@ +package com.m2pool.system.api.entity; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.m2pool.common.core.web.entity.BaseEntity; +import lombok.Data; + +import java.util.Date; + +/** + * @Description 系统访问记录表实体类 + * @Date 2024/6/11 10:40 + * @Author dy + */ +@Data +public class SysLogininfor extends BaseEntity { + + private static final long serialVersionUID = 3217638467115846123L; + + /** ID */ + private Long infoId; + + /** 用户账号 */ + private String userName; + + /** 状态 0成功 1失败 */ + private String status; + + /** 地址 */ + private String ipaddr; + + /** 描述 */ + private String msg; + + /** 访问时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date accessTime; +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysOperLog.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysOperLog.java new file mode 100644 index 0000000..1d68b51 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysOperLog.java @@ -0,0 +1,68 @@ +package com.m2pool.system.api.entity; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.m2pool.common.core.web.entity.BaseEntity; +import lombok.Data; + +import java.util.Date; + +/** + * @Description 操作日志记录表实体类 + * @Date 2024/6/11 10:43 + * @Author dy + */ +@Data +public class SysOperLog extends BaseEntity { + + private static final long serialVersionUID = 7117642119125846123L; + + /** 日志主键 */ + private Long operId; + + /** 操作模块 */ + private String title; + + /** 业务类型(0其它 1新增 2修改 3删除) */ + private Integer businessType; + + /** 业务类型数组 */ + private Integer[] businessTypes; + + /** 请求方法 */ + private String method; + + /** 请求方式 */ + private String requestMethod; + + /** 操作类别(0其它 1后台用户 2手机端用户) */ + private Integer operatorType; + + /** 操作人员 */ + private String operName; + + /** 部门名称 */ + private String deptName; + + /** 请求url */ + private String operUrl; + + /** 操作地址 */ + private String operIp; + + /** 请求参数 */ + private String operParam; + + /** 返回参数 */ + private String jsonResult; + + /** 操作状态(0正常 1异常) */ + private Integer status; + + /** 错误消息 */ + private String errorMsg; + + /** 操作时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date operTime; + +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysRole.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysRole.java new file mode 100644 index 0000000..8956d3e --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysRole.java @@ -0,0 +1,87 @@ +package com.m2pool.system.api.entity; + +import com.m2pool.common.core.web.entity.BaseEntity; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.Date; + +/** + * @Description 角色表实体 + * @Date 2024/6/12 11:18 + * @Author dy + */ +@Data +public class SysRole extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** 角色ID */ + private Long roleId; + + /** 角色名称 */ + private String roleName; + + /** 角色权限 */ + private String roleKey; + + /** 角色排序 */ + private String roleSort; + + + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + private String dataScope; + + /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ + private boolean menuCheckStrictly; + + /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */ + private boolean deptCheckStrictly; + + /** 角色状态(0正常 1停用) */ + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 用户是否存在此角色标识 默认不存在 */ + private boolean flag = false; + + /** 菜单组 */ + private Long[] menuIds; + + /** 部门组(数据权限) */ + //private Long[] deptIds; + + public boolean isAdmin() + { + return isAdmin(this.roleId); + } + + public static boolean isAdmin(Long roleId) + { + return roleId != null && 1L == roleId; + } + + @NotBlank(message = "角色名称不能为空") + @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") + public String getRoleName() + { + return roleName; + } + + @NotBlank(message = "权限字符不能为空") + @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") + public String getRoleKey() + { + return roleKey; + } + + @NotBlank(message = "显示顺序不能为空") + public String getRoleSort() + { + return roleSort; + } + +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysUser.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysUser.java new file mode 100644 index 0000000..b16244d --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysUser.java @@ -0,0 +1,117 @@ +package com.m2pool.system.api.entity; + +import com.m2pool.common.core.web.entity.BaseEntity; +import lombok.Data; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.Date; +import java.util.List; + +/** + * @Description 用户实体类 + * @Date 2024/6/12 10:59 + * @Author dy + */ +@Data +public class SysUser extends BaseEntity { + + private static final long serialVersionUID = 2417642483325474318L; + + /** 用户ID */ + private Long userId; + + /** 部门ID */ + private Long deptId; + + /** 用户账号 */ + private String userName; + + /** 用户昵称 */ + private String nickName; + + /** 用户邮箱 */ + private String email; + + /** 手机号码 */ + private String phone; + + /** 用户性别 */ + private String sex; + + /** 用户头像 */ + private String avatar; + + /** 密码 */ + private String password; + + /** 帐号状态(0正常 1停用) */ + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 最后登录IP */ + private String loginIp; + + /** 登录次数 */ + private Long loginCount; + + /** 最后登录时间 */ + private Date loginDate; + + /** 部门对象 */ + private SysDept dept; + + /** 角色对象 */ + private List roles; + + /** 角色组 用户角色更新操作使用*/ + private Long[] roleIds; + + /** 岗位组 */ + private Long[] postIds; + + /** 角色ID */ + private Long roleId; + + public boolean isAdmin() + { + return isAdmin(this.userId); + } + + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + // TODO: xss + @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") + public String getNickName() + { + return nickName; + } + + // TODO: xss + @NotBlank(message = "用户账号不能为空") + @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") + public String getUserName() + { + return userName; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符") + public String getPhone() + { + return phone; + } + +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysUserLeveDate.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysUserLeveDate.java new file mode 100644 index 0000000..c6ca532 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/entity/SysUserLeveDate.java @@ -0,0 +1,41 @@ +package com.m2pool.system.api.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2024/6/11 15:41 + * @Author dy + */ +@Data +public class SysUserLeveDate { + + + private static final long serialVersionUID = 1L; + + /** 用户ID */ + private Long userId; + + /** 用户账号 */ + private String userName; + + private String email; + + /** 会员开始时间 */ + private Date startTime; + + /** 会员过期时间 */ + private Date expireTime; + + private int levelType; + + //选择操作 默认0新增 1续费 + private int oper = 0; + + /** 角色对象 */ + private Long roleId; + + +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteFileFallbackFactory.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteFileFallbackFactory.java new file mode 100644 index 0000000..9fc7ae5 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteFileFallbackFactory.java @@ -0,0 +1,40 @@ +package com.m2pool.system.api.factory; + +import com.m2pool.common.core.Result.R; +import com.m2pool.system.api.RemoteFileService; +import com.m2pool.system.api.entity.SysFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件服务降级处理 + * + * @author dy + */ +@Component +public class RemoteFileFallbackFactory implements FallbackFactory +{ + private static final Logger log = LoggerFactory.getLogger(RemoteFileFallbackFactory.class); + + @Override + public RemoteFileService create(Throwable throwable) + { + log.error("文件服务调用失败:{}", throwable.getMessage()); + return new RemoteFileService() + { + @Override + public R upload(MultipartFile file) + { + return R.fail("远程调用上传文件失败:" + throwable.getMessage()); + } + + @Override + public R windMinerUploadFile(MultipartFile file) { + return R.fail("windMiner远程调用上传文件失败:" + throwable.getMessage()); + } + }; + } +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteLogFallbackFactory.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteLogFallbackFactory.java new file mode 100644 index 0000000..85d58a6 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteLogFallbackFactory.java @@ -0,0 +1,40 @@ +package com.m2pool.system.api.factory; + +import com.m2pool.common.core.Result.R; +import com.m2pool.system.api.RemoteLogService; +import com.m2pool.system.api.entity.SysLogininfor; +import com.m2pool.system.api.entity.SysOperLog; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; + +/** + * @Description 日志服务降级处理 + * @Date 2024/6/11 11:58 + * @Author dy + */ +@Component +public class RemoteLogFallbackFactory implements FallbackFactory { + + private static final Logger log = LoggerFactory.getLogger(RemoteLogFallbackFactory.class); + + @Override + public RemoteLogService create(Throwable cause) { + log.error("日志服务调用失败:{}", cause.getMessage()); + return new RemoteLogService() + { + @Override + public R saveLog(SysOperLog sysOperLog, String source) + { + return null; + } + + @Override + public R saveLogininfor(SysLogininfor sysLogininfor, String source) + { + return null; + } + }; + } +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteMailFallbackFactory.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteMailFallbackFactory.java new file mode 100644 index 0000000..45a0c96 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteMailFallbackFactory.java @@ -0,0 +1,40 @@ +package com.m2pool.system.api.factory; + +import com.m2pool.common.core.Result.R; +import com.m2pool.system.api.RemoteMailService; +import com.m2pool.system.api.entity.EmailEntity; +import com.m2pool.system.api.entity.GetEmailCodeEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * 文件服务降级处理 + * + * @author dy + */ +@Component +public class RemoteMailFallbackFactory implements FallbackFactory +{ + private static final Logger log = LoggerFactory.getLogger(RemoteMailFallbackFactory.class); + + @Override + public RemoteMailService create(Throwable cause) + { + return new RemoteMailService() { + @Override + public R + sendAddCreditEmailCode(GetEmailCodeEntity entity) { + return R.fail("提现邮箱验证发送失败:" + cause.getMessage()); + } + + @Override + public R sendTextMail(@RequestBody EmailEntity entity){ + return R.fail("邮箱发送失败:" + cause.getMessage()); + } + + }; + } +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteUserFallbackFactory.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteUserFallbackFactory.java new file mode 100644 index 0000000..68b90b8 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/factory/RemoteUserFallbackFactory.java @@ -0,0 +1,68 @@ +package com.m2pool.system.api.factory; + +import com.m2pool.common.core.Result.R; +import com.m2pool.system.api.RemoteUserService; +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.api.entity.SysUserLeveDate; +import com.m2pool.system.api.model.LoginUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; + +/** + * @Description 用户服务降级处理 + * @Date 2024/6/11 15:48 + * @Author dy + */ +@Component +public class RemoteUserFallbackFactory implements FallbackFactory { + + private static final Logger log = LoggerFactory.getLogger(RemoteUserFallbackFactory.class); + + + @Override + public RemoteUserService create(Throwable cause) { + log.error("用户服务调用失败:{}",cause.getMessage()); + + return new RemoteUserService(){ + + @Override + public R getUserInfo(String username, String source) { + return R.fail("获取用户失败:" + cause.getMessage()); + } + + @Override + public R updateUserInfo(SysUser sysUser) { + return R.fail("修改用户失败:" + cause.getMessage()); + } + + @Override + public R registerUserInfo(SysUser sysUser) { + return R.fail("注册用户失败:" + cause.getMessage()); + } + + @Override + public R resetPwdByEmail(SysUser sysUser) { + return R.fail("重置用户密码失败:" + cause.getMessage()); + } + + @Override + public R userLevel(Long userId) { + return R.fail("获取用户会员信息失败:" + cause.getMessage()); + } + + @Override + public R userLevelup(SysUserLeveDate user, String source) { + return R.fail("用户升级失败:" + cause.getMessage()); + } + + @Override + public R checkGoogleCode(Long code, String source) { + return R.fail("谷歌验证器校验失败:" + cause.getMessage()); + } + + + }; + } +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/model/LoginUser.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/model/LoginUser.java new file mode 100644 index 0000000..816e2d0 --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/model/LoginUser.java @@ -0,0 +1,46 @@ +package com.m2pool.system.api.model; + +import com.m2pool.system.api.entity.SysUser; +import lombok.Data; + +import java.io.Serializable; +import java.util.Set; + +/** + * @Description 登陆的用户信息 + * @Date 2024/6/12 15:13 + * @Author dy + */ +@Data +public class LoginUser implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 用户唯一标识 */ + private String token; + + /** 用户名id */ + private Long userid; + + /** 用户名 */ + private String username; + + /** 登录时间 */ + private Long loginTime; + + /** 过期时间 */ + private Long expireTime; + + /** 登录IP地址 */ + private String ipaddr; + + /** 权限列表 */ + private Set permissions; + + /** 角色列表 */ + private Set roles; + + /** 用户信息 */ + private SysUser sysUser; + +} diff --git a/m2pool-api/api-system/src/main/java/com/m2pool/system/api/model/OpenApiKeyInfo.java b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/model/OpenApiKeyInfo.java new file mode 100644 index 0000000..fd2d95f --- /dev/null +++ b/m2pool-api/api-system/src/main/java/com/m2pool/system/api/model/OpenApiKeyInfo.java @@ -0,0 +1,40 @@ +package com.m2pool.system.api.model; + +import com.m2pool.system.api.entity.SysUser; +import lombok.Data; + +import java.io.Serializable; +import java.util.Set; + +/** + * @Description 登陆的用户信息 + * @Date 2024/6/12 15:13 + * @Author dy + */ +@Data +public class OpenApiKeyInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 用户唯一标识 */ + private String key; + + /** 用户邮箱 */ + private String user; + + /** 登录时间 */ + private Long loginTime; + + /** 过期时间 */ + private Long expireTime; + + /** apiKey绑定的ip */ + private String apiIp; + + /** 登录IP地址 */ + private String ipaddr; + + private Set permissions; + + +} diff --git a/m2pool-api/api-system/src/main/resources/META-INF/spring.factories b/m2pool-api/api-system/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..8793a53 --- /dev/null +++ b/m2pool-api/api-system/src/main/resources/META-INF/spring.factories @@ -0,0 +1,5 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.m2pool.system.api.factory.RemoteUserFallbackFactory,\ + com.m2pool.system.api.factory.RemoteFileFallbackFactory,\ + com.m2pool.system.api.factory.RemoteMailFallbackFactory,\ + com.m2pool.system.api.factory.RemoteLogFallbackFactory diff --git a/m2pool-api/m2pool-api.iml b/m2pool-api/m2pool-api.iml new file mode 100644 index 0000000..7519e50 --- /dev/null +++ b/m2pool-api/m2pool-api.iml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-api/pom.xml b/m2pool-api/pom.xml new file mode 100644 index 0000000..7267544 --- /dev/null +++ b/m2pool-api/pom.xml @@ -0,0 +1,23 @@ + + + + m2pool + com.m2pool + 3.5.0 + + 4.0.0 + + m2pool-api + pom + + api-system + + + + m2pool-api系统接口 + + + + diff --git a/m2pool-auth/m2pool-auth.iml b/m2pool-auth/m2pool-auth.iml new file mode 100644 index 0000000..b8d7a1e --- /dev/null +++ b/m2pool-auth/m2pool-auth.iml @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-auth/pom.xml b/m2pool-auth/pom.xml new file mode 100644 index 0000000..5d7eaca --- /dev/null +++ b/m2pool-auth/pom.xml @@ -0,0 +1,81 @@ + + + + m2pool + com.m2pool + 3.5.0 + + 4.0.0 + + m2pool-auth + + 认证模块:登录认证、权限鉴定等 + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + org.springframework.boot + spring-boot-starter-web + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.projectlombok + lombok-maven-plugin + provided + + + + + com.m2pool + common-security + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/M2PoolAuthApplication.java b/m2pool-auth/src/main/java/com/m2pool/auth/M2PoolAuthApplication.java new file mode 100644 index 0000000..53f56bb --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/M2PoolAuthApplication.java @@ -0,0 +1,21 @@ +package com.m2pool.auth; + +import com.m2pool.common.security.annotation.EnableM2PoolFeignClients; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +@EnableM2PoolFeignClients +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +public class M2PoolAuthApplication{ + + public static void main(String[] args) { + + SpringApplication.run(M2PoolAuthApplication.class,args); + + System.out.println("认证授权中心启动成功"); + + + } + +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/controller/TokenController.java b/m2pool-auth/src/main/java/com/m2pool/auth/controller/TokenController.java new file mode 100644 index 0000000..1e7bed5 --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/controller/TokenController.java @@ -0,0 +1,152 @@ +package com.m2pool.auth.controller; + +import com.m2pool.auth.entity.*; +import com.m2pool.auth.service.SysLoginService; +import com.m2pool.auth.service.impl.MaliServiceImpl; +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.utils.JwtUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.security.annotation.RequiresLogin; +import com.m2pool.common.security.auth.AuthUtil; +import com.m2pool.common.security.service.TokenService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.api.model.LoginUser; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; + + +/** + * @Description token 控制 + * @Date 2024/6/12 14:59 + * @Author dy + */ +@RestController +@Api(tags = "token令牌") +public class TokenController { + + @Autowired + private SysLoginService sysLoginService; + + @Autowired + private TokenService tokenService; + + @Autowired + private MaliServiceImpl maliService; + + @PostMapping("login") + public R login(@RequestBody @Valid LoginBody loginBody,@RequestHeader("User-Agent") String userAgent) + { + return sysLoginService.login(loginBody); + } + + + @PostMapping("registerCode") + public R emailCode(@Validated @RequestBody GetEmailCodeEntity entity) + { + return maliService.emailCode(entity); + } + + @PostMapping("loginCode") + public R loginCode(@Valid @RequestBody GetLoginEmailCodeEntity entity) + { + return maliService.loginCode(entity); + } + + @PostMapping("addCreditEmailCode") + public R addCreditEmailCode(@RequestBody GetEmailCodeEntity entity) + { + return maliService.AddCreditCode(entity); + } + + @PostMapping("resetPwdCode") + public R resetPwdCode(@Valid @RequestBody GetEmailCodeEntity entity) + { + return maliService.resetPwdCode(entity); + } + + @PostMapping("updatePwdCode") + public R updatePwdCode() + { + return maliService.updatePwdCode(); + } + + + @GetMapping("hello") + public R hello() + { + return R.success("浏览器安全认证已确认,请重新返回原页面进行登陆"); + } + + @DeleteMapping("logout") + public R logout(HttpServletRequest request) + { + String token = SecurityUtils.getToken(request); + if (StringUtils.isNotEmpty(token)) + { + String username = JwtUtils.getUserName(token); + //删除用户缓存记录 + AuthUtil.logoutByToken(token); + // 记录用户退出日志 + sysLoginService.logout(username); + } + return R.success("用户登出"); + } + + @PostMapping("refresh") + public R refresh(HttpServletRequest request) + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)){ + //刷新令牌有效期 + tokenService.refreshToken(loginUser); + return R.success(); + } + return R.success(); + } + + @PostMapping("register") + public R register(@Valid() @RequestBody RegisterBody registerBody) + { + // 用户注册 + sysLoginService.register(registerBody); + return R.success("用户"+registerBody.getEmail()+"注册成功"); + } + + @PostMapping("resetPwd") + public R resetPwd(@Valid @RequestBody ResetPwdBody resetPwdBody) + { + // 重置密码 + sysLoginService.resetPwd(resetPwdBody); + return R.success("账号"+ resetPwdBody.getEmail()+"密码重置成功"); + } + + @PostMapping("updatePwd") + @RequiresLogin + public R updatePwd(@Valid @RequestBody ResetPwdBody resetPwdBody) + { + String email = SecurityUtils.getUsername(); + resetPwdBody.setEmail(email); + + // 修改密码 + sysLoginService.resetPwd(resetPwdBody); + return R.success("账号"+ resetPwdBody.getEmail()+"密码修改成功"); + } + + @PostMapping("sendTextMail") + public R sendTextMail(@Valid @RequestBody EmailEntity entity) + { + maliService.sendTextMailMessage(entity.getEmail(), entity.getSubject(),entity.getText()); + return R.success("邮件已发送"); + } + + +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/entity/EmailCodeEntity.java b/m2pool-auth/src/main/java/com/m2pool/auth/entity/EmailCodeEntity.java new file mode 100644 index 0000000..3a6dea2 --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/entity/EmailCodeEntity.java @@ -0,0 +1,25 @@ +package com.m2pool.auth.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @Description 用户登录对象 + * @Date 2024/6/12 16:13 + * @Author dy + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EmailCodeEntity implements Serializable { + + /** 邮箱 */ + private String email; + + private String emailCode; + + private int times; +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/entity/EmailEntity.java b/m2pool-auth/src/main/java/com/m2pool/auth/entity/EmailEntity.java new file mode 100644 index 0000000..34ac40a --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/entity/EmailEntity.java @@ -0,0 +1,27 @@ +package com.m2pool.auth.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; +import java.io.Serializable; + +/** + * @Description 用户登录对象 + * @Date 2022/5/12 16:13 + * @Author 杜懿 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EmailEntity implements Serializable { + + /** 邮箱 */ + @Email + private String email; + + private String subject; + + private String text; +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/entity/GetEmailCodeEntity.java b/m2pool-auth/src/main/java/com/m2pool/auth/entity/GetEmailCodeEntity.java new file mode 100644 index 0000000..4572170 --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/entity/GetEmailCodeEntity.java @@ -0,0 +1,33 @@ +package com.m2pool.auth.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; + +/** + * @Description 用户登录对象 + * @Date 2024/6/12 16:13 + * @Author dy + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GetEmailCodeEntity { + + /** 用户名或邮箱 */ + private String userName; + + /** 邮箱 */ + @NotNull(message = "用户邮箱不能为空") + @Email(message = "邮箱格式错误") + private String email; + + //@NotNull(message = "用户密码不能为空") + //@Size(min=6, max=15,message="密码长度必须在 5 ~ 15 字符之间!") + ////@Pattern(regexp="^[a-zA-Z0-9|_]+$",message="密码必须由字母、数字、下划线组成!") + //private String password; + +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/entity/GetLoginEmailCodeEntity.java b/m2pool-auth/src/main/java/com/m2pool/auth/entity/GetLoginEmailCodeEntity.java new file mode 100644 index 0000000..6b2c87a --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/entity/GetLoginEmailCodeEntity.java @@ -0,0 +1,30 @@ +package com.m2pool.auth.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; + +/** + * @Description 用户登录对象 + * @Date 2024/6/12 16:13 + * @Author dy + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GetLoginEmailCodeEntity { + + /** 邮箱 */ + @NotNull(message = "用户邮箱不能为空") + @Email(message = "邮箱格式错误") + private String account; + + //@NotNull(message = "用户密码不能为空") + //@Size(min=6, max=15,message="密码长度必须在 5 ~ 15 字符之间!") + ////@Pattern(regexp="^[a-zA-Z0-9|_]+$",message="密码必须由字母、数字、下划线组成!") + //private String password; + +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/entity/LoginBody.java b/m2pool-auth/src/main/java/com/m2pool/auth/entity/LoginBody.java new file mode 100644 index 0000000..5d7e68b --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/entity/LoginBody.java @@ -0,0 +1,27 @@ +package com.m2pool.auth.entity; + +import lombok.Data; + +import javax.validation.constraints.Email; + +/** + * @Description 用户登录对象 + * @Date 2024/6/12 16:13 + * @Author dy + */ +@Data +public class LoginBody { + + /** 邮箱 */ + @Email + private String userName; + + /** 密码 */ + private String password; + + private String code; + + private String uuid; + + //private boolean flag = false; +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/entity/RegisterBody.java b/m2pool-auth/src/main/java/com/m2pool/auth/entity/RegisterBody.java new file mode 100644 index 0000000..ef4a2ba --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/entity/RegisterBody.java @@ -0,0 +1,32 @@ +package com.m2pool.auth.entity; + +import lombok.Data; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +/** + * @Description 用户注册对象 + * @Date 2024/6/12 16:16 + * @Author dy + */ +@Data +public class RegisterBody{ + + //todo 添加注册详细信息 + /** 手机号码 */ + private String phone; + + @Email(message = "邮箱格式错误") + private String email; + + private String emailCode; + + /** 密码 */ + @NotNull(message = "密码不能为空") + //@Size(min=8, max=32,message="密码长度必须在 8 ~ 32 字符之间!") + //@Pattern(regexp="^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\\W_]+$)(?![a-z0-9]+$)(?![a-z\\W_]+$)(?![0-9\\W_]+$)[a-zA-Z0-9\\W_]{8,32}$",message="密码格式错误,密码应包含大写字母、小写字母、数字以及特殊字符,8到32位") + private String password; +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/entity/ResetPwdBody.java b/m2pool-auth/src/main/java/com/m2pool/auth/entity/ResetPwdBody.java new file mode 100644 index 0000000..10ddb74 --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/entity/ResetPwdBody.java @@ -0,0 +1,29 @@ +package com.m2pool.auth.entity; + +import lombok.Data; + +import javax.validation.constraints.Email; + +/** + * @Description 用户重置密码对象 + * @Date 2024/6/12 16:16 + * @Author dy + */ +@Data +public class ResetPwdBody { + + //todo 添加详细信息 + + @Email + private String email; + + private String resetPwdCode; + + /** 新密码 */ + private String password; + + /** + * 谷歌验证器验证码 + */ + public long gCode; +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/entity/UpdatePwdBody.java b/m2pool-auth/src/main/java/com/m2pool/auth/entity/UpdatePwdBody.java new file mode 100644 index 0000000..054c6a1 --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/entity/UpdatePwdBody.java @@ -0,0 +1,20 @@ +package com.m2pool.auth.entity; + +import lombok.Data; + +import javax.validation.constraints.Email; + +/** + * @Description 用户修改密码对象 + * @Date 2024/6/12 16:16 + * @Author dy + */ +@Data +public class UpdatePwdBody { + + private String updatePwdCode; + + /** 新密码 */ + private String password; + +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/service/MailService.java b/m2pool-auth/src/main/java/com/m2pool/auth/service/MailService.java new file mode 100644 index 0000000..6fce450 --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/service/MailService.java @@ -0,0 +1,78 @@ +package com.m2pool.auth.service; + +import com.m2pool.auth.entity.GetEmailCodeEntity; +import com.m2pool.auth.entity.GetLoginEmailCodeEntity; +import com.m2pool.common.core.Result.R; + +/** + * @Description TODO + * @Date 2024/6/11 14:56 + * @Author dy + */ +public interface MailService { + /** + * 发送纯文本邮件 + * @param to + * @param subject + * @param text + */ + public void sendTextMailMessage(String to,String subject,String text); + + /** + * 发送html邮件 + * @param to + * @param subject + * @param content + */ + public void sendHtmlMailMessage(String to,String subject,String content); + + /** + * 发送带附件的邮件 + * @param to 邮件收信人 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param filePath 附件路径 + */ + public void sendAttachmentMailMessage(String to,String subject,String content,String filePath); + + /** + * 发送注册邮箱验证码 + * @param to + * @param code + */ + public void sendCodeMailMessage(String to, String code); + + /** + * 发送登录邮箱验证码 + * @param to + * @param code + */ + public void sendLoginCodeMailMessage(String to, String code); + + /** + * 发送重置密码邮箱验证码 + * @param to + * @param code + */ + public void sendResetPwdMailMessage(String to, String code); + + /** + * 发送修改密码邮箱验证码 + * @param to + * @param code + */ + public void sendUpdatePwdMailMessage(String to, String code); + + + + public R emailCode(GetEmailCodeEntity entity); + + public R loginCode(GetLoginEmailCodeEntity entity); + + public R AddCreditCode(GetEmailCodeEntity entity); + + public R resetPwdCode(GetEmailCodeEntity entity); + + public R updatePwdCode(); + +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/service/SysLoginService.java b/m2pool-auth/src/main/java/com/m2pool/auth/service/SysLoginService.java new file mode 100644 index 0000000..ff70cb5 --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/service/SysLoginService.java @@ -0,0 +1,453 @@ +package com.m2pool.auth.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.shaded.org.checkerframework.checker.units.qual.A; +import com.m2pool.auth.entity.*; +import com.m2pool.common.core.RedisTransKey; +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.constant.Constants; +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.constant.UserConstants; +import com.m2pool.common.core.enums.UserStatus; +import com.m2pool.common.core.exception.ServiceException; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.ip.IpUtils; +import com.m2pool.common.core.utils.sign.RsaUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.service.TokenService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.RemoteLogService; +import com.m2pool.system.api.RemoteUserService; +import com.m2pool.system.api.entity.SysLogininfor; +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.api.model.LoginUser; +import org.apache.ibatis.annotations.Update; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + + +/** + * @Description 登录校验方法 + * @Date 2024/6/12 16:19 + * @Author dy + */ +@Component +public class SysLoginService { + + @Autowired + private RemoteLogService remoteLogService; + + @Autowired + private RemoteUserService remoteUserService; + + @Autowired + private TokenService tokenService; + + @Autowired + private RedisService redisService; + + //public static String PWD_REGEX="^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)[a-zA-Z0-9\\W]{8,32}$"; + public static String PWD_REGEX="^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[!@#$%^&*()./_])[A-Za-z\\d!@#$%^&*()./_]{8,32}$"; + + public static String EMAIL_REGEX="^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; + /** + * 登录 + */ + public R login(LoginBody loginBody) + { + //邮箱 + String email = loginBody.getUserName(); + + //String password= loginBody.getPassword(); + String password=""; + + try { + password = RsaUtils.decryptByPrivateKey(loginBody.getPassword()); + password = StringUtils.clean(password); + }catch (Exception e){ + return R.fail(401,"加密密码传参有误"); + } + + String emailCode = loginBody.getCode(); + // 用户名或密码为空 错误 + if (StringUtils.isAnyBlank(email, password)) + { + recordLogininfor(email, Constants.LOGIN_FAIL, "邮箱/密码必须填写"); + throw new ServiceException("邮箱/密码必须填写"); + } + if(!StringUtils.isBlank(email)){ + if(!email.matches(EMAIL_REGEX)){ + throw new ServiceException("邮箱格式错误"); + } + }else { + throw new ServiceException("邮箱为必填项"); + } + + // 密码如果不在指定范围内 错误 + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + recordLogininfor(email, Constants.LOGIN_FAIL, "用户密码不在指定范围"); + throw new ServiceException("用户密码不在指定范围"); + } + + + //if(!"admin".equals(username)){ + // if(!password.matches(PWD_REGEX)){ + // recordLogininfor(username, Constants.LOGIN_FAIL, "密码格式错误"); + // throw new ServiceException("密码格式错误,密码应包含大写字母、小写字母、数字以及特殊字符,8到32位"); + // } + //} + //todo 可以添加校验验证码的功能 + if("1328642438@qq.com".equals(email)){ + //不做处理 进入后续登录流程 + }else if(redisService.hasKey(RedisTransKey.getLoginKey(email))){ + + Object o = redisService.getCacheObject(RedisTransKey.getLoginKey(email));//user:emailCode:email + + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + //验证验证码 + if(emailCode.equals(emailCodeEntity.getEmailCode())){ + //不做处理 进入后续登录流程 + }else { + throw new ServiceException("验证码错误"); + } + }else { + throw new ServiceException("验证码未获取或已过期,请重新获取验证码"); + } + + // 查询用户信息 + R userResult = remoteUserService.getUserInfo(email, SecurityConstants.INNER); + + if (R.FAIL == userResult.getCode()) + { + throw new ServiceException(userResult.getMsg()); + } + + if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) + { + recordLogininfor(email, Constants.LOGIN_FAIL, "登录用户不存在"); + throw new ServiceException("登录用户:" + email + " 不存在"); + } + LoginUser userInfo = userResult.getData(); + //Long userid = userInfo.getUserid(); + SysUser user = userResult.getData().getSysUser(); + if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + recordLogininfor(email, Constants.LOGIN_FAIL, "对不起,您的账号已被删除"); + throw new ServiceException("对不起,您的账号:" + email + " 已被删除"); + } + if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + recordLogininfor(email, Constants.LOGIN_FAIL, "用户已停用,请联系管理员"); + throw new ServiceException("对不起,您的账号:" + email + " 已停用"); + } + //根据user:userId:lock + if(redisService.hasKey("user:"+email+":lock")){ + //账户输入密码错误 + throw new ServiceException("该账户因密码错误多次被暂时限制登陆24h!"); + } + + if (!SecurityUtils.matchesPassword(password, user.getPassword())) + { + recordLogininfor(email, Constants.LOGIN_FAIL, "用户密码错误"); + //redis 记录用户密码错误次数 大于5时锁定账户24小时 + int count = 0; + if(redisService.hasKey("user:"+email+":pwdError")){ + //判断key是否过期 + long expire = redisService.getExpire("user:" + email + ":pwdError", TimeUnit.SECONDS); + if(expire > 0){ + count = Convert.toInt(redisService.getCacheObject("user:"+email+":pwdError")); + } + } + count++; + if(count == 1){ + redisService.setCacheObject("user:"+email+":pwdError",count,1L,TimeUnit.HOURS); + }else if(count < 6){ + long expire = redisService.getExpire("user:" + email + ":pwdError", TimeUnit.SECONDS); + System.out.println(expire); + redisService.setCacheObject("user:"+email+":pwdError",count,redisService.getExpire("user:"+email+":pwdError",TimeUnit.SECONDS),TimeUnit.SECONDS); + } + if(count >= 5){ + //锁定用户账号 并清除输入错误次数的记录 + redisService.setCacheObject("user:"+email+":lock", email, 24L, TimeUnit.HOURS); + //清除输入错误次数的记录 + redisService.deleteObject("user:"+email+":pwdError"); + } + throw new ServiceException("密码错误,一小时内错误五次之后锁定账户24h,当前已失败"+count+"次"); + } + + //todo 判断当前ip和用户上一次登陆ip是否一致 + //if(StringUtils.isNotNull(userResult.getData().getSysUser().getLoginIp()) && StringUtils.isNotBlank(userResult.getData().getSysUser().getLoginIp())){ + // //上一次ip不为空 判断 + // if(StringUtils.isBlank(loginBody.getUuid()) || StringUtils.isNull(loginBody.getUuid())){ + // + // //没传uuid说明是刚发起的登录请求 需要判断ip是否发生变化 + // String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest()); + // if(!ipAddr.equals(userResult.getData().getSysUser().getLoginIp())){ + // return R.fail(201,"登录ip发生变化,请通过验证再登录"); + // } + // }else { + // //传了uuid说明是验证登录 验证 + // if (StringUtils.isBlank(loginBody.getCode())) + // { + // return R.fail("验证码不能为空"); + // } + // + // String verifyKey = Constants.CAPTCHA_CODE_KEY + loginBody.getUuid(); + // String captcha = redisService.getCacheObject(verifyKey); + // + // if(StringUtils.isNull(captcha)){ + // return R.fail("验证码已失效"); + // } + // //redisService.deleteObject(verifyKey); + // + // if (!loginBody.getCode().equalsIgnoreCase(captcha)) + // { + // return R.fail("验证码错误"); + // } + // redisService.deleteObject(verifyKey); + // } + // + //} + + + recordLogininfor(email, Constants.LOGIN_SUCCESS, "登录成功"); + + //修改数据库中的用户登录次数和最后一次登录的ip + user.setLoginCount(user.getLoginCount()+1); + user.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest())); + user.setLoginDate(DateUtils.getNowDate()); + + //todo 单独存储用户的登录时间和ip + remoteUserService.updateUserInfo(user); + + return R.success(tokenService.createToken(userInfo)); + } + + public void logout(String loginName) + { + recordLogininfor(loginName, Constants.LOGOUT, "退出成功"); + } + + /** + * 注册 + */ + public void register(RegisterBody registerBody) + { + String password = registerBody.getPassword(); + try { + password = RsaUtils.decryptByPrivateKey(registerBody.getPassword()); + password = StringUtils.clean(password); + }catch (Exception e){ + throw new ServiceException("加密密码传参有误",401); + } + + String email = registerBody.getEmail(); + + + String phone = registerBody.getPhone(); + String emailCode = registerBody.getEmailCode(); + //String key = username+"+"+email; + // 用户名或密码为空 错误 + if (StringUtils.isAnyBlank(email, password)) + { + throw new ServiceException("邮箱/密码必须填写"); + } + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + throw new ServiceException("密码长度必须在8到32个字符之间"); + } + if(!password.matches(PWD_REGEX)){ + throw new ServiceException("密码格式错误,密码应包含大写字母、小写字母、数字以及特殊字符,8到32位"); + } + + //todo 手机号验证 + if(!StringUtils.isEmpty(phone)){ + String phoneRex = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8}$"; + + if (!phone.matches(phoneRex)) + { + throw new ServiceException("手机号格式不正确"); + } + } + if(!StringUtils.isBlank(email)){ + if(!email.matches(EMAIL_REGEX)){ + throw new ServiceException("邮箱格式错误"); + } + }else { + throw new ServiceException("邮箱为必填项"); + } + + if(redisService.hasKey(RedisTransKey.getEmailKey(email))){ + + Object o = redisService.getCacheObject(RedisTransKey.getEmailKey(email));//user:emailCode:email + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + + //验证验证码 + if(emailCode.equals(emailCodeEntity.getEmailCode())){ + // 注册用户信息 + SysUser sysUser = new SysUser(); + sysUser.setUserName(email); + sysUser.setNickName(email); + sysUser.setPhone(phone); + sysUser.setEmail(email); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + R registerResult = remoteUserService.registerUserInfo(sysUser); + + if (R.FAIL == registerResult.getCode()) + { + throw new ServiceException(registerResult.getMsg()); + } + recordLogininfor(email, Constants.REGISTER, "注册成功"); + }else { + throw new ServiceException("验证码错误"); + } + }else { + throw new ServiceException("验证码未获取或已过期,请重新获取验证码"); + } + + } + + + /** + * 重置密码 + */ + public void resetPwd(ResetPwdBody resetPwdBody) + { + + String password = resetPwdBody.getPassword(); + try { + password = RsaUtils.decryptByPrivateKey(resetPwdBody.getPassword()); + }catch (Exception e){ + throw new ServiceException("加密密码传参有误",401); + //password = resetPwdBody.getPassword(); + } + + String email = resetPwdBody.getEmail(); + String resetPwdCode = resetPwdBody.getResetPwdCode(); + // 邮箱或密码为空 错误 + if (StringUtils.isAnyBlank(email, password)) + { + throw new ServiceException("邮箱/新密码不能为空"); + } + if(!StringUtils.isBlank(email)){ + if(!email.matches(EMAIL_REGEX)){ + throw new ServiceException("邮箱格式错误"); + } + }else { + throw new ServiceException("邮箱为必填项"); + } + + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + throw new ServiceException("密码长度必须在8到32个字符之间"); + } + //password格式校验 + if(!password.matches(PWD_REGEX)){ + throw new ServiceException("密码格式错误,密码应包含大写字母、小写字母、数字以及特殊字符,8到32位"); + } + R userResult = remoteUserService.getUserInfo(email, SecurityConstants.INNER); + + if (R.FAIL == userResult.getCode()) + { + throw new ServiceException("服务器请求失败请稍后再试"); + } + + if(SecurityUtils.matchesPassword(password,userResult.getData().getSysUser().getPassword())){ + throw new ServiceException("新密码不能与原密码一致"); + } + + //if(resetPwdBody.getGCode() == 0){ + // throw new ServiceException("验证码不能为空"); + //} + //谷歌验证码校验 + //R checkCodeResult = remoteUserService.checkGoogleCode(resetPwdBody.getGCode(),SecurityConstants.INNER); + // + //if (R.FAIL == checkCodeResult.getCode()) + //{ + // System.out.println(checkCodeResult); + // throw new ServiceException(checkCodeResult.getMsg()); + //} + // + //System.out.println(checkCodeResult); + + if(redisService.hasKey(RedisTransKey.getResetPwdKey(email))){ + + Object o = redisService.getCacheObject(RedisTransKey.getResetPwdKey(email));//user:emailCode:username + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + if (email.equals(emailCodeEntity.getEmail())) { + //邮箱必须和刚刚传的一致 + //验证验证码 + if(resetPwdCode.equals(emailCodeEntity.getEmailCode())){ + // 重置用户密码 + SysUser sysUser = new SysUser(); + sysUser.setEmail(email); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + + R resetPwdResult = remoteUserService.resetPwdByEmail(sysUser); + + if (R.FAIL == resetPwdResult.getCode()) + { + throw new ServiceException(resetPwdResult.getMsg()); + } + //recordLogininfor(username, Constants.REGISTER, "注册成功"); + }else { + throw new ServiceException("邮箱验证码错误"); + } + }else { + throw new ServiceException("请勿修改已输入的邮箱"); + } + }else { + ////判断是邮箱不存在还是操作超时 + ////通过邮箱获取用户 + //R userByEmail = remoteUserService.getUserInfo(email, SecurityConstants.INNER); + // + //if(StringUtils.isNull(userByEmail.getData())){ + // throw new ServiceException("邮箱"+email+"未被注册,请检查邮箱是否正确"); + //} + throw new ServiceException("验证码校验失败:操作超时,请重新操作"); + } + + } + + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息内容 + * @return + */ + public void recordLogininfor(String username, String status, String message) + { + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest())); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) + { + logininfor.setStatus(Constants.LOGIN_SUCCESS_STATUS); + } + else if (Constants.LOGIN_FAIL.equals(status)) + { + logininfor.setStatus(Constants.LOGIN_FAIL_STATUS); + } + remoteLogService.saveLogininfor(logininfor, SecurityConstants.INNER); + } + + +} diff --git a/m2pool-auth/src/main/java/com/m2pool/auth/service/impl/MaliServiceImpl.java b/m2pool-auth/src/main/java/com/m2pool/auth/service/impl/MaliServiceImpl.java new file mode 100644 index 0000000..527d0d8 --- /dev/null +++ b/m2pool-auth/src/main/java/com/m2pool/auth/service/impl/MaliServiceImpl.java @@ -0,0 +1,518 @@ +package com.m2pool.auth.service.impl; + +import com.alibaba.fastjson.JSON; +import com.m2pool.auth.entity.EmailCodeEntity; +import com.m2pool.auth.entity.GetEmailCodeEntity; +import com.m2pool.auth.entity.GetLoginEmailCodeEntity; +import com.m2pool.auth.service.MailService; +import com.m2pool.common.core.RedisTransKey; +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.exception.ServiceException; +import com.m2pool.common.core.utils.CodeUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.RemoteUserService; +import com.m2pool.system.api.model.LoginUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * @Description TODO + * @Date 2024/6/12 15:08 + * @Author dy + */ +@Service +public class MaliServiceImpl implements MailService { + /** + * 注入邮件工具类 + */ + @Autowired + private JavaMailSenderImpl javaMailSender; + + @Autowired + private RemoteUserService remoteUserService; + + @Autowired + private RedisService redisService; + + @Value("${spring.mail.username}") + private String sendMailer; + + public static String EMAIL_REGEX="^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; + + /** + * 检测邮件信息类 + * @param to + * @param subject + * @param text + */ + private void checkMail(String to,String subject,String text){ + if(StringUtils.isEmpty(to)){ + throw new RuntimeException("邮件收信人不能为空"); + } + if(StringUtils.isEmpty(subject)){ + throw new RuntimeException("邮件主题不能为空"); + } + if(StringUtils.isEmpty(text)){ + throw new RuntimeException("邮件内容不能为空"); + } + } + + /** + * 发送纯文本邮件 + * @param to + * @param subject + * @param text + */ + @Override + public void sendTextMailMessage(String to,String subject,String text){ + + try { + //true 代表支持复杂的类型 + MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true); + //邮件发信人 + mimeMessageHelper.setFrom(sendMailer); + //邮件收信人 1或多个 + mimeMessageHelper.setTo(to.split(",")); + //邮件主题 + mimeMessageHelper.setSubject(subject); + //邮件内容 + mimeMessageHelper.setText(text); + //邮件发送时间 + mimeMessageHelper.setSentDate(new Date()); + + //发送邮件 + javaMailSender.send(mimeMessageHelper.getMimeMessage()); + //System.out.println("发送邮件成功:"+sendMailer+"->"+to); + + } catch (Exception e) { + e.printStackTrace(); + //System.out.println("发送邮件失败:"+e.getMessage()); + } + } + + + /** + * 发送html邮件 + * @param to + * @param subject + * @param content + */ + @Override + public void sendHtmlMailMessage(String to,String subject,String content){ + + content="\n" + + "\n" + + "\n" + + "\n" + + "邮件\n" + + "\n" + + "\n" + + "\t

这是一封HTML邮件!

\n" + + "\n" + + ""; + try { + //true 代表支持复杂的类型 + MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true); + //邮件发信人 + mimeMessageHelper.setFrom(sendMailer); + //邮件收信人 1或多个 + mimeMessageHelper.setTo(to.split(",")); + //邮件主题 + mimeMessageHelper.setSubject(subject); + //邮件内容 true 代表支持html + mimeMessageHelper.setText(content,true); + //邮件发送时间 + mimeMessageHelper.setSentDate(new Date()); + + //发送邮件 + javaMailSender.send(mimeMessageHelper.getMimeMessage()); + System.out.println("发送邮件成功:"+sendMailer+"->"+to); + + } catch (Exception e) { + e.printStackTrace(); + System.out.println("发送邮件失败:"+e.getMessage()); + } + } + + /** + * 发送带附件的邮件 + * @param to 邮件收信人 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param filePath 附件路径 + */ + @Override + public void sendAttachmentMailMessage(String to,String subject,String content,String filePath){ + try { + //true 代表支持复杂的类型 + MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true); + //邮件发信人 + mimeMessageHelper.setFrom(sendMailer); + //邮件收信人 1或多个 + mimeMessageHelper.setTo(to.split(",")); + //邮件主题 + mimeMessageHelper.setSubject(subject); + //邮件内容 true 代表支持html + mimeMessageHelper.setText(content,true); + //邮件发送时间 + mimeMessageHelper.setSentDate(new Date()); + //添加邮件附件 + FileSystemResource file = new FileSystemResource(new File(filePath)); + String fileName = file.getFilename(); + mimeMessageHelper.addAttachment(fileName, file); + + //发送邮件 + javaMailSender.send(mimeMessageHelper.getMimeMessage()); + //System.out.println("发送邮件成功:"+sendMailer+"->"+to); + + } catch (Exception e) { + e.printStackTrace(); + //System.out.println("发送邮件失败:"+e.getMessage()); + } + } + + /** + * 发送账号注册邮箱验证码 + * @param to + * @param code + */ + @Override + public void sendCodeMailMessage(String to, String code) { + String subject = "账号注册,邮箱验证码"; + String text = "注册验证码10分钟内有效:\n\t"+code; + sendTextMailMessage(to,subject,text); + } + + /** + * 发送账号登录邮箱验证码 + * @param to + * @param code + */ + @Override + public void sendLoginCodeMailMessage(String to, String code) { + String subject = "用户登录,邮箱验证码"; + String text = "登录验证码10分钟内有效:\n\t"+code; + sendTextMailMessage(to,subject,text); + } + + + + @Override + public void sendResetPwdMailMessage(String to, String code) { + String subject = "账号重置密码,邮箱验证码"; + String text = "您正在重置密码,如果不是您本人操作,请忽略。验证码10分钟内有效:\n\t"+code; + sendTextMailMessage(to,subject,text); + } + + @Override + public void sendUpdatePwdMailMessage(String to, String code) { + String subject = "修改密码,邮箱验证码"; + String text = "您正在修改密码,如果不是您本人操作,请忽略。验证码10分钟内有效:\n\t"+code; + sendTextMailMessage(to,subject,text); + } + + @Override + public R emailCode(GetEmailCodeEntity entity) { + String email = entity.getEmail(); + if(!StringUtils.isBlank(email)){ + if(!email.matches(EMAIL_REGEX)){ + return R.fail("邮箱格式错误"); + } + }else { + return R.fail("601,\"邮箱不能为空\""); + } + + //手动格式验证 + + //判断用户是不是恶意刷邮箱,在规定时间内进行的 + if (redisService.hasKey(RedisTransKey.getEmailKey(email))) { + Object o = redisService.getCacheObject(RedisTransKey.getEmailKey(email));//user:emailCode:email + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + if (emailCodeEntity.getTimes() > 4) { + return R.fail("请求次数过多,请10分钟后再试,(同一用户10分钟内只能请求4次)"); + } else { + + String emailCode = CodeUtils.creatCode(6); + emailCodeEntity.setEmailCode(emailCode); + + + //if(!email.equals(emailCodeEntity.getEmail())){ + // //email与缓存中所存邮箱不同 则请求次数重置为1 + // emailCodeEntity.setTimes(1); + //}else { + // emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1); + //} + + emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1); + long overTime = redisService.getExpire(RedisTransKey.setEmailKey(email)); + redisService.setCacheObject(RedisTransKey.setEmailKey(email), emailCodeEntity, overTime, TimeUnit.SECONDS + ); + sendCodeMailMessage(email, emailCodeEntity.getEmailCode()); + } + } else { + R userByEmail = remoteUserService.getUserInfo(email, SecurityConstants.INNER); + + //if (userByName.getData() != null) { + // return R.fail(603,"用户名"+username+"已被注册"); + //} else + if(userByEmail.getData() !=null){ + + return R.fail(604,"邮箱"+email+"已被注册"); + } else { + String emailCode = CodeUtils.creatCode(6); + // 最多允许用户在10分钟内发送2次的邮箱验证 + // 0s倒计时后用户可以再发送验证码,但是间隔在10分钟内只能再发送1次 + EmailCodeEntity emailCodeEntity = new EmailCodeEntity( + email, emailCode,1 + ); + //设置失效时间10分钟 + redisService.setCacheObject(RedisTransKey.getEmailKey(email), emailCodeEntity, + 10L, TimeUnit.MINUTES + ); + sendCodeMailMessage(email, emailCodeEntity.getEmailCode()); + } + } + return R.success("请求成功,验证码已经发送至用户邮箱"); + + } + + @Override + public R loginCode(GetLoginEmailCodeEntity entity) { + String email = entity.getAccount(); + if(!StringUtils.isBlank(email)){ + if(!email.matches(EMAIL_REGEX)){ + return R.fail("邮箱格式错误"); + } + }else { + return R.fail(601,"邮箱不能为空"); + } + //String username = entity.getUserName(); + + //if(StringUtils.isNull(username)){ + // return R.fail(602,"用户名不能为空"); + //} + + //判断用户是不是恶意刷邮箱,在规定时间内进行的 + if (redisService.hasKey(RedisTransKey.getLoginKey(email))) { + Object o = redisService.getCacheObject(RedisTransKey.getLoginKey(email));//user:login:email + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + if (emailCodeEntity.getTimes() > 4) { + return R.fail("请求次数过多,请10分钟后再试,(同一用户10分钟内只能请求4次)"); + } else { + + String emailCode = CodeUtils.creatCode(6); + emailCodeEntity.setEmailCode(emailCode); + + //if(!email.equals(emailCodeEntity.getEmail())){ + // //email与缓存中所存邮箱不同 则请求次数重置为1 + // emailCodeEntity.setTimes(1); + //}else { + // emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1); + //} + + emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1); + long overTime = redisService.getExpire(RedisTransKey.setLoginKey(email)); + redisService.setCacheObject(RedisTransKey.setLoginKey(email), emailCodeEntity, overTime, TimeUnit.SECONDS + ); + sendLoginCodeMailMessage(email, emailCodeEntity.getEmailCode()); + } + } else { + R userByEmail = remoteUserService.getUserInfo(email, SecurityConstants.INNER); + + //if (userByName.getData() != null) { + // return R.fail(603,"用户名"+username+"已被注册"); + //} else + if(StringUtils.isNull(userByEmail.getData())){ + + return R.fail(606,"邮箱"+email+"未注册"); + } else { + String emailCode = CodeUtils.creatCode(6); + // 最多允许用户在10分钟内发送2次的邮箱验证 + // 0s倒计时后用户可以再发送验证码,但是间隔在10分钟内只能再发送1次 + EmailCodeEntity emailCodeEntity = new EmailCodeEntity( + email, emailCode,1 + ); + //设置失效时间10分钟 + redisService.setCacheObject(RedisTransKey.getLoginKey(email), emailCodeEntity, + 10L, TimeUnit.MINUTES + ); + sendLoginCodeMailMessage(email, emailCodeEntity.getEmailCode()); + } + } + return R.success("请求成功,验证码已经发送至用户邮箱"); + } + + @Override + public R AddCreditCode(GetEmailCodeEntity entity) { + String email = entity.getEmail(); + String username = entity.getUserName(); + + if(StringUtils.isNull(email)){ + return R.fail(601,"邮箱不能为空"); + } + if(StringUtils.isNull(username)){ + return R.fail(602,"用户名不能为空"); + } + + //判断用户是不是恶意刷邮箱,在规定时间内进行的 + if (redisService.hasKey(RedisTransKey.getAddCreditEmailKey(email))) { + long t1 = System.currentTimeMillis(); + Object o = redisService.getCacheObject(RedisTransKey.getAddCreditEmailKey(email));//user:addCreditEmailCode:email + + com.m2pool.system.api.entity.EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), com.m2pool.system.api.entity.EmailCodeEntity.class); + if (emailCodeEntity.getTimes() > 4) { + return R.fail("请求次数过多,请10分钟后再试,(同一用户10分钟内只能请求4次)"); + } else { + + String emailCode = CodeUtils.creatCode(6); + emailCodeEntity.setEmailCode(emailCode); + emailCodeEntity.setUserName(username); + + + emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1); + long overTime = redisService.getExpire(RedisTransKey.getAddCreditEmailKey(email)); + redisService.setCacheObject(RedisTransKey.setAddCreditEmailKey(email), emailCodeEntity, overTime, TimeUnit.SECONDS + ); + sendCodeMailMessage(email, emailCodeEntity.getEmailCode()); + } + long t2 = System.currentTimeMillis(); + System.out.println("处理业务1耗时"+(t2-t1)); + } else { + long t1 = System.currentTimeMillis(); + String emailCode = CodeUtils.creatCode(6); + // 最多允许用户在10分钟内发送2次的邮箱验证 + // 0s倒计时后用户可以再发送验证码,但是间隔在10分钟内只能再发送1次 + com.m2pool.system.api.entity.EmailCodeEntity emailCodeEntity = new com.m2pool.system.api.entity.EmailCodeEntity ( + username , email, emailCode,1 + ); + //设置失效时间10分钟 + redisService.setCacheObject(RedisTransKey.getAddCreditEmailKey(email), emailCodeEntity, + 10L, TimeUnit.MINUTES + ); + String subject = "用户提现邮箱验证码"; + String text = "提现验证码,10分钟内有效:\n\t"+emailCodeEntity.getEmailCode(); + long t2 = System.currentTimeMillis(); + System.out.println("处理业务2耗时"+(t2-t1)); + sendTextMailMessage(email,subject,text); + long t3 = System.currentTimeMillis(); + System.out.println("发送邮箱耗时"+(t3-t2)); + } + return R.success("请求成功,验证码已经发送至用户邮箱"); + + } + + @Override + public R resetPwdCode(GetEmailCodeEntity entity) { + String email = entity.getEmail(); + if(!StringUtils.isBlank(email)){ + if(!email.matches(EMAIL_REGEX)){ + return R.fail("邮箱格式错误"); + } + }else { + return R.fail(601,"邮箱为不能为空"); + } + + //通过邮箱获取用户 + R userByEmail = remoteUserService.getUserInfo(email, SecurityConstants.INNER); + + if(StringUtils.isNull(userByEmail.getData())){ + return R.fail("邮箱"+email+"未被注册,请检查邮箱是否正确"); + } + + //判断用户是不是恶意刷邮箱,在规定时间内进行的 + if (redisService.hasKey(RedisTransKey.getResetPwdKey(email))) { + + Object o = redisService.getCacheObject(RedisTransKey.getResetPwdKey(email));//user:restPwdCode:email + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + if (emailCodeEntity.getTimes() >= 5) { + return R.fail("请求次数过多,请10分钟后再试"); + } else { + //这里就不去判断两次绑定的邮箱是不是一样的了,不排除第一次输入错了邮箱的情况 + String emailCode = CodeUtils.creatCode(6); + emailCodeEntity.setEmailCode(emailCode); + emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1); + long overTime = redisService.getExpire(RedisTransKey.getResetPwdKey(email)); + redisService.setCacheObject(RedisTransKey.getResetPwdKey(email), emailCodeEntity, overTime, TimeUnit.SECONDS + ); + sendResetPwdMailMessage(email, emailCodeEntity.getEmailCode()); + } + } else { + String emailCode = CodeUtils.creatCode(6); + // 最多允许用户在10分钟内发送2次的邮箱验证 + // 0s倒计时后用户可以再发送验证码,但是间隔在10分钟内只能再发送1次 + EmailCodeEntity emailCodeEntity = new EmailCodeEntity( + email, emailCode,1 + ); + //设置失效时间10分钟 + redisService.setCacheObject(RedisTransKey.getResetPwdKey(email), emailCodeEntity, + 10L, TimeUnit.MINUTES + ); + sendResetPwdMailMessage(email, emailCodeEntity.getEmailCode()); + } + return R.success("请求成功,重置密码验证码已经发送至用户邮箱"); + + } + + @Override + public R updatePwdCode() { + String email = SecurityUtils.getUsername(); + if(StringUtils.isBlank(email)){ + return R.fail("token解析失败"); + } + //通过邮箱获取用户 + R userByEmail = remoteUserService.getUserInfo(email, SecurityConstants.INNER); + + if(StringUtils.isNull(userByEmail.getData())){ + return R.fail("token解析异常"); + } + + //判断用户是不是恶意刷邮箱,在规定时间内进行的 + if (redisService.hasKey(RedisTransKey.getUpdatePwdKey(email))) { + + Object o = redisService.getCacheObject(RedisTransKey.getUpdatePwdKey(email));//user:updatePwdCode:email + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + if (emailCodeEntity.getTimes() >= 5) { + return R.fail("请求次数过多,请10分钟后再试"); + } else { + //这里就不去判断两次绑定的邮箱是不是一样的了,不排除第一次输入错了邮箱的情况 + String emailCode = CodeUtils.creatCode(6); + emailCodeEntity.setEmailCode(emailCode); + emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1); + long overTime = redisService.getExpire(RedisTransKey.getUpdatePwdKey(email)); + redisService.setCacheObject(RedisTransKey.getUpdatePwdKey(email), emailCodeEntity, overTime, TimeUnit.SECONDS + ); + sendUpdatePwdMailMessage(email, emailCodeEntity.getEmailCode()); + } + } else { + String emailCode = CodeUtils.creatCode(6); + // 最多允许用户在10分钟内发送2次的邮箱验证 + // 0s倒计时后用户可以再发送验证码,但是间隔在10分钟内只能再发送1次 + EmailCodeEntity emailCodeEntity = new EmailCodeEntity( + email, emailCode,1 + ); + //设置失效时间10分钟 + redisService.setCacheObject(RedisTransKey.getUpdatePwdKey(email), emailCodeEntity, + 10L, TimeUnit.MINUTES + ); + sendUpdatePwdMailMessage(email, emailCodeEntity.getEmailCode()); + } + return R.success("请求成功,修改密码验证码已经发送至用户邮箱"); + + } + +} diff --git a/m2pool-auth/src/main/resources/bootstrap.yml b/m2pool-auth/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..ad7b21e --- /dev/null +++ b/m2pool-auth/src/main/resources/bootstrap.yml @@ -0,0 +1,159 @@ +#Tomcat +server: + port: 9200 + +# Spring +spring: + #邮箱基本配置 + mail: + # 配置在limit_time内,用户可以发送limit次验证码 + limit: 2 这个是我额外的配置,结合邮箱服务用的 + limitTime: 10 这个是我额外的配置 + #配置smtp服务主机地址 + # sina smtp.sina.cn + # aliyun smtp.aliyun.com + # 163 smtp.163.com 端口号465或994 + host: mail.privateemail.com + #发送者邮箱 + username: do.not.reply@m2pool.com + #配置密码,注意不是真正的密码,而是刚刚申请到的授权码 +# password: +# password: M2pool2024@! +# password: axvm-zfgx-cgcg-qhhu + password: M2202401! + + + #端口号 + port: 587 + #默认的邮件编码为UTF-8 + default-encoding: UTF-8 + #其他参数 + properties: + mail: + #配置SSL 加密工厂 + smtp: + ssl: + #本地测试,先放开ssl + enable: false + required: false + #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误 + debug: false + socketFactory: + class: javax.net.ssl.SSLSocketFactory + + application: + # 应用名称 + name: m2pool-auth + profiles: + # 环境配置 + active: prod + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: 127.0.0.1:8808 + namespace: m2_prod + group: m2_prod_group + config: + # 配置中心地址 + server-addr: 127.0.0.1:8808 + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} + namespace: m2_prod + group: m2_prod_group + +#server: +# port: 9500 +# +## Spring +#spring: +# #邮箱基本配置 +# mail: +# # 配置在limit_time内,用户可以发送limit次验证码 +# limit: 2 这个是我额外的配置,结合邮箱服务用的 +# limitTime: 10 这个是我额外的配置 +# #配置smtp服务主机地址 +# # sina smtp.sina.cn +# # aliyun smtp.aliyun.com +# # 163 smtp.163.com 端口号465或994 +# host: mail.privateemail.com +# #发送者邮箱 +# username: do.not.reply@m2pool.com +# #配置密码,注意不是真正的密码,而是刚刚申请到的授权码 +# # password: +# # password: M2202401! +# # password: axvm-zfgx-cgcg-qhhu +# password: M2202401! +# +# #端口号 +# port: 587 +## port: 465 +# #默认的邮件编码为UTF-8 +# default-encoding: UTF-8 +# #其他参数 +# properties: +# mail: +# #配置SSL 加密工厂 +# smtp: +# ssl: +# #本地测试,先放开ssl +# enable: false +# required: false +# #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误 +# debug: false +# socketFactory: +# class: javax.net.ssl.SSLSocketFactory +## +## host: smtp.qq.com +## #发送者邮箱 +## username: 1328642438@qq.com +## #配置密码,注意不是真正的密码,而是刚刚申请到的授权码 +## # password: +## # password: M2pool2024@! +## # password: axvm-zfgx-cgcg-qhhu +## password: eqrzqxeqzlshhedh +## +## #端口号 +## port: 465 +## #默认的邮件编码为UTF-8 +## default-encoding: UTF-8 +## #其他参数 +## properties: +## mail: +## #配置SSL 加密工厂 +## smtp: +## ssl: +## #本地测试,先放开ssl +## enable: true +## required: true +## #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误 +## debug: false +## socketFactory: +## class: javax.net.ssl.SSLSocketFactory +# +# application: +# # 应用名称 +# name: m2pool-auth +# profiles: +# # 环境配置 +# active: test +# cloud: +# nacos: +# discovery: +# # 服务注册地址 +# server-addr: 127.0.0.1:8808 +# namespace: m2_test +# group: m2_test_group +# config: +# # 配置中心地址 +# server-addr: 127.0.0.1:8808 +# # 配置文件格式 +# file-extension: yml +# # 共享配置 +# shared-configs: +# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} +# namespace: m2_test +# group: m2_test_group diff --git a/m2pool-auth/src/main/resources/logback.xml b/m2pool-auth/src/main/resources/logback.xml new file mode 100644 index 0000000..e518e07 --- /dev/null +++ b/m2pool-auth/src/main/resources/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-common/common-core/common-core.iml b/m2pool-common/common-core/common-core.iml new file mode 100644 index 0000000..6104229 --- /dev/null +++ b/m2pool-common/common-core/common-core.iml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-common/common-core/pom.xml b/m2pool-common/common-core/pom.xml new file mode 100644 index 0000000..dfa105c --- /dev/null +++ b/m2pool-common/common-core/pom.xml @@ -0,0 +1,133 @@ + + + + m2pool-common + com.m2pool + 3.5.0 + + 4.0.0 + + common-core + + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + + org.springframework + spring-context-support + + + + + org.projectlombok + lombok-maven-plugin + provided + + + + + org.springframework + spring-web + + + + + com.alibaba + transmittable-thread-local + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.alibaba + fastjson + + + + + io.jsonwebtoken + jjwt + + + + + javax.xml.bind + jaxb-api + + + + + org.apache.commons + commons-lang3 + + + + + commons-io + commons-io + + + + + commons-fileupload + commons-fileupload + + + + + org.apache.poi + poi-ooxml + + + + + javax.servlet + javax.servlet-api + + + + + io.swagger + swagger-annotations + + + + org.springframework.boot + spring-boot-starter-mail + + + + + + diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/RedisTransKey.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/RedisTransKey.java new file mode 100644 index 0000000..fe8b693 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/RedisTransKey.java @@ -0,0 +1,50 @@ +package com.m2pool.common.core; + +/** + * @Description TODO + * @Date 2024/6/12 15:35 + * @Author dy + */ +public class RedisTransKey { + + + public static final String RedisNameSpace="user"; + public static final String RedisTokenName="token"; + public static final String RedisLoginName="login"; + public static final String RedisEmailCodeName="emailCode"; + public static final String RedisAddCreditEmailCodeName="addCreditEmailCode"; + public static final String RedisResetPwdCodeName="restPwdCode"; + public static final String RedisUpdatePwdCodeName="updatePwdCode"; + + public static String setEmailKey(String key){ + return RedisNameSpace+":"+RedisEmailCodeName+":"+key; + } + public static String setRootKey(String key){ + return RedisNameSpace+":"+key+":"; + } + public static String setTokenKey(String key){ + return RedisNameSpace+':'+RedisTokenName+":"+key; + } + public static String setLoginKey(String key){ + return RedisNameSpace+':'+RedisLoginName+":"+key; + } + public static String setResetPwdKey(String key){ + return RedisNameSpace+":"+RedisResetPwdCodeName+":"+key; + } + public static String setUpdatePwdKey(String key){ + return RedisNameSpace+":"+RedisUpdatePwdCodeName+":"+key; + } + + public static String setAddCreditEmailKey(String key){ + return RedisNameSpace+":"+RedisAddCreditEmailCodeName+":"+key; + } + + public static String getEmailKey(String key){return setEmailKey(key);} + public static String getRootKey(String key){return setRootKey(key);} + public static String getTokenKey(String key){return setTokenKey(key);} + public static String getLoginKey(String key){return setLoginKey(key);} + public static String getResetPwdKey(String key){return setResetPwdKey(key);} + public static String getUpdatePwdKey(String key){return setUpdatePwdKey(key);} + public static String getAddCreditEmailKey(String key){return setAddCreditEmailKey(key);} + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/Result/R.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/Result/R.java new file mode 100644 index 0000000..fa6362f --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/Result/R.java @@ -0,0 +1,78 @@ +package com.m2pool.common.core.Result; + +import com.m2pool.common.core.constant.Constants; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 响应信息主体 + * @Date 2024/6/11 9:18 + * @Author dy + */ +@Data +public class R implements Serializable +{ + private static final long serialVersionUID = 3276842119125241681L; + + /** 成功 */ + public static final int SUCCESS = Constants.SUCCESS; + + /** 失败 */ + public static final int FAIL = Constants.FAIL; + + private int code; + + private String msg; + + private T data; + + public static R success() + { + return restResult(null, SUCCESS, null); + } + + public static R success(T data) + { + return restResult(data, SUCCESS, null); + } + + public static R success(T data, String msg) + { + return restResult(data, SUCCESS, msg); + } + + public static R fail() + { + return restResult(null, FAIL, null); + } + + public static R fail(String msg) + { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) + { + return restResult(data, FAIL, null); + } + + public static R fail(T data, String msg) + { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) + { + return restResult(null, code, msg); + } + + private static R restResult(T data, int code, String msg) + { + R apiResult = new R<>(); + apiResult.setCode(code); + apiResult.setData(data); + apiResult.setMsg(msg); + return apiResult; + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/Excel.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/Excel.java new file mode 100644 index 0000000..fd1cefe --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/Excel.java @@ -0,0 +1,163 @@ +package com.m2pool.common.core.annotation; + +import com.m2pool.common.core.utils.poi.ExcelHandlerAdapter; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.math.BigDecimal; + +/** + * 自定义导出Excel数据注解 + * + * @author dy + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Excel +{ + /** + * 导出时在excel中排序 + */ + public int sort() default Integer.MAX_VALUE; + + /** + * 导出到Excel中的名字. + */ + public String name() default ""; + + /** + * 日期格式, 如: yyyy-MM-dd + */ + public String dateFormat() default ""; + + /** + * 读取内容转表达式 (如: 0=男,1=女,2=未知) + */ + public String readConverterExp() default ""; + + /** + * 分隔符,读取字符串组内容 + */ + public String separator() default ","; + + /** + * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) + */ + public int scale() default -1; + + /** + * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN + */ + public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; + + /** + * 导出时在excel中每个列的高度 单位为字符 + */ + public double height() default 14; + + /** + * 导出时在excel中每个列的宽 单位为字符 + */ + public double width() default 16; + + /** + * 文字后缀,如% 90 变成90% + */ + public String suffix() default ""; + + /** + * 当值为空时,字段的默认值 + */ + public String defaultValue() default ""; + + /** + * 提示信息 + */ + public String prompt() default ""; + + /** + * 设置只能选择不能输入的列内容. + */ + public String[] combo() default {}; + + /** + * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. + */ + public boolean isExport() default true; + + /** + * 另一个类中的属性名称,支持多级获取,以小数点隔开 + */ + public String targetAttr() default ""; + + /** + * 是否自动统计数据,在最后追加一行统计数据总和 + */ + public boolean isStatistics() default false; + + /** + * 导出类型(0数字 1字符串) + */ + public ColumnType cellType() default ColumnType.STRING; + + /** + * 导出字体颜色 + */ + public IndexedColors color() default IndexedColors.BLACK; + + /** + * 导出字段对齐方式 + */ + public HorizontalAlignment align() default HorizontalAlignment.CENTER; + + /** + * 自定义数据处理器 + */ + public Class handler() default ExcelHandlerAdapter.class; + + /** + * 自定义数据处理器参数 + */ + public String[] args() default {}; + + /** + * 字段类型(0:导出导入;1:仅导出;2:仅导入) + */ + Type type() default Type.ALL; + + public enum Type + { + ALL(0), EXPORT(1), IMPORT(2); + private final int value; + + Type(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } + + public enum ColumnType + { + NUMERIC(0), STRING(1), IMAGE(2); + private final int value; + + ColumnType(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/Excels.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/Excels.java new file mode 100644 index 0000000..3daa65c --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/Excels.java @@ -0,0 +1,18 @@ +package com.m2pool.common.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel注解集 + * + * @author dy + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Excels +{ + Excel[] value(); +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/ParamName.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/ParamName.java new file mode 100644 index 0000000..b2494f7 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/ParamName.java @@ -0,0 +1,21 @@ +package com.m2pool.common.core.annotation; + +import java.lang.annotation.*; + +/** + * @Description TODO + * @Date 2024/6/12 17:24 + * @Author dy + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value={ElementType.FIELD}) +@Documented +@Inherited +public @interface ParamName { + /** + * 参数名称 + * + * @return + */ + String value(); +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/RateLimiter.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/RateLimiter.java new file mode 100644 index 0000000..0fc739d --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/annotation/RateLimiter.java @@ -0,0 +1,30 @@ +package com.m2pool.common.core.annotation; + +import com.m2pool.common.core.constant.CacheConstants; +import com.m2pool.common.core.constant.Constants; +import com.m2pool.common.core.enums.LimitType; + +import java.lang.annotation.*; + +/** + * @Description TODO + * @Date 2024/6/12 17:28 + * @Author dy + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter +{ + // 限流key + public String key() default CacheConstants.RATE_LIMIT_KEY; + + // 限流时间,单位秒 + public int time() default 30; + + // 限流次数 + public int count() default 30; + + // 限流类型 + public LimitType limitType() default LimitType.DEFAULT; +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/CacheConstants.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/CacheConstants.java new file mode 100644 index 0000000..dcbab10 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/CacheConstants.java @@ -0,0 +1,39 @@ +package com.m2pool.common.core.constant; + +/** + * @Description 缓存的key + * @Date 2024/6/12 17:22 + * @Author dy + */ +public class CacheConstants +{ + /** + * 缓存有效期,默认6个月 60*24*30*3=129600(分钟) + */ + public final static long EXPIRATION = 129600; + + /** + * 缓存刷新时间,默认120(分钟) + */ + public final static long REFRESH_TIME = 120; + + /** + * 权限缓存前缀 + */ + public final static String LOGIN_TOKEN_KEY = "login_tokens:"; + + /** + * 防重提交 redis key + */ + public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; + + /** + * 限流 redis key + */ + public static final String RATE_LIMIT_KEY = "rate_limit:"; + + /** + * api-key缓存前缀 + */ + public final static String OPEN_API_KEY = "api_key:"; +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/Constants.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/Constants.java new file mode 100644 index 0000000..33a90a3 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/Constants.java @@ -0,0 +1,126 @@ +package com.m2pool.common.core.constant; + +/** + * @Description 常量信息 + * @Date 2024/6/11 18:13 + * @Author dy + */ +public class Constants { + + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi:"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 成功标记 + */ + public static final Integer SUCCESS = 200; + + /** + * 失败标记 + */ + public static final Integer FAIL = 500; + + /** + * 登录成功状态 + */ + public static final String LOGIN_SUCCESS_STATUS = "0"; + + /** + * 登录失败状态 + */ + public static final String LOGIN_FAIL_STATUS = "1"; + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 注册 + */ + public static final String REGISTER = "Register"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; + + /** + * 验证码有效期(分钟) + */ + public static final long CAPTCHA_EXPIRATION = 2; + + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/HttpStatus.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/HttpStatus.java new file mode 100644 index 0000000..134745a --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/HttpStatus.java @@ -0,0 +1,101 @@ +package com.m2pool.common.core.constant; + +/** + * @Description 返回状态码 + * @Date 2024/6/12 11:35 + * @Author dy + */ +public class HttpStatus +{ + /** + * 操作成功 + */ + public static final int SUCCESS = 200; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 登陆过期 + */ + public static final int LOGINEXPIRES = 421; + + /** + * 登陆过期 + */ + public static final int TOKENEXPIRES = 422; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + public static final int TOO_MANY_REQUESTS = 429; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/OpenApiKeyConstants.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/OpenApiKeyConstants.java new file mode 100644 index 0000000..0f19530 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/OpenApiKeyConstants.java @@ -0,0 +1,42 @@ +package com.m2pool.common.core.constant; + +/** + * @Description 权限相关通用常量 + * @Date 2024/6/11 16:19 + * @Author dy + */ +public class OpenApiKeyConstants { + + /** + * 授权信息字段 + */ + public static final String API_KEY_HEADER = "API-KEY"; + + + /** + * api标识 + */ + public static final String API_KEY = "api_key"; + + /** + * api对应用户 + */ + public static final String API_USER = "api_user"; + + /** + * api 绑定ip + */ + public static final String API_IP = "api_ip"; + + /** + * api 相关信息 + */ + public static final String API_KEY_INFO = "api_key_info"; + + + /** + * 请求来源 + */ + public static final String FROM_SOURCE = "from-source"; + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/SecurityConstants.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/SecurityConstants.java new file mode 100644 index 0000000..95bbdab --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/SecurityConstants.java @@ -0,0 +1,45 @@ +package com.m2pool.common.core.constant; + +/** + * @Description 权限相关通用常量 + * @Date 2024/6/11 16:19 + * @Author dy + */ +public class SecurityConstants { + + /** + * 用户ID字段 + */ + public static final String DETAILS_USER_ID = "user_id"; + + /** + * 用户名字段 + */ + public static final String DETAILS_USERNAME = "username"; + + /** + * 授权信息字段 + */ + public static final String AUTHORIZATION_HEADER = "authorization"; + + /** + * 请求来源 + */ + public static final String FROM_SOURCE = "from-source"; + + /** + * 内部请求 + */ + public static final String INNER = "inner"; + + /** + * 用户标识 + */ + public static final String USER_KEY = "user_key"; + + /** + * 登录用户 + */ + public static final String LOGIN_USER = "login_user"; + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/ServiceNameConstants.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/ServiceNameConstants.java new file mode 100644 index 0000000..d24f948 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/ServiceNameConstants.java @@ -0,0 +1,32 @@ +package com.m2pool.common.core.constant; + +/** + * @Description 服务名常量 + * @Date 2024/6/12 17:14 + * @Author dy + */ +public class ServiceNameConstants +{ + /** + * 认证服务的serviceid + */ + public static final String AUTH_SERVICE = "m2pool-auth"; + + /** + * 系统模块的serviceid + */ + public static final String SYSTEM_SERVICE = "m2pool-system"; + + ///** + // * 邮件模块的serviceid + // */ + //public static final String Mail_SERVICE = "m2pool-mail"; + + /** + * 文件服务的serviceid + */ + public static final String FILE_SERVICE = "m2pool-file"; + + public static final String POOL_SERVICE = "m2pool-pool"; + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/TokenConstants.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/TokenConstants.java new file mode 100644 index 0000000..2e266b3 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/TokenConstants.java @@ -0,0 +1,30 @@ +package com.m2pool.common.core.constant; + +/** + * @Description Token的Key常量 + * @Date 2024/6/11 16:12 + * @Author dy + */ +public class TokenConstants { + + /** + * 令牌自定义标识 + */ + public static final String AUTHENTICATION = "Authorization"; + + public static final String API_KEY = "API-KEY"; + + /** + * 令牌前缀 + */ + public static final String PREFIX = "Bearer "; + + /** + * 令牌秘钥 + */ + //TODO 根据情况更改密钥 + public final static String SECRET = "mabsadba2basdsanwepsdaowwwxxsodddl"; + + public final static String API_SECRET = "mabsadba2bttxdnwcabpiowwwxxsodddl"; + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/UserConstants.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/UserConstants.java new file mode 100644 index 0000000..8cbccf2 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/constant/UserConstants.java @@ -0,0 +1,81 @@ +package com.m2pool.common.core.constant; + +/** + * @Description 用户相关常量 + * @Date 2024/6/12 17:39 + * @Author dy + */ +public class UserConstants +{ + /** + * 平台内系统用户的唯一标志 + */ + public static final String SYS_USER = "SYS_USER"; + + /** 正常状态 */ + public static final String NORMAL = "0"; + + /** 异常状态 */ + public static final String EXCEPTION = "1"; + + /** 用户封禁状态 */ + public static final String USER_DISABLE = "1"; + + /** 角色封禁状态 */ + public static final String ROLE_DISABLE = "1"; + + /** 部门正常状态 */ + public static final String DEPT_NORMAL = "0"; + + /** 部门停用状态 */ + public static final String DEPT_DISABLE = "1"; + + /** 字典正常状态 */ + public static final String DICT_NORMAL = "0"; + + /** 是否为系统默认(是) */ + public static final String YES = "Y"; + + /** 是否菜单外链(是) */ + public static final String YES_FRAME = "0"; + + /** 是否菜单外链(否) */ + public static final String NO_FRAME = "1"; + + /** 菜单类型(目录) */ + public static final String TYPE_DIR = "M"; + + /** 菜单类型(菜单) */ + public static final String TYPE_MENU = "C"; + + /** 菜单类型(按钮) */ + public static final String TYPE_BUTTON = "F"; + + /** Layout组件标识 */ + public final static String LAYOUT = "Layout"; + + /** ParentView组件标识 */ + public final static String PARENT_VIEW = "ParentView"; + + /** InnerLink组件标识 */ + public final static String INNER_LINK = "InnerLink"; + + /** 校验返回结果码 */ + public final static String UNIQUE = "0"; + + public final static String NOT_UNIQUE = "1"; + + /** + * 用户名长度限制 + */ + public static final int USERNAME_MIN_LENGTH = 3; + + public static final int USERNAME_MAX_LENGTH = 16; + + /** + * 密码长度限制 + */ + public static final int PASSWORD_MIN_LENGTH = 6; + + public static final int PASSWORD_MAX_LENGTH = 32; +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/context/OpenApiContextHolder.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/context/OpenApiContextHolder.java new file mode 100644 index 0000000..ad88e9d --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/context/OpenApiContextHolder.java @@ -0,0 +1,88 @@ +package com.m2pool.common.core.context; + +import com.alibaba.ttl.TransmittableThreadLocal; +import com.m2pool.common.core.constant.OpenApiKeyConstants; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.StringUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** +* @Description 获取当前线程变量中的 用户id、用户名称、Token等信息 +* @Date 2024/6/12 11:32 +* @Author dy +*/ +public class OpenApiContextHolder +{ + private static final TransmittableThreadLocal> THREAD_LOCAL = new TransmittableThreadLocal<>(); + + public static void set(String key, Object value) + { + Map map = getLocalMap(); + map.put(key, value == null ? StringUtils.EMPTY : value); + } + + public static String get(String key) + { + Map map = getLocalMap(); + return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY)); + } + + public static T get(String key, Class clazz) + { + Map map = getLocalMap(); + return StringUtils.cast(map.getOrDefault(key, null)); + } + + public static Map getLocalMap() + { + Map map = THREAD_LOCAL.get(); + if (map == null) + { + map = new ConcurrentHashMap(); + THREAD_LOCAL.set(map); + } + return map; + } + + public static void setLocalMap(Map threadLocalMap) + { + THREAD_LOCAL.set(threadLocalMap); + } + + public static String getApiUser() + { + return get(OpenApiKeyConstants.API_USER); + } + + public static void setApiUser(String user) + { + set(OpenApiKeyConstants.API_USER, user); + } + + public static String getApiIp() + { + return get(OpenApiKeyConstants.API_IP); + } + + public static void setApiIp(String ip) + { + set(OpenApiKeyConstants.API_IP, ip); + } + + public static String getApiKey() + { + return get(OpenApiKeyConstants.API_KEY); + } + + public static void setApiKey(String apiKey) + { + set(OpenApiKeyConstants.API_KEY, apiKey); + } + + public static void remove() + { + THREAD_LOCAL.remove(); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/context/SecurityContextHolder.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/context/SecurityContextHolder.java new file mode 100644 index 0000000..1518bd1 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/context/SecurityContextHolder.java @@ -0,0 +1,89 @@ +package com.m2pool.common.core.context; + +import com.alibaba.ttl.TransmittableThreadLocal; +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.StringUtils; + + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + /** + * @Description 获取当前线程变量中的 用户id、用户名称、Token等信息 + * @Date 2024/6/12 11:32 + * @Author dy + */ +public class SecurityContextHolder +{ + private static final TransmittableThreadLocal> THREAD_LOCAL = new TransmittableThreadLocal<>(); + + public static void set(String key, Object value) + { + Map map = getLocalMap(); + map.put(key, value == null ? StringUtils.EMPTY : value); + } + + public static String get(String key) + { + Map map = getLocalMap(); + return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY)); + } + + public static T get(String key, Class clazz) + { + Map map = getLocalMap(); + return StringUtils.cast(map.getOrDefault(key, null)); + } + + public static Map getLocalMap() + { + Map map = THREAD_LOCAL.get(); + if (map == null) + { + map = new ConcurrentHashMap(); + THREAD_LOCAL.set(map); + } + return map; + } + + public static void setLocalMap(Map threadLocalMap) + { + THREAD_LOCAL.set(threadLocalMap); + } + + public static Long getUserId() + { + return Convert.toLong(get(SecurityConstants.DETAILS_USER_ID), 0L); + } + + public static void setUserId(String account) + { + set(SecurityConstants.DETAILS_USER_ID, account); + } + + public static String getUserName() + { + return get(SecurityConstants.DETAILS_USERNAME); + } + + public static void setUserName(String username) + { + set(SecurityConstants.DETAILS_USERNAME, username); + } + + public static String getUserKey() + { + return get(SecurityConstants.USER_KEY); + } + + public static void setUserKey(String userKey) + { + set(SecurityConstants.USER_KEY, userKey); + } + + public static void remove() + { + THREAD_LOCAL.remove(); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/enums/LimitType.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/enums/LimitType.java new file mode 100644 index 0000000..7ab037e --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/enums/LimitType.java @@ -0,0 +1,14 @@ +package com.m2pool.common.core.enums; + +public enum LimitType { + + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/enums/UserRole.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/enums/UserRole.java new file mode 100644 index 0000000..2ef4e6a --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/enums/UserRole.java @@ -0,0 +1,34 @@ +package com.m2pool.common.core.enums; + +public enum UserRole { + ADMIN(1, "admin", "管理员"), + REGISTERED(2, "registered", "注册用户"), + ADVANCE(3, "advance", "付费用户"), + VIP(4, "vip", "vip"), + SUPPORT(5, "support", "支持人员"), + VERIFIER(6, "verifier", "审核人员"), + ACCOUNTTING(7, "accounting", "财务"); + + private final int roleId; + private final String role; + private final String des; + + UserRole(int roleId, String role, String des) + { + this.roleId = roleId; + this.role = role; + this.des = des; + } + + public int getRoleId() + { + return roleId; + } + + public String getRole() + { + return role; + } + + public String getDes(){return des;} +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/enums/UserStatus.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/enums/UserStatus.java new file mode 100644 index 0000000..0896d0a --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/enums/UserStatus.java @@ -0,0 +1,31 @@ +package com.m2pool.common.core.enums; + +/** + * @Description 用户状态 + * @Date 2024/6/12 14:52 + * @Author dy + */ +public enum UserStatus { + + OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除"); + + private final String code; + private final String info; + + UserStatus(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/CaptchaException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/CaptchaException.java new file mode 100644 index 0000000..407f1b2 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/CaptchaException.java @@ -0,0 +1,17 @@ +package com.m2pool.common.core.exception; + +/** + * @Description 验证码错误异常类 + * @Date 2024/6/13 10:51 + * @Author dy + */ +public class CaptchaException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + public CaptchaException(String msg) + { + super(msg); + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/GlobalException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/GlobalException.java new file mode 100644 index 0000000..8eecc0c --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/GlobalException.java @@ -0,0 +1,57 @@ +package com.m2pool.common.core.exception; + +/** + * @Description 全局异常 + * @Date 2024/6/12 14:39 + * @Author dy + */ +public class GlobalException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public GlobalException() + { + } + + public GlobalException(String message) + { + this.message = message; + } + + public String getDetailMessage() + { + return detailMessage; + } + + public GlobalException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } + + public String getMessage() + { + return message; + } + + public GlobalException setMessage(String message) + { + this.message = message; + return this; + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/InnerAuthException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/InnerAuthException.java new file mode 100644 index 0000000..9ae9bee --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/InnerAuthException.java @@ -0,0 +1,17 @@ +package com.m2pool.common.core.exception; + +/** + * @Description 内部认证异常 + * @Date 2024/6/11 18:23 + * @Author dy + */ +public class InnerAuthException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + public InnerAuthException(String message) + { + super(message); + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/PreAuthorizeException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/PreAuthorizeException.java new file mode 100644 index 0000000..cd1dce5 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/PreAuthorizeException.java @@ -0,0 +1,16 @@ +package com.m2pool.common.core.exception; + +/** + * @Description 权限异常 + * @Date 2024/6/11 11:31 + * @Author dy + */ +public class PreAuthorizeException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + public PreAuthorizeException() + { + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/ServiceException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/ServiceException.java new file mode 100644 index 0000000..3bd2ed9 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/ServiceException.java @@ -0,0 +1,75 @@ +package com.m2pool.common.core.exception; + +import lombok.Data; + +/** + * @Description 业务异常 + * @Date 2024/6/11 16:32 + * @Author dy + */ +@Data +public final class ServiceException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() + { + } + + public ServiceException(String message) + { + this.message = message; + } + + public ServiceException(String message, Integer code) + { + this.message = message; + this.code = code; + } + + public String getDetailMessage() + { + return detailMessage; + } + + public String getMessage() + { + return message; + } + + public Integer getCode() + { + return code; + } + + public ServiceException setMessage(String message) + { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } +} \ No newline at end of file diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/UtilException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/UtilException.java new file mode 100644 index 0000000..98a47ed --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.m2pool.common.core.exception; + +/** + * @Description 工具类异常 + * @Date 2024/6/12 11:32 + * @Author dy + */ +public class UtilException extends RuntimeException +{ + private static final long serialVersionUID = 8247642119125313488L; + + public UtilException(Throwable e) + { + super(e.getMessage(), e); + } + + public UtilException(String message) + { + super(message); + } + + public UtilException(String message, Throwable throwable) + { + super(message, throwable); + } +} \ No newline at end of file diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotApiKeyException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotApiKeyException.java new file mode 100644 index 0000000..4178322 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotApiKeyException.java @@ -0,0 +1,17 @@ +package com.m2pool.common.core.exception.auth; + +/** + * @Description 没有通过的登陆认证异常 + * @Date 2024/6/12 9:39 + * @Author dy + */ +public class NotApiKeyException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + public NotApiKeyException(String message) + { + super(message); + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotLoginException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotLoginException.java new file mode 100644 index 0000000..ed24b3d --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotLoginException.java @@ -0,0 +1,17 @@ +package com.m2pool.common.core.exception.auth; + +/** + * @Description 没有通过的登陆认证异常 + * @Date 2024/6/12 9:39 + * @Author dy + */ +public class NotLoginException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + public NotLoginException(String message) + { + super(message); + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotPermissionException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotPermissionException.java new file mode 100644 index 0000000..fb46c34 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotPermissionException.java @@ -0,0 +1,24 @@ +package com.m2pool.common.core.exception.auth; + +import org.apache.commons.lang3.StringUtils; + +/** + * @Description 未能通过的权限认证异常 + * @Date 2024/6/12 9:45 + * @Author dy + */ +public class NotPermissionException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + public NotPermissionException(String permission) + { + super(permission); + } + + public NotPermissionException(String[] permissions) + { + super(StringUtils.join(permissions, ",")); + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotRoleException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotRoleException.java new file mode 100644 index 0000000..8e54c76 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/auth/NotRoleException.java @@ -0,0 +1,25 @@ +package com.m2pool.common.core.exception.auth; + +import org.apache.commons.lang3.StringUtils; + +/** + * @Description 未能通过的角色认证异常 + * @Date 2024/6/12 9:48 + * @Author dy + */ +public class NotRoleException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + public NotRoleException(String role) + { + super(role); + } + + public NotRoleException(String[] roles) + { + super(StringUtils.join(roles, ",")); + } + + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/base/BaseException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/base/BaseException.java new file mode 100644 index 0000000..955f06b --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/base/BaseException.java @@ -0,0 +1,84 @@ +package com.m2pool.common.core.exception.base; + +import lombok.Data; + +/** + * @Description 基础异常 + * @Date 2024/6/12 11:42 + * @Author dy + */ +@Data +public class BaseException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + /** + * 所属模块 + */ + private String module; + + /** + * 错误码 + */ + private String code; + + /** + * 错误码对应的参数 + */ + private Object[] args; + + /** + * 错误消息 + */ + private String defaultMessage; + + public BaseException(String module, String code, Object[] args, String defaultMessage) + { + this.module = module; + this.code = code; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public BaseException(String module, String code, Object[] args) + { + this(module, code, args, null); + } + + public BaseException(String module, String defaultMessage) + { + this(module, null, null, defaultMessage); + } + + public BaseException(String code, Object[] args) + { + this(null, code, args, null); + } + + + public BaseException(String defaultMessage) + { + this(null, null, null, defaultMessage); + } + + public String getModule() + { + return module; + } + + public String getCode() + { + return code; + } + + public Object[] getArgs() + { + return args; + } + + public String getDefaultMessage() + { + return defaultMessage; + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/FileException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/FileException.java new file mode 100644 index 0000000..b29e5fd --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/FileException.java @@ -0,0 +1,19 @@ +package com.m2pool.common.core.exception.file; + +import com.m2pool.common.core.exception.base.BaseException; + +/** + * 文件信息异常类 + * + * @author dy + */ +public class FileException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public FileException(String code, Object[] args) + { + super("file", code, args); + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/FileNameLengthLimitExceededException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 0000000..4c6876a --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +package com.m2pool.common.core.exception.file; + +/** + * 文件名称超长限制异常类 + * + * @author dy + */ +public class FileNameLengthLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileNameLengthLimitExceededException(int defaultFileNameLength) + { + super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/FileSizeLimitExceededException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 0000000..aac1d68 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +package com.m2pool.common.core.exception.file; + +/** + * 文件名大小限制异常类 + * + * @author dy + */ +public class FileSizeLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileSizeLimitExceededException(long defaultMaxSize) + { + super("文件名超长", new Object[] { defaultMaxSize }); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/InvalidExtensionException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/InvalidExtensionException.java new file mode 100644 index 0000000..98f9dc3 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/file/InvalidExtensionException.java @@ -0,0 +1,82 @@ +package com.m2pool.common.core.exception.file; + +import org.apache.commons.fileupload.FileUploadException; + +import java.util.Arrays; + +/** + * 文件上传 误异常类 + * + * @author dy + */ +public class InvalidExtensionException extends FileUploadException +{ + private static final long serialVersionUID = 1L; + + private String[] allowedExtension; + private String extension; + private String filename; + + public InvalidExtensionException(String[] allowedExtension, String extension, String filename) + { + super("filename : [" + filename + "], extension : [" + extension + "], allowed extension : [" + Arrays.toString(allowedExtension) + "]"); + this.allowedExtension = allowedExtension; + this.extension = extension; + this.filename = filename; + } + + public String[] getAllowedExtension() + { + return allowedExtension; + } + + public String getExtension() + { + return extension; + } + + public String getFilename() + { + return filename; + } + + public static class InvalidImageExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidFlashExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidMediaExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidVideoExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/pool/PoolException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/pool/PoolException.java new file mode 100644 index 0000000..47587f5 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/pool/PoolException.java @@ -0,0 +1,17 @@ +package com.m2pool.common.core.exception.pool; + +import com.m2pool.common.core.exception.base.BaseException; + +/** + * @Description pool模块异常类 + * @Date 2024/6/12 14:05 + * @Author dy + */ +public class PoolException extends BaseException { + private static final long serialVersionUID = 1L; + + public PoolException(String code, Object[] args) { + + super("pool",code, args,null); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/user/CaptchaExpireException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/user/CaptchaExpireException.java new file mode 100644 index 0000000..8625532 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/user/CaptchaExpireException.java @@ -0,0 +1,17 @@ +package com.m2pool.common.core.exception.user; + +/** + * @Description 验证码失效异常类 + * @Date 2024/6/11 17:51 + * @Author dy + */ +public class CaptchaExpireException extends UserException{ + + private static final long serialVersionUID = 1L; + + public CaptchaExpireException() + { + super("user.jcaptcha.expire", null); + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/user/UserException.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/user/UserException.java new file mode 100644 index 0000000..9c0c2a3 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/exception/user/UserException.java @@ -0,0 +1,18 @@ +package com.m2pool.common.core.exception.user; + +import com.m2pool.common.core.exception.base.BaseException; + +/** + * @Description 用户信息异常类 + * @Date 2024/6/12 14:05 + * @Author dy + */ +public class UserException extends BaseException { + + private static final long serialVersionUID = 1L; + + public UserException(String code, Object[] args) { + + super("user",code, args,null); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/CharsetKit.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/CharsetKit.java new file mode 100644 index 0000000..6a830dd --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/CharsetKit.java @@ -0,0 +1,87 @@ +package com.m2pool.common.core.text; + +import com.m2pool.common.core.utils.StringUtils; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * @Description 字符集工具 + * @Date 2024/6/11 9:01 + * @Author dy + */ +public class CharsetKit +{ + /** ISO-8859-1 */ + public static final String ISO_8859_1 = "ISO-8859-1"; + /** UTF-8 */ + public static final String UTF_8 = "UTF-8"; + /** GBK */ + public static final String GBK = "GBK"; + + /** ISO-8859-1 */ + public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); + /** UTF-8 */ + public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); + /** GBK */ + public static final Charset CHARSET_GBK = Charset.forName(GBK); + + /** + * 转换为Charset对象 + * + * @param charset 字符集,为空则返回默认字符集 + * @return Charset + */ + public static Charset charset(String charset) + { + return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, String srcCharset, String destCharset) + { + return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, Charset srcCharset, Charset destCharset) + { + if (null == srcCharset) + { + srcCharset = StandardCharsets.ISO_8859_1; + } + + if (null == destCharset) + { + destCharset = StandardCharsets.UTF_8; + } + + if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) + { + return source; + } + return new String(source.getBytes(srcCharset), destCharset); + } + + /** + * @return 系统字符集编码 + */ + public static String systemCharset() + { + return Charset.defaultCharset().name(); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/Convert.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/Convert.java new file mode 100644 index 0000000..4bbdbee --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/Convert.java @@ -0,0 +1,916 @@ +package com.m2pool.common.core.text; + +import com.m2pool.common.core.utils.StringUtils; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.util.Set; + +/** + * @Description 类型转换器 + * @Date 2024/6/11 9:13 + * @Author dy + */ +public class Convert +{ + /** + * 转换为字符串
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static String toStr(Object value, String defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof String) + { + return (String) value; + } + return value.toString(); + } + + /** + * 转换为字符串
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static String toStr(Object value) + { + return toStr(value, null); + } + + /** + * 转换为字符
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Character toChar(Object value, Character defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof Character) + { + return (Character) value; + } + + final String valueStr = toStr(value, null); + return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); + } + + /** + * 转换为字符
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static Character toChar(Object value) + { + return toChar(value, null); + } + + /** + * 转换为byte
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Byte toByte(Object value, Byte defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Byte) + { + return (Byte) value; + } + if (value instanceof Number) + { + return ((Number) value).byteValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Byte.parseByte(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为byte
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static Byte toByte(Object value) + { + return toByte(value, null); + } + + /** + * 转换为Short
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Short toShort(Object value, Short defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Short) + { + return (Short) value; + } + if (value instanceof Number) + { + return ((Number) value).shortValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Short.parseShort(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Short
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static Short toShort(Object value) + { + return toShort(value, null); + } + + /** + * 转换为Number
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Number toNumber(Object value, Number defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Number) + { + return (Number) value; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return NumberFormat.getInstance().parse(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Number
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static Number toNumber(Object value) + { + return toNumber(value, null); + } + + /** + * 转换为int
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Integer toInt(Object value, Integer defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Integer) + { + return (Integer) value; + } + if (value instanceof Number) + { + return ((Number) value).intValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Integer.parseInt(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为int
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static Integer toInt(Object value) + { + return toInt(value, null); + } + + /** + * 转换为Integer数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String str) + { + return toIntArray(",", str); + } + + /** + * 转换为Long数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String str) + { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Integer[] {}; + } + String[] arr = str.split(split); + final Integer[] ints = new Integer[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Integer v = toInt(arr[i], 0); + ints[i] = v; + } + return ints; + } + + /** + * 转换为Long数组
+ * + * @param split 分隔符 + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Long[] {}; + } + String[] arr = str.split(split); + final Long[] longs = new Long[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Long v = toLong(arr[i], null); + longs[i] = v; + } + return longs; + } + + /** + * 转换为String数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String str) + { + return toStrArray(",", str); + } + + /** + * 转换为String数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String split, String str) + { + return str.split(split); + } + + /** + * 转换为long
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Long toLong(Object value, Long defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Long) + { + return (Long) value; + } + if (value instanceof Number) + { + return ((Number) value).longValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).longValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为long
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static Long toLong(Object value) + { + return toLong(value, null); + } + + /** + * 转换为double
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Double toDouble(Object value, Double defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Double) + { + return (Double) value; + } + if (value instanceof Number) + { + return ((Number) value).doubleValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).doubleValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为double
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static Double toDouble(Object value) + { + return toDouble(value, null); + } + + /** + * 转换为Float
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Float toFloat(Object value, Float defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Float) + { + return (Float) value; + } + if (value instanceof Number) + { + return ((Number) value).floatValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Float.parseFloat(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Float
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static Float toFloat(Object value) + { + return toFloat(value, null); + } + + /** + * 转换为boolean
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Boolean toBool(Object value, Boolean defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Boolean) + { + return (Boolean) value; + } + String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + valueStr = valueStr.trim().toLowerCase(); + switch (valueStr) + { + case "true": + return true; + case "false": + return false; + case "yes": + return true; + case "ok": + return true; + case "no": + return false; + case "1": + return true; + case "0": + return false; + default: + return defaultValue; + } + } + + /** + * 转换为boolean
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static Boolean toBool(Object value) + { + return toBool(value, null); + } + + /** + * 转换为BigInteger
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value, BigInteger defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigInteger) + { + return (BigInteger) value; + } + if (value instanceof Long) + { + return BigInteger.valueOf((Long) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigInteger(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigInteger
+ * + * @param value 被转换的值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value) + { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal
+ * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigDecimal) + { + return (BigDecimal) value; + } + if (value instanceof Long) + { + return new BigDecimal((Long) value); + } + if (value instanceof Double) + { + return new BigDecimal((Double) value); + } + if (value instanceof Integer) + { + return new BigDecimal((Integer) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigDecimal(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value) + { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串
+ * + * @param obj 对象 + * @return 字符串 + */ + public static String utf8Str(Object obj) + { + return str(obj, CharsetKit.CHARSET_UTF_8); + } + + /** + * 将对象转为字符串
+ * + * @param obj 对象 + * @param charsetName 字符集 + * @return 字符串 + */ + public static String str(Object obj, String charsetName) + { + return str(obj, Charset.forName(charsetName)); + } + + /** + * 将对象转为字符串
+ * + * @param obj 对象 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(Object obj, Charset charset) + { + if (null == obj) + { + return null; + } + + if (obj instanceof String) + { + return (String) obj; + } + else if (obj instanceof byte[] || obj instanceof Byte[]) + { + if (obj instanceof byte[]) + { + return str((byte[]) obj, charset); + } + else + { + Byte[] bytes = (Byte[]) obj; + int length = bytes.length; + byte[] dest = new byte[length]; + for (int i = 0; i < length; i++) + { + dest[i] = bytes[i]; + } + return str(dest, charset); + } + } + else if (obj instanceof ByteBuffer) + { + return str((ByteBuffer) obj, charset); + } + return obj.toString(); + } + + /** + * 将byte数组转为字符串 + * + * @param bytes byte数组 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(byte[] bytes, String charset) + { + return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); + } + + /** + * 解码字节码 + * + * @param data 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * @return 解码后的字符串 + */ + public static String str(byte[] data, Charset charset) + { + if (data == null) + { + return null; + } + + if (null == charset) + { + return new String(data); + } + return new String(data, charset); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, String charset) + { + if (data == null) + { + return null; + } + + return str(data, Charset.forName(charset)); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, Charset charset) + { + if (null == charset) + { + charset = Charset.defaultCharset(); + } + return charset.decode(data).toString(); + } + + // ----------------------------------------------------------------------- 全角半角转换 + /** + * 半角转全角 + * + * @param input String. + * @return 全角字符串. + */ + public static String toSBC(String input) + { + return toSBC(input, null); + } + + /** + * 半角转全角 + * + * @param input String + * @param notConvertSet 不替换的字符集合 + * @return 全角字符串. + */ + public static String toSBC(String input, Set notConvertSet) + { + char c[] = input.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == ' ') + { + c[i] = '\u3000'; + } + else if (c[i] < '\177') + { + c[i] = (char) (c[i] + 65248); + + } + } + return new String(c); + } + + /** + * 全角转半角 + * + * @param input String. + * @return 半角字符串 + */ + public static String toDBC(String input) + { + return toDBC(input, null); + } + + /** + * 替换全角为半角 + * + * @param text 文本 + * @param notConvertSet 不替换的字符集合 + * @return 替换后的字符 + */ + public static String toDBC(String text, Set notConvertSet) + { + char c[] = text.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == '\u3000') + { + c[i] = ' '; + } + else if (c[i] > '\uFF00' && c[i] < '\uFF5F') + { + c[i] = (char) (c[i] - 65248); + } + } + String returnString = new String(c); + + return returnString; + } + + /** + * 数字金额大写转换 先写个完整的然后将如零拾替换成零 + * + * @param n 数字 + * @return 中文大写数字 + */ + public static String digitUppercase(double n) + { + String[] fraction = { "角", "分" }; + String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; + String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } }; + + String head = n < 0 ? "负" : ""; + n = Math.abs(n); + + String s = ""; + for (int i = 0; i < fraction.length; i++) + { + s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); + } + if (s.length() < 1) + { + s = "整"; + } + int integerPart = (int) Math.floor(n); + + for (int i = 0; i < unit[0].length && integerPart > 0; i++) + { + String p = ""; + for (int j = 0; j < unit[1].length && n > 0; j++) + { + p = digit[integerPart % 10] + unit[1][j] + p; + integerPart = integerPart / 10; + } + s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; + } + return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整"); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/DoubleCalculate.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/DoubleCalculate.java new file mode 100644 index 0000000..9f2fa44 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/DoubleCalculate.java @@ -0,0 +1,144 @@ +package com.m2pool.common.core.text; + +import java.math.BigDecimal; + +/** + * @Description 浮点数计算 + * @Date 2024/6/12 15:46 + * @Author dy + */ +public class DoubleCalculate { + + /** + * 除法 v1除v2 默认保留一位小数 四舍五入 + * @param v1 + * @param v2 + * @return + */ + public static double divide(double v1, double v2) { + + return divide(v1,v2,1); + + } + + /** + * 除法 v1除v2 默认保留一位小数 向上取整 + * @param v1 + * @param v2 + * @return + */ + public static double divideCEILING(double v1, double v2) { + + return divideCEILING(v1,v2,1); + + } + + /** + * 除法 v1除v2 默认保留一位小数 向下取整 + * @param v1 + * @param v2 + * @return + */ + public static double divideFLOOR(double v1, double v2) { + + return divideFLOOR(v1,v2,1); + + } + + /** + * 除法 v1除v2 向上取整 + * @param v1 + * @param v2 + * @return + */ + public static double divideCEILING(double v1, double v2,int scale) { + + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + + //scale表示保留几位小数 ROUND_CEILING向上取整 + return b1.divide(b2,scale, BigDecimal.ROUND_CEILING).doubleValue(); + } + + /** + * 除法 v1除v2 向下取整 + * @param v1 + * @param v2 + * @return + */ + public static double divideFLOOR(double v1, double v2,int scale) { + + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + + //scale表示保留几位小数 ROUND_FLOOR + return b1.divide(b2,scale, BigDecimal.ROUND_FLOOR).doubleValue(); + } + + /** + * 除法 v1除v2 四舍五入 + * @param v1 + * @param v2 + * @return + */ + public static double divide(double v1, double v2,int scale) { + + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + + //scale表示保留几位小数 ROUND_HALF_UP四舍五入 + return b1.divide(b2,scale, BigDecimal.ROUND_HALF_UP).doubleValue(); + } + + /** + * v1-v2 + * @param v1 + * @param v2 + * @return + */ + public static double subtract(double v1, double v2) { + + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + + return b1.subtract(b2).doubleValue(); + + } + + /** + * v1 + v2 + * @param v1 + * @param v2 + * @return + */ + public static double add(double v1, double v2) { + + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + + return b1.add(b2).doubleValue(); + + } + + /** + * v1*v2 + * @param v1 + * @param v2 + * @return + */ + public static double multiply(double v1, double v2) { + + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + + return b1.multiply(b2).doubleValue(); + + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/StrFormatter.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/StrFormatter.java new file mode 100644 index 0000000..4bb4f3a --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/text/StrFormatter.java @@ -0,0 +1,92 @@ +package com.m2pool.common.core.text; + +import com.m2pool.common.core.utils.StringUtils; + +/** + * @Description 字符串格式化 + * @Date 2024/6/13 9:52 + * @Author dy + */ +public class StrFormatter +{ + public static final String EMPTY_JSON = "{}"; + public static final char C_BACKSLASH = '\\'; + public static final char C_DELIM_START = '{'; + public static final char C_DELIM_END = '}'; + + /** + * 格式化字符串
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param strPattern 字符串模板 + * @param argArray 参数列表 + * @return 结果 + */ + public static String format(final String strPattern, final Object... argArray) + { + if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) + { + return strPattern; + } + final int strPatternLength = strPattern.length(); + + // 初始化定义好的长度以获得更好的性能 + StringBuilder sbuf = new StringBuilder(strPatternLength + 50); + + int handledPosition = 0; + int delimIndex;// 占位符所在位置 + for (int argIndex = 0; argIndex < argArray.length; argIndex++) + { + delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); + if (delimIndex == -1) + { + if (handledPosition == 0) + { + return strPattern; + } + else + { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + sbuf.append(strPattern, handledPosition, strPatternLength); + return sbuf.toString(); + } + } + else + { + if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) + { + if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) + { + // 转义符之前还有一个转义符,占位符依旧有效 + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + else + { + // 占位符被转义 + argIndex--; + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(C_DELIM_START); + handledPosition = delimIndex + 1; + } + } + else + { + // 正常占位符 + sbuf.append(strPattern, handledPosition, delimIndex); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + } + } + // 加入最后一个占位符后所有的字符 + sbuf.append(strPattern, handledPosition, strPattern.length()); + + return sbuf.toString(); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/CheckMobile.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/CheckMobile.java new file mode 100644 index 0000000..444939b --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/CheckMobile.java @@ -0,0 +1,47 @@ +//package com.m2pool.common.core.utils; +// +//import java.util.regex.Matcher; +//import java.util.regex.Pattern; +// +///** +// * @Description TODO +// * @Date 2024/6/14 11:47 +// * @Author dy +// */ +//public class CheckMobile { +// +// // \b 是单词边界(连着的两个(字母字符 与 非字母字符) 之间的逻辑上的间隔), +// // 字符串在编译时会被转码一次,所以是 "\\b" +// // \B 是单词内部逻辑间隔(连着的两个字母字符之间的逻辑上的间隔) +// static String phoneReg = "\\b(ip(hone|od)|android|opera m(ob|in)i" +// +"|windows (phone|ce)|blackberry" +// +"|s(ymbian|eries60|amsung)|p(laybook|alm|rofile/midp" +// +"|laystation portable)|nokia|fennec|htc[-_]" +// +"|mobile|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\b"; +// static String tableReg = "\\b(ipad|tablet|(Nexus 7)|up.browser" +// +"|[1-4][0-9]{2}x[1-4][0-9]{2})\\b"; +// +// //移动设备正则匹配:手机端、平板 +// static Pattern phonePat = Pattern.compile(phoneReg, Pattern.CASE_INSENSITIVE); +// static Pattern tablePat = Pattern.compile(tableReg, Pattern.CASE_INSENSITIVE); +// +// /** +// * @Description 检测是否是移动设备访问 +// * @param userAgent 浏览器标识 +// * @return true:pc端接入,false:移动设备接入 +// */ +// public static boolean isPC(String userAgent){ +// if(null == userAgent){ +// userAgent = ""; +// } +// // 匹配 +// Matcher matcherPhone = phonePat.matcher(userAgent); +// Matcher matcherTable = tablePat.matcher(userAgent); +// if(matcherPhone.find() || matcherTable.find()){ +// return false; +// } else { +// return true; +// } +// } +// +//} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/CodeUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/CodeUtils.java new file mode 100644 index 0000000..05650a1 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/CodeUtils.java @@ -0,0 +1,43 @@ +package com.m2pool.common.core.utils; + +import java.util.Random; + +/** + * @Description TODO + * @Date 2024/6/13 14:26 + * @Author dy + */ +public class CodeUtils { + //public static void main(String[] args) { + // String s = creatCode(4); + // //System.out.println("随机验证码为:" + s); + //} + + //定义一个方法返回一个随机验证码 + public static String creatCode(int n) { + + String code = ""; + Random r = new Random(); + //2.在方法内部使用for循环生成指定位数的随机字符,并连接起来 + for (int i = 0; i <= n; i++) { + //生成一个随机字符:大写 ,小写 ,数字(0 1 2) + int type = r.nextInt(3); + switch (type) { + case 0: + char ch = (char) (r.nextInt(26) + 65); + code += ch; + break; + case 1: + char ch1 = (char) (r.nextInt(26) + 97); + code += ch1; + break; + case 2: + code += r.nextInt(10); + break; + } + + } + return code; + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/DateUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/DateUtils.java new file mode 100644 index 0000000..b5a556a --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/DateUtils.java @@ -0,0 +1,323 @@ +package com.m2pool.common.core.utils; + +import org.apache.commons.lang3.time.DateFormatUtils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * @Description 时间工具类 + * @Date 2024/6/13 11:21 + * @Author dy + */ +public class DateUtils extends org.apache.commons.lang3.time.DateUtils +{ + public static String YYYY = "yyyy"; + + public static String YYYY_MM = "yyyy-MM"; + + public static String YYYY_MM_DD = "yyyy-MM-dd"; + + public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + + public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private static String[] parsePatterns = { + "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", + "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; + + /** + * 获取当前Date型日期 + * + * @return Date() 当前日期 + */ + public static Date getNowDate() + { + return new Date(); + } + + /** + * 获取当前日期, 默认格式为yyyy-MM-dd + * + * @return String + */ + public static String getDate() + { + return dateTimeNow(YYYY_MM_DD); + } + + public static final String getTime() + { + return dateTimeNow(YYYY_MM_DD_HH_MM_SS); + } + + public static final String dateTimeNow() + { + return dateTimeNow(YYYYMMDDHHMMSS); + } + + public static final String dateTimeNow(final String format) + { + return parseDateToStr(format, new Date()); + } + + public static final String dateTime(final Date date) + { + return parseDateToStr(YYYY_MM_DD, date); + } + + public static final String parseDateToStr(final String format, final Date date) + { + return new SimpleDateFormat(format).format(date); + } + + public static final Date dateTime(final String format, final String ts) + { + try + { + return new SimpleDateFormat(format).parse(ts); + } + catch (ParseException e) + { + throw new RuntimeException(e); + } + } + + /** + * 日期路径 即年/月/日 如2018/08/08 + */ + public static final String datePath() + { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyy/MM/dd"); + } + + /** + * 日期路径 即年/月/日 如20180808 + */ + public static final String dateTime() + { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyyMMdd"); + } + + /** + * 日期型字符串转化为日期 格式 + */ + public static Date parseDate(Object str) + { + if (str == null) + { + return null; + } + try + { + return parseDate(str.toString(), parsePatterns); + } + catch (ParseException e) + { + return null; + } + } + + /** + * 日期型字符串转化为日期 格式 + */ + public static Date parseCNDate(Object str) + { + if (str == null) + { + return null; + } + try + { + Date date = parseDate(str.toString(), parsePatterns); + return UTCToCNDate(date); + } + catch (ParseException e) + { + return null; + } + } + + /** + * 获取服务器启动时间 + */ + public static Date getServerStartDate() + { + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new Date(time); + } + + /** + * 计算两个时间差 + */ + public static String getDatePoor(Date endDate, Date nowDate) + { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - nowDate.getTime(); + // 计算差多少天 + long day = diff / nd; + // 计算差多少小时 + long hour = diff % nd / nh; + // 计算差多少分钟 + long min = diff % nd % nh / nm; + // 计算差多少秒//输出结果 + // long sec = diff % nd % nh % nm / ns; + return day + "天" + hour + "小时" + min + "分钟"; + } + /** + * 计算两个时间差 天数 + */ + public static Long getDateDiff(Date endDate, Date nowDate) + { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - nowDate.getTime(); + // 计算差多少天 + long day = diff / nd; + return day; + } + + public static Long getDateDiffSecond(Date endDate, Date nowDate) + { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - nowDate.getTime(); + // 计算差多少秒 + long sec = diff % nd % nh % nm / ns; + return sec; + } + + public static Long getDateDiffMinute(Date endDate, Date preDate) + { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - preDate.getTime(); + // 计算差多少秒 + long minute = diff / nm; + return minute; + } + + /** + * 计算两个时间差 按月 + */ + public static int getMonthDiff(Date d1, Date d2) { + Calendar c1 = Calendar.getInstance(); + Calendar c2 = Calendar.getInstance(); + c1.setTime(d1); + c2.setTime(d2); + int year1 = c1.get(Calendar.YEAR); + int year2 = c2.get(Calendar.YEAR); + int month1 = c1.get(Calendar.MONTH); + int month2 = c2.get(Calendar.MONTH); + int day1 = c1.get(Calendar.DAY_OF_MONTH); + int day2 = c2.get(Calendar.DAY_OF_MONTH); + // 获取年的差值 + int yearInterval = year1 - year2; + // 如果 d1的 月-日 小于 d2的 月-日 那么 yearInterval-- 这样就得到了相差的年数 + if (month1 < month2 || month1 == month2 && day1 < day2) { + yearInterval--; + } + // 获取月数差值 + int monthInterval = (month1 + 12) - month2; + if (day1 < day2) { + monthInterval--; + } + monthInterval %= 12; + int monthsDiff = Math.abs(yearInterval * 12 + monthInterval); + return monthsDiff; + } + + + /** + * 增加 LocalDateTime ==> Date + */ + public static Date toDate(LocalDateTime temporalAccessor) + { + ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** + * 增加 LocalDate ==> Date + */ + public static Date toDate(LocalDate temporalAccessor) + { + LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + public static LocalDate toLocalDate(Date date) + { + LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + return localDate; + } + + public static LocalDateTime toLocalDateTime(Date date) + { + LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + return localDateTime; + } + + public static boolean isSameDay(final Date d1,final Date d2){ + String s1 = dateTime(d1); + String s2 = dateTime(d2); + + if (s1 == null || s2 == null){ + return false; + } + + if (s1.equals(s2)){ + return true; + } + return false; + } + + public static boolean isSameDate(final Date d1,final Date d2){ + + String s1 = parseDateToStr(YYYY_MM_DD_HH_MM_SS,d1); + String s2 = parseDateToStr(YYYY_MM_DD_HH_MM_SS,d2); + + if (s1 == null || s2 == null){ + return false; + } + + if (s1.equals(s2)){ + return true; + } + return false; + } + + public static Date UTCToCNDate(Date date) + { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + // 设置时区为北京 + simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); + + String format = simpleDateFormat.format(date); + + return DateUtils.parseDate(format); + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/ExceptionUtil.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/ExceptionUtil.java new file mode 100644 index 0000000..0225b2b --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/ExceptionUtil.java @@ -0,0 +1,40 @@ +package com.m2pool.common.core.utils; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * @Description 错误信息处理类 + * @Date 2024/6/12 14:06 + * @Author dy + */ +public class ExceptionUtil +{ + /** + * 获取exception的详细错误信息。 + */ + public static String getExceptionMessage(Throwable e) + { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + return sw.toString(); + } + + public static String getRootErrorMessage(Exception e) + { + Throwable root = ExceptionUtils.getRootCause(e); + root = (root == null ? e : root); + if (root == null) + { + return ""; + } + String msg = root.getMessage(); + if (msg == null) + { + return "null"; + } + return StringUtils.defaultString(msg); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/FileUploadUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/FileUploadUtils.java new file mode 100644 index 0000000..5d970b8 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/FileUploadUtils.java @@ -0,0 +1,282 @@ +package com.m2pool.common.core.utils; + +import com.m2pool.common.core.exception.file.FileNameLengthLimitExceededException; +import com.m2pool.common.core.exception.file.FileSizeLimitExceededException; +import com.m2pool.common.core.exception.file.InvalidExtensionException; +import com.m2pool.common.core.utils.file.MimeTypeUtils; +import com.m2pool.common.core.utils.uuid.Seq; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; + +/** + * 文件上传工具类 + * + * @author dy + */ +public class FileUploadUtils +{ + /** + * 默认大小 200M + */ + public static final long DEFAULT_MAX_SIZE = 10 * 1024 * 1024; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + + /** + * 系统工具大小 200M + */ + public static final long TOOLS_MAX_SIZE = 200 * 1024 * 1024; + + /** + * 系统工具的文件名最大长度 100 + */ + public static final int TOOLS_NAME_LENGTH = 100; + + /** + * 根据文件路径上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @return 文件名称 + * @throws IOException + */ + public static final String upload(String baseDir, MultipartFile file) throws IOException + { + try + { + return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + catch (Exception e) + { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param allowedExtension 上传文件类型 + * @return 返回上传成功的文件名 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileNameLengthLimitExceededException 文件名太长 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + InvalidExtensionException + { + int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) + { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + + assertAllowed(file, allowedExtension); + + String fileName = extractFilename(file); + + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + file.transferTo(Paths.get(absPath)); + return getPathFileName(fileName); + } + + ///** + // * 系统工具文件上传 + // * + // * @param baseDir 相对应用的基目录 + // * @param file 上传的文件 + // * @param allowedExtension 上传文件类型 + // * @return 返回上传成功的文件名 + // * @throws FileSizeLimitExceededException 如果超出最大大小 + // * @throws FileNameLengthLimitExceededException 文件名太长 + // * @throws IOException 比如读写文件出错时 + // * @throws InvalidExtensionException 文件校验异常 + // */ + //public static final String toolsUpload(String baseDir, MultipartFile file, String fileName, String[] allowedExtension) + // throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + // InvalidExtensionException + //{ + // int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); + // if (fileNamelength > FileUploadUtils.TOOLS_NAME_LENGTH) + // { + // throw new FileNameLengthLimitExceededException(FileUploadUtils.TOOLS_NAME_LENGTH); + // } + // System.out.println("toolsAssertAllowed调用"); + // toolsAssertAllowed(file, allowedExtension); + // System.out.println("toolsAssertAllowed调用结束"); + // String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + // System.out.println("absPath|"+absPath); + // file.transferTo(Paths.get(absPath)); + // return getPathFileName(fileName); + //} + + /** + * 编码文件名 + */ + public static final String extractFilename(MultipartFile file) + { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), + FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); + } + + private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException + { + File desc = new File(uploadDir + File.separator + fileName); + + if (!desc.exists()) + { + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + } + return desc.isAbsolute() ? desc : desc.getAbsoluteFile(); + } + + private static final String getPathFileName(String fileName) throws IOException + { + String pathFileName = "/" + fileName; + return pathFileName; + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final void assertAllowed(MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException + { + long size = file.getSize(); + if (size > DEFAULT_MAX_SIZE) + { + throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); + } + + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) + { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) + { + throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) + { + throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) + { + throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) + { + throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + fileName); + } + else + { + throw new InvalidExtensionException(allowedExtension, extension, fileName); + } + } + } + + ///** + // * tools文件大小校验 + // * + // * @param file 上传的文件 + // * @throws FileSizeLimitExceededException 如果超出最大大小 + // * @throws InvalidExtensionException 文件校验异常 + // */ + //public static final void toolsAssertAllowed(MultipartFile file, String[] allowedExtension) + // throws FileSizeLimitExceededException, InvalidExtensionException + //{ + // long size = file.getSize(); + // if (size > TOOLS_MAX_SIZE) + // { + // throw new FileSizeLimitExceededException(TOOLS_MAX_SIZE / 1024 / 1024); + // } + // + // String fileName = file.getOriginalFilename(); + // String extension = getExtension(file); + // if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) + // { + // if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) + // { + // throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + // fileName); + // } + // else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) + // { + // throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + // fileName); + // } + // else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) + // { + // throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + // fileName); + // } + // else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) + // { + // throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + // fileName); + // } + // else + // { + // throw new InvalidExtensionException(allowedExtension, extension, fileName); + // } + // } + //} + + /** + * 判断MIME类型是否是允许的MIME类型 + * + * @param extension 上传文件类型 + * @param allowedExtension 允许上传文件类型 + * @return true/false + */ + public static final boolean isAllowedExtension(String extension, String[] allowedExtension) + { + for (String str : allowedExtension) + { + if (str.equalsIgnoreCase(extension)) + { + return true; + } + } + return false; + } + + /** + * 获取文件名的后缀 + * + * @param file 表单文件 + * @return 后缀名 + */ + public static final String getExtension(MultipartFile file) + { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (StringUtils.isEmpty(extension)) + { + extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); + } + return extension; + } +} + diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/GoogleAuthenticator.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/GoogleAuthenticator.java new file mode 100644 index 0000000..57eb044 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/GoogleAuthenticator.java @@ -0,0 +1,157 @@ +package com.m2pool.common.core.utils; + +import org.apache.commons.codec.binary.Base32; +import org.apache.commons.codec.binary.Hex; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +/** + * @Description 谷歌身份验证器工具类 + * @Date 2024/8/21 10:14 + * @Author 杜懿 + */ +public class GoogleAuthenticator { + + /** + * 时间前后偏移量 + * 用于防止客户端时间不精确导致生成的TOTP与服务器端的TOTP一直不一致 + * 如果为0,当前时间为 10:10:15 + * 则表明在 10:10:00-10:10:30 之间生成的TOTP 能校验通过 + * 如果为1,则表明在 + * 10:09:30-10:10:00 + * 10:10:00-10:10:30 + * 10:10:30-10:11:00 之间生成的TOTP 能校验通过 + * 以此类推 + */ + private static int WINDOW_SIZE = 1; + + /** + * 加密方式,HmacSHA1、HmacSHA256、HmacSHA512 + */ + private static final String CRYPTO = "HmacSHA1"; + + /** + * 生成密钥,每个用户独享一份密钥 + * @return + */ + public static String getSecretKey() { + SecureRandom random = new SecureRandom(); +// byte[] bytes = new byte[20]; + byte[] bytes = new byte[10]; + random.nextBytes(bytes); + Base32 base32 = new Base32(); + String secretKey = base32.encodeToString(bytes); + // make the secret key more human-readable by lower-casing and + // inserting spaces between each group of 4 characters + return secretKey.toUpperCase(); + } + + /** + * 生成二维码内容 + * @param secretKey 密钥 + * @param account 账户名 + * @param issuer 网站地址(可不写) + * @return + */ + public static String getQrCodeText(String secretKey, String account, String issuer) { + String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase(); + try { + return "otpauth://totp/" + + URLEncoder.encode((!StringUtils.isEmpty(issuer) ? (issuer + ":") : "") + account, "UTF-8").replace("+", "%20") + + "?secret=" + URLEncoder.encode(normalizedBase32Key, "UTF-8").replace("+", "%20") + + (!StringUtils.isEmpty(issuer) ? ("&issuer=" + URLEncoder.encode(issuer, "UTF-8").replace("+", "%20")) : ""); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + + /** + * 获取验证码 + * @param secretKey + * @return + */ + public static String getCode(String secretKey) { + String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase(); + Base32 base32 = new Base32(); + byte[] bytes = base32.decode(normalizedBase32Key); + String hexKey = Hex.encodeHexString(bytes); + long time = (System.currentTimeMillis() / 1000) / 30; + String hexTime = Long.toHexString(time); + return TOTP.generateTOTP(hexKey, hexTime, "6", CRYPTO); + } + + /** + * 检验 code 是否正确 + * @param secret 密钥 + * @param code code + * @param time 时间戳 + * @return + */ + public static boolean checkCode(String secret, long code, long time) { + Base32 codec = new Base32(); + byte[] decodedKey = codec.decode(secret); + // convert unix msec time into a 30 second "window" + // this is per the TOTP spec (see the RFC for details) + long t = (time / 1000L) / 30L; + // Window is used to check codes generated in the near past. + // You can use this value to tune how far you're willing to go. + long hash; + for (int i = -WINDOW_SIZE; i <= WINDOW_SIZE; ++i) { + try { + hash = verifyCode(decodedKey, t + i); + } catch (Exception e) { + // Yes, this is bad form - but + // the exceptions thrown would be rare and a static + // configuration problem + // e.printStackTrace(); + +// throw new RuntimeException(e.getMessage()); + return false; + + } + if (hash == code) { + return true; + } + } + return false; + } + + /** + * 根据时间偏移量计算 + * @param key + * @param t + * @return + * @throws NoSuchAlgorithmException + * @throws java.security.InvalidKeyException + */ + private static long verifyCode(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException { + byte[] data = new byte[8]; + long value = t; + for (int i = 8; i-- > 0; value >>>= 8) { + data[i] = (byte) value; + } + SecretKeySpec signKey = new SecretKeySpec(key, CRYPTO); + Mac mac = Mac.getInstance(CRYPTO); + mac.init(signKey); + byte[] hash = mac.doFinal(data); + int offset = hash[20 - 1] & 0xF; + // We're using a long because Java hasn't got unsigned int. + long truncatedHash = 0; + for (int i = 0; i < 4; ++i) { + truncatedHash <<= 8; + // We are dealing with signed bytes: + // we just keep the first byte. + truncatedHash |= (hash[offset + i] & 0xFF); + } + truncatedHash &= 0x7FFFFFFF; + truncatedHash %= 1000000; + return truncatedHash; + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/JwtUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/JwtUtils.java new file mode 100644 index 0000000..65a31f9 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/JwtUtils.java @@ -0,0 +1,124 @@ +package com.m2pool.common.core.utils; + +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.constant.TokenConstants; +import com.m2pool.common.core.text.Convert; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +import java.util.Map; + +/** + * @Description Jwt工具类 + * @Date 2024/6/12 16:38 + * @Author dy + */ +public class JwtUtils +{ + public static String secret = TokenConstants.SECRET; + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + public static String createToken(Map claims) + { + String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact(); + return token; + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + public static Claims parseToken(String token) + { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + } + + /** + * 根据令牌获取用户标识 + * + * @param token 令牌 + * @return 用户ID + */ + public static String getUserKey(String token) + { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.USER_KEY); + } + + /** + * 根据令牌获取用户标识 + * + * @param claims 身份信息 + * @return 用户ID + */ + public static String getUserKey(Claims claims) + { + return getValue(claims, SecurityConstants.USER_KEY); + } + + /** + * 根据令牌获取用户ID + * + * @param token 令牌 + * @return 用户ID + */ + public static String getUserId(String token) + { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.DETAILS_USER_ID); + } + + /** + * 根据身份信息获取用户ID + * + * @param claims 身份信息 + * @return 用户ID + */ + public static String getUserId(Claims claims) + { + return getValue(claims, SecurityConstants.DETAILS_USER_ID); + } + + /** + * 根据令牌获取用户名 + * + * @param token 令牌 + * @return 用户名 + */ + public static String getUserName(String token) + { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.DETAILS_USERNAME); + } + + /** + * 根据身份信息获取用户名 + * + * @param claims 身份信息 + * @return 用户名 + */ + public static String getUserName(Claims claims) + { + return getValue(claims, SecurityConstants.DETAILS_USERNAME); + } + + /** + * 根据身份信息获取键值 + * + * @param claims 身份信息 + * @param key 键 + * @return 值 + */ + public static String getValue(Claims claims, String key) + { + return Convert.toStr(claims.get(key), ""); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/OpenApiJwtUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/OpenApiJwtUtils.java new file mode 100644 index 0000000..4031ffa --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/OpenApiJwtUtils.java @@ -0,0 +1,141 @@ +package com.m2pool.common.core.utils; + +import com.m2pool.common.core.constant.OpenApiKeyConstants; +import com.m2pool.common.core.constant.TokenConstants; +import com.m2pool.common.core.text.Convert; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +import java.util.Map; + +/** + * @Description Jwt工具类 + * @Date 2024/6/12 16:38 + * @Author dy + */ +public class OpenApiJwtUtils +{ + public static String secret = TokenConstants.API_SECRET; + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + public static String createApiKey(Map claims) + { + String apiKey = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact(); + return apiKey; + } + + /** + * 从令牌中获取数据声明 + * + * @param apiKey 令牌 + * @return 数据声明 + */ + public static Claims parseApiKey(String apiKey) + { + Claims claims = null; + try { + claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(apiKey).getBody(); + }catch (Exception e){ + System.out.println("Jwt解析失败"); + } + return claims; + } + + /** + * 根据apiKey获取apiKey + * + * @param apiKey 令牌 + * @return apiKey + */ + public static String getApiKey(String apiKey) + { + Claims claims = parseApiKey(apiKey); + return getValue(claims, OpenApiKeyConstants.API_KEY); + } + + /** + * 根据apiKey获取apiKey + * + * @return apiKey + */ + public static String getApiKey(Claims claims) + { + return getValue(claims, OpenApiKeyConstants.API_KEY); + } + + /** + * 根据apiKey获取用户邮箱 + * + * @param apiKey 令牌 + * @return 用户邮箱 + */ + public static String getApiUser(String apiKey) + { + Claims claims = parseApiKey(apiKey); + return getValue(claims, OpenApiKeyConstants.API_USER); + } + + /** + * 根据身份信息获取用户邮箱 + * + * @param claims 身份信息 + * @return 用户有 + */ + public static String getApiUser(Claims claims) + { + return getValue(claims, OpenApiKeyConstants.API_USER); + } + + /** + * 根据apiKey获取用户绑定ip + * + * @param apiKey 令牌 + * @return 用户绑定ip + */ + public static String getApiIp(String apiKey) + { + Claims claims = parseApiKey(apiKey); + return getValue(claims, OpenApiKeyConstants.API_IP); + } + + /** + * 根据apiKey获取用户绑定ip + * + * @param claims 身份信息 + * @return 用户绑定ip + */ + public static String getApiIp(Claims claims) + { + return getValue(claims, OpenApiKeyConstants.API_IP); + } + + + /** + * 根据身份信息获取用户绑定ip + * + * @param claims 身份信息 + * @return 用户绑定ip + */ + public static String getUserName(Claims claims) + { + return getValue(claims, OpenApiKeyConstants.API_IP); + } + + /** + * 根据身份信息获取键值 + * + * @param claims 身份信息 + * @param key 键 + * @return 值 + */ + public static String getValue(Claims claims, String key) + { + return Convert.toStr(claims.get(key), ""); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/PageUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/PageUtils.java new file mode 100644 index 0000000..1a09959 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/PageUtils.java @@ -0,0 +1,36 @@ +package com.m2pool.common.core.utils; + +import com.github.pagehelper.PageHelper; +import com.m2pool.common.core.utils.sql.SqlUtil; +import com.m2pool.common.core.web.page.Page; +import com.m2pool.common.core.web.page.TableSupport; + +/** + * @Description 分页工具类 + * @Date 2024/6/12 14:37 + * @Author dy + */ +public class PageUtils extends PageHelper +{ + /** + * 设置请求分页数据 + */ + public static void startPage() + { + Page page = TableSupport.buildPageRequest(); + Integer pageNum = page.getPageNum(); + Integer pageSize = page.getPageSize(); + String orderBy = SqlUtil.escapeOrderBySql(page.getOrderBy()); + Boolean reasonable = page.getReasonable(); + PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); + } + + /** + * 清理分页的线程变量 + */ + public static void clearPage() + { + PageHelper.clearPage(); + } +} + diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/ServletUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/ServletUtils.java new file mode 100644 index 0000000..78b49e1 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/ServletUtils.java @@ -0,0 +1,304 @@ +package com.m2pool.common.core.utils; + + + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import com.alibaba.fastjson.JSONObject; +import com.m2pool.common.core.constant.Constants; +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.text.Convert; +import reactor.core.publisher.Mono; + +/** + * @Description 客户端工具类 + * @Date 2024/6/12 14:45 + * @Author dy + */ +public class ServletUtils +{ + /** + * 获取String参数 + */ + public static String getParameter(String name) + { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) + { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) + { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) + { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) + { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) + { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() + { + try + { + return getRequestAttributes().getRequest(); + } + catch (Exception e) + { + return null; + } + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() + { + try + { + return getRequestAttributes().getResponse(); + } + catch (Exception e) + { + return null; + } + } + + /** + * 获取session + */ + public static HttpSession getSession() + { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() + { + try + { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + catch (Exception e) + { + return null; + } + } + + public static String getHeader(HttpServletRequest request, String name) + { + String value = request.getHeader(name); + if (StringUtils.isEmpty(value)) + { + return StringUtils.EMPTY; + } + return urlDecode(value); + } + + public static Map getHeaders(HttpServletRequest request) + { + Map map = new LinkedHashMap<>(); + Enumeration enumeration = request.getHeaderNames(); + if (enumeration != null) + { + while (enumeration.hasMoreElements()) + { + String key = enumeration.nextElement(); + String value = request.getHeader(key); + map.put(key, value); + } + } + return map; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) + { + try + { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) + { + String accept = request.getHeader("accept"); + if (accept != null && accept.contains("application/json")) + { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) + { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) + { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); + } + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) + { + try + { + return URLEncoder.encode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) + { + try + { + return URLDecoder.decode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param value 响应内容 + * @return Mono + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, Object value) + { + return webFluxResponseWriter(response, HttpStatus.OK, value, R.FAIL); + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param code 响应状态码 + * @param value 响应内容 + * @return Mono + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, Object value, int code) + { + return webFluxResponseWriter(response, HttpStatus.OK, value, code); + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param status http状态码 + * @param code 响应状态码 + * @param value 响应内容 + * @return Mono + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code) + { + return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code); + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param contentType content-type + * @param status http状态码 + * @param code 响应状态码 + * @param value 响应内容 + * @return Mono + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code) + { + response.setStatusCode(status); + response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType); + R result = R.fail(code, value.toString()); + DataBuffer dataBuffer = response.bufferFactory().wrap(JSONObject.toJSONString(result).getBytes()); + return response.writeWith(Mono.just(dataBuffer)); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/SpringUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/SpringUtils.java new file mode 100644 index 0000000..8e094e1 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/SpringUtils.java @@ -0,0 +1,114 @@ +package com.m2pool.common.core.utils; + +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.stereotype.Component; + +/** + * @Description Spring工具类 + * 方便在非spring管理环境中获取bean + * @Date 2024/6/12 17:27 + * @Author dy + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor +{ + /** Spring应用Context环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + /** + * 获取对象 + * + * @param name + * @return Object + * @throws org.springframework.beans.BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws org.springframework.beans.BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + *判断BeanFactory中是否包含有所给名字的bean + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 + * + * @param name + * @return boolean + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + return (T) AopContext.currentProxy(); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/StringUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/StringUtils.java new file mode 100644 index 0000000..27753b4 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/StringUtils.java @@ -0,0 +1,548 @@ +package com.m2pool.common.core.utils; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.m2pool.common.core.text.StrFormatter; +import org.springframework.util.AntPathMatcher; +import com.m2pool.common.core.constant.Constants; + +/** + * @Description 字符串工具类 + * @Date 2024/6/12 14:41 + * @Author dy + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils +{ + /** 空字符串 */ + private static final String NULLSTR = ""; + + public static final int INDEX_NOT_FOUND = -1; + + /** 下划线 */ + private static final char SEPARATOR = '_'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static T nvl(T value, T defaultValue) + { + return value != null ? value : defaultValue; + } + + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) + { + return isNull(coll) || coll.isEmpty(); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) + { + return !isEmpty(coll); + } + + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + ** @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) + { + return isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) + { + return !isEmpty(objects); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) + { + return isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) + { + return !isEmpty(map); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) + { + return isNull(str) || NULLSTR.equals(str.trim()); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) + { + return !isEmpty(str); + } + + /** + * * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) + { + return object == null; + } + + /** + * * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) + { + return !isNull(object); + } + + /** + * * 判断一个对象是否是数组类型(Java基本型别的数组) + * + * @param object 对象 + * @return true:是数组 false:不是数组 + */ + public static boolean isArray(Object object) + { + return isNotNull(object) && object.getClass().isArray(); + } + + /** + * 去空格 + */ + public static String trim(String str) + { + return (str == null ? "" : str.trim()); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) + { + if (str == null) + { + return NULLSTR; + } + + if (start < 0) + { + start = str.length() + start; + } + + if (start < 0) + { + start = 0; + } + if (start > str.length()) + { + return NULLSTR; + } + + return str.substring(start); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) + { + if (str == null) + { + return NULLSTR; + } + + if (end < 0) + { + end = str.length() + end; + } + if (start < 0) + { + start = str.length() + start; + } + + if (end > str.length()) + { + end = str.length(); + } + + if (start > end) + { + return NULLSTR; + } + + if (start < 0) + { + start = 0; + } + if (end < 0) + { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 判断是否为空,并且不是空白字符 + * + * @param str 要判断的value + * @return 结果 + */ + public static boolean hasText(String str) + { + return (str != null && !str.isEmpty() && containsText(str)); + } + + private static boolean containsText(CharSequence str) + { + int strLen = str.length(); + for (int i = 0; i < strLen; i++) + { + if (!Character.isWhitespace(str.charAt(i))) + { + return true; + } + } + return false; + } + + /** + * 格式化文本, {} 表示占位符
+ * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) + { + if (isEmpty(params) || isEmpty(template)) + { + return template; + } + return StrFormatter.format(template, params); + } + + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ + public static boolean isHttp(String link) + { + return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); + } + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) + { + if (str == null) + { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) + { + char c = str.charAt(i); + if (i > 0) + { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } + else + { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) + { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) + { + sb.append(SEPARATOR); + } + else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) + { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) + { + if (str != null && strs != null) + { + for (String s : strs) + { + if (str.equalsIgnoreCase(trim(s))) + { + return true; + } + } + } + return false; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) + { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) + { + // 没必要转换 + return ""; + } + else if (!name.contains("_")) + { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) + { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) + { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 驼峰式命名法 例如:user_name->userName + */ + public static String toCamelCase(String s) + { + if (s == null) + { + return null; + } + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + + if (c == SEPARATOR) + { + upperCase = true; + } + else if (upperCase) + { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } + else + { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 + * + * @param str 指定字符串 + * @param strs 需要检查的字符串数组 + * @return 是否匹配 + */ + public static boolean matches(String str, List strs) + { + if (isEmpty(str) || isEmpty(strs)) + { + return false; + } + for (String pattern : strs) + { + if (isMatch(pattern, str)) + { + return true; + } + } + return false; + } + + /** + * 判断url是否与规则配置: + * ? 表示单个字符; + * * 表示一层路径内的任意字符串,不可跨层级; + * ** 表示任意层路径; + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + * @return + */ + public static boolean isMatch(String pattern, String url) + { + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } + + @SuppressWarnings("unchecked") + public static T cast(Object obj) + { + return (T) obj; + } + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static final String padl(final Number num, final int size) + { + return padl(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static final String padl(final String s, final int size, final char c) + { + final StringBuilder sb = new StringBuilder(size); + if (s != null) + { + final int len = s.length(); + if (s.length() <= size) + { + for (int i = size - len; i > 0; i--) + { + sb.append(c); + } + sb.append(s); + } + else + { + return s.substring(len - size, len); + } + } + else + { + for (int i = size; i > 0; i--) + { + sb.append(c); + } + } + return sb.toString(); + } + public static String substringAfter(final String str, final String separator) { + if (isEmpty(str)) { + return str; + } + if (separator == null) { + return NULLSTR; + } + final int pos = str.indexOf(separator); + if (pos == INDEX_NOT_FOUND) { + return NULLSTR; + } + return str.substring(pos + separator.length()); + } + + public static String clean(String str){ + if (isEmpty(str)) { + return str; + } + + return str.replaceAll("\\r\\n|\\n|\\\\n| |\\s",""); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/TOTP.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/TOTP.java new file mode 100644 index 0000000..57daf44 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/TOTP.java @@ -0,0 +1,97 @@ +package com.m2pool.common.core.utils; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.lang.reflect.UndeclaredThrowableException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; + +/** + * @Description 验证码生成工具类 + * @Date 2024/8/21 11:12 + * @Author 杜懿 + */ +public class TOTP { + + // 0 1 2 3 4 5 6 7 8 + private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000}; + + /** + * This method uses the JCE to provide the crypto algorithm. HMAC computes a + * Hashed Message Authentication Code with the crypto hash algorithm as a + * parameter. + * + * @param crypto : the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512) + * @param keyBytes : the bytes to use for the HMAC key + * @param text : the message or text to be authenticated + */ + private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) { + try { + Mac hmac; + hmac = Mac.getInstance(crypto); + SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW"); + hmac.init(macKey); + return hmac.doFinal(text); + } catch (GeneralSecurityException gse) { + throw new UndeclaredThrowableException(gse); + } + } + + /** + * This method converts a HEX string to Byte[] + * + * @param hex : the HEX string + * @return: a byte array + */ + private static byte[] hexStr2Bytes(String hex) { + // Adding one byte to get the right conversion + // Values starting with "0" can be converted + byte[] bArray = new BigInteger("10" + hex, 16).toByteArray(); + + // Copy all the REAL bytes, not the "first" + byte[] ret = new byte[bArray.length - 1]; + System.arraycopy(bArray, 1, ret, 0, ret.length); + return ret; + } + + /** + * This method generates a TOTP value for the given set of parameters. + * + * @param key : the shared secret, HEX encoded + * @param time : a value that reflects a time + * @param returnDigits : number of digits to return + * @param crypto : the crypto function to use + * @return: a numeric String in base 10 that includes + */ + public static String generateTOTP(String key, String time, String returnDigits, String crypto) { + int codeDigits = Integer.decode(returnDigits); + String result = null; + + // Using the counter + // First 8 bytes are for the movingFactor + // Compliant with base RFC 4226 (HOTP) + while (time.length() < 16) { + time = "0" + time; + } + + // Get the HEX in a Byte[] + byte[] msg = hexStr2Bytes(time); + byte[] k = hexStr2Bytes(key); + byte[] hash = hmac_sha(crypto, k, msg); + + // put selected bytes into result int + int offset = hash[hash.length - 1] & 0xf; + + int binary = ((hash[offset] & 0x7f) << 24) + | ((hash[offset + 1] & 0xff) << 16) + | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff); + + int otp = binary % DIGITS_POWER[codeDigits]; + + result = Integer.toString(otp); + while (result.length() < codeDigits) { + result = "0" + result; + } + return result; + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/bean/BeanUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/bean/BeanUtils.java new file mode 100644 index 0000000..4d37c66 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/bean/BeanUtils.java @@ -0,0 +1,112 @@ +package com.m2pool.common.core.utils.bean; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @Description Bean工具类 + * @Date 2024/6/12 10:16 + * @Author dy + */ + +public class BeanUtils extends org.springframework.beans.BeanUtils +{ + /** Bean方法名中属性名开始的下标 */ + private static final int BEAN_METHOD_PROP_INDEX = 3; + + /** * 匹配getter方法的正则表达式 */ + private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); + + /** * 匹配setter方法的正则表达式 */ + private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); + + /** + * Bean属性复制工具方法。 + * + * @param dest 目标对象 + * @param src 源对象 + */ + public static void copyBeanProp(Object dest, Object src) + { + try + { + copyProperties(src, dest); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * 获取对象的setter方法。 + * + * @param obj 对象 + * @return 对象的setter方法列表 + */ + public static List getSetterMethods(Object obj) + { + // setter方法列表 + List setterMethods = new ArrayList(); + + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + + // 查找setter方法 + + for (Method method : methods) + { + Matcher m = SET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 1)) + { + setterMethods.add(method); + } + } + // 返回setter方法列表 + return setterMethods; + } + + /** + * 获取对象的getter方法。 + * + * @param obj 对象 + * @return 对象的getter方法列表 + */ + + public static List getGetterMethods(Object obj) + { + // getter方法列表 + List getterMethods = new ArrayList(); + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + // 查找getter方法 + for (Method method : methods) + { + Matcher m = GET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 0)) + { + getterMethods.add(method); + } + } + // 返回getter方法列表 + return getterMethods; + } + + /** + * 检查Bean方法名中的属性名是否相等。
+ * 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。 + * + * @param m1 方法名1 + * @param m2 方法名2 + * @return 属性名一样返回true,否则返回false + */ + + public static boolean isMethodPropEquals(String m1, String m2) + { + return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); + } +} + diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/bean/BeanValidators.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/bean/BeanValidators.java new file mode 100644 index 0000000..69d8a78 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/bean/BeanValidators.java @@ -0,0 +1,25 @@ +package com.m2pool.common.core.utils.bean; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; +import java.util.Set; + +/** + * @Description bean对象属性验证 + * @Date 2024/6/12 10:43 + * @Author dy + */ +public class BeanValidators { + + public static void validateWithException(Validator validator, Object object, Class... groups) + throws ConstraintViolationException + { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/FileTypeUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/FileTypeUtils.java new file mode 100644 index 0000000..7dfda7a --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/FileTypeUtils.java @@ -0,0 +1,80 @@ +package com.m2pool.common.core.utils.file; + +import org.apache.commons.lang3.StringUtils; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.net.URLConnection; + +/** + * @Description 文件类型工具类 + * @Date 2024/6/12 10:49 + * @Author dy + */ +public class FileTypeUtils { + /** + * 根据文件获取文件类型 + *

+ * 例如: aa.txt, 返回: txt + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(File file) + { + if (null == file) + { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + *

+ * 例如: aa.txt, 返回: txt + * + * @param fileName 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(String fileName) + { + int separatorIndex = fileName.lastIndexOf("."); + if (separatorIndex < 0) + { + return ""; + } + return fileName.substring(separatorIndex + 1).toLowerCase(); + } + + /** + * 获取文件类型 + * + * @param photoByte 文件字节码 + * @return 后缀(不含".") + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "JPG"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "GIF"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "JPG"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "BMP"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "PNG"; + } + return strFileExtendName; + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/FileUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/FileUtils.java new file mode 100644 index 0000000..7a9e77c --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/FileUtils.java @@ -0,0 +1,257 @@ +package com.m2pool.common.core.utils.file; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * @Description 文件处理工具类 + * @Date 2024/6/2 11:31 + * @Author dy + */ +public class FileUtils { + + /** 字符常量:斜杠 {@code '/'} */ + public static final char SLASH = '/'; + + /** 字符常量:反斜杠 {@code '\\'} */ + public static final char BACKSLASH = '\\'; + + public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; + + /** + * 输出指定文件的byte数组 + * + * @param filePath 文件路径 + * @param os 输出流 + * @return + */ + public static void writeBytes(String filePath, OutputStream os) throws IOException + { + FileInputStream fis = null; + try + { + File file = new File(filePath); + if (!file.exists()) + { + throw new FileNotFoundException(filePath); + } + fis = new FileInputStream(file); + byte[] b = new byte[1024]; + int length; + while ((length = fis.read(b)) > 0) + { + os.write(b, 0, length); + } + } + catch (IOException e) + { + throw e; + } + finally + { + if (os != null) + { + try + { + os.close(); + } + catch (IOException e1) + { + e1.printStackTrace(); + } + } + if (fis != null) + { + try + { + fis.close(); + } + catch (IOException e1) + { + e1.printStackTrace(); + } + } + } + } + + /** + * 删除文件 + * + * @param filePath 文件 + * @return + */ + public static boolean deleteFile(String filePath) + { + boolean flag = false; + File file = new File(filePath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) + { + file.delete(); + flag = true; + } + return flag; + } + + /** + * 文件名称验证 + * + * @param filename 文件名称 + * @return true 正常 false 非法 + */ + public static boolean isValidFilename(String filename) + { + return filename.matches(FILENAME_PATTERN); + } + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public static boolean checkAllowDownload(String resource) + { + // 禁止目录上跳级别 + if (StringUtils.contains(resource, "..")) + { + return false; + } + + // 检查允许下载的文件规则 + if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) + { + return true; + } + + // 不在允许下载的文件规则 + return false; + } + + /** + * 下载文件名重新编码 + * + * @param request 请求对象 + * @param fileName 文件名 + * @return 编码后的文件名 + */ + public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException + { + final String agent = request.getHeader("USER-AGENT"); + String filename = fileName; + if (agent.contains("MSIE")) + { + // IE浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + filename = filename.replace("+", " "); + } + else if (agent.contains("Firefox")) + { + // 火狐浏览器 + filename = new String(fileName.getBytes(), "ISO8859-1"); + } + else if (agent.contains("Chrome")) + { + // google浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + else + { + // 其它浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + return filename; + } + + /** + * 获取文件名 + * + * @param filePath 文件 + * @return 文件名 + */ + public static String getName(String filePath) + { + if (null == filePath) + { + return null; + } + int len = filePath.length(); + if (0 == len) + { + return filePath; + } + if (isFileSeparator(filePath.charAt(len - 1))) + { + // 以分隔符结尾的去掉结尾分隔符 + len--; + } + + int begin = 0; + char c; + for (int i = len - 1; i > -1; i--) + { + c = filePath.charAt(i); + if (isFileSeparator(c)) + { + // 查找最后一个路径分隔符(/或者\) + begin = i + 1; + break; + } + } + + return filePath.substring(begin, len); + } + + /** + * 是否为Windows或者Linux(Unix)文件分隔符
+ * Windows平台下分隔符为\,Linux(Unix)为/ + * + * @param c 字符 + * @return 是否为Windows或者Linux(Unix)文件分隔符 + */ + public static boolean isFileSeparator(char c) + { + return SLASH == c || BACKSLASH == c; + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + * @return + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException + { + String percentEncodedFileName = percentEncode(realFileName); + + StringBuilder contentDispositionValue = new StringBuilder(); + contentDispositionValue.append("attachment; filename=") + .append(percentEncodedFileName) + .append(";") + .append("filename*=") + .append("utf-8''") + .append(percentEncodedFileName); + + response.setHeader("Content-disposition", contentDispositionValue.toString()); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) throws UnsupportedEncodingException + { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + return encode.replaceAll("\\+", "%20"); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/ImageUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/ImageUtils.java new file mode 100644 index 0000000..81dad9a --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/ImageUtils.java @@ -0,0 +1,85 @@ +package com.m2pool.common.core.utils.file; + +import org.apache.poi.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; + +/** + * @Description 图片处理工具类 + * @Date 2024/6/12 14:03 + * @Author dy + */ +public class ImageUtils +{ + private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); + + public static byte[] getImage(String imagePath) + { + InputStream is = getFile(imagePath); + try + { + return IOUtils.toByteArray(is); + } + catch (Exception e) + { + log.error("图片加载异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(is); + } + } + + public static InputStream getFile(String imagePath) + { + try + { + byte[] result = readFile(imagePath); + result = Arrays.copyOf(result, result.length); + return new ByteArrayInputStream(result); + } + catch (Exception e) + { + log.error("获取图片异常 {}", e); + } + return null; + } + + /** + * 读取文件为字节数据 + * + * @param url 地址 + * @return 字节数据 + */ + public static byte[] readFile(String url) + { + InputStream in = null; + try + { + // 网络地址 + URL urlObj = new URL(url); + URLConnection urlConnection = urlObj.openConnection(); + urlConnection.setConnectTimeout(30 * 1000); + urlConnection.setReadTimeout(60 * 1000); + urlConnection.setDoInput(true); + in = urlConnection.getInputStream(); + return IOUtils.toByteArray(in); + } + catch (Exception e) + { + log.error("访问文件异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(in); + } + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/MimeTypeUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/MimeTypeUtils.java new file mode 100644 index 0000000..1c1ad0d --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/file/MimeTypeUtils.java @@ -0,0 +1,67 @@ +package com.m2pool.common.core.utils.file; + +/** + * @Description 媒体类型工具类 + * @Date 2024/6/12 14:31 + * @Author dy + */ +public class MimeTypeUtils +{ + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" }; + + public static final String[] FLASH_EXTENSION = { "swf", "flv" }; + + public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb" }; + + public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" }; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf" }; + + public static final String[] DEFAULT_USER_HELP_EXTENSION = { + // 图片 + "jpg", "png", + // word excel powerpoint + "doc", "txt", + // 压缩文件 + "rar" }; + + public static String getExtension(String prefix) + { + switch (prefix) + { + case IMAGE_PNG: + return "png"; + case IMAGE_JPG: + return "jpg"; + case IMAGE_JPEG: + return "jpeg"; + case IMAGE_BMP: + return "bmp"; + case IMAGE_GIF: + return "gif"; + default: + return ""; + } + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/html/EscapeUtil.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/html/EscapeUtil.java new file mode 100644 index 0000000..bae5459 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/html/EscapeUtil.java @@ -0,0 +1,171 @@ +package com.m2pool.common.core.utils.html; + + +import com.m2pool.common.core.utils.StringUtils; + +/** + * 转义和反转义工具类 + * + * @author dy + */ +public class EscapeUtil +{ + public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + + private static final char[][] TEXT = new char[64][]; + + static + { + for (int i = 0; i < 64; i++) + { + TEXT[i] = new char[] { (char) i }; + } + + // special HTML characters + TEXT['\''] = "'".toCharArray(); // 单引号 + TEXT['"'] = """.toCharArray(); // 双引号 + TEXT['&'] = "&".toCharArray(); // &符 + TEXT['<'] = "<".toCharArray(); // 小于号 + TEXT['>'] = ">".toCharArray(); // 大于号 + } + + /** + * 转义文本中的HTML字符为安全的字符 + * + * @param text 被转义的文本 + * @return 转义后的文本 + */ + public static String escape(String text) + { + return encode(text); + } + + /** + * 还原被转义的HTML特殊字符 + * + * @param content 包含转义符的HTML内容 + * @return 转换后的字符串 + */ + public static String unescape(String content) + { + return decode(content); + } + + /** + * 清除所有HTML标签,但是不删除标签内的内容 + * + * @param content 文本 + * @return 清除标签后的文本 + */ + public static String clean(String content) + { + return new HTMLFilter().filter(content); + } + + /** + * Escape编码 + * + * @param text 被编码的文本 + * @return 编码后的字符 + */ + private static String encode(String text) + { + if (StringUtils.isEmpty(text)) + { + return StringUtils.EMPTY; + } + + final StringBuilder tmp = new StringBuilder(text.length() * 6); + char c; + for (int i = 0; i < text.length(); i++) + { + c = text.charAt(i); + if (c < 256) + { + tmp.append("%"); + if (c < 16) + { + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + else + { + tmp.append("%u"); + if (c <= 0xfff) + { + // issue#I49JU8@Gitee + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + } + return tmp.toString(); + } + + /** + * Escape解码 + * + * @param content 被转义的内容 + * @return 解码后的字符串 + */ + public static String decode(String content) + { + if (StringUtils.isEmpty(content)) + { + return content; + } + + StringBuilder tmp = new StringBuilder(content.length()); + int lastPos = 0, pos = 0; + char ch; + while (lastPos < content.length()) + { + pos = content.indexOf("%", lastPos); + if (pos == lastPos) + { + if (content.charAt(pos + 1) == 'u') + { + ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16); + tmp.append(ch); + lastPos = pos + 6; + } + else + { + ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16); + tmp.append(ch); + lastPos = pos + 3; + } + } + else + { + if (pos == -1) + { + tmp.append(content.substring(lastPos)); + lastPos = content.length(); + } + else + { + tmp.append(content.substring(lastPos, pos)); + lastPos = pos; + } + } + } + return tmp.toString(); + } + + public static void main(String[] args) + { + //String html = ""; + + + String html = "ipt>alert(\"XSS\")ipt>"; + // String html = "<123"; + // String html = "123>"; + + String escape = EscapeUtil.escape(html); + System.out.println("clean: " + EscapeUtil.clean(html)); + System.out.println("escape: " + escape); + System.out.println("unescape: " + EscapeUtil.unescape(escape)); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/html/HTMLFilter.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/html/HTMLFilter.java new file mode 100644 index 0000000..39559f8 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/html/HTMLFilter.java @@ -0,0 +1,566 @@ +package com.m2pool.common.core.utils.html; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HTML过滤器,用于去除XSS漏洞隐患。 + * + * @author dy + */ +public final class HTMLFilter +{ + /** + * regex flag union representing /si modifiers in php + **/ + private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; + private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); + private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); + private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); + private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); + private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); + private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); + private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); + private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); + private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); + private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); + private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); + private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); + private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); + private static final Pattern P_END_ARROW = Pattern.compile("^>"); + private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_AMP = Pattern.compile("&"); + private static final Pattern P_QUOTE = Pattern.compile("\""); + private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); + private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); + private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); + + // @xxx could grow large... maybe use sesat's ReferenceMap + private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + private final String[] vNeedClosingTags; + /** + * set of disallowed html elements + **/ + private final String[] vDisallowed; + /** + * attributes which should be checked for valid protocols + **/ + private final String[] vProtocolAtts; + /** + * allowed protocols + **/ + private final String[] vAllowedProtocols; + /** + * tags which should be removed if they contain no content (e.g. "" or "") + **/ + private final String[] vRemoveBlanks; + /** + * entities allowed within html markup + **/ + private final String[] vAllowedEntities; + /** + * flag determining whether comments are allowed in input String. + */ + private final boolean stripComment; + private final boolean encodeQuotes; + /** + * flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "" + * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() + { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList img_atts = new ArrayList<>(); + img_atts.add("src"); + img_atts.add("width"); + img_atts.add("height"); + img_atts.add("alt"); + vAllowed.put("img", img_atts); + + final ArrayList no_atts = new ArrayList<>(); + vAllowed.put("b", no_atts); + vAllowed.put("strong", no_atts); + vAllowed.put("i", no_atts); + vAllowed.put("em", no_atts); + + vSelfClosingTags = new String[] { "img" }; + vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" }; + vDisallowed = new String[] {}; + vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp. + vProtocolAtts = new String[] { "src", "href" }; + vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" }; + vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" }; + stripComment = true; + encodeQuotes = true; + alwaysMakeTags = false; + } + + /** + * Map-parameter configurable constructor. + * + * @param conf map containing configuration. keys match field names. + */ + @SuppressWarnings("unchecked") + public HTMLFilter(final Map conf) + { + + assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; + assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; + assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; + assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; + assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; + assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; + assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; + assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; + + vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); + vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); + vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); + vDisallowed = (String[]) conf.get("vDisallowed"); + vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); + vProtocolAtts = (String[]) conf.get("vProtocolAtts"); + vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); + vAllowedEntities = (String[]) conf.get("vAllowedEntities"); + stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; + encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; + alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; + } + + private void reset() + { + vTagCounts.clear(); + } + + // --------------------------------------------------------------- + // my versions of some PHP library functions + public static String chr(final int decimal) + { + return String.valueOf((char) decimal); + } + + public static String htmlSpecialChars(final String s) + { + String result = s; + result = regexReplace(P_AMP, "&", result); + result = regexReplace(P_QUOTE, """, result); + result = regexReplace(P_LEFT_ARROW, "<", result); + result = regexReplace(P_RIGHT_ARROW, ">", result); + return result; + } + + // --------------------------------------------------------------- + + /** + * given a user submitted input String, filter out any invalid or restricted html. + * + * @param input text (i.e. submitted by a user) than may contain html + * @return "clean" version of input, with only valid, whitelisted html elements allowed + */ + public String filter(final String input) + { + reset(); + String s = input; + + s = escapeComments(s); + + s = balanceHTML(s); + + s = checkTags(s); + + s = processRemoveBlanks(s); + + // s = validateEntities(s); + + return s; + } + + public boolean isAlwaysMakeTags() + { + return alwaysMakeTags; + } + + public boolean isStripComments() + { + return stripComment; + } + + private String escapeComments(final String s) + { + final Matcher m = P_COMMENTS.matcher(s); + final StringBuffer buf = new StringBuffer(); + if (m.find()) + { + final String match = m.group(1); // (.*?) + m.appendReplacement(buf, Matcher.quoteReplacement("")); + } + m.appendTail(buf); + + return buf.toString(); + } + + private String balanceHTML(String s) + { + if (alwaysMakeTags) + { + // + // try and form html + // + s = regexReplace(P_END_ARROW, "", s); + // 不追加结束标签 + s = regexReplace(P_BODY_TO_END, "<$1>", s); + s = regexReplace(P_XML_CONTENT, "$1<$2", s); + + } + else + { + // + // escape stray brackets + // + s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); + + // + // the last regexp causes '<>' entities to appear + // (we need to do a lookahead assertion so that the last bracket can + // be used in the next pass of the regexp) + // + s = regexReplace(P_BOTH_ARROWS, "", s); + } + + return s; + } + + private String checkTags(String s) + { + Matcher m = P_TAGS.matcher(s); + + final StringBuffer buf = new StringBuffer(); + while (m.find()) + { + String replaceStr = m.group(1); + replaceStr = processTag(replaceStr); + m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); + } + m.appendTail(buf); + + // these get tallied in processTag + // (remember to reset before subsequent calls to filter method) + final StringBuilder sBuilder = new StringBuilder(buf.toString()); + for (String key : vTagCounts.keySet()) + { + for (int ii = 0; ii < vTagCounts.get(key); ii++) + { + sBuilder.append(""); + } + } + s = sBuilder.toString(); + + return s; + } + + private String processRemoveBlanks(final String s) + { + String result = s; + for (String tag : vRemoveBlanks) + { + if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) + { + P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); + } + result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); + if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) + { + P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); + } + result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); + } + + return result; + } + + private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) + { + Matcher m = regex_pattern.matcher(s); + return m.replaceAll(replacement); + } + + private String processTag(final String s) + { + // ending tags + Matcher m = P_END_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + if (allowed(name)) + { + if (false == inArray(name, vSelfClosingTags)) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) - 1); + return ""; + } + } + } + } + + // starting tags + m = P_START_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + final String body = m.group(2); + String ending = m.group(3); + + // debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); + if (allowed(name)) + { + final StringBuilder params = new StringBuilder(); + + final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); + final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); + final List paramNames = new ArrayList<>(); + final List paramValues = new ArrayList<>(); + while (m2.find()) + { + paramNames.add(m2.group(1)); // ([a-z0-9]+) + paramValues.add(m2.group(3)); // (.*?) + } + while (m3.find()) + { + paramNames.add(m3.group(1)); // ([a-z0-9]+) + paramValues.add(m3.group(3)); // ([^\"\\s']+) + } + + String paramName, paramValue; + for (int ii = 0; ii < paramNames.size(); ii++) + { + paramName = paramNames.get(ii).toLowerCase(); + paramValue = paramValues.get(ii); + + // debug( "paramName='" + paramName + "'" ); + // debug( "paramValue='" + paramValue + "'" ); + // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); + + if (allowedAttribute(name, paramName)) + { + if (inArray(paramName, vProtocolAtts)) + { + paramValue = processParamProtocol(paramValue); + } + params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\""); + } + } + + if (inArray(name, vSelfClosingTags)) + { + ending = " /"; + } + + if (inArray(name, vNeedClosingTags)) + { + ending = ""; + } + + if (ending == null || ending.length() < 1) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) + 1); + } + else + { + vTagCounts.put(name, 1); + } + } + else + { + ending = " /"; + } + return "<" + name + params + ending + ">"; + } + else + { + return ""; + } + } + + // comments + m = P_COMMENT.matcher(s); + if (!stripComment && m.find()) + { + return "<" + m.group() + ">"; + } + + return ""; + } + + private String processParamProtocol(String s) + { + s = decodeEntities(s); + final Matcher m = P_PROTOCOL.matcher(s); + if (m.find()) + { + final String protocol = m.group(1); + if (!inArray(protocol, vAllowedProtocols)) + { + // bad protocol, turn into local anchor link instead + s = "#" + s.substring(protocol.length() + 1); + if (s.startsWith("#//")) + { + s = "#" + s.substring(3); + } + } + } + + return s; + } + + private String decodeEntities(String s) + { + StringBuffer buf = new StringBuffer(); + + Matcher m = P_ENTITY.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.decode(match).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENTITY_UNICODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENCODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + s = validateEntities(s); + return s; + } + + private String validateEntities(final String s) + { + StringBuffer buf = new StringBuffer(); + + // validate entities throughout the string + Matcher m = P_VALID_ENTITIES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // ([^&;]*) + final String two = m.group(2); // (?=(;|&|$)) + m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); + } + m.appendTail(buf); + + return encodeQuotes(buf.toString()); + } + + private String encodeQuotes(final String s) + { + if (encodeQuotes) + { + StringBuffer buf = new StringBuffer(); + Matcher m = P_VALID_QUOTES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // (>|^) + final String two = m.group(2); // ([^<]+?) + final String three = m.group(3); // (<|$) + // 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two) + m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three)); + } + m.appendTail(buf); + return buf.toString(); + } + else + { + return s; + } + } + + private String checkEntity(final String preamble, final String term) + { + + return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; + } + + private boolean isValidEntity(final String entity) + { + return inArray(entity, vAllowedEntities); + } + + private static boolean inArray(final String s, final String[] array) + { + for (String item : array) + { + if (item != null && item.equals(s)) + { + return true; + } + } + return false; + } + + private boolean allowed(final String name) + { + return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); + } + + private boolean allowedAttribute(final String name, final String paramName) + { + return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/ip/IpUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/ip/IpUtils.java new file mode 100644 index 0000000..002837f --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/ip/IpUtils.java @@ -0,0 +1,266 @@ +package com.m2pool.common.core.utils.ip; + +import com.m2pool.common.core.utils.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * @Description 获取ip + * @Date 2024/6/12 16:51 + * @Author dy + */ +public class IpUtils +{ + /** + * 获取客户端IP + * + * @param request 请求对象 + * @return IP地址 + */ + public static String getIpAddr(HttpServletRequest request) + { + if (request == null) + { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getRemoteAddr(); + } + + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param ip IP地址 + * @return 结果 + */ + public static boolean internalIp(String ip) + { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param addr byte地址 + * @return 结果 + */ + private static boolean internalIp(byte[] addr) + { + if (StringUtils.isNull(addr) || addr.length < 2) + + { + return true; + } + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) + { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) + { + return true; + } + case SECTION_5: + switch (b1) + { + case SECTION_6: + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) + { + if (text.length() == 0) + { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try + { + long l; + int i; + switch (elements.length) + { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) + { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) + { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) + { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } + catch (NumberFormatException e) + { + return null; + } + return bytes; + } + + /** + * 获取IP地址 + * + * @return 本地IP地址 + */ + public static String getHostIp() + { + try + { + return InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + } + return "127.0.0.1"; + } + + /** + * 获取主机名 + * + * @return 本地主机名 + */ + public static String getHostName() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException e) + { + } + return "未知"; + } + + /** + * 从多级反向代理中获得第一个非unknown IP地址 + * + * @param ip 获得的IP地址 + * @return 第一个非unknown IP地址 + */ + public static String getMultistageReverseProxyIp(String ip) + { + // 多级反向代理检测 + if (ip != null && ip.indexOf(",") > 0) + { + final String[] ips = ip.trim().split(","); + for (String subIp : ips) + { + if (false == isUnknown(subIp)) + { + ip = subIp; + break; + } + } + } + return ip; + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关 + * + * @param checkString 被检测的字符串 + * @return 是否未知 + */ + public static boolean isUnknown(String checkString) + { + return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); + } +} \ No newline at end of file diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/poi/ExcelHandlerAdapter.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 0000000..92fceaf --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,19 @@ +package com.m2pool.common.core.utils.poi; + +/** + * Excel数据格式处理适配器 + * + * @author dy + */ +public interface ExcelHandlerAdapter +{ + /** + * 格式化 + * + * @param value 单元格数据值 + * @param args excel注解args参数组 + * + * @return 处理后的值 + */ + Object format(Object value, String[] args); +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/poi/ExcelUtil.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/poi/ExcelUtil.java new file mode 100644 index 0000000..3240922 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/poi/ExcelUtil.java @@ -0,0 +1,1193 @@ +package com.m2pool.common.core.utils.poi; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.RegExUtils; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFDataValidation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.m2pool.common.core.annotation.Excel; +import com.m2pool.common.core.annotation.Excel.ColumnType; +import com.m2pool.common.core.annotation.Excel.Type; +import com.m2pool.common.core.annotation.Excels; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.file.FileTypeUtils; +import com.m2pool.common.core.utils.file.ImageUtils; +import com.m2pool.common.core.utils.reflect.ReflectUtils; + + +/** + * Excel相关处理 + * + * @author dy + */ +public class ExcelUtil +{ + private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + + public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; + + public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + + /** + * Excel sheet最大行数,默认65536 + */ + public static final int sheetSize = 65536; + + /** + * 工作表名称 + */ + private String sheetName; + + /** + * 导出类型(EXPORT:导出数据;IMPORT:导入模板) + */ + private Type type; + + /** + * 工作薄对象 + */ + private Workbook wb; + + /** + * 工作表对象 + */ + private Sheet sheet; + + /** + * 样式列表 + */ + private Map styles; + + /** + * 导入导出数据列表 + */ + private List list; + + /** + * 注解列表 + */ + private List fields; + + /** + * 当前行号 + */ + private int rownum; + + /** + * 标题 + */ + private String title; + + /** + * 最大高度 + */ + private short maxHeight; + + /** + * 统计列表 + */ + private Map statistics = new HashMap(); + + /** + * 数字格式 + */ + private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00"); + + /** + * 实体对象 + */ + public Class clazz; + + public ExcelUtil(Class clazz) + { + this.clazz = clazz; + } + + public void init(List list, String sheetName, String title, Excel.Type type) + { + if (list == null) + { + list = new ArrayList(); + } + this.list = list; + this.sheetName = sheetName; + this.type = type; + this.title = title; + createExcelField(); + createWorkbook(); + createTitle(); + } + + /** + * 创建excel第一行标题 + */ + public void createTitle() + { + if (StringUtils.isNotEmpty(title)) + { + Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), + this.fields.size() - 1)); + } + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(InputStream is) throws Exception + { + return importExcel(is, 0); + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @param titleNum 标题占用行数 + * @return 转换后集合 + */ + public List importExcel(InputStream is, int titleNum) throws Exception + { + return importExcel(StringUtils.EMPTY, is, titleNum); + } + + /** + * 对excel表单指定表格索引名转换成list + * + * @param sheetName 表格索引名 + * @param titleNum 标题占用行数 + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception + { + this.type = Type.IMPORT; + this.wb = WorkbookFactory.create(is); + List list = new ArrayList(); + // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet + Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); + if (sheet == null) + { + throw new IOException("文件sheet不存在"); + } + + // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1 + int rows = sheet.getLastRowNum(); + + if (rows > 0) + { + // 定义一个map用于存放excel列的序号和field. + Map cellMap = new HashMap(); + // 获取表头 + Row heard = sheet.getRow(titleNum); + for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) + { + Cell cell = heard.getCell(i); + if (StringUtils.isNotNull(cell)) + { + String value = this.getCellValue(heard, i).toString(); + cellMap.put(value, i); + } + else + { + cellMap.put(null, i); + } + } + // 有数据时才处理 得到类的所有field. + List fields = this.getFields(); + Map fieldsMap = new HashMap(); + for (Object[] objects : fields) + { + Excel attr = (Excel) objects[1]; + Integer column = cellMap.get(attr.name()); + if (column != null) + { + fieldsMap.put(column, objects); + } + } + for (int i = titleNum + 1; i <= rows; i++) + { + // 从第2行开始取数据,默认第一行是表头. + Row row = sheet.getRow(i); + // 判断当前行是否是空行 + if (isRowEmpty(row)) + { + continue; + } + T entity = null; + for (Map.Entry entry : fieldsMap.entrySet()) + { + Object val = this.getCellValue(row, entry.getKey()); + + // 如果不存在实例则新建. + entity = (entity == null ? clazz.newInstance() : entity); + // 从map中得到对应列的field. + Field field = (Field) entry.getValue()[0]; + Excel attr = (Excel) entry.getValue()[1]; + // 取得类型,并根据对象类型设置值. + Class fieldType = field.getType(); + if (String.class == fieldType) + { + String s = Convert.toStr(val); + if (StringUtils.endsWith(s, ".0")) + { + val = StringUtils.substringBefore(s, ".0"); + } + else + { + String dateFormat = field.getAnnotation(Excel.class).dateFormat(); + if (StringUtils.isNotEmpty(dateFormat)) + { + val = parseDateToStr(dateFormat, val); + } + else + { + val = Convert.toStr(val); + } + } + } + else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toInt(val); + } + else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toLong(val); + } + else if (Double.TYPE == fieldType || Double.class == fieldType) + { + val = Convert.toDouble(val); + } + else if (Float.TYPE == fieldType || Float.class == fieldType) + { + val = Convert.toFloat(val); + } + else if (BigDecimal.class == fieldType) + { + val = Convert.toBigDecimal(val); + } + else if (Date.class == fieldType) + { + if (val instanceof String) + { + val = DateUtils.parseDate(val); + } + else if (val instanceof Double) + { + val = DateUtil.getJavaDate((Double) val); + } + } + else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) + { + val = Convert.toBool(val, false); + } + if (StringUtils.isNotNull(fieldType)) + { + String propertyName = field.getName(); + if (StringUtils.isNotEmpty(attr.targetAttr())) + { + propertyName = field.getName() + "." + attr.targetAttr(); + } + else if (StringUtils.isNotEmpty(attr.readConverterExp())) + { + val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + val = dataFormatHandlerAdapter(val, attr); + } + ReflectUtils.invokeSetter(entity, propertyName, val); + } + } + list.add(entity); + } + } + return list; + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + * @throws IOException + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName) + { + exportExcel(response, list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + * @throws IOException + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(list, sheetName, title, Type.EXPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName) + { + importTemplateExcel(response, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(null, sheetName, title, Type.IMPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public void exportExcel(HttpServletResponse response) + { + try + { + writeSheet(); + wb.write(response.getOutputStream()); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + } + finally + { + IOUtils.closeQuietly(wb); + } + } + + /** + * 创建写入数据到Sheet + */ + public void writeSheet() + { + // 取出一共有多少个sheet. + int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); + for (int index = 0; index < sheetNo; index++) + { + createSheet(sheetNo, index); + + // 产生一行 + Row row = sheet.createRow(rownum); + int column = 0; + // 写入各个字段的列头名称 + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + this.createCell(excel, row, column++); + } + if (Type.EXPORT.equals(type)) + { + fillExcelData(index, row); + addStatisticsRow(); + } + } + } + + /** + * 填充excel数据 + * + * @param index 序号 + * @param row 单元格行 + */ + public void fillExcelData(int index, Row row) + { + int startNo = index * sheetSize; + int endNo = Math.min(startNo + sheetSize, list.size()); + for (int i = startNo; i < endNo; i++) + { + row = sheet.createRow(i + 1 + rownum - startNo); + // 得到导出对象. + T vo = (T) list.get(i); + int column = 0; + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + this.addCell(excel, row, vo, field, column++); + } + } + } + + /** + * 创建表格样式 + * + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) + { + // 写入各条记录,每条记录对应excel表中的一行 + Map styles = new HashMap(); + CellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(IndexedColors.WHITE.getIndex()); + style.setFont(headerFont); + styles.put("header", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font totalFont = wb.createFont(); + totalFont.setFontName("Arial"); + totalFont.setFontHeightInPoints((short) 10); + style.setFont(totalFont); + styles.put("total", style); + + styles.putAll(annotationStyles(wb)); + + return styles; + } + + /** + * 根据Excel注解创建表格样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationStyles(Workbook wb) + { + Map styles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = "data_" + excel.align() + "_" + excel.color(); + if (!styles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style = wb.createCellStyle(); + style.setAlignment(excel.align()); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + dataFont.setColor(excel.color().index); + style.setFont(dataFont); + styles.put(key, style); + } + } + return styles; + } + + /** + * 创建单元格 + */ + public Cell createCell(Excel attr, Row row, int column) + { + // 创建列 + Cell cell = row.createCell(column); + // 写入列信息 + cell.setCellValue(attr.name()); + setDataValidation(attr, row, column); + cell.setCellStyle(styles.get("header")); + return cell; + } + + /** + * 设置单元格信息 + * + * @param value 单元格值 + * @param attr 注解相关 + * @param cell 单元格信息 + */ + public void setCellVo(Object value, Excel attr, Cell cell) + { + if (ColumnType.STRING == attr.cellType()) + { + String cellValue = Convert.toStr(value); + // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 + if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) + { + cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); + } + cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); + } + else if (ColumnType.NUMERIC == attr.cellType()) + { + if (StringUtils.isNotNull(value)) + { + cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value)); + } + } + else if (ColumnType.IMAGE == attr.cellType()) + { + ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1); + String imagePath = Convert.toStr(value); + if (StringUtils.isNotEmpty(imagePath)) + { + byte[] data = ImageUtils.getImage(imagePath); + getDrawingPatriarch(cell.getSheet()).createPicture(anchor, + cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); + } + } + } + + /** + * 获取画布 + */ + public static Drawing getDrawingPatriarch(Sheet sheet) + { + if (sheet.getDrawingPatriarch() == null) + { + sheet.createDrawingPatriarch(); + } + return sheet.getDrawingPatriarch(); + } + + /** + * 获取图片类型,设置图片插入类型 + */ + public int getImageType(byte[] value) + { + String type = FileTypeUtils.getFileExtendName(value); + if ("JPG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_JPEG; + } + else if ("PNG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_PNG; + } + return Workbook.PICTURE_TYPE_JPEG; + } + + /** + * 创建表格样式 + */ + public void setDataValidation(Excel attr, Row row, int column) + { + if (attr.name().indexOf("注:") >= 0) + { + sheet.setColumnWidth(column, 6000); + } + else + { + // 设置列宽 + sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); + } + if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0) + { + // 提示信息或只能选择不能输入的列内容. + setPromptOrValidation(sheet, attr.combo(), attr.prompt(), 1, 100, column, column); + } + } + + /** + * 添加单元格 + */ + public Cell addCell(Excel attr, Row row, T vo, Field field, int column) + { + Cell cell = null; + try + { + // 设置行高 + row.setHeight(maxHeight); + // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. + if (attr.isExport()) + { + // 创建cell + cell = row.createCell(column); + cell.setCellStyle(styles.get("data_" + attr.align() + "_" + attr.color())); + + // 用于读取对象中的属性 + Object value = getTargetValue(vo, field, attr); + String dateFormat = attr.dateFormat(); + String readConverterExp = attr.readConverterExp(); + String separator = attr.separator(); + if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) + { + cell.setCellValue(parseDateToStr(dateFormat, value)); + } + else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) + { + cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator)); + } + else if (value instanceof BigDecimal && -1 != attr.scale()) + { + cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + cell.setCellValue(dataFormatHandlerAdapter(value, attr)); + } + else + { + // 设置列类型 + setCellVo(value, attr, cell); + } + addStatisticsData(column, Convert.toStr(value), attr); + } + } + catch (Exception e) + { + log.error("导出Excel失败{}", e); + } + return cell; + } + + /** + * 设置 POI XSSFSheet 单元格提示或选择框 + * + * @param sheet 表单 + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, + int firstCol, int endCol) + { + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1"); + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + sheet.addValidationData(dataValidation); + } + + /** + * 解析导出值 0=男,1=女,2=未知 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String convertByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(","); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(separator, propertyValue)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[0].equals(value)) + { + propertyString.append(itemArray[1] + separator); + break; + } + } + } + else + { + if (itemArray[0].equals(propertyValue)) + { + return itemArray[1]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 反向解析值 男=0,女=1,未知=2 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String reverseByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(","); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(separator, propertyValue)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[1].equals(value)) + { + propertyString.append(itemArray[0] + separator); + break; + } + } + } + else + { + if (itemArray[1].equals(propertyValue)) + { + return itemArray[0]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 数据处理器 + * + * @param value 数据值 + * @param excel 数据注解 + * @return + */ + public String dataFormatHandlerAdapter(Object value, Excel excel) + { + try + { + Object instance = excel.handler().newInstance(); + Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class }); + value = formatMethod.invoke(instance, value, excel.args()); + } + catch (Exception e) + { + log.error("不能格式化数据 " + excel.handler(), e.getMessage()); + } + return Convert.toStr(value); + } + + /** + * 合计统计信息 + */ + private void addStatisticsData(Integer index, String text, Excel entity) + { + if (entity != null && entity.isStatistics()) + { + Double temp = 0D; + if (!statistics.containsKey(index)) + { + statistics.put(index, temp); + } + try + { + temp = Double.valueOf(text); + } + catch (NumberFormatException e) + { + } + statistics.put(index, statistics.get(index) + temp); + } + } + + /** + * 创建统计行 + */ + public void addStatisticsRow() + { + if (statistics.size() > 0) + { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + Set keys = statistics.keySet(); + Cell cell = row.createCell(0); + cell.setCellStyle(styles.get("total")); + cell.setCellValue("合计"); + + for (Integer key : keys) + { + cell = row.createCell(key); + cell.setCellStyle(styles.get("total")); + cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key))); + } + statistics.clear(); + } + } + + /** + * 获取bean中的属性值 + * + * @param vo 实体对象 + * @param field 字段 + * @param excel 注解 + * @return 最终的属性值 + * @throws Exception + */ + private Object getTargetValue(T vo, Field field, Excel excel) throws Exception + { + Object o = field.get(vo); + if (StringUtils.isNotEmpty(excel.targetAttr())) + { + String target = excel.targetAttr(); + if (target.contains(".")) + { + String[] targets = target.split("[.]"); + for (String name : targets) + { + o = getValue(o, name); + } + } + else + { + o = getValue(o, target); + } + } + return o; + } + + /** + * 以类的属性的get方法方法形式获取值 + * + * @param o + * @param name + * @return value + * @throws Exception + */ + private Object getValue(Object o, String name) throws Exception + { + if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) + { + Class clazz = o.getClass(); + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + o = field.get(o); + } + return o; + } + + /** + * 得到所有定义字段 + */ + private void createExcelField() + { + this.fields = getFields(); + this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); + this.maxHeight = getRowHeight(); + } + + /** + * 获取字段注解信息 + */ + public List getFields() + { + List fields = new ArrayList(); + List tempFields = new ArrayList<>(); + tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); + tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + for (Field field : tempFields) + { + // 单注解 + if (field.isAnnotationPresent(Excel.class)) + { + Excel attr = field.getAnnotation(Excel.class); + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + { + field.setAccessible(true); + fields.add(new Object[] { field, attr }); + } + } + + // 多注解 + if (field.isAnnotationPresent(Excels.class)) + { + Excels attrs = field.getAnnotation(Excels.class); + Excel[] excels = attrs.value(); + for (Excel attr : excels) + { + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + { + field.setAccessible(true); + fields.add(new Object[] { field, attr }); + } + } + } + } + return fields; + } + + /** + * 根据注解获取最大行高 + */ + public short getRowHeight() + { + double maxHeight = 0; + for (Object[] os : this.fields) + { + Excel excel = (Excel) os[1]; + maxHeight = Math.max(maxHeight, excel.height()); + } + return (short) (maxHeight * 20); + } + + /** + * 创建一个工作簿 + */ + public void createWorkbook() + { + this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet(); + wb.setSheetName(0, sheetName); + this.styles = createStyles(wb); + } + + /** + * 创建工作表 + * + * @param sheetNo sheet数量 + * @param index 序号 + */ + public void createSheet(int sheetNo, int index) + { + // 设置工作表的名称. + if (sheetNo > 1 && index > 0) + { + this.sheet = wb.createSheet(); + this.createTitle(); + wb.setSheetName(index, sheetName + index); + } + } + + /** + * 获取单元格值 + * + * @param row 获取的行 + * @param column 获取单元格列号 + * @return 单元格值 + */ + public Object getCellValue(Row row, int column) + { + if (row == null) + { + return row; + } + Object val = ""; + try + { + Cell cell = row.getCell(column); + if (StringUtils.isNotNull(cell)) + { + if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) + { + val = cell.getNumericCellValue(); + if (DateUtil.isCellDateFormatted(cell)) + { + val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换 + } + else + { + if ((Double) val % 1 != 0) + { + val = new BigDecimal(val.toString()); + } + else + { + val = new DecimalFormat("0").format(val); + } + } + } + else if (cell.getCellType() == CellType.STRING) + { + val = cell.getStringCellValue(); + } + else if (cell.getCellType() == CellType.BOOLEAN) + { + val = cell.getBooleanCellValue(); + } + else if (cell.getCellType() == CellType.ERROR) + { + val = cell.getErrorCellValue(); + } + + } + } + catch (Exception e) + { + return val; + } + return val; + } + + /** + * 判断是否是空行 + * + * @param row 判断的行 + * @return + */ + private boolean isRowEmpty(Row row) + { + if (row == null) + { + return true; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) + { + Cell cell = row.getCell(i); + if (cell != null && cell.getCellType() != CellType.BLANK) + { + return false; + } + } + return true; + } + + /** + * 格式化不同类型的日期对象 + * + * @param dateFormat 日期格式 + * @param val 被格式化的日期对象 + * @return 格式化后的日期字符 + */ + public String parseDateToStr(String dateFormat, Object val) + { + if (val == null) + { + return ""; + } + String str; + if (val instanceof Date) + { + str = DateUtils.parseDateToStr(dateFormat, (Date) val); + } + else if (val instanceof LocalDateTime) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val)); + } + else if (val instanceof LocalDate) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val)); + } + else + { + str = val.toString(); + } + return str; + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/reflect/ReflectUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/reflect/ReflectUtils.java new file mode 100644 index 0000000..3591aed --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/reflect/ReflectUtils.java @@ -0,0 +1,406 @@ +package com.m2pool.common.core.utils.reflect; + +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.DateUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.poi.ss.usermodel.DateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.*; +import java.util.Date; + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * + * @author dy + */ +@SuppressWarnings("rawtypes") +public class ReflectUtils +{ + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + */ + @SuppressWarnings("unchecked") + public static E invokeGetter(Object obj, String propertyName) + { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + return (E) object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + */ + public static void invokeSetter(Object obj, String propertyName, E value) + { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i = 0; i < names.length; i++) + { + if (i < names.length - 1) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + else + { + String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + invokeMethodByName(object, setterMethodName, new Object[] { value }); + } + } + } + + /** + * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. + */ + @SuppressWarnings("unchecked") + public static E getFieldValue(final Object obj, final String fieldName) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return null; + } + E result = null; + try + { + result = (E) field.get(obj); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常{}", e.getMessage()); + } + return result; + } + + /** + * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. + */ + public static void setFieldValue(final Object obj, final String fieldName, final E value) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return; + } + try + { + field.set(obj, value); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常: {}", e.getMessage()); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符. + * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. + * 同时匹配方法名+参数类型, + */ + @SuppressWarnings("unchecked") + public static E invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, + final Object[] args) + { + if (obj == null || methodName == null) + { + return null; + } + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. + * 只匹配函数名,如果有多个同名函数调用第一个。 + */ + @SuppressWarnings("unchecked") + public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) + { + Method method = getAccessibleMethodByName(obj, methodName, args.length); + if (method == null) + { + // 如果为空不报错,直接返回空。 + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + // 类型转换(将参数数据类型转换为目标方法参数类型) + Class[] cs = method.getParameterTypes(); + for (int i = 0; i < cs.length; i++) + { + if (args[i] != null && !args[i].getClass().equals(cs[i])) + { + if (cs[i] == String.class) + { + args[i] = Convert.toStr(args[i]); + if (StringUtils.endsWith((String) args[i], ".0")) + { + args[i] = StringUtils.substringBefore((String) args[i], ".0"); + } + } + else if (cs[i] == Integer.class) + { + args[i] = Convert.toInt(args[i]); + } + else if (cs[i] == Long.class) + { + args[i] = Convert.toLong(args[i]); + } + else if (cs[i] == Double.class) + { + args[i] = Convert.toDouble(args[i]); + } + else if (cs[i] == Float.class) + { + args[i] = Convert.toFloat(args[i]); + } + else if (cs[i] == Date.class) + { + if (args[i] instanceof String) + { + args[i] = DateUtils.parseDate(args[i]); + } + else + { + args[i] = DateUtil.getJavaDate((Double) args[i]); + } + } + else if (cs[i] == boolean.class || cs[i] == Boolean.class) + { + args[i] = Convert.toBool(args[i]); + } + } + } + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + */ + public static Field getAccessibleField(final Object obj, final String fieldName) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(fieldName, "fieldName can't be blank"); + for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) + { + try + { + Field field = superClass.getDeclaredField(fieldName); + makeAccessible(field); + return field; + } + catch (NoSuchFieldException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 匹配函数名+参数类型。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + try + { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + makeAccessible(method); + return method; + } + catch (NoSuchMethodException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 只匹配函数名。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) + { + if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) + { + makeAccessible(method); + return method; + } + } + } + return null; + } + + /** + * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Method method) + { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.isAccessible()) + { + method.setAccessible(true); + } + } + + /** + * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Field field) + { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) + || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) + { + field.setAccessible(true); + } + } + + /** + * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 + * 如无法找到, 返回Object.class. + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) + { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + */ + public static Class getClassGenricType(final Class clazz, final int index) + { + Type genType = clazz.getGenericSuperclass(); + + if (!(genType instanceof ParameterizedType)) + { + logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) + { + logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + if (!(params[index] instanceof Class)) + { + logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + public static Class getUserClass(Object instance) + { + if (instance == null) + { + throw new RuntimeException("Instance must not be null"); + } + Class clazz = instance.getClass(); + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) + { + Class superClass = clazz.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) + { + return superClass; + } + } + return clazz; + + } + + /** + * 将反射时的checked exception转换为unchecked exception. + */ + public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) + { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) + { + return new IllegalArgumentException(msg, e); + } + else if (e instanceof InvocationTargetException) + { + return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); + } + return new RuntimeException(msg, e); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sign/Base64.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sign/Base64.java new file mode 100644 index 0000000..d7d5844 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sign/Base64.java @@ -0,0 +1,292 @@ +package com.m2pool.common.core.utils.sign; + +/** + * @Description Base64工具类 + * @Date 2024/6/12 15:19 + * @Author dy + */ +public final class Base64 { + + static private final int BASELENGTH = 256; + static private final int LOOKUPLENGTH = 64; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int FOURBYTE = 4; + static private final int SIGN = -128; + static private final char PAD = '='; + static final private byte[] base64Alphabet = new byte[BASELENGTH]; + static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; + + static + { + for (int i = 0; i < BASELENGTH; ++i) + { + base64Alphabet[i] = -1; + } + for (int i = 'Z'; i >= 'A'; i--) + { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) + { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + + for (int i = '9'; i >= '0'; i--) + { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) + { + lookUpBase64Alphabet[i] = (char) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('0' + j); + } + lookUpBase64Alphabet[62] = (char) '+'; + lookUpBase64Alphabet[63] = (char) '/'; + } + + private static boolean isWhiteSpace(char octect) + { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isPad(char octect) + { + return (octect == PAD); + } + + private static boolean isData(char octect) + { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static String encode(byte[] binaryData) + { + if (binaryData == null) + { + return null; + } + + int lengthDataBits = binaryData.length * EIGHTBIT; + if (lengthDataBits == 0) + { + return ""; + } + + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; + char encodedData[] = null; + + encodedData = new char[numberQuartet * 4]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + + for (int i = 0; i < numberTriplets; i++) + { + b1 = binaryData[dataIndex++]; + b2 = binaryData[dataIndex++]; + b3 = binaryData[dataIndex++]; + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; + } + + // form integral number of 6-bit groups + if (fewerThan24bits == EIGHTBIT) + { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex++] = PAD; + encodedData[encodedIndex++] = PAD; + } + else if (fewerThan24bits == SIXTEENBIT) + { + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex++] = PAD; + } + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode(String encoded) + { + if (encoded == null) + { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) + { + return null;// should be divisible by four + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) + { + return new byte[0]; + } + + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) + { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) + { + return null; + } // if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) + { + return null;// if found "no data" just return null + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + if (!isData((d3)) || !isData((d4))) + {// Check if they are PAD characters + if (isPad(d3) && isPad(d4)) + { + if ((b2 & 0xf) != 0)// last 4 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } + else if (!isPad(d3) && isPad(d4)) + { + b3 = base64Alphabet[d3]; + if ((b3 & 0x3) != 0)// last 2 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } + else + { + return null; + } + } + else + { // No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + return decodedData; + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) + { + if (data == null) + { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) + { + if (!isWhiteSpace(data[i])) + { + data[newSize++] = data[i]; + } + } + return newSize; + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sign/Rsa2Utils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sign/Rsa2Utils.java new file mode 100644 index 0000000..0ec0f39 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sign/Rsa2Utils.java @@ -0,0 +1,155 @@ +package com.m2pool.common.core.utils.sign; + + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * @Description TODO + * @Date 2024/6/12 17:35 + * @Author dy + */ +public class Rsa2Utils { + + //Rsa 公钥 + public static String publicKey ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbMEkGLB+J/SSAOp7iCp2J6g7k\n" + + "zI5lNXEe6vqSkPLCKfUCAw94FI8RwTQYh3HIR778JZkLskxgm8PZjAWknWPKr4T7\n" + + "Ma7cUWo9aWswtCFaa8ojTaKbJ7OqABpEIRoucrmrvna5Z0XwHmli9XgReJVg9gv3\n" + + "pvHZnrmAXNQI9f7rCQIDAQAB"; + + // Rsa 私钥 + public static String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANswSQYsH4n9JIA6nuIKnYnqDuTMjmU1cR7q+pKQ8sIp9QIDD3gUjxHBNBiHcchHvvwlmQuyTGCbw9mMBaSdY8qvhPsxrtxRaj1pazC0IVpryiNNopsns6oAGkQhGi5yuau+drlnRfAeaWL1eBF4lWD2C/em8dmeuYBc1Aj1/usJAgMBAAECgYEAywVzbhsqb2ahC4DAr8CDYT4B450w+7+/cpLV2zIVGRFB1kY9as0oI5rgBSRXmNoTpXuxwaq5ofZFNtjCVVJPHbQUmKaoHm2M6BRgPwP6OySlCaqQnyMnb0V8GP0QtrRiJDz1X+jklXtwyQ6MWIbvjlz7szKUmlCCkwpz3TLG2QECQQDu/go7C27FxQKb2k4T7rVJv5MJYDv6OvA8foHcC5BQtbrz1t2pLvBhyU2x6jVx/SaTR81vwKSXUoqj3OplLDapAkEA6sl2DgDBYbY8OAMmbgp998VdkpxEnkjyUnDp7ZnWs+UkwY8ZfHnISLaESkZVozXhZBpV1nMBVEDxdXF4y56tYQJATAw+SSeMKhZUjC9dJO6SdVMmgJdEvo0+oKFIxTJQy73oLWszwYAUMamStYhnVUxOmBMDBgpw1U4Im7fSRjtZcQJBALcn5A12b2U/WWjEpFURoEUKVT3K5AiqlUbUyNhOu1vo9Kx+an5dLm3y2+5pQeMpZCPIG5BIdQ/5/aMFmxsVNOECQG4CvMu7NTwx5ugKVpT112Z4LJCwGa/Xd25L/5ps4yLey0DonlkT/jr0p5aKFQ0e/ufinEmVHrAXsLLwg8iQZlc="; + + /** + * 私钥解密 + * + * @param text 待解密的文本 + * @return 解密后的文本 + */ + public static String decryptByPrivateKey(String text) throws Exception + { + return decryptByPrivateKey(privateKey, text); + } + + /** + * 公钥解密 + * + * @param publicKeyString 公钥 + * @param text 待解密的信息 + * @return 解密后的文本 + */ + public static String decryptByPublicKey(String publicKeyString, String text) throws Exception + { + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + byte[] result = cipher.doFinal(Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 私钥加密 + * + * @param privateKeyString 私钥 + * @param text 待加密的信息 + * @return 加密后的文本 + */ + public static String encryptByPrivateKey(String privateKeyString, String text) throws Exception + { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + byte[] result = cipher.doFinal(text.getBytes()); + return Base64.encodeBase64String(result); + } + + /** + * 私钥解密 + * + * @param privateKeyString 私钥 + * @param text 待解密的文本 + * @return 解密后的文本 + */ + public static String decryptByPrivateKey(String privateKeyString, String text) throws Exception + { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] result = cipher.doFinal(Base64.decodeBase64(text)); + return new String(result); + + } + + /** + * 公钥加密 + * + * @param publicKeyString 公钥 + * @param text 待加密的文本 + * @return 加密后的文本 + */ + public static String encryptByPublicKey(String publicKeyString, String text) throws Exception + { + X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] result = cipher.doFinal(text.getBytes()); + return Base64.encodeBase64String(result); + } + + /** + * 构建RSA密钥对 + * + * @return 生成后的公私钥信息 + */ + public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException + { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(1024); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); + String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded()); + String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded()); + return new RsaKeyPair(publicKeyString, privateKeyString); + } + + /** + * RSA密钥对对象 + */ + public static class RsaKeyPair + { + private final String publicKey; + private final String privateKey; + + public RsaKeyPair(String publicKey, String privateKey) + { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public String getPublicKey() + { + return publicKey; + } + + public String getPrivateKey() + { + return privateKey; + } + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sign/RsaUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sign/RsaUtils.java new file mode 100644 index 0000000..1b30f94 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sign/RsaUtils.java @@ -0,0 +1,187 @@ +package com.m2pool.common.core.utils.sign; + + +import org.apache.commons.codec.binary.Base64; +import javax.crypto.Cipher; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * @Description TODO + * @Date 2024/6/12 17:35 + * @Author dy + */ +public class RsaUtils { + + // Rsa 私钥 + //public static String privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTla3smq/59AVL\n" + + // "g0PayphcAyefuJlgJOHF7nN9eOpnbtDdsgLQ0giAoByujHcp7mZv6EngPmlgVudF\n" + + // "1pELpfXrhAMwszE13aOBWtJXAlsQgloE14sjNxLpnOjNUQyAiq44F+6vC3yKXTvR\n" + + // "4VzOgEuIVed8uWZOnHR9J6bilMEshE+4zR9BAB4PjX0+7+nTrsS5gEw7LX1zql/R\n" + + // "BCdRVt09mMO9lvxYtde2kqEBZP/2joKMmSJ5OaSOyU/jv1+T8iZFduYAvCgHJWlj\n" + + // "6Rfo1NfqAETLney+LtquULUgr4+n2df9FtSQtGhcfaF7p2B3kX/xAJXh4cwnoJ4u\n" + + // "5Q5FXHYVAgMBAAECggEAMEj8Q/6ZIkWZ3725Ankxg+4EYOLTvaktDOp52Kx7cddM\n" + + // "OwugsN79qbzgTsUnfJ43KlqsYUxc5+ttI/bvauUY1gJuZ/K8zDokUiTT059qAL5+\n" + + // "pJQ74HF1E3MHfbN9UuhTEeIESlYoubrFKARyFX4Zvqc8CK6WtmHmA5nE7/hajTnK\n" + + // "aToKPLP8KXa/U8W8paKUhUV1aoEBtqxOLQs1rg8PREGCUmUyTRzzxblm/SVd6YYB\n" + + // "vCKcbZgKQwt7lrKrUVcVc+VDrJ6UWU+XNMABWT+NwUvrTayQehot5lxfZxfPk0IG\n" + + // "h9OhOtclOQ8FfRh2UeLmkAG4PTadr+r7Lrbcm9NokQKBgQDtKbNcvJIuXKqecVxA\n" + + // "+QiINx/PPp44wFhZk9iIHh02MVRNtgxEIMpUleyIHlEGMmTKcchO56GRXiFfqNu5\n" + + // "8DduoU4tbKztl1YtYctMRWvYDvcNcveEhm/+J01e/Zyej4ElLjTZeg+rOSREhkMr\n" + + // "EKDSA6AoltjpcCThYxG+Rtr0SwKBgQDkY+PRDc5BWw+icKXu6DgajUw+i3pzVRMW\n" + + // "lF/Q4sNi7zlTP+YcnuhqgHu0ILFUgTQAgbdqVKZjG3g9bI7Ziaus10H+LXTizNue\n" + + // "QNibe/UZ29Q2SsDeATMwDGrUQ2d0FuGaVWJoY4auNWuZr+6XaEnih2x5FniktdQk\n" + + // "+YbYinQDHwKBgFBCs4un2YTNIYS7cnAel0+Z8C7vzxX/qiaujTILlvE3IoOmH2KT\n" + + // "AkY78q9iKyOAvHFyrkpdw2TxyTOZbrrvW6MZ/d4LkD5b3/M9zFJEkCmvbtZjWPbF\n" + + // "lHMbk+iYxX83q4oMqCANWe4lSWvTUDnrx7ErPvFdk4z0wdZw85lEW2cfAoGBAICn\n" + + // "3I/JTST33QjOmErKucALVKXvAF2z0PrpPkh3VUWIKSzCVChPQ/GqywSfXgWSeu7G\n" + + // "I8JcSRaPRN6lJptYuEK3R8+dX7jbWeP994cu/tVARn0HAzqMRn+MnylPhxmYQiIk\n" + + // "czkGx7mfEiwTNT5JW0Wmr+5OQEvYudbSUant5IhVAoGBAM0rAsaQ2ZQEeUCZGuu1\n" + + // "IfW21gwl13G4mWt2ng1rV8iv8Kn9yu0o7CRGAf4IlUYqOcJtfh8F5liQBcJzd2Bc\n" + + // "odT7Zo+P4Zqtgg9nKehkd5Z7+zmFNog3Z/lZMj/dJuwO+Q8Ef6qKMYdPQuwrsVBD\n" + + // "RY91AS8cjE/RsmNeWNJjTkq5"; + + public static String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwgg" + + "JcAgEAAoGBALAeRR9PYYC6lkAvkMyfARNLLC48sgpQBHeGOEq86Wza7Ri0geVx+O" + + "GoYF+DQ0Q+LoiDYaYrkqeNx0iml818GlKIkES0zAzYTtYltcGxhTQHjAHzpcZb18" + + "VCLR7ykZi9Vsq/A/0xB4udQLjF/Yv7o3DfFBn0YCwToZQni1Q5Ior9AgMBAAECgY" + + "BZ+f0CdAGyTKE6hZKKl6lq+/rEXInxLtQ5ZD8aH815qZd8CaxbVpD6aJVj9qHFGa" + + "eYYtbemGBCbfKkMUhY2NBxAYJ8WVbfHPav4WoLk4lerW2/+6w/sr/MSoMd3D1gNV" + + "Zkt3L61/d2lJqWiD94L+fEV4fj6sC+IayAegb/TpXOWQJBAP5wlmxDlnEmKxMi9r" + + "PxezlsxCTpyAWtZd02xIxfIB0KlC0TqjdiJr+IbC/fS1U9OBuNJX3Wfecc8PuD6I" + + "95F08CQQCxMrxG04qySfuNcY5oWjkcz3QNTn5l1AMLeq982aNBFhxBDCcoLToUQb" + + "+a285sz4y8hZFOXHiWl6yt79SVPSXzAkBl+YWwGl5/NsbowoiRkeTLHRZ3nOK3s8" + + "kxobOgdreOLCE697iuvb294dVKUnoEzaZhFG7EQmTvbZ//jdx1NBjtAkEApb1R02" + + "F8IkUG0Aa976csMmFsKCIaIb6LVopbaAjdJTy8Eq9+VJrw7w+MWxlrW+VJuu+nuM" + + "bPV/PoZQylJy35twJAEayAR+lE4G5LTY8Oonr4LBYgJebviJFto9gDNbJkpiYlpN" + + "tXT0At+fI61B7n3Wvm4piv+8FBPxNKXZbliGD4tw=="; + + /** + * 私钥解密 + * + * @param text 待解密的文本 + * @return 解密后的文本 + */ + public static String decryptByPrivateKey(String text) throws Exception + { + return decryptByPrivateKey(privateKey, text); + } + + /** + * 公钥解密 + * + * @param publicKeyString 公钥 + * @param text 待解密的信息 + * @return 解密后的文本 + */ + public static String decryptByPublicKey(String publicKeyString, String text) throws Exception + { + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + byte[] result = cipher.doFinal(Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 私钥加密 + * + * @param privateKeyString 私钥 + * @param text 待加密的信息 + * @return 加密后的文本 + */ + public static String encryptByPrivateKey(String privateKeyString, String text) throws Exception + { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + byte[] result = cipher.doFinal(text.getBytes()); + return Base64.encodeBase64String(result); + } + + /** + * 私钥解密 + * + * @param privateKeyString 私钥 + * @param text 待解密的文本 + * @return 解密后的文本 + */ + public static String decryptByPrivateKey(String privateKeyString, String text) throws Exception + { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] result = cipher.doFinal(Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 公钥加密 + * + * @param publicKeyString 公钥 + * @param text 待加密的文本 + * @return 加密后的文本 + */ + public static String encryptByPublicKey(String publicKeyString, String text) throws Exception + { + X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] result = cipher.doFinal(text.getBytes()); + return Base64.encodeBase64String(result); + } + + /** + * 构建RSA密钥对 + * + * @return 生成后的公私钥信息 + */ + public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException + { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(1024); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); + String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded()); + String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded()); + return new RsaKeyPair(publicKeyString, privateKeyString); + } + + /** + * RSA密钥对对象 + */ + public static class RsaKeyPair + { + private final String publicKey; + private final String privateKey; + + public RsaKeyPair(String publicKey, String privateKey) + { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public String getPublicKey() + { + return publicKey; + } + + public String getPrivateKey() + { + return privateKey; + } + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sql/SqlUtil.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sql/SqlUtil.java new file mode 100644 index 0000000..20a74c1 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/sql/SqlUtil.java @@ -0,0 +1,61 @@ +package com.m2pool.common.core.utils.sql; + +import com.m2pool.common.core.exception.UtilException; +import com.m2pool.common.core.utils.StringUtils; + +/** + * @Description sql操作工具类 + * @Date 2024/6/13 11:40 + * @Author dy + */ +public class SqlUtil +{ + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare "; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) + { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) + { + throw new UtilException("参数不符合规范,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public static boolean isValidOrderBySql(String value) + { + return value.matches(SQL_PATTERN); + } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) + { + if (StringUtils.isEmpty(value)) + { + return; + } + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (String sqlKeyword : sqlKeywords) + { + if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) + { + throw new UtilException("参数存在SQL注入风险"); + } + } + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/uuid/IdUtils.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/uuid/IdUtils.java new file mode 100644 index 0000000..532ec0e --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/uuid/IdUtils.java @@ -0,0 +1,49 @@ +package com.m2pool.common.core.utils.uuid; + +/** + * @Description ID生成器 + * @Date 2024/6/13 11:24 + * @Author dy + */ +public class IdUtils +{ + /** + * 获取随机UUID + * + * @return 随机UUID + */ + public static String randomUUID() + { + return UUID.randomUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线 + * + * @return 简化的UUID,去掉了横线 + */ + public static String simpleUUID() + { + return UUID.randomUUID().toString(true); + } + + /** + * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 随机UUID + */ + public static String fastUUID() + { + return UUID.fastUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 简化的UUID,去掉了横线 + */ + public static String fastSimpleUUID() + { + return UUID.fastUUID().toString(true); + } +} \ No newline at end of file diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/uuid/Seq.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/uuid/Seq.java new file mode 100644 index 0000000..ed7964c --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/uuid/Seq.java @@ -0,0 +1,87 @@ +package com.m2pool.common.core.utils.uuid; + +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author m2pool 序列生成类 + */ +public class Seq +{ + // 通用序列类型 + public static final String commSeqType = "COMMON"; + + // 上传序列类型 + public static final String uploadSeqType = "UPLOAD"; + + // 通用接口序列数 + private static AtomicInteger commSeq = new AtomicInteger(1); + + // 上传接口序列数 + private static AtomicInteger uploadSeq = new AtomicInteger(1); + + // 机器标识 + private static String machineCode = "A"; + + /** + * 获取通用序列号 + * + * @return 序列值 + */ + public static String getId() + { + return getId(commSeqType); + } + + /** + * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 + * + * @return 序列值 + */ + public static String getId(String type) + { + AtomicInteger atomicInt = commSeq; + if (uploadSeqType.equals(type)) + { + atomicInt = uploadSeq; + } + return getId(atomicInt, 3); + } + + /** + * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 + * + * @param atomicInt 序列数 + * @param length 数值长度 + * @return 序列值 + */ + public static String getId(AtomicInteger atomicInt, int length) + { + String result = DateUtils.dateTimeNow(); + result += machineCode; + result += getSeq(atomicInt, length); + return result; + } + + /** + * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 + * + * @return 序列值 + */ + private synchronized static String getSeq(AtomicInteger atomicInt, int length) + { + // 先取值再+1 + int value = atomicInt.getAndIncrement(); + + // 如果更新后值>=10 的 (length)幂次方则重置为1 + int maxSeq = (int) Math.pow(10, length); + if (atomicInt.get() >= maxSeq) + { + atomicInt.set(1); + } + // 转字符串,用0左补齐 + return StringUtils.padl(value, length); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/uuid/UUID.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/uuid/UUID.java new file mode 100644 index 0000000..880a985 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/utils/uuid/UUID.java @@ -0,0 +1,485 @@ +package com.m2pool.common.core.utils.uuid; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import com.m2pool.common.core.exception.UtilException; + +/** + * @Description 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * @Date 2024/6/13 9:26 + * @Author dy + */ + +public final class UUID implements java.io.Serializable, Comparable +{ + private static final long serialVersionUID = -1285054133654744140L; + + /** + * SecureRandom 的单例 + * + */ + private static class Holder + { + static final SecureRandom numberGenerator = getSecureRandom(); + } + + /** 此UUID的最高64有效位 */ + private final long mostSigBits; + + /** 此UUID的最低64有效位 */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID(byte[] data) + { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0; i < 8; i++) + { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8; i < 16; i++) + { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID(long mostSigBits, long leastSigBits) + { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID() + { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID() + { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID(boolean isSecure) + { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes(byte[] name) + { + MessageDigest md; + try + { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException nsae) + { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * @return 具有指定值的 {@code UUID} + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + * + */ + public static UUID fromString(String name) + { + String[] components = name.split("-"); + if (components.length != 5) + { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0; i < 5; i++) + { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits() + { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits() + { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant() + { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp() throws UnsupportedOperationException + { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48// + | ((mostSigBits >> 16) & 0x0FFFFL) << 32// + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence() throws UnsupportedOperationException + { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString(boolean isSimple) + { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (false == isSimple) + { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (false == isSimple) + { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (false == isSimple) + { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (false == isSimple) + { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode() + { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) + { + if ((null == obj) || (obj.getClass() != UUID.class)) + { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

+ * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + * + */ + @Override + public int compareTo(UUID val) + { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : // + (this.mostSigBits > val.mostSigBits ? 1 : // + (this.leastSigBits < val.leastSigBits ? -1 : // + (this.leastSigBits > val.leastSigBits ? 1 : // + 0)))); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * @return 值 + */ + private static String digits(long val, int digits) + { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase() + { + if (version() != 1) + { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom() + { + try + { + return SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + throw new UtilException(e); + } + } + + /** + * 获取随机数生成器对象
+ * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} + diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/Result/AjaxResult.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/Result/AjaxResult.java new file mode 100644 index 0000000..0417eb3 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/Result/AjaxResult.java @@ -0,0 +1,166 @@ +package com.m2pool.common.core.web.Result; + +import com.m2pool.common.core.constant.HttpStatus; +import com.m2pool.common.core.utils.StringUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.util.HashMap; + +/** + * @Description 操作消息提醒 + * @Date 2024/6/11 15:05 + * @Author dy + */ +public class AjaxResult extends HashMap +{ + private static final long serialVersionUID = 3247645319123122183L; + + /** 状态码 */ + public static final String CODE_TAG = "code"; + + /** 返回内容 */ + public static final String MSG_TAG = "msg"; + + /** 数据对象 */ + public static final String DATA_TAG = "data"; + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() + { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public AjaxResult(int code, String msg) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (StringUtils.isNotNull(data)) + { + super.put(DATA_TAG, data); + } + } + + /** + * 方便链式调用 + * + * @param key + * @param value + * @return + */ + @Override + public AjaxResult put(String key, Object value) + { + super.put(key, value); + return this; + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() + { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) + { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static AjaxResult success(String msg) + { + return AjaxResult.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static AjaxResult success(String msg, Object data) + { + return new AjaxResult(HttpStatus.SUCCESS, msg, data); + } + + /** + * 返回错误消息 + * + * @return + */ + public static AjaxResult error() + { + return AjaxResult.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult error(String msg) + { + return AjaxResult.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult error(String msg, Object data) + { + return new AjaxResult(HttpStatus.ERROR, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult error(int code, String msg) + { + return new AjaxResult(code, msg, null); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/Result/PageResult.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/Result/PageResult.java new file mode 100644 index 0000000..3604896 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/Result/PageResult.java @@ -0,0 +1,31 @@ +package com.m2pool.common.core.web.Result; + +import com.m2pool.common.core.constant.HttpStatus; +import com.m2pool.common.core.utils.StringUtils; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.apache.poi.ss.formula.functions.T; + +import java.util.HashMap; + +/** + * @Description 操作消息提醒 + * @Date 2024/6/11 15:05 + * @Author dy + */ +@ApiModel(description = "响应信息") +@Data +public class PageResult +{ + //总记录条数 + @ApiModelProperty(value = "总记录条数") + private Long count; + + @ApiModelProperty(value = "总页数") + private Long totalPage; + + //返回的数据 + @ApiModelProperty(value = "返回的数据") + private T data; +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/controller/BaseController.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/controller/BaseController.java new file mode 100644 index 0000000..1024093 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/controller/BaseController.java @@ -0,0 +1,129 @@ +package com.m2pool.common.core.web.controller; + +import com.github.pagehelper.PageInfo; +import com.m2pool.common.core.constant.HttpStatus; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.PageUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.page.TableDataInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; + +import java.beans.PropertyEditorSupport; +import java.util.Date; +import java.util.List; + +/** + * @Description web层数据处理 + * @Date 2024/6/13 10:52 + * @Author dy + */ +public class BaseController +{ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * 将前台传递过来的日期格式的字符串,自动转化为Date类型 + */ + @InitBinder + public void initBinder(WebDataBinder binder) + { + // Date 类型转换 + binder.registerCustomEditor(Date.class, new PropertyEditorSupport() + { + @Override + public void setAsText(String text) + { + setValue(DateUtils.parseDate(text)); + } + }); + } + + /** + * 设置请求分页数据 + */ + protected void startPage() + { + PageUtils.startPage(); + } + + /** + * 清理分页的线程变量 + */ + protected void clearPage() + { + PageUtils.clearPage(); + } + + /** + * 响应请求分页数据 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected TableDataInfo getDataTable(List list) + { + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setRows(list); + rspData.setMsg("查询成功"); + PageInfo pageInfo = new PageInfo(list); + rspData.setTotal(pageInfo.getTotal()); + rspData.setTotalPage(pageInfo.getPages()); + return rspData; + } + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected AjaxResult toAjax(int rows) + { + return rows > 0 ? AjaxResult.success() : AjaxResult.error(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected AjaxResult toAjax(boolean result) + { + return result ? success() : error(); + } + + /** + * 返回成功 + */ + public AjaxResult success() + { + return AjaxResult.success(); + } + + /** + * 返回失败消息 + */ + public AjaxResult error() + { + return AjaxResult.error(); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(String message) + { + return AjaxResult.success(message); + } + + /** + * 返回失败消息 + */ + public AjaxResult error(String message) + { + return AjaxResult.error(message); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/entity/BaseEntity.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/entity/BaseEntity.java new file mode 100644 index 0000000..fc4f90d --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/entity/BaseEntity.java @@ -0,0 +1,53 @@ +package com.m2pool.common.core.web.entity; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @Description Entity基类 + * @Date 2024/6/13 11:31 + * @Author dy + */ +@Data +public class BaseEntity implements Serializable +{ + private static final long serialVersionUID = 2417642483861314318L; + + /** 搜索值 */ + private String searchValue; + + /** 创建者 */ + private String createBy; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新者 */ + private String updateBy; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 备注 */ + private String remark; + + /** 请求参数 */ + private Map params; + + + public Map getParams() + { + if (params == null) + { + params = new HashMap<>(); + } + return params; + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/page/Page.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/page/Page.java new file mode 100644 index 0000000..24a7fd0 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/page/Page.java @@ -0,0 +1,64 @@ +package com.m2pool.common.core.web.page; + +import com.m2pool.common.core.utils.StringUtils; +import lombok.Data; + +/** + * @Description 数据分页 + * @Date 2024/6/13 11:14 + * @Author dy + */ +@Data +public class Page { + + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + + /** 排序列 */ + private String orderByColumn; + + /** 排序的方向desc或者asc */ + private String isAsc = "asc"; + + /** 分页参数合理化 */ + private Boolean reasonable = true; + + public String getOrderBy() + { + if (StringUtils.isEmpty(orderByColumn)) + { + return ""; + } + return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc; + } + + public void setIsAsc(String isAsc) + { + if (StringUtils.isNotEmpty(isAsc)) + { + // 兼容前端排序类型 + if ("ascending".equals(isAsc)) + { + isAsc = "asc"; + } + else if ("descending".equals(isAsc)) + { + isAsc = "desc"; + } + this.isAsc = isAsc; + } + } + + public Boolean getReasonable() + { + if (StringUtils.isNull(reasonable)) + { + return Boolean.TRUE; + } + return reasonable; + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/page/TableDataInfo.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/page/TableDataInfo.java new file mode 100644 index 0000000..1f44b27 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/page/TableDataInfo.java @@ -0,0 +1,52 @@ +package com.m2pool.common.core.web.page; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @Description 表格分页数据对象 + * @Date 2024/6/13 11:59 + * @Author dy + */ +@Data +public class TableDataInfo implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 总记录数 */ + private long total; + + /** 总页数 */ + private long totalPage; + + /** 列表数据 */ + private List rows; + + /** 消息状态码 */ + private int code; + + /** 消息内容 */ + private String msg; + + /** + * 表格数据对象 + */ + public TableDataInfo() + { + } + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List list, int total) + { + this.rows = list; + this.total = total; + } + +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/page/TableSupport.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/page/TableSupport.java new file mode 100644 index 0000000..587817e --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/web/page/TableSupport.java @@ -0,0 +1,56 @@ +package com.m2pool.common.core.web.page; + +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.ServletUtils; + +/** + * @Description 表格数据处理 + * @Date 2024/6/12 11:32 + * @Author dy + */ +public class TableSupport +{ + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 分页参数合理化 + */ + public static final String REASONABLE = "reasonable"; + + /** + * 封装分页对象 + */ + public static Page getPage() + { + Page page = new Page(); + page.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1)); + page.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 20)); + page.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN)); + page.setIsAsc(ServletUtils.getParameter(IS_ASC)); + page.setReasonable(ServletUtils.getParameterToBool(REASONABLE)); + return page; + } + + public static Page buildPageRequest() + { + return getPage(); + } +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/xss/Xss.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/xss/Xss.java new file mode 100644 index 0000000..8781650 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/xss/Xss.java @@ -0,0 +1,27 @@ +package com.m2pool.common.core.xss; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author dy + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) +@Constraint(validatedBy = { XssValidator.class }) +public @interface Xss +{ + String message() + + default "不允许任何脚本运行"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/m2pool-common/common-core/src/main/java/com/m2pool/common/core/xss/XssValidator.java b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/xss/XssValidator.java new file mode 100644 index 0000000..c4f9337 --- /dev/null +++ b/m2pool-common/common-core/src/main/java/com/m2pool/common/core/xss/XssValidator.java @@ -0,0 +1,36 @@ +package com.m2pool.common.core.xss; + + +import com.m2pool.common.core.utils.StringUtils; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author dy + */ +public class XssValidator implements ConstraintValidator +{ + private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; + + @Override + public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) + { + if (StringUtils.isBlank(value)) + { + return true; + } + return !containsHtml(value); + } + + public static boolean containsHtml(String value) + { + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + return matcher.matches(); + } +} diff --git a/m2pool-common/common-core/src/main/resources/META-INF/spring.factories b/m2pool-common/common-core/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..7307847 --- /dev/null +++ b/m2pool-common/common-core/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.m2pool.common.core.utils.SpringUtils + + diff --git a/m2pool-common/common-datascope/common-datascope.iml b/m2pool-common/common-datascope/common-datascope.iml new file mode 100644 index 0000000..56914d4 --- /dev/null +++ b/m2pool-common/common-datascope/common-datascope.iml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-common/common-datascope/pom.xml b/m2pool-common/common-datascope/pom.xml new file mode 100644 index 0000000..904ffc4 --- /dev/null +++ b/m2pool-common/common-datascope/pom.xml @@ -0,0 +1,28 @@ + + + + m2pool-common + com.m2pool + 3.5.0 + + 4.0.0 + + common-datascope + + + common-datascope权限范围 + + + + + + + com.m2pool + common-security + + + + + diff --git a/m2pool-common/common-datascope/src/main/java/com/m2pool/common/datascope/annotation/DataScope.java b/m2pool-common/common-datascope/src/main/java/com/m2pool/common/datascope/annotation/DataScope.java new file mode 100644 index 0000000..0ff086c --- /dev/null +++ b/m2pool-common/common-datascope/src/main/java/com/m2pool/common/datascope/annotation/DataScope.java @@ -0,0 +1,25 @@ +package com.m2pool.common.datascope.annotation; + +import java.lang.annotation.*; + +/** + * @Description 数据权限过滤注解 + * @Date 2024/6/12 15:13 + * @Author dy + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataScope { + + /** + * 部门表的别名 + */ + public String deptAlias() default ""; + + /** + * 用户表的别名 + */ + public String userAlias() default ""; + +} diff --git a/m2pool-common/common-datascope/src/main/java/com/m2pool/common/datascope/aspect/DataScopeAspect.java b/m2pool-common/common-datascope/src/main/java/com/m2pool/common/datascope/aspect/DataScopeAspect.java new file mode 100644 index 0000000..f4a2524 --- /dev/null +++ b/m2pool-common/common-datascope/src/main/java/com/m2pool/common/datascope/aspect/DataScopeAspect.java @@ -0,0 +1,139 @@ +package com.m2pool.common.datascope.aspect; + +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.entity.BaseEntity; +import com.m2pool.common.datascope.annotation.DataScope; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.entity.SysRole; +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.api.model.LoginUser; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +/** + * @Description 数据过滤处理 + * @Date 2024/6/12 15:23 + * @Author dy + */ +@Aspect +@Component +public class DataScopeAspect { + + /** 全部数据权限 */ + public static final String DATA_SCOPE_ALL = "1"; + + /** 自定数据权限 */ + public static final String DATA_SCOPE_CUSTOM = "2"; + + /** 部门数据权限 */ + public static final String DATA_SCOPE_DEPT = "3"; + + /** 部门及以下数据权限 */ + public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; + + /** 仅本人数据权限 */ + public static final String DATA_SCOPE_SELF = "5"; + + /** 数据权限过滤关键字 */ + public static final String DATA_SCOPE = "dataScope"; + + @Before("@annotation(controllerDataScope)") + public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable + { + clearDataScope(point); + handleDataScope(point, controllerDataScope); + } + + protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) + { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) + { + SysUser currentUser = loginUser.getSysUser(); + // 超级管理员,无需过滤 + if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) + { + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), + controllerDataScope.userAlias()); + } + } + } + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param deptAlias 部门别名 + * @param userAlias 用户别名 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) + { + StringBuilder sqlString = new StringBuilder(); + + for (SysRole role : user.getRoles()) + { + String dataScope = role.getDataScope(); + if (DATA_SCOPE_ALL.equals(dataScope)) + { + sqlString = new StringBuilder(); + break; + } + else if (DATA_SCOPE_CUSTOM.equals(dataScope)) + { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, + role.getRoleId())); + } + else if (DATA_SCOPE_DEPT.equals(dataScope)) + { + sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); + } + else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) + { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", + deptAlias, user.getDeptId(), user.getDeptId())); + } + else if (DATA_SCOPE_SELF.equals(dataScope)) + { + if (StringUtils.isNotBlank(userAlias)) + { + sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); + } + else + { + // 数据权限为仅本人且没有userAlias别名不查询任何数据 + sqlString.append(" OR 1=0 "); + } + } + } + + if (StringUtils.isNotBlank(sqlString.toString())) + { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); + } + } + } + + /** + * 先清空params.dataScope参数防止注入 + */ + private void clearDataScope(final JoinPoint joinPoint) + { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, ""); + } + } + +} diff --git a/m2pool-common/common-datascope/src/main/resources/META-INF/spring.factories b/m2pool-common/common-datascope/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..7c034c3 --- /dev/null +++ b/m2pool-common/common-datascope/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.m2pool.common.datascope.aspect.DataScopeAspect + + diff --git a/m2pool-common/common-datasource/common-datasource.iml b/m2pool-common/common-datasource/common-datasource.iml new file mode 100644 index 0000000..aefc56e --- /dev/null +++ b/m2pool-common/common-datasource/common-datasource.iml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-common/common-datasource/pom.xml b/m2pool-common/common-datasource/pom.xml new file mode 100644 index 0000000..a5a9599 --- /dev/null +++ b/m2pool-common/common-datasource/pom.xml @@ -0,0 +1,61 @@ + + + + m2pool-common + com.m2pool + 3.5.0 + + 4.0.0 + + common-datasource + + + 多数据源配置 + + + + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + + com.baomidou + dynamic-datasource-spring-boot-starter + ${dynamic-ds.version} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/DistributionDB.java b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/DistributionDB.java new file mode 100644 index 0000000..71ec559 --- /dev/null +++ b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/DistributionDB.java @@ -0,0 +1,15 @@ +package com.m2pool.common.datasource.annotation; + +import com.baomidou.dynamic.datasource.annotation.DS; + +import java.lang.annotation.*; + +/** + * 收益数据库(只读) + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@DS("distribution") +public @interface DistributionDB { +} diff --git a/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/GAuth.java b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/GAuth.java new file mode 100644 index 0000000..a70e180 --- /dev/null +++ b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/GAuth.java @@ -0,0 +1,15 @@ +package com.m2pool.common.datasource.annotation; + +import com.baomidou.dynamic.datasource.annotation.DS; + +import java.lang.annotation.*; + +/** + * 谷歌验证器相关数据库 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@DS("gauth") +public @interface GAuth { +} diff --git a/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/HashRateDB.java b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/HashRateDB.java new file mode 100644 index 0000000..845bb80 --- /dev/null +++ b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/HashRateDB.java @@ -0,0 +1,15 @@ +package com.m2pool.common.datasource.annotation; + +import com.baomidou.dynamic.datasource.annotation.DS; + +import java.lang.annotation.*; + +/** + * 算力数据库(只读) + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@DS("hashrate") +public @interface HashRateDB { +} diff --git a/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/InfoDB.java b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/InfoDB.java new file mode 100644 index 0000000..6e9bbab --- /dev/null +++ b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/InfoDB.java @@ -0,0 +1,15 @@ +package com.m2pool.common.datasource.annotation; + +import com.baomidou.dynamic.datasource.annotation.DS; + +import java.lang.annotation.*; + +/** + * 矿池数据库(只读) + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@DS("infodb") +public @interface InfoDB { +} diff --git a/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/Master.java b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/Master.java new file mode 100644 index 0000000..22657d0 --- /dev/null +++ b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/Master.java @@ -0,0 +1,15 @@ +package com.m2pool.common.datasource.annotation; + +import com.baomidou.dynamic.datasource.annotation.DS; + +import java.lang.annotation.*; + +/** + * 主数据库 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@DS("master") +public @interface Master { +} diff --git a/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/Pool2DB.java b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/Pool2DB.java new file mode 100644 index 0000000..274417c --- /dev/null +++ b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/Pool2DB.java @@ -0,0 +1,15 @@ +package com.m2pool.common.datasource.annotation; + +import com.baomidou.dynamic.datasource.annotation.DS; + +import java.lang.annotation.*; + +/** + * 矿池数据库(只读) + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@DS("pool2") +public @interface Pool2DB { +} diff --git a/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/UserDB.java b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/UserDB.java new file mode 100644 index 0000000..1c3cc3c --- /dev/null +++ b/m2pool-common/common-datasource/src/main/java/com/m2pool/common/datasource/annotation/UserDB.java @@ -0,0 +1,15 @@ +package com.m2pool.common.datasource.annotation; + +import com.baomidou.dynamic.datasource.annotation.DS; + +import java.lang.annotation.*; + +/** + * 矿池数据库(只读) + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@DS("userdb") +public @interface UserDB { +} diff --git a/m2pool-common/common-log/common-log.iml b/m2pool-common/common-log/common-log.iml new file mode 100644 index 0000000..56914d4 --- /dev/null +++ b/m2pool-common/common-log/common-log.iml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-common/common-log/pom.xml b/m2pool-common/common-log/pom.xml new file mode 100644 index 0000000..6c6966f --- /dev/null +++ b/m2pool-common/common-log/pom.xml @@ -0,0 +1,28 @@ + + + + m2pool-common + com.m2pool + 3.5.0 + + 4.0.0 + + common-log + + + 日志记录 + + + + + + + com.m2pool + common-security + + + + + \ No newline at end of file diff --git a/m2pool-common/common-log/src/main/java/com/m2pool/common/log/annotation/Log.java b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/annotation/Log.java new file mode 100644 index 0000000..491dcbf --- /dev/null +++ b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/annotation/Log.java @@ -0,0 +1,43 @@ +package com.m2pool.common.log.annotation; + +import com.m2pool.common.log.enums.BusinessType; +import com.m2pool.common.log.enums.OperatorType; + +import java.lang.annotation.*; + +/** + * @Description 日志记录处理 + * @Date 2024/6/11 14:47 + * @Author dy + */ +@Target({ ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log { + + /** + * 模块 + */ + public String title() default ""; + + /** + * 功能 + */ + public BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + public OperatorType operatorType() default OperatorType.MANAGE; + + /** + * 是否保存请求的参数 + */ + public boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + public boolean isSaveResponseData() default true; + +} diff --git a/m2pool-common/common-log/src/main/java/com/m2pool/common/log/aspect/LogAspect.java b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/aspect/LogAspect.java new file mode 100644 index 0000000..73e4613 --- /dev/null +++ b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/aspect/LogAspect.java @@ -0,0 +1,213 @@ +package com.m2pool.common.log.aspect; + +import com.alibaba.fastjson.JSON; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.ip.IpUtils; +import com.m2pool.common.log.annotation.Log; +import com.m2pool.common.log.enums.BusinessStatus; +import com.m2pool.common.log.service.AsyncLogService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.entity.SysOperLog; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Collection; +import java.util.Map; + +/** + * @Description 日志记录处理 + * @Date 2024/6/12 15:21 + * @Author dy + */ +@Component +@Aspect +public class LogAspect { + + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + @Autowired + private AsyncLogService asyncLogService; + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) + { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) + { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) + { + try + { + // *========数据库日志=========*// + SysOperLog operLog = new SysOperLog(); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); + operLog.setOperIp(ip); + operLog.setOperUrl(ServletUtils.getRequest().getRequestURI()); + String username = SecurityUtils.getUsername(); + if (StringUtils.isNotBlank(username)) + { + operLog.setOperName(username); + } + + if (e != null) + { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 保存数据库 + asyncLogService.saveSysLog(operLog); + } + catch (Exception exp) + { + // 记录本地异常日志 + log.error("==前置通知异常=="); + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + * @throws Exception + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception + { + // 设置action动作 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 设置操作人类别 + operLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) + { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operLog); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) + { + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception + { + String requestMethod = operLog.getRequestMethod(); + if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) + { + String params = argsArrayToString(joinPoint.getArgs()); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) + { + String params = ""; + if (paramsArray != null && paramsArray.length > 0) + { + for (Object o : paramsArray) + { + if (StringUtils.isNotNull(o) && !isFilterObject(o)) + { + try + { + Object jsonObj = JSON.toJSON(o); + params += jsonObj.toString() + " "; + } + catch (Exception e) + { + } + } + } + } + return params.trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) + { + Class clazz = o.getClass(); + if (clazz.isArray()) + { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } + else if (Collection.class.isAssignableFrom(clazz)) + { + Collection collection = (Collection) o; + for (Object value : collection) + { + return value instanceof MultipartFile; + } + } + else if (Map.class.isAssignableFrom(clazz)) + { + Map map = (Map) o; + for (Object value : map.entrySet()) + { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } + +} diff --git a/m2pool-common/common-log/src/main/java/com/m2pool/common/log/enums/BusinessStatus.java b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/enums/BusinessStatus.java new file mode 100644 index 0000000..31309fb --- /dev/null +++ b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/enums/BusinessStatus.java @@ -0,0 +1,16 @@ +package com.m2pool.common.log.enums; + +/** + * @Description 业务操作状态 + * @Date 2024/6/12 14:10 + * @Author dy + */ +public enum BusinessStatus { + + /** 成功 */ + SUCCESS, + + /** 失败 */ + FAIL, + +} diff --git a/m2pool-common/common-log/src/main/java/com/m2pool/common/log/enums/BusinessType.java b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/enums/BusinessType.java new file mode 100644 index 0000000..5909f0b --- /dev/null +++ b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/enums/BusinessType.java @@ -0,0 +1,38 @@ +package com.m2pool.common.log.enums; + +/** + * @Description 业务操作类型 + * @Date 2024/6/12 14:20 + * @Author dy + */ +public enum BusinessType { + + /** 其它 */ + OTHER, + + /** 新增 */ + INSERT, + + /** 修改 */ + UPDATE, + + /** 删除 */ + DELETE, + + /** 授权 */ + GRANT, + + /** 导出 */ + EXPORT, + + /** 导入 */ + IMPORT, + + /** 强退 */ + FORCE, + + /** 清空数据 */ + CLEAN, + + //Todo 根据需求再增加业务操作类型 +} diff --git a/m2pool-common/common-log/src/main/java/com/m2pool/common/log/enums/OperatorType.java b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/enums/OperatorType.java new file mode 100644 index 0000000..b50cac0 --- /dev/null +++ b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/enums/OperatorType.java @@ -0,0 +1,20 @@ +package com.m2pool.common.log.enums; + +/** + * @Description 操作者类型 + * @Date 2024/6/12 14:42 + * @Author dy + */ +public enum OperatorType { + + /** 其它 */ + OTHER, + + /** 后台用户 */ + MANAGE, + + /** 手机端用户 */ + MOBILE + + +} diff --git a/m2pool-common/common-log/src/main/java/com/m2pool/common/log/service/AsyncLogService.java b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/service/AsyncLogService.java new file mode 100644 index 0000000..173f88a --- /dev/null +++ b/m2pool-common/common-log/src/main/java/com/m2pool/common/log/service/AsyncLogService.java @@ -0,0 +1,31 @@ +package com.m2pool.common.log.service; + +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.system.api.RemoteLogService; +import com.m2pool.system.api.entity.SysOperLog; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +/** + * @Description 异步调用日志 + * @Date 2024/6/13 10:08 + * @Author dy + */ +@Service +public class AsyncLogService { + + @Autowired + private RemoteLogService remoteLogService; + + /** + * 保存系统日志记录 + */ + @Async + public void saveSysLog(SysOperLog sysOperLog) + { + remoteLogService.saveLog(sysOperLog, SecurityConstants.INNER); + } + + +} diff --git a/m2pool-common/common-log/src/main/resources/META-INF/spring.factories b/m2pool-common/common-log/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..8c131ce --- /dev/null +++ b/m2pool-common/common-log/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.m2pool.common.log.service.AsyncLogService,\ + com.m2pool.common.log.aspect.LogAspect diff --git a/m2pool-common/common-redis/common-redis.iml b/m2pool-common/common-redis/common-redis.iml new file mode 100644 index 0000000..7cebc60 --- /dev/null +++ b/m2pool-common/common-redis/common-redis.iml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-common/common-redis/pom.xml b/m2pool-common/common-redis/pom.xml new file mode 100644 index 0000000..be747d2 --- /dev/null +++ b/m2pool-common/common-redis/pom.xml @@ -0,0 +1,34 @@ + + + + m2pool-common + com.m2pool + 3.5.0 + + 4.0.0 + + common-redis + + + redis缓存服务 + + + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + com.m2pool + common-core + + + + + \ No newline at end of file diff --git a/m2pool-common/common-redis/src/main/java/com/m2pool/common/redis/config/FastJson2JsonRedisSerializer.java b/m2pool-common/common-redis/src/main/java/com/m2pool/common/redis/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..46157ee --- /dev/null +++ b/m2pool-common/common-redis/src/main/java/com/m2pool/common/redis/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,73 @@ +package com.m2pool.common.redis.config; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.parser.ParserConfig; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.util.Assert; + +import java.nio.charset.Charset; + +/** + * @Description redis使用FastJson序列化 + * @Date 2024/6/13 10:01 + * @Author dy + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer { + + @SuppressWarnings("unused") + private ObjectMapper objectMapper = new ObjectMapper(); + + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private Class clazz; + + static + { + ParserConfig.getGlobalInstance().setAutoTypeSupport(true); + } + + public FastJson2JsonRedisSerializer(Class clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz); + } + + public void setObjectMapper(ObjectMapper objectMapper) + { + Assert.notNull(objectMapper, "'objectMapper' must not be null"); + this.objectMapper = objectMapper; + } + + protected JavaType getJavaType(Class clazz) + { + return TypeFactory.defaultInstance().constructType(clazz); + } + +} diff --git a/m2pool-common/common-redis/src/main/java/com/m2pool/common/redis/config/RedisConfig.java b/m2pool-common/common-redis/src/main/java/com/m2pool/common/redis/config/RedisConfig.java new file mode 100644 index 0000000..c7aaf8a --- /dev/null +++ b/m2pool-common/common-redis/src/main/java/com/m2pool/common/redis/config/RedisConfig.java @@ -0,0 +1,85 @@ +package com.m2pool.common.redis.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * @Description redis配置 + * @Date 2024/6/13 09:58 + * @Author dy + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport { + + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + + ObjectMapper mapper = new ObjectMapper(); + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + serializer.setObjectMapper(mapper); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } + + @Bean + public DefaultRedisScript limitScript() + { + // 泛型是返回值的类型 + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + // 设置脚本 + redisScript.setScriptText(limitScriptText()); + // 设置返回值类型 + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() + { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } + + +} diff --git a/m2pool-common/common-redis/src/main/java/com/m2pool/common/redis/service/RedisService.java b/m2pool-common/common-redis/src/main/java/com/m2pool/common/redis/service/RedisService.java new file mode 100644 index 0000000..579571b --- /dev/null +++ b/m2pool-common/common-redis/src/main/java/com/m2pool/common/redis/service/RedisService.java @@ -0,0 +1,267 @@ +package com.m2pool.common.redis.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * @Description redis 相关 + * @Date 2024/6/13 9:22 + * @Author dy + */ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +public class RedisService { + + @Autowired + private RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) + { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) + { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) + { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) + { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) + { + return redisTemplate.getExpire(key); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key,TimeUnit timetype) + { + return redisTemplate.getExpire(key,timetype); + } + + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) + { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) + { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) + { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public long deleteObject(final Collection collection) + { + return redisTemplate.delete(collection); + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) + { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) + { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) + { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) + { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) + { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) + { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) + { + return redisTemplate.opsForHash().entries(key); + + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) + { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) + { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) + { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) + { + return redisTemplate.keys(pattern); + } + +} diff --git a/m2pool-common/common-redis/src/main/resources/META-INF/spring.factories b/m2pool-common/common-redis/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..15ca3a1 --- /dev/null +++ b/m2pool-common/common-redis/src/main/resources/META-INF/spring.factories @@ -0,0 +1,5 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.m2pool.common.redis.config.RedisConfig,\ + com.m2pool.common.redis.service.RedisService + + diff --git a/m2pool-common/common-security/common-security.iml b/m2pool-common/common-security/common-security.iml new file mode 100644 index 0000000..c2d43b0 --- /dev/null +++ b/m2pool-common/common-security/common-security.iml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-common/common-security/pom.xml b/m2pool-common/common-security/pom.xml new file mode 100644 index 0000000..22d2659 --- /dev/null +++ b/m2pool-common/common-security/pom.xml @@ -0,0 +1,41 @@ + + + + m2pool-common + com.m2pool + 3.5.0 + + 4.0.0 + + common-security + + + 安全模块 + + + + + + + org.springframework + spring-webmvc + + + + + com.m2pool + api-system + + + + + + com.m2pool + common-redis + + + + + diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/EnableCustomConfig.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/EnableCustomConfig.java new file mode 100644 index 0000000..d9168b0 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/EnableCustomConfig.java @@ -0,0 +1,27 @@ +package com.m2pool.common.security.annotation; + +import com.m2pool.common.security.config.ApplicationConfig; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.cloud.openfeign.FeignAutoConfiguration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; +import org.springframework.scheduling.annotation.EnableAsync; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan("com.m2pool.**.com.m2pool.chat.mapper") +// 开启线程异步执行 +@EnableAsync +// 自动加载类 +@Import({ ApplicationConfig.class, FeignAutoConfiguration.class }) +public @interface EnableCustomConfig +{ + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/EnableM2PoolFeignClients.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/EnableM2PoolFeignClients.java new file mode 100644 index 0000000..a7b0750 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/EnableM2PoolFeignClients.java @@ -0,0 +1,28 @@ +package com.m2pool.common.security.annotation; + +import org.springframework.cloud.openfeign.EnableFeignClients; + +import java.lang.annotation.*; + +/** + * @Description 自定义feign注解 + * * 添加basePackages 主类包路径 + * @Date 2024/6/13 11:12 + * @Author dy + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@EnableFeignClients +public @interface EnableM2PoolFeignClients { + + String[] value() default {}; + + String[] basePackages() default { "com.m2pool" }; + + Class[] basePackageClasses() default {}; + + Class[] defaultConfiguration() default {}; + + Class[] clients() default {}; +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/InnerAuth.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/InnerAuth.java new file mode 100644 index 0000000..dadfa0a --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/InnerAuth.java @@ -0,0 +1,20 @@ +package com.m2pool.common.security.annotation; + +import java.lang.annotation.*; + +/** + * @Description 内部认证注解 + * @Date 2024/6/13 11:16 + * @Author dy + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface InnerAuth { + + /** + * 是否校验用户信息 + */ + boolean isUser() default false; + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/Logical.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/Logical.java new file mode 100644 index 0000000..3184065 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/Logical.java @@ -0,0 +1,19 @@ +package com.m2pool.common.security.annotation; + +/** + * @Description 权限注解验证模式设定 + * @Date 2024/6/14 9:33 + * @Author dy + */ +public enum Logical +{ + /** + * 必须具有所有的元素 + */ + AND, + + /** + * 只需具有其中一个元素 + */ + OR +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/OpenApiPermissions.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/OpenApiPermissions.java new file mode 100644 index 0000000..1cf8597 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/OpenApiPermissions.java @@ -0,0 +1,27 @@ +package com.m2pool.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @Description oepn api key权限认证:必须拥有该方法所需权限标识才能进入方法 + * @Date 2024/6/14 10:12 + * @Author dy + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface OpenApiPermissions { + + /** + * 需要校验的角色标识 + */ + String[] value() default {}; + + /** + * 验证逻辑:AND | OR,默认AND + */ + Logical logical() default Logical.AND; + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/RequiresLogin.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/RequiresLogin.java new file mode 100644 index 0000000..dd41783 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/RequiresLogin.java @@ -0,0 +1,16 @@ +package com.m2pool.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @Description 登录认证:登录成功后才能进入该方法 + * @Date 2024/6/14 10:11 + * @Author dy + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequiresLogin { +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/RequiresPermissions.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/RequiresPermissions.java new file mode 100644 index 0000000..c08b2bf --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/RequiresPermissions.java @@ -0,0 +1,27 @@ +package com.m2pool.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @Description 权限认证:必须拥有该方法所需权限标识才能进入方法 + * @Date 2024/6/14 10:12 + * @Author dy + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequiresPermissions { + + /** + * 需要校验的角色标识 + */ + String[] value() default {}; + + /** + * 验证逻辑:AND | OR,默认AND + */ + Logical logical() default Logical.AND; + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/RequiresRoles.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/RequiresRoles.java new file mode 100644 index 0000000..51a43d3 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/annotation/RequiresRoles.java @@ -0,0 +1,27 @@ +package com.m2pool.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @Description 角色认证:必须拥有该方法所需角色标识才能进入方法 + * @Date 2024/6/14 10:25 + * @Author 杜懿 + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequiresRoles { + + /** + * 需要校验的角色标识 + */ + String[] value() default {}; + + /** + * 验证逻辑:AND | OR,默认AND + */ + Logical logical() default Logical.AND; + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/aspect/InnerAuthAspect.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/aspect/InnerAuthAspect.java new file mode 100644 index 0000000..5d9c5f8 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/aspect/InnerAuthAspect.java @@ -0,0 +1,50 @@ +package com.m2pool.common.security.aspect; + +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.exception.InnerAuthException; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.security.annotation.InnerAuth; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; + +/** + * @Description 内部服务调用验证处理 + * @Date 2024/6/13 18:08 + * @Author dy + */ +@Aspect +@Component +public class InnerAuthAspect implements Ordered { + + @Around("@annotation(innerAuth)") + public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable { + String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE); + // 内部请求验证 + if (!StringUtils.equals(SecurityConstants.INNER, source)) + { + throw new InnerAuthException("没有内部访问权限,不允许访问"); + } + + String userId = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID); + String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME); + + // 用户信息验证 + if (innerAuth.isUser() && (StringUtils.isEmpty(userId) || StringUtils.isEmpty(username))) + { + throw new InnerAuthException("没有设置用户信息,不允许访问 "); + } + return point.proceed(); + } + + /** + * 确保方法在权限认证aop执行前执行 + */ + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE + 1; + } +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/aspect/PreAuthAspect.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/aspect/PreAuthAspect.java new file mode 100644 index 0000000..6936dcb --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/aspect/PreAuthAspect.java @@ -0,0 +1,105 @@ +package com.m2pool.common.security.aspect; + +import com.m2pool.common.security.annotation.OpenApiPermissions; +import com.m2pool.common.security.annotation.RequiresLogin; +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.common.security.annotation.RequiresRoles; +import com.m2pool.common.security.auth.ApiUtil; +import com.m2pool.common.security.auth.AuthUtil; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +/** + * @Description 基于 Spring Aop 的注解鉴权 + * @Date 2024/6/13 18:47 + * @Author dy + */ +@Aspect +@Component +public class PreAuthAspect { + + /** + * 构建 + */ + public PreAuthAspect(){ + + } + + /** + * 定义AOP签名 (切入所有使用鉴权注解的方法) + */ + public static final String POINTCUT_SIGN = " @annotation(com.m2pool.common.security.annotation.RequiresLogin) || " + + "@annotation(com.m2pool.common.security.annotation.RequiresPermissions) || " + + "@annotation(com.m2pool.common.security.annotation.RequiresRoles) || " + + "@annotation(com.m2pool.common.security.annotation.OpenApiPermissions)"; + + /** + * 声明AOP签名 + */ + @Pointcut(POINTCUT_SIGN) + public void pointcut() + { + } + + /** + * 环绕切入 + * + * @param joinPoint 切面对象 + * @return 底层方法执行后的返回值 + * @throws Throwable 底层方法抛出的异常 + */ + @Around("pointcut()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + + // 注解鉴权 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + checkMethodAnnotation(signature.getMethod()); + try + { + // 执行原有逻辑 + Object obj = joinPoint.proceed(); + return obj; + } + catch (Throwable e) + { + throw e; + } + } + + /** + * 对一个Method对象进行注解检查 + */ + public void checkMethodAnnotation(Method method) + { + //校验 @RequiresLogin 注解 + RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class); + if(requiresLogin != null){ + AuthUtil.checkLogin(); + } + + //校验 @RequiresRoles 注解 + RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class); + if(requiresRoles != null){ + AuthUtil.checkRole(requiresRoles); + } + + //校验 @RequiresPermissions 注解 + RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class); + if(requiresPermissions != null){ + AuthUtil.checkPermission(requiresPermissions); + } + + //校验 @OpenApiPermissions 注解 + OpenApiPermissions openApiPermissions = method.getAnnotation(OpenApiPermissions.class); + if(openApiPermissions != null){ + System.out.println("校验 @OpenApiPermissions 注解"); + ApiUtil.checkPermission(openApiPermissions); + } + } +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/aspect/RateLimiterAspect.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/aspect/RateLimiterAspect.java new file mode 100644 index 0000000..b43e535 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/aspect/RateLimiterAspect.java @@ -0,0 +1,94 @@ +package com.m2pool.common.security.aspect; + +import com.m2pool.common.core.annotation.RateLimiter; +import com.m2pool.common.core.constant.HttpStatus; +import com.m2pool.common.core.enums.LimitType; +import com.m2pool.common.core.exception.ServiceException; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.ip.IpUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +/** + * @Description 限流处理 + * @Date 2024/6/13 18:38 + * @Author dy + */ +@Aspect +@Component +public class RateLimiterAspect { + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private RedisTemplate redisTemplate; + + private RedisScript limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript limitScript) + { + this.limitScript = limitScript; + } + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable + { + String key = rateLimiter.key(); + int time = rateLimiter.time(); + int count = rateLimiter.count(); + + String combineKey = getCombineKey(rateLimiter, point); + List keys = Collections.singletonList(combineKey); + try + { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (StringUtils.isNull(number) || number.intValue() > count) + { + throw new ServiceException("访问过于频繁,请稍候再试", HttpStatus.TOO_MANY_REQUESTS); + } + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey); + } + catch (ServiceException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) + { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); + if (rateLimiter.limitType() == LimitType.IP) + { + stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-"); + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuffer.toString(); + } +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/ApiUtil.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/ApiUtil.java new file mode 100644 index 0000000..ca83b92 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/ApiUtil.java @@ -0,0 +1,106 @@ +package com.m2pool.common.security.auth; + +import com.m2pool.common.security.annotation.OpenApiPermissions; +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.system.api.model.OpenApiKeyInfo; + +/** + * @Description api 验证工具类 + * @Date 2024/6/13 19:17 + * @Author dy + */ +public class ApiUtil { + + /** + * 底层的oaReq对象 + */ + public static OpenApiRequest oaReq = new OpenApiRequest(); + + /** + * 检查当前apikey是否有效 + */ + public static void checkApiKey(){ + oaReq.checkApiKey(); + } + + /** + * 获取当前api-key信息 + * @param apiKey + * @return + */ + public static OpenApiKeyInfo getApiKeyInfo(String apiKey){return oaReq.getOpenApiKeyInfo(apiKey);} + + /** + * 验证当前api-key有效期 + * @param openApiKeyInfo + */ + public static void verifyApiKeyExpire(OpenApiKeyInfo openApiKeyInfo){ + oaReq.verifyApiKeyExpire(openApiKeyInfo); + } + + + /** + * 判断当前账号是否含有指定权限 + * + * @param permission 权限码 + * @return 是否含有指定权限 + */ + public static boolean hasPermission(String permission) + { + return oaReq.hasPermission(permission); + } + + /** + * 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param permission 权限码 + */ + public static void checkPermission(String permission) + { + oaReq.checkPermission(permission); + } + + /** + * 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param openApiPermissions 权限注解 + */ + public static void checkPermission(OpenApiPermissions openApiPermissions) + { + oaReq. checkPermission(openApiPermissions); + } + + /** + * 当前账号是否含有指定权限 [指定多个,必须全部验证通过] + * + * @param permissions 权限码数组 + */ + public static void checkPermissionAnd(String... permissions) + { + oaReq.checkPermissionAnd(permissions); + } + + /** + * 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] + * + * @param permissions 权限码数组 + */ + public static void checkPermissionOr(String... permissions) + { + oaReq.checkPermissionOr(permissions); + } + + + + /** + * 会话注销 + */ + public static void logout(){oaReq.logout();} + + /** + * 根据token注销会话 + * @param apiKey + */ + public static void logoutByApiKey(String apiKey){oaReq.logoutByApiKey(apiKey);} + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/AuthLogic.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/AuthLogic.java new file mode 100644 index 0000000..56c57d1 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/AuthLogic.java @@ -0,0 +1,373 @@ +package com.m2pool.common.security.auth; + +import com.m2pool.common.core.exception.auth.NotLoginException; +import com.m2pool.common.core.exception.auth.NotPermissionException; +import com.m2pool.common.core.exception.auth.NotRoleException; +import com.m2pool.common.core.utils.SpringUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.security.annotation.Logical; +import com.m2pool.common.security.annotation.RequiresLogin; +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.common.security.annotation.RequiresRoles; +import com.m2pool.common.security.service.TokenService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.model.LoginUser; +import org.springframework.util.PatternMatchUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * @Description 权限验证的逻辑实现类 + * @Date 2024/6/13 19:04 + * @Author dy + */ +public class AuthLogic { + + /** 所有权限标识 */ + private static final String ALL_PERMISSION = "*:*:*"; + + /** 管理员角色权限标识 */ + private static final String SUPER_ADMIN = "admin"; + + private TokenService tokenService = SpringUtils.getBean(TokenService.class); + + /** + * 会话注销 + */ + public void logout() + { + String token = SecurityUtils.getToken(); + if (token == null) + { + return; + } + logoutByToken(token); + } + + /** + * 会话注销,根据指定Token + */ + public void logoutByToken(String token) + { + tokenService.delLoginUser(token); + } + + /** + * 检验用户是否已经登录,如未登录,则抛出异常 + */ + public void checkLogin(){getLoginUser();} + + /** + * 获取当前用户缓存信息, 如果未登录,则抛出异常 + * + * @return 用户缓存信息 + */ + public LoginUser getLoginUser() + { + String token = SecurityUtils.getToken(); + if (token == null) + { + throw new NotLoginException("未提供token"); + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null) + { + throw new NotLoginException("无效的token"); + } + return loginUser; + } + + /** + * 获取当前用户缓存信息, 如果未登录,则抛出异常 + * + * @param token 前端传递的认证信息 + * @return 用户缓存信息 + */ + public LoginUser getLoginUser(String token) + { + return tokenService.getLoginUser(token); + } + + /** + * 验证当前用户有效期, 如果相差不足120分钟,自动刷新缓存 + * + * @param loginUser 当前用户信息 + */ + public void verifyLoginUserExpire(LoginUser loginUser) + { + tokenService.verifyToken(loginUser); + } + + /** + * 根据注解(@RequiresLogin)鉴权 + * + * @param at 注解对象 + */ + public void checkByAnnotation(RequiresLogin at) + { + this.checkLogin(); + } + + /** + * 根据注解(@RequiresRoles)鉴权 + * + * @param at 注解对象 + */ + public void checkByAnnotation(RequiresRoles at) + { + String[] roleArray = at.value(); + if (at.logical() == Logical.AND) + { + this.checkRoleAnd(roleArray); + } + else + { + this.checkRoleOr(roleArray); + } + } + + /** + * 根据注解(@RequiresPermissions)鉴权 + * + * @param at 注解对象 + */ + public void checkByAnnotation(RequiresPermissions at) + { + String[] permissionArray = at.value(); + if (at.logical() == Logical.AND) + { + this.checkPermissionAnd(permissionArray); + } + else + { + this.checkPermissionOr(permissionArray); + } + } + + /** + * 获取当前账号的角色列表 + * + * @return 角色列表 + */ + public Set getRoleList() + { + try + { + LoginUser loginUser = getLoginUser(); + return loginUser.getRoles(); + } + catch (Exception e) + { + return new HashSet<>(); + } + } + + /** + * 获取当前账号的权限列表 + * + * @return 权限列表 + */ + public Set getPermissionList() + { + try + { + LoginUser loginUser = getLoginUser(); + return loginUser.getPermissions(); + } + catch (Exception e) + { + return new HashSet<>(); + } + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色标识 + * @return 用户是否具备某角色 + */ + public boolean hasRole(String role) + { + return hasRole(getRoleList(), role); + } + + /** + * 判断用户是否拥有某个角色, 如果验证未通过,则抛出异常: NotRoleException + * + * @param role 角色标识 + */ + public void checkRole(String role) + { + if (!hasRole(role)) + { + throw new NotRoleException(role); + } + } + + /** + * 根据注解(@RequiresRoles)鉴权 + * + * @param requiresRoles 注解对象 + */ + public void checkRole(RequiresRoles requiresRoles) + { + if (requiresRoles.logical() == Logical.AND) + { + checkRoleAnd(requiresRoles.value()); + } + else + { + checkRoleOr(requiresRoles.value()); + } + } + + /** + * 验证用户是否含有指定角色,必须全部拥有 + * + * @param roles 角色标识数组 + */ + public void checkRoleAnd(String... roles) + { + Set roleList = getRoleList(); + for (String role : roles) + { + if (!hasRole(roleList, role)) + { + throw new NotRoleException(role); + } + } + } + + /** + * 验证用户是否含有指定角色,只需包含其中一个 + * + * @param roles 角色标识数组 + */ + public void checkRoleOr(String... roles) + { + Set roleList = getRoleList(); + for (String role : roles) + { + if (hasRole(roleList, role)) + { + return; + } + } + if (roles.length > 0) + { + throw new NotRoleException(roles); + } + } + + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermission(String permission) + { + return hasPermission(getPermissionList(), permission); + } + + /** + * 验证用户是否具备某权限, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public void checkPermission(String permission) + { + System.out.println("被调用的实际是我"); + if (!hasPermission(getPermissionList(), permission)) + { + throw new NotPermissionException(permission); + } + } + + /** + * 根据注解(@RequiresPermissions)鉴权, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param requiresPermissions 注解对象 + */ + public void checkPermission(RequiresPermissions requiresPermissions) + { + if (requiresPermissions.logical() == Logical.AND) + { + checkPermissionAnd(requiresPermissions.value()); + } + else + { + checkPermissionOr(requiresPermissions.value()); + } + } + + /** + * 验证用户是否含有指定权限,必须全部拥有 + * + * @param permissions 权限列表 + */ + public void checkPermissionAnd(String... permissions) + { + Set permissionList = getPermissionList(); + for (String permission : permissions) + { + if (!hasPermission(permissionList, permission)) + { + throw new NotPermissionException(permission); + } + } + } + + /** + * 验证用户是否含有指定权限,只需包含其中一个 + * + * @param permissions 权限码数组 + */ + public void checkPermissionOr(String... permissions) + { + Set permissionList = getPermissionList(); + for (String permission : permissions) + { + if (hasPermission(permissionList, permission)) + { + return; + } + } + if (permissions.length > 0) + { + throw new NotPermissionException(permissions); + } + } + + + /** + * 判断是否包含权限 + * + * @param authorities 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermission(Collection authorities, String permission) + { + return authorities.stream().filter(StringUtils::hasText) + .anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission)); + } + + /** + * 判断是否包含角色 + * + * @param roles 角色列表 + * @param role 角色 + * @return 用户是否具备某角色权限 + */ + public boolean hasRole(Collection roles, String role) + { + return roles.stream().filter(StringUtils::hasText) + .anyMatch(x -> SUPER_ADMIN.contains(x) || PatternMatchUtils.simpleMatch(x, role)); + } + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/AuthUtil.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/AuthUtil.java new file mode 100644 index 0000000..30c4379 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/AuthUtil.java @@ -0,0 +1,149 @@ +package com.m2pool.common.security.auth; + +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.common.security.annotation.RequiresRoles; +import com.m2pool.system.api.model.LoginUser; + +/** + * @Description 权限验证工具类 + * @Date 2024/6/13 19:17 + * @Author dy + */ +public class AuthUtil { + + /** + * 底层的AuthLogic对象 + */ + public static AuthLogic authLogic = new AuthLogic(); + + /** + * 检查当前用户是否登陆 + */ + public static void checkLogin(){ + authLogic.checkLogin(); + } + + /** + * 获取当前登陆用户信息 + * @param token + * @return + */ + public static LoginUser getLoginUser(String token){return authLogic.getLoginUser(token);} + + /** + * 验证当前用户有效期 + * @param loginUser + */ + public static void verifyLoginUserExpire(LoginUser loginUser){ + authLogic.verifyLoginUserExpire(loginUser); + } + + /** + *判断当前用户是否有指定的角色标识 + * @param role 角色标识 + * @return 返回布尔值 + */ + public static boolean hasRole(String role){return authLogic.hasRole(role);} + + /** + *当前账号是否含有指定角色标识,如果验证未通过,则抛出异常: NotRoleException + * @param role + */ + public static void checkRole(String role){authLogic.checkRole(role);} + + /** + * 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotRoleException + * + * @param requiresRoles 角色权限注解 + */ + public static void checkRole(RequiresRoles requiresRoles) + { + authLogic.checkRole(requiresRoles); + } + + /** + * 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过] + * + * @param roles 角色标识数组 + */ + public static void checkRoleAnd(String... roles) + { + authLogic.checkRoleAnd(roles); + } + + /** + * 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可] + * + * @param roles 角色标识数组 + */ + public static void checkRoleOr(String... roles) + { + authLogic.checkRoleOr(roles); + } + + + /** + * 判断当前账号是否含有指定权限 + * + * @param permission 权限码 + * @return 是否含有指定权限 + */ + public static boolean hasPermission(String permission) + { + return authLogic.hasPermission(permission); + } + + /** + * 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param permission 权限码 + */ + public static void checkPermission(String permission) + { + authLogic.checkPermission(permission); + } + + /** + * 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param requiresPermissions 权限注解 + */ + public static void checkPermission(RequiresPermissions requiresPermissions) + { + authLogic. checkPermission(requiresPermissions); + } + + /** + * 当前账号是否含有指定权限 [指定多个,必须全部验证通过] + * + * @param permissions 权限码数组 + */ + public static void checkPermissionAnd(String... permissions) + { + authLogic.checkPermissionAnd(permissions); + } + + /** + * 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] + * + * @param permissions 权限码数组 + */ + public static void checkPermissionOr(String... permissions) + { + authLogic.checkPermissionOr(permissions); + } + + + + /** + * 会话注销 + */ + public static void logout(){authLogic.logout();} + + /** + * 根据token注销会话 + * @param token + */ + public static void logoutByToken(String token){authLogic.logoutByToken(token);} + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/OpenApiRequest.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/OpenApiRequest.java new file mode 100644 index 0000000..e6f6b51 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/auth/OpenApiRequest.java @@ -0,0 +1,237 @@ +package com.m2pool.common.security.auth; + +import com.m2pool.common.core.exception.auth.NotLoginException; +import com.m2pool.common.core.exception.auth.NotPermissionException; +import com.m2pool.common.core.utils.SpringUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.security.annotation.Logical; +import com.m2pool.common.security.annotation.OpenApiPermissions; +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.common.security.service.OpenApiService; +import com.m2pool.common.security.utils.OpenApiUtils; +import com.m2pool.system.api.model.LoginUser; +import com.m2pool.system.api.model.OpenApiKeyInfo; +import org.springframework.util.PatternMatchUtils; + +import javax.sound.midi.Soundbank; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * @Description 权限验证的逻辑实现类 + * @Date 2024/6/13 19:04 + * @Author dy + */ +public class OpenApiRequest { + + /** 所有权限标识 */ + private static final String ALL_PERMISSION = "*:*:*"; + + private OpenApiService openApiService = SpringUtils.getBean(OpenApiService.class); + + /** + * 会话注销 + */ + public void logout() + { + String apiKey = OpenApiUtils.getApiKey(); + if (apiKey == null) + { + return; + } + logoutByApiKey(apiKey); + } + + /** + * 会话注销,根据指定Token + */ + public void logoutByApiKey(String apiKey) + { + openApiService.delOpenApiKey(apiKey); + } + + /** + * 检验用户是否已经生成过apiKey,如未生成,则抛出异常 + */ + public void checkApiKey(){getApiKey();} + + /** + * 获取当前用户缓存信息, 如果未登录,则抛出异常 + * + * @return 用户缓存信息 + */ + public OpenApiKeyInfo getApiKey() + { + String apiKey = OpenApiUtils.getApiKey(); + if (apiKey == null) + { + throw new NotLoginException("未提供API-KEY"); + } + OpenApiKeyInfo apiKeyInfo = OpenApiUtils.getOpenApiKeyInfo(); + if (apiKeyInfo == null) + { + throw new NotLoginException("无效的API-KEY"); + } + return apiKeyInfo; + } + + /** + * 获取当前用户缓存信息, 如果未登录,则抛出异常 + * + * @param apiKey 前端传递的认证信息 + * @return 用户缓存信息 + */ + public OpenApiKeyInfo getOpenApiKeyInfo(String apiKey) + { + return openApiService.getOpenApiKeyInfo(apiKey); + } + + /** + * 验证当前api-key有效期, 如果相差不足120分钟,自动刷新缓存 + * + * @param openApiKey 当前用户信息 + */ + public void verifyApiKeyExpire(OpenApiKeyInfo openApiKey) + { + openApiService.verifyApiKey(openApiKey); + } + + /** + * 根据注解(@RequiresPermissions)鉴权 + * + * @param at 注解对象 + */ + public void checkByAnnotation(RequiresPermissions at) + { + String[] permissionArray = at.value(); + if (at.logical() == Logical.AND) + { + this.checkPermissionAnd(permissionArray); + } + else + { + this.checkPermissionOr(permissionArray); + } + } + + /** + * 获取当前账号的权限列表 + * + * @return 权限列表 + */ + public Set getPermissionList() + { + try + { + OpenApiKeyInfo apiKeyInfo = getApiKey(); + return apiKeyInfo.getPermissions(); + } + catch (Exception e) + { + return new HashSet<>(); + } + } + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermission(String permission) + { + System.out.println("hasPermission"); + return hasPermission(getPermissionList(), permission); + } + + /** + * 验证用户是否具备某权限, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public void checkPermission(String permission) + { + System.out.println("OpenApiRequest.checkPermission"); + if (!hasPermission(getPermissionList(), permission)) + { + throw new NotPermissionException(permission); + } + } + + /** + * 根据注解(@RequiresPermissions)鉴权, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param openApiPermissions 注解对象 + */ + public void checkPermission(OpenApiPermissions openApiPermissions) + { + if (openApiPermissions.logical() == Logical.AND) + { + checkPermissionAnd(openApiPermissions.value()); + } + else + { + checkPermissionOr(openApiPermissions.value()); + } + } + + /** + * 验证用户是否含有指定权限,必须全部拥有 + * + * @param permissions 权限列表 + */ + public void checkPermissionAnd(String... permissions) + { + Set permissionList = getPermissionList(); + for (String permission : permissions) + { + if (!hasPermission(permissionList, permission)) + { + throw new NotPermissionException(permission); + } + } + } + + /** + * 验证用户是否含有指定权限,只需包含其中一个 + * + * @param permissions 权限码数组 + */ + public void checkPermissionOr(String... permissions) + { + System.out.println("OpenApiRequest.checkPermissionOr"); + Set permissionList = getPermissionList(); + for (String permission : permissions) + { + if (hasPermission(permissionList, permission)) + { + return; + } + } + if (permissions.length > 0) + { + throw new NotPermissionException(permissions); + } + } + + + /** + * 判断是否包含权限 + * + * @param authorities 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermission(Collection authorities, String permission) + { + System.out.println("权限列表:"+authorities); + System.out.println("所需权限:"+permission); + return authorities.stream().filter(StringUtils::hasText) + .anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission)); + } + + + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/config/ApplicationConfig.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/config/ApplicationConfig.java new file mode 100644 index 0000000..c646b3b --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/config/ApplicationConfig.java @@ -0,0 +1,24 @@ +package com.m2pool.common.security.config; + +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; + +import java.util.TimeZone; + +/** + * @Description 系统配置 + * @Date 2024/6/13 10:59 + * @Author dy + */ +public class ApplicationConfig { + + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/config/WebMvcConfig.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/config/WebMvcConfig.java new file mode 100644 index 0000000..b3e6583 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/config/WebMvcConfig.java @@ -0,0 +1,48 @@ +package com.m2pool.common.security.config; + +import com.m2pool.common.security.interceptor.HeaderInterceptor; +import com.m2pool.common.security.interceptor.OpenApiHeaderInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @Description 拦截器配置 + * @Date 2024/6/13 19:56 + * @Author dy + */ +public class WebMvcConfig implements WebMvcConfigurer { + + /** token 不需要拦截的地址 */ + public static final String[] excludeUrls = {"/login","/logout","/refresh", + "/account/hashrate_real","/account/hashrate_history","/account/hashrate_last24h", + "/account/miners_list","/account/watch","/account/test1", + "/miner/hashrate_real","/miner/hashrate_history","/miner/hashrate_last24h", + "/pool/hashrate","/pool/hashrate_history","/pool/miners_list","/pool/watch","/oapi/**"}; + + public static final String[] matchUrls = { + "/account/hashrate_real","/account/hashrate_history","/account/hashrate_last24h", + "/account/miners_list","/account/watch","/account/test1", + "/miner/hashrate_real","/miner/hashrate_history","/miner/hashrate_last24h", + "/pool/hashrate","/pool/hashrate_history","/pool/miners_list","/pool/watch","/oapi/**"}; + + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(getHeaderInterceptor()) + .addPathPatterns("/**") + .excludePathPatterns(excludeUrls) + .order(-10); + + registry.addInterceptor(getOpenApiHeaderInterceptor()) + .addPathPatterns(matchUrls). + excludePathPatterns("/auth/**,/system/**,/pool/**") + .order(-15); + } + /** + * 自定义请求头拦截器 + */ + public HeaderInterceptor getHeaderInterceptor(){return new HeaderInterceptor();} + + public OpenApiHeaderInterceptor getOpenApiHeaderInterceptor(){return new OpenApiHeaderInterceptor();} + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/feign/FeignAutoConfig.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/feign/FeignAutoConfig.java new file mode 100644 index 0000000..12afc73 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/feign/FeignAutoConfig.java @@ -0,0 +1,18 @@ +package com.m2pool.common.security.feign; + +import feign.RequestInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @Description fegin 配置注册 + * @Date 2024/6/13 20:33 + * @Author dy + */ +@Configuration +public class FeignAutoConfig { + + @Bean + public RequestInterceptor requestInterceptor(){return new FeignRequestInterceptor();} + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/feign/FeignRequestInterceptor.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/feign/FeignRequestInterceptor.java new file mode 100644 index 0000000..8717bc5 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/feign/FeignRequestInterceptor.java @@ -0,0 +1,51 @@ +package com.m2pool.common.security.feign; + +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.ip.IpUtils; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * @Description fegin 请求拦截器 + * @Date 2024/6/13 20:12 + * @Author dy + */ +@Component +public class FeignRequestInterceptor implements RequestInterceptor { + @Override + public void apply(RequestTemplate template) { + System.out.println("feign拦截器开始执行"); + HttpServletRequest httpServletRequest = ServletUtils.getRequest(); + if (StringUtils.isNotNull(httpServletRequest)) + { + Map headers = ServletUtils.getHeaders(httpServletRequest); + // 传递用户信息请求头,防止丢失 + String userId = headers.get(SecurityConstants.DETAILS_USER_ID); + if (StringUtils.isNotEmpty(userId)) + { + template.header(SecurityConstants.DETAILS_USER_ID, userId); + } + String userName = headers.get(SecurityConstants.DETAILS_USERNAME); + if (StringUtils.isNotEmpty(userName)) + { + template.header(SecurityConstants.DETAILS_USERNAME, userName); + } + String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER); + if (StringUtils.isNotEmpty(authentication)) + { + template.header(SecurityConstants.AUTHORIZATION_HEADER, authentication); + } + System.out.println("userId:"+userId); + System.out.println("userName:"+userName); + System.out.println("ip:"+IpUtils.getIpAddr(ServletUtils.getRequest())); + // 配置客户端IP + template.header("X-Forwarded-For", IpUtils.getIpAddr(ServletUtils.getRequest())); + } + } +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/handler/GlobalExceptionHandler.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..9856b42 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/handler/GlobalExceptionHandler.java @@ -0,0 +1,181 @@ +package com.m2pool.common.security.handler; + +import com.m2pool.common.core.constant.HttpStatus; +import com.m2pool.common.core.exception.InnerAuthException; +import com.m2pool.common.core.exception.ServiceException; +import com.m2pool.common.core.exception.auth.NotPermissionException; +import com.m2pool.common.core.exception.auth.NotRoleException; +import com.m2pool.common.core.exception.file.FileNameLengthLimitExceededException; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.model.LoginUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.multipart.MultipartException; + +import javax.servlet.http.HttpServletRequest; +import java.sql.SQLException; + +/** + * @Description 全局异常处理器 + * @Date 2024/6/13 20:36 + * @Author dy + */ +@Order(Ordered.HIGHEST_PRECEDENCE) +@RestControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限码异常 + */ + @ExceptionHandler(NotPermissionException.class) + public AjaxResult handleNotPermissionException(NotPermissionException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 角色权限异常 + */ + @ExceptionHandler(NotRoleException.class) + public AjaxResult handleNotRoleException(NotRoleException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + LoginUser loginUser = SecurityUtils.getLoginUser(); + System.out.println(loginUser.getRoles()); + log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "权限不足,请自行升级或联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return AjaxResult.error(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 拦截文件异常异常 + */ + @ExceptionHandler(MultipartException.class) + public AjaxResult handleMultException(MultipartException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生文件异常.", requestURI, e); + return AjaxResult.error("文件过大,单个文件最大2M"); + } + + /** + * 拦截文件名超长异常 + */ + @ExceptionHandler(FileNameLengthLimitExceededException.class) + public AjaxResult handleFileNameLengthException(FileNameLengthLimitExceededException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生文件异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public AjaxResult handleBindException(BindException e) + { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) + { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 内部认证异常 + */ + @ExceptionHandler(InnerAuthException.class) + public AjaxResult handleInnerAuthException(InnerAuthException e) + { + return AjaxResult.error(e.getMessage()); + } + + + /** + * sql查询验证异常 + */ + @ExceptionHandler(SQLException.class) + public AjaxResult handleSQLException(SQLException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生sql执行异常.", requestURI, e); + return AjaxResult.error("请求地址'{}',发生sql执行异常."); + } + + @ExceptionHandler(BadSqlGrammarException.class) + public AjaxResult handleBadSqlGrammarException(BadSqlGrammarException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生SQL异常.", requestURI, e); + return AjaxResult.error(HttpStatus.ERROR, "数据库异常!"); + } + //todo 根据业务需求增加异常 +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/interceptor/HeaderInterceptor.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/interceptor/HeaderInterceptor.java new file mode 100644 index 0000000..2544bb5 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/interceptor/HeaderInterceptor.java @@ -0,0 +1,61 @@ +package com.m2pool.common.security.interceptor; + +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.context.SecurityContextHolder; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.ip.IpUtils; +import com.m2pool.common.security.auth.AuthUtil; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.model.LoginUser; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.AsyncHandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @Description 自定义请求头拦截器,将请求头数据封装到线程变量中方便后续获取使用 + * 同时获取当前用户有效时间并刷新有效期 + * @Date 2024/6/13 20:29 + * @Author dy + */ +public class HeaderInterceptor implements AsyncHandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + System.out.println("调用默认拦截器,路径:"+request.getRequestURI()); + System.out.println("调用默认拦截器,路径:"+ IpUtils.getIpAddr(request)); + System.out.println(handler); + if(!(handler instanceof HandlerMethod)){ + System.out.println("默认拦截器放行"); + return true; + + } + System.out.println("默认拦截器处理"); + SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID)); + SecurityContextHolder.setUserName(ServletUtils.getHeader(request,SecurityConstants.DETAILS_USERNAME)); + SecurityContextHolder.setUserKey(ServletUtils.getHeader(request,SecurityConstants.USER_KEY)); + + String token = SecurityUtils.getToken(); + if(StringUtils.isNotEmpty(token)){ + + LoginUser loginUser = AuthUtil.getLoginUser(token); + if(StringUtils.isNotNull(loginUser)){ + + //验证当前用户有效时间 + AuthUtil.verifyLoginUserExpire(loginUser); + SecurityContextHolder.set(SecurityConstants.LOGIN_USER,loginUser); + } + + } + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + + SecurityContextHolder.remove(); + + } +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/interceptor/OpenApiHeaderInterceptor.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/interceptor/OpenApiHeaderInterceptor.java new file mode 100644 index 0000000..057bacc --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/interceptor/OpenApiHeaderInterceptor.java @@ -0,0 +1,68 @@ +package com.m2pool.common.security.interceptor; + +import com.m2pool.common.core.constant.OpenApiKeyConstants; +import com.m2pool.common.core.context.OpenApiContextHolder; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.ip.IpUtils; +import com.m2pool.common.security.auth.ApiUtil; +import com.m2pool.common.security.utils.OpenApiUtils; +import com.m2pool.system.api.model.OpenApiKeyInfo; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.AsyncHandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @Description 自定义请求头拦截器,将请求头数据封装到线程变量中方便后续获取使用 + * 同时获取当前用户有效时间并刷新有效期 + * @Date 2024/6/13 20:29 + * @Author dy + */ +public class OpenApiHeaderInterceptor implements AsyncHandlerInterceptor { + + private static final Map limitCounter = new ConcurrentHashMap<>(); + private static final int MAX_REQUESTS_SECOND = 5; + private static final int MAX_REQUESTS_DAY = 5000; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + System.out.println("调用Oapi拦截器,路径:"+request.getRequestURI()); + System.out.println(IpUtils.getIpAddr(request)); + if(!(handler instanceof HandlerMethod)){ + return true; + + } + + //System.out.println("OpenApiHeaderInterceptor.preHandle 开始赋值"); + + OpenApiContextHolder.setApiUser(ServletUtils.getHeader(request, OpenApiKeyConstants.API_USER)); + OpenApiContextHolder.setApiIp(ServletUtils.getHeader(request,OpenApiKeyConstants.API_IP)); + OpenApiContextHolder.setApiKey(ServletUtils.getHeader(request,OpenApiKeyConstants.API_KEY)); + + String apiKey = OpenApiUtils.getApiKey(); + if(StringUtils.isNotEmpty(apiKey)){ + + OpenApiKeyInfo apiKeyInfo = ApiUtil.getApiKeyInfo(apiKey); + if(StringUtils.isNotNull(apiKeyInfo)){ + + //验证当前用户有效时间 + ApiUtil.verifyApiKeyExpire(apiKeyInfo); + OpenApiContextHolder.set(OpenApiKeyConstants.API_KEY_INFO,apiKeyInfo); + } + + } + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + + OpenApiContextHolder.remove(); + + } +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/service/OpenApiService.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/service/OpenApiService.java new file mode 100644 index 0000000..616e622 --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/service/OpenApiService.java @@ -0,0 +1,186 @@ +package com.m2pool.common.security.service; + +import com.m2pool.common.core.constant.CacheConstants; +import com.m2pool.common.core.constant.OpenApiKeyConstants; +import com.m2pool.common.core.utils.OpenApiJwtUtils; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.ip.IpUtils; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.utils.OpenApiUtils; +import com.m2pool.system.api.model.OpenApiKeyInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @Description open api key 验证处理 + * @Date 2024/6/13 21:06 + * @Author dy + */ +@Component +public class OpenApiService { + + @Autowired + private RedisService redisService; + + /** 秒 */ + protected static final long MILLIS_SECOND = 1000; + + /** 分 */ + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + /** 过期时间/缓存有效期 设置为1年*/ + private final static long expireTime = CacheConstants.EXPIRATION * 2; + + /** 权限缓存前缀 */ + private final static String ACCESS_API_KEY = CacheConstants.OPEN_API_KEY; + + /** 缓存刷新时间 */ + private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE; + + /** + * 创建令牌 + */ + public Map createApiKey(OpenApiKeyInfo openApiKey) + { + String apiKey = openApiKey.getKey(); + System.out.println("生成的apiKey:"+apiKey); + String user = openApiKey.getUser(); + String apiIp = openApiKey.getApiIp(); + openApiKey.setKey(apiKey); + openApiKey.setUser(user); + if (StringUtils.isNotBlank(apiIp)){ + openApiKey.setApiIp(apiIp); + }else { + openApiKey.setApiIp(IpUtils.getIpAddr(ServletUtils.getRequest())); + } + + refreshApiKey(openApiKey); + + // Jwt存储信息 + Map claimsMap = new HashMap(); + claimsMap.put(OpenApiKeyConstants.API_KEY, apiKey); + claimsMap.put(OpenApiKeyConstants.API_USER, user); + claimsMap.put(OpenApiKeyConstants.API_IP, openApiKey.getApiIp()); + + // 接口返回信息 + Map rspMap = new HashMap(); + rspMap.put("api_key", OpenApiJwtUtils.createApiKey(claimsMap)); + rspMap.put("uuid生成的api_key", apiKey); + rspMap.put("expires_in", expireTime); + rspMap.put("user", user); + return rspMap; + } + + /** + * 获取apiKey信息 + * + * @return apiKey信息 + */ + public OpenApiKeyInfo getOpenApiKeyInfo() + { + return getOpenApiKeyInfo(ServletUtils.getRequest()); + } + + /** + * 获取apiKey信息 + * + * @return apiKey信息 + */ + public OpenApiKeyInfo getOpenApiKeyInfo(HttpServletRequest request) + { + // 获取请求携带的令牌 + String apiKey = OpenApiUtils.getApiKey(request); + return getOpenApiKeyInfo(apiKey); + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public OpenApiKeyInfo getOpenApiKeyInfo(String apiKey) + { + OpenApiKeyInfo openApiKey = null; + try + { + if (StringUtils.isNotEmpty(apiKey)) + { + String key = OpenApiJwtUtils.getApiKey(apiKey); + openApiKey = redisService.getCacheObject(getAPIRedisKey(key)); + return openApiKey; + } + } + catch (Exception e) + { + System.out.println("oapiService.getOpenApiKeyInfo 异常"); + e.printStackTrace(); + } + return openApiKey; + } + + /** + * 设置用户api_key信息 + */ + public void setOpenApiKeyInfo(OpenApiKeyInfo openApiKey) + { + if (StringUtils.isNotNull(openApiKey) && StringUtils.isNotEmpty(openApiKey.getKey())) + { + refreshApiKey(openApiKey); + } + } + + /** + * 删除api_key缓存信息 + */ + public void delOpenApiKey(String apiKey) + { + if (StringUtils.isNotEmpty(apiKey)) + { + String key = OpenApiJwtUtils.getApiKey(apiKey); + //System.out.println("用户登出,从redis删除key:"+api_key:...); + redisService.deleteObject(getAPIRedisKey(key)); + } + } + + /** + * 验证令牌有效期,相差不足120分钟,自动刷新缓存 + * + * @param openApiKey + */ + public void verifyApiKey(OpenApiKeyInfo openApiKey) + { + long expireTime = openApiKey.getExpireTime(); + long currentTime = System.currentTimeMillis(); + //可以考虑直接刷新缓存 而不是小于才刷新缓存 + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) + { + refreshApiKey(openApiKey); + } + } + + /** + * 刷新api-key有效期 + * + * @param openApiKey api-key请求信息 + */ + public void refreshApiKey(OpenApiKeyInfo openApiKey) + { + openApiKey.setLoginTime(System.currentTimeMillis()); + openApiKey.setExpireTime(openApiKey.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将api-key缓存 + String apiKey = getAPIRedisKey(openApiKey.getKey()); + redisService.setCacheObject(apiKey, openApiKey, expireTime, TimeUnit.MINUTES); + } + + private String getAPIRedisKey(String token) + { + return ACCESS_API_KEY + token; + } + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/service/TokenService.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/service/TokenService.java new file mode 100644 index 0000000..9a54a9e --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/service/TokenService.java @@ -0,0 +1,179 @@ +package com.m2pool.common.security.service; + +import com.m2pool.common.core.constant.CacheConstants; +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.utils.JwtUtils; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.ip.IpUtils; +import com.m2pool.common.core.utils.uuid.IdUtils; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.model.LoginUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @Description token验证处理 + * @Date 2024/6/13 21:06 + * @Author dy + */ +@Component +public class TokenService { + + @Autowired + private RedisService redisService; + + /** 秒 */ + protected static final long MILLIS_SECOND = 1000; + + /** 分 */ + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + /** 过期时间/缓存有效期 */ + private final static long expireTime = CacheConstants.EXPIRATION; + + /** 权限缓存前缀 */ + private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY; + + /** 缓存刷新时间 */ + private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE; + + /** + * 创建令牌 + */ + public Map createToken(LoginUser loginUser) + { + String token = IdUtils.fastUUID(); + Long userId = loginUser.getSysUser().getUserId(); + String userName = loginUser.getSysUser().getUserName(); + loginUser.setToken(token); + loginUser.setUserid(userId); + loginUser.setUsername(userName); + loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest())); + refreshToken(loginUser); + + // Jwt存储信息 + Map claimsMap = new HashMap(); + claimsMap.put(SecurityConstants.USER_KEY, token); + claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId); + claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName); + + // 接口返回信息 + Map rspMap = new HashMap(); + rspMap.put("access_token", JwtUtils.createToken(claimsMap)); + rspMap.put("expires_in", expireTime); + rspMap.put("userName", loginUser.getUsername()); + return rspMap; + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser() + { + return getLoginUser(ServletUtils.getRequest()); + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) + { + // 获取请求携带的令牌 + String token = SecurityUtils.getToken(request); + return getLoginUser(token); + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(String token) + { + LoginUser user = null; + try + { + if (StringUtils.isNotEmpty(token)) + { + String userkey = JwtUtils.getUserKey(token); + user = redisService.getCacheObject(getTokenKey(userkey)); + return user; + } + } + catch (Exception e) + { + } + return user; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) + { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) + { + refreshToken(loginUser); + } + } + + /** + * 删除用户缓存信息 + */ + public void delLoginUser(String token) + { + if (StringUtils.isNotEmpty(token)) + { + String userkey = JwtUtils.getUserKey(token); + //System.out.println("用户登出,从redis删除key:"+userkey); + redisService.deleteObject(getTokenKey(userkey)); + } + } + + /** + * 验证令牌有效期,相差不足120分钟,自动刷新缓存 + * + * @param loginUser + */ + public void verifyToken(LoginUser loginUser) + { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + //可以考虑直接刷新缓存 而不是小于才刷新缓存 + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) + { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) + { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + } + + private String getTokenKey(String token) + { + return ACCESS_TOKEN + token; + } + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/utils/OpenApiUtils.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/utils/OpenApiUtils.java new file mode 100644 index 0000000..c3c0acc --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/utils/OpenApiUtils.java @@ -0,0 +1,86 @@ +package com.m2pool.common.security.utils; + + +import com.m2pool.common.core.constant.OpenApiKeyConstants; +import com.m2pool.common.core.constant.TokenConstants; +import com.m2pool.common.core.context.OpenApiContextHolder; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.system.api.model.OpenApiKeyInfo; + + +import javax.servlet.http.HttpServletRequest; + +/** + * @Description 权限获取工具类 + * @Date 2024/6/11 16:58 + * @Author dy + */ +public class OpenApiUtils { + + + /** + * 获取api用户 + */ + public static String getApiUser() + { + return OpenApiContextHolder.getApiUser(); + } + + /** + * 获取api绑定的ip + */ + public static String getApiIp() + { + return OpenApiContextHolder.getApiIp(); + } + + /** + * 获取api key + */ + public static String getKey() + { + return OpenApiContextHolder.getApiKey(); + } + + + /** + * 获取apiKey对应的apiKey详细信息 + */ + public static OpenApiKeyInfo getOpenApiKeyInfo() + { + OpenApiKeyInfo info = OpenApiContextHolder.get(OpenApiKeyConstants.API_KEY_INFO, OpenApiKeyInfo.class); + return info; + } + /** + * 获取请求token + */ + public static String getApiKey() + { + return getApiKey(ServletUtils.getRequest()); + } + + /** + * 根据request获取请求api-key + */ + public static String getApiKey(HttpServletRequest request) + { + // 从header获取token标识 + String token = request.getHeader(TokenConstants.API_KEY); + return token; + } + + /** + * 是否为管理员 + * + * @param user 用户邮箱 + * @return 结果 + */ + public static boolean isAdmin(String user) + { + return StringUtils.isNotBlank(user) && "".equals(user); + } + + + +} diff --git a/m2pool-common/common-security/src/main/java/com/m2pool/common/security/utils/SecurityUtils.java b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/utils/SecurityUtils.java new file mode 100644 index 0000000..65ab37c --- /dev/null +++ b/m2pool-common/common-security/src/main/java/com/m2pool/common/security/utils/SecurityUtils.java @@ -0,0 +1,120 @@ +package com.m2pool.common.security.utils; + +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.constant.TokenConstants; +import com.m2pool.common.core.context.SecurityContextHolder; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.system.api.model.LoginUser; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import javax.servlet.http.HttpServletRequest; + +/** + * @Description 权限获取工具类 + * @Date 2024/6/11 16:58 + * @Author dy + */ +public class SecurityUtils { + + + /** + * 获取用户ID + */ + public static Long getUserId() + { + return SecurityContextHolder.getUserId(); + } + + /** + * 获取用户名称 + */ + public static String getUsername() + { + return SecurityContextHolder.getUserName(); + } + + /** + * 获取用户key + */ + public static String getUserKey() + { + return SecurityContextHolder.getUserKey(); + } + + /** + * 获取登录用户信息 + */ + public static LoginUser getLoginUser() + { + return SecurityContextHolder.get(SecurityConstants.LOGIN_USER, LoginUser.class); + } + + /** + * 获取请求token + */ + public static String getToken() + { + return getToken(ServletUtils.getRequest()); + } + + /** + * 根据request获取请求token + */ + public static String getToken(HttpServletRequest request) + { + // 从header获取token标识 + String token = request.getHeader(TokenConstants.AUTHENTICATION); + return replaceTokenPrefix(token); + } + + /** + * 裁剪token前缀 + */ + public static String replaceTokenPrefix(String token) + { + // 如果前端设置了令牌前缀,则裁剪掉前缀 + if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) + { + token = token.replaceFirst(TokenConstants.PREFIX, ""); + } + return token; + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + +} diff --git a/m2pool-common/common-security/src/main/resources/META-INF/spring.factories b/m2pool-common/common-security/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..53e2cb9 --- /dev/null +++ b/m2pool-common/common-security/src/main/resources/META-INF/spring.factories @@ -0,0 +1,7 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.m2pool.common.security.config.WebMvcConfig,\ + com.m2pool.common.security.service.TokenService,\ + com.m2pool.common.security.service.OpenApiService,\ + com.m2pool.common.security.aspect.PreAuthAspect,\ + com.m2pool.common.security.aspect.InnerAuthAspect,\ + com.m2pool.common.security.handler.GlobalExceptionHandler diff --git a/m2pool-common/common-swagger/common-swagger.iml b/m2pool-common/common-swagger/common-swagger.iml new file mode 100644 index 0000000..d29857f --- /dev/null +++ b/m2pool-common/common-swagger/common-swagger.iml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-common/common-swagger/pom.xml b/m2pool-common/common-swagger/pom.xml new file mode 100644 index 0000000..1b5bbfb --- /dev/null +++ b/m2pool-common/common-swagger/pom.xml @@ -0,0 +1,37 @@ + + + + m2pool-common + com.m2pool + 3.5.0 + + 4.0.0 + + common-swagger + + + + common-swagger系统接口 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + io.springfox + springfox-swagger2 + ${swagger.fox.version} + + + + + + \ No newline at end of file diff --git a/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/annotation/EnableCustomSwagger2.java b/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/annotation/EnableCustomSwagger2.java new file mode 100644 index 0000000..edb7963 --- /dev/null +++ b/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/annotation/EnableCustomSwagger2.java @@ -0,0 +1,16 @@ +package com.m2pool.common.swagger.annotation; + + +import com.m2pool.common.swagger.config.SwaggerAutoConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Import({ SwaggerAutoConfiguration.class }) +public @interface EnableCustomSwagger2 { + +} diff --git a/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerAutoConfiguration.java b/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerAutoConfiguration.java new file mode 100644 index 0000000..7516fe1 --- /dev/null +++ b/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerAutoConfiguration.java @@ -0,0 +1,128 @@ +package com.m2pool.common.swagger.config; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.*; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.ApiSelectorBuilder; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +/** + * @Description swagger自动配置 + * @Date 2024/6/12 15:28 + * @Author dy + */ +@EnableSwagger2 +@Configuration +@EnableAutoConfiguration +@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true) +public class SwaggerAutoConfiguration { + + /** 默认的排除路径,排除Spring Boot默认的错误处理路径和端点 */ + private static final List DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**"); + + private static final String BASE_PATH = "/**"; + + @Bean + @ConditionalOnMissingBean + public SwaggerProperties swaggerProperties() + { + return new SwaggerProperties(); + } + + @Bean + public Docket api(SwaggerProperties swaggerProperties) + { + // base-path处理 + if (swaggerProperties.getBasePath().isEmpty()) + { + swaggerProperties.getBasePath().add(BASE_PATH); + } + + // noinspection unchecked + List> basePath = new ArrayList>(); + swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path))); + + // exclude-path处理 需要额外排除的url规则 + if (swaggerProperties.getExcludePath().isEmpty()) + { + swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH); + } + + ApiSelectorBuilder builder = new Docket(DocumentationType.SWAGGER_2).host(swaggerProperties.getHost()) + .apiInfo(apiInfo(swaggerProperties)).select() + .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage())); + + swaggerProperties.getBasePath().forEach(p -> builder.paths(PathSelectors.ant(p))); + swaggerProperties.getExcludePath().forEach(p -> builder.paths(PathSelectors.ant(p).negate())); + + + return builder.build().securitySchemes(securitySchemes()).securityContexts(securityContexts()).pathMapping("/"); + + } + + /** 安全模式,指定token通过Authorization头请求头传递 */ + private List securitySchemes() + { + List apiKeyList = new ArrayList(); + apiKeyList.add(new ApiKey("Authorization", "Authorization", "header")); + return apiKeyList; + } + + /** 安全上下文 */ + private List securityContexts() + { + List securityContexts = new ArrayList<>(); + securityContexts.add( + SecurityContext.builder() + .securityReferences(defaultAuth()) + .operationSelector(o -> o.requestMappingPattern().matches("/.*")) + .build()); + return securityContexts; + } + + + /** + * 默认的全局鉴权策略 + * + * @return + */ + private List defaultAuth() + { + // + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; + } + + + private ApiInfo apiInfo(SwaggerProperties swaggerProperties) + { + return new ApiInfoBuilder() + .title(swaggerProperties.getTitle()) + .description(swaggerProperties.getDescription()) + .license(swaggerProperties.getLicense()) + .licenseUrl(swaggerProperties.getLicenseUrl()) + .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl()) + .contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail())) + .version(swaggerProperties.getVersion()) + .build(); + + } +} diff --git a/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerBeanPostProcessor.java b/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerBeanPostProcessor.java new file mode 100644 index 0000000..cfdf8a8 --- /dev/null +++ b/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerBeanPostProcessor.java @@ -0,0 +1,56 @@ +package com.m2pool.common.swagger.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; +import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider; +import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @Description TODO + * @Date 2024/6/12 17:14 + * @Author dy + */ +@Component +public class SwaggerBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException + { + if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) + { + customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); + } + return bean; + } + + private void customizeSpringfoxHandlerMappings(List mappings) + { + List copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null) + .collect(Collectors.toList()); + mappings.clear(); + mappings.addAll(copy); + } + + @SuppressWarnings("unchecked") + private List getHandlerMappings(Object bean) + { + try + { + Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); + field.setAccessible(true); + return (List) field.get(bean); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + throw new IllegalStateException(e); + } + } + +} diff --git a/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerProperties.java b/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerProperties.java new file mode 100644 index 0000000..82fc222 --- /dev/null +++ b/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerProperties.java @@ -0,0 +1,350 @@ +package com.m2pool.common.swagger.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Description Swagger配置 + * @Date 2024/6/12 15:16 + * @Author dy + */ +@Component +@ConfigurationProperties("swagger") +public class SwaggerProperties { + /** + * 是否开启swagger + */ + private Boolean enabled; + + /** + * swagger会解析的包路径 + **/ + private String basePackage = ""; + + /** + * swagger会解析的url规则 + **/ + private List basePath = new ArrayList<>(); + + /** + * 在basePath基础上需要排除的url规则 + **/ + private List excludePath = new ArrayList<>(); + + /** + * 标题 + **/ + private String title = ""; + + /** + * 描述 + **/ + private String description = ""; + + /** + * 版本 + **/ + private String version = ""; + + /** + * 许可证 + **/ + private String license = ""; + + /** + * 许可证URL + **/ + private String licenseUrl = ""; + + /** + * 服务条款URL + **/ + private String termsOfServiceUrl = ""; + + /** + * host信息 + **/ + private String host = ""; + + /** + * 联系人信息 + */ + private Contact contact = new Contact(); + + /** + * 全局统一鉴权配置 + **/ + private Authorization authorization = new Authorization(); + + public Boolean getEnabled() + { + return enabled; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public String getBasePackage() + { + return basePackage; + } + + public void setBasePackage(String basePackage) + { + this.basePackage = basePackage; + } + + public List getBasePath() + { + return basePath; + } + + public void setBasePath(List basePath) + { + this.basePath = basePath; + } + + public List getExcludePath() + { + return excludePath; + } + + public void setExcludePath(List excludePath) + { + this.excludePath = excludePath; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getDescription() + { + return description; + } + + public void setDescription(String description) + { + this.description = description; + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getLicense() + { + return license; + } + + public void setLicense(String license) + { + this.license = license; + } + + public String getLicenseUrl() + { + return licenseUrl; + } + + public void setLicenseUrl(String licenseUrl) + { + this.licenseUrl = licenseUrl; + } + + public String getTermsOfServiceUrl() + { + return termsOfServiceUrl; + } + + public void setTermsOfServiceUrl(String termsOfServiceUrl) + { + this.termsOfServiceUrl = termsOfServiceUrl; + } + + public String getHost() + { + return host; + } + + public void setHost(String host) + { + this.host = host; + } + + public Contact getContact() + { + return contact; + } + + public void setContact(Contact contact) + { + this.contact = contact; + } + + public Authorization getAuthorization() + { + return authorization; + } + + public void setAuthorization(Authorization authorization) + { + this.authorization = authorization; + } + + public static class Contact + { + /** + * 联系人 + **/ + private String name = ""; + /** + * 联系人url + **/ + private String url = ""; + /** + * 联系人email + **/ + private String email = ""; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getUrl() + { + return url; + } + + public void setUrl(String url) + { + this.url = url; + } + + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + } + + public static class Authorization + { + /** + * 鉴权策略ID,需要和SecurityReferences ID保持一致 + */ + private String name = ""; + + /** + * 需要开启鉴权URL的正则 + */ + private String authRegex = "^.*$"; + + /** + * 鉴权作用域列表 + */ + private List authorizationScopeList = new ArrayList<>(); + + private List tokenUrlList = new ArrayList<>(); + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getAuthRegex() + { + return authRegex; + } + + public void setAuthRegex(String authRegex) + { + this.authRegex = authRegex; + } + + public List getAuthorizationScopeList() + { + return authorizationScopeList; + } + + public void setAuthorizationScopeList(List authorizationScopeList) + { + this.authorizationScopeList = authorizationScopeList; + } + + public List getTokenUrlList() + { + return tokenUrlList; + } + + public void setTokenUrlList(List tokenUrlList) + { + this.tokenUrlList = tokenUrlList; + } + } + + public static class AuthorizationScope + { + /** + * 作用域名称 + */ + private String scope = ""; + + /** + * 作用域描述 + */ + private String description = ""; + + public String getScope() + { + return scope; + } + + public void setScope(String scope) + { + this.scope = scope; + } + + public String getDescription() + { + return description; + } + + public void setDescription(String description) + { + this.description = description; + } + } +} diff --git a/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerWebConfiguration.java b/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerWebConfiguration.java new file mode 100644 index 0000000..cc9b6d4 --- /dev/null +++ b/m2pool-common/common-swagger/src/main/java/com/m2pool/common/swagger/config/SwaggerWebConfiguration.java @@ -0,0 +1,23 @@ +package com.m2pool.common.swagger.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @Description 资源映射路径 + * @Date 2024/6/12 17:25 + * @Author dy + */ +@Configuration +public class SwaggerWebConfiguration implements WebMvcConfigurer { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** swagger-ui 地址 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); + } + +} diff --git a/m2pool-common/common-swagger/src/main/resources/META-INF/spring.factories b/m2pool-common/common-swagger/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..2b6fa59 --- /dev/null +++ b/m2pool-common/common-swagger/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.m2pool.common.swagger.config.SwaggerAutoConfiguration,\ + com.m2pool.common.swagger.config.SwaggerWebConfiguration,\ + com.m2pool.common.swagger.config.SwaggerBeanPostProcessor \ No newline at end of file diff --git a/m2pool-common/m2pool-common.iml b/m2pool-common/m2pool-common.iml new file mode 100644 index 0000000..7519e50 --- /dev/null +++ b/m2pool-common/m2pool-common.iml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-common/pom.xml b/m2pool-common/pom.xml new file mode 100644 index 0000000..78ef0ab --- /dev/null +++ b/m2pool-common/pom.xml @@ -0,0 +1,26 @@ + + + + m2pool + com.m2pool + 3.5.0 + + 公共模块:存放工具类、日志等公共信息 + + common-core + common-datasource + common-log + common-security + common-redis + common-datascope + common-swagger + + 4.0.0 + + m2pool-common + pom + + + \ No newline at end of file diff --git a/m2pool-gateway/m2pool-gateway.iml b/m2pool-gateway/m2pool-gateway.iml new file mode 100644 index 0000000..7a7ebfd --- /dev/null +++ b/m2pool-gateway/m2pool-gateway.iml @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-gateway/pom.xml b/m2pool-gateway/pom.xml new file mode 100644 index 0000000..fae7029 --- /dev/null +++ b/m2pool-gateway/pom.xml @@ -0,0 +1,128 @@ + + + + m2pool + com.m2pool + 3.5.0 + + gateway网关模块 + + 4.0.0 + + m2pool-gateway + + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + com.alibaba.cloud + spring-cloud-alibaba-sentinel-gateway + + + + + com.alibaba.csp + sentinel-datasource-nacos + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springframework.cloud + spring-cloud-loadbalancer + + + + + com.github.penggle + kaptcha + + + + + + io.springfox + springfox-swagger-ui + ${swagger.fox.version} + + + io.springfox + springfox-swagger2 + ${swagger.fox.version} + + + + + org.projectlombok + lombok-maven-plugin + provided + + + + + com.m2pool + common-redis + + + + com.m2pool + api-system + + + + com.m2pool + common-core + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + + diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/M2PoolGatewayApplication.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/M2PoolGatewayApplication.java new file mode 100644 index 0000000..aab8f92 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/M2PoolGatewayApplication.java @@ -0,0 +1,18 @@ +package com.m2pool.gateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +/** + * @Description 网关启动程序 + * @Date 2024/6/13 12:05 + * @Author dy + */ +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +public class M2PoolGatewayApplication { + public static void main(String[] args) { + SpringApplication.run(M2PoolGatewayApplication.class,args); + System.out.println("网关启动成功"); + } +} \ No newline at end of file diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/config/CaptchaConfig.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/CaptchaConfig.java new file mode 100644 index 0000000..471b6a1 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/CaptchaConfig.java @@ -0,0 +1,86 @@ +package com.m2pool.gateway.config; + +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static com.google.code.kaptcha.Constants.*; + +import java.util.Properties; + +/** + * @Description 验证码配置 + * @Date 2024/6/13 10:55 + * @Author dy + */ +@Configuration +public class CaptchaConfig { + + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.m2pool.gateway.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/config/CorsConfig.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/CorsConfig.java new file mode 100644 index 0000000..9b397d8 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/CorsConfig.java @@ -0,0 +1,56 @@ +package com.m2pool.gateway.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.cors.reactive.CorsUtils; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +/** + * @Description TODO + * @Date 2024/6/13 10:49 + * @Author dy + */ +@Configuration +public class CorsConfig { + /** + * 这里为支持的请求头,如果有自定义的header字段请自己添加 + */ + private static final String ALLOWED_HEADERS = "X-Requested-With, Content-Type, Authorization, credential, X-XSRF-TOKEN, token, Admin-Token, App-Token,os,from,model,net,API-KEY"; + private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS,HEAD"; + private static final String ALLOWED_ORIGIN = "*"; + private static final String ALLOWED_EXPOSE = "*"; + private static final String MAX_AGE = "18000L"; + + @Bean + public WebFilter corsFilter() + { + return (ServerWebExchange ctx, WebFilterChain chain) -> { + ServerHttpRequest request = ctx.getRequest(); + if (CorsUtils.isCorsRequest(request)) + { + ServerHttpResponse response = ctx.getResponse(); + HttpHeaders headers = response.getHeaders(); + headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS); + headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS); + headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN); + headers.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE); + headers.add("Access-Control-Max-Age", MAX_AGE); + headers.add("Access-Control-Allow-Credentials", "true"); + if (request.getMethod() == HttpMethod.OPTIONS) + { + response.setStatusCode(HttpStatus.OK); + return Mono.empty(); + } + } + return chain.filter(ctx); + }; + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/config/GatewayConfig.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/GatewayConfig.java new file mode 100644 index 0000000..530463e --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/GatewayConfig.java @@ -0,0 +1,24 @@ +package com.m2pool.gateway.config; + +import com.m2pool.gateway.handler.SentinelFallbackHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; + +/** + * @Description 网关限流配置 + * @Date 2024/6/13 10:07 + * @Author dy + */ +@Configuration +public class GatewayConfig { + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + public SentinelFallbackHandler sentinelGatewayExceptionHandler() + { + return new SentinelFallbackHandler(); + } + +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/config/KaptchaTextCreator.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/KaptchaTextCreator.java new file mode 100644 index 0000000..4516e33 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/KaptchaTextCreator.java @@ -0,0 +1,77 @@ +package com.m2pool.gateway.config; + +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +import java.util.Random; + +/** + * @Description 验证码文本生成器 + * @Date 2024/6/13 10:10 + * @Author dy + */ +public class KaptchaTextCreator extends DefaultTextCreator { + + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); + + @Override + public String getText() + { + Integer result = 0; + Random random = new Random(); + int x = random.nextInt(10); + int y = random.nextInt(10); + StringBuilder suChinese = new StringBuilder(); + int randomoperands = (int) Math.round(Math.random() * 2); + if (randomoperands == 0) + { + result = x * y; + suChinese.append(CNUMBERS[x]); + suChinese.append("*"); + suChinese.append(CNUMBERS[y]); + } + else if (randomoperands == 1) + { + if (!(x == 0) && y % x == 0) + { + result = y / x; + suChinese.append(CNUMBERS[y]); + suChinese.append("/"); + suChinese.append(CNUMBERS[x]); + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + } + else if (randomoperands == 2) + { + if (x >= y) + { + result = x - y; + suChinese.append(CNUMBERS[x]); + suChinese.append("-"); + suChinese.append(CNUMBERS[y]); + } + else + { + result = y - x; + suChinese.append(CNUMBERS[y]); + suChinese.append("-"); + suChinese.append(CNUMBERS[x]); + } + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } + +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/config/RouterFunctionConfiguration.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/RouterFunctionConfiguration.java new file mode 100644 index 0000000..89626f5 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/RouterFunctionConfiguration.java @@ -0,0 +1,32 @@ +package com.m2pool.gateway.config; + +import com.m2pool.gateway.handler.ValidateCodeHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.server.RequestPredicates; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; + +/** + * @Description 路由配置信息 + * @Date 2024/6/13 9:45 + * @Author dy + */ +@Configuration +public class RouterFunctionConfiguration { + + @Autowired + private ValidateCodeHandler validateCodeHandler; + + + @SuppressWarnings("rawtypes") + @Bean + public RouterFunction routerFunction() + { + return RouterFunctions.route( + RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), + validateCodeHandler); + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/config/SwaggerProvider.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/SwaggerProvider.java new file mode 100644 index 0000000..9de18de --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/SwaggerProvider.java @@ -0,0 +1,77 @@ +package com.m2pool.gateway.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.config.GatewayProperties; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.support.NameUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.config.ResourceHandlerRegistry; +import org.springframework.web.reactive.config.WebFluxConfigurer; +import springfox.documentation.swagger.web.SwaggerResource; +import springfox.documentation.swagger.web.SwaggerResourcesProvider; + +import java.util.ArrayList; +import java.util.List; + +/** + * 聚合系统接口 + * + * @author dy + */ +@Component +public class SwaggerProvider implements SwaggerResourcesProvider, WebFluxConfigurer +{ + /** + * Swagger2默认的url后缀 + */ + public static final String SWAGGER2URL = "/v2/api-docs"; + /** + * 网关路由 + */ + @Autowired + private RouteLocator routeLocator; + + @Autowired + private GatewayProperties gatewayProperties; + + /** + * 聚合其他服务接口 + * + * @return + */ + @Override + public List get() + { + List resourceList = new ArrayList<>(); + List routes = new ArrayList<>(); + // 获取网关中配置的route + routeLocator.getRoutes().subscribe(route -> routes.add(route.getId())); + gatewayProperties.getRoutes().stream() + .filter(routeDefinition -> routes + .contains(routeDefinition.getId())) + .forEach(routeDefinition -> routeDefinition.getPredicates().stream() + .filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName())) + .filter(predicateDefinition -> !"m2pool-auth".equalsIgnoreCase(routeDefinition.getId())) + .forEach(predicateDefinition -> resourceList + .add(swaggerResource(routeDefinition.getId(), predicateDefinition.getArgs() + .get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", SWAGGER2URL))))); + return resourceList; + } + + private SwaggerResource swaggerResource(String name, String location) + { + SwaggerResource swaggerResource = new SwaggerResource(); + swaggerResource.setName(name); + swaggerResource.setLocation(location); + swaggerResource.setSwaggerVersion("2.0"); + return swaggerResource; + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** swagger-ui 地址 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/ApiKeyProperties.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/ApiKeyProperties.java new file mode 100644 index 0000000..8d931d8 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/ApiKeyProperties.java @@ -0,0 +1,49 @@ +package com.m2pool.gateway.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +/** + * ApiKey脚本配置 + * + * @author dy + */ +@Configuration +@RefreshScope +@ConfigurationProperties(prefix = "security.apikey") +public class ApiKeyProperties +{ + /** + * ApiKey开关 + */ + private Boolean enabled; + + /** + * 排除路径 + */ + private List matchUrls = new ArrayList<>(); + + public Boolean getEnabled() + { + return enabled; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public List getMatchUrls() + { + return matchUrls; + } + + public void setMatchUrls(List matchUrls) + { + this.matchUrls = matchUrls; + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/CaptchaProperties.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/CaptchaProperties.java new file mode 100644 index 0000000..4384760 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/CaptchaProperties.java @@ -0,0 +1,46 @@ +package com.m2pool.gateway.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +/** + * @Description 验证码配置 + * @Date 2024/6/14 9:26 + * @Author dy + */ +@Configuration +@RefreshScope +@ConfigurationProperties(prefix = "security.captcha") +public class CaptchaProperties { + + /** + * 验证码开关 + */ + private Boolean enabled; + + /** + * 验证码类型(math 数组计算 char 字符) + */ + private String type; + + public Boolean getEnabled() + { + return enabled; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public String getType() + { + return type; + } + + public void setType(String type) + { + this.type = type; + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/IgnoreWhiteProperties.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/IgnoreWhiteProperties.java new file mode 100644 index 0000000..2a88858 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/IgnoreWhiteProperties.java @@ -0,0 +1,34 @@ +package com.m2pool.gateway.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Description 放行白名单 + * @Date 2024/6/12 14:24 + * @Author dy + */ +@Configuration +@RefreshScope +@ConfigurationProperties(prefix = "security.ignore") +public class IgnoreWhiteProperties +{ + /** + * 放行白名单配置,网关不校验此处的白名单 + */ + private List whites = new ArrayList<>(); + + public List getWhites() + { + return whites; + } + + public void setWhites(List whites) + { + this.whites = whites; + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/XssProperties.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/XssProperties.java new file mode 100644 index 0000000..7d84388 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/config/properties/XssProperties.java @@ -0,0 +1,49 @@ +package com.m2pool.gateway.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +/** + * XSS跨站脚本配置 + * + * @author dy + */ +@Configuration +@RefreshScope +@ConfigurationProperties(prefix = "security.xss") +public class XssProperties +{ + /** + * Xss开关 + */ + private Boolean enabled; + + /** + * 排除路径 + */ + private List excludeUrls = new ArrayList<>(); + + public Boolean getEnabled() + { + return enabled; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public List getExcludeUrls() + { + return excludeUrls; + } + + public void setExcludeUrls(List excludeUrls) + { + this.excludeUrls = excludeUrls; + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/ApiKeyFilter.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/ApiKeyFilter.java new file mode 100644 index 0000000..c8492f2 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/ApiKeyFilter.java @@ -0,0 +1,233 @@ +package com.m2pool.gateway.filter; + +import com.m2pool.common.core.constant.*; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.OpenApiJwtUtils; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.ip.IpUtils; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.gateway.config.properties.ApiKeyProperties; +import com.m2pool.system.api.model.OpenApiKeyInfo; +import io.jsonwebtoken.Claims; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; +import sun.net.util.IPAddressUtil; + +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @Description apiKey鉴权 + * @Date 2024/6/12 14:13 + * @Author dy + */ +@Component +public class ApiKeyFilter implements GlobalFilter,Ordered +{ + private static final Logger log = LoggerFactory.getLogger(ApiKeyFilter.class); + + private static final Map dayLimitCounter = new ConcurrentHashMap<>(); + private static final Map secondLimitCounter = new ConcurrentHashMap<>(); + private static final int MAX_REQUESTS_SECOND = 5; + private static final int MAX_REQUESTS_DAY = 5000; + + @Autowired + private RedisService redisService; + @Autowired + private ApiKeyProperties apiKeyProperties; + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) + { + ServerHttpRequest request = exchange.getRequest(); + ServerHttpRequest.Builder mutate = request.mutate(); + + String url = request.getURI().getPath(); + + // 跳过不需要验证的路径 只验证/oapi/** 对应的接口 + if (!StringUtils.matches(url, apiKeyProperties.getMatchUrls())) + { + return chain.filter(exchange); + } + String jwtApiKey = getApiKey(request); + System.out.println("API-KEY:"+jwtApiKey); + if (StringUtils.isEmpty(jwtApiKey)) + { + return unauthorizedResponse(exchange, "api-key不能为空"); + } + Claims claims = OpenApiJwtUtils.parseApiKey(jwtApiKey); + System.out.println("API-KEY解析的身份信息:"+claims); + if (claims == null) + { + System.out.println("claim null"); + return unauthorizedResponse(exchange, "api-key已过期或格式不正确!",HttpStatus.TOKENEXPIRES); + } + String apiKey = OpenApiJwtUtils.getApiKey(claims); + boolean isSign = redisService.hasKey(getApiKeyRedisKey(apiKey)); + if (!isSign) + { + return unauthorizedResponse(exchange, "api-key已过期",HttpStatus.LOGINEXPIRES); + } + + OpenApiKeyInfo info = redisService.getCacheObject(getApiKeyRedisKey(apiKey)); + String apiIp = info.getApiIp(); + System.out.println("绑定的ip:"+apiIp); + String username = OpenApiJwtUtils.getUserName(claims); + if (StringUtils.isEmpty(apiIp) || StringUtils.isEmpty(username)) + { + return unauthorizedResponse(exchange, "api-key验证失败"); + } + + //todo 验证ip + String ip = getIP(request); + System.out.println("当前ip:"+ip); + if(!ip.contains(apiIp)){ + return unauthorizedResponse(exchange, "当前ip与API-KEY绑定的ip不一致"); + } + //AtomicInteger counter = dayLimitCounter.get(apiKey); + //if (counter == null) { + // counter = new AtomicInteger(0); + // dayLimitCounter.put(apiKey, counter); + //} + ////判断当前分钟 + // + //if (counter.incrementAndGet() > MAX_REQUESTS_DAY) { + // return unauthorizedResponse(exchange, "Too many requests from this api-key",HttpStatus.TOO_MANY_REQUESTS); + //} + + // 设置用户信息到请求 + addHeader(mutate, OpenApiKeyConstants.API_KEY, apiKey); + addHeader(mutate, OpenApiKeyConstants.API_IP, apiIp); + addHeader(mutate, OpenApiKeyConstants.API_USER, username); + // 内部请求来源参数清除 + removeHeader(mutate, OpenApiKeyConstants.FROM_SOURCE); + System.out.println("ApiKeyFilter走到最后了"); + return chain.filter(exchange.mutate().request(mutate.build()).build()); + } + + private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) + { + if (value == null) + { + return; + } + String valueStr = value.toString(); + String valueEncode = ServletUtils.urlEncode(valueStr); + mutate.header(name, valueEncode); + } + + private void removeHeader(ServerHttpRequest.Builder mutate, String name) + { + mutate.headers(httpHeaders -> httpHeaders.remove(name)).build(); + } + + private Mono unauthorizedResponse(ServerWebExchange exchange, String msg) + { + log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED); + } + + private Mono unauthorizedResponse(ServerWebExchange exchange, String msg,int code) + { + log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, code); + } + + /** + * 获取缓存key + */ + private String getApiKeyRedisKey(String apiKey) + { + return CacheConstants.OPEN_API_KEY + apiKey; + } + + /** + * 获取请求token + */ + private String getApiKey(ServerHttpRequest request) + { + String apiKey = request.getHeaders().getFirst(TokenConstants.API_KEY); + // 如果前端设置了令牌前缀,则裁剪掉前缀 + //if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) + //{ + // token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY); + //} + return apiKey; + } + + @Override + public int getOrder() + { + return -101; + } + + // 多次反向代理后会有多个ip值 的分割符 + private final static String IP_UTILS_FLAG = ","; + // 未知IP + private final static String UNKNOWN = "unknown"; + // 本地 IP + private final static String LOCALHOST_IP = "0:0:0:0:0:0:0:1"; + private final static String LOCALHOST_IP1 = "127.0.0.1"; + + private static String getIP(ServerHttpRequest request){ + // 根据 HttpHeaders 获取 请求 IP地址 + String ip = request.getHeaders().getFirst("X-Forwarded-For"); + if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("x-forwarded-for"); + if (ip != null && ip.length() != 0 && !UNKNOWN.equalsIgnoreCase(ip)) { + // 多次反向代理后会有多个ip值,第一个ip才是真实ip + if (ip.contains(IP_UTILS_FLAG)) { + ip = ip.split(IP_UTILS_FLAG)[0]; + } + } + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("HTTP_CLIENT_IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("HTTP_X_FORWARDED_FOR"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("X-Real-IP"); + } + //兼容k8s集群获取ip + if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddress().getAddress().getHostAddress(); + if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) { + //根据网卡取本机配置的IP + InetAddress iNet = null; + try { + iNet = InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + log.error("getClientIp error: ", e); + } + ip = iNet.getHostAddress(); + } + } + if(StringUtils.isNull(ip)){ + ip=""; + } + return ip; + } + +} + diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/AuthFilter.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/AuthFilter.java new file mode 100644 index 0000000..d030eea --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/AuthFilter.java @@ -0,0 +1,147 @@ +package com.m2pool.gateway.filter; + +import com.m2pool.common.core.constant.CacheConstants; +import com.m2pool.common.core.constant.HttpStatus; +import com.m2pool.common.core.constant.SecurityConstants; +import com.m2pool.common.core.constant.TokenConstants; +import com.m2pool.common.core.utils.JwtUtils; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.gateway.config.properties.IgnoreWhiteProperties; +import io.jsonwebtoken.Claims; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * @Description 网关鉴权 + * @Date 2024/6/12 14:13 + * @Author dy + */ +@Component +public class AuthFilter implements GlobalFilter, Ordered +{ + private static final Logger log = LoggerFactory.getLogger(AuthFilter.class); + + // 排除过滤的 uri 地址,nacos自行添加 + @Autowired + private IgnoreWhiteProperties ignoreWhite; + + @Autowired + private RedisService redisService; + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) + { + ServerHttpRequest request = exchange.getRequest(); + ServerHttpRequest.Builder mutate = request.mutate(); + + String url = request.getURI().getPath(); + // 跳过不需要验证的路径 + if (StringUtils.matches(url, ignoreWhite.getWhites())) + { + System.out.println("当前路径在白名单,路径:"+url); + return chain.filter(exchange); + } + String token = getToken(request); + System.out.println("token:"+token); + if (StringUtils.isEmpty(token)) + { + return unauthorizedResponse(exchange, "令牌不能为空"); + } + Claims claims = JwtUtils.parseToken(token); + System.out.println("身份信息:"+claims); + if (claims == null) + { + return unauthorizedResponse(exchange, "令牌已过期或验证不正确!",HttpStatus.TOKENEXPIRES); + } + String userkey = JwtUtils.getUserKey(claims); + System.out.println("userKey"+userkey); + boolean islogin = redisService.hasKey(getTokenKey(userkey)); + if (!islogin) + { + return unauthorizedResponse(exchange, "登录状态已过期",HttpStatus.LOGINEXPIRES); + } + String userid = JwtUtils.getUserId(claims); + String username = JwtUtils.getUserName(claims); + if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) + { + return unauthorizedResponse(exchange, "令牌验证失败"); + } + + // 设置用户信息到请求 + addHeader(mutate, SecurityConstants.USER_KEY, userkey); + addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid); + addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username); + // 内部请求来源参数清除 + removeHeader(mutate, SecurityConstants.FROM_SOURCE); + return chain.filter(exchange.mutate().request(mutate.build()).build()); + } + + private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) + { + if (value == null) + { + return; + } + String valueStr = value.toString(); + String valueEncode = ServletUtils.urlEncode(valueStr); + mutate.header(name, valueEncode); + } + + private void removeHeader(ServerHttpRequest.Builder mutate, String name) + { + mutate.headers(httpHeaders -> httpHeaders.remove(name)).build(); + } + + private Mono unauthorizedResponse(ServerWebExchange exchange, String msg) + { + log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED); + } + + private Mono unauthorizedResponse(ServerWebExchange exchange, String msg,int code) + { + System.out.println("111 [鉴权异常处理]请求路径:"+exchange.getRequest().getPath()); + log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); + System.out.println("222 [鉴权异常处理]请求路径:"+exchange.getRequest().getPath()); + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, code); + } + + /** + * 获取缓存key + */ + private String getTokenKey(String token) + { + return CacheConstants.LOGIN_TOKEN_KEY + token; + } + + /** + * 获取请求token + */ + private String getToken(ServerHttpRequest request) + { + String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION); + // 如果前端设置了令牌前缀,则裁剪掉前缀 + if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) + { + token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY); + } + return token; + } + + @Override + public int getOrder() + { + return -200; + } +} + diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/CacheRequestFilter.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/CacheRequestFilter.java new file mode 100644 index 0000000..9c80524 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/CacheRequestFilter.java @@ -0,0 +1,107 @@ +package com.m2pool.gateway.filter; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collections; +import java.util.List; + +/** + * @Description 获取body请求数据(解决流不能重复读取问题) + * @Date 2024/6/14 10:31 + * @Author dy + */ +@Component +public class CacheRequestFilter extends AbstractGatewayFilterFactory { + + static class Config + { + private Integer order; + + public Integer getOrder() + { + return order; + } + + public void setOrder(Integer order) + { + this.order = order; + } + } + + public CacheRequestFilter() + { + super(Config.class); + } + + @Override + public String name() + { + return "CacheRequestFilter"; + } + + @Override + public GatewayFilter apply(Config config) + { + CacheRequestGatewayFilter cacheRequestGatewayFilter = new CacheRequestGatewayFilter(); + Integer order = config.getOrder(); + if (order == null) + { + return cacheRequestGatewayFilter; + } + return new OrderedGatewayFilter(cacheRequestGatewayFilter, order); + } + + public static class CacheRequestGatewayFilter implements GatewayFilter + { + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) + { + // GET DELETE 不过滤 + HttpMethod method = exchange.getRequest().getMethod(); + if (method == null || method.matches("GET") || method.matches("DELETE")) + { + return chain.filter(exchange); + } + return DataBufferUtils.join(exchange.getRequest().getBody()).map(dataBuffer -> { + byte[] bytes = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(bytes); + DataBufferUtils.release(dataBuffer); + return bytes; + }).defaultIfEmpty(new byte[0]).flatMap(bytes -> { + DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory(); + ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) + { + @Override + public Flux getBody() + { + if (bytes.length > 0) + { + return Flux.just(dataBufferFactory.wrap(bytes)); + } + return Flux.empty(); + } + }; + return chain.filter(exchange.mutate().request(decorator).build()); + }); + } + } + + @Override + public List shortcutFieldOrder() + { + return Collections.singletonList("order"); + } + +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/ValidateCodeFilter.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/ValidateCodeFilter.java new file mode 100644 index 0000000..b0c9d56 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/ValidateCodeFilter.java @@ -0,0 +1,84 @@ +package com.m2pool.gateway.filter; + +import com.alibaba.fastjson.JSONObject; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +//import com.m2pool.gateway.com.m2pool.common.swagger.config.properties.CaptchaProperties; +//import com.m2pool.gateway.com.m2pool.chat.service.ValidateCodeService; +import com.m2pool.gateway.config.properties.CaptchaProperties; +import com.m2pool.gateway.service.ValidateCodeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; + +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @Description 验证码过滤器 + * @Date 2024/6/13 10:45 + * @Author dy + */ +@Component +public class ValidateCodeFilter extends AbstractGatewayFilterFactory { + + //private final static String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" }; + + private final static String[] VALIDATE_URL = new String[] {};//邮箱使用邮件发送验证 + + @Autowired + private ValidateCodeService validateCodeService; + + @Autowired + private CaptchaProperties captchaProperties; + + private static final String CODE = "code"; + + private static final String UUID = "uuid"; + + @Override + public GatewayFilter apply(Object config) + { + return (exchange, chain) -> { + ServerHttpRequest request = exchange.getRequest(); + + // 非登录/注册请求或验证码关闭,不处理 + if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) + { + //System.out.println(request.getURI().getPath()); + return chain.filter(exchange); + } + + try + { + String rspStr = resolveBodyFromRequest(request); + JSONObject obj = JSONObject.parseObject(rspStr); + validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID)); + } + catch (Exception e) + { + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage()); + } + return chain.filter(exchange); + }; + } + + private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) + { + // 获取请求体 + Flux body = serverHttpRequest.getBody(); + AtomicReference bodyRef = new AtomicReference<>(); + body.subscribe(buffer -> { + CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); + DataBufferUtils.release(buffer); + bodyRef.set(charBuffer.toString()); + }); + return bodyRef.get(); + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/XssFilter.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/XssFilter.java new file mode 100644 index 0000000..047c55f --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/filter/XssFilter.java @@ -0,0 +1,121 @@ +package com.m2pool.gateway.filter; + +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.html.EscapeUtil; +import com.m2pool.gateway.config.properties.XssProperties; +import io.netty.buffer.ByteBufAllocator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.*; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; + +/** + * 跨站脚本过滤器 + * + * @author dy + */ +@Component +@ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true") +public class XssFilter implements GlobalFilter, Ordered +{ + // 跨站脚本的 xss 配置,nacos自行添加 + @Autowired + private XssProperties xss; + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) + { + ServerHttpRequest request = exchange.getRequest(); + // GET DELETE 不过滤 + HttpMethod method = request.getMethod(); + if (method == null || method.matches("GET") || method.matches("DELETE")) + { + return chain.filter(exchange); + } + // 非json类型,不过滤 + if (!isJsonRequest(exchange)) + { + return chain.filter(exchange); + } + // excludeUrls 不过滤 + String url = request.getURI().getPath(); + if (StringUtils.matches(url, xss.getExcludeUrls())) + { + return chain.filter(exchange); + } + ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange); + return chain.filter(exchange.mutate().request(httpRequestDecorator).build()); + + } + + private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange) + { + ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) + { + @Override + public Flux getBody() + { + Flux body = super.getBody(); + return body.buffer().map(dataBuffers -> { + DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); + DataBuffer join = dataBufferFactory.join(dataBuffers); + byte[] content = new byte[join.readableByteCount()]; + join.read(content); + DataBufferUtils.release(join); + String bodyStr = new String(content, StandardCharsets.UTF_8); + // 防xss攻击过滤 + bodyStr = EscapeUtil.clean(bodyStr); + // 转成字节 + byte[] bytes = bodyStr.getBytes(); + NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); + DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); + buffer.write(bytes); + return buffer; + }); + } + + @Override + public HttpHeaders getHeaders() + { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.putAll(super.getHeaders()); + // 由于修改了请求体的body,导致content-length长度不确定,因此需要删除原先的content-length + httpHeaders.remove(HttpHeaders.CONTENT_LENGTH); + httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); + return httpHeaders; + } + + }; + return serverHttpRequestDecorator; + } + + /** + * 是否是Json请求 + * + * @param exchange HTTP请求 + */ + public boolean isJsonRequest(ServerWebExchange exchange) + { + String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } + + @Override + public int getOrder() + { + return -100; + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/GatewayExceptionHandler.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/GatewayExceptionHandler.java new file mode 100644 index 0000000..ab01178 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/GatewayExceptionHandler.java @@ -0,0 +1,51 @@ +package com.m2pool.gateway.handler; + +import com.m2pool.common.core.utils.ServletUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; +import org.springframework.cloud.gateway.support.NotFoundException; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * @Description 网关统一异常处理 + * @Date 2024/6/14 10:09 + * @Author dy + */ +public class GatewayExceptionHandler implements ErrorWebExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class); + + @Override + public Mono handle(ServerWebExchange exchange, Throwable ex) { + ServerHttpResponse response = exchange.getResponse(); + + if (exchange.getResponse().isCommitted()) + { + return Mono.error(ex); + } + + String msg; + + if (ex instanceof NotFoundException) + { + msg = "服务未找到"; + } + else if (ex instanceof ResponseStatusException) + { + ResponseStatusException responseStatusException = (ResponseStatusException) ex; + msg = responseStatusException.getMessage(); + } + else + { + msg = "内部服务器错误"; + } + + log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage()); + + return ServletUtils.webFluxResponseWriter(response, msg); + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/SentinelFallbackHandler.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/SentinelFallbackHandler.java new file mode 100644 index 0000000..63c50db --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/SentinelFallbackHandler.java @@ -0,0 +1,48 @@ +package com.m2pool.gateway.handler; + +import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.m2pool.common.core.utils.ServletUtils; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebExceptionHandler; +import reactor.core.publisher.Mono; + +/** + * @Description 自定义限流异常处理 + * @Date 2024/6/14 10:07 + * @Author dy + */ +public class SentinelFallbackHandler implements WebExceptionHandler { + + private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) + { + + ServerHttpResponse serverHttpResponse = exchange.getResponse(); + serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); + + + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试"); + } + + @Override + public Mono handle(ServerWebExchange exchange, Throwable ex) { + if (exchange.getResponse().isCommitted()) + { + return Mono.error(ex); + } + if (!BlockException.isBlockException(ex)) + { + return Mono.error(ex); + } + return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange)); + + } + + private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) + { + return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); + } + +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/SwaggerHandler.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/SwaggerHandler.java new file mode 100644 index 0000000..6001499 --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/SwaggerHandler.java @@ -0,0 +1,53 @@ +package com.m2pool.gateway.handler; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; +import springfox.documentation.swagger.web.*; + +import java.util.Optional; + +@RestController +@RequestMapping("/swagger-resources") +public class SwaggerHandler +{ + @Autowired(required = false) + private SecurityConfiguration securityConfiguration; + + @Autowired(required = false) + private UiConfiguration uiConfiguration; + + private final SwaggerResourcesProvider swaggerResources; + + @Autowired + public SwaggerHandler(SwaggerResourcesProvider swaggerResources) + { + this.swaggerResources = swaggerResources; + } + + @GetMapping("/configuration/security") + public Mono> securityConfiguration() + { + return Mono.just(new ResponseEntity<>( + Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), + HttpStatus.OK)); + } + + @GetMapping("/configuration/ui") + public Mono> uiConfiguration() + { + return Mono.just(new ResponseEntity<>( + Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK)); + } + + @SuppressWarnings("rawtypes") + @GetMapping("") + public Mono swaggerResources() + { + return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK))); + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/ValidateCodeHandler.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/ValidateCodeHandler.java new file mode 100644 index 0000000..938f88a --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/handler/ValidateCodeHandler.java @@ -0,0 +1,41 @@ +package com.m2pool.gateway.handler; + +import com.m2pool.common.core.exception.CaptchaException; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.gateway.service.ValidateCodeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.server.HandlerFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +import java.io.IOException; + +/** + * @Description 验证码获取 + * @Date 2024/6/13 10:22 + * @Author dy + */ +@Component +public class ValidateCodeHandler implements HandlerFunction { + + @Autowired + private ValidateCodeService validateCodeService; + + @Override + public Mono handle(ServerRequest request) { + AjaxResult ajax; + try + { + ajax = validateCodeService.createCaptcha(); + } + catch (CaptchaException | IOException e) + { + return Mono.error(e); + } + return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(ajax)); + } +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/service/ValidateCodeService.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/service/ValidateCodeService.java new file mode 100644 index 0000000..8644e9f --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/service/ValidateCodeService.java @@ -0,0 +1,25 @@ +package com.m2pool.gateway.service; + +import com.m2pool.common.core.exception.CaptchaException; +import com.m2pool.common.core.web.Result.AjaxResult; + +import java.io.IOException; + +/** + * @Description 验证码处理 + * @Date 2024/6/13 9:16 + * @Author dy + */ +public interface ValidateCodeService { + + /** + * 生成验证码 + */ + public AjaxResult createCaptcha() throws IOException, CaptchaException; + + /** + * 校验验证码 + */ + public void checkCaptcha(String key, String value) throws CaptchaException; + +} diff --git a/m2pool-gateway/src/main/java/com/m2pool/gateway/service/impl/ValidateCodeServiceImpl.java b/m2pool-gateway/src/main/java/com/m2pool/gateway/service/impl/ValidateCodeServiceImpl.java new file mode 100644 index 0000000..c9b765c --- /dev/null +++ b/m2pool-gateway/src/main/java/com/m2pool/gateway/service/impl/ValidateCodeServiceImpl.java @@ -0,0 +1,121 @@ +package com.m2pool.gateway.service.impl; + + +import com.google.code.kaptcha.Producer; +import com.m2pool.common.core.constant.Constants; +import com.m2pool.common.core.exception.CaptchaException; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.sign.Base64; +import com.m2pool.common.core.utils.uuid.IdUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.gateway.config.properties.CaptchaProperties; +import com.m2pool.gateway.service.ValidateCodeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.FastByteArrayOutputStream; + +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * @Description 验证码业务处理 + * @Date 2024/6/13 9:17 + * @Author dy + */ +@Service +public class ValidateCodeServiceImpl implements ValidateCodeService { + + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private RedisService redisService; + + @Autowired + private CaptchaProperties captchaProperties; + + + /** + * 生成验证码 + */ + @Override + public AjaxResult createCaptcha() throws IOException, CaptchaException { + + AjaxResult ajax = AjaxResult.success(); + + boolean captchaOnOff = captchaProperties.getEnabled(); + + ajax.put("captchaOnOff", captchaOnOff); + + if (!captchaOnOff) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; + + String capStr = null, code = null; + BufferedImage image = null; + + String captchaType = captchaProperties.getType(); + // 生成验证码 + if ("math".equals(captchaType)) + { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } + else if ("char".equals(captchaType)) + { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + + redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try + { + ImageIO.write(image, "jpg", os); + } + catch (IOException e) + { + return AjaxResult.error(e.getMessage()); + } + + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } + + @Override + public void checkCaptcha(String code, String uuid) throws CaptchaException { + + if (StringUtils.isEmpty(code)) + { + throw new CaptchaException("验证码不能为空"); + } + if (StringUtils.isEmpty(uuid)) + { + throw new CaptchaException("验证码已失效"); + } + String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; + String captcha = redisService.getCacheObject(verifyKey); + redisService.deleteObject(verifyKey); + + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException("验证码错误"); + } + } +} diff --git a/m2pool-gateway/src/main/resources/bootstrap.yml b/m2pool-gateway/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..32ba790 --- /dev/null +++ b/m2pool-gateway/src/main/resources/bootstrap.yml @@ -0,0 +1,100 @@ +# Tomcat +server: + port: 8201 +# Spring +spring: + application: + # 应用名称 + name: m2pool-gateway + profiles: + # 环境配置 + active: prod + main: + allow-circular-references: true + allow-bean-definition-overriding: true + + cloud: + nacos: + discovery: + server-addr: 127.0.0.1:8808 + namespace: m2_prod + group: m2_prod_group + config: + # 配置中心地址 + server-addr: 127.0.0.1:8808 + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} + namespace: m2_prod + group: m2_prod_group + sentinel: + # 取消控制台懒加载 + eager: true + transport: + # 控制台地址 + dashboard: 127.0.0.1:8718 + # nacos配置持久化 + datasource: + ds1: + nacos: + server-addr: 127.0.0.1:8808 + dataId: sentinel-m2pool-gateway + groupId: m2_prod_group + data-type: json + rule-type: flow + servlet: + multipart: + max-file-size: 2MB + max-request-size: 8MB + +#server: +# port: 8101 +## Spring +#spring: +# application: +# # 应用名称 +# name: m2pool-gateway +# profiles: +# # 环境配置 +# active: test +# main: +# allow-circular-references: true +# allow-bean-definition-overriding: true +# +# cloud: +# nacos: +# discovery: +# server-addr: 127.0.0.1:8808 +# namespace: m2_test +# group: m2_test_group +# config: +# # 配置中心地址 +# server-addr: 127.0.0.1:8808 +# # 配置文件格式 +# file-extension: yml +# # 共享配置 +# shared-configs: +# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} +# namespace: m2_test +# group: m2_test_group +# sentinel: +# # 取消控制台懒加载 +# eager: true +# transport: +# # 控制台地址 +# dashboard: 127.0.0.1:8718 +# # nacos配置持久化 +# datasource: +# ds1: +# nacos: +# server-addr: 127.0.0.1:8808 +# dataId: sentinel-m2pool-gateway +# groupId: m2_test_group +# data-type: json +# rule-type: flow +# servlet: +# multipart: +# max-file-size: 2MB +# max-request-size: 8MB diff --git a/m2pool-gateway/src/main/resources/logback.xml b/m2pool-gateway/src/main/resources/logback.xml new file mode 100644 index 0000000..836eaf2 --- /dev/null +++ b/m2pool-gateway/src/main/resources/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-gateway/src/main/resources/nginx-selfsigned.pfx b/m2pool-gateway/src/main/resources/nginx-selfsigned.pfx new file mode 100644 index 0000000..ddd4326 Binary files /dev/null and b/m2pool-gateway/src/main/resources/nginx-selfsigned.pfx differ diff --git a/m2pool-modules/m2pool-chat/pom.xml b/m2pool-modules/m2pool-chat/pom.xml new file mode 100644 index 0000000..2dfdc63 --- /dev/null +++ b/m2pool-modules/m2pool-chat/pom.xml @@ -0,0 +1,213 @@ + + + + + m2pool-modules + com.m2pool + 3.5.0 + + 4.0.0 + + m2pool-chat + + + + + + + + + + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + org.springframework.boot + spring-boot-starter-web + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + io.springfox + springfox-swagger-ui + ${swagger.fox.version} + + + + + mysql + mysql-connector-java + + + + + org.projectlombok + lombok-maven-plugin + provided + + + + + + + + + + + + + + + + + + + + + com.m2pool + common-datasource + + + + + com.m2pool + common-security + + + + + com.m2pool + common-log + + + + com.m2pool + common-swagger + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + org.java-websocket + Java-WebSocket + 1.3.5 + + + + cn.hutool + hutool-all + 5.6.2 + + + + org.mybatis + mybatis + 3.5.7 + compile + + + + + de.taimos + totp + 1.0 + + + + commons-codec + commons-codec + 1.10 + + + + com.google.zxing + javase + 3.3.0 + + + + + + + + + dev + + dev + 127.0.0.1:8808 + + + + true + + + + + test + + test + 127.0.0.1:8808 + + + false + + + + + prod + + prod + 127.0.0.1:8808 + + + false + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + diff --git a/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/M2ChatApplication.java b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/M2ChatApplication.java new file mode 100644 index 0000000..02eed4f --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/M2ChatApplication.java @@ -0,0 +1,34 @@ +package com.m2pool.chat; + +import com.m2pool.common.security.annotation.EnableCustomConfig; +import com.m2pool.common.security.annotation.EnableM2PoolFeignClients; +import com.m2pool.common.swagger.annotation.EnableCustomSwagger2; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +/** + * @Description 在线客服服务入口类 + * @Date 2025/2/25 10:42 + * @Author dy + */ +@EnableCustomConfig +@EnableCustomSwagger2 +@EnableM2PoolFeignClients +@SpringBootApplication +@MapperScan({"com.m2pool.chat.mapper"}) +public class M2ChatApplication { + + @Bean + public RestTemplate restTemplate(){ + return new RestTemplate(); + } + + public static void main(String[] args) { + SpringApplication.run(M2ChatApplication.class,args); + System.out.println("m2pool矿池 开放api微服务启动成功"); + } + +} diff --git a/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/config/WebSocketConfig.java b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/config/WebSocketConfig.java new file mode 100644 index 0000000..a9918b4 --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/config/WebSocketConfig.java @@ -0,0 +1,21 @@ +package com.m2pool.chat.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +import javax.websocket.server.ServerEndpointConfig; + +/** + * @Description TODO + * @Date 2025/2/25 14:43 + * @Author 杜懿 + */ +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter(){ + return new ServerEndpointExporter(); + } +} diff --git a/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/controller/ChatController.java b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/controller/ChatController.java new file mode 100644 index 0000000..5d031cb --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/controller/ChatController.java @@ -0,0 +1,45 @@ +package com.m2pool.chat.controller; + +import com.m2pool.chat.entity.ChatMessage; +import com.m2pool.chat.entity.ChatMsg; +import com.m2pool.chat.service.ChatService; +import com.m2pool.chat.service.WebSocketServer; +import com.m2pool.chat.vo.ChatHistoryVo; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.system.api.entity.SysUser; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.List; + +/** + * @Description TODO + * @Date 2025/2/24 15:05 + * @Author 杜懿 + */ +@RestController +@RequestMapping("/chat") +public class ChatController extends BaseController { + + @Resource + private ChatService chatService; + + //@Resource + // //private WebSocketServer webSocketServer; + + @GetMapping("/history") + public AjaxResult getChatHistory(@RequestBody ChatHistoryVo vo) { + + if(StringUtils.isNull(vo)){ + return AjaxResult.error("请求参数为空"); + } + + String identifier = StringUtils.isNotBlank(vo.getEmail()) ? vo.getEmail() : vo.getBrowserId(); + return chatService.getHistory(identifier); + } + +} diff --git a/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/entity/ChatMessage.java b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/entity/ChatMessage.java new file mode 100644 index 0000000..3c7f254 --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/entity/ChatMessage.java @@ -0,0 +1,22 @@ +package com.m2pool.chat.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2025/3/7 15:53 + * @Author 杜懿 + */ +@Data +public class ChatMessage { + private Long id; + private String senderType; // USER 0/CUSTOMER_SERVICE 1 + private String senderId; // 邮箱或客服ID + private String receiverId; + private String content; + private String userIdentifier; // 邮箱或浏览器指纹 + private String browserFingerprint; + private Date createdTime; +} diff --git a/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/entity/ChatMsg.java b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/entity/ChatMsg.java new file mode 100644 index 0000000..ef5661b --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/entity/ChatMsg.java @@ -0,0 +1,60 @@ +package com.m2pool.chat.entity; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Transient; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2025/2/28 18:11 + * @Author 杜懿 + */ +@Data +public class ChatMsg { + @Id + private String id; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy_MM_dd_HH_mm_ss") + private Date createTime=new Date(); + + /** + * 通信key(sendId_receiveId) + */ + private String msgKey; + + /** + * 通信消息 + */ + private String chatMsg; + + /** + * 发送id + */ + private String sendId; + + /** + * 接收id + */ + private String receiveId; + + /** + * 查看状态 0未看 1已看 + */ + private String readState; + + /** + *1为我 0 为他 + */ + @Transient + private Integer flag; + + @Transient + private String name; + +} diff --git a/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/mapper/ChatMapper.java b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/mapper/ChatMapper.java new file mode 100644 index 0000000..7f56ada --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/mapper/ChatMapper.java @@ -0,0 +1,20 @@ +package com.m2pool.chat.mapper; + +import com.m2pool.chat.entity.ChatMessage; +import com.m2pool.common.datasource.annotation.Master; +import org.apache.ibatis.annotations.Param; + +import javax.validation.constraints.Pattern; +import java.util.List; + +/** + * @Description 角色数据层 + * @Date 2024/6/12 17:37 + * @Author dy + */ +@Master +public interface ChatMapper { + + public boolean insert(@Param("vo") ChatMessage vo); + +} diff --git a/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/service/ChatService.java b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/service/ChatService.java new file mode 100644 index 0000000..cefc38d --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/service/ChatService.java @@ -0,0 +1,30 @@ +package com.m2pool.chat.service; + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.system.api.entity.SysUser; +import org.springframework.cache.CacheManager; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @Description TODO + * @Date 2025/2/28 18:06 + * @Author 杜懿 + */ +public interface ChatService { + + //public AjaxResult userList(UserItem userItem, SysUser data); + + public AjaxResult getHistory(String identifier); + +} diff --git a/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/service/WebSocketServer.java b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/service/WebSocketServer.java new file mode 100644 index 0000000..673edad --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/service/WebSocketServer.java @@ -0,0 +1,169 @@ +package com.m2pool.chat.service; + +import com.alibaba.fastjson.JSONObject; +import com.m2pool.chat.entity.ChatMsg; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @Description TODO + * @Date 2025/2/28 18:02 + * @Author 杜懿 + */ +@ServerEndpoint(value = "/ws/asset") +@Component +public class WebSocketServer { + + private static ChatService chatService; + @Autowired + public void setChatService(ChatService chatService) { + WebSocketServer.chatService = chatService; + } + + @PostConstruct + public void init() { + System.out.println("websocket 加载"); + } + private static Logger log = LoggerFactory.getLogger(WebSocketServer.class); + private static final AtomicInteger OnlineCount = new AtomicInteger(0); + // concurrent包的线程安全Set,用来存放每个客户端对应的Session对象。 + private static CopyOnWriteArraySet SessionSet = new CopyOnWriteArraySet(); + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session) { + SessionSet.add(session); + int cnt = OnlineCount.incrementAndGet(); + log.info("有连接加入,当前连接数为:{}", cnt); + SendMessage(session, "连接成功"); + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose(Session session) { + SessionSet.remove(session); + int cnt = OnlineCount.decrementAndGet(); + log.info("有连接关闭,当前连接数为:{}", cnt); + } + + /** + * 收到客户端消息后调用的方法 + * @param message 客户端发送过来的消息 + */ + @OnMessage + public void onMessage(String message, Session session) { + log.info("来自客户端的消息:{}",message); + try{ + JSONObject jsonObject = JSONObject.parseObject(message); + String linkType = jsonObject.getString("linkType"); + String sendId = jsonObject.getString("sendId"); + if (linkType.equals("bind")){ + //CacheManager.set("bind_"+sendId,session); + SendMessage(session, "连接成功"); + }else if (linkType.equals("msg")){ + //聊天 + ChatMsg msg = new ChatMsg(); + //发消息 + String chatMsg = jsonObject.getString("chatMsg"); + String receiveId = jsonObject.getString("receiveId"); + msg.setChatMsg(chatMsg); + msg.setSendId(sendId); + msg.setMsgKey(sendId+"_"+receiveId); + msg.setReceiveId(receiveId); + msg.setReadState("0"); + //chatService.sendOne(msg); + } + }catch (Exception e){ + e.printStackTrace(); + } + } + + /** + * 出现错误 + * @param session + * @param error + */ + @OnError + public void onError(Session session, Throwable error) { + log.error("发生错误:{},Session ID: {}",error.getMessage(),session.getId()); + error.printStackTrace(); + } + + /** + * 发送消息,实践表明,每次浏览器刷新,session会发生变化。 + * @param session + * @param message + */ + public static void SendMessage(Session session, String message) { + try { + //session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId())); + log.info("sessionID="+session.getId()); + session.getBasicRemote().sendText(message); + } catch (IOException e) { + log.error("发送消息出错:{}", e.getMessage()); + e.printStackTrace(); + } + } + + + /** + * 指定Session发送消息 + * @param sessionId + * @param message + * @throws IOException + */ + public static void SendMessageById(String message,String sessionId) throws IOException { + Session session = null; + for (Session s : SessionSet) { + if(s.getId().equals(sessionId)){ + session = s; + break; + } + } + if(session!=null){ + SendMessage(session, message); + } + else{ + log.warn("没有找到你指定ID的会话:{}",sessionId); + } + } + /** + * @description: 指定发送 + * @author: lvyq + * @date: 2021/9/24 11:30 + * @version 1.0 + */ + public static void SendMessageByRecId(String message,String receiveId) throws IOException { + //Session session= CacheManager.get("bind_"+receiveId); + Session session= null; + String sessionId = ""; + if (session!=null){ + sessionId=session.getId(); + } + for (Session s : SessionSet) { + if(s.getId().equals(sessionId)){ + session = s; + break; + } + } + if(session!=null){ + SendMessage(session, message); + } + else{ + System.out.println("没有找到你指定ID的会话:"+sessionId); + } + } +} diff --git a/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/service/impl/ChatServiceImpl.java b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/service/impl/ChatServiceImpl.java new file mode 100644 index 0000000..dccd4a7 --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/service/impl/ChatServiceImpl.java @@ -0,0 +1,37 @@ +package com.m2pool.chat.service.impl; + +import com.m2pool.chat.entity.ChatMessage; +import com.m2pool.chat.mapper.ChatMapper; +import com.m2pool.chat.service.ChatService; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.security.utils.SecurityUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + + +/** + * @Description TODO + * @Date 2025/2/28 18:08 + * @Author 杜懿 + */ +@Service +public class ChatServiceImpl implements ChatService { + + @Autowired + private ChatMapper chatMapper; + + @Async("messageExecutor") + public void saveMessageAsync(ChatMessage message) { + // 批量插入优化 + chatMapper.insert(message); + } + + @Override + public AjaxResult getHistory(String identifier) { + + return null; + } + + +} diff --git a/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/vo/ChatHistoryVo.java b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/vo/ChatHistoryVo.java new file mode 100644 index 0000000..df10153 --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/java/com/m2pool/chat/vo/ChatHistoryVo.java @@ -0,0 +1,14 @@ +package com.m2pool.chat.vo; + +import lombok.Data; + +/** + * @Description TODO + * @Date 2025/3/7 15:55 + * @Author 杜懿 + */ +@Data +public class ChatHistoryVo { + private String email; + private String browserId; +} diff --git a/m2pool-modules/m2pool-chat/src/main/resources/bootstrap.yml b/m2pool-modules/m2pool-chat/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..8233eb9 --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/resources/bootstrap.yml @@ -0,0 +1,156 @@ +server: + port: 9209 + compression: + enabled: true + mime-types: application/json + +spring: + #邮箱基本配置 + mail: + # 配置在limit_time内,用户可以发送limit次验证码 + limit: 2 这个是我额外的配置,结合邮箱服务用的 + limitTime: 10 这个是我额外的配置 + #配置smtp服务主机地址 + # sina smtp.sina.cn + # aliyun smtp.aliyun.com + # 163 smtp.163.com 端口号465或994 + host: mail.privateemail.com + #发送者邮箱 + username: support@m2pool.cc + #配置密码,注意不是真正的密码,而是刚刚申请到的授权码 + # password: + # password: m2pool2024@! + # password: axvm-zfgx-cgcg-qhhu + password: m2pool2024@! + #端口号 + port: 587 + #默认的邮件编码为UTF-8 + default-encoding: UTF-8 + #其他参数 + properties: + mail: + #配置SSL 加密工厂 + smtp: + ssl: + #本地测试,先放开ssl + enable: false + required: false + #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误 + debug: false + socketFactory: + class: javax.net.ssl.SSLSocketFactory + + application: + # 应用名称 + name: m2pool-chat + profiles: + # 环境配置 + active: prod + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: 127.0.0.1:8808 + namespace: m2_prod + group: m2_prod_group + # server-addr: 127.0.0.1:8808 + config: + # 配置中心地址 + server-addr: 127.0.0.1:8808 + # server-addr: 127.0.0.1:8808 + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} + namespace: m2_prod + group: m2_prod_group + servlet: + multipart: + max-file-size: 2MB + max-request-size: 8MB + +myenv: + domain: https://www.m2pool.com + path: /var/www/html/web + img: /img + + +#测试环境 +#server: +# port: 9509 +# compression: +# enabled: true +# mime-types: application/json +# +#spring: +# #邮箱基本配置 +# mail: +# # 配置在limit_time内,用户可以发送limit次验证码 +# limit: 2 这个是我额外的配置,结合邮箱服务用的 +# limitTime: 10 这个是我额外的配置 +# #配置smtp服务主机地址 +# # sina smtp.sina.cn +# # aliyun smtp.aliyun.com +# # 163 smtp.163.com 端口号465或994 +# host: mail.privateemail.com +# #发送者邮箱 +# username: support@m2pool.cc +# #配置密码,注意不是真正的密码,而是刚刚申请到的授权码 +# # password: +# # password: m2pool2024@! +# # password: axvm-zfgx-cgcg-qhhu +# password: m2pool2024@! +# #端口号 +# port: 587 +# #默认的邮件编码为UTF-8 +# default-encoding: UTF-8 +# #其他参数 +# properties: +# mail: +# #配置SSL 加密工厂 +# smtp: +# ssl: +# #本地测试,先放开ssl +# enable: false +# required: false +# #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误 +# debug: false +# socketFactory: +# class: javax.net.ssl.SSLSocketFactory +# +# application: +# # 应用名称 +# name: m2pool-chat +# profiles: +# # 环境配置 +# active: test +# cloud: +# nacos: +# discovery: +# # 服务注册地址 +# server-addr: 127.0.0.1:8808 +# namespace: m2_test +# group: m2_test_group +# # server-addr: 127.0.0.1:8808 +# config: +# # 配置中心地址 +# server-addr: 127.0.0.1:8808 +# # server-addr: 127.0.0.1:8808 +# # 配置文件格式 +# file-extension: yml +# # 共享配置 +# shared-configs: +# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} +# namespace: m2_test +# group: m2_test_group +# servlet: +# multipart: +# max-file-size: 2MB +# max-request-size: 8MB +# +#myenv: +# domain: https://test.m2pool.com +# path: /var/www/html/web_test +# img: /imgpp +# diff --git a/m2pool-modules/m2pool-chat/src/main/resources/logback.xml b/m2pool-modules/m2pool-chat/src/main/resources/logback.xml new file mode 100644 index 0000000..c6511cc --- /dev/null +++ b/m2pool-modules/m2pool-chat/src/main/resources/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 30 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 30 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-file/pom.xml b/m2pool-modules/m2pool-file/pom.xml new file mode 100644 index 0000000..c299468 --- /dev/null +++ b/m2pool-modules/m2pool-file/pom.xml @@ -0,0 +1,83 @@ + + + + com.m2pool + m2pool-modules + 3.5.0 + + 4.0.0 + + m2pool-file + + + m2pool-file文件服务 + + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + com.github.tobato + fastdfs-client + + + + + com.m2pool + api-system + + + + + com.m2pool + common-swagger + + + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + diff --git a/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/M2PoolFileApplication.java b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/M2PoolFileApplication.java new file mode 100644 index 0000000..0f63649 --- /dev/null +++ b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/M2PoolFileApplication.java @@ -0,0 +1,22 @@ +package com.m2pool.file; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import com.m2pool.common.swagger.annotation.EnableCustomSwagger2; + +/** + * 文件服务 + * + * @author dy + */ +@EnableCustomSwagger2 +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class }) +public class M2PoolFileApplication +{ + public static void main(String[] args) + { + SpringApplication.run(M2PoolFileApplication.class, args); + System.out.println("m2pool文件服务模块启动成功 ლ(´ڡ`ლ)゙ \n"); + } +} diff --git a/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/config/ResourcesConfig.java b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/config/ResourcesConfig.java new file mode 100644 index 0000000..4718c15 --- /dev/null +++ b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/config/ResourcesConfig.java @@ -0,0 +1,50 @@ +package com.m2pool.file.config; + +import java.io.File; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 通用映射配置 + * + * @author dy + */ +@Configuration +public class ResourcesConfig implements WebMvcConfigurer +{ + /** + * 上传文件存储在本地的根路径 + */ + @Value("${file.path}") + private String localFilePath; + + /** + * 资源映射路径 前缀 + */ + @Value("${file.prefix}") + public String localFilePrefix; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** 本地文件上传路径 */ + registry.addResourceHandler(localFilePrefix + "/**") + .addResourceLocations("file:" + localFilePath + File.separator); + } + + /** + * 开启跨域 + */ + @Override + public void addCorsMappings(CorsRegistry registry) { + // 设置允许跨域的路由 + registry.addMapping(localFilePrefix + "/**") + // 设置允许跨域请求的域名 + .allowedOrigins("*") + // 设置允许的方法 + .allowedMethods("GET"); + } +} diff --git a/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/controller/SysFileController.java b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/controller/SysFileController.java new file mode 100644 index 0000000..3c1ac6b --- /dev/null +++ b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/controller/SysFileController.java @@ -0,0 +1,108 @@ +package com.m2pool.file.controller; + +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import com.m2pool.common.core.utils.file.FileUtils; +import com.m2pool.file.service.ISysFileService; +import com.m2pool.system.api.entity.SysFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 文件请求处理 + * + * @author dy + */ +@RestController +public class SysFileController +{ + private static final Logger log = LoggerFactory.getLogger(SysFileController.class); + + @Autowired + private ISysFileService sysFileService; + + /** + * 上传文件存储在本地的根路径 + */ + @Value("${file.path}") + private String localFilePath; + + /** + * 文件上传请求 + */ + @PostMapping("upload") + public R upload(MultipartFile file) + { + try + { + // 上传并返回访问地址 + String url = sysFileService.uploadFile(file); + SysFile sysFile = new SysFile(); + sysFile.setName(FileUtils.getName(url)); + sysFile.setUrl(url); + return R.success(sysFile); + } + catch (Exception e) + { + log.error("上传文件失败", e); + return R.fail(e.getMessage()); + } + } + + /** + * 下载请求 + * + * @param url 访问路径 + */ + @PostMapping("/download") + public void fileDownload(@RequestBody() String url, HttpServletResponse response, @RequestHeader("User-Agent") String userAgent, HttpServletRequest request) + { + + String fileName = FileUtils.getName(url); + try + { + if (!FileUtils.checkAllowDownload(fileName)) + { + throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); + } + // 数据库资源地址 + String downloadPath = localFilePath + StringUtils.substringAfter(url, "statics"); + // 下载名称 + String downloadName = StringUtils.substringAfterLast(downloadPath, "/"); + + + + response.reset(); + response.setCharacterEncoding("GBK"); + // 针对IE或者以IE为内核的浏览器: + if (userAgent.contains("MSIE") || userAgent.contains("Trident")) { + downloadName = java.net.URLEncoder.encode(downloadName, "UTF-8"); + } else { + // 非IE浏览器的处理: + downloadName = new String(downloadName.getBytes("UTF-8"), "ISO-8859-1"); + } + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + response.setHeader( + "Content-disposition", + "attachment;download-filename=" + + downloadName); + FileUtils.setAttachmentResponseHeader(response, downloadName); + FileUtils.writeBytes(downloadPath, response.getOutputStream()); + + } + catch (Exception e) + { + log.error("下载文件失败", e); + } + } + +} diff --git a/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/service/ISysFileService.java b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/service/ISysFileService.java new file mode 100644 index 0000000..cfa7e20 --- /dev/null +++ b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/service/ISysFileService.java @@ -0,0 +1,21 @@ +package com.m2pool.file.service; + +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件上传接口 + * + * @author dy + */ +public interface ISysFileService +{ + /** + * 文件上传接口 + * + * @param file 上传的文件 + * @return 访问地址 + * @throws Exception + */ + public String uploadFile(MultipartFile file) throws Exception; + +} diff --git a/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/service/LocalSysFileServiceImpl.java b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/service/LocalSysFileServiceImpl.java new file mode 100644 index 0000000..dcad69d --- /dev/null +++ b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/service/LocalSysFileServiceImpl.java @@ -0,0 +1,53 @@ +package com.m2pool.file.service; + +import com.m2pool.common.core.utils.file.MimeTypeUtils; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import com.m2pool.file.utils.FileUploadUtils; + +/** + * 本地文件存储 + * + * @author dy + */ +@Primary +@Service +public class LocalSysFileServiceImpl implements ISysFileService +{ + /** + * 资源映射路径 前缀 + */ + @Value("${file.prefix}") + public String localFilePrefix; + + /** + * 域名或本机访问地址 + */ + @Value("${file.domain}") + public String domain; + + /** + * 上传文件存储在本地的根路径 + */ + @Value("${file.path}") + private String localFilePath; + + /** + * 本地文件上传接口 + * + * @param file 上传的文件 + * @return 访问地址 + * @throws Exception + */ + @Override + public String uploadFile(MultipartFile file) throws Exception + { + String name = FileUploadUtils.upload(localFilePath, file ,MimeTypeUtils.DEFAULT_USER_HELP_EXTENSION); + String url = domain + localFilePrefix + name; + return url; + } + +} diff --git a/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/utils/FileUploadUtils.java b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/utils/FileUploadUtils.java new file mode 100644 index 0000000..07ff888 --- /dev/null +++ b/m2pool-modules/m2pool-file/src/main/java/com/m2pool/file/utils/FileUploadUtils.java @@ -0,0 +1,197 @@ +package com.m2pool.file.utils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; +import com.m2pool.common.core.exception.file.FileNameLengthLimitExceededException; +import com.m2pool.common.core.exception.file.FileSizeLimitExceededException; +import com.m2pool.common.core.exception.file.InvalidExtensionException; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.file.MimeTypeUtils; +import com.m2pool.common.core.utils.uuid.Seq; + +import javax.sound.midi.Soundbank; + +/** + * 文件上传工具类 + * + * @author dy + */ +public class FileUploadUtils +{ + /** + * 默认大小 2M + */ + public static final long DEFAULT_MAX_SIZE = 2 * 1024 * 1024; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + + /** + * 根据文件路径上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @return 文件名称 + * @throws IOException + */ + public static final String upload(String baseDir, MultipartFile file) throws IOException + { + try + { + return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + catch (Exception e) + { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param allowedExtension 上传文件类型 + * @return 返回上传成功的文件名 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileNameLengthLimitExceededException 文件名太长 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + InvalidExtensionException + { + int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) + { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + + assertAllowed(file, allowedExtension); + + String fileName = extractFilename(file); + + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + file.transferTo(Paths.get(absPath)); + return getPathFileName(fileName); + } + + /** + * 编码文件名 + */ + public static final String extractFilename(MultipartFile file) + { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), + FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); + } + + private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException + { + File desc = new File(uploadDir + File.separator + fileName); + + if (!desc.exists()) + { + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + } + return desc.isAbsolute() ? desc : desc.getAbsoluteFile(); + } + + private static final String getPathFileName(String fileName) throws IOException + { + String pathFileName = "/" + fileName; + return pathFileName; + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final void assertAllowed(MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException + { + long size = file.getSize(); + if (size > DEFAULT_MAX_SIZE) + { + throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); + } + + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) + { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) + { + throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) + { + throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) + { + throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) + { + throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + fileName); + } + else + { + throw new InvalidExtensionException(allowedExtension, extension, fileName); + } + } + } + + /** + * 判断MIME类型是否是允许的MIME类型 + * + * @param extension 上传文件类型 + * @param allowedExtension 允许上传文件类型 + * @return true/false + */ + public static final boolean isAllowedExtension(String extension, String[] allowedExtension) + { + for (String str : allowedExtension) + { + if (str.equalsIgnoreCase(extension)) + { + return true; + } + } + return false; + } + + /** + * 获取文件名的后缀 + * + * @param file 表单文件 + * @return 后缀名 + */ + public static final String getExtension(MultipartFile file) + { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (StringUtils.isEmpty(extension)) + { + extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); + } + return extension; + } +} diff --git a/m2pool-modules/m2pool-file/src/main/resources/bootstrap.yml b/m2pool-modules/m2pool-file/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..e8b7e41 --- /dev/null +++ b/m2pool-modules/m2pool-file/src/main/resources/bootstrap.yml @@ -0,0 +1,25 @@ +# Tomcat +server: + port: 9300 + +# Spring +spring: + application: + # 应用名称 + name: m2pool-file + profiles: + # 环境配置 + active: dev + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: 127.0.0.1:8808 + config: + # 配置中心地址 + server-addr: 127.0.0.1:8808 + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} diff --git a/m2pool-modules/m2pool-file/src/main/resources/logback.xml b/m2pool-modules/m2pool-file/src/main/resources/logback.xml new file mode 100644 index 0000000..6a220c9 --- /dev/null +++ b/m2pool-modules/m2pool-file/src/main/resources/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-oapi/pom.xml b/m2pool-modules/m2pool-oapi/pom.xml new file mode 100644 index 0000000..b198e2c --- /dev/null +++ b/m2pool-modules/m2pool-oapi/pom.xml @@ -0,0 +1,159 @@ + + + + + m2pool-modules + com.m2pool + 3.5.0 + + 4.0.0 + + m2pool-oapi + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + org.springframework.boot + spring-boot-starter-web + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + io.springfox + springfox-swagger-ui + ${swagger.fox.version} + + + + + mysql + mysql-connector-java + + + + + org.projectlombok + lombok-maven-plugin + provided + + + + + + com.m2pool + common-datasource + + + + + com.m2pool + common-security + + + + + com.m2pool + common-log + + + + com.m2pool + common-swagger + + + + cn.hutool + hutool-all + 5.6.2 + + + + org.mybatis + mybatis + 3.5.7 + compile + + + + + + + + dev + + dev + 127.0.0.1:8808 + + + + true + + + + + test + + test + 127.0.0.1:8808 + + + false + + + + + prod + + prod + 127.0.0.1:8808 + + + false + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + + diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/M2OApiApplication.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/M2OApiApplication.java new file mode 100644 index 0000000..915b9c0 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/M2OApiApplication.java @@ -0,0 +1,34 @@ +package com.m2pool.oapi; + +import com.m2pool.common.security.annotation.EnableCustomConfig; +import com.m2pool.common.security.annotation.EnableM2PoolFeignClients; +import com.m2pool.common.swagger.annotation.EnableCustomSwagger2; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +/** + * @Description open api服务入口类 + * @Date 2024/6/14 13:42 + * @Author dy + */ +@EnableCustomConfig +@EnableCustomSwagger2 +@EnableM2PoolFeignClients +@SpringBootApplication +@MapperScan({"com.m2pool.oapi.mapper"}) +public class M2OApiApplication { + + @Bean + public RestTemplate restTemplate(){ + return new RestTemplate(); + } + + public static void main(String[] args) { + SpringApplication.run(M2OApiApplication.class,args); + System.out.println("m2pool矿池 开放api微服务启动成功"); + } + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/controller/AccountController.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/controller/AccountController.java new file mode 100644 index 0000000..b633914 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/controller/AccountController.java @@ -0,0 +1,84 @@ +package com.m2pool.oapi.controller; + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.security.annotation.OpenApiPermissions; +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.oapi.service.AccountService; +import com.m2pool.oapi.vo.AccountDateVo; +import com.m2pool.oapi.vo.AccountVo; +import com.m2pool.oapi.vo.CoinVo; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + * @Description 统一请求接口 + * @Date 2024/6/14 9:54 + * @Author dy + */ +@RestController +@RequestMapping("/account") +public class AccountController extends BaseController { + + @Autowired + private AccountService accountService; + + @PostMapping("/hashrate_real") + @ApiOperation(value = "挖矿账户实时算力、24小时算力") + @OpenApiPermissions("account") + public AjaxResult hashrate_real(@Valid @RequestBody AccountVo vo){ + + return accountService.getHashRateReal(vo); + + } + + @PostMapping("/hashrate_history") + @ApiOperation(value = "挖矿账户历史算力(只支持24小时,最多3个月)") + @OpenApiPermissions("account") + public AjaxResult hashRateHistory(@Valid @RequestBody AccountDateVo vo){ + + return accountService.getHashRateHistory(vo); + + } + + @PostMapping("/hashrate_last24h") + @ApiOperation(value = "挖矿账户最近24小时算力(30分钟算力)") + @OpenApiPermissions("account") + public AjaxResult getHashRateLast24h(@Valid @RequestBody AccountVo vo){ + + return accountService.getHashRateLast24h(vo); + + } + + @PostMapping("/miners_list") + @ApiOperation(value = "挖矿账号矿工列表") + @OpenApiPermissions("account") + public AjaxResult minersList(@Valid @RequestBody AccountVo vo){ + + return accountService.getMinersList(vo); + + } + + @PostMapping("/watch") + @ApiOperation(value = "挖矿账号矿工总览") + @OpenApiPermissions("account") + public AjaxResult watch(@Valid @RequestBody AccountVo vo){ + + return accountService.getWatch(vo); + + } + + @GetMapping("/test1") + @ApiOperation(value = "挖矿账号矿工总览") + //@OpenApiPermissions("account") + public AjaxResult test1(){ + + return AjaxResult.success(); + + } + + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/controller/MinerController.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/controller/MinerController.java new file mode 100644 index 0000000..13b1ab2 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/controller/MinerController.java @@ -0,0 +1,62 @@ +package com.m2pool.oapi.controller; + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.security.annotation.OpenApiPermissions; +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.oapi.service.MinerService; +import com.m2pool.oapi.vo.AccountDateVo; +import com.m2pool.oapi.vo.AccountMinerDateVo; +import com.m2pool.oapi.vo.AccountMinerVo; +import com.m2pool.oapi.vo.AccountVo; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; + +/** + * @Description 矿机开放api统一请求接口 + * @Date 2024/6/14 9:54 + * @Author dy + */ +@RestController +@RequestMapping("/miner") +public class MinerController extends BaseController { + + @Autowired + private MinerService minerService; + + @PostMapping("/hashrate_real") + @ApiOperation(value = "挖矿账户实时算力、24小时算力") + @OpenApiPermissions("miner") + public AjaxResult hashrate_real(@Valid @RequestBody AccountMinerVo vo){ + + return minerService.getHashRateReal(vo); + + } + + @PostMapping("/hashrate_history") + @ApiOperation(value = "挖矿账户历史算力(只支持24小时,最多3个月)") + @OpenApiPermissions("miner") + public AjaxResult hashRateHistory(@Valid @RequestBody AccountMinerDateVo vo){ + + return minerService.getHashRateHistory(vo); + + } + + @PostMapping("/hashrate_last24h") + @ApiOperation(value = "挖矿账户最近24小时算力(30分钟算力)") + @OpenApiPermissions("miner") + public AjaxResult getHashRateLast24h(@Valid @RequestBody AccountMinerVo vo){ + + return minerService.getHashRateLast24h(vo); + + } + + + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/controller/PoolController.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/controller/PoolController.java new file mode 100644 index 0000000..bc8a0da --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/controller/PoolController.java @@ -0,0 +1,78 @@ +package com.m2pool.oapi.controller; + +import com.m2pool.common.core.annotation.RateLimiter; +import com.m2pool.common.core.enums.LimitType; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.security.annotation.OpenApiPermissions; +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.oapi.service.PoolService; +import com.m2pool.oapi.vo.CoinVo; +import com.m2pool.oapi.vo.DateVo; + +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + * @Description 统一请求接口 + * @Date 2024/6/14 9:54 + * @Author dy + */ +@RestController +@RequestMapping("/pool") +public class PoolController extends BaseController { + + @Autowired + private PoolService poolService; + + @GetMapping("/hashrate") + @ApiOperation(value = "矿池实时算力") + @OpenApiPermissions("pool") + public AjaxResult hashrate(@Valid CoinVo vo){ + + return poolService.getHashRate(vo); + + } + + @GetMapping("/hashrate_history") + @ApiOperation(value = "矿池历史算力(最多3个月)") + @OpenApiPermissions("pool") + public AjaxResult hashRateHistory(@Valid DateVo vo){ + + return poolService.getHashRateHistory(vo); + + } + + @GetMapping("/miners_list") + @ApiOperation(value = "矿池当前矿工数") + @OpenApiPermissions("pool") + public AjaxResult getMinersList(@Valid CoinVo vo){ + + return poolService.getMinersList(vo); + + } + + @GetMapping("/watch") + @ApiOperation(value = "矿池总览") + @OpenApiPermissions("pool") + @RateLimiter(time = 1,count = 5,limitType = LimitType.IP) + public AjaxResult watch(@Valid CoinVo vo){ + + return poolService.getWatch(vo); + + } + + @PostMapping("/test") + @ApiOperation(value = "矿池总览") + @OpenApiPermissions("pool") + public AjaxResult test(@Valid @RequestBody CoinVo vo){ + + return AjaxResult.success(); + + } + + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateDto.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateDto.java new file mode 100644 index 0000000..a27ec42 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateDto.java @@ -0,0 +1,24 @@ +package com.m2pool.oapi.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class HashRateDto implements Serializable { + + /** 实时(30m)算力 */ + @JsonProperty("hashrate") + private BigDecimal mhs; + + private Date date; + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateHistory24hDto.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateHistory24hDto.java new file mode 100644 index 0000000..7eee814 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateHistory24hDto.java @@ -0,0 +1,26 @@ +package com.m2pool.oapi.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.m2pool.common.core.utils.uuid.UUID; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class HashRateHistory24hDto implements Serializable { + + /** 24h算力 */ + @JsonProperty("hashrate_24h") + private List list; + + /** 单位 h/s, kh/s, mh/s, gh/s,th/s */ + private String unit; + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateHistory30mDto.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateHistory30mDto.java new file mode 100644 index 0000000..0618b74 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateHistory30mDto.java @@ -0,0 +1,24 @@ +package com.m2pool.oapi.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class HashRateHistory30mDto implements Serializable { + + /** 实时(30m)算力 */ + @JsonProperty("hashrate_30m") + private List list; + + /** 单位 h/s, kh/s, mh/s, gh/s,th/s */ + private String unit; + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateRealDto.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateRealDto.java new file mode 100644 index 0000000..6557a2d --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/HashRateRealDto.java @@ -0,0 +1,29 @@ +package com.m2pool.oapi.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class HashRateRealDto implements Serializable { + + /** 实时(30m)算力 */ + @JsonProperty("hashrate_realtime") + private BigDecimal mhs; + + /** 有效提交数 */ + @JsonProperty("hashrate_24h") + private BigDecimal mhs24h; + + /** 单位 h/s, kh/s, mh/s, gh/s,th/s */ + private String unit; + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/MinerStateDto.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/MinerStateDto.java new file mode 100644 index 0000000..d6db190 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/MinerStateDto.java @@ -0,0 +1,23 @@ +package com.m2pool.oapi.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class MinerStateDto implements Serializable { + + /** 实时(30m)算力 */ + private String miner; + + private int state; + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/MinerStateListDto.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/MinerStateListDto.java new file mode 100644 index 0000000..fad5ceb --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/MinerStateListDto.java @@ -0,0 +1,22 @@ +package com.m2pool.oapi.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class MinerStateListDto implements Serializable { + + /** 24h算力 */ + @JsonProperty("miner_list") + private List list; + + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/MinerWatchDto.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/MinerWatchDto.java new file mode 100644 index 0000000..8a1f764 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/MinerWatchDto.java @@ -0,0 +1,24 @@ +package com.m2pool.oapi.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class MinerWatchDto implements Serializable { + + private long total; + + private long online; + + private long offline; + + private long timeout; + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/PoolHashRateHistoryDto.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/PoolHashRateHistoryDto.java new file mode 100644 index 0000000..aee3f7a --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/PoolHashRateHistoryDto.java @@ -0,0 +1,28 @@ +package com.m2pool.oapi.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class PoolHashRateHistoryDto implements Serializable { + + /** 矿池(30m)算力 */ + @JsonProperty("hashrate_30m") + private List mhsList; + + /** 矿池(24h)算力 */ + @JsonProperty("hashrate_24h") + private List mhs24hList; + + /** 单位 h/s, kh/s, mh/s, gh/s,th/s */ + private String unit; + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/PoolWatchDto.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/PoolWatchDto.java new file mode 100644 index 0000000..79fb584 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/dto/PoolWatchDto.java @@ -0,0 +1,36 @@ +package com.m2pool.oapi.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * @Description 矿池总览 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class PoolWatchDto implements Serializable { + + @JsonProperty("pool_fee") + private String fee; + + @JsonProperty("min_pay") + private double minPay; + + @JsonProperty("history_last_7days") + private List list; + + private long miners; + + @JsonProperty("hashrate") + private BigDecimal mhs; + + @JsonProperty("last_found") + private long height; + + private String unit; +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/entity/ApiKeyInfo.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/entity/ApiKeyInfo.java new file mode 100644 index 0000000..87324fd --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/entity/ApiKeyInfo.java @@ -0,0 +1,25 @@ +package com.m2pool.oapi.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class ApiKeyInfo implements Serializable { + + /** api_key */ + private String key; + + private String ip; + + private String user; + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/entity/MinerState.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/entity/MinerState.java new file mode 100644 index 0000000..34566a8 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/entity/MinerState.java @@ -0,0 +1,22 @@ +package com.m2pool.oapi.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class MinerState implements Serializable { + + /** 实时(30m)算力 */ + @JsonProperty("hashrate") + private String miner; + + private String state; + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/enums/PoolAmount.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/enums/PoolAmount.java new file mode 100644 index 0000000..e0a5498 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/enums/PoolAmount.java @@ -0,0 +1,49 @@ +package com.m2pool.oapi.enums; + +/** + * @Description 矿池默认起付额 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum PoolAmount { + + GRS("grs", 100), + MONA("mona", 100), + DGB_SKEIN("dgb_skein", 100), + DGB_QUBIT("dgb_qubit", 100), + DGB_ODO("dgb_odo", 100), + DGB_QUBIT_A10("dgb_qubit_a10", 100), + DGB_SKEIN_A10("dgb_skein_a10", 100), + DGB_ODO_B20("dgb_odo_b20", 100), + NEXA("nexa", 10000), + RXD("rxd", 10000), + ALPH("alph", 10000), + ENX("enx", 10000); + + /** 币种参数名 */ + private final String coin; + /** 单位 */ + private final double amount; + + + PoolAmount(String coin, double amount) { + this.coin = coin; + this.amount = amount; + } + + public String getCoin() { + return coin; + } + + public double getAmount(){return amount;} + + public static double getAmountByCoin(String coin){ + for (PoolAmount t : PoolAmount.values()) { + if(t.getCoin().equals(coin)){ + return t.getAmount(); + } + } + return 10000; + } + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/enums/PoolUnits.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/enums/PoolUnits.java new file mode 100644 index 0000000..5cd99e6 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/enums/PoolUnits.java @@ -0,0 +1,62 @@ +package com.m2pool.oapi.enums; + +/** + * @Description 矿池单位换算 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum PoolUnits { + + GRS("grs", "MH/s", 1000, 1000), + MONA("mona", "MH/s", 1000, 1000), + DGB_SKEIN("dgb_skein", "MH/s", 1000, 1000), + DGB_QUBIT("dgb_qubit", "MH/s", 1000, 1000), + DGB_ODO("dgb_odo", "MH/s", 1000, 1000), + DGB2_ODO("dgb2_odo", "MH/s", 1000, 1000), + DGB_QUBIT_A10("dgb_qubit_a10", "MH/s", 1000, 1000), + DGB_SKEIN_A10("dgb_skein_a10", "MH/s", 1000, 1000), + DGB_ODO_B20("dgb_odo_b20", "MH/s", 1000, 1000), + NEXA("nexa", "MH/s", 1000*1000, 1000), + RXD("rxd", "MH/s", 1000*1000, 1000), + ALPH("aplh", "MH/s", 1000*1000, 1000), + ENX("enx", "MH/s", 1000*1000, 1000); + + /** 币种参数名 */ + private final String coin; + /** 矿池数据单位 */ + private final String unit; + /** 换算率 数据库原始数据换算为H/s的换算率 */ + private final int hRate ; + + /** 换算率 数据库原始数据换算为H/s的换算率 */ + private final int gRate ; + + + + + PoolUnits(String coin, String unit, int hRate, int gRate) { + this.coin = coin; + this.unit = unit; + this.hRate = hRate; + this.gRate = gRate; + } + + public String getCoin() { + return coin; + } + + public String getUnit() { + return unit; + } + + public int gethRate() { + return hRate; + } + + public int getgRate() { + return gRate; + } + + + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/mapper/AccountMapper.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/mapper/AccountMapper.java new file mode 100644 index 0000000..6d483c9 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/mapper/AccountMapper.java @@ -0,0 +1,34 @@ +package com.m2pool.oapi.mapper; + +import com.m2pool.common.datasource.annotation.HashRateDB; +import com.m2pool.common.datasource.annotation.InfoDB; +import com.m2pool.oapi.dto.HashRateDto; +import com.m2pool.oapi.dto.HashRateRealDto; +import com.m2pool.oapi.entity.MinerState; +import com.m2pool.oapi.vo.AccountDateVo; +import com.m2pool.oapi.vo.AccountVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + +/** + * @Description TODO + * @Date 2022/8/30 17:47 + * @Author 杜懿 + */ +@InfoDB +public interface AccountMapper { + + + @HashRateDB + public List getAccountRealHashRateListByCoinAndAccount(@Param("vo") AccountVo vo); + + public List getAccountHashRateHistory24hListByCoinAndAccount(@Param("vo") AccountDateVo vo); + + public List getAccountHashRateHistory30mListByCoinAndAccount(@Param("vo") AccountVo vo,@Param("start") String start); + + @HashRateDB + public List getMinerListByCoinAndAccount(@Param("vo") AccountVo vo); + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/mapper/MinerMapper.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/mapper/MinerMapper.java new file mode 100644 index 0000000..8d19de4 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/mapper/MinerMapper.java @@ -0,0 +1,33 @@ +package com.m2pool.oapi.mapper; + +import com.m2pool.common.datasource.annotation.HashRateDB; +import com.m2pool.common.datasource.annotation.InfoDB; +import com.m2pool.oapi.dto.HashRateDto; +import com.m2pool.oapi.dto.HashRateRealDto; +import com.m2pool.oapi.vo.AccountDateVo; +import com.m2pool.oapi.vo.AccountMinerDateVo; +import com.m2pool.oapi.vo.AccountMinerVo; +import com.m2pool.oapi.vo.AccountVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + +/** + * @Description TODO + * @Date 2022/8/30 17:47 + * @Author 杜懿 + */ +@InfoDB +public interface MinerMapper { + + @HashRateDB + public HashRateRealDto getMinerRealHashRateByCoinAndAccountMiner(@Param("vo") AccountMinerVo vo); + + public List getMinerHashRateHistory24hListByCoinAndAccountMiner(@Param("vo") AccountMinerDateVo vo); + + public List getMinerHashRateHistory30mListByCoinAndAccountMiner(@Param("vo") AccountMinerVo vo, @Param("start") String start); + + + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/mapper/PoolMapper.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/mapper/PoolMapper.java new file mode 100644 index 0000000..cf58bc0 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/mapper/PoolMapper.java @@ -0,0 +1,42 @@ +package com.m2pool.oapi.mapper; + +import com.m2pool.common.datasource.annotation.HashRateDB; +import com.m2pool.common.datasource.annotation.InfoDB; +import com.m2pool.oapi.dto.HashRateDto; +import com.m2pool.oapi.dto.HashRateRealDto; +import com.m2pool.oapi.dto.PoolWatchDto; +import com.m2pool.oapi.entity.MinerState; +import com.m2pool.oapi.vo.AccountVo; +import com.m2pool.oapi.vo.CoinVo; +import com.m2pool.oapi.vo.DateVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + +/** + * @Description TODO + * @Date 2022/8/30 17:47 + * @Author 杜懿 + */ +@InfoDB +public interface PoolMapper { + + @HashRateDB + public HashRateRealDto getPoolRealHashRateListByCoin(@Param("vo") CoinVo vo); + + public List getPoolHashRateHistory24hListByCoin(@Param("vo") DateVo vo); + + public List getPoolHashRateHistory30mListByCoin(@Param("vo") DateVo vo); + + @HashRateDB + public List getMinerListByCoin(@Param("vo") CoinVo vo); + + @HashRateDB + public PoolWatchDto getPoolWatchByCoin(@Param("vo") CoinVo vo); + + @HashRateDB + public HashRateDto getPoolRealHashRateByCoin(@Param("vo") CoinVo vo); + + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/AccountService.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/AccountService.java new file mode 100644 index 0000000..2a925de --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/AccountService.java @@ -0,0 +1,25 @@ +package com.m2pool.oapi.service; + + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.oapi.vo.AccountDateVo; +import com.m2pool.oapi.vo.AccountVo; +import com.m2pool.oapi.vo.DateVo; + +/** + * @Description 开放api 矿池 相关服务类 + * @Date 2024/6/14 11:40 + * @Author dy + */ +public interface AccountService { + + public AjaxResult getHashRateReal(AccountVo vo); + + public AjaxResult getHashRateHistory(AccountDateVo vo); + + public AjaxResult getHashRateLast24h(AccountVo vo); + + public AjaxResult getMinersList(AccountVo vo); + + public AjaxResult getWatch(AccountVo vo); +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/MinerService.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/MinerService.java new file mode 100644 index 0000000..59df4a6 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/MinerService.java @@ -0,0 +1,23 @@ +package com.m2pool.oapi.service; + + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.oapi.vo.AccountDateVo; +import com.m2pool.oapi.vo.AccountMinerDateVo; +import com.m2pool.oapi.vo.AccountMinerVo; +import com.m2pool.oapi.vo.AccountVo; + +/** + * @Description 开放api 矿池 相关服务类 + * @Date 2024/6/14 11:40 + * @Author dy + */ +public interface MinerService { + + public AjaxResult getHashRateReal(AccountMinerVo vo); + + public AjaxResult getHashRateHistory(AccountMinerDateVo vo); + + public AjaxResult getHashRateLast24h(AccountMinerVo vo); + +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/PoolService.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/PoolService.java new file mode 100644 index 0000000..3dbc312 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/PoolService.java @@ -0,0 +1,22 @@ +package com.m2pool.oapi.service; + + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.oapi.vo.CoinVo; +import com.m2pool.oapi.vo.DateVo; + +/** + * @Description 开放api 矿池 相关服务类 + * @Date 2024/6/14 11:40 + * @Author dy + */ +public interface PoolService { + + public AjaxResult getHashRate(CoinVo vo); + + public AjaxResult getHashRateHistory(DateVo vo); + + public AjaxResult getMinersList(CoinVo vo); + + public AjaxResult getWatch(CoinVo vo); +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/impl/AccountServiceImpl.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/impl/AccountServiceImpl.java new file mode 100644 index 0000000..cbecd79 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/impl/AccountServiceImpl.java @@ -0,0 +1,192 @@ +package com.m2pool.oapi.service.impl; + +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.oapi.dto.*; +import com.m2pool.oapi.entity.MinerState; +import com.m2pool.oapi.enums.PoolUnits; +import com.m2pool.oapi.mapper.AccountMapper; +import com.m2pool.oapi.service.AccountService; +import com.m2pool.oapi.utils.EnumUtils; +import com.m2pool.oapi.vo.AccountDateVo; +import com.m2pool.oapi.vo.AccountVo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Description TODO + * @Date 2024/9/13 10:47 + * @Author 杜懿 + */ +@Service +public class AccountServiceImpl implements AccountService { + + @Autowired + private AccountMapper accountMapperMapper; + + @Override + public AjaxResult getHashRateReal(AccountVo vo) { + + PoolUnits unit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + if(StringUtils.isNull(unit)){ + return AjaxResult.error("coin error"); + } + List list = accountMapperMapper.getAccountRealHashRateListByCoinAndAccount(vo); + + BigDecimal mhs = list.stream().map(e -> { + return e.getMhs(); + }).reduce(BigDecimal.ZERO, BigDecimal::add); + BigDecimal mhs24h = list.stream().map(e -> { + return e.getMhs24h(); + }).reduce(BigDecimal.ZERO, BigDecimal::add); + + HashRateRealDto dto = new HashRateRealDto(); + dto.setUnit(unit.getUnit()); + dto.setMhs(mhs); + dto.setMhs24h(mhs24h); + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getHashRateHistory(AccountDateVo vo) { + System.out.println(vo); + + PoolUnits unit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + if(StringUtils.isNull(unit)){ + return AjaxResult.error("coin error"); + } + + Date today = new Date(); + today.setHours(0); + today.setMinutes(0); + today.setMinutes(0); + + Calendar ca = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + ca.setTime(today); + ca.add(Calendar.MONTH,-3); + + Date firstDate = ca.getTime(); + + if (StringUtils.isBlank(vo.getStart())){ + vo.setStart(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,firstDate)); + }else { + if(DateUtils.parseDate(vo.getStart()).before(firstDate)){ + vo.setStart(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,firstDate)); + }else { + //在三个月之内 不做处理 + try { + DateUtils.parseDate(vo.getStart()); + }catch (Exception e){ + //出错说明格式错误有 + return AjaxResult.error("start param error"); + } + } + } + + if (StringUtils.isNotBlank(vo.getEnd())){ + //在三个月之内 不做处理 + try { + Date end = DateUtils.parseDate(vo.getEnd()); + if(end.before(DateUtils.parseDate(vo.getStart()))){ + //end时间在start之前 + return AjaxResult.error("end Date can not before start Date"); + } + + if(firstDate.before(end)){ + return AjaxResult.error("end Date can not before "+DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD,firstDate)); + } + }catch (Exception e){ + //出错说明格式错误有 + return AjaxResult.error("end param error"); + } + }else { + //没有end 则不做处理 + } + + List mhsList = accountMapperMapper.getAccountHashRateHistory24hListByCoinAndAccount(vo); + HashRateHistory24hDto dto = new HashRateHistory24hDto(); + dto.setList(mhsList); + dto.setUnit(unit.getUnit()); + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getHashRateLast24h(AccountVo vo) { + + long t0 = System.currentTimeMillis(); + + PoolUnits unit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + if(StringUtils.isNull(unit)){ + return AjaxResult.error("coin error"); + } + + Date today = new Date(); + + Calendar ca = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + ca.setTime(today); + ca.add(Calendar.DATE,-1); + + String start = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,ca.getTime()); + long t1 = System.currentTimeMillis(); + List list = accountMapperMapper.getAccountHashRateHistory30mListByCoinAndAccount(vo, start); + long t2 = System.currentTimeMillis(); + HashRateHistory30mDto dto = new HashRateHistory30mDto(); + dto.setList(list); + dto.setUnit(unit.getUnit()); + + System.out.println("挖矿账户last24h业务 数据库查询耗时:"+(t2-t1)+"ms,业务耗时"+(t2-t0)+"ms"); + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getMinersList(AccountVo vo) { + + List list = accountMapperMapper.getMinerListByCoinAndAccount(vo); + List minerList = list.stream().map(e -> { + MinerStateDto dto = new MinerStateDto(); + dto.setMiner(e.getMiner()); + if ("online".equals(e.getState().toLowerCase())) { + dto.setState(0); + } else if ("offline".equals(e.getState().toLowerCase())) { + dto.setState(1); + } else { + dto.setState(2); + } + return dto; + }).collect(Collectors.toList()); + + MinerStateListDto minerStateListDto = new MinerStateListDto(); + minerStateListDto.setList(minerList); + + return AjaxResult.success(minerStateListDto); + } + + @Override + public AjaxResult getWatch(AccountVo vo) { + MinerWatchDto dto = new MinerWatchDto(); + List list = accountMapperMapper.getMinerListByCoinAndAccount(vo); + Map map = list.stream().collect(Collectors.groupingBy( + MinerState::getState, + Collectors.counting() + )); + + long total = Convert.toLong(list.size(),0L); + long online = map.getOrDefault("online", 0L); + long offline = map.getOrDefault("offline", 0L); + dto.setTotal(total); + dto.setOnline(online); + dto.setOffline(offline); + dto.setTimeout(BigDecimal.valueOf(total) + .subtract(BigDecimal.valueOf(online)) + .subtract(BigDecimal.valueOf(offline)) + .longValue()); + + return AjaxResult.success(dto); + } +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/impl/MinerServiceImpl.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/impl/MinerServiceImpl.java new file mode 100644 index 0000000..69b5e24 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/impl/MinerServiceImpl.java @@ -0,0 +1,138 @@ +package com.m2pool.oapi.service.impl; + +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.oapi.dto.HashRateDto; +import com.m2pool.oapi.dto.HashRateHistory24hDto; +import com.m2pool.oapi.dto.HashRateHistory30mDto; +import com.m2pool.oapi.dto.HashRateRealDto; +import com.m2pool.oapi.enums.PoolUnits; +import com.m2pool.oapi.mapper.MinerMapper; +import com.m2pool.oapi.service.MinerService; +import com.m2pool.oapi.utils.EnumUtils; +import com.m2pool.oapi.vo.AccountMinerDateVo; +import com.m2pool.oapi.vo.AccountMinerVo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +/** + * @Description TODO + * @Date 2024/9/13 10:49 + * @Author 杜懿 + */ +@Service +public class MinerServiceImpl implements MinerService { + + @Autowired + private MinerMapper minerMapper; + + + @Override + public AjaxResult getHashRateReal(AccountMinerVo vo) { + PoolUnits unit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + if(StringUtils.isNull(unit)){ + return AjaxResult.error("coin error"); + } + + HashRateRealDto dto = minerMapper.getMinerRealHashRateByCoinAndAccountMiner(vo); + + dto.setUnit(unit.getUnit()); + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getHashRateHistory(AccountMinerDateVo vo) { + PoolUnits unit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + if(StringUtils.isNull(unit)){ + return AjaxResult.error("coin error"); + } + + Date today = new Date(); + today.setHours(0); + today.setMinutes(0); + today.setMinutes(0); + + Calendar ca = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + ca.setTime(today); + ca.add(Calendar.MONTH,-3); + + Date firstDate = ca.getTime(); + + if (StringUtils.isBlank(vo.getStart())){ + vo.setStart(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,firstDate)); + }else { + if(DateUtils.parseDate(vo.getStart()).before(firstDate)){ + vo.setStart(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,firstDate)); + }else { + //在三个月之内 不做处理 + try { + DateUtils.parseDate(vo.getStart()); + }catch (Exception e){ + //出错说明格式错误有 + return AjaxResult.error("start param error"); + } + } + } + + if (StringUtils.isNotBlank(vo.getEnd())){ + //在三个月之内 不做处理 + try { + Date end = DateUtils.parseDate(vo.getEnd()); + if(end.before(DateUtils.parseDate(vo.getStart()))){ + //end时间在start之前 + return AjaxResult.error("end Date can not before start Date"); + } + + if(firstDate.before(end)){ + return AjaxResult.error("end Date can not before "+DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD,firstDate)); + } + }catch (Exception e){ + //出错说明格式错误有 + return AjaxResult.error("end param error"); + } + }else { + //没有end 则不做处理 + } + + List mhsList = minerMapper.getMinerHashRateHistory24hListByCoinAndAccountMiner(vo); + HashRateHistory24hDto dto = new HashRateHistory24hDto(); + dto.setList(mhsList); + dto.setUnit(unit.getUnit()); + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getHashRateLast24h(AccountMinerVo vo) { + + long t0 = System.currentTimeMillis(); + + PoolUnits unit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + if(StringUtils.isNull(unit)){ + return AjaxResult.error("coin error"); + } + + Date today = new Date(); + + Calendar ca = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + ca.setTime(today); + ca.add(Calendar.DATE,-1); + + String start = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,ca.getTime()); + long t1 = System.currentTimeMillis(); + List list = minerMapper.getMinerHashRateHistory30mListByCoinAndAccountMiner(vo, start); + long t2 = System.currentTimeMillis(); + HashRateHistory30mDto dto = new HashRateHistory30mDto(); + dto.setList(list); + dto.setUnit(unit.getUnit()); + long t3 = System.currentTimeMillis(); + + return AjaxResult.success(dto); + } +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/impl/PoolServiceImpl.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/impl/PoolServiceImpl.java new file mode 100644 index 0000000..c5b1647 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/service/impl/PoolServiceImpl.java @@ -0,0 +1,213 @@ +package com.m2pool.oapi.service.impl; + +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.oapi.dto.*; +import com.m2pool.oapi.entity.MinerState; +import com.m2pool.oapi.enums.PoolAmount; +import com.m2pool.oapi.enums.PoolUnits; +import com.m2pool.oapi.mapper.PoolMapper; +import com.m2pool.oapi.service.PoolService; +import com.m2pool.oapi.utils.EnumUtils; +import com.m2pool.oapi.vo.CoinVo; +import com.m2pool.oapi.vo.DateVo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Description TODO + * @Date 2024/9/13 10:44 + * @Author 杜懿 + */ +@Service +public class PoolServiceImpl implements PoolService { + + @Autowired + private PoolMapper poolMapper; + + @Override + public AjaxResult getHashRate(CoinVo vo) { + PoolUnits unit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + if(StringUtils.isNull(unit)){ + return AjaxResult.error("coin error"); + } + + HashRateRealDto dto = poolMapper.getPoolRealHashRateListByCoin(vo); + + dto.setUnit(unit.getUnit()); + + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getHashRateHistory(DateVo vo) { + if(StringUtils.isNull(vo)){ + return AjaxResult.error("request body cannot be null"); + } + + PoolUnits unit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + if(StringUtils.isNull(unit)){ + return AjaxResult.error("coin error"); + } + + Date today = new Date(); + today.setHours(0); + today.setMinutes(0); + today.setMinutes(0); + + Calendar ca = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + ca.setTime(today); + ca.add(Calendar.MONTH,-3); + + //只允许查询当前日期之前3个月的记录 所以firstDate代表运行查询是最早时间值 + Date firstDate = ca.getTime(); + //用start保存最开始用户传入的开始时间 没传start或者错误格式都会导致start为null 后续可以根据start是否为空判断是否有正确传入开始时间 + Date start = DateUtils.parseDate(vo.getStart()); + + Date end = DateUtils.parseDate(vo.getEnd()); + + if (StringUtils.isNotNull(end)){ + //在三个月之内 不做处理 + if(StringUtils.isNull(start)){ + //用户没传start 只需比较end 和 firsrDate + if(end.before(firstDate) || end.equals(firstDate)){ + return AjaxResult.error("end Date can not before "+DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD,firstDate)); + } + //end 在firstDate之后 符合条件 可以进入后续步骤 + }else { + //用户传了 start 此时有start 和 end以及firstDate 需要优先比较这两个start和end是否符合 start before end + if(end.before(start) || end.equals(start)){ + return AjaxResult.error("end Date must after start Date"); + }else { + //start before end 此时可能的时间顺序有 1、firstDate start end 2、start firstDate end 3、 start end firstDate + //第一种情况则正常按用户传的start、end查询 即不做处理 + //第二种情况则设vo.start为firstDate + //第三种情况提示 end 不能在firstDate之前 + if(start.before(firstDate)){ + //满足次条件的只有2和3 3会在后面进行拦截 所以此处不用处理 + //处理第二种情况 + vo.setStart(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,firstDate)); + } + + //执行到这里只有1、修正了时间的2 以及3 + if(end.before(firstDate)){ + // + return AjaxResult.error("end Date can not before "+DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD,firstDate)); + } + } + } + }else { + //没有end + if(StringUtils.isNotNull(start) && firstDate.before(start)){ + //只有用户传入了正确格式的开始时间 并且这个时间在firstDate之后才不用修改vo.start 否则都需要设置vo.start为firstDate + }else { + vo.setStart(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,firstDate)); + } + + } + List mhs24hList = poolMapper.getPoolHashRateHistory24hListByCoin(vo); + List mhs30mList = poolMapper.getPoolHashRateHistory30mListByCoin(vo); + PoolHashRateHistoryDto dto = new PoolHashRateHistoryDto(); + dto.setMhs24hList(mhs24hList); + dto.setMhsList(mhs30mList); + dto.setUnit(unit.getUnit()); + + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getMinersList(CoinVo vo) { + PoolUnits unit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + if(StringUtils.isNull(unit)){ + //防止coin注入异常数据 + return AjaxResult.error("coin error"); + } + + MinerWatchDto dto = new MinerWatchDto(); + List list = poolMapper.getMinerListByCoin(vo); + Map map = list.stream().collect(Collectors.groupingBy( + MinerState::getState, + Collectors.counting() + )); + + long total = Convert.toLong(list.size(),0L); + long online = map.getOrDefault("online", 0L); + long offline = map.getOrDefault("offline", 0L); + dto.setTotal(total); + dto.setOnline(online); + dto.setOffline(offline); + dto.setTimeout(BigDecimal.valueOf(total) + .subtract(BigDecimal.valueOf(online)) + .subtract(BigDecimal.valueOf(offline)) + .longValue()); + + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getWatch(CoinVo vo) { + PoolUnits unit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + if(StringUtils.isNull(unit)){ + //防止coin注入异常数据 + return AjaxResult.error("coin error"); + } + + PoolAmount poolAmount = (PoolAmount) EnumUtils.get(PoolAmount.class, vo.getCoin()); + if(StringUtils.isNull(poolAmount)){ + //防止coin注入异常数据 + return AjaxResult.error("coin error"); + } + + PoolWatchDto watch = poolMapper.getPoolWatchByCoin(vo); + if(StringUtils.isNull(watch)){ + watch = new PoolWatchDto(); + watch.setMiners(0); + watch.setMhs(BigDecimal.ZERO); + watch.setHeight(0); + } + //todo 加到配置文件中 + int fee = 1; + if("enx".equals(vo.getCoin())){ + Date now =new Date(); + if(now.before(DateUtils.parseDate("2025-04-21"))){{ + fee =0; + }} + } + watch.setFee(fee+"% PPLNS"); + watch.setMinPay(poolAmount.getAmount()); + + DateVo dateVo = new DateVo(); + dateVo.setCoin(vo.coin); + //Date today = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getDate()); + Date today = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getDate()+" 00:00:00"); + + Calendar ca = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + ca.setTime(today); + ca.add(Calendar.DATE,-7); + + Date start = ca.getTime(); + dateVo.setStart(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,start)); + List list = poolMapper.getPoolHashRateHistory24hListByCoin(dateVo); + + list.stream().forEach(e -> { + e.setDate(DateUtils.addDays(e.getDate(),-1)); + //MH/s 每秒转GH/s + //e.setPv(e.getPv().divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + }); + + //最后一条用实时表最新一组数据 字段mhs24h对应实时24h算力 + HashRateDto lastDto = poolMapper.getPoolRealHashRateByCoin(vo); + Date now = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + lastDto.setDate(now); + list.add(lastDto); + watch.setList(list); + watch.setUnit(unit.getUnit()); + return AjaxResult.success(watch); + } +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/utils/EnumUtils.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/utils/EnumUtils.java new file mode 100644 index 0000000..f538f2c --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/utils/EnumUtils.java @@ -0,0 +1,99 @@ +package com.m2pool.oapi.utils; + +import com.m2pool.common.core.utils.StringUtils; + +import java.lang.reflect.Method; + +/** + * @Description 枚举工具类 + * @Date 2024/6/13 10:54 + * @Author dy + */ +public class EnumUtils { + + /** + * 通过值判断数值是否属于枚举类的值 + * @param clzz 枚举类 Enum + * @param code + * @author wayleung + * @return + */ + public static boolean isInclude(Class clzz,String code){ + + if(StringUtils.isEmpty(code)){ + return false; + } + + try { + if(clzz.isEnum()){ + Object[] enumConstants = clzz.getEnumConstants(); + Method name = clzz.getMethod("name"); + + for (Object enumConstant:enumConstants){ + if (name.invoke(enumConstant).equals(code.toUpperCase())) { + return true; + } + } + + } + }catch (Exception e){ + throw new RuntimeException(); + } + return false; + } + /** + * 通过值判断数值是否属于枚举类的值 + * @param clzz 枚举类 Enum + * @param code + * @author wayleung + * @return + */ + + public static Object get(Class clzz,String code){ + + if(StringUtils.isEmpty(code)){ + return null; + } + + + try { + if(clzz.isEnum()){ + Object[] enumConstants = clzz.getEnumConstants(); + Method getInfo = clzz.getMethod("name"); + for (Object enumConstant:enumConstants){ + if (getInfo.invoke(enumConstant).equals(code.toUpperCase())) { + return enumConstant; + } + } + + } + }catch (Exception e){ + throw new RuntimeException(); + } + return null; + } + + public static String getNumberTypeStr(Class clzz,String value){ + + if(StringUtils.isEmpty(value)){ + return null; + } + + try { + if(clzz.isEnum()){ + Object[] enumConstants = clzz.getEnumConstants(); + Method getInfo = clzz.getMethod("getInfo"); + Method getValue = clzz.getMethod("getValue"); + for (Object enumConstant:enumConstants){ + if (getInfo.invoke(enumConstant).equals(value)) { + return (String) getValue.invoke(enumConstant); + } + } + + } + }catch (Exception e){ + throw new RuntimeException(); + } + return null; + } +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountDateVo.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountDateVo.java new file mode 100644 index 0000000..9914d8c --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountDateVo.java @@ -0,0 +1,32 @@ +package com.m2pool.oapi.vo; + +import com.alibaba.fastjson.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/9/12 15:49 + * @Author 杜懿 + */ +@Data +public class AccountDateVo { + + @NotBlank(message = "矿工账号不能为空") + @Xss + @JsonProperty("mining_user") + @JSONField(name = "mining_user") + public String ma; + + @Xss + public String coin; + + @Xss + public String start; + + @Xss + public String end; +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountMinerDateVo.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountMinerDateVo.java new file mode 100644 index 0000000..141ac60 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountMinerDateVo.java @@ -0,0 +1,36 @@ +package com.m2pool.oapi.vo; + +import com.alibaba.fastjson.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/9/12 15:49 + * @Author 杜懿 + */ +@Data +public class AccountMinerDateVo { + + @NotBlank(message = "矿工账号不能为空") + @Xss + @JsonProperty("mining_user") + @JSONField(name = "mining_user") + public String ma; + + @NotBlank(message = "矿机号不能为空") + @Xss + public String miner; + + @Xss + public String coin; + + @Xss + public String start; + + @Xss + public String end; +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountMinerVo.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountMinerVo.java new file mode 100644 index 0000000..2e5aa15 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountMinerVo.java @@ -0,0 +1,30 @@ +package com.m2pool.oapi.vo; + +import com.alibaba.fastjson.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/9/12 15:49 + * @Author 杜懿 + */ +@Data +public class AccountMinerVo { + + @NotBlank(message = "矿工账号不能为空") + @Xss + @JsonProperty("mining_user") + @JSONField(name = "mining_user") + public String ma; + + @Xss + public String coin; + + @NotBlank(message = "矿机号不能为空") + @Xss + public String miner; +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountVo.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountVo.java new file mode 100644 index 0000000..1ecf943 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/AccountVo.java @@ -0,0 +1,26 @@ +package com.m2pool.oapi.vo; + +import com.alibaba.fastjson.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/9/12 15:49 + * @Author 杜懿 + */ +@Data +public class AccountVo { + + @NotBlank(message = "矿工账号不能为空") + @Xss + @JsonProperty("mining_user") + @JSONField(name = "mining_user") + public String ma; + + @Xss + public String coin; +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/CoinVo.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/CoinVo.java new file mode 100644 index 0000000..198e8b3 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/CoinVo.java @@ -0,0 +1,16 @@ +package com.m2pool.oapi.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +/** + * @Description TODO + * @Date 2024/9/12 15:49 + * @Author 杜懿 + */ +@Data +public class CoinVo { + + @Xss + public String coin; +} diff --git a/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/DateVo.java b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/DateVo.java new file mode 100644 index 0000000..9f09817 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/java/com/m2pool/oapi/vo/DateVo.java @@ -0,0 +1,24 @@ +package com.m2pool.oapi.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/9/12 15:49 + * @Author 杜懿 + */ +@Data +public class DateVo { + + @Xss + public String start; + + @Xss + public String end; + + @Xss + public String coin; +} diff --git a/m2pool-modules/m2pool-oapi/src/main/resources/bootstrap.yml b/m2pool-modules/m2pool-oapi/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..f2754f8 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/resources/bootstrap.yml @@ -0,0 +1,156 @@ +server: + port: 9207 + compression: + enabled: true + mime-types: application/json + +spring: + #邮箱基本配置 + mail: + # 配置在limit_time内,用户可以发送limit次验证码 + limit: 2 这个是我额外的配置,结合邮箱服务用的 + limitTime: 10 这个是我额外的配置 + #配置smtp服务主机地址 + # sina smtp.sina.cn + # aliyun smtp.aliyun.com + # 163 smtp.163.com 端口号465或994 + host: mail.privateemail.com + #发送者邮箱 + username: support@m2pool.cc + #配置密码,注意不是真正的密码,而是刚刚申请到的授权码 + # password: + # password: m2pool2024@! + # password: axvm-zfgx-cgcg-qhhu + password: m2pool2024@! + #端口号 + port: 587 + #默认的邮件编码为UTF-8 + default-encoding: UTF-8 + #其他参数 + properties: + mail: + #配置SSL 加密工厂 + smtp: + ssl: + #本地测试,先放开ssl + enable: false + required: false + #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误 + debug: false + socketFactory: + class: javax.net.ssl.SSLSocketFactory + + application: + # 应用名称 + name: m2pool-oapi + profiles: + # 环境配置 + active: prod + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: 127.0.0.1:8808 + namespace: m2_prod + group: m2_prod_group + # server-addr: 127.0.0.1:8808 + config: + # 配置中心地址 + server-addr: 127.0.0.1:8808 + # server-addr: 127.0.0.1:8808 + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} + namespace: m2_prod + group: m2_prod_group + servlet: + multipart: + max-file-size: 2MB + max-request-size: 8MB + +myenv: + domain: https://www.m2pool.com + path: /var/www/html/web + img: /img + + +##测试环境 +#server: +# port: 9507 +# compression: +# enabled: true +# mime-types: application/json +# +#spring: +# #邮箱基本配置 +# mail: +# # 配置在limit_time内,用户可以发送limit次验证码 +# limit: 2 这个是我额外的配置,结合邮箱服务用的 +# limitTime: 10 这个是我额外的配置 +# #配置smtp服务主机地址 +# # sina smtp.sina.cn +# # aliyun smtp.aliyun.com +# # 163 smtp.163.com 端口号465或994 +# host: mail.privateemail.com +# #发送者邮箱 +# username: support@m2pool.cc +# #配置密码,注意不是真正的密码,而是刚刚申请到的授权码 +# # password: +# # password: m2pool2024@! +# # password: axvm-zfgx-cgcg-qhhu +# password: m2pool2024@! +# #端口号 +# port: 587 +# #默认的邮件编码为UTF-8 +# default-encoding: UTF-8 +# #其他参数 +# properties: +# mail: +# #配置SSL 加密工厂 +# smtp: +# ssl: +# #本地测试,先放开ssl +# enable: false +# required: false +# #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误 +# debug: false +# socketFactory: +# class: javax.net.ssl.SSLSocketFactory +# +# application: +# # 应用名称 +# name: m2pool-oapi +# profiles: +# # 环境配置 +# active: test +# cloud: +# nacos: +# discovery: +# # 服务注册地址 +# server-addr: 127.0.0.1:8808 +# namespace: m2_test +# group: m2_test_group +# # server-addr: 127.0.0.1:8808 +# config: +# # 配置中心地址 +# server-addr: 127.0.0.1:8808 +# # server-addr: 127.0.0.1:8808 +# # 配置文件格式 +# file-extension: yml +# # 共享配置 +# shared-configs: +# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} +# namespace: m2_test +# group: m2_test_group +# servlet: +# multipart: +# max-file-size: 2MB +# max-request-size: 8MB +# +#myenv: +# domain: https://test.m2pool.com +# path: /var/www/html/web_test +# img: /img + diff --git a/m2pool-modules/m2pool-oapi/src/main/resources/logback.xml b/m2pool-modules/m2pool-oapi/src/main/resources/logback.xml new file mode 100644 index 0000000..837f568 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/resources/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 30 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 30 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-oapi/src/main/resources/mapper/oapi/AccountMapper.xml b/m2pool-modules/m2pool-oapi/src/main/resources/mapper/oapi/AccountMapper.xml new file mode 100644 index 0000000..c1dd921 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/resources/mapper/oapi/AccountMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-oapi/src/main/resources/mapper/oapi/MinerMapper.xml b/m2pool-modules/m2pool-oapi/src/main/resources/mapper/oapi/MinerMapper.xml new file mode 100644 index 0000000..2dd1501 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/resources/mapper/oapi/MinerMapper.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-oapi/src/main/resources/mapper/oapi/PoolMapper.xml b/m2pool-modules/m2pool-oapi/src/main/resources/mapper/oapi/PoolMapper.xml new file mode 100644 index 0000000..39ca017 --- /dev/null +++ b/m2pool-modules/m2pool-oapi/src/main/resources/mapper/oapi/PoolMapper.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-pool/pom.xml b/m2pool-modules/m2pool-pool/pom.xml new file mode 100644 index 0000000..6b3ad59 --- /dev/null +++ b/m2pool-modules/m2pool-pool/pom.xml @@ -0,0 +1,216 @@ + + + + m2pool-modules + com.m2pool + 3.5.0 + + 4.0.0 + + m2pool-pool + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + org.springframework.boot + spring-boot-starter-web + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + io.springfox + springfox-swagger-ui + ${swagger.fox.version} + + + + + mysql + mysql-connector-java + + + + + org.projectlombok + lombok-maven-plugin + provided + + + + + + + + + + + + + + + + + + + + + com.m2pool + common-datasource + + + + + com.m2pool + common-security + + + + + com.m2pool + common-log + + + + com.m2pool + common-swagger + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + org.java-websocket + Java-WebSocket + 1.3.5 + + + + cn.hutool + hutool-all + 5.6.2 + + + + org.mybatis + mybatis + 3.5.7 + compile + + + + + de.taimos + totp + 1.0 + + + + commons-codec + commons-codec + 1.10 + + + + com.google.zxing + javase + 3.3.0 + + + + commons-codec + commons-codec + 1.18.0 + + + + org.bouncycastle + bcprov-jdk15on + 1.70 + + + + + + + + dev + + dev + 127.0.0.1:8808 + + + + true + + + + + test + + test + 127.0.0.1:8808 + + + false + + + + + prod + + prod + 127.0.0.1:8808 + + + false + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + + diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/M2PoolApplication.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/M2PoolApplication.java new file mode 100644 index 0000000..bcfd54f --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/M2PoolApplication.java @@ -0,0 +1,34 @@ +package com.m2pool.pool; + +import com.m2pool.common.swagger.annotation.EnableCustomSwagger2; +import com.m2pool.common.security.annotation.EnableCustomConfig; +import com.m2pool.common.security.annotation.EnableM2PoolFeignClients; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +/** + * @Description pool服务入口类 + * @Date 2024/6/14 13:42 + * @Author dy + */ +@EnableCustomConfig +@EnableCustomSwagger2 +@EnableM2PoolFeignClients +@SpringBootApplication +@MapperScan({"com.m2pool.pool.mapper"}) +public class M2PoolApplication { + + @Bean + public RestTemplate restTemplate(){ + return new RestTemplate(); + } + + public static void main(String[] args) { + SpringApplication.run(M2PoolApplication.class,args); + System.out.println("m2pool矿池微服务启动成功"); + } + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/MybatisPlusConfig.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/MybatisPlusConfig.java new file mode 100644 index 0000000..51a6868 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/MybatisPlusConfig.java @@ -0,0 +1,19 @@ +//package com.m2pool.pool.config; +// +//import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; +//import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +// +///** +// * @Description TODO +// * @Date 2024/6/14 11:15 +// * @Author dy +// */ +//@Configuration +//public class MybatisPlusConfig { +// @Bean +// public PaginationInterceptor paginationInterceptor(){ +// return new PaginationInterceptor(); +// } +//} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/RestTemplateConfig.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/RestTemplateConfig.java new file mode 100644 index 0000000..4776eb5 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/RestTemplateConfig.java @@ -0,0 +1,77 @@ +package com.m2pool.pool.config; + +import com.m2pool.pool.factory.IgnoreSSLRequestFactory; + +import org.apache.http.client.HttpClient; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContextBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + + +/** + * @Description TODO + * @Date 2024/6/14 14:59 + * @Author dy + */ +@Configuration +public class RestTemplateConfig { + + /** + * 使用ssl对证书进行验证,需要将证书导出到jdk管理仓库, + * 命令:keytool -import -v -trustcacerts -alias mytest -file "D:/tmp/mytest.cer" -keystore "C:/Program Files/Java/jdk1.8.0_131/jre/lib/security/cacerts" + */ + //@Bean("restTemplate") + //@Primary + //public RestTemplate restTemplate() throws NoSuchAlgorithmException, KeyManagementException { + // HttpComponentsClientHttpRequestFactory httpRequestFactory = new + // HttpComponentsClientHttpRequestFactory(); + // httpRequestFactory.setConnectionRequestTimeout(6 * 1000); //获取连接池连接的超时时间(毫秒) + // httpRequestFactory.setConnectTimeout(6 * 1000); //连接上服务器(握手成功)的时间(毫秒) + // httpRequestFactory.setReadTimeout(60 * 1000); //返回数据时间(毫秒) + // httpRequestFactory.setHttpClient(httpClient()); + // RestTemplate restTemplate = new RestTemplate(httpRequestFactory); + // return restTemplate; + //} + + /** + * 绕过ssl对证书进行验证,无需将证书导出到jdk管理仓库 + */ + @Bean("restTemplateIgnoreSSL") + public RestTemplate restTemplateIgnoreSSL() throws NoSuchAlgorithmException, KeyManagementException { + IgnoreSSLRequestFactory requestFactory = new IgnoreSSLRequestFactory(httpClient()); + requestFactory.setConnectTimeout(60000); //连接上服务器(握手成功)的时间 + requestFactory.setReadTimeout(60000); //返回数据时间 + RestTemplate restTemplate = new RestTemplate(requestFactory); + return restTemplate; + } + + @Bean + public HttpClient httpClient() throws KeyManagementException, NoSuchAlgorithmException { + SSLContextBuilder contextBuilder = new SSLContextBuilder(); + SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(contextBuilder.build(), NoopHostnameVerifier.INSTANCE); + Registry registry = RegistryBuilder.create() + .register("http", new PlainConnectionSocketFactory()) + .register("https", socketFactory).build(); + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry); + connectionManager.setMaxTotal(200); + connectionManager.setDefaultMaxPerRoute(100); + CloseableHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connectionManager).build(); + return httpClient; + } + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/ScheduledTaskConfig.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/ScheduledTaskConfig.java new file mode 100644 index 0000000..73856b6 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/ScheduledTaskConfig.java @@ -0,0 +1,22 @@ +package com.m2pool.pool.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +/** + * @Description scheduler配置类 + * @Date 2024/10/25 10:26 + * @Author 杜懿 + */ +@Configuration +public class ScheduledTaskConfig implements SchedulingConfigurer { + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); + taskScheduler.setPoolSize(20); + taskScheduler.initialize(); + taskRegistrar.setTaskScheduler(taskScheduler); + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/WebSocketConfig.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/WebSocketConfig.java new file mode 100644 index 0000000..6c2dc4a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/config/WebSocketConfig.java @@ -0,0 +1,20 @@ +package com.m2pool.pool.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/** + * @Description websocket 配置 + * @Date 2024/6/14 11:09 + * @Author dy + */ +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() + { + return new ServerEndpointExporter(); + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/NoticeController.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/NoticeController.java new file mode 100644 index 0000000..d67d508 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/NoticeController.java @@ -0,0 +1,79 @@ +package com.m2pool.pool.controller; + +import com.github.pagehelper.PageHelper; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.common.security.annotation.RequiresLogin; +import com.m2pool.pool.dto.NoticeListDto; +import com.m2pool.pool.service.NoticeService; +import com.m2pool.pool.vo.*; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @Description 告警系统相关请求接口 + * @Date 2024/6/14 9:54 + * @Author dy + */ +@RestController +@RequestMapping("/notice") +public class NoticeController extends BaseController { + + @Autowired + private NoticeService noticeService; + + + @PostMapping("/addNoticeEmail") + @ApiOperation(value = "添加告警邮箱") + @RequiresLogin + public AjaxResult addNoticeEmail(@RequestBody NoticeAddVo vo){ + + return noticeService.addNoticeEmail(vo); + + } + + @PostMapping("/getCode") + @ApiOperation(value = "绑定通知邮箱 发送邮箱验证码") + @RequiresLogin + public AjaxResult getCode(@RequestBody NoticeAddVo vo){ + clearPage(); + return noticeService.getCode(vo); + + } + + @PostMapping("/getList") + @ApiOperation(value = "获取当前用户绑定的通知邮箱列表 ") + @RequiresLogin + public TableDataInfo getNoticeEmailList(@RequestBody NoticePageVo vo){ + + PageHelper.startPage(vo.getPage(),vo.getLimit()); + List list = noticeService.getNoticeEmailList(vo); + return getDataTable(list); + + } + + @PostMapping("/updateInfo") + @ApiOperation(value = "修改通知邮箱备注信息") + @RequiresLogin + public AjaxResult updateInfo(@RequestBody NoticeUpdateVo vo){ + + return noticeService.updateInfo(vo); + + } + + @DeleteMapping("/deleteEmail") + @ApiOperation(value = "删除通知邮箱") + @RequiresLogin + public AjaxResult deleteEmail(@RequestBody NoticeDelVo vo){ + + return noticeService.deleteEmail(vo); + + } + + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/PoolController.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/PoolController.java new file mode 100644 index 0000000..f0fdb31 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/PoolController.java @@ -0,0 +1,203 @@ +package com.m2pool.pool.controller; + +import com.github.pagehelper.PageHelper; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.common.security.annotation.RequiresLogin; +import com.m2pool.pool.dto.BlockInfoDto; +import com.m2pool.pool.service.MinerAccountService; +import com.m2pool.pool.service.PoolService; +import com.m2pool.pool.vo.*; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; + +/** + * @Description 统一请求接口 + * @Date 2024/6/14 9:54 + * @Author dy + */ +@RestController +//@RequestMapping("/pool") +public class PoolController extends BaseController { + + @Autowired + private PoolService poolService; + + @Autowired + private MinerAccountService maService; + + + @PostMapping("/getCoinInfo") + @ApiOperation(value = "获取币种(矿池)信息") + public AjaxResult getCoinInfo(@Valid @RequestBody CoinVo vo){ + + return poolService.getPoolInfo(vo); + + } + + @PostMapping("/checkAccount") + @ApiOperation(value = "校验挖矿账号是否存在") + public boolean checkAccount(@Valid @RequestBody MinerAccountAddVo vo){ + + return poolService.checkAccount(vo); + + } + + @PostMapping("/getPoolPower") + @ApiOperation(value = "获取币种(矿池)算力曲线图") + public AjaxResult getPoolPower(@Valid @RequestBody CoinVo vo){ + + return poolService.getPoolPower(vo); + + } + + //@PostMapping("/getMinerCount") + //@ApiOperation(value = "获取币种(矿池)矿工数量曲线图") + //public AjaxResult getMinerCount(@Valid @RequestBody CoinVo com.m2pool.chat.vo){ + // + // return poolService.getMinerCount(com.m2pool.chat.vo); + // + //} + + @PostMapping("/getLuck") + @ApiOperation(value = "获取币种(矿池)幸运值") + public AjaxResult getLuck(@Valid @RequestBody CoinVo vo){ + + return poolService.getLuckByCoin(vo); + + } + + @PostMapping("/getBlockInfo") + @ApiOperation(value = "获取币种(矿池)报块信息") + public TableDataInfo getBlockInfo(@RequestBody() CoinVo vo){ + PageHelper.startPage(vo.getPage(),vo.getLimit()); + List list = poolService.getBlockInfoList(vo); + return getDataTable(list); + } + + //@PostMapping("/getPoolPowerDistribution") + //@ApiOperation(value = "获取当前矿池算力分布 柱状图") + //public AjaxResult getPoolPowerDistribution(@Valid @RequestBody CoinVo com.m2pool.chat.vo){ + // //暂未使用 + // return poolService.getPoolPowerDistribution(com.m2pool.chat.vo); + // + //} + + @PostMapping("/getMinerAccountPower") + @ApiOperation(value = "获取当前挖矿账号算力曲线图") + @RequiresLogin + public AjaxResult getMinerAccountPower(@Valid @RequestBody AccountVo vo){ + + return maService.getMinerAccountPower(vo); + + } + + @PostMapping("/getAccountPowerDistribution") + @ApiOperation(value = "获取当前挖矿账号算力分布 柱状图") + @RequiresLogin + public AjaxResult getAccountPowerDistribution(@Valid @RequestBody AccountVo vo){ + + return maService.getAccountPowerDistribution(vo); + + } + + @PostMapping("/getMinerAccountInfo") + @ApiOperation(value = "获取当前挖矿账户收益信息") + @RequiresLogin + public AjaxResult getMinerAccountInfo(@Valid @RequestBody AccountVo vo){ + + return maService.getMinerAccountInfo(vo); + + } + + @PostMapping("/getMinerList") + @ApiOperation(value = "获取当前挖矿账户下矿工详情") + @RequiresLogin + public AjaxResult getMinerList(@Valid @RequestBody MinerListVo vo){ + + return maService.getMinerList(vo); + + } + + @PostMapping("/getMinerPower") + @ApiOperation(value = "获取矿工算力曲线图") + @RequiresLogin + public AjaxResult getMinerPower(@Valid @RequestBody MinerVo vo){ + + return maService.getMinerPower(vo); + + } + + //@PostMapping("/getMinerPowerDistribution") + //@ApiOperation(value = "获取矿工算力分布 柱状图") + //@RequiresLogin + //public AjaxResult getMinerPowerDistribution(@Valid @RequestBody MinerVo com.m2pool.chat.vo){ + // + // return maService.getMinerPowerDistribution(com.m2pool.chat.vo); + // + //} + + @PostMapping("/getHistoryIncome") + @ApiOperation(value = "获取收益历史记录") + @RequiresLogin + public TableDataInfo getHistoryIncome(@RequestBody() AccountVo vo){ + + //PageHelper.startPage(com.m2pool.chat.vo.getPage(),com.m2pool.chat.vo.getLimit()); + return maService.getHistoryIncome(vo); + } + + @PostMapping("/getHistoryOutcome") + @ApiOperation(value = "获取账号提现历史记录") + @RequiresLogin + public TableDataInfo getHistoryOutcome(@RequestBody() AccountVo vo){ + + return maService.getHistoryOutcome(vo); + } + + @PostMapping(value = {"/getCoinBalanceList","/getCoinBalancetList"}) + @ApiOperation(value = "获取币种余额list") + public AjaxResult getCoinBalancetList(@RequestBody BalanceListGetVo vo){ + + return poolService.getCoinBalanceList(vo); + + } + + @PostMapping("/getNetPower") + @ApiOperation(value = "获取全网算力曲线") + public AjaxResult getNetPower(@Valid @RequestBody CoinVo vo){ + + return poolService.getNetPower(vo); + + } + + @PostMapping("/getParam") + @ApiOperation(value = "根据币种获取计算器相关参数") + public AjaxResult getParam(@Valid @RequestBody CoinVo vo){ + + return poolService.getParam(vo); + + } + + + @GetMapping("/test2") + @ApiOperation(value = "") + public AjaxResult test2(){ + + return poolService.test2(); + } + + + @GetMapping("/test1") + @ApiOperation(value = "") + public AjaxResult test1(){ + + return poolService.test1(); + } + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/ReadOnlyController.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/ReadOnlyController.java new file mode 100644 index 0000000..274a954 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/ReadOnlyController.java @@ -0,0 +1,146 @@ +package com.m2pool.pool.controller; + +import com.github.pagehelper.PageHelper; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.common.security.annotation.RequiresLogin; +import com.m2pool.pool.dto.UserReadPageInfoReturnDto; +import com.m2pool.pool.service.ReadOnlyService; +import com.m2pool.pool.vo.*; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; + +/** + * @Description 统一请求接口 + * @Date 2024/6/14 9:54 + * @Author dy + */ +@RestController +@RequestMapping("/read") +public class ReadOnlyController extends BaseController { + + @Autowired + private ReadOnlyService roService; + + @PostMapping("/getHtmlUrl") + @ApiOperation(value = "创建只读页面 ") + @RequiresLogin + public AjaxResult getHtmlUrl(@Valid @RequestBody ReadOnlyHtmlCreateVo vo){ + + return roService.createPageUrl(vo); + + } + + @PostMapping("/getUrlList") + @ApiOperation(value = "获取当前用户只读页面列表 ") + @RequiresLogin + public TableDataInfo getUrlList(@RequestBody(required = false) PageVo vo){ + + if(StringUtils.isNull(vo)){ + vo = new PageVo(); + } + PageHelper.clearPage(); + return roService.getUrlList(vo); + + } + + + @PostMapping("/getUrlInfo") + @ApiOperation(value = "获取当前只读页面基础信息 ") + @RequiresLogin + public AjaxResult getUrlInfo(@Valid @RequestBody ReadOnlyKeyVo vo){ + return roService.getUrlInfo(vo); + + } + + @PostMapping("/changeUrlInfo") + @ApiOperation(value = "修改只读页面 只能修改备注和权限") + @RequiresLogin + public AjaxResult changeUrlInfo(@Valid @RequestBody ReadOnlyHtmlChangeVo vo){ + + return roService.changeUrlInfo(vo); + + } + + @DeleteMapping("/delPage") + @ApiOperation(value = "修改只读页面 只能修改备注和权限") + @RequiresLogin + public AjaxResult delPage(@Valid @RequestBody ReadOnlyKeyVo vo){ + + return roService.delPageByKey(vo); + + } + + + @PostMapping("/getPageInfo") + @ApiOperation(value = "只读页面 获取只读页面 权限信息") + public AjaxResult getPageInfo(@Valid @RequestBody ReadOnlyKeyVo vo){ + + return roService.getPageInfo(vo); + + } + + @PostMapping("/getMinerAccountPower") + @ApiOperation(value = "只读页面 获取只读页面 挖矿账号算力曲线图") + public AjaxResult getMinerAccountPower(@Valid @RequestBody ReadOnlyIntervalVo vo){ + + return roService.getMinerAccountPower(vo); + + } + + @PostMapping("/getAccountPowerDistribution") + @ApiOperation(value = "只读页面 获取挖矿账号算力分布 柱状图") + public AjaxResult getAccountPowerDistribution(@Valid @RequestBody ReadOnlyIntervalVo vo){ + + return roService.getAccountPowerDistribution(vo); + + } + + @PostMapping("/getProfitInfo") + @ApiOperation(value = "只读页面 获取挖矿账户收益信息") + public AjaxResult getMinerAccountInfo(@Valid @RequestBody ReadOnlyKeyVo vo){ + + return roService.getProfitInfo(vo); + + } + + @PostMapping("/getMinerList") + @ApiOperation(value = "只读页面 获取挖矿账户下矿工详情") + public AjaxResult getMinerList(@Valid @RequestBody ReadOnlyListVo vo){ + PageHelper.clearPage(); + return roService.getMinerList(vo); + + } + + @PostMapping("/getMinerPower") + @ApiOperation(value = "只读页面 获取矿工算力曲线图") + public AjaxResult getMinerPower(@Valid @RequestBody ReadOnlyMinerVo vo){ + + return roService.getMinerPower(vo); + + } + + + @PostMapping("/getHistoryIncome") + @ApiOperation(value = "获取收益历史记录") + public TableDataInfo getHistoryIncome(@RequestBody() ReadOnlyPageVo vo){ + PageHelper.clearPage(); + return roService.getHistoryIncome(vo); + } + + @PostMapping("/getHistoryOutcome") + @ApiOperation(value = "获取账号提现历史记录") + public TableDataInfo getHistoryOutcome(@RequestBody() ReadOnlyPageVo vo){ + + PageHelper.clearPage(); + return roService.getHistoryOutcome(vo); + } + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/TicketController.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/TicketController.java new file mode 100644 index 0000000..c64a368 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/TicketController.java @@ -0,0 +1,192 @@ +package com.m2pool.pool.controller; + +import com.github.pagehelper.PageHelper; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.common.security.annotation.Logical; +import com.m2pool.common.security.annotation.RequiresLogin; +import com.m2pool.common.security.annotation.RequiresRoles; +import com.m2pool.pool.dto.IdsDto; +import com.m2pool.pool.dto.TicketListDto; +import com.m2pool.pool.service.TicketService; +import com.m2pool.pool.vo.*; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * @Description 统一请求接口 + * @Date 2022/5/27 9:54 + * @Author 杜懿 + */ +@RestController +@RequestMapping("/ticket") +public class TicketController extends BaseController { + + @Autowired + private TicketService ticketService; + + @PostMapping("/uploadFile") + @ApiOperation(value = "文件上传") + public AjaxResult uploadFile(@RequestParam("file") MultipartFile[] file){ + try { + return ticketService.uploadFiles(file); + } catch (SQLException e) { + e.printStackTrace(); + return AjaxResult.error("服务器异常,请联系管理员"); + } + + } + + @GetMapping("/downloadFile") + @ApiOperation(value = "文件批量下载") + public void download(IdsDto dto, HttpServletRequest request, HttpServletResponse response){ + + ticketService.downloadByIds(dto.getIds(),request,response); + + } + + @PostMapping("/submitTicket") + @ApiOperation(value = "提交工单") + @RequiresLogin + public AjaxResult submitTicket(@RequestBody(required = false) TicketVo vo){ + if(StringUtils.isNull(vo)){vo = new TicketVo();} + return ticketService.submitTicket(vo); + + } + + @PostMapping("/resubmitTicket") + @ApiOperation(value = "工单内容补充工单") + @RequiresLogin + @RequiresRoles(value = {"admin", "registered"}, logical = Logical.OR) + public AjaxResult resubmitTicket(@RequestBody ResubmitTicketVo vo){ + return ticketService.resubmitTicket(vo); + } + + @PostMapping({"/getPrivateTicket","privateConsume"}) + @ApiOperation(value = "个人工单列表") + @RequiresLogin + @RequiresRoles(value = {"admin", "registered"}, logical = Logical.OR) + public AjaxResult getPrivateTicket(@RequestBody(required = false) PrivateTicketListVo vo){ + if(StringUtils.isNull(vo)){ + vo = new PrivateTicketListVo(); + vo.setStatus(0); + } + return ticketService.getPrivateTicketByStatus(vo); + } + + @PostMapping({"/privateTicketDetails","getTicketDetails"}) + @ApiOperation(value = "个人工单详情") + @RequiresLogin + public AjaxResult getPrivateTicketDetails(@RequestBody() IdVo vo){ + return ticketService.getPrivateTicketDetails(vo.getId()); + } + + @PostMapping("/endTicket") + @ApiOperation(value = "用户关闭工单") + @RequiresLogin + @RequiresRoles(value = {"admin", "registered"}, logical = Logical.OR) + public AjaxResult endTicket(@RequestBody(required = false) EndTicketVo vo){ + + return ticketService.endTicket(vo); + } + + +/********************* admin相关接口 *************************/ + //@PostMapping("/getTicketList") + //@ApiOperation(value = "按状态搜索后台工单列表") + //@RequiresLogin + //@RequiresRoles(value = {"admin", "accounting","support","maintainer"}, logical = Logical.OR) + //public TableDataInfo getTicketList(@RequestBody StatusVo com.m2pool.chat.vo){ + // PageHelper.startPage(com.m2pool.chat.vo.getPage(),com.m2pool.chat.vo.getLimit()); + // List list = ticketService.getTicketList(com.m2pool.chat.vo.getStatus()); + // return getDataTable(list); + //} + + @PostMapping("/bk/getAllTicket") + @ApiOperation(value = "条件搜索后台所有工单列表") + @RequiresLogin + @RequiresRoles("admin") + public TableDataInfo getAllTicket(@RequestBody AllTicketListVo vo){ + System.out.println("com.m2pool.chat.vo"+vo); + if(StringUtils.isNotBlank(vo.getCond())){ + System.out.println("不为空"); + Pattern pattern = Pattern.compile("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"); + if(pattern.matcher(vo.getCond()).matches()){ + //验证是否是邮箱 + System.out.println("邮箱"+vo.getCond()); + vo.setEmail(vo.getCond()); + }else { + //判断是否是id + Long id = Convert.toLong(vo.getCond(), 0L); + System.out.println(id); + if(id > 0){ + System.out.println("工单id:"+vo.getCond()); + vo.setId(id); + }else { + return getDataTable(new ArrayList<>()); + } + } + } + + PageHelper.startPage(vo.getPage(),vo.getLimit()); + return getDataTable(ticketService.getAllTicket(vo)) ; + } + + @PostMapping("/bk/endTicket") + @ApiOperation(value = "关闭用户工单") + @RequiresLogin + @RequiresRoles(value = {"admin"}, logical = Logical.OR) + public AjaxResult endUserTicket(@RequestBody(required = false) IdVo vo){ + + return ticketService.closeTicket(vo); + } + + + @PostMapping("/bk/list") + @ApiOperation(value = "根据状态获取type为线上(type=0)的工单") + @RequiresLogin + @RequiresRoles(value = {"admin"}, logical = Logical.OR) + public TableDataInfo onlineTicketList(@RequestBody StatusVo vo){ + + PageHelper.startPage(vo.getPage(),vo.getLimit()); + List list = ticketService.getSupportTicketList(vo); + return getDataTable(list); + } + + @PostMapping("/bk/details") + @ApiOperation(value = "工单详情") + @RequiresLogin + @RequiresRoles(value = {"admin"}, logical = Logical.OR) + public AjaxResult onlineTicketDetails(@RequestBody IdVo vo){ + if(StringUtils.isNull(vo)){ + return AjaxResult.error("工单id缺失"); + } + return ticketService.getTicketDetails(vo.getId()); + } + + @PostMapping("/bk/respon") + @ApiOperation(value = "回复工单") + @RequiresLogin + @RequiresRoles(value = {"admin"}, logical = Logical.OR) + public AjaxResult onlineResponTicket(@RequestBody ResponTicketVo vo){ + return ticketService.onlineResponTicket(vo); + } + + + + + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/UserController.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/UserController.java new file mode 100644 index 0000000..f8e22fb --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/controller/UserController.java @@ -0,0 +1,246 @@ +package com.m2pool.pool.controller; + +import com.github.pagehelper.PageHelper; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.common.security.annotation.RequiresLogin; +import com.m2pool.pool.dto.UserApiDto; +import com.m2pool.pool.service.GoogleAuthService; +import com.m2pool.pool.service.MinerAccountService; +import com.m2pool.pool.service.UserMinserService; +import com.m2pool.pool.vo.*; +import com.m2pool.system.api.RemoteUserService; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; + +/** + * @Description 统一请求接口 + * @Date 2024/6/14 9:54 + * @Author dy + */ +@RestController +@RequestMapping("/user") +public class UserController extends BaseController { + + @Autowired + private UserMinserService umService; + + @Autowired + private GoogleAuthService googleAuthService; + + @Autowired + private MinerAccountService maService; + + @Autowired + private RemoteUserService userService; + + @PostMapping("/addMinerAccount") + @ApiOperation(value = "绑定挖矿账号") + @RequiresLogin + public AjaxResult addMinerAccount(@Valid @RequestBody MinerAccountAddVo vo){ + + return umService.addMinerAccount(vo); + + } + + @PostMapping("/checkAccount") + @ApiOperation(value = "校验挖矿账号是否存在") + @RequiresLogin + public AjaxResult checkAccount(@Valid @RequestBody MinerAccountAddVo vo){ + + return umService.checkAccount(vo); + + } + + @PostMapping("/checkBalance") + @ApiOperation(value = "校验钱包可用性") + @RequiresLogin + public AjaxResult checkBalance(@Valid @RequestBody BalanceCheckVo vo){ + + return umService.checkBalance(vo); + + } + + @PostMapping("/check") + @ApiOperation(value = "校验账户是否可用和钱包可用性") + @RequiresLogin + public AjaxResult check(@Valid @RequestBody MaBalanceCheckVo vo){ + + return umService.checkMaAndBalance(vo); + + } + + @DeleteMapping("/delMinerAccount") + @ApiOperation(value = "删除挖矿账号") + @RequiresLogin + public AjaxResult delMinerAccount(@Valid @RequestBody MinerAccountDelVo vo){ + + return umService.delMinerAccount(vo); + + } + + @PostMapping("/addBalance") + @ApiOperation(value = "挖矿账户绑定钱包-->>现改为 修改钱包地址或起付线以及切换自动提现激活状态") + @RequiresLogin + public AjaxResult addBalance(@Valid @RequestBody AccountBalanceAddVo vo){ + + return umService.addBalance(vo); + + } + + //@PostMapping("/updateBalance") + //@ApiOperation(value = "挖矿账户解绑钱包") + //@RequiresLogin + //public AjaxResult updateBalance(@Valid @RequestBody AccountBalanceAddVo com.m2pool.chat.vo){ + // + // return umService.updateBalance(com.m2pool.chat.vo); + // + //} + + @PostMapping("/getAccountList") + @ApiOperation(value = "获取用户当前币种下所有挖矿账号") + @RequiresLogin + public AjaxResult getAccountList(){ + + return umService.getAccountList(); + + } + + @PostMapping("/getAccountGradeList") + @ApiOperation(value = "获取用户当前币种下所有挖矿账号") + @RequiresLogin + public AjaxResult getAccountGradeList(){ + + return umService.getAccountGradeList(); + + } + + @PostMapping("/getMinerAccountBalance") + @ApiOperation(value = "获取当前矿工账号绑定钱包地址") + @RequiresLogin + public AjaxResult getMinerAccountBalance(@RequestBody MinerIdVo vo){ + + return umService.getMinerAccountBalance(vo); + + } + + /**************************************** google两步验证相关接口 ***********************************************************/ + @PostMapping("/ifBind") + @ApiOperation(value = "校验钱包可用性") + @RequiresLogin + public AjaxResult ifBind(){ + clearPage(); + return googleAuthService.ifBind(); + + } + + @PostMapping("/getBindInfo") + @ApiOperation(value = "获取谷歌验证器 安全码和验证码") + @RequiresLogin + public AjaxResult getBindInfo(){ + clearPage(); + return googleAuthService.getBindInfo(); + + } + + @PostMapping("/getBindCode") + @ApiOperation(value = "绑定谷歌验证器 发送邮箱验证码") + @RequiresLogin + public AjaxResult getBindCode(){ + clearPage(); + return googleAuthService.getBindCode(); + + } + + @PostMapping("/bindGoogle") + @ApiOperation(value = "绑定谷歌验证器") + @RequiresLogin + public AjaxResult bindGoogle(@Valid @RequestBody GoogleBindVo vo){ + clearPage(); + return googleAuthService.bindGoogle(vo); + + } + + @PostMapping("/closeStepTwo") + @ApiOperation(value = "关闭两步验证") + @RequiresLogin + public AjaxResult closeStepTwo(@Valid @RequestBody GoogleCloseVo vo){ + clearPage(); + return googleAuthService.closeStepTwp(vo); + + } + + @PostMapping("/getCloseCode") + @ApiOperation(value = "关闭谷歌验证器 发送邮箱验证码") + @RequiresLogin + public AjaxResult getCloseCode(){ + clearPage(); + return googleAuthService.getCloseCode(); + + } + + @PostMapping("/emailIfBind") + @ApiOperation(value = "校验钱包可用性") + public AjaxResult emailIfBind(@Valid @RequestBody EmailVo vo){ + clearPage(); + return googleAuthService.emailIfBind(vo); + + } + + @PostMapping("/getApiKey") + @ApiOperation(value = "创建apiKey") + @RequiresLogin + public AjaxResult getApiKey(@Valid @RequestBody ApiKeyCreateVo vo){ + + return umService.createApiKey(vo); + + } + + @PostMapping("/getApiList") + @ApiOperation(value = "获取当前用户apiKEY列表 ") + @RequiresLogin + public TableDataInfo getApiList(@RequestBody(required = false) PageVo vo){ + + if(StringUtils.isNull(vo)){ + vo = new PageVo(); + } + + PageHelper.startPage(vo.getPage(),vo.getLimit()); + List list = umService.getApiList(vo); + return getDataTable(list); + + } + + @PostMapping("/getApiInfo") + @ApiOperation(value = "获取当前只读页面基础信息 ") + @RequiresLogin + public AjaxResult getApiInfo(@Valid @RequestBody ApiKeyInfoVo vo){ + return umService.getApiInfo(vo); + + } + + + @PostMapping("/updateAPI") + @ApiOperation(value = "修改API绑定信息 只能修改备注和权限") + @RequiresLogin + public AjaxResult updateAPI(@Valid @RequestBody ApiKeyUpdateVo vo){ + + return umService.updateApi(vo); + + } + + @DeleteMapping("/delApi") + @ApiOperation(value = "删除指定api 只能修改备注和权限") + @RequiresLogin + public AjaxResult delApi(@Valid @RequestBody ApiKeyDelVo vo){ + + return umService.deleteApi(vo); + + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/AccountPowerDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/AccountPowerDto.java new file mode 100644 index 0000000..983d6b5 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/AccountPowerDto.java @@ -0,0 +1,29 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class AccountPowerDto implements Serializable { + + /** 日期 */ + private Date date; + + /** 矿池算力 */ + private double pv; + + /** 有效提交数 */ + private BigDecimal accepts; + + /** 被拒提交数 */ + private BigDecimal rejects; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/AccountProfitInfoDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/AccountProfitInfoDto.java new file mode 100644 index 0000000..0b95634 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/AccountProfitInfoDto.java @@ -0,0 +1,37 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class AccountProfitInfoDto implements Serializable { + + /** 挖矿账户 */ + private String account; + + /** 总收益 */ + private BigDecimal totalProfit; + + /** 总支出 */ + private BigDecimal expend; + + /** 昨日收益 */ + private BigDecimal preProfit; + + /** 今日收益 */ + private BigDecimal todayPorfit; + + /** 余额 */ + private BigDecimal balance; + + private BigDecimal withdrawable; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/AllTicketListDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/AllTicketListDto.java new file mode 100644 index 0000000..8e63da3 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/AllTicketListDto.java @@ -0,0 +1,26 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description TODO + * @Date 2022/6/20 17:44 + * @Author 杜懿 + */ +@Data +public class AllTicketListDto { + + private int id; + + private Date date; + + private String desc; + + private String email; + + private String status; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/BalanceListDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/BalanceListDto.java new file mode 100644 index 0000000..5150eb8 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/BalanceListDto.java @@ -0,0 +1,25 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 幸运值 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class BalanceListDto implements Serializable { + + /** 挖矿账户名 */ + private String user; + + /** 钱包地址 */ + private String address; + + /** 起付线 */ + private double amount; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/BlockInfoDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/BlockInfoDto.java new file mode 100644 index 0000000..2a667a0 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/BlockInfoDto.java @@ -0,0 +1,31 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 当前币种矿池报块信息 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class BlockInfoDto implements Serializable { + + /** 区块高度 */ + private int height; + + /** 报块时间 */ + private Date date; + + /** 区块hash */ + private String hash; + + /** 报块奖励 */ + private double reward; + + /** 交易费 */ + private double fees; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/CalParamDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/CalParamDto.java new file mode 100644 index 0000000..0d8242a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/CalParamDto.java @@ -0,0 +1,32 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class CalParamDto implements Serializable { + + /** 币种 */ + private String coin; + + /** 全网算力 */ + private BigDecimal netHashrate; + + /** 区块奖励 */ + private BigDecimal reward; + + /** 每日报块数 */ + private BigDecimal count; + + /** 矿池手续费 */ + private BigDecimal poolFee; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/ComDataDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/ComDataDto.java new file mode 100644 index 0000000..4de0c0e --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/ComDataDto.java @@ -0,0 +1,21 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class ComDataDto implements Serializable { + + /** 时间 */ + private Date date; + + /** 数据值 */ + private Object value; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/CountDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/CountDto.java new file mode 100644 index 0000000..be7e450 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/CountDto.java @@ -0,0 +1,25 @@ +package com.m2pool.pool.dto; + +import com.alibaba.nacos.shaded.com.google.gson.annotations.SerializedName; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class CountDto implements Serializable { + + /** 时间 */ + private Date dataTime; + + /** 数据条数 */ + private int num; + + private BigDecimal price; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateBigDecimalDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateBigDecimalDto.java new file mode 100644 index 0000000..1db18f2 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateBigDecimalDto.java @@ -0,0 +1,25 @@ +package com.m2pool.pool.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class DateBigDecimalDto implements Serializable { + + /** 时间 */ + private Date pDate; + + /** 数据值 */ + private BigDecimal value; + + private BigDecimal price; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateBigDecimalsDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateBigDecimalsDto.java new file mode 100644 index 0000000..f4c6637 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateBigDecimalsDto.java @@ -0,0 +1,28 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class DateBigDecimalsDto implements Serializable { + + /** 时间 */ + private Date date; + + /** 数据值 */ + private BigDecimal value1; + + private BigDecimal value2; + + private BigDecimal value3; + + private BigDecimal value4; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateStrBigDecimalDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateStrBigDecimalDto.java new file mode 100644 index 0000000..87ca289 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateStrBigDecimalDto.java @@ -0,0 +1,22 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class DateStrBigDecimalDto implements Serializable { + + /** 时间 */ + private String date; + + /** 数据值 */ + private BigDecimal value; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateStrStringDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateStrStringDto.java new file mode 100644 index 0000000..6233804 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateStrStringDto.java @@ -0,0 +1,21 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class DateStrStringDto implements Serializable { + + /** 时间 */ + private String date; + + /** 数据值 */ + private String value; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateStringDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateStringDto.java new file mode 100644 index 0000000..57cbdc9 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DateStringDto.java @@ -0,0 +1,22 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class DateStringDto implements Serializable { + + /** 时间 */ + private Date date; + + /** 数据值 */ + private String value; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DistributionDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DistributionDto.java new file mode 100644 index 0000000..56c84f0 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/DistributionDto.java @@ -0,0 +1,25 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class DistributionDto implements Serializable { + + /** 算力范围低值 */ + private int low; + + /** 算力范围高值 */ + private int high; + + /** 矿工数 */ + private int count; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/GoogleInfoDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/GoogleInfoDto.java new file mode 100644 index 0000000..cf5a3d5 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/GoogleInfoDto.java @@ -0,0 +1,22 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class GoogleInfoDto implements Serializable { + + /** 谷歌验证码 */ + private String secret; + + /** 二维码 */ + private String img; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/IdsDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/IdsDto.java new file mode 100644 index 0000000..384c4e1 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/IdsDto.java @@ -0,0 +1,15 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 返回数据实体 + * @Date 2022/5/30 11:17 + * @Author 杜懿 + */ +@Data +public class IdsDto implements Serializable { + private String ids; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/IncomeDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/IncomeDto.java new file mode 100644 index 0000000..5267db1 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/IncomeDto.java @@ -0,0 +1,35 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 矿工信息总览表对象 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class IncomeDto implements Serializable { + + /** 时间 */ + private Date date; + + /** 难度 */ + private String difficult; + + /** 币种/矿池 */ + private String coin; + + /** 收益 */ + private BigDecimal amount; + + /** 备注 */ + private double mhs; + + private String comment; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/LuckDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/LuckDto.java new file mode 100644 index 0000000..3644878 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/LuckDto.java @@ -0,0 +1,28 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 幸运值 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class LuckDto implements Serializable { + + /** 3日幸运值 */ + private double luck3d; + + /** 7日幸运值 */ + private double luck7d; + + /** 30日幸运值 */ + private double luck30d; + + /** 90日幸运值 */ + private double luck90d; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerAccountBalanceDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerAccountBalanceDto.java new file mode 100644 index 0000000..db26abb --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerAccountBalanceDto.java @@ -0,0 +1,31 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 矿工信息总览表对象 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class MinerAccountBalanceDto implements Serializable { + + private long id; + + /** 挖矿账户 */ + private String ma; + + /** 提现收款地址 */ + private String balance; + + /** 是否激活 */ + private int active; + + private double amount; + + private String remark; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerCountLineDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerCountLineDto.java new file mode 100644 index 0000000..b52e1fc --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerCountLineDto.java @@ -0,0 +1,22 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 币种(矿池)矿工数量曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class MinerCountLineDto implements Serializable { + + /** 日期 */ + private Date date; + + /** 矿工数量 */ + private int value; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerDataDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerDataDto.java new file mode 100644 index 0000000..70e1ed2 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerDataDto.java @@ -0,0 +1,59 @@ +package com.m2pool.pool.dto; + +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.Data; + +import javax.naming.Name; +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 当前币种矿池信息 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class MinerDataDto implements Serializable { + + /** id */ + private long id; + + /** 时间 */ + private Date date; + + /** 挖矿账号 */ + private String user; + + /** 矿工名 */ + private String miner; + + private String refindex; + + /** 1小时平均算力 */ + private double mhs; + + /** 24小时平均算力 */ + private double mhs24h; + + /** 申请次数?通过次数? */ + private int accepts; + + /** 被拒次数 */ + private int rejects; + + /** 拒绝率 */ + //private int rejects_ratio; + @JsonAlias("rejects_ratio") + private double rejectsRatio; + + /** 最后一次提交的时间 */ + @JsonAlias("last_submit") + private Date lastSubmit; + + /** 1天内在线时间 */ + private String state; + + /** 是否在线 */ + private String online; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerDataInsertDBDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerDataInsertDBDto.java new file mode 100644 index 0000000..c068c0e --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerDataInsertDBDto.java @@ -0,0 +1,59 @@ +package com.m2pool.pool.dto; + +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 当前币种矿池信息 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class MinerDataInsertDBDto implements Serializable { + + /** id */ + private long id; + + /** 时间 */ + private String date; + + /** 挖矿账号 */ + private String user; + + /** 矿工名 */ + private String miner; + + /** 1小时平均算力 */ + private double mhs; + + /** 24小时平均算力 */ + private double mhs24h; + + ///** 申请次数?通过次数? */ + //private int accepts; + // + ///** 被拒次数 */ + //private int rejects; + // + ///** 拒绝率 */ + ////private int rejects_ratio; + //@JsonAlias("rejects_ratio") + //private double rejectsRatio; + + /** 最后一次提交的时间 */ + @JsonAlias("last_submit") + private String lastSubmit; + + ///** */ + //private double duration; + + /** 是否在线 */ + //private String online; + + private String state; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerDto.java new file mode 100644 index 0000000..91d028b --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerDto.java @@ -0,0 +1,37 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 矿工信息总览表对象 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class MinerDto implements Serializable { + + private String id; + + /** 矿工名 */ + private String miner; + + /** 1小时平均算力 */ + private double rate; + + /** 24小时平均算力 */ + private double dailyRate; + + /** 拒绝率 */ + private double reject; + + /** 最后提交时间 */ + private Date submit; + + private String status; + + private long offline = 0; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerListDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerListDto.java new file mode 100644 index 0000000..7db0020 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/MinerListDto.java @@ -0,0 +1,50 @@ +package com.m2pool.pool.dto; + +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * @Description 矿工信息总览表对象 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class MinerListDto implements Serializable { + + /** 总矿机数 */ + private int all; + + /** 在线矿机数 */ + private int online; + + /** 离线矿机数 */ + private int offline; + + /** 矿工名 */ + private String miner; + + /** 1小时平均算力 */ + private double rate; + + /** 24小时平均算力 */ + private double dailyRate; + + /** 拒绝率 */ + private double reject; + + private Date submit; + + /** 总条数 */ + private int total; + + /** 总页数 */ + private int page; + + /** 最后一次提交的时间 */ + private List rows; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/NoticeListDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/NoticeListDto.java new file mode 100644 index 0000000..32d447d --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/NoticeListDto.java @@ -0,0 +1,27 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 矿工信息总览表对象 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class NoticeListDto implements Serializable { + + /** 通知邮箱记录表id */ + private long id; + + /** 通知邮箱 */ + private String email; + + /** 币种/矿池 */ + private String remark; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/NoticeMinerCoinListDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/NoticeMinerCoinListDto.java new file mode 100644 index 0000000..e1d0244 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/NoticeMinerCoinListDto.java @@ -0,0 +1,28 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 矿工信息总览表对象 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class NoticeMinerCoinListDto implements Serializable { + + ///** 通知邮箱记录表id */ + //private long id; + + /** 通知邮箱 */ + private String email; + + /** 挖矿账户 */ + private String miner; + + ///** 币种 */ + //private String coin; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/OfflineUserMinerDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/OfflineUserMinerDto.java new file mode 100644 index 0000000..b0c2f9d --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/OfflineUserMinerDto.java @@ -0,0 +1,25 @@ +package com.m2pool.pool.dto; + + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class OfflineUserMinerDto implements Serializable { + + private Date date; + + private String user; + + private String miners; + + private int offline; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/OutcomeDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/OutcomeDto.java new file mode 100644 index 0000000..eb0ab90 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/OutcomeDto.java @@ -0,0 +1,34 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 支出表实体类 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class OutcomeDto implements Serializable { + + /** 时间 */ + private Date date; + + /** 提现地址 */ + private String address; + + /** 币种/矿池 */ + private String coin; + + /** 收益 */ + private double amount; + + /** 交易id */ + private String txid; + + private int status = 1; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PageInfoDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PageInfoDto.java new file mode 100644 index 0000000..996c879 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PageInfoDto.java @@ -0,0 +1,22 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 幸运值 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class PageInfoDto implements Serializable { + + /** 网址 */ + private String url; + + /** 页面标号 */ + private String key; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PageUrlDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PageUrlDto.java new file mode 100644 index 0000000..3fbe801 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PageUrlDto.java @@ -0,0 +1,22 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 幸运值 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class PageUrlDto implements Serializable { + + /** 网址 */ + private String url; + + /** 页面标号 */ + private String key; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PoolInfoDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PoolInfoDto.java new file mode 100644 index 0000000..9603fec --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PoolInfoDto.java @@ -0,0 +1,49 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 当前币种矿池信息 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class PoolInfoDto implements Serializable { + + /** 币种参数名 */ + private String coin; + + /** 页面显示名 */ + private String name; + + /** 算法 */ + private String algorithm; + + /** 全网算力 */ + private String totalPower; + + /** 全网难度 */ + private String totalDifficulty; + + /** 币价 */ + private String price; + + /** 当前高度 */ + private long height; + + /** 矿池算力 */ + private String poolPower; + + /** 矿工数 */ + private String poolMc; + + /** 收费模式 **/ + private String model ="PPLNS"; + + /** 费率% 默认为1%**/ + private int fee = 1; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PowerLineDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PowerLineDto.java new file mode 100644 index 0000000..8b579aa --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PowerLineDto.java @@ -0,0 +1,28 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class PowerLineDto implements Serializable { + + /** 日期 */ + private Date date; + + /** 矿工算力 */ + private BigDecimal pv; + + /** 拒绝率 */ + private BigDecimal rejectRate; + + private double price; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PrivateTicketListDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PrivateTicketListDto.java new file mode 100644 index 0000000..503b154 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/PrivateTicketListDto.java @@ -0,0 +1,23 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2022/6/20 17:44 + * @Author 杜懿 + */ +@Data +public class PrivateTicketListDto { + + private int id; + + private Date date; + + private String email; + + private String status; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/ProfitDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/ProfitDto.java new file mode 100644 index 0000000..5ea82aa --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/ProfitDto.java @@ -0,0 +1,27 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 收益 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class ProfitDto implements Serializable { + + /** 算力范围低值 */ + private long id; + + /** 结算时间 */ + private Date date; + + /** 收益 */ + private BigDecimal profit; + + private int status; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/ReadOnlyPageInfo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/ReadOnlyPageInfo.java new file mode 100644 index 0000000..35c07fe --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/ReadOnlyPageInfo.java @@ -0,0 +1,29 @@ +package com.m2pool.pool.dto; + + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class ReadOnlyPageInfo implements Serializable { + + /** 只读页面表id */ + private long id; + + /** 权限配置 */ + private String[] config; + + private String coin; + + private String account; + + private String img; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/SysFileDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/SysFileDto.java new file mode 100644 index 0000000..7e1c2cc --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/SysFileDto.java @@ -0,0 +1,29 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 返回数据实体 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class SysFileDto implements Serializable { + + /** 多文件拼接 */ + private String id; + + /** + * 文件名称 + */ + private String name; + + /** + * 文件地址 + */ + private String url; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketContentDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketContentDto.java new file mode 100644 index 0000000..cb306f9 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketContentDto.java @@ -0,0 +1,31 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2022/6/20 17:44 + * @Author 杜懿 + */ +@Data +public class TicketContentDto { + + + private Date time; + + private String content; + + private String name; + + private String files; + + private String videoPath; + + private String audioPath; + + private String imagePath; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketDto.java new file mode 100644 index 0000000..1d59a15 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketDto.java @@ -0,0 +1,25 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2022/6/20 17:44 + * @Author 杜懿 + */ +@Data +public class TicketDto { + + private int id; + + private String email; + + private String desc; + + private Date createTime; + + private int status; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketListDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketListDto.java new file mode 100644 index 0000000..075e90b --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketListDto.java @@ -0,0 +1,23 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2022/6/20 17:44 + * @Author 杜懿 + */ +@Data +public class TicketListDto { + + private int id; + + private Date date; + + private String email; + + private String status; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketPrivateDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketPrivateDto.java new file mode 100644 index 0000000..d09c179 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/TicketPrivateDto.java @@ -0,0 +1,29 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * @Description TODO + * @Date 2022/6/20 17:44 + * @Author 杜懿 + */ +@Data +public class TicketPrivateDto { + + private int id; + + private String email; + + private String desc; + + private Date createTime; + + private String status; + + private List list; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountDto.java new file mode 100644 index 0000000..85515bc --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountDto.java @@ -0,0 +1,27 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 幸运值 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class UserAccountDto implements Serializable { + + private long id; + + /** 网站用户名 */ + private String user; + + /** 挖矿账户名 */ + private String account; + + /** 币种 */ + private String coin; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountGradeDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountGradeDto.java new file mode 100644 index 0000000..f16d0e7 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountGradeDto.java @@ -0,0 +1,20 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 幸运值 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class UserAccountGradeDto implements Serializable { + + private long id; + + /** 挖矿账户名 */ + private String account; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountGradeListDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountGradeListDto.java new file mode 100644 index 0000000..9de6d3e --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountGradeListDto.java @@ -0,0 +1,27 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @Description 幸运值 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class UserAccountGradeListDto implements Serializable { + + + /** 币种 */ + private String coin; + + /** 矿池展示名 */ + private String title; + + /** 图片地址 */ + private String img; + + private List children; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountListDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountListDto.java new file mode 100644 index 0000000..bfc7072 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserAccountListDto.java @@ -0,0 +1,35 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 幸运值 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class UserAccountListDto implements Serializable { + + private long id; + + /** 挖矿账户名 */ + private String ma; + + /** 币种 */ + private String coin; + + private String pool; + + /** 备注 */ + private String remark; + + private String img; + + private String addr; + + private int addrStatus; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserApiDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserApiDto.java new file mode 100644 index 0000000..6d8da8c --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserApiDto.java @@ -0,0 +1,28 @@ +package com.m2pool.pool.dto; + + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class UserApiDto implements Serializable { + + /** 只读页面表id */ + private long id; + + private String user; + + private String key; + + private String ip; + + /** 权限 */ + private String[] perms; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserApiInfoReturnDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserApiInfoReturnDto.java new file mode 100644 index 0000000..94283b6 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserApiInfoReturnDto.java @@ -0,0 +1,25 @@ +package com.m2pool.pool.dto; + + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class UserApiInfoReturnDto implements Serializable { + + /** 只读页面表id */ + private long id; + + /** 页面请求路径 */ + private String ip; + + /** 权限配置 */ + private String[] perms; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserPowerDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserPowerDto.java new file mode 100644 index 0000000..438eb28 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserPowerDto.java @@ -0,0 +1,23 @@ +package com.m2pool.pool.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 曲线图数据结构 + * @Date 2024/6/13 11:17 + * @Author dy + */ +@Data +public class UserPowerDto implements Serializable { + + /** 挖矿账户名 */ + private String user; + + /** 矿池算力 */ + private BigDecimal mhs; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserReadPageInfoReturnDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserReadPageInfoReturnDto.java new file mode 100644 index 0000000..dd5a4c9 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/dto/UserReadPageInfoReturnDto.java @@ -0,0 +1,44 @@ +package com.m2pool.pool.dto; + + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class UserReadPageInfoReturnDto implements Serializable { + + /** 只读页面表id */ + private long id; + + /** 只读页面编码 */ + private String key; + + private String user; + + /** 页面请求路径 */ + private String url; + + private String img; + + /** 挖矿账户 */ + private String account; + + /** 币种 */ + private String coin; + + /** 币种展示名*/ + private String label; + + /** 权限配置 */ + private String[] config; + + /** 备注 */ + private String remark; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/BlockInfo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/BlockInfo.java new file mode 100644 index 0000000..ed6b265 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/BlockInfo.java @@ -0,0 +1,36 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class BlockInfo implements Serializable { + + /** 区块高度 */ + private long height; + + /** 全网难度 */ + private BigDecimal difficulty; + + /** 全网算力 */ + private BigDecimal power; + + /** 报块奖励 */ + private BigDecimal reward; + + /** 报块手续费 */ + private BigDecimal fees; + + /** 报块实际收益 */ + private BigDecimal profit; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/GoogleInfo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/GoogleInfo.java new file mode 100644 index 0000000..591dd6c --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/GoogleInfo.java @@ -0,0 +1,26 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class GoogleInfo implements Serializable { + + /** 邮箱 */ + private String email; + + /** 谷歌验证码 */ + private String secret; + + /** 谷歌验证码 */ + private int status; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/M2File.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/M2File.java new file mode 100644 index 0000000..7576f03 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/M2File.java @@ -0,0 +1,33 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.util.Date; + +/** + * @Description + * @Date 2024/11/19 15:57 + * @Author 杜懿 + */ +@Data +public class M2File { + + /** id */ + private Long id; + + /** 时间 */ + private Date createTime; + + /** value */ + private String fileName; + + private String url; + + // 0 图片 1音频 2视频 + private int fileType; + + + private String userName; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/M2PoolFile.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/M2PoolFile.java new file mode 100644 index 0000000..1e23038 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/M2PoolFile.java @@ -0,0 +1,30 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.util.Date; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class M2PoolFile { + + /** id */ + private Long id; + + /** 时间 */ + private Date createTime; + + /** value */ + private String fileName; + + private String url; + + + private String userName; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/NotifyMsgAlph.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/NotifyMsgAlph.java new file mode 100644 index 0000000..d57c329 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/NotifyMsgAlph.java @@ -0,0 +1,19 @@ +//package com.m2pool.pool.entity; +// +//import lombok.Data; +// +///** +// * @Description TODO +// * @Date 2025/3/21 18:16 +// * @Author 杜懿 +// */ +//@Data +//public class NotifyMsgAlph { +// //ID interface{} `json:"id"` +// //Method string `json:"method"` +// //Params [5]interface{} `json:"params"` +// +// private Object id; +// private String method; +// private Object[] params; +//} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/NotifyMsgParams.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/NotifyMsgParams.java new file mode 100644 index 0000000..530e193 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/NotifyMsgParams.java @@ -0,0 +1,47 @@ +package com.m2pool.pool.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @Description TODO + * @Date 2025/4/3 12:47 + * @Author 杜懿 + */ +@Data +public class NotifyMsgParams { + + @JsonProperty("jobId") + public String jobId; + + //jobId + @JsonProperty("fromGroup") + public int fromGroup; + //header + @JsonProperty("toGroup") + public int toGroup; + //header + @JsonProperty("headerBlob") + public String header; + + @JsonProperty("txsBlob") + public String txs; + + @JsonProperty("targetBlob") + public String target; + + @JsonProperty("height") + public long height; + + @JsonProperty("diff") + public double diff; + + @JsonProperty("nonce") + public String nonce; + + @JsonProperty("extranonce1") + public String extranonce1; + + @JsonProperty("extranonce2_size") + public long extranonce2Size; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/OpenApiKey.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/OpenApiKey.java new file mode 100644 index 0000000..14e98b5 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/OpenApiKey.java @@ -0,0 +1,29 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Set; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class OpenApiKey implements Serializable { + + /** 用户唯一标识 */ + private String key; + + /** 用户邮箱 */ + private String user; + + /** apiKey绑定的ip */ + private String apiIp; + + private String permissions; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/PoolPower.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/PoolPower.java new file mode 100644 index 0000000..7a5ab0e --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/PoolPower.java @@ -0,0 +1,36 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class PoolPower implements Serializable { + + /** 日期 */ + private Date date; + + /** 日期str 入库要用String */ + private String dateStr; + + /** 矿池算力 */ + private BigDecimal mhs; + + /** 总矿工数 */ + private int miners; + + /** 在线矿工数 */ + private int online; + + /** 离线矿工数 */ + private int offline; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/TempMiner.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/TempMiner.java new file mode 100644 index 0000000..3703335 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/TempMiner.java @@ -0,0 +1,44 @@ +package com.m2pool.pool.entity; + +import lombok.Data; + +import java.io.OutputStream; +import java.math.BigDecimal; +import java.net.HttpURLConnection; +import java.net.Socket; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; + +/** + * @Description TODO + * @Date 2025/3/17 10:00 + * @Author 杜懿 + */ +@Data +public class TempMiner { + public String coin; + public String pool; + public String user; + public String password; + public int id; + public String state; + // net.conn + public OutputStream conn; + public String extraNonce1; + public int extraNonce2Size; + public double difficulty; + public String jobId; + public String header; + public String nbits; + //64位无符号整数 + public BigDecimal jobTime; + public String blockData; + //64位无符号整数 + public long nonce; + public long blockNonce; + //chan bool + private final BlockingQueue newJobCh = new LinkedBlockingDeque<>(); + public volatile boolean running = false; + public volatile boolean newJob = false; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/Ticket.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/Ticket.java new file mode 100644 index 0000000..36673b2 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/Ticket.java @@ -0,0 +1,27 @@ +package com.m2pool.pool.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2022/6/20 17:44 + * @Author 杜懿 + */ +@Data +public class Ticket { + + private int id; + + private String email; + + private Date createTime; + + private int status; + + //问题描述 + private String desc; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserApi.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserApi.java new file mode 100644 index 0000000..867ddd2 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserApi.java @@ -0,0 +1,28 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class UserApi implements Serializable { + + /** 只读页面表id */ + private long id; + + private String user; + + private String key; + + private String ip; + + /** 权限 */ + private String perms; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserGoogleAuthInfo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserGoogleAuthInfo.java new file mode 100644 index 0000000..1e1fcdd --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserGoogleAuthInfo.java @@ -0,0 +1,29 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class UserGoogleAuthInfo implements Serializable { + + /** 邮箱 */ + private String email; + + /** 谷歌验证码 */ + private String secret; + + private String pwd; + + /** 谷歌验证码 */ + private int status; + + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserMinerAccount.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserMinerAccount.java new file mode 100644 index 0000000..5a5508e --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserMinerAccount.java @@ -0,0 +1,31 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.util.Date; + +/** + * @Description 用户网站账户-用户挖矿账号名数据 + * 对应数据库user_miner_account数据 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class UserMinerAccount { + + private long id; + + /** 网站账号名 */ + private String user; + + /** 挖矿账号名 */ + private String ma; + + /** 对应矿池 参数名 */ + private String coin; + + private String remark; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserReadOnlyPage.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserReadOnlyPage.java new file mode 100644 index 0000000..5a2d0ef --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserReadOnlyPage.java @@ -0,0 +1,45 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class UserReadOnlyPage implements Serializable { + + private long id; + + /** 邮箱 */ + private String email; + + /** 币种 */ + private String coin; + + /** 挖矿账户 */ + private String account; + + /** 只读页面key */ + private String key; + + /** 页面权限配置 */ + private String config; + + /** 备注 */ + private String remark; + + private String lang; + + /** 创建时间 */ + private Date createTime; + + /** 修改时间 */ + private Date updateTime; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserReadOnlyPageInfo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserReadOnlyPageInfo.java new file mode 100644 index 0000000..3c01b3b --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/entity/UserReadOnlyPageInfo.java @@ -0,0 +1,42 @@ +package com.m2pool.pool.entity; + + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class UserReadOnlyPageInfo implements Serializable { + + /** 只读页面表id */ + private long id; + + /** 只读页面编码 */ + private String key; + + private String user; + + /** 页面请求路径 */ + private String url; + + /** 挖矿账户 */ + private String account; + + /** 币种 */ + private String coin; + + /** 权限配置 */ + private String config; + + /** 备注 */ + private String remark; + + private String lang; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/BalanceUseStatus.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/BalanceUseStatus.java new file mode 100644 index 0000000..18525eb --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/BalanceUseStatus.java @@ -0,0 +1,39 @@ +package com.m2pool.pool.enums; + +/** + * @Description 矿池 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum BalanceUseStatus { + + START(0, "正常"), + DELETE(99, "停用"); + + /** 币种参数名 */ + private final int code; + /** 页面显示名 */ + private final String status; + + BalanceUseStatus(int code, String status) { + this.code = code; + this.status = status; + } + + public int getCode() { + return code; + } + + public String getStatus() { + return status; + } + + public static String getStatusByCode(int code){ + for (BalanceUseStatus t : BalanceUseStatus.values()) { + if(t.getCode()==code){ + return t.getStatus(); + } + } + return ""; + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/MinerAccountStatus.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/MinerAccountStatus.java new file mode 100644 index 0000000..511e1a5 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/MinerAccountStatus.java @@ -0,0 +1,39 @@ +package com.m2pool.pool.enums; + +/** + * @Description 矿池 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum MinerAccountStatus { + + START(0, "正常"), + DELETE(11, "停用"); + + /** 币种参数名 */ + private final int code; + /** 页面显示名 */ + private final String status; + + MinerAccountStatus(int code, String status) { + this.code = code; + this.status = status; + } + + public int getCode() { + return code; + } + + public String getStatus() { + return status; + } + + public static String getStatusByCode(int code){ + for (MinerAccountStatus t : MinerAccountStatus.values()) { + if(t.getCode()==code){ + return t.getStatus(); + } + } + return ""; + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/NodeConfig.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/NodeConfig.java new file mode 100644 index 0000000..87b5030 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/NodeConfig.java @@ -0,0 +1,63 @@ +package com.m2pool.pool.enums; + +/** + * @Description 矿池 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum NodeConfig { + + GRS("grs", "13.213.4.56", "1441", "test", "test"), + MONA("mona", "13.213.4.56", "9402", "test", "test"), + DGBS("dgbs", "13.214.176.64", "14022", "test", "test"), + DGBQ("dgbq", "13.214.176.64", "14022", "test", "test"), + DGBO("dgbo", "13.214.176.64", "14022", "test", "test"), + DGB_QUBIT_A10("dgb_qubit_a10", "127.0.0.1", "3306", "user", "pwd"), + DGB_SKEIN_A10("dgb_skein_a10", "127.0.0.1", "3306", "user", "pwd"), + DGB_ODO_B20("dgb_odo_b20", "127.0.0.1", "3306", "user", "pwd"), + NEXA("nexa", "18.139.85.190", "7227", "test", "test"), + RXD("rxd", "13.214.176.64", "7332", "test", "test"), + ALPH("alph", "18.141.161.129", "12973", "test", "test"), + ENX("enx", "13.214.6.253", "12973", "test", "test"); + + /** 币种参数名 */ + private final String coin; + /** 页面显示名 */ + private final String ip; + /** 算法 */ + private final String host; + + private final String user; + + private final String pwd; + + + NodeConfig(String coin, String ip, String host, String user, String pwd) { + this.coin = coin; + this.ip = ip; + this.host = host; + this.user = user; + this.pwd = pwd; + } + + public String getCoin() { + return coin; + } + + public String getIp() { + return ip; + } + + public String getHost() { + return host; + } + + public String getUser() { + return user; + } + + public String getPwd() { + return pwd; + } + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/NodeConstant.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/NodeConstant.java new file mode 100644 index 0000000..7c3151b --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/NodeConstant.java @@ -0,0 +1,45 @@ +package com.m2pool.pool.enums; + +import java.math.BigDecimal; + +/** + * @Description 矿池 相关的 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum NodeConstant { + + GRS("grs", BigDecimal.valueOf(Math.pow(2,32)).divide(BigDecimal.valueOf(60),2,BigDecimal.ROUND_HALF_UP)), + MONA("mona", BigDecimal.valueOf(Math.pow(2,32)).divide(BigDecimal.valueOf(90),2,BigDecimal.ROUND_HALF_UP)), + DGBS("dgbs", BigDecimal.valueOf(Math.pow(2,32)).divide(BigDecimal.valueOf(73.2),2,BigDecimal.ROUND_HALF_UP)), + DGBQ("dgbq", BigDecimal.valueOf(Math.pow(2,32)).divide(BigDecimal.valueOf(73.2),2,BigDecimal.ROUND_HALF_UP)), + DGBO("dgbo", BigDecimal.valueOf(Math.pow(2,32)).divide(BigDecimal.valueOf(73.2),2,BigDecimal.ROUND_HALF_UP)), + DGB_QUBIT_A10("dgb_qubit_a10", BigDecimal.valueOf(Math.pow(2,32)).divide(BigDecimal.valueOf(120),2,BigDecimal.ROUND_HALF_UP)), + DGB_SKEIN_A10("dgb_skein_a10", BigDecimal.valueOf(Math.pow(2,32)).divide(BigDecimal.valueOf(120),2,BigDecimal.ROUND_HALF_UP)), + DGB_ODO_B20("dgb_odo_b20", BigDecimal.valueOf(Math.pow(2,32)).divide(BigDecimal.valueOf(120),2,BigDecimal.ROUND_HALF_UP)), + NEXA("nexa", BigDecimal.valueOf(Math.pow(2,32)).subtract(BigDecimal.ONE).divide(BigDecimal.valueOf(120),2,BigDecimal.ROUND_HALF_UP)), + RXD("rxd", BigDecimal.valueOf(Math.pow(2,32)).subtract(BigDecimal.ONE).divide(BigDecimal.valueOf(300),2,BigDecimal.ROUND_HALF_UP)), + ALPH("alph", BigDecimal.valueOf(Math.pow(2,32)).subtract(BigDecimal.ONE).divide(BigDecimal.valueOf(16),2,BigDecimal.ROUND_HALF_UP)), + ENX("enx", BigDecimal.valueOf(Math.pow(2,32)).subtract(BigDecimal.ONE).divide(BigDecimal.valueOf(1),2,BigDecimal.ROUND_HALF_UP)); + + /** 币种参数名 */ + private final String coin; + /** 换算系数 */ + private final BigDecimal factor; + + + + NodeConstant(String coin, BigDecimal factor) { + this.coin = coin; + this.factor = factor; + } + + public String getCoin() { + return coin; + } + + public BigDecimal getFactor() { + return factor; + } + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolAmount.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolAmount.java new file mode 100644 index 0000000..6c0f527 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolAmount.java @@ -0,0 +1,49 @@ +package com.m2pool.pool.enums; + +/** + * @Description 矿池默认起付额 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum PoolAmount { + + GRS("grs", 1), + MONA("mona", 1), + DGBS("dgbs", 1), + DGBQ("dgbq", 1), + DGBO("dgbo", 1), + DGB_QUBIT_A10("dgb_qubit_a10", 100), + DGB_SKEIN_A10("dgb_skein_a10", 100), + DGB_ODO_B20("dgb_odo_b20", 100), + NEXA("nexa", 10000), + RXD("rxd", 100), + ALPH("alph", 1), + ENX("enx", 5000); + + /** 币种参数名 */ + private final String coin; + /** 单位 */ + private final double amount; + + + PoolAmount(String coin, double amount) { + this.coin = coin; + this.amount = amount; + } + + public String getCoin() { + return coin; + } + + public double getAmount(){return amount;} + + public static double getAmountByCoin(String coin){ + for (PoolAmount t : PoolAmount.values()) { + if(t.getCoin().equals(coin)){ + return t.getAmount(); + } + } + return 10000; + } + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolCalParamConfig.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolCalParamConfig.java new file mode 100644 index 0000000..79c2df6 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolCalParamConfig.java @@ -0,0 +1,59 @@ +package com.m2pool.pool.enums; + +import java.math.BigDecimal; + +/** + * @Description 矿池 收益计数器相关常量 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum PoolCalParamConfig { + + GRS("grs", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(1440),BigDecimal.valueOf(0.01)), + MONA("mona", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(960),BigDecimal.valueOf(0.01)), + DGBS("dgbs", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(1180),BigDecimal.valueOf(0.01)), + DGBQ("dgbq", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(1180),BigDecimal.valueOf(0.01)), + DGBO("dgbo", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(1180),BigDecimal.valueOf(0.01)), + DGB2_ODO("dgb2_odo", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(720),BigDecimal.valueOf(0.01)), + DGB_QUBIT_A10("dgb_qubit_a10", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(720),BigDecimal.valueOf(0.01)), + DGB_SKEIN_A10("dgb_skein_a10", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(720),BigDecimal.valueOf(0.01)), + DGB_ODO_B20("dgb_odo_b20", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(720),BigDecimal.valueOf(0.01)), + NEXA("nexa", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(720),BigDecimal.valueOf(0.01)), + RXD("rxd", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(288),BigDecimal.valueOf(0.01)), + ALPH("alph", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(5400),BigDecimal.valueOf(0.01)), + ENX("enx", BigDecimal.valueOf(Math.pow(2,32)), BigDecimal.valueOf(86400),BigDecimal.valueOf(0.00)); + + /** 币种参数名 */ + private final String coin; + + /** 报块所需哈希次数 难度为1时 */ + private final BigDecimal hashTime; + + /** 每日出块数 s*/ + private final BigDecimal count; + + private final BigDecimal poolFees; + + PoolCalParamConfig(String coin, BigDecimal hashTime, BigDecimal count, BigDecimal poolFees) { + this.coin = coin; + this.hashTime = hashTime; + this.count = count; + this.poolFees = poolFees; + } + + public String getCoin() { + return coin; + } + + public BigDecimal getHashTime() { + return hashTime; + } + + public BigDecimal getCount() { + return count; + } + + public BigDecimal getPoolFees() { + return poolFees; + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolProfitScale.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolProfitScale.java new file mode 100644 index 0000000..685939e --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolProfitScale.java @@ -0,0 +1,51 @@ +package com.m2pool.pool.enums; + +/** + * @Description 矿池单位换算 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum PoolProfitScale { + + GRS("grs", 2), + MONA("mona", 2), + DGBS("dgbs", 2), + DGBQ("dgbq", 2), + DGBO("dgbo", 2), + DGB2_ODO("dgb2_odo", 2), + DGB_QUBIT_A10("dgb_qubit_a10", 2), + DGB_SKEIN_A10("dgb_skein_a10", 2), + DGB_ODO_B20("dgb_odo_b20", 2), + NEXA("nexa", 2), + RXD("rxd", 2), + ALPH("alph", 2), + ENX("enx", 2); + + /** 币种参数名 */ + private final String coin; + + private final int scale ; + + + PoolProfitScale(String coin, int scale) { + this.coin = coin; + this.scale = scale; + } + + public String getCoin() { + return coin; + } + + public int getScale() { + return scale; + } + + public static int getScaleByCoin(String coin){ + for (PoolProfitScale t : PoolProfitScale.values()) { + if(t.getCoin().equals(coin)){ + return t.getScale(); + } + } + return 8; + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolUnits.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolUnits.java new file mode 100644 index 0000000..c3cbf83 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/PoolUnits.java @@ -0,0 +1,62 @@ +package com.m2pool.pool.enums; + +/** + * @Description 矿池单位换算 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum PoolUnits { + + GRS("grs", "MH/s", 1000*1000, 1000), + MONA("mona", "MH/s", 1000*1000, 1000), + DGBS("dgbs", "MH/s", 1000*1000, 1000), + DGBQ("dgbq", "MH/s", 1000*1000, 1000), + DGBO("dgbo", "MH/s", 1000*1000, 1000), + DGB2_ODO("dgb2_odo", "MH/s", 1000*1000, 1000), + DGB_QUBIT_A10("dgb_qubit_a10", "MH/s", 1000*1000, 1000), + DGB_SKEIN_A10("dgb_skein_a10", "MH/s", 1000*1000, 1000), + DGB_ODO_B20("dgb_odo_b20", "MH/s", 1000*1000, 1000), + NEXA("nexa", "MH/s", 1000*1000, 1000), + RXD("rxd", "MH/s", 1000*1000, 1000), + ALPH("alph", "MH/s", 1000*1000, 1000), + ENX("enx", "MH/s", 1000*1000, 1000); + + /** 币种参数名 */ + private final String coin; + /** 矿池数据单位 */ + private final String unit; + /** 换算率 数据库原始数据换算为H/s的换算率 */ + private final int hRate ; + + /** 换算率 数据库原始数据换算为H/s的换算率 */ + private final int gRate ; + + + + + PoolUnits(String coin, String unit, int hRate, int gRate) { + this.coin = coin; + this.unit = unit; + this.hRate = hRate; + this.gRate = gRate; + } + + public String getCoin() { + return coin; + } + + public String getUnit() { + return unit; + } + + public int gethRate() { + return hRate; + } + + public int getgRate() { + return gRate; + } + + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/Pools.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/Pools.java new file mode 100644 index 0000000..12b1e42 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/Pools.java @@ -0,0 +1,104 @@ +package com.m2pool.pool.enums; + +/** + * @Description 矿池 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum Pools { + + GRS("grs", "grs", "groestl", "grs_pool_blkstats", "grs_mhs", "grs_pool","grs.svg",0.985), + MONA("mona", "mona", "Lyra2REv2", "mona_pool_blkstats", "mona_mhs", "mona_pool","mona.svg",0.985), + DGBS("dgbs", "dgb(skein)", "DigiByte(Skein)", "dgbs_pool_blkstats", "dgbs_mhs", "dgbs_pool","dgb.svg",0.985), + DGBQ("dgbq", "dgb(qubit)", "DigiByte(Qubit)", "dgbq_pool_blkstats", "dgbq_mhs", "dgbq_pool","dgb.svg",0.985), + DGBO("dgbo", "dgb(odocrypt)", "DigiByte(Odocrypt)", "dgbo_pool_blkstats", "dgbo_mhs", "dgbo_pool","dgb.svg",0.985), + //DGB2_ODO("dgb2_odo", "dgb-odocrypt-pool2", "odocrypt", "dgb2_odo_pool_blkstats", "dgb2_odo_miners", "dgb2_odo_pool"), + DGB_QUBIT_A10("dgb_qubit_a10", "dgb-qubit-pool2", "qubit", "dgb_qubit_a10_pool_blkstats", "dgb_qubit_a10_miners", "dgb_qubit_a10_pool","dgb.svg",0.985), + DGB_SKEIN_A10("dgb_skein_a10", "dgb-skein-pool2", "skein", "dgb_skein_a10_pool_blkstats", "dgb_skein_a10_miners", "dgb_skein_a10_pool","dgb.svg",0.985), + DGB_ODO_B20("dgb_odo_b20", "dgb-odoscrypt-pool3", "odocrypt", "dgb_odo_b20_pool_blkstats", "dgb_odo_b20_miners", "dgb_odo_b20_pool","dgb.svg",0.985), + NEXA("nexa", "nexa", "NexaPow", "nexa_pool_blkstats", "nexa_mhs", "nexa_pool","nexa.png",0.985), + RXD("rxd", "Radiant", "Sha512256D", "rxd_pool_blkstats", "rxd_mhs", "rxd_pool","rxd.png",0.985), + ALPH("alph", "Alephium", "Blake3", "alph_pool_blkstats", "alph_mhs", "alph_pool","alph.svg",0.985), + ENX("enx", "entropyx", "kHeavyHash", "enx_pool_blkstats", "enx_mhs", "enx_pool","enx.svg",0.985); + + + /** 币种参数名 */ + private final String coin; + /** 页面显示名 */ + private final String name; + /** 算法 */ + private final String algorithm; + /** 报块数据表名 */ + private final String blkTable; + /** 矿机表名 */ + private final String mhs; + /** 矿池表名 */ + private final String poolTable; + + private final String img; + + private final double fac; + + Pools(String coin, String name, String algorithm, String blkTable, String mhs, String poolTable,String img,double fac) { + this.coin = coin; + this.name = name; + this.algorithm = algorithm; + this.blkTable = blkTable; + this.mhs = mhs; + this.poolTable = poolTable; + this.img = img; + this.fac = fac; + } + + public String getCoin() { + return coin; + } + + public String getName() { + return name; + } + + public String getAlgorithm() { + return algorithm; + } + + public String getBlkTable() { + return blkTable; + } + + public String getMhs() { + return mhs; + } + + public String getPoolTable() { + return poolTable; + } + + public String getImg() { + return img; + } + + public double getFac() { + return fac; + } + + + + public static String getNameByCoin(String coin){ + for (Pools t : Pools.values()) { + if(t.getCoin().equals(coin)){ + return t.getName(); + } + } + return ""; + } + + public static String getImgByCoin(String coin){ + for (Pools t : Pools.values()) { + if(t.getCoin().equals(coin)){ + return t.getImg(); + } + } + return ""; + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/TicketStatusType.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/TicketStatusType.java new file mode 100644 index 0000000..eb43823 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/enums/TicketStatusType.java @@ -0,0 +1,44 @@ +package com.m2pool.pool.enums; + +/** + * @Description 工单状态枚举类 + * @Date 2022/5/20 11:15 + * @Author 杜懿 + */ +public enum TicketStatusType { +//ps:4是后台人员已回复用户但用户还未读 13是用户已读 + STATUS_NULL(0,""), + STATUS_ONE(1,"待处理"), + STATUS_TWO(2,"客服处理中"), + STATUS_TEN(10,"已完结"); + + private final int code; + private final String status; + + TicketStatusType(int code, String status) + { + this.code = code; + this.status = status; + } + + public int getCode() + { + return code; + } + + public String getStatus() + { + return status; + } + + public static String getStatusByCode(int code){ + for (TicketStatusType t : TicketStatusType.values()) { + if(t.getCode()==code){ + return t.getStatus(); + } + } + return ""; + } + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/factory/HttpsClientRequestFactory.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/factory/HttpsClientRequestFactory.java new file mode 100644 index 0000000..92ffb22 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/factory/HttpsClientRequestFactory.java @@ -0,0 +1,133 @@ +package com.m2pool.pool.factory; + +import org.springframework.http.client.SimpleClientHttpRequestFactory; + +import javax.net.ssl.*; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.Socket; +import java.security.cert.X509Certificate; + +/** + * @Description TODO + * @Date 2024/6/14 11:47 + * @Author dy + */ +public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory{ + + + @Override + protected void prepareConnection(HttpURLConnection connection, String httpMethod) { + try { + if (!(connection instanceof HttpsURLConnection)) { + throw new RuntimeException("An instance of HttpsURLConnection is expected"); + } + + HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; + + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + + } + }; + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory())); + + httpsConnection.setHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } + }); + + super.prepareConnection(httpsConnection, httpMethod); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"}); + * see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section) + */ + // SSLSocketFactory用于创建 SSLSockets + private static class MyCustomSSLSocketFactory extends SSLSocketFactory { + + private final SSLSocketFactory delegate; + + public MyCustomSSLSocketFactory(SSLSocketFactory delegate) { + this.delegate = delegate; + } + + // 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。 + // 这些默认的服务的最低质量要求保密保护和服务器身份验证 + @Override + public String[] getDefaultCipherSuites() { + return delegate.getDefaultCipherSuites(); + } + + // 返回的密码套件可用于SSL连接启用的名字 + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + + @Override + public Socket createSocket(final Socket socket, final String host, final int port, + final boolean autoClose) throws IOException { + final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose); + return overrideProtocol(underlyingSocket); + } + + + @Override + public Socket createSocket(final String host, final int port) throws IOException { + final Socket underlyingSocket = delegate.createSocket(host, port); + return overrideProtocol(underlyingSocket); + } + + @Override + public Socket createSocket(final String host, final int port, final InetAddress localAddress, + final int localPort) throws + IOException { + final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort); + return overrideProtocol(underlyingSocket); + } + + @Override + public Socket createSocket(final InetAddress host, final int port) throws IOException { + final Socket underlyingSocket = delegate.createSocket(host, port); + return overrideProtocol(underlyingSocket); + } + + @Override + public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, + final int localPort) throws + IOException { + final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort); + return overrideProtocol(underlyingSocket); + } + + private Socket overrideProtocol(final Socket socket) { + if (!(socket instanceof SSLSocket)) { + throw new RuntimeException("An instance of SSLSocket is expected"); + } + ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1"}); + return socket; + } + } +} + diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/factory/IgnoreSSLRequestFactory.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/factory/IgnoreSSLRequestFactory.java new file mode 100644 index 0000000..477b84a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/factory/IgnoreSSLRequestFactory.java @@ -0,0 +1,85 @@ +package com.m2pool.pool.factory; + + +import org.apache.http.client.HttpClient; +import org.springframework.http.client.SimpleClientHttpRequestFactory; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * @Description TODO + * @Date 2024/6/14 14:56 + * @Author dy + */ +public class IgnoreSSLRequestFactory extends SimpleClientHttpRequestFactory { + + public IgnoreSSLRequestFactory(HttpClient httpClient) { + super(); + } + + @Override + protected void prepareConnection(HttpURLConnection connection, String httpMethod) + throws IOException { + if (connection instanceof HttpsURLConnection) { + prepareHttpsConnection((HttpsURLConnection) connection); + } + connection.setConnectTimeout(3000); + connection.setReadTimeout(3000); + super.prepareConnection(connection, httpMethod); + } + + private void prepareHttpsConnection(HttpsURLConnection connection) { + connection.setHostnameVerifier(new SkipHostnameVerifier()); + try { + connection.setSSLSocketFactory(createSslSocketFactory()); + } catch (Exception ex) { + // Ignore + } + } + + private SSLSocketFactory createSslSocketFactory() throws Exception { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, new TrustManager[]{new SkipX509TrustManager()}, + new SecureRandom()); + return context.getSocketFactory(); + } + + private class SkipHostnameVerifier implements HostnameVerifier { + + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } + + } + + private static class SkipX509TrustManager implements X509TrustManager { + + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/AccountDistributionMapper.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/AccountDistributionMapper.java new file mode 100644 index 0000000..657fff4 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/AccountDistributionMapper.java @@ -0,0 +1,31 @@ +package com.m2pool.pool.mapper; + +import com.m2pool.common.datasource.annotation.DistributionDB; +import com.m2pool.pool.dto.ProfitDto; +import com.m2pool.pool.dto.OutcomeDto; +import org.apache.ibatis.annotations.Param; + +import java.math.BigDecimal; +import java.util.List; + +/** + * @Description 挖矿账号收益、提现 + * @Date 2022/8/30 17:47 + * @Author 杜懿 + */ +@DistributionDB +public interface AccountDistributionMapper { + + public List getDistributionByMinerAccount(@Param("coin") String coin, @Param("ma") String ma); + + public List getDayDistributionByMinerAccount(@Param("coin") String coin, @Param("ma") String ma); + + + public List getDayPendingByMinerAccount(@Param("coin") String coin, @Param("ma") String ma); + + + public List getOutcomeListByCoinAndUser(@Param("coin") String coin, @Param("ma") String ma); + + public BigDecimal getTotalOutByCoinAndUser(@Param("coin") String coin, @Param("ma") String ma); + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/GoogleAuthMapper.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/GoogleAuthMapper.java new file mode 100644 index 0000000..d33e492 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/GoogleAuthMapper.java @@ -0,0 +1,50 @@ +package com.m2pool.pool.mapper; + +import com.m2pool.common.datasource.annotation.DistributionDB; +import com.m2pool.common.datasource.annotation.GAuth; +import com.m2pool.pool.dto.GoogleInfoDto; +import com.m2pool.pool.dto.ProfitDto; +import com.m2pool.pool.entity.GoogleInfo; +import com.m2pool.pool.entity.UserGoogleAuthInfo; +import com.m2pool.system.api.entity.SysUser; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @Description 谷歌验证器相关sql + * @Date 2022/8/30 17:47 + * @Author 杜懿 + */ +@GAuth +public interface GoogleAuthMapper { + + /** + * 查询是否绑定google验证器 + * @param email + * @return + */ + public boolean checkIfBindByEmail(@Param("email") String email); + + /** + * 获取谷歌验证码 + * @param email + * @return + */ + public GoogleInfo getGoogleInfoByEmail(@Param("email") String email); + + public UserGoogleAuthInfo getUserInfoByEmail(@Param("email") String email); + + public boolean bingGoogleAuthenticator(@Param("info") UserGoogleAuthInfo info); + + + + /** + * 判断验证码是否已存在 + * @param secret + * @return + */ + public boolean checkSecretIfExist(@Param("secret") String secret); + + public boolean closeGoogleAuthenticator(@Param("info") UserGoogleAuthInfo info); +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/NoticeMapper.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/NoticeMapper.java new file mode 100644 index 0000000..5bea1bb --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/NoticeMapper.java @@ -0,0 +1,49 @@ +package com.m2pool.pool.mapper; + +import com.m2pool.common.datasource.annotation.UserDB; +import com.m2pool.pool.dto.NoticeListDto; +import com.m2pool.pool.dto.NoticeMinerCoinListDto; +import com.m2pool.pool.dto.OfflineUserMinerDto; +import com.m2pool.pool.dto.UserAccountDto; +import com.m2pool.pool.vo.NoticeAddVo; +import com.m2pool.pool.vo.NoticeDelVo; +import com.m2pool.pool.vo.NoticePageVo; +import com.m2pool.pool.vo.NoticeUpdateVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * @Description 邮箱通知相关sql + * @Date 2022/8/30 17:47 + * @Author 杜懿 + */ +@UserDB +public interface NoticeMapper { + + //查询当前账号绑定通知邮箱 + public List getNoticeEmailListByMaId(@Param("vo") NoticePageVo vo,@Param("user") String user); + + //检查当前记录id和当前挖矿账号以及当前用户账号是否对应 + public boolean checkIdAndMa(@Param("id") long id,@Param("user") String user); + + public boolean checkEmailIfExist(@Param("maId") long maId,@Param("email") String email); + + public long getMaIdByIdAndUser(@Param("id") long id, @Param("user") String user); + + //查询当前账号绑定通知邮箱个数 + public int getNoticeEmailCountByMaId(@Param("maId") long maId); + + //新增通知邮箱 + public boolean addNoticeEmail(@Param("vo") NoticeAddVo vo); + + //修改通知邮箱备注 + public boolean updateInfo(@Param("vo") NoticeUpdateVo vo); + + //删除通知邮箱 + public boolean deleteNoticeInfoById(@Param("vo") NoticeDelVo vo,@Param("maId") Long maId); + + public List getNoticeEmailListByMinerAndCoin(@Param("list") List list, @Param("coin") String coin); + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/PoolMapper.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/PoolMapper.java new file mode 100644 index 0000000..02a8500 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/PoolMapper.java @@ -0,0 +1,176 @@ +package com.m2pool.pool.mapper; + + +import com.m2pool.common.datasource.annotation.DistributionDB; +import com.m2pool.common.datasource.annotation.HashRateDB; +import com.m2pool.common.datasource.annotation.InfoDB; +import com.m2pool.pool.dto.*; +import com.m2pool.pool.entity.PoolPower; +import com.m2pool.pool.vo.DateValueVo; +import org.apache.ibatis.annotations.Param; + +import java.math.BigDecimal; +import java.util.List; + +/** + * @Description TODO + * @Date 2022/8/30 17:47 + * @Author 杜懿 + */ +@InfoDB +public interface PoolMapper { + + @HashRateDB + public PoolInfoDto getPoolInfoByCoin(@Param("table") String table); + + @HashRateDB + public Double getNowPoolDailyPower(@Param("table") String table); + + @HashRateDB + public Double getNowPoolPower(@Param("table") String table); + + public List get30mPoolPowerList(@Param("table") String table); + + public List getDailyPoolPowerList(@Param("table") String table); + + public List getHourNetPowerList(@Param("table") String table); + + public List getGRSBlockInfoList(); + + public List getMonaBlockInfoList(); + + public List getDgbQubitBlockInfoList(); + + public List getDgbSkeinBlockInfoList(); + + public List getDGBODOBlockInfoList(); + + public List getDgbQubitA10BlockInfoList(); + + public List getDgbSkeinA10BlockInfoList(); + + public List getDGBODOB20BlockInfoList(); + + public List getRXDBlockInfoList(); + + public List getNEXABlockInfoList(); + + public List getALPHBlockInfoList(); + + public List getENXBlockInfoList(); + + @DistributionDB + public List getNewNEXABlockInfoList(@Param("date") String date); + + @DistributionDB + public List getNewGRSBlockInfoList(@Param("date") String date); + + @DistributionDB + public List getNewMONABlockInfoList(@Param("date") String date); + + @DistributionDB + public List getNewDGBQBlockInfoList(@Param("date") String date); + + @DistributionDB + public List getNewDGBSBlockInfoList(@Param("date") String date); + + @DistributionDB + public List getNewDGBOBlockInfoList(@Param("date") String date); + + @DistributionDB + public List getNewRXDBlockInfoList(@Param("date") String date); + + @DistributionDB + public List getNewALPHBlockInfoList(@Param("date") String date); + + @DistributionDB + public List getNewENXBlockInfoList(@Param("date") String date); + + @HashRateDB + public List getMinerInfoList(@Param("table") String table); + + //public List get24hMinerInfoList(@Param("table") String table); + + public List get30mAccountPowerListByAccount(@Param("table") String table, @Param("user") String user); + + + + public List getDailyAccountPowerListByAccount(@Param("table") String table, @Param("user") String user); + + public List get24hAccountPowerListByAccount(@Param("table") String table, @Param("user") String user); + + //todo 考虑删除 + public List getDailyPoolPowerDistributionList(@Param("table") String table); + + //todo 考虑删除 + public List getDailyAccountPowerDistributionList(@Param("table") String table, @Param("user") String user); + + public List getMinerPowerList(@Param("table") String table, @Param("user") String user, @Param("miner") String miner); + + //todo 考虑删除 + public List getDailyMinerPowerDistributionList(@Param("table") String table, @Param("user") String user, @Param("miner") String miner); + + @HashRateDB + public List getHourMinerDataList(@Param("table") String table,@Param("now") String now); + + @HashRateDB + public List getDailyMinerDataList(@Param("table") String table,@Param("now") String now); + + //todo 考虑删除 + public List getUserHourMinerDataList(@Param("table") String table); + + //todo 考虑删除 + public List getUserDailyMinerDataList(@Param("table") String table); + + //从网站的算力总表拿大于date的数据条数 + public int getLastDataTime(@Param("table") String table,@Param("date") String date); + + //批量写入算力数据 + public boolean batchInsertMhsDataToDB(@Param("table") String table,@Param("list") List list); + + public boolean batchInsertUserMhsDateToDB(@Param("table") String table,@Param("list") List list); + + //public boolean batchInsertUserMhsDataToDB(@Param("table") String table,@Param("list") List list); + + //写入全网算力数据 30m + public boolean insertNetPower(@Param("table") String table,@Param("date") String date,@Param("vo") DateValueVo vo); + + //写入币价数据 30m + public boolean insertPrice(@Param("table") String table,@Param("date") String date,@Param("vo") DateValueVo vo); + //@Pool2DB + //public boolean insertMhsDataToDB(@Param("table") String table,@Param("list") List list); + //报块信息表 + public boolean batchInsertNexaPoolBlkToDB(@Param("list") List list); + + public boolean batchInsertGRSPoolBlkToDB(@Param("list") List list); + + public boolean batchInsertMONAPoolBlkToDB(@Param("list") List list); + + public boolean batchInsertDGBOPoolBlkToDB(@Param("list") List list); + + public boolean batchInsertDGBQPoolBlkToDB(@Param("list") List list); + + public boolean batchInsertDGBSPoolBlkToDB(@Param("list") List list); + + public boolean batchInsertRXDPoolBlkToDB(@Param("list") List list); + + public boolean batchInsertALPHPoolBlkToDB(@Param("list") List list); + + public boolean batchInsertENXPoolBlkToDB(@Param("list") List list); + + //todo 矿池统计数据入库 + public boolean insertPoolPower(@Param("table") String table,@Param("vo") PoolPower vo); + + public BigDecimal getPoolTodayTotalPower(@Param("coin") String coin, @Param("date") String date); + + public List getUserTodayTotalPower(@Param("coin") String coin, @Param("date") String date); + + public List getDailyPoolPower(@Param("coin") String coin); + + public List getDailyNetPower(@Param("coin") String coin); + + public BigDecimal getAvgNetPower(@Param("coin") String coin, @Param("date") String date); + //todo 分配数据入库 目的:整合主库分库 + + public List getOfflineList(@Param("table") String table, @Param("date") String date); +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/TicketMapper.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/TicketMapper.java new file mode 100644 index 0000000..c9cef7f --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/TicketMapper.java @@ -0,0 +1,158 @@ +package com.m2pool.pool.mapper; + +import com.m2pool.pool.dto.*; +import com.m2pool.pool.entity.M2File; +import com.m2pool.pool.entity.Ticket; +import com.m2pool.pool.vo.*; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface TicketMapper { + + /** + * 修改工单状态 + * @param id + * @param status + * @return + */ + public boolean changeTicketStatus(@Param("id") long id, @Param("status") int status); + + /** + * 修改工单update_time + * @param id + * @return + */ + public boolean changeTicketUpdateTime(@Param("id") long id); + + /** + * 添加上传文件的保存信息 + * @param vo + * @return + */ + public boolean insertFileInfo(@Param("vo") M2File vo); + + /** + * 根据id获取文件相关信息 + * @param id + * @return + */ + public M2File getFileInfoById(Long id); + + /** + * 根据工单id查询工单是否存在 + * @param id + * @return + */ + public int checkTicketExist(@Param("id") long id); + + /** + * 新增工单 + * @param data + * @return + */ + public boolean insertTicket(@Param("data") Ticket data); + + + + + /** + * 工单补充内容提交工单信息 + * @param vo + * @return + */ + public boolean insertTicketSupplement(@Param("vo") ResubmitTicketVo vo); + + public Integer getTicketSupplementCountByTicketId(@Param("id") long id); + + + /** + * 根据工单id 获取工单补充提交内容 + * @param id + * @return + */ + public List getTicketSupplementListByTicketId(@Param("id") long id); + + /** + * 根据工单id 获取工单回复内容 + * @param id + * @return + */ + public List getTicketResponListByTicketId(@Param("id") long id); + + /** + * 查询个人工单列表 + * @return + * @param vo + */ + public List getPrivateTicketList(@Param("vo") PrivateTicketListVo vo, @Param("email") String email); + + /** + * 个人处理中的工单列表 + * @return + */ + public List getPrivateProcessingTicketList(@Param("vo") PrivateTicketListVo vo, @Param("email") String email); + + /** + * 个人 已完结的工单列表 + * @return + */ + public List getPrivateEndTicketList(@Param("vo") PrivateTicketListVo vo,@Param("email") String email); + + + /** + * 查看工单详情 + * @param id + * @return + */ + public TicketDto getTicketById(@Param("id") long id); + + /** + * 根据工单id查看工单状态 + * @param id + * @return + */ + public int getTicketStatusById(@Param("id") long id); + + /** + * 查看工单详情 + * @param id + * @return + */ + public TicketPrivateDto getBackendTicketById(@Param("id") long id); + + + /** + * 查看个人工单详情 + * @param id + * @param email + * @return + */ + public TicketPrivateDto getPrivateTicketById(@Param("id") long id, @Param("email") String email); + + /** + * 根据状态和类型查询工单列表 + * @param vo + * @return + */ + public List getTicketListByStatusAndType(@Param("vo") StatusVo vo); + + + /** + * 后台获取总览工单 条件搜索 工单号/用户邮箱/时间 + * @param vo + * @return + */ + public List getTicketListByCond(@Param("vo") AllTicketListVo vo); + + public List getAllTicketList(@Param("vo") StatusVo vo); + + /** + * 添加工单回复 + * @param vo + * @return + */ + public boolean insertRespon(@Param("vo") ResponTicketVo vo, @Param("name") String name); + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/UserAccountMapper.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/UserAccountMapper.java new file mode 100644 index 0000000..32fffca --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/mapper/UserAccountMapper.java @@ -0,0 +1,105 @@ +package com.m2pool.pool.mapper; + +import com.m2pool.common.datasource.annotation.UserDB; +import com.m2pool.pool.dto.*; +import com.m2pool.pool.entity.*; +import com.m2pool.pool.vo.AccountBalanceAddVo; +import com.m2pool.pool.vo.ApiKeyUpdateVo; +import com.m2pool.pool.vo.ReadOnlyHtmlChangeVo; +import com.m2pool.system.api.model.OpenApiKeyInfo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @Description 用户网站账号和挖矿账号相关sql + * @Date 2022/8/30 17:47 + * @Author 杜懿 + */ +@UserDB +public interface UserAccountMapper { + + /** + * 添加挖矿账号 + * @param vo + * @return + */ + public boolean userAddMinerAccount(@Param("vo") UserMinerAccount vo); + + /** + * 修改挖矿账号 备注等 + * @param accountId + * @param vo + * @return + */ + public boolean updateMinerAccountRemark(@Param("accountId") long accountId,@Param("vo") AccountBalanceAddVo vo); + + /** + * 挖矿账户绑定钱包 + * @param accountId + * @param vo + * @return + */ + public boolean accountAddBalance(@Param("accountId") long accountId, @Param("vo") AccountBalanceAddVo vo); + + /** 停止使用地址 */ + public boolean accountStopBalance(@Param("id") long id, @Param("vo") AccountBalanceAddVo vo); + + /** + * 修改挖矿账户绑定的钱包地址 + * @param id + * @param vo + * @return + */ + public boolean updateBalance(@Param("id") long id, @Param("vo") AccountBalanceAddVo vo); + + //public List getAccountByUser(@Param("user") String user, @Param("vo") AccountVo vo); + + public UserAccountDto getAccountByUserAccountAndCoin(@Param("user") String user, @Param("account") String account, @Param("coin") String coin); + + public boolean checkAccountIfExist(@Param("account") String account, @Param("coin") String coin); + + public UserAccountDto getAccountByUserAccountId(@Param("user") String user,@Param("id") long id); + + + public int getAccountNumberByUserAccountAndCoin(@Param("user") String user, @Param("coin") String coin); + + public List getAccountByUserAndCoin(@Param("user") String user); + + public UserAccountDto getAccountByUserAndMinerAccount(@Param("user") String user,@Param("ma") String ma); + + public MinerAccountBalanceDto getBalanceByMinerAccount(@Param("maId") long maId); + + boolean batchDelAccount(@Param("ids") List ids); + + public List getBalanceByCoin(@Param("coin") String coin); + + public boolean checkKeyIfExist(@Param("key") String key); + + public boolean createReadOnlyPage(@Param("vo") UserReadOnlyPage vo); + + public List getPageListByUser(@Param("user") String user); + + public UserReadOnlyPageInfo getPageInfoByKey(@Param("key") String key); + + public boolean changeReadOnlyPage(@Param("vo") ReadOnlyHtmlChangeVo vo); + + public boolean batchDeletePageByKeyAndUser(@Param("keys") List keys,@Param("user") String user); + + public int getApiKeyCountByUser(@Param("user") String user); + /** + * 用户绑定apiKey + * @param vo + * @return + */ + public boolean userCreateApiKey(@Param("vo") OpenApiKey vo); + + public List getAPIListByUser(@Param("user") String user); + + public UserApi getAPIInfoByUserAndId(@Param("id") long id,@Param("user") String user); + + public boolean updateApiKey(@Param("vo") ApiKeyUpdateVo vo,@Param("user") String user); + + public boolean batchDeleteAPIByIdsAndUser(@Param("ids") List ids,@Param("user") String user); + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/FileService.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/FileService.java new file mode 100644 index 0000000..63dcfe9 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/FileService.java @@ -0,0 +1,32 @@ +package com.m2pool.pool.service; + + +import com.m2pool.common.core.Result.R; +import com.m2pool.system.api.entity.SysFile; +import org.springframework.web.multipart.MultipartFile; + + +/** + * @Description M2pool 文件上传 + * @Date 2022/5/19 17:40 + * @Author 杜懿 + */ +public interface FileService { + + + /** + * 文件上传 + * @return + */ + public R uploadFile(MultipartFile file); + + + ///** + // * 工具类文件上传 + // * @return + // */ + //public R uploadToolsFile(MultipartFile file, String fileName); + + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/GoogleAuthService.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/GoogleAuthService.java new file mode 100644 index 0000000..9fdc4e3 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/GoogleAuthService.java @@ -0,0 +1,60 @@ +package com.m2pool.pool.service; + + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.pool.vo.*; + +import javax.validation.constraints.Email; + +/** + * @Description 谷歌验证器服务类 + * @Date 2024/8/19 11:40 + * @Author dy + */ +public interface GoogleAuthService { + + /** + * 判断账户是否绑定谷歌验证器 + * @return + */ + public AjaxResult ifBind(); + + /** + * 判断邮箱是否绑定谷歌验证器 + * @return + * @param vo + */ + public AjaxResult emailIfBind(EmailVo vo); + /** + * 获取google两步验证二维码和安全码字符串 + * @return + */ + public AjaxResult getBindInfo(); + + /** + * 绑定谷歌验证器 + * @param vo + * @return + */ + public AjaxResult bindGoogle(GoogleBindVo vo); + + /** + * 获取谷歌绑定邮箱验证码 + * @return + */ + public AjaxResult getBindCode(); + + + /** + * 获取谷歌绑定邮箱验证码 + * @return + */ + public AjaxResult closeStepTwp(GoogleCloseVo vo); + + /** + * 获取关闭谷歌绑定邮箱验证码 + * @return + */ + public AjaxResult getCloseCode(); + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/ISysFileService.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/ISysFileService.java new file mode 100644 index 0000000..f70bbf2 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/ISysFileService.java @@ -0,0 +1,32 @@ +package com.m2pool.pool.service; + +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件上传接口 + * + * @author du + */ +public interface ISysFileService +{ + /** + * 文件上传接口 + * + * @param file 上传的文件 + * @return 访问地址 + * @throws Exception + */ + public String uploadFile(MultipartFile file) throws Exception; + + ///** + // * 相关工具类文件上传接口 + // * + // * @param file 上传的文件 + // * @param fileName + // * @return 访问地址 + // * @throws Exception + // */ + //public String uploadToolsFile(MultipartFile file,String fileName) throws Exception; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/LocalSysFileServiceImpl.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/LocalSysFileServiceImpl.java new file mode 100644 index 0000000..a67b53b --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/LocalSysFileServiceImpl.java @@ -0,0 +1,69 @@ +package com.m2pool.pool.service; +import com.m2pool.common.core.utils.FileUploadUtils; +import com.m2pool.common.core.utils.file.MimeTypeUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +/** + * 本地文件存储 + * + * @author du + */ +@Primary +@Service +public class LocalSysFileServiceImpl implements ISysFileService +{ + /** + * 资源映射路径 前缀 + */ + @Value("/statics") + public String localFilePrefix; + + /** + * 域名或本机访问地址 + */ + //@Value("http://10.168.2.125:9808") + @Value("${myenv.domain}") + public String domain; + + /** + * 上传文件存储在本地的根路径 + */ + @Value("${myenv.filepath}") + private String localFilePath; + + /** + * 本地文件上传接口 + * + * @param file 上传的文件 + * @return 访问地址 + * @throws Exception + */ + @Override + public String uploadFile(MultipartFile file) throws Exception + { + String name = FileUploadUtils.upload(localFilePath, file , MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + String url = domain + localFilePrefix + name; + return url; + } + + ///** + // * 本地使用手册等相关工具文件文件上传接口 + // * + // * @param file 上传的文件 + // * @param fileName + // * @return 访问地址 + // * @throws Exception + // */ + //@Override + //public String uploadToolsFile(MultipartFile file, String fileName) throws Exception + //{ + // String name = FileUploadUtils.toolsUpload(localFilePath + "/tools", file ,fileName, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + // String url = domain + localFilePrefix+ "/tools" + name; + // System.out.println("url"+url); + // return url; + //} + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/MinerAccountService.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/MinerAccountService.java new file mode 100644 index 0000000..2252295 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/MinerAccountService.java @@ -0,0 +1,35 @@ +package com.m2pool.pool.service; + + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.pool.dto.OutcomeDto; +import com.m2pool.pool.vo.AccountVo; +import com.m2pool.pool.vo.MinerListVo; +import com.m2pool.pool.vo.MinerVo; + +import java.util.List; + +/** + * @Description 挖矿账户相关服务类 + * @Date 2024/6/14 11:40 + * @Author dy + */ +public interface MinerAccountService { + + public AjaxResult getMinerAccountPower(AccountVo vo); + + public AjaxResult getMinerAccountInfo(AccountVo vo); + + public AjaxResult getAccountPowerDistribution(AccountVo vo); + + public AjaxResult getMinerList(MinerListVo vo); + + public AjaxResult getMinerPower(MinerVo vo); + + //public AjaxResult getMinerPowerDistribution(MinerVo com.m2pool.chat.vo); + + public TableDataInfo getHistoryIncome(AccountVo vo); + + public TableDataInfo getHistoryOutcome(AccountVo vo); +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/NoticeService.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/NoticeService.java new file mode 100644 index 0000000..d2099b2 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/NoticeService.java @@ -0,0 +1,52 @@ +package com.m2pool.pool.service; + + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.pool.dto.NoticeListDto; +import com.m2pool.pool.vo.*; + +import java.util.List; + +/** + * @Description 告警系统服务类 + * @Date 2024/8/19 11:40 + * @Author dy + */ +public interface NoticeService { + + /** + * 查看当前挖矿账户绑定的通知邮箱 分页 + * @return + * @param vo + */ + public List getNoticeEmailList(NoticePageVo vo); + + /** + * 对指定挖矿账户添加通知邮箱 + * @return + */ + public AjaxResult addNoticeEmail(NoticeAddVo vo); + + /** + * 对指定挖矿账户添加通知邮箱 + * @return + */ + public AjaxResult getCode(NoticeAddVo vo); + + /** + * 修改备注信息 + * @param vo + * @return + */ + public AjaxResult updateInfo(NoticeUpdateVo vo); + + /** + * 删除挖矿账户绑定的通知邮箱 + * @return + */ + public AjaxResult deleteEmail(NoticeDelVo vo); + + + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/PoolService.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/PoolService.java new file mode 100644 index 0000000..b309947 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/PoolService.java @@ -0,0 +1,42 @@ +package com.m2pool.pool.service; + + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.pool.dto.BlockInfoDto; +import com.m2pool.pool.vo.*; + +import java.util.List; + +/** + * @Description 矿池相关服务类 + * @Date 2024/6/14 11:40 + * @Author dy + */ +public interface PoolService { + + public AjaxResult getPoolInfo(CoinVo vo); + + public AjaxResult getPoolPower(CoinVo vo); + + public boolean checkAccount(MinerAccountAddVo vo); + + //public AjaxResult getMinerCount(CoinVo com.m2pool.chat.vo); + + + public AjaxResult getLuckByCoin(CoinVo vo); + + public List getBlockInfoList(CoinVo vo); + + //public AjaxResult getPoolPowerDistribution(CoinVo com.m2pool.chat.vo); + + public AjaxResult getCoinBalanceList(BalanceListGetVo vo); + + public AjaxResult getNetPower(CoinVo vo); + + public AjaxResult getParam(CoinVo vo); + + public AjaxResult test2(); + + public AjaxResult test1(); + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/ReadOnlyService.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/ReadOnlyService.java new file mode 100644 index 0000000..696fb90 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/ReadOnlyService.java @@ -0,0 +1,53 @@ +package com.m2pool.pool.service; + + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.pool.dto.UserReadPageInfoReturnDto; +import com.m2pool.pool.vo.*; + +import java.util.List; + +/** + * @Description 只读页面 挖矿账户相关服务类 + * @Date 2024/6/14 11:40 + * @Author dy + */ +public interface ReadOnlyService { + + /** + * 生成只读页面链接 + * @param vo 只读页面基本信息 挖矿账号、币种、备注、权限配置 + * @return + */ + public AjaxResult createPageUrl(ReadOnlyHtmlCreateVo vo); + + /** + * 获取只读页面列表 查看用户生成的只读页面 + * @param vo + * @return + */ + public TableDataInfo getUrlList(PageVo vo); + + public AjaxResult getUrlInfo(ReadOnlyKeyVo vo); + + public AjaxResult changeUrlInfo(ReadOnlyHtmlChangeVo vo); + + public AjaxResult delPageByKey(ReadOnlyKeyVo vo); + + public AjaxResult getPageInfo(ReadOnlyKeyVo vo); + + public AjaxResult getProfitInfo(ReadOnlyKeyVo vo); + + public AjaxResult getMinerAccountPower(ReadOnlyIntervalVo vo); + + public AjaxResult getAccountPowerDistribution(ReadOnlyIntervalVo vo); + + public AjaxResult getMinerList(ReadOnlyListVo vo); + + public AjaxResult getMinerPower(ReadOnlyMinerVo vo); + + public TableDataInfo getHistoryIncome(ReadOnlyPageVo vo); + + public TableDataInfo getHistoryOutcome(ReadOnlyPageVo vo); +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/TicketService.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/TicketService.java new file mode 100644 index 0000000..5297af3 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/TicketService.java @@ -0,0 +1,117 @@ +package com.m2pool.pool.service; + + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.pool.dto.AllTicketListDto; +import com.m2pool.pool.dto.TicketListDto; +import com.m2pool.pool.vo.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.sql.SQLException; +import java.util.List; + + +/** + * @Description DragonBall Miner KS6工单系统服务类 + * @Date 2022/5/19 17:40 + * @Author 杜懿 + */ +public interface TicketService { + + + /** + * 根据文件id下载文件 多个 + * @return + */ + public void downloadByIds(String ids, HttpServletRequest request, HttpServletResponse response); + + /** + * 多文件上传 + * @return + */ + public AjaxResult uploadFiles(MultipartFile[] file) throws SQLException; + + /** + * 提交工单 + * @param vo + * @return + */ + public AjaxResult submitTicket(TicketVo vo); + + + /** + * 用户 再次补充提交线上技术支持工单内容 + * @param vo + * @return + */ + public AjaxResult resubmitTicket(ResubmitTicketVo vo); + + /** + * 用户 条件搜索获取个人工单列表 + * @param vo + * @return + */ + public AjaxResult getPrivateTicketByStatus(PrivateTicketListVo vo); + + /** + * + * 查看工单详情 + * @param id + * @return + */ + public AjaxResult getPrivateTicketDetails(long id); + + + /** + * 用户结束工单 仅能关闭工单状态为2或者9的工单 + * + * @param vo@return + */ + public AjaxResult endTicket(EndTicketVo vo); + +/*************************************** 维修人员相关接口 *************************************************/ + + /** + * 查看所有工单 条件搜索 + * @param + * @param vo + * @return + */ + public List getAllTicket(AllTicketListVo vo); + + + /** + * 根据status获取技术支持工单列表 + * @param vo + * @return + */ + public List getSupportTicketList(StatusVo vo); + + + /** + * 技术支持人员回复工单 + * @param vo + * @return + */ + public AjaxResult onlineResponTicket(ResponTicketVo vo); + + /** + * 技术支持人员/维修人员关闭用户工单 仅能关闭工单状态为2或者9的工单 + * @param vo + * @return + */ + public AjaxResult closeTicket(IdVo vo); + + /** + * 查看工单详情 + * @param id + * @return + */ + public AjaxResult getTicketDetails(long id); + + + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/UserMinserService.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/UserMinserService.java new file mode 100644 index 0000000..26349b5 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/UserMinserService.java @@ -0,0 +1,60 @@ +package com.m2pool.pool.service; + + +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.pool.dto.UserApiDto; +import com.m2pool.pool.vo.*; + +import java.util.List; + +/** + * @Description 矿池相关服务类 + * @Date 2024/6/14 11:40 + * @Author dy + */ +public interface UserMinserService { + + public AjaxResult addMinerAccount(MinerAccountAddVo vo); + + public AjaxResult checkAccount(MinerAccountAddVo vo); + + public AjaxResult checkBalance(BalanceCheckVo vo); + + public AjaxResult checkMaAndBalance(MaBalanceCheckVo vo); + + public AjaxResult delMinerAccount(MinerAccountDelVo vo); + + public AjaxResult addBalance(AccountBalanceAddVo vo); + + public AjaxResult getAccountList(); + + public AjaxResult getAccountGradeList(); + + public AjaxResult getMinerAccountBalance(MinerIdVo vo); + + public AjaxResult createApiKey(ApiKeyCreateVo vo); + + public List getApiList(PageVo vo); + + public AjaxResult getApiInfo(ApiKeyInfoVo vo); + + public AjaxResult updateApi(ApiKeyUpdateVo vo); + + public AjaxResult deleteApi(ApiKeyDelVo vo); + // + //public AjaxResult getLuckByCoin(CoinVo com.m2pool.chat.vo); + // + //public List getBlockInfoList(CoinVo com.m2pool.chat.vo); + + //public AjaxResult getMinerAccountPower(AccountVo com.m2pool.chat.vo); + // + //public AjaxResult getMinerAccountInfo(AccountVo com.m2pool.chat.vo); + // + //public AjaxResult getAccountPowerDistribution(AccountVo com.m2pool.chat.vo); + // + //public AjaxResult getMinerList(MinerListVo com.m2pool.chat.vo); + // + //public AjaxResult getMinerPower(MinerVo com.m2pool.chat.vo); + // + //public AjaxResult getMinerPowerDistribution(MinerVo com.m2pool.chat.vo); +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/FileServiceImpl.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/FileServiceImpl.java new file mode 100644 index 0000000..2495b24 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/FileServiceImpl.java @@ -0,0 +1,62 @@ +package com.m2pool.pool.service.impl; + +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.utils.file.FileUtils; +import com.m2pool.pool.service.FileService; +import com.m2pool.pool.service.ISysFileService; +import com.m2pool.system.api.entity.SysFile; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +/** + * 本地文件存储 + * + * @author jxy + */ +@Service +public class FileServiceImpl implements FileService +{ + + @Autowired + private ISysFileService sysFileService; + + @Override + public R uploadFile(MultipartFile file){ + try + { + // 上传并返回访问地址 + String url = sysFileService.uploadFile(file); + SysFile sysFile = new SysFile(); + sysFile.setName(FileUtils.getName(url)); + sysFile.setUrl(url); + return R.success(sysFile); + } + catch (Exception e) + { + return R.fail(e.getMessage()); + } + } + + //@Override + //public R uploadToolsFile(MultipartFile file, String fileName){ + // try + // { + // // 上传并返回访问地址 + // String url = sysFileService.uploadToolsFile(file,fileName); + // SysFile sysFile = new SysFile(); + // sysFile.setName(FileUtils.getName(url)); + // sysFile.setUrl(url); + // + // System.out.println("工具类上传 构造的文件数据:"+sysFile); + // return R.success(sysFile); + // } + // catch (Exception e) + // { + // return R.fail(e.getMessage()); + // } + //} + + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/GoogleAuthServiceImpl.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/GoogleAuthServiceImpl.java new file mode 100644 index 0000000..37d7727 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/GoogleAuthServiceImpl.java @@ -0,0 +1,370 @@ +package com.m2pool.pool.service.impl; + +import com.alibaba.fastjson.JSON; +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.utils.CodeUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.sign.RsaUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.pool.dto.GoogleInfoDto; +import com.m2pool.pool.entity.GoogleInfo; +import com.m2pool.pool.entity.UserGoogleAuthInfo; +import com.m2pool.pool.mapper.GoogleAuthMapper; +import com.m2pool.pool.service.GoogleAuthService; +import com.m2pool.common.core.utils.GoogleAuthenticator; +import com.m2pool.pool.utils.QrCodeUtils; +import com.m2pool.pool.vo.EmailVo; +import com.m2pool.pool.vo.GoogleBindVo; +import com.m2pool.pool.vo.GoogleCloseVo; +import com.m2pool.system.api.RemoteMailService; +import com.m2pool.system.api.entity.EmailCodeEntity; +import com.m2pool.system.api.entity.EmailEntity; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +/** + * @Description TODO + * @Date 2024/8/22 14:50 + * @Author 杜懿 + */ +@Service +public class GoogleAuthServiceImpl implements GoogleAuthService { + + @Autowired + private GoogleAuthMapper googleAuthMapper; + + @Autowired + private RemoteMailService mailService; + + @Autowired + private RedisService redisService; + + @Value("${myenv.domain}") + private String domain; + + + @Override + public AjaxResult ifBind() { + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return AjaxResult.error("token解析异常"); + } + boolean result = googleAuthMapper.checkIfBindByEmail(email); + return AjaxResult.success(result); + } + + @Override + public AjaxResult emailIfBind(EmailVo vo) { + String email = StringUtils.clean(vo.getEmail()); + if(StringUtils.isBlank(email)){ + return AjaxResult.error("邮箱不能为空"); + } + boolean result = googleAuthMapper.checkIfBindByEmail(email); + return AjaxResult.success(result); + } + + /** + * 获取验证码和二维码 + * @return + */ + @Override + public AjaxResult getBindInfo() { + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return AjaxResult.error("token解析异常"); + } + GoogleInfo info = googleAuthMapper.getGoogleInfoByEmail(email); + if (StringUtils.isNull(info)){ + return AjaxResult.error("未能从token获取到所需内容,请勿擅自修改token"); + } + + GoogleInfoDto dto = new GoogleInfoDto(); + if(StringUtils.isBlank(info.getSecret())){ + //生成谷歌验证码 + String secret = GoogleAuthenticator.getSecretKey(); + String qrCodeText = GoogleAuthenticator.getQrCodeText(secret, email, domain); + String img = QrCodeUtils.creatRrCode(qrCodeText, 200, 200); + + boolean ifExist = googleAuthMapper.checkSecretIfExist(secret); + if(ifExist){ + secret = GoogleAuthenticator.getSecretKey(); + qrCodeText = GoogleAuthenticator.getQrCodeText(secret, email, domain); + img = QrCodeUtils.creatRrCode(qrCodeText,200,200); + + ifExist = googleAuthMapper.checkSecretIfExist(secret); + if(ifExist){ + return AjaxResult.error("网络异常,请稍后再试"); + } + } + dto.setSecret(secret); + dto.setImg(img); + + }else { + //数据库已有谷歌验证码 直接返回 + BeanUtils.copyProperties(info,dto); + dto.setImg(QrCodeUtils.creatRrCode( GoogleAuthenticator.getQrCodeText(dto.getSecret(),email , domain),200,200)); + } + return AjaxResult.success(dto); + } + + @Override + public AjaxResult bindGoogle(GoogleBindVo vo) { + // 参数校验 + if (StringUtils.isNull(vo)){ + return AjaxResult.error("未获取到请求参数"); + } + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return AjaxResult.error("token解析异常"); + } + + String password=""; + try { + password = RsaUtils.decryptByPrivateKey(vo.getPwd()); + password = StringUtils.clean(password); + }catch (Exception e){ + return AjaxResult.error(401,"加密密码传参有误"); + } + System.out.println("email :"+email); + // 根据邮箱查询是否已绑定 + UserGoogleAuthInfo info = googleAuthMapper.getUserInfoByEmail(email); + if (StringUtils.isNull(info)){ + return AjaxResult.error("未能从token获取到所需内容,请勿擅自修改token"); + } + System.out.println("info :"+info); + + if(StringUtils.isBlank(info.getSecret())){ + //未绑定 正常走绑定流程 + //校验gcode + boolean gResult = GoogleAuthenticator.checkCode(vo.getSecret(), vo.getGCode(), System.currentTimeMillis()); + if(!gResult){ + return AjaxResult.error("谷歌验证码错误"); + } + //校验邮箱验证码 + String redisKey = "google:"+email+":code"; + if(redisService.hasKey(redisKey)){ + + Object o = redisService.getCacheObject(redisKey);//google:email:code + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + + //验证验证码 + if(!vo.getECode().equals(emailCodeEntity.getEmailCode())){ + return AjaxResult.error("邮箱验证码错误"); + } + }else { + return AjaxResult.error("邮箱验证码 未获取或已过期(有效期10分钟),请重新获取验证码"); + } + + //校验密码 + if (!SecurityUtils.matchesPassword(password, info.getPwd())){ + return AjaxResult.error("密码错误"); + } + //入库 + UserGoogleAuthInfo bingInfo = new UserGoogleAuthInfo(); + bingInfo.setSecret(vo.getSecret()); + bingInfo.setStatus(1); + bingInfo.setEmail(info.getEmail()); + boolean bingResult = googleAuthMapper.bingGoogleAuthenticator(bingInfo); + if(bingResult){ + return AjaxResult.success(); + }else { + return AjaxResult.error("请求服务器失败,请重试"); + } + + }else { + if(info.getStatus() == 2){ + return AjaxResult.error("您绑定但是关闭了双重验证,请重新打开"); + }if(info.getStatus() == 1){ + return AjaxResult.error("当前用户已绑定过谷歌登录器"); + }else { + return AjaxResult.error("当前用户已绑定过谷歌登录器."); + } + } + + } + + @Override + public AjaxResult getBindCode() { + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return AjaxResult.error("token解析异常"); + } + + //判断用户是不是恶意刷邮箱,在规定时间内进行的 + String redisKey = "google:"+email+":code"; + if (redisService.hasKey(redisKey)) { + Object o = redisService.getCacheObject(redisKey);//google:email:code + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + if (emailCodeEntity.getTimes() > 4) { + return AjaxResult.error("请求次数过多,请10分钟后再试,(同一用户10分钟内只能请求4次)"); + } else { + + String emailCode = CodeUtils.creatCode(6); + emailCodeEntity.setEmailCode(emailCode); + + + emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1); + long overTime = redisService.getExpire(redisKey); + redisService.setCacheObject(redisKey, emailCodeEntity, overTime, TimeUnit.SECONDS + ); + return sendEmail(email,emailCode); + } + } else { + String emailCode = CodeUtils.creatCode(6); + // 最多允许用户在10分钟内发送2次的邮箱验证 + // 0s倒计时后用户可以再发送验证码,但是间隔在10分钟内只能再发送1次 + EmailCodeEntity emailCodeEntity = new EmailCodeEntity(); + emailCodeEntity.setEmail(email); + emailCodeEntity.setEmailCode(emailCode); + emailCodeEntity.setTimes(1); + //设置失效时间10分钟 + redisService.setCacheObject(redisKey, emailCodeEntity, + 10L, TimeUnit.MINUTES + ); + return sendEmail(email,emailCode); + } + + + } + + + @Override + public AjaxResult closeStepTwp(GoogleCloseVo vo) { + // 参数校验 + if (StringUtils.isNull(vo)){ + return AjaxResult.error("未获取到请求参数"); + } + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return AjaxResult.error("token解析异常"); + } + + System.out.println("email :"+email); + // 根据邮箱查询是否已绑定 + UserGoogleAuthInfo info = googleAuthMapper.getUserInfoByEmail(email); + if (StringUtils.isNull(info)){ + return AjaxResult.error("未能从token获取到所需内容,请勿擅自修改token"); + } + System.out.println("info :"+info); + + if(StringUtils.isBlank(info.getSecret())){ + return AjaxResult.success(); + }else { + + //已绑定 正常走关闭谷歌验证流程 + //校验gcode + boolean gResult = GoogleAuthenticator.checkCode(info.getSecret(), vo.getGCode(), System.currentTimeMillis()); + if(!gResult){ + return AjaxResult.error("谷歌验证码错误"); + } + //校验邮箱验证码 + String redisKey = "google:"+email+":close"; + if(redisService.hasKey(redisKey)){ + + Object o = redisService.getCacheObject(redisKey);//google:email:close + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + + //验证验证码 + if(!vo.getECode().equals(emailCodeEntity.getEmailCode())){ + return AjaxResult.error("邮箱验证码错误"); + } + }else { + return AjaxResult.error("邮箱验证码 未获取或已过期(有效期10分钟),请重新获取验证码"); + } + + //入库 + boolean bingResult = googleAuthMapper.closeGoogleAuthenticator(info); + if(bingResult){ + return AjaxResult.success(); + }else { + return AjaxResult.error("请求服务器失败,请重试"); + } + } + } + + @Override + public AjaxResult getCloseCode() { + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return AjaxResult.error("token解析异常"); + } + + //判断用户是不是恶意刷邮箱,在规定时间内进行的 + String redisKey = "google:"+email+":close"; + if (redisService.hasKey(redisKey)) { + Object o = redisService.getCacheObject(redisKey);//google:email:close + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + if (emailCodeEntity.getTimes() > 4) { + return AjaxResult.error("请求次数过多,请10分钟后再试,(同一用户10分钟内只能请求4次)"); + } else { + + String emailCode = CodeUtils.creatCode(6); + emailCodeEntity.setEmailCode(emailCode); + + emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1); + long overTime = redisService.getExpire(redisKey); + redisService.setCacheObject(redisKey, emailCodeEntity, overTime, TimeUnit.SECONDS + ); + return sendEmail(email,emailCode); + } + } else { + String emailCode = CodeUtils.creatCode(6); + // 最多允许用户在10分钟内发送2次的邮箱验证 + // 0s倒计时后用户可以再发送验证码,但是间隔在10分钟内只能再发送1次 + EmailCodeEntity emailCodeEntity = new EmailCodeEntity(); + emailCodeEntity.setEmail(email); + emailCodeEntity.setEmailCode(emailCode); + emailCodeEntity.setTimes(1); + //设置失效时间10分钟 + redisService.setCacheObject(redisKey, emailCodeEntity, + 10L, TimeUnit.MINUTES + ); + return sendCloseEmail(email,emailCode); + } + } + + public AjaxResult sendEmail(String email,String code){ + String text = "您正在绑定谷歌验证器!\n" + + "您的邮箱验证验证码是:\n\t"+code + + "\n此验证码10分钟有效。"; + EmailEntity emailEntity = new EmailEntity(); + emailEntity.setEmail(email); + emailEntity.setSubject("[M2Pool] 邮箱验证码"); + emailEntity.setText(text); + R r = mailService.sendTextMail(emailEntity); + if(r.getCode() == 200){ + return AjaxResult.success(); + }else { + return AjaxResult.error(r.getCode(),r.getMsg()); + } + } + + + + public AjaxResult sendCloseEmail(String email,String code){ + String text = "M2Pool:\n" + + "您正在解绑谷歌验证器!\n" + + "您的邮箱验证验证码是:\n\t"+code + + "\n此验证码10分钟有效。"; + EmailEntity emailEntity = new EmailEntity(); + emailEntity.setEmail(email); + emailEntity.setSubject("[M2Pool] 邮箱验证码"); + emailEntity.setText(text); + R r = mailService.sendTextMail(emailEntity); + if(r.getCode() == 200){ + return AjaxResult.success(); + }else { + return AjaxResult.error(r.getCode(),r.getMsg()); + } + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/MinerAccountServiceImpl.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/MinerAccountServiceImpl.java new file mode 100644 index 0000000..9929794 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/MinerAccountServiceImpl.java @@ -0,0 +1,799 @@ +package com.m2pool.pool.service.impl; + +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.m2pool.common.core.constant.HttpStatus; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.pool.dto.*; +import com.m2pool.pool.enums.PoolProfitScale; +import com.m2pool.pool.enums.PoolUnits; +import com.m2pool.pool.enums.Pools; +import com.m2pool.pool.mapper.AccountDistributionMapper; +import com.m2pool.pool.mapper.PoolMapper; +import com.m2pool.pool.mapper.UserAccountMapper; +import com.m2pool.pool.service.MinerAccountService; +import com.m2pool.pool.utils.EnumUtils; +import com.m2pool.pool.vo.AccountVo; +import com.m2pool.pool.vo.MinerListVo; +import com.m2pool.pool.vo.MinerVo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Description TODO + * @Date 2024/6/14 14:29 + * @Author dy + */ +@Service +public class MinerAccountServiceImpl implements MinerAccountService { + + @Autowired + private RedisService redisService; + + @Autowired + private PoolMapper poolMapper; + + @Autowired + private UserAccountMapper userAccountMapper; + + @Autowired + private AccountDistributionMapper distributionMapper; + + /** + * + * 获取挖矿账户算力曲线 + * @param vo + * @return + */ + @Override + @DSTransactional + public AjaxResult getMinerAccountPower(AccountVo vo) { + + long t0 = System.currentTimeMillis(); + String username = SecurityUtils.getUsername(); + //根据用户名获取对应挖矿账号 再根据挖矿账号计算算力 + UserAccountDto dto = userAccountMapper.getAccountByUserAccountAndCoin(username, vo.getAccount(), vo.coin); + + if(StringUtils.isNull(dto)){ + return AjaxResult.error("账号异常:当前用户名下不存在该挖矿账户!"); + } + Pools pool = (Pools) EnumUtils.get(Pools.class, dto.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("数据异常!请稍后再试"); + } + int scale = PoolProfitScale.getScaleByCoin(pool.getCoin()); + if("1h".equals(vo.getInterval()) || "rt".equals(vo.getInterval())){ + //根据需求改成返回30分钟的数据 + //List list = poolMapper.get30mAccountPowerListByAccount(pool.getCoin()+"_users_1h", com.m2pool.chat.vo.getAccount()); + List list = poolMapper.get30mAccountPowerListByAccount(pool.getCoin()+"_users_30m", vo.getAccount()); + Collections.reverse(list); + + + list.stream().forEach(e -> { + //MH/s 每秒转GH/s + e.setPv(e.getPv().divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + }); + long t1 = System.currentTimeMillis(); + System.out.println("个人账户算力曲线 实时数据 业务耗时:"+(t1-t0) +"ms"); + return AjaxResult.success(list); + }else if ("1d".equals(vo.getInterval())){ + List list = poolMapper.getDailyAccountPowerListByAccount(pool.getMhs()+"24h",vo.getAccount()); + //从里面筛选date=0 + Collections.reverse(list); + + list.stream().forEach(e -> { + //MH/s 每秒转GH/s + e.setDate(DateUtils.addDays(e.getDate(),-1)); + e.setPv(e.getPv().divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + }); + + //最后一条用实时表最新一组数据 字段mhs24h对应实时24h算力 + List lastList = poolMapper.getMinerInfoList(pool.getMhs()); + BigDecimal mhs = lastList.stream().filter(e -> e.getUser().equals(dto.getAccount())).map(e -> { + return BigDecimal.valueOf(e.getMhs24h()); + }).reduce(BigDecimal.ZERO, BigDecimal::add).multiply(BigDecimal.valueOf(pool.getFac())); + PowerLineDto lastPLDto= new PowerLineDto(); + Date now = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + lastPLDto.setDate(now); + lastPLDto.setPv(mhs.divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + list.add(lastPLDto); + + long t1 = System.currentTimeMillis(); + System.out.println("个人账户算力曲线 1天数据 业务耗时:"+(t1-t0) +"ms"); + return AjaxResult.success(list); + }else { + return AjaxResult.error("参数错误:interval"); + } + } + + @Override + public AjaxResult getMinerAccountInfo(AccountVo vo) { + //校验账号和token关系 + String username = SecurityUtils.getUsername(); + //根据用户名获取对应挖矿账号 再根据挖矿账号计算算力 + UserAccountDto dto = userAccountMapper.getAccountByUserAccountAndCoin(username, vo.getAccount(), vo.coin); + if(StringUtils.isNull(dto)){ + return AjaxResult.error("账号异常:当前用户名下不存在该挖矿账户!"); + } + + try { + AccountProfitInfoDto infoDto = new AccountProfitInfoDto(); + //根据用户名拿所有状态不为3(失败)的数据 然后根据拿到的数据分情况算总和 + System.out.println(dto.getCoin()+":"+dto.getAccount()); + List list = distributionMapper.getDistributionByMinerAccount(dto.getCoin(), dto.getAccount()); + + Map countMap = list.stream().collect(Collectors.groupingBy( + ProfitDto::getStatus, + Collectors.reducing(BigDecimal.ZERO,ProfitDto::getProfit,BigDecimal::add) + )); + + System.out.println(list.size()); + System.out.println(list.get(0)); + System.out.println("countMap:"+countMap); + + BigDecimal total = list.stream().map(ProfitDto::getProfit).reduce(BigDecimal.ZERO, BigDecimal::add); + + //支付中 status =0 + BigDecimal pengding = countMap.getOrDefault(0, BigDecimal.ZERO); + //已支付 status =1 + BigDecimal success = countMap.getOrDefault(1, BigDecimal.ZERO); + // + //2不足起付额 激活了自动提现 + BigDecimal state2 = countMap.getOrDefault(2, BigDecimal.ZERO); + //3满足起付额 没激活自动提现 + BigDecimal state3 = countMap.getOrDefault(3, BigDecimal.ZERO); + //4不足起付额 且没激活自动提现 + BigDecimal state4 = countMap.getOrDefault(4, BigDecimal.ZERO); + ////总收入 计算所有状态总和 + + + //wallet_outv2表 已支出 + BigDecimal out = distributionMapper.getTotalOutByCoinAndUser(dto.getCoin(), dto.getAccount()); + if(StringUtils.isNull(out)){ + out = BigDecimal.ZERO; + } + + System.out.println("success:"+success+",pengding:"+pengding+",out:"+out); + /******************************************** 获取今日和昨天起始时间 开始 *********************************************************/ + //因为数据库create_date为结算时间 所以以今日0为时间的数据代表昨日已挖 + Date today = DateUtils.dateTime(DateUtils.YYYY_MM_DD, DateUtils.getDate()); + + Calendar ca = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + ca.setTime(today); + ca.add(Calendar.DATE,1); + + Date todayEnd = ca.getTime(); + System.out.println(today+" to "+todayEnd); + /******************************************** 获取今日和昨天起始时间 结束 *********************************************************/ + + List preList = list.stream().filter(e -> + ((e.getDate().after(today) || e.getDate().equals(today)) && e.getDate().before(todayEnd))).collect(Collectors.toList()); + BigDecimal preProfit = BigDecimal.ZERO; + if (preList.size() > 0){ + preProfit = preList.stream() + .map(ProfitDto::getProfit) + .reduce(BigDecimal::add) + .get(); + System.out.println("昨日收益:"+preProfit+",size:"+preList.size()); + } + + //今日收益通过算力占比和今日报块总和去计算 当前挖矿账户 + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes(minute); + now.setSeconds(0); + List tbList = new ArrayList<>(); + switch (dto.getCoin().toLowerCase()){ + case "nexa": tbList = poolMapper.getNewNEXABlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "mona": tbList = poolMapper.getNewMONABlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "grs": tbList = poolMapper.getNewGRSBlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "dgbo": tbList = poolMapper.getNewDGBOBlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "dgbq": tbList = poolMapper.getNewDGBQBlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "dgbs": tbList = poolMapper.getNewDGBSBlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "rxd": tbList = poolMapper.getNewRXDBlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + default: break; + } + //今日矿池总收益 + BigDecimal reward = tbList.stream() + .filter(e -> (e.getDate().before(now) || e.getDate().equals(now))) + .map(e -> { + return BigDecimal.valueOf(e.getReward()); + }).reduce(BigDecimal.ZERO, BigDecimal::add); + //今日总收益 * 用户今日平均算力占比 = 用户今日预估收益 + BigDecimal ratio = Convert.toBigDecimal(redisService.getCacheMapValue(dto.getCoin().toUpperCase()+"UserPowerRatio", vo.getAccount()), BigDecimal.ZERO); + + //List todayList = list.stream().filter(e -> + // (e.getDate().after(today) || e.getDate().equals(today))).collect(Collectors.toList()); + BigDecimal todayProfit = reward.multiply(ratio).setScale(2,BigDecimal.ROUND_HALF_UP); + + infoDto.setAccount(dto.getAccount()); + int scale = PoolProfitScale.getScaleByCoin(vo.getCoin()); + // 总收益 支付中+已支付 + infoDto.setTotalProfit(total.setScale(scale,BigDecimal.ROUND_HALF_UP)); + //总支出 支付中+wallet_outv2表中全部 + infoDto.setExpend(pengding.add(out).setScale(scale,BigDecimal.ROUND_HALF_UP)); + //昨日收益 + infoDto.setPreProfit(preProfit.setScale(scale,BigDecimal.ROUND_HALF_UP)); + //今日收益 根据公式 (挖矿账户mhs24h/矿池mhs24h) * 当日矿池收益 当日矿池收益:select sum( + infoDto.setTodayPorfit(todayProfit.setScale(scale,BigDecimal.ROUND_HALF_UP)); + //账户余额 已成熟 + //总余额 总收益-总支出 + //infoDto.setBalance(infoDto.getTotalProfit().subtract(infoDto.getExpend())); + infoDto.setBalance(state2.add(state3).add(state4).setScale(scale,BigDecimal.ROUND_HALF_UP)); + //可提现余额 已成熟0 + infoDto.setWithdrawable(BigDecimal.ZERO); + System.out.println("infoDto:"+infoDto); + + return AjaxResult.success(infoDto); + }catch (Exception e){ + System.out.println("抛出异常"); + e.printStackTrace(); + AccountProfitInfoDto infoDto = new AccountProfitInfoDto(); + infoDto.setAccount(dto.getAccount()); + infoDto.setTotalProfit(BigDecimal.valueOf(0)); + infoDto.setBalance(BigDecimal.valueOf(0)); + infoDto.setBalance(BigDecimal.valueOf(0)); + infoDto.setExpend(BigDecimal.valueOf(0)); + infoDto.setPreProfit(BigDecimal.valueOf(0)); + infoDto.setTodayPorfit(BigDecimal.valueOf(0)); + + return AjaxResult.success(infoDto); + } + } + + @Override + public AjaxResult getAccountPowerDistribution(AccountVo vo) { + String username = SecurityUtils.getUsername(); + //根据用户名获取对应挖矿账号 再根据挖矿账号计算算力 + UserAccountDto dto = userAccountMapper.getAccountByUserAccountAndCoin(username, vo.getAccount(), vo.coin); + if(StringUtils.isNull(dto)){ + return AjaxResult.error("账号异常:当前用户名下不存在该挖矿账户!"); + } + + Pools pool = (Pools) EnumUtils.get(Pools.class, dto.getCoin()); + List minerList= poolMapper.getMinerInfoList(pool.getMhs()); + minerList.stream().forEach(e ->{ + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * pool.getFac()); + } + }); + List list = minerList.stream().filter(e -> vo.getAccount().equals(e.getUser())).collect(Collectors.toList()); + if(StringUtils.isNull(list)){ + return AjaxResult.success(); + } + /************************************************ 过滤异常数据 开始 ******************************************************/ + // + Date nowDate = DateUtils.getNowDate(); + Date start = DateUtils.addDays(nowDate, -1); + //过滤掉last_submit超过一天的offline数据 + list = list.stream().filter(e -> e.getLastSubmit().after(start)).collect(Collectors.toList()); + /************************************************ 过滤异常数据 结束 ******************************************************/ + if(StringUtils.isNull(list)){ + return AjaxResult.success(); + } + + if("rt".equals(vo.getInterval())){ + //用mhs + List returnList = getDistributionDtoByDataList(list, pool.getCoin()); + return AjaxResult.success(returnList); + + } + else if ("1d".equals(vo.getInterval())){ + //用mhs24h + List returnList = getDailyDistributionDtoByDataList(list, pool.getCoin()); + return AjaxResult.success(returnList); + + }else { + return AjaxResult.error("参数错误:interval"); + } + } + + @Override + public AjaxResult getMinerList(MinerListVo vo) { + String username = SecurityUtils.getUsername(); + //根据用户名获取对应挖矿账号 再根据挖矿账号计算算力 + UserAccountDto uaDto = userAccountMapper.getAccountByUserAccountAndCoin(username, vo.getAccount(), vo.coin); + + if(StringUtils.isNull(uaDto)){ + return AjaxResult.error("账号异常:当前用户名下不存在该挖矿账户!"); + } + + Pools pool = (Pools) EnumUtils.get(Pools.class, uaDto.getCoin()); + + // + List minerList= poolMapper.getMinerInfoList(pool.getMhs()); + minerList.stream().forEach(e ->{ + if(StringUtils.isNotNull(e.getMhs())){ + //e.setMhs(e.getMhs() * pool.getFac()); + e.setMhs(BigDecimal.valueOf(e.getMhs() * pool.getFac()).setScale(6,BigDecimal.ROUND_HALF_UP).doubleValue()); + } + }); + + List list = minerList.stream().filter(e -> vo.getAccount().equals(e.getUser())).collect(Collectors.toList()); + + /************************************************ 过滤异常数据 开始 ******************************************************/ + // + Date nowDate = DateUtils.getNowDate(); + Date start = DateUtils.addDays(nowDate, -1); + //过滤掉last_submit超过一天的offline数据 + list = list.stream().filter(e -> e.getLastSubmit().after(start)).collect(Collectors.toList()); + /************************************************ 过滤异常数据 结束 ******************************************************/ + + //判断是否需要排序 + if("30m".equals(vo.getSort())){ + //根据30分钟算力排序 默认降序 即算力从大到小 + list = list.stream().sorted( + Comparator.comparing(MinerDataDto::getMhs).reversed().thenComparing(MinerDataDto::getMhs) + ).collect(Collectors.toList()); + if("asc".equals(vo.getCollation())){ + Collections.reverse(list); + } + }else if("24h".equals(vo.getSort())){ + //根据24h算力排序 默认认降序 即算力从大到小 + list = list.stream().sorted( + Comparator.comparing(MinerDataDto::getMhs24h).reversed().thenComparing(MinerDataDto::getMhs24h) + ).collect(Collectors.toList()); + if("asc".equals(vo.getCollation())){ + Collections.reverse(list); + } + }else { + //其他情况不做处理 + } + + Map countMap = list.stream().collect(Collectors.groupingBy( + MinerDataDto::getOnline, + Collectors.counting() + )); + Long online = countMap.getOrDefault("online", 0L); + //if(online < 0){ + // return AjaxResult.error(""); + //} + Long offline = countMap.getOrDefault("offline", 0L) +countMap.getOrDefault("disconnected", 0L) ; + + //if(offline < 0){ + // return AjaxResult.error(""); + //} + if((online+offline) != list.size()){ + return AjaxResult.error("后台数据暂不可以用,请等待后台修复.."); + } + //计算1h算力总和 + double rate = list.stream().map(e -> BigDecimal.valueOf(e.getMhs())).collect(Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)).setScale(6,BigDecimal.ROUND_HALF_UP).doubleValue(); + //计算24h算力总和 + double dailyRate = list.stream().map(e -> BigDecimal.valueOf(e.getMhs24h())).collect(Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)).setScale(6,BigDecimal.ROUND_HALF_UP).doubleValue(); + + //根据公式 rejects / (rejects+accepts) + BigDecimal rejects = list.stream().map(e -> BigDecimal.valueOf(e.getRejects())).collect(Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)); + BigDecimal submits = list.stream().map(e -> BigDecimal.valueOf(e.getRejects()).add(BigDecimal.valueOf(e.getAccepts()))).collect(Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)); + double reject =0; + if(submits.compareTo(BigDecimal.ZERO) != 0){ + reject = rejects.divide(submits,6,BigDecimal.ROUND_HALF_UP).doubleValue(); + } + + + //根据用户传参 构造矿工列表表格数据 + //1.通过type判断是返回全部/在线/离线 + String type = ""; + int total = list.size(); + List row = new ArrayList<>(); + //todo 增加自定义排序功能 算力:30m/24h 排序方式:顺序/逆序 + if(0 == vo.getType()){ + row = list.stream().map(e -> { + MinerDto mDto = new MinerDto(); + mDto.setId("all"+e.getMiner()); + mDto.setMiner(e.getMiner()); + mDto.setRate(e.getMhs()); + mDto.setDailyRate(e.getMhs24h()); + mDto.setReject(e.getRejectsRatio()); + mDto.setSubmit(e.getLastSubmit()); + if("online".equals(e.getOnline())){ + mDto.setStatus("1"); + }else if("offline".equals(e.getOnline())){ + mDto.setStatus("2"); + long offTime = DateUtils.getDateDiffMinute(e.getDate(),e.getLastSubmit()); + offTime = offTime < 0 ? 0 : offTime; + mDto.setOffline(offTime); + }else { + mDto.setStatus(e.getOnline()); + } + return mDto; + }).collect(Collectors.toList()); + }else if(1 == vo.getType()){ + row = list.stream().filter(e -> "online".equals(e.getOnline())).map(e -> { + MinerDto mDto = new MinerDto(); + mDto.setId("on"+e.getMiner()); + mDto.setMiner(e.getMiner()); + mDto.setRate(e.getMhs()); + mDto.setDailyRate(e.getMhs24h()); + mDto.setReject(e.getRejectsRatio()); + mDto.setSubmit(e.getLastSubmit()); + mDto.setStatus("1"); + return mDto; + }).collect(Collectors.toList()); + + total = Convert.toInt(online); + }else if(2 == vo.getType()){ + row = list.stream().filter(e -> ("offline".equals(e.getOnline()) || "disconnected".equals(e.getOnline()))).map(e -> { + MinerDto mDto = new MinerDto(); + mDto.setId("off"+e.getMiner()); + mDto.setMiner(e.getMiner()); + mDto.setRate(e.getMhs()); + mDto.setDailyRate(e.getMhs24h()); + mDto.setReject(e.getRejectsRatio()); + mDto.setSubmit(e.getLastSubmit()); + mDto.setStatus("2"); + long offTime = DateUtils.getDateDiffMinute(e.getDate(),e.getLastSubmit()); + offTime = offTime < 0 ? 0 : offTime; + mDto.setOffline(offTime); + return mDto; + }).collect(Collectors.toList()); + + total = Convert.toInt(offline); + }else { + return AjaxResult.error("参数错误"); + } + //2.通过filter参数传的矿工名根据矿工再次筛选 + if(StringUtils.isNotBlank(vo.getFilter())){ + row = row.stream().filter(e -> vo.getFilter().equals(e.getMiner())).collect(Collectors.toList()); + } + + + //分页 + int page = vo.getPage() > 0 ? vo.getPage() : 1; + int size = vo.getLimit() > 0 ? vo.getLimit() : 10; + row = row.stream().skip((page - 1) * size).limit(size).collect(Collectors.toList()); + + + //构造返回体数据 + MinerListDto dto = new MinerListDto(); + //筛选出的list数据条数据即矿工总数 + dto.setAll(list.size()); + dto.setOffline(Convert.toInt(offline)); + dto.setOnline(Convert.toInt(online)); + dto.setDailyRate(dailyRate); + dto.setMiner("合计"); + dto.setRate(rate); + dto.setReject(reject); + dto.setTotal(total); + dto.setPage(getTotalPage(total,size)); + dto.setRows(row); + //todo 删除掉 + dto.setSubmit(nowDate); + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getMinerPower(MinerVo vo) { + String username = SecurityUtils.getUsername(); + //根据用户名获取对应挖矿账号 再根据挖矿账号计算算力 + UserAccountDto uaDto = userAccountMapper.getAccountByUserAccountAndCoin(username, vo.getAccount(), vo.coin); + + if(StringUtils.isNull(uaDto)){ + return AjaxResult.error("账号异常:当前用户名下不存在该挖矿账户!"); + } + + Pools pool = (Pools) EnumUtils.get(Pools.class, uaDto.getCoin()); + //根据account和miner查询 + String table = pool.getMhs()+"30m"; + + List list = poolMapper.getMinerPowerList(table, vo.getAccount(), vo.getMiner()); + + Collections.reverse(list); + + return AjaxResult.success(list); + + } + + //@Override + //public AjaxResult getMinerPowerDistribution(MinerVo com.m2pool.chat.vo) { + // String username = SecurityUtils.getUsername(); + // //根据用户名获取对应挖矿账号 再根据挖矿账号计算算力 + // UserAccountDto dto = userAccountMapper.getAccountByUserAccountAndCoin(username, com.m2pool.chat.vo.getAccount(), com.m2pool.chat.vo.coin); + // if(StringUtils.isNull(dto)){ + // return AjaxResult.error("账号异常:当前用户名下不存在该挖矿账户!"); + // } + // + // Pools pool = (Pools) EnumUtils.get(Pools.class, dto.getCoin()); + // if("rt".equals(com.m2pool.chat.vo.getInterval())){ + // //实时分布 从miners和miners_stats拿 + // List minerList= poolMapper.getNowMinerInfoList(pool.getMhs()); + // + // List list = minerList.stream().filter(e -> com.m2pool.chat.vo.getAccount().equals(e.getUser()) && com.m2pool.chat.vo.getMiner().equals(e.getMiner())).collect(Collectors.toList()); + // if(StringUtils.isNull(list)){ + // return AjaxResult.success(); + // } + // List returnList = getDistributionDtoByDataList(list, pool.getName()); + // + // return AjaxResult.success(returnList); + // } + // else if ("1d".equals(com.m2pool.chat.vo.getInterval())){ + // //从每日数据表拿最新一组数据 如 grs从grs_mhs_1d表拿user为指定挖矿账户的一组数据 + // String table = pool.getMhs()+"_1d"; + // List list = poolMapper.getDailyMinerPowerDistributionList(table,com.m2pool.chat.vo.getAccount(),com.m2pool.chat.vo.getMiner()); + // List returnList = getDistributionDtoByDataList(list, pool.getName()); + // return AjaxResult.success(returnList); + // }else { + // return AjaxResult.error("参数错误:interval"); + // } + //} + + @Override + public TableDataInfo getHistoryIncome(AccountVo vo) { + String username = SecurityUtils.getUsername(); + //根据用户名获取对应挖矿账号 再根据挖矿账号计算算力g + UserAccountDto dto = userAccountMapper.getAccountByUserAccountAndCoin(username, vo.getAccount(), vo.coin); + if(StringUtils.isNull(dto)){ + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setRows(new ArrayList<>()); + rspData.setMsg("未找到挖矿账户"); + rspData.setTotal(0); + rspData.setTotalPage(0); + return rspData; + } + + //查询每日 总收益 + List list = distributionMapper.getDayDistributionByMinerAccount(dto.getCoin(), dto.getAccount()); + //查询挖矿账户每日0点的算力 查询结果需要-1 才能代表当天的算力 + List mhsList = poolMapper.get24hAccountPowerListByAccount(dto.getCoin() + "_users_24h", dto.getAccount()); + + //查询账号每日0点24h算力 + List returnList = list.stream().map(e -> { + IncomeDto income = new IncomeDto(); + income.setCoin(dto.getCoin()); + income.setDate(e.getDate()); + mhsList.stream().anyMatch(m ->{ + if(DateUtils.isSameDay(m.getDate(),e.getDate())){ + income.setMhs(m.getPv().stripTrailingZeros().doubleValue()); + } + return false; + }); + //income.setDifficult(); + if(StringUtils.isNotNull(e.getProfit())){ + income.setAmount(e.getProfit() + .setScale(10,BigDecimal.ROUND_HALF_UP)); + } + return income; + }).collect(Collectors.toList()); + + + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); + PageInfo pageInfo = new PageInfo(returnList); + rspData.setTotal(pageInfo.getTotal()); + rspData.setTotalPage(pageInfo.getPages()); + + int page = vo.getPage() < 1 ? 1 : vo.getPage(); + int limit = vo.getLimit() < 1 ? 1 : vo.getLimit(); + + List pageList = returnList.stream().skip((page - 1) * limit).limit(limit).collect(Collectors.toList()); + + rspData.setRows(pageList); + + return rspData; + } + + @Override + public TableDataInfo getHistoryOutcome(AccountVo vo) { + + String username = SecurityUtils.getUsername(); + //根据用户名获取对应挖矿账号 再根据挖矿账号计算算力 + UserAccountDto dto = userAccountMapper.getAccountByUserAccountAndCoin(username, vo.getAccount(), vo.coin); + TableDataInfo rspData = new TableDataInfo(); + if(StringUtils.isNull(dto)){ + rspData.setCode(HttpStatus.ERROR); + rspData.setRows(new ArrayList<>()); + rspData.setMsg("未获取到挖矿账户信息"); + rspData.setTotal(0); + rspData.setTotalPage(0); + return rspData; + } + + List inPendingList = distributionMapper.getDayPendingByMinerAccount(vo.getCoin(), vo.getAccount()); + List pendingList = inPendingList.stream().map(e -> { + OutcomeDto dto1 = new OutcomeDto(); + dto1.setDate(e.getDate()); + dto1.setAmount(e.getProfit().doubleValue()); + dto1.setCoin(vo.getCoin()); + dto1.setStatus(e.getStatus()); + return dto1; + }).collect(Collectors.toList()); + List list = distributionMapper.getOutcomeListByCoinAndUser(vo.getCoin(),vo.getAccount()); + + list.addAll(pendingList); + Collections.sort(list,Comparator.comparing(OutcomeDto::getDate,(o1,o2) -> o2.compareTo(o1))); + + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); + PageInfo pageInfo = new PageInfo(list); + rspData.setTotal(pageInfo.getTotal()); + rspData.setTotalPage(pageInfo.getPages()); + + int page = vo.getPage() < 1 ? 1 : vo.getPage(); + int limit = vo.getLimit() < 1 ? 1 : vo.getLimit(); + + List pageList = list.stream().skip((page - 1) * limit).limit(limit).collect(Collectors.toList()); + + rspData.setRows(pageList); + + return rspData; + + } + + public List getDistributionDtoByDataList(List list,String coin){ + PoolUnits poolUnit = (PoolUnits) EnumUtils.get(PoolUnits.class, coin); + int rate = 1000; + if(StringUtils.isNotNull(poolUnit)){ + rate = poolUnit.getgRate(); + } + Map map =new LinkedHashMap<>(); + //设置默认格子 + int interval = 10; + if (list.size() < 1){ + map.put(0,0); + map.put(10,0); + map.put(20,0); + map.put(30,0); + map.put(40,0); + map.put(50,0); + map.put(60,0); + map.put(70,0); + map.put(80,0); + map.put(90,0); + }else { + double min = list.stream().min(Comparator.comparingDouble(MinerDataDto::getMhs)).get().getMhs(); + double max = list.stream().max(Comparator.comparingDouble(MinerDataDto::getMhs)).get().getMhs(); + + //最大最小值除以10 根据这个值去判断每个格子的Value 固定为10格 + double v = BigDecimal.valueOf(max).subtract(BigDecimal.valueOf(min)).divide(BigDecimal.valueOf(10), 2, BigDecimal.ROUND_HALF_UP).doubleValue(); + + //根据上一步计算的值确定格子 + + if(v<=10){ + interval=10; + }else if(v <=100){ + interval = 100; + }else if(v <=200){ + interval = 200; + }else if(v <=500){ + interval = 500; + }else if(v <=1000){ + interval = 1000; + }else { + //todo 修改为先进行list进制转换 如MH/s -> GH/s再进行处理 + interval = 10000; + } + + int temp = (int) (min / interval); + int start = temp * interval; + for(int i=0;i<10;i++){ + int key = start + (i*interval); + map.put(key,0); + } + + int gRate = rate; + int dataInterval = interval; + list.stream().forEach(e -> { + int a = (int) (e.getMhs() / dataInterval) * dataInterval; + if(map.containsKey(a)){ + map.replace(a,map.get(a)+1); + }else { + map.put(a,1); + } + }); + } + + + int finalInterval = interval; + List returnList = map.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey())) + .map(e -> { + DistributionDto dto = new DistributionDto(); + dto.setLow(e.getKey()); + dto.setHigh(e.getKey()+ finalInterval); + dto.setCount(e.getValue()); + return dto; + }).collect(Collectors.toList()); + + return returnList; + } + + public List getDailyDistributionDtoByDataList(List list,String coin){ + PoolUnits poolUnit = (PoolUnits) EnumUtils.get(PoolUnits.class, coin); + int rate = 1000; + if(StringUtils.isNotNull(poolUnit)){ + rate = poolUnit.getgRate(); + } + Map map =new LinkedHashMap<>(); + //设置默认格子 + int interval = 10; + if (list.size() < 1){ + map.put(0,0); + map.put(10,0); + map.put(20,0); + map.put(30,0); + map.put(40,0); + map.put(50,0); + map.put(60,0); + map.put(70,0); + map.put(80,0); + map.put(90,0); + }else { + double min = list.stream().min(Comparator.comparingDouble(MinerDataDto::getMhs24h)).get().getMhs24h(); + double max = list.stream().max(Comparator.comparingDouble(MinerDataDto::getMhs24h)).get().getMhs24h(); + + //最大最小值除以10 根据这个值去判断每个格子的Value 固定为10格 + double v = BigDecimal.valueOf(max).subtract(BigDecimal.valueOf(min)).divide(BigDecimal.valueOf(10), 2, BigDecimal.ROUND_HALF_UP).doubleValue(); + + //根据上一步计算的值确定格子 + + if(v<=10){ + interval=10; + }else if(v <=100){ + interval = 100; + }else if(v <=200){ + interval = 200; + }else if(v <=500){ + interval = 500; + }else if(v <=1000){ + interval = 1000; + }else { + //todo 修改为先进行list进制转换 如MH/s -> GH/s再进行处理 + interval = 10000; + } + + int temp = (int) (min / interval); + int start = temp * interval; + for(int i=0;i<10;i++){ + int key = start + (i*interval); + map.put(key,0); + } + + int gRate = rate; + int dataInterval = interval; + list.stream().forEach(e -> { + int a = (int) (e.getMhs24h() / dataInterval) * dataInterval; + if(map.containsKey(a)){ + map.replace(a,map.get(a)+1); + }else { + map.put(a,1); + } + }); + } + + + int finalInterval = interval; + List returnList = map.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey())) + .map(e -> { + DistributionDto dto = new DistributionDto(); + dto.setLow(e.getKey()); + dto.setHigh(e.getKey()+ finalInterval); + dto.setCount(e.getValue()); + return dto; + }).collect(Collectors.toList()); + + return returnList; + } + + + public int getTotalPage(int total,int size){ + if(size == 0){ + return 0; + } + int page = total /size; + if(total % size != 0){ + page++; + } + return page; + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/NoticeServiceImpl.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/NoticeServiceImpl.java new file mode 100644 index 0000000..ffd2f37 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/NoticeServiceImpl.java @@ -0,0 +1,226 @@ +package com.m2pool.pool.service.impl; + +import com.alibaba.fastjson.JSON; +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.utils.CodeUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.pool.dto.NoticeListDto; +import com.m2pool.pool.dto.UserAccountDto; +import com.m2pool.pool.mapper.NoticeMapper; +import com.m2pool.pool.mapper.UserAccountMapper; +import com.m2pool.pool.service.NoticeService; +import com.m2pool.pool.vo.*; +import com.m2pool.system.api.RemoteMailService; +import com.m2pool.system.api.entity.EmailCodeEntity; +import com.m2pool.system.api.entity.EmailEntity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @Description TODO + * @Date 2024/8/22 14:50 + * @Author 杜懿 + */ +@Service +public class NoticeServiceImpl implements NoticeService { + + @Autowired + private NoticeMapper noticeMapper; + + @Autowired + private UserAccountMapper uaMapper; + + @Autowired + private RemoteMailService mailService; + + @Autowired + private RedisService redisService; + + @Override + public List getNoticeEmailList(NoticePageVo vo) { + String user = SecurityUtils.getUsername(); + if(StringUtils.isBlank(user)){ + return new ArrayList<>(); + } + List list = noticeMapper.getNoticeEmailListByMaId(vo,user); + return list; + } + + @Override + public AjaxResult addNoticeEmail(NoticeAddVo vo) { + String user = SecurityUtils.getUsername(); + if(StringUtils.isBlank(user)){ + return AjaxResult.error("token身份解析异常"); + } + if(vo.getMaId() <= 0){ + return AjaxResult.error("挖矿账户id传参错误"); + } + UserAccountDto userAccount = uaMapper.getAccountByUserAccountId(user, vo.getMaId()); + + if(StringUtils.isNull(userAccount)){ + //未查询到对应ma_id; + System.out.println("当前挖矿账号id和当前登录账号无关"); + return AjaxResult.error("挖矿账号id错误"); + } + + boolean exist = noticeMapper.checkEmailIfExist(vo.getMaId(), vo.getEmail()); + if(exist){ + return AjaxResult.error("不能添加相同邮箱!"); + } + + //校验邮箱验证码 + String redisKey = "notice:"+vo.getEmail()+":code"; + if(redisService.hasKey(redisKey)){ + + Object o = redisService.getCacheObject(redisKey);//notice:email:code + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + + //验证验证码 + if(!vo.getCode().equals(emailCodeEntity.getEmailCode())){ + return AjaxResult.error("邮箱验证码错误"); + } + }else { + return AjaxResult.error("邮箱验证码 未获取或已过期(有效期10分钟),请重新获取验证码"); + } + + + int count = noticeMapper.getNoticeEmailCountByMaId(userAccount.getId()); + + if(count >= 3){ + return AjaxResult.error("当前挖矿账号下通知邮箱数已达上限:3"); + } + + boolean result = noticeMapper.addNoticeEmail(vo); + if(result){ + redisService.deleteObject(redisKey); + return AjaxResult.success("通知邮箱添加成功"); + }else { + return AjaxResult.error("通知邮箱添加失败,请稍后再试"); + } + } + + @Override + public AjaxResult getCode(NoticeAddVo vo) { + + String user = SecurityUtils.getUsername(); + if(StringUtils.isBlank(user)){ + return AjaxResult.error("token身份解析异常"); + } + UserAccountDto userAccount = uaMapper.getAccountByUserAccountId(user, vo.getMaId()); + + if(StringUtils.isNull(userAccount)){ + //未查询到对应ma_id; + System.out.println("当前挖矿账号id和当前登录账号无关"); + return AjaxResult.error("挖矿账号id错误"); + } + + //判断用户是不是恶意刷邮箱,在规定时间内进行的 + String redisKey = "notice:"+vo.email+":code"; + if (redisService.hasKey(redisKey)) { + Object o = redisService.getCacheObject(redisKey);//notice:email:code + + EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class); + if (emailCodeEntity.getTimes() > 4) { + return AjaxResult.error("请求次数过多,请10分钟后再试,(同一用户10分钟内只能请求4次)"); + } else { + + String emailCode = CodeUtils.creatCode(6); + emailCodeEntity.setEmailCode(emailCode); + + + emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1); + long overTime = redisService.getExpire(redisKey); + redisService.setCacheObject(redisKey, emailCodeEntity, overTime, TimeUnit.SECONDS + ); + return sendEmail(vo.getEmail(), emailCode,userAccount.getAccount()); + } + } else { + String emailCode = CodeUtils.creatCode(6); + // 最多允许用户在10分钟内发送2次的邮箱验证 + // 0s倒计时后用户可以再发送验证码,但是间隔在10分钟内只能再发送1次 + EmailCodeEntity emailCodeEntity = new EmailCodeEntity(); + emailCodeEntity.setEmail(vo.getEmail()); + emailCodeEntity.setEmailCode(emailCode); + emailCodeEntity.setTimes(1); + //设置失效时间10分钟 + redisService.setCacheObject(redisKey, emailCodeEntity, + 10L, TimeUnit.MINUTES + ); + return sendEmail(vo.getEmail(),emailCode,userAccount.getAccount()); + } + + + } + + @Override + public AjaxResult updateInfo(NoticeUpdateVo vo) { + + String user = SecurityUtils.getUsername(); + if(StringUtils.isBlank(user)){ + return AjaxResult.error("token身份解析异常"); + } + + boolean ifExist = noticeMapper.checkIdAndMa(vo.getId(), user); + if(ifExist){ + //存在 执行修改 + if(vo.getRemark().length() >=299){ + return AjaxResult.error("备注过长,请控制在300字符以内!"); + } + boolean result = noticeMapper.updateInfo(vo); + if(result){ + return AjaxResult.success("修改成功"); + }else { + return AjaxResult.error("修改失败,请稍后再试"); + } + }else { + return AjaxResult.error("当前账号与对应通知id无关联,请勿擅自修改参数"); + } + } + + @Override + public AjaxResult deleteEmail(NoticeDelVo vo) { + String user = SecurityUtils.getUsername(); + if(StringUtils.isBlank(user)){ + return AjaxResult.error("token身份解析异常"); + } + long maId = noticeMapper.getMaIdByIdAndUser(vo.getId(), user); + if(StringUtils.isNull(maId)){ + //maId为null + return AjaxResult.error("当前账号与对应通知id无关联,请勿擅自修改参数"); + }else if(maId < 1){ + return AjaxResult.error("当前账号与对应通知id无关联,请勿擅自修改参数"); + }else { + boolean result = noticeMapper.deleteNoticeInfoById(vo,maId); + if(result){ + return AjaxResult.success("删除成功"); + }else { + return AjaxResult.error("删除成功,请稍后再试"); + } + } + } + + public AjaxResult sendEmail(String email,String code,String ma){ + String text = "您正在为您的挖矿账户: "+ma+" 添加离线通知邮箱!\n" + + "当前账户将会做为接收邮箱接收您挖矿账户下的矿机离线通知!\n" + + "您的邮箱验证验证码是:\n\t"+code + + "\n此验证码10分钟有效。"; + EmailEntity emailEntity = new EmailEntity(); + emailEntity.setEmail(email); + emailEntity.setSubject("[M2Pool] 接收离线通知邮箱 邮箱验证码"); + emailEntity.setText(text); + R r = mailService.sendTextMail(emailEntity); + if(r.getCode() == 200){ + return AjaxResult.success(); + }else { + return AjaxResult.error(r.getCode(),r.getMsg()); + } + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/PoolServiceImpl.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/PoolServiceImpl.java new file mode 100644 index 0000000..b2b7be7 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/PoolServiceImpl.java @@ -0,0 +1,611 @@ +package com.m2pool.pool.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.github.pagehelper.PageHelper; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.OpenApiJwtUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.datasource.annotation.UserDB; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.pool.dto.*; +import com.m2pool.pool.entity.BlockInfo; +import com.m2pool.pool.entity.OpenApiKey; +import com.m2pool.pool.enums.*; +import com.m2pool.pool.mapper.PoolMapper; +import com.m2pool.pool.mapper.UserAccountMapper; +import com.m2pool.pool.service.PoolService; +import com.m2pool.pool.utils.EnumUtils; +import com.m2pool.pool.utils.NodeRpc; +import com.m2pool.pool.utils.PowerUnitUtils; +import com.m2pool.pool.vo.BalanceListGetVo; +import com.m2pool.pool.vo.CoinVo; +import com.m2pool.pool.vo.MinerAccountAddVo; + +import com.m2pool.pool.vo.TestVo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Description TODO + * @Date 2024/6/14 14:29 + * @Author dy + */ +@Service +public class PoolServiceImpl implements PoolService { + + @Autowired + private RedisService redisService; + + @Autowired + private PoolMapper poolMapper; + + @Autowired + private UserAccountMapper uaMapper; + + + @Override + public AjaxResult getPoolInfo(CoinVo vo) { + //校验coin是否在规定范围之内 + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("参数错误,未能根据当前参数获取到数据"); + } + + //构造返回参数 + + PageHelper.clearPage(); + //todo 从30分钟表拿最新的一组 根据状态为online的统计 -> 从mhs_real表拿最新的一组 根据状态为online的统计->从mhs_realv2表拿 然后统一乘以算力系数 + //PoolInfoDto dto = poolMapper.getPoolInfoByCoin(pool.getMhs()+"_realv2"); + //if(StringUtils.isNull(dto)){ + // dto = new PoolInfoDto(); + //} + PoolInfoDto dto = new PoolInfoDto(); + + if("enx".equals(pool.getCoin())){ + //enx 暂时不拿节点相关的东西(全网算力、高度、难度) + dto.setTotalDifficulty(""); + dto.setTotalPower(""); + dto.setHeight(0); + dto.setPrice(""); + Date now= new Date(); + if(now.before(DateUtils.parseDate("2025-04-21"))){ + dto.setFee(0); + } + }else { + //从redis中拿币种当前高度、全网算力、全网难度 + BlockInfo info = redisService.getCacheObject(pool.getCoin() + "_block"); + if(StringUtils.isNotNull(info)){ + //System.out.println("info:"+info); + //高度 + dto.setHeight(info.getHeight()); + + //全网难度 + dto.setTotalDifficulty(PowerUnitUtils.difficultyFormat(info.getDifficulty())); + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + Calendar ca = new GregorianCalendar(); + ca.setTime(now); + ca.add(ca.DATE,-1); + String start = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, ca.getTime()); + BigDecimal netPower = poolMapper.getAvgNetPower(pool.getCoin(), start); + //全网算力 调用转换工具 动态转换为数值+单位 + dto.setTotalPower(PowerUnitUtils.powerFormat(netPower,pool.getCoin())); + + + //System.out.println("dto"+dto); + }else { + System.out.println("未能从节点获取到信息"); + } + + BigDecimal price = redisService.getCacheObject(pool.getCoin() + "_price"); + dto.setPrice(price.toEngineeringString()+ " USD"); + } + + //从enums中拿币种名、页面显示名、币种对应算法 + dto.setCoin(pool.getCoin()); + dto.setName(pool.getName()); + dto.setAlgorithm(pool.getAlgorithm()); + + //从池子算力统计表pool_stats拿对应矿池算力 30m + //从实时表拿24h + double mhs = Convert.toDouble(poolMapper.getNowPoolDailyPower(pool.getMhs()+"_realv2"),0.0) * pool.getFac(); + + PoolUnits poolUnit = (PoolUnits) EnumUtils.get(PoolUnits.class, vo.getCoin()); + //将数据库数据转换为H/s + BigDecimal hPower = BigDecimal.valueOf(mhs).multiply(BigDecimal.valueOf(poolUnit.gethRate())); + + dto.setPoolPower(PowerUnitUtils.powerFormat(hPower,pool.getCoin())); + + + //System.out.println("矿池详情数据"+dto); + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getPoolPower(CoinVo vo) { + + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("参数错误,未能根据当前参数获取到数据"); + } + + if(StringUtils.isBlank(vo.getInterval())){ + return AjaxResult.error("缺失参数:interval"); + } + + List priceList = poolMapper.getHourNetPowerList(pool.getCoin()); + + int scale = PoolProfitScale.getScaleByCoin(pool.getCoin()); + + if("1h".equals(vo.getInterval()) || "rt".equals(vo.getInterval())){ + //30m + PageHelper.clearPage(); + //矿池算力从$coin_pool_30m拿 + List list = poolMapper.get30mPoolPowerList(pool.getPoolTable()+"_30m"); + Collections.reverse(list); + + //根据币种做参数处理 + list.stream().forEach(e -> { + //MH/s 每秒转GH/s + e.setPv(e.getPv().divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + }); + + list.stream().forEach(e -> priceList.stream().anyMatch(p ->{ + if(p.getDate().equals(e.getDate())){ + e.setPrice(p.getPrice()); + } + return false; + })); + + return AjaxResult.success(list); + }else if ("1d".equals(vo.getInterval())){ + PageHelper.clearPage(); + //矿池算力从$coin_pool_1d拿 + List list = poolMapper.getDailyPoolPowerList(pool.getPoolTable()+"_24h"); + Collections.reverse(list); + + //根据币种做参数处理 + list.stream().forEach(e -> { + //MH/s 每秒转GH/s + e.setDate(DateUtils.addDays(e.getDate(),-1)); + e.setPv(e.getPv().divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + }); + + List lastList = poolMapper.getMinerInfoList(pool.getMhs()); + BigDecimal mhs = lastList.stream().map(e -> { + return BigDecimal.valueOf(e.getMhs24h()); + }).reduce(BigDecimal.ZERO, BigDecimal::add).multiply(BigDecimal.valueOf(pool.getFac())); + PowerLineDto lastPLDto= new PowerLineDto(); + Date now = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + lastPLDto.setDate(now); + lastPLDto.setPv(mhs.divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + list.add(lastPLDto); + + //todo 最后一天数据用当天实时24h算力 + //List lastList = poolMapper.getMinerInfoList(pool.getMhs()); + //BigDecimal mhs = lastList.stream().map(e -> { + // return BigDecimal.valueOf(e.getMhs24h()); + //}).reduce(BigDecimal.ZERO, BigDecimal::add); + // + //PowerLineDto lastPLDto= new PowerLineDto(); + //Date now = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + //lastPLDto.setDate(now); + //lastPLDto.setPv(mhs); + //list.add(lastPLDto); + + list.stream().forEach(e -> priceList.stream().anyMatch(p ->{ + if(p.getDate().equals(e.getDate())){ + e.setPrice(p.getPrice()); + } + return false; + })); + + return AjaxResult.success(list); + }else { + return AjaxResult.error("参数错误:interval"); + } + + + } + + @Override + public boolean checkAccount(MinerAccountAddVo vo) { + + System.out.println("收到的参数为:"+vo); + + //校验coin是否在规定范围之内 + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return false; + } + + if(uaMapper.checkAccountIfExist(vo.getMa(),vo.getCoin())){ + System.out.println(true); + return true; + } + return false; + } + + //@Override + //public AjaxResult getMinerCount(CoinVo com.m2pool.chat.vo) { + // + // Pools pool = (Pools) EnumUtils.get(Pools.class, com.m2pool.chat.vo.getCoin()); + // if(StringUtils.isNull(pool)){ + // return AjaxResult.error("参数错误,未能根据当前参数获取到数据"); + // } + // + // PageHelper.clearPage(); + // List list = poolMapper.getMinerCountList(pool.getPoolTable()); + // + // return AjaxResult.success(list); + //} + + @Override + public AjaxResult getLuckByCoin(CoinVo vo) { + + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("参数错误,未能根据当前参数获取到数据"); + } + + //幸运值 + //直接从redis拿 key ${coin}Luck ${coin}.toUP + + LuckDto dto = redisService.getCacheObject(pool.getCoin().toUpperCase()+"Luck"); + if(StringUtils.isNull(dto)){ + dto = new LuckDto(); + } + + return AjaxResult.success(dto); + } + + @Override + public List getBlockInfoList(CoinVo vo) { + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return null; + } + + List list = new ArrayList<>(); + //todo 换到hashrate表查询新的矿池报块表 + switch (pool.getCoin()){ + case "grs": list=poolMapper.getGRSBlockInfoList();break; + case "mona": list=poolMapper.getMonaBlockInfoList();break; + case "dgbs": list=poolMapper.getDgbSkeinBlockInfoList();break; + case "dgbq": list=poolMapper.getDgbQubitBlockInfoList();break; + case "dgbo": list=poolMapper.getDGBODOBlockInfoList();break; + case "dgb_qubit_a10": list=poolMapper.getDgbQubitA10BlockInfoList();break; + case "dgb_skein_a10": list=poolMapper.getDgbSkeinA10BlockInfoList();break; + case "dgb_odo_b20": list=poolMapper.getDGBODOB20BlockInfoList();break; + case "nexa": list=poolMapper.getNEXABlockInfoList();break; + case "rxd": list=poolMapper.getRXDBlockInfoList();break; + case "alph": list=poolMapper.getALPHBlockInfoList();break; + case "enx": list=poolMapper.getENXBlockInfoList();break; + default: + break; + } + + //list.stream().forEach(e -> + // e.setReward(BigDecimal.valueOf(e.getReward()).subtract(BigDecimal.valueOf(e.getFees())).doubleValue())); + return list; + } + + //@Override + //public AjaxResult getPoolPowerDistribution(CoinVo com.m2pool.chat.vo) { + // Pools pool = (Pools) EnumUtils.get(Pools.class, com.m2pool.chat.vo.getCoin()); + // + // if("rt".equals(com.m2pool.chat.vo.getInterval())){ + // //实时分布 直接从miners_stats表拿最新数据 + // //List minerList= poolMapper.getNowMinerInfoList(pool.getMhs()); + // //从30m算力表 mhs30m + // List minerList= poolMapper.getMinerInfoList(pool.getMhs()+"30m"); + // + // List returnList = getDistributionDtoByDataList(minerList); + // + // return AjaxResult.success(returnList); + // } + // else if ("1d".equals(com.m2pool.chat.vo.getInterval())){ + // //从每日数据表拿最新一组数据 如 grs从grs_miners_1d表拿user为指定挖矿账户的一组数据 + // String table = pool.getMhs()+"24h"; + // List list = poolMapper.getDailyPoolPowerDistributionList(table); + // List returnList = getDistributionDtoByDataList(list); + // return AjaxResult.success(returnList); + // }else { + // return AjaxResult.error("参数错误:interval"); + // } + //} + + @Override + @Transactional(rollbackFor = Exception.class) + @UserDB + public AjaxResult getCoinBalanceList(BalanceListGetVo vo) { + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + + if(StringUtils.isNull(pool)){ + return AjaxResult.error(707,"coin error"); + } + + List list = uaMapper.getBalanceByCoin(pool.getCoin()); + return AjaxResult.success(list); + } + + @Override + public AjaxResult getNetPower(CoinVo vo) { + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("参数错误,未能根据当前参数获取到数据"); + } + + if(StringUtils.isBlank(vo.getInterval())){ + return AjaxResult.error("缺失参数:interval"); + } + + int scale = PoolProfitScale.getScaleByCoin(pool.getCoin()); + List list = poolMapper.getHourNetPowerList(pool.getCoin()); + //根据币种做参数处理 + list.stream().forEach(e -> { + //H/s 每秒转GH/s + if(StringUtils.isNotNull(e.getPv())){ + if("alph".equals(pool.getCoin())){ + //alph 数据库按MH/s存的 + e.setPv(e.getPv().divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + }else { + e.setPv(e.getPv().divide(BigDecimal.valueOf(1000000000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + } + }else { + e.setPv(BigDecimal.valueOf(0)); + } + + }); + + if("1h".equals(vo.getInterval()) || "rt".equals(vo.getInterval())){ + PageHelper.clearPage(); + //30m数据 限制条数为最近1天的条数 即1*48=48 + list = list.stream().sorted(Comparator.comparing(PowerLineDto::getDate, Comparator.reverseOrder())).limit(48).collect(Collectors.toList()); + Collections.reverse(list); + return AjaxResult.success(list); + }else if ("1d".equals(vo.getInterval())){ + PageHelper.clearPage(); + //每天数据 从list中赛选Hour为0的值 + list = list.stream().filter(e -> e.getDate().getHours() == 0 && e.getDate().getMinutes() == 0 ).collect(Collectors.toList()); + if(list.size()>30){ + list = list.stream().sorted(Comparator.comparing(PowerLineDto::getDate, Comparator.reverseOrder())).limit(30).collect(Collectors.toList()); + Collections.reverse(list); + } + return AjaxResult.success(list); + }else { + return AjaxResult.error("参数错误:interval"); + } + } + + @Override + public AjaxResult getParam(CoinVo vo) { + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error(606,"参数错误,未能获取到该币种数据"); + } + + CalParamDto dto = new CalParamDto(); + if("enx".equals(pool.getCoin())){ + dto.setNetHashrate(BigDecimal.valueOf(1)); + dto.setCount(BigDecimal.valueOf(86400)); + dto.setCoin(pool.getCoin()); + dto.setReward(BigDecimal.ZERO); + Date now = new Date(); + if(now.before(DateUtils.parseDate("2025-04-21"))){ + dto.setPoolFee(BigDecimal.valueOf(0)); + }else { + dto.setPoolFee(BigDecimal.valueOf(1)); + } + + PoolCalParamConfig calConfig = (PoolCalParamConfig) EnumUtils.get(PoolCalParamConfig.class, vo.getCoin()); + if(StringUtils.isNull(calConfig)){ + return AjaxResult.error(606,"参数错误,未能根据当前参数获取到数据"); + } + + dto.setCoin(pool.getCoin()); + dto.setCount(calConfig.getCount()); + + + //预计26-33 ph + double hash = (Math.random() * 33) + 26; + dto.setNetHashrate(BigDecimal.valueOf(hash).multiply(BigDecimal.valueOf(1000)) + .multiply(BigDecimal.valueOf(1000)) + .multiply(BigDecimal.valueOf(1000)) + .multiply(BigDecimal.valueOf(1000)) + .multiply(BigDecimal.valueOf(1000)).setScale(2,BigDecimal.ROUND_HALF_UP)); + dto.setPoolFee(calConfig.getPoolFees()); + + dto.setReward(BigDecimal.valueOf(333)); + + return AjaxResult.success(dto); + + } + else { + BlockInfo info = redisService.getCacheObject(pool.getCoin() + "_block"); + + if(StringUtils.isNull(info)){ + if(!"enx".equals(pool.getCoin()) && !"alph".equals(pool.getCoin())){ + // + return AjaxResult.error(606,"相关系数获取失败"); + } + + } + + PoolCalParamConfig calConfig = (PoolCalParamConfig) EnumUtils.get(PoolCalParamConfig.class, vo.getCoin()); + if(StringUtils.isNull(calConfig)){ + return AjaxResult.error(606,"参数错误,未能根据当前参数获取到数据"); + } + + + + dto.setCoin(pool.getCoin()); + dto.setCount(calConfig.getCount()); + dto.setNetHashrate(info.getPower()); + dto.setPoolFee(calConfig.getPoolFees()); + + //BigDecimal price = redisService.getCacheObject(pool.getCoin() + "_price"); + //if(StringUtils.isNull(price)){ + // return AjaxResult.error(606,"币价未获取到"); + //} + //dto.setPrice(price); + + if(StringUtils.isNull(info.getReward())){ + return AjaxResult.error(606,"相关系数获取失败."); + } + + if(StringUtils.isNull(info.getFees())){ + return AjaxResult.error(606,"相关系数获取失败。"); + } + dto.setReward(info.getReward().subtract(info.getFees())); + + + + return AjaxResult.success(dto); + } + + } + + @Override + @DSTransactional + public AjaxResult test2() { + BlockInfo blockInfo = NodeRpc.getBlock("nexa"); + return AjaxResult.success(blockInfo); + } + + @Override + public AjaxResult test1() { + String coin = "mona"; + String nowHeight = NodeRpc.getHeight(coin); + + if(nowHeight.contains("error:")){ + return AjaxResult.error("error"); + } + + BlockInfo blockInfo = new BlockInfo(); + //todo 调用getblockstats 拿subsidy/100000000做reward fees设置为0、拿blockhash + Object[] statsParams = {Convert.toLong(nowHeight)}; + String blockstats = NodeRpc.getResultTest(coin, "getblockstats", statsParams); + if(StringUtils.isBlank(blockstats)){ + System.out.println("blockstats查询结果为空"); + return AjaxResult.success(blockInfo); + } + + if(blockstats.contains("error:")){ + System.out.println("blockstats包含错误:"+blockstats); + return AjaxResult.success(blockInfo); + } + + JSONObject statsJs = JSON.parseObject(blockstats); + String blockhash = statsJs.getString("blockhash"); + if(StringUtils.isBlank(blockstats)){ + System.out.println("blockhash获取结果为空"); + return AjaxResult.success(blockInfo); + } + System.out.println(blockhash); + + + BigDecimal subsidy = statsJs.getBigDecimal("subsidy"); + if(StringUtils.isNull(subsidy)) { + System.out.println("subsidy获取结果为空"); + return AjaxResult.success(blockInfo); + + } + subsidy = subsidy.divide(BigDecimal.valueOf(Math.pow(10,8)),4,BigDecimal.ROUND_HALF_UP); + System.out.println(subsidy); + Object[] params = {blockhash}; + + String result = NodeRpc.getResultTest(coin, "getblockheader", params); + System.out.println("result"+result); + + + + if(StringUtils.isBlank(result)){ + System.out.println("查询结果为空"); + return AjaxResult.success(blockInfo); + + } + + if(result.contains("error:")){ + System.out.println("包含错误:"+result); + return AjaxResult.success(blockInfo); + } + + JSONObject jsonObject = JSON.parseObject(result); + long height = jsonObject.getLongValue("height"); + blockInfo.setHeight(height); + + BigDecimal difficulty = jsonObject.getBigDecimal("difficulty"); + if(StringUtils.isNotNull(difficulty)){ + blockInfo.setDifficulty(difficulty.setScale(2,BigDecimal.ROUND_HALF_UP)); + NodeConstant constant = (NodeConstant) EnumUtils.get(NodeConstant.class, coin); + BigDecimal factor = constant.getFactor(); + if(StringUtils.isNotNull(factor)){ + blockInfo.setPower(difficulty.multiply(factor).setScale(2,BigDecimal.ROUND_HALF_UP)); + } + + } + + blockInfo.setReward(subsidy); + blockInfo.setFees(BigDecimal.valueOf(0)); + blockInfo.setProfit(blockInfo.getReward().subtract(blockInfo.getFees())); + return AjaxResult.success(blockInfo); + } + + + public List getDistributionDtoByDataList(List list){ + + Map map =new LinkedHashMap<>(); + map.put(0,0); + map.put(5,0); + map.put(10,0); + map.put(15,0); + map.put(20,0); + map.put(25,0); + map.put(30,0); + map.put(35,0); + map.put(40,0); + map.put(45,0); + map.put(50,0); + map.put(55,0); + map.put(60,0); + map.put(65,0); + map.put(70,0); + map.put(75,0); + map.put(80,0); + map.put(85,0); + map.put(90,0); + map.put(95,0); + list.stream().forEach(e -> { + int a = (int) ((Math.round(e.getMhs()) / 1000) / 5 * 5); + if(map.containsKey(a)){ + map.replace(a,map.get(a)+1); + }else { + map.put(a,1); + } + }); + List returnList = map.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey())) + .map(e -> { + DistributionDto dto = new DistributionDto(); + dto.setLow(e.getKey()); + dto.setHigh(e.getKey()+5); + dto.setCount(e.getValue()); + return dto; + }).collect(Collectors.toList()); + + return returnList; + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/ReadOnlyServiceImpl.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/ReadOnlyServiceImpl.java new file mode 100644 index 0000000..ed14673 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/ReadOnlyServiceImpl.java @@ -0,0 +1,1012 @@ +package com.m2pool.pool.service.impl; + +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.m2pool.common.core.constant.HttpStatus; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.uuid.IdUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.pool.dto.*; +import com.m2pool.pool.entity.UserReadOnlyPageInfo; +import com.m2pool.pool.entity.UserReadOnlyPage; +import com.m2pool.pool.enums.PoolProfitScale; +import com.m2pool.pool.enums.PoolUnits; +import com.m2pool.pool.enums.Pools; +import com.m2pool.pool.mapper.AccountDistributionMapper; +import com.m2pool.pool.mapper.PoolMapper; +import com.m2pool.pool.mapper.UserAccountMapper; +import com.m2pool.pool.service.ReadOnlyService; +import com.m2pool.pool.utils.EnumUtils; +import com.m2pool.pool.vo.*; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Description TODO + * @Date 2024/6/14 14:29 + * @Author dy + */ +@Service +public class ReadOnlyServiceImpl implements ReadOnlyService { + + @Autowired + private UserAccountMapper uaMapper; + + @Autowired + private AccountDistributionMapper distributionMapper; + + @Autowired + private RedisService redisService; + + @Autowired + private PoolMapper poolMapper; + + @Value("${myenv.domain}") + private String domain; + + @Value("${myenv.img}") + private String STATIC_IMG; + + private final String READ_ONLY="/readOnlyDisplay"; + + + @DSTransactional + @Override + public AjaxResult createPageUrl(ReadOnlyHtmlCreateVo vo) { + long t0 = System.currentTimeMillis(); + //获取邮箱 网站用的邮箱做用户名 + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return AjaxResult.error("token解析异常"); + } + UserAccountDto info = uaMapper.getAccountByUserAccountAndCoin(email, vo.getAccount(), vo.getCoin()); + if(StringUtils.isNull(info)){ + return AjaxResult.success("根据币种["+vo.getCoin()+"]未找到对应挖矿账号"); + } + String config = StringUtils.clean(vo.getConfig()); + if(StringUtils.isBlank(config)){ + return AjaxResult.error("请选择至少一个权限"); + } + //生产uuid作为唯一id + String key = IdUtils.fastSimpleUUID(); + //判断key是否重复 重复则更换 + if(uaMapper.checkKeyIfExist(key)){ + key = IdUtils.fastSimpleUUID(); + if(uaMapper.checkKeyIfExist(key)){ + return AjaxResult.success("网络异常,请稍后再试"); + } + } + + //构造入库数据 + UserReadOnlyPage data = new UserReadOnlyPage(); + data.setEmail(info.getUser()); + data.setAccount(info.getAccount()); + data.setCoin(info.getCoin()); + data.setKey(key); + //默认设置为zh + data.setLang("zh"); + if(StringUtils.isNotNull(vo.getLang())){ + if("en".equals(vo.getLang())){ + data.setLang("en"); + } + } + data.setRemark(StringUtils.clean(vo.getRemark())); + data.setConfig(config); + + //数据入库 + boolean result = uaMapper.createReadOnlyPage(data); + if(result){ + PageUrlDto dto = new PageUrlDto(); + dto.setUrl(domain+"/"+data.getLang()+READ_ONLY+"?key="+key); + dto.setKey(key); + long t1 = System.currentTimeMillis(); + System.out.println("创建页面业务耗时"+(t1-t0)+"ms"); + return AjaxResult.success(dto); + }else { + return AjaxResult.error("网络异常,请稍后再试"); + } + } + + @Override + public TableDataInfo getUrlList(PageVo vo) { + long t0 = System.currentTimeMillis(); + //获取邮箱 网站用的邮箱做用户名 + String email = SecurityUtils.getUsername(); + TableDataInfo table = new TableDataInfo(); + if(StringUtils.isNull(email)){ + table.setCode(HttpStatus.ERROR); + table.setRows(new ArrayList<>()); + table.setMsg("未获取到token中的用户名,请稍后再试"); + table.setTotal(0); + table.setTotalPage(0); + return table; + } + List list = uaMapper.getPageListByUser(email); + List returnList = list.stream().map(e -> { + UserReadPageInfoReturnDto dto = new UserReadPageInfoReturnDto(); + BeanUtils.copyProperties(e,dto); + dto.setLabel(Pools.getNameByCoin(e.getCoin())); + if(StringUtils.isBlank(e.getLang())){ + //没值则默认设为中文 + e.setLang("zh"); + } + if("en".equals(e.getLang()) && "zh".equals(e.getLang())){ + //违规参数则默认设为中文 + e.setLang("zh"); + } + dto.setUrl(domain +"/"+e.getLang() + READ_ONLY + "?key=" + e.getKey()); + dto.setImg(domain+STATIC_IMG+"/"+Pools.getImgByCoin(e.getCoin())); + if(StringUtils.isNull(e.getConfig())){ + //防止字段为null时抛出异常 字段未null时设置字段为"" + dto.setConfig("".split(",")); + }else { + dto.setConfig(e.getConfig().split(",")); + } + return dto; + }).collect(Collectors.toList()); + + table.setCode(HttpStatus.SUCCESS); + table.setMsg("查询成功"); + PageInfo pageInfo = new PageInfo(list); + table.setTotal(pageInfo.getTotal()); + table.setTotalPage(pageInfo.getPages()); + + int page = vo.getPage() < 1 ? 1 : vo.getPage(); + int limit = vo.getLimit() < 1 ? 1 : vo.getLimit(); + + List pageList = returnList.stream().skip((page - 1) * limit).limit(limit).collect(Collectors.toList()); + + table.setRows(pageList); + + return table; + } + + @Override + public AjaxResult getUrlInfo(ReadOnlyKeyVo vo) { + String key = StringUtils.clean(vo.getKey()); + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return null; + } + PageHelper.clearPage(); + UserReadOnlyPageInfo info = uaMapper.getPageInfoByKey(key); + if(StringUtils.isNull(info)){ + return AjaxResult.error("未找到对应Key"); + } + + if(!email.equals(info.getUser())){ + return AjaxResult.error("请勿擅自修改Key"); + } + UserReadPageInfoReturnDto dto = new UserReadPageInfoReturnDto(); + BeanUtils.copyProperties(info,dto); + + dto.setLabel(Pools.getNameByCoin(info.getCoin())); + if(StringUtils.isBlank(info.getLang())){ + //没值则默认设为中文 + info.setLang("zh"); + } + if("en".equals(info.getLang()) && "zh".equals(info.getLang())){ + //违规参数则默认设为中文 + info.setLang("zh"); + } + dto.setUrl(domain + "/" +info.getLang() + READ_ONLY + "?key=" + info.getKey()); + if(StringUtils.isNull(info.getConfig())){ + //防止字段为null时抛出异常 字段未null时设置字段为"" + dto.setConfig("".split(",")); + }else { + dto.setConfig(info.getConfig().split(",")); + } + return AjaxResult.success(dto); + } + + @Override + public AjaxResult changeUrlInfo(ReadOnlyHtmlChangeVo vo) { + if(StringUtils.isNotBlank(vo.getConfig())){ + if(!vo.getConfig().equals(StringUtils.clean(vo.getConfig()))){ + return AjaxResult.error("配置传参中传入违规字符"); + } + }else { + return AjaxResult.error("配置传参不能为空"); + } + + if(StringUtils.isNotBlank(vo.getKey())){ + if(!vo.getKey().equals(StringUtils.clean(vo.getKey()))){ + return AjaxResult.error("页面码传参中传入违规字符"); + } + } + + boolean result = uaMapper.changeReadOnlyPage(vo); + if(result){ + return AjaxResult.success("修改成功"); + }else { + result = uaMapper.changeReadOnlyPage(vo); + if(result){ + return AjaxResult.success("修改成功"); + }else { + if(uaMapper.checkKeyIfExist(vo.getKey())){ + return AjaxResult.error("网络异常,请稍后再试"); + }else { + return AjaxResult.error("当前Key失效,请检查是否修改"); + } + } + } + } + + @Override + @DSTransactional + public AjaxResult delPageByKey(ReadOnlyKeyVo vo) { + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return AjaxResult.error("token解析异常"); + } + + List keys = Arrays.asList(StringUtils.clean(vo.getKey()).split(",")); + + if(keys.size() < 1){ + return AjaxResult.error("key不能为空"); + } + + //todo 改成一次性查多个 + List list = uaMapper.getPageListByUser(email); + if(list.size() < 0){ + return AjaxResult.error("key不存在"); + } + + List userKeys = list.stream().map(UserReadOnlyPageInfo::getKey).collect(Collectors.toList()); + + if(userKeys.containsAll(keys)){ + //执行删除 + try { + uaMapper.batchDeletePageByKeyAndUser(keys, email); + return AjaxResult.success("删除成功"); + }catch (Exception e){ + return AjaxResult.error("网络异常,请稍后再试"); + } + + }else { + StringBuilder msg = new StringBuilder(); + msg.append("当前用户下不存在对应key:"); + keys.stream().forEach(key -> { + if(!userKeys.contains(key)){ + msg.append(key+"\n"); + } + }); + return AjaxResult.error(msg.toString()); + } + + } + + @Override + public AjaxResult getPageInfo(ReadOnlyKeyVo vo) { + String key = StringUtils.clean(vo.getKey()); + UserReadOnlyPageInfo info = uaMapper.getPageInfoByKey(key); + + ReadOnlyPageInfo returnInfo = new ReadOnlyPageInfo(); + returnInfo.setCoin(info.getCoin()); + returnInfo.setAccount(info.getAccount()); + returnInfo.setImg(domain+STATIC_IMG+"/"+Pools.getImgByCoin(info.getCoin())); + if(StringUtils.isNull(info.getConfig())){ + //防止字段为null时抛出异常 字段未null时设置字段为"" + returnInfo.setConfig("".split(",")); + }else { + returnInfo.setConfig(info.getConfig().split(",")); + } + returnInfo.setId(info.getId()); + return AjaxResult.success(returnInfo); + } + + @Override + public AjaxResult getProfitInfo(ReadOnlyKeyVo vo) { + //权限鉴定 通过key鉴定 权限配置中需要包含2 + UserReadOnlyPageInfo info = uaMapper.getPageInfoByKey(vo.getKey()); + if(StringUtils.isNull(info)){ + return AjaxResult.error("key不存在"); + } + boolean check = checkPermission("2", info.getConfig()); + if(!check){ + return AjaxResult.error("鉴权失败"); + } + + UserAccountDto dto = uaMapper.getAccountByUserAccountAndCoin(info.getUser(), info.getAccount(), info.getCoin()); + AccountProfitInfoDto infoDto = new AccountProfitInfoDto(); + if(StringUtils.isNull(dto)){ + return AjaxResult.error("找不到"+vo.getKey()+"对应数据"); + } + + infoDto.setAccount(dto.getAccount()); + + try { + + //根据用户名拿所有状态不为3(失败)的数据 然后根据拿到的数据分情况算总和 ->不筛选状态 + List list = distributionMapper.getDistributionByMinerAccount(dto.getCoin(), dto.getAccount()); + + Map countMap = list.stream().collect(Collectors.groupingBy( + ProfitDto::getStatus, + Collectors.reducing(BigDecimal.ZERO,ProfitDto::getProfit,BigDecimal::add) + )); + + BigDecimal total = list.stream().map(ProfitDto::getProfit).reduce(BigDecimal.ZERO, BigDecimal::add); + + //支付中 status =0 + BigDecimal pengding = countMap.getOrDefault(0, BigDecimal.ZERO); + //已支付 status =1 + BigDecimal success = countMap.getOrDefault(1, BigDecimal.ZERO); + + //2不足起付额 激活了自动提现 + BigDecimal state2 = countMap.getOrDefault(2, BigDecimal.ZERO); + //3满足起付额 没激活自动提现 + BigDecimal state3 = countMap.getOrDefault(3, BigDecimal.ZERO); + //4不足起付额 且没激活自动提现 + BigDecimal state4 = countMap.getOrDefault(4, BigDecimal.ZERO); + + //wallet_outv2表 已支出 + BigDecimal out = distributionMapper.getTotalOutByCoinAndUser(dto.getCoin(), dto.getAccount()); + if(StringUtils.isNull(out)) { + out =BigDecimal.ZERO; + } + + System.out.println("success:"+success+",pengding:"+pengding); + /******************************************** 获取今日和昨天起始时间 开始 *********************************************************/ + //因为数据库create_date为结算时间 所以以今日0为时间的数据代表昨日已挖 + Date today = DateUtils.dateTime(DateUtils.YYYY_MM_DD, DateUtils.getDate()); + + Calendar ca = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + ca.setTime(today); + ca.add(Calendar.DATE,1); + + Date todayEnd = ca.getTime(); + System.out.println(today+" to "+todayEnd); + /******************************************** 获取今日和昨天起始时间 结束 *********************************************************/ + + List preList = list.stream().filter(e -> + ((e.getDate().after(today) || e.getDate().equals(today)) && e.getDate().before(todayEnd))).collect(Collectors.toList()); + BigDecimal preProfit = BigDecimal.ZERO; + if (preList.size() > 0){ + preProfit = preList.stream() + .map(ProfitDto::getProfit) + .reduce(BigDecimal::add) + .get(); + } + + + //今日收益通过算力占比和今日报块总和去计算 当前挖矿账户 + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes(minute); + now.setSeconds(0); + List tbList = new ArrayList<>(); + switch (dto.getCoin().toLowerCase()){ + case "nexa": tbList = poolMapper.getNewNEXABlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "mona": tbList = poolMapper.getNewMONABlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "grs": tbList = poolMapper.getNewGRSBlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "dgbo": tbList = poolMapper.getNewDGBOBlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "dgbq": tbList = poolMapper.getNewDGBQBlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "dgbs": tbList = poolMapper.getNewDGBSBlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + case "rxd": tbList = poolMapper.getNewRXDBlockInfoList(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, today));break; + default: break; + } //今日矿池总收益 + BigDecimal reward = tbList.stream() + .filter(e -> (e.getDate().before(now) || e.getDate().equals(now))) + .map(e -> { + return BigDecimal.valueOf(e.getReward()); + }) + .reduce(BigDecimal.ZERO, BigDecimal::add); + //今日总收益 * 用户今日平均算力占比 = 用户今日预估收益 + BigDecimal ratio = Convert.toBigDecimal(redisService.getCacheMapValue(dto.getCoin().toUpperCase()+"UserPowerRatio", dto.getAccount()), BigDecimal.ZERO); + + //List todayList = list.stream().filter(e -> + // (e.getDate().after(today) || e.getDate().equals(today))).collect(Collectors.toList()); + BigDecimal todayProfit = reward.multiply(ratio).setScale(2,BigDecimal.ROUND_HALF_UP); + + + int scale = PoolProfitScale.getScaleByCoin(info.getCoin()); + // 总收益 支付中+已支付 ——》total + infoDto.setTotalProfit(total.setScale(scale,BigDecimal.ROUND_HALF_UP)); + System.out.println(infoDto); + //总支出 支付中+wallet_outv2表中全部 + infoDto.setExpend(pengding.add(out).setScale(scale,BigDecimal.ROUND_HALF_UP)); + System.out.println(infoDto); + //昨日收益 + infoDto.setPreProfit(preProfit.setScale(scale,BigDecimal.ROUND_HALF_UP)); + System.out.println(infoDto); + //今日收益 根据公式 (挖矿账户mhs24h/矿池mhs24h) * 当日矿池收益 当日矿池收益:select sum( + infoDto.setTodayPorfit(todayProfit.setScale(scale,BigDecimal.ROUND_HALF_UP)); + //账户余额 已成熟 + //总余额 总收益-总支出 + //infoDto.setBalance(infoDto.getTotalProfit().subtract(infoDto.getExpend())); + infoDto.setBalance(state2.add(state3).add(state4).setScale(scale,BigDecimal.ROUND_HALF_UP)); + //可提现余额 已成熟0 + infoDto.setWithdrawable(BigDecimal.ZERO); + + return AjaxResult.success(infoDto); + }catch (Exception e){ + System.out.println("异常:"); + e.printStackTrace(); + + infoDto.setTotalProfit(BigDecimal.valueOf(0)); + infoDto.setBalance(BigDecimal.valueOf(0)); + infoDto.setBalance(BigDecimal.valueOf(0)); + infoDto.setExpend(BigDecimal.valueOf(0)); + infoDto.setPreProfit(BigDecimal.valueOf(0)); + infoDto.setTodayPorfit(BigDecimal.valueOf(0)); + + return AjaxResult.success(infoDto); + } + } + + @Override + public AjaxResult getMinerAccountPower(ReadOnlyIntervalVo vo) { + //权限鉴定 通过key鉴定 权限配置中需要包含1 + UserReadOnlyPageInfo info = uaMapper.getPageInfoByKey(vo.getKey()); + if(StringUtils.isNull(info)){ + return AjaxResult.error("key不存在"); + } + //boolean check = checkPermission("1", info.getConfig()); + //if(!check){ + // return AjaxResult.error("鉴权失败"); + //} + + UserAccountDto dto = uaMapper.getAccountByUserAccountAndCoin(info.getUser(), info.getAccount(), info.getCoin()); + if(StringUtils.isNull(dto)){ + return AjaxResult.error("未请求到数据,请确认Key是否失效"); + } + Pools pool = (Pools) EnumUtils.get(Pools.class, dto.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("数据异常!请稍后再试"); + } + int scale = PoolProfitScale.getScaleByCoin(pool.getCoin()); + if("1h".equals(vo.getInterval()) || "rt".equals(vo.getInterval())){ + //根据需求改成返回30分钟的数据 + List list = poolMapper.get30mAccountPowerListByAccount(pool.getCoin()+"_users_30m", info.getAccount()); + Collections.reverse(list); + list.stream().forEach(e -> { + //MH/s 每秒转GH/s + e.setPv(e.getPv().divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + }); + return AjaxResult.success(list); + }else if ("1d".equals(vo.getInterval())){ + List list = poolMapper.getDailyAccountPowerListByAccount(pool.getMhs()+"24h",info.getAccount()); + //从里面筛选date=0 + Collections.reverse(list); + + list.stream().forEach(e -> { + //MH/s 每秒转GH/s + e.setDate(DateUtils.addDays(e.getDate(),-1)); + e.setPv(e.getPv().divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + }); + + List lastList = poolMapper.getMinerInfoList(pool.getMhs()); + BigDecimal mhs = lastList.stream().filter(e -> e.getUser().equals(dto.getAccount())).map(e -> { + return BigDecimal.valueOf(e.getMhs24h()); + }).reduce(BigDecimal.ZERO, BigDecimal::add).multiply(BigDecimal.valueOf(pool.getFac())); + PowerLineDto lastPLDto= new PowerLineDto(); + Date now = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + lastPLDto.setDate(now); + lastPLDto.setPv(mhs.divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).stripTrailingZeros()); + list.add(lastPLDto); + + + return AjaxResult.success(list); + }else { + return AjaxResult.error("参数错误:interval"); + } + } + + @Override + public AjaxResult getAccountPowerDistribution(ReadOnlyIntervalVo vo) { + //权限鉴定 通过key鉴定 权限配置中需要包含1 + UserReadOnlyPageInfo info = uaMapper.getPageInfoByKey(vo.getKey()); + if(StringUtils.isNull(info)){ + return AjaxResult.error("key不存在"); + } + //boolean check = checkPermission("1", info.getConfig()); + //if(!check){ + // return AjaxResult.error("鉴权失败"); + //} + + UserAccountDto dto = uaMapper.getAccountByUserAccountAndCoin(info.getUser(), info.getAccount(), info.getCoin()); + if(StringUtils.isNull(dto)){ + return AjaxResult.error("未请求到数据,请确认Key是否失效"); + } + Pools pool = (Pools) EnumUtils.get(Pools.class, dto.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("数据异常!请稍后再试"); + } + List minerList= poolMapper.getMinerInfoList(pool.getMhs()); + minerList.stream().forEach(e ->{ + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * pool.getFac()); + } + }); + List list = minerList.stream().filter(e -> info.getAccount().equals(e.getUser())).collect(Collectors.toList()); + if(StringUtils.isNull(list)){ + return AjaxResult.success(); + } + /************************************************ 过滤异常数据 开始 ******************************************************/ + // + Date nowDate = DateUtils.getNowDate(); + Date start = DateUtils.addDays(nowDate, -1); + //过滤掉last_submit超过一天的offline数据 + list = list.stream().filter(e -> e.getLastSubmit().after(start)).collect(Collectors.toList()); + /************************************************ 过滤异常数据 结束 ******************************************************/ + if(StringUtils.isNull(list)){ + return AjaxResult.success(); + } + + if("rt".equals(vo.getInterval())){ + //用mhs + List returnList = getDailyDistributionDtoByDataList(list, pool.getCoin()); + return AjaxResult.success(returnList); + + } + else if ("1d".equals(vo.getInterval())){ + //用mhs24h + List returnList = getDailyDistributionDtoByDataList(list, pool.getCoin()); + return AjaxResult.success(returnList); + + }else { + return AjaxResult.error("参数错误:interval"); + } + } + + @Override + public AjaxResult getMinerList(ReadOnlyListVo vo) { + //权限鉴定 通过key鉴定 权限配置中需要包含1 + UserReadOnlyPageInfo info = uaMapper.getPageInfoByKey(vo.getKey()); + if(StringUtils.isNull(info)){ + return AjaxResult.error("key不存在"); + } + boolean check = checkPermission("1", info.getConfig()); + if(!check){ + return AjaxResult.error("鉴权失败"); + } + + UserAccountDto uaDto = uaMapper.getAccountByUserAccountAndCoin(info.getUser(), info.getAccount(), info.getCoin()); + if(StringUtils.isNull(uaDto)){ + return AjaxResult.error("未请求到数据,请确认Key是否失效"); + } + Pools pool = (Pools) EnumUtils.get(Pools.class, uaDto.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("数据异常!请稍后再试"); + } + + List minerList= poolMapper.getMinerInfoList(pool.getMhs()); + minerList.stream().forEach(e ->{ + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(BigDecimal.valueOf(e.getMhs() * pool.getFac()).setScale(6,BigDecimal.ROUND_HALF_UP).doubleValue()); + } + }); + List list = minerList.stream().filter(e -> info.getAccount().equals(e.getUser())).collect(Collectors.toList()); + /************************************************ 过滤异常数据 开始 ******************************************************/ + // + Date nowDate = DateUtils.getNowDate(); + Date start = DateUtils.addDays(nowDate, -1); + //过滤掉last_submit超过一天的offline数据 + list = list.stream().filter(e -> e.getLastSubmit().after(start)).collect(Collectors.toList()); + /************************************************ 过滤异常数据 结束 ******************************************************/ + + //判断是否需要排序 + if("30m".equals(vo.getSort())){ + //根据30分钟算力排序 默认降序 即算力从大到小 + list = list.stream().sorted( + Comparator.comparing(MinerDataDto::getMhs).reversed().thenComparing(MinerDataDto::getMhs) + ).collect(Collectors.toList()); + if("asc".equals(vo.getCollation())){ + Collections.reverse(list); + } + }else if("24h".equals(vo.getSort())){ + //根据24h算力排序 默认升序 + list = list.stream().sorted( + Comparator.comparing(MinerDataDto::getMhs24h).reversed().thenComparing(MinerDataDto::getMhs24h) + ).collect(Collectors.toList()); + if("asc".equals(vo.getCollation())){ + Collections.reverse(list); + } + }else { + //其他情况不做处理 + } + + Map countMap = list.stream().collect(Collectors.groupingBy( + MinerDataDto::getOnline, + Collectors.counting() + )); + Long online = countMap.getOrDefault("online", 0L); + //if(online < 0){ + // return AjaxResult.error(""); + //} + Long offline = countMap.getOrDefault("offline", 0L) +countMap.getOrDefault("disconnected", 0L) ; + + //if(offline < 0){ + // return AjaxResult.error(""); + //} + if((online+offline) != list.size()){ + return AjaxResult.error("后台数据暂不可以用,请等待后台修复.."); + } + //计算1h算力总和 + double rate = list.stream().map(e -> BigDecimal.valueOf(e.getMhs())).collect(Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)).setScale(6,BigDecimal.ROUND_HALF_UP).doubleValue(); + //计算24h算力总和 + double dailyRate = list.stream().map(e -> BigDecimal.valueOf(e.getMhs24h())).collect(Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)).setScale(6,BigDecimal.ROUND_HALF_UP).doubleValue(); + + //根据公式 rejects / (rejects+accepts) + BigDecimal rejects = list.stream().map(e -> BigDecimal.valueOf(e.getRejects())).collect(Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)); + BigDecimal submits = list.stream().map(e -> BigDecimal.valueOf(e.getRejects()).add(BigDecimal.valueOf(e.getAccepts()))).collect(Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)); + double reject =0; + if(submits.compareTo(BigDecimal.ZERO) != 0){ + reject = rejects.divide(submits,6,BigDecimal.ROUND_HALF_UP).doubleValue(); + } + + + //根据用户传参 构造矿工列表表格数据 + //1.通过type判断是返回全部/在线/离线 + String type = ""; + int total = list.size(); + List row = new ArrayList<>(); + if(0 == vo.getType()){ + row = list.stream().map(e -> { + MinerDto mDto = new MinerDto(); + mDto.setId("all"+e.getMiner()); + mDto.setMiner(e.getMiner()); + mDto.setRate(e.getMhs()); + mDto.setDailyRate(e.getMhs24h()); + mDto.setReject(e.getRejectsRatio()); + mDto.setSubmit(e.getLastSubmit()); + if("online".equals(e.getOnline())){ + mDto.setStatus("1"); + }else if("offline".equals(e.getOnline())){ + mDto.setStatus("2"); + long offTime = DateUtils.getDateDiffMinute(e.getDate(),e.getLastSubmit()); + offTime = offTime < 0 ? 0 : offTime; + mDto.setOffline(offTime); + }else { + mDto.setStatus(e.getOnline()); + } + return mDto; + }).collect(Collectors.toList()); + }else if(1 == vo.getType()){ + row = list.stream().filter(e -> "online".equals(e.getOnline())).map(e -> { + MinerDto mDto = new MinerDto(); + mDto.setId("on"+e.getMiner()); + mDto.setMiner(e.getMiner()); + mDto.setRate(e.getMhs()); + mDto.setDailyRate(e.getMhs24h()); + mDto.setReject(e.getRejectsRatio()); + mDto.setSubmit(e.getLastSubmit()); + mDto.setStatus("1"); + return mDto; + }).collect(Collectors.toList()); + + total = Convert.toInt(online); + }else if(2 == vo.getType()){ + row = list.stream().filter(e -> ("offline".equals(e.getOnline()) || "disconnected".equals(e.getOnline()))).map(e -> { + MinerDto mDto = new MinerDto(); + mDto.setId("off"+e.getMiner()); + mDto.setMiner(e.getMiner()); + mDto.setRate(e.getMhs()); + mDto.setDailyRate(e.getMhs24h()); + mDto.setReject(e.getRejectsRatio()); + mDto.setSubmit(e.getLastSubmit()); + mDto.setStatus("2"); + long offTime = DateUtils.getDateDiffMinute(e.getDate(),e.getLastSubmit()); + offTime = offTime < 0 ? 0 : offTime; + mDto.setOffline(offTime); + return mDto; + }).collect(Collectors.toList()); + + total = Convert.toInt(offline); + }else { + return AjaxResult.error("参数错误"); + } + //2.通过filter参数传的矿工名根据矿工再次筛选 + if(StringUtils.isNotBlank(vo.getFilter())){ + row = row.stream().filter(e -> vo.getFilter().equals(e.getMiner())).collect(Collectors.toList()); + } + + + //分页 + int page = vo.getPage() > 0 ? vo.getPage() : 1; + int size = vo.getLimit() > 0 ? vo.getLimit() : 10; + row = row.stream().skip((page - 1) * size).limit(size).collect(Collectors.toList()); + + + //构造返回体数据 + MinerListDto dto = new MinerListDto(); + //筛选出的list数据条数据即矿工总数 + dto.setAll(list.size()); + dto.setOffline(Convert.toInt(offline)); + dto.setOnline(Convert.toInt(online)); + dto.setDailyRate(dailyRate); + dto.setMiner("合计"); + dto.setRate(rate); + dto.setReject(reject); + dto.setTotal(total); + dto.setPage(getTotalPage(total,size)); + dto.setRows(row); + //todo 删除掉 + dto.setSubmit(nowDate); + return AjaxResult.success(dto); + } + + @Override + public AjaxResult getMinerPower(ReadOnlyMinerVo vo) { + //权限鉴定 通过key鉴定 权限配置中需要包含1 + UserReadOnlyPageInfo info = uaMapper.getPageInfoByKey(vo.getKey()); + if(StringUtils.isNull(info)){ + return AjaxResult.error("key不存在"); + } + boolean check = checkPermission("1", info.getConfig()); + if(!check){ + return AjaxResult.error("鉴权失败"); + } + UserAccountDto uaDto = null; + try { + uaDto = uaMapper.getAccountByUserAccountAndCoin(info.getUser(), info.getAccount(), info.getCoin()); + }catch (Exception e){ + //避免数据库报错未能捕捉到 + return AjaxResult.error("网络异常,请稍后再试"); + } + + if(StringUtils.isNull(uaDto)){ + return AjaxResult.error("页面已失效!"); + } + + Pools pool = (Pools) EnumUtils.get(Pools.class, uaDto.getCoin()); + //根据account和miner查询 + String table = pool.getMhs()+"30m"; + + List list = poolMapper.getMinerPowerList(table, info.getAccount(), vo.getAccount()); + + Collections.reverse(list); + + return AjaxResult.success(list); + } + + @Override + public TableDataInfo getHistoryIncome(ReadOnlyPageVo vo) { + //权限鉴定 通过key鉴定 权限配置中需要包含2 + UserReadOnlyPageInfo info = uaMapper.getPageInfoByKey(vo.getKey()); + TableDataInfo rspData = new TableDataInfo(); + if(StringUtils.isNull(info)){ + rspData.setCode(HttpStatus.ERROR); + rspData.setRows(new ArrayList<>()); + rspData.setMsg("key不存在"); + rspData.setTotal(0); + rspData.setTotalPage(0); + return rspData; + } + boolean check = checkPermission("2", info.getConfig()); + if(!check){ + rspData.setCode(HttpStatus.ERROR); + rspData.setRows(new ArrayList<>()); + rspData.setMsg("鉴权失败"); + rspData.setTotal(0); + rspData.setTotalPage(0); + return rspData; + } + + UserAccountDto dto = uaMapper.getAccountByUserAccountAndCoin(info.getUser(), info.getAccount(), info.getCoin()); + + if(StringUtils.isNull(dto)) { + rspData.setCode(HttpStatus.ERROR); + rspData.setRows(new ArrayList<>()); + rspData.setMsg("未获取到挖矿账户信息"); + rspData.setTotal(0); + rspData.setTotalPage(0); + return rspData; + } + + List list = distributionMapper.getDayDistributionByMinerAccount(dto.getCoin(), dto.getAccount()); + //查询挖矿账户每日0点的算力 查询结果需要-1 才能代表当天的算力 + List mhsList = poolMapper.get24hAccountPowerListByAccount(dto.getCoin() + "_users_24h", dto.getAccount()); + + List returnList = list.stream().map(e -> { + IncomeDto income = new IncomeDto(); + income.setCoin(dto.getCoin()); + income.setDate(e.getDate()); + //income.setDifficult(); + mhsList.stream().anyMatch(m ->{ + if(m.getDate().equals(e.getDate())){ + income.setMhs(m.getPv().stripTrailingZeros().doubleValue()); + } + return false; + }); + if(StringUtils.isNotNull(e.getProfit())){ + income.setAmount(e.getProfit() + .setScale(10,BigDecimal.ROUND_HALF_UP)); + } + return income; + }).collect(Collectors.toList()); + + + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); + PageInfo pageInfo = new PageInfo(returnList); + rspData.setTotal(pageInfo.getTotal()); + rspData.setTotalPage(pageInfo.getPages()); + + int page = vo.getPage() < 1 ? 1 : vo.getPage(); + int limit = vo.getLimit() < 1 ? 1 : vo.getLimit(); + + List pageList = returnList.stream().skip((page - 1) * limit).limit(limit).collect(Collectors.toList()); + + rspData.setRows(pageList); + + return rspData; + } + + @Override + public TableDataInfo getHistoryOutcome(ReadOnlyPageVo vo) { + + //权限鉴定 通过key鉴定 权限配置中需要包含3 + UserReadOnlyPageInfo info = uaMapper.getPageInfoByKey(vo.getKey()); + TableDataInfo rspData = new TableDataInfo(); + if(StringUtils.isNull(info)){ + rspData.setCode(HttpStatus.ERROR); + rspData.setRows(new ArrayList<>()); + rspData.setMsg("key不存在"); + rspData.setTotal(0); + rspData.setTotalPage(0); + return rspData; + } + boolean check = checkPermission("3", info.getConfig()); + if(!check){ + rspData.setCode(HttpStatus.ERROR); + rspData.setRows(new ArrayList<>()); + rspData.setMsg("鉴权失败"); + rspData.setTotal(0); + rspData.setTotalPage(0); + return rspData; + } + + UserAccountDto dto = uaMapper.getAccountByUserAccountAndCoin(info.getUser(), info.getAccount(), info.getCoin()); + if(StringUtils.isNull(dto)){ + rspData.setCode(HttpStatus.ERROR); + rspData.setRows(new ArrayList<>()); + rspData.setMsg("未获取到挖矿账户信息"); + rspData.setTotal(0); + rspData.setTotalPage(0); + return rspData; + } + + List inPendingList = distributionMapper.getDayPendingByMinerAccount(info.getCoin(),info.getAccount()); + List pendingList = inPendingList.stream().map(e -> { + OutcomeDto dto1 = new OutcomeDto(); + dto1.setDate(e.getDate()); + dto1.setAmount(e.getProfit().doubleValue()); + dto1.setCoin(info.getCoin()); + dto1.setStatus(e.getStatus()); + return dto1; + }).collect(Collectors.toList()); + List list = distributionMapper.getOutcomeListByCoinAndUser(info.getCoin(),info.getAccount()); + + list.addAll(pendingList); + Collections.sort(list,Comparator.comparing(OutcomeDto::getDate,(o1,o2) -> o2.compareTo(o1))); + + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); + PageInfo pageInfo = new PageInfo(list); + rspData.setTotal(pageInfo.getTotal()); + rspData.setTotalPage(pageInfo.getPages()); + + int page = vo.getPage() < 1 ? 1 : vo.getPage(); + int limit = vo.getLimit() < 1 ? 1 : vo.getLimit(); + + List pageList = list.stream().skip((page - 1) * limit).limit(limit).collect(Collectors.toList()); + + rspData.setRows(pageList); + + return rspData; + } + + public boolean checkPermission(String need,String config) { + //权限鉴定 通过key鉴定 权限配置中需要包含3 + if (StringUtils.isBlank(config)){ + return false; + } + try { + List list = Arrays.asList(config.split(",")); + return list.stream().anyMatch(e -> need.equals(e)); + }catch (Exception e){ + return false; + } + } + + public List getDailyDistributionDtoByDataList(List list,String coin){ + PoolUnits poolUnit = (PoolUnits) EnumUtils.get(PoolUnits.class, coin); + int rate = 1000; + if(StringUtils.isNotNull(poolUnit)){ + rate = poolUnit.getgRate(); + } + Map map =new LinkedHashMap<>(); + //设置默认格子 + int interval = 10; + if (list.size() < 1){ + map.put(0,0); + map.put(10,0); + map.put(20,0); + map.put(30,0); + map.put(40,0); + map.put(50,0); + map.put(60,0); + map.put(70,0); + map.put(80,0); + map.put(90,0); + }else { + double min = list.stream().min(Comparator.comparingDouble(MinerDataDto::getMhs24h)).get().getMhs24h(); + double max = list.stream().max(Comparator.comparingDouble(MinerDataDto::getMhs24h)).get().getMhs24h(); + + //最大最小值除以10 根据这个值去判断每个格子的Value 固定为10格 + double v = BigDecimal.valueOf(max).subtract(BigDecimal.valueOf(min)).divide(BigDecimal.valueOf(10), 2, BigDecimal.ROUND_HALF_UP).doubleValue(); + + //根据上一步计算的值确定格子 + + if(v<=10){ + interval=10; + }else if(v <=100){ + interval = 100; + }else if(v <=200){ + interval = 200; + }else if(v <=500){ + interval = 500; + }else if(v <=1000){ + interval = 1000; + }else { + //todo 修改为先进行list进制转换 如MH/s -> GH/s再进行处理 + interval = 10000; + } + + int temp = (int) (min / interval); + int start = temp * interval; + for(int i=0;i<10;i++){ + int key = start + (i*interval); + map.put(key,0); + } + + int gRate = rate; + int dataInterval = interval; + list.stream().forEach(e -> { + int a = (int) (e.getMhs24h() / dataInterval) * dataInterval; + if(map.containsKey(a)){ + map.replace(a,map.get(a)+1); + }else { + map.put(a,1); + } + }); + } + + + int finalInterval = interval; + List returnList = map.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey())) + .map(e -> { + DistributionDto dto = new DistributionDto(); + dto.setLow(e.getKey()); + dto.setHigh(e.getKey()+ finalInterval); + dto.setCount(e.getValue()); + return dto; + }).collect(Collectors.toList()); + + return returnList; + } + + public int getTotalPage(int total,int size){ + if(size == 0){ + return 0; + } + int page = total /size; + if(total % size != 0){ + page++; + } + return page; + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/TicketServiceImpl.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/TicketServiceImpl.java new file mode 100644 index 0000000..128c00c --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/TicketServiceImpl.java @@ -0,0 +1,643 @@ +package com.m2pool.pool.service.impl; + + + +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.file.FileTypeUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.annotation.RequiresLogin; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.pool.dto.*; +import com.m2pool.pool.entity.M2File; +import com.m2pool.pool.entity.Ticket; +import com.m2pool.pool.enums.TicketStatusType; +import com.m2pool.pool.mapper.TicketMapper; +import com.m2pool.pool.service.FileService; +import com.m2pool.pool.service.TicketService; +import com.m2pool.pool.vo.*; +import com.m2pool.system.api.entity.SysFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.math.BigDecimal; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + + +/** + * @Description ETF查询业务处理 + * @Date 2022/5/19 17:51 + * @Author 杜懿 + */ +@Service +public class TicketServiceImpl implements TicketService { + + private static final Logger log = LoggerFactory.getLogger(TicketServiceImpl.class); + + @Autowired + private TicketMapper ticketMapper; + + @Autowired + private RedisService redisService; + + @Autowired + private FileService fileService; + + @Value("${myenv.filepath}") + private String FILE_PATH ; + //public static final String FILE_PATH = "D:/M2pool/uploadPath"; + + @Value("${myenv.domain}") + private String MY_DOMAIN; + + + @Transactional(rollbackFor = Exception.class) + @Override + public AjaxResult uploadFiles(MultipartFile[] file) throws SQLException { + + SysFileDto dto = new SysFileDto(); + + for (MultipartFile f : file) { + R upload = fileService.uploadFile(f); + + if (StringUtils.isNull(upload) || StringUtils.isNull(upload.getData())) { + System.out.println("gg"); + System.out.println(upload.getMsg()); + return AjaxResult.error("文件服务异常,文件上传失败"); + } + + SysFile sysFile = upload.getData(); + M2File m2File = new M2File(); + BeanUtils.copyProperties(sysFile, m2File); + m2File.setUserName(SecurityUtils.getUsername()); + m2File.setFileName(StringUtils.clean(sysFile.getName())); + System.out.println(m2File); + String type = FileTypeUtils.getFileType(sysFile.getName()).toUpperCase(); + System.out.println("type:"+type); + // 图片 + //"jpg", "jpeg", "png", + // //音频 + // "mp3","aif", "aiff", "wav", "wma", + // // 视频格式 + // "mp4", "avi", "rmvb" + if("JPG".equals(type) || "JPEG".equals(type) ||"PNG".equals(type)){ + m2File.setFileType(0); + }else if("MP3".equals(type) || "AIF".equals(type) ||"AIFF".equals(type) || "WAV".equals(type) || "WMA".equals(type)){ + m2File.setFileType(1); + }else if("MP4".equals(type) || "AVI".equals(type) ||"RMVB".equals(type)){ + m2File.setFileType(2); + }else { + return AjaxResult.error("文件类型不在可允许范围之内"); + } + + //将文件信息存入数据库:(url、name、userName、createTime) + if (!ticketMapper.insertFileInfo(m2File)) { + throw new SQLException("后台异常.."); + } + + if (StringUtils.isNull(dto.getId())) { + dto.setId(Convert.toStr(m2File.getId())); + dto.setUrl(m2File.getUrl()); + dto.setName(m2File.getFileName()); + } else { + dto.setId(dto.getId() + "," + m2File.getId()); + dto.setUrl(dto.getUrl() + "," + m2File.getUrl()); + dto.setName(dto.getName() + "," + m2File.getFileName()); + } + + } + + return AjaxResult.success(dto); + } + + @Override + public void downloadByIds(String ids, HttpServletRequest request, HttpServletResponse response) { + List fileInfos = getFileInfos(ids); + System.out.println("fileInfos" + fileInfos); + + //设置响应头信息 + response.reset(); + response.setCharacterEncoding("utf-8"); + response.setContentType("multipart/form-data"); + //设置压缩包的名字,date为时间戳 + String date = DateUtils.dateTimeNow(); + String downloadName = "file" + date + ".zip"; + + String agent = request.getHeader("USER-AGENT"); + try { + //针对IE或者以IE为内核的浏览器: + if (agent.contains("MSIE") || agent.contains("Trident")) { + downloadName = URLEncoder.encode(downloadName, "UTF-8"); + } else { + //非IE浏览器的处理: + downloadName = new String(downloadName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); + } + } catch (Exception e) { + log.error("系统异常", e); + } + response.setHeader("Content-Disposition", "attachment;fileName=" + downloadName); + + + //设置压缩流:直接写入response,实现边压缩边下载 + ZipOutputStream zipOs = null; + //循环将文件写入压缩流 + DataOutputStream os = null; + //文件 + File file; + try { + zipOs = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream())); + //设置压缩方法 + zipOs.setMethod(ZipOutputStream.DEFLATED); + //遍历文件信息(主要获取文件名/文件路径等) + for (M2File fileInfo : fileInfos) { + //文件名(包含后缀名,如:测试.pdf) + String name = fileInfo.getFileName(); + //本地文件路径(绝对路径,包含后缀名) + String path = FILE_PATH + StringUtils.substringAfter(fileInfo.getUrl(), "statics"); + System.out.println("path" + path); + file = new File(path); + if (!file.exists()) { + throw new RuntimeException("文件不存在"); + } + //添加ZipEntry,并将ZipEntry写入文件流 + zipOs.putNextEntry(new ZipEntry(name)); + os = new DataOutputStream(zipOs); + FileInputStream fs = new FileInputStream(file); + byte[] b = new byte[100]; + int length; + //读入需要下载的文件的内容,打包到zip文件 + while ((length = fs.read(b)) != -1) { + os.write(b, 0, length); + } + //关闭流 + fs.close(); + zipOs.closeEntry(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + //关闭流 + try { + if (os != null) { + os.flush(); + os.close(); + } + if (zipOs != null) { + zipOs.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + @Transactional(rollbackFor = Exception.class) + @Override + @RequiresLogin + public AjaxResult submitTicket(TicketVo vo) { + + vo.setEmail(SecurityUtils.getUsername()); + + + if(StringUtils.isBlank(vo.getDesc())){ + return AjaxResult.error("问题描述不能为空"); + } + + Ticket ticket = new Ticket(); + BeanUtils.copyProperties(vo, ticket); + + ticket.setStatus(TicketStatusType.STATUS_ONE.getCode()); + ticket.setEmail(SecurityUtils.getUsername()); + + if (ticketMapper.insertTicket(ticket)) { + //工单提交成功 + System.out.println("工单提交成功:"+ticket); + //处理附件 + String video=""; + String audio=""; + String image=""; + if(StringUtils.isNotBlank(vo.getFiles())){ + List files = getFileInfos(vo.getFiles()); + + for (M2File f:files) { + if(f.getFileType() == 0){ + //图片 0 + if(StringUtils.isBlank(image)){ + image = image+f.getUrl(); + }else { + image = image+","+f.getUrl(); + } + }else if(f.getFileType() == 1){ + //音频 1 + if(StringUtils.isBlank(audio)){ + audio = audio+f.getUrl(); + }else { + audio = audio+","+f.getUrl(); + } + }else if(f.getFileType() == 2){ + //视频 2 + if(StringUtils.isBlank(video)){ + video = video+f.getUrl(); + }else { + video = video+","+f.getUrl(); + } + } + } + + } + + ResubmitTicketVo rsVo = new ResubmitTicketVo(); + rsVo.setId(ticket.getId()); + rsVo.setDesc(vo.getDesc()); + rsVo.setFiles(vo.getFiles()); + rsVo.setAudioFiles(audio); + rsVo.setImageFiles(image); + rsVo.setVideoFiles(video); + ticketMapper.insertTicketSupplement(rsVo); + return AjaxResult.success("工单提交成功"); + } else { + return AjaxResult.error(509, "服务器网络异常,提交失败"); + } + + } + + + @Override + @RequiresLogin + public AjaxResult resubmitTicket(ResubmitTicketVo vo) { + + if (StringUtils.isNull(vo)) { + return AjaxResult.error(601, "参数错误"); + } + + // 判断是否超过40条补充内容 + Integer num = ticketMapper.getTicketSupplementCountByTicketId(vo.getId()); + TicketDto ticket = ticketMapper.getTicketById(vo.getId()); + if (StringUtils.isNull(ticket)) { + return AjaxResult.error(601, "参数错误"); + } + + if (Convert.toInt(num, 0) >= 61) { + return AjaxResult.error("工单提交内容已达上限60"); + } + //判断工单当前状态 + if(ticket.getStatus() == TicketStatusType.STATUS_TEN.getCode()){ + return AjaxResult.error("工单已完结,不能再继续提交,若还有问题您可以重新提交工单"); + } + + vo.setDesc(StringUtils.clean(vo.getDesc())); + + //处理files 转换为video_files/转换为image_files/转换为audio_files + + //处理附件 + String video=""; + String audio=""; + String image=""; + if(StringUtils.isNotBlank(vo.getFiles())){ + List files = getFileInfos(vo.getFiles()); + + for (M2File f:files) { + if(f.getFileType() == 0){ + //图片 0 + if(StringUtils.isBlank(image)){ + image = image+f.getUrl(); + }else { + image = image+","+f.getUrl(); + } + }else if(f.getFileType() == 1){ + //音频 1 + if(StringUtils.isBlank(audio)){ + audio = audio+f.getUrl(); + }else { + audio = audio+","+f.getUrl(); + } + }else if(f.getFileType() == 2){ + //视频 2 + if(StringUtils.isBlank(video)){ + video = video+f.getUrl(); + }else { + video = video+","+f.getUrl(); + } + } + } + + } + vo.setAudioFiles(audio); + vo.setImageFiles(image); + vo.setVideoFiles(video); + + if (ticketMapper.insertTicketSupplement(vo)) { + //提交成功 更 + if(ticketMapper.changeTicketUpdateTime(vo.getId())){ + return AjaxResult.success("提交成功"); + }else { + return AjaxResult.error("服务器网络异常,提交失败"); + } + } else { + return AjaxResult.error("服务器网络异常,提交失败"); + } + } + + + @Override + public AjaxResult endTicket(EndTicketVo vo) { + + if (StringUtils.isNull(vo)) { + return AjaxResult.error("参数丢失"); + } + + TicketDto ticket = ticketMapper.getTicketById(vo.getId()); + + if (StringUtils.isNull(ticket)) { + return AjaxResult.error(601, "参数错误"); + } + if (!ticket.getEmail().equals(SecurityUtils.getUsername())) { + return AjaxResult.error("不能修改他人的工单"); + } + + if (ticket.getStatus() == 1 || ticket.getStatus() == 2) { + //用户可关闭工单的状态 待处理1 客服处理中2 + } else { + return AjaxResult.error("当前状态下的工单不能关闭"); + } + + return checkResult(ticketMapper.changeTicketStatus(vo.getId(), TicketStatusType.STATUS_TEN.getCode())); + } + + @Override + public AjaxResult getTicketDetails(long id) { + + if (id < 1) { + return AjaxResult.error("工单号参数缺失"); + } + + TicketPrivateDto ticket = ticketMapper.getBackendTicketById(id); + if (StringUtils.isNull(ticket)) { + return AjaxResult.error( "根据工单号未找到工单详情"); + } + + //获取工单内容list + List supplementList = ticketMapper.getTicketSupplementListByTicketId(id); + + //获取工单回复list + List responList = ticketMapper.getTicketResponListByTicketId(id); + + //合并回复和内容list并重新排序 + supplementList.addAll(responList); + List list = supplementList.stream().sorted(Comparator.comparing(TicketContentDto::getTime)).collect(Collectors.toList()); + + ticket.setList(list); + + //ticket.setStatus(getStatus(Convert.toInt(ticket.getStatus()))); + + return AjaxResult.success(ticket); + } + + @Override + public AjaxResult getPrivateTicketDetails(long id) { + + if (id < 1) { + return AjaxResult.error("工单号参数缺失"); + } + + TicketPrivateDto ticket = ticketMapper.getPrivateTicketById(id, SecurityUtils.getUsername()); + + if (StringUtils.isNull(ticket)) { + return AjaxResult.error("通过工单号:" + id + ",找不到与您对应的工单!您只能查询您个人的工单"); + } + + //获取工单内容list + List supplementList = ticketMapper.getTicketSupplementListByTicketId(id); + + //获取工单回复list + List responList = ticketMapper.getTicketResponListByTicketId(id); + + //合并回复和内容list并重新排序 + supplementList.addAll(responList); + List list = supplementList.stream().sorted(Comparator.comparing(TicketContentDto::getTime)).collect(Collectors.toList()); + + ticket.setList(list); + + //ticket.setStatus(getStatus(Convert.toInt(ticket.getStatus()))); + + return AjaxResult.success(ticket); + } + + @Override + public List getAllTicket(AllTicketListVo vo) { + + List list = ticketMapper.getTicketListByCond(vo); + //list.stream().forEach(e -> { + // e.setStatus(getStatus(Convert.toInt(e.getStatus()))); + //}); + + return list; + } + + @Override + public AjaxResult getPrivateTicketByStatus(PrivateTicketListVo vo) { + + System.out.println(vo); + String email = SecurityUtils.getUsername(); + + //com.m2pool.chat.vo.setId(Convert.toLong(SecurityUtils.getUserId(),0L)); + + List list = new ArrayList<>(); + int status = vo.getStatus(); + if (status == 0) { + //全部工单传 0 + list = ticketMapper.getPrivateTicketList(vo, email); + } else if (status == 1) { + //进行中的工单 + list = ticketMapper.getPrivateProcessingTicketList(vo, email); + } else if (status == 2) { + //已完结工单 + list = ticketMapper.getPrivateEndTicketList(vo, email); + } else { + return AjaxResult.success(list); + } + + //list.stream().forEach(e -> { + // e.setStatus(getStatus(Convert.toInt(e.getStatus()))); + //}); + + return AjaxResult.success(list); + } + + + @Override + public List getSupportTicketList(StatusVo vo) { + + if(StringUtils.isNull(vo)){ + return new ArrayList<>(); + } + if(vo.getStatus() == 0){ + //查询全部 + List list = ticketMapper.getAllTicketList(vo ); + return list; + } + else if(vo.getStatus() == 1 || vo.getStatus() == 2 || vo.getStatus() == 10 ){ + //status 取值仅有1 待处理 2客服处理中 10已关闭 + List list = ticketMapper.getTicketListByStatusAndType(vo); + + //list.stream().forEach(e -> { + // e.setStatus(getStatus(Convert.toInt(e.getStatus()))); + //}); + return list; + }else { + return new ArrayList<>(); + } + + + } + + @Override + public AjaxResult onlineResponTicket(ResponTicketVo vo) { + + if (vo.getId() < 1) { + return AjaxResult.error(601, "工单号缺失"); + } + + TicketDto ticket = ticketMapper.getTicketById(vo.getId()); + + if (StringUtils.isNull(ticket)) { + return AjaxResult.error(601, "工单不存在"); + } + + if (ticket.getStatus() == TicketStatusType.STATUS_TEN.getCode()) { + return AjaxResult.error("工单已完结,不能再回复"); + } + + vo.setFiles(StringUtils.clean(vo.getFiles())); + vo.setRespon(StringUtils.clean(vo.getRespon())); + + String video=""; + String audio=""; + String image=""; + if(StringUtils.isNotBlank(vo.getFiles())){ + List files = getFileInfos(vo.getFiles()); + + for (M2File f:files) { + if(f.getFileType() == 0){ + //图片 0 + if(StringUtils.isBlank(image)){ + image = image+f.getUrl(); + }else { + image = image+","+f.getUrl(); + } + }else if(f.getFileType() == 1){ + //音频 1 + if(StringUtils.isBlank(audio)){ + audio = audio+f.getUrl(); + }else { + audio = audio+","+f.getUrl(); + } + }else if(f.getFileType() == 2){ + //视频 2 + if(StringUtils.isBlank(video)){ + video = video+f.getUrl(); + }else { + video = video+","+f.getUrl(); + } + } + } + + } + + vo.setAudioFiles(audio); + vo.setImageFiles(image); + vo.setVideoFiles(video); + + + if (ticketMapper.insertRespon(vo, SecurityUtils.getUsername())) { + //return checkResult(ticketMapper.changeTicketStatus(com.m2pool.chat.vo.getId(), TicketStatusType.STATUS_FOUR.getCode())); + //if(ticket.getStatus() != TicketStatusType.STATUS_TWO.getCode()){ + // //若果是待处理工单 则修改工单状态为客服处理中 + // + //} + ticketMapper.changeTicketStatus(vo.getId(),TicketStatusType.STATUS_TWO.getCode()); + //回复成功 邮箱通知 异步 + //CompletableFuture.runAsync(()->{ + // mailService.sendRespondNoticeHTMLMailMessage(ticket.getEmail(), ticket.getId()); + //}); + + return AjaxResult.success("回复成功"); + } else { + return AjaxResult.error("服务器网络异常,提交失败"); + } + } + + @Override + public AjaxResult closeTicket(IdVo vo) { + TicketDto ticket = ticketMapper.getTicketById(vo.getId()); + + if (StringUtils.isNull(ticket)) { + return AjaxResult.error(601, "参数错误"); + } + + if (ticket.getStatus() == 1 || ticket.getStatus() == 2) { + //可关闭工单的状态 待处理1 客服处理中2 + } else { + return AjaxResult.error("当前状态下的工单不能关闭"); + } + return checkResult(ticketMapper.changeTicketStatus(vo.getId(), TicketStatusType.STATUS_TEN.getCode())); + } + + + + public AjaxResult checkResult(boolean b) { + if (!b) { + return AjaxResult.error("服务器网络异常,提交失败"); + } + return AjaxResult.success(); + } + + public List getFileInfos(String files) { + + List list = new ArrayList<>(); + if (StringUtils.isNull(files)) { + return list; + } + + if (StringUtils.isEmpty(files)) { + return list; + } + Long[] ids = Convert.toLongArray(files); + for (long id : ids) { + M2File file = ticketMapper.getFileInfoById(id); + if (StringUtils.isNotNull(file)) { + list.add(file); + } + } + + return list; + + } + + public String getStatus(int status) { + + return TicketStatusType.getStatusByCode(status); + } + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/UserMinerServiceImpl.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/UserMinerServiceImpl.java new file mode 100644 index 0000000..63b4523 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/service/impl/UserMinerServiceImpl.java @@ -0,0 +1,740 @@ +package com.m2pool.pool.service.impl; + +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.github.pagehelper.PageHelper; +import com.m2pool.common.core.constant.CacheConstants; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.ServletUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.utils.ip.IpUtils; +import com.m2pool.common.core.utils.uuid.IdUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.common.security.auth.ApiUtil; +import com.m2pool.common.security.service.OpenApiService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.pool.dto.*; +import com.m2pool.pool.entity.*; +import com.m2pool.pool.enums.PoolAmount; +import com.m2pool.pool.enums.Pools; +import com.m2pool.pool.mapper.GoogleAuthMapper; +import com.m2pool.pool.mapper.UserAccountMapper; +import com.m2pool.pool.service.UserMinserService; +import com.m2pool.pool.utils.EnumUtils; +import com.m2pool.common.core.utils.GoogleAuthenticator; +import com.m2pool.pool.utils.NodeRpc; +import com.m2pool.pool.vo.*; +import com.m2pool.system.api.model.OpenApiKeyInfo; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import sun.net.util.IPAddressUtil; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Description TODO + * @Date 2024/6/14 14:29 + * @Author dy + */ +@Service +public class UserMinerServiceImpl implements UserMinserService { + + @Autowired + private UserAccountMapper uaMapper; + + @Autowired + private OpenApiService openApiService; + + @Autowired + private RedisService redisService; + + + @Autowired + private GoogleAuthMapper googleAuthMapper; + + + @Value("${myenv.domain}") + private String domain; + + @Value("${myenv.img}") + private String image; + + + @Override + @DSTransactional() + public AjaxResult addMinerAccount(MinerAccountAddVo vo) { + //com.m2pool.chat.vo sql字段去脚本 + vo.setCoin(StringUtils.clean(vo.getCoin())); + vo.setMa(StringUtils.clean(vo.getMa())); + vo.setBalance(StringUtils.clean(vo.getBalance())); + vo.setRemarks(StringUtils.clean(vo.getRemarks())); + + if(StringUtils.isBlank(vo.getBalance())){ + return AjaxResult.error("请先输入需要绑定的钱包地址"); + } + + //校验coin是否在规定范围之内 + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("矿池参数错误,找不到对应矿池"); + } + + + try { + if("alph".equals(pool.getCoin())){ + /* alph只校验长度(45)和开头(值为1) */ + if(StringUtils.isBlank(vo.getBalance())){ + return AjaxResult.error("钱包地址不能为空"); + } + if(vo.getBalance().startsWith("1") && vo.getBalance().length()==45){ + //校验通过放行 + }else { + return AjaxResult.error("您输入的地址无效,地址格式错误"); + } + }else if("enx".equals(pool.getCoin())){ + /* enx只校验长度(45)和开头(值为1) */ + }else { + boolean result = NodeRpc.checkAddress(vo.getCoin(), vo.getBalance()); + if (!result){ + return AjaxResult.error("您输入的地址无效,请输入可以正常使用的地址"); + } + } + }catch (RuntimeException e){ + return AjaxResult.error("您输入的地址无效,请输入可以正常使用的地址"); + } + + String username = SecurityUtils.getUsername(); + + if(StringUtils.isBlank(username)){ + return AjaxResult.error("用户邮箱获取失败"); + } + + + /** 判断是否绑定超过5个 */ + int count = uaMapper.getAccountNumberByUserAccountAndCoin(username, vo.getCoin()); + if(count >= 5){ + return AjaxResult.error("该币种下挖矿账户已达上限!不能再添加挖矿账户"); + } + /** 判断挖矿账户是否存在 */ + if(uaMapper.checkAccountIfExist(vo.getMa(),vo.getCoin())){ + return AjaxResult.error("该挖矿账户已被他人使用,请修改后重新添加,重复添加失败"); + } + UserMinerAccount account = new UserMinerAccount(); + account.setUser(username); + BeanUtils.copyProperties(vo,account); + + //查询是否开启双重验证 + //谷歌验证码校验 + GoogleInfo googleInfo = googleAuthMapper.getGoogleInfoByEmail(username); + if(StringUtils.isBlank(googleInfo.getSecret())){ + //未绑定定谷歌验证器 + return AjaxResult.error("您的账号未开启双重验证,请先开启验证!"); + } + if(!GoogleAuthenticator.checkCode(googleInfo.getSecret(), vo.getCode(), System.currentTimeMillis())){ + return AjaxResult.error("谷歌验证码错误"); + } + //if(StringUtils.isNull(googleInfo)){ + // return AjaxResult.error("当前账户未绑定谷歌验证器,暂时不能添加账号"); + //} + // + //if(googleInfo.getStatus() == 0){ + // return AjaxResult.error("当前账户未绑定谷歌验证器,暂时不能添加账号"); + //} + // + //if(googleInfo.getStatus() == 2){ + // return AjaxResult.error("当前账户关闭了谷歌验证器,暂时不能添加账号"); + //} + + if(uaMapper.userAddMinerAccount(account) && account.getId()>0){ + System.out.println("id为"+account.getId()); + //todo 获取新写入的挖矿账户id 写入地址表 + AccountBalanceAddVo aVo = new AccountBalanceAddVo(); + aVo.setMa(vo.getMa()); + aVo.setBalance(vo.getBalance()); + aVo.setAmount(PoolAmount.getAmountByCoin(vo.getCoin())); + + uaMapper.accountAddBalance(account.getId(),aVo); + //todo 日志记录 + return AjaxResult.success("挖矿账户添加成功"); + }else { + return AjaxResult.error("挖矿账户添加失败请稍后再试"); + } + + } + + @Override + public AjaxResult checkAccount(MinerAccountAddVo vo) { + + if(!vo.getMa().equals(StringUtils.clean(vo.getMa()))){ + + } + //校验coin是否在规定范围之内 + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("矿池参数错误,找不到对应矿池"); + } + + String username = SecurityUtils.getUsername(); + + if(StringUtils.isBlank(username)){ + return AjaxResult.error("未获取到用户信息请稍后再试"); + } + + if(uaMapper.checkAccountIfExist(vo.getMa(),vo.getCoin())){ + return AjaxResult.success(false); + } + return AjaxResult.success(true); + } + + @Override + public AjaxResult checkBalance(BalanceCheckVo vo) { + vo.setBalance(StringUtils.clean(vo.getBalance())); + if(StringUtils.isBlank(vo.getBalance())){ + return AjaxResult.error("请先输入需要绑定的钱包地址"); + } + + //HttpHeaders headers = new HttpHeaders(); + //headers.add("Content-Type","application/json"); + //HttpEntity httpEntity = new HttpEntity(null, headers); + //StringBuffer paramsURL = new StringBuffer("http://13.229.139.183:23115/verify_wallet?address="+com.m2pool.chat.vo.getBalance()+"&coin="+com.m2pool.chat.vo.getCoin()); + //URI uri = URI.create(paramsURL.toString()); + //System.out.println(paramsURL.toString()); + try { + boolean result = NodeRpc.checkAddress(vo.getCoin(), vo.getBalance()); + return AjaxResult.success(result); + }catch (RuntimeException e){ + return AjaxResult.error(e.getMessage()); + } + + + } + + @Override + public AjaxResult checkMaAndBalance(MaBalanceCheckVo vo) { + + if(!vo.getMa().equals(StringUtils.clean(vo.getMa()))){ + + } + //校验coin是否在规定范围之内 + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("矿池参数错误,未知矿池"); + } + + String username = SecurityUtils.getUsername(); + + if(StringUtils.isBlank(username)){ + return AjaxResult.error("未获取到用户信息请稍后再试"); + } + System.out.println("挖矿账号"+vo.getMa()); + boolean b = uaMapper.checkAccountIfExist(vo.getMa(), vo.getCoin()); + System.out.println("查询结果:"+b); + if(b){ + return AjaxResult.error(801,"该挖矿账号名已被使用,不能在使用"); + } + + vo.setBalance(StringUtils.clean(vo.getBalance())); + if(StringUtils.isBlank(vo.getBalance())){ + return AjaxResult.error("请先输入有效的钱包地址"); + } + + + try { + boolean result = NodeRpc.checkAddress(vo.getCoin(), vo.getBalance()); + return AjaxResult.success(result); + }catch (RuntimeException e){ + return AjaxResult.error(802,"该钱包地址不可用,请确认钱包地址是否是当前币种可用的钱包地址"); + } + + } + + @DSTransactional() + @Override + public AjaxResult delMinerAccount(MinerAccountDelVo vo) { + String username = SecurityUtils.getUsername(); + + if(StringUtils.isBlank(username)){ + return AjaxResult.error("用户信息获取失败"); + } + + String[] idStrs = vo.getId().split(","); + + if(idStrs.length > 20){ + return AjaxResult.error("最多同时删除20个账号"); + } + //校验挖矿账户名是否已存在 + /** 判断是否已绑定过该挖矿账号 */ + Map map =new HashMap<>(); + for(String id : idStrs){ + Long maId = Convert.toLong(id,0L); + UserAccountDto dto = uaMapper.getAccountByUserAccountId(username, maId); + + if(StringUtils.isNull(dto)){ + return AjaxResult.error("挖矿账号异常:未找到id:"+maId+"对应的账户"); + } + map.put(id,maId); + } + + //查询是否开启双重验证 + //谷歌验证码校验 + GoogleInfo googleInfo = googleAuthMapper.getGoogleInfoByEmail(username); + if(StringUtils.isBlank(googleInfo.getSecret())){ + //未绑定定谷歌验证器 + return AjaxResult.error("您的账号未开启双重验证,请先开启验证!"); + } + if(!GoogleAuthenticator.checkCode(googleInfo.getSecret(), vo.getGCode(), System.currentTimeMillis())){ + return AjaxResult.error("谷歌验证码错误"); + } + + + List idsList = map.values().stream().collect(Collectors.toList()); + //删除用户挖矿账号 逻辑删除 + if(uaMapper.batchDelAccount(idsList)){ + //todo 修改钱包表钱包数据状态、并 + + //todo 日志记录 + return AjaxResult.success("账号删除成功"); + }else { + return AjaxResult.error("数据库执行异常"); + } + + } + + @DSTransactional() + @Override + public AjaxResult addBalance(AccountBalanceAddVo vo) { + //包含操作 1绑定钱包地址 2设置起付金 3设置是否自动激活 4更换绑定钱包地址 5 修改起伏金额 6 + String username = SecurityUtils.getUsername(); + + if(StringUtils.isBlank(username)){ + return AjaxResult.error("用户邮箱获取失败"); + } + + UserAccountDto dto = uaMapper.getAccountByUserAccountAndCoin(username, vo.getMa(),vo.getCoin()); + + if(StringUtils.isNull(dto)){ + return AjaxResult.error("当前用户下没有该挖矿账号"); + } + + //校验是否已绑定过地址 + MinerAccountBalanceDto balance = uaMapper.getBalanceByMinerAccount(dto.getId()); + Pools pool = (Pools) EnumUtils.get(Pools.class, vo.getCoin()); + if(StringUtils.isNull(pool)){ + return AjaxResult.error("矿池参数错误!"); + } + double amount = PoolAmount.getAmountByCoin(dto.getCoin()); + + GoogleInfo googleInfo = googleAuthMapper.getGoogleInfoByEmail(username); + if(StringUtils.isBlank(googleInfo.getSecret())){ + //未绑定定谷歌验证器 + return AjaxResult.error("您的账号未开启双重验证,请先开启验证!"); + } + if(!GoogleAuthenticator.checkCode(googleInfo.getSecret(), vo.getGCode(), System.currentTimeMillis())){ + return AjaxResult.error("谷歌验证码错误"); + } + + if(StringUtils.isNotNull(balance)){ + //todo 判断是否是更新 + if(vo.getBalance().equals(balance.getBalance())){ + //相同 说明是修改其他信息 在远有数据基础上执行修改 + if(vo.getAmount() < PoolAmount.getAmountByCoin(dto.getCoin())){ + return AjaxResult.error(pool.getName()+"设置起付金额不能小于"+amount); + } + System.out.println("com.m2pool.chat.vo:"+vo); + + vo.setRemarks(StringUtils.clean(vo.getRemarks())); + uaMapper.updateMinerAccountRemark(dto.getId(),vo); + + uaMapper.updateBalance(balance.getId(), vo); + return AjaxResult.success("修改完成"); + }else { + //todo 从redis拿 + try { + boolean result = NodeRpc.checkAddress(vo.getCoin(), vo.getBalance()); + //不同 说明是修改地址 先将原有地址数据状态修改为停用(99) 根据地址表id 再新增数据入库 + if(result){ + uaMapper.accountStopBalance(balance.getId(), vo); + //新增用户地址数据入库 写入挖矿账号id和地址信息 + uaMapper.accountAddBalance(dto.getId(),vo); + return AjaxResult.success("修改成功"); + }else { + return AjaxResult.error("地址错误"); + } + }catch (RuntimeException e){ + return AjaxResult.error("当前接口请求用户过多,请稍后再试!"); + } + + + } + + } + + //没有绑定 + if(vo.getAmount() == 0){ + vo.setAmount(amount); + } + + if(vo.getAmount() < PoolAmount.getAmountByCoin(dto.getCoin())){ + return AjaxResult.error(pool.getName()+"设置起付金额不能小于"+amount); + } + + if(uaMapper.accountAddBalance(dto.getId(),vo)){ + return AjaxResult.success(); + }else { + return AjaxResult.error("当前用户下没有该挖矿账号"); + } + + } + + + @Override + public AjaxResult getAccountList() { + + String username = SecurityUtils.getUsername(); + + if(StringUtils.isBlank(username)){ + return AjaxResult.error("用户邮箱获取失败"); + } + + List list = uaMapper.getAccountByUserAndCoin(username); + list = list.stream().filter(e -> StringUtils.isNull(e.getAddr()) || (StringUtils.isNotNull(e.getAddr()) && (e.getAddrStatus() == 0))).collect(Collectors.toList()); + + list.stream().forEach(e ->{ + Pools pool = (Pools) EnumUtils.get(Pools.class, e.getCoin()); + + if(StringUtils.isNotNull(pool)){ + e.setPool(pool.getName()); + e.setImg(domain+image+"/"+pool.getImg()); + } + + if(StringUtils.isNotBlank(e.getAddr())){ + e.setAddrStatus(1); + }else { + e.setAddrStatus(0); + } + }); + + return AjaxResult.success(list); + } + + @Override + public AjaxResult getAccountGradeList() { + String username = SecurityUtils.getUsername(); + + if(StringUtils.isBlank(username)){ + return AjaxResult.error("用户邮箱获取失败"); + } + + List list = uaMapper.getAccountByUserAndCoin(username); + list = list.stream().filter(e -> StringUtils.isNull(e.getAddr()) || (StringUtils.isNotNull(e.getAddr()) && (e.getAddrStatus() == 0))).collect(Collectors.toList()); + HashMap> map = new HashMap<>(); + + list.stream().forEach(e ->{ + Pools pool = (Pools) EnumUtils.get(Pools.class, e.getCoin()); + + if(StringUtils.isNotNull(pool)){ + UserAccountGradeDto gDto = new UserAccountGradeDto(); + gDto.setId(e.getId()); + gDto.setAccount(e.getMa()); + if(map.containsKey(pool.getCoin())){ + //已有数据则原list中加入当前对象 + map.get(pool.getCoin()).add(gDto); + }else { + //没有数据创建list 并把当前对象加入list 再把list加入map中 + List gList = new ArrayList<>(); + gList.add(gDto); + map.put(pool.getCoin(),gList); + } + } + }); + + List returnList = map.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey())).map(e -> + { + UserAccountGradeListDto lDto = new UserAccountGradeListDto(); + lDto.setTitle(Pools.getNameByCoin(e.getKey())); + lDto.setCoin(e.getKey()); + lDto.setImg(domain+image+"/"+Pools.getImgByCoin(e.getKey())); + lDto.setChildren(e.getValue()); + + return lDto; + }).collect(Collectors.toList()); + + + return AjaxResult.success(returnList); + } + + @Override + public AjaxResult getMinerAccountBalance(MinerIdVo vo) { + String username = SecurityUtils.getUsername(); + + if(StringUtils.isBlank(username)){ + return AjaxResult.error("用户邮箱获取失败"); + } + UserAccountDto uaDto = uaMapper.getAccountByUserAccountId(username, vo.getId()); + System.out.println("uaDto:"+uaDto); + if(StringUtils.isNull(uaDto)){ + return AjaxResult.error("当前矿池中未找到该挖矿账号"); + } + + + MinerAccountBalanceDto dto = uaMapper.getBalanceByMinerAccount(uaDto.getId()); + + if(StringUtils.isNotNull(dto)){ + double amount = PoolAmount.getAmountByCoin(uaDto.getCoin()); + if(dto.getAmount() <= amount){ + dto.setAmount(amount); + } + }else { + dto = new MinerAccountBalanceDto(); + dto.setMa(uaDto.getAccount()); + dto.setAmount(PoolAmount.getAmountByCoin(uaDto.getCoin())); + } + + return AjaxResult.success(dto); + } + + @Override + public AjaxResult createApiKey(ApiKeyCreateVo vo) { + String user = SecurityUtils.getUsername(); + //校验 + if(StringUtils.isBlank(vo.getPerms())){ + return AjaxResult.success("权限不能为空"); + } + + List perms = Arrays.asList(vo.getPerms().split(",").clone()); + Set permsSet = new HashSet<>(); + for (String perm : perms) + { + if (StringUtils.isNotEmpty(perm)) + { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + + int count = uaMapper.getApiKeyCountByUser(user); + if(count >= 5){ + return AjaxResult.error("apiKey创建次数已达上限:5"); + } + + //创建apikey + String apiKey = IdUtils.fastUUID(); + + OpenApiKey keyToDB = new OpenApiKey(); + keyToDB.setKey(apiKey); + keyToDB.setUser(user); + keyToDB.setPermissions(vo.getPerms()); + OpenApiKeyInfo info = new OpenApiKeyInfo(); + info.setKey(apiKey); + info.setUser(user); + if(StringUtils.isNotBlank(vo.getIp())){ + //格式校验 通过则设置api绑定的ip为输入的ip 否则设置为 + Boolean check = vo.getIp().matches("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$"); + + if(check){ + System.out.println("用户输入的地址:"+info.getApiIp()); + info.setApiIp(vo.getIp()); + keyToDB.setApiIp(vo.getIp()); + }else { + return AjaxResult.error("ip地址格式错误"); + //String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest()); + //info.setApiIp(ipAddr); + //keyToDB.setApiIp(ipAddr); + //System.out.println("iputils获取到的地址:"+ipAddr); + } + }else { + String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest()); + info.setApiIp(ipAddr); + keyToDB.setApiIp(ipAddr); + System.out.println("未传入ip,通过iputils获取到的地址:"+ipAddr); + } + info.setPermissions(permsSet); + //通过jwt创建api_key + String key = Convert.toStr(openApiService.createApiKey(info).get("api_key")); + System.out.println("创建的api_key"+key); + System.out.println("共"+key.length()+"个字符"); + keyToDB.setKey(key); + //存入数据库 + boolean result = uaMapper.userCreateApiKey(keyToDB); + if(result){ + return AjaxResult.success(); + }else { + return AjaxResult.error("服务器当前apiKey生成请求大多,apiKey生成失败,请稍后再试"); + } + } + + @Override + public List getApiList(PageVo vo) { + long t0 = System.currentTimeMillis(); + //获取邮箱 网站用的邮箱做用户名 + String user = SecurityUtils.getUsername(); + if(StringUtils.isNull(user)){ + return null; + } + List list = uaMapper.getAPIListByUser(user); + List returnList = list.stream().map(e -> { + UserApiDto dto = new UserApiDto(); + BeanUtils.copyProperties(e,dto); + if(StringUtils.isNull(e.getPerms())){ + //防止字段为null时抛出异常 字段未null时设置字段为"" + dto.setPerms("".split(",")); + }else { + dto.setPerms(e.getPerms().split(",")); + } + return dto; + }).collect(Collectors.toList()); + return returnList; + } + + @Override + public AjaxResult getApiInfo(ApiKeyInfoVo vo) { + + + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return null; + } + PageHelper.clearPage(); + UserApi info = uaMapper.getAPIInfoByUserAndId(vo.getId(), email); + if(StringUtils.isNull(info)){ + return AjaxResult.error("未找到对应ApiKey"); + } + + if(!email.equals(info.getUser())){ + return AjaxResult.error("token错误"); + } + UserApiInfoReturnDto dto = new UserApiInfoReturnDto(); + BeanUtils.copyProperties(info,dto); + if(StringUtils.isNull(info.getPerms())){ + //防止字段为null时抛出异常 字段未null时设置字段为"" + dto.setPerms("".split(",")); + }else { + dto.setPerms(info.getPerms().split(",")); + } + return AjaxResult.success(dto); + } + + @Override + public AjaxResult updateApi(ApiKeyUpdateVo vo) { + + String user = SecurityUtils.getUsername(); + if(StringUtils.isNotBlank(vo.getPerms())){ + if(!vo.getPerms().equals(StringUtils.clean(vo.getPerms()))){ + return AjaxResult.error("权限传参错误"); + } + }else { + return AjaxResult.error("权限传参不能为空"); + } + + //todo 判断ip + if(StringUtils.isBlank(vo.getIp())){ + vo.setIp(IpUtils.getIpAddr(ServletUtils.getRequest())); + } + + //ip字段格式校验 + if(!checkIP(vo.getIp())){ + //不是ip报错 + return AjaxResult.error("IP格式错误"); + } + + //todo 判断perms传参 + List perms = Arrays.asList(vo.getPerms().split(",").clone()); + Set permsSet = new HashSet<>(); + for (String perm : perms) + { + if (StringUtils.isNotEmpty(perm)) + { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + + UserApi userApi = uaMapper.getAPIInfoByUserAndId(vo.getId(), user); + if(StringUtils.isNull(userApi)){ + return AjaxResult.error("id未找到"); + } + OpenApiKeyInfo info = openApiService.getOpenApiKeyInfo(userApi.getKey()); + + boolean result = uaMapper.updateApiKey(vo,user); + if(result){ + info.setApiIp(vo.getIp()); + info.setPermissions(permsSet); + openApiService.refreshApiKey(info); + return AjaxResult.success("修改成功"); + }else { + result = uaMapper.updateApiKey(vo,user); + + if(result){ + info.setApiIp(vo.getIp()); + info.setPermissions(permsSet); + openApiService.refreshApiKey(info); + return AjaxResult.success("修改成功"); + }else { + return AjaxResult.error("当前接口请求过多,请稍后再试"); + } + } + } + + @Override + public AjaxResult deleteApi(ApiKeyDelVo vo) { + String email = SecurityUtils.getUsername(); + if(StringUtils.isNull(email)){ + return AjaxResult.error("token解析异常"); + } + + List ids = Arrays.asList(StringUtils.clean(vo.getIds()).split(",")); + + if(ids.size() < 1){ + return AjaxResult.error("id不能为空"); + } + + List list = uaMapper.getAPIListByUser(email); + if(list.size() < 0){ + return AjaxResult.error("id不存在"); + } + + List apiIds = list.stream().map(e -> {return Convert.toStr(e.getId());}).collect(Collectors.toList()); + + if(apiIds.containsAll(ids)){ + //执行删除 + try { + uaMapper.batchDeleteAPIByIdsAndUser(ids, email); + list.stream().forEach(e ->{openApiService.delOpenApiKey(e.getKey());}); + + //redis删除api_key相关 + return AjaxResult.success("删除成功"); + }catch (Exception e){ + return AjaxResult.error("网络异常,请稍后再试"); + } + + }else { + StringBuilder msg = new StringBuilder(); + msg.append("当前用户下不存在对应key:"); + ids.stream().forEach(id -> { + if(!apiIds.contains(id)){ + msg.append(id+"\n"); + } + }); + return AjaxResult.error(msg.toString()); + } + } + + + public boolean checkIP(String ip){ + if(StringUtils.isBlank(ip)){ + return false; + } + boolean iPv4Result = IPAddressUtil.isIPv4LiteralAddress(ip); + boolean iPv6Result = IPAddressUtil.isIPv6LiteralAddress(ip); + if(iPv4Result || iPv6Result){ + return true; + }else{ + return false; + } + } + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/DataTask.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/DataTask.java new file mode 100644 index 0000000..a47051f --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/DataTask.java @@ -0,0 +1,5327 @@ +package com.m2pool.pool.task; + +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.pool.dto.*; +import com.m2pool.pool.entity.PoolPower; +import com.m2pool.pool.enums.PoolCalParamConfig; +import com.m2pool.pool.enums.PoolUnits; +import com.m2pool.pool.enums.Pools; +import com.m2pool.pool.mapper.PoolMapper; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import java.math.BigDecimal; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @Description 矿池、矿机定时任务 主要处理算力数据和幸运值计算 + * 新增币种则新增定时任务 注意岔开处理时间 + * @Date 2024/6/14 14:03 + * @Author dy + */ +@Configuration +@EnableScheduling +public class DataTask { + + @Autowired + private PoolMapper poolMapper; + + @Autowired + private RedisService redisService; + + + @Scheduled(cron = "20 1,3,10,31,33,40 * * * ?") + public void NEXA30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("nexa 30分钟定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.NEXA.getMhs() + "30m", nowStr); + + if(count == 0) { + List list = poolMapper.getHourMinerDataList(Pools.NEXA.getMhs()+"_realv2",nowStr); + if(list.size() > 0){ + List offlineList = new ArrayList<>(); + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.NEXA.getFac()); + } + if("offline".equals(e.getState())){ + offlineList.add(e); + } + }); + list = filterList(list,now); + + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.NEXA.getMhs() + "30m", getToHourDBListByMinerDataList(list)); + + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.NEXA.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.NEXA.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.NEXA.getName() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.NEXA.getName() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + Map> userMap = list.stream().collect(Collectors.groupingBy(MinerDataDto::getMiner)); + if (result) { + System.out.println(DateUtils.dateTimeNow() + "nexa小时数据存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "nexa小时数据存入数据失败"); + } + + if(offlineList.size() >0){ + CompletableFuture.runAsync(()->{ + + //根据挖矿账户分组、统计离线矿机名单、离线矿机数 + + //对比redis中该挖矿账户上一次离线矿机数 + //有redis数据 需要判断 上一次离线矿机数<本次矿机离线数 才执行通知 否则跳过 + //无redis数据 说明是第一次 执行通知 + }); + + } + + } + else{ + //重来一次! + CompletableFuture.runAsync(()->{ + System.out.println("nexa 30分钟定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.NEXA.getMhs() + "30m", nowStr); + if(newCount == 0){ + List newList = poolMapper.getHourMinerDataList(Pools.NEXA.getMhs()+"_realv2",nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.NEXA.getFac()); + } + }); + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.NEXA.getMhs() + "30m", getToHourDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.NEXA.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.NEXA.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("延迟任务 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("延迟任务 user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.NEXA.getName() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.NEXA.getName() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if (newResult) { + System.out.println(DateUtils.dateTimeNow() + "nexa30m数据 延迟存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "nexa30m数据 延迟存入数据失败"); + + } + + } + } + }); + + } + } + + } + + @Scheduled(cron = "0 1,3,5 0 * * ?") + public void NEXADailyDataToDB(){ + String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); + Date now = DateUtils.parseDate( nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.NEXA.getMhs() + "24h", nowStr); + + if(count == 0) { + List list = poolMapper.getDailyMinerDataList(Pools.NEXA.getMhs()+"_realv2", nowStr); + if(list.size() > 0){ + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.NEXA.getFac()); + } + }); + list = filterList(list,now); + //list.stream().forEach(e ->{ + // e.setDate(DateUtils.addDays(e.getDate(),-1)); + //}); + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.NEXA.getMhs() + "24h", getToDailyDBListByMinerDataList(list)); + //todo 统计矿池数据写入矿池数据库 $coin_pool_24h + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.NEXA.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.NEXA.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + //写入数据到coin_users表中 + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.NEXA.getName() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.NEXA.getName() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if(result){ + System.out.println(DateUtils.dateTimeNow()+"nexa每日存入数据成功"); + }else { + System.out.println(DateUtils.dateTimeNow()+"nexa每日存入数据失败"); + + } + + }else { + CompletableFuture.runAsync(()->{ + System.out.println("nexa 每日矿池定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.NEXA.getMhs() + "24h", nowStr); + if(newCount == 0){ + List newList = poolMapper.getDailyMinerDataList(Pools.NEXA.getMhs()+"_realv2", nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.NEXA.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.NEXA.getMhs() + "24h", getToDailyDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.NEXA.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.NEXA.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.NEXA.getName() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.NEXA.getName() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + + }else { + PoolPower poolPower = new PoolPower(); + poolPower.setDate(now); + poolPower.setMhs(BigDecimal.ZERO); + poolPower.setMiners(0); + poolPower.setOnline(0); + poolPower.setOffline(0); + boolean poolResult = poolMapper.insertPoolPower(Pools.NEXA.getPoolTable() + "_24h", poolPower); + System.out.println("延时任务未查到 构造0数据入库,结果:"+poolPower); + } + } + }); + } + } + } + + @Scheduled(cron = "50 0,1,30,31 * * * ?") + public void NEXAUserPowerRatioDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + now.setHours(0); + now.setMinutes(0); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + BigDecimal poolMhs = poolMapper.getPoolTodayTotalPower("nexa", nowStr); + List userMhsList = poolMapper.getUserTodayTotalPower("nexa", nowStr); + HashMap map = new HashMap<>(); + userMhsList.stream().forEach(e ->{ + map.put(e.getUser(),e.getMhs().divide(poolMhs,8,BigDecimal.ROUND_HALF_UP)); + }); + + if(map.size() >0){ + redisService.deleteObject("NEXAUserPowerRatio"); + redisService.setCacheMap("NEXAUserPowerRatio",map); + } + + } + + //@Scheduled(cron = "30 2,7 0,12 * * ?") + @Scheduled(cron = "30 2,5 0/12 * * ?") + public void NEXALuckyDataToDB(){ + int time = 0; + LuckDto dto = new LuckDto(); + Date end = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + do { + //幸运值 + // N天应出块数量 = N天矿池平均算力/ N天全网平均算力 * 币种每日出块数 * N天 取同一时间点的矿池算力/全网算力 + // 币种每日出块数 = 720 数值来源: 每日分钟数(24*60) / 出一个块需要的时间(根据具体币种 nexa是2分钟) + // N天实际出块量 数据库查询N天实际出块量 + // N天幸运值 = N天实际出块量 / N天应出块量 * 100 结果即百分比数字% + /** 全网每日出块数 */ + BigDecimal nexaCount = PoolCalParamConfig.NEXA.getCount(); + + //计算矿池每日实际应出块数 block中算力单位是H/s + int hRate = PoolUnits.NEXA.gethRate(); + //先把矿池算力单位转换为H/s 再除以全网算力 得到比值 再乘以每日全网出块数 得到矿池每日应出块数 + List netPowerList = poolMapper.getDailyNetPower("nexa"); + if(netPowerList.size() < 1){ + continue; + } + + List poolPowerList = poolMapper.getDailyPoolPower("nexa"); + + //拿报块list 筛选实际出块数 + List list = poolMapper.getNEXABlockInfoList(); + + if (list.size() < 1){ + continue; + } + + //第一条报块数据的时间 + Date poolStart = list.get(list.size() - 1).getDate(); + Date start3d = DateUtils.addDays(end,-3); + Date start7d = DateUtils.addDays(end,-7); + Date start30d = DateUtils.addDays(end,-30); + Date start90d = DateUtils.addDays(end,-90); + //(矿池平均/全网平均) * (全网计算平均的时间/矿池计算平均的时间) * 每日全网出块数 = 矿池每日应出块数 + long pDay3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + //矿池单位转换 + BigDecimal pPow3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + BigDecimal nPow3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay3d == 0 || nDay3d == 0){ + continue; + } + //计算3天 每日出块量 + BigDecimal dayChunk3d = pPow3d + .multiply(BigDecimal.valueOf(nDay3d * nDay3d)) + .divide(nPow3d.multiply(BigDecimal.valueOf(pDay3d * pDay3d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal pPow7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal nPow7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay7d == 0 || nDay7d == 0){ + continue; + } + + //计算7d 每日应出块量 + BigDecimal dayChunk7d = pPow7d + .multiply(BigDecimal.valueOf(nDay7d * nDay7d)) + .divide(nPow7d.multiply(BigDecimal.valueOf(pDay7d * pDay7d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal pPow30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal nPow30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay30d == 0 || nDay30d == 0){ + continue; + } + + //计算30d 每日应出块量 + BigDecimal dayChunk30d = pPow30d + .multiply(BigDecimal.valueOf(nDay30d * nDay30d)) + .divide(nPow30d.multiply(BigDecimal.valueOf(pDay30d * pDay30d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal pPow90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal nPow90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay90d == 0 || nDay90d == 0){ + continue; + } + + //计算90d 每日应出块量 + BigDecimal dayChunk90d = pPow90d + .multiply(BigDecimal.valueOf(nDay90d * nDay90d)) + .divide(nPow90d.multiply(BigDecimal.valueOf(pDay90d * pDay90d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + //3天时间出块数 + long count3d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start3d) || e.getDate().equals(start3d))) + .count(); + BigDecimal chunk3d = BigDecimal.valueOf(count3d); + //7天时间出块数 + long count7d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start7d) || e.getDate().equals(start7d))) + .count(); + BigDecimal chunk7d = BigDecimal.valueOf(count7d); + //30天时间出块数 + long count30d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start30d) || e.getDate().equals(start30d))) + .count(); + BigDecimal chunk30d = BigDecimal.valueOf(count30d); + //90天时间出块数 + long count90d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start90d) || e.getDate().equals(start90d))) + .count(); + BigDecimal chunk90d = BigDecimal.valueOf(count90d); + + if(poolStart.after(start3d)){ + System.out.println("不足3天"); + long needTime = poolStart.getTime()-start3d.getTime(); + chunk3d = chunk3d + .add(dayChunk3d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("3天出块量"+chunk3d); + dto.setLuck3d(chunk3d.divide(dayChunk3d.multiply(BigDecimal.valueOf(3)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start7d)){ + System.out.println("不足7天"); + long needTime = poolStart.getTime()-start7d.getTime(); + chunk7d = chunk7d + .add(dayChunk7d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("7天出块量"+chunk7d); + dto.setLuck7d(chunk7d.divide(dayChunk7d.multiply(BigDecimal.valueOf(7)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + + if(poolStart.after(start30d)){ + System.out.println("不足30天"); + long needTime = poolStart.getTime()-start30d.getTime(); + chunk30d = chunk30d + .add(dayChunk30d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("30天出块量"+chunk30d); + dto.setLuck30d(chunk30d.divide(dayChunk30d.multiply(BigDecimal.valueOf(30)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start90d)){ + System.out.println("不足90天"); + long needTime = poolStart.getTime()-start90d.getTime(); + chunk90d = chunk90d + .add(dayChunk90d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("90天出块量"+chunk90d); + dto.setLuck90d(chunk90d.divide(dayChunk90d.multiply(BigDecimal.valueOf(90)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + break; + }while (time < 4); + + redisService.deleteObject("NEXALuck"); + redisService.setCacheObject("NEXALuck",dto); + } + + @Scheduled(cron = "11 1,3,11,31,33,41 * * * ?") + public void GRS30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("grs 30分钟定时任务执行时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.GRS.getMhs() + "30m", nowStr); + + System.out.println("二级表当前时间点数据条数:"+count); + + if(count == 0) { + List list = poolMapper.getHourMinerDataList(Pools.GRS.getMhs()+"_realv2",nowStr); + + if(list.size() > 0){ + + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.GRS.getFac()); + } + }); + + list = filterList(list,now); + + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.GRS.getMhs() + "30m", getToHourDBListByMinerDataList(list)); + + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.GRS.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.GRS.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("GRS user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.GRS.getName() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.GRS.getName() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + Map> userMap = list.stream().collect(Collectors.groupingBy(MinerDataDto::getMiner)); + if (result) { + System.out.println(DateUtils.dateTimeNow() + "grs小时数据存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "grs小时数据存入数据失败"); + + } + + } + else{ + //重来一次! + CompletableFuture.runAsync(()->{ + System.out.println("grs 30分钟定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.GRS.getMhs() + "30m", nowStr); + if(newCount == 0){ + List newList = poolMapper.getHourMinerDataList(Pools.GRS.getMhs()+"_realv2",nowStr); + if(newList.size() > 0){ + + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.GRS.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.GRS.getMhs() + "30m", getToHourDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.GRS.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.GRS.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("延迟任务 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("延迟任务 user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.GRS.getName() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.GRS.getName() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if (newResult) { + System.out.println(DateUtils.dateTimeNow() + "grs30m数据 延迟存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "grs数据 延迟存入数据失败"); + + } + + } + } + }); + + } + } + + } + + @Scheduled(cron = "1 1,3,5 0 * * ?") + public void GRSDailyDataToDB(){ + String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); + Date now = DateUtils.parseDate( nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.GRS.getMhs() + "24h", nowStr); + + if(count == 0) { + List list = poolMapper.getDailyMinerDataList(Pools.GRS.getMhs()+"_realv2", nowStr); + if(list.size() > 0){ + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.GRS.getFac()); + } + }); + list = filterList(list,now); + //list.stream().forEach(e ->{ + // e.setDate(DateUtils.addDays(e.getDate(),-1)); + //}); + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.GRS.getMhs() + "24h", getToDailyDBListByMinerDataList(list)); + //todo 统计矿池数据写入矿池数据库 $coin_pool_24h + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.GRS.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.GRS.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + //写入数据到coin_users表中 + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.GRS.getName() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.GRS.getName() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if(result){ + System.out.println(DateUtils.dateTimeNow()+"grs每日存入数据成功"); + }else { + System.out.println(DateUtils.dateTimeNow()+"grs每日存入数据失败"); + + } + + }else { + CompletableFuture.runAsync(()->{ + System.out.println("grs 每日矿池定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.GRS.getMhs() + "24h", nowStr); + if(newCount == 0){ + List newList = poolMapper.getDailyMinerDataList(Pools.GRS.getMhs()+"_realv2", nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.GRS.getFac()); + } + }); + + newList = filterList(newList,now); + + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.GRS.getMhs() + "24h", getToDailyDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.GRS.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.GRS.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.GRS.getName() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.GRS.getName() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + + }else { + PoolPower poolPower = new PoolPower(); + poolPower.setDate(now); + poolPower.setMhs(BigDecimal.ZERO); + poolPower.setMiners(0); + poolPower.setOnline(0); + poolPower.setOffline(0); + boolean poolResult = poolMapper.insertPoolPower(Pools.GRS.getPoolTable() + "_24h", poolPower); + System.out.println("延时任务未查到 构造0数据入库,结果:"+poolPower); + } + } + }); + } + } + } + + @Scheduled(cron = "51 0,1,30,31 * * * ?") + public void GRSUserPowerRatioDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + now.setHours(0); + now.setMinutes(0); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + BigDecimal poolMhs = Convert.toBigDecimal( poolMapper.getPoolTodayTotalPower("grs", nowStr),BigDecimal.ONE); + + List userMhsList = poolMapper.getUserTodayTotalPower("grs", nowStr); + HashMap map = new HashMap<>(); + userMhsList.stream().forEach(e ->{ + if(StringUtils.isNull(e.getMhs())){ + map.put(e.getUser(),BigDecimal.ZERO.setScale(8).stripTrailingZeros()); + }else { + map.put(e.getUser(),e.getMhs().divide(poolMhs,8,BigDecimal.ROUND_HALF_UP)); + } + }); + + if(map.size() >0){ + redisService.deleteObject("GRSUserPowerRatio"); + redisService.setCacheMap("GRSUserPowerRatio",map); + } + + } + + //@Scheduled(cron = "30 2,7 0,12 * * ?") + @Scheduled(cron = "31 2,5 0/12 * * ?") + public void GRSLuckyDataToDB(){ + int time = 0; + LuckDto dto = new LuckDto(); + Date end = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + do { + //幸运值 + // N天应出块数量 = N天矿池平均算力/ N天全网平均算力 * 币种每日出块数 * N天 取同一时间点的矿池算力/全网算力 + // 币种每日出块数 = 720 数值来源: 每日分钟数(24*60) / 出一个块需要的时间(根据具体币种 nexa是2分钟) + // N天实际出块量 数据库查询N天实际出块量 + // N天幸运值 = N天实际出块量 / N天应出块量 * 100 结果即百分比数字% + /** 全网每日出块数 */ + BigDecimal nexaCount = PoolCalParamConfig.GRS.getCount(); + + //计算矿池每日实际应出块数 block中算力单位是H/s + int hRate = PoolUnits.GRS.gethRate(); + //先把矿池算力单位转换为H/s 再除以全网算力 得到比值 再乘以每日全网出块数 得到矿池每日应出块数 + List netPowerList = poolMapper.getDailyNetPower("grs"); + if(netPowerList.size() < 1){ + continue; + } + + List poolPowerList = poolMapper.getDailyPoolPower("grs"); + + //拿报块list 筛选实际出块数 + //List list = poolMapper.getNEXABlockInfoList(); + List list = poolMapper.getGRSBlockInfoList(); + + if (list.size() < 1){ + continue; + } + + //第一条报块数据的时间 + Date poolStart = list.get(list.size() - 1).getDate(); + Date start3d = DateUtils.addDays(end,-3); + Date start7d = DateUtils.addDays(end,-7); + Date start30d = DateUtils.addDays(end,-30); + Date start90d = DateUtils.addDays(end,-90); + //(矿池平均/全网平均) * (全网计算平均的时间/矿池计算平均的时间) * 每日全网出块数 = 矿池每日应出块数 + long pDay3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + //矿池单位转换 + BigDecimal pPow3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + BigDecimal nPow3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay3d == 0 || nDay3d == 0){ + continue; + } + //计算3天 每日出块量 + BigDecimal dayChunk3d = pPow3d + .multiply(BigDecimal.valueOf(nDay3d * nDay3d)) + .divide(nPow3d.multiply(BigDecimal.valueOf(pDay3d * pDay3d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal pPow7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal nPow7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay7d == 0 || nDay7d == 0){ + continue; + } + + //计算7d 每日应出块量 + BigDecimal dayChunk7d = pPow7d + .multiply(BigDecimal.valueOf(nDay7d * nDay7d)) + .divide(nPow7d.multiply(BigDecimal.valueOf(pDay7d * pDay7d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal pPow30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal nPow30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay30d == 0 || nDay30d == 0){ + continue; + } + + //计算30d 每日应出块量 + BigDecimal dayChunk30d = pPow30d + .multiply(BigDecimal.valueOf(nDay30d * nDay30d)) + .divide(nPow30d.multiply(BigDecimal.valueOf(pDay30d * pDay30d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal pPow90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal nPow90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay90d == 0 || nDay90d == 0){ + continue; + } + + //计算90d 每日应出块量 + BigDecimal dayChunk90d = pPow90d + .multiply(BigDecimal.valueOf(nDay90d * nDay90d)) + .divide(nPow90d.multiply(BigDecimal.valueOf(pDay90d * pDay90d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + //3天时间出块数 + long count3d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start3d) || e.getDate().equals(start3d))) + .count(); + BigDecimal chunk3d = BigDecimal.valueOf(count3d); + //7天时间出块数 + long count7d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start7d) || e.getDate().equals(start7d))) + .count(); + BigDecimal chunk7d = BigDecimal.valueOf(count7d); + //30天时间出块数 + long count30d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start30d) || e.getDate().equals(start30d))) + .count(); + BigDecimal chunk30d = BigDecimal.valueOf(count30d); + //90天时间出块数 + long count90d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start90d) || e.getDate().equals(start90d))) + .count(); + BigDecimal chunk90d = BigDecimal.valueOf(count90d); + + if(poolStart.after(start3d)){ + System.out.println("不足3天"); + long needTime = poolStart.getTime()-start3d.getTime(); + chunk3d = chunk3d + .add(dayChunk3d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("3天出块量"+chunk3d); + dto.setLuck3d(chunk3d.divide(dayChunk3d.multiply(BigDecimal.valueOf(3)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start7d)){ + System.out.println("不足7天"); + long needTime = poolStart.getTime()-start7d.getTime(); + chunk7d = chunk7d + .add(dayChunk7d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("7天出块量"+chunk7d); + dto.setLuck7d(chunk7d.divide(dayChunk7d.multiply(BigDecimal.valueOf(7)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + + if(poolStart.after(start30d)){ + System.out.println("不足30天"); + long needTime = poolStart.getTime()-start30d.getTime(); + chunk30d = chunk30d + .add(dayChunk30d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("30天出块量"+chunk30d); + dto.setLuck30d(chunk30d.divide(dayChunk30d.multiply(BigDecimal.valueOf(30)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start90d)){ + System.out.println("不足90天"); + long needTime = poolStart.getTime()-start90d.getTime(); + chunk90d = chunk90d + .add(dayChunk90d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("90天出块量"+chunk90d); + dto.setLuck90d(chunk90d.divide(dayChunk90d.multiply(BigDecimal.valueOf(90)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + break; + }while (time < 4); + + redisService.deleteObject("GRSLuck"); + redisService.setCacheObject("GRSLuck",dto); + } + + @Scheduled(cron = "12 1,3,12,31,33,42 * * * ?") + public void MONA30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("mona 30分钟定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.MONA.getMhs() + "30m", nowStr); + + if(count == 0) { + List list = poolMapper.getHourMinerDataList(Pools.MONA.getMhs()+"_realv2",nowStr); + if(list.size() > 0){ + + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.MONA.getFac()); + } + }); + + list = filterList(list,now); + + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.MONA.getMhs() + "30m", getToHourDBListByMinerDataList(list)); + + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.MONA.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.MONA.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("MONA user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.MONA.getName() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.MONA.getName() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + Map> userMap = list.stream().collect(Collectors.groupingBy(MinerDataDto::getMiner)); + if (result) { + System.out.println(DateUtils.dateTimeNow() + "mona小时数据存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "mona小时数据存入数据失败"); + + } + + } + else{ + //重来一次! + CompletableFuture.runAsync(()->{ + System.out.println("mona 30分钟定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.MONA.getMhs() + "30m", nowStr); + if(newCount == 0){ + List newList = poolMapper.getHourMinerDataList(Pools.MONA.getMhs()+"_realv2",nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.MONA.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.MONA.getMhs() + "30m", getToHourDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.MONA.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.MONA.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("延迟任务 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("延迟任务 user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.MONA.getName() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.MONA.getName() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if (newResult) { + System.out.println(DateUtils.dateTimeNow() + "mona30m数据 延迟存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "mona30m数据 延迟存入数据失败"); + + } + + } + } + }); + + } + } + + } + + @Scheduled(cron = "2 1,3,5 0 * * ?") + public void MONADailyDataToDB(){ + String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); + Date now = DateUtils.parseDate( nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.MONA.getMhs() + "24h", nowStr); + + if(count == 0) { + List list = poolMapper.getDailyMinerDataList(Pools.MONA.getMhs()+"_realv2", nowStr); + if(list.size() > 0){ + + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.MONA.getFac()); + } + }); + + list = filterList(list,now); + //list.stream().forEach(e ->{ + // e.setDate(DateUtils.addDays(e.getDate(),-1)); + //}); + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.MONA.getMhs() + "24h", getToDailyDBListByMinerDataList(list)); + //todo 统计矿池数据写入矿池数据库 $coin_pool_24h + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + System.out.println("date:"+date); + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.MONA.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.MONA.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + //写入数据到coin_users表中 + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.MONA.getName() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.MONA.getName() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if(result){ + System.out.println(DateUtils.dateTimeNow()+"mona每日存入数据成功"); + }else { + System.out.println(DateUtils.dateTimeNow()+"mona每日存入数据失败"); + + } + + }else { + CompletableFuture.runAsync(()->{ + System.out.println("mona 每日矿池定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.MONA.getMhs() + "24h", nowStr); + if(newCount == 0){ + List newList = poolMapper.getDailyMinerDataList(Pools.MONA.getMhs()+"_realv2", nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.MONA.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.MONA.getMhs() + "24h", getToDailyDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.MONA.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.MONA.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.MONA.getName() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.MONA.getName() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + + }else { + PoolPower poolPower = new PoolPower(); + poolPower.setDate(now); + poolPower.setMhs(BigDecimal.ZERO); + poolPower.setMiners(0); + poolPower.setOnline(0); + poolPower.setOffline(0); + boolean poolResult = poolMapper.insertPoolPower(Pools.MONA.getPoolTable() + "_24h", poolPower); + System.out.println("延时任务未查到 构造0数据入库,结果:"+poolPower); + } + } + }); + } + } + } + + @Scheduled(cron = "52 0,1,30,31 * * * ?") + public void MONAUserPowerRatioDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + now.setHours(0); + now.setMinutes(0); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + BigDecimal poolMhs = Convert.toBigDecimal( poolMapper.getPoolTodayTotalPower("mona", nowStr),BigDecimal.ONE); + + List userMhsList = poolMapper.getUserTodayTotalPower("mona", nowStr); + HashMap map = new HashMap<>(); + userMhsList.stream().forEach(e ->{ + if(StringUtils.isNull(e.getMhs())){ + map.put(e.getUser(),BigDecimal.ZERO.setScale(8).stripTrailingZeros()); + }else { + map.put(e.getUser(),e.getMhs().divide(poolMhs,8,BigDecimal.ROUND_HALF_UP)); + } + }); + + if(map.size() >0){ + redisService.deleteObject("MONAUserPowerRatio"); + redisService.setCacheMap("MONAUserPowerRatio",map); + } + + } + + //@Scheduled(cron = "30 2,7 0,12 * * ?") + @Scheduled(cron = "32 2,5 0/12 * * ?") + public void MONALuckyDataToDB(){ + int time = 0; + LuckDto dto = new LuckDto(); + Date end = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + do { + //幸运值 + // N天应出块数量 = N天矿池平均算力/ N天全网平均算力 * 币种每日出块数 * N天 取同一时间点的矿池算力/全网算力 + // 币种每日出块数 = 720 数值来源: 每日分钟数(24*60) / 出一个块需要的时间(根据具体币种 nexa是2分钟) + // N天实际出块量 数据库查询N天实际出块量 + // N天幸运值 = N天实际出块量 / N天应出块量 * 100 结果即百分比数字% + /** 全网每日出块数 */ + BigDecimal nexaCount = PoolCalParamConfig.MONA.getCount(); + + //计算矿池每日实际应出块数 block中算力单位是H/s + int hRate = PoolUnits.MONA.gethRate(); + //先把矿池算力单位转换为H/s 再除以全网算力 得到比值 再乘以每日全网出块数 得到矿池每日应出块数 + List netPowerList = poolMapper.getDailyNetPower("mona"); + if(netPowerList.size() < 1){ + continue; + } + + List poolPowerList = poolMapper.getDailyPoolPower("mona"); + + //拿报块list 筛选实际出块数 + //List list = poolMapper.getNEXABlockInfoList(); + List list = poolMapper.getMonaBlockInfoList(); + + if (list.size() < 1){ + continue; + } + + //第一条报块数据的时间 + Date poolStart = list.get(list.size() - 1).getDate(); + Date start3d = DateUtils.addDays(end,-3); + Date start7d = DateUtils.addDays(end,-7); + Date start30d = DateUtils.addDays(end,-30); + Date start90d = DateUtils.addDays(end,-90); + //(矿池平均/全网平均) * (全网计算平均的时间/矿池计算平均的时间) * 每日全网出块数 = 矿池每日应出块数 + long pDay3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + //矿池单位转换 + BigDecimal pPow3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + BigDecimal nPow3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay3d == 0 || nDay3d == 0){ + continue; + } + //计算3天 每日出块量 + BigDecimal dayChunk3d = pPow3d + .multiply(BigDecimal.valueOf(nDay3d * nDay3d)) + .divide(nPow3d.multiply(BigDecimal.valueOf(pDay3d * pDay3d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal pPow7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal nPow7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay7d == 0 || nDay7d == 0){ + continue; + } + + //计算7d 每日应出块量 + BigDecimal dayChunk7d = pPow7d + .multiply(BigDecimal.valueOf(nDay7d * nDay7d)) + .divide(nPow7d.multiply(BigDecimal.valueOf(pDay7d * pDay7d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal pPow30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal nPow30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay30d == 0 || nDay30d == 0){ + continue; + } + + //计算30d 每日应出块量 + BigDecimal dayChunk30d = pPow30d + .multiply(BigDecimal.valueOf(nDay30d * nDay30d)) + .divide(nPow30d.multiply(BigDecimal.valueOf(pDay30d * pDay30d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal pPow90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal nPow90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay90d == 0 || nDay90d == 0){ + continue; + } + + //计算90d 每日应出块量 + BigDecimal dayChunk90d = pPow90d + .multiply(BigDecimal.valueOf(nDay90d * nDay90d)) + .divide(nPow90d.multiply(BigDecimal.valueOf(pDay90d * pDay90d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + //3天时间出块数 + long count3d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start3d) || e.getDate().equals(start3d))) + .count(); + BigDecimal chunk3d = BigDecimal.valueOf(count3d); + //7天时间出块数 + long count7d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start7d) || e.getDate().equals(start7d))) + .count(); + BigDecimal chunk7d = BigDecimal.valueOf(count7d); + //30天时间出块数 + long count30d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start30d) || e.getDate().equals(start30d))) + .count(); + BigDecimal chunk30d = BigDecimal.valueOf(count30d); + //90天时间出块数 + long count90d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start90d) || e.getDate().equals(start90d))) + .count(); + BigDecimal chunk90d = BigDecimal.valueOf(count90d); + + if(poolStart.after(start3d)){ + System.out.println("不足3天"); + long needTime = poolStart.getTime()-start3d.getTime(); + chunk3d = chunk3d + .add(dayChunk3d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("3天出块量"+chunk3d); + dto.setLuck3d(chunk3d.divide(dayChunk3d.multiply(BigDecimal.valueOf(3)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start7d)){ + System.out.println("不足7天"); + long needTime = poolStart.getTime()-start7d.getTime(); + chunk7d = chunk7d + .add(dayChunk7d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("7天出块量"+chunk7d); + dto.setLuck7d(chunk7d.divide(dayChunk7d.multiply(BigDecimal.valueOf(7)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + + if(poolStart.after(start30d)){ + System.out.println("不足30天"); + long needTime = poolStart.getTime()-start30d.getTime(); + chunk30d = chunk30d + .add(dayChunk30d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("30天出块量"+chunk30d); + dto.setLuck30d(chunk30d.divide(dayChunk30d.multiply(BigDecimal.valueOf(30)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start90d)){ + System.out.println("不足90天"); + long needTime = poolStart.getTime()-start90d.getTime(); + chunk90d = chunk90d + .add(dayChunk90d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("90天出块量"+chunk90d); + dto.setLuck90d(chunk90d.divide(dayChunk90d.multiply(BigDecimal.valueOf(90)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + break; + }while (time < 4); + + redisService.deleteObject("MONALuck"); + redisService.setCacheObject("MONALuck",dto); + } + + @Scheduled(cron = "14 1,3,9,31,33,39 * * * ?") + public void DGBO30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("DGBO 30分钟定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.DGBO.getMhs() + "30m", nowStr); + + if(count == 0) { + List list = poolMapper.getHourMinerDataList(Pools.DGBO.getMhs()+"_realv2",nowStr); + if(list.size() > 0){ + + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBO.getFac()); + } + }); + + list = filterList(list,now); + + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.DGBO.getMhs() + "30m", getToHourDBListByMinerDataList(list)); + + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.DGBO.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.DGBO.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("DGBO 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("DGBO user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBO.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBO.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + Map> userMap = list.stream().collect(Collectors.groupingBy(MinerDataDto::getMiner)); + if (result) { + System.out.println(DateUtils.dateTimeNow() + "DGBO 30m数据存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "DGBO 30m数据存入数据失败"); + + } + + } + else{ + //重来一次! + CompletableFuture.runAsync(()->{ + System.out.println("DGBO 30分钟定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.DGBO.getMhs() + "30m", nowStr); + if(newCount == 0){ + List newList = poolMapper.getHourMinerDataList(Pools.DGBO.getMhs()+"_realv2",nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBO.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.DGBO.getMhs() + "30m", getToHourDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.DGBO.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.DGBO.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("延迟任务 DGBO构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("延迟任务 DGBO user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBO.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBO.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if (newResult) { + System.out.println(DateUtils.dateTimeNow() + "DGBO 30m数据 延迟存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "DGBO 30m数据 延迟存入数据失败"); + + } + + } + } + }); + + } + } + + } + + @Scheduled(cron = "4 1,3,5 0 * * ?") + public void DGBODailyDataToDB(){ + String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); + Date now = DateUtils.parseDate( nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.DGBO.getMhs() + "24h", nowStr); + + if(count == 0) { + List list = poolMapper.getDailyMinerDataList(Pools.DGBO.getMhs()+"_realv2", nowStr); + if(list.size() > 0){ + + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBO.getFac()); + } + }); + + list = filterList(list,now); + //list.stream().forEach(e ->{ + // e.setDate(DateUtils.addDays(e.getDate(),-1)); + //}); + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.DGBO.getMhs() + "24h", getToDailyDBListByMinerDataList(list)); + //todo 统计矿池数据写入矿池数据库 $coin_pool_24h + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + System.out.println("date:"+date); + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.DGBO.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.DGBO.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + //写入数据到coin_users表中 + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBO.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBO.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if(result){ + System.out.println(DateUtils.dateTimeNow()+"DGBO 每日存入数据成功"); + }else { + System.out.println(DateUtils.dateTimeNow()+"DGBO 每日存入数据失败"); + + } + + }else { + CompletableFuture.runAsync(()->{ + System.out.println("DGBO 每日矿池定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.DGBO.getMhs() + "24h", nowStr); + if(newCount == 0){ + List newList = poolMapper.getDailyMinerDataList(Pools.DGBO.getMhs()+"_realv2", nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBO.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.DGBO.getMhs() + "24h", getToDailyDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.DGBO.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.DGBO.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBO.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBO.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + + }else { + PoolPower poolPower = new PoolPower(); + poolPower.setDate(now); + poolPower.setMhs(BigDecimal.ZERO); + poolPower.setMiners(0); + poolPower.setOnline(0); + poolPower.setOffline(0); + boolean poolResult = poolMapper.insertPoolPower(Pools.DGBO.getPoolTable() + "_24h", poolPower); + System.out.println("DGBO延时任务未查到 构造0数据入库,结果:"+poolPower); + } + } + }); + } + } + } + + @Scheduled(cron = "54 0,1,30,31 * * * ?") + public void DGBOUserPowerRatioDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + now.setHours(0); + now.setMinutes(0); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + BigDecimal poolMhs = Convert.toBigDecimal( poolMapper.getPoolTodayTotalPower("dgbo", nowStr),BigDecimal.ONE); + + List userMhsList = poolMapper.getUserTodayTotalPower("dgbo", nowStr); + HashMap map = new HashMap<>(); + userMhsList.stream().forEach(e ->{ + if(StringUtils.isNull(e.getMhs())){ + map.put(e.getUser(),BigDecimal.ZERO.setScale(8).stripTrailingZeros()); + }else { + map.put(e.getUser(),e.getMhs().divide(poolMhs,8,BigDecimal.ROUND_HALF_UP)); + } + }); + + if(map.size() >0){ + redisService.deleteObject("DGBOUserPowerRatio"); + redisService.setCacheMap("DGBOUserPowerRatio",map); + } + + } + + //@Scheduled(cron = "30 2,7 0,12 * * ?") + @Scheduled(cron = "34 2,5 0/12 * * ?") + public void DGBOLuckyDataToDB(){ + int time = 0; + LuckDto dto = new LuckDto(); + Date end = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + do { + //幸运值 + // N天应出块数量 = N天矿池平均算力/ N天全网平均算力 * 币种每日出块数 * N天 取同一时间点的矿池算力/全网算力 + // 币种每日出块数 = 720 数值来源: 每日分钟数(24*60) / 出一个块需要的时间(根据具体币种 nexa是2分钟) + // N天实际出块量 数据库查询N天实际出块量 + // N天幸运值 = N天实际出块量 / N天应出块量 * 100 结果即百分比数字% + /** 全网每日出块数 */ + BigDecimal dgboCount = PoolCalParamConfig.DGBO.getCount(); + + //计算矿池每日实际应出块数 block中算力单位是H/s + int hRate = PoolUnits.DGBO.gethRate(); + //先把矿池算力单位转换为H/s 再除以全网算力 得到比值 再乘以每日全网出块数 得到矿池每日应出块数 + List netPowerList = poolMapper.getDailyNetPower("dgbo"); + if(netPowerList.size() < 1){ + continue; + } + + List poolPowerList = poolMapper.getDailyPoolPower("dgbo"); + + //拿报块list 筛选实际出块数 + //List list = poolMapper.getNEXABlockInfoList(); + List list = poolMapper.getDGBODOBlockInfoList(); + + if (list.size() < 1){ + continue; + } + + //第一条报块数据的时间 + Date poolStart = list.get(list.size() - 1).getDate(); + Date start3d = DateUtils.addDays(end,-3); + Date start7d = DateUtils.addDays(end,-7); + Date start30d = DateUtils.addDays(end,-30); + Date start90d = DateUtils.addDays(end,-90); + //(矿池平均/全网平均) * (全网计算平均的时间/矿池计算平均的时间) * 每日全网出块数 = 矿池每日应出块数 + long pDay3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + //矿池单位转换 + BigDecimal pPow3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + BigDecimal nPow3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay3d == 0 || nDay3d == 0){ + continue; + } + //计算3天 每日出块量 + BigDecimal dayChunk3d = pPow3d + .multiply(BigDecimal.valueOf(nDay3d * nDay3d)) + .divide(nPow3d.multiply(BigDecimal.valueOf(pDay3d * pDay3d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(dgboCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal pPow7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal nPow7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay7d == 0 || nDay7d == 0){ + continue; + } + + //计算7d 每日应出块量 + BigDecimal dayChunk7d = pPow7d + .multiply(BigDecimal.valueOf(nDay7d * nDay7d)) + .divide(nPow7d.multiply(BigDecimal.valueOf(pDay7d * pDay7d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(dgboCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal pPow30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal nPow30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay30d == 0 || nDay30d == 0){ + continue; + } + + //计算30d 每日应出块量 + BigDecimal dayChunk30d = pPow30d + .multiply(BigDecimal.valueOf(nDay30d * nDay30d)) + .divide(nPow30d.multiply(BigDecimal.valueOf(pDay30d * pDay30d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(dgboCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal pPow90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal nPow90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay90d == 0 || nDay90d == 0){ + continue; + } + + //计算90d 每日应出块量 + BigDecimal dayChunk90d = pPow90d + .multiply(BigDecimal.valueOf(nDay90d * nDay90d)) + .divide(nPow90d.multiply(BigDecimal.valueOf(pDay90d * pDay90d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(dgboCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + //3天时间出块数 + long count3d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start3d) || e.getDate().equals(start3d))) + .count(); + BigDecimal chunk3d = BigDecimal.valueOf(count3d); + //7天时间出块数 + long count7d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start7d) || e.getDate().equals(start7d))) + .count(); + BigDecimal chunk7d = BigDecimal.valueOf(count7d); + //30天时间出块数 + long count30d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start30d) || e.getDate().equals(start30d))) + .count(); + BigDecimal chunk30d = BigDecimal.valueOf(count30d); + //90天时间出块数 + long count90d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start90d) || e.getDate().equals(start90d))) + .count(); + BigDecimal chunk90d = BigDecimal.valueOf(count90d); + + if(poolStart.after(start3d)){ + System.out.println("不足3天"); + long needTime = poolStart.getTime()-start3d.getTime(); + chunk3d = chunk3d + .add(dayChunk3d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("3天出块量"+chunk3d); + dto.setLuck3d(chunk3d.divide(dayChunk3d.multiply(BigDecimal.valueOf(3)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start7d)){ + System.out.println("不足7天"); + long needTime = poolStart.getTime()-start7d.getTime(); + chunk7d = chunk7d + .add(dayChunk7d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("7天出块量"+chunk7d); + dto.setLuck7d(chunk7d.divide(dayChunk7d.multiply(BigDecimal.valueOf(7)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + + if(poolStart.after(start30d)){ + System.out.println("不足30天"); + long needTime = poolStart.getTime()-start30d.getTime(); + chunk30d = chunk30d + .add(dayChunk30d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("30天出块量"+chunk30d); + dto.setLuck30d(chunk30d.divide(dayChunk30d.multiply(BigDecimal.valueOf(30)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start90d)){ + System.out.println("不足90天"); + long needTime = poolStart.getTime()-start90d.getTime(); + chunk90d = chunk90d + .add(dayChunk90d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("90天出块量"+chunk90d); + dto.setLuck90d(chunk90d.divide(dayChunk90d.multiply(BigDecimal.valueOf(90)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + break; + }while (time < 4); + + redisService.deleteObject("DGBOLuck"); + redisService.setCacheObject("DGBOLuck",dto); + } + + @Scheduled(cron = "16 1,3,8,31,33,38 * * * ?") + public void DGBQ30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("DGBQ 30分钟定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.DGBQ.getMhs() + "30m", nowStr); + + if(count == 0) { + List list = poolMapper.getHourMinerDataList(Pools.DGBQ.getMhs()+"_realv2",nowStr); + if(list.size() > 0){ + + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBQ.getFac()); + } + }); + + list = filterList(list,now); + + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.DGBQ.getMhs() + "30m", getToHourDBListByMinerDataList(list)); + + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.DGBQ.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.DGBQ.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("DGBQ 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("DGBQ user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBQ.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBQ.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + Map> userMap = list.stream().collect(Collectors.groupingBy(MinerDataDto::getMiner)); + if (result) { + System.out.println(DateUtils.dateTimeNow() + "DGBQ 30m数据存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "DGBQ 30m数据存入数据失败"); + + } + + } + else{ + //重来一次! + CompletableFuture.runAsync(()->{ + System.out.println(DateUtils.dateTimeNow()+"DGBQ 30分钟定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.DGBQ.getMhs() + "30m", nowStr); + if(newCount == 0){ + List newList = poolMapper.getHourMinerDataList(Pools.DGBQ.getMhs()+"_realv2",nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBQ.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.DGBQ.getMhs() + "30m", getToHourDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.DGBQ.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.DGBQ.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("延迟任务 DGBQ构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("延迟任务 DGBQ user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBQ.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBQ.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if (newResult) { + System.out.println(DateUtils.dateTimeNow() + "DGBQ 30m数据 延迟存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "DGBO 30m数据 延迟存入数据失败"); + + } + + } + } + }); + + } + } + + } + + @Scheduled(cron = "6 1,3,5 0 * * ?") + public void DGBQDailyDataToDB(){ + String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); + Date now = DateUtils.parseDate( nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.DGBQ.getMhs() + "24h", nowStr); + + if(count == 0) { + List list = poolMapper.getDailyMinerDataList(Pools.DGBQ.getMhs()+"_realv2", nowStr); + if(list.size() > 0){ + + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBQ.getFac()); + } + }); + + list = filterList(list,now); + //list.stream().forEach(e ->{ + // e.setDate(DateUtils.addDays(e.getDate(),-1)); + //}); + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.DGBQ.getMhs() + "24h", getToDailyDBListByMinerDataList(list)); + //todo 统计矿池数据写入矿池数据库 $coin_pool_24h + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + System.out.println("date:"+date); + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.DGBQ.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.DGBQ.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + //写入数据到coin_users表中 + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBQ.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBQ.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if(result){ + System.out.println(DateUtils.dateTimeNow()+"DGBQ 每日存入数据成功"); + }else { + System.out.println(DateUtils.dateTimeNow()+"DGBQ 每日存入数据失败"); + + } + + }else { + CompletableFuture.runAsync(()->{ + System.out.println("DGBQ 每日矿池定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.DGBQ.getMhs() + "24h", nowStr); + if(newCount == 0){ + List newList = poolMapper.getDailyMinerDataList(Pools.DGBQ.getMhs()+"_realv2", nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBQ.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.DGBQ.getMhs() + "24h", getToDailyDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.DGBQ.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.DGBQ.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBQ.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBQ.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + + }else { + PoolPower poolPower = new PoolPower(); + poolPower.setDate(now); + poolPower.setMhs(BigDecimal.ZERO); + poolPower.setMiners(0); + poolPower.setOnline(0); + poolPower.setOffline(0); + boolean poolResult = poolMapper.insertPoolPower(Pools.DGBQ.getPoolTable() + "_24h", poolPower); + System.out.println("DGBQ延时任务未查到 构造0数据入库,结果:"+poolPower); + } + } + }); + } + } + } + + @Scheduled(cron = "56 0,1,30,31 * * * ?") + public void DGBQUserPowerRatioDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + now.setHours(0); + now.setMinutes(0); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + BigDecimal poolMhs = Convert.toBigDecimal( poolMapper.getPoolTodayTotalPower("dgbq", nowStr),BigDecimal.ONE); + + List userMhsList = poolMapper.getUserTodayTotalPower("dgbq", nowStr); + HashMap map = new HashMap<>(); + userMhsList.stream().forEach(e ->{ + if(StringUtils.isNull(e.getMhs())){ + map.put(e.getUser(),BigDecimal.ZERO.setScale(8).stripTrailingZeros()); + }else { + map.put(e.getUser(),e.getMhs().divide(poolMhs,8,BigDecimal.ROUND_HALF_UP)); + } + }); + + if(map.size() >0){ + redisService.deleteObject("DGBQUserPowerRatio"); + redisService.setCacheMap("DGBQUserPowerRatio",map); + } + + } + + //@Scheduled(cron = "30 2,7 0,12 * * ?") + @Scheduled(cron = "36 2,5 0/12 * * ?") + public void DGBQLuckyDataToDB(){ + int time = 0; + LuckDto dto = new LuckDto(); + Date end = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + do { + //幸运值 + // N天应出块数量 = N天矿池平均算力/ N天全网平均算力 * 币种每日出块数 * N天 取同一时间点的矿池算力/全网算力 + // 币种每日出块数 = 720 数值来源: 每日分钟数(24*60) / 出一个块需要的时间(根据具体币种 nexa是2分钟) + // N天实际出块量 数据库查询N天实际出块量 + // N天幸运值 = N天实际出块量 / N天应出块量 * 100 结果即百分比数字% + /** 全网每日出块数 */ + BigDecimal nexaCount = PoolCalParamConfig.DGBQ.getCount(); + + //计算矿池每日实际应出块数 block中算力单位是H/s + int hRate = PoolUnits.DGBQ.gethRate(); + //先把矿池算力单位转换为H/s 再除以全网算力 得到比值 再乘以每日全网出块数 得到矿池每日应出块数 + List netPowerList = poolMapper.getDailyNetPower("dgbq"); + if(netPowerList.size() < 1){ + continue; + } + + List poolPowerList = poolMapper.getDailyPoolPower("dgbq"); + + //拿报块list 筛选实际出块数 + //List list = poolMapper.getNEXABlockInfoList(); + List list = poolMapper.getDgbQubitBlockInfoList(); + + if (list.size() < 1){ + continue; + } + + //第一条报块数据的时间 + Date poolStart = list.get(list.size() - 1).getDate(); + Date start3d = DateUtils.addDays(end,-3); + Date start7d = DateUtils.addDays(end,-7); + Date start30d = DateUtils.addDays(end,-30); + Date start90d = DateUtils.addDays(end,-90); + //(矿池平均/全网平均) * (全网计算平均的时间/矿池计算平均的时间) * 每日全网出块数 = 矿池每日应出块数 + long pDay3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + //矿池单位转换 + BigDecimal pPow3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + BigDecimal nPow3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay3d == 0 || nDay3d == 0){ + continue; + } + //计算3天 每日出块量 + BigDecimal dayChunk3d = pPow3d + .multiply(BigDecimal.valueOf(nDay3d * nDay3d)) + .divide(nPow3d.multiply(BigDecimal.valueOf(pDay3d * pDay3d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal pPow7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal nPow7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay7d == 0 || nDay7d == 0){ + continue; + } + + //计算7d 每日应出块量 + BigDecimal dayChunk7d = pPow7d + .multiply(BigDecimal.valueOf(nDay7d * nDay7d)) + .divide(nPow7d.multiply(BigDecimal.valueOf(pDay7d * pDay7d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal pPow30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal nPow30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay30d == 0 || nDay30d == 0){ + continue; + } + + //计算30d 每日应出块量 + BigDecimal dayChunk30d = pPow30d + .multiply(BigDecimal.valueOf(nDay30d * nDay30d)) + .divide(nPow30d.multiply(BigDecimal.valueOf(pDay30d * pDay30d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal pPow90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal nPow90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay90d == 0 || nDay90d == 0){ + continue; + } + + //计算90d 每日应出块量 + BigDecimal dayChunk90d = pPow90d + .multiply(BigDecimal.valueOf(nDay90d * nDay90d)) + .divide(nPow90d.multiply(BigDecimal.valueOf(pDay90d * pDay90d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + //3天时间出块数 + long count3d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start3d) || e.getDate().equals(start3d))) + .count(); + BigDecimal chunk3d = BigDecimal.valueOf(count3d); + //7天时间出块数 + long count7d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start7d) || e.getDate().equals(start7d))) + .count(); + BigDecimal chunk7d = BigDecimal.valueOf(count7d); + //30天时间出块数 + long count30d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start30d) || e.getDate().equals(start30d))) + .count(); + BigDecimal chunk30d = BigDecimal.valueOf(count30d); + //90天时间出块数 + long count90d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start90d) || e.getDate().equals(start90d))) + .count(); + BigDecimal chunk90d = BigDecimal.valueOf(count90d); + + if(poolStart.after(start3d)){ + System.out.println("不足3天"); + long needTime = poolStart.getTime()-start3d.getTime(); + chunk3d = chunk3d + .add(dayChunk3d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("3天出块量"+chunk3d); + dto.setLuck3d(chunk3d.divide(dayChunk3d.multiply(BigDecimal.valueOf(3)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start7d)){ + System.out.println("不足7天"); + long needTime = poolStart.getTime()-start7d.getTime(); + chunk7d = chunk7d + .add(dayChunk7d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("7天出块量"+chunk7d); + dto.setLuck7d(chunk7d.divide(dayChunk7d.multiply(BigDecimal.valueOf(7)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + + if(poolStart.after(start30d)){ + System.out.println("不足30天"); + long needTime = poolStart.getTime()-start30d.getTime(); + chunk30d = chunk30d + .add(dayChunk30d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("30天出块量"+chunk30d); + dto.setLuck30d(chunk30d.divide(dayChunk30d.multiply(BigDecimal.valueOf(30)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start90d)){ + System.out.println("不足90天"); + long needTime = poolStart.getTime()-start90d.getTime(); + chunk90d = chunk90d + .add(dayChunk90d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("90天出块量"+chunk90d); + dto.setLuck90d(chunk90d.divide(dayChunk90d.multiply(BigDecimal.valueOf(90)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + break; + }while (time < 4); + + redisService.deleteObject("DGBQLuck"); + redisService.setCacheObject("DGBQLuck",dto); + } + + @Scheduled(cron = "18 1,3,7,31,33,37 * * * ?") + public void DGBS30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("DGBS 30分钟定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.DGBS.getMhs() + "30m", nowStr); + + if(count == 0) { + List list = poolMapper.getHourMinerDataList(Pools.DGBS.getMhs()+"_realv2",nowStr); + if(list.size() > 0){ + + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBS.getFac()); + } + }); + + list = filterList(list,now); + + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.DGBS.getMhs() + "30m", getToHourDBListByMinerDataList(list)); + + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.DGBS.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.DGBS.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("DGBS 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("DGBS user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBS.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBS.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + Map> userMap = list.stream().collect(Collectors.groupingBy(MinerDataDto::getMiner)); + if (result) { + System.out.println(DateUtils.dateTimeNow() + "DGBS 30m数据存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "DGBS 30m数据存入数据失败"); + + } + + } + else{ + //重来一次! + CompletableFuture.runAsync(()->{ + System.out.println("DGBS 30分钟定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.DGBS.getMhs() + "30m", nowStr); + if(newCount == 0){ + List newList = poolMapper.getHourMinerDataList(Pools.DGBS.getMhs()+"_realv2",nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBS.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.DGBS.getMhs() + "30m", getToHourDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.DGBS.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.DGBS.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("延迟任务 DGBS构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("延迟任务 DGBS user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBS.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBS.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if (newResult) { + System.out.println(DateUtils.dateTimeNow() + "DGBS 30m数据 延迟存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "DGBS 30m数据 延迟存入数据失败"); + + } + + } + } + }); + + } + } + + } + + @Scheduled(cron = "8 1,3,5 0 * * ?") + public void DGBSDailyDataToDB(){ + String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); + Date now = DateUtils.parseDate( nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.DGBS.getMhs() + "24h", nowStr); + + if(count == 0) { + List list = poolMapper.getDailyMinerDataList(Pools.DGBS.getMhs()+"_realv2", nowStr); + if(list.size() > 0){ + + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBS.getFac()); + } + }); + + list = filterList(list,now); + + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.DGBS.getMhs() + "24h", getToDailyDBListByMinerDataList(list)); + //todo 统计矿池数据写入矿池数据库 $coin_pool_24h + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + System.out.println("date:"+date); + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.DGBS.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.DGBS.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + //写入数据到coin_users表中 + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBS.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBS.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if(result){ + System.out.println(DateUtils.dateTimeNow()+"DGBS 每日存入数据成功"); + }else { + System.out.println(DateUtils.dateTimeNow()+"DGBS 每日存入数据失败"); + + } + + }else { + CompletableFuture.runAsync(()->{ + System.out.println("DGBS 每日矿池定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.DGBS.getMhs() + "24h", nowStr); + if(newCount == 0){ + List newList = poolMapper.getDailyMinerDataList(Pools.DGBS.getMhs()+"_realv2", nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.DGBS.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.DGBS.getMhs() + "24h", getToDailyDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.DGBS.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.DGBS.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBS.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.DGBS.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + + }else { + PoolPower poolPower = new PoolPower(); + poolPower.setDate(now); + poolPower.setMhs(BigDecimal.ZERO); + poolPower.setMiners(0); + poolPower.setOnline(0); + poolPower.setOffline(0); + boolean poolResult = poolMapper.insertPoolPower(Pools.DGBS.getPoolTable() + "_24h", poolPower); + System.out.println("DGBS延时任务未查到 构造0数据入库,结果:"+poolPower); + } + } + }); + } + } + } + + @Scheduled(cron = "58 0,1,30,31 * * * ?") + public void DGBSUserPowerRatioDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + now.setHours(0); + now.setMinutes(0); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + BigDecimal poolMhs = Convert.toBigDecimal( poolMapper.getPoolTodayTotalPower("dgbs", nowStr),BigDecimal.ONE); + + List userMhsList = poolMapper.getUserTodayTotalPower("dgbs", nowStr); + HashMap map = new HashMap<>(); + userMhsList.stream().forEach(e ->{ + if(StringUtils.isNull(e.getMhs())){ + map.put(e.getUser(),BigDecimal.ZERO.setScale(8).stripTrailingZeros()); + }else { + map.put(e.getUser(),e.getMhs().divide(poolMhs,8,BigDecimal.ROUND_HALF_UP)); + } + }); + + if(map.size() >0){ + redisService.deleteObject("DGBSUserPowerRatio"); + redisService.setCacheMap("DGBSUserPowerRatio",map); + } + + } + + //@Scheduled(cron = "30 2,7 0,12 * * ?") + @Scheduled(cron = "38 2,5 0/12 * * ?") + public void DGBSLuckyDataToDB(){ + int time = 0; + LuckDto dto = new LuckDto(); + Date end = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + do { + //幸运值 + // N天应出块数量 = N天矿池平均算力/ N天全网平均算力 * 币种每日出块数 * N天 取同一时间点的矿池算力/全网算力 + // 币种每日出块数 = 720 数值来源: 每日分钟数(24*60) / 出一个块需要的时间(根据具体币种 nexa是2分钟) + // N天实际出块量 数据库查询N天实际出块量 + // N天幸运值 = N天实际出块量 / N天应出块量 * 100 结果即百分比数字% + /** 全网每日出块数 */ + BigDecimal nexaCount = PoolCalParamConfig.DGBS.getCount(); + + //计算矿池每日实际应出块数 block中算力单位是H/s + int hRate = PoolUnits.DGBS.gethRate(); + //先把矿池算力单位转换为H/s 再除以全网算力 得到比值 再乘以每日全网出块数 得到矿池每日应出块数 + List netPowerList = poolMapper.getDailyNetPower("dgbs"); + if(netPowerList.size() < 1){ + continue; + } + + List poolPowerList = poolMapper.getDailyPoolPower("dgbs"); + + //拿报块list 筛选实际出块数 + //List list = poolMapper.getNEXABlockInfoList(); + List list = poolMapper.getDgbSkeinBlockInfoList(); + + if (list.size() < 1){ + continue; + } + + //第一条报块数据的时间 + Date poolStart = list.get(list.size() - 1).getDate(); + Date start3d = DateUtils.addDays(end,-3); + Date start7d = DateUtils.addDays(end,-7); + Date start30d = DateUtils.addDays(end,-30); + Date start90d = DateUtils.addDays(end,-90); + //(矿池平均/全网平均) * (全网计算平均的时间/矿池计算平均的时间) * 每日全网出块数 = 矿池每日应出块数 + long pDay3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + //矿池单位转换 + BigDecimal pPow3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + BigDecimal nPow3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay3d == 0 || nDay3d == 0){ + continue; + } + //计算3天 每日出块量 + BigDecimal dayChunk3d = pPow3d + .multiply(BigDecimal.valueOf(nDay3d * nDay3d)) + .divide(nPow3d.multiply(BigDecimal.valueOf(pDay3d * pDay3d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal pPow7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal nPow7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay7d == 0 || nDay7d == 0){ + continue; + } + + //计算7d 每日应出块量 + BigDecimal dayChunk7d = pPow7d + .multiply(BigDecimal.valueOf(nDay7d * nDay7d)) + .divide(nPow7d.multiply(BigDecimal.valueOf(pDay7d * pDay7d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal pPow30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal nPow30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay30d == 0 || nDay30d == 0){ + continue; + } + + //计算30d 每日应出块量 + BigDecimal dayChunk30d = pPow30d + .multiply(BigDecimal.valueOf(nDay30d * nDay30d)) + .divide(nPow30d.multiply(BigDecimal.valueOf(pDay30d * pDay30d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal pPow90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal nPow90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay90d == 0 || nDay90d == 0){ + continue; + } + + //计算90d 每日应出块量 + BigDecimal dayChunk90d = pPow90d + .multiply(BigDecimal.valueOf(nDay90d * nDay90d)) + .divide(nPow90d.multiply(BigDecimal.valueOf(pDay90d * pDay90d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + //3天时间出块数 + long count3d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start3d) || e.getDate().equals(start3d))) + .count(); + BigDecimal chunk3d = BigDecimal.valueOf(count3d); + //7天时间出块数 + long count7d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start7d) || e.getDate().equals(start7d))) + .count(); + BigDecimal chunk7d = BigDecimal.valueOf(count7d); + //30天时间出块数 + long count30d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start30d) || e.getDate().equals(start30d))) + .count(); + BigDecimal chunk30d = BigDecimal.valueOf(count30d); + //90天时间出块数 + long count90d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start90d) || e.getDate().equals(start90d))) + .count(); + BigDecimal chunk90d = BigDecimal.valueOf(count90d); + + if(poolStart.after(start3d)){ + System.out.println("不足3天"); + long needTime = poolStart.getTime()-start3d.getTime(); + chunk3d = chunk3d + .add(dayChunk3d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("3天出块量"+chunk3d); + dto.setLuck3d(chunk3d.divide(dayChunk3d.multiply(BigDecimal.valueOf(3)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start7d)){ + System.out.println("不足7天"); + long needTime = poolStart.getTime()-start7d.getTime(); + chunk7d = chunk7d + .add(dayChunk7d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("7天出块量"+chunk7d); + dto.setLuck7d(chunk7d.divide(dayChunk7d.multiply(BigDecimal.valueOf(7)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + + if(poolStart.after(start30d)){ + System.out.println("不足30天"); + long needTime = poolStart.getTime()-start30d.getTime(); + chunk30d = chunk30d + .add(dayChunk30d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("30天出块量"+chunk30d); + dto.setLuck30d(chunk30d.divide(dayChunk30d.multiply(BigDecimal.valueOf(30)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start90d)){ + System.out.println("不足90天"); + long needTime = poolStart.getTime()-start90d.getTime(); + chunk90d = chunk90d + .add(dayChunk90d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("90天出块量"+chunk90d); + dto.setLuck90d(chunk90d.divide(dayChunk90d.multiply(BigDecimal.valueOf(90)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + break; + }while (time < 4); + + redisService.deleteObject("DGBSLuck"); + redisService.setCacheObject("DGBSLuck",dto); + } + + @Scheduled(cron = "19 1,3,13,31,33,43 * * * ?") + public void RXD30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("rxd 30分钟定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.RXD.getMhs() + "30m", nowStr); + + if(count == 0) { + List list = poolMapper.getHourMinerDataList(Pools.RXD.getMhs()+"_realv2",nowStr); + if(list.size() > 0){ + + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.RXD.getFac()); + } + }); + list = filterList(list,now); + + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.RXD.getMhs() + "30m", getToHourDBListByMinerDataList(list)); + + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.RXD.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.RXD.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("rxd 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("rxd user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.RXD.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.RXD.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + Map> userMap = list.stream().collect(Collectors.groupingBy(MinerDataDto::getMiner)); + if (result) { + System.out.println(DateUtils.dateTimeNow() + "rxd 小时数据存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "rxd 小时数据存入数据失败"); + + } + + } + else{ + //重来一次! + CompletableFuture.runAsync(()->{ + System.out.println("rxd 30分钟定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.RXD.getMhs() + "30m", nowStr); + if(newCount == 0){ + List newList = poolMapper.getHourMinerDataList(Pools.RXD.getMhs()+"_realv2",nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.RXD.getFac()); + } + }); + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.RXD.getMhs() + "30m", getToHourDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.RXD.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.RXD.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("rxd 延迟任务 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("rxd 延迟任务 user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.RXD.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.RXD.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if (newResult) { + System.out.println(DateUtils.dateTimeNow() + "rxd 30m数据 延迟存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "rxd 30m数据 延迟存入数据失败"); + + } + + } + } + }); + + } + } + + } + + @Scheduled(cron = "9 1,3,5 0 * * ?") + public void RXDDailyDataToDB(){ + String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); + Date now = DateUtils.parseDate( nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.RXD.getMhs() + "24h", nowStr); + + if(count == 0) { + List list = poolMapper.getDailyMinerDataList(Pools.RXD.getMhs()+"_realv2", nowStr); + if(list.size() > 0){ + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.RXD.getFac()); + } + }); + list = filterList(list,now); + //list.stream().forEach(e ->{ + // e.setDate(DateUtils.addDays(e.getDate(),-1)); + //}); + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.RXD.getMhs() + "24h", getToDailyDBListByMinerDataList(list)); + //todo 统计矿池数据写入矿池数据库 $coin_pool_24h + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.RXD.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.RXD.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + //写入数据到coin_users表中 + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.RXD.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.RXD.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if(result){ + System.out.println(DateUtils.dateTimeNow()+"rxd 每日存入数据成功"); + }else { + System.out.println(DateUtils.dateTimeNow()+"rxd 每日存入数据失败"); + + } + + }else { + CompletableFuture.runAsync(()->{ + System.out.println("rxd 每日矿池定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.RXD.getMhs() + "24h", nowStr); + if(newCount == 0){ + List newList = poolMapper.getDailyMinerDataList(Pools.RXD.getMhs()+"_realv2", nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.RXD.getFac()); + } + }); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.RXD.getMhs() + "24h", getToDailyDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.RXD.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.RXD.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.RXD.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.RXD.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + + }else { + PoolPower poolPower = new PoolPower(); + poolPower.setDate(now); + poolPower.setMhs(BigDecimal.ZERO); + poolPower.setMiners(0); + poolPower.setOnline(0); + poolPower.setOffline(0); + boolean poolResult = poolMapper.insertPoolPower(Pools.RXD.getPoolTable() + "_24h", poolPower); + System.out.println("rxd 延时任务未查到 构造0数据入库,结果:"+poolPower); + } + } + }); + } + } + } + + @Scheduled(cron = "59 0,1,30,31 * * * ?") + public void RXDUserPowerRatioDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + now.setHours(0); + now.setMinutes(0); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + BigDecimal poolMhs = poolMapper.getPoolTodayTotalPower("rxd", nowStr); + List userMhsList = poolMapper.getUserTodayTotalPower("rxd", nowStr); + HashMap map = new HashMap<>(); + userMhsList.stream().forEach(e ->{ + map.put(e.getUser(),e.getMhs().divide(poolMhs,8,BigDecimal.ROUND_HALF_UP)); + }); + + if(map.size() >0){ + redisService.deleteObject("RXDUserPowerRatio"); + redisService.setCacheMap("RXDUserPowerRatio",map); + } + + } + + //@Scheduled(cron = "30 2,7 0,12 * * ?") + @Scheduled(cron = "39 2,5 0/12 * * ?") + public void RXDLuckyDataToDB(){ + int time = 0; + LuckDto dto = new LuckDto(); + Date end = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + do { + //幸运值 + // N天应出块数量 = N天矿池平均算力/ N天全网平均算力 * 币种每日出块数 * N天 取同一时间点的矿池算力/全网算力 + // 币种每日出块数 = 720 数值来源: 每日分钟数(24*60) / 出一个块需要的时间(根据具体币种 nexa是2分钟) + // N天实际出块量 数据库查询N天实际出块量 + // N天幸运值 = N天实际出块量 / N天应出块量 * 100 结果即百分比数字% + /** 全网每日出块数 */ + BigDecimal rxdCount = PoolCalParamConfig.RXD.getCount(); + + //计算矿池每日实际应出块数 block中算力单位是H/s + int hRate = PoolUnits.RXD.gethRate(); + //先把矿池算力单位转换为H/s 再除以全网算力 得到比值 再乘以每日全网出块数 得到矿池每日应出块数 + List netPowerList = poolMapper.getDailyNetPower("rxd"); + if(netPowerList.size() < 1){ + continue; + } + + List poolPowerList = poolMapper.getDailyPoolPower("rxd"); + + //拿报块list 筛选实际出块数 + List list = poolMapper.getRXDBlockInfoList(); + + if (list.size() < 1){ + continue; + } + + //第一条报块数据的时间 + Date poolStart = list.get(list.size() - 1).getDate(); + Date start3d = DateUtils.addDays(end,-3); + Date start7d = DateUtils.addDays(end,-7); + Date start30d = DateUtils.addDays(end,-30); + Date start90d = DateUtils.addDays(end,-90); + //(矿池平均/全网平均) * (全网计算平均的时间/矿池计算平均的时间) * 每日全网出块数 = 矿池每日应出块数 + long pDay3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + //矿池单位转换 + BigDecimal pPow3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + BigDecimal nPow3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay3d == 0 || nDay3d == 0){ + continue; + } + //计算3天 每日出块量 + BigDecimal dayChunk3d = pPow3d + .multiply(BigDecimal.valueOf(nDay3d * nDay3d)) + .divide(nPow3d.multiply(BigDecimal.valueOf(pDay3d * pDay3d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(rxdCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal pPow7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal nPow7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay7d == 0 || nDay7d == 0){ + continue; + } + + //计算7d 每日应出块量 + BigDecimal dayChunk7d = pPow7d + .multiply(BigDecimal.valueOf(nDay7d * nDay7d)) + .divide(nPow7d.multiply(BigDecimal.valueOf(pDay7d * pDay7d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(rxdCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal pPow30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal nPow30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay30d == 0 || nDay30d == 0){ + continue; + } + + //计算30d 每日应出块量 + BigDecimal dayChunk30d = pPow30d + .multiply(BigDecimal.valueOf(nDay30d * nDay30d)) + .divide(nPow30d.multiply(BigDecimal.valueOf(pDay30d * pDay30d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(rxdCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal pPow90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal nPow90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay90d == 0 || nDay90d == 0){ + continue; + } + + //计算90d 每日应出块量 + BigDecimal dayChunk90d = pPow90d + .multiply(BigDecimal.valueOf(nDay90d * nDay90d)) + .divide(nPow90d.multiply(BigDecimal.valueOf(pDay90d * pDay90d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(rxdCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + //3天时间出块数 + long count3d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start3d) || e.getDate().equals(start3d))) + .count(); + BigDecimal chunk3d = BigDecimal.valueOf(count3d); + //7天时间出块数 + long count7d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start7d) || e.getDate().equals(start7d))) + .count(); + BigDecimal chunk7d = BigDecimal.valueOf(count7d); + //30天时间出块数 + long count30d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start30d) || e.getDate().equals(start30d))) + .count(); + BigDecimal chunk30d = BigDecimal.valueOf(count30d); + //90天时间出块数 + long count90d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start90d) || e.getDate().equals(start90d))) + .count(); + BigDecimal chunk90d = BigDecimal.valueOf(count90d); + + if(poolStart.after(start3d)){ + System.out.println("不足3天"); + long needTime = poolStart.getTime()-start3d.getTime(); + chunk3d = chunk3d + .add(dayChunk3d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("3天出块量"+chunk3d); + dto.setLuck3d(chunk3d.divide(dayChunk3d.multiply(BigDecimal.valueOf(3)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start7d)){ + System.out.println("不足7天"); + long needTime = poolStart.getTime()-start7d.getTime(); + chunk7d = chunk7d + .add(dayChunk7d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("7天出块量"+chunk7d); + dto.setLuck7d(chunk7d.divide(dayChunk7d.multiply(BigDecimal.valueOf(7)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + + if(poolStart.after(start30d)){ + System.out.println("不足30天"); + long needTime = poolStart.getTime()-start30d.getTime(); + chunk30d = chunk30d + .add(dayChunk30d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("30天出块量"+chunk30d); + dto.setLuck30d(chunk30d.divide(dayChunk30d.multiply(BigDecimal.valueOf(30)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start90d)){ + System.out.println("不足90天"); + long needTime = poolStart.getTime()-start90d.getTime(); + chunk90d = chunk90d + .add(dayChunk90d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("90天出块量"+chunk90d); + dto.setLuck90d(chunk90d.divide(dayChunk90d.multiply(BigDecimal.valueOf(90)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + break; + }while (time < 4); + + redisService.deleteObject("RXDLuck"); + redisService.setCacheObject("RXDLuck",dto); + } + + @Scheduled(cron = "21 1,3,13,31,33,43 * * * ?") + public void ENX30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("enx 30分钟定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.ENX.getMhs() + "30m", nowStr); + + if(count == 0) { + List list = poolMapper.getHourMinerDataList(Pools.ENX.getMhs()+"_realv2",nowStr); + if(list.size() > 0){ + + //list.stream().forEach(e -> { + // if(StringUtils.isNotNull(e.getMhs())){ + // e.setMhs(e.getMhs() * Pools.ENX.getFac()); + // } + //}); + list = filterList(list,now); + + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.ENX.getMhs() + "30m", getToHourDBListByMinerDataList(list)); + + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.ENX.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.ENX.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("rxd 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("rxd user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ENX.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ENX.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + Map> userMap = list.stream().collect(Collectors.groupingBy(MinerDataDto::getMiner)); + if (result) { + System.out.println(DateUtils.dateTimeNow() + "enx 小时数据存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "enx 小时数据存入数据失败"); + + } + + } + else{ + //重来一次! + CompletableFuture.runAsync(()->{ + System.out.println("enx 30分钟定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.ENX.getMhs() + "30m", nowStr); + if(newCount == 0){ + List newList = poolMapper.getHourMinerDataList(Pools.ENX.getMhs()+"_realv2",nowStr); + if(newList.size() > 0){ + + //newList.stream().forEach(e -> { + // if(StringUtils.isNotNull(e.getMhs())){ + // e.setMhs(e.getMhs() * Pools.ENX.getFac()); + // } + //}); + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.ENX.getMhs() + "30m", getToHourDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.ENX.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.ENX.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("enx 延迟任务 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("enx 延迟任务 user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ENX.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ENX.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if (newResult) { + System.out.println(DateUtils.dateTimeNow() + "enx 30m数据 延迟存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "enx 30m数据 延迟存入数据失败"); + + } + + } + } + }); + + } + } + + } + + @Scheduled(cron = "10 1,3,5 0 * * ?") + public void ENXDailyDataToDB(){ + String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); + Date now = DateUtils.parseDate( nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.ENX.getMhs() + "24h", nowStr); + + if(count == 0) { + List list = poolMapper.getDailyMinerDataList(Pools.ENX.getMhs()+"_realv2", nowStr); + if(list.size() > 0){ + //list.stream().forEach(e -> { + // if(StringUtils.isNotNull(e.getMhs())){ + // e.setMhs(e.getMhs() * Pools.ENX.getFac()); + // } + //}); + list = filterList(list,now); + //list.stream().forEach(e ->{ + // e.setDate(DateUtils.addDays(e.getDate(),-1)); + //}); + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.ENX.getMhs() + "24h", getToDailyDBListByMinerDataList(list)); + //todo 统计矿池数据写入矿池数据库 $coin_pool_24h + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.ENX.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.ENX.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + //写入数据到coin_users表中 + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ENX.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ENX.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if(result){ + System.out.println(DateUtils.dateTimeNow()+"enx 每日存入数据成功"); + }else { + System.out.println(DateUtils.dateTimeNow()+"enx 每日存入数据失败"); + + } + + }else { + CompletableFuture.runAsync(()->{ + System.out.println("enx 每日矿池定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.ENX.getMhs() + "24h", nowStr); + if(newCount == 0){ + List newList = poolMapper.getDailyMinerDataList(Pools.ENX.getMhs()+"_realv2", nowStr); + if(newList.size() > 0){ + + //newList.stream().forEach(e -> { + // if(StringUtils.isNotNull(e.getMhs())){ + // e.setMhs(e.getMhs() * Pools.ENX.getFac()); + // } + //}); + + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.ENX.getMhs() + "24h", getToDailyDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.ENX.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.ENX.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ENX.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ENX.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + + }else { + PoolPower poolPower = new PoolPower(); + poolPower.setDate(now); + poolPower.setMhs(BigDecimal.ZERO); + poolPower.setMiners(0); + poolPower.setOnline(0); + poolPower.setOffline(0); + boolean poolResult = poolMapper.insertPoolPower(Pools.ENX.getPoolTable() + "_24h", poolPower); + System.out.println("enx 延时任务未查到 构造0数据入库,结果:"+poolPower); + } + } + }); + } + } + } + + @Scheduled(cron = "53 0,1,30,31 * * * ?") + public void ENXUserPowerRatioDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + now.setHours(0); + now.setMinutes(0); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + BigDecimal poolMhs = poolMapper.getPoolTodayTotalPower("enx", nowStr); + List userMhsList = poolMapper.getUserTodayTotalPower("enx", nowStr); + HashMap map = new HashMap<>(); + userMhsList.stream().forEach(e ->{ + map.put(e.getUser(),e.getMhs().divide(poolMhs,8,BigDecimal.ROUND_HALF_UP)); + }); + + if(map.size() >0){ + redisService.deleteObject("ENXUserPowerRatio"); + redisService.setCacheMap("ENXUserPowerRatio",map); + } + + } + + //@Scheduled(cron = "30 2,7 0,12 * * ?") + @Scheduled(cron = "41 2,5 0/12 * * ?") + public void ENXLuckyDataToDB(){ + int time = 0; + LuckDto dto = new LuckDto(); + Date end = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + do { + //幸运值 + // N天应出块数量 = N天矿池平均算力/ N天全网平均算力 * 币种每日出块数 * N天 取同一时间点的矿池算力/全网算力 + // 币种每日出块数 = 720 数值来源: 每日分钟数(24*60) / 出一个块需要的时间(根据具体币种 nexa是2分钟) + // N天实际出块量 数据库查询N天实际出块量 + // N天幸运值 = N天实际出块量 / N天应出块量 * 100 结果即百分比数字% + /** 全网每日出块数 */ + BigDecimal rxdCount = PoolCalParamConfig.ENX.getCount(); + + //计算矿池每日实际应出块数 block中算力单位是H/s + int hRate = PoolUnits.ENX.gethRate(); + //先把矿池算力单位转换为H/s 再除以全网算力 得到比值 再乘以每日全网出块数 得到矿池每日应出块数 + List netPowerList = poolMapper.getDailyNetPower("enx"); + if(netPowerList.size() < 1){ + continue; + } + + List poolPowerList = poolMapper.getDailyPoolPower("enx"); + + //拿报块list 筛选实际出块数 + List list = poolMapper.getENXBlockInfoList(); + + if (list.size() < 1){ + continue; + } + + //第一条报块数据的时间 + Date poolStart = list.get(list.size() - 1).getDate(); + Date start3d = DateUtils.addDays(end,-3); + Date start7d = DateUtils.addDays(end,-7); + Date start30d = DateUtils.addDays(end,-30); + Date start90d = DateUtils.addDays(end,-90); + //(矿池平均/全网平均) * (全网计算平均的时间/矿池计算平均的时间) * 每日全网出块数 = 矿池每日应出块数 + long pDay3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + //矿池单位转换 + BigDecimal pPow3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + BigDecimal nPow3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay3d == 0 || nDay3d == 0){ + continue; + } + //计算3天 每日出块量 + BigDecimal dayChunk3d = pPow3d + .multiply(BigDecimal.valueOf(nDay3d * nDay3d)) + .divide(nPow3d.multiply(BigDecimal.valueOf(pDay3d * pDay3d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(rxdCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal pPow7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal nPow7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay7d == 0 || nDay7d == 0){ + continue; + } + + //计算7d 每日应出块量 + BigDecimal dayChunk7d = pPow7d + .multiply(BigDecimal.valueOf(nDay7d * nDay7d)) + .divide(nPow7d.multiply(BigDecimal.valueOf(pDay7d * pDay7d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(rxdCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal pPow30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal nPow30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay30d == 0 || nDay30d == 0){ + continue; + } + + //计算30d 每日应出块量 + BigDecimal dayChunk30d = pPow30d + .multiply(BigDecimal.valueOf(nDay30d * nDay30d)) + .divide(nPow30d.multiply(BigDecimal.valueOf(pDay30d * pDay30d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(rxdCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal pPow90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal nPow90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay90d == 0 || nDay90d == 0){ + continue; + } + + //计算90d 每日应出块量 + BigDecimal dayChunk90d = pPow90d + .multiply(BigDecimal.valueOf(nDay90d * nDay90d)) + .divide(nPow90d.multiply(BigDecimal.valueOf(pDay90d * pDay90d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(rxdCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + //3天时间出块数 + long count3d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start3d) || e.getDate().equals(start3d))) + .count(); + BigDecimal chunk3d = BigDecimal.valueOf(count3d); + //7天时间出块数 + long count7d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start7d) || e.getDate().equals(start7d))) + .count(); + BigDecimal chunk7d = BigDecimal.valueOf(count7d); + //30天时间出块数 + long count30d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start30d) || e.getDate().equals(start30d))) + .count(); + BigDecimal chunk30d = BigDecimal.valueOf(count30d); + //90天时间出块数 + long count90d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start90d) || e.getDate().equals(start90d))) + .count(); + BigDecimal chunk90d = BigDecimal.valueOf(count90d); + + if(poolStart.after(start3d)){ + System.out.println("不足3天"); + long needTime = poolStart.getTime()-start3d.getTime(); + chunk3d = chunk3d + .add(dayChunk3d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("3天出块量"+chunk3d); + dto.setLuck3d(chunk3d.divide(dayChunk3d.multiply(BigDecimal.valueOf(3)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start7d)){ + System.out.println("不足7天"); + long needTime = poolStart.getTime()-start7d.getTime(); + chunk7d = chunk7d + .add(dayChunk7d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("7天出块量"+chunk7d); + dto.setLuck7d(chunk7d.divide(dayChunk7d.multiply(BigDecimal.valueOf(7)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + + if(poolStart.after(start30d)){ + System.out.println("不足30天"); + long needTime = poolStart.getTime()-start30d.getTime(); + chunk30d = chunk30d + .add(dayChunk30d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("30天出块量"+chunk30d); + dto.setLuck30d(chunk30d.divide(dayChunk30d.multiply(BigDecimal.valueOf(30)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start90d)){ + System.out.println("不足90天"); + long needTime = poolStart.getTime()-start90d.getTime(); + chunk90d = chunk90d + .add(dayChunk90d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("90天出块量"+chunk90d); + dto.setLuck90d(chunk90d.divide(dayChunk90d.multiply(BigDecimal.valueOf(90)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + break; + }while (time < 4); + + redisService.deleteObject("ENXLuck"); + redisService.setCacheObject("ENXLuck",dto); + } + + @Scheduled(cron = "23 1,3,13,31,33,43 * * * ?") + public void ALPH30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("alph 30分钟定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.ALPH.getMhs() + "30m", nowStr); + + if(count == 0) { + List list = poolMapper.getHourMinerDataList(Pools.ALPH.getMhs()+"_realv2",nowStr); + if(list.size() > 0){ + List offlineList = new ArrayList<>(); + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.ALPH.getFac()); + } + if("offline".equals(e.getState())){ + offlineList.add(e); + } + }); + list = filterList(list,now); + + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.ALPH.getMhs() + "30m", getToHourDBListByMinerDataList(list)); + + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.ALPH.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.ALPH.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ALPH.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ALPH.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + if (result) { + System.out.println(DateUtils.dateTimeNow() + "alph小时数据存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "alph小时数据存入数据失败"); + } + } + else{ + //重来一次! + CompletableFuture.runAsync(()->{ + System.out.println("alph 30分钟定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.ALPH.getMhs() + "30m", nowStr); + if(newCount == 0){ + List newList = poolMapper.getHourMinerDataList(Pools.ALPH.getMhs()+"_realv2",nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.ALPH.getFac()); + } + }); + newList = filterList(newList,now); + + boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.ALPH.getMhs() + "30m", getToHourDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.ALPH.getPoolTable() + "_30m", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.ALPH.getPoolTable() + "_30m", poolPower); + if (time > 5) { + break; + } + time ++; + } + + System.out.println("延迟任务 构造写入user算力表数据"); + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + System.out.println("延迟任务 user算力表入库数据条数:"+userList.size()); + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ALPH.getCoin() + "_users_30m", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ALPH.getCoin() + "_users_30m", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if (newResult) { + System.out.println(DateUtils.dateTimeNow() + "alph30m数据 延迟存入数据成功"); + } else { + System.out.println(DateUtils.dateTimeNow() + "alph30m数据 延迟存入数据失败"); + + } + + } + } + }); + + } + } + + } + + @Scheduled(cron = "12 1,3,5 0 * * ?") + public void ALPHDailyDataToDB(){ + String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); + Date now = DateUtils.parseDate( nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.ALPH.getMhs() + "24h", nowStr); + + if(count == 0) { + List list = poolMapper.getDailyMinerDataList(Pools.ALPH.getMhs()+"_realv2", nowStr); + if(list.size() > 0){ + list.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.ALPH.getFac()); + } + }); + list = filterList(list,now); + boolean result = poolMapper.batchInsertMhsDataToDB(Pools.ALPH.getMhs() + "24h", getToDailyDBListByMinerDataList(list)); + //todo 统计矿池数据写入矿池数据库 $coin_pool_24h + Map> map = list.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult = poolMapper.insertPoolPower(Pools.ALPH.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult){ + poolResult = poolMapper.insertPoolPower(Pools.ALPH.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + //写入数据到coin_users表中 + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ALPH.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ALPH.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + if(result){ + System.out.println(DateUtils.dateTimeNow()+"alph每日存入数据成功"); + }else { + System.out.println(DateUtils.dateTimeNow()+"alph每日存入数据失败"); + + } + + }else { + CompletableFuture.runAsync(()->{ + System.out.println("alph 每日矿池定时任务执行失败 延时一分钟时间重新查询"); + System.out.println("延迟后使用的查询时间:"+now); + System.out.println("延迟后 格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int newCount = poolMapper.getLastDataTime(Pools.ALPH.getMhs() + "24h", nowStr); + if(newCount == 0){ + List newList = poolMapper.getDailyMinerDataList(Pools.ALPH.getMhs()+"_realv2", nowStr); + if(newList.size() > 0){ + + newList.stream().forEach(e -> { + if(StringUtils.isNotNull(e.getMhs())){ + e.setMhs(e.getMhs() * Pools.ALPH.getFac()); + } + }); + + newList = filterList(newList,now); + + //boolean newResult = poolMapper.batchInsertMhsDataToDB(Pools.ALPH.getMhs() + "24h", getToDailyDBListByMinerDataList(newList)); + + Map> map2 = newList.stream().collect(Collectors.groupingBy(MinerDataDto::getDate)); + map2.forEach((date,dList) -> { + PoolPower poolPower = new PoolPower(); + //poolPower.setDate(date); + poolPower.setDateStr(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + Map stateCount = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getState, Collectors.counting())); + poolPower.setMhs(dList.stream().map(e -> BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().stripTrailingZeros()); + poolPower.setMiners(dList.size()); + poolPower.setOnline(Convert.toInt(stateCount.get("online"),0)); + poolPower.setOffline(Convert.toInt(stateCount.get("offline"),0)); + + boolean poolResult2 = poolMapper.insertPoolPower(Pools.ALPH.getPoolTable() + "_24h", poolPower); + int time =0; + while (!poolResult2){ + poolResult2 = poolMapper.insertPoolPower(Pools.ALPH.getPoolTable() + "_24h", poolPower); + if (time > 5) { + break; + } + time ++; + } + + Map> userMap = dList.stream().collect(Collectors.groupingBy(MinerDataDto::getUser)); + List userList = new ArrayList<>(); + userMap.forEach((user,uList)->{ + MinerDataInsertDBDto uDto = new MinerDataInsertDBDto(); + uDto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,date)); + uDto.setUser(user); + uDto.setMhs(uList.stream().map(e->BigDecimal.valueOf(e.getMhs())).reduce(BigDecimal::add).get().doubleValue()); + userList.add(uDto); + } ); + + boolean userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ALPH.getCoin() + "_users_24h", userList); + int uTime =0; + while (!userResult){ + userResult = poolMapper.batchInsertUserMhsDateToDB(Pools.ALPH.getCoin() + "_users_24h", userList); + if (uTime > 5) { + break; + } + uTime ++; + } + }); + + + }else { + PoolPower poolPower = new PoolPower(); + poolPower.setDate(now); + poolPower.setMhs(BigDecimal.ZERO); + poolPower.setMiners(0); + poolPower.setOnline(0); + poolPower.setOffline(0); + //boolean poolResult = poolMapper.insertPoolPower(Pools.ALPH.getPoolTable() + "_24h", poolPower); + System.out.println("延时任务未查到 构造0数据入库,结果:"+poolPower); + } + } + }); + } + } + } + + @Scheduled(cron = "52 0,1,30,31 * * * ?") + public void ALPHUserPowerRatioDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + now.setHours(0); + now.setMinutes(0); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + BigDecimal poolMhs = poolMapper.getPoolTodayTotalPower("alph", nowStr); + List userMhsList = poolMapper.getUserTodayTotalPower("alph", nowStr); + HashMap map = new HashMap<>(); + userMhsList.stream().forEach(e ->{ + map.put(e.getUser(),e.getMhs().divide(poolMhs,8,BigDecimal.ROUND_HALF_UP)); + }); + + if(map.size() >0){ + redisService.deleteObject("ALPHUserPowerRatio"); + redisService.setCacheMap("ALPHUserPowerRatio",map); + } + + } + + //@Scheduled(cron = "30 2,7 0,12 * * ?") + @Scheduled(cron = "42 2,5 0/12 * * ?") + public void ALPHLuckyDataToDB(){ + int time = 0; + LuckDto dto = new LuckDto(); + Date end = DateUtils.parseDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD)); + do { + //幸运值 + // N天应出块数量 = N天矿池平均算力/ N天全网平均算力 * 币种每日出块数 * N天 取同一时间点的矿池算力/全网算力 + // 币种每日出块数 = 720 数值来源: 每日分钟数(24*60) / 出一个块需要的时间(根据具体币种 nexa是2分钟) + // N天实际出块量 数据库查询N天实际出块量 + // N天幸运值 = N天实际出块量 / N天应出块量 * 100 结果即百分比数字% + /** 全网每日出块数 */ + BigDecimal nexaCount = PoolCalParamConfig.ALPH.getCount(); + + //计算矿池每日实际应出块数 block中算力单位是H/s + int hRate = PoolUnits.ALPH.gethRate(); + //先把矿池算力单位转换为H/s 再除以全网算力 得到比值 再乘以每日全网出块数 得到矿池每日应出块数 + List netPowerList = poolMapper.getDailyNetPower("alph"); + if(netPowerList.size() < 1){ + continue; + } + + List poolPowerList = poolMapper.getDailyPoolPower("alph"); + + //拿报块list 筛选实际出块数 + List list = poolMapper.getNEXABlockInfoList(); + + if (list.size() < 1){ + continue; + } + + //第一条报块数据的时间 + Date poolStart = list.get(list.size() - 1).getDate(); + Date start3d = DateUtils.addDays(end,-3); + Date start7d = DateUtils.addDays(end,-7); + Date start30d = DateUtils.addDays(end,-30); + Date start90d = DateUtils.addDays(end,-90); + //(矿池平均/全网平均) * (全网计算平均的时间/矿池计算平均的时间) * 每日全网出块数 = 矿池每日应出块数 + long pDay3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + //矿池单位转换 + BigDecimal pPow3d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))).count(); + + BigDecimal nPow3d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start3d) || e.getPDate().equals(start3d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay3d == 0 || nDay3d == 0){ + continue; + } + //计算3天 每日出块量 + BigDecimal dayChunk3d = pPow3d + .multiply(BigDecimal.valueOf(nDay3d * nDay3d)) + .divide(nPow3d.multiply(BigDecimal.valueOf(pDay3d * pDay3d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal pPow7d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))).count(); + + BigDecimal nPow7d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start7d) || e.getPDate().equals(start7d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay7d == 0 || nDay7d == 0){ + continue; + } + + //计算7d 每日应出块量 + BigDecimal dayChunk7d = pPow7d + .multiply(BigDecimal.valueOf(nDay7d * nDay7d)) + .divide(nPow7d.multiply(BigDecimal.valueOf(pDay7d * pDay7d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal pPow30d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))).count(); + + BigDecimal nPow30d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start30d) || e.getPDate().equals(start30d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay30d == 0 || nDay30d == 0){ + continue; + } + + //计算30d 每日应出块量 + BigDecimal dayChunk30d = pPow30d + .multiply(BigDecimal.valueOf(nDay30d * nDay30d)) + .divide(nPow30d.multiply(BigDecimal.valueOf(pDay30d * pDay30d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + long pDay90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal pPow90d = poolPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add) + .multiply(BigDecimal.valueOf(hRate)); + + long nDay90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))).count(); + + BigDecimal nPow90d = netPowerList.stream() + .filter(e -> (e.getPDate().before(end))) + .filter(e -> (e.getPDate().after(start90d) || e.getPDate().equals(start90d))) + .map(e -> e.getValue()).reduce(BigDecimal.ZERO, BigDecimal::add); + + if(pDay90d == 0 || nDay90d == 0){ + continue; + } + + //计算90d 每日应出块量 + BigDecimal dayChunk90d = pPow90d + .multiply(BigDecimal.valueOf(nDay90d * nDay90d)) + .divide(nPow90d.multiply(BigDecimal.valueOf(pDay90d * pDay90d)),8,BigDecimal.ROUND_HALF_UP) + .multiply(nexaCount) + .setScale(8, BigDecimal.ROUND_HALF_UP); + + //3天时间出块数 + long count3d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start3d) || e.getDate().equals(start3d))) + .count(); + BigDecimal chunk3d = BigDecimal.valueOf(count3d); + //7天时间出块数 + long count7d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start7d) || e.getDate().equals(start7d))) + .count(); + BigDecimal chunk7d = BigDecimal.valueOf(count7d); + //30天时间出块数 + long count30d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start30d) || e.getDate().equals(start30d))) + .count(); + BigDecimal chunk30d = BigDecimal.valueOf(count30d); + //90天时间出块数 + long count90d = list.stream() + .filter(e -> (e.getDate().before(end))) + .filter(e -> (e.getDate().after(start90d) || e.getDate().equals(start90d))) + .count(); + BigDecimal chunk90d = BigDecimal.valueOf(count90d); + + if(poolStart.after(start3d)){ + System.out.println("不足3天"); + long needTime = poolStart.getTime()-start3d.getTime(); + chunk3d = chunk3d + .add(dayChunk3d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("3天出块量"+chunk3d); + dto.setLuck3d(chunk3d.divide(dayChunk3d.multiply(BigDecimal.valueOf(3)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start7d)){ + System.out.println("不足7天"); + long needTime = poolStart.getTime()-start7d.getTime(); + chunk7d = chunk7d + .add(dayChunk7d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("7天出块量"+chunk7d); + dto.setLuck7d(chunk7d.divide(dayChunk7d.multiply(BigDecimal.valueOf(7)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + + if(poolStart.after(start30d)){ + System.out.println("不足30天"); + long needTime = poolStart.getTime()-start30d.getTime(); + chunk30d = chunk30d + .add(dayChunk30d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("30天出块量"+chunk30d); + dto.setLuck30d(chunk30d.divide(dayChunk30d.multiply(BigDecimal.valueOf(30)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + if(poolStart.after(start90d)){ + System.out.println("不足90天"); + long needTime = poolStart.getTime()-start90d.getTime(); + chunk90d = chunk90d + .add(dayChunk90d.multiply(BigDecimal.valueOf(needTime).divide(BigDecimal.valueOf(86400000),8,BigDecimal.ROUND_HALF_UP))); + } + System.out.println("90天出块量"+chunk90d); + dto.setLuck90d(chunk90d.divide(dayChunk90d.multiply(BigDecimal.valueOf(90)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + break; + }while (time < 4); + + redisService.deleteObject("ALPHLuck"); + redisService.setCacheObject("ALPHLuck",dto); + } + + //@Scheduled(cron = "5 2,4,6 0/1 * * ?") + //public void NEXAHourUserDataToDB(){ + // String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:00:00"); + // Date now = DateUtils.parseDate( nowStr); + // + // System.out.println("执行时间:"+now); + // + // //检查二级表是否有当前时间数据 + // int count = poolMapper.getLastDataTime(Pools.NEXA.getCoin() + "_users" +"_1h", nowStr); + // + // if(count == 0) { + // List list = poolMapper.getUserHourMinerDataList(Pools.NEXA.getCoin() + "_users" +"_stats"); + // + // if(list.size() > 0){ + // //处理list 1、根据时间筛选 2、根据状态筛选 3、数据时间格式化' + // list = filterList(list,now); + // + // boolean result = poolMapper.batchInsertUserMhsDataToDB(Pools.NEXA.getCoin() + "_users" + "_1h", getToHourDBListByMinerDataList(list)); + // if (result) { + // System.out.println(DateUtils.dateTimeNow() + "nexa小时数据存入数据成功"); + // } else { + // System.out.println(DateUtils.dateTimeNow() + "nexa小时数据存入数据失败"); + // + // } + // + // } + // } + // + //} + // + //@Scheduled(cron = "0 2,3,4 0 * * ?") + //public void NEXADailyUserDataToDB(){ + // String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); + // Date now = DateUtils.parseDate( nowStr); + // //检查二级表是否有当前时间数据 + // int count = poolMapper.getLastDataTime(Pools.NEXA.getCoin() + "_users" + "_1d", nowStr); + // + // if(count == 0) { + // List list = poolMapper.getUserDailyMinerDataList(Pools.NEXA.getCoin() + "_users" + "_stats"); + // if(list.size() > 0){ + // + // list = filterList(list,now); + // + // list.stream().forEach(e ->{ + // e.setDate(DateUtils.addDays(e.getDate(),-1)); + // }); + // + // boolean result = poolMapper.batchInsertUserMhsDataToDB(Pools.NEXA.getCoin() + "_users" + "_1d", getToDailyDBListByMinerDataList(list)); + // if(result){ + // System.out.println(DateUtils.dateTimeNow()+"nexa用户每日统计存入数据成功"); + // }else { + // System.out.println(DateUtils.dateTimeNow()+"nexa用户每日统计存入数据失败"); + // + // } + // + // } + // } + //} + + /** + * 格式化list中对象的日期 + * @param list + * @return + */ + public static List getToHourDBListByMinerDataList(List list){ + List dbList = list.stream().map(e -> { + MinerDataInsertDBDto dto = new MinerDataInsertDBDto(); + BeanUtils.copyProperties(e, dto); + dto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,e.getDate())); + if(StringUtils.isNotNull(e.getLastSubmit())){ + dto.setLastSubmit(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,e.getLastSubmit())); + } + return dto; + }).collect(Collectors.toList()); + + return dbList; + } + + /** + *格式化对象 日期属性为YYYY_MM_DD + * @param list + * @return + */ + public static List getToDailyDBListByMinerDataList(List list){ + List dbList = list.stream().map(e -> { + MinerDataInsertDBDto dto = new MinerDataInsertDBDto(); + BeanUtils.copyProperties(e, dto); + dto.setDate(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD,e.getDate())); + if(StringUtils.isNotNull(e.getLastSubmit())){ + dto.setLastSubmit(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,e.getLastSubmit())); + } + return dto; + }).collect(Collectors.toList()); + + return dbList; + } + + /** + * 过滤重复数据 + * @param list + * @param now + * @return + */ + public static List filterList(List list,Date now){ + + if(list.size()<1){ + return list; + } + + list = list.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getUser() + ":" + o.getMiner()))), ArrayList::new)); + + //list user:miner 重复拿date最新的数据 + list = list.stream(). + collect(Collectors.toMap( + e -> e.getUser()+":"+ e.getMiner(), + Function.identity(), + (p1,p2) -> p1.getDate().after(p2.getDate()) ? p1 : p2)).values().stream().collect(Collectors.toList()); + + list.stream().forEach(e ->{ + if (!DateUtils.isSameDate(e.getDate(),now)){ + System.out.println(e.getDate().getTime()+" before "+now.getTime()); + e.setAccepts(0); + e.setRejects(0); + e.setRejectsRatio(0); + e.setMhs(0); + e.setMhs24h(0); + } + + e.setDate(now); + + }); + + return list; + } + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/NeaxPriceTask.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/NeaxPriceTask.java new file mode 100644 index 0000000..93dbfb4 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/NeaxPriceTask.java @@ -0,0 +1,465 @@ +package com.m2pool.pool.task; + +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.pool.mapper.PoolMapper; +import com.m2pool.pool.utils.NodeRpc; +import com.m2pool.pool.vo.DateValueVo; +import org.apache.http.HttpHeaders; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @Description nexa 币价定时任务 每小时定时从CoinMarketCap拿 + * @Date 2024/6/14 14:03 + * @Author dy + */ +@Configuration +@EnableScheduling +public class NeaxPriceTask { + + @Autowired + private PoolMapper poolMapper; + + private String COINMARKETCAP_KEY="ba4fc555-e8b3-4bb2-aa91-bfa81044e8bf"; + //private String COINMARKETCAP_KEY="7b7a5fc2-795d-4900-a4db-152681382d52"; + + + private String ALL_COINS="nexa,monacoin,groestlcoin,digibyte,radiant,alephium"; + + @Autowired + private RedisService redisService; + + //@Scheduled(cron = "0 0/1 * * * ?") + @Scheduled(cron = "0 0,3,30,33 * * * ?") + public void NEXAPriceToRedis() + { + Map map = getResultFromNetByAllCoins(); + int count = 1; + while (StringUtils.isNull(map) + || StringUtils.isNull(map.get("nexa")) + || StringUtils.isNull(map.get("grs")) + || StringUtils.isNull(map.get("dgb")) + || StringUtils.isNull(map.get("rxd")) + || StringUtils.isNull(map.get("alph"))){ + if(count >= 10){ + break; + } + map = getResultFromNetByAllCoins(); + count++; + } + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + if(StringUtils.isNotNull(map)){ + + //处理nexa + BigDecimal nexa = map.get("nexa"); + if(StringUtils.isNull(nexa)){ + //重拿nexa + nexa = getResultFromNet("nexa"); + int nexaCount = 1; + while (StringUtils.isNull(nexa)){ + if(nexaCount >= 5){ + break; + } + nexa = getResultFromNet("nexa"); + nexaCount++; + } + }else { + redisService.setCacheObject("nexa_price",nexa); + //存入数据库 + DateValueVo nexaVo = new DateValueVo(); + nexaVo.setDate(now); + nexaVo.setValue(nexa); + poolMapper.insertPrice("nexa_price",date , nexaVo); + } + + //处理mona + BigDecimal mona = map.get("mona"); + if(StringUtils.isNull(mona)){ + //重拿mona + mona = getResultFromNet("monacoin"); + int monaCount = 1; + while (StringUtils.isNull(mona)){ + if(monaCount >= 5){ + break; + } + mona = getResultFromNet("monacoin"); + monaCount++; + } + }else { + redisService.setCacheObject("mona_price",mona); + //存入数据库 + DateValueVo monaVo = new DateValueVo(); + monaVo.setDate(now); + monaVo.setValue(mona); + poolMapper.insertPrice("mona_price",date , monaVo); + } + + //处理grs + BigDecimal grs = map.get("grs"); + if(StringUtils.isNull(grs)){ + //重拿grs + grs = getResultFromNet("groestlcoin"); + int grsCount = 1; + while (StringUtils.isNull(grs)){ + if(grsCount >= 5){ + break; + } + grs = getResultFromNet("groestlcoin"); + grsCount++; + } + }else { + redisService.setCacheObject("grs_price",grs); + //存入数据库 + DateValueVo grsVo = new DateValueVo(); + grsVo.setDate(now); + grsVo.setValue(grs); + poolMapper.insertPrice("grs_price",date , grsVo); + } + + //处理dgb + BigDecimal dgb = map.get("dgb"); + if(StringUtils.isNull(dgb)){ + //重拿dgb + dgb = getResultFromNet("digibyte"); + int dgbCount = 1; + while (StringUtils.isNull(dgb)){ + if(dgbCount >= 5){ + break; + } + dgb = getResultFromNet("digibyte"); + dgbCount++; + } + }else { + redisService.setCacheObject("dgbo_price",dgb); + redisService.setCacheObject("dgbq_price",dgb); + redisService.setCacheObject("dgbs_price",dgb); + //存入数据库 + DateValueVo dgbVo = new DateValueVo(); + dgbVo.setDate(now); + dgbVo.setValue(dgb); + poolMapper.insertPrice("dgbo_price",date , dgbVo); + poolMapper.insertPrice("dgbq_price",date , dgbVo); + poolMapper.insertPrice("dgbs_price",date , dgbVo); + } + + //处理rxd + BigDecimal rxd = map.get("rxd"); + if(StringUtils.isNull(rxd)){ + //重拿rxd + rxd = getResultFromNet("radiant"); + int rxdCount = 1; + while (StringUtils.isNull(rxd)){ + if(rxdCount >= 5){ + break; + } + rxd = getResultFromNet("radiant"); + rxdCount++; + } + }else { + redisService.setCacheObject("rxd_price",rxd); + //存入数据库 + DateValueVo rxdVo = new DateValueVo(); + rxdVo.setDate(now); + rxdVo.setValue(rxd); + poolMapper.insertPrice("rxd_price",date , rxdVo); + } + + //处理alph + BigDecimal alph = map.get("alph"); + if(StringUtils.isNull(alph)){ + //重拿alph + alph = getResultFromNet("alephium"); + int alphCount = 1; + while (StringUtils.isNull(alph)){ + if(alphCount >= 5){ + break; + } + alph = getResultFromNet("alephium"); + alphCount++; + } + }else { + redisService.setCacheObject("alph_price",alph); + //存入数据库 + DateValueVo alphVo = new DateValueVo(); + alphVo.setDate(now); + alphVo.setValue(alph); + poolMapper.insertPrice("alph_price",date , alphVo); + } + } + } + + //@Scheduled(cron = "1 0,30 * * * ?") + //public void GRSPriceToRedis(){ + // BigDecimal price = getResultFromNet("groestlcoin"); + // int count = 1; + // while (StringUtils.isNull(price)){ + // if(count >= 10){ + // break; + // } + // price = getResultFromNet("groestlcoin"); + // count++; + // } + // if(StringUtils.isNotNull(price)){ + // redisService.setCacheObject("grs_price",price); + // //存入数据库 + // DateValueVo com.m2pool.chat.vo = new DateValueVo(); + // Date now = new Date(); + // int minute = (now.getMinutes() / 30) * 30; + // now.setMinutes( minute); + // now.setSeconds(0); + // com.m2pool.chat.vo.setDate(now); + // com.m2pool.chat.vo.setValue(price); + // String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + // poolMapper.insertPrice("grs_price",date , com.m2pool.chat.vo); + // } + //} + // + // + //@Scheduled(cron = "2 0,30 * * * ?") + //public void MONAPriceToRedis(){ + // BigDecimal price = getResultFromNet("monacoin"); + // int count = 1; + // while (StringUtils.isNull(price)){ + // if(count >= 10){ + // break; + // } + // price = getResultFromNet("monacoin"); + // count++; + // } + // if(StringUtils.isNotNull(price)){ + // redisService.setCacheObject("mona_price",price); + // //存入数据库 + // DateValueVo com.m2pool.chat.vo = new DateValueVo(); + // Date now = new Date(); + // int minute = (now.getMinutes() / 30) * 30; + // now.setMinutes( minute); + // now.setSeconds(0); + // com.m2pool.chat.vo.setDate(now); + // com.m2pool.chat.vo.setValue(price); + // String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + // poolMapper.insertPrice("mona_price",date , com.m2pool.chat.vo); + // } + //} + // + //@Scheduled(cron = "3 0,30 * * * ?") + //public void DGBPriceToRedis(){ + // BigDecimal price = getResultFromNet("digibyte"); + // int count = 1; + // while (StringUtils.isNull(price)){ + // if(count >= 10){ + // break; + // } + // price = getResultFromNet("digibyte"); + // count++; + // } + // if(StringUtils.isNotNull(price)){ + // redisService.setCacheObject("dgbq_price",price); + // redisService.setCacheObject("dgbo_price",price); + // redisService.setCacheObject("dgbs_price",price); + // + // //存入数据库 + // DateValueVo com.m2pool.chat.vo = new DateValueVo(); + // Date now = new Date(); + // int minute = (now.getMinutes() / 30) * 30; + // now.setMinutes( minute); + // now.setSeconds(0); + // com.m2pool.chat.vo.setDate(now); + // com.m2pool.chat.vo.setValue(price); + // String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + // poolMapper.insertPrice("dgbq_price",date , com.m2pool.chat.vo); + // poolMapper.insertPrice("dgbo_price",date , com.m2pool.chat.vo); + // poolMapper.insertPrice("dgbs_price",date , com.m2pool.chat.vo); + // } + //} + // + // + //@Scheduled(cron = "4 0,30 * * * ?") + //public void RXDPriceToRedis(){ + // BigDecimal price = getResultFromNet("radiant"); + // int count = 1; + // while (StringUtils.isNull(price)){ + // if(count >= 10){ + // break; + // } + // price = getResultFromNet("radiant"); + // count++; + // } + // if(StringUtils.isNotNull(price)){ + // redisService.setCacheObject("rxd_price",price); + // + // //存入数据库 + // DateValueVo com.m2pool.chat.vo = new DateValueVo(); + // Date now = new Date(); + // int minute = (now.getMinutes() / 30) * 30; + // now.setMinutes( minute); + // now.setSeconds(0); + // com.m2pool.chat.vo.setDate(now); + // com.m2pool.chat.vo.setValue(price); + // String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + // poolMapper.insertPrice("rxd_price",date ,com.m2pool.chat.vo); + // } + //} + + + public BigDecimal getResultFromNet(String coin){ + String uri = "https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest?slug="+coin; + + try { + String body = HttpRequest + .get(uri) + .header(HttpHeaders.ACCEPT,"application/json") + .header("X-CMC_PRO_API_KEY",COINMARKETCAP_KEY) + .timeout(3000) + .execute() + .body(); + + JSONObject jsonObject = JSON.parseObject(body); + if (StringUtils.isNull(jsonObject)){ + throw new RuntimeException(); + } + JSONObject data = jsonObject.getJSONObject("data"); + if (StringUtils.isNull(data)){ + throw new RuntimeException(); + } + + JSONObject object = null; + if("nexa".equals(coin.toLowerCase())){ + object = data.getJSONObject("23380"); + }else if("monacoin".equals(coin.toLowerCase())){ + object = data.getJSONObject("213"); + } + else if("groestlcoin".equals(coin.toLowerCase())){ + object = data.getJSONObject("258"); + }else if("digibyte".equals(coin.toLowerCase())){ + object = data.getJSONObject("109"); + } + else if("radiant".equals(coin.toLowerCase())){ + object = data.getJSONObject("22866"); + } + else if("alephium".equals(coin.toLowerCase())){ + object = data.getJSONObject("14878"); + } + + if (StringUtils.isNull(object)){ + throw new RuntimeException(); + } + JSONObject quote = object.getJSONObject("quote"); + if (StringUtils.isNull(quote)){ + throw new RuntimeException(); + } + JSONObject usd = quote.getJSONObject("USD"); + if (StringUtils.isNull(usd)){ + throw new RuntimeException(); + } + BigDecimal price = usd.getBigDecimal("price").setScale(8,BigDecimal.ROUND_HALF_UP); + if (StringUtils.isNull(price)){ + throw new RuntimeException(); + } + System.out.println(price); + return price; + } catch (Exception e) { + System.out.println("Error: cannont access content - " + e.toString()); + return null; + } + } + + + public Map getResultFromNetByAllCoins(){ + String uri = "https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest?slug="+ALL_COINS; + + Map map = new HashMap<>(); + map.put("nexa",null); + map.put("mona",null); + map.put("grs",null); + map.put("dgb",null); + map.put("rxd",null); + map.put("alph",null); + + + + try { + String body = HttpRequest + .get(uri) + .header(HttpHeaders.ACCEPT,"application/json") + .header("X-CMC_PRO_API_KEY",COINMARKETCAP_KEY) + .timeout(3000) + .execute() + .body(); + + JSONObject jsonObject = JSON.parseObject(body); + if (StringUtils.isNull(jsonObject)){ + return map; + } + JSONObject data = jsonObject.getJSONObject("data"); + if (StringUtils.isNull(data)){ + return map; + } + + //数字是通过coinmarketcap 查询的对应币种id 可以切换曲线图时间粒度浏览器后台查看coinmarketcap调用的id值 + JSONObject nexaObject = data.getJSONObject("23380"); + JSONObject monaObject = data.getJSONObject("213"); + JSONObject grsObject = data.getJSONObject("258"); + JSONObject dgbObject = data.getJSONObject("109"); + JSONObject rxdObject = data.getJSONObject("22866"); + JSONObject alphObject = data.getJSONObject("14878"); + + BigDecimal nexaPrice = getPriceByCoinIdJSONObject(nexaObject); + BigDecimal monaPrice = getPriceByCoinIdJSONObject(monaObject); + BigDecimal grsPrice = getPriceByCoinIdJSONObject(grsObject); + BigDecimal dgbPrice = getPriceByCoinIdJSONObject(dgbObject); + BigDecimal rxdPrice = getPriceByCoinIdJSONObject(rxdObject); + BigDecimal alphPrice = getPriceByCoinIdJSONObject(alphObject); + + map.put("nexa",nexaPrice); + map.put("mona",monaPrice); + map.put("grs",grsPrice); + map.put("dgb",dgbPrice); + map.put("rxd",rxdPrice); + map.put("alph",alphPrice); + + return map; + + } catch (Exception e) { + System.out.println("Error: cannont access content - " + e.toString()); + return map; + } + } + + public BigDecimal getPriceByCoinIdJSONObject(JSONObject coinIdjs){ + if (StringUtils.isNull(coinIdjs)){ + return null; + } + JSONObject quote = coinIdjs.getJSONObject("quote"); + if (StringUtils.isNull(quote)){ + return null; + } + JSONObject usd = quote.getJSONObject("USD"); + if (StringUtils.isNull(usd)){ + return null; + } + BigDecimal price = usd.getBigDecimal("price").setScale(8,BigDecimal.ROUND_HALF_UP); + if (StringUtils.isNull(price)){ + return null; + } + return price; + } + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/NodeTask.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/NodeTask.java new file mode 100644 index 0000000..52cdf74 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/NodeTask.java @@ -0,0 +1,484 @@ +package com.m2pool.pool.task; + +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.redis.service.RedisService; + +import com.m2pool.pool.entity.BlockInfo; +import com.m2pool.pool.mapper.PoolMapper; +import com.m2pool.pool.utils.NodeRpc; +import com.m2pool.pool.vo.DateValueVo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import java.util.Date; +import java.util.Map; + +/** + * @Description TODO + * @Date 2024/6/14 14:03 + * @Author dy + */ +@Configuration +@EnableScheduling +public class NodeTask { + + @Autowired + private PoolMapper poolMapper; + + @Autowired + private RedisService redisService; + + //@Scheduled(cron = "0 0/2 * * * ?") + @Scheduled(cron = "5 0,30 * * * ?") + public void NEXABlockInfoToRedisAndDB(){ + BlockInfo blockInfo = NodeRpc.getBlock("nexa"); + + int count = 1; + while (StringUtils.isNull(blockInfo)){ + if(count >= 10){ + break; + } + blockInfo = NodeRpc.getBlock("nexa"); + count++; + } + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + redisService.setCacheObject("nexa_block",blockInfo); + //存入数据库 + DateValueVo vo = new DateValueVo(); + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + vo.setDate(now); + String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + System.out.println("nexa全网算力数据入库时间:"+date); + vo.setValue(blockInfo.getPower()); + poolMapper.insertNetPower("nexa_net_power",date,vo); + } + } + } + + @Scheduled(cron = "0 0/1 * * * ?") + public void NEXABlockInfoToRedis(){ + BlockInfo blockInfo = NodeRpc.getBlock("nexa"); + + int count = 0; + while (StringUtils.isNull(blockInfo)){ + if(count >= 3){ + break; + } + blockInfo = NodeRpc.getBlock("nexa"); + count++; + } + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + + if (redisService.hasKey("nexa_block")){ + redisService.deleteObject("nexa_block"); + redisService.setCacheObject("nexa_block",blockInfo); + }else { + redisService.setCacheObject("nexa_block",blockInfo); + } + + } + } + } + + @Scheduled(cron = "10 0,30 * * * ?") + public void GRSBlockInfoToRedisAndDB(){ + BlockInfo blockInfo = NodeRpc.getBlock("grs"); + + int count = 1; + while (StringUtils.isNull(blockInfo)){ + if(count >= 10){ + break; + } + blockInfo = NodeRpc.getBlock("grs"); + count++; + } + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + redisService.setCacheObject("grs_block",blockInfo); + //存入数据库 + DateValueVo vo = new DateValueVo(); + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + vo.setDate(now); + String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + vo.setValue(blockInfo.getPower()); + poolMapper.insertNetPower("grs_net_power",date,vo); + } + } + } + + @Scheduled(cron = "2 0/1 * * * ?") + public void GRSBlockInfoToRedis(){ + BlockInfo blockInfo = NodeRpc.getBlock("grs"); + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + + if (redisService.hasKey("grs_block")){ + redisService.deleteObject("grs_block"); + redisService.setCacheObject("grs_block",blockInfo); + }else { + redisService.setCacheObject("grs_block",blockInfo); + } + + } + } + else { + System.out.println("未从节点获取到grs最新数据 :"+DateUtils.dateTimeNow()); + } + } + + @Scheduled(cron = "15 0,30 * * * ?") + public void MONABlockInfoToRedisAndDB(){ + BlockInfo blockInfo = NodeRpc.getBlock("mona"); + + int count = 1; + while (StringUtils.isNull(blockInfo)){ + if(count >= 10){ + break; + } + blockInfo = NodeRpc.getBlock("mona"); + count++; + } + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + redisService.setCacheObject("mona_block",blockInfo); + //存入数据库 + DateValueVo vo = new DateValueVo(); + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + vo.setDate(now); + String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + System.out.println("mona 全网算力数据入库时间:"+date); + vo.setValue(blockInfo.getPower()); + poolMapper.insertNetPower("mona_net_power",date,vo); + } + } + } + + @Scheduled(cron = "4 0/1 * * * ?") + public void MONABlockInfoToRedis(){ + BlockInfo blockInfo = NodeRpc.getBlock("mona"); + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + + if (redisService.hasKey("mona_block")){ + redisService.deleteObject("mona_block"); + redisService.setCacheObject("mona_block",blockInfo); + }else { + redisService.setCacheObject("mona_block",blockInfo); + } + + } + }else { + System.out.println("未从节点获取到mona最新数据 :"+DateUtils.dateTimeNow()); + } + + } + + @Scheduled(cron = "20 0,30 * * * ?") + public void DGBBlockInfoToRedisAndDB(){ + Map dgbMap = NodeRpc.getDGBBlock(); + int count = 1; + while (StringUtils.isNull(dgbMap)){ + if(count >= 10){ + break; + } + dgbMap = NodeRpc.getDGBBlock(); + count++; + } + if(StringUtils.isNotNull(dgbMap)){ + BlockInfo dgbo = dgbMap.getOrDefault("dgbo", null); + if(StringUtils.isNotNull(dgbo)){ + if(StringUtils.isNotNull(dgbo.getPower())){ + redisService.setCacheObject("dgbo_block",dgbo); + //存入数据库 + DateValueVo vo = new DateValueVo(); + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + vo.setDate(now); + String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + System.out.println("dgbo 全网算力数据入库时间:"+date); + vo.setValue(dgbo.getPower()); + poolMapper.insertNetPower("dgbo_net_power",date,vo); + }else { + System.out.println("未获取到dgbo的power"); + } + } + else { + System.out.println("未获取到dgbo"); + } + + BlockInfo dgbq = dgbMap.getOrDefault("dgbq", null); + if(StringUtils.isNotNull(dgbq)){ + if(StringUtils.isNotNull(dgbo.getPower())){ + redisService.setCacheObject("dgbq_block",dgbq); + //存入数据库 + DateValueVo vo = new DateValueVo(); + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + vo.setDate(now); + String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + System.out.println("dgbq 全网算力数据入库时间:"+date); + vo.setValue(dgbq.getPower()); + poolMapper.insertNetPower("dgbq_net_power",date,vo); + } + else { + System.out.println("未获取到dgbo的power"); + } + } + else { + System.out.println("未获取到dgbq"); + } + + BlockInfo dgbs = dgbMap.getOrDefault("dgbs", null); + if(StringUtils.isNotNull(dgbs)){ + if(StringUtils.isNotNull(dgbs.getPower())){ + redisService.setCacheObject("dgbs_block",dgbs); + //存入数据库 + DateValueVo vo = new DateValueVo(); + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + vo.setDate(now); + String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + System.out.println("dgbs 全网算力数据入库时间:"+date); + vo.setValue(dgbs.getPower()); + poolMapper.insertNetPower("dgbs_net_power",date,vo); + } + else { + System.out.println("未获取到dgbs的power"); + } + }else { + System.out.println("未获取到dgbs"); + } + + }else { + System.out.println("未从节点获取到dgpMap最新数据 :"+DateUtils.dateTimeNow()); + } + } + + @Scheduled(cron = "6 0/1 * * * ?") + public void DGBBlockInfoToRedis(){ + Map dgbMap = NodeRpc.getDGBBlock(); + if(StringUtils.isNotNull(dgbMap)){ + BlockInfo dgbo = dgbMap.getOrDefault("dgbo", null); + if(StringUtils.isNotNull(dgbo)){ + + if (redisService.hasKey("dgbo_block")){ + redisService.deleteObject("dgbo_block"); + redisService.setCacheObject("dgbo_block",dgbo); + }else { + redisService.setCacheObject("dgbo_block",dgbo); + } + + } + BlockInfo dgbq = dgbMap.getOrDefault("dgbq", null); + if(StringUtils.isNotNull(dgbq)){ + + if (redisService.hasKey("dgbq_block")){ + redisService.deleteObject("dgbq_block"); + redisService.setCacheObject("dgbq_block",dgbq); + }else { + redisService.setCacheObject("dgbq_block",dgbq); + } + + } + BlockInfo dgbs = dgbMap.getOrDefault("dgbs", null); + if(StringUtils.isNotNull(dgbs)){ + + if (redisService.hasKey("dgbs_block")){ + redisService.deleteObject("dgbs_block"); + redisService.setCacheObject("dgbs_block",dgbs); + }else { + redisService.setCacheObject("dgbs_block",dgbs); + } + + } + + }else { + System.out.println("未从节点获取到dgpMap最新数据 :"+DateUtils.dateTimeNow()); + } + + } + + + @Scheduled(cron = "25 0,30 * * * ?") + public void RXDBlockInfoToRedisAndDB(){ + BlockInfo blockInfo = NodeRpc.getBlock("rxd"); + + int count = 1; + while (StringUtils.isNull(blockInfo)){ + if(count >= 10){ + break; + } + blockInfo = NodeRpc.getBlock("rxd"); + count++; + } + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + redisService.setCacheObject("rxd_block",blockInfo); + //存入数据库 + DateValueVo vo = new DateValueVo(); + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + vo.setDate(now); + String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + System.out.println("rxd 全网算力数据入库时间:"+date); + vo.setValue(blockInfo.getPower()); + poolMapper.insertNetPower("rxd_net_power",date,vo); + } + } + } + + @Scheduled(cron = "8 0/1 * * * ?") + public void RXDBlockInfoToRedis(){ + BlockInfo blockInfo = NodeRpc.getBlock("rxd"); + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + + if (redisService.hasKey("rxd_block")){ + redisService.deleteObject("rxd_block"); + redisService.setCacheObject("rxd_block",blockInfo); + }else { + redisService.setCacheObject("rxd_block",blockInfo); + } + + } + }else { + System.out.println("未从节点获取到最新数据 :"+DateUtils.dateTimeNow()); + } + + } + + @Scheduled(cron = "30 0,30 * * * ?") + public void ALPHBlockInfoToRedisAndDB(){ + BlockInfo blockInfo = NodeRpc.getBlock("alph"); + //改成根据swagger api调用 用与原有节点调用相比新的路径、新的api + + int count = 1; + while (StringUtils.isNull(blockInfo)){ + if(count >= 10){ + break; + } + blockInfo = NodeRpc.getBlock("alph"); + count++; + } + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + redisService.setCacheObject("alph_block",blockInfo); + //存入数据库 + DateValueVo vo = new DateValueVo(); + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + vo.setDate(now); + String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + System.out.println("alph 全网算力数据入库时间:"+date); + vo.setValue(blockInfo.getPower()); + poolMapper.insertNetPower("alph_net_power",date,vo); + } + } + } + + @Scheduled(cron = "14 0/1 * * * ?") + public void ALPHBlockInfoToRedis(){ + BlockInfo blockInfo = NodeRpc.getBlock("alph"); + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + + if (redisService.hasKey("alph_block")){ + redisService.deleteObject("alph_block"); + redisService.setCacheObject("alph_block",blockInfo); + }else { + redisService.setCacheObject("alph_block",blockInfo); + } + + } + }else { + System.out.println("alph 未从节点获取到最新数据 :"+DateUtils.dateTimeNow()); + } + + } + + @Scheduled(cron = "35 0,30 * * * ?") + public void ENXBlockInfoToRedisAndDB(){ + BlockInfo blockInfo = NodeRpc.getBlock("enx"); + + int count = 1; + while (StringUtils.isNull(blockInfo)){ + if(count >= 10){ + break; + } + blockInfo = NodeRpc.getBlock("enx"); + count++; + } + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + redisService.setCacheObject("enx_block",blockInfo); + //存入数据库 + DateValueVo vo = new DateValueVo(); + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + vo.setDate(now); + String date = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + System.out.println("enx 全网算力数据入库时间:"+date); + vo.setValue(blockInfo.getPower()); + poolMapper.insertNetPower("enx_net_power",date,vo); + } + } + } + + @Scheduled(cron = "12 0/1 * * * ?") + public void ENXBlockInfoToRedis(){ + BlockInfo blockInfo = NodeRpc.getBlock("enx"); + + if(StringUtils.isNotNull(blockInfo)){ + if(StringUtils.isNotNull(blockInfo.getPower())){ + + if (redisService.hasKey("enx_block")){ + redisService.deleteObject("enx_block"); + redisService.setCacheObject("enx_block",blockInfo); + }else { + redisService.setCacheObject("enx_block",blockInfo); + } + + } + }else { + System.out.println("未从节点获取到最新数据 :"+DateUtils.dateTimeNow()); + } + + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/OffLineNoticeTask.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/OffLineNoticeTask.java new file mode 100644 index 0000000..a212795 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/OffLineNoticeTask.java @@ -0,0 +1,743 @@ +package com.m2pool.pool.task; + +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.pool.dto.*; +import com.m2pool.pool.enums.Pools; +import com.m2pool.pool.mapper.NoticeMapper; +import com.m2pool.pool.mapper.PoolMapper; +import com.m2pool.system.api.RemoteMailService; +import com.m2pool.system.api.entity.EmailEntity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Description 离线通知定时任务 + * @Date 2024/6/14 14:03 + * @Author dy + */ +@Configuration +@EnableScheduling +public class OffLineNoticeTask { + + @Autowired + private PoolMapper poolMapper; + + @Autowired + private NoticeMapper noticeMapper; + + @Autowired + private RemoteMailService mailService; + + + @Autowired + private RedisService redisService; + + + @Scheduled(cron = "20 15,45 * * * ?") + public void NEXA30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("nexa 预警定时任务执行时间:"+now); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.NEXA.getMhs() + "30m", nowStr); + System.out.println("count:"+count); + + if(count > 0) { + //查询离线矿机数和离线矿机 按挖矿账户分 + List list = poolMapper.getOfflineList(Pools.NEXA.getMhs() + "30m", nowStr); + + System.out.println("查询到离线矿机结果 "+list.size()+"条"); + if(StringUtils.isNotNull(list)){ + //list不为空才处理 + + //先获取通知列表 + List nmcList = noticeMapper.getNoticeEmailListByMinerAndCoin(list, Pools.NEXA.getCoin()); + System.out.println("查询到离线通知列表"+nmcList); + Map> userEmails = nmcList.stream(). + collect(Collectors.groupingBy( + NoticeMinerCoinListDto::getMiner, + Collectors.mapping(NoticeMinerCoinListDto::getEmail, Collectors.toList()) + )); + + + if(StringUtils.isNotNull(userEmails)){ + //获取上一次离线矿机数 可能为null 要做处理 如果没有redis记录 则上次离线数设置为0 若有redis但是key没有此挖矿账户也设置上次离线数为0 + Map map = redisService.getCacheMap("NEXA_USERS_OFFLINE"); + list.stream().forEach(e -> { + long lastOff = 0; + if(StringUtils.isNotNull(map)){ + lastOff =map.getOrDefault(e.getUser(),0L); + } + if(e.getOffline() > lastOff){ + //对比redis中该挖矿账户上一次离线矿机数 上一次不存在则设上一次离线数为0来进行比较 + //仅当本次离线数大于上次离线数时才通知 + //执行通知 + List emails = userEmails.getOrDefault(e.getUser(), null); + if(StringUtils.isNotNull(emails)){ + String text = "您的"+Pools.NEXA.getCoin()+"下挖矿账户: "+e.getUser()+"有矿机离线!离线矿机列表如下:\n" + + e.getMiners() + " \n"+ + "若您的矿机是因异常断开,请及时处理!"; + emails.stream().forEach(email ->{ + System.out.println("用户"+e.getUser()+"矿机离线通知到"+email); + EmailEntity entity = new EmailEntity(); + entity.setSubject("[M2Pool] 矿机离线提示"); + entity.setEmail(email); + entity.setText(text); + mailService.sendTextMail(entity); + }); + }else { + System.out.println("该用户未添加离线通知 :"+userEmails.toString()); + } + + }else { + //不满足通知条件 不通知 + System.out.println("挖矿账户"+e.getUser()+"本次无需通知,本次离线为"+e.getOffline()+",上次离线数"+ + map.getOrDefault(e.getUser(),0L)); + } + }); + }else { + System.out.println("未获取到nexa下相关的离线告警订阅信息"); + } + + } + + + }else{ + //最新数据未入库暂不做处理 + } + + } + + @Scheduled(cron = "11 16,46 * * * ?") + public void GRS30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("grs 30分钟定时任务执行时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.GRS.getMhs() + "30m", nowStr); + + System.out.println("二级表当前时间点数据条数:"+count); + + if(count > 0) { + //查询离线矿机数和离线矿机 按挖矿账户分 + List list = poolMapper.getOfflineList(Pools.GRS.getMhs() + "30m", nowStr); + + if(StringUtils.isNotNull(list)){ + //list不为空才处理 + + //先获取通知列表 + List nmcList = noticeMapper.getNoticeEmailListByMinerAndCoin(list, Pools.NEXA.getCoin()); + System.out.println("查询到离线通知列表"+nmcList); + Map> userEmails = nmcList.stream(). + collect(Collectors.groupingBy( + NoticeMinerCoinListDto::getMiner, + Collectors.mapping(NoticeMinerCoinListDto::getEmail, Collectors.toList()) + )); + + + if(StringUtils.isNotNull(userEmails)){ + //获取上一次离线矿机数 可能为null 要做处理 如果没有redis记录 则上次离线数设置为0 若有redis但是key没有此挖矿账户也设置上次离线数为0 + Map map = redisService.getCacheMap("NEXA_USERS_OFFLINE"); + list.stream().forEach(e -> { + long lastOff = 0; + if(StringUtils.isNotNull(map)){ + lastOff =map.getOrDefault(e.getUser(),0L); + } + if(e.getOffline() > lastOff){ + //对比redis中该挖矿账户上一次离线矿机数 上一次不存在则设上一次离线数为0来进行比较 + //仅当本次离线数大于上次离线数时才通知 + //执行通知 + List emails = userEmails.getOrDefault(e.getUser(), null); + if(StringUtils.isNotNull(emails)){ + String text = "您的"+Pools.NEXA.getName()+"下挖矿账户: "+e.getUser()+"有矿机离线!离线矿机列表如下:\n" + + e.getMiners() + " \n"+ + "若您的矿机是因异常断开,请及时处理!"; + emails.stream().forEach(email ->{ + System.out.println("用户"+e.getUser()+"矿机离线通知到"+email); + EmailEntity entity = new EmailEntity(); + entity.setSubject("[M2Pool] 矿机离线提示"); + entity.setEmail(email); + entity.setText(text); + mailService.sendTextMail(entity); + }); + }else { + System.out.println("该用户未添加离线通知 :"+userEmails.toString()); + } + + }else { + //不满足通知条件 不通知 + System.out.println("挖矿账户"+e.getUser()+"本次无需通知,本次离线为"+e.getOffline()+",上次离线数"+ + map.getOrDefault(e.getUser(),0L)); + } + }); + }else { + System.out.println("未获取到nexa下相关的离线告警订阅信息"); + } + + } + } + + } + + @Scheduled(cron = "12 17,47 * * * ?") + public void MONA30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("mona 离线告警定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.MONA.getMhs() + "30m", nowStr); + + if(count > 0) { + //查询离线矿机数和离线矿机 按挖矿账户分 + List list = poolMapper.getOfflineList(Pools.MONA.getMhs() + "30m", nowStr); + + if(StringUtils.isNotNull(list)){ + //list不为空才处理 + + //先获取通知列表 + List nmcList = noticeMapper.getNoticeEmailListByMinerAndCoin(list, Pools.MONA.getCoin()); + + Map> userEmails = nmcList.stream(). + collect(Collectors.groupingBy( + NoticeMinerCoinListDto::getMiner, + Collectors.mapping(NoticeMinerCoinListDto::getEmail, Collectors.toList()) + )); + + + if(StringUtils.isNotNull(userEmails)){ + //获取上一次离线矿机数 可能为null 要做处理 如果没有redis记录 则上次离线数设置为0 若有redis但是key没有此挖矿账户也设置上次离线数为0 + Map map = redisService.getCacheMap("MONA_USERS_OFFLINE"); + + list.stream().forEach(e -> { + long lastOff = 0; + if(StringUtils.isNotNull(map)){ + lastOff =map.getOrDefault(e.getUser(),0L); + } + if(e.getOffline() > lastOff){ + //对比redis中该挖矿账户上一次离线矿机数 上一次不存在则设上一次离线数为0来进行比较 + //仅当本次离线数大于上次离线数时才通知 + //执行通知 + List emails = userEmails.getOrDefault(e.getUser(), null); + if(StringUtils.isNotNull(emails)){ + String text = "您的"+Pools.MONA.getName()+"下挖矿账户: "+e.getUser()+"有矿机离线!离线矿机列表如下:\n" + + e.getMiners() + " \n"+ + "若您的矿机是因异常断开,请及时处理!"; + emails.stream().forEach(email ->{ + System.out.println("用户"+e.getUser()+"矿机离线通知到"+email); + EmailEntity entity = new EmailEntity(); + entity.setSubject("[M2Pool] 矿机离线提示"); + entity.setEmail(email); + entity.setText(text); + mailService.sendTextMail(entity); + }); + }else { + System.out.println("该用户未添加离线通知 :"+userEmails.toString()); + } + + }else { + //不满足通知条件 不通知 + System.out.println("挖矿账户"+e.getUser()+"本次无需通知,本次离线为"+e.getOffline()+",上次离线数"+ + map.getOrDefault(e.getUser(),0L)); + } + }); + }else { + System.out.println("未获取到MONA下相关的离线告警订阅信息"); + } + + } + } + + } + + @Scheduled(cron = "14 18,48 * * * ?") + public void DGBO30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("DGBO 离线告警定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.DGBO.getMhs() + "30m", nowStr); + + if(count > 0) { + //查询离线矿机数和离线矿机 按挖矿账户分 + List list = poolMapper.getOfflineList(Pools.DGBO.getMhs() + "30m", nowStr); + + if(StringUtils.isNotNull(list)){ + //list不为空才处理 + + //先获取通知列表 + List nmcList = noticeMapper.getNoticeEmailListByMinerAndCoin(list, Pools.DGBO.getCoin()); + + Map> userEmails = nmcList.stream(). + collect(Collectors.groupingBy( + NoticeMinerCoinListDto::getMiner, + Collectors.mapping(NoticeMinerCoinListDto::getEmail, Collectors.toList()) + )); + + + if(StringUtils.isNotNull(userEmails)){ + //获取上一次离线矿机数 可能为null 要做处理 如果没有redis记录 则上次离线数设置为0 若有redis但是key没有此挖矿账户也设置上次离线数为0 + Map map = redisService.getCacheMap("DGBO_USERS_OFFLINE"); + list.stream().forEach(e -> { + long lastOff = 0; + if(StringUtils.isNotNull(map)){ + lastOff =map.getOrDefault(e.getUser(),0L); + } + if(e.getOffline() > lastOff){ + //对比redis中该挖矿账户上一次离线矿机数 上一次不存在则设上一次离线数为0来进行比较 + //仅当本次离线数大于上次离线数时才通知 + //执行通知 + List emails = userEmails.getOrDefault(e.getUser(), null); + if(StringUtils.isNotNull(emails)){ + String text = "您的"+Pools.DGBO.getName()+"下挖矿账户: "+e.getUser()+"有矿机离线!离线矿机列表如下:\n" + + e.getMiners() + " \n"+ + "若您的矿机是因异常断开,请及时处理!"; + emails.stream().forEach(email ->{ + System.out.println("用户"+e.getUser()+"矿机离线通知到"+email); + EmailEntity entity = new EmailEntity(); + entity.setSubject("[M2Pool] 矿机离线提示"); + entity.setEmail(email); + entity.setText(text); + mailService.sendTextMail(entity); + }); + }else { + System.out.println("该用户未添加离线通知 :"+userEmails.toString()); + } + + }else { + //不满足通知条件 不通知 + System.out.println("挖矿账户"+e.getUser()+"本次无需通知,本次离线为"+e.getOffline()+",上次离线数"+ + map.getOrDefault(e.getUser(),0L)); + } + }); + }else { + System.out.println("未获取到DGBO下相关的离线告警订阅信息"); + } + + } + } + + } + + @Scheduled(cron = "16 19,49 * * * ?") + public void DGBQ30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("DGBQ 离线告警定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.DGBQ.getMhs() + "30m", nowStr); + + if(count > 0) { + //查询离线矿机数和离线矿机 按挖矿账户分 + List list = poolMapper.getOfflineList(Pools.DGBQ.getMhs() + "30m", nowStr); + + if(StringUtils.isNotNull(list)){ + //list不为空才处理 + + //先获取通知列表 + List nmcList = noticeMapper.getNoticeEmailListByMinerAndCoin(list, Pools.DGBQ.getCoin()); + + Map> userEmails = nmcList.stream(). + collect(Collectors.groupingBy( + NoticeMinerCoinListDto::getMiner, + Collectors.mapping(NoticeMinerCoinListDto::getEmail, Collectors.toList()) + )); + + + if(StringUtils.isNotNull(userEmails)){ + //获取上一次离线矿机数 可能为null 要做处理 如果没有redis记录 则上次离线数设置为0 若有redis但是key没有此挖矿账户也设置上次离线数为0 + Map map = redisService.getCacheMap("DGBQ_USERS_OFFLINE"); + list.stream().forEach(e -> { + long lastOff = 0; + if(StringUtils.isNotNull(map)){ + lastOff =map.getOrDefault(e.getUser(),0L); + } + if(e.getOffline() > lastOff){ + //对比redis中该挖矿账户上一次离线矿机数 上一次不存在则设上一次离线数为0来进行比较 + //仅当本次离线数大于上次离线数时才通知 + //执行通知 + List emails = userEmails.getOrDefault(e.getUser(), null); + if(StringUtils.isNotNull(emails)){ + String text = "您的"+Pools.DGBQ.getName()+"下挖矿账户: "+e.getUser()+"有矿机离线!离线矿机列表如下:\n" + + e.getMiners() + " \n"+ + "若您的矿机是因异常断开,请及时处理!"; + emails.stream().forEach(email ->{ + System.out.println("用户"+e.getUser()+"矿机离线通知到"+email); + EmailEntity entity = new EmailEntity(); + entity.setSubject("[M2Pool] 矿机离线提示"); + entity.setEmail(email); + entity.setText(text); + mailService.sendTextMail(entity); + }); + }else { + System.out.println("该用户未添加离线通知 :"+userEmails.toString()); + } + + }else { + //不满足通知条件 不通知 + System.out.println("挖矿账户"+e.getUser()+"本次无需通知,本次离线为"+e.getOffline()+",上次离线数"+ + map.getOrDefault(e.getUser(),0L)); + } + }); + }else { + System.out.println("未获取到DGBO下相关的离线告警订阅信息"); + } + + } + } + + } + + @Scheduled(cron = "18 20,50 * * * ?") + public void DGBS30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("DGBS 离线告警定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.DGBS.getMhs() + "30m", nowStr); + + if(count > 0) { + //查询离线矿机数和离线矿机 按挖矿账户分 + List list = poolMapper.getOfflineList(Pools.DGBS.getMhs() + "30m", nowStr); + + if(StringUtils.isNotNull(list)){ + //list不为空才处理 + + //先获取通知列表 + List nmcList = noticeMapper.getNoticeEmailListByMinerAndCoin(list, Pools.DGBS.getCoin()); + + Map> userEmails = nmcList.stream(). + collect(Collectors.groupingBy( + NoticeMinerCoinListDto::getMiner, + Collectors.mapping(NoticeMinerCoinListDto::getEmail, Collectors.toList()) + )); + + + if(StringUtils.isNotNull(userEmails)){ + //获取上一次离线矿机数 可能为null 要做处理 如果没有redis记录 则上次离线数设置为0 若有redis但是key没有此挖矿账户也设置上次离线数为0 + Map map = redisService.getCacheMap("DGBS_USERS_OFFLINE"); + list.stream().forEach(e -> { + long lastOff = 0; + if(StringUtils.isNotNull(map)){ + lastOff =map.getOrDefault(e.getUser(),0L); + } + if(e.getOffline() > lastOff){ + //对比redis中该挖矿账户上一次离线矿机数 上一次不存在则设上一次离线数为0来进行比较 + //仅当本次离线数大于上次离线数时才通知 + //执行通知 + List emails = userEmails.getOrDefault(e.getUser(), null); + if(StringUtils.isNotNull(emails)){ + String text = "您的"+Pools.DGBS.getName()+"下挖矿账户: "+e.getUser()+"有矿机离线!离线矿机列表如下:\n" + + e.getMiners() + " \n"+ + "若您的矿机是因异常断开,请及时处理!"; + emails.stream().forEach(email ->{ + System.out.println("用户"+e.getUser()+"矿机离线通知到"+email); + EmailEntity entity = new EmailEntity(); + entity.setSubject("[M2Pool] 矿机离线提示"); + entity.setEmail(email); + entity.setText(text); + mailService.sendTextMail(entity); + }); + }else { + System.out.println("该用户未添加离线通知 :"+userEmails.toString()); + } + + }else { + //不满足通知条件 不通知 + System.out.println("挖矿账户"+e.getUser()+"本次无需通知,本次离线为"+e.getOffline()+",上次离线数"+ + map.getOrDefault(e.getUser(),0L)); + } + }); + }else { + System.out.println("未获取到DGBS下相关的离线告警订阅信息"); + } + + } + } + + } + + @Scheduled(cron = "19 21,51 * * * ?") + public void RXD30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("rxd 离线告警定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.RXD.getMhs() + "30m", nowStr); + + if(count > 0) { + //查询离线矿机数和离线矿机 按挖矿账户分 + List list = poolMapper.getOfflineList(Pools.RXD.getMhs() + "30m", nowStr); + + if(StringUtils.isNotNull(list)){ + //list不为空才处理 + + //先获取通知列表 + List nmcList = noticeMapper.getNoticeEmailListByMinerAndCoin(list, Pools.RXD.getCoin()); + + Map> userEmails = nmcList.stream(). + collect(Collectors.groupingBy( + NoticeMinerCoinListDto::getMiner, + Collectors.mapping(NoticeMinerCoinListDto::getEmail, Collectors.toList()) + )); + + + if(StringUtils.isNotNull(userEmails)){ + //获取上一次离线矿机数 可能为null 要做处理 如果没有redis记录 则上次离线数设置为0 若有redis但是key没有此挖矿账户也设置上次离线数为0 + Map map = redisService.getCacheMap("RXD_USERS_OFFLINE"); + list.stream().forEach(e -> { + long lastOff = 0; + if(StringUtils.isNotNull(map)){ + lastOff =map.getOrDefault(e.getUser(),0L); + } + if(e.getOffline() > lastOff){ + //对比redis中该挖矿账户上一次离线矿机数 上一次不存在则设上一次离线数为0来进行比较 + //仅当本次离线数大于上次离线数时才通知 + //执行通知 + List emails = userEmails.getOrDefault(e.getUser(), null); + if(StringUtils.isNotNull(emails)){ + String text = "您的"+Pools.RXD.getName()+"下挖矿账户: "+e.getUser()+"有矿机离线!离线矿机列表如下:\n" + + e.getMiners() + " \n"+ + "若您的矿机是因异常断开,请及时处理!"; + emails.stream().forEach(email ->{ + System.out.println("用户"+e.getUser()+"矿机离线通知到"+email); + EmailEntity entity = new EmailEntity(); + entity.setSubject("[M2Pool] 矿机离线提示"); + entity.setEmail(email); + entity.setText(text); + mailService.sendTextMail(entity); + }); + }else { + System.out.println("该用户未添加离线通知 :"+userEmails.toString()); + } + + }else { + //不满足通知条件 不通知 + System.out.println("挖矿账户"+e.getUser()+"本次无需通知,本次离线为"+e.getOffline()+",上次离线数"+ + map.getOrDefault(e.getUser(),0L)); + } + }); + }else { + System.out.println("未获取到RXD下相关的离线告警订阅信息"); + } + + } + } + + } + + @Scheduled(cron = "20 23,53 * * * ?") + public void ALPH30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("alph 离线告警定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.ALPH.getMhs() + "30m", nowStr); + + if(count > 0) { + //查询离线矿机数和离线矿机 按挖矿账户分 + List list = poolMapper.getOfflineList(Pools.ALPH.getMhs() + "30m", nowStr); + + if(StringUtils.isNotNull(list)){ + //list不为空才处理 + + //先获取通知列表 + List nmcList = noticeMapper.getNoticeEmailListByMinerAndCoin(list, Pools.ALPH.getCoin()); + + Map> userEmails = nmcList.stream(). + collect(Collectors.groupingBy( + NoticeMinerCoinListDto::getMiner, + Collectors.mapping(NoticeMinerCoinListDto::getEmail, Collectors.toList()) + )); + + + if(StringUtils.isNotNull(userEmails)){ + //获取上一次离线矿机数 可能为null 要做处理 如果没有redis记录 则上次离线数设置为0 若有redis但是key没有此挖矿账户也设置上次离线数为0 + Map map = redisService.getCacheMap("ALPH_USERS_OFFLINE"); + list.stream().forEach(e -> { + long lastOff = 0; + if(StringUtils.isNotNull(map)){ + lastOff =map.getOrDefault(e.getUser(),0L); + } + if(e.getOffline() > lastOff){ + //对比redis中该挖矿账户上一次离线矿机数 上一次不存在则设上一次离线数为0来进行比较 + //仅当本次离线数大于上次离线数时才通知 + //执行通知 + List emails = userEmails.getOrDefault(e.getUser(), null); + if(StringUtils.isNotNull(emails)){ + String text = "您的"+Pools.ALPH.getName()+"下挖矿账户: "+e.getUser()+"有矿机离线!离线矿机列表如下:\n" + + e.getMiners() + " \n"+ + "若您的矿机是因异常断开,请及时处理!"; + emails.stream().forEach(email ->{ + System.out.println("用户"+e.getUser()+"矿机离线通知到"+email); + EmailEntity entity = new EmailEntity(); + entity.setSubject("[M2Pool] 矿机离线提示"); + entity.setEmail(email); + entity.setText(text); + mailService.sendTextMail(entity); + }); + }else { + System.out.println("该用户未添加离线通知 :"+userEmails.toString()); + } + + }else { + //不满足通知条件 不通知 + System.out.println("挖矿账户"+e.getUser()+"本次无需通知,本次离线为"+e.getOffline()+",上次离线数"+ + map.getOrDefault(e.getUser(),0L)); + } + }); + }else { + System.out.println("未获取到ALPH下相关的离线告警订阅信息"); + } + + } + } + + } + + @Scheduled(cron = "21 25,55 * * * ?") + public void ENX30mDataToDB(){ + //String nowStr = DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:00"); + + Date now = new Date(); + int minute = (now.getMinutes() / 30) * 30; + now.setMinutes( minute); + now.setSeconds(0); + String nowStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, now); + + System.out.println("enx 离线告警定时任务执行时间:"+now); + System.out.println("格式化后的时间:"+nowStr); + + //检查二级表是否有当前时间数据 + int count = poolMapper.getLastDataTime(Pools.ENX.getMhs() + "30m", nowStr); + + if(count > 0) { + //查询离线矿机数和离线矿机 按挖矿账户分 + List list = poolMapper.getOfflineList(Pools.ENX.getMhs() + "30m", nowStr); + + if(StringUtils.isNotNull(list)){ + //list不为空才处理 + + //先获取通知列表 + List nmcList = noticeMapper.getNoticeEmailListByMinerAndCoin(list, Pools.ENX.getCoin()); + + Map> userEmails = nmcList.stream(). + collect(Collectors.groupingBy( + NoticeMinerCoinListDto::getMiner, + Collectors.mapping(NoticeMinerCoinListDto::getEmail, Collectors.toList()) + )); + + + if(StringUtils.isNotNull(userEmails)){ + //获取上一次离线矿机数 可能为null 要做处理 如果没有redis记录 则上次离线数设置为0 若有redis但是key没有此挖矿账户也设置上次离线数为0 + Map map = redisService.getCacheMap("ENX_USERS_OFFLINE"); + list.stream().forEach(e -> { + long lastOff = 0; + if(StringUtils.isNotNull(map)){ + lastOff =map.getOrDefault(e.getUser(),0L); + } + if(e.getOffline() > lastOff){ + //对比redis中该挖矿账户上一次离线矿机数 上一次不存在则设上一次离线数为0来进行比较 + //仅当本次离线数大于上次离线数时才通知 + //执行通知 + List emails = userEmails.getOrDefault(e.getUser(), null); + if(StringUtils.isNotNull(emails)){ + String text = "您的"+Pools.ENX.getName()+"下挖矿账户: "+e.getUser()+"有矿机离线!离线矿机列表如下:\n" + + e.getMiners() + " \n"+ + "若您的矿机是因异常断开,请及时处理!"; + emails.stream().forEach(email ->{ + System.out.println("用户"+e.getUser()+"矿机离线通知到"+email); + EmailEntity entity = new EmailEntity(); + entity.setSubject("[M2Pool] 矿机离线提示"); + entity.setEmail(email); + entity.setText(text); + mailService.sendTextMail(entity); + }); + }else { + System.out.println("该用户未添加离线通知 :"+userEmails.toString()); + } + + }else { + //不满足通知条件 不通知 + System.out.println("挖矿账户"+e.getUser()+"本次无需通知,本次离线为"+e.getOffline()+",上次离线数"+ + map.getOrDefault(e.getUser(),0L)); + } + }); + }else { + System.out.println("未获取到ENX下相关的离线告警订阅信息"); + } + + } + } + + } + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/PoolBlkStatsTask.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/PoolBlkStatsTask.java new file mode 100644 index 0000000..4c7564a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/task/PoolBlkStatsTask.java @@ -0,0 +1,483 @@ +package com.m2pool.pool.task; + +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.redis.service.RedisService; +import com.m2pool.pool.dto.BlockInfoDto; +import com.m2pool.pool.mapper.PoolMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + * @Description TODO + * @Date 2024/6/14 14:03 + * @Author dy + */ +@Configuration +@EnableScheduling +public class PoolBlkStatsTask { + + @Autowired + private PoolMapper poolMapper; + + @Autowired + private RedisService redisService; + + @Scheduled(cron = "0 0/2 * * * ?") + public void NEXABlockInfoToRedis(){ + + boolean result = NEXABlockInfo(); + + int count = 1; + //执行失败返回false 再次执行 + while (!result){ + if(count >= 3){ + break; + } + result = NEXABlockInfo(); + count++; + } + } + + @Scheduled(cron = "5 0/2 * * * ?") + public void GRSBlockInfoToRedis(){ + + boolean result = GRSBlockInfo(); + + int count = 1; + //执行失败返回false 再次执行 + while (!result){ + if(count >= 3){ + break; + } + result = GRSBlockInfo(); + count++; + } + } + + @Scheduled(cron = "10 0/2 * * * ?") + public void MONABlockInfoToRedis(){ + + boolean result = MONABlockInfo(); + + int count = 1; + //执行失败返回false 再次执行 + while (!result){ + if(count >= 3){ + break; + } + result = MONABlockInfo(); + count++; + } + } + + @Scheduled(cron = "15 0/2 * * * ?") + public void DGBOBlockInfoToRedis(){ + + boolean result = DGBOBlockInfo(); + + int count = 1; + //执行失败返回false 再次执行 + while (!result){ + if(count >= 3){ + break; + } + result = DGBOBlockInfo(); + count++; + } + } + + @Scheduled(cron = "20 0/2 * * * ?") + public void DGBQBlockInfoToRedis(){ + + boolean result = DGBQBlockInfo(); + + int count = 1; + //执行失败返回false 再次执行 + while (!result){ + if(count >= 3){ + break; + } + result = DGBQBlockInfo(); + count++; + } + } + + @Scheduled(cron = "25 0/2 * * * ?") + public void DGBSBlockInfoToRedis(){ + + boolean result = DGBSBlockInfo(); + + int count = 1; + //执行失败返回false 再次执行 + while (!result){ + if(count >= 3){ + break; + } + result = DGBSBlockInfo(); + count++; + } + } + + @Scheduled(cron = "30 0/2 * * * ?") + public void RXDBlockInfoToRedis(){ + + boolean result = RXDBlockInfo(); + + int count = 1; + //执行失败返回false 再次执行 + while (!result){ + if(count >= 3){ + break; + } + result = RXDBlockInfo(); + count++; + } + } + + @Scheduled(cron = "35 0/2 * * * ?") + public void ALPHBlockInfoToRedis(){ + + boolean result = ALPHBlockInfo(); + + int count = 1; + //执行失败返回false 再次执行 + while (!result){ + if(count >= 3){ + break; + } + result = ALPHBlockInfo(); + count++; + } + } + + @Scheduled(cron = "40 0/2 * * * ?") + public void ENXBlockInfoToRedis(){ + + boolean result = ENXBlockInfo(); + + int count = 1; + //执行失败返回false 再次执行 + while (!result){ + if(count >= 3){ + break; + } + result = ENXBlockInfo(); + count++; + } + } + + public boolean NEXABlockInfo(){ + try { + String lastDate= ""; + + if(redisService.hasKey("nexa_pool_last_date")){ + lastDate = (String) redisService.getCacheObject("nexa_pool_last_date"); + System.out.println("redis中的lastDate:"+lastDate); + }else { + List list = poolMapper.getNEXABlockInfoList(); + if (list.size() > 0){ + lastDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(0).getDate()); + }else { + lastDate = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,new Date()); + } + } + System.out.println("最终的lastDate:"+lastDate); + + List list = poolMapper.getNewNEXABlockInfoList(lastDate); + + if(list.size() > 0){ + //存入数据库 + poolMapper.batchInsertNexaPoolBlkToDB(list); + //把最大时间存入redis + String maxDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(list.size()-1).getDate()); + redisService.setCacheObject("nexa_pool_last_date",maxDate); + } + return true; + }catch (Exception e){ + System.out.println("出错内容:"+e); + return false; + } + } + + public boolean GRSBlockInfo(){ + try { + String lastDate= ""; + + if(redisService.hasKey("grs_pool_last_date")){ + lastDate = (String) redisService.getCacheObject("grs_pool_last_date"); + System.out.println("redis中的grs lastDate:"+lastDate); + }else { + List list = poolMapper.getGRSBlockInfoList(); + if (list.size() > 0){ + lastDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(0).getDate()); + }else { + lastDate = "2024-10-01"; + } + } + System.out.println("grs最终的lastDate:"+lastDate); + + List list = poolMapper.getNewGRSBlockInfoList(lastDate); + + if(list.size() > 0){ + //存入数据库 + poolMapper.batchInsertGRSPoolBlkToDB(list); + //把最大时间存入redis + String maxDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(list.size()-1).getDate()); + redisService.setCacheObject("grs_pool_last_date",maxDate); + } + return true; + }catch (Exception e){ + System.out.println("出错内容:"+e); + return false; + } + } + + public boolean MONABlockInfo(){ + try { + String lastDate= ""; + + if(redisService.hasKey("mona_pool_last_date")){ + lastDate = (String) redisService.getCacheObject("mona_pool_last_date"); + System.out.println("redis中的mona lastDate:"+lastDate); + }else { + List list = poolMapper.getMonaBlockInfoList(); + if (list.size() > 0){ + lastDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(0).getDate()); + }else { + lastDate = "2024-10-01"; + } + } + System.out.println("mona最终的lastDate:"+lastDate); + + List list = poolMapper.getNewMONABlockInfoList(lastDate); + + if(list.size() > 0){ + //存入数据库 + poolMapper.batchInsertMONAPoolBlkToDB(list); + //把最大时间存入redis + String maxDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(list.size()-1).getDate()); + redisService.setCacheObject("mona_pool_last_date",maxDate); + } + return true; + }catch (Exception e){ + System.out.println("出错内容:"+e); + return false; + } + } + + public boolean DGBOBlockInfo(){ + try { + String lastDate= ""; + + if(redisService.hasKey("dgbo_pool_last_date")){ + lastDate = (String) redisService.getCacheObject("dgbo_pool_last_date"); + System.out.println("redis中的dgbo lastDate:"+lastDate); + }else { + List list = poolMapper.getDGBODOBlockInfoList(); + if (list.size() > 0){ + lastDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(0).getDate()); + }else { + lastDate = "2024-10-01"; + } + } + System.out.println("DGBO最终的lastDate:"+lastDate); + + List list = poolMapper.getNewDGBOBlockInfoList(lastDate); + + if(list.size() > 0){ + //存入数据库 + poolMapper.batchInsertDGBOPoolBlkToDB(list); + //把最大时间存入redis + String maxDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(list.size()-1).getDate()); + redisService.setCacheObject("dgbo_pool_last_date",maxDate); + } + return true; + }catch (Exception e){ + System.out.println("出错内容:"+e); + return false; + } + } + + public boolean DGBQBlockInfo(){ + try { + String lastDate= ""; + + if(redisService.hasKey("dgbq_pool_last_date")){ + lastDate = (String) redisService.getCacheObject("dgbq_pool_last_date"); + System.out.println("redis中的dgbq lastDate:"+lastDate); + }else { + List list = poolMapper.getDgbQubitBlockInfoList(); + if (list.size() > 0){ + lastDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(0).getDate()); + }else { + lastDate = "2024-10-01"; + } + } + System.out.println("DGBQ最终的lastDate:"+lastDate); + + List list = poolMapper.getNewDGBQBlockInfoList(lastDate); + + if(list.size() > 0){ + //存入数据库 + poolMapper.batchInsertDGBQPoolBlkToDB(list); + //把最大时间存入redis + String maxDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(list.size()-1).getDate()); + redisService.setCacheObject("dgbq_pool_last_date",maxDate); + } + return true; + }catch (Exception e){ + System.out.println("出错内容:"+e); + return false; + } + } + + public boolean DGBSBlockInfo(){ + try { + String lastDate= ""; + + if(redisService.hasKey("dgbs_pool_last_date")){ + lastDate = (String) redisService.getCacheObject("dgbs_pool_last_date"); + System.out.println("redis中的dgbs lastDate:"+lastDate); + }else { + List list = poolMapper.getDgbSkeinBlockInfoList(); + if (list.size() > 0){ + lastDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(0).getDate()); + }else { + lastDate = "2024-10-01"; + } + } + System.out.println("DGBS最终的lastDate:"+lastDate); + + List list = poolMapper.getNewDGBSBlockInfoList(lastDate); + + if(list.size() > 0){ + //存入数据库 + poolMapper.batchInsertDGBSPoolBlkToDB(list); + //把最大时间存入redis + String maxDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(list.size()-1).getDate()); + redisService.setCacheObject("dgbs_pool_last_date",maxDate); + } + return true; + }catch (Exception e){ + System.out.println("出错内容:"+e); + return false; + } + } + + public boolean RXDBlockInfo(){ + try { + String lastDate= ""; + + if(redisService.hasKey("rxd_pool_last_date")){ + lastDate = (String) redisService.getCacheObject("rxd_pool_last_date"); + System.out.println("redis中的rxd lastDate:"+lastDate); + }else { + List list = poolMapper.getRXDBlockInfoList(); + if (list.size() > 0){ + lastDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(0).getDate()); + }else { + lastDate = "2024-10-01"; + } + } + System.out.println("rxd最终的lastDate:"+lastDate); + + List list = poolMapper.getNewRXDBlockInfoList(lastDate); + + if(list.size() > 0){ + //存入数据库 + poolMapper.batchInsertRXDPoolBlkToDB(list); + //把最大时间存入redis + String maxDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(list.size()-1).getDate()); + redisService.setCacheObject("rxd_pool_last_date",maxDate); + } + return true; + }catch (Exception e){ + System.out.println("出错内容:"+e); + return false; + } + } + + public boolean ALPHBlockInfo(){ + try { + String lastDate= ""; + + if(redisService.hasKey("alph_pool_last_date")){ + lastDate = (String) redisService.getCacheObject("alph_pool_last_date"); + System.out.println("redis中的alph lastDate:"+lastDate); + }else { + List list = poolMapper.getALPHBlockInfoList(); + if (list.size() > 0){ + lastDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(0).getDate()); + }else { + lastDate = "2024-10-01"; + } + } + System.out.println("rxd最终的lastDate:"+lastDate); + + List list = poolMapper.getNewALPHBlockInfoList(lastDate); + + if(list.size() > 0){ + //存入数据库 + //alph 报块奖励需要除以10的18次方 + list.stream().forEach(e ->{ + if(e.getReward() !=0){ + BigDecimal reward = BigDecimal.valueOf(e.getReward()).divide(BigDecimal.valueOf(1000 * 1000), 6, BigDecimal.ROUND_HALF_UP) + .divide(BigDecimal.valueOf(1000*1000), 12, BigDecimal.ROUND_HALF_UP) + .divide(BigDecimal.valueOf(1000*1000), 8, BigDecimal.ROUND_HALF_UP); + e.setReward(reward.doubleValue()); + } + }); + //poolMapper.batchInsertRXDPoolBlkToDB(list); + poolMapper.batchInsertALPHPoolBlkToDB(list); + //把最大时间存入redis + String maxDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(list.size()-1).getDate()); + redisService.setCacheObject("alph_pool_last_date",maxDate); + } + return true; + }catch (Exception e){ + System.out.println("出错内容:"+e); + return false; + } + } + + public boolean ENXBlockInfo(){ + try { + String lastDate= ""; + + if(redisService.hasKey("enx_pool_last_date")){ + lastDate = (String) redisService.getCacheObject("enx_pool_last_date"); + System.out.println("redis中的enx lastDate:"+lastDate); + }else { + List list = poolMapper.getENXBlockInfoList(); + if (list.size() > 0){ + lastDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(0).getDate()); + }else { + lastDate = "2024-10-01"; + } + } + System.out.println("enx最终的lastDate:"+lastDate); + + List list = poolMapper.getNewENXBlockInfoList(lastDate); + + if(list.size() > 0){ + //存入数据库 + //poolMapper.batchInsertRXDPoolBlkToDB(list); + poolMapper.batchInsertENXPoolBlkToDB(list); + //把最大时间存入redis + String maxDate =DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,list.get(list.size()-1).getDate()); + redisService.setCacheObject("enx_pool_last_date",maxDate); + } + return true; + }catch (Exception e){ + System.out.println("出错内容:"+e); + return false; + } + } + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/AlphMining.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/AlphMining.java new file mode 100644 index 0000000..fff543f --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/AlphMining.java @@ -0,0 +1,338 @@ +//package com.m2pool.pool.utils; +// +//import com.alibaba.fastjson.JSONObject; +//import com.alibaba.nacos.shaded.org.checkerframework.checker.units.qual.C; +//import com.fasterxml.jackson.core.JsonProcessingException; +//import com.fasterxml.jackson.databind.JsonMappingException; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import com.m2pool.common.core.text.Convert; +//import com.m2pool.common.core.utils.StringUtils; +//import com.m2pool.pool.entity.NotifyMsgAlph; +//import com.m2pool.pool.entity.TempMiner; +//import org.apache.commons.codec.DecoderException; +//import org.apache.commons.codec.binary.Hex; +//import org.apache.commons.codec.digest.Blake3; +//import org.springframework.boot.autoconfigure.SpringBootApplication; +//import org.springframework.scheduling.annotation.EnableAsync; +// +//import java.io.IOException; +//import java.io.OutputStream; +//import java.math.BigDecimal; +//import java.math.BigInteger; +//import java.math.MathContext; +//import java.math.RoundingMode; +//import java.nio.ByteBuffer; +//import java.nio.ByteOrder; +//import java.util.Map; +//import java.util.concurrent.TimeUnit; +// +///** +// * @Description TODO +// * @Date 2025/3/17 10:17 +// * @Author 杜懿 +// */ +//@SpringBootApplication +//@EnableAsync +//public class AlphMining { +// +// private final static char[] digits = "0123456789abcdef".toCharArray(); +// +// private static final ObjectMapper mapper = new ObjectMapper(); +// +// public static void main(String[] args) { +// TempMiner miner = new TempMiner(); +// //**自定义miner属性值 +// miner.setCoin(""); +// miner.setPool(""); +// miner.setUser(""); +// miner.setPassword(""); +// int count = 0; +// int start = 0; +// // 自定义结束 +// hash(miner); +// //fmt.Printf("coin %v pool %v user %v password %v count %v start %v\n", *coin, *pool, *user, *password, *count, *start) +// +// } +// +// private static JSONObject subscribe(TempMiner m){ +// m.setState("subscribe"); +// // fmt.Sprintf("{\"id\": %d, +// // \"method\": \"mining.subscribe\", +// // \"params\": [\"YxMiner/v20.0.0\"]}\n", m.Id) +// JSONObject requestBody = new JSONObject(); +// requestBody.put("id", m.getId()); +// requestBody.put("method", "mining.subscribe"); +// requestBody.put("params","YxMiner/v20.0.0"); +// return requestBody; +// } +// +// private static JSONObject authorize(TempMiner m){ +// m.setState("authorize"); +// // fmt.Sprintf( +// // "{\"id\": %d, \"jsonrpc\": \"2.0\", \"method\": \"mining.authorize\", \"params\": [\"%s\"]}\n", +// JSONObject requestBody = new JSONObject(); +// requestBody.put("id", m.getId()); +// requestBody.put("jsonrpc", "2.0"); +// requestBody.put("method", "mining.authorize"); +// requestBody.put("params",m.getUser()); +// return requestBody; +// } +// +// private static JSONObject submit(TempMiner m,String nonce){ +// m.setState("submit"); +// if(StringUtils.isNull(m.getExtraNonce1())){ +// m.setExtraNonce1(""); +// } +// String finalNonce = m.getExtraNonce1().concat(nonce); +// //fmt.Sprintf( +// // "{\"params\": [\"%s\", \"%s\", \"%s\"], \"id\": %d, \"method\": \"mining.submit\"}\n", +// // m.User, +// // m.JobId, +// // final_nonce, +// // m.Id, +// JSONObject requestBody = new JSONObject(); +// String[] params ={m.getUser(),m.getJobId(),finalNonce}; +// requestBody.put("params", params); +// requestBody.put("id", m.getId()); +// requestBody.put("method", "mining.submit"); +// return requestBody; +// } +// +// private static JSONObject pong(TempMiner m,float pingId){ +// BigInteger pongID = BigInteger.valueOf(Convert.toLong(pingId,0L)); +// // +// JSONObject requestBody = new JSONObject(); +// requestBody.put("id", pongID); +// requestBody.put("result", "pong"); +// requestBody.put("error",null); +// return requestBody; +// } +// +// private static byte[] hash(TempMiner m) throws DecoderException { +// // 解码Header +// byte[] headerBytes = Hex.decodeHex(m.getHeader().toCharArray()); +// long nonce = m.getNonce(); +// m.setBlockNonce(m.getNonce()); +// byte[] nonceBytes = ByteBuffer.allocate(8) +// .order(ByteOrder.LITTLE_ENDIAN) +// .putLong(nonce) +// .array(); +// // 解码ExtraNonce1 +// byte[] extraBytes = Hex.decodeHex(m.getExtraNonce1().toCharArray()); +// +// byte[] inb = new byte[49]; +// System.arraycopy(headerBytes, 0, inb, 0, 32); // 前32字节 +// inb[32] = 0x10; // 固定分隔符 +// System.arraycopy(nonceBytes, 0, inb, 33, 8); // 33-40字节 +// System.arraycopy(extraBytes, 0, inb, 41, 8); // 41-48字节 +// +// m.setBlockData(Hex.encodeHexString(inb)); +// +// //fmt.Printf("[nexa]in %s", hex.EncodeToString(inb)) +// byte[] hashResult = hexStringToBytes(hashHandle(m.getBlockData())); +// byte[] outputs32 = new byte[32]; +// for (int i = 0; i < 32; i++) { +// outputs32[i] = hashResult[31 - i]; +// } +// +// return outputs32; +// } +// +// public static BigInteger difficultyToTarget(double diff) throws ArithmeticException{ +// if (diff <= 0) { +// throw new ArithmeticException("Difficulty must be greater than zero"); +// } +// +// // 创建大整数 2^224 +// BigInteger twoTo224 = BigInteger.ONE.shiftLeft(224); +// +// // 使用 big.Float 进行高精度计算 +// BigDecimal difficultyDecimal = new BigDecimal(diff); +// +// // 将 2^224 转换为 big.Float +// BigDecimal twoTo224Decimal = new BigDecimal(twoTo224); +// +// // 计算目标值 = 2^224 / 难度 +// try { +// // 使用UNNECESSARY模式确保精确除法 +// BigDecimal targetDecimal = twoTo224Decimal.divide( +// difficultyDecimal, +// new MathContext(0, RoundingMode.UNNECESSARY) +// ); +// +// // 4. 转换为BigInteger +// return targetDecimal.toBigIntegerExact(); +// +// } catch (ArithmeticException e) { +// throw new ArithmeticException("Conversion resulted in inexact result: " + e.getMessage()); +// } +// } +// +// private static void lookup(TempMiner m){ +// // +// System.out.println("Lookup start"); +// m.setRunning(true); +// while (m.isRunning()){ +// try { +// if(m.isNewJob()){ +// m.setNewJob(false); +// m.getNewJobCh().poll(10, TimeUnit.MILLISECONDS); +// System.out.println("Lookup New job"); +// } +// byte[] hashBytes = hash(m); +// m.setNonce(m.getNonce()+1); +// // 比较哈希难度 +// BigInteger hashInt = new BigInteger(1, hashBytes); +// BigInteger diffInt = difficultyToTarget(m.getDifficulty()); +// +// if (hashInt.compareTo(diffInt) <= 0) { +// // 比较当前target(hashint)和下发的任务target (diffint) <=0代表 +// +// System.out.printf("calc diff %s\ntarget diff %s\n", +// hashInt.toString(), diffInt.toString()); +// System.out.println("block data " + m.getBlockData()); +// +// // 准备提交数据 +// ByteBuffer buffer = ByteBuffer.allocate(8) +// .order(ByteOrder.LITTLE_ENDIAN) +// .putLong(m.getBlockNonce()); +// String submitData = Hex.encodeHexString(buffer.array()); +// +// // 发送提交请求 +// JSONObject response = submit(m, submitData); +// System.out.println("submit " + response); +// m.getConn().write(response.toJSONString().getBytes()); +// } +// Thread.sleep(10); // 10毫秒间隔 +// }catch (InterruptedException | IOException | DecoderException e){ +// System.out.println("Error in lookup: " + e.getMessage()); +// m.setRunning(false); +// } +// +// } +// System.out.println("Lookup end"); +// } +// +// public static void handle(TempMiner m, String msg, OutputStream conn){ +// // +// m.setConn(conn); +// try { +// Map ret = mapper.readValue(msg,Map.class); +// if (ret.containsKey("method")){ +// String method = Convert.toStr(ret.get("method")); +// switch (method){ +// case "mining.notify": +// handleNotify(msg); +// break; +// case "mining.set_difficulty": +// handleSetDifficulty(msg); +// break; +// case "mining.ping": +// handlePing(msg); +// break; +// } +// }else { +// handleStatefulMessage(msg); +// } +// }catch (IOException e){ +// System.out.println("JSON解析错误: " + e.getMessage()); +// } catch (JsonMappingException e) { +// e.printStackTrace(); +// } catch (JsonProcessingException e) { +// e.printStackTrace(); +// } +// +// } +// +// public static void handleNotify(TempMiner m,String msg) throws IOException { +// NotifyMsgAlph notify = mapper.readValue(msg, NotifyMsgAlph.class); +// +// if () { +// newJob = true; +// while (newJob) { +// try { +// Thread.sleep(10); +// } catch (InterruptedException e) { +// Thread.currentThread().interrupt(); +// } +// } +// } +// +// this.jobId = notify.params[0]; +// this.header = notify.params[1]; +// this.nBits = notify.params[2]; +// this.jobTime = Long.parseLong(notify.params[3]); +// this.nonce = 0; +// +// if (!running) { +// startMining(); +// } else { +// newJobCh.offer(true); +// } +// } +// +// private void handleSetDifficulty(String msg) throws IOException { +// DifficultyMsg diff = mapper.readValue(msg, DifficultyMsg.class); +// this.difficulty = diff.params[0]; +// } +// +// private static String hashHandle(String in) { +// //字符串判空 +// if(StringUtils.isBlank(in)){ +// return null; +// } +// Blake3 hasher = Blake3.initHash(); +// byte[] hash = new byte[32]; +// hasher.update(hexStringToBytes(in)); +// hasher.doFinalize(hash); +// String result1 = bytesToHexString(hash); +// System.out.println(result1); +// +// hasher.reset(); +// byte[] hash2 = new byte[32]; +// hasher.update(hexStringToBytes(result1)); +// hasher.doFinalize(hash2); +// String result2 = bytesToHexString(hash2); +// System.out.println(result2); +// return result2; +// } +// +// /** +// * 高效写法 16进制字符串转成byte数组 +// * +// * @param hex 16进制字符串,支持大小写 +// * @return byte数组 +// */ +// public static byte[] hexStringToBytes(String hex) { +// byte[] result = new byte[hex.length() / 2]; +// char[] chars = hex.toCharArray(); +// for (int i = 0, j = 0; i < result.length; i++) { +// result[i] = (byte) (toByte(chars[j++]) << 4 | toByte(chars[j++])); +// } +// return result; +// } +// +// private static int toByte(char c) { +// if (c >= '0' && c <= '9') return (c - '0'); +// if (c >= 'A' && c <= 'F') return (c - 'A' + 0x0A); +// if (c >= 'a' && c <= 'f') return (c - 'a' + 0x0a); +// throw new RuntimeException("invalid hex char '" + c + "'"); +// } +// +// +// /** +// * 高效写法 byte数组转成16进制字符串 +// * +// * @param bytes byte数组 +// * @return 16进制字符串 +// */ +// public static String bytesToHexString(byte[] bytes) { +// char[] buf = new char[bytes.length * 2]; +// int c = 0; +// for (byte b : bytes) { +// buf[c++] = digits[(b >> 4) & 0x0F]; +// buf[c++] = digits[b & 0x0F]; +// } +// return new String(buf); +// } +//} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Demo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Demo.java new file mode 100644 index 0000000..85d9b5a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Demo.java @@ -0,0 +1,72 @@ +//package com.m2pool.pool.utils; +// +//import cn.hutool.http.HttpRequest; +//import com.alibaba.fastjson.JSONObject; +// +//import org.springframework.boot.autoconfigure.SpringBootApplication; +//import org.springframework.scheduling.annotation.EnableAsync; +// +//import java.util.concurrent.CountDownLatch; +//import java.util.concurrent.ExecutorService; +//import java.util.concurrent.Executors; +//import java.util.concurrent.TimeUnit; +// +// +///** +// * @Description TODO +// * @Date 2024/6/24 10:21 +// * @Author 杜懿 +// */ +//@SpringBootApplication +//@EnableAsync // 启用异步支持 +//public class Demo { +// +// // 目标 API 地址 +// private static final String API_URL = "http://10.168.2.249:22334/bindAddress"; +// +// +// public static void main(String[] args) throws InterruptedException { +// // 定义并发线程数 +// final int concurrency = 3; +// final CountDownLatch startLatch = new CountDownLatch(1); +// final CountDownLatch finishLatch = new CountDownLatch(concurrency); +// +// for (int i = 0; i < concurrency; i++) { +// int id = i+1; +// new Thread(() -> { +// try { +// startLatch.await(); // 等待所有线程准备就绪 +// JSONObject requestBody = new JSONObject(); +// requestBody.put("chain", "eth"); +// requestBody.put("symbol", "usdt"); +// requestBody.put("user", "testAccount"+id); +// +// try { +// String body = HttpRequest +// .post(API_URL) +// .header("Content-Type", "application/json") +// .body(requestBody.toString()) +// .timeout(3000) +// .execute() +// .body(); +// System.out.println(Thread.currentThread().getName() + " 响应结果长度:" + body.length()); +// System.out.println(body); +// }catch (Exception e){ +// +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } finally { +// finishLatch.countDown(); +// } +// }).start(); +// } +// +// System.out.println("所有线程准备完成,开始并发请求..."); +// startLatch.countDown(); // 同时释放所有线程 +// finishLatch.await(2000, TimeUnit.MILLISECONDS); // 等待所有线程完成 +// System.out.println("所有请求执行完毕"); +// } +// +//} +// diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Demo2.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Demo2.java new file mode 100644 index 0000000..0c027f3 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Demo2.java @@ -0,0 +1,109 @@ +package com.m2pool.pool.utils; + +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.pool.entity.TempMiner; +import org.apache.commons.codec.digest.Blake3; + + +/** + * @Description TODO + * @Date 2025/3/11 13:48 + * @Author 杜懿 + */ +public class Demo2 { + + private final static char[] digits = "0123456789abcdef".toCharArray(); + + public static void main(String[] args) { + //Blake3 hasher = Blake3.initHash(); + String header ="0007b49c0e58baeb41210e9df4152cde2e55e1f4805e8e44c7ad3685b198197e9ce5639aad9be58f4278eb6c5323673c7b8050182f15f2f4cd7c1c18d9d0c2c482ba3297300d77dcc730e671441a2d625598da6ec549a719e7754112173afd3abe9f00000001fc048896d8cb49801427e6d000dc8b90b32aca0ab895b9ae473832600b7f0f2126cd66f9b64ad33db4d2349e698248ab9217dd2a3b3a8ec2f9d22f4189bc709c94bcc6b8c1fdaedd543a7a7ba5a2b8d68da2a3fd449d1595a55a2ac27c25bcd2019bf81da613a561fe098e49cc5b6cf02a2b743c6d2a1d441284af43af9176ba91d5509c9e3cb2b76f3993c88783a0d1f53e27a6619db3bbbaf2a991777df922b241da7b46a37824ee1fab5e71aece49e4c11048576ca28a9703987800000195fa8c66bf1d08f849"; + String nonce = "bb57294afe0218a928d9e4fd1856c53e578066c0"; + //byte[] hash = new byte[32]; + //hasher.update(hexStringToBytes(a.concat(b))); + //hasher.doFinalize(hash); + //System.out.println(hash.length); + //String result1 = bytesToHexString(hash); + //System.out.println(result1); + //System.out.println(result1.length()); + // + //hasher.reset(); + //byte[] hash2 = new byte[32]; + //hasher.update(hexStringToBytes(result1)); + //hasher.doFinalize(hash2); + //String result2 = bytesToHexString(hash2); + //System.out.println(result2); + + + + //Blake3Digest digest = new Blake3Digest(); + //byte[] dateBytes = hexStringToBytes(data); + //digest.update(dateBytes,0,data.length()); + //byte[] hash2 = new byte[digest.getDigestSize()]; + //digest.doFinal(hash2,0); + //System.out.println(hash2); + //System.out.println(bytesToHex(hash2)); + hashHandle(header.concat(nonce)); + } + + private static String hashHandle(String in) { + //字符串判空 + if(StringUtils.isBlank(in)){ + return null; + } + Blake3 hasher = Blake3.initHash(); + byte[] hash = new byte[32]; + hasher.update(hexStringToBytes(in)); + hasher.doFinalize(hash); + String result1 = bytesToHexString(hash); + System.out.println(result1); + + hasher.reset(); + byte[] hash2 = new byte[32]; + hasher.update(hexStringToBytes(result1)); + hasher.doFinalize(hash2); + String result2 = bytesToHexString(hash2); + System.out.println(result2); + return result2; + } + + + /** + * 高效写法 16进制字符串转成byte数组 + * + * @param hex 16进制字符串,支持大小写 + * @return byte数组 + */ + public static byte[] hexStringToBytes(String hex) { + byte[] result = new byte[hex.length() / 2]; + char[] chars = hex.toCharArray(); + for (int i = 0, j = 0; i < result.length; i++) { + result[i] = (byte) (toByte(chars[j++]) << 4 | toByte(chars[j++])); + } + return result; + } + + private static int toByte(char c) { + if (c >= '0' && c <= '9') return (c - '0'); + if (c >= 'A' && c <= 'F') return (c - 'A' + 0x0A); + if (c >= 'a' && c <= 'f') return (c - 'a' + 0x0a); + throw new RuntimeException("invalid hex char '" + c + "'"); + } + + + /** + * 高效写法 byte数组转成16进制字符串 + * + * @param bytes byte数组 + * @return 16进制字符串 + */ + public static String bytesToHexString(byte[] bytes) { + char[] buf = new char[bytes.length * 2]; + int c = 0; + for (byte b : bytes) { + buf[c++] = digits[(b >> 4) & 0x0F]; + buf[c++] = digits[b & 0x0F]; + } + return new String(buf); + } + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/EnumUtils.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/EnumUtils.java new file mode 100644 index 0000000..65ca02c --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/EnumUtils.java @@ -0,0 +1,99 @@ +package com.m2pool.pool.utils; + +import com.m2pool.common.core.utils.StringUtils; + +import java.lang.reflect.Method; + +/** + * @Description 枚举工具类 + * @Date 2024/6/13 10:54 + * @Author dy + */ +public class EnumUtils { + + /** + * 通过值判断数值是否属于枚举类的值 + * @param clzz 枚举类 Enum + * @param code + * @author wayleung + * @return + */ + public static boolean isInclude(Class clzz,String code){ + + if(StringUtils.isEmpty(code)){ + return false; + } + + try { + if(clzz.isEnum()){ + Object[] enumConstants = clzz.getEnumConstants(); + Method name = clzz.getMethod("name"); + + for (Object enumConstant:enumConstants){ + if (name.invoke(enumConstant).equals(code.toUpperCase())) { + return true; + } + } + + } + }catch (Exception e){ + throw new RuntimeException(); + } + return false; + } + /** + * 通过值判断数值是否属于枚举类的值 + * @param clzz 枚举类 Enum + * @param code + * @author wayleung + * @return + */ + + public static Object get(Class clzz,String code){ + + if(StringUtils.isEmpty(code)){ + return null; + } + + + try { + if(clzz.isEnum()){ + Object[] enumConstants = clzz.getEnumConstants(); + Method getInfo = clzz.getMethod("name"); + for (Object enumConstant:enumConstants){ + if (getInfo.invoke(enumConstant).equals(code.toUpperCase())) { + return enumConstant; + } + } + + } + }catch (Exception e){ + throw new RuntimeException(); + } + return null; + } + + public static String getNumberTypeStr(Class clzz,String value){ + + if(StringUtils.isEmpty(value)){ + return null; + } + + try { + if(clzz.isEnum()){ + Object[] enumConstants = clzz.getEnumConstants(); + Method getInfo = clzz.getMethod("getInfo"); + Method getValue = clzz.getMethod("getValue"); + for (Object enumConstant:enumConstants){ + if (getInfo.invoke(enumConstant).equals(value)) { + return (String) getValue.invoke(enumConstant); + } + } + + } + }catch (Exception e){ + throw new RuntimeException(); + } + return null; + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Miner.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Miner.java new file mode 100644 index 0000000..659c39a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Miner.java @@ -0,0 +1,529 @@ +package com.m2pool.pool.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.pool.entity.NotifyMsgParams; +import lombok.Data; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.Blake3; + +import java.io.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.math.RoundingMode; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.SecureRandom; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; + +/** + * @Description alph 矿机接入模拟 可对矿池进行并发测试 + * @Date 2025/3/17 10:00 + * @Author 杜懿 + */ +@Data +public class Miner { + + private final char[] digits = "0123456789abcdef".toCharArray(); + + private final ObjectMapper mapper = new ObjectMapper(); + + private String coin; + private String pool; + private String user; + private String password; + private int id; + private String state; + // net.conn + private Socket conn; + //private OutputStream conn; + private String extraNonce1; + private int extraNonce2Size; + private double difficulty; + private String jobId; + private String header; + //private String nbits; + //64位无符号整数 + //private long jobTime; + private String blockData; + //64位无符号整数-> 48位 前8固定为extraNonce+"0000" + private BigInteger nonce; + //private final AtomicLong nonce = new AtomicLong(0); + + private BigInteger blockNonce; + //chan bool + private BlockingQueue newJobCh; + private volatile boolean running = false; + //private final AtomicBoolean running = new AtomicBoolean(false); + private volatile boolean newJob = false; + //private final AtomicBoolean newJob = new AtomicBoolean(false); + private String targetBlob; + + public static void main(String[] args) { + //Miner m = new Miner(); + //m.setCoin("alph"); + //m.setPool("192.168.3.79:21233"); + //m.setUser("alphtest"); + //m.setPassword("jxys123546"); + int count =1; + int start =0; + ExecutorService executor = Executors.newFixedThreadPool(count); // 创建一个固定大小的线程池 + for(int cnt=0;cnt < count;cnt++){ + int id = start+cnt; + System.out.println(id); + executor.execute(() -> { + Miner m = new Miner(); + m.setCoin("alph"); + m.setPool("10.168.2.167:21233"); + m.setUser("alphtest"); + m.setPassword("jxys123546"); + String newUser = m.user + "." + Convert.toStr(id); + m.setUser(newUser); + System.out.println(m.getUser()+"已创建线程"); + + m.newJobCh = new LinkedBlockingQueue<>(1); + + m.setNewJob(false); + try { + //String host, int port + //Socket socket = new Socket("192.168.3.79", + // Integer.parseInt(m.getPool().split(":")[1])); + Socket socket = new Socket("10.168.2.167",21233); + //Socket socket = new Socket("10.168.2.167",12973); + //Socket socket = new Socket("192.168.3.82",12973); + m.setConn(socket); + //System.out.println("miner:"+m); + //BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(m.getConn().getOutputStream())); + //System.out.println("writer:"+writer); + System.out.println("out:"+m.getConn().getOutputStream()); + //m.getConn().getOutputStream().write(m.subscribe().getBytes()); + m.getConn().getOutputStream().write(m.authorize().getBytes()); + m.getConn().getOutputStream().flush(); + + //InputStream reader = m.getConn().getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(m.getConn().getInputStream())); + String message; + while ((message = reader.readLine()) != null) { + System.out.println("Received: " + message); + Map json = m.mapper.readValue(message, Map.class); + if (json.containsKey("method")) { + m.handle(message,m.getConn()); + } else { + System.out.println("未解析到method属性:"+message); + } + } + } catch (IOException e) { + System.out.println(newUser+"连接矿池["+m.getPool()+"]失败!"); + System.out.println("------------------------------------原因如下------------------------------------"); + e.printStackTrace(); + System.out.println("------------------------------------分割线------------------------------------"); + } + + }); + } + } + + private String subscribe(){ + //this.setState("subscribe"); + state="subscribe"; + // fmt.Sprintf("{\"id\": %d, + // \"method\": \"mining.subscribe\", + // \"params\": [\"YxMiner/v20.0.0\"]}\n", m.Id) + return String.format("{\"id\": %d, \"method\": \"mining.subscribe\", \"params\": [\"YxMiner/v20.0.0\"]}\n",id); + } + + private String authorize(){ + state="authorize"; + // fmt.Sprintf( + // "{\"id\": %d, \"jsonrpc\": \"2.0\", \"method\": \"mining.authorize\", \"params\": [\"%s\"]}\n", + return String.format("{\"id\": %d, \"jsonrpc\": \"2.0\", \"method\": \"mining.authorize\", \"params\": [\"%s\"]}\n",id,user); + } + + private String submit(String nonce){ + state = "submit"; + if(StringUtils.isNull(extraNonce1)){ + extraNonce1=""; + } + String finalNonce = extraNonce1.concat(nonce); + //fmt.Sprintf( + // "{\"params\": [\"%s\", \"%s\", \"%s\"], \"id\": %d, \"method\": \"mining.submit\"}\n", + // m.User, + // m.JobId, + // final_nonce, + // m.Id, + return String.format("{\"params\": [\"%s\", \"%s\", \"%s\"], \"id\": %d, \"method\": \"mining.submit\"}\n", + user, + jobId, + finalNonce, + id); + + } + + private String pong(double pingId){ + BigInteger pongID = BigInteger.valueOf(Convert.toLong(pingId,0L)); + // + return String.format("{\"id\": %d, \"result\": \"pong\", \"error\": null}\n", + pongID); + } + + private byte[] hash(Miner m) throws DecoderException { + // 解码Header + //byte[] headerBytes = Hex.decodeHex(m.getHeader().toCharArray()); + ////自行生成40位? + //long nonce = m.getNonce(); + //m.setBlockNonce(m.getNonce()); + //byte[] nonceBytes = ByteBuffer.allocate(8) + // .order(ByteOrder.LITTLE_ENDIAN) + // .putLong(nonce) + // .array(); + //// 解码ExtraNonce1 + //byte[] extraBytes = null; + //extraBytes = Hex.decodeHex(m.getExtraNonce1().toCharArray()); + //byte[] inb = new byte[49]; + //System.arraycopy(headerBytes, 0, inb, 0, 32); // 前32字节 + //inb[32] = 0x10; // 固定分隔符 + //System.arraycopy(nonceBytes, 0, inb, 33, 8); // 33-40字节 + //System.arraycopy(extraBytes, 0, inb, 41, 8); // 41-48字节 + // + //m.setBlockData(Hex.encodeHexString(inb)); + + //header+nonce nonce是自行生成是长度为40的十六进制字符串转的bigint + byte[] bytes = new byte[20]; + new SecureRandom().nextBytes(bytes); + + // 转换为正数(BigInteger 自动处理符号) + BigInteger nonce = new BigInteger(1, bytes); + System.out.println("随机生成的nonce:"+String.format("%040x", nonce)); + System.out.println("(BigInt)nonce:"+nonce); + + m.setNonce(nonce); + m.blockNonce = m.nonce; + System.out.println("header "+m.getHeader()); + System.out.println("in "+m.getExtraNonce1().concat(String.format("%040x", nonce)).concat(m.getHeader())); + System.out.println("------------ in end ------------"); + //byte[] hashResult = hexStringToBytes(hashHandle(m.getExtraNonce1().concat(String.format("%040x", nonce)).concat(m.getHeader()))); + String s = hashHandle(m.getExtraNonce1().concat(String.format("%040x", nonce)).concat(m.getHeader())); + System.out.println("result:"+s); + return s.getBytes(); + } + + public BigInteger difficultyToTarget(double diff) throws ArithmeticException{ + if (diff <= 0) { + throw new ArithmeticException("Difficulty must be greater than zero"); + } + + // 创建大整数 2^224 + BigInteger twoTo224 = BigInteger.ONE.shiftLeft(224); + + // 使用 big.Float 进行高精度计算 + BigDecimal difficultyDecimal = new BigDecimal(diff); + + // 将 2^224 转换为 big.Float + BigDecimal twoTo224Decimal = new BigDecimal(twoTo224); + + // 计算目标值 = 2^224 / 难度 + try { + // 使用UNNECESSARY模式确保精确除法 + BigDecimal targetDecimal = twoTo224Decimal.divide( + difficultyDecimal, + new MathContext(0, RoundingMode.UNNECESSARY) + ); + + // 4. 转换为BigInteger + return targetDecimal.toBigIntegerExact(); + + } catch (ArithmeticException e) { + throw new ArithmeticException("Conversion resulted in inexact result: " + e.getMessage()); + } + } + + private void lookup(Miner m){ + // + System.out.println("Lookup start"); + m.running = true; + + while (m.running){ + try { + if(m.newJob){ + m.newJob = false; + //m.getNewJobCh().poll(10, TimeUnit.MILLISECONDS);. + m.getNewJobCh().poll(10, TimeUnit.MILLISECONDS); + //m.getNewJobCh().take();//阻塞直到有通知 + System.out.println("Lookup New job"); + } + byte[] hashBytes = hash(m); + m.nonce = m.nonce.add(BigInteger.ONE); + // 比较哈希难度 + BigInteger hashInt = new BigInteger(1, hashBytes); + System.out.println("hashInt "+hashInt); + //BigInteger diffInt = difficultyToTarget(m.difficulty); + BigInteger diffInt = new BigInteger(1,targetBlob.getBytes()); + System.out.println("diffInt "+diffInt); + + if (hashInt.compareTo(diffInt) <= 0) { + // 比较当前target(hashint)和下发的任务target (diffint) <=0代表 + + System.out.printf("calc diff %s\ntarget diff %s\n", + hashInt.toString(), diffInt.toString()); + System.out.println("block data " + m.blockData); + + // 准备提交数据 + ByteBuffer buffer = ByteBuffer.allocate(20) + .order(ByteOrder.LITTLE_ENDIAN) + .putLong(m.blockNonce.longValue()); + System.out.println(buffer); + //alph的submitData应该是40个字符 + String submitData = Hex.encodeHexString(buffer.array()); + + // 发送提交请求 + String response = submit(submitData); + System.out.println("submit " + response); + m.getConn().getOutputStream().write(response.getBytes()); + } + Thread.sleep(10); // 10毫秒间隔 + }catch (InterruptedException | IOException | DecoderException e){ + System.out.println("Error in lookup: " + e.getMessage()); + m.running = false; + } + + } + System.out.println("Lookup end"); + } + + public void handle(String msg, Socket conn){ + // + System.out.println("handle执行"); + this.conn = conn; + try { + Map ret = mapper.readValue(msg,Map.class); + if (ret.containsKey("method")){ + String method = Convert.toStr(ret.get("method")); + switch (method){ + case "mining.notify": + handleNotify(msg); + break; + case "mining.set_difficulty": + handleSetDifficulty(msg); + break; + case "mining.set_extranonce": + handleSetExtranoce(msg); + break; + case "mining.ping": + handlePing(msg); + break; + } + }else { + handleStatefulMessage(msg); + } + }catch (IOException e){ + System.out.println("JSON解析错误: " + e.getMessage()); + } + + } + static class NotifyMsgNexa { + public Object id; + public String method; + public List params; + } + //static class NotifyMsgParams { + // public String jobId; + // + // //jobId + // public int fromGroup; + // //header + // public int toGroup; + // //header + // public String headerBlob; + // public String txsBlob; + // public String targetBlob; + // public long height; + // public double diff; + // public String nonce; + // public String extranonce1; + // public long extranonce2_size; + //} + + + + public void handleNotify(String msg) throws IOException { + System.out.println("handleNotify执行"); + NotifyMsgNexa notify = mapper.readValue(msg, NotifyMsgNexa.class); + + if (running) { + newJob = true; + while (newJob) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + this.jobId = notify.params.get(0).getJobId(); + this.header = notify.params.get(0).getHeader(); + this.targetBlob = notify.params.get(0).getTarget(); + //this.nbits = (String) notify.params[2]; + //this.jobTime = Convert.toLong(notify.params[3],0l); + //是否要将解析到的diff、nonce、extranonce1赋值到矿机? + this.nonce = BigInteger.ZERO; + + if (!running) { + //System.out.println("jobId:"+this.jobId+",header:"+this.header); + System.out.println("this "+this+" |"); + this.lookup(this); + } else { + newJobCh.offer(true); + } + } + + private void handleSetDifficulty(String msg) throws IOException { + //stratum.Difficulty_msg stratum是第三方库"pool/internal/stratum" + System.out.println("setDiff msg:| "+msg+" |"); + DifficultyMsg diff = mapper.readValue(msg, DifficultyMsg.class); + System.out.println(diff); + //stratum + this.difficulty = diff.params[0]; + } + + private void handleSetExtranoce(String msg) throws IOException { + //stratum.Difficulty_msg stratum是第三方库"pool/internal/stratum" + ExtranonceMsg extranonce = mapper.readValue(msg, ExtranonceMsg.class); + + //stratum + this.extraNonce1 = extranonce.params[0]+"0000"; + System.out.println("extranonce"+extranonce+",miner extranonce:"+this.extraNonce1); + } + private void handlePing(String msg) throws IOException { + System.out.println("执行ping响应"); + System.out.println("ping msg:| "+msg+" |"); + PingMsg ping = mapper.readValue(msg, PingMsg.class); + String pong = pong(ping.id); + System.out.println(this.user+" pong:"+pong); + conn.getOutputStream().write(pong.getBytes()); + System.out.println("ping响应执行完毕"); + } + + private void handleStatefulMessage(String msg) throws IOException { + switch (state) { + case "subscribe": + SubscribeReply subscribeReply = mapper.readValue(msg, SubscribeReply.class); + this.extraNonce1 = (String) subscribeReply.result[1]; + this.extraNonce2Size = ((Number) subscribeReply.result[2]).intValue(); + this.id++; + conn.getOutputStream().write(authorize().getBytes()); + break; + + case "authorize": + AuthorizeReply authReply = mapper.readValue(msg, AuthorizeReply.class); + // 处理授权响应 + //authorize(); + break; + + case "submit": + SubmitAck submitAck = mapper.readValue(msg, SubmitAck.class); + // 处理提交确认 + //submit(nonce); + break; + } + } + + + + private String hashHandle(String in) { + //字符串判空 + if(StringUtils.isBlank(in)){ + return null; + } + Blake3 hasher = Blake3.initHash(); + byte[] hash = new byte[32]; + hasher.update(hexStringToBytes(in)); + hasher.doFinalize(hash); + String result1 = bytesToHexString(hash); + System.out.println(result1); + + hasher.reset(); + byte[] hash2 = new byte[32]; + hasher.update(hexStringToBytes(result1)); + hasher.doFinalize(hash2); + String result2 = bytesToHexString(hash2); + System.out.println(result2); + return result2; + } + + /** + * 高效写法 16进制字符串转成byte数组 + * + * @param hex 16进制字符串,支持大小写 + * @return byte数组 + */ + public byte[] hexStringToBytes(String hex) { + System.out.println("hex:"+hex+" |"); + System.out.println(hex.length() / 2); + byte[] result = new byte[hex.length() / 2]; + char[] chars = hex.toCharArray(); + for (int i = 0, j = 0; i < result.length; i++) { + result[i] = (byte) (toByte(chars[j++]) << 4 | toByte(chars[j++])); + } + return result; + } + + private int toByte(char c) { + if (c >= '0' && c <= '9') return (c - '0'); + if (c >= 'A' && c <= 'F') return (c - 'A' + 0x0A); + if (c >= 'a' && c <= 'f') return (c - 'a' + 0x0a); + throw new RuntimeException("invalid hex char '" + c + "'"); + } + + + /** + * 高效写法 byte数组转成16进制字符串 + * + * @param bytes byte数组 + * @return 16进制字符串 + */ + public String bytesToHexString(byte[] bytes) { + char[] buf = new char[bytes.length * 2]; + int c = 0; + for (byte b : bytes) { + buf[c++] = digits[(b >> 4) & 0x0F]; + buf[c++] = digits[b & 0x0F]; + } + return new String(buf); + } + + static class DifficultyMsg { + public int id; + public String method; + public double[] params; + } + + static class ExtranonceMsg { + public int id; + public String method; + public String[] params; + } + + static class PingMsg { + public int id; + public String method; + public String params; + } + + static class SubscribeReply { + public Object[] result; + } + + static class AuthorizeReply { + public boolean result; + } + + static class SubmitAck { + public boolean result; + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/NodeRpc.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/NodeRpc.java new file mode 100644 index 0000000..77ff401 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/NodeRpc.java @@ -0,0 +1,949 @@ +package com.m2pool.pool.utils; + + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.http.HttpRequest; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.nacos.shaded.org.checkerframework.checker.units.qual.A; +import com.m2pool.common.core.text.Convert; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.pool.entity.BlockInfo; +import com.m2pool.pool.enums.NodeConfig; +import com.m2pool.pool.enums.NodeConstant; +import org.apache.http.HttpHeaders; + +import java.math.BigDecimal; +import java.util.*; + +/** + * @Description 节点调用 + * @Date 2024/6/24 10:21 + * @Author 杜懿 + */ +public class NodeRpc{ + + public static String getResult(String coin,String method,String[] params) { + + NodeConfig config = (NodeConfig) EnumUtils.get(NodeConfig.class, coin); + + String uri ="http://"+config.getIp()+":"+config.getHost(); + + String auth =config.getUser()+":"+config.getPwd(); + + JSONObject requestBody = new JSONObject(); + requestBody.put("jsonrpc", "1.0"); + requestBody.put("id", "testnet"); + requestBody.put("method", method); + requestBody.put("params",params); + + try { + String body = HttpRequest + .post(uri) + .header("Authorization", "Basic " + Base64.getEncoder().encodeToString(auth.getBytes())) + .header("Content-Type","application/json") + .body(requestBody.toString()) + .timeout(3000) + .execute() + .body(); + + + JSONObject jsonObject = JSON.parseObject(body); + + + String result = jsonObject.getString("result"); + String error = jsonObject.getString("error"); + + if (StringUtils.isNull(result) && StringUtils.isNotBlank(error)){ + System.out.println("error:"+JSON.parseObject(error).getString("message")); + return "error:"+JSON.parseObject(error).getString("message"); + } + if(StringUtils.isNull(result) && StringUtils.isBlank(error)){ + System.out.println("result和error为空"); + return "error:connection failed"; + } + return result; + }catch (IORuntimeException e){ + return "error:connection failed by IORuntimeException" +e; + }catch (Exception e){ + return "error:connection failed by Exception"+e; + } + + } + + public static String getResultTest(String coin, String method, Object[] params) { + + NodeConfig config = (NodeConfig) EnumUtils.get(NodeConfig.class, coin); + + String uri ="http://"+config.getIp()+":"+config.getHost(); + + String auth =config.getUser()+":"+config.getPwd(); + + JSONObject requestBody = new JSONObject(); + requestBody.put("jsonrpc", "1.0"); + requestBody.put("id", "testnet"); + requestBody.put("method", method); + requestBody.put("params",params); + + try { + String body = HttpRequest + .post(uri) + .header("Authorization", "Basic " + Base64.getEncoder().encodeToString(auth.getBytes())) + .header("Content-Type","application/json") + .body(requestBody.toString()) + .timeout(3000) + .execute() + .body(); + + + JSONObject jsonObject = JSON.parseObject(body); + + + String result = jsonObject.getString("result"); + String error = jsonObject.getString("error"); + + if (StringUtils.isNull(result) && StringUtils.isNotBlank(error)){ + System.out.println("error:"+JSON.parseObject(error).getString("message")); + return "error:"+JSON.parseObject(error).getString("message"); + } + if(StringUtils.isNull(result) && StringUtils.isBlank(error)){ + System.out.println("result和error为空"); + return "error:connection failed"; + } + return result; + }catch (IORuntimeException e){ + return "error:connection failed by IORuntimeException" +e; + }catch (Exception e){ + return "error:connection failed by Exception"+e; + } + + } + + + public static boolean checkAddress(String coin,String address) { + //不同币种要单独设置验证方法 + String[] params = {address}; + String result = getResult(coin, "validateaddress", params); + //处理result + if(StringUtils.isBlank(result)){ + throw new RuntimeException("当前请求接口的用户过多,请稍后再试"); + } + if("error:connection failed".equals(result)){ + throw new RuntimeException("当前请求接口的用户过多,请稍后再试"); + } + if(result.contains("error:")){ + return false; + }else { + JSONObject jsonObject = JSON.parseObject(result); + Boolean isvalid = jsonObject.getBoolean("isvalid"); + return isvalid; + } + } + + public static String getHeight(String coin) { + String result = getResult(coin, "getblockcount",null); + //处理result + if(StringUtils.isBlank(result)){ + System.out.println(coin+"获取高度失败 result为空"); + throw new RuntimeException(); + } + if("error:connection failed".equals(result)){ + System.out.println(coin+"获取高度失败 连接失败"); + return "error:connection failed"; + } + + if(result.contains("error:")){ + System.out.println(coin+"获取高度失败"); + return "error: height get failed"; + }else { + try { + return result; + }catch (Exception e){ + return "error: height get failed"; + } + } + + } + + public static BlockInfo getBlock(String coin) { + + BlockInfo blockInfo = new BlockInfo(); + if(StringUtils.isBlank(coin)){ + return blockInfo; + } + coin = coin.toLowerCase(); + switch (coin){ + case "nexa": blockInfo=getNEXABlock();break; + case "grs": blockInfo=getGRSBlock();break; + case "mona": blockInfo=getMONABlock();break; + case "dgbo": blockInfo=getDGBBlock().get("dgbo");break; + case "dgbq": blockInfo=getDGBBlock().get("dgbq");break; + case "dgbs": blockInfo=getDGBBlock().get("dgbs");break; + case "rxd": blockInfo=getRXDBlock();break; + case "alph": blockInfo=getALPHBlock();break; + case "enx": blockInfo=getENXBlock();break; + default: break; + } + + return blockInfo; + } + + + public static BlockInfo getNEXABlock() { + String coin = "nexa"; + String nowHeight = getHeight(coin); + + if(nowHeight.contains("error:")){ + return null; + } + + String[] params = {nowHeight,"2"}; + String result = getResult(coin, "getblock", params); + + BlockInfo blockInfo = new BlockInfo(); + + if(StringUtils.isBlank(result)){ + System.out.println("查询结果为空"); + return blockInfo; + } + + if(result.contains("error:")){ + System.out.println("包含错误:"+result); + return blockInfo; + } + + JSONObject jsonObject = JSON.parseObject(result); + long height = jsonObject.getLongValue("height"); + blockInfo.setHeight(height); + + BigDecimal difficulty = jsonObject.getBigDecimal("difficulty"); + if(StringUtils.isNotNull(difficulty)){ + blockInfo.setDifficulty(difficulty.setScale(2,BigDecimal.ROUND_HALF_UP)); + NodeConstant constant = (NodeConstant) EnumUtils.get(NodeConstant.class, coin); + BigDecimal factor = constant.getFactor(); + if(StringUtils.isNotNull(factor)){ + blockInfo.setPower(difficulty.multiply(factor).setScale(2,BigDecimal.ROUND_HALF_UP)); + } + + } + + JSONArray txs = jsonObject.getJSONArray("tx"); + if(StringUtils.isNull(txs)){ + return null; + } + //System.out.println(txs); + final double[] reward = {0}; + final double[] fees = {0}; + txs.iterator().forEachRemaining(e -> { + JSONObject json = (JSONObject) e; + String vin = json.getString("vin"); + if(StringUtils.isNotNull(vin)){ + if(vin.length() == 2){ + reward[0] = json.getDoubleValue("sends"); + }else if(vin.length() > 2){ + BigDecimal fee = json.getBigDecimal("fee"); + BigDecimal newFees = fee.add(BigDecimal.valueOf(fees[0])); + fees[0] = newFees.doubleValue(); + } + } + }); + + blockInfo.setReward(BigDecimal.valueOf(reward[0])); + blockInfo.setFees(BigDecimal.valueOf(fees[0])); + blockInfo.setProfit(blockInfo.getReward().subtract(blockInfo.getFees())); + return blockInfo; + } + + public static BlockInfo getGRSBlock() { + String coin = "grs"; + String nowHeight = getHeight(coin); + + if(nowHeight.contains("error:")){ + return null; + } + + BlockInfo blockInfo = new BlockInfo(); + //todo 调用getblockstats 拿subsidy/100000000做reward fees设置为0、拿blockhash + Object[] statsParams = {Convert.toLong(nowHeight)}; + String blockstats = getResultTest(coin, "getblockstats", statsParams); + if(StringUtils.isBlank(blockstats)){ + System.out.println("blockstats查询结果为空"); + return blockInfo; + } + + if(blockstats.contains("error:")){ + System.out.println("blockstats包含错误:"+blockstats); + return blockInfo; + } + + JSONObject statsJs = JSON.parseObject(blockstats); + String blockhash = statsJs.getString("blockhash"); + if(StringUtils.isBlank(blockstats)){ + System.out.println("blockhash获取结果为空"); + return blockInfo; + } + System.out.println(blockhash); + + + BigDecimal subsidy = statsJs.getBigDecimal("subsidy"); + if(StringUtils.isNull(subsidy)) { + System.out.println("subsidy获取结果为空"); + return blockInfo; + } + subsidy = subsidy.divide(BigDecimal.valueOf(Math.pow(10,8)),4,BigDecimal.ROUND_HALF_UP); + System.out.println(subsidy); + Object[] params = {blockhash}; + + String result = getResultTest(coin, "getblockheader", params); + + + + if(StringUtils.isBlank(result)){ + System.out.println("查询结果为空"); + return blockInfo; + } + + if(result.contains("error:")){ + System.out.println("包含错误:"+result); + return blockInfo; + } + + JSONObject jsonObject = JSON.parseObject(result); + long height = jsonObject.getLongValue("height"); + blockInfo.setHeight(height); + + BigDecimal difficulty = jsonObject.getBigDecimal("difficulty"); + if(StringUtils.isNotNull(difficulty)){ + blockInfo.setDifficulty(difficulty.setScale(2,BigDecimal.ROUND_HALF_UP)); + NodeConstant constant = (NodeConstant) EnumUtils.get(NodeConstant.class, coin); + BigDecimal factor = constant.getFactor(); + if(StringUtils.isNotNull(factor)){ + blockInfo.setPower(difficulty.multiply(factor).setScale(2,BigDecimal.ROUND_HALF_UP)); + } + + } + + blockInfo.setReward(subsidy); + blockInfo.setFees(BigDecimal.valueOf(0)); + blockInfo.setProfit(blockInfo.getReward().subtract(blockInfo.getFees())); + return blockInfo; + } + + public static BlockInfo getMONABlock() { + String coin = "mona"; + String nowHeight = getHeight(coin); + + if(nowHeight.contains("error:")){ + return null; + } + + BlockInfo blockInfo = new BlockInfo(); + //todo 调用getblockstats 拿subsidy/100000000做reward fees设置为0、拿blockhash + Object[] statsParams = {Convert.toLong(nowHeight)}; + String blockstats = getResultTest(coin, "getblockstats", statsParams); + if(StringUtils.isBlank(blockstats)){ + System.out.println("blockstats查询结果为空"); + return blockInfo; + } + + if(blockstats.contains("error:")){ + System.out.println("blockstats包含错误:"+blockstats); + return blockInfo; + } + + JSONObject statsJs = JSON.parseObject(blockstats); + String blockhash = statsJs.getString("blockhash"); + if(StringUtils.isBlank(blockstats)){ + System.out.println("blockhash获取结果为空"); + return blockInfo; + } + System.out.println(blockhash); + + + BigDecimal subsidy = statsJs.getBigDecimal("subsidy"); + if(StringUtils.isNull(subsidy)) { + System.out.println("subsidy获取结果为空"); + return blockInfo; + } + subsidy = subsidy.divide(BigDecimal.valueOf(Math.pow(10,8)),4,BigDecimal.ROUND_HALF_UP); + System.out.println(subsidy); + Object[] params = {blockhash}; + + String result = getResultTest(coin, "getblockheader", params); + System.out.println("result"+result); + + + + if(StringUtils.isBlank(result)){ + System.out.println("查询结果为空"); + return blockInfo; + } + + if(result.contains("error:")){ + System.out.println("包含错误:"+result); + return blockInfo; + } + + JSONObject jsonObject = JSON.parseObject(result); + long height = jsonObject.getLongValue("height"); + blockInfo.setHeight(height); + + BigDecimal difficulty = jsonObject.getBigDecimal("difficulty"); + if(StringUtils.isNotNull(difficulty)){ + blockInfo.setDifficulty(difficulty.setScale(2,BigDecimal.ROUND_HALF_UP)); + NodeConstant constant = (NodeConstant) EnumUtils.get(NodeConstant.class, coin); + BigDecimal factor = constant.getFactor(); + if(StringUtils.isNotNull(factor)){ + blockInfo.setPower(difficulty.multiply(factor).setScale(2,BigDecimal.ROUND_HALF_UP)); + } + + } + + blockInfo.setReward(subsidy); + blockInfo.setFees(BigDecimal.valueOf(0)); + blockInfo.setProfit(blockInfo.getReward().subtract(blockInfo.getFees())); + return blockInfo; + } + + public static BlockInfo getRXDBlock() { + String coin = "rxd"; + String nowHeight = getHeight(coin); + + if(nowHeight.contains("error:")){ + return null; + } + + BlockInfo blockInfo = new BlockInfo(); + //todo 调用getblockstats 拿subsidy/100000000做reward fees设置为0、拿blockhash + Object[] statsParams = {Convert.toLong(nowHeight)}; + String blockstats = getResultTest(coin, "getblockstats", statsParams); + if(StringUtils.isBlank(blockstats)){ + System.out.println("blockstats查询结果为空"); + return blockInfo; + } + + if(blockstats.contains("error:")){ + System.out.println("blockstats包含错误:"+blockstats); + return blockInfo; + } + + JSONObject statsJs = JSON.parseObject(blockstats); + String blockhash = statsJs.getString("blockhash"); + if(StringUtils.isBlank(blockstats)){ + System.out.println("blockhash获取结果为空"); + return blockInfo; + } + System.out.println(blockhash); + + + BigDecimal subsidy = statsJs.getBigDecimal("subsidy"); + if(StringUtils.isNull(subsidy)) { + System.out.println("subsidy获取结果为空"); + return blockInfo; + } + subsidy = subsidy.divide(BigDecimal.valueOf(Math.pow(10,8)),4,BigDecimal.ROUND_HALF_UP); + System.out.println(subsidy); + Object[] params = {blockhash}; + + String result = getResultTest(coin, "getblockheader", params); + System.out.println("result"+result); + + + + if(StringUtils.isBlank(result)){ + System.out.println("查询结果为空"); + return blockInfo; + } + + if(result.contains("error:")){ + System.out.println("包含错误:"+result); + return blockInfo; + } + + JSONObject jsonObject = JSON.parseObject(result); + long height = jsonObject.getLongValue("height"); + blockInfo.setHeight(height); + + BigDecimal difficulty = jsonObject.getBigDecimal("difficulty"); + if(StringUtils.isNotNull(difficulty)){ + blockInfo.setDifficulty(difficulty.setScale(2,BigDecimal.ROUND_HALF_UP)); + NodeConstant constant = (NodeConstant) EnumUtils.get(NodeConstant.class, coin); + BigDecimal factor = constant.getFactor(); + if(StringUtils.isNotNull(factor)){ + blockInfo.setPower(difficulty.multiply(factor).setScale(2,BigDecimal.ROUND_HALF_UP)); + } + + } + + blockInfo.setReward(subsidy); + blockInfo.setFees(BigDecimal.valueOf(0)); + blockInfo.setProfit(blockInfo.getReward().subtract(blockInfo.getFees())); + return blockInfo; + } + + public static BlockInfo getALPHBlock() { + String coin = "alph"; + //拿当前高度 + NodeConfig config = (NodeConfig) EnumUtils.get(NodeConfig.class, coin); + + String uri ="http://"+config.getIp()+":"+config.getHost(); + + try { + String heightUri = uri + "/blockflow/chain-info?fromGroup=0&toGroup=0"; + String heightBody = HttpRequest + .get(heightUri) + .header("X-API-KEY", "0x09e220e226f2feb7a971a2b6f958e7d4b1c187c8") + .header("Content-Type","application/json") + //.body(requestBody.toString()) + .timeout(3000) + .execute() + .body(); + + JSONObject heightJsonObject = JSON.parseObject(heightBody); + System.out.println("alph 高度api请求结果:|"+heightBody+"|"); + + long height = heightJsonObject.getLong("currentHeight"); + String heightError = heightJsonObject.getString("detail"); + + if (StringUtils.isNull(height) && StringUtils.isNotBlank(heightError)){ + System.out.println("高度api调用 error:"+heightError); + return null; + } + if(StringUtils.isNull(height) && StringUtils.isBlank(heightError)){ + System.out.println("height和error为空"); + return null; + } + + BlockInfo blockInfo = new BlockInfo(); + blockInfo.setHeight(height); + + //根据高度拿到hash + String hashUri = uri + "/blockflow/hashes?fromGroup=0&toGroup=0&height="+height; + String hashBody = HttpRequest + .get(hashUri) + .header("X-API-KEY", "0x09e220e226f2feb7a971a2b6f958e7d4b1c187c8") + .header("Content-Type","application/json") + //.body(requestBody.toString()) + .timeout(3000) + .execute() + .body(); + + System.out.println("alph 哈希api请求结果:|"+hashBody+"|"); + JSONObject hashJsonObject = JSON.parseObject(hashBody); + List hashes = hashJsonObject.getJSONArray("headers").toJavaList(String.class); + String hashError = hashJsonObject.getString("detail"); + if (StringUtils.isNull(hashes) && StringUtils.isNotBlank(hashError)){ + System.out.println("hashApi调用 error:"+hashError); + return null; + } + if(StringUtils.isNull(hashes) && StringUtils.isBlank(hashError)){ + System.out.println("hashApi调用 未通过hashes获取到数据"); + return null; + } + + String mainHash = ""; + for (String hash : hashes){ + //对每一个hash 请求is-block-in-main-chain 找出为true的hash 其他忽略 + String checkUri = uri + "/blockflow/is-block-in-main-chain?blockHash="+hash; + String checkBody = HttpRequest + .get(checkUri) + .header("X-API-KEY", "0x09e220e226f2feb7a971a2b6f958e7d4b1c187c8") + .header("Content-Type","application/json") + //.body(requestBody.toString()) + .timeout(3000) + .execute() + .body(); + System.out.println("alph 哈希校验api请求结果:|"+checkBody+"|"); + System.out.println("checkBody"+checkBody); + if("true".equals(checkBody)){ + mainHash = hash; + break; + } + } + + if(StringUtils.isBlank(mainHash)){ + return null; + } + System.out.println("mainHash:"+mainHash); + //通过hash获取当前全网算力current-hashrate、获取当前全网难度current-difficulty + String hashRateUri = uri + "/infos/current-hashrate"; + String hashRateBody = HttpRequest + .get(hashRateUri) + .header("X-API-KEY", "0x09e220e226f2feb7a971a2b6f958e7d4b1c187c8") + .header("Content-Type","application/json") + //.body(requestBody.toString()) + .timeout(3000) + .execute() + .body(); + + JSONObject hashRateJsonObject = JSON.parseObject(hashRateBody); + System.out.println("alph 当前算力api请求结果:|"+hashRateJsonObject+"|"); + String hashRateStr = hashRateJsonObject.getString("hashrate"); + String hashRateError = hashRateJsonObject.getString("detail"); + + if (StringUtils.isNull(hashRateStr) && StringUtils.isNotBlank(hashRateError)){ + System.out.println("当前全网算力APi调用 error:"+hashRateError); + return null; + } + if(StringUtils.isNull(hashRateStr) && StringUtils.isBlank(hashRateError)){ + System.out.println("当前全网算力APi调用 未通过hashes获取到数据"); + return null; + } + BigDecimal hashrate= BigDecimal.ONE; + if(hashRateStr.toUpperCase().contains("kh/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("KH/s",""). + replace(" ","")). + multiply(BigDecimal.valueOf(1000)); + }else if(hashRateStr.toLowerCase().contains("mh/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("MH/s",""). + replace(" ","")). + multiply(BigDecimal.valueOf(1000*1000)); + }else if(hashRateStr.toLowerCase().contains("gh/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("GH/s",""). + replace(" ","")). + multiply(BigDecimal.valueOf(1000*1000)). + multiply(BigDecimal.valueOf(1000)); + }else if(hashRateStr.toLowerCase().contains("th/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("TH/s",""). + replace(" ","")). + multiply(BigDecimal.valueOf(1000*1000)). + multiply(BigDecimal.valueOf(1000*1000)); + }else if(hashRateStr.toLowerCase().contains("ph/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("PH/s",""). + replace(" ","")). + multiply(BigDecimal.valueOf(1000*1000)). + multiply(BigDecimal.valueOf(1000*1000)). + multiply(BigDecimal.valueOf(1000)); + }else if(hashRateStr.toLowerCase().contains("h/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("H/s",""). + replace(" ","")); + }else { + // + } + + blockInfo.setPower(hashrate); + + String diffUri = uri + "/infos/current-difficulty"; + String diffBody = HttpRequest + .get(diffUri) + .header("X-API-KEY", "0x09e220e226f2feb7a971a2b6f958e7d4b1c187c8") + .header("Content-Type","application/json") + //.body(requestBody.toString()) + .timeout(3000) + .execute() + .body(); + + JSONObject diffJsonObject = JSON.parseObject(diffBody); + System.out.println("alph 当前难度api请求结果:|"+diffJsonObject+"|"); + BigDecimal diff = diffJsonObject.getBigDecimal("difficulty"); + String diffError = diffJsonObject.getString("detail"); + + if (StringUtils.isNull(diff) && StringUtils.isNotBlank(diffError)){ + System.out.println("当前全网难度APi调用 error:"+diffError); + return null; + } + if(StringUtils.isNull(diff) && StringUtils.isBlank(diffError)){ + System.out.println("当前全网难度APi调用 未通过hashes获取到数据"); + return null; + } + blockInfo.setDifficulty(diff); + + // + String blockUri = uri + "/blockflow/blocks/"+mainHash; + String blockBody = HttpRequest + .get(blockUri) + .header("X-API-KEY", "0x09e220e226f2feb7a971a2b6f958e7d4b1c187c8") + .header("Content-Type","application/json") + //.body(requestBody.toString()) + .timeout(3000) + .execute() + .body(); + + JSONObject blockJsonObject = JSON.parseObject(blockBody); + //transactions[]的最后一个对象 判断inputs为空 再拿fixedOutputs中的attoAlphAmount/(10*8)作为reword + System.out.println("alph 区块信息api请求结果:|"+hashRateJsonObject+"|"); + JSONArray transactions = blockJsonObject.getJSONArray("transactions"); + System.out.println("transactions|"+transactions+"|"); + JSONObject lastTransactionJsonObject = transactions.getJSONObject(transactions.size() - 1); + JSONObject unsigned = lastTransactionJsonObject.getJSONObject("unsigned"); + System.out.println("unsigned|"+unsigned+"|"); + + + JSONArray inputs = unsigned.getJSONArray("inputs"); + + if (inputs.size() == 0){ + System.out.println("为空"); + + //inputs是空数组才能说明是报块奖励 而不是转账 + JSONArray fixedOutputs = unsigned.getJSONArray("fixedOutputs"); + JSONObject fixedOutput = fixedOutputs.getJSONObject(0); + BigDecimal attoAlphAmount = fixedOutput.getBigDecimal("attoAlphAmount"); + BigDecimal reword = attoAlphAmount.divide(BigDecimal.valueOf(1000*1000),6,BigDecimal.ROUND_HALF_UP) + .divide(BigDecimal.valueOf(1000*1000),8,BigDecimal.ROUND_HALF_UP) + .divide(BigDecimal.valueOf(1000*1000),8,BigDecimal.ROUND_HALF_UP); + blockInfo.setReward(reword); + blockInfo.setFees(BigDecimal.ZERO); + blockInfo.setProfit(reword); + } + + return blockInfo; + }catch (IORuntimeException e){ + System.out.println("alph IO异常:"+e.getMessage()); + return null; + }catch (Exception e){ + System.out.println("alph 抛出异常:"+e); + return null; + } + + //BigDecimal difficulty = jsonObject.getBigDecimal("difficulty"); + //if(StringUtils.isNotNull(difficulty)){ + // blockInfo.setDifficulty(difficulty.setScale(2,BigDecimal.ROUND_HALF_UP)); + // NodeConstant constant = (NodeConstant) EnumUtils.get(NodeConstant.class, coin); + // BigDecimal factor = constant.getFactor(); + // if(StringUtils.isNotNull(factor)){ + // blockInfo.setPower(difficulty.multiply(factor).setScale(2,BigDecimal.ROUND_HALF_UP)); + // } + // + //} + // + //blockInfo.setReward(subsidy); + //blockInfo.setFees(BigDecimal.valueOf(0)); + //blockInfo.setProfit(blockInfo.getReward().subtract(blockInfo.getFees())); + //return blockInfo; + } + + + public static Map getDGBBlock() { + //dgb(qubit)、dgb(skein)、dgb(odo)实质都是dgb 高度始终一致、奖励也是 + String coin = "dgbq"; + HashMap map = new HashMap<>(); + String nowHeight = getHeight(coin); + + if(nowHeight.contains("error:")){ + System.out.println("nowHeight未正常返回"); + return null; + } + + + //todo 调用getblockstats 拿subsidy/100000000做reward fees设置为0、拿blockhash + Object[] statsParams = {Convert.toLong(nowHeight)}; + String blockstats = getResultTest(coin, "getblockstats", statsParams); + if(StringUtils.isBlank(blockstats)){ + System.out.println("blockstats查询结果为空"); + return null; + } + + if(blockstats.contains("error:")){ + System.out.println("blockstats包含错误:"+blockstats); + return null; + } + + JSONObject statsJs = JSON.parseObject(blockstats); + BigDecimal subsidy = statsJs.getBigDecimal("subsidy"); + if(StringUtils.isNull(subsidy)) { + System.out.println("subsidy获取结果为空"); + return null; + } + subsidy = subsidy.divide(BigDecimal.valueOf(Math.pow(10,8)),4,BigDecimal.ROUND_HALF_UP); + System.out.println(subsidy); + Object[] params = {}; + + String result = getResultTest(coin, "getblockchaininfo", params); + System.out.println("result"+result); + + + + if(StringUtils.isBlank(result)){ + System.out.println("查询结果为空"); + return null; + } + + if(result.contains("error:")){ + System.out.println("包含错误:"+result); + return null; + } + + JSONObject jsonObject = JSON.parseObject(result); + JSONObject difficulties = jsonObject.getJSONObject("difficulties"); + if(StringUtils.isNull(difficulties)){ + System.out.println("查询结果为空"); + return null; + } + System.out.println("difficulties:"+difficulties); + BigDecimal sDiff = difficulties.getBigDecimal("skein"); + if(StringUtils.isNotNull(sDiff)){ + BlockInfo sBlockInfo = new BlockInfo(); + sBlockInfo.setHeight(Convert.toLong(nowHeight,20237944L)); + sBlockInfo.setReward(subsidy); + sBlockInfo.setFees(BigDecimal.ZERO); + sBlockInfo.setProfit(sBlockInfo.getReward().subtract(sBlockInfo.getFees())); + sBlockInfo.setDifficulty(sDiff.setScale(2,BigDecimal.ROUND_HALF_UP)); + NodeConstant constant = (NodeConstant) EnumUtils.get(NodeConstant.class, "dgbs"); + BigDecimal factor = constant.getFactor(); + if(StringUtils.isNotNull(factor)){ + sBlockInfo.setPower(sDiff.multiply(factor).setScale(2,BigDecimal.ROUND_HALF_UP)); + } + map.put("dgbs",sBlockInfo); + } + + BigDecimal oDiff = difficulties.getBigDecimal("odo"); + if(StringUtils.isNotNull(oDiff)){ + BlockInfo oBlockInfo = new BlockInfo(); + oBlockInfo.setHeight(Convert.toLong(nowHeight,20237944L)); + oBlockInfo.setReward(subsidy); + oBlockInfo.setFees(BigDecimal.ZERO); + oBlockInfo.setProfit(oBlockInfo.getReward().subtract(oBlockInfo.getFees())); + oBlockInfo.setDifficulty(oDiff.setScale(2,BigDecimal.ROUND_HALF_UP)); + NodeConstant constant = (NodeConstant) EnumUtils.get(NodeConstant.class, "dgbo"); + BigDecimal factor = constant.getFactor(); + if(StringUtils.isNotNull(factor)){ + oBlockInfo.setPower(oDiff.multiply(factor).setScale(2,BigDecimal.ROUND_HALF_UP)); + } + map.put("dgbo",oBlockInfo); + } + + BigDecimal qDiff = difficulties.getBigDecimal("qubit"); + if(StringUtils.isNotNull(qDiff)){ + BlockInfo qBlockInfo = new BlockInfo(); + qBlockInfo.setHeight(Convert.toLong(nowHeight,20237944L)); + qBlockInfo.setReward(subsidy); + qBlockInfo.setFees(BigDecimal.ZERO); + qBlockInfo.setProfit(qBlockInfo.getReward().subtract(qBlockInfo.getFees())); + qBlockInfo.setDifficulty(qDiff.setScale(2,BigDecimal.ROUND_HALF_UP)); + NodeConstant constant = (NodeConstant) EnumUtils.get(NodeConstant.class, "dgbq"); + BigDecimal factor = constant.getFactor(); + if(StringUtils.isNotNull(factor)){ + qBlockInfo.setPower(qDiff.multiply(factor).setScale(2,BigDecimal.ROUND_HALF_UP)); + } + map.put("dgbq",qBlockInfo); + } + + System.out.println("map:"+map); + + return map; + } + + + public static BlockInfo getENXBlock() { + String coin = "enx"; + //报块信息 + String uri = "https://api.entropyx.org/api/info/blockdag"; + + //get方法拿reward + String rewardUri = "https://api.entropyx.org/api/info/blockreward"; + + BlockInfo blockInfo = new BlockInfo(); + + //get方法拿fee + String feeUri = "https://api.entropyx.org/api/burn/get-current-burn-fee"; + blockInfo.setReward(BigDecimal.valueOf(333)); + try { + String body = HttpRequest + .get(uri) + .header(HttpHeaders.ACCEPT,"application/json") + .timeout(3000) + .execute() + .body(); + + JSONObject jsonObject = JSON.parseObject(body); + if (StringUtils.isNull(jsonObject)){ + return blockInfo; + } + BigDecimal reward = jsonObject.getBigDecimal("blockreward"); + blockInfo.setReward(reward); + } catch (Exception e) { + System.out.println("Error: cannont access content - " + e.toString()); + return blockInfo; + } + + + //todo 调用getblockstats 拿subsidy/100000000做reward fees设置为0、拿blockhash + //Object[] statsParams = {Convert.toLong(nowHeight)}; + //String blockstats = getResultTest(coin, "getblockstats", statsParams); + //if(StringUtils.isBlank(blockstats)){ + // System.out.println("blockstats查询结果为空"); + // return blockInfo; + //} + // + //if(blockstats.contains("error:")){ + // System.out.println("blockstats包含错误:"+blockstats); + // return blockInfo; + //} + // + //JSONObject statsJs = JSON.parseObject(blockstats); + //String blockhash = statsJs.getString("blockhash"); + //if(StringUtils.isBlank(blockstats)){ + // System.out.println("blockhash获取结果为空"); + // return blockInfo; + //} + //System.out.println(blockhash); + // + // + //BigDecimal subsidy = statsJs.getBigDecimal("subsidy"); + //if(StringUtils.isNull(subsidy)) { + // System.out.println("subsidy获取结果为空"); + // return blockInfo; + //} + //subsidy = subsidy.divide(BigDecimal.valueOf(Math.pow(10,8)),4,BigDecimal.ROUND_HALF_UP); + //System.out.println(subsidy); + //Object[] params = {blockhash}; + // + //String result = getResultTest(coin, "getblockheader", params); + //System.out.println("result"+result); + // + // + // + //if(StringUtils.isBlank(result)){ + // System.out.println("查询结果为空"); + // return blockInfo; + //} + // + //if(result.contains("error:")){ + // System.out.println("包含错误:"+result); + // return blockInfo; + //} + // + //JSONObject jsonObject = JSON.parseObject(result); + //long height = jsonObject.getLongValue("height"); + //blockInfo.setHeight(height); + // + //BigDecimal difficulty = jsonObject.getBigDecimal("difficulty"); + //if(StringUtils.isNotNull(difficulty)){ + // blockInfo.setDifficulty(difficulty.setScale(2,BigDecimal.ROUND_HALF_UP)); + // NodeConstant constant = (NodeConstant) EnumUtils.get(NodeConstant.class, coin); + // BigDecimal factor = constant.getFactor(); + // if(StringUtils.isNotNull(factor)){ + // blockInfo.setPower(difficulty.multiply(factor).setScale(2,BigDecimal.ROUND_HALF_UP)); + // } + // + //} + // + //blockInfo.setReward(subsidy); + //blockInfo.setFees(BigDecimal.valueOf(0)); + //blockInfo.setProfit(blockInfo.getReward().subtract(blockInfo.getFees())); + //return blockInfo; + return blockInfo; + } + + + +} + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/PowerUnitUtils.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/PowerUnitUtils.java new file mode 100644 index 0000000..047dc1a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/PowerUnitUtils.java @@ -0,0 +1,78 @@ +package com.m2pool.pool.utils; + +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.pool.enums.PoolProfitScale; + +import java.math.BigDecimal; + +/** + * @Description 算力转换 + * @Date 2024/6/13 10:54 + * @Author dy + */ +public class PowerUnitUtils { + + /** + * 算力数值转换 1000转 1KH/s 1000*1000转1MH/s 1000*1000*1000转1GH/s 1000*1000*1000*10转10TH/s + * @param value + * @return + */ + public static String powerFormat(BigDecimal value, String coin){ + + if(StringUtils.isNull(value)){ + return "0 H/s"; + } + + int scale = PoolProfitScale.getScaleByCoin(coin); + //ps BigDecimal最大上限少于1000*1000*1000*1000*10 所以需要中间值 gValue + BigDecimal gValue = value.divide(BigDecimal.valueOf(1000000000), scale, BigDecimal.ROUND_HALF_UP); + if (gValue.compareTo(BigDecimal.valueOf(1000*1000*1000)) >= 0){ + //1000*1000*1000G以上 转换为E + return gValue.divide(BigDecimal.valueOf(1000000000),scale,BigDecimal.ROUND_HALF_UP).toEngineeringString() + " EH/s"; + } + else if (gValue.compareTo(BigDecimal.valueOf(1000*1000)) >= 0){ + //1000*1000G以上 1000*1000*1000G以下 转换为P + return gValue.divide(BigDecimal.valueOf(1000000),scale,BigDecimal.ROUND_HALF_UP).toEngineeringString() + " PH/s"; + } + else if (gValue.compareTo(BigDecimal.valueOf(10000)) >= 0){ + //10000G 以上 1000*1000G一下 则转为T 即10T以上才转换 + return gValue.divide(BigDecimal.valueOf(1000),scale,BigDecimal.ROUND_HALF_UP).toEngineeringString() + " TH/s"; + } + else if (value.compareTo(BigDecimal.valueOf(1000*1000*1000)) >= 0){ + //1G 1G以上 10T以下 转换为G + return gValue.setScale(scale,BigDecimal.ROUND_HALF_UP).toEngineeringString() + " GH/s"; + } + else if (value.compareTo(BigDecimal.valueOf(1000*1000)) >= 0){ + //1M 1M以上 1G一下则转换为M + return value.divide(BigDecimal.valueOf(1000*1000), scale,BigDecimal.ROUND_HALF_UP).toEngineeringString() + " MH/s"; + } + else if (value.compareTo(BigDecimal.valueOf(1000)) >= 0){ + //1K 1K以上 1M一下则转换为K + return value.divide(BigDecimal.valueOf(1000), scale,BigDecimal.ROUND_HALF_UP).toEngineeringString() + " KH/s"; + } + else { + return value.setScale(scale,BigDecimal.ROUND_HALF_UP).toEngineeringString() + " H/s"; + } + } + + /** + * 难度数值转换 1000转 1K 1000*1000转1M 1000*1000*1000转1G + * @param value + * @return + */ + public static String difficultyFormat(BigDecimal value){ + + if(StringUtils.isNull(value)){ + return "0"; + } + if (value.compareTo(BigDecimal.valueOf(1000*1000*1000)) >= 0){ + return value.divide(BigDecimal.valueOf(1000*1000*1000),2,BigDecimal.ROUND_HALF_UP).toEngineeringString()+"G"; + }else if (value.compareTo(BigDecimal.valueOf(1000*1000)) >= 0){ + return value.divide(BigDecimal.valueOf(1000*1000),2,BigDecimal.ROUND_HALF_UP).toEngineeringString()+"M"; + }else if (value.compareTo(BigDecimal.valueOf(1000)) >= 0){ + return value.divide(BigDecimal.valueOf(1000),2,BigDecimal.ROUND_HALF_UP).toEngineeringString()+"K"; + }else { + return value.setScale(2,BigDecimal.ROUND_HALF_UP).toEngineeringString(); + } + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/QrCodeUtils.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/QrCodeUtils.java new file mode 100644 index 0000000..8f8ace8 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/QrCodeUtils.java @@ -0,0 +1,89 @@ +package com.m2pool.pool.utils; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import org.apache.commons.codec.binary.Base64; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Hashtable; + +/** + * @Description URL转Base64二维码 + * @Date 2024/8/21 11:18 + * @Author 杜懿 + */ +public class QrCodeUtils { + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static String creatRrCode(String contents, int width, int height) { + String base64 = ""; + + Hashtable hints = new Hashtable(); + hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); + hints.put(EncodeHintType.MARGIN, 1); + + try { + BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, width, height, hints); + bitMatrix = deleteWhite(bitMatrix); + + // 1、读取文件转换为字节数组 + ByteArrayOutputStream out = new ByteArrayOutputStream(); + BufferedImage image = toBufferedImage(bitMatrix); + + //转换成png格式的IO流 + ImageIO.write(image, "png", out); + byte[] bytes = out.toByteArray(); + + // 2、将字节数组转为二进制 + base64 = Base64.encodeBase64String(bytes).trim(); + + } catch (WriterException e) { + e.printStackTrace(); + + } catch (IOException e) { + e.printStackTrace(); + } + + return base64; + } + + /** + * image流数据处理 + */ + private static BufferedImage toBufferedImage(BitMatrix matrix) { + int width = matrix.getWidth(); + int height = matrix.getHeight(); + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + image.setRGB(x, y, matrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF); + } + } + return image; + } + + /** + * 去白边 + */ + private static BitMatrix deleteWhite(BitMatrix matrix) { + int[] rec = matrix.getEnclosingRectangle(); + int resWidth = rec[2] + 1; + int resHeight = rec[3] + 1; + + BitMatrix resMatrix = new BitMatrix(resWidth, resHeight); + resMatrix.clear(); + for (int i = 0; i < resWidth; i++) { + for (int j = 0; j < resHeight; j++) { + if (matrix.get(i + rec[0], j + rec[1])) + resMatrix.set(i, j); + } + } + return resMatrix; + } + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/SocketDemo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/SocketDemo.java new file mode 100644 index 0000000..df5a3d3 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/SocketDemo.java @@ -0,0 +1,24 @@ +package com.m2pool.pool.utils; + +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + * @Description TODO + * @Date 2025/4/3 10:21 + * @Author 杜懿 + */ +public class SocketDemo { + + public static void main(String[] args) { + try { + //Socket socket = new Socket("10.168.2.167",21233); + Socket socket = new Socket("10.168.2.167",12973); + }catch (UnknownHostException e){ + System.out.println("无法找到主机: "+e.getMessage()); + } catch (IOException e) { + System.out.println("连接失败: " + e.getMessage()); + } + } +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/TempDto.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/TempDto.java new file mode 100644 index 0000000..6b4cf87 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/TempDto.java @@ -0,0 +1,24 @@ +package com.m2pool.pool.utils; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; + +/** + * @Description TODO + * @Date 2024/9/4 11:48 + * @Author 杜懿 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TempDto { + + @Email(message = "邮箱格式错误") + private String email; + + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/TempUse b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/TempUse new file mode 100644 index 0000000..e69de29 diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Test.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Test.java new file mode 100644 index 0000000..2eedecf --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/utils/Test.java @@ -0,0 +1,186 @@ +package com.m2pool.pool.utils; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.pool.enums.NodeConstant; +import com.m2pool.system.api.RemoteUserService; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + + +/** + * @Description TODO + * @Date 2024/6/24 10:21 + * @Author 杜懿 + */ +public class Test { + + + + private static String COINMARKETCAP_KEY="ba4fc555-e8b3-4bb2-aa91-bfa81044e8bf"; + public static String PWD_REGEX="^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$%^&*)(_+}{|:?><]).{8,32}$"; + public static void main(String[] args) { +//30d 数据只有6d 6天算出来是12% 剩下24天设置未为100% +// String nowStr = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD); +// System.out.println(nowStr); + //Date now = DateUtils.parseDate( nowStr); + //System.out.println(now); + //List tbList = new ArrayList<>(); + // + //BigDecimal a1 = BigDecimal.valueOf(25); + //BigDecimal b2 = BigDecimal.valueOf(4); + //BigDecimal bigDecimal = a1.divide(b2).setScale(10, BigDecimal.ROUND_HALF_UP); + //System.out.println(BigDecimal.valueOf(6.72).setScale(10,BigDecimal.ROUND_HALF_UP)); + // + // + ////今日矿池总收益 + ////BigDecimal reward = tbList.stream() + //// .filter(e -> (e.getDate().before(now) || e.getDate().equals(now))) + //// .map(e -> { + //// return BigDecimal.valueOf(e.getReward()); + //// }).reduce(BigDecimal.ZERO, BigDecimal::add); + ////今日总收益 * 用户今日平均算力占比 = 用户今日预估收益 + ////System.out.println(reward); + //BigDecimal diff = new BigDecimal(247809.339528107); + BigDecimal diff = BigDecimal.valueOf( + 273308829306273.9).multiply(BigDecimal.valueOf(10)); + + System.out.println(diff); + + BigDecimal vip = BigDecimal.valueOf( + 4866660414177.179); + + //27764650.22400000 * + + NodeConstant constant = NodeConstant.ALPH; + + BigDecimal factor = constant.getFactor(); + System.out.println("factor:"+factor); + + BigDecimal my = diff.multiply(factor).setScale(3,BigDecimal.ROUND_HALF_UP); + String s = PowerUnitUtils.difficultyFormat(diff); + ///134.654.677 84243162 + System.out.println(s); + System.out.println(my); + + //BigDecimal out = null; + //System.out.println("收"+BigDecimal.valueOf(285).add(out).setScale(2,BigDecimal.ROUND_HALF_UP)); + //String dgbs = PowerUnitUtils.powerFormat(my, "dgbs"); + //System.out.println(dgbs); + // + //BigDecimal divide1 = BigDecimal.valueOf(167810) + // .multiply(factor) + // .divide(BigDecimal.valueOf(1000000000), 16, BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(1.02)); + // + ////1T = 1000G = 1000*1000M =1000*1000*1000K = 1000*1000*1000*1000 H/s + //BigDecimal hashrate = BigDecimal.valueOf(6552.87).multiply(BigDecimal.valueOf(1000)).multiply(BigDecimal.valueOf(1000*1000)); + //System.out.println("算力:"+hashrate+",逆推难度:"+hashrate.divide(factor,16,BigDecimal.ROUND_HALF_UP)); + // + // + //BigDecimal hashrate2 = BigDecimal.valueOf(6.56287).multiply(BigDecimal.valueOf(1000*1000)).multiply(BigDecimal.valueOf(1000*1000)); + //System.out.println("算力:"+hashrate2+",逆推难度:"+hashrate2.divide(factor,16,BigDecimal.ROUND_HALF_UP)); + //String ip=null; + //if(StringUtils.isNull(ip)){ + // ip=""; + //} + // + //BigDecimal a= BigDecimal.valueOf(34988050.7409206319) + // .multiply(BigDecimal.valueOf(1000*1000)); + //BigDecimal b= BigDecimal.valueOf(40358179185625.603034); + //System.out.println(a); + //System.out.println(a.divide(b,16,BigDecimal.ROUND_HALF_UP)); + //System.out.println(a.divide(b,16,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(960*3))); + // + // + //long pDay3d = 3; + // + ////矿池单位转换 + //BigDecimal pPow3d = BigDecimal.valueOf(35032458.5686897500) + // .add(BigDecimal.valueOf(34978857.3181887708)) + // .add(BigDecimal.valueOf(34952836.3358833750)) + // .multiply(BigDecimal.valueOf(1000*1000)); + // + //long nDay3d = 3; + // + //BigDecimal nPow3d = BigDecimal.valueOf(37823319087186.226875) + // .add(BigDecimal.valueOf(42368092071445.108958)) + // .add(BigDecimal.valueOf(40921609879113.265238)); + ////计算3天 每日出块量 + //BigDecimal dayChunk3d = pPow3d + // .multiply(BigDecimal.valueOf(nDay3d * nDay3d)) + // .divide(nPow3d.multiply(BigDecimal.valueOf(pDay3d * pDay3d)),8,BigDecimal.ROUND_HALF_UP) + // .multiply(BigDecimal.valueOf(960)) + // .setScale(8, BigDecimal.ROUND_HALF_UP); + //System.out.println("3天每日应出块量:"+dayChunk3d); + //System.out.println(BigDecimal.valueOf(2731).divide(dayChunk3d.multiply(BigDecimal.valueOf(3)),4,BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()); + + //String heightBody ="{\"headers\": [\"000000000000065796e173ae9c9f8ec1bae6c95083894444866a50bd2e61fac0\",\"000000000000065796e173ae9c9f8ec1bae6c95083894444866a50bd2e6test1\",\"000000000000065796e173ae9c9f8ec1bae6c95083894444866a50bd2e6test2\"]}"; + String hashRateBody ="{\"hashrate\": \"32071693244 MH/s\"}"; + JSONObject hashRateJsonObject = JSON.parseObject(hashRateBody); + System.out.println("alph 当前算力api请求结果:|"+hashRateJsonObject+"|"); + String hashRateStr = hashRateJsonObject.getString("hashrate"); + String hashRateError = hashRateJsonObject.getString("detail"); + + if (StringUtils.isNull(hashRateStr) && StringUtils.isNotBlank(hashRateError)){ + System.out.println("当前全网算力APi调用 error:"+hashRateError); + } + if(StringUtils.isNull(hashRateStr) && StringUtils.isBlank(hashRateError)){ + System.out.println("当前全网算力APi调用 未通过hashes获取到数据"); + } + BigDecimal hashrate= BigDecimal.ONE; + if(hashRateStr.toUpperCase().contains("kh/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("KH/s",""). + replace(" ","")). + multiply(BigDecimal.valueOf(1000,2)); + }else if(hashRateStr.toLowerCase().contains("mh/s")){ + System.out.println("mh"); + hashrate = new BigDecimal(hashRateStr. + replace("MH/s",""). + replace(" ","")). + multiply(BigDecimal.valueOf(1000*1000)); + + + }else if(hashRateStr.toLowerCase().contains("gh/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("GH/s",""). + replace(" ","")). + multiply(BigDecimal.valueOf(1000*1000,2)). + multiply(BigDecimal.valueOf(1000,2)); + }else if(hashRateStr.toLowerCase().contains("th/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("TH/s",""). + replace(" ","")). + multiply(BigDecimal.valueOf(1000*1000,2)). + multiply(BigDecimal.valueOf(1000*1000,2)); + }else if(hashRateStr.toLowerCase().contains("ph/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("PH/s",""). + replace(" ","")). + multiply(BigDecimal.valueOf(1000*1000,2)). + multiply(BigDecimal.valueOf(1000*1000,2)). + multiply(BigDecimal.valueOf(1000,2)); + }else if(hashRateStr.toLowerCase().contains("h/s")){ + hashrate = new BigDecimal(hashRateStr. + replace("H/s",""). + replace(" ","")); + }else { + // + } + + System.out.println(hashrate); + + + } + +} + diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AccountBalanceAddVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AccountBalanceAddVo.java new file mode 100644 index 0000000..950acc1 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AccountBalanceAddVo.java @@ -0,0 +1,38 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 绑定挖矿账号请求体 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class AccountBalanceAddVo { + + + @NotBlank(message = "请先输入矿工账号") + @Xss + public String ma; + + @NotBlank(message = "缺少币种参数") + @Xss + public String coin; + + @NotBlank(message = "未收到地址参数") + @Xss + public String balance; + + @Xss + public String remarks; + + public double amount; + + public int active; + + public long gCode; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AccountPowerVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AccountPowerVo.java new file mode 100644 index 0000000..5cccb1d --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AccountPowerVo.java @@ -0,0 +1,19 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 账号算力查询请求体 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class AccountPowerVo { + + public String account; + + public String interval; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AccountVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AccountVo.java new file mode 100644 index 0000000..55b593c --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AccountVo.java @@ -0,0 +1,29 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class AccountVo { + + @NotBlank(message = "未传入有效挖矿账号名") + @Xss + public String account; + + @NotBlank(message = "未传入有效矿池名") + @Xss + public String coin; + + public String interval; + + public int limit = 10; + + public int page = 1; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AllTicketListVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AllTicketListVo.java new file mode 100644 index 0000000..60619d2 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/AllTicketListVo.java @@ -0,0 +1,34 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description 指标查询条件 + * @Date 2022/5/19 17:17 + * @Author 杜懿 + */ +@Data +public class AllTicketListVo { + + //private String id; + + private long id; + + private String email; + + private String cond; + + /** 创建时间 */ + private String start; + + private String end; + + /** 状态 */ + private int status; + + /** 当前页数 */ + private int page=1; + + /** 每页数据条数 */ + private int limit=10; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyCreateVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyCreateVo.java new file mode 100644 index 0000000..8bbf099 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyCreateVo.java @@ -0,0 +1,16 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description 只读页面 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ApiKeyCreateVo { + public String ip; + + public String perms; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyDelVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyDelVo.java new file mode 100644 index 0000000..0200a9a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyDelVo.java @@ -0,0 +1,15 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ApiKeyDelVo { + + public String ids; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyInfoVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyInfoVo.java new file mode 100644 index 0000000..3ad2801 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyInfoVo.java @@ -0,0 +1,15 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ApiKeyInfoVo { + + public long id; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyUpdateVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyUpdateVo.java new file mode 100644 index 0000000..7d40ba0 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ApiKeyUpdateVo.java @@ -0,0 +1,24 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.Pattern; + +/** + * @Description + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ApiKeyUpdateVo { + + public long id; + + @Xss + public String ip; + + @Xss + public String perms; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/BalanceCheckVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/BalanceCheckVo.java new file mode 100644 index 0000000..965815a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/BalanceCheckVo.java @@ -0,0 +1,24 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 绑定挖矿账号请求体 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class BalanceCheckVo { + + @NotBlank(message = "缺少币种参数") + @Xss + public String coin; + + @NotBlank(message = "缺少钱包地址参数") + @Xss + public String balance; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/BalanceListGetVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/BalanceListGetVo.java new file mode 100644 index 0000000..9e38be2 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/BalanceListGetVo.java @@ -0,0 +1,19 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class BalanceListGetVo { + + @Xss(message = "不允许脚本执行") + public String coin; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/CoinVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/CoinVo.java new file mode 100644 index 0000000..ad7db6d --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/CoinVo.java @@ -0,0 +1,23 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class CoinVo { + + @NotBlank(message = "请先选择币种") + public String coin; + + public String interval; + + public int limit = 10; + + public int page = 1; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/DateValueVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/DateValueVo.java new file mode 100644 index 0000000..3ba03ce --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/DateValueVo.java @@ -0,0 +1,20 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Description 只读页面 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class DateValueVo { + + public Date date; + + public BigDecimal value; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/EmailVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/EmailVo.java new file mode 100644 index 0000000..2ef3e1d --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/EmailVo.java @@ -0,0 +1,20 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.Email; + +/** + * @Description 只读页面 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class EmailVo { + + @Xss + @Email + public String email; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/EndTicketVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/EndTicketVo.java new file mode 100644 index 0000000..1dd0ca9 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/EndTicketVo.java @@ -0,0 +1,17 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description TODO + * @Date 2022/8/1 18:44 + * @Author 杜懿 + */ +@Data +public class EndTicketVo { + + private long id; + + private int status; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/GoogleBindVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/GoogleBindVo.java new file mode 100644 index 0000000..d0dfac2 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/GoogleBindVo.java @@ -0,0 +1,40 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 只读页面 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class GoogleBindVo { + + /** + * 谷歌验证器 安全码 + */ + @NotBlank + public String secret; + + /** + * 谷歌验证器验证码 + */ + public long gCode; + + /** + * 邮箱验证码 + */ + @NotBlank + public String eCode; + + /** + * 当前用户密码 + */ + @NotBlank + public String pwd; + + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/GoogleCloseVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/GoogleCloseVo.java new file mode 100644 index 0000000..6951be2 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/GoogleCloseVo.java @@ -0,0 +1,27 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 只读页面 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class GoogleCloseVo { + + + /** + * 谷歌验证器验证码 + */ + public long gCode; + + /** + * 邮箱验证码 + */ + @NotBlank + public String eCode; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/IdVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/IdVo.java new file mode 100644 index 0000000..cd66402 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/IdVo.java @@ -0,0 +1,17 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description TODO + * @Date 2022/8/1 18:44 + * @Author 杜懿 + */ +@Data +public class IdVo { + + private long id; + + private String email; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MaBalanceCheckVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MaBalanceCheckVo.java new file mode 100644 index 0000000..2a5663a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MaBalanceCheckVo.java @@ -0,0 +1,28 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 绑定挖矿账号请求体 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class MaBalanceCheckVo { + + @NotBlank(message = "缺少币种参数") + @Xss + public String coin; + + @NotBlank(message = "挖矿账号不能为空") + @Xss + public String ma; + + @NotBlank(message = "缺少钱包地址参数") + @Xss + public String balance; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerAccountAddVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerAccountAddVo.java new file mode 100644 index 0000000..6286137 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerAccountAddVo.java @@ -0,0 +1,36 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +/** + * @Description 绑定挖矿账号请求体 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class MinerAccountAddVo { + + @NotBlank(message = "请先选择币种") + @Xss + public String coin; + + @NotBlank(message = "请先输入挖矿账号") + @Size(min = 3,max = 24,message="挖矿账户名长度必须在 3 ~ 24 字符之间!") + @Pattern(regexp = "[a-zA-Z0-9|_]+$",message = "挖矿账户名仅允许使用字母、数字、下划线,不能包含其他特殊字符或空格") + @Xss + public String ma; + + //@NotBlank(message = "请先输入需要绑定的钱包地址") + @Xss + public String balance; + + @Xss + public String remarks; + + public long code; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerAccountBalanceVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerAccountBalanceVo.java new file mode 100644 index 0000000..0d9df30 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerAccountBalanceVo.java @@ -0,0 +1,24 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class MinerAccountBalanceVo { + + @NotBlank(message = "矿工账号不能为空") + @Xss + public String ma; + + @NotBlank(message = "矿池不能为空") + @Xss + public String coin; +} + diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerAccountDelVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerAccountDelVo.java new file mode 100644 index 0000000..0beaea0 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerAccountDelVo.java @@ -0,0 +1,22 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +/** + * @Description TODO + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class MinerAccountDelVo { + + @NotBlank(message = "未收到id") + @Pattern(regexp="^[0-9]+(,[0-9]+)*$",message="参数格式错误") + public String id; + + public long gCode; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerIdVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerIdVo.java new file mode 100644 index 0000000..fd77db6 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerIdVo.java @@ -0,0 +1,20 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +/** + * @Description TODO + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class MinerIdVo { + + //@Pattern(regexp="^[0-9]*$",message="参数格式错误") + public long id; +} + diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerListVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerListVo.java new file mode 100644 index 0000000..0906b1a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerListVo.java @@ -0,0 +1,38 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class MinerListVo { + + @NotBlank(message = "未传入有效挖矿账号名") + @Xss + public String account; + + @NotBlank(message = "未传入有效矿池名") + @Xss + public String coin; + + public int type; + + public String filter; + + /** 排序字段 传参范围["30m","24h"]*/ + public String sort; + + /** 排序规则 传参范围["asc","desc"]*/ + public String collation; + + public int limit = 10; + + public int page = 1; +} + diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerVo.java new file mode 100644 index 0000000..32e4a86 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/MinerVo.java @@ -0,0 +1,30 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description TODO + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class MinerVo { + + @NotBlank(message = "未传入有效挖矿账号名") + @Xss + public String account; + + @NotBlank(message = "未传入有效矿池名") + @Xss + public String coin; + + @NotBlank(message = "未传入矿工名") + @Xss + public String miner; + + public String interval; +} + diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticeAddVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticeAddVo.java new file mode 100644 index 0000000..acc3ad9 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticeAddVo.java @@ -0,0 +1,28 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.Email; + +/** + * @Description 通知 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class NoticeAddVo { + + /** 挖矿账户id */ + public int maId; + + @Email + public String email; + + @Xss + public String remark; + + public String code; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticeDelVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticeDelVo.java new file mode 100644 index 0000000..2bb3e7a --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticeDelVo.java @@ -0,0 +1,17 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +/** + * @Description 通知 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class NoticeDelVo { + + /** 通知记录表id */ + public long id; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticePageVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticePageVo.java new file mode 100644 index 0000000..30d6013 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticePageVo.java @@ -0,0 +1,19 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description 通知 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class NoticePageVo { + + public int maId; + + public int limit = 10; + + public int page = 1; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticeUpdateVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticeUpdateVo.java new file mode 100644 index 0000000..d781a1b --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/NoticeUpdateVo.java @@ -0,0 +1,21 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +/** + * @Description 通知 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class NoticeUpdateVo { + + /** 通知记录表id */ + public long id; + + @Xss + public String remark; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/PageVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/PageVo.java new file mode 100644 index 0000000..6126655 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/PageVo.java @@ -0,0 +1,20 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 只读页面 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class PageVo { + + public int limit = 10; + + public int page = 1; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/PrivateTicketListVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/PrivateTicketListVo.java new file mode 100644 index 0000000..07a867f --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/PrivateTicketListVo.java @@ -0,0 +1,23 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description TODO + * @Date 2022/8/1 18:44 + * @Author 杜懿 + */ +@Data +public class PrivateTicketListVo { + + private int status; + + /** 工单号 */ + private long id; + + private String start; + + private String end; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyHtmlChangeVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyHtmlChangeVo.java new file mode 100644 index 0000000..ba7d532 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyHtmlChangeVo.java @@ -0,0 +1,29 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +/** + * @Description TODO + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ReadOnlyHtmlChangeVo { + + @NotBlank(message = "未传入有效页面有效码") + @Xss + public String key; + + //public String coin; + @Xss + public String remark; + + /** 权限配置 */ + @Xss + @Pattern(regexp = "[0-9|,]+$",message = "配置传参格式错误") + public String config; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyHtmlCreateVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyHtmlCreateVo.java new file mode 100644 index 0000000..06489ec --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyHtmlCreateVo.java @@ -0,0 +1,36 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +/** + * @Description TODO + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ReadOnlyHtmlCreateVo { + + @NotBlank(message = "未传入有效挖矿账号名") + @Xss + public String account; + + @NotBlank(message = "未传入有效矿池名") + @Xss + public String coin; + + @Xss + public String remark; + + /** 权限配置 需要根据逗号“,”分隔*/ + @Xss + @Pattern(regexp = "[0-9|,]+$",message = "配置传参格式错误") + public String config; + + @Xss + public String lang; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyIntervalVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyIntervalVo.java new file mode 100644 index 0000000..ed238d6 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyIntervalVo.java @@ -0,0 +1,23 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 只读页面 时间间隔 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ReadOnlyIntervalVo { + + @NotBlank(message = "请先选择币种") + @Xss() + public String key; + + public String interval; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyKeyVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyKeyVo.java new file mode 100644 index 0000000..8eadbdd --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyKeyVo.java @@ -0,0 +1,20 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 只读页面 key + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ReadOnlyKeyVo { + + @NotBlank(message = "页面唯一码不能为空") + @Xss() + public String key; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyListVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyListVo.java new file mode 100644 index 0000000..cb7cfd9 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyListVo.java @@ -0,0 +1,34 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 只读页面 条件分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ReadOnlyListVo { + + @NotBlank(message = "请先选择币种") + @Xss() + public String key; + + public int type; + + public String filter; + + /** 排序字段 传参范围["30m","24h"]*/ + public String sort; + + /** 排序规则 传参范围["asc","desc"]*/ + public String collation; + + public int limit = 10; + + public int page = 1; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyMinerVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyMinerVo.java new file mode 100644 index 0000000..74b5e65 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyMinerVo.java @@ -0,0 +1,25 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 只读页面 key + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ReadOnlyMinerVo { + + @NotBlank(message = "页面唯一码不能为空") + @Xss() + public String key; + + @NotBlank(message = "矿工号") + @Xss() + public String account; + + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyPageVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyPageVo.java new file mode 100644 index 0000000..0e6fc70 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ReadOnlyPageVo.java @@ -0,0 +1,24 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 只读页面 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class ReadOnlyPageVo { + + @NotBlank(message = "请先选择币种") + @Xss() + public String key; + + public int limit = 10; + + public int page = 1; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ResponTicketVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ResponTicketVo.java new file mode 100644 index 0000000..4990052 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ResponTicketVo.java @@ -0,0 +1,27 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description + * @Date 2022/5/19 17:17 + * @Author 杜懿 + */ +@Data +public class ResponTicketVo { + + /** 工单id */ + private long id; + + /** 回复内容 */ + private String respon; + + private String files; + + private String videoFiles; + + private String imageFiles; + + private String audioFiles; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ResubmitTicketVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ResubmitTicketVo.java new file mode 100644 index 0000000..30a2233 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/ResubmitTicketVo.java @@ -0,0 +1,26 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description 指标查询条件 + * @Date 2022/5/19 17:17 + * @Author 杜懿 + */ +@Data +public class ResubmitTicketVo { + + /** 工单id */ + private long id; + + /** 补充内容 */ + private String desc; + + private String files; + + private String videoFiles; + + private String imageFiles; + + private String audioFiles; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/StatusVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/StatusVo.java new file mode 100644 index 0000000..e416d1e --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/StatusVo.java @@ -0,0 +1,25 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description TODO + * @Date 2022/8/1 18:44 + * @Author 杜懿 + */ +@Data +public class StatusVo { + + private int id; + + private int status; + + private String email; + + /** 当前页数 */ + private int page =1; + + /** 每页数据条数 */ + private int limit = 10; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/TestVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/TestVo.java new file mode 100644 index 0000000..5df4387 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/TestVo.java @@ -0,0 +1,17 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description 只读页面 分页 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class TestVo { + + public String date ; + + public String interval ; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/TicketVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/TicketVo.java new file mode 100644 index 0000000..081b7cf --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/TicketVo.java @@ -0,0 +1,22 @@ +package com.m2pool.pool.vo; + +import lombok.Data; + +/** + * @Description 指标查询条件 + * @Date 2022/5/19 17:17 + * @Author 杜懿 + */ +@Data +public class TicketVo { + + private String userName; + + private String email; + + /** 问题描述 */ + private String desc; + + /** 附件标号 逗号分隔*/ + private String files; +} diff --git a/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/UserAccountVo.java b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/UserAccountVo.java new file mode 100644 index 0000000..e35de95 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/java/com/m2pool/pool/vo/UserAccountVo.java @@ -0,0 +1,24 @@ +package com.m2pool.pool.vo; + +import com.m2pool.common.core.xss.Xss; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Description 用户网站账号绑定挖矿账号请求体 + * @Date 2024/6/18 14:05 + * @Author 杜懿 + */ +@Data +public class UserAccountVo { + + @NotBlank(message = "未传入有效矿池名") + @Xss + public String coin; + + @NotBlank(message = "未传入有效挖矿账号名") + @Xss + public String ma; + +} diff --git a/m2pool-modules/m2pool-pool/src/main/resources/bootstrap.yml b/m2pool-modules/m2pool-pool/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..154eac1 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/resources/bootstrap.yml @@ -0,0 +1,156 @@ +server: + port: 9203 + compression: + enabled: true + mime-types: application/json + +spring: + #邮箱基本配置 + mail: + # 配置在limit_time内,用户可以发送limit次验证码 + limit: 2 这个是我额外的配置,结合邮箱服务用的 + limitTime: 10 这个是我额外的配置 + #配置smtp服务主机地址 + # sina smtp.sina.cn + # aliyun smtp.aliyun.com + # 163 smtp.163.com 端口号465或994 + host: mail.privateemail.com + #发送者邮箱 + username: support@m2pool.cc + #配置密码,注意不是真正的密码,而是刚刚申请到的授权码 + # password: + # password: m2pool2024@! + # password: axvm-zfgx-cgcg-qhhu + password: m2pool2024@! + #端口号 + port: 587 + #默认的邮件编码为UTF-8 + default-encoding: UTF-8 + #其他参数 + properties: + mail: + #配置SSL 加密工厂 + smtp: + ssl: + #本地测试,先放开ssl + enable: false + required: false + #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误 + debug: false + socketFactory: + class: javax.net.ssl.SSLSocketFactory + + application: + # 应用名称 + name: m2pool-pool + profiles: + # 环境配置 + active: prod + cloud: + nacos: + discovery: + # 服务注册地址 172.16.2.5 + server-addr: 127.0.0.1:8808 + namespace: m2_prod + group: m2_prod_group +# server-addr: 127.0.0.1:8808 + config: + # 配置中心地址 + server-addr: 127.0.0.1:8808 +# server-addr: 127.0.0.1:8808 + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} + namespace: m2_prod + group: m2_prod_group + servlet: + multipart: + max-file-size: 2MB + max-request-size: 8MB + +myenv: + domain: https://www.m2pool.com + path: /var/www/html/web + img: /img + filepath: /home/ubuntu/prod + + +#server: +# port: 9503 +# compression: +# enabled: true +# mime-types: application/json +# +#spring: +# #邮箱基本配置 +# mail: +# # 配置在limit_time内,用户可以发送limit次验证码 +# limit: 2 这个是我额外的配置,结合邮箱服务用的 +# limitTime: 10 这个是我额外的配置 +# #配置smtp服务主机地址 +# # sina smtp.sina.cn +# # aliyun smtp.aliyun.com +# # 163 smtp.163.com 端口号465或994 +# host: mail.privateemail.com +# #发送者邮箱 +# username: support@m2pool.cc +# #配置密码,注意不是真正的密码,而是刚刚申请到的授权码 +# # password: +# # password: m2pool2024@! +# # password: axvm-zfgx-cgcg-qhhu +# password: m2pool2024@! +# #端口号 +# port: 587 +# #默认的邮件编码为UTF-8 +# default-encoding: UTF-8 +# #其他参数 +# properties: +# mail: +# #配置SSL 加密工厂 +# smtp: +# ssl: +# #本地测试,先放开ssl +# enable: false +# required: false +# #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误 +# debug: false +# socketFactory: +# class: javax.net.ssl.SSLSocketFactory +# +# application: +# # 应用名称 +# name: m2pool-pool +# profiles: +# # 环境配置 +# active: test +# cloud: +# nacos: +# discovery: +# # 服务注册地址 172.16.2.5 +# server-addr: 127.0.0.1:8808 +# namespace: m2_test +# group: m2_test_group +## server-addr: 127.0.0.1:8808 +# config: +# # 配置中心地址 +# server-addr: 127.0.0.1:8808 +## server-addr: 127.0.0.1:8808 +# # 配置文件格式 +# file-extension: yml +# # 共享配置 +# shared-configs: +# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} +# namespace: m2_test +# group: m2_test_group +# servlet: +# multipart: +# max-file-size: 2MB +# max-request-size: 8MB +# +#myenv: +# domain: https://test.m2pool.com +# path: /var/www/html/web_test +# img: /img +# filepath: /home/ubuntu/web diff --git a/m2pool-modules/m2pool-pool/src/main/resources/logback.xml b/m2pool-modules/m2pool-pool/src/main/resources/logback.xml new file mode 100644 index 0000000..4535773 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/resources/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/AccountDistributionMapper.xml b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/AccountDistributionMapper.xml new file mode 100644 index 0000000..16c67ac --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/AccountDistributionMapper.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/GoogleAuthMapper.xml b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/GoogleAuthMapper.xml new file mode 100644 index 0000000..6e19570 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/GoogleAuthMapper.xml @@ -0,0 +1,59 @@ + + + + + update sys_user + set + google_auth = #{info.secret}, + google_status = #{info.status}, + update_time = sysdate() + where user_name = #{info.email} + + + + + + + + + + + + update sys_user + set + google_auth = '', + google_status = 0, + update_time = sysdate() + where user_name = #{info.email} + + + + + diff --git a/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/NoticeMapper.xml b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/NoticeMapper.xml new file mode 100644 index 0000000..5884fdd --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/NoticeMapper.xml @@ -0,0 +1,98 @@ + + + + + insert into notice_info( + ma_id, + email, + remark, + create_time + ) values( + #{vo.maId}, + #{vo.email}, + #{vo.remark}, + sysdate() + ) + + + + update notice_info + set + remark = #{vo.remark} + where id = #{vo.id} + + + + delete from notice_info where id=#{vo.id} and ma_id=#{maId} + + + + + + + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/PoolMapper.xml b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/PoolMapper.xml new file mode 100644 index 0000000..6c5600e --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/PoolMapper.xml @@ -0,0 +1,886 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into ${table}( + `date`, + `user`, + miner, + mhs, + `state`, + last_submit + ) values + + ( + '${i.date}', + '${i.user}', + '${i.miner}', + ${i.mhs}, + '${i.state}', + '${i.lastSubmit}' + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into ${table}( + `date`, + `user`, + mhs, + mhs24h, + accepts, + rejects, + rejects_ratio + ) values + + ( + '${i.date}', + '${i.user}', + ${i.mhs}, + ${i.mhs24h}, + ${i.accepts}, + ${i.rejects}, + ${i.rejectsRatio} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into ${table}( + `date`, + `value` + ) values( + '${date}', + ${vo.value} + ) + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into ${table}( + `date`, + `value` + ) values( + '${date}', + ${vo.value} + ) + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into nexa_pool_blkstats ( + `date`, + height, + hash, + reward, + fees + ) values + + ( + #{i.date}, + #{i.height}, + #{i.hash}, + #{i.reward}, + #{i.fees} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into ${table}( + `date`, + `mhs`, + `miners`, + `online`, + `offline` + ) values( + '${vo.dateStr}', + ${vo.mhs}, + ${vo.miners}, + ${vo.online}, + ${vo.offline} + ) + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into ${table}( + `date`, + `user`, + `mhs` + ) values + + ( + '${i.date}', + '${i.user}', + ${i.mhs} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into grs_pool_blkstats ( + `date`, + height, + hash, + reward, + fees + ) values + + ( + #{i.date}, + #{i.height}, + #{i.hash}, + #{i.reward}, + #{i.fees} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into mona_pool_blkstats ( + `date`, + height, + hash, + reward, + fees + ) values + + ( + #{i.date}, + #{i.height}, + #{i.hash}, + #{i.reward}, + #{i.fees} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into dgbo_pool_blkstats ( + `date`, + height, + hash, + reward, + fees + ) values + + ( + #{i.date}, + #{i.height}, + #{i.hash}, + #{i.reward}, + #{i.fees} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into dgbq_pool_blkstats ( + `date`, + height, + hash, + reward, + fees + ) values + + ( + #{i.date}, + #{i.height}, + #{i.hash}, + #{i.reward}, + #{i.fees} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into dgbs_pool_blkstats ( + `date`, + height, + hash, + reward, + fees + ) values + + ( + #{i.date}, + #{i.height}, + #{i.hash}, + #{i.reward}, + #{i.fees} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into rxd_pool_blkstats ( + `date`, + height, + hash, + reward, + fees + ) values + + ( + #{i.date}, + #{i.height}, + #{i.hash}, + #{i.reward}, + #{i.fees} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + insert into alph_pool_blkstats ( + `date`, + height, + hash, + reward, + fees + ) values + + ( + #{i.date}, + #{i.height}, + #{i.hash}, + #{i.reward}, + #{i.fees} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + insert into enx_pool_blkstats ( + `date`, + height, + hash, + reward, + fees + ) values + + ( + #{i.date}, + #{i.height}, + #{i.hash}, + #{i.reward}, + #{i.fees} + ) + + ON DUPLICATE KEY UPDATE + `date` = VALUES(`date`) + + + + + + diff --git a/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/TicketMapper.xml b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/TicketMapper.xml new file mode 100644 index 0000000..e648643 --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/TicketMapper.xml @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + UPDATE ticket SET update_time=sysdate() WHERE id = #{id} + + + + + + + + + + + + + + + + + + + UPDATE ticket SET status=#{status},update_time =sysdate() WHERE id = #{id} + + + + + insert into ticket( + email, + `desc`, + status, + create_time,update_time + )values( + #{data.email}, + #{data.desc}, + 1, + sysdate(),sysdate() + ) + + + + insert into ticket_supplement( + ticket_id, + content, + files, + video_file, + image_file, + audio_file, + create_time + ) values ( + #{vo.id}, + #{vo.desc}, + #{vo.files}, + #{vo.videoFiles}, + #{vo.imageFiles}, + #{vo.audioFiles}, + sysdate() + ) + + + + insert into respon( + ticket_id, + respon, + responer, + create_time + ) values( + #{vo.id}, + #{vo.respon}, + #{name}, + sysdate() + ) + + + + insert into `file`(url,file_name,user_name,`type`,create_time) values( + #{vo.url},#{vo.fileName},#{vo.userName},#{vo.fileType},sysdate() + ) + + diff --git a/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/UserAccountMapper.xml b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/UserAccountMapper.xml new file mode 100644 index 0000000..2cd64ba --- /dev/null +++ b/m2pool-modules/m2pool-pool/src/main/resources/mapper/pool/UserAccountMapper.xml @@ -0,0 +1,315 @@ + + + + + + insert into user_miner_account( + `user`, + miner_user, + coin, + remark, + create_date) values( + #{vo.user}, + #{vo.ma}, + #{vo.coin}, + #{vo.remark}, + sysdate() + ) + + + + insert into user_account_balance( + `ma_id`, + balance, + amount + ,#{vo.active} + ) values ( + #{accountId}, + #{vo.balance}, + #{vo.amount} + ,#{vo.active} + ) + + + + insert into user_page_info( + `user`, + account, + coin, + config, + url_key, + lang + ,remark + ,create_time + ) values ( + #{vo.email}, + #{vo.account}, + #{vo.coin}, + #{vo.config}, + #{vo.key}, + #{vo.lang} + ,#{vo.remark} + ,sysdate() + ) + + + + insert into user_api( + `user`, + api_key, + api_ip, + perms + ) value ( + #{vo.user}, + #{vo.key}, + #{vo.apiIp}, + #{vo.permissions} + ) + + + + + update user_miner_account set status = 11 where id in + #{id}; + + + + update user_account_balance set status = 99 where id = #{id} + + + + update + user_account_balance + set + amount = #{vo.amount} + ,active = #{vo.active} + where id = #{id} and status = 0 + + + + update + user_miner_account + set + remark = #{vo.remarks} + where id = #{accountId} and status = 0 + + + + update + user_page_info + set + config = #{vo.config} + , remark = #{vo.remark} + where url_key =#{vo.key} + + + + update + user_api + set + api_ip = #{vo.ip} + , perms = #{vo.perms} + where id =#{vo.id} and `user` =#{user} + + + + delete from user_page_info + where url_key in + + #{key} + + and `user` = #{user} + + + + delete from user_api + where id in + + #{id} + + and `user` = #{user} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-system/pom.xml b/m2pool-modules/m2pool-system/pom.xml new file mode 100644 index 0000000..7ad4d1d --- /dev/null +++ b/m2pool-modules/m2pool-system/pom.xml @@ -0,0 +1,109 @@ + + + + m2pool-modules + com.m2pool + 3.5.0 + + 4.0.0 + + m2pool-system + + + m2pool-system系统模块 + + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + io.springfox + springfox-swagger-ui + ${swagger.fox.version} + + + + + mysql + mysql-connector-java + + + + + org.projectlombok + lombok-maven-plugin + provided + + + + + com.m2pool + common-datasource + + + + + com.m2pool + common-datascope + + + + + + com.m2pool + common-log + + + + + com.m2pool + common-swagger + + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/M2PoolSystemApplication.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/M2PoolSystemApplication.java new file mode 100644 index 0000000..8758501 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/M2PoolSystemApplication.java @@ -0,0 +1,33 @@ +package com.m2pool.system; + +import com.m2pool.common.security.annotation.EnableCustomConfig; +import com.m2pool.common.security.annotation.EnableM2PoolFeignClients; +import com.m2pool.common.swagger.annotation.EnableCustomSwagger2; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @Description 系统模块启动类 + * @Date 2024/6/13 11:13 + * @Author dy + */ + +@EnableCustomConfig +@EnableCustomSwagger2 +@EnableM2PoolFeignClients +@SpringBootApplication +public class M2PoolSystemApplication { + + public static void main(String[] args) { + try { + SpringApplication.run(M2PoolSystemApplication.class); + System.out.println("m2pool-system启动成功"); + }catch (Exception e){ + System.out.println(e); + } + + } + + + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysLogininforController.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysLogininforController.java new file mode 100644 index 0000000..b316fdb --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysLogininforController.java @@ -0,0 +1,74 @@ +package com.m2pool.system.controller; + +import com.m2pool.common.core.utils.poi.ExcelUtil; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.common.log.annotation.Log; +import com.m2pool.common.log.enums.BusinessType; +import com.m2pool.common.security.annotation.InnerAuth; +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.system.api.entity.SysLogininfor; +import com.m2pool.system.service.SysLogininforService; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 系统访问记录 + * + * @author dy + */ +@RestController +@RequestMapping("/logininfor") +public class SysLogininforController extends BaseController +{ + @Autowired + private SysLogininforService logininforService; + + @RequiresPermissions("system:logininfor:list") + @GetMapping("/list") + public TableDataInfo list(SysLogininfor logininfor) + { + startPage(); + List list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + + @Log(title = "登录日志", businessType = BusinessType.EXPORT) + @RequiresPermissions("system:logininfor:export") + @PostMapping("/export") + public void export(HttpServletResponse response, SysLogininfor logininfor) + { + List list = logininforService.selectLogininforList(logininfor); + ExcelUtil util = new ExcelUtil(SysLogininfor.class); + util.exportExcel(response, list, "登录日志"); + } + + @RequiresPermissions("system:logininfor:remove") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public AjaxResult remove(@PathVariable Long[] infoIds) + { + return toAjax(logininforService.deleteLogininforByIds(infoIds)); + } + + @RequiresPermissions("system:logininfor:remove") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/clean") + public AjaxResult clean() + { + logininforService.cleanLogininfor(); + return AjaxResult.success(); + } + + @InnerAuth + @PostMapping + public AjaxResult add(@RequestBody SysLogininfor logininfor) + { + return toAjax(logininforService.insertLogininfor(logininfor)); + } +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysOperlogController.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysOperlogController.java new file mode 100644 index 0000000..f6baec6 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysOperlogController.java @@ -0,0 +1,74 @@ +package com.m2pool.system.controller; + + +import com.m2pool.common.core.utils.poi.ExcelUtil; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.core.web.page.TableDataInfo; +import com.m2pool.common.log.annotation.Log; +import com.m2pool.common.log.enums.BusinessType; +import com.m2pool.common.security.annotation.InnerAuth; +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.system.api.entity.SysOperLog; +import com.m2pool.system.service.SysOperLogService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 操作日志记录 + * + * @author dy + */ +@RestController +@RequestMapping("/operlog") +public class SysOperlogController extends BaseController +{ + @Autowired + private SysOperLogService operLogService; + + @RequiresPermissions("system:operlog:list") + @GetMapping("/list") + public TableDataInfo list(SysOperLog operLog) + { + startPage(); + List list = operLogService.selectOperLogList(operLog); + return getDataTable(list); + } + + @Log(title = "操作日志", businessType = BusinessType.EXPORT) + @RequiresPermissions("system:operlog:export") + @PostMapping("/export") + public void export(HttpServletResponse response, SysOperLog operLog) + { + List list = operLogService.selectOperLogList(operLog); + ExcelUtil util = new ExcelUtil(SysOperLog.class); + util.exportExcel(response, list, "操作日志"); + } + + @Log(title = "操作日志", businessType = BusinessType.DELETE) + @RequiresPermissions("system:operlog:remove") + @DeleteMapping("/{operIds}") + public AjaxResult remove(@PathVariable Long[] operIds) + { + return toAjax(operLogService.deleteOperLogByIds(operIds)); + } + + @RequiresPermissions("system:operlog:remove") + @Log(title = "操作日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + operLogService.cleanOperLog(); + return AjaxResult.success(); + } + + @InnerAuth + @PostMapping + public AjaxResult add(@RequestBody SysOperLog operLog) + { + return toAjax(operLogService.insertOperlog(operLog)); + } +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysProfileController.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysProfileController.java new file mode 100644 index 0000000..7784c79 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysProfileController.java @@ -0,0 +1,239 @@ +package com.m2pool.system.controller; + +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.constant.UserConstants; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; + +import com.m2pool.common.security.annotation.InnerAuth; +import com.m2pool.common.security.annotation.RequiresLogin; +import com.m2pool.common.security.service.TokenService; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.entity.SysRole; +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.api.entity.SysUserLeveDate; +import com.m2pool.system.api.model.LoginUser; +import com.m2pool.system.entity.SysRoleDto; +import com.m2pool.system.entity.SysUserDto; +import com.m2pool.system.mapper.SysUserLevelMapper; +import com.m2pool.system.mapper.SysUserRoleMapper; +import com.m2pool.system.service.SysRoleService; +import com.m2pool.system.service.SysUserService; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + + +/** + * 个人信息 业务处理 + * + * @author m2pool + */ +@RestController +@RequestMapping("/user/profile") +public class SysProfileController extends BaseController +{ + @Autowired + private SysUserService userService; + + @Autowired + private SysRoleService roleService; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysUserLevelMapper userLevelMapper; + + + @Autowired + private TokenService tokenService; + + /** + * 个人信息 + */ + @GetMapping + public AjaxResult profile() + { + String username = SecurityUtils.getUsername(); + SysUser user = userService.selectUserByUserName(username); + + //隐藏不需要返回前端的信息 + SysUserDto userDto = new SysUserDto(); + SysRoleDto roleDto = new SysRoleDto(); + + BeanUtils.copyProperties(user,userDto); + + if (StringUtils.isNull(user.getRoles()) || user.getRoles().size() < 1) + { + //todo 动态获取注册用户信息 + roleDto.setRoleId(2L); + roleDto.setRoleKey("registered"); + roleDto.setRoleName("L2"); + roleDto.setLevel("L2"); + }else { + SysRole sysRole = user.getRoles().get(0); + BeanUtils.copyProperties(sysRole,roleDto); + if (sysRole.getRoleId() == 1L) { + roleDto.setLevel("admin"); + } else if (sysRole.getRoleId() == 2L) { + roleDto.setLevel("L2"); + } else if (sysRole.getRoleId() == 3L) { + roleDto.setLevel("L3"); + + //此时角色是L3 说明应该在会员有效期 + SysUserLeveDate userLevelInfo = userLevelMapper.getUserLevelInfo(SecurityUtils.getUserId()); + if(StringUtils.isNotNull(userLevelInfo)){ + if(userLevelInfo.getLevelType() == 0){ + roleDto.setRoleName("月度"+roleDto.getRoleName()); + }else if(userLevelInfo.getLevelType() == 1){ + roleDto.setRoleName("年度"+roleDto.getRoleName()); + } + }else { + //如果查询不到userLevelInfo 说明用户已过期 修改profit返回的用户等级为L2 + roleDto.setRoleId(2L); + roleDto.setRoleKey("registered"); + roleDto.setRoleName("L2"); + roleDto.setLevel("L2"); + } + } else if (sysRole.getRoleId() == 4L) { + roleDto.setLevel("L4"); + }else { + roleDto.setLevel(roleDto.getRoleKey()); + } + + } + + userDto.setRole(roleDto); + + //AjaxResult ajax = AjaxResult.success(userDto); + + //ajax.put("roleGroup", userService.selectUserRoleGroup(username)); + return AjaxResult.success(userDto); + } + + @PostMapping("userLevel") + public R userLevel(@RequestBody() Long userId) + { + //先看用户是否有会员记录 有才查询 + System.out.println("调用system接口查询userLevel"); + System.out.println("用户id:"+userId); + SysUserLeveDate info = userLevelMapper.getUserLevelInfo(userId); + return R.success(info); + } + + /** + * 修改用户 + */ + @PutMapping + @ApiOperation(value = "个人信息") + @RequiresLogin + public AjaxResult updateProfile(@RequestBody SysUser user) + { + LoginUser loginUser = SecurityUtils.getLoginUser(); + SysUser sysUser = loginUser.getSysUser(); + user.setUserName(sysUser.getUserName()); + if (StringUtils.isNotEmpty(user.getPhone()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) + { + return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUserId(sysUser.getUserId()); + user.setPassword(null); + if (userService.updateUserProfile(user) > 0) + { + // 更新缓存用户信息 + loginUser.getSysUser().setNickName(user.getNickName()); + loginUser.getSysUser().setPhone(user.getPhone()); + loginUser.getSysUser().setEmail(user.getEmail()); + loginUser.getSysUser().setSex(user.getSex()); + tokenService.setLoginUser(loginUser); + return AjaxResult.success(); + } + return AjaxResult.error("修改个人信息异常,请联系管理员"); + } + + /** + * 重置密码(已知原密码修改为新密码) + */ + @ApiOperation(value = "重置密码") + @PutMapping("/updatePwd") + @RequiresLogin + public AjaxResult updatePwd(String oldPassword, String newPassword) + { + String username = SecurityUtils.getUsername(); + SysUser user = userService.selectUserByUserName(username); + String password = user.getPassword(); + if (!SecurityUtils.matchesPassword(oldPassword, password)) + { + return AjaxResult.error("修改密码失败,旧密码错误"); + } + if (SecurityUtils.matchesPassword(newPassword, password)) + { + return AjaxResult.error("新密码不能与旧密码相同"); + } + if (userService. resetUserPwd(username, SecurityUtils.encryptPassword(newPassword)) > 0) + { + // 更新缓存用户密码 + LoginUser loginUser = SecurityUtils.getLoginUser(); + loginUser.getSysUser().setPassword(SecurityUtils.encryptPassword(newPassword)); + tokenService.setLoginUser(loginUser); + return AjaxResult.success(); + } + return AjaxResult.error("修改密码异常,请联系管理员"); + } + + /** + * 重置密码(忘记原密码通过邮箱验证修改为新密码) + */ + @ApiOperation(value = "重置密码") + @PostMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user){ + + if(userService.resetPwdByEmail(user.getEmail(),user.getPassword()) > 0){ + // + return AjaxResult.success(); + } + return AjaxResult.error("修改密码异常,请联系管理员"); + } + + + + /** + * 用户升级 + */ + @PostMapping("/Levelup") + @ApiOperation(value = "用户升级") + @InnerAuth + public AjaxResult LevelUp(@RequestBody SysUserLeveDate user) + { + Long roleId = user.getRoleId(); + if(roleId == 1 || roleId > 4){ + return AjaxResult.success("流程最后一步修改用户等级,admin等内部账户不会更改等级"); + } + + if (userService.userLevelUp(user) > 0) + { + return AjaxResult.success("用户已升级"); + } + return AjaxResult.error("修改个人信息异常,请联系管理员"); + } + + /** + * 谷歌安全验证码验证 + */ + @InnerAuth + @ApiOperation(value = "谷歌安全验证码验证") + @PostMapping("/checkGoogleCode") + public R checkGoogleCode(@RequestBody Long code){ + return R.success(userService.checkGoogleCode(code)); + } + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysUserController.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysUserController.java new file mode 100644 index 0000000..fa0506e --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/controller/SysUserController.java @@ -0,0 +1,237 @@ +package com.m2pool.system.controller; + +import com.m2pool.common.core.Result.R; +import com.m2pool.common.core.constant.UserConstants; +import com.m2pool.common.core.utils.DateUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.core.web.controller.BaseController; +import com.m2pool.common.log.annotation.Log; +import com.m2pool.common.log.enums.BusinessType; +import com.m2pool.common.security.annotation.InnerAuth; +import com.m2pool.common.security.annotation.RequiresPermissions; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.entity.SysRole; +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.api.model.LoginUser; +import com.m2pool.system.service.SysPermissionService; +import com.m2pool.system.service.SysRoleService; +import com.m2pool.system.service.SysUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + + +/** + * @Description TODO + * @Date 2022/6/14 17:33 + * @Author 杜懿 + */ +@RestController +@RequestMapping("/user") +@Api(tags = "用户相关接口") +public class SysUserController extends BaseController { + + @Autowired + private SysUserService userService; + + @Autowired + private SysRoleService roleService; + + @Autowired + private SysPermissionService permissionService; + + /** + * 根据用户名获取当前用户信息 + */ + @InnerAuth + @GetMapping("/info/{account}") + @ApiOperation(value = "",response = List.class) + public R info(@PathVariable("account") String account) + { + String em = "^[a-zA-Z0-9][\\w\\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\\w\\.-]*[a-zA-Z0-9]\\.[a-zA-Z][a-zA-Z\\.]*[a-zA-Z]$"; + + System.out.println(account); + + + SysUser user = userService.selectUserByEmail(account); + + if (StringUtils.isNull(user)) + { + return R.fail("邮箱未被注册"); + } + + List roleIdList = roleService.selectRoleListByUserId(user.getUserId()); + Long[] roleIds = roleIdList.toArray(new Long[roleIdList.size()]); + user.setRoleIds(roleIds); + //if(roleIds.length > 0){ + // user.setRoleId(roleIds[0]); + //} + + + // 角色集合 + Set roles = permissionService.getRolePermission(user.getUserId()); + // 权限集合 + Set permissions = permissionService.getMenuPermission(user.getUserId()); + LoginUser sysUserVo = new LoginUser(); + sysUserVo.setUserid(user.getUserId()); + sysUserVo.setUsername(user.getUserName()); + sysUserVo.setIpaddr(user.getLoginIp()); + sysUserVo.setSysUser(user); + sysUserVo.setRoles(roles); + sysUserVo.setPermissions(permissions); + return R.success(sysUserVo); + } + + + /** + * 注册用户信息 + */ + //@InnerAuth + @PostMapping("/register") + public R register(@RequestBody SysUser sysUser) + { + String username = sysUser.getUserName(); + String email = sysUser.getEmail(); + if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username))) + { + return R.fail("用户'" + username + "'注册失败,注册账号已存在"); + } + if (UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(sysUser))) + { + return R.fail("用户'" + username + "'注册失败,邮箱"+email+"已被其他用户注册"); + } + + return R.success(userService.registerUser(sysUser)); + } + + /** + * 修改用户登录次数和最后一次登录的ip + */ + @PostMapping("/update") + public AjaxResult update(@RequestBody SysUser user) + { + //user.setUpdateBy(SecurityUtils.getUsername()); + return toAjax(userService.updateUser(user)); + } + + /** + * 修改用户 + */ + @RequiresPermissions("system:user:edit") + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + if (StringUtils.isNotEmpty(user.getPhone()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) + { + return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUpdateBy(SecurityUtils.getUsername()); + List roleIdList = roleService.selectRoleListByUserId(user.getUserId()); + Long[] roleIds = roleIdList.toArray(new Long[roleIdList.size()]); + user.setRoleIds(roleIds); + return toAjax(userService.updateUser(user)); + } + + /** + * 重置密码 + */ + @RequiresPermissions("system:user:edit") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + user.setUpdateBy(SecurityUtils.getUsername()); + return toAjax(userService.resetPwd(user)); + } + + /** + * 状态修改 + */ + @RequiresPermissions("system:user:edit") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setUpdateBy(SecurityUtils.getUsername()); + return toAjax(userService.updateUserStatus(user)); + } + + /** + * 根据用户编号获取授权角色 + */ + @RequiresPermissions("system:user:query") + @GetMapping("/authRole/{userId}") + public AjaxResult authRole(@PathVariable("userId") Long userId) + { + AjaxResult ajax = AjaxResult.success(); + SysUser user = userService.selectUserById(userId); + List roles = roleService.selectRolesByUserId(userId); + ajax.put("user", user); + ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + return ajax; + } + + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @GetMapping("getInfo") + public AjaxResult getInfo() + { + Long userId = SecurityUtils.getUserId(); + // 角色集合 + Set roles = permissionService.getRolePermission(userId); + // 权限集合 + Set permissions = permissionService.getMenuPermission(userId); + AjaxResult ajax = AjaxResult.success(); + ajax.put("user", userService.selectUserById(userId)); + ajax.put("roles", roles); + ajax.put("permissions", permissions); + return ajax; + } + + @GetMapping("/checkMail/{email}") + public AjaxResult checkMail(@PathVariable("email") String email) + { + + String em = "^[a-zA-Z0-9][\\w\\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\\w\\.-]*[a-zA-Z0-9]\\.[a-zA-Z][a-zA-Z\\.]*[a-zA-Z]$"; + if(!email.matches(em)){ + return AjaxResult.error(300,"邮箱格式错误"); + } + + + if (UserConstants.NOT_UNIQUE.equals(userService.checkEmailExist(email))) + { + return AjaxResult.error(301,"邮箱已被注册"); + } + return AjaxResult.success("邮箱未被注册"); + } + + @GetMapping("/getCSList") + public R> getCSList() + { + List list = userService.getCSList(); + return R.success(list); + } +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/GoogleInfo.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/GoogleInfo.java new file mode 100644 index 0000000..3563903 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/GoogleInfo.java @@ -0,0 +1,25 @@ +package com.m2pool.system.entity; + + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Description 文件 + * @Date 2024/6/14 15:57 + * @Author dy + */ +@Data +public class GoogleInfo implements Serializable { + + /** 邮箱 */ + private String email; + + /** 谷歌验证码 */ + private String secret; + + /** 谷歌验证码 */ + private int status; + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysMenu.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysMenu.java new file mode 100644 index 0000000..ca14e89 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysMenu.java @@ -0,0 +1,262 @@ +package com.m2pool.system.entity; + +import com.m2pool.common.core.web.entity.BaseEntity; +import lombok.Data; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.List; + +/** + * 菜单权限表 sys_menu + * + * @author dy + */ +@Data +public class SysMenu extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 菜单ID */ + private Long menuId; + + /** 菜单名称 */ + private String menuName; + + /** 父菜单名称 */ + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private Integer orderNum; + + /** 路由地址 */ + private String path; + + /** 组件路径 */ + private String component; + + /** 路由参数 */ + private String query; + + /** 是否为外链(0是 1否) */ + private String isFrame; + + /** 是否缓存(0缓存 1不缓存) */ + private String isCache; + + /** 类型(M目录 C菜单 F按钮) */ + private String menuType; + + /** 显示状态(0显示 1隐藏) */ + private String visible; + + /** 菜单状态(0显示 1隐藏) */ + private String status; + + /** 权限字符串 */ + private String perms; + + /** 菜单图标 */ + private String icon; + + /** 子菜单 */ + private List children = new ArrayList(); + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @NotBlank(message = "菜单名称不能为空") + @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符") + public String getMenuName() + { + return menuName; + } + + public void setMenuName(String menuName) + { + this.menuName = menuName; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + @Size(min = 0, max = 200, message = "路由地址不能超过200个字符") + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + @Size(min = 0, max = 200, message = "组件路径不能超过255个字符") + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public String getIsFrame() + { + return isFrame; + } + + public void setIsFrame(String isFrame) + { + this.isFrame = isFrame; + } + + public String getIsCache() + { + return isCache; + } + + public void setIsCache(String isCache) + { + this.isCache = isCache; + } + + @NotBlank(message = "菜单类型不能为空") + public String getMenuType() + { + return menuType; + } + + public void setMenuType(String menuType) + { + this.menuType = menuType; + } + + public String getVisible() + { + return visible; + } + + public void setVisible(String visible) + { + this.visible = visible; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") + public String getPerms() + { + return perms; + } + + public void setPerms(String perms) + { + this.perms = perms; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("menuId", getMenuId()) + .append("menuName", getMenuName()) + .append("parentId", getParentId()) + .append("orderNum", getOrderNum()) + .append("path", getPath()) + .append("component", getComponent()) + .append("isFrame", getIsFrame()) + .append("IsCache", getIsCache()) + .append("menuType", getMenuType()) + .append("visible", getVisible()) + .append("status ", getStatus()) + .append("perms", getPerms()) + .append("icon", getIcon()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysRoleDept.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysRoleDept.java new file mode 100644 index 0000000..2c0183b --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysRoleDept.java @@ -0,0 +1,18 @@ +package com.m2pool.system.entity; + +import lombok.Data; + +/** + * @Description 角色部门表实体类 + * @Date 2024/6/13 10:53 + * @Author dy + */ +@Data +public class SysRoleDept { + + /** 角色ID */ + private Long roleId; + + /** 部门ID */ + private Long deptId; +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysRoleDto.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysRoleDto.java new file mode 100644 index 0000000..40f47f7 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysRoleDto.java @@ -0,0 +1,30 @@ +package com.m2pool.system.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2024/6/13 11:41 + * @Author dy + */ +@Data +public class SysRoleDto { + + private static final long serialVersionUID = 1941856464L; + + /** 角色ID */ + private Long roleId; + + /** 角色名称 */ + private String roleName; + + /** 角色权限 */ + private String roleKey; + + private Date LevelExpireDate; + + private String level; + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysRoleMenu.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysRoleMenu.java new file mode 100644 index 0000000..45f530d --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysRoleMenu.java @@ -0,0 +1,46 @@ +package com.m2pool.system.entity; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 角色和菜单关联 sys_role_menu + * + * @author dy + */ +public class SysRoleMenu +{ + /** 角色ID */ + private Long roleId; + + /** 菜单ID */ + private Long menuId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("menuId", getMenuId()) + .toString(); + } +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysUserDto.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysUserDto.java new file mode 100644 index 0000000..a6288b3 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysUserDto.java @@ -0,0 +1,55 @@ +package com.m2pool.system.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2024/6/13 11:51 + * @Author dy + */ +@Data +public class SysUserDto { + + + private static final long serialVersionUID = 1L; + + /** 用户ID */ + private Long userId; + + /** 用户账号 */ + private String userName; + + /** 用户昵称 */ + private String nickName; + + /** 用户邮箱 */ + private String email; + + /** 手机号码 */ + private String phone; + + /** 用户性别 */ + private String sex; + + /** 帐号状态(0正常 1停用) */ + //private String status; + + /** 删除标志(0代表存在 2代表删除) */ + //private String delFlag; + + /** 最后登录IP */ + private String loginIp; + + /** 登录次数 */ + private Long loginCount; + + /** 最后登录时间 */ + private Date loginDate; + + /** 角色对象 */ + private SysRoleDto role; + + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysUserRole.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysUserRole.java new file mode 100644 index 0000000..a84a4c2 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/SysUserRole.java @@ -0,0 +1,19 @@ +package com.m2pool.system.entity; + +import lombok.Data; + +/** + * @Description 用户角色中间表实体 + * @Date 2024/6/13 10:47 + * @Author dy + */ +@Data +public class SysUserRole { + + /** 用户ID */ + private Long userId; + + /** 角色ID */ + private Long roleId; + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/UserLevelInfo.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/UserLevelInfo.java new file mode 100644 index 0000000..8becc8a --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/entity/UserLevelInfo.java @@ -0,0 +1,33 @@ +package com.m2pool.system.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * @Description TODO + * @Date 2024/6/14 10:41 + * @Author dy + */ +@Data +public class UserLevelInfo { + + private Long id; + + /** 用户ID */ + private Long userId; + + private String userName; + + private Date createTime; + + private Date expireTime; + + /** 会员状态(0正常 1停用) */ + private int status; + + /** 删除标志(0代表存在 2代表删除) */ + //private String delFlag; + + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysLogininforMapper.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysLogininforMapper.java new file mode 100644 index 0000000..1c07d1a --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysLogininforMapper.java @@ -0,0 +1,46 @@ +package com.m2pool.system.mapper; + + +import com.m2pool.common.datasource.annotation.Master; +import com.m2pool.system.api.entity.SysLogininfor; + +import java.util.List; + +/** + * 系统访问日志情况信息 数据层 + * + * @author dy + */ +@Master +public interface SysLogininforMapper +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public int insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + * + * @return 结果 + */ + public int cleanLogininfor(); +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysMenuMapper.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..75f9a17 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysMenuMapper.java @@ -0,0 +1,23 @@ +package com.m2pool.system.mapper; + +import com.m2pool.common.datasource.annotation.Master; + +import java.util.List; + +/** + * @Description 角色数据层 + * @Date 2024/6/12 17:37 + * @Author dy + */ +@Master +public interface SysMenuMapper { + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public List selectMenuPermsByUserId(Long userId); + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysOperLogMapper.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysOperLogMapper.java new file mode 100644 index 0000000..3003ede --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysOperLogMapper.java @@ -0,0 +1,53 @@ +package com.m2pool.system.mapper; + + + +import com.m2pool.common.datasource.annotation.Master; +import com.m2pool.system.api.entity.SysOperLog; + +import java.util.List; + +/** + * 操作日志 数据层 + * + * @author dy + */ +@Master +public interface SysOperLogMapper +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public int insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysRoleMapper.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..9742c66 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysRoleMapper.java @@ -0,0 +1,59 @@ +package com.m2pool.system.mapper; + +import com.m2pool.common.datasource.annotation.Master; +import com.m2pool.system.api.entity.SysRole; + +import java.util.List; + +/** + * @Description 角色数据层 + * @Date 2024/6/12 17:37 + * @Author dy + */ +@Master +public interface SysRoleMapper { + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + + /** + * 根据用户名查询角色 + * + * @param userName 用户名 + * @return 角色列表 + */ + public List selectRolesByUserName(String userName); + + public Long[] getResigesterId(); + + public Long[] getVipId(); +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysRoleMenuMapper.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..ca3f625 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,47 @@ +package com.m2pool.system.mapper; + +import com.m2pool.common.datasource.annotation.Master; +import com.m2pool.system.entity.SysRoleMenu; + +import java.util.List; + +/** + * 角色与菜单关联表 数据层 + * + * @author dy + */ +@Master +public interface SysRoleMenuMapper +{ + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int checkMenuExistRole(Long menuId); + + /** + * 通过角色ID删除角色和菜单关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleMenuByRoleId(Long roleId); + + /** + * 批量删除角色菜单关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleMenu(Long[] ids); + + /** + * 批量新增角色菜单信息 + * + * @param roleMenuList 角色菜单列表 + * @return 结果 + */ + public int batchRoleMenu(List roleMenuList); +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysUserLevelMapper.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysUserLevelMapper.java new file mode 100644 index 0000000..5e9dc79 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysUserLevelMapper.java @@ -0,0 +1,36 @@ +package com.m2pool.system.mapper; + +import com.m2pool.common.datasource.annotation.Master; +import com.m2pool.system.api.entity.SysUserLeveDate; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @Description TODO + * @Date 2024/6/13 11:33 + * @Author dy + */ +@Master +public interface SysUserLevelMapper { + + public SysUserLeveDate getUserLevelInfo(@Param("userId") Long userId); + + /** + * 批量(逻辑)删除过期用户 根据会员时间表id + * @param ids + */ + public void deleteExpireUserLevelInfo(@Param("ids") List ids); + + public int deleteUserLevelInfoByUserId(@Param("userId") Long userId); + + public int createUserLevelInfo(@Param("vo") SysUserLeveDate vo); + + /** + * 获取过期会员 + * @return + */ + public List getExpireUserIds(); + + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysUserMapper.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..b13d17f --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysUserMapper.java @@ -0,0 +1,132 @@ +package com.m2pool.system.mapper; + +import com.m2pool.common.datasource.annotation.Master; +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.entity.GoogleInfo; +import org.apache.ibatis.annotations.MapKey; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * @Description TODO + * @Date 2024/6/14 10:37 + * @Author dy + */ +@Master +public interface SysUserMapper { + + /** + * 根据条件分页查询用户列表 + * + * @param sysUser 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser sysUser); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过邮箱查询用户 + * + * @param email 邮箱 + * @return 用户对象信息 + */ + public SysUser selectUserByEmail(String email); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public int checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public SysUser checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public SysUser checkEmailUnique(String email); + + /** + * 校验email是否存在 + * + * @param email 用户邮箱 + * @return 结果 + */ + public String checkEmailExist(String email); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(@Param("userName") String userName, @Param("password") String password); + + + /** + * 重置用户密码 + * + * @param email 邮箱 + * @param password 密码 + * @return 结果 + */ + public int resetPwdByEmail(@Param("email") String email, @Param("password") String password); + + + @MapKey("user_id") + public Map getUserInfoMap(); + + /** + * 获取谷歌验证码 + * @param email + * @return + */ + public GoogleInfo getGoogleInfoByEmail(@Param("email") String email); + + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysUserRoleMapper.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..c61f105 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,73 @@ +package com.m2pool.system.mapper; + +import com.m2pool.common.datasource.annotation.Master; +import com.m2pool.system.entity.SysUserRole; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户与角色关联表 数据层 + * + * @author dy + */ +@Master +public interface SysUserRoleMapper +{ + /** + * 通过用户ID删除用户和角色关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserRoleByUserId(Long userId); + + /** + * 批量删除用户和角色关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserRole(Long[] ids); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 批量新增用户角色信息 + * + * @param userRoleList 用户角色列表 + * @return 结果 + */ + public int batchUserRole(List userRoleList); + + /** + * 批量新增用户角色信息 + * + * @param userRole 用户角色列表 + * @return 结果 + */ + public int createUserRole(SysUserRole userRole); + + /** + * 删除用户和角色关联信息 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteUserRoleInfo(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds); +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysLogininforService.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysLogininforService.java new file mode 100644 index 0000000..bdc3cb5 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysLogininforService.java @@ -0,0 +1,42 @@ +package com.m2pool.system.service; + + +import com.m2pool.system.api.entity.SysLogininfor; + +import java.util.List; + +/** + * 系统访问日志情况信息 服务层 + * + * @author dy + */ +public interface SysLogininforService +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public int insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + */ + public void cleanLogininfor(); +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysMenuService.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysMenuService.java new file mode 100644 index 0000000..4475b3d --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysMenuService.java @@ -0,0 +1,14 @@ +package com.m2pool.system.service; + +import java.util.Set; + +public interface SysMenuService { + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectMenuPermsByUserId(Long userId); +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysOperLogService.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysOperLogService.java new file mode 100644 index 0000000..0595446 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysOperLogService.java @@ -0,0 +1,51 @@ +package com.m2pool.system.service; + + +import com.m2pool.system.api.entity.SysOperLog; + +import java.util.List; + +/** + * 操作日志 服务层 + * + * @author dy + */ +public interface SysOperLogService +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + * @return 结果 + */ + public int insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysPermissionService.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysPermissionService.java new file mode 100644 index 0000000..f9c184d --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysPermissionService.java @@ -0,0 +1,22 @@ +package com.m2pool.system.service; + +import java.util.Set; + +public interface SysPermissionService { + + /** + * 获取角色数据权限 + * + * @param userId 用户Id + * @return 角色权限信息 + */ + public Set getRolePermission(Long userId); + + /** + * 获取菜单数据权限 + * + * @param userId 用户Id + * @return 菜单权限信息 + */ + public Set getMenuPermission(Long userId); +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysRoleService.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysRoleService.java new file mode 100644 index 0000000..625fd97 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysRoleService.java @@ -0,0 +1,49 @@ +package com.m2pool.system.service; + +import com.m2pool.system.api.entity.SysRole; + +import java.util.List; +import java.util.Set; + +public interface SysRoleService { + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectRolePermissionByUserId(Long userId); + + /** + * 根据用户ID查询角色列表 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolesByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysUserService.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysUserService.java new file mode 100644 index 0000000..fcfe7c0 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/SysUserService.java @@ -0,0 +1,176 @@ +package com.m2pool.system.service; + +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.api.entity.SysUserLeveDate; + +import java.util.List; + +public interface SysUserService { + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + + /** + * 通过邮箱查询用户 + * + * @param email 邮箱 + * @return 用户对象信息 + */ + public SysUser selectUserByEmail(String email); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 根据用户ID查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserRoleGroup(String userName); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public String checkUserNameUnique(String userName); + + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public String checkPhoneUnique(SysUser user); + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public String checkEmailUnique(SysUser user); + + /** + * 校验email是否唯一 + * + * @param email 用户信息 + * @return 结果 + */ + public String checkEmailExist(String email); + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + public void checkUserAllowed(SysUser user); + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + public void checkUserDataScope(Long userId); + + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean registerUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserStatus(SysUser user); + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserProfile(SysUser user); + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + public int resetPwd(SysUser user); + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(String userName, String password); + + /** + * 重置用户密码 + * + * @param email 邮箱 + * @param password 密码 + * @return 结果 + */ + public int resetPwdByEmail(String email, String password); + + /** + * 重置用户密码 + * @return 结果 + * @param userLeveDate + */ + public int userLevelUp(SysUserLeveDate userLeveDate); + + /** + * 谷歌验证码校验 + * @return 结果 + * @param code + */ + public boolean checkGoogleCode(Long code); + + /** + * 查询所有聊天客服 + * @return 结果 + */ + public List getCSList(); +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysLogininforServiceImpl.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysLogininforServiceImpl.java new file mode 100644 index 0000000..cb20c91 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysLogininforServiceImpl.java @@ -0,0 +1,67 @@ +package com.m2pool.system.service.impl; + +import com.m2pool.system.api.entity.SysLogininfor; +import com.m2pool.system.mapper.SysLogininforMapper; +import com.m2pool.system.service.SysLogininforService; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 系统访问日志情况信息 服务层处理 + * + * @author dy + */ +@Service +public class SysLogininforServiceImpl implements SysLogininforService +{ + + @Autowired + private SysLogininforMapper logininforMapper; + + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + @Override + public int insertLogininfor(SysLogininfor logininfor) + { + return logininforMapper.insertLogininfor(logininfor); + } + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + @Override + public List selectLogininforList(SysLogininfor logininfor) + { + return logininforMapper.selectLogininforList(logininfor); + } + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + @Override + public int deleteLogininforByIds(Long[] infoIds) + { + return logininforMapper.deleteLogininforByIds(infoIds); + } + + /** + * 清空系统登录日志 + */ + @Override + public void cleanLogininfor() + { + logininforMapper.cleanLogininfor(); + } +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysMenuServiceImpl.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..f7798b3 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,39 @@ +package com.m2pool.system.service.impl; + +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.system.mapper.SysMenuMapper; +import com.m2pool.system.service.SysMenuService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @Description 用户业务层 + * @Date 2024/6/14 10:37 + * @Author dy + */ +@Service +public class SysMenuServiceImpl implements SysMenuService { + + @Autowired + private SysMenuMapper menuMapper; + + + @Override + public Set selectMenuPermsByUserId(Long userId) { + List perms = menuMapper.selectMenuPermsByUserId(userId); + Set permsSet = new HashSet<>(); + for (String perm : perms) + { + if (StringUtils.isNotEmpty(perm)) + { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysOperLogServiceImpl.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysOperLogServiceImpl.java new file mode 100644 index 0000000..6ab3e69 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysOperLogServiceImpl.java @@ -0,0 +1,78 @@ +package com.m2pool.system.service.impl; + +import com.m2pool.system.api.entity.SysOperLog; +import com.m2pool.system.mapper.SysOperLogMapper; +import com.m2pool.system.service.SysOperLogService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 操作日志 服务层处理 + * + * @author dy + */ +@Service +public class SysOperLogServiceImpl implements SysOperLogService +{ + @Autowired + private SysOperLogMapper operLogMapper; + + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + * @return 结果 + */ + @Override + public int insertOperlog(SysOperLog operLog) + { + return operLogMapper.insertOperlog(operLog); + } + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + @Override + public List selectOperLogList(SysOperLog operLog) + { + return operLogMapper.selectOperLogList(operLog); + } + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + @Override + public int deleteOperLogByIds(Long[] operIds) + { + return operLogMapper.deleteOperLogByIds(operIds); + } + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + @Override + public SysOperLog selectOperLogById(Long operId) + { + return operLogMapper.selectOperLogById(operId); + } + + /** + * 清空操作日志 + */ + @Override + public void cleanOperLog() + { + operLogMapper.cleanOperLog(); + } +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysPermissionServiceImpl.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysPermissionServiceImpl.java new file mode 100644 index 0000000..9ca953b --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysPermissionServiceImpl.java @@ -0,0 +1,58 @@ +package com.m2pool.system.service.impl; + +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.service.SysMenuService; +import com.m2pool.system.service.SysPermissionService; +import com.m2pool.system.service.SysRoleService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.Set; + +/** + * @Description 权限实现类 + * @Date 2024/6/14 11:37 + * @Author dy + */ +@Service +public class SysPermissionServiceImpl implements SysPermissionService { + + @Autowired + private SysRoleService roleService; + + @Autowired + private SysMenuService menuService; + + @Override + public Set getRolePermission(Long userId) { + + Set roles = new HashSet(); + // 管理员拥有所有权限 + if (SysUser.isAdmin(userId)) + { + roles.add("admin"); + } + else + { + roles.addAll(roleService.selectRolePermissionByUserId(userId)); + } + return roles; + } + + @Override + public Set getMenuPermission(Long userId) { + + Set perms = new HashSet(); + // 管理员拥有所有权限 + if (SysUser.isAdmin(userId)) + { + perms.add("*:*:*"); + } + else + { + perms.addAll(menuService.selectMenuPermsByUserId(userId)); + } + return perms; + } +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysRoleServiceImpl.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..1ca1199 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,113 @@ +package com.m2pool.system.service.impl; + +import com.m2pool.common.core.utils.SpringUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.datascope.annotation.DataScope; +import com.m2pool.system.api.entity.SysRole; +import com.m2pool.system.mapper.SysRoleMapper; +import com.m2pool.system.service.SysRoleService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @Description 用户业务层 + * @Date 2024/6/14 10:37 + * @Author dy + */ +@Service +public class SysRoleServiceImpl implements SysRoleService { + + private static final Logger log = LoggerFactory.getLogger(SysRoleServiceImpl.class); + + @Autowired + private SysRoleMapper roleMapper; + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + @Override + @DataScope(deptAlias = "d") + public List selectRoleList(SysRole role) + { + return roleMapper.selectRoleList(role); + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectRolePermissionByUserId(Long userId) + { + List perms = roleMapper.selectRolePermissionByUserId(userId); + Set permsSet = new HashSet<>(); + for (SysRole perm : perms) + { + if (StringUtils.isNotNull(perm)) + { + permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + @Override + public List selectRolesByUserId(Long userId) + { + List userRoles = roleMapper.selectRolePermissionByUserId(userId); + List roles = selectRoleAll(); + for (SysRole role : roles) + { + for (SysRole userRole : userRoles) + { + if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) + { + role.setFlag(true); + break; + } + } + } + return roles; + } + + /** + * 根据用户ID获取用户角色id列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + @Override + public List selectRoleListByUserId(Long userId) { + return roleMapper.selectRoleListByUserId(userId); + } + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + @Override + public List selectRoleAll() + { + return SpringUtils.getAopProxy(this).selectRoleList(new SysRole()); + } + +} diff --git a/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysUserServiceImpl.java b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..f45b0f8 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/java/com/m2pool/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,445 @@ +package com.m2pool.system.service.impl; + +import com.m2pool.common.core.constant.UserConstants; +import com.m2pool.common.core.exception.ServiceException; +import com.m2pool.common.core.utils.GoogleAuthenticator; +import com.m2pool.common.core.utils.SpringUtils; +import com.m2pool.common.core.utils.StringUtils; +import com.m2pool.common.core.web.Result.AjaxResult; +import com.m2pool.common.datascope.annotation.DataScope; +import com.m2pool.common.security.utils.SecurityUtils; +import com.m2pool.system.api.entity.SysRole; +import com.m2pool.system.api.entity.SysUser; +import com.m2pool.system.api.entity.SysUserLeveDate; +import com.m2pool.system.entity.GoogleInfo; +import com.m2pool.system.entity.SysUserRole; +import com.m2pool.system.mapper.SysRoleMapper; +import com.m2pool.system.mapper.SysUserLevelMapper; +import com.m2pool.system.mapper.SysUserMapper; +import com.m2pool.system.mapper.SysUserRoleMapper; +import com.m2pool.system.service.SysUserService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + + +/** + * @Description 用户业务层 + * @Date 2024/6/14 10:37 + * @Author dy + */ +@Service +public class SysUserServiceImpl implements SysUserService { + + + private static final Logger log = LoggerFactory.getLogger(SysPermissionServiceImpl.class); + + @Autowired + private SysUserMapper userMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysUserLevelMapper userLevelMapper; + + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUserList(SysUser user) + { + return userMapper.selectUserList(user); + } + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByUserName(String userName) { + return userMapper.selectUserByUserName(userName); + } + + /** + * 通过邮箱查询用户 + * + * @param email 邮箱 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByEmail(String email) { + return userMapper.selectUserByEmail(email); + } + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + @Override + public SysUser selectUserById(Long userId) + { + return userMapper.selectUserById(userId); + } + + /** + * 查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserRoleGroup(String userName) + { + List list = roleMapper.selectRolesByUserName(userName); + if (CollectionUtils.isEmpty(list)) + { + return StringUtils.EMPTY; + } + return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(",")); + } + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + @Override + public String checkUserNameUnique(String userName) { + int count = userMapper.checkUserNameUnique(userName); + if (count > 0) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public String checkPhoneUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkPhoneUnique(user.getPhone()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public String checkEmailUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkEmailUnique(user.getEmail()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验email是否存在 + * + * @param email 邮箱 + * @return + */ + @Override + public String checkEmailExist(String email) + { + String dbEmail = userMapper.checkEmailExist(email); + if (StringUtils.isNotNull(dbEmail)) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + @Override + public void checkUserAllowed(SysUser user) + { + if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) + { + throw new ServiceException("不允许操作超级管理员用户"); + } + } + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + @Override + public void checkUserDataScope(Long userId) + { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + SysUser user = new SysUser(); + user.setUserId(userId); + List users = SpringUtils.getAopProxy(this).selectUserList(user); + if (StringUtils.isEmpty(users)) + { + throw new ServiceException("没有权限访问用户数据!"); + } + } + } + + + @Override + public boolean registerUser(SysUser user) { + + boolean result = userMapper.insertUser(user) > 0; + if(result){ + //用户添加成功后 默认设置用户为注册用户 + SysUser dbUser = userMapper.selectUserByUserName(user.getUserName()); + user.setUserId(dbUser.getUserId()); + + //查询角色:“注册用户”的id并且赋值到user中 + Long[] roleIds = roleMapper.getResigesterId(); + //先把注册用户等级都升为vip 有效期三个月(后通过定时任务去控制) + //Long[] roleIds = roleMapper.getVipId(); + user.setRoleIds(roleIds); + insertUserRole(user); + + } + + return result; + } + + /** + * 修改保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateUser(SysUser user) + { + Long userId = user.getUserId(); + + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 新增用户与角色管理 此处要保证传过来的user中要携带roleIds + insertUserRole(user); + //// 删除用户与岗位关联 + //userPostMapper.deleteUserPostByUserId(userId); + //// 新增用户与岗位管理 + //insertUserPost(user); + return userMapper.updateUser(user); + } + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserStatus(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserProfile(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int resetPwd(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + @Override + public int resetUserPwd(String userName, String password) + { + return userMapper.resetUserPwd(userName, password); + } + + /** + * 重置用户密码 + * + * @param email 邮箱 + * @param password 密码 + * @return 结果 + */ + @Override + public int resetPwdByEmail(String email, String password) + { + return userMapper.resetPwdByEmail(email, password); + } + + @Override + public int userLevelUp(SysUserLeveDate userInfo) { + System.out.println("用户升级服务收到用户信息:"+userInfo); + Long userId = userInfo.getUserId(); + if(userInfo.getOper() == 1){ + //执行续费操作 用户角色不改变 会员过期日期在原有基础上增加 + //1、查询会员有效期 + SysUserLeveDate dbUserInfo = userLevelMapper.getUserLevelInfo(userId); + Date date = dbUserInfo.getExpireTime(); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + if(userInfo.getLevelType() > 0){ + //年度会员/年度vip 会员有效期设置为一年 + calendar.add(Calendar.YEAR,1); + }else { + calendar.add(Calendar.MONTH,1); + } + //2、根据购买类型修改会员有效期 + userInfo.setStartTime(dbUserInfo.getStartTime()); + userInfo.setExpireTime(calendar.getTime()); + + + userLevelMapper.deleteUserLevelInfoByUserId(userInfo.getUserId()); + // 新增用户与用户会员有效期关联 + return userLevelMapper.createUserLevelInfo(userInfo); + + }else{ + //执行升级操作 用户角色改变 会员过期日期在更新 + + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 新增用户与角色管理 此处要保证传过来的user中要携带roleIds + if (StringUtils.isNotNull(userInfo.getRoleId())) + { + // 新增用户与角色关联 + SysUserRole ur = new SysUserRole(); + ur.setUserId(userInfo.getUserId()); + ur.setRoleId(userInfo.getRoleId()); + int userRole = userRoleMapper.createUserRole(ur); + System.out.println("用户角色修改结果:"+userRole); + //获取用户当前会员有效期信息 根据情况决定如何处理 + // 相同类型则表示续费、直接增加日期 不同类型则直接删除有效期再更新 + // 删除用户与用户会员有效期关联 + userLevelMapper.deleteUserLevelInfoByUserId(userInfo.getUserId()); + // 新增用户与用户会员有效期关联 + return userLevelMapper.createUserLevelInfo(userInfo); + } + } + return 0; + } + + @Override + public boolean checkGoogleCode(Long code) { + String username = SecurityUtils.getUsername(); + + //谷歌验证码校验 + GoogleInfo info = userMapper.getGoogleInfoByEmail(username); + if(StringUtils.isNull(info)){ + throw new ServiceException("网络异常,请稍后再试"); + } + + //没有绑定谷歌验证器 校验直接通过 + if(StringUtils.isBlank(info.getSecret())){ + return true; + } + + //没有绑定谷歌验证器 校验直接通过 + if(info.getStatus() == 0){ + return true; + } + + if(GoogleAuthenticator.checkCode(info.getSecret(), code, System.currentTimeMillis())){ + return true; + }else { + throw new ServiceException("验证码错误"); + } + } + + @Override + public List getCSList() { + + return null; + } + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + */ + public void insertUserRole(SysUser user) + { + //需要确保user.getRoleIds在整个传递过程中不丢失 + Long[] roles = user.getRoleIds(); + if (StringUtils.isNotNull(roles)) + { + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long roleId : roles) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(user.getUserId()); + ur.setRoleId(roleId); + list.add(ur); + } + if (list.size() > 0) + { + userRoleMapper.batchUserRole(list); + } + } + } + + +} diff --git a/m2pool-modules/m2pool-system/src/main/resources/bootstrap.yml b/m2pool-modules/m2pool-system/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..f430808 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/resources/bootstrap.yml @@ -0,0 +1,58 @@ +## Tomcat +#server: +# port: 9201 +# +## Spring +#spring: +# application: +# # 应用名称 +# name: m2pool-system +# profiles: +# # 环境配置 +# active: prod +# cloud: +# nacos: +# discovery: +# # 服务注册地址 +# server-addr: 127.0.0.1:8808 +# namespace: m2_prod +# group: m2_prod_group +# config: +# # 配置中心地址 +# server-addr: 127.0.0.1:8808 +# # 配置文件格式 +# file-extension: yml +# # 共享配置 +# shared-configs: +# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} +# namespace: m2_prod +# group: m2_prod_group + + +server: + port: 9501 + +spring: + application: + # 应用名称 + name: m2pool-system + profiles: + # 环境配置 + active: test + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: 127.0.0.1:8808 + namespace: m2_test + group: m2_test_group + config: + # 配置中心地址 + server-addr: 127.0.0.1:8808 + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} + namespace: m2_test + group: m2_test_group diff --git a/m2pool-modules/m2pool-system/src/main/resources/logback.xml b/m2pool-modules/m2pool-system/src/main/resources/logback.xml new file mode 100644 index 0000000..84032aa --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/resources/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysLogininforMapper.xml new file mode 100644 index 0000000..56b0ff9 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysLogininforMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + insert into sys_logininfor (user_name, status, ipaddr, msg, access_time) + values (#{userName}, #{status}, #{ipaddr}, #{msg}, sysdate()) + + + + + + delete from sys_logininfor where info_id in + + #{infoId} + + + + + truncate table sys_logininfor + + + diff --git a/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysMenuMapper.xml b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysMenuMapper.xml new file mode 100644 index 0000000..67c4357 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysOperLogMapper.xml new file mode 100644 index 0000000..e79943c --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_param, json_result, status, error_msg, oper_time + from sys_oper_log + + + + insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_param, json_result, status, error_msg, oper_time) + values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, sysdate()) + + + + + + delete from sys_oper_log where oper_id in + + #{operId} + + + + + + + truncate table sys_oper_log + + + diff --git a/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysRoleMapper.xml b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysRoleMapper.xml new file mode 100644 index 0000000..80b828d --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysRoleMapper.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly, + r.status, r.del_flag, r.create_time, r.remark + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id + + + + + + + + + + + + + + + + + diff --git a/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml new file mode 100644 index 0000000..2c0f8a1 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + delete from sys_role_menu where role_id=#{roleId} + + + + delete from sys_role_menu where role_id in + + #{roleId} + + + + + insert into sys_role_menu(role_id, menu_id) values + + (#{item.roleId},#{item.menuId}) + + + + diff --git a/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysUserLevelMapper.xml b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysUserLevelMapper.xml new file mode 100644 index 0000000..feb0e6e --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysUserLevelMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + insert into sys_user_level_date(user_id,create_time,expire_time,level_type,status) values + (#{vo.userId},#{vo.startTime},#{vo.expireTime},#{vo.levelType},0) + + + + update sys_user_level_date set status = 1 where user_id in + + #{id} + + + + + delete from sys_user_level_date where user_id = #{userId} + + + + + diff --git a/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysUserMapper.xml b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysUserMapper.xml new file mode 100644 index 0000000..5e924d5 --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,u.login_count, + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id + + + + + + + + + + + + + + + + + + + + + + + + insert into sys_user( + user_id, + dept_id, + user_name, + nick_name, + email, + avatar, + phonenumber, + sex, + password, + status, + create_by, + remark, + create_time + )values( + #{userId}, + #{deptId}, + #{userName}, + #{nickName}, + #{email}, + #{avatar}, + #{phone}, + #{sex}, + #{password}, + #{status}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_user + + dept_id = #{deptId}, + user_name = #{userName}, + nick_name = #{nickName}, + email = #{email}, + phonenumber = #{phone}, + sex = #{sex}, + avatar = #{avatar}, + password = #{password}, + status = #{status}, + login_ip = #{loginIp}, + login_date = #{loginDate}, + update_by = #{updateBy}, + remark = #{remark}, + login_count = #{loginCount}, + update_time = sysdate() + + where user_id = #{userId} + + + + update sys_user set password = #{password} where user_name = #{userName} + + + + update sys_user set password = #{password} where email = #{email} + + + diff --git a/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysUserRoleMapper.xml new file mode 100644 index 0000000..a55ff3e --- /dev/null +++ b/m2pool-modules/m2pool-system/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + delete from sys_user_role where user_id=#{userId} + + + + + + delete from sys_user_role where user_id in + + #{userId} + + + + + insert into sys_user_role(user_id, role_id) values + + (#{item.userId},#{item.roleId}) + + + + + insert into sys_user_role(user_id, role_id) values(#{userId},#{roleId}) + + + + + delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} + + + + delete from sys_user_role where role_id=#{roleId} and user_id in + + #{userId} + + + diff --git a/m2pool-modules/pom.xml b/m2pool-modules/pom.xml new file mode 100644 index 0000000..7c52f6c --- /dev/null +++ b/m2pool-modules/pom.xml @@ -0,0 +1,28 @@ + + + + m2pool + com.m2pool + 3.5.0 + + 4.0.0 + + m2pool-modules + pom + + + m2pool-modules业务模块 + + + + m2pool-system + m2pool-pool + m2pool-file + m2pool-oapi + m2pool-chat + + + + diff --git a/m2pool.iml b/m2pool.iml new file mode 100644 index 0000000..7519e50 --- /dev/null +++ b/m2pool.iml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c97d96f --- /dev/null +++ b/pom.xml @@ -0,0 +1,282 @@ + + + 4.0.0 + + com.m2pool + m2pool + pom + 3.5.0 + m2pool + + m2pool-common + m2pool-gateway + m2pool-auth + m2pool-api + m2pool-modules + + + + 1.8 + 3.5.0 + UTF-8 + UTF-8 + 2.6.7 + 2021.0.1 + 2021.0.1.0 + 2.0.4 + 2.6.6 + 2.2.2 + 3.0.0 + 1.6.2 + + 1.27.2 + 3.0.6 + 2.3.2 + 1.4.1 + 1.2.8 + 3.5.0 + 2.11.0 + 1.4 + + 2.3 + 1.2.80 + 0.9.1 + 8.2.2 + 4.1.2 + 3.2.2 + 2.12.2 + 1.18.6.0 + + + + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + com.alibaba.cloud + spring-cloud-alibaba-dependencies + ${spring-cloud-alibaba.version} + pom + import + + + + + com.alibaba.nacos + nacos-client + ${alibaba.nacos.version} + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + de.codecentric + spring-boot-admin-starter-client + ${spring-boot-admin.version} + + + + + com.github.tobato + fastdfs-client + ${tobato.version} + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + + io.swagger + swagger-models + ${swagger.core.version} + + + io.swagger + swagger-annotations + ${swagger.core.version} + + + + + com.github.penggle + kaptcha + ${kaptcha.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.boot.version} + + + + + commons-io + commons-io + ${commons.io.version} + + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + commons-fileupload + commons-fileupload + ${commons.fileupload.version} + + + + + + commons-collections + commons-collections + ${commons-collections.version} + + + + + com.alibaba + fastjson + ${fastjson.version} + + + + + io.jsonwebtoken + jjwt + ${jjwt.version} + + + + + com.alibaba + transmittable-thread-local + ${transmittable-thread-local.version} + + + + + org.projectlombok + lombok-maven-plugin + ${lombok-maven-plugin.version} + provided + + + + + + com.m2pool + common-core + ${m2pool.verson} + + + + + com.m2pool + api-system + ${m2pool.verson} + + + + + com.m2pool + common-log + ${m2pool.verson} + + + + + com.m2pool + common-datasource + ${m2pool.verson} + + + + + com.m2pool + common-datascope + ${m2pool.verson} + + + + + com.m2pool + common-security + ${m2pool.verson} + + + + + com.m2pool + common-redis + ${m2pool.verson} + + + + + com.m2pool + common-swagger + ${m2pool.verson} + + + + + + + + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + +