diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..549e00a2a96fa9d7c5dbc9859664a78d980158c2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/README.en.md b/README.en.md
deleted file mode 100644
index 410b3a1b61e3295adaef6c0ff86808aaa338bf5e..0000000000000000000000000000000000000000
--- a/README.en.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# exam-question
-
-#### Description
-{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
-
-#### Software Architecture
-Software architecture description
-
-#### Installation
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### Instructions
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### Contribution
-
-1. Fork the repository
-2. Create Feat_xxx branch
-3. Commit your code
-4. Create Pull Request
-
-
-#### Gitee Feature
-
-1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
-2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
-3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
-4. The most valuable open source project [GVP](https://gitee.com/gvp)
-5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
-6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
diff --git a/README.md b/README.md
index 0d6bdd4be7245b51823ccb082cfb757b309d95ba..fbffa6f2d5ddd978fd2b6059e1fdcad3783c5eef 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,175 @@
-# exam-question
+# 乐考刷
-#### 介绍
-{**以下是 Gitee 平台说明,您可以替换此简介**
-Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
-无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
+## 介绍
+乐考刷在线考试刷题系统具备强大的功能集,包括但不限于租户管理、用户管理、部门管理、题库管理、题目管理、在线考试、随机出题、按知识点出题以及在线刷题等。通过灵活的配置和直观的界面,用户可以轻松创建和参与各种类型的测试活动。系统基于Spring Boot 3构建,采用微服务架构设计。该架构允许系统各部分独立开发、部署和扩展,提高了系统的灵活性和可维护性。通过使用Spring Cloud的相关组件,实现了服务发现、配置管理、负载均衡、熔断器等功能,确保了系统的高可用性和稳定性。
-#### 软件架构
-软件架构说明
+## 功能说明
+### 1. 租户管理
-#### 安装教程
+- 支持多租户架构,允许不同组织独立使用同一平台。
+- 租户可以自定义品牌化设置(如Logo、颜色主题)。
+- 每个租户拥有独立的数据存储空间,确保数据隔离。
+- 可复用于其他项目,便于快速部署新的业务场景。
-1. xxxx
-2. xxxx
-3. xxxx
+### 2. 用户管理
-#### 使用说明
+- 用户角色划分(管理员、教师、学生等),权限控制精细。
+- 支持批量导入/导出用户信息,简化用户管理流程。
+- 提供用户活动日志记录,便于审计追踪。
+- 集成第三方认证服务,支持多种登录方式(如OAuth2, SSO)。
-1. xxxx
-2. xxxx
-3. xxxx
+### 3. 部门管理
-#### 参与贡献
+- 组织结构树形展示,清晰呈现各部门层级关系。
+- 支持部门间转移人员,调整组织架构。
+- 自动同步部门变动至相关联的应用模块。
+- 同样适用于非考试类项目的组织管理需求。
-1. Fork 本仓库
-2. 新建 Feat_xxx 分支
-3. 提交代码
-4. 新建 Pull Request
+### 4. 题库管理
+- 创建和分类管理各类题库,涵盖不同学科领域。
+- 题库可共享或私有化,根据需要设定访问权限。
+- 提供题库版本控制,方便更新维护。
-#### 特技
+### 5. 题目管理
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
-4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
-5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
+- 多种题型支持(选择题、填空题、简答题等)。
+- 图片、音频、视频等多媒体元素嵌入。
+- 题目难度分级,适应不同层次的学习者。
+- 题目解析与答案提示,帮助用户理解正确答案。
+
+### 6. 在线考试
+
+- 定时开考、限时作答,模拟真实考场环境。
+- 考试过程中防作弊机制(如摄像头监控、屏幕锁定)。
+- 实时统计考试进度,成绩即时反馈。
+
+### 7. 随机出题
+
+- 根据设定规则自动从题库中抽取题目组成试卷。
+- 支持指定题目数量、题型比例等参数。
+
+### 8. 按知识点出题
+
+- 精准定位知识点,构建有针对性的试题集合。
+- 有助于评估特定领域的知识掌握情况。
+
+### 9. 在线刷题
+
+- 个性化推荐练习题目,依据用户的答题历史和薄弱点。
+- 刷题记录保存,便于回顾复习。
+- 成绩跟踪与进步分析,激励持续学习。
+
+## 模块设计
+
+### 模块划分
+
+乐考刷在线考试刷题系统包含系统服务、API网关、消息服务、题目服务、考试服务、刷题服务、日志服务等7个模块。
+
+### 模块描述
+
+#### API网关
+
+API网关作为单一入口点来管理API调用之间的交互,能够将多个微服务整合在一起,为外部提供统一且简化的接口,其职责为:
+
+1. **统一入口点**:API网关作为所有外部请求进入系统的唯一通道,它可以有效地隐藏后端微服务的具体部署信息,保护内部架构不被外界直接访问。
+
+2. **请求路由与负载均衡**:API网关可以根据预定义的规则将请求分发给正确的后端服务实例。这不仅包括基本的路径匹配,还可以基于内容类型、地理位置、用户身份等因素进行更复杂的路由决策。此外,API网关通常内置了负载均衡机制,能够在多个服务实例之间分配流量,确保即使某个实例出现故障,也不会影响整体的服务可用性。
+
+3. **安全性增强**:为了保障API的安全调用,API网关实施了严格的访问控制措施,如IP黑白名单、认证鉴权、防重放攻击、防止恶意请求等。这些安全策略可以集中配置在API网关层面,避免了每个微服务都需要单独处理这些问题,大大降低了开发成本和潜在的风险。
+
+4. **流量管理和限流**:API网关支持多种算法实现精细化的流量控制,例如固定窗口、滑动窗口、令牌桶等。通过灵活自定义的流量控制策略,API网关可以帮助应对突发的大流量冲击,保证API服务的稳定性和连续性。当检测到异常高的请求量时,API网关还可以自动触发限流措施,限制某些用户的请求频率,防止过载。
+
+5. **监控与日志记录**:API网关是监控微服务运行状况的理想位置,因为它可以捕获所有的入站和出站流量。通过集成监控工具,API网关能够实时收集有关API性能、延迟、错误率等关键指标的数据,并生成详细的日志文件供后续分析使用。这对于快速定位问题根源、优化系统性能至关重要。
+
+6. **版本管理和服务演进**:随着业务的发展和技术的进步,API可能会经历多次迭代更新。API网关允许开发者在同一URL下同时维护多个版本的API,确保旧版本的兼容性不受新版本的影响。同时,API网关也支持灰度发布功能,使得新的特性或改进可以在不影响现有用户的情况下逐步推广。
+
+7. **静态响应处理**:对于一些不需要动态计算的结果,API网关可以直接返回预先准备好的静态响应,减少对后端服务的压力。这类场景特别适用于那些访问频率高但数据变化较少的资源,如网站的常见问题解答页面、API文档等。
+
+#### 系统服务
+
+系统服务主要包含管理维护租户、用户、部门、角色、权限、菜单等系统基础数据,其职责为:
+
+1. **用户管理**:管理用户信息,包括注册、登录、权限管理等,支持OAuth2和JWT进行身份验证。
+2. **部门管理**:维护组织结构,处理部门间的层级关系,支持部门人员的添加、删除和转移操作。
+3. **租户管理**:管理租户信息,支持多租户架构,实现租户级别的数据隔离,提供品牌化设置接口。
+4. **角色管理**:管理维护系统角色信息,包括角色数据权限管理、菜单权限管理等。
+5. **菜单管理**:维护菜单结构,处理菜单之间的层级管理,可修改、删除菜单图标、名称、对应路径等数据。
+
+#### 消息服务
+
+todo
+
+#### 题目服务
+
+todo
+
+#### 考试服务
+
+todo
+
+#### 刷题服务
+
+todo
+
+#### 日志服务
+
+todo
+
+## 数据库设计
+
+### 数据库选型
+
+乐考刷在线考试刷题系统使用MySQL数据库,原因如下:
+
+- MySQL是一种高性能的关系型数据库管理系统,具有优秀的读写速度。通过优化数据库结构,如范式化设计、选择合适的数据类型、添加索引等,可以进一步提高MySQL的读写性能。
+- MySQL的数据管理能力非常关键。通过数据库分区、分表等技术,MySQL可以更好地管理数据,提高查询效率。
+- MySQL具有良好的可扩展性,可以随着系统的增长而不断扩展。无论是通过增加硬件资源还是通过分布式架构,MySQL都能够应对不断增长的数据量和用户量。
+- 报警管理服务数据请求并发量较小,使用MySQL即可很好地存储和管理数据。
+
+### 数据库结构
+
+#### 表设计
+
+系统用户信息表(sys_user)如下:
+
+| 字段名称 | 字段类型 | 长度 | 是否可为null | 描述 | 备注 |
+| ----------- | --------- | ---- | ------------ | ------------ | ----------------------- |
+| user_id | int | | 否 | 用户id,主键 | |
+| username | varchar | 64 | 否 | 用户名称 | |
+| nickname | varchar | 64 | 否 | 用户昵称 | |
+| password | varchar | 512 | 否 | 密码 | |
+| phone | varchar | 11 | 否 | 手机号 | |
+| email | varchar | 64 | 是 | 邮箱 | |
+| sex | tinyint | | 是 | 性别 | 0男,1女,3未知 |
+| avatar | varchar | 512 | 是 | 头像 | |
+| user_status | tinyint | | 否 | 用户状态 | 0可用,1禁用,默认为0 |
+| remark | varchar | 512 | 是 | 备注 | |
+| dept_id | int | | 否 | 部门id | |
+| tenant_id | int | | 否 | 租户id | |
+| create_time | timestamp | | 否 | 创建时间 | |
+| creator_id | int | | 否 | 创建者id | |
+| update_time | timestamp | | 是 | 更新时间 | |
+| updater_id | int | | 是 | 更新者id | |
+| deleted | tinyint | | 否 | 删除标志 | 0未删除,1删除,默认为0 |
+
+
+
+## 技术栈
+
+- **前端**:Vue.js
+- **后端**:SpringBoot 3.4.0
+- **数据库**:MySQL
+- **缓存**:Redis
+- **搜索**:Elasticsearch
+- **部署**:Docker
+- **API文档生成**:Swagger
+
+## 贡献代码
+
+我们欢迎任何形式的贡献,无论是修复bug、优化性能还是新增特性。请参照[贡献指南](https://tongyi.aliyun.com/qianwen/CONTRIBUTING.md)了解如何参与项目开发。
+
+## 版权声明
+
+本项目遵循MIT许可证发布。更多信息,请参阅[LICENSE](https://tongyi.aliyun.com/qianwen/LICENSE)文件。
diff --git a/common-api/pom.xml b/common-api/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bd78451932eac23056b012ae717b3c2722d8c4cf
--- /dev/null
+++ b/common-api/pom.xml
@@ -0,0 +1,59 @@
+
+
+ 4.0.0
+
+ cn.edu.whut
+ exam-question
+ 0.0.1-SNAPSHOT
+
+
+
+
+
+ common-api
+ 0.0.1-SNAPSHOT
+ common-api
+ common-api
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ com.github.xiaoymin
+ knife4j-openapi3-jakarta-spring-boot-starter
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 3.3.7
+
+
+
+
+
diff --git a/common-api/src/main/java/cn/edu/whut/commonapi/core/api/ApiResult.java b/common-api/src/main/java/cn/edu/whut/commonapi/core/api/ApiResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..c8c8f9d749a9870021022dc49b1e0f851a3da346
--- /dev/null
+++ b/common-api/src/main/java/cn/edu/whut/commonapi/core/api/ApiResult.java
@@ -0,0 +1,85 @@
+package cn.edu.whut.commonapi.core.api;
+
+
+import cn.edu.whut.commonapi.core.error.CommonErrorCodes;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * api接口返回信息封装
+ */
+@Schema(description = "API接口返回信息封装")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Data
+public class ApiResult implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 状态码
+ */
+ @Schema(description = "状态码")
+ private Integer code;
+
+ /**
+ * 提示信息
+ */
+ @Schema(description = "响应信息")
+ private String msg;
+
+ /**
+ * 数据
+ */
+ @Schema(description = "数据")
+ private T data;
+
+ public ApiResult(){
+
+ }
+
+ public ApiResult(int code, String msg){
+ this.code = code;
+ this.msg = msg;
+ }
+
+ public ApiResult(int code, String msg, T data){
+ this.code = code;
+ this.msg = msg;
+ this.data = data;
+ }
+
+ public ApiResult(T data){
+ this.code = CommonErrorCodes.SUCCESS.code();
+ this.msg = CommonErrorCodes.SUCCESS.msg();
+ this.data = data;
+ }
+
+ public ApiResult(CommonErrorCodes errorCode, T data) {
+ this.code = errorCode.code();
+ this.msg = errorCode.msg();
+ this.data = data;
+ }
+
+ public static ApiResult success(T data){
+ return (ApiResult) new ApiResult(data);
+ }
+
+ public static ApiResult success(){
+ return (ApiResult) new ApiResult(CommonErrorCodes.SUCCESS.code(), CommonErrorCodes.SUCCESS.msg());
+ }
+
+ public static ApiResult error(int code, String msg){
+ return new ApiResult<>(code, msg);
+ }
+
+ public static ApiResult error(CommonErrorCodes errorCode){
+ return new ApiResult<>(errorCode, null);
+ }
+
+}
diff --git a/common-api/src/main/java/cn/edu/whut/commonapi/core/domain/BaseEntity.java b/common-api/src/main/java/cn/edu/whut/commonapi/core/domain/BaseEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e087d905ff4a19eb91d08a3c5bee445443a6fed
--- /dev/null
+++ b/common-api/src/main/java/cn/edu/whut/commonapi/core/domain/BaseEntity.java
@@ -0,0 +1,47 @@
+package cn.edu.whut.commonapi.core.domain;
+
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+@Data
+@JsonIgnoreProperties(value = "transMap")
+public class BaseEntity implements Serializable {
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Timestamp createTime;
+
+ /**
+ * 最后更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Timestamp updateTime;
+
+ /**
+ * 创建者id
+ *
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Integer creatorId;
+
+ /**
+ * 更新者id
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Integer updaterId;
+
+ /**
+ * 是否删除
+ */
+ @TableLogic
+ private Integer deleted;
+}
diff --git a/common-api/src/main/java/cn/edu/whut/commonapi/core/domain/PageEntity.java b/common-api/src/main/java/cn/edu/whut/commonapi/core/domain/PageEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..38821c6e2df6181ea654ec3152664fe57357db26
--- /dev/null
+++ b/common-api/src/main/java/cn/edu/whut/commonapi/core/domain/PageEntity.java
@@ -0,0 +1,23 @@
+package cn.edu.whut.commonapi.core.domain;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import lombok.Data;
+
+@Data
+@Schema(description = "分页参数 Request DTO")
+public class PageEntity {
+
+ /** 当前记录起始索引 */
+ @Schema(description = "当前记录起始索引", example = "0", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Min(value = 0, message = "当前记录起始索引不能小于0")
+ private Integer pageNum;
+
+ /** 每页显示记录数 */
+ @Schema(description = "每页显示记录数", example = "10", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Min(value = 1, message = "每页显示记录数不能小于1")
+ @Max(value = 100, message = "每页显示记录数不能大于100")
+ private Integer pageSize;
+
+}
diff --git a/common-api/src/main/java/cn/edu/whut/commonapi/core/error/CommonErrorCodes.java b/common-api/src/main/java/cn/edu/whut/commonapi/core/error/CommonErrorCodes.java
new file mode 100644
index 0000000000000000000000000000000000000000..96f84b9b0abab141470eeea50ef390afe86aa8a6
--- /dev/null
+++ b/common-api/src/main/java/cn/edu/whut/commonapi/core/error/CommonErrorCodes.java
@@ -0,0 +1,39 @@
+package cn.edu.whut.commonapi.core.error;
+
+public enum CommonErrorCodes implements ErrorCode{
+
+ SUCCESS(0, "成功"),
+
+ PARAM_ERROR(400, "参数错误"),
+
+ NOT_FOUND(404, "未找到资源"),
+
+ UNAUTHORIZED(401, "未授权"),
+
+ FORBIDDEN(403, "禁止访问"),
+
+ NOT_IMPLEMENTED(501, "未实现"),
+
+ SERVICE_UNAVAILABLE(503, "服务不可用"),
+
+ GATEWAY_TIMEOUT(504, "网关超时");
+
+ private int code;
+
+ private String msg;
+
+ CommonErrorCodes(int code, String msg){
+ this.code = code;
+ this.msg = msg;
+ }
+
+ @Override
+ public int code() {
+ return this.code;
+ }
+
+ @Override
+ public String msg() {
+ return this.msg;
+ }
+}
diff --git a/common-api/src/main/java/cn/edu/whut/commonapi/core/error/CommonException.java b/common-api/src/main/java/cn/edu/whut/commonapi/core/error/CommonException.java
new file mode 100644
index 0000000000000000000000000000000000000000..a502d8e8d72cf6e0378ccc6719560b75d7730a3d
--- /dev/null
+++ b/common-api/src/main/java/cn/edu/whut/commonapi/core/error/CommonException.java
@@ -0,0 +1,24 @@
+package cn.edu.whut.commonapi.core.error;
+
+import lombok.Getter;
+
+@Getter
+public class CommonException extends RuntimeException{
+
+ private int code;
+
+ public CommonException(String message, int code) {
+ super(message);
+ this.code = code;
+ }
+
+ public CommonException(String message, Throwable cause, int code) {
+ super(message, cause);
+ this.code = code;
+ }
+
+ public CommonException(Throwable cause, int code) {
+ super(cause);
+ this.code = code;
+ }
+}
diff --git a/common-api/src/main/java/cn/edu/whut/commonapi/core/error/ErrorCode.java b/common-api/src/main/java/cn/edu/whut/commonapi/core/error/ErrorCode.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bad29bcab95919191d2a505e4506627281e4cdc
--- /dev/null
+++ b/common-api/src/main/java/cn/edu/whut/commonapi/core/error/ErrorCode.java
@@ -0,0 +1,8 @@
+package cn.edu.whut.commonapi.core.error;
+
+public interface ErrorCode {
+
+ int code();
+
+ String msg();
+}
diff --git a/common-api/src/test/java/cn/edu/whut/commonapi/CommonApiApplicationTests.java b/common-api/src/test/java/cn/edu/whut/commonapi/CommonApiApplicationTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..83447b9b1c03d7a63beecce8631b3d3fa8e3b24a
--- /dev/null
+++ b/common-api/src/test/java/cn/edu/whut/commonapi/CommonApiApplicationTests.java
@@ -0,0 +1,13 @@
+package cn.edu.whut.commonapi;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class CommonApiApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/gateway-server/pom.xml b/gateway-server/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4be4e7e8ec13428091b8ff4c15f4ac22b2f9eb57
--- /dev/null
+++ b/gateway-server/pom.xml
@@ -0,0 +1,67 @@
+
+
+ 4.0.0
+
+ cn.edu.whut
+ exam-question
+ 0.0.1-SNAPSHOT
+
+
+
+ gateway-server
+ 0.0.1-SNAPSHOT
+ gateway-server
+ gateway-server
+
+
+ 17
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-loadbalancer
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ cn.hutool
+ hutool-all
+
+
+ cn.edu.whut
+ common-api
+ 0.0.1-SNAPSHOT
+ compile
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/gateway-server/src/main/java/cn/edu/whut/gateway/GatewayServerApplication.java b/gateway-server/src/main/java/cn/edu/whut/gateway/GatewayServerApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..a61336de770928e810dc762dc18e5fae6261df77
--- /dev/null
+++ b/gateway-server/src/main/java/cn/edu/whut/gateway/GatewayServerApplication.java
@@ -0,0 +1,16 @@
+package cn.edu.whut.gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+
+@SpringBootApplication
+@EnableConfigurationProperties
+public class GatewayServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(GatewayServerApplication.class, args);
+ }
+
+}
diff --git a/gateway-server/src/main/java/cn/edu/whut/gateway/config/SecurityConfig.java b/gateway-server/src/main/java/cn/edu/whut/gateway/config/SecurityConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f957649fdc7433dab032305c0cec922499a56ae
--- /dev/null
+++ b/gateway-server/src/main/java/cn/edu/whut/gateway/config/SecurityConfig.java
@@ -0,0 +1,26 @@
+package cn.edu.whut.gateway.config;
+
+import cn.edu.whut.gateway.config.properties.JwtConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
+
+import java.security.KeyPair;
+
+@Configuration
+@EnableConfigurationProperties(JwtConfigurationProperties.class)
+public class SecurityConfig {
+
+ @Bean
+ public KeyPair keyPair(JwtConfigurationProperties jwtConfigurationProperties){
+ // 获取秘钥工厂
+ KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
+ jwtConfigurationProperties.getLocation(),
+ jwtConfigurationProperties.getPassword().toCharArray());
+ return keyStoreKeyFactory.getKeyPair(
+ jwtConfigurationProperties.getAlias(),
+ jwtConfigurationProperties.getPassword().toCharArray()
+ );
+ }
+}
diff --git a/gateway-server/src/main/java/cn/edu/whut/gateway/config/properties/AuthConfigurationProperties.java b/gateway-server/src/main/java/cn/edu/whut/gateway/config/properties/AuthConfigurationProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..3eb4119003188b059c2f103de746973fc6f0f7ba
--- /dev/null
+++ b/gateway-server/src/main/java/cn/edu/whut/gateway/config/properties/AuthConfigurationProperties.java
@@ -0,0 +1,19 @@
+package cn.edu.whut.gateway.config.properties;
+
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@Data
+@ConfigurationProperties(prefix = "gateway.auth")
+@Configuration
+public class AuthConfigurationProperties {
+
+ private List includePaths;
+
+
+ private List excludePaths;
+}
diff --git a/gateway-server/src/main/java/cn/edu/whut/gateway/config/properties/JwtConfigurationProperties.java b/gateway-server/src/main/java/cn/edu/whut/gateway/config/properties/JwtConfigurationProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..44cece71a22090e2a28b1bd558a7ed3c08559903
--- /dev/null
+++ b/gateway-server/src/main/java/cn/edu/whut/gateway/config/properties/JwtConfigurationProperties.java
@@ -0,0 +1,20 @@
+package cn.edu.whut.gateway.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.core.io.Resource;
+
+import java.time.Duration;
+
+@Data
+@ConfigurationProperties(prefix = "gateway.jwt")
+public class JwtConfigurationProperties {
+
+ private Resource location;
+
+ private String password;
+
+ private String alias;
+
+ private Duration tokenExpire = Duration.ofMinutes(10);
+}
diff --git a/gateway-server/src/main/java/cn/edu/whut/gateway/error/UnauthorizedException.java b/gateway-server/src/main/java/cn/edu/whut/gateway/error/UnauthorizedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..50a9237f5227b1309032454f0e4047c8ff5604c3
--- /dev/null
+++ b/gateway-server/src/main/java/cn/edu/whut/gateway/error/UnauthorizedException.java
@@ -0,0 +1,18 @@
+package cn.edu.whut.gateway.error;
+
+import cn.edu.whut.commonapi.core.error.CommonException;
+
+public class UnauthorizedException extends CommonException {
+
+ public UnauthorizedException(String message) {
+ super(message, 401);
+ }
+
+ public UnauthorizedException(String message, Throwable cause) {
+ super(message, cause, 401);
+ }
+
+ public UnauthorizedException(Throwable cause) {
+ super(cause, 401);
+ }
+}
diff --git a/gateway-server/src/main/java/cn/edu/whut/gateway/filter/AuthGlobalFilter.java b/gateway-server/src/main/java/cn/edu/whut/gateway/filter/AuthGlobalFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e89069e3ebdeba6796bcb58bcaa6667f63b61da
--- /dev/null
+++ b/gateway-server/src/main/java/cn/edu/whut/gateway/filter/AuthGlobalFilter.java
@@ -0,0 +1,30 @@
+package cn.edu.whut.gateway.filter;
+
+import cn.edu.whut.gateway.config.properties.AuthConfigurationProperties;
+import cn.edu.whut.gateway.util.JwtUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+@Component
+@EnableConfigurationProperties(AuthConfigurationProperties.class)
+@RequiredArgsConstructor
+public class AuthGlobalFilter implements GlobalFilter, Ordered {
+
+ private final JwtUtil jwtUtil;
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ return null;
+ }
+
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+}
diff --git a/gateway-server/src/main/java/cn/edu/whut/gateway/util/JwtUtil.java b/gateway-server/src/main/java/cn/edu/whut/gateway/util/JwtUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..e13cd38a16299fb412638c3f5e9f1ce0529186de
--- /dev/null
+++ b/gateway-server/src/main/java/cn/edu/whut/gateway/util/JwtUtil.java
@@ -0,0 +1,75 @@
+package cn.edu.whut.gateway.util;
+
+import cn.edu.whut.gateway.error.UnauthorizedException;
+import cn.hutool.jwt.JWT;
+import cn.hutool.jwt.JWTValidator;
+import cn.hutool.jwt.signers.JWTSigner;
+import cn.hutool.jwt.signers.JWTSignerUtil;
+import org.springframework.stereotype.Component;
+
+import java.security.KeyPair;
+import java.time.Duration;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class JwtUtil {
+
+ private final JWTSigner jwtSigner;
+
+ public JwtUtil(KeyPair keyPair){
+ this.jwtSigner = JWTSignerUtil.createSigner("rs256", keyPair);
+ }
+
+ /**
+ * 创建token
+ *
+ * @param userId 用户id
+ * @param tenantId 租户id
+ * @param ttl token过期时间
+ * @return token
+ */
+ public String createToken(Integer userId, Integer tenantId, Duration ttl){
+ Map payloadData = new HashMap<>();
+ payloadData.put("userId", userId);
+ payloadData.put("tenantId", tenantId);
+ return JWT.create()
+ .setPayload("user", payloadData)
+ .setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis()))
+ .setSigner(jwtSigner)
+ .sign();
+ }
+
+ /**
+ * 解析token
+ * @param token token
+ * @return 用户id和租户id
+ */
+ public Map parseToken(String token) {
+ if (token == null) {
+ throw new UnauthorizedException("用户未登录");
+ }
+ JWT jwt;
+ try {
+ jwt = JWT.of(token).setSigner(jwtSigner);
+ } catch (Exception e) {
+ throw new UnauthorizedException("无效的token", e);
+ }
+ // 验证token是否有效
+ if (!jwt.verify()) {
+ throw new UnauthorizedException("无效的token");
+ }
+ try {
+ JWTValidator.of(jwt).validateDate();
+ } catch (Exception e) {
+ throw new UnauthorizedException("token已过期");
+ }
+ Object userPayloadData = jwt.getPayload("user");
+ if (userPayloadData == null) {
+ throw new UnauthorizedException("无效的token");
+ }else{
+ return (Map) jwt.getPayload("user");
+ }
+ }
+}
diff --git a/gateway-server/src/main/resources/application.yml b/gateway-server/src/main/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..97ed114ff01b0c408a5bc0b974b47d56b4bca82c
--- /dev/null
+++ b/gateway-server/src/main/resources/application.yml
@@ -0,0 +1,24 @@
+spring:
+ application:
+ name: gateway-server
+ cloud:
+ nacos:
+ server-addr: 127.0.0.1:8848
+ gateway:
+ routes:
+ - id: system-server
+ uri: lb://system-server
+ predicates:
+ - Path=/api/common/system/**
+
+server:
+ port: 8070
+gateway:
+ jwt:
+ alias: examquestion # 密钥别名
+ location: classpath:examquestion.jks # 密钥库路径
+ password: GYdx@2024.! # 密钥库密码
+ tokenExpire: 30m # token过期时间
+ auth:
+ exclude-paths: # 不需要认证的路径
+ - /api/common/system/login
diff --git a/gateway-server/src/main/resources/examquestion.jks b/gateway-server/src/main/resources/examquestion.jks
new file mode 100644
index 0000000000000000000000000000000000000000..3492c78d6af4653f9140363ca9f5819eee3d6da0
Binary files /dev/null and b/gateway-server/src/main/resources/examquestion.jks differ
diff --git a/gateway-server/src/test/java/cn/edu/whut/gateway/GatewayServerApplicationTests.java b/gateway-server/src/test/java/cn/edu/whut/gateway/GatewayServerApplicationTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..903c32d22a1c41b1c811dde5e697fb2b7002b5f8
--- /dev/null
+++ b/gateway-server/src/test/java/cn/edu/whut/gateway/GatewayServerApplicationTests.java
@@ -0,0 +1,13 @@
+package cn.edu.whut.gateway;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class GatewayServerApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d0b15fe9e517021c9ee5bd737764a26a3c575534
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,97 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.7
+
+
+
+ cn.edu.whut
+ exam-question
+ 0.0.1-SNAPSHOT
+ exam-question
+ 在线刷题和考试
+ pom
+
+
+ 17
+ 2023.0.3
+ 2023.0.3.2
+ 5.8.16
+ 4.4.0
+ 1.18.36
+ 3.5.9
+ 1.5.2
+ 1.2.24
+
+
+
+ common-api
+ system-server
+ gateway-server
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+ com.alibaba.cloud
+ spring-cloud-alibaba-dependencies
+ ${spring-cloud-alibaba.version}
+ pom
+ import
+
+
+
+ cn.hutool
+ hutool-all
+ ${hutool.version}
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+ com.github.xiaoymin
+ knife4j-openapi3-jakarta-spring-boot-starter
+ ${knife4j.version}
+
+
+
+ com.baomidou
+ mybatis-plus-bom
+ ${mybatisplus.version}
+ pom
+ import
+
+
+
+ com.github.yulichang
+ mybatis-plus-join-boot-starter
+ ${mybatisplusjoin.version}
+
+
+
+ com.alibaba
+ druid-spring-boot-3-starter
+ ${druid.version}
+
+
+
+
+
+
diff --git a/sql/sys_user.sql b/sql/sys_user.sql
new file mode 100644
index 0000000000000000000000000000000000000000..fa0520e9ede2d57274d651192e3837901eac724e
--- /dev/null
+++ b/sql/sys_user.sql
@@ -0,0 +1,20 @@
+CREATE TABLE sys_user (
+ user_id INT NOT NULL AUTO_INCREMENT, -- 用户id,主键
+ username VARCHAR(64) NOT NULL, -- 用户名称
+ nickname VARCHAR(64) NOT NULL, -- 用户昵称
+ password VARCHAR(512) NOT NULL, -- 密码
+ phone VARCHAR(11) NOT NULL, -- 手机号
+ email VARCHAR(64) DEFAULT NULL, -- 邮箱
+ sex TINYINT DEFAULT NULL COMMENT '性别 0男,1女,3未知', -- 性别
+ avatar VARCHAR(512) DEFAULT NULL, -- 头像
+ user_status TINYINT NOT NULL DEFAULT 0 COMMENT '用户状态 0可用,1禁用,默认为0', -- 用户状态
+ remark VARCHAR(512) DEFAULT NULL, -- 备注
+ dept_id INT NOT NULL, -- 部门id
+ tenant_id INT NOT NULL, -- 租户id
+ create_time TIMESTAMP NOT NULL, -- 创建时间
+ creator_id INT NOT NULL, -- 创建者id
+ update_time TIMESTAMP NULL, -- 更新时间
+ updater_id INT DEFAULT NULL, -- 更新者id
+ deleted TINYINT NOT NULL DEFAULT 0 COMMENT '删除标志 0未删除,1删除,默认为0', -- 删除标志
+ PRIMARY KEY (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';
\ No newline at end of file
diff --git a/system-server/.gitignore b/system-server/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..549e00a2a96fa9d7c5dbc9859664a78d980158c2
--- /dev/null
+++ b/system-server/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/system-server/pom.xml b/system-server/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9bb76afd6d4dd17e3443db7dc6f51bc286012ff9
--- /dev/null
+++ b/system-server/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 4.0.0
+
+ cn.edu.whut
+ exam-question
+ 0.0.1-SNAPSHOT
+
+
+
+ system-server
+ 0.0.1-SNAPSHOT
+ system-server
+ system-server
+ pom
+
+ 17
+
+
+ system-server-api
+ system-server-biz
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 3.3.7
+
+
+
+
+
diff --git a/system-server/system-server-api/pom.xml b/system-server/system-server-api/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..68c9970fd002370f4b5c99f220e9a82948c4bdd1
--- /dev/null
+++ b/system-server/system-server-api/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+ cn.edu.whut
+ exam-question
+ 0.0.1-SNAPSHOT
+
+
+
+
+
+ system-server-api
+ 0.0.1-SNAPSHOT
+ system-server-api
+ system-server-api
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 3.3.7
+
+
+
+
+
diff --git a/system-server/system-server-api/src/test/java/cn/edu/whut/system/SystemServerApiApplicationTests.java b/system-server/system-server-api/src/test/java/cn/edu/whut/system/SystemServerApiApplicationTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e26f6442f0db7475a8d041f3b3a2e89363ed4d5
--- /dev/null
+++ b/system-server/system-server-api/src/test/java/cn/edu/whut/system/SystemServerApiApplicationTests.java
@@ -0,0 +1,13 @@
+package cn.edu.whut.system;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SystemServerApiApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/system-server/system-server-biz/pom.xml b/system-server/system-server-biz/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..eb3427292bacb01e8a928829f9d38c3772f6d113
--- /dev/null
+++ b/system-server/system-server-biz/pom.xml
@@ -0,0 +1,98 @@
+
+
+ 4.0.0
+
+ cn.edu.whut
+ exam-question
+ 0.0.1-SNAPSHOT
+
+
+
+
+
+ system-server-biz
+ 0.0.1-SNAPSHOT
+ system-server-biz
+ system-server-biz
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+
+
+
+ com.baomidou
+ mybatis-plus-jsqlparser
+
+
+ com.github.xiaoymin
+ knife4j-openapi3-jakarta-spring-boot-starter
+
+
+ com.github.yulichang
+ mybatis-plus-join-boot-starter
+
+
+ com.alibaba
+ druid-spring-boot-3-starter
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ cn.hutool
+ hutool-all
+
+
+
+ cn.edu.whut
+ common-api
+ 0.0.1-SNAPSHOT
+ compile
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 3.3.7
+
+
+
+
+
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/SystemServerApplication.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/SystemServerApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..91e7cd993cdcee8f899e3b67fd354a00c30735ca
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/SystemServerApplication.java
@@ -0,0 +1,16 @@
+package cn.edu.whut.system;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+
+@SpringBootApplication(scanBasePackages = "cn.edu.whut.system")
+public class SystemServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SystemServerApplication.class, args);
+ }
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/config/SecurityConfig.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/config/SecurityConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..db121cf11618aeaafd992c900bcb745f9d8b0d9f
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/config/SecurityConfig.java
@@ -0,0 +1,15 @@
+package cn.edu.whut.system.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+public class SecurityConfig {
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/controller/SysAuthController.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/controller/SysAuthController.java
new file mode 100644
index 0000000000000000000000000000000000000000..20913d70db0ee509a6be992d866bed9e1172c233
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/controller/SysAuthController.java
@@ -0,0 +1,28 @@
+package cn.edu.whut.system.controller;
+
+import cn.edu.whut.commonapi.core.api.ApiResult;
+import cn.edu.whut.system.vo.auth.SysAuthUserLoginReq;
+import cn.edu.whut.system.vo.auth.SysAuthUserLoginRes;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+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 static cn.edu.whut.commonapi.core.api.ApiResult.success;
+
+
+@Tag(name = "系统服务 - 用户验证")
+@RequestMapping("auth")
+@RestController
+@RequiredArgsConstructor
+public class SysAuthController {
+
+ @PostMapping("v1/login")
+ @Operation(summary = "用户登录")
+ public ApiResult login(@RequestBody SysAuthUserLoginReq sysAuthUserLoginReq){
+ return success();
+ }
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/controller/SysUserController.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/controller/SysUserController.java
new file mode 100644
index 0000000000000000000000000000000000000000..8aa3796d49a0deac2db93db6e2f84112dcecdaae
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/controller/SysUserController.java
@@ -0,0 +1,29 @@
+package cn.edu.whut.system.controller;
+
+import cn.edu.whut.commonapi.core.api.ApiResult;
+import cn.edu.whut.system.service.ISysUserService;
+import cn.edu.whut.system.vo.user.SysUserReq;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+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 static cn.edu.whut.commonapi.core.api.ApiResult.success;
+
+@Tag(name = "系统服务 - 用户管理")
+@RequestMapping("user")
+@RestController
+@RequiredArgsConstructor
+public class SysUserController {
+
+ private final ISysUserService sysUserService;
+
+ @PostMapping("v1/create")
+ @Operation(summary = "新增用户")
+ public ApiResult createUser(@RequestBody SysUserReq sysUserReq) {
+ return success(sysUserService.createUser(sysUserReq));
+ }
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/mapper/SysUserMapper.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/mapper/SysUserMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..11e5b5e401ede30376e591b6f8f81e31ec39e5d0
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/mapper/SysUserMapper.java
@@ -0,0 +1,14 @@
+package cn.edu.whut.system.mapper;
+
+import cn.edu.whut.system.repository.SysUserRepository;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SysUserMapper extends BaseMapper {
+
+ default SysUserRepository findByUsername(String username){
+ return selectOne(new LambdaQueryWrapper().eq(SysUserRepository::getUsername, username));
+ }
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/repository/SysUserRepository.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/repository/SysUserRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..42fc29ec66402cb0647ce4ee906f24daef7ea55c
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/repository/SysUserRepository.java
@@ -0,0 +1,75 @@
+package cn.edu.whut.system.repository;
+
+import cn.edu.whut.commonapi.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@TableName("sys_user")
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class SysUserRepository extends BaseEntity {
+
+ /**
+ * 用户id
+ */
+ @TableId(value = "user_id", type = IdType.AUTO)
+ private Integer userId;
+
+ /**
+ * 用户名
+ */
+ private String username;
+
+ /**
+ * 昵称
+ */
+ private String nickname;
+
+ /**
+ * 密码
+ */
+ private String password;
+
+ /**
+ * 手机号
+ */
+ private String phone;
+
+ /**
+ * 邮箱
+ */
+ private String email;
+
+ /**
+ * 性别
+ */
+ private Integer sex;
+
+ /**
+ * 用户头像
+ */
+ private String avatar;
+
+ /**
+ * 状态
+ */
+ private Integer userStatus;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 部门 ID
+ */
+ private Integer deptId;
+
+ /**
+ * 租户 ID
+ */
+ private Integer tenantId;
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/service/ISysUserService.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/service/ISysUserService.java
new file mode 100644
index 0000000000000000000000000000000000000000..a73a9cb0adde95974c6a2dcd6caf5c7db484315b
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/service/ISysUserService.java
@@ -0,0 +1,16 @@
+package cn.edu.whut.system.service;
+
+import cn.edu.whut.system.repository.SysUserRepository;
+import cn.edu.whut.system.vo.user.SysUserReq;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+public interface ISysUserService extends IService {
+
+ /**
+ * 创建用户
+ *
+ * @param sysUserReq 用户信息
+ * @return 创建的用户id
+ */
+ Integer createUser(SysUserReq sysUserReq);
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/service/impl/SysUserServiceImpl.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/service/impl/SysUserServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..21b86a01667060d028a56670233dbc3e60529ac5
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/service/impl/SysUserServiceImpl.java
@@ -0,0 +1,94 @@
+package cn.edu.whut.system.service.impl;
+
+import cn.edu.whut.system.mapper.SysUserMapper;
+import cn.edu.whut.system.repository.SysUserRepository;
+import cn.edu.whut.system.service.ISysUserService;
+import cn.edu.whut.system.vo.user.SysUserReq;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import jakarta.annotation.Resource;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.sql.Timestamp;
+
+@Service
+@RequiredArgsConstructor
+public class SysUserServiceImpl extends ServiceImpl implements ISysUserService {
+
+ private final PasswordEncoder passwordEncoder;
+
+ /**
+ * 创建用户
+ *
+ * @param sysUserReq 用户信息
+ * @return 创建的用户id
+ */
+ @Override
+ public Integer createUser(SysUserReq sysUserReq) {
+ SysUserRepository sysUserRepository = BeanUtil.toBean(sysUserReq, SysUserRepository.class);
+ validateUserForCreateOrUpdate(null, sysUserReq.getUsername());
+ sysUserRepository.setCreateTime(new Timestamp(System.currentTimeMillis()));
+ sysUserRepository.setCreatorId(1);
+ sysUserRepository.setPassword(encodePassword(sysUserRepository.getPassword()));
+ baseMapper.insert(sysUserRepository);
+ return sysUserRepository.getUserId();
+ }
+
+ /**
+ * 对密码进行加密
+ *
+ * @param password 密码
+ * @return 加密后的密码
+ */
+ private String encodePassword(String password) {
+ return passwordEncoder.encode(password);
+ }
+
+ /**
+ * 校验用户信息是否符合要求
+ *
+ * @param userId 用户id
+ * @param username 用户名
+ */
+ private void validateUserForCreateOrUpdate(Integer userId, String username){
+ validateUserExists(userId);
+ validateUsernameUnique(userId, username);
+ }
+
+ /**
+ * 验证用户是否存在
+ *
+ * @param userId 用户id
+ */
+ private void validateUserExists(Integer userId) {
+ if(userId == null){
+ return ;
+ }
+ SysUserRepository sysUserRepository = baseMapper.selectById(userId);
+ if (sysUserRepository == null) {
+ throw new RuntimeException("用户不存在");
+ }
+ }
+
+ /**
+ * 验证用户名是否唯一
+ *
+ * @param userId 用户id
+ * @param username 用户名
+ */
+ private void validateUsernameUnique(Integer userId, String username){
+ if(StrUtil.isBlank(username)){
+ return;
+ }
+ SysUserRepository sysUserRepository = baseMapper.findByUsername(username);
+ if(sysUserRepository == null){
+ return;
+ }
+ if(userId == null || !userId.equals(sysUserRepository.getUserId())){
+ throw new RuntimeException("用户名已存在");
+ }
+ }
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/auth/SysAuthUserLoginReq.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/auth/SysAuthUserLoginReq.java
new file mode 100644
index 0000000000000000000000000000000000000000..5b0402021d5463542775ca53303f47aa8d5ed0f9
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/auth/SysAuthUserLoginReq.java
@@ -0,0 +1,23 @@
+package cn.edu.whut.system.vo.auth;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Data
+@Schema(description = "系统服务 - 账号密码登录 Request VO")
+public class SysAuthUserLoginReq {
+
+ @Schema(description = "账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudaoyuanma")
+ @NotEmpty(message = "登录账号不能为空")
+ @Size(min = 4, max = 16, message = "账号长度为 4-16 位")
+ @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
+ private String username;
+
+ @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao")
+ @NotEmpty(message = "密码不能为空")
+ @Size(min = 8, max = 16, message = "密码长度为 8-16 位")
+ private String password;
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/auth/SysAuthUserLoginRes.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/auth/SysAuthUserLoginRes.java
new file mode 100644
index 0000000000000000000000000000000000000000..c4215485d989491bb27fbef9ecfee2ef85790206
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/auth/SysAuthUserLoginRes.java
@@ -0,0 +1,37 @@
+package cn.edu.whut.system.vo.auth;
+
+import cn.edu.whut.system.vo.user.SysUserRes;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Schema(description = "系统服务 - 账号密码登录 Response VO")
+@Data
+public class SysAuthUserLoginRes implements Serializable {
+
+ /**
+ * 访问令牌
+ */
+ @Schema(description = "访问令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "happy")
+ private String accessToken;
+
+ /**
+ * 刷新令牌
+ */
+ @Schema(description = "刷新令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "nice")
+ private String refreshToken;
+
+ /**
+ * 过期时间
+ */
+ @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime expiresTime;
+
+ /**
+ * 用户信息
+ */
+ @Schema(description = "用户信息", requiredMode = Schema.RequiredMode.REQUIRED)
+ private SysUserRes user;
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/user/SysUserReq.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/user/SysUserReq.java
new file mode 100644
index 0000000000000000000000000000000000000000..079a9bcf899a58b3417f50a6f422b783c6786b8f
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/user/SysUserReq.java
@@ -0,0 +1,78 @@
+package cn.edu.whut.system.vo.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Schema(description = "系统服务 - 用户信息Request VO")
+public class SysUserReq {
+
+ /**
+ * 用户名
+ */
+ @Schema(description = "用户名", example = "admin", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String username;
+
+ /**
+ * 昵称
+ */
+ @Schema(description = "昵称", example = "管理员", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String nickname;
+
+ /**
+ * 密码
+ */
+ @Schema(description = "密码", example = "123456", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String password;
+
+ /**
+ * 手机号
+ */
+ @Schema(description = "手机号", example = "13888888888", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String phone;
+
+ /**
+ * 邮箱
+ */
+ @Schema(description = "邮箱", example = "admin@whut.edu.cn")
+ private String email;
+
+ /**
+ * 性别
+ */
+ @Schema(description = "性别", example = "1", allowableValues = {"0", "1", "2"})
+ private Integer sex;
+
+ /**
+ * 状态
+ */
+ @Schema(description = "状态", example = "1", allowableValues = {"0", "1"}, requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer userStatus;
+
+ /**
+ * 备注
+ */
+ @Schema(description = "备注", example = "管理员")
+ private String remark;
+
+ /**
+ * 部门id
+ */
+ @Schema(description = "部门id", example = "1")
+ private Integer deptId;
+
+ /**
+ * 租户id
+ */
+ @Schema(description = "租户id", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer tenantId;
+
+ /**
+ * 角色id列表
+ */
+ @Schema(description = "角色id列表", example = "[1,2]")
+ private List roleIdList;
+
+}
diff --git a/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/user/SysUserRes.java b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/user/SysUserRes.java
new file mode 100644
index 0000000000000000000000000000000000000000..f85ac5af8c5d3a832fe60f5022f1c90fa8ee67f2
--- /dev/null
+++ b/system-server/system-server-biz/src/main/java/cn/edu/whut/system/vo/user/SysUserRes.java
@@ -0,0 +1,64 @@
+package cn.edu.whut.system.vo.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "系统服务 - 用户信息Response VO")
+public class SysUserRes {
+
+ /**
+ * 用户id
+ */
+ @Schema(description = "用户id", example = "1")
+ private Integer userId;
+
+ /**
+ * 用户名
+ */
+ @Schema(description = "用户名", example = "admin")
+ private String username;
+
+ /**
+ * 昵称
+ */
+ @Schema(description = "昵称", example = "管理员")
+ private String nickname;
+
+ /**
+ * 手机号
+ */
+ @Schema(description = "手机号", example = "13888888888")
+ private String phone;
+
+ /**
+ * 邮箱
+ */
+ @Schema(description = "邮箱", example = "admin@whut.edu.cn")
+ private String email;
+
+ /**
+ * 性别
+ */
+ @Schema(description = "性别", example = "1", allowableValues = {"0", "1", "2"})
+ private Integer sex;
+
+ /**
+ * 状态
+ */
+ @Schema(description = "状态", example = "1", allowableValues = {"0", "1"})
+ private Integer userStatus;
+
+ /**
+ * 备注
+ */
+ @Schema(description = "备注", example = "管理员")
+ private String remark;
+
+ /**
+ * 租户id
+ */
+ @Schema(description = "租户id", example = "1")
+ private Integer tenantId;
+
+}
diff --git a/system-server/system-server-biz/src/main/resources/application-loc.yml b/system-server/system-server-biz/src/main/resources/application-loc.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4ba6d733b9d3c7fde472c4b9ed8ab72752796749
--- /dev/null
+++ b/system-server/system-server-biz/src/main/resources/application-loc.yml
@@ -0,0 +1,16 @@
+spring:
+ datasource:
+ type: com.alibaba.druid.pool.DruidDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3310/system_database?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8
+ username: root
+ password: 123456
+ data:
+ redis:
+ host: localhost
+ port: 6379
+ password:
+ cloud:
+ nacos:
+ discovery:
+ server-addr: localhost:8848
diff --git a/system-server/system-server-biz/src/main/resources/application.yml b/system-server/system-server-biz/src/main/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..59b6cbaed0fcdbf3e4fc527b1659ea446e2c534f
--- /dev/null
+++ b/system-server/system-server-biz/src/main/resources/application.yml
@@ -0,0 +1,73 @@
+spring:
+ application:
+ name: system-server
+
+ profiles:
+ active: loc
+
+ jackson:
+ serialization:
+ write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳
+ write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
+ write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
+ fail-on-empty-beans: false # 允许序列化无属性的 Bean
+
+server:
+ port: 8081
+ servlet:
+ context-path: /api/common/system
+
+mybatis-plus:
+ global-config:
+ db-config:
+ id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
+ logic-delete-value: 1 # 逻辑已删除值(默认为 1)
+ logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+ banner: false # 关闭控制台的 Banner 打印
+ type-aliases-package: cn.edu.whut.system.*.mapper
+ configuration:
+ log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+ mapper-locations: classpath:mapper/*Mapper.xml
+
+mybatis-plus-join:
+ banner: true # 是否打印 mybatis plus join banner,默认true
+ sub-table-logic: true # 全局启用副表逻辑删除,默认true。关闭后关联查询不会加副表逻辑删除
+ ms-cache: true # 拦截器MappedStatement缓存,默认 true
+ table-alias: t # 表别名(默认 t)
+ logic-del-type: on # 副表逻辑删除条件的位置,支持 WHERE、ON,默认 ON
+
+springdoc:
+ swagger-ui:
+ path: /swagger-ui.html
+ # path: 配置swagger-ui.html/UI界面的访问路径,默认为/swagger-ui.html
+ tags-sorter: alpha
+ # tags-sorter: 接口文档中的tags排序规则,默认为alpha,可选值为alpha(按字母顺序排序)或as-is(按照在代码中定义的顺序排序)
+ operations-sorter: alpha
+
+ api-docs:
+ path: /v3/api-docs
+ # path: 配置api-docs的访问路径,默认为/v3/api-docs
+
+ group-configs:
+ # group-configs: 配置分组信息
+ - group: 'default'
+ # group: 分组名称
+ paths-to-match: '/**'
+ # paths-to-match: 配置要匹配的路径,默认为/**
+ packages-to-scan: cn.edu.whut.system
+ # packages-to-scan: 配置要扫描的包的路径,直接配置为Controller类所在的包名即可
+
+# knife4j项目访问访问地址:http://127.0.0.1:8080/doc.html#/home
+knife4j:
+ enable: true
+ # 设置为true以启用Knife4j增强功能,这将再应用程序中启用Knife4j UI
+ setting:
+ # language: 设置Knife4j UI的语言,默认为zh_cn,可选值为zh_cn或en
+ language: zh-CN
+ #开启生产环境屏蔽
+ production: false
+ #是否启用登录认证
+ basic:
+ enable: false
+ username: # 自己设置一个
+ password: # 自己设置一个
\ No newline at end of file
diff --git a/system-server/system-server-biz/src/test/java/cn/edu/whut/system/SystemServerBizApplicationTests.java b/system-server/system-server-biz/src/test/java/cn/edu/whut/system/SystemServerBizApplicationTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..6811084431700517792e892f20243cdaeb16bbd1
--- /dev/null
+++ b/system-server/system-server-biz/src/test/java/cn/edu/whut/system/SystemServerBizApplicationTests.java
@@ -0,0 +1,13 @@
+package cn.edu.whut.system;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SystemServerBizApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}