Commit be52c207 authored by 李超鹏's avatar 李超鹏

登录,登录白名单

parent 260bde3d
This diff is collapsed.
package cc.shenjiying.ai.common.dto;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class AuthInfo {
private String token;
private String nickname;
private Long id;
private String account;
private String mobile;
private String channel;
private String channelUserId;
}
package cc.shenjiying.ai.common.dto;
import java.util.HashMap;
import java.util.Map;
public class R<T> {
public static final String DEF_ERROR_MESSAGE = "系统繁忙,请稍候再试";
public static final String VALID_ERROR_MESSAGE = "参数异常";
public static final int SUCCESS_CODE = 0;
public static final int FAIL_CODE = -1;
private int code;
private T data;
private String msg = "ok";
private Map<Object, Object> extra;
private R() {
}
public R(int code, T data) {
this.code = code;
this.data = data;
}
public R(int code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
public static <E> R<E> result(int code, E data, String msg) {
return new R<>(code, data, msg);
}
public static <E> R<E> success(E data) {
return new R<>(SUCCESS_CODE, data);
}
public static R<Boolean> success() {
return new R<>(SUCCESS_CODE, true);
}
public static <E> R<E> success(E data, String msg) {
return new R<>(SUCCESS_CODE, data, msg);
}
public static <E> R<E> fail(int code, String msg) {
return new R<>(code, null, (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg);
}
public static <E> R<E> fail(String msg) {
return fail(FAIL_CODE, msg);
}
public static <E> R<E> fail(String msg, Object... args) {
String message = (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg;
return new R<>(FAIL_CODE, null, String.format(message, args));
}
public static <E> R<E> fail(Throwable throwable) {
String msg = throwable != null ? throwable.getMessage() : DEF_ERROR_MESSAGE;
return fail(FAIL_CODE, msg);
}
public static <E> R<E> validFail() {
return new R<>(FAIL_CODE, null, VALID_ERROR_MESSAGE);
}
public R<T> put(String key, Object value) {
if (this.extra == null) {
this.extra = new HashMap<>(16);
}
this.extra.put(key, value);
return this;
}
public R<T> putAll(Map<Object, Object> extra) {
if (this.extra == null) {
this.extra = new HashMap<>(16);
}
this.extra.putAll(extra);
return this;
}
/**
* 逻辑处理是否成功
*
* @return 是否成功
*/
public Boolean getIsSuccess() {
return this.code == SUCCESS_CODE || this.code == 200;
}
public int getCode() {
return code;
}
public T getData() {
return data;
}
public String getMsg() {
return msg;
}
public Map<Object, Object> getExtra() {
return extra;
}
}
package cc.shenjiying.ai.config; package cc.shenjiying.ai.config;
import cc.shenjiying.ai.common.dto.AuthInfo;
import cc.shenjiying.ai.repository.entity.Questioner; import cc.shenjiying.ai.repository.entity.Questioner;
import io.smallrye.common.vertx.ContextLocals; import io.smallrye.common.vertx.ContextLocals;
...@@ -13,11 +14,13 @@ public class QuestionerHolder { ...@@ -13,11 +14,13 @@ public class QuestionerHolder {
public static final String FROM_CHANNEL_KEY = "channel"; public static final String FROM_CHANNEL_KEY = "channel";
public static final String TOKEN_KEY = "token";
public static Long getId() { public static Long getId() {
return QuestionerHolder.get(QUESTIONER_KEY).map(Questioner::getId).orElse(0L); return QuestionerHolder.get(QUESTIONER_KEY).map(AuthInfo::getId).orElse(0L);
} }
public static Optional<Questioner> get(String key) { public static Optional<AuthInfo> get(String key) {
return ContextLocals.get(key); return ContextLocals.get(key);
} }
...@@ -27,7 +30,7 @@ public class QuestionerHolder { ...@@ -27,7 +30,7 @@ public class QuestionerHolder {
ContextLocals.put(FROM_CHANNEL_KEY, channel); ContextLocals.put(FROM_CHANNEL_KEY, channel);
} }
public static void putQuestioner(Questioner questioner) { public static void putQuestioner(AuthInfo questioner) {
if (Objects.isNull(questioner)) if (Objects.isNull(questioner))
return; return;
ContextLocals.put(QUESTIONER_KEY, questioner); ContextLocals.put(QUESTIONER_KEY, questioner);
......
package cc.shenjiying.ai.config.filters;
import io.quarkus.logging.Log;
import jakarta.ws.rs.container.ContainerRequestContext;
import org.jboss.resteasy.reactive.RestResponse;
import java.util.Optional;
// Response/RestResponse Uni<Response/Response> Optional
// 返回 null or empty 代表继续执行链,其他则返回给客户端
public class AnnotateFilter {
// @ServerRequestFilter(priority = 2000)
public void test(ContainerRequestContext context) {
if (null == context.getHeaderString("token")) {
Log.info("not has token");
}
}
// @ServerRequestFilter
public Optional<RestResponse<Void>> getFilter(ContainerRequestContext ctx) {
// only allow GET methods for now
// if(ctx.getMethod().equals(HttpMethod.GET)) {
// return Optional.of(RestResponse.status(Response.Status.METHOD_NOT_ALLOWED));
// }
return Optional.empty();
}
}
package cc.shenjiying.ai.config.filters; package cc.shenjiying.ai.config.filters;
import cc.shenjiying.ai.util.IpUtils; import cc.shenjiying.ai.util.IpUtils;
import io.smallrye.common.vertx.ContextLocals;
import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerRequest;
import io.vertx.mutiny.core.Vertx;
import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext; import jakarta.ws.rs.container.ContainerResponseContext;
......
package cc.shenjiying.ai.config.filters; package cc.shenjiying.ai.config.filters;
import cc.shenjiying.ai.config.QuestionerHolder; import cc.shenjiying.ai.config.QuestionerHolder;
import cc.shenjiying.ai.repository.entity.Question;
import cc.shenjiying.ai.repository.entity.Questioner; import cc.shenjiying.ai.repository.entity.Questioner;
import cc.shenjiying.ai.repository.mapper.QuestionMapper; import cc.shenjiying.ai.manager.QuestionerService;
import cc.shenjiying.ai.repository.mapper.QuestionerMapper;
import cc.shenjiying.ai.repository.service.QuestionerService;
import cc.shenjiying.ai.util.IpUtils; import cc.shenjiying.ai.util.IpUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import cc.shenjiying.ai.util.TokenUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.quarkus.runtime.util.StringUtil; import io.quarkus.runtime.util.StringUtil;
import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerRequest;
import jakarta.inject.Inject; import jakarta.inject.Inject;
...@@ -22,20 +18,16 @@ import jakarta.ws.rs.ext.Provider; ...@@ -22,20 +18,16 @@ import jakarta.ws.rs.ext.Provider;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.sql.Wrapper;
import java.util.Objects; import java.util.Objects;
import static cc.shenjiying.ai.config.QuestionerHolder.FROM_CHANNEL_KEY; import static cc.shenjiying.ai.config.QuestionerHolder.FROM_CHANNEL_KEY;
import static cc.shenjiying.ai.config.QuestionerHolder.QUESTIONER_KEY; import static cc.shenjiying.ai.config.QuestionerHolder.TOKEN_KEY;
/** /**
* 日志拦截器 * 日志拦截器
*/ */
@Provider //@Provider
public class QuestionerFilter implements ContainerRequestFilter, ContainerResponseFilter { public class QuestionerFilter implements ContainerRequestFilter, ContainerResponseFilter {
private static final Logger LOG = LoggerFactory.getLogger(QuestionerFilter.class);
@Context
UriInfo uriInfo;
@Context @Context
HttpServerRequest request; HttpServerRequest request;
@Inject @Inject
...@@ -46,6 +38,10 @@ public class QuestionerFilter implements ContainerRequestFilter, ContainerRespon ...@@ -46,6 +38,10 @@ public class QuestionerFilter implements ContainerRequestFilter, ContainerRespon
String ipAddr = IpUtils.getIpAddr(request); String ipAddr = IpUtils.getIpAddr(request);
String channel = requestContext.getHeaderString(FROM_CHANNEL_KEY); String channel = requestContext.getHeaderString(FROM_CHANNEL_KEY);
channel = StringUtil.isNullOrEmpty(channel) ? "qs" : ""; channel = StringUtil.isNullOrEmpty(channel) ? "qs" : "";
String token = requestContext.getHeaderString(TOKEN_KEY);
if(StringUtil.isNullOrEmpty(token)){
}
Questioner questioner = questionerService.getByChannel(channel, ipAddr); Questioner questioner = questionerService.getByChannel(channel, ipAddr);
if (Objects.isNull(questioner)) { if (Objects.isNull(questioner)) {
try { try {
...@@ -54,7 +50,7 @@ public class QuestionerFilter implements ContainerRequestFilter, ContainerRespon ...@@ -54,7 +50,7 @@ public class QuestionerFilter implements ContainerRequestFilter, ContainerRespon
questioner = questionerService.getByChannel(channel, ipAddr); questioner = questionerService.getByChannel(channel, ipAddr);
} }
} }
QuestionerHolder.putQuestioner(questioner); // QuestionerHolder.putQuestioner(questioner);
QuestionerHolder.putChannel(channel); QuestionerHolder.putChannel(channel);
} }
......
package cc.shenjiying.ai.config.filters;
import cc.shenjiying.ai.common.dto.AuthInfo;
import cc.shenjiying.ai.common.dto.R;
import cc.shenjiying.ai.config.QuestionerHolder;
import cc.shenjiying.ai.manager.QuestionerService;
import cc.shenjiying.ai.repository.entity.Questioner;
import cc.shenjiying.ai.util.IpUtils;
import cc.shenjiying.ai.util.TokenUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.logging.Log;
import io.quarkus.runtime.util.StringUtil;
import io.smallrye.jwt.auth.principal.JWTParser;
import io.vertx.core.http.HttpServerRequest;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Request;
import jakarta.ws.rs.core.Response;
import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.server.ServerRequestFilter;
import org.jboss.resteasy.reactive.server.ServerResponseFilter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static cc.shenjiying.ai.config.QuestionerHolder.FROM_CHANNEL_KEY;
import static cc.shenjiying.ai.config.QuestionerHolder.TOKEN_KEY;
// Response/RestResponse Uni<Response/Response> Optional
// 返回 null or empty 代表继续执行链,其他则返回给客户端
@ApplicationScoped
public class TokenFilter {
@Inject
JWTParser jwtParser;
@Inject
ObjectMapper objectMapper;
private List<String> ignoreUrl = Arrays.asList("/questioner/login", "/questioner/login/code");
private boolean isIgnore(String url) {
return ignoreUrl.contains(url);
}
@ServerRequestFilter(priority = 2000)
public RestResponse<String> request(ContainerRequestContext context, @Context HttpServerRequest request) throws JsonProcessingException {
String channel = context.getHeaderString(FROM_CHANNEL_KEY);
channel = StringUtil.isNullOrEmpty(channel) ? "qs" : "";
String token = context.getHeaderString(TOKEN_KEY);
if (!isIgnore(context.getUriInfo().getPath()) && StringUtil.isNullOrEmpty(token)) {
return RestResponse.status(Response.Status.FORBIDDEN, objectMapper.writeValueAsString(R.fail("您需要登录后才能进行此操作")));
}
AuthInfo parse = TokenUtil.parse(jwtParser, token);
QuestionerHolder.putQuestioner(parse);
QuestionerHolder.putChannel(channel);
return null;
}
@ServerResponseFilter(priority = 2000)
public void response() {
QuestionerHolder.remove();
}
//// @ServerRequestFilter
// public Optional<RestResponse<Void>> getFilter(ContainerRequestContext ctx) {
// // only allow GET methods for now
//// if(ctx.getMethod().equals(HttpMethod.GET)) {
//// return Optional.of(RestResponse.status(Response.Status.METHOD_NOT_ALLOWED));
//// }
// return Optional.empty();
// }
}
...@@ -10,9 +10,7 @@ import jakarta.ws.rs.ext.*; ...@@ -10,9 +10,7 @@ import jakarta.ws.rs.ext.*;
import java.io.IOException; import java.io.IOException;
//@Provider
@Priority(Interceptor.Priority.APPLICATION + 1) @Priority(Interceptor.Priority.APPLICATION + 1)
//@ConstrainedTo(RuntimeType.SERVER)
public class TestWriterInterceptor implements WriterInterceptor, ReaderInterceptor { public class TestWriterInterceptor implements WriterInterceptor, ReaderInterceptor {
@Inject @Inject
......
package cc.shenjiying.ai.dto;
import lombok.Data;
@Data
public class QuestionerLoginDTO {
private String account;
private String password;
}
package cc.shenjiying.ai.repository.service; package cc.shenjiying.ai.manager;
import cc.shenjiying.ai.common.dto.AuthInfo;
import cc.shenjiying.ai.common.dto.R;
import cc.shenjiying.ai.repository.entity.Questioner; import cc.shenjiying.ai.repository.entity.Questioner;
import cc.shenjiying.ai.repository.entity.QuestionerWhiteList;
import cc.shenjiying.ai.repository.mapper.QuestionerMapper; import cc.shenjiying.ai.repository.mapper.QuestionerMapper;
import cc.shenjiying.ai.repository.mapper.QuestionerWhiteListMapper;
import cc.shenjiying.ai.util.TokenUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.quarkus.runtime.util.StringUtil;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Singleton; import jakarta.inject.Singleton;
import org.jboss.resteasy.reactive.RestQuery;
import java.util.Objects; import java.util.Objects;
...@@ -15,6 +22,12 @@ public class QuestionerService { ...@@ -15,6 +22,12 @@ public class QuestionerService {
@Inject @Inject
QuestionerMapper questionerMapper; QuestionerMapper questionerMapper;
@Inject
SmsCodeService smsCodeService;
@Inject
QuestionerWhiteListMapper questionerWhiteListMapper;
public Questioner getQuestioner(Long userId) { public Questioner getQuestioner(Long userId) {
return questionerMapper.selectById(userId); return questionerMapper.selectById(userId);
} }
...@@ -31,4 +44,33 @@ public class QuestionerService { ...@@ -31,4 +44,33 @@ public class QuestionerService {
} }
return questionerMapper.insert(questioner); return questionerMapper.insert(questioner);
} }
public R<Boolean> getLoginCode(@RestQuery String mobile) {
if (StringUtil.isNullOrEmpty(mobile)) {
return R.validFail();
}
Long count = questionerWhiteListMapper.selectCount(new LambdaQueryWrapper<QuestionerWhiteList>().eq(QuestionerWhiteList::getAccount, mobile));
if (count > 0) {
smsCodeService.send(mobile);
return R.success(Boolean.TRUE);
}
return R.fail("尊敬的用户,感谢您对我们的产品感兴趣。但请注意,目前该产品仍处于内测阶段,仅对特定用户开放");
}
public R<String> getLoginToken(String mobile, String code) {
if (StringUtil.isNullOrEmpty(mobile) || StringUtil.isNullOrEmpty(code)) {
return R.validFail();
}
if (smsCodeService.check(mobile, code)) {
Questioner questioner = questionerMapper.selectOne(new LambdaQueryWrapper<Questioner>().eq(Questioner::getAccount, mobile).last("limit 1"));
if (Objects.isNull(questioner)) {
return R.fail("用户名或密码错误,请重新输入");
}
String token = TokenUtil.create(new AuthInfo().setId(questioner.getId()).setAccount(questioner.getAccount())
.setMobile(questioner.getMobile()).setChannel(questioner.getChannel()).setChannelUserId(questioner.getChannelUserId()));
return R.success(token);
}
return R.fail("验证码错误或已过期,请重新获取验证码后再试");
}
} }
package cc.shenjiying.ai.manager;
import cc.shenjiying.ai.repository.entity.SmsSendLog;
import cc.shenjiying.ai.repository.mapper.SmsSendLogMapper;
import cc.shenjiying.ai.util.RandomUtil;
import cc.shenjiying.redis.RedisCache;
import cc.shenjiying.redis.key.MobileCodeKey;
import cc.shenjiying.sms.AliSmsSender;
import cc.shenjiying.sms.TencentSmsSender;
import cc.shenjiying.sms.dto.SmsSendResult;
import cc.shenjiying.sms.param.SmsSendParam;
import io.quarkus.runtime.util.StringUtil;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
/**
* 验证码服务
*/
@Slf4j
@ApplicationScoped
public class SmsCodeService {
@Inject
AliSmsSender smsSender;
@Inject
RedisCache redisCache;
@Inject
SmsSendLogMapper logMapper;
public void send(String mobile) {
String code = RandomUtil.generateNumber(4);
redisCache.setValue(new MobileCodeKey().key(mobile), code, 5 * 60);
SmsSendResult sendResult = smsSender.send(SmsSendParam.of(mobile, "扳手快查", "SMS_130170012", Map.of("code", code)));
logMapper.insert(new SmsSendLog().setProviderType(sendResult.getProviderType())
.setMobile(sendResult.getMobile()).setParams(sendResult.getParams())
.setResult(sendResult.isResult()).setMessage(sendResult.getMessage()));
}
public boolean check(String mobile, String code) {
if (StringUtil.isNullOrEmpty(mobile) || StringUtil.isNullOrEmpty(code)) {
return false;
}
String value = redisCache.getValue(new MobileCodeKey().key(mobile));
return code.equals(value);
}
}
...@@ -17,6 +17,12 @@ public class Questioner { ...@@ -17,6 +17,12 @@ public class Questioner {
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
private Long id; private Long id;
@TableField(value = "account")
private String account;
@TableField(value = "mobile")
private String mobile;
@TableField(value = "channel") @TableField(value = "channel")
private String channel; private String channel;
......
package cc.shenjiying.ai.repository.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@TableName("questioner_white_list")
@Accessors(chain = true)
public class QuestionerWhiteList {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(value = "account")
private String account;
@TableField("create_time")
private LocalDateTime createTime;
}
package cc.shenjiying.ai.repository.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@TableName("sms_send_log")
@Accessors(chain = true)
public class SmsSendLog {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(value = "provider_type")
private String providerType;
@TableField(value = "mobile")
private String mobile;
@TableField(value = "params")
private String params;
@TableField("result")
private boolean result;
@TableField("message")
private String message;
@TableField("create_time")
private LocalDateTime createTime;
}
package cc.shenjiying.ai.repository.mapper;
import cc.shenjiying.ai.repository.entity.QuestionerWhiteList;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface QuestionerWhiteListMapper extends BaseMapper<QuestionerWhiteList> {
}
package cc.shenjiying.ai.repository.mapper;
import cc.shenjiying.ai.repository.entity.Question;
import cc.shenjiying.ai.repository.entity.SmsSendLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SmsSendLogMapper extends BaseMapper<SmsSendLog> {
}
package cc.shenjiying.ai.resource; package cc.shenjiying.ai.resource;
import cc.shenjiying.ai.config.QuestionerHolder;
import cc.shenjiying.ai.dto.ChatAIResponse; import cc.shenjiying.ai.dto.ChatAIResponse;
import cc.shenjiying.ai.dto.FeedbackSaveDTO; import cc.shenjiying.ai.dto.FeedbackSaveDTO;
import cc.shenjiying.ai.service.KnowledgeService; import cc.shenjiying.ai.service.KnowledgeService;
...@@ -10,10 +11,8 @@ import jakarta.inject.Inject; ...@@ -10,10 +11,8 @@ import jakarta.inject.Inject;
import jakarta.ws.rs.*; import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.headers.Header; import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.annotations.Producer;
import org.jboss.resteasy.reactive.RestPath; import org.jboss.resteasy.reactive.RestPath;
import org.jboss.resteasy.reactive.RestStreamElementType; import org.jboss.resteasy.reactive.RestStreamElementType;
...@@ -23,9 +22,14 @@ public class KnowLedgeResource { ...@@ -23,9 +22,14 @@ public class KnowLedgeResource {
@Inject @Inject
KnowledgeService knowledgeService; KnowledgeService knowledgeService;
@HeaderParam(QuestionerHolder.TOKEN_KEY)
@Parameter(name = QuestionerHolder.TOKEN_KEY,description = "登录返回的token")
String myHeaderId;
@GET @GET
@Path("/stream") @Path("/stream")
@Operation(summary = "流式请求") @Operation(summary = "流式请求")
@Parameter(name = QuestionerHolder.TOKEN_KEY,description = "登录返回的token")
@Produces(MediaType.SERVER_SENT_EVENTS) @Produces(MediaType.SERVER_SENT_EVENTS)
@RestStreamElementType(MediaType.APPLICATION_JSON) @RestStreamElementType(MediaType.APPLICATION_JSON)
@RateLimited(bucket = "chat") @RateLimited(bucket = "chat")
...@@ -54,8 +58,8 @@ public class KnowLedgeResource { ...@@ -54,8 +58,8 @@ public class KnowLedgeResource {
@Operation(summary = "反馈") @Operation(summary = "反馈")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Multi<Long> feedback(FeedbackSaveDTO feedback) { public Uni<Long> feedback(FeedbackSaveDTO feedback) {
return Multi.createFrom().item(() -> knowledgeService.newFeedback(feedback)); return Uni.createFrom().item(() -> knowledgeService.newFeedback(feedback));
} }
} }
package cc.shenjiying.ai.resource;
import cc.shenjiying.ai.common.dto.R;
import cc.shenjiying.ai.dto.QuestionerLoginDTO;
import cc.shenjiying.ai.manager.QuestionerService;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.resteasy.reactive.RestQuery;
@Path("/questioner")
@Tag(name = "用户")
public class QuestionerResource {
@Inject
QuestionerService questionerService;
@Inject
JsonWebToken jwt;
@GET
@Path("/login/code")
@Operation(summary = "发送验证码")
public R<Boolean> getLoginCode(@RestQuery String mobile) {
return questionerService.getLoginCode(mobile);
}
@POST
@Path("/login")
@Operation(summary = "手机号登录")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public R<String> login(QuestionerLoginDTO loginDTO) {
return questionerService.getLoginToken(loginDTO.getAccount(), loginDTO.getPassword());
}
}
package cc.shenjiying.ai.util;
import java.security.SecureRandom;
public class RandomUtil {
private static SecureRandom random = new SecureRandom();
public static String generateNumber(int count) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
sb.append(random.nextInt(10));
}
return sb.toString();
}
}
package cc.shenjiying.ai.util;
import cc.shenjiying.ai.common.dto.AuthInfo;
import io.quarkus.runtime.util.StringUtil;
import io.smallrye.jwt.auth.principal.DefaultJWTParser;
import io.smallrye.jwt.auth.principal.JWTAuthContextInfo;
import io.smallrye.jwt.auth.principal.JWTParser;
import io.smallrye.jwt.auth.principal.ParseException;
import io.smallrye.jwt.build.Jwt;
import io.smallrye.jwt.build.impl.JwtBuildUtils;
import io.smallrye.jwt.util.KeyUtils;
import io.smallrye.jwt.util.ResourceUtils;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
public class TokenUtil {
private static final String USER_ID = "userId";
private static final String ACCOUNT = "account";
private static final String NICKNAME = "nickName";
private static final String MOBILE = "mobile";
private static final String CHANNEL = "channel";
private static final String CHANNEL_USER_ID = "channelUserId";
public static String create(AuthInfo info) {
return Jwt.claim(USER_ID, info.getId())
.claim(ACCOUNT, info.getAccount())
// .claim(NICKNAME, info.getNickname())
.claim(MOBILE, info.getMobile())
.claim(CHANNEL, info.getChannel())
.claim(CHANNEL_USER_ID, info.getChannelUserId())
.sign();
}
public static AuthInfo parse(JWTParser jwtParser, String token) {
if (StringUtil.isNullOrEmpty(token)) {
return null;
}
try {
String keyLocation = ConfigProvider.getConfig().getOptionalValue(JwtBuildUtils.ENC_KEY_LOCATION_PROPERTY, String.class).orElse("");
String issuedBy = ConfigProvider.getConfig().getOptionalValue(JwtBuildUtils.NEW_TOKEN_ISSUER_PROPERTY, String.class).orElse("");
JWTAuthContextInfo authContextInfo = new JWTAuthContextInfo(keyLocation, issuedBy);
authContextInfo.setRequireNamedPrincipal(false);
JsonWebToken webToken = jwtParser.parse(token,authContextInfo);
AuthInfo authInfo = new AuthInfo();
authInfo.setId(Long.valueOf(webToken.getClaim(USER_ID).toString()))
.setAccount(webToken.getClaim(ACCOUNT))
.setNickname(webToken.getClaim(NICKNAME))
.setMobile(webToken.getClaim(MOBILE))
.setChannel(webToken.getClaim(CHANNEL))
.setChannelUserId(webToken.getClaim(CHANNEL_USER_ID));
return authInfo;
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
package cc.shenjiying.redis;
import cc.shenjiying.redis.key.RedisCacheKey;
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.value.ValueCommands;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class RedisCache {
ValueCommands<String, String> valueCommands;
public RedisCache(RedisDataSource ds) {
valueCommands = ds.value(String.class);
}
public <T> void setValue(RedisCacheKey key, String value, long time) {
valueCommands.setex(key.getKey(), time, value);
}
public void setNxValue(RedisCacheKey key, String value) {
valueCommands.setnx(key.getKey(), value);
}
public String getValue(RedisCacheKey key) {
return valueCommands.get(key.getKey());
}
public String delete(RedisCacheKey key) {
return valueCommands.getdel(key.getKey());
}
}
package cc.shenjiying.redis.key;
public class MobileCodeKey extends RedisCacheKey {
@Override
public String biz() {
return "mobile_code";
}
}
package cc.shenjiying.redis.key;
import cc.shenjiying.redis.RedisCache;
public abstract class RedisCacheKey {
private String key;
String project() {
return "ai_agent";
}
public abstract String biz();
public RedisCacheKey key(String key) {
this.key = key;
return this;
}
public String getKey() {
return project() + ":" + biz() + ":" + this.key;
}
}
package cc.shenjiying.sms;
import cc.shenjiying.sms.dto.SmsSendResult;
import cc.shenjiying.sms.param.SmsSendParam;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class AliSmsSender {
private static final String DOMAIN = "dysmsapi.aliyuncs.com";
private final String appId = "LTAIAXZdcDQgzEt9";
private final String secret = "nlKCSemDT7fejXwdny9ebXBvd2qaS1";
@Inject
ObjectMapper objectMapper;
public SmsSendResult send(SmsSendParam param) {
SmsSendResult.SmsSendResultBuilder resultBuilder = SmsSendResult.builder().providerType("ALI").mobile(param.getMobile());
try {
Config config = new Config().setAccessKeyId(appId).setAccessKeySecret(secret);
config.endpoint = DOMAIN;
Client client = new Client(config);
String sendParam = objectMapper.writeValueAsString(param.getParams());
SendSmsRequest sendSmsRequest = new SendSmsRequest();
//必填:待发送手机号
sendSmsRequest.setPhoneNumbers(param.getMobile());
sendSmsRequest.setSignName(param.getSignName());
sendSmsRequest.setTemplateCode(param.getTempleCode());
sendSmsRequest.setTemplateParam(sendParam);
// sendSmsRequest.setOutId(String.valueOf(smsDO.getTaskId()));
SendSmsResponse response = client.sendSmsWithOptions(sendSmsRequest, new RuntimeOptions());
return resultBuilder.params(sendParam).result("OK".equals(response.body.code)).message(response.body.message).build();
} catch (Exception _error) {
return resultBuilder.result(false).message(_error.getMessage()).build();
}
}
}
\ No newline at end of file
package cc.shenjiying.sms;
import cc.shenjiying.sms.dto.SmsSendResult;
import cc.shenjiying.sms.param.SmsSendParam;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.qcloudsms.SmsSingleSender;
import com.github.qcloudsms.SmsSingleSenderResult;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Slf4j
//@ApplicationScoped
public class TencentSmsSender {
private static final Map<String, String> ERROR_CODE_MAP = new HashMap<>(50);
static {
ERROR_CODE_MAP.put("1001", "sig 校验失败");
ERROR_CODE_MAP.put("1002", "短信/语音内容中含有敏感词");
ERROR_CODE_MAP.put("1003", "请求包体没有 sig 字段或 sig 为空");
ERROR_CODE_MAP.put("1004", "请求包解析失败,通常情况下是由于没有遵守 API 接口说明规范导致的");
ERROR_CODE_MAP.put("1006", "请求没有权限");
ERROR_CODE_MAP.put("1007", "其他错误");
ERROR_CODE_MAP.put("1008", "请求下发短信/语音超时");
ERROR_CODE_MAP.put("1009", "请求 IP 不在白名单中");
ERROR_CODE_MAP.put("1011", "不存在该 REST API 接口");
ERROR_CODE_MAP.put("1012", "签名格式错误或者签名未审批");
ERROR_CODE_MAP.put("1013", "下发短信/语音命中了频率限制策略");
ERROR_CODE_MAP.put("1014", "模版未审批或请求的内容与审核通过的模版内容不匹配");
ERROR_CODE_MAP.put("1015", "手机号在阻止列表库中,通常是用户退订或者命中运营商阻止列表导致的");
ERROR_CODE_MAP.put("1016", "手机号格式错误");
ERROR_CODE_MAP.put("1017", "请求的短信内容太长");
ERROR_CODE_MAP.put("1018", "语音验证码格式错误");
ERROR_CODE_MAP.put("1019", "sdk appId 不存在");
ERROR_CODE_MAP.put("1020", "sdk appId 已禁用");
ERROR_CODE_MAP.put("1021", "请求发起时间不正常,通常是由于您的服务器时间与腾讯云服务器时间差异超过10分钟导致的");
ERROR_CODE_MAP.put("1022", "业务短信日下发条数超过设定的上限");
ERROR_CODE_MAP.put("1023", "单个手机号30秒内下发短信条数超过设定的上限");
ERROR_CODE_MAP.put("1024", "单个手机号1小时内下发短信条数超过设定的上限");
ERROR_CODE_MAP.put("1025", "单个手机号日下发短信条数超过设定的上限");
ERROR_CODE_MAP.put("1026", "单个手机号下发相同内容超过设定的上限");
ERROR_CODE_MAP.put("1029", "营销短信发送时间限制");
ERROR_CODE_MAP.put("1030", "不支持该请求");
ERROR_CODE_MAP.put("1031", "套餐包余量不足");
ERROR_CODE_MAP.put("1032", "个人用户没有发营销短信的权限");
ERROR_CODE_MAP.put("1033", "欠费被停止服务");
ERROR_CODE_MAP.put("1034", "群发请求里既有国内手机号也有国际手机号");
ERROR_CODE_MAP.put("1036", "单个模板变量字符数超过12个");
ERROR_CODE_MAP.put("1045", "不支持该地区短信下发");
ERROR_CODE_MAP.put("1046", "调用群发 API 接口单次提交的手机号个数超过200个");
ERROR_CODE_MAP.put("1047", "国际短信日下发条数被限制");
ERROR_CODE_MAP.put("60008", "处理请求超时");
}
private final int appId = 1400623773;
private final String secret = "3e566d11db636382dd498c6a01aced03";
@Inject
ObjectMapper objectMapper;
public TencentSmsSender() {
}
public SmsSendResult send(SmsSendParam param) {
SmsSendResult.SmsSendResultBuilder resultBuilder = SmsSendResult.builder().providerType("TENCENT").mobile(param.getMobile());
try {
resultBuilder.params(objectMapper.writeValueAsString(param.getParams()));
} catch (Exception e) {
log.warn("序列化错误", e);
}
try {
//初始化单发
SmsSingleSender singleSender = new SmsSingleSender(appId, secret);
Map<String, String> sendParam = param.getParams();
ArrayList<String> paramList = new ArrayList<>();
if (Objects.nonNull(sendParam)) {
for (Map.Entry<String, String> val : sendParam.entrySet()) {
paramList.add(val.getValue());
}
}
SmsSingleSenderResult singleSenderResult = singleSender.sendWithParam("86", param.getMobile(),
Integer.parseInt(param.getTempleCode()), paramList, param.getSignName(), "", "");
return resultBuilder.result(0 == singleSenderResult.result).build();
} catch (Exception e) {
return resultBuilder.result(false).message(e.getMessage()).build();
}
}
}
package cc.shenjiying.sms.dto;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Builder
@Accessors(chain = true)
public class SmsSendResult {
private String providerType;
private String mobile;
private String params;
private boolean result;
private String message;
}
package cc.shenjiying.sms.param;
import lombok.Data;
import java.util.Map;
@Data
public class SmsSendParam {
private String mobile;
private String templeCode;
private String signName;
private Map<String, String> params;
public static SmsSendParam of(String mobile, String signName, String templeCode, Map<String, String> sendParam) {
SmsSendParam param = new SmsSendParam();
param.mobile = mobile;
param.signName = signName;
param.templeCode = templeCode;
param.params = sendParam;
return param;
}
}
...@@ -8,9 +8,12 @@ quarkus.datasource.jdbc.url=jdbc:mysql://172.16.16.3:3306/ai_chat ...@@ -8,9 +8,12 @@ quarkus.datasource.jdbc.url=jdbc:mysql://172.16.16.3:3306/ai_chat
quarkus.mybatis.map-underscore-to-camel-case=true quarkus.mybatis.map-underscore-to-camel-case=true
quarkus.log.file.enable=true quarkus.log.file.enable=true
quarkus.log.file.level=WARE quarkus.log.file.level=WARN
quarkus.log.file.path=/weixiuhui/logStore/ai-chat/application.log quarkus.log.file.path=/weixiuhui/logStore/ai-chat/application.log
quarkus.log.file.format=%d{yyyy-MM-dd HH:mm:ss} %-5p [%c] (%t) %s%e%n quarkus.log.file.format=%d{yyyy-MM-dd HH:mm:ss} %-5p [%c] (%t) %s%e%n
quarkus.log.file.async=true quarkus.log.file.async=true
quarkus.redis.hosts=redis://172.16.16.21:6379/
#quarkus.redis.password=crs-duci75tn:Weixiuhui2014!
...@@ -15,3 +15,5 @@ quarkus.log.file.path=/weixiuhui/logStore/ai-chat/application.log ...@@ -15,3 +15,5 @@ quarkus.log.file.path=/weixiuhui/logStore/ai-chat/application.log
quarkus.log.file.format=%d{yyyy-MM-dd HH:mm:ss} %-5p [%c] (%t) %s%e%n quarkus.log.file.format=%d{yyyy-MM-dd HH:mm:ss} %-5p [%c] (%t) %s%e%n
quarkus.log.file.async=true quarkus.log.file.async=true
quarkus.redis.hosts=redis://127.0.0.1:6379/
#quarkus.redis.password=
\ No newline at end of file
...@@ -49,7 +49,8 @@ quarkus.mybatis.map-underscore-to-camel-case=true ...@@ -49,7 +49,8 @@ quarkus.mybatis.map-underscore-to-camel-case=true
#quarkus.mybatis.environment=development #quarkus.mybatis.environment=development
#quarkus.mybatis.mapper-locations=mapper #quarkus.mybatis.mapper-locations=mapper
#Redis\u914D\u7F6E #Redis\u914D\u7F6E
quarkus.redis.hosts=redis://192.168.1.24:3306/ quarkus.redis.hosts=redis://192.168.1.24:6379/
#quarkus.redis.password=
#Reids\u96C6\u7FA4\u6A21\u5F0F #Reids\u96C6\u7FA4\u6A21\u5F0F
#quarkus.redis.hosts=redis://localhost:7000,redis://localhost:7001,redis://localhost:7002 #quarkus.redis.hosts=redis://localhost:7000,redis://localhost:7001,redis://localhost:7002
#quarkus.redis.client-type=cluster #quarkus.redis.client-type=cluster
...@@ -61,3 +62,21 @@ quarkus.rate-limiter.buckets.chat.limits[0].period: 1S ...@@ -61,3 +62,21 @@ quarkus.rate-limiter.buckets.chat.limits[0].period: 1S
quarkus.rate-limiter.buckets.chat.limits[1].permitted-uses: 700 quarkus.rate-limiter.buckets.chat.limits[1].permitted-uses: 700
quarkus.rate-limiter.buckets.chat.limits[1].period: 5M quarkus.rate-limiter.buckets.chat.limits[1].period: 5M
#jwt
#\u53D1\u884C\u8005\u3001\u53D7\u4F17
smallrye.jwt.new-token.issuer=ai.kaying.cc
#smallrye.jwt.new-token.audience=
smallrye.jwt.claims.sub=ai
smallrye.jwt.require.named-principal=false
# \u8FC7\u671F\u65F6\u95F4 7\u5929
smallrye.jwt.new-token.lifespan=604800
#\u7B7E\u540D\u7B97\u6CD5
#smallrye.jwt.new-token.signature-algorithm=PS256
#smallrye.jwt.new-token.key-encryption-algorithm=RSA-OAEP-256
#smallrye.jwt.new-token.content-encryption-algorithm=A256CBC-HS512
#\u7B7E\u540D\u79C1\u94A5\u516C\u94A5
smallrye.jwt.sign.key.location=key/jwt_private.pem
smallrye.jwt.encrypt.key.location=key/jwt_public.pem
#smallrye.jwt.decrypt.key.location=key/jwt_private.pem
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCuNQEMVqODpEfj
srUUR4bTYFtaWcWutS279TZI2SGUEaVY2j8OCh8SGkB38u4faNuAL09b6utASQxt
YFi42+s9kfUu+x+BEgluQU7m25qNIWscvM/k0QzNfrToD0ncc6/4EYQuEzfYpic+
UDGrlkswLfWP0Nw7L4uQ4yNpGQGjU8p4u1wL7GIR1mfbOZ6HRHd/rIMW6Jd65Nmm
kgO70YvInS/8XREsw40T3rDMdRbQo9WsaxocZ8DqFVXSLSym+v/kwZuHCR+6C7KE
O599Fd2vwNf+yH9nD9hOgPv+kbbZBZazVUl3jPQvIg99Dhf7snoDhgKB76yIJ9Wh
pJmTrew9AgMBAAECggEAAW62vsrtlq6gpE4uMAFm9rdmbBrfePbNB/kihHA71vOO
mDYlnTA9hJDjsjjOQ9x0XzYqx5cDogH+KkR7DK+xdc5zJWFz2l3AGk1K+hn/LnIc
AKuXmApiBqr09apYHj1AF3ppZSMyUXbYTSpfcvynsWwHU2Xq+BYFgBNmEXF4f0cB
iSiuFN/n7HS27h9zGwSCysZfffR+CW6+lejqEWQDZeEMZPi5TEyBliwBcFXo4DY9
Ak+maAzNSsuW4oDi3BDQx7IDlLsVX1NRtYxUQbuP3/UgnBd2HfaK6Gupr4f7QEQv
8/Pon63y6Hixn7KFoIn0LN53QSkfKZtWAOnx9hNgEQKBgQDVfiBoJ6UZ7y5I/dln
j69ldsPjOYhc2qVZ6fqmWFk4t4VlZ2DWICSRr8W8dG+jgeH6rkwSdPBe+j6IVALL
6ZP9OsSW+KUbX9THd/HWDQ/mWkMfSoWTQcobbihpNDa9n7hlbFhKm2NOpD94QH/l
twqzbErCpm2qqn83EBwW4pRp6QKBgQDQ5HX5qUjBVi47QSTqgD53ObZJKAMsE+35
Ia4hPvK/hb28ao4tjB7GbyttsoqNPBTV1rZShU6OcxoT/g+CECqcYeE9NRmHaYPP
mymfy2mULvbTP/wjGKfM2vHFevGqS/PT9cY8AOV761FTQyMD7DYvwRAm57pJ6oh0
RCA0A9WnNQKBgQCmodsJcGgc5eJqdjfJ7QwpVZlvMOiOENoeY9GPJEX/Tr5/d0DN
4CiqzVa40OiR5j5LOt+FSGCUwR0tquTD43vumn/2lfvIcfPR/Hx1ZZyTXsAlHDVF
yoPFBSXldyuCtbdm8jm3gbDxIKGpai5pTYS8pjlz1BKkso64m4pREmhkgQKBgHcU
xew9BKyyZEiDKnhEA1gUHkY1+Rj3g4GyIYHyUI+9vhva4pI0fpbAaRFv5mv9BCOi
8uy38cBi/UrIWi7a6G+dnLk3vAXlsK541ZGyc811uF9U6ndXZtDxP0JEg9+AzM7Y
HNm36RprW0EL1Lo3vewbMwn6r8fTW1ZsP69nQDTBAoGBALlhyGCm2V1LXAUP4kPG
jmhtaXWUTiSx6+ZAOujjz2KOPK82BbyKS65laar+3+TrRZTsZMeOIo36MxgdmeKw
Wd22qn2bYOW30+yJZybgNOXPmFEMIPdONJixIItR2iGHN8OO6Nxk1owxJHM83O8v
F39rjadAJVFgdHmuM/U5FTGr
-----END PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArjUBDFajg6RH47K1FEeG
02BbWlnFrrUtu/U2SNkhlBGlWNo/DgofEhpAd/LuH2jbgC9PW+rrQEkMbWBYuNvr
PZH1LvsfgRIJbkFO5tuajSFrHLzP5NEMzX606A9J3HOv+BGELhM32KYnPlAxq5ZL
MC31j9DcOy+LkOMjaRkBo1PKeLtcC+xiEdZn2zmeh0R3f6yDFuiXeuTZppIDu9GL
yJ0v/F0RLMONE96wzHUW0KPVrGsaHGfA6hVV0i0spvr/5MGbhwkfuguyhDuffRXd
r8DX/sh/Zw/YToD7/pG22QWWs1VJd4z0LyIPfQ4X+7J6A4YCge+siCfVoaSZk63s
PQIDAQAB
-----END PUBLIC KEY-----
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment