diff --git a/deploy/docker-compose-backend-app.yaml b/deploy/docker-compose-backend-app.yaml index 6e0441e7..ca911ce9 100644 --- a/deploy/docker-compose-backend-app.yaml +++ b/deploy/docker-compose-backend-app.yaml @@ -28,9 +28,14 @@ services: # Telegram - TG_BOT_TOKEN= - TG_DAPP_LEARNING_GROUP_ID= + - TELEGRAM_INVITE_URL= # Discord - DISCORD_BOT_TOKEN= - GUILD_DAPP_LEARNING_ID= + - DISCORD_CHANNEL_MANAGEMENT_CLIENT_ID= + - DISCORD_CHANNEL_MANAGEMENT_CLIENT_SECRET= + - DISCORD_CHANNEL_MANAGEMENT_CALLBACK_URL= + - DISCORD_INVITE_URL= # CRON - CRON_REDPACKET= - CRON_DIS= diff --git a/src/main/java/com/dl/officialsite/activity/ActivityController.java b/src/main/java/com/dl/officialsite/activity/ActivityController.java new file mode 100644 index 00000000..1debf898 --- /dev/null +++ b/src/main/java/com/dl/officialsite/activity/ActivityController.java @@ -0,0 +1,76 @@ +package com.dl.officialsite.activity; + +import com.dl.officialsite.activity.config.ActivityConfig; +import com.dl.officialsite.activity.config.Task; +import com.dl.officialsite.activity.constant.TaskTypeEnum; +import com.dl.officialsite.activity.service.MemberTaskService; +import com.dl.officialsite.common.base.BaseResponse; +import com.dl.officialsite.common.utils.HttpSessionUtils; +import com.dl.officialsite.login.model.SessionUserInfo; +import com.dl.officialsite.member.Member; +import com.dl.officialsite.member.MemberRepository; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpSession; +import javax.validation.constraints.NotNull; +import java.util.Optional; + +/** + * 活动模块 + */ +@RestController +@RequestMapping("/activity") +@Slf4j +public class ActivityController { + @Autowired + private MemberTaskService memberTaskService; + @Autowired + private MemberRepository memberRepository; + @Autowired + private ActivityConfig activityConfig; + + /** + * 获取活动中用户的状态 + */ + @GetMapping("/status") + public BaseResponse fetchMemberTasksStatus(@RequestParam(required = false) String addressForTesting, HttpSession session) { + SessionUserInfo sessionUserInfo = HttpSessionUtils.getMember(session); + final String address = sessionUserInfo != null ? sessionUserInfo.getAddress() : addressForTesting; + + Optional memberOptional = memberRepository.findByAddress(address); + return BaseResponse.successWithData(memberTaskService.getMemberTasksStatusByAddress(address, memberOptional)); + } + + /** + * 检查用户是否完成任务 + */ + @PostMapping("/check") + public BaseResponse checkTask(@NotNull @RequestParam("taskType") TaskTypeEnum taskType, @NotNull @RequestParam("target") String target, + @RequestParam(required = false) String addressForTesting, HttpSession session) { + if (taskType == null) { + return BaseResponse.failWithReason("1201", "Parameter [taskType] should not be null."); + } + if (StringUtils.isBlank(target)) { + return BaseResponse.failWithReason("1201", "Parameter [target] should not be null."); + } + Optional task = activityConfig.findTask(taskType, target); + if (!task.isPresent()) { + return BaseResponse.failWithReason("1201", String.format("Task [%s:%s] is not defined.", taskType, target)); + } + + SessionUserInfo sessionUserInfo = HttpSessionUtils.getMember(session); + final String address = sessionUserInfo != null ? sessionUserInfo.getAddress() : addressForTesting; + + Optional memberOptional = memberRepository.findByAddress(address); + return memberOptional.map(member -> memberTaskService.checkStatus(member, address, taskType, task.get())) + .orElseGet(() -> BaseResponse.failWithReason("1001", "no user found")); // 用户需要注册 + } + +} \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/activity/bean/MemberTaskRecord.java b/src/main/java/com/dl/officialsite/activity/bean/MemberTaskRecord.java new file mode 100644 index 00000000..af488983 --- /dev/null +++ b/src/main/java/com/dl/officialsite/activity/bean/MemberTaskRecord.java @@ -0,0 +1,70 @@ +package com.dl.officialsite.activity.bean; + + +import com.dl.officialsite.activity.constant.TaskTypeEnum; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.DynamicUpdate; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@EntityListeners(AuditingEntityListener.class) +@Entity +@DynamicUpdate +@Table(name = "member_task_record", schema = "dl", uniqueConstraints = { + @UniqueConstraint(name = "unique_task_record", columnNames = { + "address","activityName", "taskType", "target" + }) +}) +public class MemberTaskRecord implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 42) + @NotNull + private String address; + + @Column(length = 32) + @NotNull + private String activityName; + + @NotNull + @Column(length = 16) + @Enumerated(EnumType.STRING) + private TaskTypeEnum taskType; + + @NotNull + @Column(length = 64) + private String target; + + private boolean finished = false; + + @CreatedDate + @Column(updatable = false) + private Long finishTime; +} diff --git a/src/main/java/com/dl/officialsite/activity/bean/MemberTaskRecordRepository.java b/src/main/java/com/dl/officialsite/activity/bean/MemberTaskRecordRepository.java new file mode 100644 index 00000000..2dce1b84 --- /dev/null +++ b/src/main/java/com/dl/officialsite/activity/bean/MemberTaskRecordRepository.java @@ -0,0 +1,23 @@ +package com.dl.officialsite.activity.bean; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface MemberTaskRecordRepository extends JpaRepository, JpaSpecificationExecutor { + + + @Query(value = "select * from member_task_record where address = :address and activity_name=:activityName", nativeQuery = true) + List findActivityRecordsByAddress(@Param("activityName") String activityName, @Param("address") String address); + + @Query(value = "select * from member_task_record where address = :address and activity_name=:activityName and " + + "task_type=:taskType and target=:target", nativeQuery = true) + List findByAddressAndActivityNameAndTaskTypeAndTarget(@Param("address") String address, + @Param("activityName") String activityName, + @Param("taskType") String taskType, + @Param("target") String target); + +} diff --git a/src/main/java/com/dl/officialsite/activity/bean/RewardDistributeRecord.java b/src/main/java/com/dl/officialsite/activity/bean/RewardDistributeRecord.java new file mode 100644 index 00000000..cacbf97f --- /dev/null +++ b/src/main/java/com/dl/officialsite/activity/bean/RewardDistributeRecord.java @@ -0,0 +1,62 @@ +package com.dl.officialsite.activity.bean; + + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.DynamicUpdate; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@EntityListeners(AuditingEntityListener.class) +@Entity +@DynamicUpdate +@Table(name = "reward_distribute_record", schema = "dl", uniqueConstraints = { + @UniqueConstraint(name = "unique_task_record", columnNames = { + "address","activityName" + }) +}) +public class RewardDistributeRecord implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 42) + @NotNull + private String address; + + @Column(length = 32) + @NotNull + private String activityName; + + private boolean rewardDistributed = false; + + @Column(length = 128) + @NotNull + private String reward; + + @CreatedDate + @Column(updatable = false) + private Long distributedTime; +} diff --git a/src/main/java/com/dl/officialsite/activity/config/ActivityConfig.java b/src/main/java/com/dl/officialsite/activity/config/ActivityConfig.java new file mode 100644 index 00000000..c93ff532 --- /dev/null +++ b/src/main/java/com/dl/officialsite/activity/config/ActivityConfig.java @@ -0,0 +1,56 @@ +package com.dl.officialsite.activity.config; + +import com.dl.officialsite.activity.constant.TaskTypeEnum; +import com.dl.officialsite.activity.model.MemberTaskStatus; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@Data +@EqualsAndHashCode(callSuper = false) +@ToString +@Configuration +@ConfigurationProperties(prefix = "activity", ignoreInvalidFields = true) +public class ActivityConfig { + private String name; + private Map> taskMap; + + private static long taskCount; + + @PostConstruct + public void init() { + taskCount = Optional.of(taskMap).orElse(Collections.emptyMap()).values().stream(). + map(Collection::size).mapToInt(Integer::intValue).sum(); + + log.info("Init ActivityConfig:[{}], taskCount:[{}]", this, taskCount); + } + + public Optional findTask(TaskTypeEnum taskType, String target) { + return Optional.ofNullable(taskMap.get(taskType)) + .flatMap(list -> list.stream().filter(task -> StringUtils.equalsIgnoreCase(task.getTarget(), target)) + .reduce((a, b) -> { + throw new IllegalStateException("Shouldn't be multiple records"); + })); + } + + public List fetchMemberTaskStatusList() { + return taskMap.entrySet().stream().flatMap(entry -> + entry.getValue().stream().map(task -> new MemberTaskStatus(entry.getKey(), task.getName(), task.getTarget(), + task.getTargetUrl())) + ).collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/activity/config/Task.java b/src/main/java/com/dl/officialsite/activity/config/Task.java new file mode 100644 index 00000000..beacb6d0 --- /dev/null +++ b/src/main/java/com/dl/officialsite/activity/config/Task.java @@ -0,0 +1,10 @@ +package com.dl.officialsite.activity.config; + +import lombok.Data; + +@Data +public class Task { + private String name; + private String target; + private String targetUrl; +} \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/activity/constant/TaskTypeEnum.java b/src/main/java/com/dl/officialsite/activity/constant/TaskTypeEnum.java new file mode 100644 index 00000000..1baa6905 --- /dev/null +++ b/src/main/java/com/dl/officialsite/activity/constant/TaskTypeEnum.java @@ -0,0 +1,16 @@ +package com.dl.officialsite.activity.constant; + +import lombok.Getter; + +@Getter +public enum TaskTypeEnum { + GIT_HUB("GitHub"), + TELEGRAM("Telegram"), + DISCORD("Discord"); + + private String value; + + private TaskTypeEnum(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/activity/model/MemberTaskStatus.java b/src/main/java/com/dl/officialsite/activity/model/MemberTaskStatus.java new file mode 100644 index 00000000..2680f794 --- /dev/null +++ b/src/main/java/com/dl/officialsite/activity/model/MemberTaskStatus.java @@ -0,0 +1,25 @@ +package com.dl.officialsite.activity.model; + +import com.dl.officialsite.activity.constant.TaskTypeEnum; +import lombok.Data; + +@Data +public class MemberTaskStatus { + private TaskTypeEnum taskType; + private String taskName; + private String target; + private String targetUrl; + + private boolean requiredAuthorization = true; + private boolean finished = false; + + public MemberTaskStatus() { + } + + public MemberTaskStatus(TaskTypeEnum taskType, String taskName, String target, String targetUrl) { + this.taskType = taskType; + this.taskName = taskName; + this.target = target; + this.targetUrl = targetUrl; + } +} \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/activity/service/MemberTaskService.java b/src/main/java/com/dl/officialsite/activity/service/MemberTaskService.java new file mode 100644 index 00000000..b217c2ae --- /dev/null +++ b/src/main/java/com/dl/officialsite/activity/service/MemberTaskService.java @@ -0,0 +1,157 @@ +package com.dl.officialsite.activity.service; + +import com.dl.officialsite.activity.bean.MemberTaskRecord; +import com.dl.officialsite.activity.bean.MemberTaskRecordRepository; +import com.dl.officialsite.activity.config.ActivityConfig; +import com.dl.officialsite.activity.config.Task; +import com.dl.officialsite.activity.constant.TaskTypeEnum; +import com.dl.officialsite.activity.model.MemberTaskStatus; +import com.dl.officialsite.bot.discord.DiscordBotService; +import com.dl.officialsite.bot.telegram.TelegramBotService; +import com.dl.officialsite.common.base.BaseResponse; +import com.dl.officialsite.member.Member; +import com.dl.officialsite.member.MemberRepository; +import com.dl.officialsite.oauth2.AccessTokenCacheService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; + +@Slf4j +@Service +public class MemberTaskService { + private static final String API_URL = "https://api.github.com/user/starred/%s"; + + @Autowired + private MemberTaskRecordRepository memberTaskRecordRepository; + @Autowired + private MemberRepository memberRepository; + @Autowired + private ActivityConfig activityConfig; + @Autowired + private DiscordBotService discordBotService; + @Autowired + private TelegramBotService telegramBotService; + @Autowired + private RestTemplate restTemplate; + + + public BaseResponse checkStatus(Member member, String address, TaskTypeEnum taskType, Task task) { + List finishRecord = + memberTaskRecordRepository.findByAddressAndActivityNameAndTaskTypeAndTarget(address, activityConfig.getName(), taskType.getValue(), + task.getTarget()); + if (CollectionUtils.size(finishRecord) == 1 && finishRecord.get(0).isFinished()) { + return BaseResponse.successWithData(true); + } + + boolean result = false; + switch (taskType) { + case DISCORD: + if (StringUtils.isBlank(member.getDiscordId())) { + return BaseResponse.failWithReason("1202", "Discord id is null."); + } + result = this.discordBotService.isUserInChannel(task.getTarget(), member.getDiscordId()); + break; + case GIT_HUB: + // GitHub 需要判断 session 是否有 access_token + if (StringUtils.isBlank(member.getGithubId())) { + return BaseResponse.failWithReason("1202", "GitHub id is null."); + } + String gitHubAccessToken = AccessTokenCacheService.getGitHubAccessToken(member.getGithubId()); + if (StringUtils.isBlank(gitHubAccessToken)) { + return BaseResponse.failWithReason("1202", "GitHub access token is null."); + } + result = githubCheckIfUserStarredRepo(gitHubAccessToken, task.getTarget()); + break; + case TELEGRAM: + if (StringUtils.isBlank(member.getTelegramUserId())) { + return BaseResponse.failWithReason("1202", "Telegram id is null."); + } + result = this.telegramBotService.isUserInChannel(task.getTarget(), member.getTelegramUserId()); + break; + default: + throw new RuntimeException("Unknown task type: " + taskType); + } + if (result) { + MemberTaskRecord memberTaskRecord = new MemberTaskRecord(); + memberTaskRecord.setAddress(address); + memberTaskRecord.setActivityName(activityConfig.getName()); + memberTaskRecord.setFinishTime(Instant.now().getEpochSecond()); + memberTaskRecord.setFinished(result); + memberTaskRecord.setTarget(task.getTarget()); + memberTaskRecord.setTaskType(taskType); + memberTaskRecordRepository.save(memberTaskRecord); + } + return BaseResponse.successWithData(result); + } + + public List getMemberTasksStatusByAddress(String address, Optional member) { + List memberTaskRecordList = + memberTaskRecordRepository.findActivityRecordsByAddress(activityConfig.getName(), address); + + // all the tasks + List memberTaskStatuses = this.activityConfig.fetchMemberTaskStatusList(); + memberTaskStatuses.forEach(status -> { + Optional finishRecord = filter(memberTaskRecordList, status.getTaskType(), status.getTarget()); + if (finishRecord.isPresent() && finishRecord.get().isFinished()) { + status.setFinished(true); + } else if (member.isPresent()) { // 用户注册了,可以检查任务完成情况 + switch (status.getTaskType()) { + case DISCORD: + status.setRequiredAuthorization(StringUtils.isBlank(member.get().getDiscordId())); + break; + case GIT_HUB: + // GitHub 判断 session 是否有 access_token + status.setRequiredAuthorization( + StringUtils.isBlank(AccessTokenCacheService.getGitHubAccessToken(member.get().getGithubId()))); + break; + case TELEGRAM: + status.setRequiredAuthorization(StringUtils.isBlank(member.get().getTelegramUserId())); + break; + } + } + }); + return memberTaskStatuses; + } + + private Optional filter(List memberTaskRecordList, TaskTypeEnum taskTypeEnum, String target) { + return memberTaskRecordList.stream() + .filter(record -> taskTypeEnum.equals(record.getTaskType())) + .filter(record -> target.equals(record.getTarget())) + .reduce((a, b) -> { + throw new IllegalStateException("Shouldn't be multiple records"); + }); + } + + private boolean githubCheckIfUserStarredRepo(String accessToken, String targetRepo) { + HttpHeaders headers = new HttpHeaders(); + headers.set("Accept", "application/vnd.github+json"); + headers.set("Authorization", "Bearer " + accessToken); + headers.set("X-GitHub-Api-Version", "2022-11-28"); + + HttpEntity entity = new HttpEntity<>(headers); + try { + targetRepo = StringUtils.removeStart(targetRepo, "/"); + ResponseEntity response = restTemplate.exchange(String.format(API_URL, targetRepo), HttpMethod.GET, entity, + String.class); + // 检查响应状态码 + return response.getStatusCode() == HttpStatus.NO_CONTENT; // 204 NO_CONTENT 表示用户已标星 + } catch (HttpClientErrorException ex) { + ex.printStackTrace(); + return false; + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/bot/BaseBotService.java b/src/main/java/com/dl/officialsite/bot/BaseBotService.java index 24620d51..9986e7a2 100644 --- a/src/main/java/com/dl/officialsite/bot/BaseBotService.java +++ b/src/main/java/com/dl/officialsite/bot/BaseBotService.java @@ -15,4 +15,6 @@ public abstract class BaseBotService { public abstract Pair sendMessage(GroupNameEnum groupNameEnum, ChannelEnum channelEnum, Message text); + public abstract boolean isUserInChannel(String channelId, String userId); + } \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/bot/discord/DiscordBotService.java b/src/main/java/com/dl/officialsite/bot/discord/DiscordBotService.java index b73a53b4..57f55e21 100644 --- a/src/main/java/com/dl/officialsite/bot/discord/DiscordBotService.java +++ b/src/main/java/com/dl/officialsite/bot/discord/DiscordBotService.java @@ -4,16 +4,45 @@ import com.dl.officialsite.bot.constant.ChannelEnum; import com.dl.officialsite.bot.constant.GroupNameEnum; import com.dl.officialsite.bot.model.Message; +import com.dl.officialsite.common.utils.HttpUtil; import org.apache.commons.lang3.tuple.Pair; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.util.EntityUtils; +import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; +import java.io.IOException; + @Service public class DiscordBotService extends BaseBotService { + private static final String IS_USER_IN_CHANNEL_API = "https://discord.com/api/v9/guilds/%s/members/%s"; @Override public Pair sendMessage(GroupNameEnum groupNameEnum, ChannelEnum channelEnum, Message msg) { Pair channelIdByName = this.getBotConfig().getGroupIdAndChannelIdByName(groupNameEnum, channelEnum); return DiscordBotUtil.sendMessageToChannel(this.getBotConfig().getBot(), channelIdByName.getValue(), msg); } + + @Override + public boolean isUserInChannel(String channelId, String userId) { + HttpGet httpGet = new HttpGet(String.format(IS_USER_IN_CHANNEL_API, channelId, userId)); + httpGet.setHeader(HttpHeaders.AUTHORIZATION, "Bot " + this.getBotConfig().getBotToken()); + + try { + HttpResponse response = HttpUtil.client().execute(httpGet); + + // Handle response + HttpEntity entity = response.getEntity(); + if (entity != null) { + String responseBody = EntityUtils.toString(entity); + return responseBody.contains(userId); + } + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } } \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/bot/discord/DiscordOAuthController.java b/src/main/java/com/dl/officialsite/bot/discord/DiscordOAuthController.java new file mode 100644 index 00000000..bd0fafb6 --- /dev/null +++ b/src/main/java/com/dl/officialsite/bot/discord/DiscordOAuthController.java @@ -0,0 +1,180 @@ +package com.dl.officialsite.bot.discord; + + +import com.dl.officialsite.common.base.BaseResponse; +import com.dl.officialsite.common.utils.GsonUtil; +import com.dl.officialsite.common.utils.HttpSessionUtils; +import com.dl.officialsite.common.utils.HttpUtil; +import com.dl.officialsite.login.model.SessionUserInfo; +import com.dl.officialsite.member.Member; +import com.dl.officialsite.member.MemberRepository; +import com.dl.officialsite.oauth2.config.OAuthConfig; +import com.dl.officialsite.oauth2.config.RegistrationConfig; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@RestController +@Slf4j +public class DiscordOAuthController { + private static final String O_AUTH_URL = "https://discord.com/api/oauth2/authorize?client_id={clientId}&redirect_uri={callbackUrl}" + + "&response_type=code&scope=guilds.members.read"; + private static final String DISCORD_TOKEN_URL = "https://discord.com/api/v9/oauth2/token"; + private static final String DISCORD_USER_INFO_URL = "https://discord.com/api/v9/users/@me"; + + @Autowired + private OAuthConfig oAuthConfig; + @Autowired + private MemberRepository memberRepository; + @Autowired + private RestTemplate restTemplate; + + RegistrationConfig discordConfig = null; + + + @PostConstruct + public void setUpTwitter() { + discordConfig = oAuthConfig.getRegistrations().get("discord"); + if (discordConfig == null) { + throw new RuntimeException("Invalid registrationId"); + } + log.info("Successfully set up Discord...."); + } + + @GetMapping("oauth2/authorization/discord") + public void twitterOauthLogin(@RequestParam(name = "test", defaultValue = "false") boolean test, HttpServletResponse response) + throws IOException { + Map uriVariables = new HashMap<>(); + uriVariables.put("clientId", discordConfig.getClientId()); + uriVariables.put("callbackUrl", discordConfig.getCallbackUrl()); + response.sendRedirect(UriComponentsBuilder.fromUriString(O_AUTH_URL).buildAndExpand(uriVariables) + .toUriString()); + } + + @GetMapping("/oauth2/callback/discord") + public BaseResponse getTwitter(@RequestParam("code") String code, @RequestParam(required = false) String addressForTesting, + HttpSession session) { + // 检查用户是否注册 + SessionUserInfo sessionUserInfo = HttpSessionUtils.getMember(session); + final String address = sessionUserInfo != null ? sessionUserInfo.getAddress() : addressForTesting; + Optional member = this.memberRepository.findByAddress(address); + if (!member.isPresent()) { + return BaseResponse.failWithReason("1001", "no user found"); // 用户需要注册 + } + + // 获取 access token + String accessToken = fetchAccessToken(code); + if (StringUtils.isBlank(accessToken)) { + return BaseResponse.failWithReason("1101", "Fetch Discord access token failed."); + } + + String discordUserId = fetchUserId(accessToken); + if (StringUtils.isBlank(discordUserId)) { + return BaseResponse.failWithReason("1102", "Fetch Discord user id failed."); + } + + member.get().setDiscordId(discordUserId); + memberRepository.save(member.get()); + return BaseResponse.success(); + } + + private String fetchAccessToken(String code) { + List paramList = getParams(code); + HttpPost httpPost = new HttpPost(DISCORD_TOKEN_URL); + try { + httpPost.setEntity(new UrlEncodedFormEntity(paramList)); + HttpResponse response = HttpUtil.client().execute(httpPost); + // Handle response + HttpEntity entity = response.getEntity(); + if (entity != null) { + String responseBody = EntityUtils.toString(entity); + return GsonUtil.fromJson(responseBody, DiscordTokenResponse.class).getAccess_token(); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private List getParams(String code) { + List params = new ArrayList<>(); + params.add(new BasicNameValuePair("client_id", discordConfig.getClientId())); + params.add(new BasicNameValuePair("client_secret", discordConfig.getClientSecret())); + params.add(new BasicNameValuePair("grant_type", "authorization_code")); + params.add(new BasicNameValuePair("code", code)); + params.add(new BasicNameValuePair("redirect_uri", discordConfig.getCallbackUrl())); + params.add(new BasicNameValuePair("scope", "guilds.members.read")); + return params; + } + + private String fetchUserId(String accessToken) { + HttpGet httpGet = new HttpGet(DISCORD_USER_INFO_URL); + httpGet.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); + + try { + HttpResponse response = HttpUtil.client().execute(httpGet); + + // Handle response + HttpEntity entity = response.getEntity(); + if (entity != null) { + String responseBody = EntityUtils.toString(entity); + return GsonUtil.fromJson(responseBody, DiscordUserInfo.class).getId(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + @Data + public static class DiscordUserInfo { + private String id; + private String username; + private String avatar; + private String discriminator; + private int publicFlags; + private int flags; + private String banner; + private String accentColor; + private String globalName; + private String avatarDecorationData; + private String bannerColor; + private String clan; + private boolean mfaEnabled; + private String locale; + private int premiumType; + } + + @Data + public static class DiscordTokenResponse { + private String token_type; + private String access_token; + private int expires_in; + private String refresh_token; + private String scope; + } +} diff --git a/src/main/java/com/dl/officialsite/bot/event/NotifyEventListener.java b/src/main/java/com/dl/officialsite/bot/event/NotifyEventListener.java index 91dc833f..65404167 100644 --- a/src/main/java/com/dl/officialsite/bot/event/NotifyEventListener.java +++ b/src/main/java/com/dl/officialsite/bot/event/NotifyEventListener.java @@ -12,7 +12,9 @@ import org.springframework.stereotype.Component; import java.util.Arrays; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; @Slf4j @@ -23,6 +25,13 @@ public class NotifyEventListener implements ApplicationListener { @Autowired private DiscordBotService discordBotService; + public static Set ENABLE_CHANNELS = new HashSet<>(); + + static { + ENABLE_CHANNELS.add(ChannelEnum.WELCOME); + ENABLE_CHANNELS.add(ChannelEnum.SHARING); + } + @Override public void onApplicationEvent(EventNotify event) { String sourceName = event.getSource().toString(); @@ -35,6 +44,10 @@ public void onApplicationEvent(EventNotify event) { } private void sendMessage(BotEnum botEnum, GroupNameEnum group, ChannelEnum channelEnum, Message message) { + if (!ENABLE_CHANNELS.contains(channelEnum)) { + return; + } + switch (botEnum) { case DISCORD: Optional.ofNullable(discordBotService.getBotConfig().getBot()).ifPresent(service -> diff --git a/src/main/java/com/dl/officialsite/bot/event/NotifyMessageFactory.java b/src/main/java/com/dl/officialsite/bot/event/NotifyMessageFactory.java index dec77b8a..3dca6804 100644 --- a/src/main/java/com/dl/officialsite/bot/event/NotifyMessageFactory.java +++ b/src/main/java/com/dl/officialsite/bot/event/NotifyMessageFactory.java @@ -14,7 +14,7 @@ public static Message testMessage(String testMsg) { } public static Message welcomeUserMessage(String nickName) { - return Message.build(String.format("Welcome %s join Dapp-Learning, introduce yourself briefly.\n%s", nickName, SOCIAL_MEDIA)); + return Message.build(String.format("Welcome %s join Dapp-Learning, introduce yourself briefly.", nickName)); } public static Message bountyMessage(String nickName, String title) { diff --git a/src/main/java/com/dl/officialsite/bot/model/BaseBotConfig.java b/src/main/java/com/dl/officialsite/bot/model/BaseBotConfig.java index 716cd957..1e93c156 100644 --- a/src/main/java/com/dl/officialsite/bot/model/BaseBotConfig.java +++ b/src/main/java/com/dl/officialsite/bot/model/BaseBotConfig.java @@ -3,13 +3,16 @@ import com.dl.officialsite.bot.constant.BotEnum; import com.dl.officialsite.bot.constant.ChannelEnum; import com.dl.officialsite.bot.constant.GroupNameEnum; -import java.util.ArrayList; -import java.util.List; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; + @Getter @Setter @Slf4j @@ -28,7 +31,7 @@ public BaseBotConfig(BotEnum botEnum) { public abstract B initBot() throws InterruptedException; -/* @PostConstruct + @PostConstruct public void init() throws InterruptedException { log.info("Start to init bot:[{}] ...", botEnum); @@ -38,7 +41,7 @@ public void init() throws InterruptedException { bot = initBot(); } log.info("The {} Bot is initialized and ready with detail:[{}]!!!", botEnum, this.toString()); - }*/ + } public Pair getGroupIdAndChannelIdByName(GroupNameEnum groupNameEnum, ChannelEnum channelEnum) { final GroupNameEnum groupName = groupNameEnum == null ? GroupNameEnum.DAPP_LEARNING : groupNameEnum; diff --git a/src/main/java/com/dl/officialsite/bot/telegram/TelegramBotService.java b/src/main/java/com/dl/officialsite/bot/telegram/TelegramBotService.java index 8bc52162..80b0838a 100644 --- a/src/main/java/com/dl/officialsite/bot/telegram/TelegramBotService.java +++ b/src/main/java/com/dl/officialsite/bot/telegram/TelegramBotService.java @@ -4,16 +4,44 @@ import com.dl.officialsite.bot.constant.ChannelEnum; import com.dl.officialsite.bot.constant.GroupNameEnum; import com.dl.officialsite.bot.model.Message; +import lombok.Data; import org.apache.commons.lang3.tuple.Pair; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; @Service public class TelegramBotService extends BaseBotService { + @Autowired + private RestTemplate restTemplate; + + public static final String IS_USER_IN_CHANNEL_API = + "https://api.telegram.org/bot%s/getChatMember?chat_id=%s&user_id=%s"; + + @Override public Pair sendMessage(GroupNameEnum groupNameEnum, ChannelEnum channelEnum, Message msg) { Pair groupIdAndChannelId = this.getBotConfig().getGroupIdAndChannelIdByName(groupNameEnum, channelEnum); return TelegramBotUtil.sendMarkdownV2MessageToTopic(this.getBotConfig().getBot(), groupIdAndChannelId.getKey(), msg, groupIdAndChannelId.getValue()); } + + @Override + public boolean isUserInChannel(String channelId, String userId) { + ResponseEntity responseEntity = + restTemplate.getForEntity(String.format(IS_USER_IN_CHANNEL_API, this.getBotConfig().getBotToken(), channelId, userId), + TelegramApiResponse.class); + + if (responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) { + return Boolean.parseBoolean(responseEntity.getBody().getOk()); + } + return false; + } + + @Data + public static class TelegramApiResponse { + private String ok; + } } \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/bot/telegram/TelegramOAuthController.java b/src/main/java/com/dl/officialsite/bot/telegram/TelegramOAuthController.java new file mode 100644 index 00000000..06e36986 --- /dev/null +++ b/src/main/java/com/dl/officialsite/bot/telegram/TelegramOAuthController.java @@ -0,0 +1,71 @@ +package com.dl.officialsite.bot.telegram; + + +import com.dl.officialsite.bot.util.TelegramVerifyValidator; +import com.dl.officialsite.common.base.BaseResponse; +import com.dl.officialsite.common.utils.HttpSessionUtils; +import com.dl.officialsite.login.model.SessionUserInfo; +import com.dl.officialsite.member.Member; +import com.dl.officialsite.member.MemberRepository; +import com.dl.officialsite.oauth2.config.OAuthConfig; +import com.dl.officialsite.oauth2.config.RegistrationConfig; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.social.twitter.connect.TwitterConnectionFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpSession; +import java.util.Map; +import java.util.Optional; + +@RestController +@Slf4j +public class TelegramOAuthController { + + @Autowired + private OAuthConfig oAuthConfig; + @Autowired + private MemberRepository memberRepository; + + private TwitterConnectionFactory connectionFactory = null; + RegistrationConfig telegramConfig = null; + + + @PostConstruct + public void setUpTelegram() { + telegramConfig = oAuthConfig.getRegistrations().get("telegram"); + if (telegramConfig == null) { + throw new RuntimeException("Invalid registrationId"); + } + log.info("Successfully set up Telegram login validator"); + } + + @GetMapping("/oauth2/callback/telegram") + public BaseResponse verifyTelegram(@RequestParam Map params, @RequestParam(required = false) String addressForTesting, + HttpSession session) { + SessionUserInfo sessionUserInfo = HttpSessionUtils.getMember(session); + final String address = sessionUserInfo != null ? sessionUserInfo.getAddress() : addressForTesting; + params.remove("addressForTesting"); + + boolean verifyResult = TelegramVerifyValidator.verifyTelegramParameter(params, telegramConfig.getClientSecret()); + if (verifyResult) { + Optional member = this.memberRepository.findByAddress(address); + if (!member.isPresent()) { + return BaseResponse.failWithReason("1001", "no user found"); // 用户需要注册 + } + if (StringUtils.equals(params.get("id"),member.get().getTelegramUserId())) { + return BaseResponse.failWithReason("1104", "Telegram user id is same to the Telegram user id in Database."); // 用户需要注册 + } + + String telegramUserId = params.get("id"); + member.get().setTelegramUserId(telegramUserId); + memberRepository.save(member.get()); + return BaseResponse.success(); + } + return BaseResponse.failWithReason("1103", "Telegram request verify failed."); + } +} diff --git a/src/main/java/com/dl/officialsite/bot/util/TelegramVerifyValidator.java b/src/main/java/com/dl/officialsite/bot/util/TelegramVerifyValidator.java new file mode 100644 index 00000000..ce47cd24 --- /dev/null +++ b/src/main/java/com/dl/officialsite/bot/util/TelegramVerifyValidator.java @@ -0,0 +1,50 @@ +package com.dl.officialsite.bot.util; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.StringUtils; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.time.Instant; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +public class TelegramVerifyValidator { + public static final long AUTH_DATE_RANGE_MINUTE = 10; + + public static boolean verifyTelegramParameter(Map params, String telegramBotToken) { + String hash = (String) params.get("hash"); + params.remove("hash"); + + // Prepare the string + String str = params.entrySet().stream() + .sorted((a, b) -> a.getKey().compareToIgnoreCase(b.getKey())) + .map(kvp -> kvp.getKey() + "=" + kvp.getValue()) + .collect(Collectors.joining("\n")); + + try { + long authDate = Long.parseLong(params.get("auth_date")); + long now = Instant.now().getEpochSecond(); + if (authDate >= now || (now - authDate) > AUTH_DATE_RANGE_MINUTE * 60) { + log.error("The auth date of telegram request is before {} minutes ago.", AUTH_DATE_RANGE_MINUTE); + return false; + } + SecretKeySpec sk = new SecretKeySpec( + MessageDigest.getInstance("SHA-256").digest(telegramBotToken.getBytes(StandardCharsets.UTF_8)), "HmacSHA256"); + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(sk); + + byte[] result = mac.doFinal(str.getBytes(StandardCharsets.UTF_8)); + String resultStr = Hex.encodeHexString(result); + + return StringUtils.equalsIgnoreCase(hash, resultStr); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/common/utils/GsonUtil.java b/src/main/java/com/dl/officialsite/common/utils/GsonUtil.java new file mode 100644 index 00000000..930ff1de --- /dev/null +++ b/src/main/java/com/dl/officialsite/common/utils/GsonUtil.java @@ -0,0 +1,36 @@ +package com.dl.officialsite.common.utils; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class GsonUtil { + private static final Gson gson = new Gson(); + + private GsonUtil() { + // 私有构造方法,防止实例化 + } + + /** + * 将对象转换为 JSON 字符串 + */ + public static String toJson(Object object) { + return gson.toJson(object); + } + + /** + * 将对象转换为格式化的 JSON 字符串(带缩进) + */ + public static String toPrettyJson(Object object) { + Gson prettyGson = new GsonBuilder().setPrettyPrinting().create(); + return prettyGson.toJson(object); + } + + /** + * 将 JSON 字符串转换为指定类型的对象 + */ + public static T fromJson(String json, Class clazz) { + return gson.fromJson(json, clazz); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/common/utils/HttpUtil.java b/src/main/java/com/dl/officialsite/common/utils/HttpUtil.java new file mode 100644 index 00000000..c7456c72 --- /dev/null +++ b/src/main/java/com/dl/officialsite/common/utils/HttpUtil.java @@ -0,0 +1,36 @@ +package com.dl.officialsite.common.utils; + +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; + +public class HttpUtil { + // 配置连接超时和socket超时时间,单位为毫秒 + private static final int connectionTimeout = 5000; // 连接超时时间为5秒 + private static final int socketTimeout = 10000; // Socket超时时间为10秒 + + private static final CloseableHttpClient httpClient; + + static { + PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); + connManager.setMaxTotal(5); + connManager.setDefaultMaxPerRoute(2); + + // 创建RequestConfig.Builder来设置超时参数 + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(connectionTimeout) + .setSocketTimeout(socketTimeout) + .build(); + + httpClient = HttpClientBuilder.create() + .setConnectionManager(connManager) + .setDefaultRequestConfig(requestConfig) + .build(); + } + + public static HttpClient client() { + return httpClient; + } +} \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/login/filter/LoginFilter.java b/src/main/java/com/dl/officialsite/login/filter/LoginFilter.java index 0e471b1e..3ade1138 100644 --- a/src/main/java/com/dl/officialsite/login/filter/LoginFilter.java +++ b/src/main/java/com/dl/officialsite/login/filter/LoginFilter.java @@ -62,9 +62,11 @@ public class LoginFilter extends OncePerRequestFilter { add("/bounty/list"); }} ; - private Set noAddrCheckApis = new HashSet(){{ - add("oauth2/bind/code/github"); - add("oauth2/callback/twitter"); + private Set noAddrCheckApis = new HashSet() {{ + add("oauth2/bind/code/github"); + add("oauth2/callback/twitter"); + add("oauth2/callback/discord"); + add("oauth2/callback/telegram"); }}; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { diff --git a/src/main/java/com/dl/officialsite/member/Member.java b/src/main/java/com/dl/officialsite/member/Member.java index c034e504..f6b183f8 100644 --- a/src/main/java/com/dl/officialsite/member/Member.java +++ b/src/main/java/com/dl/officialsite/member/Member.java @@ -39,6 +39,8 @@ @UniqueConstraint(name = "address", columnNames = {"address"}), @UniqueConstraint(name = "tweetId", columnNames = {"tweetId"}), @UniqueConstraint(name = "githubId", columnNames = {"githubId"}), + @UniqueConstraint(name = "discordId", columnNames = {"discordId"}), + @UniqueConstraint(name = "telegramUserId", columnNames = {"telegramUserId"}), @UniqueConstraint(name = "nickName", columnNames = {"nickName"})}) public class Member implements Serializable { @@ -116,5 +118,10 @@ public class Member implements Serializable { @Column(columnDefinition = "tinyint default 0") private Integer status = 0; + @Column(length = 32) + private String discordId; + + @Column(length = 32) + private String telegramUserId; } diff --git a/src/main/java/com/dl/officialsite/oauth2/AccessTokenCacheService.java b/src/main/java/com/dl/officialsite/oauth2/AccessTokenCacheService.java new file mode 100644 index 00000000..60a78511 --- /dev/null +++ b/src/main/java/com/dl/officialsite/oauth2/AccessTokenCacheService.java @@ -0,0 +1,24 @@ +package com.dl.officialsite.oauth2; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import java.util.concurrent.TimeUnit; + +public class AccessTokenCacheService { + private static final Cache ACCESS_TOKEN_CACHE = CacheBuilder.newBuilder() + .expireAfterWrite(6, TimeUnit.MINUTES) + .build(); + + public static void addGitHubAccessToken(String username, String accessToken){ + ACCESS_TOKEN_CACHE.put(gitHubAccessTokenKey(username),accessToken); + } + public static String getGitHubAccessToken(String username){ + return ACCESS_TOKEN_CACHE.getIfPresent(gitHubAccessTokenKey(username)); + } + + private static String gitHubAccessTokenKey(String username){ + return String.format("%s_GITHUB_ACCESS_TOKEN", username); + } + +} \ No newline at end of file diff --git a/src/main/java/com/dl/officialsite/oauth2/controller/OAuthProcessController.java b/src/main/java/com/dl/officialsite/oauth2/controller/OAuthProcessController.java index 8bc7a7ab..c198990b 100644 --- a/src/main/java/com/dl/officialsite/oauth2/controller/OAuthProcessController.java +++ b/src/main/java/com/dl/officialsite/oauth2/controller/OAuthProcessController.java @@ -4,6 +4,7 @@ import com.dl.officialsite.common.base.BaseResponse; import com.dl.officialsite.common.utils.HttpSessionUtils; import com.dl.officialsite.common.utils.UserSecurityUtils; +import com.dl.officialsite.oauth2.AccessTokenCacheService; import com.dl.officialsite.oauth2.config.OAuthConfig; import com.dl.officialsite.oauth2.config.OAuthSessionKey; import com.dl.officialsite.oauth2.config.RegistrationConfig; @@ -29,6 +30,7 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @@ -175,6 +177,8 @@ public BaseResponse receiveAuthorizationCode( IUserInfo userInfo = retrieveHandler.retrieve(registration.getUserInfoUri(), accessToken); log.info("user info {}", userInfo.getUsername()); Assert.notNull(userInfo, "failed to find userInfo"); + // add access token to cache + AccessTokenCacheService.addGitHubAccessToken(userInfo.getUsername(), accessToken); HttpSessionUtils.setOAuthUserName(request.getSession(), OAuthSessionKey.GITHUB_USER_NAME, userInfo.getUsername()); /** * 4. Bind userInfo @@ -185,9 +189,9 @@ public BaseResponse receiveAuthorizationCode( // // bindHandler.bind(UserSecurityUtils.getUserLogin().getAddress(), userInfo); // -// response.addCookie(new Cookie("oauth_"+registrationId, userInfo.getUsername())); -// return BaseResponse.successWithData(userInfo.getUsername()); - return BaseResponse.successWithData(accessToken); + response.addCookie(new Cookie("oauth_"+registrationId, userInfo.getUsername())); + return BaseResponse.successWithData(userInfo.getUsername()); +// return BaseResponse.successWithData(accessToken); } @GetMapping("username/{registrationId}") diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 05986397..e16bf798 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -104,6 +104,12 @@ oauth: clientId: "${TWITTER_CLIENT_KEY}" clientSecret: "${TWITTER_CLIENT_SECRET}" callbackUrl: "${TWITTER_CALLBACK_URL}" + telegram: + clientSecret: "${TG_BOT_TOKEN}" + discord: + clientId: "${DISCORD_CHANNEL_MANAGEMENT_CLIENT_ID}" + clientSecret: "${DISCORD_CHANNEL_MANAGEMENT_CLIENT_SECRET}" + callbackUrl: "${DISCORD_CHANNEL_MANAGEMENT_CALLBACK_URL}" qcloud: #初始化用户身份信息 前往控制台密钥管理查看 @@ -115,7 +121,7 @@ qcloud: regionName: ${REGION_NAME} chain: - ids: ["11155111", "10", "534352", "42161", "324", "1101", "59144"] + ids: [ "11155111", "10", "534352", "42161", "324", "1101", "59144" ] login: filter: ${LOGIN_FILTER:true} @@ -156,3 +162,19 @@ debank: userTokenList: https://pro-openapi.debank.com/v1/user/all_token_list totalBalance: https://pro-openapi.debank.com/v1/user/total_balance key: + +activity: + name: THIRD_ANNIVERSARY_EVENT + taskMap: + GitHub: + - name: Star the official GitHub Repository + target: Dapp-Learning-DAO/dapp-learning + targetUrl: https://github.com/Dapp-Learning-DAO/dapp-learning + Telegram: + - name: Join official Telegram channel + target: ${TG_DAPP_LEARNING_GROUP_ID} + targetUrl: ${TELEGRAM_INVITE_URL} + Discord: + - name: Join official Discord channel + target: ${GUILD_DAPP_LEARNING_ID} + targetUrl: ${DISCORD_INVITE_URL}