diff --git a/fad-app/pom.xml b/fad-app/pom.xml new file mode 100644 index 0000000..f262d53 --- /dev/null +++ b/fad-app/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + com.ruoyi + ruoyi-flowable-plus + 0.8.3 + + fad-app + fad-app + FAD移动端APP兼容模块 + + + + + com.ruoyi + ruoyi-common + + + + + com.ruoyi + ruoyi-system + + + + + com.ruoyi + ruoyi-oa + + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.35 + + + + + diff --git a/fad-app/src/main/java/com/ruoyi/fadapp/controller/FadAppAuthController.java b/fad-app/src/main/java/com/ruoyi/fadapp/controller/FadAppAuthController.java new file mode 100644 index 0000000..dca1739 --- /dev/null +++ b/fad-app/src/main/java/com/ruoyi/fadapp/controller/FadAppAuthController.java @@ -0,0 +1,54 @@ +package com.ruoyi.fadapp.controller; + +import com.ruoyi.common.core.domain.R; +import com.ruoyi.fadapp.domain.dto.LoginByCodeDto; +import com.ruoyi.fadapp.domain.dto.SendCodeDto; +import com.ruoyi.fadapp.domain.vo.LoginResultVo; +import com.ruoyi.fadapp.service.IFadAppAuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; + +/** + * FAD APP认证控制器 + * + * @author ruoyi + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/fadapp/auth") +public class FadAppAuthController { + + private final IFadAppAuthService authService; + + /** + * 发送验证码 + */ + @PostMapping("/send-code") + public R sendCode(@Valid @RequestBody SendCodeDto dto) { + authService.sendCode(dto.getPhone()); + return R.ok(); + } + + /** + * 验证码登录 + */ + @PostMapping("/login-by-code") + public R loginByCode(@Valid @RequestBody LoginByCodeDto dto) { + LoginResultVo result = authService.loginByCode(dto.getPhone(), dto.getCode()); + return R.ok(result); + } + + /** + * 退出登录 + */ + @PostMapping("/logout") + public R logout(@RequestHeader("Authorization") String token) { + authService.logout(token); + return R.ok(); + } +} \ No newline at end of file diff --git a/fad-app/src/main/java/com/ruoyi/fadapp/controller/FadAppUserController.java b/fad-app/src/main/java/com/ruoyi/fadapp/controller/FadAppUserController.java new file mode 100644 index 0000000..8501442 --- /dev/null +++ b/fad-app/src/main/java/com/ruoyi/fadapp/controller/FadAppUserController.java @@ -0,0 +1,66 @@ +package com.ruoyi.fadapp.controller; + +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.fadapp.domain.vo.FadAppUserVo; +import com.ruoyi.fadapp.service.IFadAppUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.constraints.NotNull; + +/** + * FAD APP用户管理Controller + * + * @author ruoyi + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/fadapp/user") +public class FadAppUserController extends BaseController { + + private final IFadAppUserService userService; + + /** + * 查询用户列表 + */ + @GetMapping("/list") + public TableDataInfo list(FadAppUserVo bo, PageQuery pageQuery) { + return userService.queryUserList(bo, pageQuery); + } + + /** + * 获取用户详细信息 + * + * @param userId 用户ID + */ + @GetMapping("/{userId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long userId) { + return R.ok(userService.getUserInfo(userId)); + } + + /** + * 根据手机号获取用户信息 + */ + @GetMapping("/phone/{phone}") + public R getUserByPhone(@NotNull(message = "手机号不能为空") + @PathVariable String phone) { + return R.ok(userService.getUserByPhone(phone)); + } + + /** + * 获取用户统计信息 + */ + @GetMapping("/stats") + public R getUserStats() { + return R.ok(userService.getUserStats()); + } +} \ No newline at end of file diff --git a/fad-app/src/main/java/com/ruoyi/fadapp/domain/dto/LoginByCodeDto.java b/fad-app/src/main/java/com/ruoyi/fadapp/domain/dto/LoginByCodeDto.java new file mode 100644 index 0000000..b877da4 --- /dev/null +++ b/fad-app/src/main/java/com/ruoyi/fadapp/domain/dto/LoginByCodeDto.java @@ -0,0 +1,29 @@ +package com.ruoyi.fadapp.domain.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +/** + * 验证码登录DTO + * + * @author ruoyi + */ +@Data +public class LoginByCodeDto { + + /** + * 手机号 + */ + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + /** + * 验证码 + */ + @NotBlank(message = "验证码不能为空") + @Pattern(regexp = "^\\d{6}$", message = "验证码格式不正确") + private String code; +} \ No newline at end of file diff --git a/fad-app/src/main/java/com/ruoyi/fadapp/domain/dto/SendCodeDto.java b/fad-app/src/main/java/com/ruoyi/fadapp/domain/dto/SendCodeDto.java new file mode 100644 index 0000000..d0bd8be --- /dev/null +++ b/fad-app/src/main/java/com/ruoyi/fadapp/domain/dto/SendCodeDto.java @@ -0,0 +1,22 @@ +package com.ruoyi.fadapp.domain.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +/** + * 发送验证码DTO + * + * @author ruoyi + */ +@Data +public class SendCodeDto { + + /** + * 手机号 + */ + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; +} \ No newline at end of file diff --git a/fad-app/src/main/java/com/ruoyi/fadapp/domain/vo/FadAppUserVo.java b/fad-app/src/main/java/com/ruoyi/fadapp/domain/vo/FadAppUserVo.java new file mode 100644 index 0000000..6c4744b --- /dev/null +++ b/fad-app/src/main/java/com/ruoyi/fadapp/domain/vo/FadAppUserVo.java @@ -0,0 +1,114 @@ +package com.ruoyi.fadapp.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.ruoyi.common.annotation.ExcelDictFormat; +import com.ruoyi.common.convert.ExcelDictConvert; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * FAD APP用户视图对象 + * + * @author ruoyi + */ +@Data +@ExcelIgnoreUnannotated +public class FadAppUserVo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + @ExcelProperty(value = "用户ID") + private Long userId; + + /** + * 部门ID + */ + @ExcelProperty(value = "部门ID") + private Long deptId; + + /** + * 用户账号 + */ + @ExcelProperty(value = "用户账号") + private String userName; + + /** + * 用户昵称 + */ + @ExcelProperty(value = "用户昵称") + private String nickName; + + /** + * 用户邮箱 + */ + @ExcelProperty(value = "用户邮箱") + private String email; + + /** + * 手机号码 + */ + @ExcelProperty(value = "手机号码") + private String phonenumber; + + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_user_sex") + private String sex; + + /** + * 用户头像 + */ + @ExcelProperty(value = "用户头像") + private String avatar; + + /** + * 帐号状态(0正常 1停用) + */ + @ExcelProperty(value = "帐号状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; + + /** + * 最后登录IP + */ + @ExcelProperty(value = "最后登录IP") + private String loginIp; + + /** + * 最后登录时间 + */ + @ExcelProperty(value = "最后登录时间") + private Date loginDate; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 部门名称 + */ + @ExcelProperty(value = "部门名称") + private String deptName; + + /** + * 负责人 + */ + @ExcelProperty(value = "负责人") + private String leader; +} \ No newline at end of file diff --git a/fad-app/src/main/java/com/ruoyi/fadapp/domain/vo/LoginResultVo.java b/fad-app/src/main/java/com/ruoyi/fadapp/domain/vo/LoginResultVo.java new file mode 100644 index 0000000..007277a --- /dev/null +++ b/fad-app/src/main/java/com/ruoyi/fadapp/domain/vo/LoginResultVo.java @@ -0,0 +1,52 @@ +package com.ruoyi.fadapp.domain.vo; + +import lombok.Data; + +/** + * 登录结果VO + * + * @author ruoyi + */ +@Data +public class LoginResultVo { + + /** + * 访问令牌 + */ + private String token; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String userName; + + /** + * 昵称 + */ + private String nickName; + + /** + * 手机号 + */ + private String phonenumber; + + /** + * 头像 + */ + private String avatar; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门名称 + */ + private String deptName; +} \ No newline at end of file diff --git a/fad-app/src/main/java/com/ruoyi/fadapp/service/IFadAppAuthService.java b/fad-app/src/main/java/com/ruoyi/fadapp/service/IFadAppAuthService.java new file mode 100644 index 0000000..e43611f --- /dev/null +++ b/fad-app/src/main/java/com/ruoyi/fadapp/service/IFadAppAuthService.java @@ -0,0 +1,34 @@ +package com.ruoyi.fadapp.service; + +import com.ruoyi.fadapp.domain.vo.LoginResultVo; + +/** + * FAD APP认证服务接口 + * + * @author ruoyi + */ +public interface IFadAppAuthService { + + /** + * 发送验证码 + * + * @param phone 手机号 + */ + void sendCode(String phone); + + /** + * 验证码登录 + * + * @param phone 手机号 + * @param code 验证码 + * @return 登录结果 + */ + LoginResultVo loginByCode(String phone, String code); + + /** + * 退出登录 + * + * @param token 访问令牌 + */ + void logout(String token); +} \ No newline at end of file diff --git a/fad-app/src/main/java/com/ruoyi/fadapp/service/IFadAppUserService.java b/fad-app/src/main/java/com/ruoyi/fadapp/service/IFadAppUserService.java new file mode 100644 index 0000000..fd70d8b --- /dev/null +++ b/fad-app/src/main/java/com/ruoyi/fadapp/service/IFadAppUserService.java @@ -0,0 +1,39 @@ +package com.ruoyi.fadapp.service; + +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.fadapp.domain.vo.FadAppUserVo; + +/** + * FAD APP用户管理Service接口 + * + * @author ruoyi + */ +public interface IFadAppUserService { + + /** + * 查询用户列表 + */ + TableDataInfo queryUserList(FadAppUserVo bo, PageQuery pageQuery); + + /** + * 获取用户详细信息 + */ + FadAppUserVo getUserInfo(Long userId); + + /** + * 根据手机号获取用户信息 + * + * @param phone 手机号 + * @return 用户信息 + */ + SysUser getUserByPhone(String phone); + + /** + * 获取用户统计信息 + * + * @return 统计信息 + */ + Object getUserStats(); +} \ No newline at end of file diff --git a/fad-app/src/main/java/com/ruoyi/fadapp/service/impl/FadAppAuthServiceImpl.java b/fad-app/src/main/java/com/ruoyi/fadapp/service/impl/FadAppAuthServiceImpl.java new file mode 100644 index 0000000..bf6cb14 --- /dev/null +++ b/fad-app/src/main/java/com/ruoyi/fadapp/service/impl/FadAppAuthServiceImpl.java @@ -0,0 +1,167 @@ +package com.ruoyi.fadapp.service.impl; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.util.RandomUtil; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.DeviceType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.helper.LoginHelper; +import com.ruoyi.common.utils.redis.RedisUtils; +import com.ruoyi.fadapp.domain.vo.LoginResultVo; +import com.ruoyi.fadapp.service.IFadAppAuthService; +import com.ruoyi.system.service.ISysUserService; +import com.ruoyi.system.service.SysPermissionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +/** + * FAD APP认证服务实现类 + * + * @author ruoyi + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class FadAppAuthServiceImpl implements IFadAppAuthService { + + private final ISysUserService userService; + private final RedisUtils redisUtils; + private final SysPermissionService permissionService; + + /** + * 验证码缓存前缀 + */ + private static final String CODE_PREFIX = "fadapp:code:"; + + /** + * 验证码过期时间(分钟) + */ + private static final int CODE_EXPIRE_MINUTES = 5; + + /** + * 固定验证码(开发环境使用) + */ + private static final String FIXED_CODE = "666666"; + + @Override + public void sendCode(String phone) { + // 验证手机号格式 + if (!phone.matches("^1[3-9]\\d{9}$")) { + throw new ServiceException("手机号格式不正确"); + } + + // 生成验证码(开发环境使用固定验证码) + String code = FIXED_CODE; + + // 存储验证码到Redis(5分钟过期) + String key = CODE_PREFIX + phone; + redisUtils.setCacheObject(key, code, Duration.ofMinutes(CODE_EXPIRE_MINUTES)); + + // 记录日志(实际项目中这里会调用短信服务) + log.info("向手机号 {} 发送验证码: {}", phone, code); + } + + @Override + public LoginResultVo loginByCode(String phone, String code) { + // 验证手机号格式 + if (!phone.matches("^1[3-9]\\d{9}$")) { + throw new ServiceException("手机号格式不正确"); + } + + // 验证验证码格式 + if (!code.matches("^\\d{6}$")) { + throw new ServiceException("验证码格式不正确"); + } + + // 从Redis获取存储的验证码 + String key = CODE_PREFIX + phone; + String storedCode = redisUtils.getCacheObject(key); + + if (storedCode == null) { + throw new ServiceException("验证码已过期,请重新获取"); + } + + if (!storedCode.equals(code)) { + throw new ServiceException("验证码不正确"); + } + + // 验证码验证通过,删除Redis中的验证码 + redisUtils.deleteObject(key); + + // 查找或创建用户 + SysUser user = findOrCreateUser(phone); + + // 构建登录用户信息 + LoginUser loginUser = buildLoginUser(user); + + // 使用原来的登录方式生成token + LoginHelper.loginByDevice(loginUser, DeviceType.APP); + String token = StpUtil.getTokenValue(); + + // 构建登录结果 + LoginResultVo result = new LoginResultVo(); + result.setToken(token); + result.setUserId(user.getUserId()); + result.setUserName(user.getUserName()); + result.setNickName(user.getNickName()); + result.setPhonenumber(user.getPhonenumber()); + result.setAvatar(user.getAvatar()); + result.setDeptId(user.getDeptId()); + // 这里可以设置部门名称,需要查询部门信息 + + return result; + } + + @Override + public void logout(String token) { + // 清除token(这里可以根据实际使用的JWT库来实现) + // 如果使用Sa-Token,可以调用 StpUtil.logout() + StpUtil.logout(); + } + + /** + * 查找或创建用户 + */ + private SysUser findOrCreateUser(String phone) { + // 先查找用户 + SysUser user = userService.selectUserByPhonenumber(phone); + + if (user == null) { + // 用户不存在,创建新用户 + user = new SysUser(); + user.setUserName(phone); // 用户名使用手机号 + user.setNickName("用户" + phone.substring(7)); // 昵称使用手机号后4位 + user.setPhonenumber(phone); + user.setPassword("123456"); // 默认密码,实际项目中应该生成随机密码 + user.setStatus("0"); // 正常状态 + + // 插入用户 + userService.insertUser(user); + + // 重新查询用户信息 + user = userService.selectUserByPhonenumber(phone); + } + + return user; + } + + /** + * 构建登录用户 + */ + private LoginUser buildLoginUser(SysUser user) { + LoginUser loginUser = new LoginUser(); + loginUser.setUserId(user.getUserId()); + loginUser.setDeptId(user.getDeptId()); + loginUser.setUsername(user.getUserName()); + loginUser.setNickName(user.getNickName()); + loginUser.setUserType(user.getUserType()); + loginUser.setMenuPermission(permissionService.getMenuPermission(user)); + loginUser.setRolePermission(permissionService.getRolePermission(user)); + loginUser.setDeptName(user.getDept() != null ? user.getDept().getDeptName() : ""); + return loginUser; + } +} \ No newline at end of file diff --git a/fad-app/src/main/java/com/ruoyi/fadapp/service/impl/FadAppUserServiceImpl.java b/fad-app/src/main/java/com/ruoyi/fadapp/service/impl/FadAppUserServiceImpl.java new file mode 100644 index 0000000..dcfddd7 --- /dev/null +++ b/fad-app/src/main/java/com/ruoyi/fadapp/service/impl/FadAppUserServiceImpl.java @@ -0,0 +1,80 @@ +package com.ruoyi.fadapp.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.fadapp.domain.vo.FadAppUserVo; +import com.ruoyi.fadapp.service.IFadAppUserService; +import com.ruoyi.system.mapper.SysUserMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * FAD APP用户管理Service业务层处理 + * + * @author ruoyi + */ +@RequiredArgsConstructor +@Service +public class FadAppUserServiceImpl implements IFadAppUserService { + + private final SysUserMapper userMapper; + + /** + * 查询用户列表 + */ + @Override + public TableDataInfo queryUserList(FadAppUserVo bo, PageQuery pageQuery) { + QueryWrapper lqw = buildQueryWrapper(bo); + Page result = userMapper.selectPage(pageQuery.build(), lqw); + return TableDataInfo.build(BeanUtil.copyToList(result.getRecords(), FadAppUserVo.class)); + } + + /** + * 获取用户详细信息 + */ + @Override + public FadAppUserVo getUserInfo(Long userId) { + SysUser user = userMapper.selectById(userId); + return BeanUtil.toBean(user, FadAppUserVo.class); + } + + /** + * 根据手机号获取用户信息 + */ + @Override + public SysUser getUserByPhone(String phone) { + return userMapper.selectUserByPhonenumber(phone); + } + + /** + * 获取用户统计信息 + */ + @Override + public Object getUserStats() { + Map stats = new HashMap<>(); + stats.put("totalUsers", userMapper.selectCount(null)); + stats.put("activeUsers", userMapper.selectCount( + Wrappers.lambdaQuery().eq(SysUser::getStatus, "0") + )); + return stats; + } + + private QueryWrapper buildQueryWrapper(FadAppUserVo bo) { + QueryWrapper lqw = Wrappers.query(); + lqw.like(StringUtils.isNotBlank(bo.getUserName()), "user_name", bo.getUserName()); + lqw.like(StringUtils.isNotBlank(bo.getNickName()), "nick_name", bo.getNickName()); + lqw.like(StringUtils.isNotBlank(bo.getPhonenumber()), "phonenumber", bo.getPhonenumber()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), "status", bo.getStatus()); + lqw.eq(bo.getDeptId() != null, "dept_id", bo.getDeptId()); + return lqw; + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index c3ce2ea..9b9762a 100644 --- a/pom.xml +++ b/pom.xml @@ -359,6 +359,13 @@ ${ruoyi-flowable-plus.version} + + + com.ruoyi + fad-app + ${ruoyi-flowable-plus.version} + + @@ -375,6 +382,7 @@ ruoyi-sms ruoyi-system ruoyi-oa + fad-app pom diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index e4bc78a..deeedf1 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -83,6 +83,12 @@ ruoyi-oa + + + com.ruoyi + fad-app + + com.ruoyi