feat: 完善户外招聘会签到详情及小程序码

This commit is contained in:
2026-07-23 15:57:37 +08:00
parent cd80052eee
commit 397e36144a
8 changed files with 671 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
package com.ruoyi.cms.controller.app;
import com.ruoyi.cms.domain.FairCompany;
import com.ruoyi.cms.domain.OutdoorFair;
import com.ruoyi.cms.domain.OutdoorFairAttendee;
import com.ruoyi.cms.mapper.CompanyMapper;
import com.ruoyi.cms.mapper.FairCompanyMapper;
import com.ruoyi.cms.mapper.OutdoorFairBoothBookingMapper;
import com.ruoyi.cms.mapper.OutdoorFairBoothMapper;
import com.ruoyi.cms.mapper.OutdoorFairJobMapper;
import com.ruoyi.cms.service.IOutdoorFairAttendeeService;
import com.ruoyi.cms.service.IOutdoorFairService;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.Company;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class AppOutdoorFairControllerTest
{
@Test
void fairStartingInFutureCannotCheckIn() throws Exception
{
IOutdoorFairService fairService = mock(IOutdoorFairService.class);
IOutdoorFairAttendeeService attendeeService = mock(IOutdoorFairAttendeeService.class);
OutdoorFair fair = fairAt(new Date(System.currentTimeMillis() + 60_000L));
when(fairService.getById(12L)).thenReturn(fair);
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));
}
@Test
void fairCanCheckInAtOrAfterStartTime() throws Exception
{
IOutdoorFairService fairService = mock(IOutdoorFairService.class);
IOutdoorFairAttendeeService attendeeService = mock(IOutdoorFairAttendeeService.class);
OutdoorFair fair = fairAt(new Date(System.currentTimeMillis() - 1_000L));
when(fairService.getById(12L)).thenReturn(fair);
when(attendeeService.count(any())).thenReturn(0L);
when(attendeeService.save(any(OutdoorFairAttendee.class))).thenReturn(true);
AppOutdoorFairController controller = controllerWith(fairService, attendeeService);
AjaxResult result = controller.checkIn(attendee(12L));
assertEquals(200, result.get(AjaxResult.CODE_TAG));
verify(attendeeService).save(any(OutdoorFairAttendee.class));
assertTrue(AppOutdoorFairController.hasFairStarted(fair, fair.getHoldTime()));
assertFalse(AppOutdoorFairController.hasFairStarted(
fair, new Date(fair.getHoldTime().getTime() - 1L)));
}
@SuppressWarnings("unchecked")
@Test
void publicDetailIncludesOnlySafeCompanyCardFields() throws Exception
{
IOutdoorFairService fairService = mock(IOutdoorFairService.class);
IOutdoorFairAttendeeService attendeeService = mock(IOutdoorFairAttendeeService.class);
FairCompanyMapper fairCompanyMapper = mock(FairCompanyMapper.class);
CompanyMapper companyMapper = mock(CompanyMapper.class);
OutdoorFairBoothMapper boothMapper = mock(OutdoorFairBoothMapper.class);
OutdoorFairBoothBookingMapper bookingMapper = mock(OutdoorFairBoothBookingMapper.class);
OutdoorFairJobMapper jobMapper = mock(OutdoorFairJobMapper.class);
OutdoorFair fair = fairAt(new Date());
fair.setId(12L);
fair.setTitle("测试招聘会");
when(fairService.getById(12L)).thenReturn(fair);
when(boothMapper.selectCount(any())).thenReturn(0L);
FairCompany relation = new FairCompany();
relation.setId(21L);
relation.setJobFairId(12L);
relation.setCompanyId(31L);
relation.setReviewStatus("1");
when(fairCompanyMapper.selectList(any())).thenReturn(Collections.singletonList(relation));
Company company = new Company();
company.setCompanyId(31L);
company.setName("参会企业");
company.setIndustry("1001");
company.setScale("2");
company.setStatus(1);
company.setContactPerson("不应公开");
company.setContactPersonPhone("13800000000");
when(companyMapper.selectBatchIds(any())).thenReturn(Collections.singletonList(company));
when(bookingMapper.selectList(any())).thenReturn(Collections.emptyList());
when(jobMapper.countJobsByCompany(12L)).thenReturn(Collections.emptyList());
AppOutdoorFairController controller = controllerWith(fairService, attendeeService);
setField(controller, "fairCompanyMapper", fairCompanyMapper);
setField(controller, "companyMapper", companyMapper);
setField(controller, "outdoorFairBoothMapper", boothMapper);
setField(controller, "outdoorFairBoothBookingMapper", bookingMapper);
setField(controller, "outdoorFairJobMapper", jobMapper);
AjaxResult result = controller.detail(12L, new MockHttpServletRequest());
Map<String, Object> data = (Map<String, Object>) result.get(AjaxResult.DATA_TAG);
List<Map<String, Object>> companies = (List<Map<String, Object>>) data.get("companies");
assertEquals(1, companies.size());
assertEquals("参会企业", companies.get(0).get("companyName"));
assertFalse(companies.get(0).containsKey("contactPerson"));
assertFalse(companies.get(0).containsKey("contactPhone"));
}
private AppOutdoorFairController controllerWith(IOutdoorFairService fairService,
IOutdoorFairAttendeeService attendeeService)
throws Exception
{
AppOutdoorFairController controller = new AppOutdoorFairController();
setField(controller, "outdoorFairService", fairService);
setField(controller, "outdoorFairAttendeeService", attendeeService);
return controller;
}
private void setField(Object target, String name, Object value) throws Exception
{
Field field = target.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
}
private OutdoorFairAttendee attendee(Long fairId)
{
OutdoorFairAttendee attendee = new OutdoorFairAttendee();
attendee.setFairId(fairId);
attendee.setName("测试人员");
attendee.setPhone("13800000000");
return attendee;
}
private OutdoorFair fairAt(Date holdTime)
{
OutdoorFair fair = new OutdoorFair();
fair.setHoldTime(holdTime);
return fair;
}
}

View File

@@ -6,9 +6,21 @@ import org.springframework.mock.web.MockHttpServletRequest;
import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class OutdoorFairControllerTest
{
@Test
void miniProgramQrCodeEndpointRequiresDedicatedPermission() throws Exception
{
Method method = OutdoorFairController.class.getDeclaredMethod("getMiniProgramQrCode", Long.class);
org.springframework.security.access.prepost.PreAuthorize annotation =
method.getAnnotation(org.springframework.security.access.prepost.PreAuthorize.class);
assertNotNull(annotation);
assertEquals("@ss.hasPermi('cms:outdoorFair:qrcode')", annotation.value());
}
@Test
void companyQrCodeUrlUsesPublicForwardedHost() throws Exception
{

View File

@@ -0,0 +1,86 @@
package com.ruoyi.cms.service;
import com.ruoyi.common.exception.ServiceException;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class WeChatMiniProgramQrCodeServiceTest
{
@Test
void generatesPermanentOutdoorFairCodeAndCachesImage()
{
WeChatMiniProgramApiClient client = mock(WeChatMiniProgramApiClient.class);
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))
.thenReturn(new WeChatMiniProgramApiClient.QrCodeImage(
"image-content".getBytes(StandardCharsets.UTF_8), "image/png"));
WeChatMiniProgramQrCodeService service = new WeChatMiniProgramQrCodeService(
client, "app-id", "app-secret", () -> 1_000L);
Map<String, Object> first = service.generateOutdoorFairQrCode(12L);
Map<String, Object> second = service.generateOutdoorFairQrCode(12L);
assertEquals(12L, first.get("fairId"));
assertEquals("fairId=12", first.get("scene"));
assertEquals("packageA/pages/outdoorFair/detail", first.get("page"));
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);
}
@Test
void refreshesAccessTokenBeforeItExpires()
{
AtomicLong now = new AtomicLong(1_000L);
WeChatMiniProgramApiClient client = mock(WeChatMiniProgramApiClient.class);
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()))
.thenReturn(new WeChatMiniProgramApiClient.QrCodeImage(new byte[]{1, 2, 3}, "image/png"));
WeChatMiniProgramQrCodeService service = new WeChatMiniProgramQrCodeService(
client, "app-id", "app-secret", now::get);
service.generateOutdoorFairQrCode(1L);
now.addAndGet(7_000_000L);
service.generateOutdoorFairQrCode(2L);
verify(client, times(2)).getAccessToken("app-id", "app-secret");
verify(client).getUnlimitedQrCode(
"token-1", "fairId=1", WeChatMiniProgramQrCodeService.OUTDOOR_FAIR_PAGE);
verify(client).getUnlimitedQrCode(
"token-2", "fairId=2", WeChatMiniProgramQrCodeService.OUTDOOR_FAIR_PAGE);
}
@Test
void rejectsMissingConfigurationWithoutCallingWechat()
{
WeChatMiniProgramApiClient client = mock(WeChatMiniProgramApiClient.class);
WeChatMiniProgramQrCodeService service = new WeChatMiniProgramQrCodeService(
client, "", "", () -> 1_000L);
ServiceException exception = assertThrows(
ServiceException.class, () -> service.generateOutdoorFairQrCode(1L));
assertEquals("微信小程序配置不完整,请联系管理员", exception.getMessage());
}
}