frist commit

This commit is contained in:
dev 2025-03-14 17:29:57 +08:00
commit 227f68f30f
1533 changed files with 835843 additions and 0 deletions

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
# http://editorconfig.org
root = true
# 空格替代Tab缩进在各种编辑工具下效果一致
[*]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.{json,yml,yaml}]
indent_size = 2
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

47
.gitignore vendored Normal file
View File

@ -0,0 +1,47 @@
######################################################################
# Build Tools
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### JRebel ###
rebel.xml
### NetBeans ###
nbproject/private/
build/*
nbbuild/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp
!*/build/*.java
!*/build/*.html
!*/build/*.xml
.flattened-pom.xml

12
.run/asinkj-auth.run.xml Normal file
View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-auth" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-auth:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-auth/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-gateway" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-gateway:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-gateway/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

12
.run/asinkj-gen.run.xml Normal file
View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-gen" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-gen:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-modules/asinkj-gen/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

12
.run/asinkj-job.run.xml Normal file
View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-job" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-job:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-modules/asinkj-job/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-monitor" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-monitor:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-visual/asinkj-monitor/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

27
.run/asinkj-nacos.run.xml Normal file
View File

@ -0,0 +1,27 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-nacos" type="docker-deploy" factoryName="dockerfile" server-name="阿里云">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-nacos:2.2.2" />
<option name="containerName" value="asinkj-nacos" />
<option name="portBindings">
<list>
<DockerPortBindingImpl>
<option name="containerPort" value="8848" />
<option name="hostPort" value="8848" />
</DockerPortBindingImpl>
<DockerPortBindingImpl>
<option name="containerPort" value="9848" />
<option name="hostPort" value="9848" />
</DockerPortBindingImpl>
</list>
</option>
<option name="commandLineOptions" value="--network 1panel-network" />
<option name="sourceFilePath" value="asinkj-visual/asinkj-nacos/Dockerfile" />
</settings>
</deployment>
<method v="2">
<option name="Maven.BeforeRunTask" enabled="true" file="F:\Asinkj-Cloud-Plus\asinkj-visual\asinkj-nacos/pom.xml" goal="package" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-resource" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-resource:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-modules/asinkj-resource/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-seata-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-seata-server:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-visual/asinkj-seata-server/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-sentinel-dashboard" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-sentinel-dashboard:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-visual/asinkj-sentinel-dashboard/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-snailjob-server:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-visual/asinkj-snailjob-server/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-system" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-system:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-modules/asinkj-system/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="asinkj-workflow" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="asinkj/asinkj-workflow:2.2.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="asinkj-modules/asinkj-workflow/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 AsinKj-Cloud-Plus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-api-bom</artifactId>
<packaging>pom</packaging>
<version>${revision}</version>
<description>
asinkj-api-bom api依赖项
</description>
<properties>
<revision>2.2.2</revision>
</properties>
<dependencyManagement>
<dependencies>
<!-- 系统接口 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-api-system</artifactId>
<version>${revision}</version>
</dependency>
<!-- 资源服务接口 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-api-resource</artifactId>
<version>${revision}</version>
</dependency>
<!-- workflow接口 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-api-workflow</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-api</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>asinkj-api-resource</artifactId>
<description>
asinkj-api-resource 资源服务接口模块
</description>
<dependencies>
<!-- AsinKj Common Core-->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,38 @@
package org.asinkj.resource.api;
import org.asinkj.common.core.exception.ServiceException;
import org.asinkj.resource.api.domain.RemoteFile;
import java.util.List;
/**
* 文件服务
*
* @author Shuo Hu
*/
public interface RemoteFileService {
/**
* 上传文件
*
* @param file 文件信息
* @return 结果
*/
RemoteFile upload(String name, String originalFilename, String contentType, byte[] file) throws ServiceException;
/**
* 通过ossId查询对应的url
*
* @param ossIds ossId串逗号分隔
* @return url串逗号分隔
*/
String selectUrlByIds(String ossIds);
/**
* 通过ossId查询列表
*
* @param ossIds ossId串逗号分隔
* @return 列表
*/
List<RemoteFile> selectByIds(String ossIds);
}

View File

@ -0,0 +1,53 @@
package org.asinkj.resource.api;
import lombok.extern.slf4j.Slf4j;
import org.asinkj.common.core.utils.StringUtils;
import org.asinkj.resource.api.domain.RemoteFile;
import java.util.List;
/**
* 文件服务(降级处理)
*
* @author Shuo Hu
*/
@Slf4j
public class RemoteFileServiceMock implements RemoteFileService {
/**
* 上传文件
*
* @param file 文件信息
* @return 结果
*/
@Override
public RemoteFile upload(String name, String originalFilename, String contentType, byte[] file) {
log.warn("服务调用异常 -> 降级处理");
return null;
}
/**
* 通过ossId查询对应的url
*
* @param ossIds ossId串逗号分隔
* @return url串逗号分隔
*/
@Override
public String selectUrlByIds(String ossIds) {
log.warn("服务调用异常 -> 降级处理");
return StringUtils.EMPTY;
}
/**
* 通过ossId查询列表
*
* @param ossIds ossId串逗号分隔
* @return 列表
*/
@Override
public List<RemoteFile> selectByIds(String ossIds) {
log.warn("服务调用异常 -> 降级处理");
return List.of();
}
}

View File

@ -0,0 +1,21 @@
package org.asinkj.resource.api;
import org.asinkj.common.core.exception.ServiceException;
/**
* 邮件服务
*
* @author Shuo Hu
*/
public interface RemoteMailService {
/**
* 发送邮件
*
* @param to 接收人
* @param subject 标题
* @param text 内容
*/
void send(String to, String subject, String text) throws ServiceException;
}

View File

@ -0,0 +1,24 @@
package org.asinkj.resource.api;
/**
* 消息服务
*
* @author Shuo Hu
*/
public interface RemoteMessageService {
/**
* 发送消息
*
* @param sessionKey session主键 一般为用户id
* @param message 消息文本
*/
void publishMessage(Long sessionKey, String message);
/**
* 发布订阅的消息(群发)
*
* @param message 消息内容
*/
void publishAll(String message);
}

View File

@ -0,0 +1,45 @@
package org.asinkj.resource.api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 消息服务
*
* @author Shuo Hu
*/
@Slf4j
@RequiredArgsConstructor
public class RemoteMessageServiceStub implements RemoteMessageService {
private final RemoteMessageService remoteMessageService;
/**
* 发送消息
*
* @param sessionKey session主键 一般为用户id
* @param message 消息文本
*/
@Override
public void publishMessage(Long sessionKey, String message) {
try {
remoteMessageService.publishMessage(sessionKey, message);
} catch (Exception e) {
log.warn("推送功能未开启或服务未找到");
}
}
/**
* 发布订阅的消息(群发)
*
* @param message 消息内容
*/
@Override
public void publishAll(String message) {
try {
remoteMessageService.publishAll(message);
} catch (Exception e) {
log.warn("推送功能未开启或服务未找到");
}
}
}

View File

@ -0,0 +1,145 @@
package org.asinkj.resource.api;
import org.asinkj.resource.api.domain.RemoteSms;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 短信服务
*
* @author Feng
*/
public interface RemoteSmsService {
/**
* 同步方法发送固定消息模板短信
*
* @param phone 目标手机号
* @param message 短信内容
* @return 封装了短信发送结果的 RemoteSms 对象
*/
RemoteSms sendMessage(String phone, String message);
/**
* 同步方法发送固定消息模板多模板参数短信
*
* @param phone 目标手机号
* @param messages 短信模板参数使用 LinkedHashMap 以保持参数顺序
* @return 封装了短信发送结果的 RemoteSms 对象
*/
RemoteSms sendMessage(String phone, LinkedHashMap<String, String> messages);
/**
* 同步方法使用自定义模板发送短信
*
* @param phone 目标手机号
* @param templateId 短信模板ID
* @param messages 短信模板参数使用 LinkedHashMap 以保持参数顺序
* @return 封装了短信发送结果的 RemoteSms 对象
*/
RemoteSms sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages);
/**
* 同步方法群发固定模板短信
*
* @param phones 目标手机号列表1~1000
* @param message 短信内容
* @return 封装了短信发送结果的 RemoteSms 对象
*/
RemoteSms messageTexting(List<String> phones, String message);
/**
* 同步方法使用自定义模板群发短信
*
* @param phones 目标手机号列表1~10001~1000
* @param templateId 短信模板ID
* @param messages 短信模板参数使用 LinkedHashMap 以保持参数顺序
* @return 封装了短信发送结果的 RemoteSms 对象
*/
RemoteSms messageTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages);
/**
* 异步方法发送固定消息模板短信
*
* @param phone 目标手机号
* @param message 短信内容
*/
void sendMessageAsync(String phone, String message);
/**
* 异步方法使用自定义模板发送短信
*
* @param phone 目标手机号
* @param templateId 短信模板ID
* @param messages 短信模板参数使用 LinkedHashMap 以保持参数顺序
*/
void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages);
/**
* 延迟发送发送固定消息模板短信
*
* @param phone 目标手机号
* @param message 短信内容
* @param delayedTime 延迟发送时间毫秒
*/
void delayMessage(String phone, String message, Long delayedTime);
/**
* 延迟发送使用自定义模板发送定时短信
*
* @param phone 目标手机号
* @param templateId 短信模板ID
* @param messages 短信模板参数使用 LinkedHashMap 以保持参数顺序
* @param delayedTime 延迟发送时间毫秒
*/
void delayMessage(String phone, String templateId, LinkedHashMap<String, String> messages, Long delayedTime);
/**
* 延迟群发群发延迟短信
*
* @param phones 目标手机号列表1~1000
* @param message 短信内容
* @param delayedTime 延迟发送时间毫秒
*/
void delayMessageTexting(List<String> phones, String message, Long delayedTime);
/**
* 延迟群发使用自定义模板发送群体延迟短信
*
* @param phones 目标手机号列表1~1000
* @param templateId 短信模板ID
* @param messages 短信模板参数使用 LinkedHashMap 以保持参数顺序
* @param delayedTime 延迟发送时间毫秒
*/
void delayMessageTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages, Long delayedTime);
/**
* 加入黑名单
*
* @param phone 手机号
*/
void addBlacklist(String phone);
/**
* 加入黑名单
*
* @param phones 手机号列表
*/
void addBlacklist(List<String> phones);
/**
* 移除黑名单
*
* @param phone 手机号
*/
void removeBlacklist(String phone);
/**
* 移除黑名单
*
* @param phones 手机号
*/
void removeBlacklist(List<String> phones);
}

View File

@ -0,0 +1,44 @@
package org.asinkj.resource.api.domain;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 文件信息
*
* @author asinkj
*/
@Data
public class RemoteFile implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* oss主键
*/
private Long ossId;
/**
* 文件名称
*/
private String name;
/**
* 文件地址
*/
private String url;
/**
* 原名
*/
private String originalName;
/**
* 文件后缀名
*/
private String fileSuffix;
}

