7.11 微信公众号实现用户登录:扫二维码、回调、保存用户状态..retrofi使用
This commit is contained in:
parent
f1119a54a2
commit
95318d5686
11
pay-mall-api/src/main/java/edu/whut/api/IAuthService.java
Normal file
11
pay-mall-api/src/main/java/edu/whut/api/IAuthService.java
Normal file
@ -0,0 +1,11 @@
|
||||
package edu.whut.api;
|
||||
|
||||
import edu.whut.api.response.Response;
|
||||
|
||||
public interface IAuthService {
|
||||
|
||||
Response<String> weixinQrCodeTicket();
|
||||
|
||||
Response<String> checkLogin(String ticket);
|
||||
|
||||
}
|
@ -10,10 +10,19 @@ import java.util.concurrent.TimeUnit;
|
||||
@Configuration
|
||||
public class GuavaConfig {
|
||||
|
||||
@Bean(name = "cache")
|
||||
public Cache<String, String> cache() {
|
||||
@Bean(name = "weixinAccessToken") // 注册名为 weixinAccessToken 的缓存 Bean
|
||||
public Cache<String, String> weixinAccessToken() {
|
||||
// 创建一个 Guava 本地缓存:写入后 2 小时过期
|
||||
return CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(3, TimeUnit.SECONDS)
|
||||
.expireAfterWrite(2, TimeUnit.HOURS) // 设置缓存条目在写入后 2 小时自动失效
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean(name = "openidToken") // 注册名为 openidToken 的缓存 Bean
|
||||
public Cache<String, String> openidToken() {
|
||||
// 创建一个 Guava 本地缓存:写入后 1 小时过期
|
||||
return CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS) // 设置缓存条目在写入后 1 小时自动失效
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,38 @@
|
||||
package edu.whut.config;
|
||||
|
||||
import edu.whut.infrastructure.gateway.IWeixinApiService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.jackson.JacksonConverterFactory;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class Retrofit2Config {
|
||||
|
||||
// 微信开放平台的基础 URL,后续所有接口都会在这个前缀下拼接路径
|
||||
private static final String BASE_URL = "https://api.weixin.qq.com/";
|
||||
|
||||
/**
|
||||
* 创建一个 Retrofit 对象,并注册为 Spring Bean
|
||||
* - baseUrl:设置所有请求的公共前缀
|
||||
* - addConverterFactory:添加 Jackson 转换器,用于 JSON <-> Java 对象的自动映射
|
||||
*/
|
||||
@Bean
|
||||
public Retrofit retrofit() {
|
||||
return new Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.addConverterFactory(JacksonConverterFactory.create()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 Retrofit 动态生成 IWeixinApiService 接口的实现,
|
||||
* 并注册为 Spring 容器中的 Bean,方便业务层直接注入使用
|
||||
*/
|
||||
@Bean
|
||||
public IWeixinApiService weixinApiService(Retrofit retrofit) {
|
||||
return retrofit.create(IWeixinApiService.class);
|
||||
}
|
||||
|
||||
}
|
@ -39,7 +39,10 @@ spring:
|
||||
weixin:
|
||||
config:
|
||||
originalid: gh_b748269e1f4c
|
||||
token: b8b6
|
||||
token: asdf
|
||||
app-id: wx7cc74be9b340b26e
|
||||
app-secret: d4e73551512c6dc7a2e8f746c26b7f2c
|
||||
template_id: ARDqdKXuGvASjsDqXzeunq0P8chMQ7tXk_4-BPULJ6U
|
||||
|
||||
# 日志
|
||||
logging:
|
||||
|
@ -0,0 +1,11 @@
|
||||
package edu.whut.domain.auth.adapter.port;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface ILoginPort {
|
||||
|
||||
String createQrCodeTicket() throws IOException;
|
||||
|
||||
void sendLoginTemplate(String openid) throws IOException;
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
package edu.whut.domain.auth.adapter.repository;
|
@ -0,0 +1,14 @@
|
||||
package edu.whut.domain.auth.service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface ILoginService {
|
||||
|
||||
String createQrCodeTicket() throws Exception;
|
||||
|
||||
String checkLogin(String ticket);
|
||||
|
||||
|
||||
void saveLoginState(String ticket, String openid) throws IOException;
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package edu.whut.domain.auth.service;
|
||||
import com.google.common.cache.Cache;
|
||||
import edu.whut.domain.auth.adapter.port.ILoginPort;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WeixinLoginService implements ILoginService {
|
||||
|
||||
@Resource
|
||||
private ILoginPort loginPort;
|
||||
@Resource
|
||||
private Cache<String, String> openidToken;
|
||||
|
||||
/**
|
||||
* 生成登录二维码的 ticket
|
||||
* 根据业务需求调用微信 API 或其他服务生成一个可用于前端展示的二维码凭证(ticket)。
|
||||
*/
|
||||
@Override
|
||||
public String createQrCodeTicket() throws Exception {
|
||||
return loginPort.createQrCodeTicket();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查扫码登录状态
|
||||
* 根据前端传回的 ticket,轮询或查询登录结果,
|
||||
* 如果用户已扫描并确认,则返回对应的 openid;否则返回 null 或空串。
|
||||
*/
|
||||
@Override
|
||||
public String checkLogin(String ticket) {
|
||||
return openidToken.getIfPresent(ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存登录状态
|
||||
* 将用户的登录态(ticket 与 openid 的映射)持久化或缓存,
|
||||
* 以便后续业务逻辑(如会话校验、权限验证等)使用。
|
||||
*/
|
||||
@Override
|
||||
public void saveLoginState(String ticket, String openid) throws IOException {
|
||||
// 保存登录信息
|
||||
openidToken.put(ticket, openid);
|
||||
// 发送模板消息
|
||||
loginPort.sendLoginTemplate(openid);
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
/**
|
||||
* 聚合对象;
|
||||
* 1. 聚合实体和值对象
|
||||
* 2. 聚合是聚合的对象,和提供基础处理对象的方法。但不建议在聚合中引入仓储和接口来做过大的逻辑。而这些复杂的操作应该放到service中处理
|
||||
* 3. 对象名称 XxxAggregate
|
||||
*/
|
||||
package edu.whut.domain.yyy.model.aggregate;
|
@ -1,7 +0,0 @@
|
||||
/**
|
||||
* 实体对象;
|
||||
* 1. 一般和数据库持久化对象1v1的关系,但因各自开发系统的不同,也有1vn的可能。
|
||||
* 2. 如果是老系统改造,那么旧的库表冗余了太多的字段,可能会有nv1的情况
|
||||
* 3. 对象名称 XxxEntity
|
||||
*/
|
||||
package edu.whut.domain.yyy.model.entity;
|
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* 值对象;
|
||||
* 1. 用于描述对象属性的值,如一个库表中有json后者一个字段多个属性信息的枚举对象
|
||||
* 2. 对象名称如;XxxVO
|
||||
*/
|
||||
package edu.whut.domain.yyy.model.valobj;
|
@ -1 +0,0 @@
|
||||
package edu.whut.domain.yyy.service;
|
@ -18,6 +18,18 @@
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.retrofit2</groupId>
|
||||
<artifactId>retrofit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.retrofit2</groupId>
|
||||
<artifactId>converter-jackson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.retrofit2</groupId>
|
||||
<artifactId>adapter-rxjava2</artifactId>
|
||||
</dependency>
|
||||
<!-- 系统模块 -->
|
||||
<dependency>
|
||||
<groupId>edu.whut</groupId>
|
||||
|
@ -0,0 +1,98 @@
|
||||
package edu.whut.infrastructure.adapter.port;
|
||||
import com.google.common.cache.Cache;
|
||||
import edu.whut.domain.auth.adapter.port.ILoginPort;
|
||||
import edu.whut.infrastructure.gateway.IWeixinApiService;
|
||||
import edu.whut.infrastructure.gateway.dto.WeixinQrCodeRequestDTO;
|
||||
import edu.whut.infrastructure.gateway.dto.WeixinQrCodeResponseDTO;
|
||||
import edu.whut.infrastructure.gateway.dto.WeixinTemplateMessageDTO;
|
||||
import edu.whut.infrastructure.gateway.dto.WeixinTokenResponseDTO;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import retrofit2.Call;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class LoginPort implements ILoginPort {
|
||||
|
||||
@Value("${weixin.config.app-id}")
|
||||
private String appid;
|
||||
@Value("${weixin.config.app-secret}")
|
||||
private String appSecret;
|
||||
@Value("${weixin.config.template_id}")
|
||||
private String template_id;
|
||||
|
||||
@Resource
|
||||
private Cache<String, String> weixinAccessToken;
|
||||
|
||||
@Resource
|
||||
private IWeixinApiService weixinApiService;
|
||||
|
||||
/**
|
||||
* 生成二维码登录凭证 ticket
|
||||
* 获取或刷新 access_token 后,调用微信接口创建带参数的二维码,返回 ticket。
|
||||
*/
|
||||
@Override
|
||||
public String createQrCodeTicket() throws IOException {
|
||||
// 1. 获取 access_token,优先从缓存读取,缓存失效时调用微信接口获取并更新缓存
|
||||
String accessToken = weixinAccessToken.getIfPresent(appid);
|
||||
if (null == accessToken) {
|
||||
Call<WeixinTokenResponseDTO> call = weixinApiService.getToken("client_credential", appid, appSecret);
|
||||
WeixinTokenResponseDTO weixinTokenRes = call.execute().body();
|
||||
assert weixinTokenRes != null;
|
||||
accessToken = weixinTokenRes.getAccess_token();
|
||||
weixinAccessToken.put(appid, accessToken);
|
||||
}
|
||||
|
||||
// 2. 构造二维码请求对象,设置过期时间及业务参数
|
||||
WeixinQrCodeRequestDTO weixinQrCodeReq = WeixinQrCodeRequestDTO.builder()
|
||||
.expire_seconds(2592000)
|
||||
.action_name(WeixinQrCodeRequestDTO.ActionNameTypeVO.QR_SCENE.getCode())
|
||||
.action_info(WeixinQrCodeRequestDTO.ActionInfo.builder()
|
||||
.scene(WeixinQrCodeRequestDTO.ActionInfo.Scene.builder()
|
||||
.scene_id(100601)
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
// 3. 调用微信接口生成二维码 ticket
|
||||
Call<WeixinQrCodeResponseDTO> call = weixinApiService.createQrCode(accessToken, weixinQrCodeReq);
|
||||
WeixinQrCodeResponseDTO weixinQrCodeRes = call.execute().body();
|
||||
assert null != weixinQrCodeRes;
|
||||
return weixinQrCodeRes.getTicket();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送登录成功模板消息
|
||||
* 获取或刷新 access_token 后,封装模板数据,调用微信接口发送模板消息。
|
||||
*/
|
||||
@Override
|
||||
public void sendLoginTemplate(String openid) throws IOException {
|
||||
// 1. 获取 accessToken 【实际业务场景,按需处理下异常】
|
||||
String accessToken = weixinAccessToken.getIfPresent(appid);
|
||||
if (null == accessToken){
|
||||
Call<WeixinTokenResponseDTO> call = weixinApiService.getToken("client_credential", appid, appSecret);
|
||||
WeixinTokenResponseDTO weixinTokenRes = call.execute().body();
|
||||
assert weixinTokenRes != null;
|
||||
accessToken = weixinTokenRes.getAccess_token();
|
||||
weixinAccessToken.put(appid, accessToken);
|
||||
}
|
||||
|
||||
// 2. 构造模板消息的 data 数据结构
|
||||
Map<String, Map<String, String>> data = new HashMap<>();
|
||||
WeixinTemplateMessageDTO.put(data, WeixinTemplateMessageDTO.TemplateKey.USER, openid);
|
||||
|
||||
// 3. 构造模板消息对象
|
||||
WeixinTemplateMessageDTO templateMessageDTO = new WeixinTemplateMessageDTO(openid, template_id);
|
||||
templateMessageDTO.setUrl("https://blog.bitday.top");
|
||||
templateMessageDTO.setData(data);
|
||||
|
||||
// 4. 调用微信接口发送模板消息
|
||||
Call<Void> call = weixinApiService.sendMessage(accessToken, templateMessageDTO);
|
||||
call.execute();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package edu.whut.infrastructure.gateway;
|
||||
import edu.whut.infrastructure.gateway.dto.WeixinQrCodeRequestDTO;
|
||||
import edu.whut.infrastructure.gateway.dto.WeixinQrCodeResponseDTO;
|
||||
import edu.whut.infrastructure.gateway.dto.WeixinTemplateMessageDTO;
|
||||
import edu.whut.infrastructure.gateway.dto.WeixinTokenResponseDTO;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
/**
|
||||
* 微信API服务 retrofit2
|
||||
*/
|
||||
public interface IWeixinApiService {
|
||||
|
||||
/**
|
||||
* 获取 Access token
|
||||
* 文档:<a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html">Get_access_token</a>
|
||||
*
|
||||
* @param grantType 获取access_token填写client_credential
|
||||
* @param appId 第三方用户唯一凭证
|
||||
* @param appSecret 第三方用户唯一凭证密钥,即appsecret
|
||||
* @return 响应结果
|
||||
*/
|
||||
@GET("cgi-bin/token")
|
||||
Call<WeixinTokenResponseDTO> getToken(@Query("grant_type") String grantType,
|
||||
@Query("appid") String appId,
|
||||
@Query("secret") String appSecret);
|
||||
|
||||
/**
|
||||
* 获取凭据 ticket
|
||||
* 文档:<a href="https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html">Generating_a_Parametric_QR_Code</a>
|
||||
* <a href="https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET">前端根据凭证展示二维码</a>
|
||||
*
|
||||
* @param accessToken getToken 获取的 token 信息
|
||||
* @param weixinQrCodeRequestDTO 入参对象
|
||||
* @return 应答结果
|
||||
*/
|
||||
@POST("cgi-bin/qrcode/create")
|
||||
Call<WeixinQrCodeResponseDTO> createQrCode(@Query("access_token") String accessToken, @Body WeixinQrCodeRequestDTO weixinQrCodeRequestDTO);
|
||||
|
||||
/**
|
||||
* 发送微信公众号模板消息
|
||||
* 文档:https://mp.weixin.qq.com/debug/cgi-bin/readtmpl?t=tmplmsg/faq_tmpl
|
||||
*
|
||||
* @param accessToken getToken 获取的 token 信息
|
||||
* @param weixinTemplateMessageDTO 入参对象
|
||||
* @return 应答结果
|
||||
*/
|
||||
@POST("cgi-bin/message/template/send")
|
||||
Call<Void> sendMessage(@Query("access_token") String accessToken, @Body WeixinTemplateMessageDTO weixinTemplateMessageDTO);
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package edu.whut.infrastructure.gateway.dto;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* @description 获取微信登录二维码请求对象
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class WeixinQrCodeRequestDTO {
|
||||
|
||||
private int expire_seconds;
|
||||
private String action_name;
|
||||
private ActionInfo action_info;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class ActionInfo {
|
||||
Scene scene;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Scene {
|
||||
int scene_id;
|
||||
String scene_str;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public enum ActionNameTypeVO {
|
||||
QR_SCENE("QR_SCENE", "临时的整型参数值"),
|
||||
QR_STR_SCENE("QR_STR_SCENE", "临时的字符串参数值"),
|
||||
QR_LIMIT_SCENE("QR_LIMIT_SCENE", "永久的整型参数值"),
|
||||
QR_LIMIT_STR_SCENE("QR_LIMIT_STR_SCENE", "永久的字符串参数值");
|
||||
|
||||
private String code;
|
||||
private String info;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package edu.whut.infrastructure.gateway.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 获取微信登录二维码响应对象
|
||||
*/
|
||||
@Data
|
||||
public class WeixinQrCodeResponseDTO {
|
||||
|
||||
private String ticket;
|
||||
private Long expire_seconds;
|
||||
private String url;
|
||||
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package edu.whut.infrastructure.gateway.dto;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信模板消息
|
||||
*/
|
||||
public class WeixinTemplateMessageDTO {
|
||||
|
||||
private String touser = "or0Ab6ivwmypESVp_bYuk92T6SvU";
|
||||
private String template_id = "GLlAM-Q4jdgsktdNd35hnEbHVam2mwsW2YWuxDhpQkU";
|
||||
private String url = "https://weixin.qq.com";
|
||||
private Map<String, Map<String, String>> data = new HashMap<>();
|
||||
|
||||
public WeixinTemplateMessageDTO(String touser, String template_id) {
|
||||
this.touser = touser;
|
||||
this.template_id = template_id;
|
||||
}
|
||||
|
||||
public void put(TemplateKey key, String value) {
|
||||
data.put(key.getCode(), new HashMap<String, String>() {
|
||||
private static final long serialVersionUID = 7092338402387318563L;
|
||||
|
||||
{
|
||||
put("value", value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void put(Map<String, Map<String, String>> data, TemplateKey key, String value) {
|
||||
data.put(key.getCode(), new HashMap<String, String>() {
|
||||
private static final long serialVersionUID = 7092338402387318563L;
|
||||
|
||||
{
|
||||
put("value", value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public enum TemplateKey {
|
||||
USER("user","用户ID")
|
||||
;
|
||||
|
||||
private String code;
|
||||
private String desc;
|
||||
|
||||
TemplateKey(String code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String getTouser() {
|
||||
return touser;
|
||||
}
|
||||
|
||||
public void setTouser(String touser) {
|
||||
this.touser = touser;
|
||||
}
|
||||
|
||||
public String getTemplate_id() {
|
||||
return template_id;
|
||||
}
|
||||
|
||||
public void setTemplate_id(String template_id) {
|
||||
this.template_id = template_id;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public Map<String, Map<String, String>> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Map<String, Map<String, String>> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package edu.whut.infrastructure.gateway.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 获取 Access token DTO 对象
|
||||
*/
|
||||
@Data
|
||||
public class WeixinTokenResponseDTO {
|
||||
|
||||
private String access_token;
|
||||
private int expires_in;
|
||||
private String errcode;
|
||||
private String errmsg;
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package edu.whut.trigger.http;
|
||||
import edu.whut.api.IAuthService;
|
||||
import edu.whut.api.response.Response;
|
||||
import edu.whut.domain.auth.service.ILoginService;
|
||||
import edu.whut.types.common.Constants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Slf4j
|
||||
@RestController()
|
||||
@CrossOrigin("*")
|
||||
@RequestMapping("/api/v1/login")
|
||||
public class LoginController implements IAuthService {
|
||||
|
||||
@Resource
|
||||
private ILoginService loginService;
|
||||
|
||||
/**
|
||||
* 生成并返回一个微信扫码登录的凭证(ticket)。
|
||||
* 前端拿到 ticket 后,会用它来换取二维码图片并展示给用户。
|
||||
*/
|
||||
@GetMapping("/weixin_qrcode_ticket")
|
||||
@Override
|
||||
public Response<String> weixinQrCodeTicket() {
|
||||
try {
|
||||
String qrCodeTicket = loginService.createQrCodeTicket();
|
||||
log.info("生成微信扫码登录 ticket:{}", qrCodeTicket);
|
||||
return Response.<String>builder()
|
||||
.code(Constants.ResponseCode.SUCCESS.getCode())
|
||||
.info(Constants.ResponseCode.SUCCESS.getInfo())
|
||||
.data(qrCodeTicket)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("生成微信扫码登录 ticket 失败", e);
|
||||
return Response.<String>builder()
|
||||
.code(Constants.ResponseCode.UN_ERROR.getCode())
|
||||
.info(Constants.ResponseCode.UN_ERROR.getInfo())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测指定 ticket 的登录状态:
|
||||
* 如果用户已扫码、后端已收到 openid 回调,就返回对应的登录令牌(如 openidToken 或 JWT)。
|
||||
* 否则返回 “未登录” 的状态码,前端可以继续轮询。
|
||||
*/
|
||||
@GetMapping("/check_login")
|
||||
@Override
|
||||
public Response<String> checkLogin(String ticket) {
|
||||
try {
|
||||
String openidToken = loginService.checkLogin(ticket);
|
||||
log.info("扫码检测登录结果 ticket:{} openidToken:{}", ticket, openidToken);
|
||||
if (StringUtils.isNotBlank(openidToken)) {
|
||||
return Response.<String>builder()
|
||||
.code(Constants.ResponseCode.SUCCESS.getCode())
|
||||
.info(Constants.ResponseCode.SUCCESS.getInfo())
|
||||
.data(openidToken)
|
||||
.build();
|
||||
} else {
|
||||
return Response.<String>builder()
|
||||
.code(Constants.ResponseCode.NO_LOGIN.getCode())
|
||||
.info(Constants.ResponseCode.NO_LOGIN.getInfo())
|
||||
.build();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("扫码检测登录结果失败 ticket:{}", ticket, e);
|
||||
return Response.<String>builder()
|
||||
.code(Constants.ResponseCode.UN_ERROR.getCode())
|
||||
.info(Constants.ResponseCode.UN_ERROR.getInfo())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package edu.whut.trigger.http;
|
||||
import edu.whut.domain.auth.service.ILoginService;
|
||||
import edu.whut.types.weixin.MessageTextEntity;
|
||||
import edu.whut.types.weixin.SignatureUtil;
|
||||
import edu.whut.types.weixin.XmlUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -9,10 +11,12 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index 平台地址
|
||||
* https://pay.bitday.top/api/v1/weixin/portal/receive 内网穿透
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController()
|
||||
@CrossOrigin("*")
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/v1/weixin/portal/")
|
||||
public class WeixinPortalController {
|
||||
|
||||
@ -21,6 +25,15 @@ public class WeixinPortalController {
|
||||
@Value("${weixin.config.token}")
|
||||
private String token;
|
||||
|
||||
private final ILoginService loginService;
|
||||
|
||||
//测试内网穿透
|
||||
@GetMapping(value = "test", produces = "text/plain;charset=UTF-8")
|
||||
public String testFrp() {
|
||||
log.info("内网穿透测试接口被调用");
|
||||
return "内网穿透测试成功";
|
||||
}
|
||||
|
||||
@GetMapping(value = "receive", produces = "text/plain;charset=utf-8")
|
||||
public String validate(@RequestParam(value = "signature", required = false) String signature,
|
||||
@RequestParam(value = "timestamp", required = false) String timestamp,
|
||||
@ -43,6 +56,9 @@ public class WeixinPortalController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户扫码登录会触发该函数
|
||||
*/
|
||||
@PostMapping(value = "receive", produces = "application/xml; charset=UTF-8")
|
||||
public String post(@RequestBody String requestBody,
|
||||
@RequestParam("signature") String signature,
|
||||
@ -55,6 +71,12 @@ public class WeixinPortalController {
|
||||
log.info("接收微信公众号信息请求{}开始 {}", openid, requestBody);
|
||||
// 消息转换
|
||||
MessageTextEntity message = XmlUtil.xmlToBean(requestBody, MessageTextEntity.class);
|
||||
|
||||
if ("event".equals(message.getMsgType()) && "SCAN".equals(message.getEvent())) {
|
||||
loginService.saveLoginState(message.getTicket(), openid);
|
||||
return buildMessageTextEntity(openid, "登录成功");
|
||||
}
|
||||
|
||||
return buildMessageTextEntity(openid, "你好," + message.getContent());
|
||||
} catch (Exception e) {
|
||||
log.error("接收微信公众号信息请求{}失败 {}", openid, requestBody, e);
|
||||
|
@ -1,4 +0,0 @@
|
||||
/**
|
||||
* HTTP 接口服务
|
||||
*/
|
||||
package edu.whut.trigger.http;
|
@ -1,7 +1,24 @@
|
||||
package edu.whut.types.common;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
public class Constants {
|
||||
|
||||
public final static String SPLIT = ",";
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
public enum ResponseCode {
|
||||
SUCCESS("0000", "调用成功"),
|
||||
UN_ERROR("0001", "调用失败"),
|
||||
ILLEGAL_PARAMETER("0002", "非法参数"),
|
||||
NO_LOGIN("0003", "未登录"),
|
||||
;
|
||||
|
||||
private String code;
|
||||
private String info;
|
||||
|
||||
}
|
||||
}
|
||||
|
15
pom.xml
15
pom.xml
@ -115,6 +115,21 @@
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.15</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.retrofit2</groupId>
|
||||
<artifactId>retrofit</artifactId>
|
||||
<version>2.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.retrofit2</groupId>
|
||||
<artifactId>converter-jackson</artifactId>
|
||||
<version>2.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.retrofit2</groupId>
|
||||
<artifactId>adapter-rxjava2</artifactId>
|
||||
<version>2.9.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 工程模块 -->
|
||||
<dependency>
|
||||
|
Loading…
x
Reference in New Issue
Block a user