完善会员审批及门户配置
- 增加首页轮播图管理接口、权限及数据库脚本。
- 接入企业微信扫码入会申请和 BPM 审批,支持审批中确认缴费。
- 同步申请与会员缴费状态,支持扫码会员首次设置密码。
- 完善课题申报成员排序、联系方式校验及会员资料未关联处理。
- 增加相关数据库迁移脚本和审批、密码设置单元测试。
验证:后端编译及相关单元测试通过;真实企业微信扫码和审批流程尚未完成端到端验证。
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- 课é¢ç»æåç±»åï¼æ§æåä¿ç空å¼ï¼å¾
ä¸å¡äººåæ ¸å®åå¡«åã |
| | | ALTER TABLE research_application_member |
| | | ADD COLUMN member_type varchar(32) DEFAULT NULL COMMENT 'æåç±»åï¼æå¸ãå¦çãæ ¡å¤äººå'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | CREATE TABLE IF NOT EXISTS `system_home_banner` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL, `image_url` varchar(1000) NOT NULL, |
| | | `sort` int NOT NULL DEFAULT 0, `status` tinyint NOT NULL DEFAULT 0, |
| | | `creator` varchar(64) DEFAULT '', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | `updater` varchar(64) DEFAULT '', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | `deleted` bit(1) NOT NULL DEFAULT b'0', PRIMARY KEY (`id`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='é¦é¡µè½®æå¾'; |
| | | -- æ§è¡å请å¨è§è²ç®¡ç䏿æã |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 'é¦é¡µè½®æå¾', 'system:home-banner:query', 2, 90, id, 'home-banner', 'lucide:images', 'system/home-banner/index', 'SystemHomeBanner', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_menu WHERE parent_id = 0 AND path = '/system' AND deleted = b'0' |
| | | AND NOT EXISTS (SELECT 1 FROM (SELECT * FROM system_menu) m WHERE m.component = 'system/home-banner/index' AND m.deleted = b'0'); |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 'è½®æå¾ç»´æ¤', 'system:home-banner:update', 3, 1, id, '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_menu WHERE component = 'system/home-banner/index' AND deleted = b'0' |
| | | AND NOT EXISTS (SELECT 1 FROM (SELECT * FROM system_menu) m WHERE m.permission = 'system:home-banner:update' AND m.deleted = b'0'); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- åå²ç³è¯·çç¼´è´¹ç¶ææªç¥ï¼ä¿ç NULLã |
| | | ALTER TABLE ums_application ADD COLUMN fee_paid bit(1) DEFAULT NULL COMMENT 'ç³è¯·æ¶æ¯å¦ç¼´è´¹'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- æ«ç 身份ä¸ç³è¯·å¯ä¸å
³èï¼æ®éç³è¯·ä¿ç NULLã |
| | | ALTER TABLE ums_application |
| | | ADD COLUMN wxcp_openid varchar(191) DEFAULT NULL COMMENT 'ä¼ä¸å¾®ä¿¡æ«ç 身份', |
| | | ADD UNIQUE KEY uk_ums_application_wxcp_openid (wxcp_openid); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ä»
æ°çæ ææºå·ä¼ä¸å¾®ä¿¡æ«ç ä¼åéè¦é¦æ¬¡è®¾ç½®å¯ç ã |
| | | ALTER TABLE ums_member ADD COLUMN password_setup_required bit(1) NOT NULL DEFAULT b'0' COMMENT 'æ¯å¦éè¦é¦æ¬¡è®¾ç½®å¯ç '; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.service.task; |
| | | |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | import org.flowable.task.api.Task; |
| | | |
| | | /** ä¸å¡æ¨¡åå¨å®¡æ¹ä»»å¡å®æåè¿è¡æ ¡éªã */ |
| | | public interface BpmTaskApprovalValidator { |
| | | void validate(Long userId, Task task, ProcessInstance instance); |
| | | } |
| | |
| | | |
| | | @Resource |
| | | private BpmProcessInstanceService processInstanceService; |
| | | @org.springframework.beans.factory.annotation.Autowired |
| | | private org.springframework.beans.factory.ObjectProvider<BpmTaskApprovalValidator> approvalValidators; |
| | | @Resource |
| | | private BpmProcessDefinitionService bpmProcessDefinitionService; |
| | | @Resource |
| | |
| | | if (instance == null) { |
| | | throw exception(PROCESS_INSTANCE_NOT_EXISTS); |
| | | } |
| | | approvalValidators.orderedStream().forEach(validator -> validator.validate(userId, task, instance)); |
| | | // 1.3 æ ¡éªç¾å |
| | | BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); |
| | | Boolean signEnable = parseSignEnable(bpmnModel, task.getTaskDefinitionKey()); |
| | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.Valid; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.Email; |
| | | import jakarta.validation.constraints.Size; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Pattern; |
| | |
| | | @Pattern(regexp = "^1[3-9]\\d{9}$", message = "èç³»çµè¯æ ¼å¼ä¸æ£ç¡®ï¼è¯·è¾å
¥æ£ç¡®ç 11 使æºå·") |
| | | private String contactPhone; |
| | | |
| | | @Schema(description = "èç³»é®ç®±", example = "zhangsan@example.com") |
| | | @Schema(description = "èç³»é®ç®±", requiredMode = Schema.RequiredMode.REQUIRED, example = "zhangsan@example.com") |
| | | @NotBlank(message = "èç³»é®ç®±ä¸è½ä¸ºç©º") |
| | | @Email(message = "èç³»é®ç®±æ ¼å¼ä¸æ£ç¡®") |
| | | @Size(max = 128, message = "èç³»é®ç®±é¿åº¦ä¸è½è¶
è¿128个å符") |
| | | private String contactEmail; |
| | | |
| | | @Schema(description = "课é¢ç»æåï¼æ§çææ¬åæ®µï¼ä»
ä¿çå
¼å®¹ï¼æ°æ°æ®è¯·ä½¿ç¨ membersï¼") |
| | |
| | | package cn.iocoder.yudao.module.research.dal.mysql.application; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.research.dal.dataobject.application.ResearchApplicationMemberDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | |
| | | public interface ResearchApplicationMemberMapper extends BaseMapperX<ResearchApplicationMemberDO> { |
| | | |
| | | default List<ResearchApplicationMemberDO> selectListByApplicationId(Long applicationId) { |
| | | return selectList(ResearchApplicationMemberDO::getApplicationId, applicationId); |
| | | LambdaQueryWrapperX<ResearchApplicationMemberDO> query = new LambdaQueryWrapperX<>(); |
| | | query.eq(ResearchApplicationMemberDO::getApplicationId, applicationId); |
| | | query.orderByAsc(ResearchApplicationMemberDO::getSortNo); |
| | | query.orderByAsc(ResearchApplicationMemberDO::getId); |
| | | return selectList(query); |
| | | } |
| | | |
| | | default int deleteByApplicationId(Long applicationId) { |
| | |
| | | */ |
| | | private Long userId; |
| | | |
| | | /** ä¼ä¸å¾®ä¿¡ææåå¯è·åçææºå·ï¼å¯è½ä¸ºç©ºã */ |
| | | private String mobile; |
| | | |
| | | public SocialUserRespDTO(String openid, String nickname, String avatar, Long userId) { |
| | | this.openid = openid; this.nickname = nickname; this.avatar = avatar; this.userId = userId; |
| | | } |
| | | |
| | | } |
| | |
| | | @Schema(description = "è§è²ç¼å·æ°ç»", example = "[1, 2]") |
| | | private Set<Long> roleIds; |
| | | |
| | | @Schema(description = "ææºå·ç ", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300") |
| | | @NotEmpty(message = "ææºå·ç ä¸è½ä¸ºç©º") |
| | | @Schema(description = "ææºå·ç ï¼æªè·åå°æ¶å¯ä¸ºç©ºï¼", example = "15601691300") |
| | | private String mobile; |
| | | |
| | | @Schema(description = "å¯ç ", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") |
| | |
| | | @Schema(description = "夿³¨", example = "夿³¨ä¿¡æ¯") |
| | | private String remark; |
| | | |
| | | } |
| | | } |
| | |
| | | @PermitAll |
| | | @Operation(summary = "社交快æ·ç»å½ï¼ä½¿ç¨ code ææç ", description = "éåæªç»å½çç¨æ·ï¼ä½æ¯ç¤¾äº¤è´¦å·å·²ç»å®ç¨æ·") |
| | | public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialLoginReqVO reqVO) { |
| | | return success(authService.socialLogin(reqVO)); |
| | | AuthLoginRespVO result = authService.socialLogin(reqVO); |
| | | if (result == null) { |
| | | return CommonResult.error(cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.AUTH_WXCP_MEMBERSHIP_PENDING); |
| | | } |
| | | return success(result); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.system.controller.admin.homebanner; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.homebanner.HomeBannerDO; |
| | | import cn.iocoder.yudao.module.system.dal.mysql.homebanner.HomeBannerMapper; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import jakarta.validation.constraints.*; |
| | | import lombok.Data; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import java.util.List; |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | @RestController @RequestMapping("/system/home-banner") @Validated |
| | | public class HomeBannerController { |
| | | @Resource private HomeBannerMapper mapper; |
| | | private LambdaQueryWrapperX<HomeBannerDO> ordered() { |
| | | LambdaQueryWrapperX<HomeBannerDO> query = new LambdaQueryWrapperX<>(); |
| | | query.orderByAsc(HomeBannerDO::getSort); query.orderByDesc(HomeBannerDO::getId); return query; |
| | | } |
| | | @GetMapping("/list") @PreAuthorize("@ss.hasPermission('system:home-banner:query')") |
| | | public CommonResult<List<HomeBannerDO>> list() { return success(mapper.selectList(ordered())); } |
| | | @GetMapping("/enabled-list") |
| | | public CommonResult<List<HomeBannerDO>> enabledList() { return success(mapper.selectList(ordered().eq(HomeBannerDO::getStatus, 0))); } |
| | | @PostMapping("/save") @PreAuthorize("@ss.hasPermission('system:home-banner:update')") |
| | | public CommonResult<Boolean> save(@Valid @RequestBody SaveRequest req) { |
| | | HomeBannerDO item = new HomeBannerDO(); item.setId(req.getId()); item.setTitle(req.getTitle().trim()); item.setImageUrl(req.getImageUrl().trim()); item.setSort(req.getSort()); item.setStatus(req.getStatus()); |
| | | if (item.getId() == null) mapper.insert(item); else mapper.updateById(item); return success(true); |
| | | } |
| | | @DeleteMapping("/delete") @PreAuthorize("@ss.hasPermission('system:home-banner:update')") |
| | | public CommonResult<Boolean> delete(@RequestParam("id") Long id) { mapper.deleteById(id); return success(true); } |
| | | @Data public static class SaveRequest { |
| | | private Long id; |
| | | @NotBlank @Size(max=100) private String title; |
| | | @NotBlank @Size(max=1000) private String imageUrl; |
| | | @NotNull @Min(0) private Integer sort; |
| | | @NotNull @Min(0) @Max(1) private Integer status; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.system.dal.dataobject.homebanner; |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | @Data @EqualsAndHashCode(callSuper = true) @TableName("system_home_banner") |
| | | public class HomeBannerDO extends BaseDO { |
| | | private Long id; |
| | | private String title; |
| | | private String imageUrl; |
| | | private Integer sort; |
| | | private Integer status; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.system.dal.mysql.homebanner; |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.homebanner.HomeBannerDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | @Mapper public interface HomeBannerMapper extends BaseMapperX<HomeBannerDO> {} |
| | |
| | | ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_002_000_005, "æªç»å®è´¦å·ï¼éè¦è¿è¡ç»å®"); |
| | | ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1_002_000_007, "ææºå·ä¸åå¨"); |
| | | ErrorCode AUTH_REGISTER_CAPTCHA_CODE_ERROR = new ErrorCode(1_002_000_008, "éªè¯ç 䏿£ç¡®ï¼åå ï¼{}"); |
| | | ErrorCode AUTH_WXCP_MEMBERSHIP_PENDING = new ErrorCode(1_002_000_009, "å
¥ä¼ç³è¯·å·²ç»è®°ï¼è¯·çå¾
管çåç¡®è®¤ç¼´è´¹å¹¶å®æå®¡æ¹å忬¡æ«ç ç»å½"); |
| | | |
| | | // ========== è忍¡å 1-002-001-000 ========== |
| | | ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1_002_001_000, "å·²ç»åå¨è¯¥ååçèå"); |
| | |
| | | import lombok.Setter; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.context.ApplicationEventPublisher; |
| | | import org.springframework.security.crypto.password.PasswordEncoder; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | private RoleService roleService; |
| | | @Resource |
| | | private PermissionService permissionService; |
| | | @Autowired(required = false) |
| | | private WxcpMembershipRegistration wxcpMembershipRegistration; |
| | | @Resource |
| | | private WxcpPasswordSetupGrant wxcpPasswordSetupGrant; |
| | | |
| | | /** |
| | | * éªè¯ç çå¼å
³ï¼é»è®¤ä¸º true |
| | |
| | | createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); |
| | | throw exception(AUTH_LOGIN_BAD_CREDENTIALS); |
| | | } |
| | | |
| | | if (!userService.isPasswordMatch(password, user.getPassword())) { |
| | | createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); |
| | | throw exception(AUTH_LOGIN_BAD_CREDENTIALS); |
| | |
| | | |
| | | // 妿 socialType é空ï¼è¯´æéè¦ç»å®ç¤¾äº¤ç¨æ· |
| | | if (reqVO.getSocialType() != null) { |
| | | if (WECHAT_ENTERPRISE_TYPE.equals(reqVO.getSocialType())) { |
| | | SocialUserRespDTO social = socialUserService.getSocialUserByCode(getUserType().getValue(), |
| | | reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()); |
| | | if (wxcpMembershipRegistration == null || social == null |
| | | || wxcpMembershipRegistration.hasPendingApplication(social.getOpenid())) { |
| | | throw exception(AUTH_WXCP_MEMBERSHIP_PENDING); |
| | | } |
| | | } |
| | | socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), |
| | | reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())); |
| | | } |
| | |
| | | AdminUserDO user = bindUserId != null ? userService.getUser(bindUserId) : null; |
| | | |
| | | if (user == null) { |
| | | if (WECHAT_ENTERPRISE_TYPE.equals(reqVO.getType())) { |
| | | if (wxcpMembershipRegistration == null) throw new IllegalStateException("ä¼å注åæå¡ä¸å¯ç¨"); |
| | | wxcpMembershipRegistration.startIfAbsent(socialUser.getOpenid(), socialUser.getNickname(), socialUser.getMobile()); |
| | | return null; // æäº¤äºå¡åç± Controller è¿åå¾
å®¡æ ¸æç¤ºï¼ä¸ç¾å令ç |
| | | } |
| | | // 2.1 馿¬¡ç»å½ï¼æåç»å®ç¨æ·å·²è¢«å é¤ï¼èªå¨æ³¨åæ°ç¨æ·å¹¶ï¼æ¹ï¼ç»å® |
| | | // |
| | | // åç»å®ç¨æ·å·²å 餿¶å¿
é¡»èµ°è¿æ¡è·¯ï¼å¦å该ä¼ä¸æå伿°¸ä¹
å¡å¨ãç¨æ·ä¸åå¨ãââ |
| | |
| | | user.getId(), user.getUsername(), reqVO.getType()); |
| | | } |
| | | |
| | | if (CommonStatusEnum.isDisable(user.getStatus())) throw exception(AUTH_LOGIN_USER_DISABLED); |
| | | // 3. å建 Token 令çï¼è®°å½ç»å½æ¥å¿ |
| | | AuthLoginRespVO result = createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); |
| | | if (WECHAT_ENTERPRISE_TYPE.equals(reqVO.getType())) { |
| | | wxcpPasswordSetupGrant.issue(result.getAccessToken(), user.getId()); |
| | | } |
| | | |
| | | // 4. åå¸ç¤¾äº¤ç»å½äºä»¶ï¼ä¾å
¶ä»æ¨¡åçå¬ï¼å¦åéä¼ä¸å¾®ä¿¡éç¥ï¼ |
| | | eventPublisher.publishEvent(new SocialLoginEvent( |
| | |
| | | |
| | | userService.updateUserPassword(userByMobile.getId(), reqVO.getPassword()); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.system.service.auth; |
| | | |
| | | /** ä¼ä¸å¾®ä¿¡é¦æ¬¡æ«ç æ¶å建å¾
å®¡æ ¸çå
¥ä¼ç³è¯·ï¼ç±ä¼å模åå®ç°ã */ |
| | | public interface WxcpMembershipRegistration { |
| | | void startIfAbsent(String openid, String nickname, String mobile); |
| | | boolean hasPendingApplication(String openid); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.system.service.auth; |
| | | |
| | | import cn.hutool.crypto.digest.DigestUtil; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.data.redis.core.StringRedisTemplate; |
| | | import org.springframework.stereotype.Service; |
| | | import java.time.Duration; |
| | | |
| | | /** ç»å®å°æ¬æ¬¡ä¼ä¸å¾®ä¿¡æ«ç ç»å½ä»¤çççæå¯ç 设置ææã */ |
| | | @Service |
| | | public class WxcpPasswordSetupGrant { |
| | | @Resource private StringRedisTemplate redis; |
| | | private String key(String token) { return "wxcp:password-setup:" + DigestUtil.sha256Hex(token); } |
| | | public void issue(String token, Long userId) { |
| | | redis.opsForValue().set(key(token), userId.toString(), Duration.ofMinutes(10)); |
| | | } |
| | | public boolean valid(String token, Long userId) { |
| | | return token != null && userId != null && userId.toString().equals(redis.opsForValue().get(key(token))); |
| | | } |
| | | public void revoke(String token) { redis.delete(key(token)); } |
| | | } |
| | |
| | | */ |
| | | public interface SocialUserService { |
| | | |
| | | /** 审æ¹éè¿åç»å®å·²è®¤è¯çä¼ä¸å¾®ä¿¡èº«ä»½ï¼ä¸å¾è¦çå
¶ä»ç¨æ·çç»å®ã */ |
| | | void bindApprovedWxcpUser(String openid, Long userId); |
| | | |
| | | /** |
| | | * è·å¾æå®ç¨æ·çç¤¾äº¤ç¨æ·å表 |
| | | * |
| | |
| | | private SocialClientService socialClientService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void bindApprovedWxcpUser(String openid, Long userId) { |
| | | int type = 30; |
| | | int userType = cn.iocoder.yudao.framework.common.enums.UserTypeEnum.ADMIN.getValue(); |
| | | SocialUserDO socialUser = socialUserMapper.selectByTypeAndOpenid(type, openid); |
| | | if (socialUser == null) throw new IllegalStateException("ä¼ä¸å¾®ä¿¡èº«ä»½ä¸åå¨ï¼è¯·éæ°æ«ç "); |
| | | SocialUserBindDO existing = socialUserBindMapper.selectByUserTypeAndSocialUserId(userType, socialUser.getId()); |
| | | if (existing != null) { |
| | | if (!existing.getUserId().equals(userId)) throw new IllegalStateException("该ä¼ä¸å¾®ä¿¡èº«ä»½å·²ç»å®å
¶ä»è´¦å·"); |
| | | return; |
| | | } |
| | | if (socialUserBindMapper.selectByUserIdAndUserTypeAndSocialType(userId, userType, type) != null) |
| | | throw new IllegalStateException("该账å·å·²ç»å®å
¶ä»ä¼ä¸å¾®ä¿¡èº«ä»½"); |
| | | socialUserBindMapper.insert(SocialUserBindDO.builder().userId(userId).userType(userType) |
| | | .socialUserId(socialUser.getId()).socialType(type).build()); |
| | | } |
| | | |
| | | @Override |
| | | public List<SocialUserDO> getSocialUserList(Long userId, Integer userType) { |
| | | // è·å¾ç»å® |
| | | List<SocialUserBindDO> socialUserBinds = socialUserBindMapper.selectListByUserIdAndUserType(userId, userType); |
| | |
| | | // è·å¾ç»å®ç¨æ· |
| | | SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserTypeAndSocialUserId(userType, |
| | | socialUser.getId()); |
| | | return new SocialUserRespDTO(socialUser.getOpenid(), socialUser.getNickname(), socialUser.getAvatar(), |
| | | SocialUserRespDTO result = new SocialUserRespDTO(socialUser.getOpenid(), socialUser.getNickname(), socialUser.getAvatar(), |
| | | socialUserBind != null ? socialUserBind.getUserId() : null); |
| | | if (socialType == 30 && cn.hutool.core.util.StrUtil.isNotBlank(socialUser.getRawUserInfo())) { |
| | | try { |
| | | cn.hutool.json.JSONObject raw = cn.hutool.json.JSONUtil.parseObj(socialUser.getRawUserInfo()); |
| | | result.setMobile(raw.getStr("mobile")); |
| | | if (cn.hutool.core.util.StrUtil.isBlank(result.getNickname())) result.setNickname(raw.getStr("name")); |
| | | } catch (Exception ignored) { |
| | | // ææä¿¡æ¯ä¸å¯ç¨æ¶ä¸å½±åæ«ç ï¼ç³è¯·ä½¿ç¨éæºè´¦å·æ¹æ¡ã |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; |
| | | |
| | |
| | | |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | |
| | | @GetMapping("/fee-confirm-available") |
| | | @Operation(summary = "夿å½åç¨æ·è½å¦ç¡®è®¤ç¼´è´¹ç¶æ") |
| | | public CommonResult<Boolean> canConfirmFee(@RequestParam("id") Long id) { |
| | | return success(applicationService.canConfirmFee(id, getLoginUserId())); |
| | | } |
| | | |
| | | @PutMapping("/confirm-fee") |
| | | @Operation(summary = "å½å审æ¹äººç¡®è®¤ç¼´è´¹ç¶æ") |
| | | public CommonResult<Boolean> confirmFee(@RequestParam("id") Long id, @RequestParam("feePaid") Boolean feePaid) { |
| | | applicationService.confirmFee(id, getLoginUserId(), feePaid); |
| | | return success(true); |
| | | } |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建å
¥ä¼ç³è¯·") |
| | |
| | | }); |
| | | } |
| | | |
| | | } |
| | | } |
| | |
| | | @Schema(description = "ç³è¯·ç级", example = "1") |
| | | private String memberGrade; |
| | | |
| | | @Schema(description = "æ¯å¦ç¼´è´¹") |
| | | private Boolean feePaid; |
| | | |
| | | @Schema(description = "å°åºç¼ç ", example = "110000") |
| | | private String regionCode; |
| | | |
| | |
| | | @Schema(description = "æ¯å¦æéä»¶") |
| | | private Boolean hasAttachment; |
| | | |
| | | } |
| | | } |
| | |
| | | @Schema(description = "ç³è¯·ç级", example = "1") |
| | | private String memberGrade; |
| | | |
| | | @Schema(description = "æ¯å¦ç¼´è´¹") |
| | | private Boolean feePaid; |
| | | |
| | | @Schema(description = "å°åºç¼ç ï¼Area æ æ«çº§ï¼", example = "110000") |
| | | private String regionCode; |
| | | |
| | |
| | | @Schema(description = "éä»¶BlobIdå表") |
| | | private List<Long> blobIds; |
| | | |
| | | } |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.UmsMemberProfileChangeRespVO; |
| | | import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.UmsMemberProfileChangeSaveReqVO; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberProfileChangeDO; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberDO; |
| | | import cn.iocoder.yudao.module.ums.service.member.UmsMemberProfileChangeService; |
| | | import cn.iocoder.yudao.module.ums.service.member.WxcpInitialPasswordService; |
| | | import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.WxcpInitialPasswordReqVO; |
| | | import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; |
| | | import cn.iocoder.yudao.framework.security.config.SecurityProperties; |
| | | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| | | import jakarta.servlet.http.HttpServletRequest; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | @Tag(name = "管çåå° - ä¼åèµæèªä¸»ç»´æ¤") |
| | | @RestController |
| | |
| | | |
| | | @Resource |
| | | private UmsMemberProfileChangeService profileChangeService; |
| | | @Resource private WxcpInitialPasswordService initialPasswordService; |
| | | @Resource private SecurityProperties securityProperties; |
| | | |
| | | @PostMapping("/initial-password") |
| | | @Operation(summary = "ä¼ä¸å¾®ä¿¡æ«ç ä¼å馿¬¡è®¾ç½®å¯ç ") |
| | | @ApiAccessLog(requestEnable = false) |
| | | public CommonResult<String> setInitialPassword(@Valid @RequestBody WxcpInitialPasswordReqVO req, HttpServletRequest request) { |
| | | String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader(), securityProperties.getTokenParameter()); |
| | | return success(initialPasswordService.setPassword(getLoginUserId(), token, req.getPassword())); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾å½åç»å½ä¼åçä¸ªäººèµæ") |
| | | public CommonResult<UmsMemberRespVO> getMyProfile() { |
| | | return success(BeanUtils.toBean(profileChangeService.getMyProfile(), UmsMemberRespVO.class)); |
| | | UmsMemberDO member = profileChangeService.getMyProfile(); |
| | | return success(member == null ? null : BeanUtils.toBean(member, UmsMemberRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/change/latest") |
| | |
| | | @Schema(description = "æ¯å¦ç¼´çº³ä¼è´¹", example = "true") |
| | | private Boolean feePaid; |
| | | |
| | | @Schema(description = "æ¯å¦éè¦é¦æ¬¡è®¾ç½®å¯ç ") |
| | | private Boolean passwordSetupRequired; |
| | | |
| | | @Schema(description = "缴费年度", example = "2026") |
| | | private Integer feeYear; |
| | | |
| | |
| | | /** æ¯å¦æéä»¶ï¼é讯å½/å表ç¨ï¼ */ |
| | | private Boolean hasAttachment; |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile; |
| | | |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import lombok.Data; |
| | | import org.hibernate.validator.constraints.Length; |
| | | |
| | | @Data |
| | | public class WxcpInitialPasswordReqVO { |
| | | @NotBlank |
| | | @Length(min = 8, max = 16, message = "å¯ç é¿åº¦é¡»ä¸º 8 è³ 16 ä½") |
| | | private String password; |
| | | } |
| | |
| | | /** ç³è¯·ççº§ï¼æ member_type åä¸ååå
¸ï¼ */ |
| | | private String memberGrade; |
| | | |
| | | /** ç³è¯·æ¶æ¯å¦ç¼´è´¹ */ |
| | | private Boolean feePaid; |
| | | |
| | | /** 馿¬¡æ«ç ç³è¯·å¯¹åºçä¼ä¸å¾®ä¿¡èº«ä»½ */ |
| | | private String wxcpOpenid; |
| | | |
| | | /** å°åºç¼ç ï¼Area æ æ«çº§ï¼ */ |
| | | private String regionCode; |
| | | |
| | |
| | | /** 夿³¨ */ |
| | | private String remark; |
| | | |
| | | } |
| | | } |
| | |
| | | /** æ¯å¦ç¼´çº³ä¼è´¹ */ |
| | | private Boolean feePaid; |
| | | |
| | | /** æ¯å¦éè¦é¦æ¬¡è®¾ç½®å¯ç */ |
| | | private Boolean passwordSetupRequired; |
| | | |
| | | /** 缴费年度ï¼å¦ 2026ï¼ */ |
| | | private Integer feeYear; |
| | | |
| | |
| | | /** 夿³¨ */ |
| | | private String remark; |
| | | |
| | | } |
| | | } |
| | |
| | | return selectOne(UmsMemberDO::getUserId, userId); |
| | | } |
| | | |
| | | default UmsMemberDO selectByApplicationId(Long applicationId) { |
| | | return selectOne(UmsMemberDO::getApplicationId, applicationId); |
| | | } |
| | | |
| | | default PageResult<UmsMemberDO> selectPage(UmsMemberPageReqVO reqVO) { |
| | | return selectPage(reqVO, new LambdaQueryWrapperX<UmsMemberDO>() |
| | | .likeIfPresent(UmsMemberDO::getNo, reqVO.getNo()) |
| | |
| | | .orderByDesc(UmsMemberDO::getId)); |
| | | } |
| | | |
| | | } |
| | | } |
| | |
| | | ErrorCode APPLICATION_DELETE_FAIL_NOT_DRAFT = new ErrorCode(1_030_108_003, "å
¥ä¼ç³è¯·({})䏿¯èç¨¿ç¶æï¼æ æ³å é¤"); |
| | | ErrorCode APPLICATION_SUBMIT_FAIL_STATUS = new ErrorCode(1_030_108_004, "æäº¤å®¡æ¹å¤±è´¥ï¼åªæè稿æå·²é©³åç¶æçå
¥ä¼ç³è¯·æè½æäº¤"); |
| | | ErrorCode APPLICATION_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_108_005, "å
¥ä¼å®¡æ¹åç±»ä¸åå¨ï¼è¯·å
å¨BPM模åå建å
¥ä¼å®¡æ¹æµç¨åç±»"); |
| | | ErrorCode APPLICATION_FEE_CONFIRM_REQUIRED = new ErrorCode(1_030_108_006, "请å
æ ¸å®å¹¶å¡«åæ¯å¦ç¼´çº³ä¼è´¹ï¼åæäº¤å®¡æ¹"); |
| | | ErrorCode APPLICATION_WXCP_INFO_REQUIRED = new ErrorCode(1_030_108_007, "请å®åä¼åççº§åææææºå·ï¼ä¼ä¸å¾®ä¿¡æ«ç ç³è¯·å¯ä¸å¡«ææºå·"); |
| | | ErrorCode APPLICATION_WXCP_DELETE_FORBIDDEN = new ErrorCode(1_030_108_008, "æ«ç 产ççå
¥ä¼ç³è¯·ä¸å¯å é¤ï¼è¯·å®ååæäº¤å®¡æ¹"); |
| | | ErrorCode APPLICATION_FEE_CONFIRM_FORBIDDEN = new ErrorCode(1_030_108_009, "åªæå½å审æ¹äººå¯ä»¥ç¡®è®¤ç¼´è´¹ç¶æ"); |
| | | |
| | | // ========== ä¼åï¼1-030-108-100 ~ 108-109ï¼ ========== |
| | | ErrorCode MEMBER_NOT_EXISTS = new ErrorCode(1_030_108_100, "ä¼åä¸åå¨"); |
| | |
| | | ErrorCode MEMBER_ARCHIVE_FAIL_STATUS = new ErrorCode(1_030_108_105, "彿¡£å¤±è´¥ï¼ä¼å({})å½åç¶æä¸ºã{}ãï¼åªæãæ£å¸¸ãç¶æçä¼åæè½å½æ¡£å°æ¡£æ¡ç®¡çï¼è¯·å
å¯ç¨è¯¥ä¼å"); |
| | | ErrorCode MEMBER_ALREADY_ARCHIVED = new ErrorCode(1_030_108_106, "éå¤å½æ¡£ï¼ä¼å({})å·²äºæ¤å彿¡£å¹¶çææ¡£æ¡(ç¼å·={})ï¼è¯·å¿éå¤å½æ¡£ï¼å¦éæ¥ç请å徿¡£æ¡ç®¡ç模å"); |
| | | ErrorCode MEMBER_FEE_YEAR_REQUIRED = new ErrorCode(1_030_108_107, "ä¼è´¹ç¼´çº³å¹´åº¦ä¸ºç©ºï¼å·²æ 记为ã已缴纳ä¼è´¹ãï¼è¯·éæ©ç¼´è´¹å¹´åº¦ï¼ä¾å¦ 2026ï¼ååä¿å"); |
| | | ErrorCode MEMBER_WXCP_ROLE_NOT_AVAILABLE = new ErrorCode(1_030_108_120, "ä¼ä¸å¾®ä¿¡æ«ç ç¨æ·è§è²ä¸å卿已åç¨ï¼è¯·è系管çåé
ç½®è§è²ç¼ç wxcp_user"); |
| | | ErrorCode MEMBER_PASSWORD_SETUP_EXPIRED = new ErrorCode(1_030_108_121, "æ«ç ææå·²è¿æï¼è¯·éæ°æ«ç ç»å½å设置å¯ç "); |
| | | ErrorCode MEMBER_PASSWORD_SETUP_NOT_REQUIRED = new ErrorCode(1_030_108_122, "å½åè´¦å·æ é馿¬¡è®¾ç½®å¯ç ï¼è¯·å¨ä¸ªäººä¸å¿ä¿®æ¹å¯ç "); |
| | | |
| | | // ========== ä¼åèµæåæ´ç³è¯·ï¼1-030-108-108 ~ 108-119ï¼ ========== |
| | | ErrorCode MEMBER_PROFILE_NOT_LINKED = new ErrorCode(1_030_108_108, "å½åç»å½è´¦å·æªå
³èä¼åï¼æ æ³ç»´æ¤ä¸ªäººèµæï¼è¯·èç³»å¦ä¼ç®¡çåç»å®ä¼åè´¦å·"); |
| | |
| | | ErrorCode MEMBER_PROFILE_CHANGE_REJECT_REASON_REQUIRED = new ErrorCode(1_030_108_112, "驳åèµæåæ´ç³è¯·æ¶å¿
须填å驳ååå ï¼è¯·è¡¥å
åå ååæä½"); |
| | | ErrorCode MEMBER_PROFILE_CHANGE_EMPTY = new ErrorCode(1_030_108_113, "èµææªåçä»»ä½åæ´ï¼æ éæäº¤å®¡æ ¸"); |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ums.service.application; |
| | | |
| | | import cn.iocoder.yudao.module.bpm.service.task.BpmTaskApprovalValidator; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.application.UmsApplicationDO; |
| | | import cn.iocoder.yudao.module.ums.dal.mysql.application.UmsApplicationMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | import org.flowable.task.api.Task; |
| | | import org.springframework.stereotype.Component; |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.ums.enums.ErrorCodeConstants.*; |
| | | |
| | | @Component |
| | | public class UmsApplicationApprovalValidator implements BpmTaskApprovalValidator { |
| | | @Resource private UmsApplicationMapper mapper; |
| | | |
| | | @Override |
| | | public void validate(Long userId, Task task, ProcessInstance instance) { |
| | | if (!"ums_membership_approve".equals(instance.getProcessDefinitionKey())) return; |
| | | // å起人èç¹ç± BPM 卿µç¨å¯å¨æ¶èªå¨æäº¤ï¼ç¼´è´¹ç±åç»å®¡æ¹äººç¡®è®¤ã |
| | | if ("StartUserNode".equals(task.getTaskDefinitionKey())) return; |
| | | Long id; |
| | | try { id = Long.valueOf(instance.getBusinessKey()); } |
| | | catch (Exception e) { throw exception(APPLICATION_NOT_EXISTS); } |
| | | UmsApplicationDO application = mapper.selectById(id); |
| | | if (application == null) throw exception(APPLICATION_NOT_EXISTS); |
| | | if (application.getFeePaid() == null) throw exception(APPLICATION_FEE_CONFIRM_REQUIRED); |
| | | } |
| | | } |
| | |
| | | |
| | | void submitApplication(Long id, String processDefinitionKey); |
| | | |
| | | boolean canConfirmFee(Long id, Long userId); |
| | | void confirmFee(Long id, Long userId, boolean feePaid); |
| | | |
| | | List<Map<String, Object>> getApproveProcessDefinitionList(); |
| | | |
| | | void updateApplicationAuditStatus(Long id, Integer bpmResult); |
| | | |
| | | void createMemberFromApplicationOnApproved(Long id); |
| | | |
| | | } |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| | | import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; |
| | | import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; |
| | | import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import cn.iocoder.yudao.module.ums.controller.admin.application.vo.UmsApplicationPageReqVO; |
| | | import cn.iocoder.yudao.module.ums.controller.admin.application.vo.UmsApplicationSaveReqVO; |
| | |
| | | |
| | | @Resource |
| | | private UmsMemberService memberService; |
| | | @Resource private BpmTaskService bpmTaskService; |
| | | |
| | | @Override |
| | | public boolean canConfirmFee(Long id, Long userId) { |
| | | UmsApplicationDO application = validateApplicationExists(id); |
| | | return userId != null && UmsApplicationStatusEnum.APPROVING.getStatus().equals(application.getStatus()) |
| | | && StrUtil.isNotBlank(application.getProcessInstanceId()) |
| | | && bpmTaskService.getTodoTask(userId, null, application.getProcessInstanceId()) != null; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void confirmFee(Long id, Long userId, boolean feePaid) { |
| | | if (!canConfirmFee(id, userId)) throw exception(APPLICATION_FEE_CONFIRM_FORBIDDEN); |
| | | int updated = applicationMapper.update(null, new LambdaUpdateWrapper<UmsApplicationDO>() |
| | | .eq(UmsApplicationDO::getId, id) |
| | | .eq(UmsApplicationDO::getStatus, UmsApplicationStatusEnum.APPROVING.getStatus()) |
| | | .set(UmsApplicationDO::getFeePaid, feePaid)); |
| | | if (updated != 1) throw exception(APPLICATION_SUBMIT_FAIL_STATUS); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteApplication(Long id) { |
| | | UmsApplicationDO application = validateApplicationExists(id); |
| | | if (StrUtil.isNotBlank(application.getWxcpOpenid())) { |
| | | throw exception(APPLICATION_WXCP_DELETE_FORBIDDEN); |
| | | } |
| | | if (!UmsApplicationStatusEnum.DRAFT.getStatus().equals(application.getStatus())) { |
| | | throw exception(APPLICATION_DELETE_FAIL_NOT_DRAFT, application.getNo()); |
| | | } |
| | |
| | | if (!UmsApplicationStatusEnum.DRAFT.getStatus().equals(status) |
| | | && !UmsApplicationStatusEnum.REJECTED.getStatus().equals(status)) { |
| | | throw exception(APPLICATION_SUBMIT_FAIL_STATUS); |
| | | } |
| | | if (application.getFeePaid() == null) { |
| | | throw exception(APPLICATION_FEE_CONFIRM_REQUIRED); |
| | | } |
| | | if (StrUtil.isBlank(application.getMemberGrade()) |
| | | || (StrUtil.isBlank(application.getContactPhone()) && StrUtil.isBlank(application.getWxcpOpenid())) |
| | | || (StrUtil.isNotBlank(application.getContactPhone()) |
| | | && !application.getContactPhone().trim().matches("^1[3-9]\\d{9}$"))) { |
| | | throw exception(APPLICATION_WXCP_INFO_REQUIRED); |
| | | } |
| | | |
| | | String processInstanceId = processInstanceApi.createProcessInstance(getLoginUserId(), |
| | |
| | | |
| | | Integer newStatus = convertBpmResultToStatus(bpmResult); |
| | | if (newStatus != null) { |
| | | if (UmsApplicationStatusEnum.APPROVED.getStatus().equals(newStatus) && application.getFeePaid() == null) { |
| | | throw exception(APPLICATION_FEE_CONFIRM_REQUIRED); |
| | | } |
| | | applicationMapper.updateById(new UmsApplicationDO() |
| | | .setId(id).setStatus(newStatus)); |
| | | if (UmsApplicationStatusEnum.APPROVED.getStatus().equals(newStatus)) { |
| | | memberService.createMemberFromApplication(application); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | memberService.createMemberFromApplication(application); |
| | | } |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ums.service.application; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import cn.iocoder.yudao.module.system.service.auth.WxcpMembershipRegistration; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.application.UmsApplicationDO; |
| | | import cn.iocoder.yudao.module.ums.dal.mysql.application.UmsApplicationMapper; |
| | | import cn.iocoder.yudao.module.ums.dal.redis.UmsNoRedisDAO; |
| | | import cn.iocoder.yudao.module.ums.enums.UmsApplicationStatusEnum; |
| | | import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; |
| | | import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; |
| | | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
| | | import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; |
| | | import cn.iocoder.yudao.module.system.service.permission.PermissionService; |
| | | import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import java.util.Collections; |
| | | import java.util.UUID; |
| | | |
| | | @Service |
| | | public class WxcpMembershipRegistrationImpl implements WxcpMembershipRegistration { |
| | | @Resource private UmsApplicationMapper mapper; |
| | | @Resource private UmsNoRedisDAO noRedisDAO; |
| | | @Resource private BpmProcessInstanceApi processInstanceApi; |
| | | @Resource private AdminUserService userService; |
| | | @Resource private PermissionService permissionService; |
| | | |
| | | @Override |
| | | public boolean hasPendingApplication(String openid) { |
| | | UmsApplicationDO application = mapper.selectOne(new LambdaQueryWrapperX<UmsApplicationDO>() |
| | | .eq(UmsApplicationDO::getWxcpOpenid, openid)); |
| | | return application != null && !UmsApplicationStatusEnum.APPROVED.getStatus().equals(application.getStatus()); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @DataPermission(enable = false) |
| | | public void startIfAbsent(String openid, String nickname, String mobile) { |
| | | if (StrUtil.isBlank(openid)) throw new IllegalArgumentException("ä¼ä¸å¾®ä¿¡èº«ä»½ä¸ºç©º"); |
| | | UmsApplicationDO existing = mapper.selectOne(new LambdaQueryWrapperX<UmsApplicationDO>() |
| | | .eq(UmsApplicationDO::getWxcpOpenid, openid)); |
| | | if (existing != null) { |
| | | // å
¼å®¹æ§çæ¬é¦æ¬¡æ«ç 产ççè稿ï¼å·²å¨å®¡æ¹æå·²ç»æçç³è¯·ä¸éå¤åèµ·ã |
| | | if (UmsApplicationStatusEnum.DRAFT.getStatus().equals(existing.getStatus()) |
| | | && StrUtil.isBlank(existing.getProcessInstanceId())) { |
| | | existing.setStatus(UmsApplicationStatusEnum.APPROVING.getStatus()); |
| | | if (StrUtil.isBlank(existing.getMemberGrade())) existing.setMemberGrade("1"); |
| | | if (StrUtil.isBlank(existing.getContactPhone()) && StrUtil.isNotBlank(mobile) |
| | | && mobile.trim().matches("^1[3-9]\\d{9}$")) existing.setContactPhone(mobile.trim()); |
| | | int claimed = mapper.update(null, new LambdaUpdateWrapper<UmsApplicationDO>() |
| | | .eq(UmsApplicationDO::getId, existing.getId()) |
| | | .eq(UmsApplicationDO::getStatus, UmsApplicationStatusEnum.DRAFT.getStatus()) |
| | | .set(UmsApplicationDO::getStatus, UmsApplicationStatusEnum.APPROVING.getStatus()) |
| | | .set(UmsApplicationDO::getMemberGrade, existing.getMemberGrade()) |
| | | .set(UmsApplicationDO::getContactPhone, existing.getContactPhone())); |
| | | if (claimed == 1) startProcess(existing); |
| | | } |
| | | return; |
| | | } |
| | | UmsApplicationDO application = new UmsApplicationDO(); |
| | | application.setNo(noRedisDAO.generate(UmsNoRedisDAO.APPLICATION_NO_PREFIX)); |
| | | application.setWxcpOpenid(openid); |
| | | application.setApplicantName(StrUtil.maxLength(StrUtil.blankToDefault(nickname, "å¾
å®å"), 64)); |
| | | application.setMemberType(1); |
| | | application.setMemberGrade("1"); |
| | | if (StrUtil.isNotBlank(mobile) && mobile.trim().matches("^1[3-9]\\d{9}$")) { |
| | | application.setContactPhone(mobile.trim()); |
| | | } |
| | | application.setStatus(UmsApplicationStatusEnum.APPROVING.getStatus()); |
| | | mapper.insert(application); |
| | | startProcess(application); |
| | | } |
| | | |
| | | private void startProcess(UmsApplicationDO application) { |
| | | Long initiatorId = getServiceUserId(); |
| | | String processId = processInstanceApi.createProcessInstance(initiatorId, |
| | | new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey("ums_membership_approve") |
| | | .setBusinessKey(String.valueOf(application.getId()))); |
| | | mapper.updateById(new UmsApplicationDO().setId(application.getId()).setProcessInstanceId(processId)); |
| | | } |
| | | |
| | | private Long getServiceUserId() { |
| | | String username = "wxcpservice"; |
| | | AdminUserDO existing = userService.getUserByUsername(username); |
| | | if (existing != null) { |
| | | if (cn.iocoder.yudao.framework.common.enums.CommonStatusEnum.isDisable(existing.getStatus())) |
| | | throw new IllegalStateException("å
¥ä¼å®¡æ¹æå¡è´¦å·å·²åç¨"); |
| | | if (!"ä»
ç¨äºç³»ç»èªå¨åèµ·å
¥ä¼å®¡æ¹".equals(existing.getRemark()) |
| | | || !permissionService.getUserRoleIdListByUserId(existing.getId()).isEmpty()) |
| | | throw new IllegalStateException("å
¥ä¼å®¡æ¹æå¡è´¦å·é
ç½®ä¸ç¬¦åè¦æ±ï¼è¯·è系管çåæ ¸æ¥"); |
| | | return existing.getId(); |
| | | } |
| | | UserSaveReqVO req = new UserSaveReqVO(); |
| | | req.setUsername(username); |
| | | req.setNickname("å
¥ä¼å®¡æ¹æå¡"); |
| | | req.setPassword(UUID.randomUUID().toString().replace("-", "").substring(0, 16)); |
| | | req.setPostIds(Collections.emptySet()); |
| | | req.setRoleIds(Collections.emptySet()); |
| | | req.setRemark("ä»
ç¨äºç³»ç»èªå¨åèµ·å
¥ä¼å®¡æ¹"); |
| | | return userService.createUser(req); |
| | | } |
| | | } |
| | |
| | | log.info("[onEvent] å
¥ä¼ç³è¯·({}) 审æ¹ç¶ææ´æ°å®æ", applicationId); |
| | | } catch (Exception e) { |
| | | log.error("[onEvent] æ´æ°å
¥ä¼ç³è¯·å®¡æ¹ç¶æå¤±è´¥ï¼applicationId={}", applicationId, e); |
| | | return; |
| | | throw e; |
| | | } |
| | | |
| | | // 2. 审æ¹éè¿åèªå¨å建ä¼å |
| | | if (cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE.getStatus().equals(event.getStatus())) { |
| | | try { |
| | | applicationService.createMemberFromApplicationOnApproved(applicationId); |
| | | log.info("[onEvent] å
¥ä¼ç³è¯·({}) 审æ¹éè¿ï¼ä¼ååå»ºå®æ", applicationId); |
| | | } catch (Exception e) { |
| | | log.error("[onEvent] å
¥ä¼ç³è¯·({}) 审æ¹éè¿åå建ä¼å失败", applicationId, e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| | |
| | | |
| | | @Override |
| | | public UmsMemberDO getMyProfile() { |
| | | return getLoginMember(); |
| | | Long userId = getLoginUserId(); |
| | | return userId != null ? memberMapper.selectByUserId(userId) : null; |
| | | } |
| | | |
| | | @Override |
| | |
| | | import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO; |
| | | import cn.iocoder.yudao.module.system.service.social.SocialUserService; |
| | | import cn.iocoder.yudao.module.system.service.permission.RoleService; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.module.ums.controller.admin.member.vo.UmsMemberPageReqVO; |
| | | import cn.iocoder.yudao.module.ums.controller.admin.member.vo.UmsMemberSaveReqVO; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.application.UmsApplicationDO; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberDO; |
| | | import cn.iocoder.yudao.module.ums.dal.mysql.application.UmsApplicationMapper; |
| | | import cn.iocoder.yudao.module.ums.dal.mysql.member.UmsMemberMapper; |
| | | import cn.iocoder.yudao.module.ums.dal.redis.UmsNoRedisDAO; |
| | | import jakarta.annotation.Resource; |
| | |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import java.util.UUID; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | |
| | | private static final Long MEMBER_DEPT_ID = 129L; |
| | | |
| | | /** |
| | | * æ®éä¼åè§è² IDï¼system_roleãæ®éä¼åãï¼ |
| | | * |
| | | * <p>该è§è²åªåé
ãæçèµæãèåï¼ä¸å
嫿¡£æ¡ç®¡ççåå°èåï¼ç¨äºå®ç°æéé离ã |
| | | * ä¼ä¸å¾®ä¿¡æ«ç ç¨æ·è§è²ç¼ç ï¼æç¼ç æ¥è¯¢ï¼é¿å
ä¸åç¯å¢è§è²ç¼å·ä¸ä¸è´ã |
| | | */ |
| | | private static final Long MEMBER_ROLE_ID = 9100L; |
| | | private static final String MEMBER_ROLE_CODE = "wxcp_user"; |
| | | |
| | | public static final String MEMBER_RECORD_TYPE = "ums_member"; |
| | | |
| | |
| | | |
| | | @Resource |
| | | private UmsMemberMapper memberMapper; |
| | | @Resource |
| | | private UmsApplicationMapper applicationMapper; |
| | | @Resource |
| | | private SocialUserService socialUserService; |
| | | @Resource |
| | | private RoleService roleService; |
| | | |
| | | @Resource |
| | | private UmsNoRedisDAO noRedisDAO; |
| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public UmsMemberDO createMemberFromApplication(UmsApplicationDO application) { |
| | | UmsMemberDO existing = memberMapper.selectByApplicationId(application.getId()); |
| | | if (existing != null) return existing; |
| | | // 1. çæä¼åç¼å· |
| | | String no = noRedisDAO.generate(UmsNoRedisDAO.MEMBER_NO_PREFIX); |
| | | if (memberMapper.selectByNo(no) != null) { |
| | |
| | | |
| | | // 2. å建系ç»ç¨æ·è´¦å· |
| | | Long userId = createSystemUser(application); |
| | | if (StrUtil.isNotBlank(application.getWxcpOpenid())) { |
| | | socialUserService.bindApprovedWxcpUser(application.getWxcpOpenid(), userId); |
| | | } |
| | | |
| | | // 3. å建ä¼åè®°å½ |
| | | UmsMemberDO member = UmsMemberDO.builder() |
| | |
| | | .applicationId(application.getId()) |
| | | .memberType(application.getMemberType()) |
| | | .memberGrade(application.getMemberGrade()) |
| | | .feePaid(application.getFeePaid()) |
| | | .passwordSetupRequired(StrUtil.isBlank(application.getContactPhone())) |
| | | .name(application.getApplicantName()) |
| | | .contactPhone(application.getContactPhone()) |
| | | .contactEmail(application.getContactEmail()) |
| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateMember(UmsMemberSaveReqVO updateReqVO) { |
| | | validateMemberExists(updateReqVO.getId()); |
| | | UmsMemberDO existingMember = validateMemberExists(updateReqVO.getId()); |
| | | validateMemberGrade(updateReqVO.getMemberType(), updateReqVO.getMemberGrade()); |
| | | validateFeePaid(updateReqVO); |
| | | |
| | |
| | | |
| | | UmsMemberDO updateObj = BeanUtils.toBean(updateReqVO, UmsMemberDO.class); |
| | | memberMapper.updateById(updateObj); |
| | | if (existingMember.getApplicationId() != null && updateReqVO.getFeePaid() != null) { |
| | | applicationMapper.updateById(new UmsApplicationDO() |
| | | .setId(existingMember.getApplicationId()).setFeePaid(updateReqVO.getFeePaid())); |
| | | } |
| | | if (clearFeeYear) { |
| | | memberMapper.update(null, new LambdaUpdateWrapper<UmsMemberDO>() |
| | | .eq(UmsMemberDO::getId, updateReqVO.getId()) |
| | |
| | | |
| | | private Long createSystemUser(UmsApplicationDO application) { |
| | | // ææºå·å¯ä½ä¸ºç¨æ·å/å¯ç ï¼åè HRM å工模å¼ï¼ |
| | | String mobile = StrUtil.blankToDefault(application.getContactPhone(), "unknown"); |
| | | String username = mobile; |
| | | String nickname = application.getApplicantName(); |
| | | String mobile = StrUtil.trim(application.getContactPhone()); |
| | | if ((StrUtil.isBlank(mobile) && StrUtil.isBlank(application.getWxcpOpenid())) |
| | | || (StrUtil.isNotBlank(mobile) && !mobile.matches("^1[3-9]\\d{9}$"))) { |
| | | throw exception(APPLICATION_WXCP_INFO_REQUIRED); |
| | | } |
| | | RoleDO role = roleService.getRoleByCode(MEMBER_ROLE_CODE); |
| | | if (role == null || CommonStatusEnum.isDisable(role.getStatus())) { |
| | | throw exception(MEMBER_WXCP_ROLE_NOT_AVAILABLE); |
| | | } |
| | | boolean hasMobile = StrUtil.isNotBlank(mobile); |
| | | String username = hasMobile ? mobile : "wx" + UUID.randomUUID().toString().replace("-", "").substring(0, 16); |
| | | String password = hasMobile ? mobile : UUID.randomUUID().toString().replace("-", "").substring(0, 16); |
| | | String nickname = StrUtil.maxLength(application.getApplicantName(), 30); |
| | | |
| | | try { |
| | | return adminUserApi.createUser(new AdminUserCreateReqDTO() |
| | |
| | | .setNickname(nickname) |
| | | .setDeptId(MEMBER_DEPT_ID) |
| | | .setPostIds(Collections.emptySet()) |
| | | .setRoleIds(Collections.singleton(MEMBER_ROLE_ID)) |
| | | .setMobile(mobile) |
| | | .setPassword(mobile) // é»è®¤å¯ç ä¸ºææºå· |
| | | .setRoleIds(Collections.singleton(role.getId())) |
| | | .setMobile(hasMobile ? mobile : null) |
| | | .setPassword(password) |
| | | .setEmail(application.getContactEmail()) |
| | | .setRemark("ç±å
¥ä¼ç³è¯·èªå¨å建ï¼" + application.getNo())); |
| | | } catch (Exception e) { |
| | |
| | | return archive != null ? archive.getNo() : null; |
| | | } |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ums.service.member; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import cn.iocoder.yudao.module.system.service.auth.WxcpPasswordSetupGrant; |
| | | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberDO; |
| | | import cn.iocoder.yudao.module.ums.dal.mysql.member.UmsMemberMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.ums.enums.ErrorCodeConstants.*; |
| | | |
| | | @Service |
| | | public class WxcpInitialPasswordService { |
| | | @Resource private UmsMemberMapper memberMapper; |
| | | @Resource private AdminUserService userService; |
| | | @Resource private WxcpPasswordSetupGrant grant; |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public String setPassword(Long userId, String token, String password) { |
| | | if (!grant.valid(token, userId)) throw exception(MEMBER_PASSWORD_SETUP_EXPIRED); |
| | | UmsMemberDO member = memberMapper.selectByUserId(userId); |
| | | if (member == null || !Boolean.TRUE.equals(member.getPasswordSetupRequired())) { |
| | | throw exception(MEMBER_PASSWORD_SETUP_NOT_REQUIRED); |
| | | } |
| | | int changed = memberMapper.update(null, new LambdaUpdateWrapper<UmsMemberDO>() |
| | | .eq(UmsMemberDO::getId, member.getId()) |
| | | .eq(UmsMemberDO::getUserId, userId) |
| | | .eq(UmsMemberDO::getPasswordSetupRequired, true) |
| | | .set(UmsMemberDO::getPasswordSetupRequired, false)); |
| | | if (changed != 1) throw exception(MEMBER_PASSWORD_SETUP_NOT_REQUIRED); |
| | | userService.updateUserPassword(userId, password); |
| | | String username = userService.getUser(userId).getUsername(); |
| | | grant.revoke(token); |
| | | return username; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ums.service.application; |
| | | |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.application.UmsApplicationDO; |
| | | import cn.iocoder.yudao.module.ums.dal.mysql.application.UmsApplicationMapper; |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | import org.flowable.task.api.Task; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.InjectMocks; |
| | | import org.mockito.Mock; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class UmsApplicationApprovalValidatorTest extends BaseMockitoUnitTest { |
| | | @InjectMocks private UmsApplicationApprovalValidator validator; |
| | | @Mock private UmsApplicationMapper mapper; |
| | | @Mock private ProcessInstance instance; |
| | | @Mock private Task task; |
| | | |
| | | @Test void requiresConfirmedFeeBeforeApproval() { |
| | | when(instance.getProcessDefinitionKey()).thenReturn("ums_membership_approve"); |
| | | when(instance.getBusinessKey()).thenReturn("12"); |
| | | when(mapper.selectById(12L)).thenReturn(new UmsApplicationDO()); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(1L, task, instance)); |
| | | } |
| | | |
| | | @Test void permitsExplicitUnpaid() { |
| | | when(instance.getProcessDefinitionKey()).thenReturn("ums_membership_approve"); |
| | | when(instance.getBusinessKey()).thenReturn("12"); |
| | | when(mapper.selectById(12L)).thenReturn(new UmsApplicationDO().setFeePaid(false)); |
| | | assertDoesNotThrow(() -> validator.validate(1L, task, instance)); |
| | | } |
| | | |
| | | @Test void ignoresOtherProcesses() { |
| | | when(instance.getProcessDefinitionKey()).thenReturn("other"); |
| | | validator.validate(1L, task, instance); |
| | | verifyNoInteractions(mapper); |
| | | } |
| | | |
| | | @Test void permitsAutomaticSubmissionOfStarterNode() { |
| | | when(instance.getProcessDefinitionKey()).thenReturn("ums_membership_approve"); |
| | | when(task.getTaskDefinitionKey()).thenReturn("StartUserNode"); |
| | | validator.validate(1L, task, instance); |
| | | verifyNoInteractions(mapper); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ums.service.application; |
| | | |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| | | import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.application.UmsApplicationDO; |
| | | import cn.iocoder.yudao.module.ums.dal.mysql.application.UmsApplicationMapper; |
| | | import cn.iocoder.yudao.module.ums.enums.UmsApplicationStatusEnum; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.InjectMocks; |
| | | import org.mockito.Mock; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class UmsApplicationFeeConfirmationTest extends BaseMockitoUnitTest { |
| | | @InjectMocks private UmsApplicationServiceImpl service; |
| | | @Mock private UmsApplicationMapper mapper; |
| | | @Mock private BpmTaskService taskService; |
| | | |
| | | @Test void nonAssigneeCannotConfirm() { |
| | | when(mapper.selectById(1L)).thenReturn(new UmsApplicationDO() |
| | | .setStatus(UmsApplicationStatusEnum.APPROVING.getStatus()).setProcessInstanceId("p1")); |
| | | when(taskService.getTodoTask(9L, null, "p1")).thenReturn(null); |
| | | assertThrows(RuntimeException.class, () -> service.confirmFee(1L, 9L, true)); |
| | | verify(mapper, never()).update(any(), any()); |
| | | } |
| | | |
| | | @Test void completedApplicationCannotBeChanged() { |
| | | when(mapper.selectById(1L)).thenReturn(new UmsApplicationDO() |
| | | .setStatus(UmsApplicationStatusEnum.APPROVED.getStatus()).setProcessInstanceId("p1")); |
| | | assertFalse(service.canConfirmFee(1L, 9L)); |
| | | verifyNoInteractions(taskService); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ums.service.application; |
| | | |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.application.UmsApplicationDO; |
| | | import cn.iocoder.yudao.module.ums.dal.mysql.application.UmsApplicationMapper; |
| | | import cn.iocoder.yudao.module.ums.dal.redis.UmsNoRedisDAO; |
| | | import cn.iocoder.yudao.module.ums.enums.UmsApplicationStatusEnum; |
| | | import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; |
| | | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
| | | import cn.iocoder.yudao.module.system.service.permission.PermissionService; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.ArgumentCaptor; |
| | | import org.mockito.InjectMocks; |
| | | import org.mockito.Mock; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class WxcpMembershipRegistrationImplTest extends BaseMockitoUnitTest { |
| | | @InjectMocks private WxcpMembershipRegistrationImpl service; |
| | | @Mock private UmsApplicationMapper mapper; |
| | | @Mock private UmsNoRedisDAO noRedisDAO; |
| | | @Mock private BpmProcessInstanceApi processInstanceApi; |
| | | @Mock private AdminUserService userService; |
| | | @Mock private PermissionService permissionService; |
| | | |
| | | @Test |
| | | void firstScanStartsApprovalWithUnconfirmedFee() { |
| | | when(noRedisDAO.generate(anyString())).thenReturn("SQ001"); |
| | | when(userService.getUserByUsername("wxcpservice")).thenReturn(new AdminUserDO().setId(5L).setStatus(0).setRemark("ä»
ç¨äºç³»ç»èªå¨åèµ·å
¥ä¼å®¡æ¹")); |
| | | when(processInstanceApi.createProcessInstance(eq(5L), any())).thenReturn("process-1"); |
| | | service.startIfAbsent("openid-1", "ä¼å", "13800138000"); |
| | | ArgumentCaptor<UmsApplicationDO> captor = ArgumentCaptor.forClass(UmsApplicationDO.class); |
| | | verify(mapper).insert(captor.capture()); |
| | | UmsApplicationDO draft = captor.getValue(); |
| | | assertEquals("openid-1", draft.getWxcpOpenid()); |
| | | assertEquals(UmsApplicationStatusEnum.APPROVING.getStatus(), draft.getStatus()); |
| | | assertNull(draft.getFeePaid()); |
| | | assertEquals("13800138000", draft.getContactPhone()); |
| | | verify(processInstanceApi).createProcessInstance(eq(5L), any()); |
| | | } |
| | | |
| | | @Test |
| | | void repeatScanDoesNotCreateAnotherDraft() { |
| | | when(mapper.selectOne(any(Wrapper.class))).thenReturn(new UmsApplicationDO()); |
| | | service.startIfAbsent("openid-1", "ä¼å", null); |
| | | verify(mapper, never()).insert(any(UmsApplicationDO.class)); |
| | | verifyNoInteractions(processInstanceApi); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ums.service.member; |
| | | |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| | | import cn.iocoder.yudao.module.system.service.auth.WxcpPasswordSetupGrant; |
| | | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
| | | import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberDO; |
| | | import cn.iocoder.yudao.module.ums.dal.mysql.member.UmsMemberMapper; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import com.baomidou.mybatisplus.core.MybatisConfiguration; |
| | | import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; |
| | | import org.apache.ibatis.builder.MapperBuilderAssistant; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.InjectMocks; |
| | | import org.mockito.Mock; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class WxcpInitialPasswordServiceTest extends BaseMockitoUnitTest { |
| | | @InjectMocks private WxcpInitialPasswordService service; |
| | | @Mock private UmsMemberMapper memberMapper; |
| | | @Mock private AdminUserService userService; |
| | | @Mock private WxcpPasswordSetupGrant grant; |
| | | |
| | | @Test void rejectsSessionWithoutWxcpGrant() { |
| | | when(grant.valid("token", 1L)).thenReturn(false); |
| | | assertThrows(RuntimeException.class, () -> service.setPassword(1L, "token", "password1")); |
| | | verifyNoInteractions(memberMapper, userService); |
| | | } |
| | | |
| | | @Test void rejectsAccountWithoutPendingSetup() { |
| | | when(grant.valid("token", 1L)).thenReturn(true); |
| | | when(memberMapper.selectByUserId(1L)).thenReturn(new UmsMemberDO().setPasswordSetupRequired(false)); |
| | | assertThrows(RuntimeException.class, () -> service.setPassword(1L, "token", "password1")); |
| | | verifyNoInteractions(userService); |
| | | } |
| | | |
| | | @Test void setsPasswordOnceAndReturnsOwnUsername() { |
| | | TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), UmsMemberDO.class); |
| | | when(grant.valid("token", 1L)).thenReturn(true); |
| | | when(memberMapper.selectByUserId(1L)).thenReturn(new UmsMemberDO().setId(2L).setPasswordSetupRequired(true)); |
| | | when(memberMapper.update(isNull(), any(Wrapper.class))).thenReturn(1); |
| | | when(userService.getUser(1L)).thenReturn(new AdminUserDO().setUsername("wx_example")); |
| | | assertEquals("wx_example", service.setPassword(1L, "token", "password1")); |
| | | verify(userService).updateUserPassword(1L, "password1"); |
| | | verify(grant).revoke("token"); |
| | | } |
| | | } |