View File

@ -0,0 +1,36 @@
package org.asinkj.resource.api.domain;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 文件信息
*
* @author asinkj
*/
@Data
public class RemoteSms implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 是否成功
*/
private Boolean success;
/**
* 配置标识名 如未配置取对应渠道名例如 Alibaba
*/
private String configId;
/**
* 厂商原返回体
* <p>
* 可自行转换为 SDK 对应的 SendSmsResponse
*/
private String response;
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-api</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>asinkj-api-system</artifactId>
<description>
asinkj-api-system系统接口模块
</description>
<dependencies>
<!-- AsinKj Common Core-->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-excel</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,20 @@
package org.asinkj.system.api;
import org.asinkj.system.api.domain.vo.RemoteClientVo;
/**
* 客户端服务
*
* @author Michelle.Chung
*/
public interface RemoteClientService {
/**
* 根据客户端id获取客户端详情
*
* @param clientId 客户端id
* @return 客户端对象
*/
RemoteClientVo queryByClientId(String clientId);
}

View File

@ -0,0 +1,17 @@
package org.asinkj.system.api;
/**
* 配置服务
*
* @author Michelle.Chung
*/
public interface RemoteConfigService {
/**
* 获取注册开关
* @param tenantId 租户id
* @return true开启false关闭
*/
boolean selectRegisterEnabled(String tenantId);
}

View File

@ -0,0 +1,26 @@
package org.asinkj.system.api;
/**
* 数据权限服务
*
* @author Shuo Hu
*/
public interface RemoteDataScopeService {
/**
* 获取角色自定义权限语句
*
* @param roleId 角色ID
* @return 返回角色的自定义权限语句如果没有找到则返回 null
*/
String getRoleCustom(Long roleId);
/**
* 获取部门和下级权限语句
*
* @param deptId 部门ID
* @return 返回部门及其下级的权限语句如果没有找到则返回 null
*/
String getDeptAndChild(Long deptId);
}

View File

@ -0,0 +1,18 @@
package org.asinkj.system.api;
/**
* 部门服务
*
* @author Shuo Hu
*/
public interface RemoteDeptService {
/**
* 通过部门ID查询部门名称
*
* @param deptIds 部门ID串逗号分隔
* @return 部门名称串逗号分隔
*/
String selectDeptNameByIds(String deptIds);
}

View File

@ -0,0 +1,22 @@
package org.asinkj.system.api;
import org.asinkj.system.api.domain.vo.RemoteDictDataVo;
import java.util.List;
/**
* 字典服务
*
* @author Shuo Hu
*/
public interface RemoteDictService {
/**
* 根据字典类型查询字典数据
*
* @param dictType 字典类型
* @return 字典数据集合信息
*/
List<RemoteDictDataVo> selectDictDataByType(String dictType);
}

View File

@ -0,0 +1,27 @@
package org.asinkj.system.api;
import org.asinkj.system.api.domain.bo.RemoteLogininforBo;
import org.asinkj.system.api.domain.bo.RemoteOperLogBo;
/**
* 日志服务
*
* @author Shuo Hu
*/
public interface RemoteLogService {
/**
* 保存系统日志
*
* @param sysOperLog 日志实体
*/
void saveLog(RemoteOperLogBo sysOperLog);
/**
* 保存访问记录
*
* @param sysLogininfor 访问实体
*/
void saveLogininfor(RemoteLogininforBo sysLogininfor);
}

View File

@ -0,0 +1,52 @@
package org.asinkj.system.api;
import org.asinkj.system.api.domain.bo.RemoteSocialBo;
import org.asinkj.system.api.domain.vo.RemoteSocialVo;
import java.util.List;
/**
* 社会化关系服务
*
* @author Michelle.Chung
*/
public interface RemoteSocialService {
/**
* 根据 authId 查询用户授权信息
*
* @param authId 认证id
* @return 授权信息
*/
List<RemoteSocialVo> selectByAuthId(String authId);
/**
* 查询列表
*
* @param bo 社会化关系业务对象
*/
List<RemoteSocialVo> queryList(RemoteSocialBo bo);
/**
* 保存社会化关系
*
* @param bo 社会化关系业务对象
*/
void insertByBo(RemoteSocialBo bo);
/**
* 更新社会化关系
*
* @param bo 社会化关系业务对象
*/
void updateByBo(RemoteSocialBo bo);
/**
* 删除社会化关系
*
* @param socialId 社会化关系ID
* @return 结果
*/
Boolean deleteWithValidById(Long socialId);
}

View File

@ -0,0 +1,28 @@
package org.asinkj.system.api;
import org.asinkj.system.api.domain.vo.RemoteTenantVo;
import java.util.List;
/**
* 租户服务
*
* @author zhujie
*/
public interface RemoteTenantService {
/**
* 根据租户id获取租户详情
* @param tenantId 租户id
* @return 结果
*/
RemoteTenantVo queryByTenantId(String tenantId);
/**
* 获取租户列表
* @return 结果
*/
List<RemoteTenantVo> queryList();
}

View File

@ -0,0 +1,134 @@
package org.asinkj.system.api;
import org.asinkj.common.core.exception.ServiceException;
import org.asinkj.common.core.exception.user.UserException;
import org.asinkj.system.api.domain.bo.RemoteUserBo;
import org.asinkj.system.api.domain.vo.RemoteUserVo;
import org.asinkj.system.api.model.LoginUser;
import org.asinkj.system.api.model.XcxLoginUser;
import java.util.List;
/**
* 用户服务
*
* @author Shuo Hu
*/
public interface RemoteUserService {
/**
* 通过用户名查询用户信息
*
* @param username 用户名
* @param tenantId 租户id
* @return 结果
*/
LoginUser getUserInfo(String username, String tenantId) throws UserException;
/**
* 通过用户id查询用户信息
*
* @param userId 用户id
* @param tenantId 租户id
* @return 结果
*/
LoginUser getUserInfo(Long userId, String tenantId) throws UserException;
/**
* 通过手机号查询用户信息
*
* @param phonenumber 手机号
* @param tenantId 租户id
* @return 结果
*/
LoginUser getUserInfoByPhonenumber(String phonenumber, String tenantId) throws UserException;
/**
* 通过邮箱查询用户信息
*
* @param email 邮箱
* @param tenantId 租户id
* @return 结果
*/
LoginUser getUserInfoByEmail(String email, String tenantId) throws UserException;
/**
* 通过openid查询用户信息
*
* @param openid openid
* @return 结果
*/
XcxLoginUser getUserInfoByOpenid(String openid) throws UserException;
/**
* 注册用户信息
*
* @param remoteUserBo 用户信息
* @return 结果
*/
Boolean registerUserInfo(RemoteUserBo remoteUserBo) throws UserException, ServiceException;
/**
* 通过userId查询用户账户
*
* @param userId 用户id
* @return 结果
*/
String selectUserNameById(Long userId);
/**
* 通过用户ID查询用户昵称
*
* @param userId 用户id
* @return 结果
*/
String selectNicknameById(Long userId);
/**
* 通过用户ID查询用户账户
*
* @param userIds 用户ID 多个用逗号隔开
* @return 用户名称
*/
String selectNicknameByIds(String userIds);
/**
* 通过用户ID查询用户手机号
*
* @param userId 用户id
* @return 用户手机号
*/
String selectPhonenumberById(Long userId);
/**
* 通过用户ID查询用户邮箱
*
* @param userId 用户id
* @return 用户邮箱
*/
String selectEmailById(Long userId);
/**
* 更新用户信息
*
* @param userId 用户ID
* @param ip IP地址
*/
void recordLoginInfo(Long userId, String ip);
/**
* 通过用户ID查询用户列表
*
* @param userIds 用户ids
* @return 用户列表
*/
List<RemoteUserVo> selectListByIds(List<Long> userIds);
/**
* 通过角色ID查询用户ID
*
* @param roleIds 角色ids
* @return 用户ids
*/
List<Long> selectUserIdsByRoleIds(List<Long> roleIds);
}

View File

@ -0,0 +1,71 @@
package org.asinkj.system.api.domain;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 当前在线会话
*
* @author Shuo Hu
*/
@Data
@NoArgsConstructor
public class SysUserOnline implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 会话编号
*/
private String tokenId;
/**
* 部门名称
*/
private String deptName;
/**
* 用户名称
*/
private String userName;
/**
* 客户端
*/
private String clientKey;
/**
* 设备类型
*/
private String deviceType;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地址
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 登录时间
*/
private Long loginTime;
}

View File

@ -0,0 +1,89 @@
package org.asinkj.system.api.domain.bo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 系统访问记录表 sys_logininfor
*
* @author Shuo Hu
*/
@Data
@NoArgsConstructor
public class RemoteLogininforBo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 访问ID
*/
private Long infoId;
/**
* 租户编号
*/
private String tenantId;
/**
* 用户账号
*/
private String userName;
/**
* 客户端
*/
private String clientKey;
/**
* 设备类型
*/
private String deviceType;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 登录状态0成功 1失败
*/
private String status;
/**
* 提示消息
*/
private String msg;
/**
* 访问时间
*/
private Date loginTime;
/**
* 请求参数
*/
private Map<String, Object> params = new HashMap<>();
}

View File

@ -0,0 +1,119 @@
package org.asinkj.system.api.domain.bo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 操作日志记录表 oper_log
*
* @author Shuo Hu
*/
@Data
@NoArgsConstructor
public class RemoteOperLogBo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 日志主键
*/
private Long operId;
/**
* 租户编号
*/
private String tenantId;
/**
* 模块标题
*/
private String title;
/**
* 业务类型0其它 1新增 2修改 3删除
*/
private Integer businessType;
/**
* 方法名称
*/
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 operLocation;
/**
* 请求参数
*/
private String operParam;
/**
* 返回参数
*/
private String jsonResult;
/**
* 操作状态0正常 1异常
*/
private Integer status;
/**
* 错误消息
*/
private String errorMsg;
/**
* 操作时间
*/
private Date operTime;
/**
* 消耗时间
*/
private Long costTime;
/**
* 请求参数
*/
private Map<String, Object> params = new HashMap<>();
}

View File

@ -0,0 +1,129 @@
package org.asinkj.system.api.domain.bo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 社会化关系业务对象 sys_social
*
* @author Michelle.Chung
*/
@Data
@NoArgsConstructor
public class RemoteSocialBo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private Long id;
/**
* 的唯一ID
*/
private String authId;
/**
* 用户来源
*/
private String source;
/**
* 用户的授权令牌
*/
private String accessToken;
/**
* 用户的授权令牌的有效期部分平台可能没有
*/
private int expireIn;
/**
* 刷新令牌部分平台可能没有
*/
private String refreshToken;
/**
* 平台唯一id
*/
private String openId;
/**
* 用户的 ID
*/
private Long userId;
/**
* 平台的授权信息部分平台可能没有
*/
private String accessCode;
/**
* 用户的 unionid
*/
private String unionId;
/**
* 授予的权限部分平台可能没有
*/
private String scope;
/**
* 授权的第三方账号
*/
private String userName;
/**
* 授权的第三方昵称
*/
private String nickName;
/**
* 授权的第三方邮箱
*/
private String email;
/**
* 授权的第三方头像地址
*/
private String avatar;
/**
* 个别平台的授权信息部分平台可能没有
*/
private String tokenType;
/**
* id token部分平台可能没有
*/
private String idToken;
/**
* 小米平台用户的附带属性部分平台可能没有
*/
private String macAlgorithm;
/**
* 小米平台用户的附带属性部分平台可能没有
*/
private String macKey;
/**
* 用户的授权code部分平台可能没有
*/
private String code;
/**
* Twitter平台用户的附带属性部分平台可能没有
*/
private String oauthToken;
/**
* Twitter平台用户的附带属性部分平台可能没有
*/
private String oauthTokenSecret;
}

View File

@ -0,0 +1,124 @@
package org.asinkj.system.api.domain.bo;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.asinkj.common.core.constant.UserConstants;
import org.asinkj.common.core.xss.Xss;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 用户信息业务对象 sys_user
*
* @author Michelle.Chung
*/
@Data
@NoArgsConstructor
public class RemoteUserBo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 租户ID
*/
private String tenantId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户账号
*/
@Xss(message = "用户账号不能包含脚本字符")
@NotBlank(message = "用户账号不能为空")
@Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符")
private String userName;
/**
* 用户昵称
*/
@Xss(message = "用户昵称不能包含脚本字符")
@Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符")
private String nickName;
/**
* 用户类型sys_user系统用户
*/
private String userType;
/**
* 用户邮箱
*/
@Email(message = "邮箱格式不正确")
@Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符")
private String email;
/**
* 手机号码
*/
private String phonenumber;
/**
* 用户性别0男 1女 2未知
*/
private String sex;
/**
* 头像地址
*/
private Long avatar;
/**
* 密码
*/
private String password;
/**
* 帐号状态0正常 1停用
*/
private String status;
/**
* 最后登录IP
*/
private String loginIp;
/**
* 最后登录时间
*/
private Date loginDate;
/**
* 备注
*/
private String remark;
/**
* 数据权限 当前角色ID
*/
private Long roleId;
public RemoteUserBo(Long userId) {
this.userId = userId;
}
public boolean isSuperAdmin() {
return UserConstants.SUPER_ADMIN_ID.equals(this.userId);
}
}

View File

@ -0,0 +1,70 @@
package org.asinkj.system.api.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 授权管理视图对象 sys_client
*
* @author Michelle.Chung
*/
@Data
public class RemoteClientVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
/**
* 客户端id
*/
private String clientId;
/**
* 客户端key
*/
private String clientKey;
/**
* 客户端秘钥
*/
private String clientSecret;
/**
* 授权类型
*/
private List<String> grantTypeList;
/**
* 授权类型
*/
private String grantType;
/**
* 设备类型
*/
private String deviceType;
/**
* token活跃超时时间
*/
private Long activeTimeout;
/**
* token固定超时时间
*/
private Long timeout;
/**
* 状态0正常 1停用
*/
private String status;
}

View File

@ -0,0 +1,76 @@
package org.asinkj.system.api.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 字典数据视图对象 sys_dict_data
*
* @author Michelle.Chung
*/
@Data
public class RemoteDictDataVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 字典编码
*/
private Long dictCode;
/**
* 字典排序
*/
private Integer dictSort;
/**
* 字典标签
*/
private String dictLabel;
/**
* 字典键值
*/
private String dictValue;
/**
* 字典类型
*/
private String dictType;
/**
* 样式属性其他样式扩展
*/
private String cssClass;
/**
* 表格回显样式
*/
private String listClass;
/**
* 是否默认Y是 N否
*/
private String isDefault;
/**
* 状态0正常 1停用
*/
private String status;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private Date createTime;
}

View File

@ -0,0 +1,135 @@
package org.asinkj.system.api.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 社会化关系视图对象 sys_social
*
* @author thiszhc
*/
@Data
public class RemoteSocialVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 租户ID
*/
private String tenantId;
/**
* 认证唯一ID
*/
private String authId;
/**
* 用户来源
*/
private String source;
/**
* 用户的授权令牌
*/
private String accessToken;
/**
* 用户的授权令牌的有效期部分平台可能没有
*/
private int expireIn;
/**
* 刷新令牌部分平台可能没有
*/
private String refreshToken;
/**
* 用户的 open id
*/
private String openId;
/**
* 授权的第三方账号
*/
private String userName;
/**
* 授权的第三方昵称
*/
private String nickName;
/**
* 授权的第三方邮箱
*/
private String email;
/**
* 授权的第三方头像地址
*/
private String avatar;
/**
* 平台的授权信息部分平台可能没有
*/
private String accessCode;
/**
* 用户的 unionid
*/
private String unionId;
/**
* 授予的权限部分平台可能没有
*/
private String scope;
/**
* 个别平台的授权信息部分平台可能没有
*/
private String tokenType;
/**
* id token部分平台可能没有
*/
private String idToken;
/**
* 小米平台用户的附带属性部分平台可能没有
*/
private String macAlgorithm;
/**
* 小米平台用户的附带属性部分平台可能没有
*/
private String macKey;
/**
* 用户的授权code部分平台可能没有
*/
private String code;
/**
* Twitter平台用户的附带属性部分平台可能没有
*/
private String oauthToken;
/**
* Twitter平台用户的附带属性部分平台可能没有
*/
private String oauthTokenSecret;
}

View File

@ -0,0 +1,91 @@
package org.asinkj.system.api.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 租户视图对象
*
* @author zhujie
*/
@Data
public class RemoteTenantVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
/**
* 租户编号
*/
private String tenantId;
/**
* 联系人
*/
private String contactUserName;
/**
* 联系电话
*/
private String contactPhone;
/**
* 企业名称
*/
private String companyName;
/**
* 统一社会信用代码
*/
private String licenseNumber;
/**
* 地址
*/
private String address;
/**
* 域名
*/
private String domain;
/**
* 企业简介
*/
private String intro;
/**
* 备注
*/
private String remark;
/**
* 租户套餐编号
*/
private Long packageId;
/**
* 过期时间
*/
private Date expireTime;
/**
* 用户数量-1不限制
*/
private Long accountCount;
/**
* 租户状态0正常 1停用
*/
private String status;
}

View File

@ -0,0 +1,73 @@
package org.asinkj.system.api.domain.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 用户
*
* @author Michelle.Chung
*/
@Data
@NoArgsConstructor
public class RemoteUserVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户账号
*/
private String userName;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户类型sys_user系统用户
*/
private String userType;
/**
* 用户邮箱
*/
private String email;
/**
* 手机号码
*/
private String phonenumber;
/**
* 用户性别0男 1女 2未知
*/
private String sex;
/**
* 帐号状态0正常 1停用
*/
private String status;
/**
* 创建时间
*/
private Date createTime;
}

View File

@ -0,0 +1,146 @@
package org.asinkj.system.api.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
/**
* 用户信息
*
* @author asinkj
*/
@Data
@NoArgsConstructor
public class LoginUser implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 部门类别编码
*/
private String deptCategory;
/**
* 部门名
*/
private String deptName;
/**
* 用户唯一标识
*/
private String token;
/**
* 用户类型
*/
private String userType;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 菜单权限
*/
private Set<String> menuPermission;
/**
* 角色权限
*/
private Set<String> rolePermission;
/**
* 用户名
*/
private String username;
/**
* 用户昵称
*/
private String nickname;
/**
* 密码
*/
private String password;
/**
* 角色对象
*/
private List<RoleDTO> roles;
/**
* 数据权限 当前角色ID
*/
private Long roleId;
/**
* 客户端
*/
private String clientKey;
/**
* 设备类型
*/
private String deviceType;
/**
* 获取登录id
*/
public String getLoginId() {
if (userType == null) {
throw new IllegalArgumentException("用户类型不能为空");
}
if (userId == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
return userType + ":" + userId;
}
}

View File

@ -0,0 +1,42 @@
package org.asinkj.system.api.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 角色
*
* @author Shuo Hu
*/
@Data
@NoArgsConstructor
public class RoleDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 角色ID
*/
private Long roleId;
/**
* 角色名称
*/
private String roleName;
/**
* 角色权限
*/
private String roleKey;
/**
* 数据范围1所有数据权限2自定义数据权限3本部门数据权限4本部门及以下数据权限5仅本人数据权限
*/
private String dataScope;
}

View File

@ -0,0 +1,27 @@
package org.asinkj.system.api.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 小程序登录用户身份权限
*
* @author Shuo Hu
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class XcxLoginUser extends LoginUser {
@Serial
private static final long serialVersionUID = 1L;
/**
* openid
*/
private String openid;
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-api</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>asinkj-api-workflow</artifactId>
<description>
asinkj-api-workflow 工作流接口模块
</description>
<dependencies>
<!-- AsinKj Common Core-->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-bus</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,78 @@
package org.asinkj.workflow.api.domain;
import java.util.List;
import java.util.Map;
/**
* 通用 工作流服务
*
* @Author ZETA
* @Date 2024/6/3
*/
public interface RemoteWorkflowService {
/**
* 运行中的实例 删除程实例删除历史记录删除业务与流程关联信息
*
* @param businessKeys 业务id
* @return 结果
*/
boolean deleteRunAndHisInstance(List<String> businessKeys);
/**
* 获取当前流程状态
*
* @param taskId 任务id
*/
String getBusinessStatusByTaskId(String taskId);
/**
* 获取当前流程状态
*
* @param businessKey 业务id
*/
String getBusinessStatus(String businessKey);
/**
* 设置流程变量(全局变量)
*
* @param taskId 任务id
* @param variableName 变量名称
* @param value 变量值
*/
void setVariable(String taskId, String variableName, Object value);
/**
* 设置流程变量(全局变量)
*
* @param taskId 任务id
* @param variables 流程变量
*/
void setVariables(String taskId, Map<String, Object> variables);
/**
* 设置流程变量(本地变量,非全局变量)
*
* @param taskId 任务id
* @param variableName 变量名称
* @param value 变量值
*/
void setVariableLocal(String taskId, String variableName, Object value);
/**
* 设置流程变量(本地变量,非全局变量)
*
* @param taskId 任务id
* @param variables 流程变量
*/
void setVariablesLocal(String taskId, Map<String, Object> variables);
/**
* 按照业务id查询流程实例id
*
* @param businessKey 业务id
* @return 结果
*/
String getInstanceIdByBusinessKey(String businessKey);
}

View File

@ -0,0 +1,46 @@
package org.asinkj.workflow.api.domain.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.asinkj.common.core.utils.SpringUtils;
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
import java.io.Serial;
/**
* 总体流程监听
*
* @author may
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ProcessEvent extends RemoteApplicationEvent {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程定义key
*/
private String key;
/**
* 业务id
*/
private String businessKey;
/**
* 状态
*/
private String status;
/**
* 当为true时为申请人节点办理
*/
private boolean submit;
public ProcessEvent() {
super(new Object(), SpringUtils.getApplicationName(), DEFAULT_DESTINATION_FACTORY.getDestination(null));
}
}

