diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5ce78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +### IntelliJ IDEA ### +.idea + +**/target/ +logs/ +**/**/**.iml +**/.DS_Store diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b731cb5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 赵日天 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..29f1e14 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# base-springboot + +## SpringBoot开发模版 + +### 功能 +* 注册、登录、用户详情、接口日志、ip属地、发送邮件、修改密码、找回密码、用户权限、角色、菜单、ip异常邮件提醒 + +*** +### 准备 +* jdk17 +* maven3.5+ +* redis +* mysql8 + +*** +### 使用 +* 数据库新建utf8mb4数据库,运行db文件夹下的db.sql文件。 +* 更改application-dev.yml中mysql数据库连接配置、redis连接配置 +* resources下的ip2region.xdb文件放在jar同通目录下 例:jar包放在/usr/local/business/ 则该文件同样放在该目录 + + +*** +### 框架选型: +

+     SpringBoot + Mybatis-plus + Redis
+     登录框架 Sa-Token 框架文档 +

+ +#### 项目结构: +``` + base: + -- admin: + -- business: + -- common: + -- common-core + -- common-framework +``` +common为公共包,包含core和framework两个子包: +

+     common-core主要是自定义注解、constant、枚举类、自定义异常、properties、utils等 +

+

+     common-framework主要是filter、listener、日志打印、日志记录、Mybatis-plus配置、统一异常处理等 +

+admin 为后台管理模块包,处理管理后台逻辑。 +business为业务包,用来处理业务。不赘述。 + +**** +#### 自定义注解: + +@ResponseResult + +

+     用来封装返回值,可放在controller上,或者controller中的单个方法上。 +

+ +@OperationLog + +

+     用来记录接口日志,包含请求接口名称、用户id、ip、入参、异常信息等,可放在controller中的单个方法上。 +

+ +@ReleaseToken + +

+     用来跳过token校验,可放在controller中的单个方法上。 +

+ +@SearchDate +

+     加在Date类型上,搜索条件开始时间结束时间,自动拼接开始时间00:00:00和结束时间23:59:59 +

+ +*** +#### 常用utils: + +LoginUtils +

+     登录相关utils,可获取用户基本信息,角色,token等 +

+ +ApiAssert +

+     断言工具类,以断言方式抛出自定义异常 +

+ +EnuUtils +

+     枚举工具类,判断指定code是否属于指定枚举类中的数据 +

+ +ApplicationUtils +

+    程序工具类,可获取SpringBean,程序上下文等 +

+ +TreeUtil +

+    通用树状工具类 +

+ +MailUtils +

+    发送邮件工具 +

diff --git a/admin/README.md b/admin/README.md new file mode 100644 index 0000000..c0d9ab2 --- /dev/null +++ b/admin/README.md @@ -0,0 +1,4 @@ +# 工程简介 + +# 延伸阅读 + diff --git a/admin/pom.xml b/admin/pom.xml new file mode 100644 index 0000000..388f47a --- /dev/null +++ b/admin/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + admin + admin + admin + + + base + com.yxx + 1.0.0 + + + + + dev + + + dev + + + + true + + + + prod + + prod + + + + + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + com.yxx + common-core + 1.0.0 + + + com.yxx + common-framework + 1.0.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + com.yxx.admin.AdminApplication + + + + repackage + + repackage + + + + + + + + diff --git a/admin/src/main/java/com/yxx/admin/AdminApplication.java b/admin/src/main/java/com/yxx/admin/AdminApplication.java new file mode 100644 index 0000000..b0ae63a --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/AdminApplication.java @@ -0,0 +1,15 @@ +package com.yxx.admin; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = {"com.yxx.admin", "com.yxx.common", "com.yxx.framework"}) +@MapperScan("com.yxx.admin.mapper") +public class AdminApplication { + + public static void main(String[] args) { + SpringApplication.run(AdminApplication.class, args); + } + +} diff --git a/admin/src/main/java/com/yxx/admin/controller/AdminAuthController.java b/admin/src/main/java/com/yxx/admin/controller/AdminAuthController.java new file mode 100644 index 0000000..946810a --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/controller/AdminAuthController.java @@ -0,0 +1,57 @@ +package com.yxx.admin.controller; + +import com.yxx.admin.model.request.LoginReq; +import com.yxx.admin.model.response.LoginRes; +import com.yxx.admin.service.AdminUserService; +import com.yxx.common.annotation.auth.ReleaseToken; +import com.yxx.common.annotation.log.OperationLog; +import com.yxx.common.annotation.response.ResponseResult; +import com.yxx.common.utils.satoken.StpAdminUtil; +import lombok.RequiredArgsConstructor; +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 jakarta.validation.Valid; + +/** + * @author yxx + * @since 2023-05-17 10:02 + */ +@Slf4j +@Validated +@ResponseResult +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +public class AdminAuthController { + private final AdminUserService adminUserService; + + /** + * 登录 + * + * @param request 请求 + * @return {@link LoginRes } + * @author yxx + */ + @ReleaseToken + @OperationLog(module = "鉴权模块", title = "pc登录") + @PostMapping("/login") + public LoginRes login(@Valid @RequestBody LoginReq request) { + return adminUserService.login(request); + } + + /** + * 注销 + * + * @author yxx + */ + @OperationLog(module = "鉴权模块", title = "pc退出") + @PostMapping("/logout") + public void logout() { + StpAdminUtil.logout(); + } +} diff --git a/admin/src/main/java/com/yxx/admin/controller/AdminUserController.java b/admin/src/main/java/com/yxx/admin/controller/AdminUserController.java new file mode 100644 index 0000000..57f2f41 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/controller/AdminUserController.java @@ -0,0 +1,85 @@ +package com.yxx.admin.controller; + +import com.yxx.admin.model.request.*; +import com.yxx.admin.service.AdminUserService; +import com.yxx.common.annotation.auth.ReleaseToken; +import com.yxx.common.annotation.log.OperationLog; +import com.yxx.common.annotation.response.ResponseResult; +import com.yxx.common.core.model.LoginUser; +import com.yxx.common.utils.auth.LoginAdminUtils; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * @author yxx + * @since 2022-11-12 02:07 + */ +@Slf4j +@Validated +@ResponseResult +@RestController +@RequestMapping("/user") +@RequiredArgsConstructor +public class AdminUserController { + + private final AdminUserService adminUserService; + + /** + * 获取用户信息 + * + * @return {@link LoginUser } + * @author yxx + */ + @OperationLog(module = "用户模块", title = "获取用户信息") + @GetMapping("/info") + public LoginUser info() { + Long userId = LoginAdminUtils.getUserId(); + log.info("userId为[{}]", userId); + return LoginAdminUtils.getLoginUser(); + } + + /** + * 发送重置密码邮件 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + @ReleaseToken + @OperationLog(module = "用户模块", title = "发送重置密码邮件") + @PostMapping("/resetPwdEmail") + public Boolean resetPwdEmail(@Valid @RequestBody ResetPwdEmailReq req){ + return adminUserService.resetPwdEmail(req); + } + + /** + * 重置密码 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + @ReleaseToken + @OperationLog(module = "用户模块", title = "重置密码") + @PostMapping("/resetPwd") + public Boolean resetPwd(@Valid @RequestBody ResetPwdReq req){ + return adminUserService.resetPwd(req); + } + + /** + * 修改密码 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + @OperationLog(module = "用户模块", title = "修改密码") + @PostMapping("/editPwd") + public Boolean editPwd(@Valid @RequestBody EditPwdReq req){ + return adminUserService.editPwd(req); + } + +} diff --git a/admin/src/main/java/com/yxx/admin/controller/OperateAdminLogController.java b/admin/src/main/java/com/yxx/admin/controller/OperateAdminLogController.java new file mode 100644 index 0000000..f5b3938 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/controller/OperateAdminLogController.java @@ -0,0 +1,59 @@ +package com.yxx.admin.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yxx.admin.model.request.OperateLogReq; +import com.yxx.admin.model.response.OperateLogResp; +import com.yxx.admin.service.OperateAdminLogService; +import com.yxx.common.annotation.response.ResponseResult; +import com.yxx.common.annotation.satoken.SaAdminCheckRole; +import lombok.RequiredArgsConstructor; +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 jakarta.validation.Valid; + +/** + * @author yxx + * @description 注:@SaCheckRole("super_admin") + * 表示只有 super_admin 角色的用户才可访问此controller。 + * 该注解亦可放在具体的方法上 + * @since 2023-05-17 15:39 + */ +@Slf4j +@Validated +@ResponseResult +@RestController +@RequestMapping("/log") +@RequiredArgsConstructor +@SaAdminCheckRole("super_admin") +public class OperateAdminLogController { + private final OperateAdminLogService operateAdminLogService; + + /** + * 身份验证登录日志数据分页 + * + * @param req 要求事情 + * @return {@link Page }<{@link OperateLogResp }> + * @author yxx + */ + @PostMapping("/auth") + public Page authLogPage(@Valid @RequestBody OperateLogReq req) { + return operateAdminLogService.authLogPage(req); + } + + /** + * 操作日志数据分页 + * + * @param req 要求事情 + * @return {@link Page }<{@link OperateLogResp }> + * @author yxx + */ + @PostMapping("/operation") + public Page operationLogPage(@Valid @RequestBody OperateLogReq req) { + return operateAdminLogService.operationLogPage(req); + } +} diff --git a/admin/src/main/java/com/yxx/admin/mapper/AdminMenuMapper.java b/admin/src/main/java/com/yxx/admin/mapper/AdminMenuMapper.java new file mode 100644 index 0000000..307937a --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/mapper/AdminMenuMapper.java @@ -0,0 +1,11 @@ +package com.yxx.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxx.admin.model.entity.AdminMenu; + +/** + * @author yxx + * @since 2023-05-18 15:04 + */ +public interface AdminMenuMapper extends BaseMapper { +} diff --git a/admin/src/main/java/com/yxx/admin/mapper/AdminRoleMapper.java b/admin/src/main/java/com/yxx/admin/mapper/AdminRoleMapper.java new file mode 100644 index 0000000..92c3176 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/mapper/AdminRoleMapper.java @@ -0,0 +1,11 @@ +package com.yxx.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxx.admin.model.entity.AdminRole; + +/** + * @author yxx + * @since 2023-05-17 09:59 + */ +public interface AdminRoleMapper extends BaseMapper { +} diff --git a/admin/src/main/java/com/yxx/admin/mapper/AdminRoleMenuMapper.java b/admin/src/main/java/com/yxx/admin/mapper/AdminRoleMenuMapper.java new file mode 100644 index 0000000..cc9c28b --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/mapper/AdminRoleMenuMapper.java @@ -0,0 +1,11 @@ +package com.yxx.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxx.admin.model.entity.AdminRoleMenu; + +/** + * @author yxx + * @since 2023-05-18 15:22 + */ +public interface AdminRoleMenuMapper extends BaseMapper { +} diff --git a/admin/src/main/java/com/yxx/admin/mapper/AdminUserMapper.java b/admin/src/main/java/com/yxx/admin/mapper/AdminUserMapper.java new file mode 100644 index 0000000..492453e --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/mapper/AdminUserMapper.java @@ -0,0 +1,11 @@ +package com.yxx.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxx.admin.model.entity.AdminUser; + +/** + * @author yxx + * @since 2022-11-12 13:57 + */ +public interface AdminUserMapper extends BaseMapper { +} diff --git a/admin/src/main/java/com/yxx/admin/mapper/AdminUserRoleMapper.java b/admin/src/main/java/com/yxx/admin/mapper/AdminUserRoleMapper.java new file mode 100644 index 0000000..8e41054 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/mapper/AdminUserRoleMapper.java @@ -0,0 +1,11 @@ +package com.yxx.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxx.admin.model.entity.AdminUserRole; + +/** + * @author yxx + * @since 2023-05-17 10:01 + */ +public interface AdminUserRoleMapper extends BaseMapper { +} diff --git a/admin/src/main/java/com/yxx/admin/mapper/OperateAdminLogMapper.java b/admin/src/main/java/com/yxx/admin/mapper/OperateAdminLogMapper.java new file mode 100644 index 0000000..6999f20 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/mapper/OperateAdminLogMapper.java @@ -0,0 +1,36 @@ +package com.yxx.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yxx.admin.model.request.OperateLogReq; +import com.yxx.admin.model.response.OperateLogResp; +import com.yxx.common.core.model.OperateAdminLog; +import org.apache.ibatis.annotations.Param; + +/** + * 操作日志表 Mapper 接口 + * + * @author yxx + * @since 2022-07-15 + */ +public interface OperateAdminLogMapper extends BaseMapper { + + /** + * 查询操作日志分页列表 + * + * @param page 分页参数 + * @param req OperateLogReq + * @return 操作日志分页列表 + */ + Page operationLogPage(@Param("page") Page page, + @Param("req") OperateLogReq req); + + /** + * 登录日志分页 + * + * @param page 分页构造器 + * @param req 请求参数 + * @return 分页结果 + */ + Page authLogPage(@Param("page") Page page, @Param("req") OperateLogReq req); +} diff --git a/admin/src/main/java/com/yxx/admin/model/entity/AdminMenu.java b/admin/src/main/java/com/yxx/admin/model/entity/AdminMenu.java new file mode 100644 index 0000000..9e4b6c1 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/entity/AdminMenu.java @@ -0,0 +1,48 @@ +package com.yxx.admin.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @author yxx + * @since 2023-05-18 14:46 + */ +@Data +public class AdminMenu implements Serializable { + /** + * 主键 + */ + + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * 父id + */ + private Integer parentId; + + /** + * 菜单标识 + */ + private String menuCode; + + /** + * 菜单名称 + */ + private String menuName; + + /** + * 是否删除: 0- 否; 1- 是 + */ + @TableLogic + private Boolean isDelete; + + /** + * 子菜单集合 + */ + @TableField(exist = false) + private List children; +} diff --git a/admin/src/main/java/com/yxx/admin/model/entity/AdminRole.java b/admin/src/main/java/com/yxx/admin/model/entity/AdminRole.java new file mode 100644 index 0000000..9c67255 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/entity/AdminRole.java @@ -0,0 +1,56 @@ +package com.yxx.admin.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @author yxx + * @since 2023-05-17 09:35 + */ +@Data +public class AdminRole implements Serializable { + /** + * id + */ + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * 角色code + */ + private String code; + + /** + * 角色名称 + */ + private String name; + + /** + * 说明 + */ + private String remark; + + /** + * 是否删除:0-未删除;1-已删除 + */ + @TableLogic + private Boolean isDelete; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; +} diff --git a/admin/src/main/java/com/yxx/admin/model/entity/AdminRoleMenu.java b/admin/src/main/java/com/yxx/admin/model/entity/AdminRoleMenu.java new file mode 100644 index 0000000..3bcf0da --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/entity/AdminRoleMenu.java @@ -0,0 +1,32 @@ +package com.yxx.admin.model.entity; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import lombok.Data; + +/** + * @author yxx + * @since 2023-05-18 15:18 + */ +@Data +public class AdminRoleMenu { + /** + * 主键 + */ + private Integer id; + + /** + * 角色id + */ + private Integer roleId; + + /** + * 菜单id + */ + private Integer menuId; + + /** + * 是否删除:0-未删除;1-已删除 + */ + @TableLogic + private Boolean isDelete; +} diff --git a/admin/src/main/java/com/yxx/admin/model/entity/AdminUser.java b/admin/src/main/java/com/yxx/admin/model/entity/AdminUser.java new file mode 100644 index 0000000..c49504b --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/entity/AdminUser.java @@ -0,0 +1,55 @@ +package com.yxx.admin.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author yxx + * @since 2022-11-12 13:38 + */ +@Data +public class AdminUser extends BaseEntity implements Serializable{ + /** + * 用户id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 登录账号 + */ + private String loginCode; + + /** + * 登录名 + */ + private String loginName; + + /** + * 密码 + */ + private String password; + + /** + * 手机号 + */ + private String linkPhone; + + /** + * 邮箱 + */ + private String email; + + /** + * ip归属地 + */ + private String ipHomePlace; + + /** + * 登录设备 + */ + private String agent; +} diff --git a/admin/src/main/java/com/yxx/admin/model/entity/AdminUserRole.java b/admin/src/main/java/com/yxx/admin/model/entity/AdminUserRole.java new file mode 100644 index 0000000..8888e6a --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/entity/AdminUserRole.java @@ -0,0 +1,51 @@ +package com.yxx.admin.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @author yxx + * @since 2023-05-17 09:38 + */ +@Data +public class AdminUserRole implements Serializable { + /** + * id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 角色id + */ + private Integer roleId; + + /** + * 是否删除:0-未删除;1-已删除 + */ + @TableLogic + private Boolean isDelete; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; +} diff --git a/admin/src/main/java/com/yxx/admin/model/entity/BaseEntity.java b/admin/src/main/java/com/yxx/admin/model/entity/BaseEntity.java new file mode 100644 index 0000000..c71510c --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/entity/BaseEntity.java @@ -0,0 +1,46 @@ +package com.yxx.admin.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 实体类基础数据 + * + * @author yxx + * @classname BaseEntity + * @since 2023-06-29 21:44 + */ +@Data +public class BaseEntity { + + /** + * 是否删除:0-未删除;1-已删除 + */ + @TableLogic + private Boolean isDelete; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; + + @TableField(fill = FieldFill.INSERT) + private Long createUid; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUid; +} diff --git a/admin/src/main/java/com/yxx/admin/model/lombok.config b/admin/src/main/java/com/yxx/admin/model/lombok.config new file mode 100644 index 0000000..8e37527 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling=true +lombok.equalsAndHashCode.callSuper=call \ No newline at end of file diff --git a/admin/src/main/java/com/yxx/admin/model/request/EditPwdReq.java b/admin/src/main/java/com/yxx/admin/model/request/EditPwdReq.java new file mode 100644 index 0000000..b884fc4 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/request/EditPwdReq.java @@ -0,0 +1,30 @@ +package com.yxx.admin.model.request; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import jakarta.validation.constraints.NotBlank; + +/** + * 修改密码请求参数 + * + * @author yxx + * @classname EditPwdReq + * @since 2023-07-25 15:46 + */ +@Data +public class EditPwdReq { + /** + * 旧密码 + */ + @NotBlank(message = "旧密码不能为空") + @Length(min = 8, max = 15, message = "旧密码的长度为8-20位") + private String password; + + /** + * 新密码 + */ + @NotBlank(message = "新密码不能为空") + @Length(min = 8, max = 15, message = "新密码的长度应为8-20位") + private String newPassword; +} diff --git a/admin/src/main/java/com/yxx/admin/model/request/LoginReq.java b/admin/src/main/java/com/yxx/admin/model/request/LoginReq.java new file mode 100644 index 0000000..f465382 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/request/LoginReq.java @@ -0,0 +1,24 @@ +package com.yxx.admin.model.request; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; + +/** + * @author yxx + * @since 2022-11-12 14:00 + */ +@Data +public class LoginReq { + /** + * 登录账号 + */ + @NotBlank(message = "登录账号不能为空") + private String loginCode; + + /** + * 登录密码 + */ + @NotBlank(message = "密码不能为空") + private String password; +} diff --git a/admin/src/main/java/com/yxx/admin/model/request/OperateLogReq.java b/admin/src/main/java/com/yxx/admin/model/request/OperateLogReq.java new file mode 100644 index 0000000..12eafd7 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/request/OperateLogReq.java @@ -0,0 +1,46 @@ +package com.yxx.admin.model.request; + +import com.yxx.common.annotation.jackson.SearchDate; +import com.yxx.common.core.page.BasePageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +/** + * @author yxx + * @since 2022/8/1 17:39 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class OperateLogReq extends BasePageRequest implements Serializable { + + /** + * 用户名 + */ + private String loginCode; + + /** + * 用户名称 + */ + private String loginName; + + /** + * 操作内容 + */ + private String title; + + + /** + * 开始时间 + */ + @SearchDate(startDate = true) + private Date startTime; + + /** + * 结束时间 + */ + @SearchDate(endDate = true) + private Date endTime; +} diff --git a/admin/src/main/java/com/yxx/admin/model/request/RegisterCaptchaReq.java b/admin/src/main/java/com/yxx/admin/model/request/RegisterCaptchaReq.java new file mode 100644 index 0000000..a97ff3c --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/request/RegisterCaptchaReq.java @@ -0,0 +1,20 @@ +package com.yxx.admin.model.request; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +/** + * 注册验证码 + * + * @author yxx + * @classname RegisterCaptchaReq + * @since 2023-07-25 20:53 + */ +@Data +public class RegisterCaptchaReq { + @NotBlank(message = "邮箱不能为空") + @Pattern(regexp = "^[A-Za-z0-9]+([_.][A-Za-z0-9]+)*@((qq|163|gmail|88|email)+\\.)+[A-Za-z]{2,6}$", message = "请输入正确邮箱号,目前仅支持 qq邮箱、163邮箱、谷歌邮箱等常用邮箱") + private String email; +} diff --git a/admin/src/main/java/com/yxx/admin/model/request/ResetPwdEmailReq.java b/admin/src/main/java/com/yxx/admin/model/request/ResetPwdEmailReq.java new file mode 100644 index 0000000..fabfd63 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/request/ResetPwdEmailReq.java @@ -0,0 +1,21 @@ +package com.yxx.admin.model.request; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +/** + * 获取重置密码邮件请求参数 + * + * @author yxx + * @classname ResetPwdReq + * @since 2023-07-25 13:59 + */ +@Data +public class ResetPwdEmailReq { + + @NotBlank(message = "邮箱不能为空") + @Pattern(regexp = "^[A-Za-z0-9]+([_.][A-Za-z0-9]+)*@((qq|163|gmail|88|email)+\\.)+[A-Za-z]{2,6}$", message = "请输入正确邮箱号,目前仅支持 qq邮箱、163邮箱、谷歌邮箱等常用邮箱") + private String email; +} diff --git a/admin/src/main/java/com/yxx/admin/model/request/ResetPwdReq.java b/admin/src/main/java/com/yxx/admin/model/request/ResetPwdReq.java new file mode 100644 index 0000000..e9d482b --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/request/ResetPwdReq.java @@ -0,0 +1,26 @@ +package com.yxx.admin.model.request; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import jakarta.validation.constraints.NotBlank; + +/** + * 重置密码请求参数 + * + * @author yxx + * @classname ResetPwdReq + * @since 2023-07-25 15:18 + */ +@Data +public class ResetPwdReq { + /** + * 新密码 + */ + @NotBlank(message = "密码不能为空") + @Length(min = 8, max = 20, message = "密码应为8-20位") + private String newPassword; + + @NotBlank(message = "token不能为空") + private String token; +} diff --git a/admin/src/main/java/com/yxx/admin/model/request/UserRegisterReq.java b/admin/src/main/java/com/yxx/admin/model/request/UserRegisterReq.java new file mode 100644 index 0000000..8199b3a --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/request/UserRegisterReq.java @@ -0,0 +1,54 @@ +package com.yxx.admin.model.request; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +/** + * @author yxx + * @since 2023-05-15 14:11 + */ +@Data +public class UserRegisterReq { + /** + * 登录账号 + */ + @NotBlank(message = "登录账号不能为空") + @Length(min = 4, max = 12, message = "登录账号应为4-12位") + private String loginCode; + + /** + * 登录名 + */ + @NotBlank(message = "昵称不能为空") + @Length(min = 2, max = 8, message = "昵称应为2-8位") + private String loginName; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空") + @Length(min = 8, max = 20, message = "密码应为8-20位") + private String password; + + /** + * 手机号 + */ + @Pattern(regexp = "^$|^1[3456789]\\d{9}$", message = "请输入正确手机号") + private String linkPhone; + + /** + * 邮箱 + */ + @Pattern(regexp = "^$|^[A-Za-z0-9]+([_.][A-Za-z0-9]+)*@((qq|163|gmail|88|email)+\\.)+[A-Za-z]{2,6}$", message = "请输入正确邮箱号,目前仅支持 qq邮箱、163邮箱、谷歌邮箱等常用邮箱") + private String email; + + + /** + * 验证码 + */ + @NotBlank(message = "验证码不能为空") + private String captcha; +} diff --git a/admin/src/main/java/com/yxx/admin/model/response/LoginRes.java b/admin/src/main/java/com/yxx/admin/model/response/LoginRes.java new file mode 100644 index 0000000..77300f3 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/response/LoginRes.java @@ -0,0 +1,16 @@ +package com.yxx.admin.model.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author yxx + * @since 2022-11-12 03:42 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginRes { + private String token; +} diff --git a/admin/src/main/java/com/yxx/admin/model/response/OperateLogResp.java b/admin/src/main/java/com/yxx/admin/model/response/OperateLogResp.java new file mode 100644 index 0000000..6672582 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/model/response/OperateLogResp.java @@ -0,0 +1,63 @@ +package com.yxx.admin.model.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * @author yxx + * @since 2022/8/1 17:43 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class OperateLogResp { + private static final long serialVersionUID = -6160374035388312821L; + + /** + * 主键ID + */ + private Long id; + + /** + * 操作模块 + */ + private String module; + + /** + * 日志标题 + */ + private String title; + + /** + * 操作IP + */ + private String ip; + + /** + * 类型:1:成功;2:失败 + */ + private Integer type; + + /** + * 异常信息 + */ + private String exception; + + /** + * 操作用户 + */ + private String loginCode; + + /** + * 用户名称 + */ + private String loginName; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; +} diff --git a/admin/src/main/java/com/yxx/admin/service/AdminMenuService.java b/admin/src/main/java/com/yxx/admin/service/AdminMenuService.java new file mode 100644 index 0000000..6d25bad --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/AdminMenuService.java @@ -0,0 +1,18 @@ +package com.yxx.admin.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.admin.model.entity.AdminMenu; + +import java.util.List; + +/** + * @author yxx + * @since 2023-05-18 15:04 + */ +public interface AdminMenuService extends IService { + /** + * 菜单树 + * @return 菜单树 + */ + List menuTree(); +} diff --git a/admin/src/main/java/com/yxx/admin/service/AdminRoleMenuService.java b/admin/src/main/java/com/yxx/admin/service/AdminRoleMenuService.java new file mode 100644 index 0000000..720bc24 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/AdminRoleMenuService.java @@ -0,0 +1,20 @@ +package com.yxx.admin.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.admin.model.entity.AdminRoleMenu; + +import java.util.List; + +/** + * @author yxx + * @since 2023-05-18 15:23 + */ +public interface AdminRoleMenuService extends IService { + /** + * 根据角色code集合 获取菜单code集合 + * + * @param roleCodeList 角色code集合 + * @return 菜单code集合 + */ + List loginUserMenu(List roleCodeList); +} diff --git a/admin/src/main/java/com/yxx/admin/service/AdminRoleService.java b/admin/src/main/java/com/yxx/admin/service/AdminRoleService.java new file mode 100644 index 0000000..8aef4ab --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/AdminRoleService.java @@ -0,0 +1,11 @@ +package com.yxx.admin.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.admin.model.entity.AdminRole; + +/** + * @author yxx + * @since 2023-05-17 09:58 + */ +public interface AdminRoleService extends IService { +} diff --git a/admin/src/main/java/com/yxx/admin/service/AdminUserRoleService.java b/admin/src/main/java/com/yxx/admin/service/AdminUserRoleService.java new file mode 100644 index 0000000..8a8f2c5 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/AdminUserRoleService.java @@ -0,0 +1,31 @@ +package com.yxx.admin.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.admin.model.entity.AdminUser; +import com.yxx.admin.model.entity.AdminUserRole; + +import java.util.List; + +/** + * @author yxx + * @since 2023-05-17 10:00 + */ +public interface AdminUserRoleService extends IService { + + /** + * 根据用户信息 获取该用户角色权限 + * + * @param user 用户信息 + * @return 用户角色code集合 + */ + List loginUserRoleManage(AdminUser user); + + /** + * 设置默认角色: 用户 + * + * @param user 用户信息 + * @return {@link Boolean } + * @author yxx + */ + Boolean setDefaultRole(AdminUser user); +} diff --git a/admin/src/main/java/com/yxx/admin/service/AdminUserService.java b/admin/src/main/java/com/yxx/admin/service/AdminUserService.java new file mode 100644 index 0000000..987bf41 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/AdminUserService.java @@ -0,0 +1,56 @@ +package com.yxx.admin.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.admin.model.entity.AdminUser; +import com.yxx.admin.model.request.*; +import com.yxx.admin.model.response.LoginRes; + +/** + * @author yxx + * @since 2022-11-12 13:54 + */ +public interface AdminUserService extends IService { + /** + * 登录 + * + * @param request 请求参数 + * @return token等结果 + */ + LoginRes login(LoginReq request); + + /** + * 发送重置密码邮件 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + Boolean resetPwdEmail(ResetPwdEmailReq req); + + /** + * 重置密码 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + Boolean resetPwd(ResetPwdReq req); + + /** + * 根据电子邮件获取用户 + * + * @param email 电子邮件 + * @return {@link AdminUser } + * @author yxx + */ + AdminUser getUserByEmail(String email); + + /** + * 修改密码 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + Boolean editPwd(EditPwdReq req); +} diff --git a/admin/src/main/java/com/yxx/admin/service/OperateAdminLogService.java b/admin/src/main/java/com/yxx/admin/service/OperateAdminLogService.java new file mode 100644 index 0000000..0451e7b --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/OperateAdminLogService.java @@ -0,0 +1,32 @@ +package com.yxx.admin.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.admin.model.request.OperateLogReq; +import com.yxx.admin.model.response.OperateLogResp; +import com.yxx.common.core.model.OperateAdminLog; + +/** + * 操作日志表 服务类 + * + * @author yxx + * @since 2022-07-15 + */ +public interface OperateAdminLogService extends IService { + + /** + * 查询操作日志分页列表 + * + * @param req OperateLogReq + * @return 操作日志分页列表 + */ + Page operationLogPage(OperateLogReq req); + + /** + * 登录日志分野 + * + * @param req 请求参数 + * @return 分页结果 + */ + Page authLogPage(OperateLogReq req); +} diff --git a/admin/src/main/java/com/yxx/admin/service/impl/AdminMenuServiceImpl.java b/admin/src/main/java/com/yxx/admin/service/impl/AdminMenuServiceImpl.java new file mode 100644 index 0000000..7e3f9f6 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/impl/AdminMenuServiceImpl.java @@ -0,0 +1,34 @@ +package com.yxx.admin.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.admin.mapper.AdminMenuMapper; +import com.yxx.admin.model.entity.AdminMenu; +import com.yxx.admin.service.AdminMenuService; +import com.yxx.common.utils.TreeUtil; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author yxx + * @since 2023-05-18 15:05 + */ +@Service +public class AdminMenuServiceImpl extends ServiceImpl implements AdminMenuService { + + @Override + public List menuTree() { + List adminMenuList = list(); + /* + * 构建树 + * + * @param listData 需要构建的结果集 这里是menuList + * @param parentKeyFunction 父节点 这里是parentId + * @param keyFunction 主键 这里是id + * @param setChildrenFunction 子集 这里是children + * @param rootParentValue 父节点的值 这里null + * @return java.util.List + */ + return TreeUtil.buildTree(adminMenuList, AdminMenu::getParentId, AdminMenu::getId, AdminMenu::setChildren, null); + } +} diff --git a/admin/src/main/java/com/yxx/admin/service/impl/AdminRoleMenuServiceImpl.java b/admin/src/main/java/com/yxx/admin/service/impl/AdminRoleMenuServiceImpl.java new file mode 100644 index 0000000..8406958 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/impl/AdminRoleMenuServiceImpl.java @@ -0,0 +1,52 @@ +package com.yxx.admin.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.admin.model.entity.AdminRoleMenu; +import com.yxx.admin.mapper.AdminRoleMenuMapper; +import com.yxx.admin.model.entity.AdminMenu; +import com.yxx.admin.model.entity.AdminRole; +import com.yxx.admin.service.AdminMenuService; +import com.yxx.admin.service.AdminRoleMenuService; +import com.yxx.admin.service.AdminRoleService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author yxx + * @since 2023-05-18 15:23 + */ +@Service +@RequiredArgsConstructor +public class AdminRoleMenuServiceImpl extends ServiceImpl implements AdminRoleMenuService { + private final AdminRoleService adminRoleService; + private final AdminMenuService adminMenuService; + + @Override + public List loginUserMenu(List roleCodeList) { + // 如果角色code集合不为空 + if (!roleCodeList.isEmpty()) { + // 根据角色code集合获取角色集合 + List adminRoleList = adminRoleService.list(new LambdaQueryWrapper().in(AdminRole::getCode, roleCodeList)); + // 根据角色集合 获取角色id集合 + List roleIdList = adminRoleList.stream().map(AdminRole::getId).collect(Collectors.toList()); + // 根据角色id集合 获取 角色菜单 集合 + List adminRoleMenuList = list(new LambdaQueryWrapper().in(AdminRoleMenu::getRoleId, roleIdList)); + + // 如果角色菜单集合不为空 + if (!adminRoleMenuList.isEmpty()) { + // 根据角色菜单集合 获取菜单id集合 + List menuIdList = adminRoleMenuList.stream().map(AdminRoleMenu::getMenuId).collect(Collectors.toList()); + // 根据菜单id集合 获取菜单集合 + List adminMenuList = adminMenuService.list(new LambdaQueryWrapper().in(AdminMenu::getId, menuIdList)); + // 根据菜单集合 获取菜单code集合 并返回 + return adminMenuList.stream().map(AdminMenu::getMenuCode).collect(Collectors.toList()); + } + } + return new ArrayList<>(); + } +} diff --git a/admin/src/main/java/com/yxx/admin/service/impl/AdminRoleServiceImpl.java b/admin/src/main/java/com/yxx/admin/service/impl/AdminRoleServiceImpl.java new file mode 100644 index 0000000..38cc16d --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/impl/AdminRoleServiceImpl.java @@ -0,0 +1,15 @@ +package com.yxx.admin.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.admin.mapper.AdminRoleMapper; +import com.yxx.admin.model.entity.AdminRole; +import com.yxx.admin.service.AdminRoleService; +import org.springframework.stereotype.Service; + +/** + * @author yxx + * @since 2023-05-17 09:59 + */ +@Service +public class AdminRoleServiceImpl extends ServiceImpl implements AdminRoleService { +} diff --git a/admin/src/main/java/com/yxx/admin/service/impl/AdminUserRoleServiceImpl.java b/admin/src/main/java/com/yxx/admin/service/impl/AdminUserRoleServiceImpl.java new file mode 100644 index 0000000..5bb9f2b --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/impl/AdminUserRoleServiceImpl.java @@ -0,0 +1,70 @@ +package com.yxx.admin.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.admin.mapper.AdminUserRoleMapper; +import com.yxx.admin.model.entity.AdminRole; +import com.yxx.admin.model.entity.AdminUser; +import com.yxx.admin.model.entity.AdminUserRole; +import com.yxx.admin.service.AdminRoleService; +import com.yxx.admin.service.AdminUserRoleService; +import com.yxx.common.enums.business.RoleEnum; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author yxx + * @since 2023-05-17 10:01 + */ +@Service +@RequiredArgsConstructor +public class AdminUserRoleServiceImpl extends ServiceImpl implements AdminUserRoleService { + private final AdminRoleService adminRoleService; + + @Override + public List loginUserRoleManage(AdminUser user) { + // 初始化返回角色code集合 + List roleList = new LinkedList<>(); + // 根据用户id 获取该用户的角色集合 + List adminUserRoleList = list(new LambdaQueryWrapper().eq(AdminUserRole::getUserId, user.getId())); + // 如果没有角色不让登录,请取消下面一行代码注释 + // ApiAssert.isTrue(ApiCode.USER_NOT_ROLE, !userRoleList.isEmpty()); + + // 遍历用户角色集合 + adminUserRoleList.forEach(adminUserRole -> { + // 根据用户的角色id 获取角色详情 + AdminRole adminRole = adminRoleService.getOne(new LambdaQueryWrapper().eq(AdminRole::getId, adminUserRole.getRoleId())); + + // 如果没有角色不让登录,请取消下面一行代码注释 + // ApiAssert.isTrue(ApiCode.USER_NOT_ROLE, ObjectUtil.isNull(role)); + + // 如果角色信息不为空 + if (ObjectUtil.isNotNull(adminRole)) { + // 将角色code添加到返回值集合中 + roleList.add(adminRole.getCode()); + } + }); + + // 返回角色code集合 + return roleList; + } + + @Override + public Boolean setDefaultRole(AdminUser user) { + // 根据角色code 获取角色详情 + AdminRole adminRole = adminRoleService.getOne( + new LambdaQueryWrapper().eq(AdminRole::getCode, RoleEnum.USER.getCode())); + // 初始化用户角色实体类 + AdminUserRole adminUserRole = new AdminUserRole(); + // 设置用户id + adminUserRole.setUserId(user.getId()); + // 设置角色id + adminUserRole.setRoleId(adminRole.getId()); + // 保存 + return save(adminUserRole); + } +} diff --git a/admin/src/main/java/com/yxx/admin/service/impl/AdminUserServiceImpl.java b/admin/src/main/java/com/yxx/admin/service/impl/AdminUserServiceImpl.java new file mode 100644 index 0000000..bec1468 --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/impl/AdminUserServiceImpl.java @@ -0,0 +1,263 @@ +package com.yxx.admin.service.impl; + +import cn.dev33.satoken.temp.SaTempUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.admin.model.request.*; +import com.yxx.admin.mapper.AdminUserMapper; +import com.yxx.admin.model.entity.AdminUser; +import com.yxx.admin.model.response.LoginRes; +import com.yxx.admin.service.AdminRoleMenuService; +import com.yxx.admin.service.AdminUserRoleService; +import com.yxx.admin.service.AdminUserService; +import com.yxx.common.constant.EmailSubjectConstant; +import com.yxx.common.constant.LoginDevice; +import com.yxx.common.constant.RedisConstant; +import com.yxx.common.core.model.LoginUser; +import com.yxx.common.enums.ApiCode; +import com.yxx.common.properties.IpProperties; +import com.yxx.common.properties.MailProperties; +import com.yxx.common.properties.MyWebProperties; +import com.yxx.common.properties.ResetPwdProperties; +import com.yxx.common.utils.ApiAssert; +import com.yxx.common.utils.DateUtils; +import com.yxx.common.utils.ServletUtils; +import com.yxx.common.utils.agent.UserAgentUtil; +import com.yxx.common.utils.auth.LoginAdminUtils; +import com.yxx.common.utils.email.MailUtils; +import com.yxx.common.utils.ip.AddressUtil; +import com.yxx.common.utils.ip.IpUtil; +import com.yxx.common.utils.redis.RedissonCache; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.util.DigestUtils; + +import jakarta.servlet.http.HttpServletRequest; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * @author yxx + * @since 2022-11-12 13:54 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminUserServiceImpl extends ServiceImpl implements AdminUserService { + private final AdminUserRoleService adminUserRoleService; + + private final AdminRoleMenuService adminRoleMenuService; + + private final RedissonCache redissonCache; + + private final MailUtils mailUtils; + + private final MailProperties mailProperties; + + private final ResetPwdProperties resetPwdProperties; + + private final IpProperties ipProperties; + + private final MyWebProperties myWebProperties; + + @Override + public LoginRes login(LoginReq request) { + // 根据登录账号获取用户信息 + AdminUser user = getOne( + new LambdaQueryWrapper().eq(AdminUser::getLoginCode, request.getLoginCode())); + // 如果用户信息不存在,抛出异常 + ApiAssert.isTrue(ApiCode.USER_NOT_EXIST, ObjectUtil.isNotNull(user)); + // 加密请求参数中的密码 + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + // 如果请求参数中的密码加密后和数据库中不一致,抛出异常 + ApiAssert.isTrue(ApiCode.PASSWORD_ERROR, encoder.matches(request.getPassword(), user.getPassword())); + + // 初始化登录信息 + LoginUser loginUser = new LoginUser(); + // 拷贝赋值数据 + BeanUtils.copyProperties(user, loginUser); + // 设置登录时间 + loginUser.setLoginTime(LocalDateTime.now()); + // 获取该用户角色信息 + List roleCodeList = adminUserRoleService.loginUserRoleManage(user); + // 赋值角色集合 + loginUser.setRolePermission(roleCodeList); + // 根据角色code获取该用户菜单集合 + List menuCodeList = adminRoleMenuService.loginUserMenu(roleCodeList); + // 赋值菜单集合 + loginUser.setMenuPermission(menuCodeList); + + // 获取请求头 + HttpServletRequest servletRequest = ServletUtils.getRequest(); + // 获取登录设备信息 + String requestAgent = servletRequest.getHeader("user-agent"); + // 解析登录设备 + String agent = UserAgentUtil.getAgent(requestAgent); + // 设置该用户登录时的设备名称 + loginUser.setAgent(agent); + + + // 如果校验ip + if (Boolean.TRUE.equals(ipProperties.getCheck())) { + // 得到请求时的ip + String requestIp = IpUtil.getRequestIp(); + // 获取ip归属地 + String ipHomePlace = AddressUtil.getIpHomePlace(requestIp, 2); + // 设置该用户登录时的ip归属地 + loginUser.setIpHomePlace(ipHomePlace); + + // 新建checkUser并将user信息赋值过来,下面异步校验使用。 + // 直接用user会有异步信息还未执行的时候,下面的用户信息已经更新的问题 + AdminUser checkUser = new AdminUser(); + BeanUtils.copyProperties(user, checkUser); + + // 异地登录校验 + CompletableFuture.runAsync(() -> checkRemoteLogin(checkUser, ipHomePlace, requestIp, agent)); + + user.setAgent(agent); + user.setIpHomePlace(ipHomePlace); + } + + // 登录 + LoginAdminUtils.login(loginUser, LoginDevice.PC); + + // 修改用户数据 + updateById(user); + + // 返回token + return new LoginRes(loginUser.getToken()); + } + + @Override + public Boolean resetPwdEmail(ResetPwdEmailReq req) { + // 校验邮件是否已经发送过 + ApiAssert.isFalse(ApiCode.MAIL_EXIST, redissonCache.exists(RedisConstant.RESET_PWD_CONTENT + req.getEmail())); + + // 根据邮箱 获取用户 + AdminUser user = getUserByEmail(req.getEmail()); + // 如果用户不存在,抛出提示 + ApiAssert.isTrue(ApiCode.EMAIL_NOT_REGISTER, ObjectUtil.isNotNull(user)); + + // 从redis中获取该邮箱号今日找回密码次数 + Integer number = redissonCache.get(RedisConstant.RESET_PWD_NUM + req.getEmail()); + // 如果找回次数不为空,并且大于等于设置的最大次数,抛出异常 + ApiAssert.isFalse(ApiCode.RESET_PWD_MAX, number != null && number >= resetPwdProperties.getMaxNumber()); + + // 创建临时token 临时时间15分钟 + String token = SaTempUtil.createToken(req.getEmail(), resetPwdProperties.getResetPwdTime()); + // 找回密码路径 拼接token + String resetPassHref = resetPwdProperties.getBasePath() + "?token=" + token; + // 邮件内容 + String emailContent = resetPwdProperties.getResetPwdContent().replace("{url}", resetPassHref) + .replace("{time}", String.valueOf(resetPwdProperties.getResetPwdTime())) + .replace("{domain}", myWebProperties.getDomain()) + .replace("{formName}", mailProperties.getFromName()) + .replace("{form}", mailProperties.getFrom()); + // 发送html格式邮件 + mailUtils.baseSendMail(req.getEmail(), EmailSubjectConstant.RESET_PWD, emailContent, true); + + // 将临时token 存入redis中 + redissonCache.putString(RedisConstant.RESET_PWD_CONTENT + req.getEmail(), token, 900, TimeUnit.SECONDS); + + // 防止恶意刷邮件 + // 今天剩余时间 + Long time = DateUtils.theRestOfTheDaySecond(); + // 添加找回密码次数到redis中 找回密码次数+1 + redissonCache.put(RedisConstant.RESET_PWD_NUM + req.getEmail(), Optional.ofNullable(number).map(x -> ++x).orElse(1), time); + + return Boolean.TRUE; + } + + @Override + public Boolean resetPwd(ResetPwdReq req) { + // 获取临时token的存活时间 -1 代表永久,-2 代表token无效 + long timeout = SaTempUtil.getTimeout(req.getToken()); + // 如果token无效,抛出提示 + ApiAssert.isFalse(ApiCode.RESET_PWD_TOKEN_ERROR, timeout == -2); + // 获取token对应的邮箱 + String email = SaTempUtil.parseToken(req.getToken(), String.class); + // 根据邮箱获取用户 + AdminUser user = getUserByEmail(email); + // 如果用户为空,抛出提示 + ApiAssert.isTrue(ApiCode.DATE_ERROR, ObjectUtil.isNotNull(user)); + + // 删除临时token + SaTempUtil.deleteToken(req.getToken()); + + // 加密密码 + String password = DigestUtils.md5DigestAsHex(req.getNewPassword().getBytes()); + // 根据邮箱修改密码 + return update(new LambdaUpdateWrapper().eq(AdminUser::getEmail, email).set(AdminUser::getPassword, password)); + } + + + @Override + public AdminUser getUserByEmail(String email) { + // 根据邮箱号获取用户信息 + return getOne(new LambdaUpdateWrapper().eq(AdminUser::getEmail, email)); + } + + @Override + public Boolean editPwd(EditPwdReq req) { + // 根据登录id 获取该用户详情 + AdminUser user = getById(LoginAdminUtils.getUserId()); + + // 加密请求参数中的旧密码 + String password = DigestUtils.md5DigestAsHex(req.getPassword().getBytes()); + // 匹对请求参数中的旧密码是否正确 + ApiAssert.isFalse(ApiCode.ORIGINAL_PASSWORD_ERROR, user.getPassword().equals(password)); + + // 加密新密码 + String newPassword = DigestUtils.md5DigestAsHex(req.getNewPassword().getBytes()); + // 根据用户id修改新密码 + return update(new LambdaUpdateWrapper().eq(AdminUser::getId, user.getId()).set(AdminUser::getPassword, newPassword)); + } + + void checkRemoteLogin(AdminUser user, String ipHomePlace, String requestIp, String requestAgent) { + if (Boolean.TRUE.equals(ipProperties.getCheck())) { + log.info("异地登录校验~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + if (AddressUtil.isValidIPv4(requestIp)) { + // 判断是否发送过异常登录通知 + boolean exists = redissonCache.exists(RedisConstant.IP_UNUSUAL_LOGIN + user.getId()); + // 如果没有发送过,进行校验 + if (!exists) { + // 如果用户ip归属地不为空,且与当前登录ip归属地不同 登录设备名称不为空且与当前设备不同 + if (CharSequenceUtil.isNotBlank(user.getAgent()) && !user.getAgent().equals(requestAgent) && + CharSequenceUtil.isNotBlank(user.getIpHomePlace()) && !user.getIpHomePlace().equals(ipHomePlace)) { + // 获取ip归属地 + String unusual = AddressUtil.getIpHomePlace(requestIp, 3); + // 邮件正文 + String time = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN); + String emailContent = mailProperties.getIpUnusualContent().replace("{time}", time) + .replace("{ip}", requestIp) + .replace("{address}", unusual) + .replace("{agent}", requestAgent) + .replace("{domain}", myWebProperties.getDomain()) + .replace("{formName}", mailProperties.getFromName()) + .replace("{form}", mailProperties.getFrom()); + // 发送邮件通知 + mailUtils.baseSendMail(user.getEmail(), EmailSubjectConstant.IP_UNUSUAL, emailContent, true); + // 加入redis(一天提醒一次) + // 今天剩余时间 + Long residueTime = DateUtils.theRestOfTheDaySecond(); + redissonCache.put(RedisConstant.IP_UNUSUAL_LOGIN + user.getId(), Boolean.TRUE, residueTime); + } + } + } else { + log.info("非ipv4"); + } + log.info("异地登录校验结束~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + } + } +} diff --git a/admin/src/main/java/com/yxx/admin/service/impl/OperateAdminLogServiceImpl.java b/admin/src/main/java/com/yxx/admin/service/impl/OperateAdminLogServiceImpl.java new file mode 100644 index 0000000..d39de0d --- /dev/null +++ b/admin/src/main/java/com/yxx/admin/service/impl/OperateAdminLogServiceImpl.java @@ -0,0 +1,53 @@ +package com.yxx.admin.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.admin.mapper.OperateAdminLogMapper; +import com.yxx.admin.model.request.OperateLogReq; +import com.yxx.admin.model.response.OperateLogResp; +import com.yxx.admin.service.OperateAdminLogService; +import com.yxx.common.core.model.LogDTO; +import com.yxx.common.core.model.OperateAdminLog; +import com.yxx.framework.service.OperationLogService; +import org.springframework.beans.BeanUtils; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +/** + * 操作日志表 服务实现类 + * + * @author yxx + * @since 2022-07-15 + */ +@Service +public class OperateAdminLogServiceImpl extends ServiceImpl + implements OperateAdminLogService, OperationLogService { + + @Async + @Override + public void saveLog(LogDTO dto) { + // 初始化日志类 + OperateAdminLog log = new OperateAdminLog(); + // 拷贝赋值数据 + BeanUtils.copyProperties(dto, log); + // 插入日志数据 + this.save(log); + } + + + @Override + public Page operationLogPage(OperateLogReq req) { + // 初始化分页构造器 + Page page = new Page<>(req.getPage(), req.getPageSize()); + // 查询分页结果并返回 + return baseMapper.operationLogPage(page, req); + } + + @Override + public Page authLogPage(OperateLogReq req) { + // 初始化分页构造器 + Page page = new Page<>(req.getPage(), req.getPageSize()); + // 查询分页结果并返回 + return this.baseMapper.authLogPage(page, req); + } +} diff --git a/admin/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/admin/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..1c92265 --- /dev/null +++ b/admin/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,44 @@ +{ + "properties": [ + { + "name": "mail.subject", + "type": "java.lang.String", + "description": "Description for mail.subject." + }, + { + "name": "reset-password.base-path", + "type": "java.lang.String", + "description": "Description for reset-password.base-path." + }, + { + "name": "reset-password.max-number", + "type": "java.lang.String", + "description": "Description for reset-password.max-number." + }, + { + "name": "mail.register-time", + "type": "java.lang.String", + "description": "Description for mail.register-time." + }, + { + "name": "mail.register-max", + "type": "java.lang.String", + "description": "Description for mail.register-max." + }, + { + "name": "mail.registerContent", + "type": "java.lang.String", + "description": "Description for mail.registerContent." + }, + { + "name": "reset-password.reset-pwd-content", + "type": "java.lang.String", + "description": "Description for reset-password.reset-pwd-content." + }, + { + "name": "web.domain", + "type": "java.lang.String", + "description": "Description for web.domain." + } + ] +} \ No newline at end of file diff --git a/admin/src/main/resources/application-dev.yml b/admin/src/main/resources/application-dev.yml new file mode 100644 index 0000000..e88a4b6 --- /dev/null +++ b/admin/src/main/resources/application-dev.yml @@ -0,0 +1,260 @@ +spring: + application: + name: business + config: + activate: + on-profile: dev + # mysql 通用配置信息内容 使用HikariDataSource连接池 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/base_springboot?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: admin123 + type: com.zaxxer.hikari.HikariDataSource + hikari: + minimum-idle: 10 + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: HikariCP + max-lifetime: 1800000 + connection-timeout: 30000 + connection-test-query: SELECT 1 + # 请求大小 + servlet: + multipart: + max-file-size: 30MB + max-request-size: 30MB + # redis 配置内容 + data: + redis: + # 设置初始数据库的数据为8 + database: 14 + host: 127.0.0.1 + port: 6379 + # password: + jedis: + pool: + # 连接池最大连接数(使用负值表示没有限制) + max-active: 50 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: 3000ms + # 连接池中的最大空闲连接数 + max-idle: 20 + # 连接池中的最小空闲连接数 + min-idle: 5 + # 连接超时时间(毫秒) + timeout: 5000ms + # 邮箱 + mail: + # 编码 + default-encoding: UTF-8 + # 邮件服务器 + host: smtp.email.cn + # 用户名(一般为邮箱号) + username: shuniversity@email.cn + # 邮件服务器端口 + port: 25 + # 专用密码 + password: 2BCZKEGQgHyCZ5i4 + +# 邮箱设置 +mail: + # 发件人邮箱 + from: shuniversity@email.cn + # 发件人名称 + from-name: SpringBoot开发模版 + # ip异常邮件正文 + ip-unusual-content: | + + + + IP异常提醒 + + + +
+

