diff --git a/baogutang-admin/pom.xml b/baogutang-admin/pom.xml index 2f1d7f1..04d7452 100644 --- a/baogutang-admin/pom.xml +++ b/baogutang-admin/pom.xml @@ -84,6 +84,26 @@ dingtalk 2.0.20 + + + cn.dev33 + sa-token-spring-boot-starter + 1.34.0 + + + + + cn.dev33 + sa-token-sso + 1.34.0 + + + + com.dtflys.forest + forest-spring-boot-starter + 1.5.26 + + diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/config/EncoderConfig.java b/baogutang-admin/src/main/java/top/baogutang/admin/config/EncoderConfig.java new file mode 100644 index 0000000..c311817 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/config/EncoderConfig.java @@ -0,0 +1,20 @@ +package top.baogutang.admin.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +/** + * @description: + * @author: developer + * @date: 2022/06/10 : 12:08 + */ +@Configuration +public class EncoderConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/config/SaTokenConfigure.java b/baogutang-admin/src/main/java/top/baogutang/admin/config/SaTokenConfigure.java new file mode 100644 index 0000000..3a3fcf4 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/config/SaTokenConfigure.java @@ -0,0 +1,20 @@ +package top.baogutang.admin.config; + +import cn.dev33.satoken.strategy.SaStrategy; +import cn.dev33.satoken.util.SaFoxUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SaTokenConfigure { + /** + * 重写 Sa-Token 框架内部算法策略 + */ + @Autowired + public void rewriteSaStrategy() { + // 重写 Token 生成策略 + SaStrategy.me.createToken = (loginId, loginType) -> { + return SaFoxUtil.getRandomString(60); + }; + } +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/config/SysGlobalExceptionHandler.java b/baogutang-admin/src/main/java/top/baogutang/admin/config/SysGlobalExceptionHandler.java new file mode 100644 index 0000000..d985973 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/config/SysGlobalExceptionHandler.java @@ -0,0 +1,31 @@ +package top.baogutang.admin.config; + +import cn.dev33.satoken.exception.NotLoginException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import top.baogutang.common.domain.Results; + +/** + * @description: 全局异常处理 + * @author: nikooh + * @date: 2023/06/15 : 12:23 + */ +@Slf4j +@RestControllerAdvice +public class SysGlobalExceptionHandler { + + + public SysGlobalExceptionHandler() { + // + } + + + @ExceptionHandler({NotLoginException.class}) + public Results businessNotLoginException(NotLoginException e) { + log.error("请求发生错误,code:{},message:{}", e.getCode(), e.getMessage()); + return Results.failed(e.getCode(), e.getMessage()); + } + + +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/contants/JwtConstant.java b/baogutang-admin/src/main/java/top/baogutang/admin/contants/JwtConstant.java new file mode 100644 index 0000000..212c037 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/contants/JwtConstant.java @@ -0,0 +1,31 @@ +package top.baogutang.admin.contants; + +/** + * Jwt相关常量类 + * + * @author + */ +public class JwtConstant { + + /** + * token名称 + */ + public static final String TOKEN_JwtConstantNAME = "authorization"; + + /** + * 加密方式 + */ + public static final String ALGORITHM_FAMILY_NAME = "RSA"; + + /** + * 私钥(加密用) + */ + public static final String PRIVATE_KEY = + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDM9s3eJdYkcmKyW6mOZcLaME/tf5cnbsIuW/t1j9jsIsol9owmGn2ZI+pVgjXZH3slOjst9w+FA83sReFmIIfbgpHNbiS0ooWw6eSR1/3mK/U2GYeAxRy3z0MvOqYfifSg/I/hWn/gQkKdXX5XKKBbpbWC5/xiSdDz2XxchxTEy8NNSQZTXQ5haVQ83iRgQVl1sLkg8qRbSoMaB5DIYnpYd+GjJ+YqPMbBsZcjul9Y+bF95PDPmFVpMUu61Exr6qCn+vvk0RqMOO7Fu5ZtFYIUjegtpM4x8jQQYk0EUcEnHQup99vCF17SPngAuU72bYfTiSh+e/niRTinnLbEFkWzAgMBAAECggEBAK9zRV64PMsOL3ZGCKqgaV+ko4bGBXn30bkle+dyr1nTnf9JQUcMvh7tI5b202l9DUkcw7PhHSPb0dZDK2LkzecVqgGUG0+VH6QFU2eU4P66+jjJObj9AkxF21j+d13gFcwI5pEdiwFJNwTGF3Q8jqhk/S+FrgHk9j0HGvGJoDmkOiTsdQm1A87itRUFODMKm2naQ15w4px/wBhBCF/gdY2uusKtSyuHVB00M9ooINaVJ/+S5PjtCEbIDc8ZLlropg6Uqs2DLsILAcVceeXc8V4jmZZaVOFVdokn47Q8NTeWgRyqbAJ+JjIIMIUkj8JHfbvD3Eyyh2eNN9BYhWBl+cECgYEA8+4Hioj7+K/nTTGTylxo4MOihSqMQxRyJJ8hKTGfNKTDC0PoGUnv7xaThmG9MFg/iIwNf3nEIaw5aaXSL3HTItFaKJ2a5/S6c7Gg6/vlyQ9MD5g+18okzOeYATfWqSP+4cADjWqGnEx0zxO4z7T4Th1io9G21ExstJVQhc1s1SsCgYEA1xstlCYJeym2I+v50DPpWCqXZ/ZhaQvXIAlAPl6FZ+qzph9mSaaXNRE0qHLcn+d+BbspCBta4uoIowV6vc4D2MdpM6UKvFesULFvkH5oLuKXEjMkYe+mTi1XqZ6FFwJaO0bBW8WtO0jPufyDHL+32WlVhQ4/Qn0g0cSTBhUvHZkCgYEAup5BwLgaZeFVyVYDpo7aYhLqFI0/r9ZFmUTxHs3q6mCfI9A5Epfha35PMUE3d3Qcb0AO15b1+XkEl5IYYtnnWTzniClDqPAvcXHVFpWp8A+29jtY4MizLNyGC3CH1vFF/7piPV/hSlSoDQEepDHkwgZzP7ei83rgs6uEZkmKWO8CgYEAnnwZwtObnLEIz8KnVm1I+Xq3/xMahikBIUtvmARQSlY0cqsj1BP3yFOa9plaUD5hLZvOCXkOJ63DG0mIO5w0XV3e9vwcwtPd075HsrBP1muAXxprLfVCFMt3kTOIX4GONapWAGXO0qU8141ilKX0sSzYo6Xxme1TZvpsUMj+49ECgYA5E5A1sNpqnEg/umbWBExkYuubqAzSQpf5X1lCtKDoveHrTmnv2IfccGY9o03tOfP0sutspC2epdHR6n5nUg/0eohtHMmE037yTJPv+Wk8uzuo4/1NYQljGK+rf1NpF5bLU0MJJDT+s5RRkwP2E8ZPKNFoPHgNuj4omt8Y+HP6RQ=="; + + /** + * 公钥(解密用) + */ + public static final String PUBLIC_KEY = + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzPbN3iXWJHJislupjmXC2jBP7X+XJ27CLlv7dY/Y7CLKJfaMJhp9mSPqVYI12R97JTo7LfcPhQPN7EXhZiCH24KRzW4ktKKFsOnkkdf95iv1NhmHgMUct89DLzqmH4n0oPyP4Vp/4EJCnV1+VyigW6W1guf8YknQ89l8XIcUxMvDTUkGU10OYWlUPN4kYEFZdbC5IPKkW0qDGgeQyGJ6WHfhoyfmKjzGwbGXI7pfWPmxfeTwz5hVaTFLutRMa+qgp/r75NEajDjuxbuWbRWCFI3oLaTOMfI0EGJNBFHBJx0Lqffbwhde0j54ALlO9m2H04kofnv54kU4p5y2xBZFswIDAQAB"; +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/controller/CommonController.java b/baogutang-admin/src/main/java/top/baogutang/admin/controller/CommonController.java new file mode 100644 index 0000000..afcf357 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/controller/CommonController.java @@ -0,0 +1,37 @@ +package top.baogutang.admin.controller; + +import lombok.extern.slf4j.Slf4j; +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 top.baogutang.admin.services.ICommonService; +import top.baogutang.common.domain.Results; + +import javax.annotation.Resource; + +/** + * @description: 通用能力控制器 + * @author: nikooh + * @date: 2023/06/19 : 12:17 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/common") +public class CommonController { + + @Resource + private ICommonService commonService; + + + /** + * 获取验证码 + * + * @param key 验证码key + * @return 验证码 + */ + @GetMapping("/randomImage/{key}") + public Results randomImage(@PathVariable("key") String key) { + return Results.ok(commonService.randomImage(key)); + } +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/controller/SysUserController.java b/baogutang-admin/src/main/java/top/baogutang/admin/controller/SysUserController.java new file mode 100644 index 0000000..d988df1 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/controller/SysUserController.java @@ -0,0 +1,73 @@ +package top.baogutang.admin.controller; + +import cn.dev33.satoken.annotation.SaCheckLogin; +import cn.dev33.satoken.stp.SaTokenInfo; +import cn.hutool.core.lang.PatternPool; +import cn.hutool.core.lang.Validator; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +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 top.baogutang.admin.domain.req.SysUserLoginReq; +import top.baogutang.admin.domain.req.SysUserRegisterReq; +import top.baogutang.admin.services.ISysUserService; +import top.baogutang.common.domain.Results; + +import javax.annotation.Resource; + +/** + * @description: 后台用户控制器 + * @author: nikooh + * @date: 2023/06/19 : 11:57 + */ +@Slf4j +@RestController +@RequestMapping("api/v1/sysUser") +public class SysUserController { + + @Resource + private ISysUserService sysUserService; + + /** + * 后台系统用户注册 + * + * @param sysUserRegisterReq 注册请求参数 + * @return 注册结果 + */ + @PostMapping("register") + public Results register(@Validated @RequestBody SysUserRegisterReq sysUserRegisterReq) { + if (!Validator.isMatchRegex(PatternPool.EMAIL, sysUserRegisterReq.getEmail())) { + return Results.failed("邮箱格式不正确!"); + } + if (!Validator.isMatchRegex(PatternPool.MOBILE, sysUserRegisterReq.getMobile())) { + return Results.failed("手机号格式不正确!"); + } + sysUserService.register(sysUserRegisterReq); + return Results.ok(); + } + + /** + * 登陆接口 + * + * @param sysUserLoginReq 登陆请求参数 + * @return 登陆用户信息 + */ + @PostMapping("/login") + public Results login(@RequestBody SysUserLoginReq sysUserLoginReq) { + return Results.ok(sysUserService.login(sysUserLoginReq)); + } + + /** + * 登出接口 + */ + @PostMapping("/logout") + @SaCheckLogin + public Results logout() { + sysUserService.logout(); + return Results.ok(); + } + + +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/dao/entity/SysUserEntity.java b/baogutang-admin/src/main/java/top/baogutang/admin/dao/entity/SysUserEntity.java new file mode 100644 index 0000000..28a544f --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/dao/entity/SysUserEntity.java @@ -0,0 +1,73 @@ +package top.baogutang.admin.dao.entity; + +import java.io.Serializable; +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author developer + * @version 1.0 + * @description: 用户实体 + * @date 2021/11/9 14:33 + */ +@Data +@TableName("t_sys_user") +public class SysUserEntity implements Serializable { + + private static final long serialVersionUID = 390648011771249275L; + + + @TableId(value = "id", type = IdType.INPUT) + private Long id; + + @ApiModelProperty(value = "创建人") + private Long creator; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "更新人") + private Long updater; + + @ApiModelProperty(value = "更新时间") + private Date updateTime; + + @ApiModelProperty(value = "备注") + private String remark; + + /** + * 删除标记,0是正常,1逻辑删除 + */ + @TableLogic + private Integer deleted; + + /** + * 名字 + */ + private String username; + + /** + * email + */ + private String email; + + /** + * mobile + */ + private String mobile; + + private String password; + + /** + * 是否启用(0:启用,1:禁用) + */ + private Integer enableFlag; + +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/dao/mapper/SysUserMapper.java b/baogutang-admin/src/main/java/top/baogutang/admin/dao/mapper/SysUserMapper.java new file mode 100644 index 0000000..ec8a537 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/dao/mapper/SysUserMapper.java @@ -0,0 +1,29 @@ +package top.baogutang.admin.dao.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import top.baogutang.admin.dao.entity.SysUserEntity; + +/** + * @author developer + * @version 1.0 + * @description: Mapper映射接口 + * @date 2021/11/9 14:34 + */ +@Mapper +public interface SysUserMapper extends BaseMapper { + + + /** + * 根据邮箱或手机号查询 + * + * @param email 邮箱 + * @param mobile 手机号 + * @return 用户信息 + */ + SysUserEntity selectByEmailOrMobile(@Param("email") String email, @Param("mobile") String mobile); +} \ No newline at end of file diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/domain/req/SysUserLoginReq.java b/baogutang-admin/src/main/java/top/baogutang/admin/domain/req/SysUserLoginReq.java new file mode 100644 index 0000000..1d7d40c --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/domain/req/SysUserLoginReq.java @@ -0,0 +1,27 @@ +package top.baogutang.admin.domain.req; + +import javax.validation.constraints.NotBlank; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author n + */ +@Data +@ApiModel("用户登录req") +public class SysUserLoginReq implements Serializable { + + private static final long serialVersionUID = -8305880247606732166L; + + @ApiModelProperty(value = "邮箱") + @NotBlank(message = "邮箱不能为空") + private String email; + + @ApiModelProperty(value = "密码") + @NotBlank(message = "密码不能为空") + private String password; +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/domain/req/SysUserRegisterReq.java b/baogutang-admin/src/main/java/top/baogutang/admin/domain/req/SysUserRegisterReq.java new file mode 100644 index 0000000..10852b1 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/domain/req/SysUserRegisterReq.java @@ -0,0 +1,49 @@ +package top.baogutang.admin.domain.req; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +/** + * @description: 注册请求参数 + * @author: nikooh + * @date: 2023/06/19 : 12:11 + */ +@Data +public class SysUserRegisterReq implements Serializable { + + private static final long serialVersionUID = -8279662824540042891L; + + /** + * 注册邮箱 + */ + @NotBlank(message = "邮箱不能为空") + private String email; + + /** + * 注册手机号 + */ + @NotBlank(message = "手机不能为空") + private String mobile; + + /** + * 账户密码 + */ + @NotBlank(message = "密码不能为空") + @Length(min = 6, max = 10, message = "密码长度请设置在6~10位之间") + private String password; + + /** + * 验证码 + */ + @NotBlank(message = "验证码不能为空") + private String captcha; + + /** + * 验证码key + */ + @NotBlank(message = "验证码key不能为空") + private String checkKey; +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/domain/res/SysUserInfo.java b/baogutang-admin/src/main/java/top/baogutang/admin/domain/res/SysUserInfo.java new file mode 100644 index 0000000..0c3dce0 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/domain/res/SysUserInfo.java @@ -0,0 +1,36 @@ +package top.baogutang.admin.domain.res; + + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author n + * @date 2021/11/21 + */ +@Data +@ApiModel("用户token信息") +public class SysUserInfo implements Serializable { + + private static final long serialVersionUID = -2577609323497270414L; + + @ApiModelProperty(value = "token(加在请求头里)") + private String accessToken; + + private String tokenType; + + @ApiModelProperty(value = "token失效后根据refresh_token刷新") + private String refreshToken; + + @ApiModelProperty(value = "用户ID") + private Long userId; + + @ApiModelProperty(value = "用户邮箱") + private String email; + + @ApiModelProperty(value = "用户手机号") + private String mobile; +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/services/ICommonService.java b/baogutang-admin/src/main/java/top/baogutang/admin/services/ICommonService.java new file mode 100644 index 0000000..74e07ca --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/services/ICommonService.java @@ -0,0 +1,17 @@ +package top.baogutang.admin.services; + +/** + * @description: + * @author: nikooh + * @date: 2023/06/19 : 12:20 + */ +public interface ICommonService { + + /** + * 获取验证码 + * + * @param key 验证码key + * @return 验证码 + */ + String randomImage(String key); +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/services/ISysUserService.java b/baogutang-admin/src/main/java/top/baogutang/admin/services/ISysUserService.java new file mode 100644 index 0000000..6f8914b --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/services/ISysUserService.java @@ -0,0 +1,34 @@ +package top.baogutang.admin.services; + +import cn.dev33.satoken.stp.SaTokenInfo; +import top.baogutang.admin.domain.req.SysUserLoginReq; +import top.baogutang.admin.domain.req.SysUserRegisterReq; +import top.baogutang.admin.domain.res.SysUserInfo; + +/** + * @description: + * @author: nikooh + * @date: 2023/06/19 : 12:13 + */ +public interface ISysUserService { + + /** + * 后台系统用户注册 + * + * @param sysUserRegisterReq 注册请求参数 + */ + void register(SysUserRegisterReq sysUserRegisterReq); + + /** + * 登陆接口 + * + * @param sysUserLoginReq 登陆请求参数 + * @return 登陆用户信息 + */ + SaTokenInfo login(SysUserLoginReq sysUserLoginReq); + + /** + * 登出接口 + */ + void logout(); +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/services/impl/CommonServiceImpl.java b/baogutang-admin/src/main/java/top/baogutang/admin/services/impl/CommonServiceImpl.java new file mode 100644 index 0000000..0719d58 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/services/impl/CommonServiceImpl.java @@ -0,0 +1,48 @@ +package top.baogutang.admin.services.impl; + +import cn.hutool.core.util.RandomUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import top.baogutang.admin.services.ICommonService; +import top.baogutang.common.constants.ErrorCodeEnum; +import top.baogutang.common.exceptions.BusinessException; +import top.baogutang.common.utils.RandImageUtil; + +import javax.annotation.Resource; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static top.baogutang.common.constants.CacheConstant.RANDOM_IMAGE_CACHE_KEY; + +/** + * @description: + * @author: nikooh + * @date: 2023/06/19 : 12:20 + */ +@Slf4j +@Service +public class CommonServiceImpl implements ICommonService { + + /** + * 验证码 + */ + private static final String BASE_CHECK_CODES = "qwertyuipkjhgfdsazxcvbnmQWERTYUPKJHGFDSAZXCVBNM23456789"; + + @Resource + private RedisTemplate redisTemplate; + + @Override + public String randomImage(String key) { + String captcha = RandomUtil.randomString(BASE_CHECK_CODES, 4); + String cacheKey = String.format(RANDOM_IMAGE_CACHE_KEY, key); + redisTemplate.opsForValue().set(cacheKey, captcha, 5, TimeUnit.MINUTES); + try { + return RandImageUtil.generate(captcha); + } catch (IOException e) { + log.error(">>>>>>>>>>获取图片验证码失败:{}<<<<<<<<<<", e.getMessage(), e); + throw new BusinessException(ErrorCodeEnum.E_BIZ_ERROR); + } + } +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/services/impl/SysUserServiceImpl.java b/baogutang-admin/src/main/java/top/baogutang/admin/services/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..7e1d96f --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/services/impl/SysUserServiceImpl.java @@ -0,0 +1,105 @@ +package top.baogutang.admin.services.impl; + +import cn.dev33.satoken.stp.SaTokenInfo; +import cn.dev33.satoken.stp.StpUtil; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.baogutang.admin.dao.entity.SysUserEntity; +import top.baogutang.admin.dao.mapper.SysUserMapper; +import top.baogutang.admin.domain.req.SysUserLoginReq; +import top.baogutang.admin.domain.req.SysUserRegisterReq; +import top.baogutang.admin.services.ISysUserService; +import top.baogutang.common.annotation.RequestLimit; +import top.baogutang.common.constants.ErrorCodeEnum; +import top.baogutang.common.exceptions.BusinessException; + +import javax.annotation.Resource; + +import java.util.Objects; + +import static top.baogutang.common.constants.CacheConstant.ENABLE; +import static top.baogutang.common.constants.CacheConstant.RANDOM_IMAGE_CACHE_KEY; + +/** + * @description: + * @author: nikooh + * @date: 2023/06/19 : 12:14 + */ +@Slf4j +@Service +public class SysUserServiceImpl extends ServiceImpl implements ISysUserService { + + @Resource + private SysUserMapper sysUserMapper; + + @Resource + private RedisTemplate redisTemplate; + + @Resource + private PasswordEncoder passwordEncoder; + + + @Override + @RequestLimit + @Transactional(rollbackFor = Exception.class) + public void register(SysUserRegisterReq sysUserRegisterReq) { + String cacheKey = String.format(RANDOM_IMAGE_CACHE_KEY, sysUserRegisterReq.getCheckKey()); + Object cacheCaptcha = redisTemplate.opsForValue().get(cacheKey); + if (Objects.isNull(cacheCaptcha)) { + throw new BusinessException(ErrorCodeEnum.CAPTCHA_NOT_EXISTS); + } + if (!sysUserRegisterReq.getCaptcha().equalsIgnoreCase((String) cacheCaptcha)) { + throw new BusinessException(ErrorCodeEnum.CAPTCHA_INCORRECT); + } + // 查询邮箱手机号是否已占用 + SysUserEntity sysUser = baseMapper.selectByEmailOrMobile(sysUserRegisterReq.getEmail(), sysUserRegisterReq.getMobile()); + if (Objects.nonNull(sysUser)) { + throw new BusinessException(ErrorCodeEnum.CURRENT_USER_EXISTS); + } + // 可以进行注册 + SysUserEntity userEntity = new SysUserEntity(); + userEntity.setId(IdWorker.getId()); + userEntity.setUsername(sysUserRegisterReq.getEmail()); + userEntity.setEmail(sysUserRegisterReq.getEmail()); + userEntity.setMobile(sysUserRegisterReq.getMobile()); + userEntity.setPassword(passwordEncoder.encode(sysUserRegisterReq.getPassword())); + saveOrUpdate(userEntity); + } + + @Override + public SaTokenInfo login(SysUserLoginReq sysUserLoginReq) { + String password = sysUserLoginReq.getPassword(); + SysUserEntity userEntity = this.findByEmail(sysUserLoginReq.getEmail()); + if (userEntity == null) { + throw new BusinessException(ErrorCodeEnum.E_BIZ_DIGITAL_ART_USER_NOT_EXISTS); + } + boolean userVerify = passwordEncoder.matches(password, userEntity.getPassword()); + if (!userVerify) { + throw new BusinessException(ErrorCodeEnum.PASSWORD_ERROR); + } + // Sa-Token + StpUtil.login(userEntity.getId()); + return StpUtil.getTokenInfo(); + } + + @Override + public void logout() { + StpUtil.logout(); + } + + + private SysUserEntity findByEmail(String email) { + Wrapper wrapper = Wrappers.query() + .lambda() + .eq(SysUserEntity::getEmail, email) + .eq(SysUserEntity::getEnableFlag, ENABLE); + return this.getOne(wrapper); + } +} diff --git a/baogutang-admin/src/main/resources/top/baogutang/admin/dao/mapper/SysUserMapper.xml b/baogutang-admin/src/main/resources/top/baogutang/admin/dao/mapper/SysUserMapper.xml new file mode 100644 index 0000000..65d349e --- /dev/null +++ b/baogutang-admin/src/main/resources/top/baogutang/admin/dao/mapper/SysUserMapper.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/baogutang-common/pom.xml b/baogutang-common/pom.xml index 912d22d..ea7ae0b 100644 --- a/baogutang-common/pom.xml +++ b/baogutang-common/pom.xml @@ -134,6 +134,16 @@ wxpusher-java-sdk 2.1.4 + + org.apache.commons + commons-lang3 + 3.12.0 + + + cn.hutool + hutool-all + 5.7.4 + \ No newline at end of file diff --git a/baogutang-common/src/main/java/top/baogutang/common/constants/CacheConstant.java b/baogutang-common/src/main/java/top/baogutang/common/constants/CacheConstant.java index c94abe8..56a0edb 100644 --- a/baogutang-common/src/main/java/top/baogutang/common/constants/CacheConstant.java +++ b/baogutang-common/src/main/java/top/baogutang/common/constants/CacheConstant.java @@ -18,4 +18,14 @@ public class CacheConstant { * 微信消息推送获取临时二维码缓存key */ public static final String WX_MSG_PUSH_QR_CODE_PREFIX = "top:baogutang:wx:msg_push:%s:"; + + /** + * 随机验证码图片key + */ + public static final String RANDOM_IMAGE_CACHE_KEY = "top:baogutang:random_image:%s:"; + + public static final String SYS_TOKEN = "top:baogutang:sys:user:token:"; + + + public static final Integer ENABLE = 0; } diff --git a/baogutang-common/src/main/java/top/baogutang/common/constants/ErrorCodeEnum.java b/baogutang-common/src/main/java/top/baogutang/common/constants/ErrorCodeEnum.java index 012ebba..2f3e5dd 100644 --- a/baogutang-common/src/main/java/top/baogutang/common/constants/ErrorCodeEnum.java +++ b/baogutang-common/src/main/java/top/baogutang/common/constants/ErrorCodeEnum.java @@ -60,7 +60,15 @@ public enum ErrorCodeEnum { FORBIDDEN(403, "没有相关权限"), AUTHORIZATION_HEADER_IS_EMPTY(600, "请求头中的token为空"), - DATA_IS_NULL(81011033, "数据为空");; + DATA_IS_NULL(81011033, "数据为空"), + + CAPTCHA_NOT_EXISTS(81011034, "验证码不存在"), + + CAPTCHA_INCORRECT(81011035, "验证码错误"), + + CURRENT_USER_EXISTS(81011036, "当前用户已存在"), + + ; private final int code; private final String msg; diff --git a/baogutang-common/src/main/java/top/baogutang/common/utils/MD5Utils.java b/baogutang-common/src/main/java/top/baogutang/common/utils/MD5Utils.java new file mode 100644 index 0000000..5bfa606 --- /dev/null +++ b/baogutang-common/src/main/java/top/baogutang/common/utils/MD5Utils.java @@ -0,0 +1,36 @@ +package top.baogutang.common.utils; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * @author developer + * 采用MD5加密 + */ +public class MD5Utils { + /*** + * MD5加密 生成32位md5码 + * @param inStr 待加密字符串 + * @return 返回32位md5码 + */ + public static String md5Encode(String inStr) { + MessageDigest md5; + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (Exception e) { + return ""; + } + + byte[] byteArray = inStr.getBytes(StandardCharsets.UTF_8); + byte[] md5Bytes = md5.digest(byteArray); + StringBuilder hexValue = new StringBuilder(); + for (byte md5Byte : md5Bytes) { + int val = (md5Byte) & 0xff; + if (val < 16) { + hexValue.append("0"); + } + hexValue.append(Integer.toHexString(val)); + } + return hexValue.toString(); + } +} diff --git a/baogutang-common/src/main/java/top/baogutang/common/utils/RandImageUtil.java b/baogutang-common/src/main/java/top/baogutang/common/utils/RandImageUtil.java new file mode 100644 index 0000000..3e9bbf2 --- /dev/null +++ b/baogutang-common/src/main/java/top/baogutang/common/utils/RandImageUtil.java @@ -0,0 +1,138 @@ +package top.baogutang.common.utils; + +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletResponse; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; +import java.util.Random; + +/** + * 登录验证码工具类 + */ +public class RandImageUtil { + + /** + * 定义图形大小 + */ + private static final int width = 105; + /** + * 定义图形大小 + */ + private static final int height = 35; + + /** + * 定义干扰线数量 + */ + private static final int count = 200; + + /** + * 干扰线的长度=1.414*lineWidth + */ + private static final int lineWidth = 2; + + /** + * 图片格式 + */ + private static final String IMG_FORMAT = "JPEG"; + + /** + * base64 图片前缀 + */ + private static final String BASE64_PRE = "data:image/jpg;base64,"; + + /** + * 直接通过response 返回图片 + * @param response + * @param resultCode + * @throws IOException + */ + public static void generate(HttpServletResponse response, String resultCode) throws IOException { + BufferedImage image = getImageBuffer(resultCode); + // 输出图象到页面 + ImageIO.write(image, IMG_FORMAT, response.getOutputStream()); + } + + /** + * 生成base64字符串 + * @param resultCode + * @return + * @throws IOException + */ + public static String generate(String resultCode) throws IOException { + BufferedImage image = getImageBuffer(resultCode); + + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + //写入流中 + ImageIO.write(image, IMG_FORMAT, byteStream); + //转换成字节 + byte[] bytes = byteStream.toByteArray(); + //转换成base64串 + String base64 = Base64.getEncoder().encodeToString(bytes).trim(); + //删除 \r\n + base64 = base64.replaceAll("\n", "").replaceAll("\r", ""); + + return BASE64_PRE+base64; + } + + private static BufferedImage getImageBuffer(String resultCode){ + // 在内存中创建图象 + final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + // 获取图形上下文 + final Graphics2D graphics = (Graphics2D) image.getGraphics(); + // 设定背景颜色 + // ---1 + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, width, height); + // 设定边框颜色 +// graphics.setColor(getRandColor(100, 200)); // ---2 + graphics.drawRect(0, 0, width - 1, height - 1); + + final Random random = new Random(); + // 随机产生干扰线,使图象中的认证码不易被其它程序探测到 + for (int i = 0; i < count; i++) { + // ---3 + graphics.setColor(getRandColor(150, 200)); + // 保证画在边框之内 + final int x = random.nextInt(width - lineWidth - 1) + 1; + final int y = random.nextInt(height - lineWidth - 1) + 1; + final int xl = random.nextInt(lineWidth); + final int yl = random.nextInt(lineWidth); + graphics.drawLine(x, y, x + xl, y + yl); + } + // 取随机产生的认证码 + for (int i = 0; i < resultCode.length(); i++) { + // 将认证码显示到图象中,调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成 + // graphics.setColor(new Color(20 + random.nextInt(130), 20 + random + // .nextInt(130), 20 + random.nextInt(130))); + // 设置字体颜色 + graphics.setColor(Color.BLACK); + // 设置字体样式 +// graphics.setFont(new Font("Arial Black", Font.ITALIC, 18)); + graphics.setFont(new Font("Times New Roman", Font.BOLD, 24)); + // 设置字符,字符间距,上边距 + graphics.drawString(String.valueOf(resultCode.charAt(i)), (23 * i) + 8, 26); + } + // 图象生效 + graphics.dispose(); + return image; + } + + private static Color getRandColor(int fc, int bc) { + final Random random = new Random(); + if (fc > 255) { + fc = 255; + } + if (bc > 255) { + bc = 255; + } + + final int r = fc + random.nextInt(bc - fc); + final int g = fc + random.nextInt(bc - fc); + final int b = fc + random.nextInt(bc - fc); + + return new Color(r, g, b); + } +}