View File

@ -0,0 +1,45 @@
package org.asinkj.workflow.api.domain.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.asinkj.common.core.utils.SpringUtils;
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
import java.io.Serial;
/**
* 流程办理监听
*
* @author may
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ProcessTaskEvent extends RemoteApplicationEvent {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程定义key
*/
private String key;
/**
* 审批节点key
*/
private String taskDefinitionKey;
/**
* 任务id
*/
private String taskId;
/**
* 业务id
*/
private String businessKey;
public ProcessTaskEvent() {
super(new Object(), SpringUtils.getApplicationName(), DEFAULT_DESTINATION_FACTORY.getDestination(null));
}
}

25
asinkj-api/pom.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-cloud-plus</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<modules>
<module>asinkj-api-bom</module>
<module>asinkj-api-system</module>
<module>asinkj-api-resource</module>
<module>asinkj-api-workflow</module>
</modules>
<artifactId>asinkj-api</artifactId>
<packaging>pom</packaging>
<description>
asinkj-api系统接口
</description>
</project>

25
asinkj-auth/Dockerfile Normal file
View File

@ -0,0 +1,25 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.3-cds
#FROM findepi/graalvm:java17-native
LABEL maintainer="Shuo Hu "
RUN mkdir -p /asinkj/auth/logs \
/asinkj/auth/temp \
/asinkj/skywalking/agent
WORKDIR /asinkj/auth
ENV SERVER_PORT=9210 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
EXPOSE ${SERVER_PORT}
ADD ./target/asinkj-auth.jar ./app.jar
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
#-Dskywalking.agent.service_name=asinkj-auth \
#-javaagent:/asinkj/skywalking/agent/skywalking-agent.jar \
-XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
-jar app.jar

133
asinkj-auth/pom.xml Normal file
View File

@ -0,0 +1,133 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-cloud-plus</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>asinkj-auth</artifactId>
<description>
asinkj-auth 认证授权中心
</description>
<dependencies>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-nacos</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-sentinel</artifactId>
</dependency>
<!-- AsinKj Common Security-->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-social</artifactId>
</dependency>
<!-- AsinKj Common Log -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-log</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-web</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-ratelimiter</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-encrypt</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-seata</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-tenant</artifactId>
</dependency>
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-api-resource</artifactId>
</dependency>
<!-- 自定义负载均衡(多团队开发使用) -->
<!-- <dependency>-->
<!-- <groupId>org.asinkj</groupId>-->
<!-- <artifactId>asinkj-common-loadbalancer</artifactId>-->
<!-- </dependency>-->
<!-- ELK 日志收集 -->
<!-- <dependency>-->
<!-- <groupId>org.asinkj</groupId>-->
<!-- <artifactId>asinkj-common-logstash</artifactId>-->
<!-- </dependency>-->
<!-- skywalking 日志收集 -->
<!-- <dependency>-->
<!-- <groupId>org.asinkj</groupId>-->
<!-- <artifactId>asinkj-common-skylog</artifactId>-->
<!-- </dependency>-->
<!-- prometheus 监控 -->
<!-- <dependency>-->
<!-- <groupId>org.asinkj</groupId>-->
<!-- <artifactId>asinkj-common-prometheus</artifactId>-->
<!-- </dependency>-->
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,23 @@
package org.asinkj.auth;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
/**
* 认证授权中心
*
* @author asinkj
*/
@EnableDubbo
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class AsinKjAuthApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(AsinKjAuthApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
System.out.println("(♥◠‿◠)ノ゙ 认证授权中心启动成功 ლ(´ڡ`ლ)゙ ");
}
}

View File

@ -0,0 +1,88 @@
package org.asinkj.auth.captcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.math.Calculator;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.RandomUtil;
import org.asinkj.common.core.utils.StringUtils;
import java.io.Serial;
/**
* 无符号计算生成器
*
* @author Shuo Hu
*/
public class UnsignedMathGenerator implements CodeGenerator {
@Serial
private static final long serialVersionUID = -5514819971774091076L;
private static final String OPERATORS = "+-*";
/**
* 参与计算数字最大长度
*/
private final int numberLength;
/**
* 构造
*/
public UnsignedMathGenerator() {
this(2);
}
/**
* 构造
*
* @param numberLength 参与计算最大数字位数
*/
public UnsignedMathGenerator(int numberLength) {
this.numberLength = numberLength;
}
@Override
public String generate() {
final int limit = getLimit();
int a = RandomUtil.randomInt(limit);
int b = RandomUtil.randomInt(limit);
String max = Integer.toString(Math.max(a,b));
String min = Integer.toString(Math.min(a,b));
max = StringUtils.rightPad(max, this.numberLength, CharUtil.SPACE);
min = StringUtils.rightPad(min, this.numberLength, CharUtil.SPACE);
return max + RandomUtil.randomChar(OPERATORS) + min + '=';
}
@Override
public boolean verify(String code, String userInputCode) {
int result;
try {
result = Integer.parseInt(userInputCode);
} catch (NumberFormatException e) {
// 用户输入非数字
return false;
}
final int calculateResult = (int) Calculator.conversion(code);
return result == calculateResult;
}
/**
* 获取验证码长度
*
* @return 验证码长度
*/
public int getLength() {
return this.numberLength * 2 + 2;
}
/**
* 根据长度获取参与计算数字最大值
*
* @return 最大值
*/
private int getLimit() {
return Integer.parseInt("1" + StringUtils.repeat('0', this.numberLength));
}
}

View File

@ -0,0 +1,62 @@
package org.asinkj.auth.config;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ShearCaptcha;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import java.awt.*;
/**
* 验证码配置
*
* @author Shuo Hu
*/
@Configuration
public class CaptchaConfig {
private static final int WIDTH = 160;
private static final int HEIGHT = 60;
private static final Color BACKGROUND = Color.LIGHT_GRAY;
private static final Font FONT = new Font("Arial", Font.BOLD, 48);
/**
* 圆圈干扰验证码
*/
@Lazy
@Bean
public CircleCaptcha circleCaptcha() {
CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(WIDTH, HEIGHT);
captcha.setBackground(BACKGROUND);
captcha.setFont(FONT);
return captcha;
}
/**
* 线段干扰的验证码
*/
@Lazy
@Bean
public LineCaptcha lineCaptcha() {
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(WIDTH, HEIGHT);
captcha.setBackground(BACKGROUND);
captcha.setFont(FONT);
return captcha;
}
/**
* 扭曲干扰验证码
*/
@Lazy
@Bean
public ShearCaptcha shearCaptcha() {
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(WIDTH, HEIGHT);
captcha.setBackground(BACKGROUND);
captcha.setFont(FONT);
return captcha;
}
}

View File

@ -0,0 +1,78 @@
package org.asinkj.auth.controller;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.asinkj.auth.domain.vo.CaptchaVo;
import org.asinkj.auth.enums.CaptchaType;
import org.asinkj.auth.properties.CaptchaProperties;
import org.asinkj.common.core.constant.Constants;
import org.asinkj.common.core.constant.GlobalConstants;
import org.asinkj.common.core.domain.R;
import org.asinkj.common.core.utils.SpringUtils;
import org.asinkj.common.core.utils.StringUtils;
import org.asinkj.common.core.utils.reflect.ReflectUtils;
import org.asinkj.common.ratelimiter.annotation.RateLimiter;
import org.asinkj.common.ratelimiter.enums.LimitType;
import org.asinkj.common.redis.utils.RedisUtils;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
/**
* 验证码操作处理
*
* @author Shuo Hu
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
public class CaptchaController {
private final CaptchaProperties captchaProperties;
/**
* 生成验证码
*/
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
@GetMapping("/code")
public R<CaptchaVo> getCode() {
CaptchaVo captchaVo = new CaptchaVo();
boolean captchaEnabled = captchaProperties.getEnabled();
if (!captchaEnabled) {
captchaVo.setCaptchaEnabled(false);
return R.ok(captchaVo);
}
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
boolean isMath = CaptchaType.MATH == captchaType;
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
// 如果是数学验证码使用SpEL表达式处理验证码结果
String code = captcha.getCode();
if (isMath) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);
}
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
captchaVo.setUuid(uuid);
captchaVo.setImg(captcha.getImageBase64());
return R.ok(captchaVo);
}
}

View File