IP异常提醒

+

您的账户在以下时间出现异常登录:

+ + + + + + + + + + + + + +
登录时间登录IP登录地址登录设备
{time}{ip}{address}{agent}
+

如果这些登录不是您的操作,请尽快修改密码并确保账户安全。

+ +
+ + + + +# 重置密码 +reset-password: + # 找回密码页面url + base-path: http://127.0.0.1 + # 每日找回密码最大次数 (防止恶意发送邮件) + max-number: 3 + # 找回密码邮件正文 + reset-pwd-content: > + + + + 重置密码 + + + +
+

重置密码

+

亲爱的用户,您收到这封邮件是因为您请求重置密码。

+

请点击下面的按钮来设置一个新密码:

+ 重置密码 +

请注意,此链接将在接下来的{time}分钟内有效,请尽快完成密码重置操作。

+

如果您没有请求重置密码,请忽略此邮件。

+ +
+ + + + # 重置密码链接有效期(单位:分钟) + reset-pwd-time: 15 + +# 是否校验ip异常 +ip: + # 开启后则校验ip异常情况 (异地换设备登录) 发送邮件通知 + check: false + +# 网站域名 +web: + domain: http://127.0.0.1 + +# mybatis-plus开发日志打印内容 +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + global-config: + db-config: + logic-delete-field: is_delete + logic-delete-value: 1 + logic-not-delete-value: 0 + banner: false + +# Sa-Token配置 +sa-token: + # token名称 (同时也是cookie名称,可自行更改) + token-name: Authorization + # token前缀 + token-prefix: Bearer + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # 是否打开自动续签 (如果此值为true, 框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + autoRenew: true + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + active-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: false + # token风格 + token-style: uuid + # 是否输出操作日志 + is-log: false + # jwt秘钥 + jwt-secret-key: TUx8IaslTsibligsqvzLmNvbQECAwQF + # 是否在初始化配置时打印版本字符画 + is-print: false + # 登录逻辑缓存和业务逻辑缓存分离 + alone-redis: + # Redis数据库索引(默认为0) + database: 13 + # Redis服务器地址 + host: 127.0.0.1 + # Redis服务器连接端口 + port: 6379 + # Redis服务器连接密码(默认为空) + password: + # 连接超时时间 + timeout: 10s + diff --git a/admin/src/main/resources/application-prod.yml b/admin/src/main/resources/application-prod.yml new file mode 100644 index 0000000..51bb98c --- /dev/null +++ b/admin/src/main/resources/application-prod.yml @@ -0,0 +1,321 @@ +spring: + application: + name: business + config: + activate: + on-profile: dev + # mysql 通用配置信息内容 使用HikariDataSource连接池 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/base_springboot?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: admin123 + type: com.zaxxer.hikari.HikariDataSource + hikari: + minimum-idle: 10 + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: HikariCP + max-lifetime: 1800000 + connection-timeout: 30000 + connection-test-query: SELECT 1 + # 请求大小 + servlet: + multipart: + max-file-size: 30MB + max-request-size: 30MB + data: + # redis 配置内容 + redis: + # 设置初始数据库的数据为8 + database: 8 + host: 127.0.0.1 + port: 6379 + # password: + jedis: + pool: + # 连接池最大连接数(使用负值表示没有限制) + max-active: 50 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: 3000ms + # 连接池中的最大空闲连接数 + max-idle: 20 + # 连接池中的最小空闲连接数 + min-idle: 5 + # 连接超时时间(毫秒) + timeout: 5000ms + # 邮箱 + mail: + # 编码 + default-encoding: UTF-8 + # 邮件服务器 + host: smtp.email.cn + # 用户名(一般为邮箱号) + username: shuniversity@email.cn + # 邮件服务器端口 + port: 25 + # 专用密码 + password: 2BCZKEGQgHyCZ5i4 + +# 邮箱设置 +mail: + # 发件人邮箱 + from: shuniversity@email.cn + # 发件人名称 + from-name: SpringBoot开发模版 + # 注册时邮箱验证码有效期 + register-time: 5 + # 一个邮箱每日最大注册次数 (防止恶意发送邮件) + register-max: 3 + # 注册邮件正文 + register-content: > + + + + 注册验证码邮件 + + + +
+

欢迎注册!

+

感谢您注册我们的服务。以下是您的验证码:

+ {captcha} +

此验证码将在 {time} 分钟后失效,请及时使用。

+

如果您没有进行注册操作,请忽略此邮件。

+ +
+ + + + # ip异常邮件正文 + ip-unusual-content: | + + + + IP异常提醒 + + + +
+

IP异常提醒

+

您的账户在以下时间出现异常登录:

+ + + + + + + + + + + + + +
登录时间登录IP登录地址登录设备
{time}{ip}{address}{agent}
+

如果这些登录不是您的操作,请尽快修改密码并确保账户安全。

+ +
+ + + + +# 重置密码 +reset-password: + # 找回密码页面url + base-path: http://127.0.0.1 + # 每日找回密码最大次数 (防止恶意发送邮件) + max-number: 3 + # 找回密码邮件正文 + reset-pwd-content: > + + + + 重置密码 + + + +
+

重置密码

+

亲爱的用户,您收到这封邮件是因为您请求重置密码。

+

请点击下面的按钮来设置一个新密码:

+ 重置密码 +

请注意,此链接将在接下来的{time}分钟内有效,请尽快完成密码重置操作。

+

如果您没有请求重置密码,请忽略此邮件。

