|
|
@@ -0,0 +1,296 @@
|
|
|
+package org.dromara.main.controller;
|
|
|
+
|
|
|
+import cn.dev33.satoken.annotation.SaIgnore;
|
|
|
+import cn.dev33.satoken.stp.StpUtil;
|
|
|
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
|
|
+import cn.hutool.core.bean.BeanUtil;
|
|
|
+import cn.hutool.core.lang.Opt;
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.core.util.RandomUtil;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import jakarta.validation.constraints.NotBlank;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.dromara.common.core.constant.Constants;
|
|
|
+import org.dromara.common.core.constant.GlobalConstants;
|
|
|
+import org.dromara.common.core.constant.SystemConstants;
|
|
|
+import org.dromara.common.core.domain.R;
|
|
|
+import org.dromara.common.core.domain.dto.PostDTO;
|
|
|
+import org.dromara.common.core.domain.dto.RoleDTO;
|
|
|
+import org.dromara.common.core.domain.model.LoginUser;
|
|
|
+import org.dromara.common.core.domain.model.SmsLoginBody;
|
|
|
+import org.dromara.common.core.exception.ServiceException;
|
|
|
+import org.dromara.common.core.utils.StringUtils;
|
|
|
+import org.dromara.common.core.utils.ValidatorUtils;
|
|
|
+import org.dromara.common.ratelimiter.annotation.RateLimiter;
|
|
|
+import org.dromara.common.redis.utils.RedisUtils;
|
|
|
+import org.dromara.common.satoken.utils.LoginHelper;
|
|
|
+import org.dromara.common.web.core.BaseController;
|
|
|
+import org.dromara.main.domain.bo.MainCompanyApplyBo;
|
|
|
+import org.dromara.main.domain.vo.MainCompanyApplyVo;
|
|
|
+import org.dromara.main.service.IMainCompanyApplyService;
|
|
|
+import org.dromara.sms4j.api.SmsBlend;
|
|
|
+import org.dromara.sms4j.api.entity.SmsResponse;
|
|
|
+import org.dromara.sms4j.core.factory.SmsFactory;
|
|
|
+import org.dromara.system.domain.SysUser;
|
|
|
+import org.dromara.system.domain.vo.SysClientVo;
|
|
|
+import org.dromara.system.domain.vo.SysDeptVo;
|
|
|
+import org.dromara.system.domain.vo.SysPostVo;
|
|
|
+import org.dromara.system.domain.vo.SysRoleVo;
|
|
|
+import org.dromara.system.domain.vo.SysTenantVo;
|
|
|
+import org.dromara.system.domain.vo.SysUserVo;
|
|
|
+import org.dromara.system.mapper.SysUserMapper;
|
|
|
+import org.dromara.system.service.ISysClientService;
|
|
|
+import org.dromara.system.service.ISysDeptService;
|
|
|
+import org.dromara.system.service.ISysPermissionService;
|
|
|
+import org.dromara.system.service.ISysPostService;
|
|
|
+import org.dromara.system.service.ISysRoleService;
|
|
|
+import org.dromara.system.service.ISysTenantService;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.validation.annotation.Validated;
|
|
|
+import org.springframework.web.bind.annotation.GetMapping;
|
|
|
+import org.springframework.web.bind.annotation.PostMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestBody;
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
+
|
|
|
+import java.time.Duration;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 门户认证Controller(公开接口,无需登录)
|
|
|
+ *
|
|
|
+ * @author sj
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Validated
|
|
|
+@RequiredArgsConstructor
|
|
|
+@RestController
|
|
|
+@RequestMapping("/portal/auth")
|
|
|
+public class PortalAuthController extends BaseController {
|
|
|
+
|
|
|
+ private final IMainCompanyApplyService mainCompanyApplyService;
|
|
|
+ private final ISysClientService clientService;
|
|
|
+ private final ISysTenantService tenantService;
|
|
|
+ private final ISysPermissionService permissionService;
|
|
|
+ private final ISysRoleService roleService;
|
|
|
+ private final ISysPostService postService;
|
|
|
+ private final ISysDeptService deptService;
|
|
|
+ private final SysUserMapper userMapper;
|
|
|
+
|
|
|
+ @Value("${spring.profiles.active:}")
|
|
|
+ private String activeProfile;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送短信验证码
|
|
|
+ *
|
|
|
+ * @param phonenumber 手机号
|
|
|
+ */
|
|
|
+ @SaIgnore
|
|
|
+ @RateLimiter(key = "#phonenumber", time = 60, count = 1)
|
|
|
+ @GetMapping("/sms/code")
|
|
|
+ public R<Void> smsCode(@NotBlank(message = "手机号不能为空") String phonenumber) {
|
|
|
+ String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
|
|
|
+ String code = RandomUtil.randomNumbers(4);
|
|
|
+ // 验证码缓存5分钟
|
|
|
+ RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
|
|
+
|
|
|
+ if ("dev".equalsIgnoreCase(activeProfile)) {
|
|
|
+ log.info("开发环境短信验证码: phone={}, code={}", phonenumber, code);
|
|
|
+ return R.ok();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送短信验证码
|
|
|
+ // 验证码模板id 需要在短信服务商配置
|
|
|
+ String templateId = "";
|
|
|
+ LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
|
|
+ map.put("code", code);
|
|
|
+
|
|
|
+ try {
|
|
|
+ SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
|
+ SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
|
|
|
+ if (!smsResponse.isSuccess()) {
|
|
|
+ log.error("验证码短信发送异常 => {}", smsResponse);
|
|
|
+ return R.fail(smsResponse.getData().toString());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("验证码短信发送异常 => {}", e.getMessage());
|
|
|
+ throw new ServiceException("短信发送失败,请稍后重试");
|
|
|
+ }
|
|
|
+
|
|
|
+ return R.ok();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 门户短信登录
|
|
|
+ */
|
|
|
+ @SaIgnore
|
|
|
+ @PostMapping("/smsLogin")
|
|
|
+ public R<Map<String, Object>> smsLogin(@Validated @RequestBody SmsLoginBody loginBody) {
|
|
|
+ ValidatorUtils.validate(loginBody);
|
|
|
+
|
|
|
+ String clientId = loginBody.getClientId();
|
|
|
+ String grantType = loginBody.getGrantType();
|
|
|
+ if (!"sms".equalsIgnoreCase(grantType)) {
|
|
|
+ return R.fail("授权类型错误");
|
|
|
+ }
|
|
|
+
|
|
|
+ SysClientVo client = clientService.queryByClientId(clientId);
|
|
|
+ if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
|
|
|
+ log.info("门户登录客户端异常 clientId={}, grantType={}", clientId, grantType);
|
|
|
+ return R.fail("客户端配置不存在或未开启短信认证");
|
|
|
+ }
|
|
|
+ if (!SystemConstants.NORMAL.equals(client.getStatus())) {
|
|
|
+ return R.fail("客户端已被停用");
|
|
|
+ }
|
|
|
+
|
|
|
+ MainCompanyApplyVo companyApply = queryLatestCompanyApply(loginBody.getPhonenumber());
|
|
|
+ if (companyApply == null) {
|
|
|
+ return R.fail("该手机号未注册企业账号");
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer applyStatus = companyApply.getApplyStatus();
|
|
|
+ Map<String, Object> result = new HashMap<>(4);
|
|
|
+
|
|
|
+ if (!Integer.valueOf(2).equals(applyStatus)) {
|
|
|
+ result.put("auditStatus", Integer.valueOf(3).equals(applyStatus) ? 2 : 0);
|
|
|
+ result.put("company", companyApply);
|
|
|
+ return R.ok(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ SysUserVo employee = queryUserByPhone(loginBody.getPhonenumber());
|
|
|
+ if (employee == null) {
|
|
|
+ return R.fail("登录账号不存在,请联系管理员");
|
|
|
+ }
|
|
|
+ if (!SystemConstants.NORMAL.equals(employee.getStatus())) {
|
|
|
+ return R.fail("登录账号已被停用");
|
|
|
+ }
|
|
|
+
|
|
|
+ validateSmsCode(loginBody.getPhonenumber(), loginBody.getSmsCode());
|
|
|
+ LoginUser loginUser = buildLoginUser(employee, client);
|
|
|
+ issueToken(loginUser, client);
|
|
|
+ RedisUtils.deleteObject(GlobalConstants.CAPTCHA_CODE_KEY + loginBody.getPhonenumber());
|
|
|
+
|
|
|
+ SysTenantVo company = null;
|
|
|
+ if (StringUtils.isNotBlank(companyApply.getTenantId())) {
|
|
|
+ company = tenantService.queryByTenantId(companyApply.getTenantId());
|
|
|
+ }
|
|
|
+
|
|
|
+ result.put("auditStatus", 1);
|
|
|
+ result.put("token", StpUtil.getTokenValue());
|
|
|
+ result.put("clientId", client.getClientId());
|
|
|
+ result.put("employee", employee);
|
|
|
+ result.put("company", ObjectUtil.defaultIfNull(company, companyApply));
|
|
|
+ return R.ok(result);
|
|
|
+ } catch (ServiceException e) {
|
|
|
+ throw e;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("门户短信登录失败: {}", e.getMessage(), e);
|
|
|
+ throw new ServiceException("登录失败,请稍后重试");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前登录信息
|
|
|
+ */
|
|
|
+ @GetMapping("/getInfo")
|
|
|
+ public R<Map<String, Object>> getInfo() {
|
|
|
+ Long userId = LoginHelper.getUserId();
|
|
|
+ if (userId == null) {
|
|
|
+ return R.fail("未登录或token已失效");
|
|
|
+ }
|
|
|
+
|
|
|
+ SysUserVo employee = userMapper.selectVoById(userId);
|
|
|
+ if (employee == null) {
|
|
|
+ return R.fail("用户信息不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ SysTenantVo company = null;
|
|
|
+ if (StringUtils.isNotBlank(employee.getTenantId())) {
|
|
|
+ company = tenantService.queryByTenantId(employee.getTenantId());
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> result = new HashMap<>(2);
|
|
|
+ result.put("employee", employee);
|
|
|
+ result.put("company", company);
|
|
|
+ return R.ok(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 退出登录
|
|
|
+ */
|
|
|
+ @SaIgnore
|
|
|
+ @PostMapping("/logout")
|
|
|
+ public R<Void> logout() {
|
|
|
+ try {
|
|
|
+ if (LoginHelper.isLogin()) {
|
|
|
+ StpUtil.logout();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("门户退出登录异常: {}", e.getMessage());
|
|
|
+ }
|
|
|
+ return R.ok();
|
|
|
+ }
|
|
|
+
|
|
|
+ private MainCompanyApplyVo queryLatestCompanyApply(String phonenumber) {
|
|
|
+ MainCompanyApplyBo bo = new MainCompanyApplyBo();
|
|
|
+ bo.setMobile(phonenumber);
|
|
|
+ List<MainCompanyApplyVo> list = mainCompanyApplyService.queryList(bo);
|
|
|
+ return list.isEmpty() ? null : list.get(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private SysUserVo queryUserByPhone(String phonenumber) {
|
|
|
+ return userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>()
|
|
|
+ .eq(SysUser::getPhonenumber, phonenumber)
|
|
|
+ .last("limit 1"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void validateSmsCode(String phonenumber, String smsCode) {
|
|
|
+ String cacheKey = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
|
|
|
+ String cachedCode = RedisUtils.getCacheObject(cacheKey);
|
|
|
+ if (StringUtils.isBlank(cachedCode)) {
|
|
|
+ throw new ServiceException("验证码已过期,请重新获取");
|
|
|
+ }
|
|
|
+ if (!StringUtils.equals(cachedCode, smsCode)) {
|
|
|
+ throw new ServiceException("验证码错误");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private LoginUser buildLoginUser(SysUserVo user, SysClientVo client) {
|
|
|
+ LoginUser loginUser = new LoginUser();
|
|
|
+ Long userId = user.getUserId();
|
|
|
+ loginUser.setTenantId(user.getTenantId());
|
|
|
+ loginUser.setUserId(userId);
|
|
|
+ loginUser.setDeptId(user.getDeptId());
|
|
|
+ loginUser.setUsername(user.getUserName());
|
|
|
+ loginUser.setNickname(user.getNickName());
|
|
|
+ loginUser.setUserType(user.getUserType());
|
|
|
+ loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
|
|
|
+ loginUser.setRolePermission(permissionService.getRolePermission(userId));
|
|
|
+ loginUser.setClientKey(client.getClientKey());
|
|
|
+ loginUser.setDeviceType(client.getDeviceType());
|
|
|
+ if (ObjectUtil.isNotNull(user.getDeptId())) {
|
|
|
+ Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
|
|
|
+ loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
|
|
|
+ loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
|
|
|
+ }
|
|
|
+ List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
|
|
|
+ List<SysPostVo> posts = postService.selectPostsByUserId(userId);
|
|
|
+ loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
|
|
|
+ loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
|
|
|
+ return loginUser;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void issueToken(LoginUser loginUser, SysClientVo client) {
|
|
|
+ SaLoginParameter model = new SaLoginParameter();
|
|
|
+ model.setDeviceType(client.getDeviceType());
|
|
|
+ model.setTimeout(client.getTimeout());
|
|
|
+ model.setActiveTimeout(client.getActiveTimeout());
|
|
|
+ model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
|
|
|
+ LoginHelper.login(loginUser, model);
|
|
|
+ }
|
|
|
+}
|