@ -0,0 +1,231 @@
package org.asinkj.auth.controller;
import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.asinkj.auth.domain.vo.LoginTenantVo;
import org.asinkj.auth.domain.vo.LoginVo;
import org.asinkj.auth.domain.vo.TenantListVo;
import org.asinkj.auth.form.RegisterBody;
import org.asinkj.auth.form.SocialLoginBody;
import org.asinkj.auth.service.IAuthStrategy;
import org.asinkj.auth.service.SysLoginService;
import org.asinkj.common.core.constant.UserConstants;
import org.asinkj.common.core.domain.R;
import org.asinkj.common.core.domain.model.LoginBody;
import org.asinkj.common.core.utils.*;
import org.asinkj.common.encrypt.annotation.ApiEncrypt;
import org.asinkj.common.json.utils.JsonUtils;
import org.asinkj.common.satoken.utils.LoginHelper;
import org.asinkj.common.social.config.properties.SocialLoginConfigProperties;
import org.asinkj.common.social.config.properties.SocialProperties;
import org.asinkj.common.social.utils.SocialUtils;
import org.asinkj.common.tenant.helper.TenantHelper;
import org.asinkj.resource.api.RemoteMessageService;
import org.asinkj.system.api.RemoteClientService;
import org.asinkj.system.api.RemoteConfigService;
import org.asinkj.system.api.RemoteSocialService;
import org.asinkj.system.api.RemoteTenantService;
import org.asinkj.system.api.domain.vo.RemoteClientVo;
import org.asinkj.system.api.domain.vo.RemoteTenantVo;
import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* token 控制
*
* @author Shuo Hu
*/
@Slf4j
@RequiredArgsConstructor
@RestController
public class TokenController {
private final SocialProperties socialProperties;
private final SysLoginService sysLoginService;
private final ScheduledExecutorService scheduledExecutorService;
@DubboReference
private final RemoteConfigService remoteConfigService;
@DubboReference
private final RemoteTenantService remoteTenantService;
@DubboReference
private final RemoteClientService remoteClientService;
@DubboReference
private final RemoteSocialService remoteSocialService;
@DubboReference(stub = "true")
private final RemoteMessageService remoteMessageService;
/**
* 登录方法
*
* @param body 登录信息
* @return 结果
*/
@ApiEncrypt
@PostMapping("/login")
public R<LoginVo> login(@RequestBody String body) {
LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
ValidatorUtils.validate(loginBody);
// 授权类型和客户端id
String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType();
RemoteClientVo clientVo = remoteClientService.queryByClientId(clientId);
// 查询不到 client client 内不包含 grantType
if (ObjectUtil.isNull(clientVo) || !StringUtils.contains(clientVo.getGrantType(), grantType)) {
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
return R.fail(MessageUtils.message("auth.grant.type.error"));
} else if (!UserConstants.NORMAL.equals(clientVo.getStatus())) {
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
}
// 校验租户
sysLoginService.checkTenant(loginBody.getTenantId());
// 登录
LoginVo loginVo = IAuthStrategy.login(body, clientVo, grantType);
Long userId = LoginHelper.getUserId();
scheduledExecutorService.schedule(() -> {
remoteMessageService.publishMessage(userId, "欢迎登录瑷胜科技管理系统");
}, 3, TimeUnit.SECONDS);
return R.ok(loginVo);
}
/**
* 第三方登录请求
*
* @param source 登录来源
* @return 结果
*/
@GetMapping("/binding/{source}")
public R<String> authBinding(@PathVariable("source") String source,
@RequestParam String tenantId, @RequestParam String domain) {
SocialLoginConfigProperties obj = socialProperties.getType().get(source);
if (ObjectUtil.isNull(obj)) {
return R.fail(source + "平台账号暂不支持");
}
AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
Map<String, String> map = new HashMap<>();
map.put("tenantId", tenantId);
map.put("domain", domain);
map.put("state", AuthStateUtils.createState());
String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
return R.ok("操作成功", authorizeUrl);
}
/**
* 第三方登录回调业务处理 绑定授权
*
* @param loginBody 请求体
* @return 结果
*/
@PostMapping("/social/callback")
public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
// 获取第三方登录信息
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
AuthUser authUserData = response.getData();
// 判断授权响应是否成功
if (!response.ok()) {
return R.fail(response.getMsg());
}
sysLoginService.socialRegister(authUserData);
return R.ok();
}
/**
* 取消授权
*
* @param socialId socialId
*/
@DeleteMapping(value = "/unlock/{socialId}")
public R<Void> unlockSocial(@PathVariable Long socialId) {
Boolean rows = remoteSocialService.deleteWithValidById(socialId);
return rows ? R.ok() : R.fail("取消授权失败");
}
/**
* 登出方法
*/
@PostMapping("logout")
public R<Void> logout() {
sysLoginService.logout();
return R.ok();
}
/**
* 用户注册
*/
@ApiEncrypt
@PostMapping("register")
public R<Void> register(@RequestBody RegisterBody registerBody) {
if (!remoteConfigService.selectRegisterEnabled(registerBody.getTenantId())) {
return R.fail("当前系统没有开启注册功能!");
}
// 用户注册
sysLoginService.register(registerBody);
return R.ok();
}
/**
* 登录页面租户下拉框
*
* @return 租户列表
*/
@GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
// 返回对象
LoginTenantVo result = new LoginTenantVo();
boolean enable = TenantHelper.isEnable();
result.setTenantEnabled(enable);
// 如果未开启租户这直接返回
if (!enable) {
return R.ok(result);
}
List<RemoteTenantVo> tenantList = remoteTenantService.queryList();
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
try {
// 如果只超管返回所有租户
if (LoginHelper.isSuperAdmin()) {
result.setVoList(voList);
return R.ok(result);
}
} catch (NotLoginException ignored) {
}
// 获取域名
String host;
String referer = request.getHeader("referer");
if (StringUtils.isNotBlank(referer)) {
// 这里从referer中取值是为了本地使用hosts添加虚拟域名方便本地环境调试
host = referer.split("//")[1].split("/")[0];
} else {
host = new URL(request.getRequestURL().toString()).getHost();
}
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
StringUtils.equals(vo.getDomain(), host));
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
return R.ok(result);
}
}

View File

@ -0,0 +1,16 @@
package org.asinkj.auth.domain.convert;
import io.github.linpeilie.BaseMapper;
import org.asinkj.auth.domain.vo.TenantListVo;
import org.asinkj.system.api.domain.vo.RemoteTenantVo;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
/**
* 租户vo转换器
* @author zhujie
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface TenantVoConvert extends BaseMapper<RemoteTenantVo, TenantListVo> {
}

View File

@ -0,0 +1,25 @@
package org.asinkj.auth.domain.vo;
import lombok.Data;
/**
* 验证码信息
*
* @author Michelle.Chung
*/
@Data
public class CaptchaVo {
/**
* 是否开启验证码
*/
private Boolean captchaEnabled = true;
private String uuid;
/**
* 验证码图片
*/
private String img;
}

View File

@ -0,0 +1,25 @@
package org.asinkj.auth.domain.vo;
import lombok.Data;
import java.util.List;
/**
* 登录租户对象
*
* @author Michelle.Chung
*/
@Data
public class LoginTenantVo {
/**
* 租户开关
*/
private Boolean tenantEnabled;
/**
* 租户对象列表
*/
private List<TenantListVo> voList;
}

View File

@ -0,0 +1,54 @@
package org.asinkj.auth.domain.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 登录验证信息
*
* @author Michelle.Chung
*/
@Data
public class LoginVo {
/**
* 授权令牌
*/
@JsonProperty("access_token")
private String accessToken;
/**
* 刷新令牌
*/
@JsonProperty("refresh_token")
private String refreshToken;
/**
* 授权令牌 access_token 的有效期
*/
@JsonProperty("expire_in")
private Long expireIn;
/**
* 刷新令牌 refresh_token 的有效期
*/
@JsonProperty("refresh_expire_in")
private Long refreshExpireIn;
/**
* 应用id
*/
@JsonProperty("client_id")
private String clientId;
/**
* 令牌权限
*/
private String scope;
/**
* 用户 openid
*/
private String openid;
}

View File

@ -0,0 +1,28 @@
package org.asinkj.auth.domain.vo;
import lombok.Data;
/**
* 租户列表
*
* @author zhujie
*/
@Data
public class TenantListVo {
/**
* 租户编号
*/
private String tenantId;
/**
* 企业名称
*/
private String companyName;
/**
* 域名
*/
private String domain;
}

View File

@ -0,0 +1,35 @@
package org.asinkj.auth.enums;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ShearCaptcha;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 验证码类别
*
* @author Shuo Hu
*/
@Getter
@AllArgsConstructor
public enum CaptchaCategory {
/**
* 线段干扰
*/
LINE(LineCaptcha.class),
/**
* 圆圈干扰
*/
CIRCLE(CircleCaptcha.class),
/**
* 扭曲干扰
*/
SHEAR(ShearCaptcha.class);
private final Class<? extends AbstractCaptcha> clazz;
}

View File

@ -0,0 +1,29 @@
package org.asinkj.auth.enums;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import org.asinkj.auth.captcha.UnsignedMathGenerator;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 验证码类型
*
* @author Shuo Hu
*/
@Getter
@AllArgsConstructor
public enum CaptchaType {
/**
* 数字
*/
MATH(UnsignedMathGenerator.class),
/**
* 字符
*/
CHAR(RandomGenerator.class);
private final Class<? extends CodeGenerator> clazz;
}

View File

@ -0,0 +1,31 @@
package org.asinkj.auth.form;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.asinkj.common.core.domain.model.LoginBody;
/**
* 邮件登录对象
*
* @author Shuo Hu
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class EmailLoginBody extends LoginBody {
/**
* 邮箱
*/
@NotBlank(message = "{user.email.not.blank}")
@Email(message = "{user.email.not.valid}")
private String email;
/**
* 邮箱code
*/
@NotBlank(message = "{email.code.not.blank}")
private String emailCode;
}

View File

@ -0,0 +1,34 @@
package org.asinkj.auth.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.asinkj.common.core.domain.model.LoginBody;
import org.hibernate.validator.constraints.Length;
import static org.asinkj.common.core.constant.UserConstants.*;
/**
* 密码登录对象
*
* @author Shuo Hu
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class PasswordLoginBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
private String password;
}

View File

@ -0,0 +1,39 @@
package org.asinkj.auth.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.asinkj.common.core.domain.model.LoginBody;
import org.hibernate.validator.constraints.Length;
import static org.asinkj.common.core.constant.UserConstants.*;
/**
* 用户注册对象
*
* @author Shuo Hu
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RegisterBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
private String password;
/**
* 用户类型
*/
private String userType;
}

View File

@ -0,0 +1,29 @@
package org.asinkj.auth.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.asinkj.common.core.domain.model.LoginBody;
/**
* 短信登录对象
*
* @author Shuo Hu
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SmsLoginBody extends LoginBody {
/**
* 手机号
*/
@NotBlank(message = "{user.phonenumber.not.blank}")
private String phonenumber;
/**
* 短信code
*/
@NotBlank(message = "{sms.code.not.blank}")
private String smsCode;
}

View File

@ -0,0 +1,35 @@
package org.asinkj.auth.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.asinkj.common.core.domain.model.LoginBody;
/**
* 三方登录对象
*
* @author Shuo Hu
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SocialLoginBody extends LoginBody {
/**
* 第三方登录平台
*/
@NotBlank(message = "{social.source.not.blank}")
private String source;
/**
* 第三方登录code
*/
@NotBlank(message = "{social.code.not.blank}")
private String socialCode;
/**
* 第三方登录socialState
*/
@NotBlank(message = "{social.state.not.blank}")
private String socialState;
}

View File

@ -0,0 +1,28 @@
package org.asinkj.auth.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.asinkj.common.core.domain.model.LoginBody;
/**
* 三方登录对象
*
* @author Shuo Hu
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class XcxLoginBody extends LoginBody {
/**
* 小程序id(多个小程序时使用)
*/
private String appid;
/**
* 小程序code
*/
@NotBlank(message = "{xcx.code.not.blank}")
private String xcxCode;
}

View File