+ +
+ + + + # 重置密码链接有效期(单位:分钟) + reset-pwd-time: 15 + +# 是否校验ip异常 +ip: + # 开启后则校验ip异常情况 (异地换设备登录) 发送邮件通知 + check: true + +# 网站域名 +web: + domain: http://127.0.0.1 + +# mybatis-plus开发日志打印内容 +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + global-config: + db-config: + logic-delete-field: is_delete + logic-delete-value: 1 + logic-not-delete-value: 0 + banner: false + +# Sa-Token配置 +sa-token: + # token名称 (同时也是cookie名称,可自行更改) + token-name: Authorization + # token前缀 + token-prefix: Bearer + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # 是否打开自动续签 (如果此值为true, 框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + autoRenew: true + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + activity-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: false + # token风格 + token-style: uuid + # 是否输出操作日志 + is-log: false + # jwt秘钥 + jwt-secret-key: TUx8IaslTsibligsqvzLmNvbQECAwQF + # 是否在初始化配置时打印版本字符画 + is-print: false + # 登录逻辑缓存和业务逻辑缓存分离 + alone-redis: + # Redis数据库索引(默认为0) + database: 2 + # Redis服务器地址 + host: 127.0.0.1 + # Redis服务器连接端口 + port: 6379 + # Redis服务器连接密码(默认为空) + password: + # 连接超时时间 + timeout: 10s + diff --git a/admin/src/main/resources/application.yml b/admin/src/main/resources/application.yml new file mode 100644 index 0000000..d1f1d55 --- /dev/null +++ b/admin/src/main/resources/application.yml @@ -0,0 +1,17 @@ +server: + port: 6060 + +spring: + profiles: + active: @profileActive@ + +app: + name: admin + +logging: + file: + path: ./logs + name: admin + level: + # mapper包下打印debug级别日志 + com.yxx.business.mapper: debug diff --git a/admin/src/main/resources/banner.txt b/admin/src/main/resources/banner.txt new file mode 100644 index 0000000..7ceb28d --- /dev/null +++ b/admin/src/main/resources/banner.txt @@ -0,0 +1,6 @@ +${AnsiColor.BRIGHT_GREEN} + + . + |-. ,-. ,-. ,-. + | | ,-| `-. |-' + ^-' `-^ `-' `-' diff --git a/admin/src/main/resources/ip2region/ip2region.xdb b/admin/src/main/resources/ip2region/ip2region.xdb new file mode 100644 index 0000000..c78b792 Binary files /dev/null and b/admin/src/main/resources/ip2region/ip2region.xdb differ diff --git a/admin/src/main/resources/logback-spring.xml b/admin/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..3fce3ba --- /dev/null +++ b/admin/src/main/resources/logback-spring.xml @@ -0,0 +1,27 @@ + + + + + + + %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) [traceId:%X{Trace-Id}] %cyan(%logger{50}) - %magenta(%msg) %n + + + + ${LOG_HOME}/${APP_NAME}.log + + ${LOG_HOME}/${APP_NAME}.log.%d{yyyy-MM-dd}.%i.log + 30 + 100MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [traceId:%X{Trace-Id}] %logger{50} - %msg%n + + + + + + + + diff --git a/admin/src/main/resources/mapper/OperateAdminLogMapper.xml b/admin/src/main/resources/mapper/OperateAdminLogMapper.xml new file mode 100644 index 0000000..d661c39 --- /dev/null +++ b/admin/src/main/resources/mapper/OperateAdminLogMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + diff --git a/admin/src/test/java/com/yxx/business/AdminApplicationTests.java b/admin/src/test/java/com/yxx/business/AdminApplicationTests.java new file mode 100644 index 0000000..59278d2 --- /dev/null +++ b/admin/src/test/java/com/yxx/business/AdminApplicationTests.java @@ -0,0 +1,71 @@ +package com.yxx.business; + +import cn.dev33.satoken.temp.SaTempUtil; +import com.yxx.admin.AdminApplication; +import com.yxx.admin.model.entity.AdminMenu; +import com.yxx.admin.model.entity.AdminUser; +import com.yxx.admin.service.AdminMenuService; +import com.yxx.common.utils.email.MailUtils; +import com.yxx.common.utils.redis.RedissonCache; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +@SpringBootTest(classes = AdminApplication.class) +class AdminApplicationTests { + @Autowired + private RedissonCache redissonCache; + @Autowired + private AdminMenuService menuService; + @Autowired + private MailUtils mailUtils; + + String key = "test"; + String userKey = "user"; + + @Test + void redisPutTest() { + redissonCache.put(key, "测试呀"); + + AdminUser user = new AdminUser(); + user.setLoginCode("120"); + user.setLoginName("护士"); + user.setPassword("120"); + redissonCache.put(userKey, user); + } + + @Test + void redisGetTest(){ + String string = redissonCache.getString(key); + System.out.println("string = " + string); + + AdminUser value = redissonCache.get(userKey); + System.out.println("user = " + value); + } + + @Test + void redisRemoveTest(){ + redissonCache.remove(key); + redissonCache.remove(userKey); + } + + @Test + void menuTree(){ + List menus = menuService.menuTree(); + System.out.println("menus = " + menus); + } + + @Test + void mailTest() { + mailUtils.baseSendMail("yangxx@88.com", "主题", "test", false); + } + + @Test + void tokenTest(){ + long timeout = SaTempUtil.getTimeout("asldfjaklsjfk"); + System.out.println("timeout = " + timeout); + } + +} diff --git a/business/README.md b/business/README.md new file mode 100644 index 0000000..c0d9ab2 --- /dev/null +++ b/business/README.md @@ -0,0 +1,4 @@ +# 工程简介 + +# 延伸阅读 + diff --git a/business/pom.xml b/business/pom.xml new file mode 100644 index 0000000..1e3e5c9 --- /dev/null +++ b/business/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + business + business + business + + + base + com.yxx + 1.0.0 + + + + + dev + + + dev + + + + true + + + + prod + + prod + + + + + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + com.yxx + common-core + 1.0.0 + + + com.yxx + common-framework + 1.0.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + com.yxx.business.BusinessApplication + + + + repackage + + repackage + + + + + + + + diff --git a/business/src/main/java/com/yxx/business/BusinessApplication.java b/business/src/main/java/com/yxx/business/BusinessApplication.java new file mode 100644 index 0000000..3ce435c --- /dev/null +++ b/business/src/main/java/com/yxx/business/BusinessApplication.java @@ -0,0 +1,15 @@ +package com.yxx.business; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = {"com.yxx", "com.yxx.common", "com.yxx.framework"}) +@MapperScan("com.yxx.business.mapper") +public class BusinessApplication { + + public static void main(String[] args) { + SpringApplication.run(BusinessApplication.class, args); + } + +} diff --git a/business/src/main/java/com/yxx/business/controller/AuthController.java b/business/src/main/java/com/yxx/business/controller/AuthController.java new file mode 100644 index 0000000..f1a1d18 --- /dev/null +++ b/business/src/main/java/com/yxx/business/controller/AuthController.java @@ -0,0 +1,57 @@ +package com.yxx.business.controller; + +import cn.dev33.satoken.stp.StpUtil; +import com.yxx.business.model.request.LoginReq; +import com.yxx.business.model.response.LoginRes; +import com.yxx.business.service.UserService; +import com.yxx.common.annotation.auth.ReleaseToken; +import com.yxx.common.annotation.log.OperationLog; +import com.yxx.common.annotation.response.ResponseResult; +import lombok.RequiredArgsConstructor; +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 jakarta.validation.Valid; + +/** + * @author yxx + * @since 2023-05-17 10:02 + */ +@Slf4j +@Validated +@ResponseResult +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +public class AuthController { + private final UserService userService; + + /** + * 登录 + * + * @param request 请求 + * @return {@link LoginRes } + * @author yxx + */ + @ReleaseToken + @OperationLog(module = "鉴权模块", title = "pc登录") + @PostMapping("/login") + public LoginRes login(@Valid @RequestBody LoginReq request) { + return userService.login(request); + } + + /** + * 注销 + * + * @author yxx + */ + @OperationLog(module = "鉴权模块", title = "pc退出") + @PostMapping("/logout") + public void logout() { + StpUtil.logout(); + } +} diff --git a/business/src/main/java/com/yxx/business/controller/OperateLogController.java b/business/src/main/java/com/yxx/business/controller/OperateLogController.java new file mode 100644 index 0000000..d98c236 --- /dev/null +++ b/business/src/main/java/com/yxx/business/controller/OperateLogController.java @@ -0,0 +1,59 @@ +package com.yxx.business.controller; + +import cn.dev33.satoken.annotation.SaCheckRole; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yxx.business.model.request.OperateLogReq; +import com.yxx.business.model.response.OperateLogResp; +import com.yxx.business.service.OperateLogService; +import com.yxx.common.annotation.response.ResponseResult; +import lombok.RequiredArgsConstructor; +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 jakarta.validation.Valid; + +/** + * @author yxx + * @description 注:@SaCheckRole("super_admin") + * 表示只有 super_admin 角色的用户才可访问此controller。 + * 该注解亦可放在具体的方法上 + * @since 2023-05-17 15:39 + */ +@Slf4j +@Validated +@ResponseResult +@RestController +@RequestMapping("/log") +@RequiredArgsConstructor +@SaCheckRole("super_admin") +public class OperateLogController { + private final OperateLogService operateLogService; + + /** + * 身份验证登录日志数据分页 + * + * @param req 要求事情 + * @return {@link Page }<{@link OperateLogResp }> + * @author yxx + */ + @PostMapping("/auth") + public Page authLogPage(@Valid @RequestBody OperateLogReq req) { + return operateLogService.authLogPage(req); + } + + /** + * 操作日志数据分页 + * + * @param req 要求事情 + * @return {@link Page }<{@link OperateLogResp }> + * @author yxx + */ + @PostMapping("/operation") + public Page operationLogPage(@Valid @RequestBody OperateLogReq req) { + return operateLogService.operationLogPage(req); + } +} diff --git a/business/src/main/java/com/yxx/business/controller/UserController.java b/business/src/main/java/com/yxx/business/controller/UserController.java new file mode 100644 index 0000000..f96e4cc --- /dev/null +++ b/business/src/main/java/com/yxx/business/controller/UserController.java @@ -0,0 +1,113 @@ +package com.yxx.business.controller; + +import com.yxx.business.model.request.*; +import com.yxx.business.service.UserService; +import com.yxx.common.annotation.auth.ReleaseToken; +import com.yxx.common.annotation.log.OperationLog; +import com.yxx.common.annotation.response.ResponseResult; +import com.yxx.common.core.model.LoginUser; +import com.yxx.common.utils.auth.LoginUtils; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * @author yxx + * @since 2022-11-12 02:07 + */ +@Slf4j +@Validated +@ResponseResult +@RestController +@RequestMapping("/user") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + /** + * 注册 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + @ReleaseToken + @OperationLog(module = "用户模块", title = "用户注册") + @PostMapping("/register") + public Boolean register(@Valid @RequestBody UserRegisterReq req) { + return userService.register(req); + } + + /** + * 发送注册验证码 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + @ReleaseToken + @OperationLog(module = "用户模块", title = "发送注册邮箱验证码") + @PostMapping("/sendCaptcha") + public Boolean sendRegisterCaptcha(@Valid @RequestBody RegisterCaptchaReq req){ + return userService.sendRegisterCaptcha(req); + } + + /** + * 获取用户信息 + * + * @return {@link LoginUser } + * @author yxx + */ + @OperationLog(module = "用户模块", title = "获取用户信息") + @GetMapping("/info") + public LoginUser info() { + Long userId = LoginUtils.getUserId(); + log.info("userId为[{}]", userId); + return LoginUtils.getLoginUser(); + } + + /** + * 发送重置密码邮件 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + @ReleaseToken + @OperationLog(module = "用户模块", title = "发送重置密码邮件") + @PostMapping("/resetPwdEmail") + public Boolean resetPwdEmail(@Valid @RequestBody ResetPwdEmailReq req){ + return userService.resetPwdEmail(req); + } + + /** + * 重置密码 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + @ReleaseToken + @OperationLog(module = "用户模块", title = "重置密码") + @PostMapping("/resetPwd") + public Boolean resetPwd(@Valid @RequestBody ResetPwdReq req){ + return userService.resetPwd(req); + } + + /** + * 修改密码 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + @OperationLog(module = "用户模块", title = "修改密码") + @PostMapping("/editPwd") + public Boolean editPwd(@Valid @RequestBody EditPwdReq req){ + return userService.editPwd(req); + } + +} diff --git a/business/src/main/java/com/yxx/business/mapper/MenuMapper.java b/business/src/main/java/com/yxx/business/mapper/MenuMapper.java new file mode 100644 index 0000000..0c7de5b --- /dev/null +++ b/business/src/main/java/com/yxx/business/mapper/MenuMapper.java @@ -0,0 +1,11 @@ +package com.yxx.business.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxx.business.model.entity.Menu; + +/** + * @author yxx + * @since 2023-05-18 15:04 + */ +public interface MenuMapper extends BaseMapper { +} diff --git a/business/src/main/java/com/yxx/business/mapper/OperateLogMapper.java b/business/src/main/java/com/yxx/business/mapper/OperateLogMapper.java new file mode 100644 index 0000000..90f3b2a --- /dev/null +++ b/business/src/main/java/com/yxx/business/mapper/OperateLogMapper.java @@ -0,0 +1,36 @@ +package com.yxx.business.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yxx.business.model.request.OperateLogReq; +import com.yxx.business.model.response.OperateLogResp; +import com.yxx.common.core.model.OperateLog; +import org.apache.ibatis.annotations.Param; + +/** + * 操作日志表 Mapper 接口 + * + * @author yxx + * @since 2022-07-15 + */ +public interface OperateLogMapper extends BaseMapper { + + /** + * 查询操作日志分页列表 + * + * @param page 分页参数 + * @param req OperateLogReq + * @return 操作日志分页列表 + */ + Page operationLogPage(@Param("page") Page page, + @Param("req") OperateLogReq req); + + /** + * 登录日志分页 + * + * @param page 分页构造器 + * @param req 请求参数 + * @return 分页结果 + */ + Page authLogPage(@Param("page") Page page, @Param("req") OperateLogReq req); +} diff --git a/business/src/main/java/com/yxx/business/mapper/RoleMapper.java b/business/src/main/java/com/yxx/business/mapper/RoleMapper.java new file mode 100644 index 0000000..516ad31 --- /dev/null +++ b/business/src/main/java/com/yxx/business/mapper/RoleMapper.java @@ -0,0 +1,11 @@ +package com.yxx.business.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxx.business.model.entity.Role; + +/** + * @author yxx + * @since 2023-05-17 09:59 + */ +public interface RoleMapper extends BaseMapper { +} diff --git a/business/src/main/java/com/yxx/business/mapper/RoleMenuMapper.java b/business/src/main/java/com/yxx/business/mapper/RoleMenuMapper.java new file mode 100644 index 0000000..ce2b1a0 --- /dev/null +++ b/business/src/main/java/com/yxx/business/mapper/RoleMenuMapper.java @@ -0,0 +1,11 @@ +package com.yxx.business.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxx.business.model.entity.RoleMenu; + +/** + * @author yxx + * @since 2023-05-18 15:22 + */ +public interface RoleMenuMapper extends BaseMapper { +} diff --git a/business/src/main/java/com/yxx/business/mapper/UserMapper.java b/business/src/main/java/com/yxx/business/mapper/UserMapper.java new file mode 100644 index 0000000..bab55a7 --- /dev/null +++ b/business/src/main/java/com/yxx/business/mapper/UserMapper.java @@ -0,0 +1,11 @@ +package com.yxx.business.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxx.business.model.entity.User; + +/** + * @author yxx + * @since 2022-11-12 13:57 + */ +public interface UserMapper extends BaseMapper { +} diff --git a/business/src/main/java/com/yxx/business/mapper/UserRoleMapper.java b/business/src/main/java/com/yxx/business/mapper/UserRoleMapper.java new file mode 100644 index 0000000..55dbf11 --- /dev/null +++ b/business/src/main/java/com/yxx/business/mapper/UserRoleMapper.java @@ -0,0 +1,11 @@ +package com.yxx.business.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxx.business.model.entity.UserRole; + +/** + * @author yxx + * @since 2023-05-17 10:01 + */ +public interface UserRoleMapper extends BaseMapper { +} diff --git a/business/src/main/java/com/yxx/business/model/entity/BaseEntity.java b/business/src/main/java/com/yxx/business/model/entity/BaseEntity.java new file mode 100644 index 0000000..6e7c5ea --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/entity/BaseEntity.java @@ -0,0 +1,46 @@ +package com.yxx.business.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 实体类基础数据 + * + * @author yxx + * @classname BaseEntity + * @since 2023-06-29 21:44 + */ +@Data +public class BaseEntity { + + /** + * 是否删除:0-未删除;1-已删除 + */ + @TableLogic + private Boolean isDelete; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; + + @TableField(fill = FieldFill.INSERT) + private Long createUid; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUid; +} diff --git a/business/src/main/java/com/yxx/business/model/entity/Menu.java b/business/src/main/java/com/yxx/business/model/entity/Menu.java new file mode 100644 index 0000000..b954eec --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/entity/Menu.java @@ -0,0 +1,48 @@ +package com.yxx.business.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @author yxx + * @since 2023-05-18 14:46 + */ +@Data +public class Menu implements Serializable { + /** + * 主键 + */ + + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * 父id + */ + private Integer parentId; + + /** + * 菜单标识 + */ + private String menuCode; + + /** + * 菜单名称 + */ + private String menuName; + + /** + * 是否删除: 0- 否; 1- 是 + */ + @TableLogic + private Boolean isDelete; + + /** + * 子菜单集合 + */ + @TableField(exist = false) + private List children; +} \ No newline at end of file diff --git a/business/src/main/java/com/yxx/business/model/entity/Role.java b/business/src/main/java/com/yxx/business/model/entity/Role.java new file mode 100644 index 0000000..d523edf --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/entity/Role.java @@ -0,0 +1,56 @@ +package com.yxx.business.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @author yxx + * @since 2023-05-17 09:35 + */ +@Data +public class Role implements Serializable { + /** + * id + */ + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * 角色code + */ + private String code; + + /** + * 角色名称 + */ + private String name; + + /** + * 说明 + */ + private String remark; + + /** + * 是否删除:0-未删除;1-已删除 + */ + @TableLogic + private Boolean isDelete; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; +} diff --git a/business/src/main/java/com/yxx/business/model/entity/RoleMenu.java b/business/src/main/java/com/yxx/business/model/entity/RoleMenu.java new file mode 100644 index 0000000..aea8177 --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/entity/RoleMenu.java @@ -0,0 +1,32 @@ +package com.yxx.business.model.entity; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import lombok.Data; + +/** + * @author yxx + * @since 2023-05-18 15:18 + */ +@Data +public class RoleMenu { + /** + * 主键 + */ + private Integer id; + + /** + * 角色id + */ + private Integer roleId; + + /** + * 菜单id + */ + private Integer menuId; + + /** + * 是否删除:0-未删除;1-已删除 + */ + @TableLogic + private Boolean isDelete; +} diff --git a/business/src/main/java/com/yxx/business/model/entity/User.java b/business/src/main/java/com/yxx/business/model/entity/User.java new file mode 100644 index 0000000..90c04d2 --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/entity/User.java @@ -0,0 +1,55 @@ +package com.yxx.business.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author yxx + * @since 2022-11-12 13:38 + */ +@Data +public class User extends BaseEntity implements Serializable{ + /** + * 用户id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 登录账号 + */ + private String loginCode; + + /** + * 登录名 + */ + private String loginName; + + /** + * 密码 + */ + private String password; + + /** + * 手机号 + */ + private String linkPhone; + + /** + * 邮箱 + */ + private String email; + + /** + * ip归属地 + */ + private String ipHomePlace; + + /** + * 登录设备 + */ + private String agent; +} diff --git a/business/src/main/java/com/yxx/business/model/entity/UserRole.java b/business/src/main/java/com/yxx/business/model/entity/UserRole.java new file mode 100644 index 0000000..d941f04 --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/entity/UserRole.java @@ -0,0 +1,51 @@ +package com.yxx.business.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @author yxx + * @since 2023-05-17 09:38 + */ +@Data +public class UserRole implements Serializable { + /** + * id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 角色id + */ + private Integer roleId; + + /** + * 是否删除:0-未删除;1-已删除 + */ + @TableLogic + private Boolean isDelete; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; +} diff --git a/business/src/main/java/com/yxx/business/model/request/EditPwdReq.java b/business/src/main/java/com/yxx/business/model/request/EditPwdReq.java new file mode 100644 index 0000000..31a454d --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/request/EditPwdReq.java @@ -0,0 +1,30 @@ +package com.yxx.business.model.request; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import jakarta.validation.constraints.NotBlank; + +/** + * 修改密码请求参数 + * + * @author yxx + * @classname EditPwdReq + * @since 2023-07-25 15:46 + */ +@Data +public class EditPwdReq { + /** + * 旧密码 + */ + @NotBlank(message = "旧密码不能为空") + @Length(min = 8, max = 15, message = "旧密码的长度为8-20位") + private String password; + + /** + * 新密码 + */ + @NotBlank(message = "新密码不能为空") + @Length(min = 8, max = 15, message = "新密码的长度应为8-20位") + private String newPassword; +} diff --git a/business/src/main/java/com/yxx/business/model/request/LoginReq.java b/business/src/main/java/com/yxx/business/model/request/LoginReq.java new file mode 100644 index 0000000..166ab28 --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/request/LoginReq.java @@ -0,0 +1,24 @@ +package com.yxx.business.model.request; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; + +/** + * @author yxx + * @since 2022-11-12 14:00 + */ +@Data +public class LoginReq { + /** + * 登录账号 + */ + @NotBlank(message = "登录账号不能为空") + private String loginCode; + + /** + * 登录密码 + */ + @NotBlank(message = "密码不能为空") + private String password; +} diff --git a/business/src/main/java/com/yxx/business/model/request/OperateLogReq.java b/business/src/main/java/com/yxx/business/model/request/OperateLogReq.java new file mode 100644 index 0000000..a011c86 --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/request/OperateLogReq.java @@ -0,0 +1,46 @@ +package com.yxx.business.model.request; + +import com.yxx.common.annotation.jackson.SearchDate; +import com.yxx.common.core.page.BasePageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +/** + * @author yxx + * @since 2022/8/1 17:39 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class OperateLogReq extends BasePageRequest implements Serializable { + + /** + * 用户名 + */ + private String loginCode; + + /** + * 用户名称 + */ + private String loginName; + + /** + * 操作内容 + */ + private String title; + + + /** + * 开始时间 + */ + @SearchDate(startDate = true) + private Date startTime; + + /** + * 结束时间 + */ + @SearchDate(endDate = true) + private Date endTime; +} diff --git a/business/src/main/java/com/yxx/business/model/request/RegisterCaptchaReq.java b/business/src/main/java/com/yxx/business/model/request/RegisterCaptchaReq.java new file mode 100644 index 0000000..21f4481 --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/request/RegisterCaptchaReq.java @@ -0,0 +1,20 @@ +package com.yxx.business.model.request; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +/** + * 注册验证码 + * + * @author yxx + * @classname RegisterCaptchaReq + * @since 2023-07-25 20:53 + */ +@Data +public class RegisterCaptchaReq { + @NotBlank(message = "邮箱不能为空") + @Pattern(regexp = "^[A-Za-z0-9]+([_.][A-Za-z0-9]+)*@((qq|163|gmail|88|email)+\\.)+[A-Za-z]{2,6}$", message = "请输入正确邮箱号,目前仅支持 qq邮箱、163邮箱、谷歌邮箱等常用邮箱") + private String email; +} diff --git a/business/src/main/java/com/yxx/business/model/request/ResetPwdEmailReq.java b/business/src/main/java/com/yxx/business/model/request/ResetPwdEmailReq.java new file mode 100644 index 0000000..a75aaf9 --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/request/ResetPwdEmailReq.java @@ -0,0 +1,21 @@ +package com.yxx.business.model.request; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +/** + * 获取重置密码邮件请求参数 + * + * @author yxx + * @classname ResetPwdReq + * @since 2023-07-25 13:59 + */ +@Data +public class ResetPwdEmailReq { + + @NotBlank(message = "邮箱不能为空") + @Pattern(regexp = "^[A-Za-z0-9]+([_.][A-Za-z0-9]+)*@((qq|163|gmail|88|email)+\\.)+[A-Za-z]{2,6}$", message = "请输入正确邮箱号,目前仅支持 qq邮箱、163邮箱、谷歌邮箱等常用邮箱") + private String email; +} diff --git a/business/src/main/java/com/yxx/business/model/request/ResetPwdReq.java b/business/src/main/java/com/yxx/business/model/request/ResetPwdReq.java new file mode 100644 index 0000000..e3569d5 --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/request/ResetPwdReq.java @@ -0,0 +1,26 @@ +package com.yxx.business.model.request; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import jakarta.validation.constraints.NotBlank; + +/** + * 重置密码请求参数 + * + * @author yxx + * @classname ResetPwdReq + * @since 2023-07-25 15:18 + */ +@Data +public class ResetPwdReq { + /** + * 新密码 + */ + @NotBlank(message = "密码不能为空") + @Length(min = 8, max = 20, message = "密码应为8-20位") + private String newPassword; + + @NotBlank(message = "token不能为空") + private String token; +} diff --git a/business/src/main/java/com/yxx/business/model/request/UserRegisterReq.java b/business/src/main/java/com/yxx/business/model/request/UserRegisterReq.java new file mode 100644 index 0000000..d81790a --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/request/UserRegisterReq.java @@ -0,0 +1,54 @@ +package com.yxx.business.model.request; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +/** + * @author yxx + * @since 2023-05-15 14:11 + */ +@Data +public class UserRegisterReq { + /** + * 登录账号 + */ + @NotBlank(message = "登录账号不能为空") + @Length(min = 4, max = 12, message = "登录账号应为4-12位") + private String loginCode; + + /** + * 登录名 + */ + @NotBlank(message = "昵称不能为空") + @Length(min = 2, max = 8, message = "昵称应为2-8位") + private String loginName; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空") + @Length(min = 8, max = 20, message = "密码应为8-20位") + private String password; + + /** + * 手机号 + */ + @Pattern(regexp = "^$|^1[3456789]\\d{9}$", message = "请输入正确手机号") + private String linkPhone; + + /** + * 邮箱 + */ + @Pattern(regexp = "^$|^[A-Za-z0-9]+([_.][A-Za-z0-9]+)*@((qq|163|gmail|88|email)+\\.)+[A-Za-z]{2,6}$", message = "请输入正确邮箱号,目前仅支持 qq邮箱、163邮箱、谷歌邮箱等常用邮箱") + private String email; + + + /** + * 验证码 + */ + @NotBlank(message = "验证码不能为空") + private String captcha; +} diff --git a/business/src/main/java/com/yxx/business/model/response/LoginRes.java b/business/src/main/java/com/yxx/business/model/response/LoginRes.java new file mode 100644 index 0000000..e0cbe2e --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/response/LoginRes.java @@ -0,0 +1,16 @@ +package com.yxx.business.model.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author yxx + * @since 2022-11-12 03:42 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginRes { + private String token; +} diff --git a/business/src/main/java/com/yxx/business/model/response/OperateLogResp.java b/business/src/main/java/com/yxx/business/model/response/OperateLogResp.java new file mode 100644 index 0000000..6e3c657 --- /dev/null +++ b/business/src/main/java/com/yxx/business/model/response/OperateLogResp.java @@ -0,0 +1,63 @@ +package com.yxx.business.model.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * @author yxx + * @since 2022/8/1 17:43 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class OperateLogResp { + private static final long serialVersionUID = -6160374035388312821L; + + /** + * 主键ID + */ + private Long id; + + /** + * 操作模块 + */ + private String module; + + /** + * 日志标题 + */ + private String title; + + /** + * 操作IP + */ + private String ip; + + /** + * 类型:1:成功;2:失败 + */ + private Integer type; + + /** + * 异常信息 + */ + private String exception; + + /** + * 操作用户 + */ + private String loginCode; + + /** + * 用户名称 + */ + private String loginName; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; +} diff --git a/business/src/main/java/com/yxx/business/service/MenuService.java b/business/src/main/java/com/yxx/business/service/MenuService.java new file mode 100644 index 0000000..363858e --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/MenuService.java @@ -0,0 +1,18 @@ +package com.yxx.business.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.business.model.entity.Menu; + +import java.util.List; + +/** + * @author yxx + * @since 2023-05-18 15:04 + */ +public interface MenuService extends IService { + /** + * 菜单树 + * @return 菜单树 + */ + List menuTree(); +} diff --git a/business/src/main/java/com/yxx/business/service/OperateLogService.java b/business/src/main/java/com/yxx/business/service/OperateLogService.java new file mode 100644 index 0000000..95d25ee --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/OperateLogService.java @@ -0,0 +1,32 @@ +package com.yxx.business.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.business.model.request.OperateLogReq; +import com.yxx.business.model.response.OperateLogResp; +import com.yxx.common.core.model.OperateLog; + +/** + * 操作日志表 服务类 + * + * @author yxx + * @since 2022-07-15 + */ +public interface OperateLogService extends IService { + + /** + * 查询操作日志分页列表 + * + * @param req OperateLogReq + * @return 操作日志分页列表 + */ + Page operationLogPage(OperateLogReq req); + + /** + * 登录日志分野 + * + * @param req 请求参数 + * @return 分页结果 + */ + Page authLogPage(OperateLogReq req); +} diff --git a/business/src/main/java/com/yxx/business/service/RoleMenuService.java b/business/src/main/java/com/yxx/business/service/RoleMenuService.java new file mode 100644 index 0000000..c35e672 --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/RoleMenuService.java @@ -0,0 +1,20 @@ +package com.yxx.business.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.business.model.entity.RoleMenu; + +import java.util.List; + +/** + * @author yxx + * @since 2023-05-18 15:23 + */ +public interface RoleMenuService extends IService { + /** + * 根据角色code集合 获取菜单code集合 + * + * @param roleCodeList 角色code集合 + * @return 菜单code集合 + */ + List loginUserMenu(List roleCodeList); +} diff --git a/business/src/main/java/com/yxx/business/service/RoleService.java b/business/src/main/java/com/yxx/business/service/RoleService.java new file mode 100644 index 0000000..6571539 --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/RoleService.java @@ -0,0 +1,11 @@ +package com.yxx.business.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.business.model.entity.Role; + +/** + * @author yxx + * @since 2023-05-17 09:58 + */ +public interface RoleService extends IService { +} diff --git a/business/src/main/java/com/yxx/business/service/UserRoleService.java b/business/src/main/java/com/yxx/business/service/UserRoleService.java new file mode 100644 index 0000000..011fef6 --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/UserRoleService.java @@ -0,0 +1,31 @@ +package com.yxx.business.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.business.model.entity.User; +import com.yxx.business.model.entity.UserRole; + +import java.util.List; + +/** + * @author yxx + * @since 2023-05-17 10:00 + */ +public interface UserRoleService extends IService { + + /** + * 根据用户信息 获取该用户角色权限 + * + * @param user 用户信息 + * @return 用户角色code集合 + */ + List loginUserRoleManage(User user); + + /** + * 设置默认角色: 用户 + * + * @param user 用户信息 + * @return {@link Boolean } + * @author yxx + */ + Boolean setDefaultRole(User user); +} diff --git a/business/src/main/java/com/yxx/business/service/UserService.java b/business/src/main/java/com/yxx/business/service/UserService.java new file mode 100644 index 0000000..a84255c --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/UserService.java @@ -0,0 +1,74 @@ +package com.yxx.business.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yxx.business.model.entity.User; +import com.yxx.business.model.request.*; +import com.yxx.business.model.response.LoginRes; + +/** + * @author yxx + * @since 2022-11-12 13:54 + */ +public interface UserService extends IService { + /** + * 登录 + * + * @param request 请求参数 + * @return token等结果 + */ + LoginRes login(LoginReq request); + + /** + * 注册 + * + * @param req 用户注册信息 + * @return true-成功 + */ + Boolean register(UserRegisterReq req); + + /** + * 发送重置密码邮件 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + Boolean resetPwdEmail(ResetPwdEmailReq req); + + /** + * 重置密码 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + Boolean resetPwd(ResetPwdReq req); + + /** + * 根据电子邮件获取用户 + * + * @param email 电子邮件 + * @return {@link User } + * @author yxx + */ + User getUserByEmail(String email); + + + /** + * 修改密码 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + Boolean editPwd(EditPwdReq req); + + /** + * 发送注册验证码 + * + * @param req 要求事情 + * @return {@link Boolean } + * @author yxx + */ + Boolean sendRegisterCaptcha(RegisterCaptchaReq req); +} diff --git a/business/src/main/java/com/yxx/business/service/impl/MenuServiceImpl.java b/business/src/main/java/com/yxx/business/service/impl/MenuServiceImpl.java new file mode 100644 index 0000000..3290843 --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/impl/MenuServiceImpl.java @@ -0,0 +1,34 @@ +package com.yxx.business.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.business.mapper.MenuMapper; +import com.yxx.business.model.entity.Menu; +import com.yxx.business.service.MenuService; +import com.yxx.common.utils.TreeUtil; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author yxx + * @since 2023-05-18 15:05 + */ +@Service +public class MenuServiceImpl extends ServiceImpl implements MenuService { + + @Override + public List menuTree() { + List menuList = list(); + /* + * 构建树 + * + * @param listData 需要构建的结果集 这里是menuList + * @param parentKeyFunction 父节点 这里是parentId + * @param keyFunction 主键 这里是id + * @param setChildrenFunction 子集 这里是children + * @param rootParentValue 父节点的值 这里null + * @return java.util.List + */ + return TreeUtil.buildTree(menuList, Menu::getParentId, Menu::getId, Menu::setChildren, null); + } +} diff --git a/business/src/main/java/com/yxx/business/service/impl/OperateLogServiceImpl.java b/business/src/main/java/com/yxx/business/service/impl/OperateLogServiceImpl.java new file mode 100644 index 0000000..cb262af --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/impl/OperateLogServiceImpl.java @@ -0,0 +1,53 @@ +package com.yxx.business.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.business.mapper.OperateLogMapper; +import com.yxx.business.model.request.OperateLogReq; +import com.yxx.business.model.response.OperateLogResp; +import com.yxx.business.service.OperateLogService; +import com.yxx.common.core.model.LogDTO; +import com.yxx.common.core.model.OperateLog; +import com.yxx.framework.service.OperationLogService; +import org.springframework.beans.BeanUtils; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +/** + * 操作日志表 服务实现类 + * + * @author yxx + * @since 2022-07-15 + */ +@Service +public class OperateLogServiceImpl extends ServiceImpl + implements OperateLogService, OperationLogService { + + @Async + @Override + public void saveLog(LogDTO dto) { + // 初始化日志类 + OperateLog log = new OperateLog(); + // 拷贝赋值数据 + BeanUtils.copyProperties(dto, log); + // 插入日志数据 + this.save(log); + } + + + @Override + public Page operationLogPage(OperateLogReq req) { + // 初始化分页构造器 + Page page = new Page<>(req.getPage(), req.getPageSize()); + // 查询分页结果并返回 + return baseMapper.operationLogPage(page, req); + } + + @Override + public Page authLogPage(OperateLogReq req) { + // 初始化分页构造器 + Page page = new Page<>(req.getPage(), req.getPageSize()); + // 查询分页结果并返回 + return this.baseMapper.authLogPage(page, req); + } +} diff --git a/business/src/main/java/com/yxx/business/service/impl/RoleMenuServiceImpl.java b/business/src/main/java/com/yxx/business/service/impl/RoleMenuServiceImpl.java new file mode 100644 index 0000000..a5bf602 --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/impl/RoleMenuServiceImpl.java @@ -0,0 +1,52 @@ +package com.yxx.business.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.business.mapper.RoleMenuMapper; +import com.yxx.business.model.entity.Menu; +import com.yxx.business.model.entity.Role; +import com.yxx.business.model.entity.RoleMenu; +import com.yxx.business.service.MenuService; +import com.yxx.business.service.RoleMenuService; +import com.yxx.business.service.RoleService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author yxx + * @since 2023-05-18 15:23 + */ +@Service +@RequiredArgsConstructor +public class RoleMenuServiceImpl extends ServiceImpl implements RoleMenuService { + private final RoleService roleService; + private final MenuService menuService; + + @Override + public List loginUserMenu(List roleCodeList) { + // 如果角色code集合不为空 + if (!roleCodeList.isEmpty()) { + // 根据角色code集合获取角色集合 + List roleList = roleService.list(new LambdaQueryWrapper().in(Role::getCode, roleCodeList)); + // 根据角色集合 获取角色id集合 + List roleIdList = roleList.stream().map(Role::getId).collect(Collectors.toList()); + // 根据角色id集合 获取 角色菜单 集合 + List roleMenuList = list(new LambdaQueryWrapper().in(RoleMenu::getRoleId, roleIdList)); + + // 如果角色菜单集合不为空 + if (!roleMenuList.isEmpty()) { + // 根据角色菜单集合 获取菜单id集合 + List menuIdList = roleMenuList.stream().map(RoleMenu::getMenuId).collect(Collectors.toList()); + // 根据菜单id集合 获取菜单集合 + List menuList = menuService.list(new LambdaQueryWrapper().in(Menu::getId, menuIdList)); + // 根据菜单集合 获取菜单code集合 并返回 + return menuList.stream().map(Menu::getMenuCode).collect(Collectors.toList()); + } + } + return new ArrayList<>(); + } +} diff --git a/business/src/main/java/com/yxx/business/service/impl/RoleServiceImpl.java b/business/src/main/java/com/yxx/business/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..8410fe0 --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/impl/RoleServiceImpl.java @@ -0,0 +1,15 @@ +package com.yxx.business.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.business.mapper.RoleMapper; +import com.yxx.business.model.entity.Role; +import com.yxx.business.service.RoleService; +import org.springframework.stereotype.Service; + +/** + * @author yxx + * @since 2023-05-17 09:59 + */ +@Service +public class RoleServiceImpl extends ServiceImpl implements RoleService { +} diff --git a/business/src/main/java/com/yxx/business/service/impl/UserRoleServiceImpl.java b/business/src/main/java/com/yxx/business/service/impl/UserRoleServiceImpl.java new file mode 100644 index 0000000..5c4de40 --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/impl/UserRoleServiceImpl.java @@ -0,0 +1,70 @@ +package com.yxx.business.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.business.mapper.UserRoleMapper; +import com.yxx.business.model.entity.Role; +import com.yxx.business.model.entity.User; +import com.yxx.business.model.entity.UserRole; +import com.yxx.business.service.RoleService; +import com.yxx.business.service.UserRoleService; +import com.yxx.common.enums.business.RoleEnum; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author yxx + * @since 2023-05-17 10:01 + */ +@Service +@RequiredArgsConstructor +public class UserRoleServiceImpl extends ServiceImpl implements UserRoleService { + private final RoleService roleService; + + @Override + public List loginUserRoleManage(User user) { + // 初始化返回角色code集合 + List roleList = new LinkedList<>(); + // 根据用户id 获取该用户的角色集合 + List userRoleList = list(new LambdaQueryWrapper().eq(UserRole::getUserId, user.getId())); + // 如果没有角色不让登录,请取消下面一行代码注释 + // ApiAssert.isTrue(ApiCode.USER_NOT_ROLE, !userRoleList.isEmpty()); + + // 遍历用户角色集合 + userRoleList.forEach(userRole -> { + // 根据用户的角色id 获取角色详情 + Role role = roleService.getOne(new LambdaQueryWrapper().eq(Role::getId, userRole.getRoleId())); + + // 如果没有角色不让登录,请取消下面一行代码注释 + // ApiAssert.isTrue(ApiCode.USER_NOT_ROLE, ObjectUtil.isNull(role)); + + // 如果角色信息不为空 + if (ObjectUtil.isNotNull(role)) { + // 将角色code添加到返回值集合中 + roleList.add(role.getCode()); + } + }); + + // 返回角色code集合 + return roleList; + } + + @Override + public Boolean setDefaultRole(User user) { + // 根据角色code 获取角色详情 + Role role = roleService.getOne( + new LambdaQueryWrapper().eq(Role::getCode, RoleEnum.USER.getCode())); + // 初始化用户角色实体类 + UserRole userRole = new UserRole(); + // 设置用户id + userRole.setUserId(user.getId()); + // 设置角色id + userRole.setRoleId(role.getId()); + // 保存 + return save(userRole); + } +} diff --git a/business/src/main/java/com/yxx/business/service/impl/UserServiceImpl.java b/business/src/main/java/com/yxx/business/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..42919bb --- /dev/null +++ b/business/src/main/java/com/yxx/business/service/impl/UserServiceImpl.java @@ -0,0 +1,359 @@ +package com.yxx.business.service.impl; + +import cn.dev33.satoken.temp.SaTempUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yxx.business.mapper.UserMapper; +import com.yxx.business.model.entity.User; +import com.yxx.business.model.request.*; +import com.yxx.business.model.response.LoginRes; +import com.yxx.business.service.RoleMenuService; +import com.yxx.business.service.UserRoleService; +import com.yxx.business.service.UserService; +import com.yxx.common.constant.EmailSubjectConstant; +import com.yxx.common.constant.LoginDevice; +import com.yxx.common.constant.RedisConstant; +import com.yxx.common.core.model.LoginUser; +import com.yxx.common.enums.ApiCode; +import com.yxx.common.exceptions.ApiException; +import com.yxx.common.properties.IpProperties; +import com.yxx.common.properties.MailProperties; +import com.yxx.common.properties.MyWebProperties; +import com.yxx.common.properties.ResetPwdProperties; +import com.yxx.common.utils.ApiAssert; +import com.yxx.common.utils.DateUtils; +import com.yxx.common.utils.ServletUtils; +import com.yxx.common.utils.agent.UserAgentUtil; +import com.yxx.common.utils.auth.LoginUtils; +import com.yxx.common.utils.email.MailUtils; +import com.yxx.common.utils.ip.AddressUtil; +import com.yxx.common.utils.ip.IpUtil; +import com.yxx.common.utils.redis.RedissonCache; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.DigestUtils; + +import jakarta.servlet.http.HttpServletRequest; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * @author yxx + * @since 2022-11-12 13:54 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl extends ServiceImpl implements UserService { + private final UserRoleService userRoleService; + + private final RoleMenuService roleMenuService; + + private final RedissonCache redissonCache; + + private final MailUtils mailUtils; + + private final MailProperties mailProperties; + + private final ResetPwdProperties resetPwdProperties; + + private final IpProperties ipProperties; + + private final MyWebProperties myWebProperties; + + @Override + public LoginRes login(LoginReq request) { + // 根据登录账号获取用户信息 + User user = getOne( + new LambdaQueryWrapper().eq(User::getLoginCode, request.getLoginCode())); + // 如果用户信息不存在,抛出异常 + ApiAssert.isTrue(ApiCode.USER_NOT_EXIST, ObjectUtil.isNotNull(user)); + // 加密请求参数中的密码 + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + // 如果请求参数中的密码加密后和数据库中不一致,抛出异常 + ApiAssert.isTrue(ApiCode.PASSWORD_ERROR, encoder.matches(request.getPassword(), user.getPassword())); + + // 初始化登录信息 + LoginUser loginUser = new LoginUser(); + // 拷贝赋值数据 + BeanUtils.copyProperties(user, loginUser); + // 设置登录时间 + loginUser.setLoginTime(LocalDateTime.now()); + // 获取该用户角色信息 + List roleCodeList = userRoleService.loginUserRoleManage(user); + // 赋值角色集合 + loginUser.setRolePermission(roleCodeList); + // 根据角色code获取该用户菜单集合 + List menuCodeList = roleMenuService.loginUserMenu(roleCodeList); + // 赋值菜单集合 + loginUser.setMenuPermission(menuCodeList); + + // 获取请求头 + HttpServletRequest servletRequest = ServletUtils.getRequest(); + // 获取登录设备信息 + String requestAgent = servletRequest.getHeader("user-agent"); + // 解析登录设备 + String agent = UserAgentUtil.getAgent(requestAgent); + // 设置该用户登录时的设备名称 + loginUser.setAgent(agent); + + + // 如果校验ip + if (Boolean.TRUE.equals(ipProperties.getCheck())) { + // 得到请求时的ip + String requestIp = IpUtil.getRequestIp(); + // 获取ip归属地 + String ipHomePlace = AddressUtil.getIpHomePlace(requestIp, 2); + // 设置该用户登录时的ip归属地 + loginUser.setIpHomePlace(ipHomePlace); + + // 新建checkUser并将user信息赋值过来,下面异步校验使用。 + // 直接用user会有异步信息还未执行的时候,下面的用户信息已经更新的问题 + User checkUser = new User(); + BeanUtils.copyProperties(user, checkUser); + + // 异地登录校验 + CompletableFuture.runAsync(() -> checkRemoteLogin(checkUser, ipHomePlace, requestIp, agent)); + + user.setAgent(agent); + user.setIpHomePlace(ipHomePlace); + } + + // 登录 + LoginUtils.login(loginUser, LoginDevice.PC); + + // 修改用户数据 + updateById(user); + + // 返回token + return new LoginRes(loginUser.getToken()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Boolean register(UserRegisterReq req) { + // 判断该邮箱是否存在验证码 + Boolean emailIsSend = redissonCache.isExists(RedisConstant.EMAIL_REGISTER + req.getEmail()); + // 如果不存在,抛出提示 + ApiAssert.isTrue(ApiCode.CAPTCHA_NOT_EXIST, emailIsSend); + + // 获取验证码 + String captcha = redissonCache.getString(RedisConstant.EMAIL_REGISTER + req.getEmail()); + //对比用户传入的验证码是否正确 + ApiAssert.isTrue(ApiCode.CAPTCHA_ERROR, req.getCaptcha().equals(captcha)); + + // 根据注册账号查询用户信息 + User userByLoginCode = getOne( + new LambdaQueryWrapper().eq(User::getLoginCode, req.getLoginCode())); + // 根据注册邮箱号查询用户信息 + User userByEmail = getUserByEmail(req.getEmail()); + // 如果存在该账号信息 表示用户已存在,抛出提示 + ApiAssert.isTrue(ApiCode.USER_EXIST, + ObjectUtil.isNull(userByLoginCode) && ObjectUtil.isNull(userByEmail)); + // 创建 BCryptPasswordEncoder 对象 + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + // 对密码进行哈希 + String password = encoder.encode(req.getPassword()); + + + // 初始化用户类 + User user = new User(); + // 拷贝赋值数据 + BeanUtils.copyProperties(req, user); + // 设置加密后密码 + user.setPassword(password); + // 插入 + boolean saveResult = save(user); + + // 设置默认角色 + Boolean result = userRoleService.setDefaultRole(user); + + // 删除该邮箱注册验证码 + redissonCache.remove(RedisConstant.EMAIL_REGISTER + req.getEmail()); + + // 校验操作结果 + if (!(saveResult && result)) { + throw new ApiException(ApiCode.SYSTEM_ERROR); + } + + return Boolean.TRUE; + } + + @Override + public Boolean resetPwdEmail(ResetPwdEmailReq req) { + // 校验邮件是否已经发送过 + ApiAssert.isFalse(ApiCode.MAIL_EXIST, redissonCache.exists(RedisConstant.RESET_PWD_CONTENT + req.getEmail())); + + // 根据邮箱 获取用户 + User user = getUserByEmail(req.getEmail()); + // 如果用户不存在,抛出提示 + ApiAssert.isTrue(ApiCode.EMAIL_NOT_REGISTER, ObjectUtil.isNotNull(user)); + + // 从redis中获取该邮箱号今日找回密码次数 + Integer number = redissonCache.get(RedisConstant.RESET_PWD_NUM + req.getEmail()); + // 如果找回次数不为空,并且大于等于设置的最大次数,抛出异常 + ApiAssert.isFalse(ApiCode.RESET_PWD_MAX, number != null && number >= resetPwdProperties.getMaxNumber()); + + // 创建临时token 临时时间15分钟 + String token = SaTempUtil.createToken(req.getEmail(), resetPwdProperties.getResetPwdTime()); + // 找回密码路径 拼接token + String resetPassHref = resetPwdProperties.getBasePath() + "?token=" + token; + // 邮件内容 + String emailContent = resetPwdProperties.getResetPwdContent().replace("{url}", resetPassHref) + .replace("{time}", String.valueOf(resetPwdProperties.getResetPwdTime())) + .replace("{domain}", myWebProperties.getDomain()) + .replace("{formName}", mailProperties.getFromName()) + .replace("{form}", mailProperties.getFrom()); + // 发送html格式邮件 + mailUtils.baseSendMail(req.getEmail(), EmailSubjectConstant.RESET_PWD, emailContent, true); + + // 将临时token 存入redis中 + redissonCache.putString(RedisConstant.RESET_PWD_CONTENT + req.getEmail(), token, 900, TimeUnit.SECONDS); + + // 防止恶意刷邮件 + // 今天剩余时间 + Long time = DateUtils.theRestOfTheDaySecond(); + // 添加找回密码次数到redis中 找回密码次数+1 + redissonCache.put(RedisConstant.RESET_PWD_NUM + req.getEmail(), Optional.ofNullable(number).map(x -> ++x).orElse(1), time); + + return Boolean.TRUE; + } + + @Override + public Boolean resetPwd(ResetPwdReq req) { + // 获取临时token的存活时间 -1 代表永久,-2 代表token无效 + long timeout = SaTempUtil.getTimeout(req.getToken()); + // 如果token无效,抛出提示 + ApiAssert.isFalse(ApiCode.RESET_PWD_TOKEN_ERROR, timeout == -2); + // 获取token对应的邮箱 + String email = SaTempUtil.parseToken(req.getToken(), String.class); + // 根据邮箱获取用户 + User user = getUserByEmail(email); + // 如果用户为空,抛出提示 + ApiAssert.isTrue(ApiCode.DATE_ERROR, ObjectUtil.isNotNull(user)); + + // 删除临时token + SaTempUtil.deleteToken(req.getToken()); + + // 加密密码 + String password = DigestUtils.md5DigestAsHex(req.getNewPassword().getBytes()); + // 根据邮箱修改密码 + return update(new LambdaUpdateWrapper().eq(User::getEmail, email).set(User::getPassword, password)); + } + + + @Override + public User getUserByEmail(String email) { + // 根据邮箱号获取用户信息 + return getOne(new LambdaUpdateWrapper().eq(User::getEmail, email)); + } + + @Override + public Boolean editPwd(EditPwdReq req) { + // 根据登录id 获取该用户详情 + User user = getById(LoginUtils.getUserId()); + + // 加密请求参数中的旧密码 + String password = DigestUtils.md5DigestAsHex(req.getPassword().getBytes()); + // 匹对请求参数中的旧密码是否正确 + ApiAssert.isFalse(ApiCode.ORIGINAL_PASSWORD_ERROR, user.getPassword().equals(password)); + + // 加密新密码 + String newPassword = DigestUtils.md5DigestAsHex(req.getNewPassword().getBytes()); + // 根据用户id修改新密码 + return update(new LambdaUpdateWrapper().eq(User::getId, user.getId()).set(User::getPassword, newPassword)); + } + + @Override + public Boolean sendRegisterCaptcha(RegisterCaptchaReq req) { + // 判断该邮箱是否注册过 + User userByEmail = getUserByEmail(req.getEmail()); + // 如果注册过,抛出提示 + ApiAssert.isTrue(ApiCode.EMAIL_EXIST, ObjectUtil.isNull(userByEmail)); + // 判断该邮箱是否已经发送过验证码 + Boolean emailIsSend = redissonCache.isExists(RedisConstant.EMAIL_REGISTER + req.getEmail()); + // 如果已经发送过,抛出提示 + ApiAssert.isFalse(ApiCode.MAIL_EXIST, emailIsSend); + + // 防止恶意发送邮件 + // 从redis中获取该邮箱号今日注册次数 + Integer number = redissonCache.get(RedisConstant.EMAIL_REGISTER_NUM + req.getEmail()); + // 如果注册次数不为空,并且大于等于设置的最大次数,抛出异常 + ApiAssert.isFalse(ApiCode.REGISTER_MAX, number != null && number >= mailProperties.getRegisterMax()); + + // 获得六位随机数 + int random = RandomUtil.randomInt(100000, 999999); + // 拼接邮件内容 + String resultText = mailProperties.getRegisterContent() + .replace("{captcha}", String.valueOf(random)) + .replace("{time}", String.valueOf(mailProperties.getRegisterTime())) + .replace("{domain}", myWebProperties.getDomain()) + .replace("{formName}", mailProperties.getFromName()) + .replace("{form}", mailProperties.getFrom()); + + // 发送邮件 + mailUtils.baseSendMail(req.getEmail(), EmailSubjectConstant.REGISTER_SUBJECT, resultText, true); + + // 存入redis + redissonCache.putString(RedisConstant.EMAIL_REGISTER + req.getEmail(), String.valueOf(random), + mailProperties.getRegisterTime(), TimeUnit.MINUTES); + + // 防止恶意发送邮件 + // 今天剩余时间 + Long time = DateUtils.theRestOfTheDaySecond(); + // 添加注册次数到redis中 注册次数+1 + redissonCache.put(RedisConstant.EMAIL_REGISTER_NUM + req.getEmail(), Optional.ofNullable(number).map(x -> ++x).orElse(1), time); + + return Boolean.TRUE; + } + + void checkRemoteLogin(User user, String ipHomePlace, String requestIp, String requestAgent) { + if (Boolean.TRUE.equals(ipProperties.getCheck())) { + log.info("异地登录校验~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + if (AddressUtil.isValidIPv4(requestIp)) { + // 判断是否发送过异常登录通知 + boolean exists = redissonCache.exists(RedisConstant.IP_UNUSUAL_LOGIN + user.getId()); + // 如果没有发送过,进行校验 + if (!exists) { + // 如果用户ip归属地不为空,且与当前登录ip归属地不同 登录设备名称不为空且与当前设备不同 + if (CharSequenceUtil.isNotBlank(user.getAgent()) && !user.getAgent().equals(requestAgent) && + CharSequenceUtil.isNotBlank(user.getIpHomePlace()) && !user.getIpHomePlace().equals(ipHomePlace)) { + // 获取ip归属地 + String unusual = AddressUtil.getIpHomePlace(requestIp, 3); + // 邮件正文 + String time = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN); + String emailContent = mailProperties.getIpUnusualContent().replace("{time}", time) + .replace("{ip}", requestIp) + .replace("{address}", unusual) + .replace("{agent}", requestAgent) + .replace("{domain}", myWebProperties.getDomain()) + .replace("{formName}", mailProperties.getFromName()) + .replace("{form}", mailProperties.getFrom()); + // 发送邮件通知 + mailUtils.baseSendMail(user.getEmail(), EmailSubjectConstant.IP_UNUSUAL, emailContent, true); + // 加入redis(一天提醒一次) + // 今天剩余时间 + Long residueTime = DateUtils.theRestOfTheDaySecond(); + redissonCache.put(RedisConstant.IP_UNUSUAL_LOGIN + user.getId(), Boolean.TRUE, residueTime); + } + } + } else { + log.info("非ipv4"); + } + log.info("异地登录校验结束~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + } + } +} diff --git a/business/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/business/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..1c92265 --- /dev/null +++ b/business/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,44 @@ +{ + "properties": [ + { + "name": "mail.subject", + "type": "java.lang.String", + "description": "Description for mail.subject." + }, + { + "name": "reset-password.base-path", + "type": "java.lang.String", + "description": "Description for reset-password.base-path." + }, + { + "name": "reset-password.max-number", + "type": "java.lang.String", + "description": "Description for reset-password.max-number." + }, + { + "name": "mail.register-time", + "type": "java.lang.String", + "description": "Description for mail.register-time." + }, + { + "name": "mail.register-max", + "type": "java.lang.String", + "description": "Description for mail.register-max." + }, + { + "name": "mail.registerContent", + "type": "java.lang.String", + "description": "Description for mail.registerContent." + }, + { + "name": "reset-password.reset-pwd-content", + "type": "java.lang.String", + "description": "Description for reset-password.reset-pwd-content." + }, + { + "name": "web.domain", + "type": "java.lang.String", + "description": "Description for web.domain." + } + ] +} \ No newline at end of file diff --git a/business/src/main/resources/application-dev.yml b/business/src/main/resources/application-dev.yml new file mode 100644 index 0000000..9f38ed0 --- /dev/null +++ b/business/src/main/resources/application-dev.yml @@ -0,0 +1,321 @@ +spring: + application: + name: business + config: + activate: + on-profile: dev + # mysql 通用配置信息内容 使用HikariDataSource连接池 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/base_springboot?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: admin123 + type: com.zaxxer.hikari.HikariDataSource + hikari: + minimum-idle: 10 + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: HikariCP + max-lifetime: 1800000 + connection-timeout: 30000 + connection-test-query: SELECT 1 + # 请求大小 + servlet: + multipart: + max-file-size: 30MB + max-request-size: 30MB + # redis 配置内容 + data: + redis: + # 设置初始数据库的数据为8 + database: 14 + host: 127.0.0.1 + port: 6379 + # password: + jedis: + pool: + # 连接池最大连接数(使用负值表示没有限制) + max-active: 50 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: 3000ms + # 连接池中的最大空闲连接数 + max-idle: 20 + # 连接池中的最小空闲连接数 + min-idle: 5 + # 连接超时时间(毫秒) + timeout: 5000ms + # 邮箱 + mail: + # 编码 + default-encoding: UTF-8 + # 邮件服务器 + host: smtp.email.cn + # 用户名(一般为邮箱号) + username: shuniversity@email.cn + # 邮件服务器端口 + port: 25 + # 专用密码 + password: 2BCZKEGQgHyCZ5i4 + +# 邮箱设置 +mail: + # 发件人邮箱 + from: shuniversity@email.cn + # 发件人名称 + from-name: SpringBoot开发模版 + # 注册时邮箱验证码有效期 + register-time: 5 + # 一个邮箱每日最大注册次数 (防止恶意发送邮件) + register-max: 3 + # 注册邮件正文 + register-content: > + + + + 注册验证码邮件 + + + +
+

