Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -221,10 +221,10 @@ public class AppOutdoorFairController extends BaseController
|
||||
return AjaxResult.error("招聘会不存在");
|
||||
}
|
||||
if (fair.getHoldTime() == null) {
|
||||
return AjaxResult.error("招聘会开始时间未设置,暂不能签到");
|
||||
return AjaxResult.error("招聘会还没有开始,暂不能签到");
|
||||
}
|
||||
if (!hasFairStarted(fair, new Date())) {
|
||||
return AjaxResult.error("招聘会尚未开始,暂不能签到");
|
||||
return AjaxResult.error("招聘会还没有开始,暂不能签到");
|
||||
}
|
||||
// 同一招聘会下同手机号已签到则不重复记录
|
||||
Long exists = outdoorFairAttendeeService.count(new LambdaQueryWrapper<OutdoorFairAttendee>()
|
||||
|
||||
@@ -42,14 +42,15 @@ public class WeChatMiniProgramApiClient
|
||||
return new AccessTokenResult(accessToken, expiresIn == null ? 7200 : expiresIn);
|
||||
}
|
||||
|
||||
public QrCodeImage getUnlimitedQrCode(String accessToken, String scene, String page)
|
||||
public QrCodeImage getUnlimitedQrCode(String accessToken, String scene, String page,
|
||||
String envVersion)
|
||||
{
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.put("scene", scene);
|
||||
requestBody.put("page", page);
|
||||
// 新页面尚未随小程序版本发布时也允许先生成码;扫码仍会进入正式版小程序。
|
||||
// 新页面尚未随小程序版本发布时也允许先生成码;扫码进入配置指定的小程序版本。
|
||||
requestBody.put("check_path", false);
|
||||
requestBody.put("env_version", "release");
|
||||
requestBody.put("env_version", envVersion);
|
||||
requestBody.put("width", 430);
|
||||
|
||||
HttpResponse response = null;
|
||||
|
||||
@@ -18,13 +18,15 @@ import java.util.function.LongSupplier;
|
||||
public class WeChatMiniProgramQrCodeService
|
||||
{
|
||||
public static final String OUTDOOR_FAIR_PAGE = "packageA/pages/outdoorFair/detail";
|
||||
public static final String DEFAULT_ENV_VERSION = "trial";
|
||||
private static final long TOKEN_REFRESH_AHEAD_MILLIS = 300_000L;
|
||||
|
||||
private final WeChatMiniProgramApiClient apiClient;
|
||||
private final String appId;
|
||||
private final String appSecret;
|
||||
private final String envVersion;
|
||||
private final LongSupplier currentTimeMillis;
|
||||
private final Map<Long, String> qrCodeDataUrlCache = new ConcurrentHashMap<>();
|
||||
private final Map<String, String> qrCodeDataUrlCache = new ConcurrentHashMap<>();
|
||||
|
||||
private volatile String accessToken;
|
||||
private volatile long accessTokenExpiresAt;
|
||||
@@ -32,17 +34,26 @@ public class WeChatMiniProgramQrCodeService
|
||||
@Autowired
|
||||
public WeChatMiniProgramQrCodeService(WeChatMiniProgramApiClient apiClient,
|
||||
@Value("${wx.appid}") String appId,
|
||||
@Value("${wx.secret}") String appSecret)
|
||||
@Value("${wx.secret}") String appSecret,
|
||||
@Value("${wx.env-version:trial}") String envVersion)
|
||||
{
|
||||
this(apiClient, appId, appSecret, System::currentTimeMillis);
|
||||
this(apiClient, appId, appSecret, envVersion, System::currentTimeMillis);
|
||||
}
|
||||
|
||||
WeChatMiniProgramQrCodeService(WeChatMiniProgramApiClient apiClient, String appId,
|
||||
String appSecret, LongSupplier currentTimeMillis)
|
||||
{
|
||||
this(apiClient, appId, appSecret, DEFAULT_ENV_VERSION, currentTimeMillis);
|
||||
}
|
||||
|
||||
WeChatMiniProgramQrCodeService(WeChatMiniProgramApiClient apiClient, String appId,
|
||||
String appSecret, String envVersion,
|
||||
LongSupplier currentTimeMillis)
|
||||
{
|
||||
this.apiClient = apiClient;
|
||||
this.appId = appId;
|
||||
this.appSecret = appSecret;
|
||||
this.envVersion = normalizeEnvVersion(envVersion);
|
||||
this.currentTimeMillis = currentTimeMillis;
|
||||
}
|
||||
|
||||
@@ -52,16 +63,17 @@ public class WeChatMiniProgramQrCodeService
|
||||
throw new ServiceException("招聘会参数不正确");
|
||||
}
|
||||
String scene = "fairId=" + fairId;
|
||||
String imageData = qrCodeDataUrlCache.get(fairId);
|
||||
String cacheKey = envVersion + ":" + fairId;
|
||||
String imageData = qrCodeDataUrlCache.get(cacheKey);
|
||||
if (imageData == null) {
|
||||
synchronized (qrCodeDataUrlCache) {
|
||||
imageData = qrCodeDataUrlCache.get(fairId);
|
||||
imageData = qrCodeDataUrlCache.get(cacheKey);
|
||||
if (imageData == null) {
|
||||
WeChatMiniProgramApiClient.QrCodeImage image = apiClient.getUnlimitedQrCode(
|
||||
getValidAccessToken(), scene, OUTDOOR_FAIR_PAGE);
|
||||
getValidAccessToken(), scene, OUTDOOR_FAIR_PAGE, envVersion);
|
||||
imageData = "data:" + image.getContentType() + ";base64,"
|
||||
+ Base64.getEncoder().encodeToString(image.getContent());
|
||||
qrCodeDataUrlCache.put(fairId, imageData);
|
||||
qrCodeDataUrlCache.put(cacheKey, imageData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +82,7 @@ public class WeChatMiniProgramQrCodeService
|
||||
data.put("fairId", fairId);
|
||||
data.put("page", OUTDOOR_FAIR_PAGE);
|
||||
data.put("scene", scene);
|
||||
data.put("envVersion", envVersion);
|
||||
data.put("imageData", imageData);
|
||||
// getUnlimitedQRCode 生成的小程序码长期有效;需要轮换的是 access token,而不是已经生成的码。
|
||||
data.put("permanent", true);
|
||||
@@ -77,6 +90,20 @@ public class WeChatMiniProgramQrCodeService
|
||||
return data;
|
||||
}
|
||||
|
||||
private String normalizeEnvVersion(String value)
|
||||
{
|
||||
String normalized = value == null ? "" : value.trim().toLowerCase();
|
||||
if (normalized.isEmpty()) {
|
||||
return DEFAULT_ENV_VERSION;
|
||||
}
|
||||
if (!"trial".equals(normalized)
|
||||
&& !"release".equals(normalized)
|
||||
&& !"develop".equals(normalized)) {
|
||||
throw new ServiceException("微信小程序版本配置不正确,仅支持 trial、release 或 develop");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String getValidAccessToken()
|
||||
{
|
||||
long now = currentTimeMillis.getAsLong();
|
||||
|
||||
@@ -44,7 +44,22 @@ class AppOutdoorFairControllerTest
|
||||
AjaxResult result = controller.checkIn(attendee(12L));
|
||||
|
||||
assertEquals(500, result.get(AjaxResult.CODE_TAG));
|
||||
assertEquals("招聘会尚未开始,暂不能签到", result.get(AjaxResult.MSG_TAG));
|
||||
assertEquals("招聘会还没有开始,暂不能签到", result.get(AjaxResult.MSG_TAG));
|
||||
verify(attendeeService, never()).save(any(OutdoorFairAttendee.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fairWithoutStartTimeUsesNotStartedMessage() throws Exception
|
||||
{
|
||||
IOutdoorFairService fairService = mock(IOutdoorFairService.class);
|
||||
IOutdoorFairAttendeeService attendeeService = mock(IOutdoorFairAttendeeService.class);
|
||||
when(fairService.getById(12L)).thenReturn(fairAt(null));
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService);
|
||||
|
||||
AjaxResult result = controller.checkIn(attendee(12L));
|
||||
|
||||
assertEquals(500, result.get(AjaxResult.CODE_TAG));
|
||||
assertEquals("招聘会还没有开始,暂不能签到", result.get(AjaxResult.MSG_TAG));
|
||||
verify(attendeeService, never()).save(any(OutdoorFairAttendee.class));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class WeChatMiniProgramQrCodeServiceTest
|
||||
when(client.getAccessToken("app-id", "app-secret"))
|
||||
.thenReturn(new WeChatMiniProgramApiClient.AccessTokenResult("token-1", 7200));
|
||||
when(client.getUnlimitedQrCode("token-1", "fairId=12",
|
||||
WeChatMiniProgramQrCodeService.OUTDOOR_FAIR_PAGE))
|
||||
WeChatMiniProgramQrCodeService.OUTDOOR_FAIR_PAGE, "trial"))
|
||||
.thenReturn(new WeChatMiniProgramApiClient.QrCodeImage(
|
||||
"image-content".getBytes(StandardCharsets.UTF_8), "image/png"));
|
||||
|
||||
@@ -38,13 +38,14 @@ class WeChatMiniProgramQrCodeServiceTest
|
||||
assertEquals(12L, first.get("fairId"));
|
||||
assertEquals("fairId=12", first.get("scene"));
|
||||
assertEquals("packageA/pages/outdoorFair/detail", first.get("page"));
|
||||
assertEquals("trial", first.get("envVersion"));
|
||||
assertTrue((Boolean) first.get("permanent"));
|
||||
assertFalse((Boolean) first.get("rotationRequired"));
|
||||
assertTrue(String.valueOf(first.get("imageData")).startsWith("data:image/png;base64,"));
|
||||
assertEquals(first.get("imageData"), second.get("imageData"));
|
||||
verify(client, times(1)).getAccessToken("app-id", "app-secret");
|
||||
verify(client, times(1)).getUnlimitedQrCode(
|
||||
"token-1", "fairId=12", WeChatMiniProgramQrCodeService.OUTDOOR_FAIR_PAGE);
|
||||
"token-1", "fairId=12", WeChatMiniProgramQrCodeService.OUTDOOR_FAIR_PAGE, "trial");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -55,7 +56,7 @@ class WeChatMiniProgramQrCodeServiceTest
|
||||
when(client.getAccessToken("app-id", "app-secret"))
|
||||
.thenReturn(new WeChatMiniProgramApiClient.AccessTokenResult("token-1", 7200))
|
||||
.thenReturn(new WeChatMiniProgramApiClient.AccessTokenResult("token-2", 7200));
|
||||
when(client.getUnlimitedQrCode(anyString(), anyString(), anyString()))
|
||||
when(client.getUnlimitedQrCode(anyString(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(new WeChatMiniProgramApiClient.QrCodeImage(new byte[]{1, 2, 3}, "image/png"));
|
||||
|
||||
WeChatMiniProgramQrCodeService service = new WeChatMiniProgramQrCodeService(
|
||||
@@ -66,9 +67,9 @@ class WeChatMiniProgramQrCodeServiceTest
|
||||
|
||||
verify(client, times(2)).getAccessToken("app-id", "app-secret");
|
||||
verify(client).getUnlimitedQrCode(
|
||||
"token-1", "fairId=1", WeChatMiniProgramQrCodeService.OUTDOOR_FAIR_PAGE);
|
||||
"token-1", "fairId=1", WeChatMiniProgramQrCodeService.OUTDOOR_FAIR_PAGE, "trial");
|
||||
verify(client).getUnlimitedQrCode(
|
||||
"token-2", "fairId=2", WeChatMiniProgramQrCodeService.OUTDOOR_FAIR_PAGE);
|
||||
"token-2", "fairId=2", WeChatMiniProgramQrCodeService.OUTDOOR_FAIR_PAGE, "trial");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,4 +84,17 @@ class WeChatMiniProgramQrCodeServiceTest
|
||||
|
||||
assertEquals("微信小程序配置不完整,请联系管理员", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnsupportedEnvironmentVersion()
|
||||
{
|
||||
WeChatMiniProgramApiClient client = mock(WeChatMiniProgramApiClient.class);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> new WeChatMiniProgramQrCodeService(
|
||||
client, "app-id", "app-secret", "unknown", () -> 1_000L));
|
||||
|
||||
assertEquals("微信小程序版本配置不正确,仅支持 trial、release 或 develop",
|
||||
exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user