@ -0,0 +1,170 @@
package org.asinkj.auth.listener;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.asinkj.common.core.constant.CacheConstants;
import org.asinkj.common.core.constant.Constants;
import org.asinkj.common.core.utils.MessageUtils;
import org.asinkj.common.core.utils.ServletUtils;
import org.asinkj.common.core.utils.SpringUtils;
import org.asinkj.common.core.utils.ip.AddressUtils;
import org.asinkj.common.log.event.LogininforEvent;
import org.asinkj.common.redis.utils.RedisUtils;
import org.asinkj.common.satoken.utils.LoginHelper;
import org.asinkj.common.tenant.helper.TenantHelper;
import org.asinkj.resource.api.RemoteMessageService;
import org.asinkj.system.api.RemoteUserService;
import org.asinkj.system.api.domain.SysUserOnline;
import org.springframework.stereotype.Component;
import java.time.Duration;
/**
* 用户行为 侦听器的实现
*
* @author Shuo Hu
*/
@RequiredArgsConstructor
@Component
@Slf4j
public class UserActionListener implements SaTokenListener {
private final SaTokenConfig tokenConfig;
@DubboReference
private RemoteUserService remoteUserService;
@DubboReference
private RemoteMessageService remoteMessageService;
/**
* 每次登录时触发
*/
@Override
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP();
SysUserOnline userOnline = new SysUserOnline();
userOnline.setIpaddr(ip);
userOnline.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
userOnline.setBrowser(userAgent.getBrowser().getName());
userOnline.setOs(userAgent.getOs().getName());
userOnline.setLoginTime(System.currentTimeMillis());
userOnline.setTokenId(tokenValue);
String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
userOnline.setUserName(username);
userOnline.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
userOnline.setDeviceType(loginModel.getDevice());
userOnline.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
TenantHelper.dynamic(tenantId, () -> {
if (tokenConfig.getTimeout() == -1) {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, userOnline);
} else {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, userOnline, Duration.ofSeconds(tokenConfig.getTimeout()));
}
});
// 记录登录日志
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
logininforEvent.setMessage(MessageUtils.message("user.login.success"));
SpringUtils.context().publishEvent(logininforEvent);
// 更新登录信息
remoteUserService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
log.info("user doLogin, useId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次注销时触发
*/
@Override
public void doLogout(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doLogout, useId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次被踢下线时触发
*/
@Override
public void doKickout(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doLogoutByLoginId, useId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次被顶下线时触发
*/
@Override
public void doReplaced(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doReplaced, useId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次被封禁时触发
*/
@Override
public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
}
/**
* 每次被解封时触发
*/
@Override
public void doUntieDisable(String loginType, Object loginId, String service) {
}
/**
* 每次打开二级认证时触发
*/
@Override
public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
}
/**
* 每次创建Session时触发
*/
@Override
public void doCloseSafe(String loginType, String tokenValue, String service) {
}
/**
* 每次创建Session时触发
*/
@Override
public void doCreateSession(String id) {
}
/**
* 每次注销Session时触发
*/
@Override
public void doLogoutSession(String id) {
}
/**
* 每次Token续期时触发
*/
@Override
public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
}
}

View File

@ -0,0 +1,46 @@
package org.asinkj.auth.properties;
import org.asinkj.auth.enums.CaptchaCategory;
import org.asinkj.auth.enums.CaptchaType;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 验证码配置
*
* @author asinkj
*/
@Data
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.captcha")
public class CaptchaProperties {
/**
* 验证码类型
*/
private CaptchaType type;
/**
* 验证码类别
*/
private CaptchaCategory category;
/**
* 数字验证码位数
*/
private Integer numberLength;
/**
* 字符验证码长度
*/
private Integer charLength;
/**
* 验证码开关
*/
private Boolean enabled;
}

View File

@ -0,0 +1,29 @@
package org.asinkj.auth.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 用户密码配置
*
* @author Shuo Hu
*/
@Data
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "user.password")
public class UserPasswordProperties {
/**
* 密码最大错误次数
*/
private Integer maxRetryCount;
/**
* 密码锁定时间默认10分钟
*/
private Integer lockTime;
}

View File

@ -0,0 +1,44 @@
package org.asinkj.auth.service;
import org.asinkj.auth.domain.vo.LoginVo;
import org.asinkj.common.core.exception.ServiceException;
import org.asinkj.common.core.utils.SpringUtils;
import org.asinkj.system.api.domain.vo.RemoteClientVo;
/**
* 授权策略
*
* @author Michelle.Chung
*/
public interface IAuthStrategy {
String BASE_NAME = "AuthStrategy";
/**
* 登录
*
* @param body 登录对象
* @param client 授权管理视图对象
* @param grantType 授权类型
* @return 登录验证信息
*/
static LoginVo login(String body, RemoteClientVo client, String grantType) {
// 授权类型和客户端id
String beanName = grantType + BASE_NAME;
if (!SpringUtils.containsBean(beanName)) {
throw new ServiceException("授权类型不正确!");
}
IAuthStrategy instance = SpringUtils.getBean(beanName);
return instance.login(body, client);
}
/**
* 登录
*
* @param body 登录对象
* @param client 授权管理视图对象
* @return 登录验证信息
*/
LoginVo login(String body, RemoteClientVo client);
}

View File

@ -0,0 +1,269 @@
package org.asinkj.auth.service;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.lock.annotation.Lock4j;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.apache.dubbo.config.annotation.DubboReference;
import org.asinkj.auth.form.RegisterBody;
import org.asinkj.auth.properties.CaptchaProperties;
import org.asinkj.auth.properties.UserPasswordProperties;
import org.asinkj.common.core.constant.CacheConstants;
import org.asinkj.common.core.constant.Constants;
import org.asinkj.common.core.constant.GlobalConstants;
import org.asinkj.common.core.constant.TenantConstants;
import org.asinkj.common.core.enums.LoginType;
import org.asinkj.common.core.enums.TenantStatus;
import org.asinkj.common.core.enums.UserType;
import org.asinkj.common.core.exception.ServiceException;
import org.asinkj.common.core.exception.user.CaptchaException;
import org.asinkj.common.core.exception.user.CaptchaExpireException;
import org.asinkj.common.core.exception.user.UserException;
import org.asinkj.common.core.utils.MessageUtils;
import org.asinkj.common.core.utils.SpringUtils;
import org.asinkj.common.core.utils.StringUtils;
import org.asinkj.common.log.event.LogininforEvent;
import org.asinkj.common.redis.utils.RedisUtils;
import org.asinkj.common.satoken.utils.LoginHelper;
import org.asinkj.common.tenant.exception.TenantException;
import org.asinkj.common.tenant.helper.TenantHelper;
import org.asinkj.system.api.RemoteSocialService;
import org.asinkj.system.api.RemoteTenantService;
import org.asinkj.system.api.RemoteUserService;
import org.asinkj.system.api.domain.bo.RemoteSocialBo;
import org.asinkj.system.api.domain.bo.RemoteUserBo;
import org.asinkj.system.api.domain.vo.RemoteSocialVo;
import org.asinkj.system.api.domain.vo.RemoteTenantVo;
import org.asinkj.system.api.model.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.function.Supplier;
/**
* 登录校验方法
*
* @author asinkj
*/
@RequiredArgsConstructor
@Service
@Slf4j
public class SysLoginService {
@DubboReference
private RemoteUserService remoteUserService;
@DubboReference
private RemoteTenantService remoteTenantService;
@DubboReference
private RemoteSocialService remoteSocialService;
@Autowired
private UserPasswordProperties userPasswordProperties;
@Autowired
private final CaptchaProperties captchaProperties;
/**
* 绑定第三方用户
*
* @param authUserData 授权响应实体
*/
@Lock4j
public void socialRegister(AuthUser authUserData) {
String authId = authUserData.getSource() + authUserData.getUuid();
// 第三方用户信息
RemoteSocialBo bo = BeanUtil.toBean(authUserData, RemoteSocialBo.class);
BeanUtil.copyProperties(authUserData.getToken(), bo);
Long userId = LoginHelper.getUserId();
bo.setUserId(userId);
bo.setAuthId(authId);
bo.setOpenId(authUserData.getUuid());
bo.setUserName(authUserData.getUsername());
bo.setNickName(authUserData.getNickname());
List<RemoteSocialVo> checkList = remoteSocialService.selectByAuthId(authId);
if (CollUtil.isNotEmpty(checkList)) {
throw new ServiceException("此三方账号已经被绑定!");
}
// 查询是否已经绑定用户
RemoteSocialBo params = new RemoteSocialBo();
params.setUserId(userId);
params.setSource(bo.getSource());
List<RemoteSocialVo> list = remoteSocialService.queryList(params);
if (CollUtil.isEmpty(list)) {
// 没有绑定用户, 新增用户信息
remoteSocialService.insertByBo(bo);
} else {
// 更新用户信息
bo.setId(list.get(0).getId());
remoteSocialService.updateByBo(bo);
// 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断
// throw new ServiceException("此平台账号已经被绑定!");
}
}
/**
* 退出登录
*/
public void logout() {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
return;
}
if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
// 超级管理员 登出清除动态租户
TenantHelper.clearDynamic();
}
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
} catch (NotLoginException ignored) {
} finally {
try {
StpUtil.logout();
} catch (NotLoginException ignored) {
}
}
}
/**
* 注册
*/
public void register(RegisterBody registerBody) {
String tenantId = registerBody.getTenantId();
String username = registerBody.getUsername();
String password = registerBody.getPassword();
// 校验用户类型是否存在
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
boolean captchaEnabled = captchaProperties.getEnabled();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
}
// 注册用户信息
RemoteUserBo remoteUserBo = new RemoteUserBo();
remoteUserBo.setTenantId(tenantId);
remoteUserBo.setUserName(username);
remoteUserBo.setNickName(username);
remoteUserBo.setPassword(BCrypt.hashpw(password));
remoteUserBo.setUserType(userType);
boolean regFlag = remoteUserService.registerUserInfo(remoteUserBo);
if (!regFlag) {
throw new UserException("user.register.error");
}
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
public void recordLogininfor(String tenantId, String username, String status, String message) {
// 封装对象
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
SpringUtils.context().publishEvent(logininforEvent);
}
/**
* 登录校验
*/
public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL;
Integer maxRetryCount = userPasswordProperties.getMaxRetryCount();
Integer lockTime = userPasswordProperties.getLockTime();
// 获取用户登录错误次数默认为0 (可自定义限制策略 例如: key + username + ip)
int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
// 锁定时间内登录 则踢出
if (errorNumber >= maxRetryCount) {
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
}
if (supplier.get()) {
// 错误次数递增
errorNumber++;
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
// 达到规定错误次数 则锁定登录
if (errorNumber >= maxRetryCount) {
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else {
// 未达到规定错误次数
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
}
}
// 登录成功 清空错误次数
RedisUtils.deleteObject(errorKey);
}
/**
* 校验租户
*
* @param tenantId 租户ID
*/
public void checkTenant(String tenantId) {
if (!TenantHelper.isEnable()) {
return;
}
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
return;
}
if (StringUtils.isBlank(tenantId)) {
throw new TenantException("tenant.number.not.blank");
}
RemoteTenantVo tenant = remoteTenantService.queryByTenantId(tenantId);
if (ObjectUtil.isNull(tenant)) {
log.info("登录租户:{} 不存在.", tenantId);
throw new TenantException("tenant.not.exists");
} else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
log.info("登录租户:{} 已被停用.", tenantId);
throw new TenantException("tenant.blocked");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
&& new Date().after(tenant.getExpireTime())) {
log.info("登录租户:{} 已超过有效期.", tenantId);
throw new TenantException("tenant.expired");
}
}
}