欢迎注册!

+

感谢您注册我们的服务。以下是您的验证码:

+ {captcha} +

此验证码将在 {time} 分钟后失效,请及时使用。

+

如果您没有进行注册操作,请忽略此邮件。

+ +
+ + + + # ip异常邮件正文 + ip-unusual-content: | + + + + IP异常提醒 + + + +
+

IP异常提醒

+

您的账户在以下时间出现异常登录:

+ + + + + + + + + + + + + +
登录时间登录IP登录地址登录设备
{time}{ip}{address}{agent}
+

如果这些登录不是您的操作,请尽快修改密码并确保账户安全。

+ +
+ + + + +# 重置密码 +reset-password: + # 找回密码页面url + base-path: http://127.0.0.1 + # 每日找回密码最大次数 (防止恶意发送邮件) + max-number: 3 + # 找回密码邮件正文 + reset-pwd-content: > + + + + 重置密码 + + + +
+

重置密码

+

亲爱的用户,您收到这封邮件是因为您请求重置密码。

+

请点击下面的按钮来设置一个新密码:

+ 重置密码 +

请注意,此链接将在接下来的{time}分钟内有效,请尽快完成密码重置操作。

+

如果您没有请求重置密码,请忽略此邮件。

+ +
+ + + + # 重置密码链接有效期(单位:分钟) + reset-pwd-time: 15 + +# 是否校验ip异常 +ip: + # 开启后则校验ip异常情况 (异地换设备登录) 发送邮件通知 + check: false + +# 网站域名 +web: + domain: http://127.0.0.1 + +# mybatis-plus开发日志打印内容 +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + global-config: + db-config: + logic-delete-field: is_delete + logic-delete-value: 1 + logic-not-delete-value: 0 + banner: false + +# Sa-Token配置 +sa-token: + # token名称 (同时也是cookie名称,可自行更改) + token-name: Authorization + # token前缀 + token-prefix: Bearer + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # 是否打开自动续签 (如果此值为true, 框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + autoRenew: true + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + active-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: false + # token风格 + token-style: uuid + # 是否输出操作日志 + is-log: false + # jwt秘钥 + jwt-secret-key: TUx8IaslTsibligsqvzLmNvbQECAwQF + # 是否在初始化配置时打印版本字符画 + is-print: false + # 登录逻辑缓存和业务逻辑缓存分离 + alone-redis: + # Redis数据库索引(默认为0) + database: 13 + # Redis服务器地址 + host: 127.0.0.1 + # Redis服务器连接端口 + port: 6379 + # Redis服务器连接密码(默认为空) + password: + # 连接超时时间 + timeout: 10s + diff --git a/business/src/main/resources/application-prod.yml b/business/src/main/resources/application-prod.yml new file mode 100644 index 0000000..51bb98c --- /dev/null +++ b/business/src/main/resources/application-prod.yml @@ -0,0 +1,321 @@ +spring: + application: + name: business + config: + activate: + on-profile: dev + # mysql 通用配置信息内容 使用HikariDataSource连接池 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/base_springboot?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: admin123 + type: com.zaxxer.hikari.HikariDataSource + hikari: + minimum-idle: 10 + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: HikariCP + max-lifetime: 1800000 + connection-timeout: 30000 + connection-test-query: SELECT 1 + # 请求大小 + servlet: + multipart: + max-file-size: 30MB + max-request-size: 30MB + data: + # redis 配置内容 + redis: + # 设置初始数据库的数据为8 + database: 8 + host: 127.0.0.1 + port: 6379 + # password: + jedis: + pool: + # 连接池最大连接数(使用负值表示没有限制) + max-active: 50 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: 3000ms + # 连接池中的最大空闲连接数 + max-idle: 20 + # 连接池中的最小空闲连接数 + min-idle: 5 + # 连接超时时间(毫秒) + timeout: 5000ms + # 邮箱 + mail: + # 编码 + default-encoding: UTF-8 + # 邮件服务器 + host: smtp.email.cn + # 用户名(一般为邮箱号) + username: shuniversity@email.cn + # 邮件服务器端口 + port: 25 + # 专用密码 + password: 2BCZKEGQgHyCZ5i4 + +# 邮箱设置 +mail: + # 发件人邮箱 + from: shuniversity@email.cn + # 发件人名称 + from-name: SpringBoot开发模版 + # 注册时邮箱验证码有效期 + register-time: 5 + # 一个邮箱每日最大注册次数 (防止恶意发送邮件) + register-max: 3 + # 注册邮件正文 + register-content: > + + + + 注册验证码邮件 + + + +
+

欢迎注册!

+

感谢您注册我们的服务。以下是您的验证码:

+ {captcha} +

此验证码将在 {time} 分钟后失效,请及时使用。

+

如果您没有进行注册操作,请忽略此邮件。

+ +
+ + + + # ip异常邮件正文 + ip-unusual-content: | + + + + IP异常提醒 + + + +
+

IP异常提醒

+

您的账户在以下时间出现异常登录:

+ + + + + + + + + + + + + +
登录时间登录IP登录地址登录设备
{time}{ip}{address}{agent}
+

如果这些登录不是您的操作,请尽快修改密码并确保账户安全。

+ +
+ + + + +# 重置密码 +reset-password: + # 找回密码页面url + base-path: http://127.0.0.1 + # 每日找回密码最大次数 (防止恶意发送邮件) + max-number: 3 + # 找回密码邮件正文 + reset-pwd-content: > + + + + 重置密码 + + + +
+

重置密码

+

亲爱的用户,您收到这封邮件是因为您请求重置密码。

+

请点击下面的按钮来设置一个新密码:

+ 重置密码 +

请注意,此链接将在接下来的{time}分钟内有效,请尽快完成密码重置操作。

+

如果您没有请求重置密码,请忽略此邮件。

+ +
+ + + + # 重置密码链接有效期(单位:分钟) + reset-pwd-time: 15 + +# 是否校验ip异常 +ip: + # 开启后则校验ip异常情况 (异地换设备登录) 发送邮件通知 + check: true + +# 网站域名 +web: + domain: http://127.0.0.1 + +# mybatis-plus开发日志打印内容 +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + global-config: + db-config: + logic-delete-field: is_delete + logic-delete-value: 1 + logic-not-delete-value: 0 + banner: false + +# Sa-Token配置 +sa-token: + # token名称 (同时也是cookie名称,可自行更改) + token-name: Authorization + # token前缀 + token-prefix: Bearer + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # 是否打开自动续签 (如果此值为true, 框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + autoRenew: true + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + activity-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: false + # token风格 + token-style: uuid + # 是否输出操作日志 + is-log: false + # jwt秘钥 + jwt-secret-key: TUx8IaslTsibligsqvzLmNvbQECAwQF + # 是否在初始化配置时打印版本字符画 + is-print: false + # 登录逻辑缓存和业务逻辑缓存分离 + alone-redis: + # Redis数据库索引(默认为0) + database: 2 + # Redis服务器地址 + host: 127.0.0.1 + # Redis服务器连接端口 + port: 6379 + # Redis服务器连接密码(默认为空) + password: + # 连接超时时间 + timeout: 10s + diff --git a/business/src/main/resources/application.yml b/business/src/main/resources/application.yml new file mode 100644 index 0000000..c66bbdc --- /dev/null +++ b/business/src/main/resources/application.yml @@ -0,0 +1,17 @@ +server: + port: 6059 + +spring: + profiles: + active: @profileActive@ + +app: + name: user + +logging: + file: + path: ./logs + name: business + level: + # mapper包下打印debug级别日志 + com.yxx.business.mapper: debug diff --git a/business/src/main/resources/banner.txt b/business/src/main/resources/banner.txt new file mode 100644 index 0000000..7ceb28d --- /dev/null +++ b/business/src/main/resources/banner.txt @@ -0,0 +1,6 @@ +${AnsiColor.BRIGHT_GREEN} + + . + |-. ,-. ,-. ,-. + | | ,-| `-. |-' + ^-' `-^ `-' `-' diff --git a/business/src/main/resources/ip2region/ip2region.xdb b/business/src/main/resources/ip2region/ip2region.xdb new file mode 100644 index 0000000..c78b792 Binary files /dev/null and b/business/src/main/resources/ip2region/ip2region.xdb differ diff --git a/business/src/main/resources/logback-spring.xml b/business/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..3fce3ba --- /dev/null +++ b/business/src/main/resources/logback-spring.xml @@ -0,0 +1,27 @@ + + + + + + + %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) [traceId:%X{Trace-Id}] %cyan(%logger{50}) - %magenta(%msg) %n + + + + ${LOG_HOME}/${APP_NAME}.log + + ${LOG_HOME}/${APP_NAME}.log.%d{yyyy-MM-dd}.%i.log + 30 + 100MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [traceId:%X{Trace-Id}] %logger{50} - %msg%n + + + + + + + + diff --git a/business/src/main/resources/mapper/OperateLogMapper.xml b/business/src/main/resources/mapper/OperateLogMapper.xml new file mode 100644 index 0000000..8a57d97 --- /dev/null +++ b/business/src/main/resources/mapper/OperateLogMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + \ No newline at end of file diff --git a/business/src/test/java/com/yxx/business/BusinessApplicationTests.java b/business/src/test/java/com/yxx/business/BusinessApplicationTests.java new file mode 100644 index 0000000..c403199 --- /dev/null +++ b/business/src/test/java/com/yxx/business/BusinessApplicationTests.java @@ -0,0 +1,74 @@ +package com.yxx.business; + +import cn.dev33.satoken.temp.SaTempUtil; +import com.yxx.business.model.entity.Menu; +import com.yxx.business.model.entity.User; +import com.yxx.business.service.MenuService; +import com.yxx.common.properties.MailProperties; +import com.yxx.common.utils.email.MailUtils; +import com.yxx.common.utils.redis.RedissonCache; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +@SpringBootTest +class BusinessApplicationTests { + @Autowired + private RedissonCache redissonCache; + @Autowired + private MenuService menuService; + @Autowired + private MailUtils mailUtils; + + @Autowired + private MailProperties mailProperties; + + String key = "test"; + String userKey = "user"; + + @Test + void redisPutTest() { + redissonCache.put(key, "测试呀"); + + User user = new User(); + user.setLoginCode("120"); + user.setLoginName("护士"); + user.setPassword("120"); + redissonCache.put(userKey, user); + } + + @Test + void redisGetTest(){ + String string = redissonCache.getString(key); + System.out.println("string = " + string); + + User value = redissonCache.get(userKey); + System.out.println("user = " + value); + } + + @Test + void redisRemoveTest(){ + redissonCache.remove(key); + redissonCache.remove(userKey); + } + + @Test + void menuTree(){ + List menus = menuService.menuTree(); + System.out.println("menus = " + menus); + } + + @Test + void mailTest() { + mailUtils.baseSendMail("yangxx@88.com", "主题", "test", false); + } + + @Test + void tokenTest(){ + long timeout = SaTempUtil.getTimeout("asldfjaklsjfk"); + System.out.println("timeout = " + timeout); + } + +} diff --git a/common/common-core/pom.xml b/common/common-core/pom.xml new file mode 100644 index 0000000..d516f4e --- /dev/null +++ b/common/common-core/pom.xml @@ -0,0 +1,186 @@ + + + 4.0.0 + common-core + jar + + common + com.yxx + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + + org.springframework.security + spring-security-core + + + + + org.springframework.boot + spring-boot-starter-jdbc + ${spring-boot.version} + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + + mysql + mysql-connector-java + runtime + ${mysql.version} + + + + + org.apache.commons + commons-lang3 + ${lang3.version} + + + + + org.springframework.boot + spring-boot-starter-data-redis + ${spring-boot.version} + + + + io.netty + netty-all + ${netty.version} + + + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + + + org.springframework.boot + spring-boot-starter-aop + ${spring-boot.version} + + + + + cn.dev33 + sa-token-spring-boot3-starter + ${sa-token.version} + + + + + cn.dev33 + sa-token-jwt + ${sa-token.version} + + + + + org.apache.commons + commons-pool2 + ${pool2.version} + + + + + cn.dev33 + sa-token-redis-jackson + ${sa-token.version} + + + + + cn.dev33 + sa-token-alone-redis + ${sa-token.version} + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + + com.dtflys.forest + forest-spring-boot-starter + ${forest.verskon} + + + + org.jasypt + jasypt + ${jasypt.version} + + + + + org.springframework.boot + spring-boot-starter-mail + ${spring-boot.version} + + + + + org.lionsoul + ip2region + ${ip2region.version} + + + + commons-io + commons-io + ${ip2region-commons.version} + + + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat.version} + + + + com.alibaba + transmittable-thread-local + ${ttl.version} + + + + + + + + ossrh + OSS Snapshot repository + https://oss.sonatype.org/content/repositories/snapshots/ + + false + + + true + + + + + + diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/auth/ReleaseToken.java b/common/common-core/src/main/java/com/yxx/common/annotation/auth/ReleaseToken.java new file mode 100644 index 0000000..ef66027 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/annotation/auth/ReleaseToken.java @@ -0,0 +1,19 @@ +package com.yxx.common.annotation.auth; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * date:Created 2021/11/30 17:20 + * description:放行token + * + * @author yxx + */ + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ReleaseToken { +} + diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/jackson/SearchDate.java b/common/common-core/src/main/java/com/yxx/common/annotation/jackson/SearchDate.java new file mode 100644 index 0000000..85f46a4 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/annotation/jackson/SearchDate.java @@ -0,0 +1,31 @@ +package com.yxx.common.annotation.jackson; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.yxx.common.utils.jackson.SearchDateDeserializer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author yxx + * @since 2022/12/2 10:36 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@JsonDeserialize(using = SearchDateDeserializer.class) +public @interface SearchDate { + + /** + * 开始时间 + */ + boolean startDate() default false; + + /** + * 结束时间 + */ + boolean endDate() default false; +} diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/log/OperationLog.java b/common/common-core/src/main/java/com/yxx/common/annotation/log/OperationLog.java new file mode 100644 index 0000000..4a9fa79 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/annotation/log/OperationLog.java @@ -0,0 +1,26 @@ +package com.yxx.common.annotation.log; + +import java.lang.annotation.*; + +/** + * 自定义操作日志注解 + * + * @author yxx + * @since 2022/7/26 11:19 + */ +@Documented +@Target({ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface OperationLog { + + /** + * 操作模块 为空的话取@Api的tags值 + */ + String module() default ""; + + /** + * 日志标题 为空的话取@ApiOperation的Value值 + */ + String title() default ""; + +} diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/mybaits/EncryptedField.java b/common/common-core/src/main/java/com/yxx/common/annotation/mybaits/EncryptedField.java new file mode 100644 index 0000000..04bd948 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/annotation/mybaits/EncryptedField.java @@ -0,0 +1,32 @@ +package com.yxx.common.annotation.mybaits; + +import com.yxx.common.utils.encryptor.DESUtil; +import com.yxx.common.utils.encryptor.IEncryptor; + +import java.lang.annotation.*; + +/** + * description: + * 自定义注解,用来加在类字段上进行mybatis插入更新加密,查询解密 + * 默认key为 eRiw3nvi2lg (key必须大于8位数) + * 默认加密类为 DESUtil.class + * 默认查询解密 + * + * @author yxx + * @since 2022/11/25 + */ +@Documented +@Inherited +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface EncryptedField { + + //加密key + String key() default "eRiw3nvi2lg"; + + // 加密类 + Class encryptor() default DESUtil.class; + + // 是否解密 + boolean decode() default true; +} diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/response/ResponseResult.java b/common/common-core/src/main/java/com/yxx/common/annotation/response/ResponseResult.java new file mode 100644 index 0000000..73ddb6c --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/annotation/response/ResponseResult.java @@ -0,0 +1,14 @@ +package com.yxx.common.annotation.response; + +import java.lang.annotation.*; + +/** + * @author yxx + * @description: controller加该注解后 可统一返回结果json格式 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD}) +@Documented +public @interface ResponseResult { + +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckLogin.java b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckLogin.java new file mode 100644 index 0000000..f94a679 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckLogin.java @@ -0,0 +1,19 @@ +package com.yxx.common.annotation.satoken; + +import cn.dev33.satoken.annotation.SaCheckLogin; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 登录认证(User版):只有登录之后才能进入该方法 + *