View File

@ -0,0 +1,86 @@
package org.asinkj.auth.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.asinkj.auth.domain.vo.LoginVo;
import org.asinkj.auth.form.EmailLoginBody;
import org.asinkj.auth.service.IAuthStrategy;
import org.asinkj.auth.service.SysLoginService;
import org.asinkj.common.core.constant.Constants;
import org.asinkj.common.core.constant.GlobalConstants;
import org.asinkj.common.core.enums.LoginType;
import org.asinkj.common.core.exception.user.CaptchaExpireException;
import org.asinkj.common.core.utils.MessageUtils;
import org.asinkj.common.core.utils.StringUtils;
import org.asinkj.common.core.utils.ValidatorUtils;
import org.asinkj.common.json.utils.JsonUtils;
import org.asinkj.common.redis.utils.RedisUtils;
import org.asinkj.common.satoken.utils.LoginHelper;
import org.asinkj.common.tenant.helper.TenantHelper;
import org.asinkj.system.api.RemoteUserService;
import org.asinkj.system.api.domain.vo.RemoteClientVo;
import org.asinkj.system.api.model.LoginUser;
import org.springframework.stereotype.Service;
/**
* 邮件认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("email" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class EmailAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {
EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String email = loginBody.getEmail();
String emailCode = loginBody.getEmailCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
LoginUser user = remoteUserService.getUserInfoByEmail(email, tenantId);
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUsername(), () -> !validateEmailCode(tenantId, email, emailCode));
return user;
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验邮箱验证码
*/
private boolean validateEmailCode(String tenantId, String email, String emailCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(emailCode);
}
}

View File

@ -0,0 +1,107 @@
package org.asinkj.auth.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.asinkj.auth.domain.vo.LoginVo;
import org.asinkj.auth.form.PasswordLoginBody;
import org.asinkj.auth.properties.CaptchaProperties;
import org.asinkj.auth.service.IAuthStrategy;
import org.asinkj.auth.service.SysLoginService;
import org.asinkj.common.core.constant.Constants;
import org.asinkj.common.core.constant.GlobalConstants;
import org.asinkj.common.core.enums.LoginType;
import org.asinkj.common.core.exception.user.CaptchaException;
import org.asinkj.common.core.exception.user.CaptchaExpireException;
import org.asinkj.common.core.utils.MessageUtils;
import org.asinkj.common.core.utils.StringUtils;
import org.asinkj.common.core.utils.ValidatorUtils;
import org.asinkj.common.json.utils.JsonUtils;
import org.asinkj.common.redis.utils.RedisUtils;
import org.asinkj.common.satoken.utils.LoginHelper;
import org.asinkj.common.tenant.helper.TenantHelper;
import org.asinkj.system.api.RemoteUserService;
import org.asinkj.system.api.domain.vo.RemoteClientVo;
import org.asinkj.system.api.model.LoginUser;
import org.springframework.stereotype.Service;
/**
* 密码认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("password" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class PasswordAuthStrategy implements IAuthStrategy {
private final CaptchaProperties captchaProperties;
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {
PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String username = loginBody.getUsername();
String password = loginBody.getPassword();
String code = loginBody.getCode();
String uuid = loginBody.getUuid();
// 验证码开关
if (captchaProperties.getEnabled()) {
validateCaptcha(tenantId, username, code, uuid);
}
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
LoginUser user = remoteUserService.getUserInfo(username, tenantId);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
return user;
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
private void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
}

View File

@ -0,0 +1,86 @@
package org.asinkj.auth.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.asinkj.auth.domain.vo.LoginVo;
import org.asinkj.auth.form.SmsLoginBody;
import org.asinkj.auth.service.IAuthStrategy;
import org.asinkj.auth.service.SysLoginService;
import org.asinkj.common.core.constant.Constants;
import org.asinkj.common.core.constant.GlobalConstants;
import org.asinkj.common.core.enums.LoginType;
import org.asinkj.common.core.exception.user.CaptchaExpireException;
import org.asinkj.common.core.utils.MessageUtils;
import org.asinkj.common.core.utils.StringUtils;
import org.asinkj.common.core.utils.ValidatorUtils;
import org.asinkj.common.json.utils.JsonUtils;
import org.asinkj.common.redis.utils.RedisUtils;
import org.asinkj.common.satoken.utils.LoginHelper;
import org.asinkj.common.tenant.helper.TenantHelper;
import org.asinkj.system.api.RemoteUserService;
import org.asinkj.system.api.domain.vo.RemoteClientVo;
import org.asinkj.system.api.model.LoginUser;
import org.springframework.stereotype.Service;
/**
* 短信认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("sms" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class SmsAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {
SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String phonenumber = loginBody.getPhonenumber();
String smsCode = loginBody.getSmsCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
LoginUser user = remoteUserService.getUserInfoByPhonenumber(phonenumber, tenantId);
loginService.checkLogin(LoginType.SMS, tenantId, user.getUsername(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
return user;
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验短信验证码
*/
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
}

View File

@ -0,0 +1,114 @@
package org.asinkj.auth.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import org.apache.dubbo.config.annotation.DubboReference;
import org.asinkj.auth.domain.vo.LoginVo;
import org.asinkj.auth.form.SocialLoginBody;
import org.asinkj.auth.service.IAuthStrategy;
import org.asinkj.common.core.exception.ServiceException;
import org.asinkj.common.core.utils.StreamUtils;
import org.asinkj.common.core.utils.ValidatorUtils;
import org.asinkj.common.json.utils.JsonUtils;
import org.asinkj.common.satoken.utils.LoginHelper;
import org.asinkj.common.social.config.properties.SocialProperties;
import org.asinkj.common.social.utils.SocialUtils;
import org.asinkj.common.tenant.helper.TenantHelper;
import org.asinkj.system.api.RemoteSocialService;
import org.asinkj.system.api.RemoteUserService;
import org.asinkj.system.api.domain.vo.RemoteClientVo;
import org.asinkj.system.api.domain.vo.RemoteSocialVo;
import org.asinkj.system.api.model.LoginUser;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* 第三方授权策略
*
* @author thiszhc is 三三
*/
@Slf4j
@Service("social" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class SocialAuthStrategy implements IAuthStrategy {
private final SocialProperties socialProperties;
@DubboReference
private RemoteSocialService remoteSocialService;
@DubboReference
private RemoteUserService remoteUserService;
/**
* 登录-第三方授权登录
*
* @param body 登录信息
* @param client 客户端信息
*/
@Override
public LoginVo login(String body, RemoteClientVo client) {
SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
ValidatorUtils.validate(loginBody);
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
if (!response.ok()) {
throw new ServiceException(response.getMsg());
}
AuthUser authUserData = response.getData();
if ("GITEE".equals(authUserData.getSource())) {
// 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/asinkj/AsinKj-Vue-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/asinkj/AsinKj-Cloud-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
}
List<RemoteSocialVo> list = remoteSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
if (CollUtil.isEmpty(list)) {
throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
}
RemoteSocialVo socialVo;
if (TenantHelper.isEnable()) {
Optional<RemoteSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
if (opt.isEmpty()) {
throw new ServiceException("对不起,你没有权限登录当前租户!");
}
socialVo = opt.get();
} else {
socialVo = list.get(0);
}
LoginUser loginUser = remoteUserService.getUserInfo(socialVo.getUserId(), socialVo.getTenantId());
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
}

View File

@ -0,0 +1,69 @@
package org.asinkj.auth.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.asinkj.auth.domain.vo.LoginVo;
import org.asinkj.auth.form.XcxLoginBody;
import org.asinkj.auth.service.IAuthStrategy;
import org.asinkj.auth.service.SysLoginService;
import org.asinkj.common.core.utils.ValidatorUtils;
import org.asinkj.common.json.utils.JsonUtils;
import org.asinkj.common.satoken.utils.LoginHelper;
import org.asinkj.system.api.RemoteUserService;
import org.asinkj.system.api.domain.vo.RemoteClientVo;
import org.asinkj.system.api.model.XcxLoginUser;
import org.springframework.stereotype.Service;
/**
* 邮件认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("xcx" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class XcxAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {
XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class);
ValidatorUtils.validate(loginBody);
// xcxCode 小程序调用 wx.login 授权后获取
String xcxCode = loginBody.getXcxCode();
// 多个小程序识别使用
String appid = loginBody.getAppid();
// todo 以下自行实现
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key openid
String openid = "";
XcxLoginUser loginUser = remoteUserService.getUserInfoByOpenid(openid);
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
loginVo.setOpenid(openid);
return loginVo;
}
}

View File

@ -0,0 +1,33 @@
# Tomcat
server:
port: 9210
# Spring
spring:
application:
# 应用名称
name: asinkj-auth
profiles:
# 环境配置
active: @profiles.active@
--- # nacos 配置
spring:
cloud:
nacos:
# nacos 服务地址
server-addr: @nacos.server@
username: @nacos.username@
password: @nacos.password@
discovery:
# 注册组
group: @nacos.discovery.group@
namespace: ${spring.profiles.active}
config:
# 配置组
group: @nacos.config.group@
namespace: ${spring.profiles.active}
config:
import:
- optional:nacos:application-common.yml
- optional:nacos:${spring.application.name}.yml

View File

@ -0,0 +1,8 @@
Spring Boot Version: ${spring-boot.version}
Spring Application Name: ${spring.application.name}
_ _ _ _ _
/_\ ___(_)_ __ /\ /(_) __ _ _ _| |_| |__
//_\\/ __| | '_ \ / //_/ |_____ / _` | | | | __| '_ \
/ _ \__ \ | | | / __ \| |_____| (_| | |_| | |_| | | |
\_/ \_/___/_|_| |_\/ \// | \__,_|\__,_|\__|_| |_|
|__/

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 日志存放路径 -->
<property name="log.path" value="logs/${project.artifactId}"/>
<!-- 日志输出格式 -->
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${console.log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<include resource="logback-common.xml" />
<include resource="logback-logstash.xml" />
<!-- 开启 skywalking 日志收集 -->
<include resource="logback-skylog.xml" />
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console"/>
</root>
</configuration>

View File

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-alibaba-bom</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<description>
asinkj-common-alibaba-bom alibaba依赖项
</description>
<properties>
<revision>2.2.2</revision>
<spring-cloud-alibaba.version>2023.0.1.2</spring-cloud-alibaba.version>
<sentinel.version>1.8.8</sentinel.version>
<seata.version>1.7.1</seata.version>
<nacos.client.version>2.3.3</nacos.client.version>
<dubbo.version>3.2.14</dubbo.version>
<spring.context.support.version>1.0.11</spring.context.support.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos.client.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-apollo</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-zookeeper</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-redis</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-consul</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-reactor-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-server-default</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-client-default</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-webflux-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-webmvc-v6x-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-dubbo-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo3-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
<!-- Apache Dubbo 配置 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-actuator</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-metadata-report-redis</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.context.support.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@ -0,0 +1,262 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-bom</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<description>
asinkj-common-bom common依赖项
</description>
<properties>
<revision>2.2.2</revision>
</properties>
<dependencyManagement>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 接口模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-doc</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-security</artifactId>
<version>${revision}</version>
</dependency>
<!-- 权限认证服务 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-satoken</artifactId>
<version>${revision}</version>
</dependency>
<!-- 日志记录 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-log</artifactId>
<version>${revision}</version>
</dependency>
<!-- 字典 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-dict</artifactId>
<version>${revision}</version>
</dependency>
<!-- excel -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-excel</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存服务 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-redis</artifactId>
<version>${revision}</version>
</dependency>
<!-- web服务 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-web</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据库服务 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-mybatis</artifactId>
<version>${revision}</version>
</dependency>
<!-- 定时任务 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-job</artifactId>
<version>${revision}</version>
</dependency>
<!-- RPC服务 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-dubbo</artifactId>
<version>${revision}</version>
</dependency>
<!-- 分布式事务 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-seata</artifactId>
<version>${revision}</version>
</dependency>
<!-- 自定义负载均衡 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-loadbalancer</artifactId>
<version>${revision}</version>
</dependency>
<!-- oss服务 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-oss</artifactId>
<version>${revision}</version>
</dependency>
<!-- 限流功能 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-ratelimiter</artifactId>
<version>${revision}</version>
</dependency>
<!-- 幂等功能 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-idempotent</artifactId>
<version>${revision}</version>
</dependency>
<!-- 邮件模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-mail</artifactId>
<version>${revision}</version>
</dependency>
<!-- 短信模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-sms</artifactId>
<version>${revision}</version>
</dependency>
<!-- logstash日志推送模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-logstash</artifactId>
<version>${revision}</version>
</dependency>
<!-- ES搜索引擎服务 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-elasticsearch</artifactId>
<version>${revision}</version>
</dependency>
<!-- 限流模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-sentinel</artifactId>
<version>${revision}</version>
</dependency>
<!-- skywalking日志收集模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-skylog</artifactId>
<version>${revision}</version>
</dependency>
<!-- prometheus监控 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-prometheus</artifactId>
<version>${revision}</version>
</dependency>
<!-- 通用翻译功能 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-translation</artifactId>
<version>${revision}</version>
</dependency>
<!-- 脱敏模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-sensitive</artifactId>
<version>${revision}</version>
</dependency>
<!-- 序列化模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-json</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据加解密模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-encrypt</artifactId>
<version>${revision}</version>
</dependency>
<!-- 租户模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-tenant</artifactId>
<version>${revision}</version>
</dependency>
<!-- websocket模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-websocket</artifactId>
<version>${revision}</version>
</dependency>
<!-- 授权认证 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-social</artifactId>
<version>${revision}</version>
</dependency>
<!-- 配置中心 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-nacos</artifactId>
<version>${revision}</version>
</dependency>
<!-- 消息总线模块 -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-bus</artifactId>
<version>${revision}</version>
</dependency>
<!-- sse -->
<dependency>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common-sse</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>asinkj-common-bus</artifactId>
<description>
asinkj-common-bus 消息总线模块
</description>
<dependencies>
<!-- rabbitmq -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!-- kafka -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-bus-kafka</artifactId>-->
<!-- </dependency>-->
<!-- rocketmq -->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-bus-rocketmq</artifactId>-->
<!-- </dependency>-->
</dependencies>
</project>

View File

@ -0,0 +1,15 @@
package org.asinkj.common.bus.config;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.cloud.bus.jackson.RemoteApplicationEventScan;
/**
* bus 配置
*
* @author Shuo Hu
*/
@AutoConfiguration
@RemoteApplicationEventScan(basePackages = "${spring.cloud.bus.base-packages}")
public class BusCustomConfiguration {
}

View File

@ -0,0 +1 @@
org.asinkj.common.bus.config.BusCustomConfiguration

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.asinkj</groupId>
<artifactId>asinkj-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>asinkj-common-core</artifactId>
<description>
asinkj-common-core 核心模块
</description>
<dependencies>
<!-- Spring框架基本的核心工具 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- SpringWeb模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<!-- Hibernate Validator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- servlet包 -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 自动生成YML配置关联JSON文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-spring-boot-starter</artifactId>
</dependency>
<!-- 离线IP地址定位库 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,17 @@
package org.asinkj.common.core.config;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* 程序注解配置
*
* @author Shuo Hu
*/
@AutoConfiguration
@EnableAspectJAutoProxy
@EnableAsync(proxyTargetClass = true)
public class ApplicationConfig {
}

View File

@ -0,0 +1,52 @@
package org.asinkj.common.core.config;
import cn.hutool.core.util.ArrayUtil;
import org.asinkj.common.core.exception.ServiceException;
import org.asinkj.common.core.utils.SpringUtils;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import java.util.Arrays;
import java.util.concurrent.Executor;
/**
* 异步配置
* <p>
* 如果未使用虚拟线程则生效
*
* @author Shuo Hu
*/
@AutoConfiguration
public class AsyncConfig implements AsyncConfigurer {
/**
* 自定义 @Async 注解使用系统线程池
*/
@Override
public Executor getAsyncExecutor() {
if(SpringUtils.isVirtual()) {
return new VirtualThreadTaskExecutor("async-");
}
return SpringUtils.getBean("scheduledExecutorService");
}
/**
* 异步执行异常处理
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
throwable.printStackTrace();
StringBuilder sb = new StringBuilder();
sb.append("Exception message - ").append(throwable.getMessage())
.append(", Method name - ").append(method.getName());
if (ArrayUtil.isNotEmpty(objects)) {
sb.append(", Parameter value - ").append(Arrays.toString(objects));
}
throw new ServiceException(sb.toString());
};
}
}

View File

@ -0,0 +1,61 @@
package org.asinkj.common.core.config;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.asinkj.common.core.utils.Threads;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 线程池配置
*
* @author Shuo Hu
**/
@Slf4j
@AutoConfiguration
public class ThreadPoolConfig {
/**
* 核心线程数 = cpu 核心数 + 1
*/
private final int core = Runtime.getRuntime().availableProcessors() + 1;
private ScheduledExecutorService scheduledExecutorService;
/**
* 执行周期性或定时任务
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
Threads.printException(r, t);
}
};
this.scheduledExecutorService = scheduledThreadPoolExecutor;
return scheduledThreadPoolExecutor;
}
/**
* 销毁事件
*/
@PreDestroy
public void destroy() {
try {
log.info("====关闭后台任务任务线程池====");
Threads.shutdownAndAwaitTermination(scheduledExecutorService);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,40 @@
package org.asinkj.common.core.config;
import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.util.Properties;
/**
* 校验框架配置类
*
* @author Shuo Hu
*/
@AutoConfiguration
public class ValidatorConfig {
/**
* 配置校验框架 快速返回模式
*/
@Bean
public Validator validator(MessageSource messageSource) {
try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
// 国际化
factoryBean.setValidationMessageSource(messageSource);
// 设置使用 HibernateValidator 校验器
factoryBean.setProviderClass(HibernateValidator.class);
Properties properties = new Properties();
// 设置 快速异常返回
properties.setProperty("hibernate.validator.fail_fast", "true");
factoryBean.setValidationProperties(properties);
// 加载配置
factoryBean.afterPropertiesSet();
return factoryBean.getValidator();
}
}
}

View File

@ -0,0 +1,30 @@
package org.asinkj.common.core.constant;
/**
* 缓存的key 常量
*
* @author Shuo Hu
*/
public interface CacheConstants {
/**
* 在线用户 redis key
*/
String ONLINE_TOKEN_KEY = "online_tokens:";
/**
* 参数管理 cache key
*/
String SYS_CONFIG_KEY = "sys_config:";
/**
* 字典管理 cache key
*/
String SYS_DICT_KEY = "sys_dict:";
/**
* 登录账户密码错误次数 redis key
*/
String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
}

View File

@ -0,0 +1,73 @@
package org.asinkj.common.core.constant;
/**
* 缓存组名称常量
* <p>
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize
* <p>
* ttl 过期时间 如果设置为0则不过期 默认为0
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
* <p>
* 例子: test#60stest#0#60stest#0#1m#1000test#1h#0#500
*
* @author Shuo Hu
*/
public interface CacheNames {
/**
* 演示案例
*/
String DEMO_CACHE = "demo:cache#60s#10m#20";
/**
* 系统配置
*/
String SYS_CONFIG = "sys_config";
/**
* 数据字典
*/
String SYS_DICT = "sys_dict";
/**
* 租户
*/
String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
/**
* 客户端
*/
String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
/**
* 用户账户
*/
String SYS_USER_NAME = "sys_user_name#30d";
/**
* 用户名称
*/
String SYS_NICKNAME = "sys_nickname#30d";
/**
* 部门
*/
String SYS_DEPT = "sys_dept#30d";
/**
* OSS内容
*/
String SYS_OSS = "sys_oss#30d";
/**
* OSS配置
*/
String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
/**
* 在线用户
*/
String ONLINE_TOKEN = "online_tokens";
}

View File

@ -0,0 +1,81 @@
package org.asinkj.common.core.constant;
/**
* 通用常量信息
*
* @author asinkj
*/
public interface Constants {
/**
* UTF-8 字符集
*/
String UTF8 = "UTF-8";
/**
* GBK 字符集
*/
String GBK = "GBK";
/**
* www主域
*/
String WWW = "www.";
/**
* http请求
*/
String HTTP = "http://";
/**
* https请求
*/
String HTTPS = "https://";
/**
* 通用成功标识
*/
String SUCCESS = "0";
/**
* 通用失败标识
*/
String FAIL = "1";
/**
* 登录成功
*/
String LOGIN_SUCCESS = "Success";
/**
* 注销
*/
String LOGOUT = "Logout";
/**
* 注册
*/
String REGISTER = "Register";
/**
* 登录失败
*/
String LOGIN_FAIL = "Error";
/**
* 验证码有效期分钟
*/
Integer CAPTCHA_EXPIRATION = 2;
/**
* 令牌
*/
String TOKEN = "token";
/**
* 顶级部门id
*/
Long TOP_PARENT_ID = 0L;
}

View File

@ -0,0 +1,35 @@
package org.asinkj.common.core.constant;
/**
* 全局的key常量 (业务无关的key)
*
* @author Shuo Hu
*/
public interface GlobalConstants {
/**
* 全局 redis key (业务无关的key)
*/
String GLOBAL_REDIS_KEY = "global:";
/**
* 验证码 redis key
*/
String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
/**
* 防重提交 redis key
*/
String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
/**
* 限流 redis key
*/
String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
/**
* 三方认证 redis key
*/
String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
}

Some files were not shown because too many files have changed in this diff Show More