可标注在函数、类上(效果等同于标注在此类的所有方法上) + */ +@SaCheckLogin(type = "user") +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE}) +public @interface SaAdminCheckLogin { + +} diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckPermission.java b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckPermission.java new file mode 100644 index 0000000..fa4fdbd --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckPermission.java @@ -0,0 +1,39 @@ +package com.yxx.common.annotation.satoken; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.yxx.common.utils.satoken.StpAdminUtil; +import org.springframework.core.annotation.AliasFor; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; + +/** + * 权限认证(User版):必须具有指定权限才能进入该方法 + *

可标注在函数、类上(效果等同于标注在此类的所有方法上) + * @author click33 + * + */ +@SaCheckPermission(type = StpAdminUtil.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE}) +public @interface SaAdminCheckPermission { + + /** + * 需要校验的权限码 + * @return 需要校验的权限码 + */ + @AliasFor(annotation = SaCheckPermission.class) + String [] value() default {}; + + /** + * 验证模式:AND | OR,默认AND + * @return 验证模式 + */ + @AliasFor(annotation = SaCheckPermission.class) + SaMode mode() default SaMode.AND; + +} diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckRole.java b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckRole.java new file mode 100644 index 0000000..36f9454 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckRole.java @@ -0,0 +1,39 @@ +package com.yxx.common.annotation.satoken; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.yxx.common.utils.satoken.StpAdminUtil; +import org.springframework.core.annotation.AliasFor; + +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaMode; + +/** + * 角色认证(User版):必须具有指定角色标识才能进入该方法 + *

可标注在函数、类上(效果等同于标注在此类的所有方法上) + * @author click33 + * + */ +@SaCheckRole(type = StpAdminUtil.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE}) +public @interface SaAdminCheckRole { + + /** + * 需要校验的角色标识 + * @return 需要校验的角色标识 + */ + @AliasFor(annotation = SaCheckRole.class) + String [] value() default {}; + + /** + * 验证模式:AND | OR,默认AND + * @return 验证模式 + */ + @AliasFor(annotation = SaCheckRole.class) + SaMode mode() default SaMode.AND; + +} diff --git a/common/common-core/src/main/java/com/yxx/common/constant/Constant.java b/common/common-core/src/main/java/com/yxx/common/constant/Constant.java new file mode 100644 index 0000000..3c4e41d --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/constant/Constant.java @@ -0,0 +1,15 @@ +package com.yxx.common.constant; + +/** + * @author yxx + * @since 2022-11-11 17:46 + */ +public interface Constant { + + /** + * 登录用户key + */ + String LOGIN_USER_KEY = "loginUser"; + + String EMPTY = ""; +} diff --git a/common/common-core/src/main/java/com/yxx/common/constant/EmailSubjectConstant.java b/common/common-core/src/main/java/com/yxx/common/constant/EmailSubjectConstant.java new file mode 100644 index 0000000..f404943 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/constant/EmailSubjectConstant.java @@ -0,0 +1,15 @@ +package com.yxx.common.constant; + +/** + * @author yxx + * @since 2023-07-25 14:07 + */ +public interface EmailSubjectConstant { + String RESET_PWD = "重置密码"; + + String REGISTER_SUBJECT = "【SpringBoot开发模板】注册提醒"; + + String IP_UNUSUAL = "已使用新的 IP 地址访问您的 山河大学 帐户"; + + +} diff --git a/common/common-core/src/main/java/com/yxx/common/constant/LoginDevice.java b/common/common-core/src/main/java/com/yxx/common/constant/LoginDevice.java new file mode 100644 index 0000000..bdfeb3b --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/constant/LoginDevice.java @@ -0,0 +1,10 @@ +package com.yxx.common.constant; + +/** + * @author yxx + * @since 2022-11-12 14:10 + */ +public interface LoginDevice { + String PC = "pc"; + String IPHONE = "iphone"; +} diff --git a/common/common-core/src/main/java/com/yxx/common/constant/RedisConstant.java b/common/common-core/src/main/java/com/yxx/common/constant/RedisConstant.java new file mode 100644 index 0000000..ebd7f1d --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/constant/RedisConstant.java @@ -0,0 +1,41 @@ +package com.yxx.common.constant; + +/** + * redis常数 + * + * @author yxx + * @classname RedisConstant + * @since 2023/07/05 + */ +public interface RedisConstant { + + /** + * 注册验证码 + */ + String EMAIL_REGISTER = "email:register:"; + + /** + * 当日发送注册验证码次数 + */ + String EMAIL_REGISTER_NUM = "email:register:num:"; + + /** + * 当日找回密码次数 + */ + String RESET_PWD_NUM = "reset:pwd:num:"; + + /** + * 重置密码内容 + */ + String RESET_PWD_CONTENT = "reset:pwd:content:"; + + /** + * ip异常操作 + */ + String IP_UNUSUAL_OPERATE = "ip:unusual:operate:"; + + /** + * ip异常登录 + */ + String IP_UNUSUAL_LOGIN = "ip:unusual:login:"; +} diff --git a/common/common-core/src/main/java/com/yxx/common/constant/SeparatorConstant.java b/common/common-core/src/main/java/com/yxx/common/constant/SeparatorConstant.java new file mode 100644 index 0000000..2409afd --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/constant/SeparatorConstant.java @@ -0,0 +1,14 @@ +package com.yxx.common.constant; + +/** + * @author yxx + * @since 2022/7/26 10:38 + */ +public interface SeparatorConstant { + + String COMMA = ","; + + String BACKSLASH = "/"; + + String RUNG = "-"; +} diff --git a/common/common-core/src/main/java/com/yxx/common/core/model/LogDTO.java b/common/common-core/src/main/java/com/yxx/common/core/model/LogDTO.java new file mode 100644 index 0000000..1717e69 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/core/model/LogDTO.java @@ -0,0 +1,85 @@ +package com.yxx.common.core.model; + +import cn.hutool.core.convert.Convert; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author yxx + * @since 2022/7/26 11:30 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class LogDTO extends Convert { + private static final long serialVersionUID = -2704193808014207916L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 操作模块 + */ + private String module; + + /** + * 日志类型 1-正常日志 2-异常日志 + */ + private Integer type; + + /** + * 日志标题 + */ + private String title; + + /** + * 操作IP + */ + private String ip; + + /** + * ip归属地 + */ + private String ipHomePlace; + + /** + * 用户代理 + */ + private String userAgent; + + /** + * 请求URI + */ + private String requestUri; + + private String traceId; + + private String spanId; + + /** + * 操作方式 + */ + private String method; + + /** + * 操作提交的数据 + */ + private String params; + + /** + * 执行时间 + */ + private Long time; + + /** + * 异常信息 + */ + private String exception; + + /** + * 创建人 + */ + private Long createUid; +} diff --git a/common/common-core/src/main/java/com/yxx/common/core/model/LoginUser.java b/common/common-core/src/main/java/com/yxx/common/core/model/LoginUser.java new file mode 100644 index 0000000..2d4b054 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/core/model/LoginUser.java @@ -0,0 +1,91 @@ +package com.yxx.common.core.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 登录用户身份权限 + * + * @author yxx + * @since 2022/4/13 14:23 + */ +@Data +@NoArgsConstructor +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = false) +public class LoginUser { + + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long id; + + /** + * 用户token + */ + private String token; + + /** + * 用户账号 + */ + private String loginCode; + + /** + * 登录名称 + */ + private String loginName; + + /** + * 学号 + */ + private String studentNumber; + + /** + * 联系手机 + */ + private String linkPhone; + + /** + * ip归属地 + */ + private String ipHomePlace; + + /** + * 登录设备 + */ + private String agent; + + /** + * 邮箱 + */ + private String email; + + /** + * 菜单权限 + */ + private List menuPermission; + + /** + * 按钮权限 + */ + private List buttonPermission; + + /** + * 角色权限 + */ + private List rolePermission; + + /** + * 登录时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime loginTime; +} diff --git a/common/common-core/src/main/java/com/yxx/common/core/model/OperateAdminLog.java b/common/common-core/src/main/java/com/yxx/common/core/model/OperateAdminLog.java new file mode 100644 index 0000000..ffde787 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/core/model/OperateAdminLog.java @@ -0,0 +1,112 @@ +package com.yxx.common.core.model; + +import cn.hutool.core.convert.Convert; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 操作日志表 + * + * @author yxx + * @since 2022-07-15 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("operate_admin_log") +public class OperateAdminLog extends Convert { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 日志类型 1-正常日志 2-异常日志 + */ + private Integer type; + + /** + * 操作模块 + */ + private String module; + + /** + * 日志标题 + */ + private String title; + + /** + * 操作IP + */ + private String ip; + + /** + * 用户代理 + */ + private String userAgent; + + /** + * 请求URI + */ + private String requestUri; + + /** + * 操作方式 + */ + private String method; + + /** + * 操作提交的数据 + */ + private String params; + + /** + * tLog中的traceId + */ + private String traceId; + + /** + * tLog中的spanId + */ + private String spanId; + + /** + * 执行时间 + */ + private Long time; + + /** + * 异常信息 + */ + private String exception; + + /** + * 创建人 + */ + @TableField(fill = FieldFill.INSERT) + private Long createUid; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 是否删除 + */ + @TableLogic + private Boolean isDelete; + +} diff --git a/common/common-core/src/main/java/com/yxx/common/core/model/OperateLog.java b/common/common-core/src/main/java/com/yxx/common/core/model/OperateLog.java new file mode 100644 index 0000000..10402eb --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/core/model/OperateLog.java @@ -0,0 +1,112 @@ +package com.yxx.common.core.model; + +import cn.hutool.core.convert.Convert; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 操作日志表 + * + * @author yxx + * @since 2022-07-15 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("operate_log") +public class OperateLog extends Convert { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 日志类型 1-正常日志 2-异常日志 + */ + private Integer type; + + /** + * 操作模块 + */ + private String module; + + /** + * 日志标题 + */ + private String title; + + /** + * 操作IP + */ + private String ip; + + /** + * 用户代理 + */ + private String userAgent; + + /** + * 请求URI + */ + private String requestUri; + + /** + * 操作方式 + */ + private String method; + + /** + * 操作提交的数据 + */ + private String params; + + /** + * tLog中的traceId + */ + private String traceId; + + /** + * tLog中的spanId + */ + private String spanId; + + /** + * 执行时间 + */ + private Long time; + + /** + * 异常信息 + */ + private String exception; + + /** + * 创建人 + */ + @TableField(fill = FieldFill.INSERT) + private Long createUid; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 是否删除 + */ + @TableLogic + private Boolean isDelete; + +} diff --git a/common/common-core/src/main/java/com/yxx/common/core/page/BasePageRequest.java b/common/common-core/src/main/java/com/yxx/common/core/page/BasePageRequest.java new file mode 100644 index 0000000..28d8ddf --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/core/page/BasePageRequest.java @@ -0,0 +1,26 @@ +package com.yxx.common.core.page; + +import lombok.Data; + +import jakarta.validation.constraints.Min; + +/** + * 分页参数基本信息 + * + * @author yxx + * @since 2022-02-07 21:40 + */ +@Data +public class BasePageRequest { + /** + * 页面大小 + */ + @Min(value = 1, message = "页面大小不合法") + private Integer pageSize = 10; + + /** + * 页码 + */ + @Min(value = 1, message = "页码不合法") + private Integer page = 1; +} diff --git a/common/common-core/src/main/java/com/yxx/common/core/response/BaseResponse.java b/common/common-core/src/main/java/com/yxx/common/core/response/BaseResponse.java new file mode 100644 index 0000000..11846c1 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/core/response/BaseResponse.java @@ -0,0 +1,121 @@ +package com.yxx.common.core.response; + +import com.yxx.common.enums.ApiCode; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author yxx + * @description: 返回结果实体类 + */ +@Data +public class BaseResponse implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 返回码 + */ + private Integer code; + + /** + * 返回消息 + */ + private String message; + + /** + * 返回数据 + */ + private Object data; + + private String traceId; + + private BaseResponse() { + + } + + public BaseResponse(ApiCode apiCode, Object data) { + this.code = apiCode.code(); + this.message = apiCode.message(); + this.data = data; + } + + private void setResultCode(ApiCode apiCode) { + this.code = apiCode.code(); + this.message = apiCode.message(); + } + + /** + * 返回数据 + * + * @return 返回成功 + */ + public static BaseResponse success() { + BaseResponse result = new BaseResponse(); + result.setResultCode(ApiCode.SUCCESS); + return result; + } + + /** + * 返回成功 + * + * @param data 自定义返回结果 + * @return 返回成功 + */ + public static BaseResponse success(Object data, String traceId) { + BaseResponse result = new BaseResponse(); + result.setResultCode(ApiCode.SUCCESS); + result.setData(data); + result.setTraceId(traceId); + return result; + } + + public static BaseResponse success(Object data) { + BaseResponse result = new BaseResponse(); + result.setResultCode(ApiCode.SUCCESS); + result.setData(data); + return result; + } + + /** + * 返回失败 + * + * @param code 状态码 + * @param message 描述信息 + * @return 返回失败结果 + */ + public static BaseResponse fail(Integer code, String message, String traceId) { + BaseResponse result = new BaseResponse(); + result.setCode(code); + result.setMessage(message); + result.setTraceId(traceId); + return result; + } + + public static BaseResponse fail(Integer code, String message) { + BaseResponse result = new BaseResponse(); + result.setCode(code); + result.setMessage(message); + return result; + } + + /** + * 返回失败 + * + * @param apiCode 状态枚举类 + * @return 返回失败结果 + */ + public static BaseResponse fail(ApiCode apiCode, String traceId) { + BaseResponse result = new BaseResponse(); + result.setResultCode(apiCode); + result.setTraceId(traceId); + return result; + } + + public static BaseResponse fail(ApiCode apiCode) { + BaseResponse result = new BaseResponse(); + result.setResultCode(apiCode); + return result; + } +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/core/response/ErrorResponse.java b/common/common-core/src/main/java/com/yxx/common/core/response/ErrorResponse.java new file mode 100644 index 0000000..ff8a7f6 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/core/response/ErrorResponse.java @@ -0,0 +1,56 @@ +package com.yxx.common.core.response; + +import com.yxx.common.enums.ApiCode; +import lombok.Data; + +/** + * 异常结果包装类 + * + * @author yxx + */ +@Data +public class ErrorResponse { + + private Integer code; + + private String message; + + private String exception; + + /** + * @param apiCode 状态枚举类 + * @param e 异常 + * @param message 异常描述信息 + * @return 自定义异常返回 + */ + public static ErrorResponse fail(ApiCode apiCode, Throwable e, String message) { + ErrorResponse errorResult = ErrorResponse.fail(apiCode, e); + errorResult.setMessage(message); + return errorResult; + } + + /** + * @param apiCode 状态枚举类 + * @param e 异常 + * @return 自定义异常返回 + */ + public static ErrorResponse fail(ApiCode apiCode, Throwable e) { + ErrorResponse errorResult = new ErrorResponse(); + errorResult.setCode(apiCode.code()); + errorResult.setMessage(apiCode.message()); + errorResult.setException(e.getClass().getName()); + return errorResult; + } + + /** + * @param code 异常状态码 + * @param message 异常描述信息 + * @return 自定义异常返回 + */ + public static ErrorResponse fail(Integer code, String message) { + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setCode(code); + errorResponse.setMessage(message); + return errorResponse; + } +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/enums/ApiCode.java b/common/common-core/src/main/java/com/yxx/common/enums/ApiCode.java new file mode 100644 index 0000000..7c01bae --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/enums/ApiCode.java @@ -0,0 +1,103 @@ +package com.yxx.common.enums; + +import lombok.Getter; + +/** + * @author yxx + * @description: 状态枚举类 + */ +@Getter +public enum ApiCode { + /** + * 成功状态码 + */ + SUCCESS(200, "成功"), + + TOKEN_ERROR(401, "token不存在或失效"), + + /** + * 参数错误 + */ + PARAM_IS_INVALID(1001, "参数无效"), + + PARAM_IS_BLANK(1002, "参数为空"), + + PARAM_ERROR(1003, "参数错误"), + + PARAM_TYPE_BIND_ERROR(1004, "参数类型错误"), + + PARAM_NOT_COMPLETE(1005, "参数缺失"), + + /** + * 用户错误 2001-2999 + */ + USER_NOT_LOGIN(2001, "用户未登录"), + + USER_NOT_EXIST(2002, "账号不存在"), + + USER_EXIST(2003, "账号或手机号已存在"), + + PASSWORD_ERROR(2004, "密码错误"), + + USER_PERMISSION_ERROR(2005, "无权查看他人信息"), + + USER_NOT_ROLE(2006, "用户无角色"), + + CAPTCHA_ERROR(2007, "验证码错误"), + ORIGINAL_PASSWORD_ERROR(2008, "原密码错误"), + REGISTER_MAX(2009, "已经达到今日最大注册次数,明日再试吧"), + + /** + * 找回密码 + */ + RESET_PWD_MAX(3000, "已经达到今日最大找回密码次数,明日再试吧"), + RESET_PWD_TOKEN_ERROR(3001, "链接已失效,请重新找回密码"), + + + /** + * 邮件错误 + */ + MAIL_ERROR(8000, "邮件发送失败"), + + MAIL_EXIST(8001, "邮件已成功发送过,请前往邮箱查看,如收件箱不存在,请前往垃圾邮箱查看"), + + CAPTCHA_NOT_EXIST(8002, "验证码已过期或未发送,请重新发送"), + + EMAIL_NOT_EXIST(8003, "邮箱不存在"), + EMAIL_NOT_REGISTER(8004, "邮箱尚未注册,先去注册吧~"), + EMAIL_EXIST(8005, "该邮箱已经注册过"), + + + /** + * 其它 + */ + DATE_ERROR(9000, "开始时间存在多个"), + + ENCRYPTION_ERROR(90001, "加密异常"), + + DECODE_ERROR(90002, "解密异常"), + + KEY_ERROR(90003, "密钥异常"), + + KEY_LENGTH_ERROR(90004, "加密失败,key不能小于8位"), + + + SYSTEM_ERROR(10000, "系统异常,请稍后重试"), + ; + + private final Integer code; + private final String message; + + ApiCode(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer code() { + return this.code; + } + + public String message() { + return this.message; + } +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/enums/LogTypeEnum.java b/common/common-core/src/main/java/com/yxx/common/enums/LogTypeEnum.java new file mode 100644 index 0000000..95c0802 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/enums/LogTypeEnum.java @@ -0,0 +1,32 @@ +package com.yxx.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author yxx + * @since 2022/7/26 11:20 + */ +@Getter +@AllArgsConstructor +public enum LogTypeEnum { + /** + * 正常日志类型 + */ + NORMAL(1, "正常日志"), + + /** + * 错误日志类型 + */ + ERROR(2, "错误日志"); + + /** + * 类型 + */ + private final Integer code; + + /** + * 描述 + */ + private final String message; +} diff --git a/common/common-core/src/main/java/com/yxx/common/enums/business/DelFlagEnum.java b/common/common-core/src/main/java/com/yxx/common/enums/business/DelFlagEnum.java new file mode 100644 index 0000000..03eb494 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/enums/business/DelFlagEnum.java @@ -0,0 +1,26 @@ +package com.yxx.common.enums.business; + +import lombok.Getter; + +/** + * @author yxx + * @since 2022-05-07 11:36 + */ +@Getter +public enum DelFlagEnum { + /** + * 是否删除 + */ + YES(1, "删除"), + NO(0, "未删除"); + + private final Integer code; + + private final String message; + + DelFlagEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/common/common-core/src/main/java/com/yxx/common/enums/business/RoleEnum.java b/common/common-core/src/main/java/com/yxx/common/enums/business/RoleEnum.java new file mode 100644 index 0000000..1119135 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/enums/business/RoleEnum.java @@ -0,0 +1,28 @@ +package com.yxx.common.enums.business; + +import lombok.Getter; + +/** + * @program: baseProject + * @description: 角色枚举 + * @author: yxx + * @date: 2022-02-16 22:11 + */ +@Getter +public enum RoleEnum { + /** + * 角色 + */ + USER("user", "用户"), + ADMIN("admin", "管理员"), + SUPER_ADMIN("super-admin", "超级管理员"), + ; + + private final String code; + private final String message; + + RoleEnum(String code, String message){ + this.code = code; + this.message = message; + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/exceptions/ApiException.java b/common/common-core/src/main/java/com/yxx/common/exceptions/ApiException.java new file mode 100644 index 0000000..dbe9e13 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/exceptions/ApiException.java @@ -0,0 +1,61 @@ +package com.yxx.common.exceptions; + +import com.yxx.common.enums.ApiCode; +import lombok.Data; + +/** + * @author yxx + * @description: 自定义异常类 + */ +@Data +public class ApiException extends RuntimeException { + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误信息 + */ + private String message; + + public ApiException() { + super(); + } + + public ApiException(ApiCode resultCode) { + super(resultCode.message()); + this.code = resultCode.code(); + this.message = resultCode.message(); + } + + public ApiException(ApiCode resultCode, Throwable cause) { + super(resultCode.message(), cause); + this.code = resultCode.code(); + this.message = resultCode.message(); + } + + public ApiException(String message) { + super(message); + this.code = -1; + this.message = message; + } + + public ApiException(Integer code, String message) { + super(message); + this.code = code; + this.message = message; + } + + public ApiException(Integer code, String message, Throwable cause) { + super(message, cause); + this.code = code; + this.message = message; + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/exceptions/BaseException.java b/common/common-core/src/main/java/com/yxx/common/exceptions/BaseException.java new file mode 100644 index 0000000..3bc0fb2 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/exceptions/BaseException.java @@ -0,0 +1,38 @@ +package com.yxx.common.exceptions; + +import com.yxx.common.enums.ApiCode; +import lombok.Getter; + +import java.io.Serializable; + +/** + * 基础异常 + * + * @author yxx + * @since 2022/7/13 18:19 + */ +@Getter +public class BaseException extends RuntimeException implements Serializable { + private static final long serialVersionUID = 2588129946203384980L; + + private Integer code; + + public BaseException(Integer code) { + super(); + this.code = code; + } + + public BaseException(Integer code, String message) { + super(message); + this.code = code; + } + + public BaseException(ApiCode apiCode) { + super(apiCode.getMessage()); + this.code = apiCode.getCode(); + } + + public BaseException(Throwable cause) { + super(cause); + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/properties/IpProperties.java b/common/common-core/src/main/java/com/yxx/common/properties/IpProperties.java new file mode 100644 index 0000000..7f04e16 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/properties/IpProperties.java @@ -0,0 +1,22 @@ +package com.yxx.common.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * ip + * + * @author yxx + * @classname IpProperties + * @since 2023-08-05 23:13 + */ +@Component +@Data +@ConfigurationProperties(prefix = "ip") +public class IpProperties { + /** + * 是否校验 + */ + private Boolean check; +} diff --git a/common/common-core/src/main/java/com/yxx/common/properties/MailProperties.java b/common/common-core/src/main/java/com/yxx/common/properties/MailProperties.java new file mode 100644 index 0000000..f6c7e9b --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/properties/MailProperties.java @@ -0,0 +1,44 @@ +package com.yxx.common.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author yxx + * @since 2022-11-12 02:11 + */ +@Component +@Data +@ConfigurationProperties(prefix = "mail") +public class MailProperties { + /** + * 发件人邮箱 + */ + private String from; + + /** + * 发件人昵称 + */ + private String fromName; + + /** + * 注册验证码过期时间 + */ + private Integer registerTime; + + /** + * 一个邮箱每日最大注册次数 (防止恶意发送邮件) + */ + private Integer registerMax; + + /** + * 注册邮件正文 + */ + private String registerContent; + + /** + * ip异常邮件正文 + */ + private String ipUnusualContent; +} diff --git a/common/common-core/src/main/java/com/yxx/common/properties/MyWebProperties.java b/common/common-core/src/main/java/com/yxx/common/properties/MyWebProperties.java new file mode 100644 index 0000000..d9175fa --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/properties/MyWebProperties.java @@ -0,0 +1,22 @@ +package com.yxx.common.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 网站 + * + * @author yxx + * @classname WebProperties + * @since 2023-08-06 02:24 + */ +@Component +@Data +@ConfigurationProperties(prefix = "web") +public class MyWebProperties { + /** + * 域名 + */ + private String domain; +} diff --git a/common/common-core/src/main/java/com/yxx/common/properties/ResetPwdProperties.java b/common/common-core/src/main/java/com/yxx/common/properties/ResetPwdProperties.java new file mode 100644 index 0000000..d8035ef --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/properties/ResetPwdProperties.java @@ -0,0 +1,38 @@ +package com.yxx.common.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 找回密码配置文件 + * + * @author yxx + * @classname ResetPwdProperties + * @since 2023-07-26 10:56 + */ +@Component +@Data +@ConfigurationProperties(prefix = "reset-password") +public class ResetPwdProperties { + + /** + * # 找回密码页面url + */ + private String basePath; + + /** + * 每日找回密码最大次数 (防止恶意发送邮件) + */ + private Integer maxNumber; + + /** + * 找回密码邮件正文 + */ + private String resetPwdContent; + + /** + * 找回密码链接有效期 + */ + private Integer resetPwdTime; +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/ApiAssert.java b/common/common-core/src/main/java/com/yxx/common/utils/ApiAssert.java new file mode 100644 index 0000000..6927e1b --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/ApiAssert.java @@ -0,0 +1,297 @@ +package com.yxx.common.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ObjectUtil; +import com.yxx.common.exceptions.ApiException; +import com.yxx.common.enums.ApiCode; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; + +/** + * API断言 + * + * @author yxx + * @since 2022/4/13 14:37 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ApiAssert { + + /** + * 两个对象必须相等 + * + * @param apiCode ApiCode + * @param obj1 对象1 + * @param obj2 对象2 + */ + public static void equals(ApiCode apiCode, Object obj1, Object obj2) { + equals(apiCode, obj1, obj2, null); + } + + /** + * 条件必须为true + * + * @param apiCode ApiCode + * @param condition 判断条件 + */ + public static void isTrue(ApiCode apiCode, boolean condition) { + isTrue(apiCode, condition, null); + } + + /** + * 条件必须为false + * + * @param apiCode ApiCode + * @param condition 判断条件 + */ + public static void isFalse(ApiCode apiCode, boolean condition) { + isFalse(apiCode, condition, null); + } + + /** + * 对象必须为null + * + * @param apiCode ApiCode + * @param obj 校验对象 + */ + public static void isNull(ApiCode apiCode, Object obj) { + isNull(apiCode, obj, null); + } + + /** + * 对象不能为null + * + * @param apiCode ApiCode + * @param obj 校验对象 + */ + public static void isNotNull(ApiCode apiCode, Object obj) { + isNotNull(apiCode, obj, null); + } + + /** + * 数组不能为空 + * + * @param apiCode ApiCode + * @param array 数组 + */ + public static void notEmpty(ApiCode apiCode, Object[] array) { + notEmpty(apiCode, array, null); + } + + /** + * 数组不能包含空元素 + * + * @param apiCode ApiCode + * @param array 数组 + */ + public static void noNullElements(ApiCode apiCode, Object[] array) { + noNullElements(apiCode, array, null); + } + + /** + * 集合不能为空 + * + * @param apiCode ApiCode + * @param collection 集合对象 + */ + public static void notEmpty(ApiCode apiCode, Collection collection) { + notEmpty(apiCode, collection, null); + } + + /** + * 集合必须为空 + * + * @param apiCode ApiCode + * @param collection 集合对象 + */ + public static void isEmpty(ApiCode apiCode, Collection collection) { + isEmpty(apiCode, collection, null); + } + + /** + * Map不能为空 + * + * @param apiCode ApiCode + * @param map MAP对象 + */ + public static void notEmpty(ApiCode apiCode, Map map) { + notEmpty(apiCode, map, null); + } + + /** + * Map必须为空 + * + * @param apiCode ApiCode + * @param map Map对象 + */ + public static void isEmpty(ApiCode apiCode, Map map) { + isEmpty(apiCode, map, null); + } + + /** + * 两个对象必须相等 + * + * @param apiCode ApiCode + * @param obj1 对象1 + * @param obj2 对象2 + * @param errorMsg 错误描述 + */ + public static void equals(ApiCode apiCode, Object obj1, Object obj2, String errorMsg) { + if (!Objects.equals(obj1, obj2)) { + failure(apiCode, errorMsg); + } + } + + /** + * 条件必须为true + * + * @param apiCode ApiCode + * @param condition 判断条件 + * @param errorMsg 错误描述 + */ + public static void isTrue(ApiCode apiCode, boolean condition, String errorMsg) { + if (!condition) { + failure(apiCode, errorMsg); + } + } + + /** + * 条件必须为false + * + * @param apiCode ApiCode + * @param condition 判断条件 + * @param errorMsg 错误描述 + */ + public static void isFalse(ApiCode apiCode, boolean condition, String errorMsg) { + if (condition) { + failure(apiCode, errorMsg); + } + } + + /** + * 对象必须为null + * + * @param apiCode ApiCode + * @param obj 校验对象 + * @param errorMsg 错误描述 + */ + public static void isNull(ApiCode apiCode, Object obj, String errorMsg) { + if (ObjectUtil.isNotNull(obj)) { + failure(apiCode, errorMsg); + } + } + + /** + * 对象不能为null + * + * @param apiCode ApiCode + * @param obj 校验对象 + * @param errorMsg 错误描述 + */ + public static void isNotNull(ApiCode apiCode, Object obj, String errorMsg) { + if (ObjectUtil.isNull(obj)) { + failure(apiCode, errorMsg); + } + } + + /** + * 数组不能为空 + * + * @param apiCode ApiCode + * @param array 数组 + * @param errorMsg 错误描述 + */ + public static void notEmpty(ApiCode apiCode, Object[] array, String errorMsg) { + if (ObjectUtil.isEmpty(array)) { + failure(apiCode, errorMsg); + } + } + + /** + * 数组不能包含空元素 + * + * @param apiCode ApiCode + * @param array 数组 + * @param errorMsg 错误描述 + */ + public static void noNullElements(ApiCode apiCode, Object[] array, String errorMsg) { + if (array != null) { + for (Object element : array) { + if (element == null) { + failure(apiCode, errorMsg); + } + } + } + } + + /** + * 集合不能为空 + * + * @param apiCode ApiCode + * @param collection 集合对象 + * @param errorMsg 错误描述 + */ + public static void notEmpty(ApiCode apiCode, Collection collection, String errorMsg) { + if (CollUtil.isEmpty(collection)) { + failure(apiCode, errorMsg); + } + } + + /** + * 集合必须为空 + * + * @param apiCode ApiCode + * @param collection 集合对象 + * @param errorMsg 错误描述 + */ + public static void isEmpty(ApiCode apiCode, Collection collection, String errorMsg) { + if (CollUtil.isNotEmpty(collection)) { + failure(apiCode, errorMsg); + } + } + + /** + * Map不能为空 + * + * @param apiCode ApiCode + * @param map MAP对象 + * @param errorMsg 错误描述 + */ + public static void notEmpty(ApiCode apiCode, Map map, String errorMsg) { + if (MapUtil.isEmpty(map)) { + failure(apiCode, errorMsg); + } + } + + /** + * Map必须为空 + * + * @param apiCode ApiCode + * @param map Map对象 + * @param errorMsg 错误描述 + */ + public static void isEmpty(ApiCode apiCode, Map map, String errorMsg) { + if (MapUtil.isNotEmpty(map)) { + failure(apiCode, errorMsg); + } + } + + + /** + * 失败结果 + * + * @param apiCode ApiCode + * @param errorMsg 错误描述 如果有值则覆盖原错误提示 + */ + public static void failure(ApiCode apiCode, String errorMsg) { + if (CharSequenceUtil.isBlank(errorMsg)) { + throw new ApiException(apiCode); + } + throw new ApiException(apiCode.getCode(), errorMsg); + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/ApplicationUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/ApplicationUtils.java new file mode 100644 index 0000000..475918f --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/ApplicationUtils.java @@ -0,0 +1,148 @@ +package com.yxx.common.utils; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.annotation.Lazy; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Objects; + +/** + * Spring Application 工具类 + * + * @author yxx + * @since 2021/12/29 10:43 + */ +@Slf4j +@Lazy(false) +@Component +public class ApplicationUtils implements ApplicationContextAware, DisposableBean { + + /** + * 全局的ApplicationContext + */ + private static ApplicationContext applicationContext = null; + + /** + * 获取ApplicationContext + */ + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + /** + * 获取springbean + */ + public static T getBean(String beanName, Class requiredType) { + if (containsBean(beanName)) { + return applicationContext.getBean(beanName, requiredType); + } + return null; + } + + /** + * 获取springbean + */ + public static T getBean(Class requiredType) { + return applicationContext.getBean(requiredType); + } + + /** + * 获取springbean + */ + public static T getBean(String beanName) { + if (containsBean(beanName)) { + Class type = getType(beanName); + return applicationContext.getBean(beanName, type); + } + return null; + } + + /** + * 依赖spring框架获取HttpServletRequest + * + * @return HttpServletRequest + */ + public static HttpServletRequest getRequest() { + HttpServletRequest request = null; + try { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (Objects.nonNull(requestAttributes)) { + request = requestAttributes.getRequest(); + } + } catch (Exception ignored) { + } + return request; + } + + public static HttpServletResponse getResponse() { + HttpServletResponse response = null; + try { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (Objects.nonNull(requestAttributes)) { + response = requestAttributes.getResponse(); + } + } catch (Exception ignored) { + } + return response; + } + + /** + * 发布事件 + */ + public static void publishEvent(ApplicationEvent event) { + if (applicationContext == null) { + return; + } + applicationContext.publishEvent(event); + } + + /** + * ApplicationContext是否包含该Bean + */ + public static boolean containsBean(String name) { + return applicationContext.containsBean(name); + } + + /** + * ApplicationContext该Bean是否为单例 + */ + public static boolean isSingleton(String name) { + return applicationContext.isSingleton(name); + } + + /** + * 获取该Bean的Class + */ + @SuppressWarnings("unchecked") + public static Class getType(String name) { + return (Class) applicationContext.getType(name); + } + + @Override + public void destroy() { + ApplicationUtils.clearHolder(); + } + + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { + ApplicationUtils.applicationContext = applicationContext; + } + + /** + * 清除ApplicationUtils中的ApplicationContext为Null. + */ + public static void clearHolder() { + log.debug("清除ApplicationUtils中的ApplicationContext:" + applicationContext); + applicationContext = null; + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/DateUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/DateUtils.java new file mode 100644 index 0000000..0726963 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/DateUtils.java @@ -0,0 +1,416 @@ +package com.yxx.common.utils; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.NumberUtil; +import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; +import com.yxx.common.enums.ApiCode; +import com.yxx.common.exceptions.ApiException; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; + +/** + * @author yxx + * @since 2022/7/18 9:40 + */ +@SuppressWarnings("unused") +public class DateUtils extends DateUtil { + + private static final String[] PARSE_PATTERNS = { + "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", + "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; + + public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); + public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + public static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + /** + * 格式化时间为 yyyy-MM-dd HH:mm:ss + * + * @param date 时间 + * @return 转换后字符串 + */ + public static String formatDateTime(Date date) { + return format(date, DATETIME_FORMAT); + } + + /** + * 格式化时间为 yyyy-MM + * 获取当前年月 + * + * @return 转换后字符串 + */ + public static String getDateTime(LocalDateTime localDate) { + //设置日期格式 + return localDate.format(DateTimeFormatter.ofPattern("yyyy-MM")); + } + + + /** + * 格式化时间为 yyyy-MM-dd HH:mm:ss + * + * @param dateTime 时间 + * @return 转换后字符串 + */ + public static String formatDateTime(LocalDateTime dateTime) { + return format(dateTime, DATETIME_FORMAT); + } + + /** + * Date类型转字符串 + * + * @param date 日期 + * @return yyyy-MM-dd HH:mm:ss + */ + public static String date2Str(Date date) { + return date2Str(null, date); + } + + /** + * Date类型转字符串 + * + * @param format 日期格式 + * @param date 日期 + * @return 转换后的字符串 + */ + public static String date2Str(String format, Date date) { + if (ObjectUtils.isNull(date)) { + return null; + } + SimpleDateFormat dateFormat = CharSequenceUtil.isBlank(format) ? + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") : + new SimpleDateFormat(format); + return dateFormat.format(date); + } + + /** + * 字符串转date类型 + * + * @param dateStr 日期字符串 + * @return Date + */ + public static Date parseDate(Object dateStr) { + if (ObjectUtils.isNull(dateStr)) { + return null; + } + return parse(dateStr.toString(), PARSE_PATTERNS); + } + + /** + * 字符串转LocalTime类型 + * + * @param dateStr 日期字符串 + * @return LocalTime + */ + public static LocalTime parseLocalTime(Object dateStr) { + if (ObjectUtils.isNull(dateStr)) { + return null; + } + if (dateStr instanceof Double) { + return date2LocalTime(parseDate(dateStr)); + } + return LocalTime.parse(dateStr.toString(), TIME_FORMATTER); + } + + /** + * Date转LocalTime + * + * @param date 日期 + * @return LocalTime + */ + public static LocalTime date2LocalTime(Date date) { + if (null == date) { + return null; + } + return date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime(); + } + + /** + * LocalTime转Str + * + * @param localTime 时间 + * @return HH:mm:ss + */ + public static String localTime2Str(LocalTime localTime) { + return localTime2Str(null, localTime); + } + + /** + * LocalTime转str + * + * @param format 格式 + * @param localTime 时间 + * @return 转换后的时间字符串 + */ + public static String localTime2Str(String format, LocalTime localTime) { + if (null == localTime) { + return null; + } + DateTimeFormatter formatter = CharSequenceUtil.isBlank(format) ? + TIME_FORMATTER : DateTimeFormatter.ofPattern(format); + return localTime.format(formatter); + } + + /** + * 字符串转LocalDate类型 + * + * @param dateStr 日期字符串 + * @return LocalDate + */ + public static LocalDate parseLocalDate(Object dateStr) { + if (ObjectUtils.isNull(dateStr)) { + return null; + } + if (dateStr instanceof Double) { + return date2LocalDate(parseDate(dateStr)); + } + return LocalDate.parse(dateStr.toString(), DATE_FORMATTER); + } + + /** + * Date转LocalDate + * + * @param date 日期 + * @return LocalDate + */ + public static LocalDate date2LocalDate(Date date) { + if (null == date) { + return null; + } + return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + + /** + * LocalDate转Str + * + * @param localDate 日期 + * @return 转换后的字符串 + */ + public static String localDate2Str(LocalDate localDate) { + return localDate2Str(null, localDate); + } + + /** + * LocalDate转Str + * + * @param format 格式 + * @param localDate 日期 + * @return 转换后的字符串 + */ + public static String localDate2Str(String format, LocalDate localDate) { + if (null == localDate) { + return null; + } + DateTimeFormatter formatter = CharSequenceUtil.isBlank(format) ? + DATE_FORMATTER : DateTimeFormatter.ofPattern(format); + return localDate.format(formatter); + } + + /** + * 字符串转LocalDateTime类型 + * + * @param dateStr 日期字符串 + * @return LocalDateTime + */ + public static LocalDateTime parseLocalDateTime(Object dateStr) { + if (ObjectUtils.isNull(dateStr)) { + return null; + } + if (dateStr instanceof Double) { + return date2LocalDateTime(parseDate(dateStr)); + } + return LocalDateTime.parse(dateStr.toString(), DATETIME_FORMATTER); + } + + /** + * Date转LocalDateTime + * + * @param date 日期 + * @return LocalDateTime + */ + public static LocalDateTime date2LocalDateTime(Date date) { + if (null == date) { + return null; + } + return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + } + + /** + * LocalDate转Str + * + * @param localDateTime 时间 + * @return 转换后的日期字符串 + */ + public static String localDateTime2Str(LocalDateTime localDateTime) { + return localDateTime2Str(null, localDateTime); + } + + /** + * LocalDate转Str + * + * @param format 格式 + * @param localDateTime 时间 + * @return 转换后的日期字符串 + */ + public static String localDateTime2Str(String format, LocalDateTime localDateTime) { + if (null == localDateTime) { + return null; + } + DateTimeFormatter formatter = CharSequenceUtil.isBlank(format) ? + DATETIME_FORMATTER : DateTimeFormatter.ofPattern(format); + return localDateTime.format(formatter); + } + + public static LocalDate convertLocalDate(String source) { + source = source.trim(); + if ("".equals(source)) { + return null; + } + if (source.matches("^\\d{4}-\\d{1,2}$")) { + // yyyy-MM + return LocalDate.parse(source + "-01", DATE_FORMATTER); + } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) { + // yyyy-MM-dd + return LocalDate.parse(source, DATE_FORMATTER); + } else if (NumberUtil.isLong(source)) { + if (source.length() == 10 || source.length() == 13) { + source = source.length() == 10 ? source + "000" : source; + return Instant.ofEpochMilli(Long.parseLong(source)).atZone(ZoneId.systemDefault()).toLocalDate(); + } + return null; + } else { + throw new IllegalArgumentException("Invalid datetime value '" + source + "'"); + } + } + + public static LocalDateTime convertLocalDateTime(String source) { + source = source.trim(); + if ("".equals(source)) { + return null; + } + if (source.matches("^\\d{4}-\\d{1,2}$")) { + // yyyy-MM + return LocalDateTime.parse(source + "-01 00:00:00", DATETIME_FORMATTER); + } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) { + // yyyy-MM-dd + return LocalDateTime.parse(source + " 00:00:00", DATETIME_FORMATTER); + } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}$")) { + // yyyy-MM-dd HH:mm + return LocalDateTime.parse(source + ":00", DATETIME_FORMATTER); + } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}:\\d{1,2}$")) { + // yyyy-MM-dd HH:mm:ss + return LocalDateTime.parse(source, DATETIME_FORMATTER); + } else if (NumberUtil.isLong(source)) { + if (source.length() == 10 || source.length() == 13) { + source = source.length() == 10 ? source + "000" : source; + return LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(source)), ZoneId.systemDefault()); + } + return null; + } else { + throw new IllegalArgumentException("Invalid datetime value '" + source + "'"); + } + } + + /** + * 传入 字符串格式 日期,获得周几 + * + * @param date 字符串类型日期 + * @return 周几: 1-周一;2-周二;3-周三;4-周四;5-周五;6-周六;7-周日 + */ + public static Integer whichDayToWeekByStr(String date) { + try { + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Date enteringDate = sdf.parse(date); + + // 创建Calendar类实例 + Calendar instance = Calendar.getInstance(); + // 获取传入的日期是周几 + instance.setTime(enteringDate); + int i = instance.get(Calendar.DAY_OF_WEEK); + // 获取周日 + int sunday = Calendar.SUNDAY; + // 如果是周日 特殊处理 + if (i == sunday) { + return 7; + } else { + return i - 1; + } + } catch (ParseException e) { + throw new ApiException(ApiCode.DATE_ERROR); + } + } + + /** + * 传入 LocalDateTime 格式日期 获得周几 + * + * @param enteringDate LocalDateTime格式日期 + * @return 周几: 1-周一;2-周二;3-周三;4-周四;5-周五;6-周六;7-周日 + */ + public static Integer whichDayToWeekByLDT(LocalDate enteringDate) { + ZoneId zoneId = ZoneId.systemDefault(); + ZonedDateTime zdt = enteringDate.atStartOfDay().atZone(zoneId); + Date date = Date.from(zdt.toInstant()); + + // 创建Calendar类实例 + Calendar instance = Calendar.getInstance(); + // 获取传入的日期是周几 + instance.setTime(date); + int i = instance.get(Calendar.DAY_OF_WEEK); + // 获取周日 + int sunday = Calendar.SUNDAY; + // 如果是周日 特殊处理 + if (i == sunday) { + return 7; + } else { + return i - 1; + } + } + + /** + * 传入 LocalDateTime 格式日期 和 n 天 获取n天后的日期 + * + * @param enteringDate 开始时间 LocalDateTime格式 + * @param day n为正数 为后n天; n为负数 为前n天 + * @return n天后的日期 LocalDateTime 格式 + */ + public static LocalDate getBeforeNumDay(LocalDate enteringDate, int day) { + ZoneId zoneId = ZoneId.systemDefault(); + ZonedDateTime zdt = enteringDate.atStartOfDay().atZone(zoneId); + Date date = Date.from(zdt.toInstant()); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.DATE, day); + Date time = calendar.getTime(); + + Instant instant = time.toInstant(); + return instant.atZone(zoneId).toLocalDate(); + } + + /** + * 今天剩余时间 单位(秒) + * + * @return {@link Long } + * @author yxx + */ + public static Long theRestOfTheDaySecond(){ + // 获取当前日期和时间 + LocalDateTime now = LocalDateTime.now(); + + // 获取今晚的十二点整时间 + LocalDateTime midnight = now.toLocalDate().atTime(LocalTime.MAX); + + // 计算当前时间距离今晚十二点整的秒数 + Duration duration = Duration.between(now, midnight); + return duration.getSeconds(); + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/RepeatedlyRequestWrapper.java b/common/common-core/src/main/java/com/yxx/common/utils/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..d9abfb3 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/RepeatedlyRequestWrapper.java @@ -0,0 +1,68 @@ +package com.yxx.common.utils; + +import cn.hutool.core.io.IoUtil; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * 构建可重复读取inputStream的request + * + * @author yxx + * @since 2022/4/13 17:29 + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { + super(request); + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); + + body = IoUtil.readUtf8(request.getInputStream()).getBytes(StandardCharsets.UTF_8); + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() { + @Override + public int read() { + return bais.read(); + } + + @Override + public int available() { + return body.length; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + }; + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/RequestWrapper.java b/common/common-core/src/main/java/com/yxx/common/utils/RequestWrapper.java new file mode 100644 index 0000000..acb8c38 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/RequestWrapper.java @@ -0,0 +1,131 @@ +package com.yxx.common.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import java.io.*; +import java.nio.charset.Charset; + +/** + * 包装HttpServletRequest,目的是让其输入流可重复读 + * + * @author jing.li + * @since 1.2.5 + */ +public class RequestWrapper extends HttpServletRequestWrapper { + + private static final Logger log = LoggerFactory.getLogger(RequestWrapper.class); + + /** + * 存储body数据的容器 + */ + private final byte[] body; + + /** + * Constructs a request object wrapping the given request. + * + * @param request The request to wrap + * @throws IllegalArgumentException if the request is null + */ + public RequestWrapper(HttpServletRequest request) { + super(request); + String bodyStr = getBodyString(request); + body = bodyStr.getBytes(Charset.defaultCharset()); + } + + + /** + * 获取请求Body + * + * @param request request + * @return String + */ + public String getBodyString(final ServletRequest request) { + try { + return inputStream2String(request.getInputStream()); + } catch (IOException e) { + log.error("", e); + throw new RuntimeException(e); + } + } + + /** + * 获取请求Body + * + * @return String + */ + public String getBodyString() { + final InputStream inputStream = new ByteArrayInputStream(body); + + return inputStream2String(inputStream); + } + + /** + * 将inputStream里的数据读取出来并转换成字符串 + * + * @param inputStream inputStream + * @return String + */ + private String inputStream2String(InputStream inputStream) { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + + try { + reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + log.error("", e); + throw new RuntimeException(e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + log.error("", e); + } + } + } + + return sb.toString(); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + + final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); + + return new ServletInputStream() { + @Override + public int read() throws IOException { + return inputStream.read(); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + }; + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/ServletUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/ServletUtils.java new file mode 100644 index 0000000..44afe7c --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/ServletUtils.java @@ -0,0 +1,70 @@ +package com.yxx.common.utils; + +import cn.hutool.core.map.MapUtil; +import com.yxx.common.utils.jackson.JacksonUtil; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Arrays; +import java.util.Map; + +/** + * Spring Application 工具类 + * + * @author yxx + * @since 2022/4/13 11:45 + */ +public class ServletUtils { + + public static ServletRequestAttributes getRequestAttributes() { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() { + return getRequestAttributes().getRequest(); + } + + /** + * 获取请求参数 + * + * @param request HttpServletRequest + * @return 请求参数 + */ + public static String getRequestParms(HttpServletRequest request) { + String params = ""; + if (isJsonRequest(request)) { + params = new RequestWrapper(request).getBodyString(); + } else { + Map parameterMap = request.getParameterMap(); + if (MapUtil.isNotEmpty(parameterMap)) { + params = JacksonUtil.toJson(parameterMap); + } + } + return params; + } + + /** + * 判断本次请求的数据类型是否为json + * + * @param request request + * @return boolean + */ + public static boolean isJsonRequest(HttpServletRequest request) { + String contentType = request.getContentType(); + if (contentType != null) { + return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE) + && request instanceof RepeatedlyRequestWrapper + && Arrays.asList(HttpMethod.POST.name(), HttpMethod.PUT.name()).contains(request.getMethod()); + } + return false; + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/SnowflakeConfig.java b/common/common-core/src/main/java/com/yxx/common/utils/SnowflakeConfig.java new file mode 100644 index 0000000..bab13f7 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/SnowflakeConfig.java @@ -0,0 +1,44 @@ +package com.yxx.common.utils; + +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.IdUtil; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import jakarta.annotation.PostConstruct; + +/** + * @author yxx + */ +@Component +@Slf4j +public class SnowflakeConfig { + /** + * 终端ID + */ + @JsonFormat(shape = JsonFormat.Shape.STRING) + private long workerId; + + /** + * 数据中心ID + */ + private final long datacenterId = 1; + private final Snowflake snowflake = IdUtil.getSnowflake(workerId, datacenterId); + + @PostConstruct + public void init() { + workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr()); + log.info("当前机器的workId:{}", workerId); + } + + public synchronized long snowflakeId() { + return snowflake.nextId(); + } + + public synchronized long snowflakeId(long workerId, long datacenterId) { + Snowflake snowflake = IdUtil.getSnowflake(workerId, datacenterId); + return snowflake.nextId(); + } +} + diff --git a/common/common-core/src/main/java/com/yxx/common/utils/TreeUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/TreeUtil.java new file mode 100644 index 0000000..23d5d8a --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/TreeUtil.java @@ -0,0 +1,140 @@ +package com.yxx.common.utils; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author yxx + * @apiNote 通用树形工具类 + * @since 2023-05-18 14:22 + */ +public class TreeUtil { + + + /*** + * 构建树 + * + * @param listData 需要构建的结果集 + * @param parentKeyFunction 父节点id + * @param keyFunction 主键 + * @param setChildrenFunction 子集 + * @param rootParentValue 父节点的值 0就填0 null就填null + * @return java.util.List + */ + public static List buildTree(List listData, Function parentKeyFunction, + Function keyFunction, BiConsumer> setChildrenFunction, U rootParentValue) { + return buildTree(listData, parentKeyFunction, keyFunction, setChildrenFunction, rootParentValue, null, null); + } + + /*** + * 构建树,并且对结果集做升序处理 + * + * @param listData 需要构建的结构集 + * @param parentKeyFunction 父节点 + * @param keyFunction 主键 + * @param setChildrenFunction 子集 + * @param rootParentValue 父节点的值 + * @param sortFunction 排序字段 + * @return java.util.List + */ + public static List buildAscTree(List listData, Function parentKeyFunction, + Function keyFunction, BiConsumer> setChildrenFunction, U rootParentValue, + Function sortFunction) { + List resultList = + buildTree(listData, parentKeyFunction, keyFunction, setChildrenFunction, rootParentValue, sortFunction, 0); + + return sortList(resultList, sortFunction, 0); + } + + /*** + * 构建树,并且对结果集做降序处理 + * + * @param listData 需要构建的结构集 + * @param parentKeyFunction 父节点 + * @param keyFunction 主键 + * @param setChildrenFunction 子集 + * @param rootParentValue 父节点的值 + * @param sortFunction 排序字段 + * @return java.util.List + */ + public static List buildDescTree(List listData, Function parentKeyFunction, + Function keyFunction, BiConsumer> setChildrenFunction, U rootParentValue, + Function sortFunction) { + List resultList = + buildTree(listData, parentKeyFunction, keyFunction, setChildrenFunction, rootParentValue, sortFunction, 1); + + return sortList(resultList, sortFunction, 1); + } + + private static List buildTree(List listData, Function parentKeyFunction, + Function keyFunction, BiConsumer> setChildrenFunction, U rootParentValue, + Function sortFunction, Integer sortedFlag) { + // 筛选出根节点 + Map rootKeyMap = new HashMap<>(); + // 所有的节点 + Map allKeyMap = new HashMap<>(); + // 存id:List + Map> keyParentKeyMap = new HashMap<>(); + + for (T t : listData) { + Comparable key = keyFunction.apply(t); + Comparable parentKey = parentKeyFunction.apply(t); + // 如果根节点标识为null,值也为null,表示为根节点 + if (rootParentValue == null && parentKeyFunction.apply(t) == null) { + rootKeyMap.put(key, t); + } + // 根节点标识有值,值相同表示为根节点 + if (rootParentValue != null && parentKeyFunction.apply(t).compareTo(rootParentValue) == 0) { + rootKeyMap.put(key, t); + } + allKeyMap.put(key, t); + if (parentKey != null) { + List children = keyParentKeyMap.getOrDefault(parentKey, new ArrayList<>()); + children.add(key); + keyParentKeyMap.put(parentKey, children); + } + } + List returnList = new ArrayList<>(); + // 封装根节点数据 + for (Comparable comparable : rootKeyMap.keySet()) { + setChildren(comparable, returnList, allKeyMap, keyParentKeyMap, setChildrenFunction, sortFunction, + sortedFlag); + } + + return returnList; + } + + private static void setChildren(Comparable comparable, List childrenList, Map childrenKeyMap, + Map> keyParentKeyMap, BiConsumer> setChildrenFunction, + Function sortFunction, Integer sortedFlag) { + T t = childrenKeyMap.get(comparable); + if (keyParentKeyMap.containsKey(comparable)) { + List subChildrenList = new ArrayList<>(); + List childrenComparable = keyParentKeyMap.get(comparable); + for (Comparable c : childrenComparable) { + setChildren(c, subChildrenList, childrenKeyMap, keyParentKeyMap, setChildrenFunction, sortFunction, + sortedFlag); + } + subChildrenList = sortList(subChildrenList, sortFunction, sortedFlag); + setChildrenFunction.accept(t, subChildrenList); + + } + childrenList.add(t); + + } + + private static List sortList(List list, Function sortFunction, + Integer sortedFlag) { + if (sortFunction != null) { + if (sortedFlag == 1) { + return (List) list.stream().sorted(Comparator.comparing(sortFunction).reversed()) + .collect(Collectors.toList()); + } else { + return (List) list.stream().sorted(Comparator.comparing(sortFunction)).collect(Collectors.toList()); + } + } + return list; + } +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/utils/agent/UserAgentUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/agent/UserAgentUtil.java new file mode 100644 index 0000000..e2ff7d4 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/agent/UserAgentUtil.java @@ -0,0 +1,51 @@ +package com.yxx.common.utils.agent; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.http.useragent.UserAgent; +import com.yxx.common.utils.jackson.JacksonUtil; +import lombok.extern.slf4j.Slf4j; + +/** + * agen工具类 + * + * @author yxx + * @classname UserAgentUtil + * @since 2023-08-01 18:27 + */ +@Slf4j +public class UserAgentUtil { + /** + * 获得代理 + * + * @param userAgent 用户代理 + * @return {@link String } + * @author yxx + */ + public static String getAgent(String userAgent) { + if (CharSequenceUtil.isBlank(userAgent)) { + return "未知"; + } + UserAgent ua = cn.hutool.http.useragent.UserAgentUtil.parse(userAgent); + String json = JacksonUtil.toJson(ua); + log.info("ua json = " + json); + String platform = ua.getPlatform().toString();//Windows + log.info("系统:{} ", platform); + String os = ua.getOs().toString();//Windows 7 + log.info("系统版本:{} ", os); + String browser = ua.getBrowser().toString();//Chrome + log.info("浏览器:{} ", browser); + String version = ua.getVersion();//14.0.835.163 + log.info("版本:{} ", version); + String engine = ua.getEngine().toString();//Webkit + log.info("浏览器引擎:{} ", engine); + String engineVersion = ua.getEngineVersion();//535.1 + log.info("浏览器引擎版本:{} ", engineVersion); + boolean mobile = ua.isMobile(); + log.info("是否是移动端:{} ", mobile); + if (CharSequenceUtil.isBlank(platform) || "Unknown".equals(platform) || "null".equals(platform)) { + return userAgent; + } else { + return platform + "-" + os + "-" + browser + "-" + version; + } + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginAdminUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginAdminUtils.java new file mode 100644 index 0000000..5b42946 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginAdminUtils.java @@ -0,0 +1,81 @@ +package com.yxx.common.utils.auth; + +import cn.hutool.core.util.ObjectUtil; +import com.yxx.common.constant.Constant; +import com.yxx.common.core.model.LoginUser; +import com.yxx.common.utils.satoken.StpAdminUtil; + +import java.util.List; + +/** + * 登录鉴权工具 + * + * @author yxx + * @since 2022/4/13 14:22 + */ +public class LoginAdminUtils { + + /** + * 登录系统 + * + * @param loginUser 登录用户信息 + */ + public static void login(LoginUser loginUser) { + StpAdminUtil.login(loginUser.getLoginCode()); + setLoginUser(loginUser); + } + + /** + * 登录系统 并指定登录设备类型 + * + * @param device 登录设备类型 + * @param loginUser 登录用户信息 + */ + public static void login(LoginUser loginUser, String device) { + StpAdminUtil.login(loginUser.getLoginCode(), device); + setLoginUser(loginUser); + loginUser.setToken(StpAdminUtil.getTokenValue()); + } + + /** + * 设置用户数据 + */ + public static void setLoginUser(LoginUser loginUser) { + StpAdminUtil.getTokenSession().set(Constant.LOGIN_USER_KEY, loginUser); + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() { + return (LoginUser) StpAdminUtil.getTokenSession().get(Constant.LOGIN_USER_KEY); + } + + /** + * 获取用户id + */ + public static Long getUserId() { + LoginUser loginUser = getLoginUser(); + if (ObjectUtil.isNull(loginUser)) { + return null; + } + return loginUser.getId(); + } + + /** + * 获取用户账户 + **/ + public static String getLoginCode() { + return getLoginUser().getLoginCode(); + } + + /** + * 获取用户角色 + * + * @return 用户角色 + */ + public static List getRoleCode() { + return getLoginUser().getRolePermission(); + } + +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginUtils.java new file mode 100644 index 0000000..c5cadf8 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginUtils.java @@ -0,0 +1,81 @@ +package com.yxx.common.utils.auth; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.util.ObjectUtil; +import com.yxx.common.constant.Constant; +import com.yxx.common.core.model.LoginUser; + +import java.util.List; + +/** + * 登录鉴权工具 + * + * @author yxx + * @since 2022/4/13 14:22 + */ +public class LoginUtils { + + /** + * 登录系统 + * + * @param loginUser 登录用户信息 + */ + public static void login(LoginUser loginUser) { + StpUtil.login(loginUser.getLoginCode()); + setLoginUser(loginUser); + } + + /** + * 登录系统 并指定登录设备类型 + * + * @param device 登录设备类型 + * @param loginUser 登录用户信息 + */ + public static void login(LoginUser loginUser, String device) { + StpUtil.login(loginUser.getLoginCode(), device); + setLoginUser(loginUser); + loginUser.setToken(StpUtil.getTokenValue()); + } + + /** + * 设置用户数据 + */ + public static void setLoginUser(LoginUser loginUser) { + StpUtil.getTokenSession().set(Constant.LOGIN_USER_KEY, loginUser); + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() { + return (LoginUser) StpUtil.getTokenSession().get(Constant.LOGIN_USER_KEY); + } + + /** + * 获取用户id + */ + public static Long getUserId() { + LoginUser loginUser = getLoginUser(); + if (ObjectUtil.isNull(loginUser)) { + return null; + } + return loginUser.getId(); + } + + /** + * 获取用户账户 + **/ + public static String getLoginCode() { + return getLoginUser().getLoginCode(); + } + + /** + * 获取用户角色 + * + * @return 用户角色 + */ + public static List getRoleCode() { + return getLoginUser().getRolePermission(); + } + +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/email/MailUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/email/MailUtils.java new file mode 100644 index 0000000..11dde45 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/email/MailUtils.java @@ -0,0 +1,84 @@ +package com.yxx.common.utils.email; + +import com.yxx.common.enums.ApiCode; +import com.yxx.common.exceptions.ApiException; +import com.yxx.common.properties.MailProperties; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import java.io.UnsupportedEncodingException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class MailUtils { + + private final JavaMailSender mailSender; + + private final MailProperties mailProperties; + + + /** + * 发送邮件 单个接收人 + * + * @param to 接收人邮箱 + * @param subject 邮件主题 + * @param text 邮件正文 + * @param html true:发送html格式邮件;false:发送普通邮件 + * @author yxx + */ + public void baseSendMail(String to, String subject, String text, boolean html) { + MimeMessage message = mailSender.createMimeMessage(); + try { + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + helper.setFrom(mailProperties.getFrom(), mailProperties.getFromName()); + helper.setTo(to); + helper.setSubject(subject); + if (html) { + helper.setText(text, true); + } else { + helper.setText(text); + } + mailSender.send(message); + log.info("邮件已经发送"); + } catch (MessagingException | UnsupportedEncodingException e) { + log.error("发送邮件时发生异常!", e); + throw new ApiException(ApiCode.MAIL_ERROR); + } + } + + /** + * 发送邮件,多个接收人 + * + * @param to 接收人邮箱 + * @param subject 邮件主题 + * @param text 邮件正文 + * @param html true:发送html格式邮件;false:发送普通邮件 + * @author yxx + */ + public void baseSendMail(String[] to, String subject, String text, boolean html) { + MimeMessage message = mailSender.createMimeMessage(); + try { + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + helper.setFrom(mailProperties.getFrom(), mailProperties.getFromName()); + helper.setTo(to); + helper.setSubject(subject); + if (html) { + helper.setText(text, true); + } else { + helper.setText(text); + } + mailSender.send(message); + log.info("邮件已经发送"); + } catch (MessagingException | UnsupportedEncodingException e) { + log.error("发送邮件时发生异常!", e); + throw new ApiException(ApiCode.MAIL_ERROR); + } + } + +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/utils/encryptor/DESUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/encryptor/DESUtil.java new file mode 100644 index 0000000..58f4f26 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/encryptor/DESUtil.java @@ -0,0 +1,131 @@ +package com.yxx.common.utils.encryptor; + +import com.yxx.common.enums.ApiCode; +import com.yxx.common.exceptions.ApiException; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Base64; + +/** + * Description: + * + * @author yxx + * @since 2022-11-25 + */ +public class DESUtil implements IEncryptor { + /** + * 偏移变量,固定占8位字节 + */ + private static final String IV_PARAMETER = "12345678"; + /** + * 加密算法 + */ + private static final String ALGORITHM = "DES"; + /** + * 加密/解密算法-工作模式-填充模式 + */ + private static final String CIPHER_ALGORITHM = "DES/CBC/PKCS5Padding"; + /** + * 默认编码 + */ + private static final String CHARSET = String.valueOf(StandardCharsets.UTF_8); + + /** + * 生成key + * + * @param password + * @return + * @throws Exception + */ + private static Key generateKey(String password) { + try { + DESKeySpec dks = new DESKeySpec(password.getBytes(CHARSET)); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM); + return keyFactory.generateSecret(dks); + } catch (InvalidKeySpecException | InvalidKeyException | UnsupportedEncodingException | + NoSuchAlgorithmException e) { + throw new ApiException(ApiCode.KEY_ERROR); + } + } + + + /** + * DES加密字符串 + * + * @param key 加密密码,长度不能够小于8位 + * @param obj 待加密字符串 + * @return 加密后内容 + */ + @Override + public String encrypt(Object obj, String key) { + String data = obj.toString(); + if (key == null){ + return null; + } + if ( key.length() < 8) { + throw new ApiException(ApiCode.KEY_LENGTH_ERROR); + } + try { + Key secretKey = generateKey(key); + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET)); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); + + // 加密 + byte[] bytes = cipher.doFinal(data.getBytes(CHARSET)); + + // base64编码 JDK1.8及以上可直接使用Base64,JDK1.7及以下可以使用BASE64Encoder + byte[] encode = Base64.getEncoder().encode(bytes); + + return new String(encode); + + } catch (Exception e) { + e.printStackTrace(); + return data; + } + } + + /** + * DES解密字符串 + * + * @param key 解密密码,长度不能够小于8位 + * @param obj 待解密字符串 + * @return 解密后内容 + */ + @Override + public String decrypt(Object obj, String key) { + String data = obj.toString(); + if (key == null || key.length() < 8) { + throw new ApiException(ApiCode.KEY_LENGTH_ERROR); + } + if (data == null) + return null; + try { + Key secretKey = generateKey(key); + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET)); + cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); + + // base64解码 + byte[] decode = Base64.getDecoder().decode(data.getBytes(CHARSET)); + + // 解密 + byte[] decrypt = cipher.doFinal(decode); + + return new String(decrypt, CHARSET); + } catch (Exception e) { + e.printStackTrace(); + return data; + } + } + +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/utils/encryptor/IEncryptor.java b/common/common-core/src/main/java/com/yxx/common/utils/encryptor/IEncryptor.java new file mode 100644 index 0000000..4873a90 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/encryptor/IEncryptor.java @@ -0,0 +1,12 @@ +package com.yxx.common.utils.encryptor; + +/** + * @author yxx + */ +public interface IEncryptor { + + String encrypt(Object val2bEncrypted, String key); + + String decrypt(Object val2bDecrypted, String key); + +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/utils/enums/EnumUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/enums/EnumUtils.java new file mode 100644 index 0000000..74faf38 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/enums/EnumUtils.java @@ -0,0 +1,35 @@ +package com.yxx.common.utils.enums; + +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +@Slf4j +public class EnumUtils { + + /** + * 判断值是否在枚举类的code中 + * @param clzz 枚举类 + * @param code 值 + * @return true-在;false-不在 + */ + public static boolean isInclude(Class clzz, String code) { + boolean include = false; + try { + if (clzz.isEnum()) { + Object[] enumConstants = clzz.getEnumConstants(); + Method getCode = clzz.getMethod("getCode"); + for (Object enumConstant : enumConstants) { + if (getCode.invoke(enumConstant).equals(code)) { + include = true; + break; + } + } + } + } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { + log.error(String.valueOf(e)); + } + return include; + } +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/utils/ip/AddressUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/ip/AddressUtil.java new file mode 100644 index 0000000..6b4a6e5 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/ip/AddressUtil.java @@ -0,0 +1,172 @@ +package com.yxx.common.utils.ip; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.CharSequenceUtil; +import lombok.extern.slf4j.Slf4j; +import org.lionsoul.ip2region.xdb.Searcher; +import org.springframework.stereotype.Component; + +import java.io.FileInputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * 解析ip属地 + * + * @author yxx + * @classname AddressUtil + * @since 2023/07/28 + */ +@Slf4j +@Component +public class AddressUtil { + + /** + * 根据IP地址查询登录来源 线上用 + * + * @param ip ip + * @return {@link String } + * @author yxx + */ + public static String getCityInfo(String ip) { + if (isIPv6Address(ip)) { + return null; + } + if (!isValidIPv4(ip)) { + return null; + } + + try { + String path = System.getProperty("user.dir"); + FileInputStream fileInputStream = new FileInputStream(path + "/ip2region.xdb"); + Searcher searcher = Searcher.newWithBuffer(IoUtil.readBytes(fileInputStream)); + return searcher.search(ip); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 得到ip归属地 + * + * @return {@link String } + * @author yxx + */ + public static String getIpHomePlace(String requestIp, Integer num) { + log.info("ip:{}", requestIp); + String cityInfo = AddressUtil.getCityInfo(requestIp); + log.info("地理位置信息:{}", cityInfo); + String ipHomePlace = extractLocationInfo(cityInfo, num); + log.info("解析后地址为:{}", ipHomePlace); + return ipHomePlace; + } + + /** + * 提取位置信息 + * + * @param ipLocation ip位置 + * @param dataType 数据类型 + * @return {@link String } + * @author yxx + */ + public static String extractLocationInfo(String ipLocation, int dataType) { + if (CharSequenceUtil.isBlank(ipLocation)) { + return "未知"; + } + // 按照“|”符号将IP归属地拆分成数组 + String[] locationParts = ipLocation.split("\\|"); + + // 获取省份和市区信息 + String province = locationParts[2]; + String city = locationParts[3]; + + // 检查是否为内网IP + if ("内网IP".equals(province) || "内网IP".equals(city)) { + return "内网"; + } + + // 获取省份数据 + String result = ""; + if (dataType == 2) { + result = removeSuffix(province); + } + // 获取市区数据,如果市区为空,则返回省份数据;如果省份数据也为空,则返回“未知” + else if (dataType == 3) { + result = removeSuffix(city); + if ("0".equals(result) || "".equals(result)) { + result = removeSuffix(province); + if ("0".equals(result) || "".equals(result)) { + result = "未知"; + } + } + } + return result; + } + + /** + * 辅助方法:去除字符串末尾的特定后缀 + * + * @param str str + * @return {@link String } + * @author yxx + */ + private static String removeSuffix(String str) { + if (str.endsWith("省") || str.endsWith("市")) { + return str.substring(0, str.length() - 1); + } + return str; + } + + /** + * 是有效ipv4 + * + * @param ip ip地址 + * @return boolean + * @author yxx + */ + public static boolean isValidIPv4(String ip) { + if (ip == null || ip.isEmpty()) { + return false; + } + + // 切分IP地址的四个部分 + String[] parts = ip.split("\\."); + if (parts.length != 4) { + return false; + } + + // 检查每个部分是否是有效的数字(0-255之间) + for (String part : parts) { + try { + int num = Integer.parseInt(part); + if (num < 0 || num > 255) { + return false; + } + } catch (NumberFormatException e) { + return false; + } + } + return true; + } + + /** + * ipv6地址是 + * + * @param ipAddress ip地址 + * @return boolean + * @author yxx + */ + public static boolean isIPv6Address(String ipAddress) { + if (ipAddress == null || ipAddress.isEmpty()) { + return false; + } + + try { + InetAddress inetAddress = InetAddress.getByName(ipAddress); + return inetAddress instanceof java.net.Inet6Address; + } catch (UnknownHostException e) { + return false; + } + } + +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/ip/IpUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/ip/IpUtil.java new file mode 100644 index 0000000..1749a2b --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/ip/IpUtil.java @@ -0,0 +1,78 @@ +package com.yxx.common.utils.ip; + +import com.yxx.common.constant.SeparatorConstant; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import jakarta.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Objects; + +/** + * IP工具类 + * + * @author yxx + * @since 2022/07/19 + */ +@Slf4j +public class IpUtil { + + private static final Integer MAX_LENGTH = 15; + private static final String UNKNOWN = "unknown"; + private static final String IPV6_LOCAL = "0:0:0:0:0:0:0:1"; + + private IpUtil() { + throw new AssertionError(); + } + + /** + * 获取请求用户的IP地址 + * + * @return IP地址 + */ + public static String getRequestIp() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); + return getRequestIp(request); + } + + /** + * 获取请求用户的IP地址 + * + * @param request HttpServletRequest + * @return IP地址 + */ + public static String getRequestIp(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + if (IPV6_LOCAL.equals(ip)) { + ip = getLocalhostIp(); + } + + if (ip != null && ip.length() > MAX_LENGTH) { + if (ip.indexOf(SeparatorConstant.COMMA) > 0) { + ip = ip.substring(0, ip.indexOf(SeparatorConstant.COMMA)); + } + } + return ip; + } + + public static String getLocalhostIp() { + try { + return InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException ignored) { + } + return null; + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/jackson/JacksonUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/jackson/JacksonUtil.java new file mode 100644 index 0000000..cfec4e3 --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/jackson/JacksonUtil.java @@ -0,0 +1,206 @@ +package com.yxx.common.utils.jackson; + +import cn.hutool.core.date.DatePattern; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.DateSerializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import com.yxx.common.utils.DateUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.Objects; + +/** + * Jackson工具类 + * + * @author yxx + * @since 2022/7/15 16:54 + */ +@Slf4j +@SuppressWarnings("unused") +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public abstract class JacksonUtil { + + private static final ObjectMapper OBJECT_MAPPER; + + static { + OBJECT_MAPPER = initObjectMapper(new ObjectMapper()); + } + + /** + * 转换Json + * + * @param object 需要转换的对象 + * @return String + */ + public static String toJson(Object object) { + if (Boolean.TRUE.equals(isCharSequence(object))) { + return (String) object; + } + try { + return getObjectMapper().writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * Json转换为对象 转换失败返回null + * + * @param json Json字符串 + * @return Object + */ + public static Object parse(String json) { + Object object = null; + try { + object = getObjectMapper().readValue(json, Object.class); + } catch (Exception ignored) { + log.error("json转换对象异常"); + } + return object; + } + + /** + * Json转换为对象 转换失败返回null + * + * @param json Json字符串 + * @param clazz 转换的类型 + * @return 转换后的对象 + */ + public static T readValue(String json, Class clazz) { + T t = null; + try { + t = getObjectMapper().readValue(json, clazz); + } catch (Exception e) { + e.printStackTrace(); + } + return t; + } + + /** + * Json转换为对象 转换失败返回null + * + * @param json json字符串 + * @param valueTypeRef TypeReference + * @return 对象 + */ + public static T readValue(String json, TypeReference valueTypeRef) { + T t = null; + try { + t = getObjectMapper().readValue(json, valueTypeRef); + } catch (Exception e) { + log.error("JacksonUtil readValue", e); + } + return t; + } + + /** + * 获取ObjectMapper + * + * @return ObjectMapper + */ + public static ObjectMapper getObjectMapper() { + return OBJECT_MAPPER; + } + + /** + *

+ * 是否为CharSequence类型 + *

+ * + * @param object 对象 + * @return boolean + */ + public static Boolean isCharSequence(Object object) { + return !Objects.isNull(object) && CharSequence.class.isAssignableFrom(object.getClass()); + } + + /** + * 初始化 ObjectMapper + * + * @param objectMapper ObjectMapper + * @return ObjectMapper + */ + public static ObjectMapper initObjectMapper(ObjectMapper objectMapper) { + if (Objects.isNull(objectMapper)) { + objectMapper = new ObjectMapper(); + } + return doInitObjectMapper(objectMapper); + } + + /** + * 初始化 ObjectMapper 时间方法 + * + * @param objectMapper ObjectMapper + * @return ObjectMapper + */ + private static ObjectMapper doInitObjectMapper(ObjectMapper objectMapper) { + // 忽略不能转移的字符 + JsonMapper.builder().configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true); + // 忽略目标对象没有的属性 + JsonMapper.builder().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + JsonMapper.builder().configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + //忽略transient + JsonMapper.builder().configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true); + objectMapper.getSerializerProvider().setNullValueSerializer(new NullValueSerializer()); + return registerModule(objectMapper); + } + + /** + * 注册模块 + * + * @param objectMapper ObjectMapper + * @return ObjectMapper + */ + private static ObjectMapper registerModule(ObjectMapper objectMapper) { + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(Date.class, new DateSerializer(true, null)); + simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))); + + simpleModule.addDeserializer(LocalDateTime.class, new JsonDeserializer<>() { + @Override + public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return DateUtils.convertLocalDateTime(p.getText()); + } + }); + simpleModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))); + simpleModule.addDeserializer(LocalDate.class, new JsonDeserializer<>() { + @Override + public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return DateUtils.convertLocalDate(p.getText()); + } + }); + + simpleModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN))); + simpleModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN))); + objectMapper.registerModule(simpleModule); + return objectMapper; + } + + /** + * NULL值处理为空字符串 + */ + public static class NullValueSerializer extends JsonSerializer { + @Override + public void serialize(Object object, JsonGenerator generator, SerializerProvider provider) throws IOException { + generator.writeString(""); + } + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/jackson/SearchDateDeserializer.java b/common/common-core/src/main/java/com/yxx/common/utils/jackson/SearchDateDeserializer.java new file mode 100644 index 0000000..f3a03ae --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/jackson/SearchDateDeserializer.java @@ -0,0 +1,62 @@ +package com.yxx.common.utils.jackson; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.yxx.common.annotation.jackson.SearchDate; + +import java.io.IOException; +import java.util.Date; + +/** + * @author yxx + * @since 2022/11/2 10:38 + */ +public class SearchDateDeserializer extends JsonDeserializer implements ContextualDeserializer { + + private SearchDate searchDate; + + @SuppressWarnings("unused") + public SearchDateDeserializer() { + super(); + } + + public SearchDateDeserializer(final SearchDate searchDate) { + super(); + this.searchDate = searchDate; + } + + @Override + public Date deserialize(JsonParser p, DeserializationContext context) throws IOException { + Date date = p.readValueAs(Date.class); + if (ObjectUtil.isNull(date)) { + return null; + } + if (searchDate.startDate()) { + return DateUtil.beginOfDay(date); + } + if (searchDate.endDate()) { + return DateUtil.endOfDay(date); + } + return date; + } + + @Override + public JsonDeserializer createContextual(DeserializationContext context, BeanProperty property) throws JsonMappingException { + if (property != null) { + if (ObjectUtil.equal(property.getType().getRawClass(), Date.class)) { + searchDate = property.getAnnotation(SearchDate.class); + if (searchDate != null) { + return new SearchDateDeserializer(searchDate); + } + } + return context.findContextualValueDeserializer(property.getType(), property); + } + return context.findNonContextualValueDeserializer(context.getContextualType()); + } +} diff --git a/common/common-core/src/main/java/com/yxx/common/utils/redis/RedissonCache.java b/common/common-core/src/main/java/com/yxx/common/utils/redis/RedissonCache.java new file mode 100644 index 0000000..89eb3ea --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/redis/RedissonCache.java @@ -0,0 +1,279 @@ +package com.yxx.common.utils.redis; + +import com.yxx.common.constant.Constant; +import lombok.extern.slf4j.Slf4j; +import org.redisson.Redisson; +import org.redisson.api.*; +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.StringCodec; +import org.redisson.codec.JsonJacksonCodec; +import org.redisson.config.Config; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; +import org.springframework.context.annotation.Configuration; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Resource; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * @author yxx + * @description redisson工具类 + */ +@Slf4j +@Configuration +public class RedissonCache { + + /** + * 默认缓存时间 + */ + private static final Long DEFAULT_EXPIRED = 5 * 60L; + /** + * redis key前缀 + */ + private static final String REDIS_KEY_PREFIX = ""; + /** + * redisson client对象 + */ + private RedissonClient redisson; + + @Resource + private RedisProperties redisProperties; + + /** + * 初始化连接 + * + * @throws IOException + */ + @PostConstruct + public void init(){ + // 初始化配置 + Config config = new Config(); + // redis url + String redisUrl = String.format("redis://%s:%s", redisProperties.getHost() + Constant.EMPTY, redisProperties.getPort() + Constant.EMPTY); + // 设置url和密码 + config.useSingleServer().setAddress(redisUrl).setPassword(redisProperties.getPassword()); + // 设置库 + config.useSingleServer().setDatabase(redisProperties.getDatabase()); + // 设置最小空闲Redis连接量 + config.useSingleServer().setConnectionMinimumIdleSize(10); + // 设置Redis数据编解码器 序列化方式 + Codec codec = new JsonJacksonCodec(); + config.setCodec(codec); + if (redisson == null) { + redisson = Redisson.create(config); + log.info( "redis连接成功,server={}", redisUrl); + } else { + log.warn("redis 重复连接,config={}", config); + } + } + + /** + * 读取缓存 + * + * @param key 缓存key + * @param + * @return 缓存返回值 + */ + public T get(String key) { + RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key); + return bucket.get(); + } + + /** + * 判断key是否存在 + * + * @param key key + * @return {@link Boolean } + * @author yxx + */ + public Boolean isExists(String key) { + return redisson.getBucket(REDIS_KEY_PREFIX + key).isExists(); + } + + /** + * 以string的方式读取缓存 + * + * @param key 缓存key + * @return 缓存返回值 + */ + public String getString(String key) { + RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE); + return bucket.get(); + } + + /** + * 设置缓存(注:redisson会自动选择序列化反序列化方式) + * + * @param key 缓存key + * @param value 缓存值 + * @param + */ + public void put(String key, T value) { + RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key); + bucket.set(value, DEFAULT_EXPIRED, TimeUnit.SECONDS); + } + + /** + * 以string的方式设置缓存 + * + * @param key + * @param value + */ + public void putString(String key, String value) { + RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE); + bucket.set(value, DEFAULT_EXPIRED, TimeUnit.SECONDS); + } + + /** + * 以string的方式保存缓存(与其他应用共用redis时需要使用该函数) + * + * @param key 缓存key + * @param value 缓存值 + * @param expired 缓存过期时间 + */ + public void putString(String key, String value, long expired) { + RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE); + bucket.set(value, expired <= 0 ? DEFAULT_EXPIRED : expired, TimeUnit.SECONDS); + } + + /** + * 以string的方式保存缓存(与其他应用共用redis时需要使用该函数) + * + * @param key 缓存key + * @param value 缓存值 + * @param expired 缓存过期时间 + * @param timeStyle 时间风格 {@link TimeUnit} + * @author yxx + */ + public void putString(String key, String value, long expired, TimeUnit timeStyle) { + RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE); + bucket.set(value, expired <= 0 ? DEFAULT_EXPIRED : expired, timeStyle); + } + + /** + * 如果不存在则写入缓存(string方式,不带有redisson的格式信息) + * + * @param key 缓存key + * @param value 缓存值 + * @param expired 缓存过期时间 + */ + public boolean putStringIfAbsent(String key, String value, long expired) { + RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE); + return bucket.trySet(value, expired <= 0 ? DEFAULT_EXPIRED : expired, TimeUnit.SECONDS); + } + + /** + * 如果不存在则写入缓存(string方式,不带有redisson的格式信息)(不带过期时间,永久保存) + * + * @param key 缓存key + * @param value 缓存值 + */ + public boolean putStringIfAbsent(String key, String value) { + RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE); + return bucket.trySet(value); + } + + /** + * 设置缓存 + * + * @param key 缓存key + * @param value 缓存值 + * @param expired 缓存过期时间 + * @param 类型 + */ + public void put(String key, T value, long expired) { + RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key); + bucket.set(value, expired <= 0 ? DEFAULT_EXPIRED : expired, TimeUnit.SECONDS); + } + + /** + * 移除缓存 + * + * @param key + */ + public void remove(String key) { + redisson.getBucket(REDIS_KEY_PREFIX + key).delete(); + } + + /** + * 判断缓存是否存在 + * + * @param key + * @return + */ + public boolean exists(String key) { + return redisson.getBucket(REDIS_KEY_PREFIX + key).isExists(); + } + + + /** + * 暴露redisson的RList对象 + * + * @param key + * @param + * @return + */ + public RList getRedisList(String key) { + return redisson.getList(REDIS_KEY_PREFIX + key); + } + + /** + * 暴露redisson的RMapCache对象 + * + * @param key + * @param + * @param + * @return + */ + public RMapCache getRedisMap(String key) { + return redisson.getMapCache(REDIS_KEY_PREFIX + key); + } + + /** + * 暴露redisson的RSET对象 + * + * @param key + * @param + * @return + */ + public RSet getRedisSet(String key) { + return redisson.getSet(REDIS_KEY_PREFIX + key); + } + + + /** + * 暴露redisson的RScoredSortedSet对象 + * + * @param key + * @param + * @return + */ + public RScoredSortedSet getRedisScoredSortedSet(String key) { + return redisson.getScoredSortedSet(REDIS_KEY_PREFIX + key); + } + + /** + * 获取redisson的Lock对象 + * + * @param key key + * @return 锁 + */ + public RLock getRedisLock(String key) { + return redisson.getLock(REDIS_KEY_PREFIX + key); + } + + + @PreDestroy + public void close() { + try { + if (redisson != null) { + redisson.shutdown(); + } + } catch (Exception ex) { + log.error( ex.getMessage(), ex); + } + } + +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/yxx/common/utils/satoken/StpAdminUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/satoken/StpAdminUtil.java new file mode 100644 index 0000000..88148cd --- /dev/null +++ b/common/common-core/src/main/java/com/yxx/common/utils/satoken/StpAdminUtil.java @@ -0,0 +1,1221 @@ +package com.yxx.common.utils.satoken; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.fun.SaFunction; +import cn.dev33.satoken.listener.SaTokenEventCenter; +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.session.TokenSign; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.SaTokenInfo; +import cn.dev33.satoken.stp.StpLogic; + +import java.util.List; + +/** + * Sa-Token 权限认证工具类 + * + * @author click33 + * @since 1.0.0 + */ +public class StpAdminUtil { + + private StpAdminUtil() {} + + /** + * 多账号体系下的类型标识 + */ + public static final String TYPE = "admin"; + + /** + * 底层使用的 StpLogic 对象 + */ + public static StpLogic stpLogic = new StpLogic(TYPE); + + /** + * 获取当前 StpLogic 的账号类型 + * + * @return / + */ + public static String getLoginType(){ + return stpLogic.getLoginType(); + } + + /** + * 安全的重置 StpLogic 对象 + * + *
1、更改此账户的 StpLogic 对象 + *
2、put 到全局 StpLogic 集合中 + *
3、发送日志 + * + * @param newStpLogic / + */ + public static void setStpLogic(StpLogic newStpLogic) { + // 1、重置此账户的 StpLogic 对象 + stpLogic = newStpLogic; + + // 2、添加到全局 StpLogic 集合中 + // 以便可以通过 SaManager.getStpLogic(type) 的方式来全局获取到这个 StpLogic + SaManager.putStpLogic(newStpLogic); + + // 3、$$ 发布事件:更新了 stpLogic 对象 + SaTokenEventCenter.doSetStpLogic(stpLogic); + } + + /** + * 获取 StpLogic 对象 + * + * @return / + */ + public static StpLogic getStpLogic() { + return stpLogic; + } + + + // ------------------- 获取 token 相关 ------------------- + + /** + * 返回 token 名称,此名称在以下地方体现:Cookie 保存 token 时的名称、提交 token 时参数的名称、存储 token 时的 key 前缀 + * + * @return / + */ + public static String getTokenName() { + return stpLogic.getTokenName(); + } + + /** + * 在当前会话写入指定 token 值 + * + * @param tokenValue token 值 + */ + public static void setTokenValue(String tokenValue){ + stpLogic.setTokenValue(tokenValue); + } + + /** + * 在当前会话写入指定 token 值 + * + * @param tokenValue token 值 + * @param cookieTimeout Cookie存活时间(秒) + */ + public static void setTokenValue(String tokenValue, int cookieTimeout){ + stpLogic.setTokenValue(tokenValue, cookieTimeout); + } + + /** + * 在当前会话写入指定 token 值 + * + * @param tokenValue token 值 + * @param loginModel 登录参数 + */ + public static void setTokenValue(String tokenValue, SaLoginModel loginModel){ + stpLogic.setTokenValue(tokenValue, loginModel); + } + + /** + * 获取当前请求的 token 值 + * + * @return 当前tokenValue + */ + public static String getTokenValue() { + return stpLogic.getTokenValue(); + } + + /** + * 获取当前请求的 token 值 (不裁剪前缀) + * + * @return / + */ + public static String getTokenValueNotCut(){ + return stpLogic.getTokenValueNotCut(); + } + + /** + * 获取当前会话的 token 参数信息 + * + * @return token 参数信息 + */ + public static SaTokenInfo getTokenInfo() { + return stpLogic.getTokenInfo(); + } + + + // ------------------- 登录相关操作 ------------------- + + // --- 登录 + + /** + * 会话登录 + * + * @param id 账号id,建议的类型:(long | int | String) + */ + public static void login(Object id) { + stpLogic.login(id); + } + + /** + * 会话登录,并指定登录设备类型 + * + * @param id 账号id,建议的类型:(long | int | String) + * @param device 设备类型 + */ + public static void login(Object id, String device) { + stpLogic.login(id, device); + } + + /** + * 会话登录,并指定是否 [记住我] + * + * @param id 账号id,建议的类型:(long | int | String) + * @param isLastingCookie 是否为持久Cookie,值为 true 时记住我,值为 false 时关闭浏览器需要重新登录 + */ + public static void login(Object id, boolean isLastingCookie) { + stpLogic.login(id, isLastingCookie); + } + + /** + * 会话登录,并指定此次登录 token 的有效期, 单位:秒 + * + * @param id 账号id,建议的类型:(long | int | String) + * @param timeout 此次登录 token 的有效期, 单位:秒 + */ + public static void login(Object id, long timeout) { + stpLogic.login(id, timeout); + } + + /** + * 会话登录,并指定所有登录参数 Model + * + * @param id 账号id,建议的类型:(long | int | String) + * @param loginModel 此次登录的参数Model + */ + public static void login(Object id, SaLoginModel loginModel) { + stpLogic.login(id, loginModel); + } + + /** + * 创建指定账号 id 的登录会话数据 + * + * @param id 账号id,建议的类型:(long | int | String) + * @return 返回会话令牌 + */ + public static String createLoginSession(Object id) { + return stpLogic.createLoginSession(id); + } + + /** + * 创建指定账号 id 的登录会话数据 + * + * @param id 账号id,建议的类型:(long | int | String) + * @param loginModel 此次登录的参数Model + * @return 返回会话令牌 + */ + public static String createLoginSession(Object id, SaLoginModel loginModel) { + return stpLogic.createLoginSession(id, loginModel); + } + + // --- 注销 + + /** + * 在当前客户端会话注销 + */ + public static void logout() { + stpLogic.logout(); + } + + /** + * 会话注销,根据账号id + * + * @param loginId 账号id + */ + public static void logout(Object loginId) { + stpLogic.logout(loginId); + } + + /** + * 会话注销,根据账号id 和 设备类型 + * + * @param loginId 账号id + * @param device 设备类型 (填 null 代表注销该账号的所有设备类型) + */ + public static void logout(Object loginId, String device) { + stpLogic.logout(loginId, device); + } + + /** + * 会话注销,根据指定 Token + * + * @param tokenValue 指定 token + */ + public static void logoutByTokenValue(String tokenValue) { + stpLogic.logoutByTokenValue(tokenValue); + } + + /** + * 踢人下线,根据账号id + *

当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5

+ * + * @param loginId 账号id + */ + public static void kickout(Object loginId) { + stpLogic.kickout(loginId); + } + + /** + * 踢人下线,根据账号id 和 设备类型 + *

当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5

+ * + * @param loginId 账号id + * @param device 设备类型 (填 null 代表踢出该账号的所有设备类型) + */ + public static void kickout(Object loginId, String device) { + stpLogic.kickout(loginId, device); + } + + /** + * 踢人下线,根据指定 token + *

当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5

+ * + * @param tokenValue 指定 token + */ + public static void kickoutByTokenValue(String tokenValue) { + stpLogic.kickoutByTokenValue(tokenValue); + } + + /** + * 顶人下线,根据账号id 和 设备类型 + *

当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4

+ * + * @param loginId 账号id + * @param device 设备类型 (填 null 代表顶替该账号的所有设备类型) + */ + public static void replaced(Object loginId, String device) { + stpLogic.replaced(loginId, device); + } + + // 会话查询 + + /** + * 判断当前会话是否已经登录 + * + * @return 已登录返回 true,未登录返回 false + */ + public static boolean isLogin() { + return stpLogic.isLogin(); + } + + /** + * 判断指定账号是否已经登录 + * + * @return 已登录返回 true,未登录返回 false + */ + public static boolean isLogin(Object loginId) { + return stpLogic.isLogin(loginId); + } + + /** + * 检验当前会话是否已经登录,如未登录,则抛出异常 + */ + public static void checkLogin() { + stpLogic.checkLogin(); + } + + /** + * 获取当前会话账号id,如果未登录,则抛出异常 + * + * @return 账号id + */ + public static Object getLoginId() { + return stpLogic.getLoginId(); + } + + /** + * 获取当前会话账号id, 如果未登录,则返回默认值 + * + * @param 返回类型 + * @param defaultValue 默认值 + * @return 登录id + */ + public static T getLoginId(T defaultValue) { + return stpLogic.getLoginId(defaultValue); + } + + /** + * 获取当前会话账号id, 如果未登录,则返回null + * + * @return 账号id + */ + public static Object getLoginIdDefaultNull() { + return stpLogic.getLoginIdDefaultNull(); + } + + /** + * 获取当前会话账号id, 并转换为 String 类型 + * + * @return 账号id + */ + public static String getLoginIdAsString() { + return stpLogic.getLoginIdAsString(); + } + + /** + * 获取当前会话账号id, 并转换为 int 类型 + * + * @return 账号id + */ + public static int getLoginIdAsInt() { + return stpLogic.getLoginIdAsInt(); + } + + /** + * 获取当前会话账号id, 并转换为 long 类型 + * + * @return 账号id + */ + public static long getLoginIdAsLong() { + return stpLogic.getLoginIdAsLong(); + } + + /** + * 获取指定 token 对应的账号id,如果未登录,则返回 null + * + * @param tokenValue token + * @return 账号id + */ + public static Object getLoginIdByToken(String tokenValue) { + return stpLogic.getLoginIdByToken(tokenValue); + } + + /** + * 获取当前 Token 的扩展信息(此函数只在jwt模式下生效) + * + * @param key 键值 + * @return 对应的扩展数据 + */ + public static Object getExtra(String key) { + return stpLogic.getExtra(key); + } + + /** + * 获取指定 Token 的扩展信息(此函数只在jwt模式下生效) + * + * @param tokenValue 指定的 Token 值 + * @param key 键值 + * @return 对应的扩展数据 + */ + public static Object getExtra(String tokenValue, String key) { + return stpLogic.getExtra(tokenValue, key); + } + + + // ------------------- Account-Session 相关 ------------------- + + /** + * 获取指定账号 id 的 Account-Session, 如果该 SaSession 尚未创建,isCreate=是否新建并返回 + * + * @param loginId 账号id + * @param isCreate 是否新建 + * @return SaSession 对象 + */ + public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) { + return stpLogic.getSessionByLoginId(loginId, isCreate); + } + + /** + * 获取指定 key 的 SaSession, 如果该 SaSession 尚未创建,则返回 null + * + * @param sessionId SessionId + * @return Session对象 + */ + public static SaSession getSessionBySessionId(String sessionId) { + return stpLogic.getSessionBySessionId(sessionId); + } + + /** + * 获取指定账号 id 的 Account-Session,如果该 SaSession 尚未创建,则新建并返回 + * + * @param loginId 账号id + * @return SaSession 对象 + */ + public static SaSession getSessionByLoginId(Object loginId) { + return stpLogic.getSessionByLoginId(loginId); + } + + /** + * 获取当前已登录账号的 Account-Session, 如果该 SaSession 尚未创建,isCreate=是否新建并返回 + * + * @param isCreate 是否新建 + * @return Session对象 + */ + public static SaSession getSession(boolean isCreate) { + return stpLogic.getSession(isCreate); + } + + /** + * 获取当前已登录账号的 Account-Session,如果该 SaSession 尚未创建,则新建并返回 + * + * @return Session对象 + */ + public static SaSession getSession() { + return stpLogic.getSession(); + } + + + // ------------------- Token-Session 相关 ------------------- + + /** + * 获取指定 token 的 Token-Session,如果该 SaSession 尚未创建,则新建并返回 + * + * @param tokenValue Token值 + * @return Session对象 + */ + public static SaSession getTokenSessionByToken(String tokenValue) { + return stpLogic.getTokenSessionByToken(tokenValue); + } + + /** + * 获取当前 token 的 Token-Session,如果该 SaSession 尚未创建,则新建并返回 + * + * @return Session对象 + */ + public static SaSession getTokenSession() { + return stpLogic.getTokenSession(); + } + + /** + * 获取当前匿名 Token-Session (可在未登录情况下使用的Token-Session) + * + * @return Token-Session 对象 + */ + public static SaSession getAnonTokenSession() { + return stpLogic.getAnonTokenSession(); + } + + + // ------------------- Active-Timeout token 最低活跃度 验证相关 ------------------- + + /** + * 续签当前 token:(将 [最后操作时间] 更新为当前时间戳) + *

+ * 请注意: 即使 token 已被冻结 也可续签成功, + * 如果此场景下需要提示续签失败,可在此之前调用 checkActiveTimeout() 强制检查是否冻结即可 + *

+ */ + public static void updateLastActiveToNow() { + stpLogic.updateLastActiveToNow(); + } + + /** + * 检查当前 token 是否已被冻结,如果是则抛出异常 + */ + public static void checkActiveTimeout() { + stpLogic.checkActiveTimeout(); + } + + + // ------------------- 过期时间相关 ------------------- + + /** + * 获取当前会话 token 剩余有效时间(单位: 秒,返回 -1 代表永久有效,-2 代表没有这个值) + * + * @return token剩余有效时间 + */ + public static long getTokenTimeout() { + return stpLogic.getTokenTimeout(); + } + + /** + * 获取指定 token 剩余有效时间(单位: 秒,返回 -1 代表永久有效,-2 代表没有这个值) + * + * @param token 指定token + * @return token剩余有效时间 + */ + public static long getTokenTimeout(String token) { + return stpLogic.getTokenTimeout(token); + } + + /** + * 获取当前登录账号的 Account-Session 剩余有效时间(单位: 秒,返回 -1 代表永久有效,-2 代表没有这个值) + * + * @return token剩余有效时间 + */ + public static long getSessionTimeout() { + return stpLogic.getSessionTimeout(); + } + + /** + * 获取当前 token 的 Token-Session 剩余有效时间(单位: 秒,返回 -1 代表永久有效,-2 代表没有这个值) + * + * @return token剩余有效时间 + */ + public static long getTokenSessionTimeout() { + return stpLogic.getTokenSessionTimeout(); + } + + /** + * 获取当前 token 剩余活跃有效期:当前 token 距离被冻结还剩多少时间(单位: 秒,返回 -1 代表永不冻结,-2 代表没有这个值或 token 已被冻结了) + * + * @return / + */ + public static long getTokenActiveTimeout() { + return stpLogic.getTokenActiveTimeout(); + } + + /** + * 对当前 token 的 timeout 值进行续期 + * + * @param timeout 要修改成为的有效时间 (单位: 秒) + */ + public static void renewTimeout(long timeout) { + stpLogic.renewTimeout(timeout); + } + + /** + * 对指定 token 的 timeout 值进行续期 + * + * @param tokenValue 指定 token + * @param timeout 要修改成为的有效时间 (单位: 秒,填 -1 代表要续为永久有效) + */ + public static void renewTimeout(String tokenValue, long timeout) { + stpLogic.renewTimeout(tokenValue, timeout); + } + + + // ------------------- 角色认证操作 ------------------- + + /** + * 获取:当前账号的角色集合 + * + * @return / + */ + public static List getRoleList() { + return stpLogic.getRoleList(); + } + + /** + * 获取:指定账号的角色集合 + * + * @param loginId 指定账号id + * @return / + */ + public static List getRoleList(Object loginId) { + return stpLogic.getRoleList(loginId); + } + + /** + * 判断:当前账号是否拥有指定角色, 返回 true 或 false + * + * @param role 角色 + * @return / + */ + public static boolean hasRole(String role) { + return stpLogic.hasRole(role); + } + + /** + * 判断:指定账号是否含有指定角色标识, 返回 true 或 false + * + * @param loginId 账号id + * @param role 角色标识 + * @return 是否含有指定角色标识 + */ + public static boolean hasRole(Object loginId, String role) { + return stpLogic.hasRole(loginId, role); + } + + /** + * 判断:当前账号是否含有指定角色标识 [ 指定多个,必须全部验证通过 ] + * + * @param roleArray 角色标识数组 + * @return true或false + */ + public static boolean hasRoleAnd(String... roleArray){ + return stpLogic.hasRoleAnd(roleArray); + } + + /** + * 判断:当前账号是否含有指定角色标识 [ 指定多个,只要其一验证通过即可 ] + * + * @param roleArray 角色标识数组 + * @return true或false + */ + public static boolean hasRoleOr(String... roleArray){ + return stpLogic.hasRoleOr(roleArray); + } + + /** + * 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException + * + * @param role 角色标识 + */ + public static void checkRole(String role) { + stpLogic.checkRole(role); + } + + /** + * 校验:当前账号是否含有指定角色标识 [ 指定多个,必须全部验证通过 ] + * + * @param roleArray 角色标识数组 + */ + public static void checkRoleAnd(String... roleArray){ + stpLogic.checkRoleAnd(roleArray); + } + + /** + * 校验:当前账号是否含有指定角色标识 [ 指定多个,只要其一验证通过即可 ] + * + * @param roleArray 角色标识数组 + */ + public static void checkRoleOr(String... roleArray){ + stpLogic.checkRoleOr(roleArray); + } + + + // ------------------- 权限认证操作 ------------------- + + /** + * 获取:当前账号的权限码集合 + * + * @return / + */ + public static List getPermissionList() { + return stpLogic.getPermissionList(); + } + + /** + * 获取:指定账号的权限码集合 + * + * @param loginId 指定账号id + * @return / + */ + public static List getPermissionList(Object loginId) { + return stpLogic.getPermissionList(loginId); + } + + /** + * 判断:当前账号是否含有指定权限, 返回 true 或 false + * + * @param permission 权限码 + * @return 是否含有指定权限 + */ + public static boolean hasPermission(String permission) { + return stpLogic.hasPermission(permission); + } + + /** + * 判断:指定账号 id 是否含有指定权限, 返回 true 或 false + * + * @param loginId 账号 id + * @param permission 权限码 + * @return 是否含有指定权限 + */ + public static boolean hasPermission(Object loginId, String permission) { + return stpLogic.hasPermission(loginId, permission); + } + + /** + * 判断:当前账号是否含有指定权限 [ 指定多个,必须全部具有 ] + * + * @param permissionArray 权限码数组 + * @return true 或 false + */ + public static boolean hasPermissionAnd(String... permissionArray){ + return stpLogic.hasPermissionAnd(permissionArray); + } + + /** + * 判断:当前账号是否含有指定权限 [ 指定多个,只要其一验证通过即可 ] + * + * @param permissionArray 权限码数组 + * @return true 或 false + */ + public static boolean hasPermissionOr(String... permissionArray){ + return stpLogic.hasPermissionOr(permissionArray); + } + + /** + * 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param permission 权限码 + */ + public static void checkPermission(String permission) { + stpLogic.checkPermission(permission); + } + + /** + * 校验:当前账号是否含有指定权限 [ 指定多个,必须全部验证通过 ] + * + * @param permissionArray 权限码数组 + */ + public static void checkPermissionAnd(String... permissionArray) { + stpLogic.checkPermissionAnd(permissionArray); + } + + /** + * 校验:当前账号是否含有指定权限 [ 指定多个,只要其一验证通过即可 ] + * + * @param permissionArray 权限码数组 + */ + public static void checkPermissionOr(String... permissionArray) { + stpLogic.checkPermissionOr(permissionArray); + } + + + // ------------------- id 反查 token 相关操作 ------------------- + + /** + * 获取指定账号 id 的 token + *

+ * 在配置为允许并发登录时,此方法只会返回队列的最后一个 token, + * 如果你需要返回此账号 id 的所有 token,请调用 getTokenValueListByLoginId + *

+ * + * @param loginId 账号id + * @return token值 + */ + public static String getTokenValueByLoginId(Object loginId) { + return stpLogic.getTokenValueByLoginId(loginId); + } + + /** + * 获取指定账号 id 指定设备类型端的 token + *

+ * 在配置为允许并发登录时,此方法只会返回队列的最后一个 token, + * 如果你需要返回此账号 id 的所有 token,请调用 getTokenValueListByLoginId + *

+ * + * @param loginId 账号id + * @param device 设备类型,填 null 代表不限设备类型 + * @return token值 + */ + public static String getTokenValueByLoginId(Object loginId, String device) { + return stpLogic.getTokenValueByLoginId(loginId, device); + } + + /** + * 获取指定账号 id 的 token 集合 + * + * @param loginId 账号id + * @return 此 loginId 的所有相关 token + */ + public static List getTokenValueListByLoginId(Object loginId) { + return stpLogic.getTokenValueListByLoginId(loginId); + } + + /** + * 获取指定账号 id 指定设备类型端的 token 集合 + * + * @param loginId 账号id + * @param device 设备类型,填 null 代表不限设备类型 + * @return 此 loginId 的所有登录 token + */ + public static List getTokenValueListByLoginId(Object loginId, String device) { + return stpLogic.getTokenValueListByLoginId(loginId, device); + } + + /** + * 获取指定账号 id 指定设备类型端的 tokenSign 集合 + * + * @param loginId 账号id + * @param device 设备类型,填 null 代表不限设备类型 + * @return 此 loginId 的所有登录 tokenSign + */ + public static List getTokenSignListByLoginId(Object loginId, String device) { + return stpLogic.getTokenSignListByLoginId(loginId, device); + } + + /** + * 返回当前会话的登录设备类型 + * + * @return 当前令牌的登录设备类型 + */ + public static String getLoginDevice() { + return stpLogic.getLoginDevice(); + } + + + // ------------------- 会话管理 ------------------- + + /** + * 根据条件查询缓存中所有的 token + * + * @param keyword 关键字 + * @param start 开始处索引 + * @param size 获取数量 (-1代表一直获取到末尾) + * @param sortType 排序类型(true=正序,false=反序) + * + * @return token集合 + */ + public static List searchTokenValue(String keyword, int start, int size, boolean sortType) { + return stpLogic.searchTokenValue(keyword, start, size, sortType); + } + + /** + * 根据条件查询缓存中所有的 SessionId + * + * @param keyword 关键字 + * @param start 开始处索引 + * @param size 获取数量 (-1代表一直获取到末尾) + * @param sortType 排序类型(true=正序,false=反序) + * + * @return sessionId集合 + */ + public static List searchSessionId(String keyword, int start, int size, boolean sortType) { + return stpLogic.searchSessionId(keyword, start, size, sortType); + } + + /** + * 根据条件查询缓存中所有的 Token-Session-Id + * + * @param keyword 关键字 + * @param start 开始处索引 + * @param size 获取数量 (-1代表一直获取到末尾) + * @param sortType 排序类型(true=正序,false=反序) + * + * @return sessionId集合 + */ + public static List searchTokenSessionId(String keyword, int start, int size, boolean sortType) { + return stpLogic.searchTokenSessionId(keyword, start, size, sortType); + } + + + // ------------------- 账号封禁 ------------------- + + /** + * 封禁:指定账号 + *

此方法不会直接将此账号id踢下线,如需封禁后立即掉线,请追加调用 StpUtil.logout(id) + * + * @param loginId 指定账号id + * @param time 封禁时间, 单位: 秒 (-1=永久封禁) + */ + public static void disable(Object loginId, long time) { + stpLogic.disable(loginId, time); + } + + /** + * 判断:指定账号是否已被封禁 (true=已被封禁, false=未被封禁) + * + * @param loginId 账号id + * @return / + */ + public static boolean isDisable(Object loginId) { + return stpLogic.isDisable(loginId); + } + + /** + * 校验:指定账号是否已被封禁,如果被封禁则抛出异常 + * + * @param loginId 账号id + */ + public static void checkDisable(Object loginId) { + stpLogic.checkDisable(loginId); + } + + /** + * 获取:指定账号剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁) + * + * @param loginId 账号id + * @return / + */ + public static long getDisableTime(Object loginId) { + return stpLogic.getDisableTime(loginId); + } + + /** + * 解封:指定账号 + * + * @param loginId 账号id + */ + public static void untieDisable(Object loginId) { + stpLogic.untieDisable(loginId); + } + + + // ------------------- 分类封禁 ------------------- + + /** + * 封禁:指定账号的指定服务 + *

此方法不会直接将此账号id踢下线,如需封禁后立即掉线,请追加调用 StpUtil.logout(id) + * + * @param loginId 指定账号id + * @param service 指定服务 + * @param time 封禁时间, 单位: 秒 (-1=永久封禁) + */ + public static void disable(Object loginId, String service, long time) { + stpLogic.disable(loginId, service, time); + } + + /** + * 判断:指定账号的指定服务 是否已被封禁(true=已被封禁, false=未被封禁) + * + * @param loginId 账号id + * @param service 指定服务 + * @return / + */ + public static boolean isDisable(Object loginId, String service) { + return stpLogic.isDisable(loginId, service); + } + + /** + * 校验:指定账号 指定服务 是否已被封禁,如果被封禁则抛出异常 + * + * @param loginId 账号id + * @param services 指定服务,可以指定多个 + */ + public static void checkDisable(Object loginId, String... services) { + stpLogic.checkDisable(loginId, services); + } + + /** + * 获取:指定账号 指定服务 剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁) + * + * @param loginId 账号id + * @param service 指定服务 + * @return see note + */ + public static long getDisableTime(Object loginId, String service) { + return stpLogic.getDisableTime(loginId, service); + } + + /** + * 解封:指定账号、指定服务 + * + * @param loginId 账号id + * @param services 指定服务,可以指定多个 + */ + public static void untieDisable(Object loginId, String... services) { + stpLogic.untieDisable(loginId, services); + } + + + // ------------------- 阶梯封禁 ------------------- + + /** + * 封禁:指定账号,并指定封禁等级 + * + * @param loginId 指定账号id + * @param level 指定封禁等级 + * @param time 封禁时间, 单位: 秒 (-1=永久封禁) + */ + public static void disableLevel(Object loginId, int level, long time) { + stpLogic.disableLevel(loginId, level, time); + } + + /** + * 封禁:指定账号的指定服务,并指定封禁等级 + * + * @param loginId 指定账号id + * @param service 指定封禁服务 + * @param level 指定封禁等级 + * @param time 封禁时间, 单位: 秒 (-1=永久封禁) + */ + public static void disableLevel(Object loginId, String service, int level, long time) { + stpLogic.disableLevel(loginId, service, level, time); + } + + /** + * 判断:指定账号是否已被封禁到指定等级 + * + * @param loginId 指定账号id + * @param level 指定封禁等级 + * @return / + */ + public static boolean isDisableLevel(Object loginId, int level) { + return stpLogic.isDisableLevel(loginId, level); + } + + /** + * 判断:指定账号的指定服务,是否已被封禁到指定等级 + * + * @param loginId 指定账号id + * @param service 指定封禁服务 + * @param level 指定封禁等级 + * @return / + */ + public static boolean isDisableLevel(Object loginId, String service, int level) { + return stpLogic.isDisableLevel(loginId, service, level); + } + + /** + * 校验:指定账号是否已被封禁到指定等级(如果已经达到,则抛出异常) + * + * @param loginId 指定账号id + * @param level 封禁等级 (只有 封禁等级 ≥ 此值 才会抛出异常) + */ + public static void checkDisableLevel(Object loginId, int level) { + stpLogic.checkDisableLevel(loginId, level); + } + + /** + * 校验:指定账号的指定服务,是否已被封禁到指定等级(如果已经达到,则抛出异常) + * + * @param loginId 指定账号id + * @param service 指定封禁服务 + * @param level 封禁等级 (只有 封禁等级 ≥ 此值 才会抛出异常) + */ + public static void checkDisableLevel(Object loginId, String service, int level) { + stpLogic.checkDisableLevel(loginId, service, level); + } + + /** + * 获取:指定账号被封禁的等级,如果未被封禁则返回-2 + * + * @param loginId 指定账号id + * @return / + */ + public static int getDisableLevel(Object loginId) { + return stpLogic.getDisableLevel(loginId); + } + + /** + * 获取:指定账号的 指定服务 被封禁的等级,如果未被封禁则返回-2 + * + * @param loginId 指定账号id + * @param service 指定封禁服务 + * @return / + */ + public static int getDisableLevel(Object loginId, String service) { + return stpLogic.getDisableLevel(loginId, service); + } + + + // ------------------- 临时身份切换 ------------------- + + /** + * 临时切换身份为指定账号id + * + * @param loginId 指定loginId + */ + public static void switchTo(Object loginId) { + stpLogic.switchTo(loginId); + } + + /** + * 结束临时切换身份 + */ + public static void endSwitch() { + stpLogic.endSwitch(); + } + + /** + * 判断当前请求是否正处于 [ 身份临时切换 ] 中 + * + * @return / + */ + public static boolean isSwitch() { + return stpLogic.isSwitch(); + } + + /** + * 在一个 lambda 代码段里,临时切换身份为指定账号id,lambda 结束后自动恢复 + * + * @param loginId 指定账号id + * @param function 要执行的方法 + */ + public static void switchTo(Object loginId, SaFunction function) { + stpLogic.switchTo(loginId, function); + } + + + // ------------------- 二级认证 ------------------- + + /** + * 在当前会话 开启二级认证 + * + * @param safeTime 维持时间 (单位: 秒) + */ + public static void openSafe(long safeTime) { + stpLogic.openSafe(safeTime); + } + + /** + * 在当前会话 开启二级认证 + * + * @param service 业务标识 + * @param safeTime 维持时间 (单位: 秒) + */ + public static void openSafe(String service, long safeTime) { + stpLogic.openSafe(service, safeTime); + } + + /** + * 判断:当前会话是否处于二级认证时间内 + * + * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 + */ + public static boolean isSafe() { + return stpLogic.isSafe(); + } + + /** + * 判断:当前会话 是否处于指定业务的二级认证时间内 + * + * @param service 业务标识 + * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 + */ + public static boolean isSafe(String service) { + return stpLogic.isSafe(service); + } + + /** + * 判断:指定 token 是否处于二级认证时间内 + * + * @param tokenValue Token 值 + * @param service 业务标识 + * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 + */ + public static boolean isSafe(String tokenValue, String service) { + return stpLogic.isSafe(tokenValue, service); + } + + /** + * 校验:当前会话是否已通过二级认证,如未通过则抛出异常 + */ + public static void checkSafe() { + stpLogic.checkSafe(); + } + + /** + * 校验:检查当前会话是否已通过指定业务的二级认证,如未通过则抛出异常 + * + * @param service 业务标识 + */ + public static void checkSafe(String service) { + stpLogic.checkSafe(service); + } + + /** + * 获取:当前会话的二级认证剩余有效时间(单位: 秒, 返回-2代表尚未通过二级认证) + * + * @return 剩余有效时间 + */ + public static long getSafeTime() { + return stpLogic.getSafeTime(); + } + + /** + * 获取:当前会话的二级认证剩余有效时间(单位: 秒, 返回-2代表尚未通过二级认证) + * + * @param service 业务标识 + * @return 剩余有效时间 + */ + public static long getSafeTime(String service) { + return stpLogic.getSafeTime(service); + } + + /** + * 在当前会话 结束二级认证 + */ + public static void closeSafe() { + stpLogic.closeSafe(); + } + + /** + * 在当前会话 结束指定业务标识的二级认证 + * + * @param service 业务标识 + */ + public static void closeSafe(String service) { + stpLogic.closeSafe(service); + } + +} diff --git a/common/common-framework/pom.xml b/common/common-framework/pom.xml new file mode 100644 index 0000000..8fb2b16 --- /dev/null +++ b/common/common-framework/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + common-framework + common-framework + common-framework + + + common + com.yxx + 1.0.0 + + + + + + com.yxx + common-core + 1.0.0 + + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + + diff --git a/common/common-framework/src/main/java/com/yxx/framework/advice/ExceptionAdvice.java b/common/common-framework/src/main/java/com/yxx/framework/advice/ExceptionAdvice.java new file mode 100644 index 0000000..7a8ded5 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/advice/ExceptionAdvice.java @@ -0,0 +1,119 @@ +package com.yxx.framework.advice; + +import cn.dev33.satoken.exception.DisableServiceException; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotRoleException; +import com.yxx.common.core.response.ErrorResponse; +import com.yxx.common.enums.ApiCode; +import com.yxx.common.exceptions.ApiException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.multipart.MaxUploadSizeExceededException; + +import jakarta.validation.ConstraintViolationException; + + +/** + * @author yxx + * @description: 全局异常处理类 + */ +@Slf4j +@RestControllerAdvice +public class ExceptionAdvice { + private static final String ERROR_CODE = "发生业务异常!原因是: {}"; + private static final String LOGIN_ERROR_CODE = "发生登录异常!原因是: {},被封禁账号id: {}"; + private static final String UN_KNOW_ERROR_CODE = "发生未知异常!原因是: {}"; + private static final String CHECK_PARAMETERS_ERROR_CODE = "发生参数校验异常!原因是:{}"; + private static final String CHECK_PARAMETERS_ERROR = "发生参数校验异常!原因是:"; + private static final String REQUEST_ERROR_CODE = "发生请求异常!原因是:{}"; + + /** + * 处理自定义的业务异常 + * + * @param e 异常对象 + * @return 错误结果 + */ + @ExceptionHandler(ApiException.class) + public ErrorResponse bizExceptionHandler(ApiException e) { + log.error(ERROR_CODE, e.getMessage()); + return ErrorResponse.fail(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(NotLoginException.class) + public ErrorResponse notLoginException(NotLoginException e) { + log.error(ERROR_CODE, e.getMessage()); + return switch (e.getType()) { + case NotLoginException.NOT_TOKEN, + NotLoginException.INVALID_TOKEN, + NotLoginException.TOKEN_TIMEOUT, + NotLoginException.BE_REPLACED -> ErrorResponse.fail(ApiCode.TOKEN_ERROR.getCode(), e.getMessage()); + default -> ErrorResponse.fail(Integer.parseInt(e.getType()), e.getMessage()); + }; + } + + @ExceptionHandler(NotRoleException.class) + public ErrorResponse notRoleException(NotRoleException e) { + log.error(ERROR_CODE, e.getMessage()); + return ErrorResponse.fail(10000, e.getMessage()); + } + + @ExceptionHandler(DisableServiceException.class) + public ErrorResponse disableLoginException(DisableServiceException e) { + log.error(LOGIN_ERROR_CODE, e.getMessage(), e.getLoginId()); + long disableTime = e.getDisableTime(); + long day = disableTime / 86400L; + long hour = (disableTime % 86400) / 3600L; + long minute = (disableTime % 86400) % 3600L / 60L; + long second = (disableTime % 86400) % 3600L % 60L; + return ErrorResponse.fail(10000, e.getMessage() + ",距离解封还剩:" + day + "天" + hour + "时" + + minute + "分" + second + "秒"); + } + + /** + * 拦截抛出的异常 + * + * @param e 异常 + * @return 拦截抛出的异常 + */ + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(Throwable.class) + public ErrorResponse handlerThrowable(Throwable e) { + log.error(UN_KNOW_ERROR_CODE, e.getMessage()); + return ErrorResponse.fail(ApiCode.SYSTEM_ERROR, e); + } + + /** + * 参数校验异常 + * + * @param e 异常 + * @return 参数校验异常返回 + */ + @ExceptionHandler(BindException.class) + public ErrorResponse bindException(BindException e) { + log.error(CHECK_PARAMETERS_ERROR, e); + return ErrorResponse.fail(ApiCode.PARAM_IS_INVALID, e, e.getAllErrors().get(0).getDefaultMessage()); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ErrorResponse constraintViolationException(ConstraintViolationException e) { + log.error(CHECK_PARAMETERS_ERROR_CODE, e.getConstraintViolations().stream().iterator().next().getMessage()); + return ErrorResponse.fail(ApiCode.PARAM_IS_INVALID, e, e.getConstraintViolations().iterator().next().getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ErrorResponse methodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error(CHECK_PARAMETERS_ERROR_CODE, e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); + return ErrorResponse.fail(ApiCode.PARAM_IS_INVALID, e, e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); + } + + @ExceptionHandler(MaxUploadSizeExceededException.class) + public ErrorResponse maxUploadSizeExceededException(MaxUploadSizeExceededException e) { + log.error(REQUEST_ERROR_CODE, e.getMessage()); + return ErrorResponse.fail(ApiCode.PARAM_IS_INVALID, e, e.getMessage()); + } +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/advice/ResponseResultHandler.java b/common/common-framework/src/main/java/com/yxx/framework/advice/ResponseResultHandler.java new file mode 100644 index 0000000..ccec9f1 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/advice/ResponseResultHandler.java @@ -0,0 +1,80 @@ +package com.yxx.framework.advice; + +import com.yxx.common.annotation.response.ResponseResult; +import com.yxx.common.core.response.BaseResponse; +import com.yxx.common.core.response.ErrorResponse; +import com.yxx.framework.context.AppContext; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * @author yxx + * @description: 使用 @ControllerAdvice & ResponseBodyAdvice 拦截Controller方法默认返回参数,统一处理返回值/响应体 + */ +@Slf4j +@ControllerAdvice +public class ResponseResultHandler implements ResponseBodyAdvice { + + /** + * 标记名称 + */ + public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT"; + + + /** + * 判断是否要执行 beforeBodyWrite 方法,true为执行,false不执行,有注解标记的时候处理返回值 + * + * @param arg0 the return type + * @param arg1 the selected converter type + * @return return + */ + @Override + public boolean supports(@NotNull MethodParameter arg0, @NotNull Class> arg1) { + ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + assert sra != null; + HttpServletRequest request = sra.getRequest(); + // 判断请求是否有包装标记 + ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN); + return responseResultAnn != null; + } + + + /** + * 对返回值做包装处理,如果属于异常结果,则需要再包装 + * + * @param body the body to be written + * @param arg1 the return type of the controller method + * @param arg2 the content type selected through content negotiation + * @param arg3 the converter type selected to write to the response + * @param arg4 the current request + * @param arg5 the current response + * @return the body that was passed in or a modified (possibly new) instance + */ + @Override + public Object beforeBodyWrite(Object body, @NotNull MethodParameter arg1, @NotNull MediaType arg2, + @NotNull Class> arg3, + @NotNull ServerHttpRequest arg4, @NotNull ServerHttpResponse arg5) { + String traceId = AppContext.getContext().getTraceId(); + if (body instanceof ErrorResponse error) { + return BaseResponse.fail(error.getCode(), error.getMessage(), traceId); + } else if (body instanceof BaseResponse baseResponse) { + baseResponse.setTraceId(traceId); + return body; + } else if (body instanceof String) { + return BaseResponse.success(body, traceId); + } + + return BaseResponse.success(body, traceId); + } +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/config/FilterConfig.java b/common/common-framework/src/main/java/com/yxx/framework/config/FilterConfig.java new file mode 100644 index 0000000..b914cf0 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/config/FilterConfig.java @@ -0,0 +1,29 @@ +package com.yxx.framework.config; + +import com.yxx.framework.filter.RepeatableFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; + +/** + * + * @author yxx + * @since 2022/4/13 17:30 + */ +@Configuration +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class FilterConfig { + @Bean + public FilterRegistrationBean someFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(Ordered.LOWEST_PRECEDENCE); + return registration; + } + +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/config/WebConfigurer.java b/common/common-framework/src/main/java/com/yxx/framework/config/WebConfigurer.java new file mode 100644 index 0000000..8934600 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/config/WebConfigurer.java @@ -0,0 +1,92 @@ +package com.yxx.framework.config; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.jwt.StpLogicJwtForSimple; +import cn.dev33.satoken.stp.StpLogic; +import com.yxx.framework.interceptor.response.ResponseResultInterceptor; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +/** + * 注册拦截器 + * + * @author zhanglf + * @since 2022/11/12 03:21 + */ +@Configuration +public class WebConfigurer implements WebMvcConfigurer { + + + private final ResponseResultInterceptor interceptor; + + @Autowired(required = false) + public WebConfigurer(ResponseResultInterceptor interceptor) { + this.interceptor = interceptor; + } + + /** + * Sa-Token 整合 jwt (Style模式) + * + * @return 权限认证,逻辑实现类 + */ + @Bean + public StpLogic getStpLogicJwt() { + return new StpLogicJwtForSimple(); + } + + /** + * 用来注册拦截器,拦截器需要通过这里添加注册才能生效 + * + * @param registry 拦截器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // addPathPatterns("/**") 表示拦截所有的请求, + // excludePathPatterns("/login", "/register") 表示除了登录与注册之外,因为登录注册不需要登录也可以访问 + registry.addInterceptor(interceptor).addPathPatterns("/**"); + // 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关) + registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); + } + + @Override + public void extendMessageConverters(List> converters) { + // 删除springboot默认的StringHttpMessageConverter解析器 + // 不删除的话,ResponseResultHandler类的beforeBodyWrite方法解析封装String时会出现转换异常 + converters.removeIf(StringHttpMessageConverter.class::isInstance); + } + + /** + * 配置静态资源,比如html,js,css,等等 + * + * @param registry registry + */ + @Override + public void addResourceHandlers(@NotNull ResourceHandlerRegistry registry) { + // 配置静态资源,比如html,js,css,等等 + } + + /** + * 跨域配置 + * + * @param registry 跨域注册表 + */ + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + // SpringBoot2.4.0 [allowedOriginPatterns]代替[allowedOrigins] + .allowedOriginPatterns("*") + .allowedMethods("*") + .maxAge(3600) + .allowCredentials(true); + } +} \ No newline at end of file diff --git a/common/common-framework/src/main/java/com/yxx/framework/config/mybatis/MyBatisPlusConfig.java b/common/common-framework/src/main/java/com/yxx/framework/config/mybatis/MyBatisPlusConfig.java new file mode 100644 index 0000000..472c7dd --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/config/mybatis/MyBatisPlusConfig.java @@ -0,0 +1,33 @@ +package com.yxx.framework.config.mybatis; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.yxx.framework.hander.CommonMetaObjectHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author yxx + */ +@Configuration +public class MyBatisPlusConfig { + /** + * 分页插件 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } + + /** + * 自动填充参数 + */ + @Bean + public CommonMetaObjectHandler commonMetaObjectHandler() { + return new CommonMetaObjectHandler(); + } + +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/config/redis/RedisConfig.java b/common/common-framework/src/main/java/com/yxx/framework/config/redis/RedisConfig.java new file mode 100644 index 0000000..24adb75 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/config/redis/RedisConfig.java @@ -0,0 +1,53 @@ +package com.yxx.framework.config.redis; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * @author yxx + * @description Redis相关配置 + */ +@EnableCaching +@Configuration +public class RedisConfig extends CachingConfigurerSupport { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisSerializer serializer = redisSerializer(); + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(serializer); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(serializer); + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + + @Bean + public RedisSerializer redisSerializer() { + //创建JSON序列化器 + Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY); + serializer.setObjectMapper(objectMapper); + return serializer; + } + + +} + diff --git a/common/common-framework/src/main/java/com/yxx/framework/context/AppContext.java b/common/common-framework/src/main/java/com/yxx/framework/context/AppContext.java new file mode 100644 index 0000000..3535731 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/context/AppContext.java @@ -0,0 +1,41 @@ +package com.yxx.framework.context; + +import com.alibaba.ttl.TransmittableThreadLocal; + +import java.io.Serial; +import java.io.Serializable; + +public class AppContext implements Serializable { + public static final String KEY_TRACE_ID = "Trace-Id"; + @Serial + private static final long serialVersionUID = -979220111440953115L; + + private String traceId; + + private static final TransmittableThreadLocal LOCAL = new TransmittableThreadLocal<>() { + @Override + protected AppContext initialValue() { + return new AppContext(); + } + }; + + public static AppContext getContext() { + return LOCAL.get(); + } + + public static void setContext(AppContext context) { + LOCAL.set(context); + } + + public static void removeContext() { + LOCAL.remove(); + } + + public String getTraceId() { + return traceId; + } + + public void setTraceId(String traceId) { + this.traceId = traceId; + } +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/filter/RepeatableFilter.java b/common/common-framework/src/main/java/com/yxx/framework/filter/RepeatableFilter.java new file mode 100644 index 0000000..bbd9e16 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/filter/RepeatableFilter.java @@ -0,0 +1,47 @@ +package com.yxx.framework.filter; + +import cn.hutool.core.text.CharSequenceUtil; +import com.yxx.common.utils.ApplicationUtils; +import com.yxx.common.utils.RepeatedlyRequestWrapper; +import com.yxx.common.utils.SnowflakeConfig; +import com.yxx.framework.context.AppContext; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.MediaType; + +import java.io.IOException; + +/** + * Repeatable过滤器 + * + * @author yxx + * @since 2022/4/13 17:29 + */ +@Slf4j +public class RepeatableFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest httpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { + requestWrapper = new RepeatedlyRequestWrapper(httpServletRequest, response); + String traceId = httpServletRequest.getHeader("Trace-Id"); + if (CharSequenceUtil.isNotBlank(traceId)) { + log.info(traceId); + } else { + SnowflakeConfig snowflake = ApplicationUtils.getBean(SnowflakeConfig.class); + AppContext.getContext().setTraceId(String.valueOf(snowflake.snowflakeId())); + } + } + if (null == requestWrapper) { + chain.doFilter(request, response); + } else { + chain.doFilter(requestWrapper, response); + } + } + +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/hander/CommonMetaObjectHandler.java b/common/common-framework/src/main/java/com/yxx/framework/hander/CommonMetaObjectHandler.java new file mode 100644 index 0000000..797caae --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/hander/CommonMetaObjectHandler.java @@ -0,0 +1,68 @@ +package com.yxx.framework.hander; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.yxx.common.utils.auth.LoginAdminUtils; +import com.yxx.common.utils.auth.LoginUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.beans.factory.annotation.Value; + +import java.time.LocalDateTime; + +/** + * @author yxx + * @since 2022/4/13 11:23 + */ +@Slf4j +public class CommonMetaObjectHandler implements MetaObjectHandler { + @Value("${app.name}") + private String appName; + + /** + * 创建人 + */ + private static final String CREATE_UID = "createUid"; + + /** + * 更新人 + */ + private static final String UPDATE_UID = "updateUid"; + + /** + * 创建时间 + */ + private static final String CREATE_TIME = "createTime"; + + /** + * 修改时间 + */ + private static final String UPDATE_TIME = "updateTime"; + + @Override + public void insertFill(MetaObject metaObject) { + Long uid = currentUid(); + strictInsertFill(metaObject, CREATE_UID, Long.class, uid); + strictInsertFill(metaObject, UPDATE_UID, Long.class, uid); + strictInsertFill(metaObject, CREATE_TIME, LocalDateTime.class, LocalDateTime.now()); + strictInsertFill(metaObject, UPDATE_TIME, LocalDateTime.class, LocalDateTime.now()); + } + + @Override + public void updateFill(MetaObject metaObject) { + strictUpdateFill(metaObject, UPDATE_UID, Long.class, currentUid()); + strictUpdateFill(metaObject, UPDATE_TIME, LocalDateTime.class, LocalDateTime.now()); + } + + public Long currentUid() { + try { + if ("user".equals(appName)) { + return LoginUtils.getUserId(); + } else { + return LoginAdminUtils.getUserId(); + } + } catch (Exception ignore) { + log.error("生成uid错误"); + } + return 1L; + } +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/interceptor/log/LogAspect.java b/common/common-framework/src/main/java/com/yxx/framework/interceptor/log/LogAspect.java new file mode 100644 index 0000000..f94a275 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/interceptor/log/LogAspect.java @@ -0,0 +1,300 @@ +package com.yxx.framework.interceptor.log; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.yxx.common.annotation.log.OperationLog; +import com.yxx.common.constant.Constant; +import com.yxx.common.constant.EmailSubjectConstant; +import com.yxx.common.constant.RedisConstant; +import com.yxx.common.core.model.LogDTO; +import com.yxx.common.core.model.LoginUser; +import com.yxx.common.enums.LogTypeEnum; +import com.yxx.common.properties.IpProperties; +import com.yxx.common.properties.MailProperties; +import com.yxx.common.properties.MyWebProperties; +import com.yxx.common.utils.ServletUtils; +import com.yxx.common.utils.agent.UserAgentUtil; +import com.yxx.common.utils.auth.LoginUtils; +import com.yxx.common.utils.email.MailUtils; +import com.yxx.common.utils.ip.AddressUtil; +import com.yxx.common.utils.ip.IpUtil; +import com.yxx.common.utils.redis.RedissonCache; +import com.yxx.framework.context.AppContext; +import com.yxx.framework.service.OperationLogService; +import com.yxx.framework.service.impl.OperationLogDefaultServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.*; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.MDC; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import jakarta.servlet.http.HttpServletRequest; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +/** + * @author yxx + * @since 2022/11/12 03:21 + */ +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class LogAspect { + private final MailUtils mailUtils; + + private final MailProperties mailProperties; + + private final RedissonCache redissonCache; + + private final IpProperties ipProperties; + + private final MyWebProperties myWebProperties; + + /** + * 用来记录请求进入的时间,防止多线程时出错,这里用了ThreadLocal + */ + ThreadLocal startTime = new ThreadLocal<>(); + + private OperationLogService logService; + + /** + * 定义切入点,controller下面的所有类的所有公有方法 + */ + @Pointcut("execution(public * com.yxx..controller.*.*(..))") + public void requestLog() { + // document why this method is empty + } + + /** + * 方法之前执行,日志打印请求信息 + * + * @param joinPoint joinPoint + */ + @Before("requestLog()") + public void doBefore(JoinPoint joinPoint) { + MDC.put(AppContext.KEY_TRACE_ID, AppContext.getContext().getTraceId()); + startTime.set(System.currentTimeMillis()); + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + HttpServletRequest request = servletRequestAttributes.getRequest(); + //打印当前的请求路径 + log.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); + log.info("RequestMapping:[{}]", request.getRequestURI()); + + + //这里是从token中获取用户信息,打印当前的访问用户,代码不通用 + if (StpUtil.isLogin()) { + Object loginId = StpUtil.getLoginId(); + log.info("User is:" + loginId); + } + + // 打印请求参数,如果需要打印其他的信息可以到request中去拿 + log.info("RequestParam:{}", Arrays.toString(joinPoint.getArgs())); + log.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); + // 获取请求ip + String requestIp = IpUtil.getRequestIp(); + // 判断是否登录 + boolean isLogin = StpUtil.isLogin(); + if (isLogin) { + LoginUser loginUser = LoginUtils.getLoginUser(); + log.info("异步前"); + // 获取agent + String requestAgent = request.getHeader("user-agent"); + CompletableFuture.runAsync(() -> checkIpUnusual(requestIp, loginUser, requestAgent)); + log.info("异步后"); + } + } + + /** + * 方法返回之前执行,打印才返回值以及方法消耗时间 + * + * @param response 返回值 + */ + @AfterReturning(returning = "response", pointcut = "requestLog()") + public void doAfterRunning(Object response) { + try { + //打印返回值信息 + log.info("++++++++++++++++++++++++++++++++++++++++++++"); + ObjectMapper jsonMapper = new ObjectMapper(); + jsonMapper.registerModule(new JavaTimeModule()); + log.info("Response:[{}]", jsonMapper.writeValueAsString(response)); + //打印请求耗时 + log.info("Request spend times : [{}ms]", System.currentTimeMillis() - startTime.get()); + log.info("++++++++++++++++++++++++++++++++++++++++++++"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + startTime.remove(); + } + } + + public OperationLogService getLogService() { + try { + if (null == logService) { + logService = SpringUtil.getBean(OperationLogService.class); + } + } catch (NoSuchBeanDefinitionException e) { + log.warn("Please implement this OperationLogService interface"); + logService = new OperationLogDefaultServiceImpl(); + } + return logService; + } + + @Around("@annotation(operationLog)") + @SneakyThrows + public Object around(ProceedingJoinPoint point, OperationLog operationLog) { + String strClassName = point.getTarget().getClass().getName(); + String strMethodName = point.getSignature().getName(); + log.info("[类名]:{},[方法]:{}", strClassName, strMethodName); + + // 方法开始时间 + Long beginTime = System.currentTimeMillis(); + LogTypeEnum type = LogTypeEnum.NORMAL; + String exception = null; + Object obj; + try { + obj = point.proceed(); + } catch (Exception e) { + type = LogTypeEnum.ERROR; + exception = e.getMessage(); + throw e; + } finally { + // 结束时间 + Long endTime = System.currentTimeMillis(); + Long time = endTime - beginTime; + MethodSignature signature = (MethodSignature) point.getSignature(); + log.info("signature:[{}]", signature); + String module = operationLog.module(); + String title = operationLog.title(); + String traceId = AppContext.getContext().getTraceId(); + String spanId = null; + LogDTO dto = createLog(module, title, type, time, traceId, spanId, exception); + getLogService().saveLog(dto); + } + + return obj; + } + + private LogDTO createLog(String module, String title, LogTypeEnum logType, Long time, + String traceId, String spanId, String exception) { + HttpServletRequest request = ServletUtils.getRequest(); + + LogDTO logDTO = new LogDTO(); + logDTO.setModule(module); + logDTO.setTitle(title); + logDTO.setType(logType.getCode()); + + String ip = IpUtil.getRequestIp(); + log.info("当前IP为{}", ip); + if (Boolean.TRUE.equals(ipProperties.getCheck())) { + String ipHomePlace = AddressUtil.getIpHomePlace(ip, 2); + logDTO.setIpHomePlace(ipHomePlace); + } + logDTO.setIp(ip); + logDTO.setUserAgent(request.getHeader("user-agent")); + logDTO.setMethod(request.getMethod()); + logDTO.setTime(time); + logDTO.setException(exception); + + if (StpUtil.isLogin()) { + LoginUser loginUser = (LoginUser) StpUtil.getTokenSession().get(Constant.LOGIN_USER_KEY); + logDTO.setUserId(loginUser.getId()); + logDTO.setCreateUid(loginUser.getId()); + } + logDTO.setParams(ServletUtils.getRequestParms(request)); + logDTO.setRequestUri(request.getRequestURI()); + logDTO.setTraceId(traceId); + logDTO.setSpanId(spanId); + return logDTO; + } + + /** + * 检查ip是否异常 + * + * @param requestIp 请求ip + * @author yxx + */ + private void checkIpUnusual(String requestIp, LoginUser loginUser, String requestAgent) { + if (Boolean.TRUE.equals(ipProperties.getCheck())) { + log.info("开始校验ip是否异常~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + // 如果是有效ip 进行校验 + if (AddressUtil.isValidIPv4(requestIp) || AddressUtil.isIPv6Address(requestIp)) { + // 判断ip是否是ipv6 + boolean iPv6Address = AddressUtil.isIPv6Address(requestIp); + if (iPv6Address) { + log.info("该ip:{},为ipv6暂不解析", requestIp); + } + // 如果已经登录 并且是ipv4 则判断ip是否异常 + if (AddressUtil.isValidIPv4(requestIp)) { + log.info("校验ipv4"); + // 获取登录时ip属地 + String ipHomePlace = loginUser.getIpHomePlace(); + // 登录时设备名称 + String loginAgent = loginUser.getAgent(); + // 判断是否发送过ip异常邮件 如果没发送过,进行ip异常校验 + boolean exists = redissonCache.exists(RedisConstant.IP_UNUSUAL_OPERATE + loginUser.getId()); + if (!exists) { + // 当前操作的ip + String currentIpHomePlace = AddressUtil.getIpHomePlace(requestIp, 2); + String agent = UserAgentUtil.getAgent(requestAgent); + // 判断当前ip归属地和登录时ip归属地是否一致,如果不一致,发送邮件告知用户 + if (!currentIpHomePlace.equals(ipHomePlace) && !loginAgent.equals(agent)) { + log.info("校验完成,ip行为异常"); + // 邮件正文 + String unusual = AddressUtil.getIpHomePlace(requestIp, 3); + String time = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN); + String emailContent = mailProperties.getIpUnusualContent().replace("{time}", time) + .replace("{ip}", requestIp) + .replace("{address}", unusual) + .replace("{agent}", agent) + .replace("{formName}", mailProperties.getFromName()) + .replace("{form}", mailProperties.getFrom() + .replace("{domain}", myWebProperties.getDomain())); + // 发送邮件 + mailUtils.baseSendMail(loginUser.getEmail(), EmailSubjectConstant.IP_UNUSUAL, emailContent, true); + // 加入redis(一天提醒一次) + // 今天剩余时间 + Long residueTime = theRestOfTheDaySecond(); + redissonCache.put(RedisConstant.IP_UNUSUAL_OPERATE + loginUser.getId(), Boolean.TRUE, residueTime); + } + } + } + } else { + log.info("ip地址:{} 无法解析", requestIp); + } + log.info("校验ip是否异常结束~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + } + } + + /** + * 今天剩余时间(单位秒) + * + * @return {@link Long } + * @author yxx + */ + public static Long theRestOfTheDaySecond() { + // 获取当前日期和时间 + LocalDateTime now = LocalDateTime.now(); + + // 获取今晚的十二点整时间 + LocalDateTime midnight = now.toLocalDate().atTime(LocalTime.MAX); + + // 计算当前时间距离今晚十二点整的秒数 + Duration duration = Duration.between(now, midnight); + return duration.getSeconds(); + } +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/interceptor/response/ResponseResultInterceptor.java b/common/common-framework/src/main/java/com/yxx/framework/interceptor/response/ResponseResultInterceptor.java new file mode 100644 index 0000000..3c26444 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/interceptor/response/ResponseResultInterceptor.java @@ -0,0 +1,60 @@ +package com.yxx.framework.interceptor.response; + +import cn.dev33.satoken.stp.StpUtil; +import com.yxx.common.annotation.auth.ReleaseToken; +import com.yxx.common.annotation.response.ResponseResult; +import com.yxx.common.utils.satoken.StpAdminUtil; +import com.yxx.framework.context.AppContext; +import org.jetbrains.annotations.NotNull; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; + +/** + * @author yxx + * @since 2022/11/12 03:21 + */ +@Component +public class ResponseResultInterceptor implements HandlerInterceptor { + /** + * 标记名称 + */ + public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT"; + + @Value("${app.name}") + private String appName; + + @Override + public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, + @NotNull Object handler) { + if (handler instanceof HandlerMethod handlerMethod) { + final Class clazz = handlerMethod.getBeanType(); + final Method method = handlerMethod.getMethod(); + + // 判断是否在类对象上添加了格式化返回结果注解 + if (clazz.isAnnotationPresent(ResponseResult.class)) { + // 设置此请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口进行判断 + request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); + } else if (method.isAnnotationPresent(ResponseResult.class)) { + request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class)); + } + + // 判断方法上是否加了放行token校验的注解 + if ("user".equals(appName) && !method.isAnnotationPresent(ReleaseToken.class)) { + StpUtil.checkLogin(); + } + + if ("admin".equals(appName) && !method.isAnnotationPresent(ReleaseToken.class)) { + StpAdminUtil.checkLogin(); + } + } + MDC.put(AppContext.KEY_TRACE_ID, AppContext.getContext().getTraceId()); + return true; + } +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/listener/MySaTokenListener.java b/common/common-framework/src/main/java/com/yxx/framework/listener/MySaTokenListener.java new file mode 100644 index 0000000..8ae172a --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/listener/MySaTokenListener.java @@ -0,0 +1,120 @@ +package com.yxx.framework.listener; + +import cn.dev33.satoken.listener.SaTokenListener; +import cn.dev33.satoken.stp.SaLoginModel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 自定义侦听器的实现 + * + * @author yxx + * @since 2022/11/12 03:21 + */ +@Slf4j +@Component +public class MySaTokenListener implements SaTokenListener { + private static final String HALVING_LINE = "--------------------------------------------"; + /** + * 每次登录时触发 + */ + @Override + public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) { + log.info(HALVING_LINE); + log.info("ID为:[{}]的用户, 正在登录[{}]端", loginId, loginModel.getDevice()); + log.info(HALVING_LINE); + } + + /** + * 每次注销时触发 + */ + @Override + public void doLogout(String loginType, Object loginId, String tokenValue) { + log.info(HALVING_LINE); + log.info("ID为:[{}]的用户, 正在注销登录", loginId); + log.info(HALVING_LINE); + } + + /** + * 每次被踢下线时触发 + */ + @Override + public void doKickout(String loginType, Object loginId, String tokenValue) { + log.info(HALVING_LINE); + log.info("ID为:[{}]的用户, 被踢下线", loginId); + log.info(HALVING_LINE); + } + + /** + * 每次被顶下线时触发 + */ + @Override + public void doReplaced(String loginType, Object loginId, String tokenValue) { + log.info(HALVING_LINE); + log.info("ID为:[{}]的用户, 被顶下线,token值为[{}]", loginId, tokenValue); + log.info(HALVING_LINE); + } + + /** + * 每次被封禁时触发 + */ + @Override + public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) { + long day = disableTime / 86400L; + long hour = (disableTime % 86400) / 3600L; + long minute = (disableTime % 86400) % 3600L / 60L; + long second = (disableTime % 86400) % 3600L % 60L; + log.info(HALVING_LINE); + log.info("ID为:[{}]的用户, 被封禁账号,封禁时间为[{}]天[{}]小时[{}]分钟[{}]秒", loginId, day, hour, minute, second); + log.info(HALVING_LINE); + } + + /** + * 每次被解封时触发 + */ + @Override + public void doUntieDisable(String loginType, Object loginId, String service) { + log.info(HALVING_LINE); + log.info("ID为:[{}]的用户, 已经解封", loginId); + log.info(HALVING_LINE); + } + + /** 每次二级认证时触发 */ + @Override + public void doOpenSafe(String s, String s1, String s2, long l) { + log.info("---------- 自定义侦听器实现 doOpenSafe"); + } + + /** 每次退出二级认证时触发 */ + @Override + public void doCloseSafe(String s, String s1, String s2) { + log.info("---------- 自定义侦听器实现 doOpenSafe"); + } + + /** + * 每次创建Session时触发 + */ + @Override + public void doCreateSession(String id) { + log.info("session被创建"); + } + + /** + * 每次注销Session时触发 + */ + @Override + public void doLogoutSession(String id) { + log.info("session被注销"); + } + + /** + * 每次Token续期时触发 + */ + @Override + public void doRenewTimeout(String tokenValue, Object loginId, long timeout) { + log.info(HALVING_LINE); + log.info("ID为:[{}]的用户, token被续期,续期时间为[{}]秒", loginId, timeout); + log.info(HALVING_LINE); + } +} + diff --git a/common/common-framework/src/main/java/com/yxx/framework/service/OperationLogService.java b/common/common-framework/src/main/java/com/yxx/framework/service/OperationLogService.java new file mode 100644 index 0000000..159f6e6 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/service/OperationLogService.java @@ -0,0 +1,19 @@ +package com.yxx.framework.service; + + +import com.yxx.common.core.model.LogDTO; + +/** + * + * @author yxx + * @since 2022/7/26 11:29 + */ +public interface OperationLogService { + + /** + * 保存操作日志 + * + * @param dto LogDTO + */ + void saveLog(LogDTO dto); +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/service/impl/OperationLogDefaultServiceImpl.java b/common/common-framework/src/main/java/com/yxx/framework/service/impl/OperationLogDefaultServiceImpl.java new file mode 100644 index 0000000..3efc1ea --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/service/impl/OperationLogDefaultServiceImpl.java @@ -0,0 +1,20 @@ +package com.yxx.framework.service.impl; + +import com.yxx.common.core.model.LogDTO; +import com.yxx.common.utils.jackson.JacksonUtil; +import com.yxx.framework.service.OperationLogService; +import lombok.extern.slf4j.Slf4j; + +/** + * + * @author yxx + * @since 2022/7/26 14:44 + */ +@Slf4j +public class OperationLogDefaultServiceImpl implements OperationLogService { + + @Override + public void saveLog(LogDTO dto) { + log.info("操作日志====> {}", JacksonUtil.toJson(dto)); + } +} diff --git a/common/common-framework/src/main/java/com/yxx/framework/service/impl/SaInterfaceImpl.java b/common/common-framework/src/main/java/com/yxx/framework/service/impl/SaInterfaceImpl.java new file mode 100644 index 0000000..ebad958 --- /dev/null +++ b/common/common-framework/src/main/java/com/yxx/framework/service/impl/SaInterfaceImpl.java @@ -0,0 +1,31 @@ +package com.yxx.framework.service.impl; + +import cn.dev33.satoken.stp.StpInterface; +import com.yxx.common.core.model.LoginUser; +import com.yxx.common.utils.auth.LoginUtils; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author yxx + * @since 2022/4/13 14:21 + */ +@Component +public class SaInterfaceImpl implements StpInterface { + + @Override + public List getPermissionList(Object loginId, String loginType) { + LoginUser loginUser = LoginUtils.getLoginUser(); + List permissions = loginUser.getMenuPermission(); + permissions.addAll(loginUser.getButtonPermission()); + return new ArrayList<>(permissions); + } + + @Override + public List getRoleList(Object loginId, String loginType) { + LoginUser loginUser = LoginUtils.getLoginUser(); + return new ArrayList<>(loginUser.getRolePermission()); + } +} diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 0000000..1826d94 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + common + pom + common + + + base + com.yxx + 1.0.0 + + + common-core + common-framework + + + diff --git a/db/db.sql b/db/db.sql new file mode 100644 index 0000000..aa75e96 --- /dev/null +++ b/db/db.sql @@ -0,0 +1,208 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for admin_menu +-- ---------------------------- +DROP TABLE IF EXISTS `admin_menu`; +CREATE TABLE `admin_menu` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键', + `parent_id` int DEFAULT NULL COMMENT '父id。顶级节点为null', + `menu_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单标识', + `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称', + `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除: 0- 否; 1- 是', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理段-菜单表'; + +-- ---------------------------- +-- Table structure for admin_role +-- ---------------------------- +DROP TABLE IF EXISTS `admin_role`; +CREATE TABLE `admin_role` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '业务主键', + `code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '编码', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注', + `is_delete` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除', + `update_time` datetime NOT NULL COMMENT '修改时间', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理段-角色表'; + +-- ---------------------------- +-- Table structure for admin_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `admin_role_menu`; +CREATE TABLE `admin_role_menu` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键', + `role_id` int NOT NULL COMMENT '角色id', + `menu_id` int NOT NULL COMMENT '菜单id', + `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-否;1-是', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理段-角色—菜单关联表'; + +-- ---------------------------- +-- Table structure for admin_user +-- ---------------------------- +DROP TABLE IF EXISTS `admin_user`; +CREATE TABLE `admin_user` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', + `login_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录账号', + `login_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名称', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', + `link_phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '联系号码', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '邮箱', + `ip_home_place` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'ip归属地', + `agent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '登录设备名称', + `student_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '学号', + `update_time` datetime NOT NULL COMMENT '修改时间', + `create_time` datetime NOT NULL COMMENT '创建时间', + `create_uid` bigint NOT NULL COMMENT '创建人', + `update_uid` bigint NOT NULL COMMENT '修改人', + `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理段-用户表'; + +-- ---------------------------- +-- Table structure for admin_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `admin_user_role`; +CREATE TABLE `admin_user_role` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '业务主键', + `user_id` bigint NOT NULL COMMENT '用户id', + `role_id` int NOT NULL COMMENT '角色id', + `is_delete` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除', + `update_time` datetime NOT NULL COMMENT '修改时间', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理段-用户角色关联表'; + +-- ---------------------------- +-- Table structure for menu +-- ---------------------------- +DROP TABLE IF EXISTS `menu`; +CREATE TABLE `menu` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键', + `parent_id` int DEFAULT NULL COMMENT '父id。顶级节点为null', + `menu_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单标识', + `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称', + `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除: 0- 否; 1- 是', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='菜单表'; + +-- ---------------------------- +-- Table structure for operate_admin_log +-- ---------------------------- +DROP TABLE IF EXISTS `operate_admin_log`; +CREATE TABLE `operate_admin_log` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` bigint DEFAULT NULL COMMENT '用户id', + `type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '类型:1-正常日志 2-异常日志', + `module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作模块', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '日志标题', + `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作IP', + `ip_home_place` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'ip归属地', + `user_agent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户代理', + `request_uri` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '请求URI', + `method` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作方式', + `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '操作提交的数据', + `trace_id` bigint DEFAULT NULL COMMENT 'tLog中的traceId', + `span_id` int DEFAULT NULL COMMENT 'tLog中的spanId', + `time` bigint DEFAULT NULL COMMENT '执行时间', + `exception` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '异常信息', + `create_uid` bigint DEFAULT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除 0-否 1-是', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操作日志表'; + +-- ---------------------------- +-- Table structure for operate_log +-- ---------------------------- +DROP TABLE IF EXISTS `operate_log`; +CREATE TABLE `operate_log` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` bigint DEFAULT NULL COMMENT '用户id', + `type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '类型:1-正常日志 2-异常日志', + `module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作模块', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '日志标题', + `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作IP', + `ip_home_place` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'ip归属地', + `user_agent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户代理', + `request_uri` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '请求URI', + `method` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作方式', + `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '操作提交的数据', + `trace_id` bigint DEFAULT NULL COMMENT 'tLog中的traceId', + `span_id` int DEFAULT NULL COMMENT 'tLog中的spanId', + `time` bigint DEFAULT NULL COMMENT '执行时间', + `exception` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '异常信息', + `create_uid` bigint DEFAULT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除 0-否 1-是', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操作日志表'; + +-- ---------------------------- +-- Table structure for role +-- ---------------------------- +DROP TABLE IF EXISTS `role`; +CREATE TABLE `role` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '业务主键', + `code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '编码', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注', + `is_delete` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除', + `update_time` datetime NOT NULL COMMENT '修改时间', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色表'; + +-- ---------------------------- +-- Table structure for role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `role_menu`; +CREATE TABLE `role_menu` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键', + `role_id` int NOT NULL COMMENT '角色id', + `menu_id` int NOT NULL COMMENT '菜单id', + `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-否;1-是', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色—菜单关联表'; + +-- ---------------------------- +-- Table structure for user +-- ---------------------------- +DROP TABLE IF EXISTS `user`; +CREATE TABLE `user` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', + `login_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录账号', + `login_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名称', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', + `link_phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '联系号码', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '邮箱', + `ip_home_place` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'ip归属地', + `agent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '登录设备名称', + `student_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '学号', + `update_time` datetime NOT NULL COMMENT '修改时间', + `create_time` datetime NOT NULL COMMENT '创建时间', + `create_uid` bigint NOT NULL COMMENT '创建人', + `update_uid` bigint NOT NULL COMMENT '修改人', + `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; + +-- ---------------------------- +-- Table structure for user_role +-- ---------------------------- +DROP TABLE IF EXISTS `user_role`; +CREATE TABLE `user_role` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '业务主键', + `user_id` bigint NOT NULL COMMENT '用户id', + `role_id` int NOT NULL COMMENT '角色id', + `is_delete` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除', + `update_time` datetime NOT NULL COMMENT '修改时间', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户角色关联表'; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..af97d6a --- /dev/null +++ b/pom.xml @@ -0,0 +1,159 @@ + + + 4.0.0 + com.yxx + base + 1.0.0 + base-springboot + base-springboot + pom + + + common + business + admin + + + + 17 + UTF-8 + UTF-8 + 3.0.2 + 3.5.3 + 8.0.30 + 1.35.0.RC + 3.12.0 + 2.11.1 + 2.19.0 + 1.18.28 + 5.8.21 + 1.5.27 + 3.17.0 + 4.1.32.Final + 2.9.3 + 1.9.3 + 2.6.4 + 2.6 + 10.1.5 + 2.11.4 + + + + + dev + + dev + + + true + + + + prod + + prod + + + false + + + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + + org.springframework.boot + spring-boot-starter-validation + ${spring-boot.version} + + + + org.springframework.boot + spring-boot-configuration-processor + true + ${spring-boot.version} + + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + + org.projectlombok + lombok + ${lombok.version} + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + ${spring-boot.version} + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + ${project.build.finalName} + + + + + repackage + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + UTF-8 + + + + + + + src/main/resources + true + + + +