完成对Redis的构建及邮件登陆的操作,晚上功能组建,进行补丁修正

This commit is contained in:
筱锋xiao_lfeng 2024-01-16 14:52:16 +08:00
parent 4690a28ab0
commit d1415e89bb
Signed by: XiaoLFeng
GPG Key ID: F693AA12AABBFA87
65 changed files with 0 additions and 3828 deletions

37
.gitignore vendored
View File

@ -1,37 +0,0 @@
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/
### 自定义 ###
*.pdf
/src/main/resources/application-dev.yml

Binary file not shown.

View File

@ -1,2 +0,0 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar

View File

View File

@ -1,83 +0,0 @@
# OrganizeInternalOA Config表详细内容设计
<div align=right>Author: 筱锋xiao_lfeng | Version: 1.1.0</div>
## oa_config 数据表
### 数据表结构
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :----------: | :--------: | :-------------: | :--: | :--: | :---------------: | :---------------: |
| 1 | `id` | 主键 | bigint unsigned | PRI | NO | auto_increment | |
| 2 | `value` | 调用关键字 | varchar(50) | UNI | NO | | |
| 3 | `data` | json数据 | json | | YES | | |
| 4 | `created_at` | 创建时间 | timestamp | | NO | DEFAULT_GENERATED | CURRENT_TIMESTAMP |
| 5 | `updated_at` | 修改时间 | timestamp | | YES | | |
### 各功能内容解释
1. `id` : 主键,构建索引所用
2. `value` : 构建索引,用于索引字符串内容,方便对 `data` 的获取
3. `data` : 输出的 json 数据(重要)
4. `created_at` : 创建时间
5. `updated_at` : 修改时间
## 说明
> 在这张数据表中,为存储相关配置信息,例如前端需要展示的轮播图、展览业、合作厂商列表、优秀成员列表等等,为了方便开发以及后续维护不需要进行额外进行对表的设计(对于新的一个前端展示功能模块就要新建一张表,显得麻烦),故将所有的内容压缩到这张表,需要展示的内容已结构化 json 数据存储到 `data` 字段。下面是内容举例。
假设我现在有一个config表
id value data
然后我有两个前端模块要动态(可修改)展示,一个是团队信息展示,另外一个轮播图,那么我新增一行
id为主键不管团队成员信息定义valueorganize_user_info设计数据结构最终结构化为json存储到 data
轮播图一样定义value: image_index设计数据结构存储 data
一段时间后,有新的业务要展示,例如合作厂家展示
那就在这张表新建一行数据定义value:cooperate然后设计数据结构 存储data
以后有新的展示业务,以此类推。
这样取对应地方数据WHERE value后取出data直接发送前端或定时解析后给redis。也不用一个业务新建一张表也没那么多数据表查找循环。
对于数据体量非巨大的应该有一定可行性。
## 轮播图数据结构设计
```json
{
"order": string,
"data": [
{
"display_order": integer,
"image": string,
"title": string,
"description": string,
"is_active": boolean,
"created_at": timestamp,
"updated_at": timestamp,
"author": string
},
{
......
}
]
}
```
1. `order`: 展示顺序,可选值 [asc|desc]
2. `data`: 数据内容
1. `display_order`: 展示顺序输入integer自定义处理
2. `image`: 图片地址
3. `title`: 标题
4. `description`: 描述
5. `is_active`: 是否展示 [true|false]
6. `created_at`: 创建时间
7. `updated_at`: 修改时间
8. `autohr`: 填写作者

View File

@ -1,399 +0,0 @@
# OrganizeInternalOA 数据库设计
<div align=right>Author: 筱锋xiao_lfeng | Version: 1.1.0</div>
## 数据库设计
本数据库设计基于原有 [《数据库设计》](./数据库设计.md) 进行测试修改,进一步处理数据库结构与增加可维护性。
> 数据库所有字段均待定,后续可能会对某些字段进行删除,或者新增一些字段,尽量做成可维护的接口
>
> 所有的表之间,关联性尽量不要做的那么强,适当解耦,不然可维护性不高
另外,为保证数据库的完整性,对数据库适当位置加以外键约束,若后续需要进行维护删除表等操作,可删除外键约束后操作。
使用数据库系统 `Mysql8`
## 建表准备
总建表语句在 /mysql/[organize_oa.sql](../mysql/organize_oa.sql) 下存放。**(重要:在执行导入中,请先看下面内容)**
若需要导入数据库中,请先操作数据库进入对应数据库进行操作。无需执行表导入
```mysql
USE organize_oa;
```
### 数据库、用户名和密码
> 为了保证业务后期可拓展性及充分利用 mysql 默认数据库中数据,请参考下面内容。
另外,由于各位开发者配置环境为本地环境,非测试环境。故数据库部署并非统一,避免在 `application.yml` 文件中对 `spring.datasource` 频繁修改操作git带来不必要的麻烦建议开发者建数据库时数据库名字为 `organize_oa` 。单独开设账号 `organize_oa` 密码 `123456`
```mysql
-- 数据库及用户建表语句(执行后创建 organize_oa 数据库与同名 organize_oa 用户密码123456
CREATE USER 'organize_oa'@'%' IDENTIFIED WITH caching_sha2_password BY '123456';GRANT USAGE ON *.* TO 'organize_oa'@'%';ALTER USER 'organize_oa'@'%' REQUIRE NONE WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0;CREATE DATABASE IF NOT EXISTS `organize_oa`;GRANT ALL PRIVILEGES ON `organize_oa`.* TO 'organize_oa'@'%';
```
application.yml 配置文件如下:
```yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306
username: organize_oa
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
```
> 注意url部分中没有使用 `jdbc:mysql://localhost:3306/organize_oa` ,在 mysql 中存在默认数据库 `information_schema` 该部分可以提取出该数据库(含 organize_oa 数据库)的信息,以及可以支持同账户下多张数据表,对后期若有需要可以调整使用。
### url差别对于mapper的使用区别
在习惯使用 `jdbc:mysql://localhost:3306/organize_oa`Mapper中数据库语法的描写较为简单。
```java
@Mapper
public interface UserMapper {
@Select("SELECT * FROM oa_user WHERE username = #{username}")
UserDO getUserByUsername(String username);
}
```
若修改为 `jdbc:mysql://localhost:3306` 之后,操作数据库都需要加上数据库名字,告知 **DBMS** 使用哪一个数据库后再选择数据表
```java
@Mapper
public interface UserMapper {
@Select("SELECT * FROM organize_oa.oa_user WHERE username = #{username}")
UserDO getUserByUsername(String username);
}
```
> 为何这样使用。
>
> 上述这样操作可以保证此服务对应一个数据库的用户,不使用 root 用户。使用 root 用户是一个很危险的事情,分出专门一个用户处理并且专门的用户中对应 `information_schema` 也有自己及名下相关的数据内容方便利用。除此之外,可以进行业务拓展。**若后期需要额外的开发要求,需要多个数据库,而不是单个数据库对应多个数据表的时候。**无需进行Mapper内繁琐代码修改替换只需在编写新的业务Mapper代码写入新的数据表名称即可。
```java
// 举个例子
@Mapper
public interface GeneralMapper {
@Select("SELECT * FROM organize_oa.oa_user WHERE username = #{username}")
UserDO getUserByUsername(String username);
@Select("SELECT * FROM organize_log.oa_logs WHERE id = #{id}")
LogDO getLogById(Long id);
}
```
## organize_oa 数据库设计
> 以下是关于此数据库目前结构样式图,其中箭头为外键约束。对于外键约束内容详细会在下面进行详细叙述。
```mermaid
classDiagram
direction BT
class node7 {
varchar(50) value /* 调用关键字 */
json data /* json数据 */
timestamp created_at /* 创建时间 */
timestamp updated_at /* 修改时间 */
bigint unsigned id /* 主键 */
}
class node0 {
bigint unsigned pid /* 权限父id */
varchar(100) name /* 权限名称 */
varchar(50) code /* 权限编码 */
tinyint(1) type /* 0为菜单1为权限 */
deleted_at /* 删除时间(没有删除应当为空) */ timestamp
bigint unsigned id /* 主键 */
}
class node5 {
varchar(255) name /* 项目名称 */
varchar(255) description /* 一句话描述 */
text introduction /* 项目详细介绍 */
tinyint(1) code_open /* 代码是否开放 */
text core_code /* 核心代码内容Markdown */
json git /* git代码仓库内容 */
tinyint unsigned difficulty_level /* 难度等级 */
int unsigned type /* 类型 */
double reward /* 报酬 */
tinyint unsigned status /* 状态 */
bigint unsigned id /* 项目id */
}
class node2 {
varchar(50) name /* 类型名字 */
timestamp created_at /* 创建时间 */
timestamp updated_at /* 修改时间 */
int unsigned id /* 项目类型id */
}
class node3 {
varchar(20) role_name /* 角色名称 */
timestamp created_at /* 创建时间 */
timestamp updated_at /* 修改时间 */
int unsigned id /* 角色id */
}
class node1 {
bigint unsigned pid /* 权限id */
timestamp created_at /* 创建时间 */
int unsigned rid /* 角色id */
}
class node6 {
int unsigned rid /* 角色id */
timestamp createdt_at /* 创建时间 */
timestamp updated_at /* 修改时间 */
bigint unsigned uid /* 用户id */
}
class node4 {
char(10) job_id /* 工作ID */
varchar(40) username /* 用户名 */
varchar(255) password /* 密码 */
varchar(255) address /* 用户家庭地址 */
varchar(11) phone /* 电话 */
varchar(100) email /* 邮箱 */
tinyint unsigned age /* 年龄 */
varchar(50) signature /* 一句话描述自己 */
tinyint unsigned sex /* 0/1/2:保密/男/女 */
text avatar /* 头像地址 */
varchar(20) nickname /* 昵称 */
tinyint(1) enabled /* 账户是否可用 */
tinyint(1) account_no_expired /* 账户是否过期 */
tinyint(1) credentials_no_expired /* 密码是否过期 */
tinyint(1) recommend /* 账户是否被推荐 */
tinyint(1) account_no_locked /* 账户是否被锁定 */
text description /* 个人简介 */
timestamp created_at /* 创建时间 */
timestamp updated_at /* 更新时间 */
bigint unsigned id /* 主键 */
}
node0 --|> node0
node5 --|> node2
node1 --|> node0
node1 --|> node3
node6 --|> node3
node6 --|> node4
```
### oa_user 数据表
#### 说明
> 【用户表】存放本项目所有用户数据
#### 数据表字段属性
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | `id` | 主键 | bigint unsigned | PRI | NO | auto_increment | |
| 2 | `job_id` | 工作ID正则表达 "^\[STU\|TEA\|OTH\]\[0-9\]{7}" | char(10) | UNI | NO | | |
| 3 | `username` | 用户名 | varchar(40) | UNI | NO | | |
| 4 | `password` | 密码 | varchar(255) | | NO | | |
| 5 | `address` | 用户家庭地址 | varchar(255) | | NO | | |
| 6 | `phone` | 电话 | varchar(11) | UNI | NO | | |
| 7 | `email` | 邮箱 | varchar(100) | UNI | NO | | |
| 8 | `age` | 年龄 | tinyint unsigned | | NO | | |
| 9 | `signature` | 一句话描述自己 | varchar(50) | | YES | | |
| 10 | `sex` | 0/1/2:保密/男/女 | tinyint unsigned | | NO | | 0 |
| 11 | `avatar` | 头像地址 | text | | YES | | |
| 12 | `nickname` | 昵称 | varchar(20) | | YES | | |
| 13 | `enabled` | 账户是否可用 | tinyint(1) | | NO | | 1 |
| 14 | `account_no_expired` | 账户是否过期 | tinyint(1) | | NO | | 1 |
| 15 | `credentials_no_expired` | 密码是否过期 | tinyint(1) | | NO | | 0 |
| 16 | `recommend` | 账户是否被推荐 | tinyint(1) | | NO | | 0 |
| 17 | `account_no_locked` | 账户是否被锁定 | tinyint(1) | | NO | | |
| 18 | `description` | 个人简介 | text | | YES | | |
| 19 | `created_at` | 创建时间 | timestamp | | NO | DEFAULT_GENERATED | CURRENT_TIMESTAMP |
| 20 | `updated_at` | 更新时间 | timestamp | | YES | | |
### oa_role 数据表
#### 说明
> 【角色表】包含所有的角色(目前主要为三个,学生、老师、管理员)
#### 数据表字段属性
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | `id` | 角色id | int unsigned | PRI | NO | auto_increment | |
| 2 | `role_name` | 角色名称 | varchar(20) | | NO | | |
| 3 | `created_at` | 创建时间 | timestamp | | NO | DEFAULT_GENERATED | CURRENT_TIMESTAMP |
| 4 | `updated_at` | 修改时间 | timestamp | | YES | | |
### oa_permissions 数据表
#### 说明
> 【权限表】所有的权限,具体到每一个功能
字段 `pid` 外键约束 `id` 模式CASCADE更新与删除
#### 数据表字段属性
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | `id` | 主键 | bigint unsigned | PRI | NO | auto_increment | |
| 2 | `pid` | 权限父id | bigint unsigned | MUL | YES | | |
| 3 | `name` | 权限名称 | varchar(100) | | NO | | |
| 4 | `code` | 权限编码 | varchar(50) | | NO | | |
| 5 | `type` | 0为菜单1为权限 | tinyint(1) | | NO | | 1 |
| 6 | `deleted_at` | 删除时间(没有删除应当为空) | timestamp | | YES | | |
### oa_role_user 数据表
#### 说明
> 【用户角色分配表】为用户赋予默认权限属性的内容
字段 `rid` 外键约束 `oa_role.id` 模式RESTRICT更新与删除
字段 `uid` 外键约束 `oa_user.id` 模式CASCADE更新与删除
#### 数据表字段属性
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | `uid` | 用户id | bigint unsigned | PRI | NO | | |
| 2 | `rid` | 角色id | int unsigned | MUL | NO | | |
| 3 | `createdt_at` | 创建时间 | timestamp | | NO | DEFAULT_GENERATED | CURRENT_TIMESTAMP |
| 4 | `updated_at` | 修改时间 | timestamp | | YES | | |
### oa_role_permissions 数据表
#### 说明
> 【角色权限表】为角色赋予指定权限操作
字段 `rid` 外键约束 `oa_role.id` 模式CASCADE更新与删除
字段 `pid` 外键约束 `oa_permissions.id` 模式CASCADE更新与删除
#### 数据表字段属性
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | `rid` | 角色id | int unsigned | PRI | NO | | |
| 2 | `pid` | 权限id | bigint unsigned | MUL | NO | | |
| 3 | `created_at` | 创建时间 | timestamp | | NO | DEFAULT_GENERATED | CURRENT_TIMESTAMP |
### oa_project 数据表
#### 说明
> 【项目表】用于存放项目相关内容
字段 `type` 外键约束 `oa_project_type.id` 模式CASCADE更新模式RESTRICT删除
#### 数据表字段属性
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | `id` | 项目id | bigint unsigned | PRI | NO | auto_increment | |
| 2 | `name` | 项目名称 | varchar(255) | | NO | | |
| 3 | `description` | 一句话描述 | varchar(255) | | NO | | |
| 4 | `introduction` | 项目详细介绍 | text | | NO | | |
| 5 | `code_open` | 代码是否开放 | tinyint(1) | | NO | | 0 |
| 6 | `core_code` | 核心代码内容Markdown | text | | YES | | |
| 7 | `git` | git代码仓库内容 | json | | YES | | |
| 8 | `difficulty_level` | 难度等级 | tinyint unsigned | | NO | | 1 |
| 9 | `type` | 类型 | int unsigned | MUL | NO | | |
| 10 | `reward` | 报酬 | bigint unsigned | | YES | | |
| 11 | `status` | 状态 | tinyint unsigned | | NO | | 0 |
### oa_project_type 数据表
#### 说明
> 【项目类型表】用于存放项目种类的种类分类表
#### 数据表字段属性
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | `id` | 项目类型id | int unsigned | PRI | NO | auto_increment | |
| 2 | `name` | 类型名字 | varchar(50) | | NO | | |
| 3 | `created_at` | 创建时间 | timestamp | | NO | DEFAULT_GENERATED | CURRENT_TIMESTAMP |
| 4 | `updated_at` | 修改时间 | timestamp | | YES | | |
### oa_project_cutting 数据表
#### 说明
> 【项目切割表】用于存放项目分割模块后的各模块内容
字段 `pid` 外键约束 `oa_project.id` 模式CASCADE更新与删除
#### 数据表字段属性
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--------------: | :--------------: | :--------------: | :--: | :--: | :---------------: | :---------------: |
| 1 | `id` | 主键 | bigint unsigned | PRI | NO | auto_increment | |
| 2 | `pid` | 项目id | bigint unsigned | MUL | NO | | |
| 3 | `name` | 项目分割模块名字 | varchar(40) | | NO | | |
| 4 | `tag` | 模块标签 | json | | YES | | |
| 5 | `engineering` | 工程量计算 | tinyint unsigned | | NO | | 1 |
| 6 | `estimated_time` | 预估时间(小时) | int unsigned | | NO | | 3 |
| 7 | `real_time` | 实际时间 | timestamp | | YES | | |
| 8 | `created_at` | 创建时间 | timestamp | | NO | DEFAULT_GENERATED | CURRENT_TIMESTAMP |
| 9 | `updated_at` | 修改时间 | timestamp | | YES | | |
### oa_project_user 数据表
#### 说明
> 【用户项目分配表】用于存放用户所分配到的子项目内容
字段 `uid` 外键约束 `oa_user.id` 模式RESTRICT更新与删除
字段 `pid` 外键约束 `oa_project_cutting.id` 模式CASCADE更新与删除
#### 数据表字段属性
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | `id` | 主键id | bigint unsigned | PRI | NO | auto_increment | |
| 2 | `uid` | 用户id | bigint unsigned | MUL | NO | | |
| 3 | `pid` | 接到分割项目内容 | bigint unsigned | MUL | NO | | |
| 4 | `created_at` | 创建时间 | timestamp | | NO | DEFAULT_GENERATED | CURRENT_TIMESTAMP |
| 5 | `updated_at` | 修改时间 | timestamp | | YES | | |
### oa_config 数据表
#### 说明
> 【配置表】用于相关内容的配置
**请注意,这部分内容需要有额外的定义,具体定义内容请参考 [《Config表详细内容设计》](./OrganizeInternalOA-ConfigTableConfig.md)**
#### 数据表字段属性
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | `id` | 主键 | bigint unsigned | PRI | NO | auto_increment | |
| 2 | `value` | 调用关键字 | varchar(50) | UNI | NO | | |
| 3 | `data` | json数据 | json | | YES | | |
| 4 | `created_at` | 创建时间 | timestamp | | NO | DEFAULT_GENERATED | CURRENT_TIMESTAMP |
| 5 | `updated_at` | 修改时间 | timestamp | | YES | | |

View File

@ -1,78 +0,0 @@
## 内部系统前端(管理)
PS组件化
+ 顶部导航栏
+ 侧边栏
+ 信息管理
+ 底部信息栏
### 顶部导航栏
+ logo
+ 用户名
+ 头像
| 可交互组件 | 事件 | 效果 |
| ----------- | ---- | -------------------------- |
| logo | 单击 | 回到首页 |
| 头像/用户名 | 单击 | 下拉菜单(可点击退出登录) |
### 侧边栏
+ 成员信息管理
+ 角色管理
+ 权限管理
+ 轮播图管理
+ 项目信息管理
+ 团队信息管理
+ 新闻信息管理
+ 消息通知管理
+ 日报信息管理
+ 系统日志管理(待定)
### 信息管理
PS主要对信息进行增删查改的操作
+ 新增
+ 单行输入框
+ 表格
| 可交互组件 | 事件 | 效果 |
| ----------------------- | -------------------------- | ------------------------------ |
| 新增按钮 | 点击 | 弹窗显示新增信息 |
| 单行输入框 | 获取焦点后输入要查询的信息 | 失去焦点/按下enter键后进行查询 |
| 表单展示 | - | 显示数据 |
| 表单-编辑 | 点击 | 编辑信息 |
| 表单-删除 | 点击 | 删除信息 |
| 表单-左侧复选框(优化) | 点击 | 可进行多选删除 |
### 底部信息栏
PS信息待定

View File

@ -1,72 +0,0 @@
## 内部系统前端(首页)
PS组件化编写代码
首页组件划分:
+ 顶层导航栏
+ 项目推荐
+ 团队简介
+ 新闻展示
+ 领导者/优秀学员展示
+ 底部信息栏
### 顶部导航栏
| 可交互组件 | 事件 | 效果 |
| ---------- | ---- | ---------------------------------------------- |
| logo | 单击 | 回到首页 |
| 首页 | 单击 | 回到首页 |
| 项目 | 单击 | 进入项目页 |
| 关于 | 单击 | 下拉菜单,显示(关于我们,加入我们,项目合作) |
| 关于我们 | 单击 | 进入团队信息展示页面 |
| 加入我们 | 单击 | 进入加入我们页面 |
### 项目推荐
| 可交互组件 | 事件 | 效果 |
| ------------ | -------- | -------------------------------------- |
| 项目卡片 | 鼠标悬浮 | 显示此项目的名称,归类,状态,进入按钮 |
| 悬浮后的卡片 | 单击 | 进入对应项目详情页面 |
| 向右箭头 | 单击 | 进入项目页 |
### 关于
| 可交互组件 | 事件 | 效果 |
| ---------- | ---- | ---------------- |
| 向右箭头 | 单击 | 进入关于我们页面 |
### 新闻
| 可交互组件 | 事件 | 效果 |
| ------------ | -------- | -------------------- |
| 向右箭头 | 点击 | 进入新闻页 |
| 新闻卡片 | 鼠标悬浮 | 颜色突出,可点击进入 |
| 悬浮后的卡片 | 点击 | 进入此新闻详情页 |
### 领导者展示
| 可交互组件 | 事件 | 效果 |
| ---------- | ---- | ---------------------- |
| 头像 | 单击 | 进入此成员的个人详情页 |
### 底部信息栏
暂时未定,摆放虚假信息即可

View File

@ -1,20 +0,0 @@
## 内部系统后端(管理)
PS主要是针对内部系统项目首页的各个信息进行管理增删查改
### 功能点
PS主要包含对数据库信息的增删查改以及查询全部信息
+ 成员信息管理
+ 角色管理
+ 权限管理
+ 轮播图管理
+ 项目信息管理
+ 团队信息管理
+ 新闻信息管理
+ 消息通知管理
+ 日报信息管理
+ 系统日志管理(待定)

View File

@ -1,71 +0,0 @@
## 内部系统后端(首页)
### 功能点设计
#### 功能点一:权限认证
★★★★★
要求如下:
+ 框架自选Shiro/springsecurity
+ token验证
+ redis存储token
接口:
+ 登录接口login
+ 返回用户详细信息
+ 返回此用户对应的权限
+ 返回生成的token
+ 退出接口logout
+ 删除redis中的token
#### 功能点二:轮播图展示
接口:
+ 查询推荐的轮播图4-6张
+ 返回所有被推荐的轮播图数据4-6条
#### 功能点三:推荐项目展示
接口:
+ 查询推荐的项目
+ 返回所有被推荐的项目条目6条
#### 功能点四:团队信息推荐
接口:
+ 查询团队信息
+ 返回团队信息简介
#### 功能点五:最新新闻展示
接口:
+ 查询最新新闻
+ 返回最新新闻条目3条
#### 功能点六:优秀成员推荐
接口:
+ 查询被推荐的成员
+ 返回被推荐的成员条目(至少三条)

View File

@ -1,316 +0,0 @@
### 数据库设计
PS
+ 数据库所有字段均待定,后续可能会对某些字段进行删除,或者新增一些字段,尽量做成可维护的接口
+ 所有的表之间,关联性尽量不要做的那么强,适当解耦,不然可维护性不高
#### 角色权限
##### user
PS`用户表`
msg*包含所有用户*
| 字段 | 类型 | 注释 |
| ---------------------- | -------- | -------------------------- |
| user_id | int | 用户id |
| username | varchar | 用户名 |
| password | varchar | 密码 |
| address | varchar | 地址 |
| phone | varchar | 电话 |
| email | varchar | 电子邮箱 |
| age | int | 年龄 |
| signature | varchar | 签名 |
| sex | varchar | 性别 |
| avatar | varchar | 头像 |
| nack_name | varchar | 昵称 |
| enabled | int | 账户是否可用1可用0 |
| account_no_expired | int | 账户是否过期1可用0 |
| credentials_no_expired | int | 密码是否过期1可用0 |
| recommend | int | 账户是否被推荐1推荐0 |
| account_no_locked | int | 账户是否被锁定1可用0 |
| create_time | datetime | 创建时间 |
| description | varchar | 个人简介 |
##### role
PS`角色表`
msg*包含所有的角色,目前有三个*
| 字段 | 类型 | 注释 |
| --------- | ------- | -------- |
| id | int | 角色id |
| role_name | varchar | 角色名称 |
| remark | varchar | 标记 |
##### permissions
PS`权限表`
msg*所有的权限,具体到每一个功能*
| 字段 | 类型 | 解释 |
| ----------- | ------- | ---------------------- |
| id | int | 权限id |
| pid | int | 权限的父id |
| name | varchar | 名称 |
| code | varchar | 编码 |
| type | int | 0为菜单1为权限 |
| delete_flag | tinyint | 0代表未删除1代表删除 |
##### role_user
PS`角色用户表`
msg*给用户分配角色*
| 字段 | 类型 | 解释 |
| ---- | ---- | ------ |
| uid | int | 用户id |
| rid | int | 角色id |
##### role_permissions
PS`角色权限表`
msg*每个角色对应很多权限*
| 字段 | 类型 | 解释 |
| ---- | ---- | ------ |
| rid | int | 角色id |
| pid | int | 权限id |
#### 轮播图(可选)
msg*主页第一部分展示内容*
##### carousel
PS`轮播图表`
| 字段 | 类型 | 解释 |
| ------------- | -------- | -------------- |
| id | id | 轮播图id |
| image | varchar | 图片路径或字符 |
| title | varchar | 标题 |
| description | varchar | 描述 |
| display_order | int | 展示顺序 |
| is_active | int | 是否展示 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
| author | varchar | 作者 |
#### 团队信息(字段暂定)
msg团队信息展示
##### teamInfo
PS`团队信息表`
| 字段 | 类型 | 解释 |
| ----------- | ------- | -------- |
| id | int | 团队id |
| name | varchar | 团队名称 |
| count | int | 团队人数 |
| description | text | 团队简介 |
#### 项目(慢一点)
msg*主要在项目展示页面*
##### project
PS`项目内容表`
| 字段 | 类型 | 解释 |
| ---------------- | -------- | ---------------- |
| id | int | 项目id |
| project_name | varchar | 项目名称 |
| signature | varchar | 签名 |
| description | text | 描述 |
| core_code_md | varchar | 核心代码文件 |
| github_http | varchar | github的http链接 |
| github_ssh | varchar | github的ssh链接 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
| difficulty_level | int | 难度等级 |
| type | varchar | 类型 |
| reward | varchar | 报酬 |
| status | int | 状态 |
##### user_project
PS`用户项目表`
| 字段 | 类型 | 解释 |
| ---- | ---- | ------ |
| uid | int | 用户id |
| pid | int | 项目id |
#### 新闻
##### news
PS`新闻内容表`
| 字段 | 类型 | 解释 |
| ----------- | -------- | ------------------ |
| id | int | id |
| title | varchar | 标题 |
| content | text | 内容 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
| tags | varchar | 标签(项目,通知) |
| status | int | 状态 |
| likes | int | 点赞数(后面用) |
| comments | int | 评论数(后面用) |
##### news_user
PS`新闻作者表`
| 字段 | 类型 | 解释 |
| ---- | ---- | ------ |
| uid | int | 用户id |
| nid | int | 新闻id |
#### 消息
##### message
PS`发送消息表`
| 字段 | 类型 | 解释 |
| ----------- | -------- | ------------------ |
| id | int | 消息id |
| sender_id | int | 发送者id |
| content | text | 内容 |
| send_time | datetime | 发送时间 |
| status | int | 状态 |
| delete_flag | int | 删除标记 |
| read_time | datetime | 阅读时间 |
| tags | varchar | 标签(项目,财务) |
##### message_user
PS`接收消息表`
| 字段 | 类型 | 解释 |
| ---- | ---- | ------------------ |
| mid | int | 消息id |
| uid | int | 用户id接收者id |
#### 日报
##### daily
PS`日报信息表`
| 字段 | 类型 | 解释 |
| ----------- | -------- | ---------------- |
| id | int | 日报id |
| name | datetime | 日报名称(时间) |
| content | text | 内容 |
| plan | text | 计划 |
| create_time | datetime | 创建时间 |
##### daily_project
PS`日报项目表`
| 字段 | 类型 | 解释 |
| ---- | ---- | ------ |
| did | int | 日报id |
| pid | int | 项目id |
##### daily-user
PS`日报用户表`
| 字段 | 类型 | 解释 |
| ---- | ---- | ------ |
| did | int | 日报id |
| uid | int | 用户id |

View File

@ -1,380 +0,0 @@
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- 数据库: `organize_oa`
--
-- --------------------------------------------------------
--
-- 表的结构 `oa_config`
--
CREATE TABLE `oa_config`
(
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
`value` varchar(50) NOT NULL COMMENT '调用关键字',
`data` json DEFAULT NULL COMMENT 'json数据',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT NULL COMMENT '修改时间'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci COMMENT ='配置数据表';
-- --------------------------------------------------------
--
-- 表的结构 `oa_permissions`
--
CREATE TABLE `oa_permissions`
(
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
`pid` bigint UNSIGNED DEFAULT NULL COMMENT '权限父id',
`name` varchar(100) NOT NULL COMMENT '权限名称',
`code` varchar(50) NOT NULL COMMENT '权限编码',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0为菜单1为权限',
`deleted_at` timestamp NULL DEFAULT NULL COMMENT '删除时间(没有删除应当为空)'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci COMMENT ='权限表';
-- --------------------------------------------------------
--
-- 表的结构 `oa_project`
--
CREATE TABLE `oa_project`
(
`id` bigint UNSIGNED NOT NULL COMMENT '项目id',
`name` varchar(255) NOT NULL COMMENT '项目名称',
`description` varchar(255) NOT NULL COMMENT '一句话描述',
`introduction` text NOT NULL COMMENT '项目详细介绍',
`code_open` tinyint(1) NOT NULL DEFAULT '0' COMMENT '代码是否开放',
`core_code` text COMMENT '核心代码内容Markdown',
`git` json DEFAULT NULL COMMENT 'git代码仓库内容',
`difficulty_level` tinyint UNSIGNED NOT NULL DEFAULT '1' COMMENT '难度等级',
`type` int UNSIGNED NOT NULL COMMENT '类型',
`reward` bigint UNSIGNED DEFAULT NULL COMMENT '报酬',
`status` tinyint UNSIGNED NOT NULL DEFAULT '0' COMMENT '状态'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci COMMENT ='项目内容表';
-- --------------------------------------------------------
--
-- 表的结构 `oa_project_cutting`
--
CREATE TABLE `oa_project_cutting`
(
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
`pid` bigint UNSIGNED NOT NULL COMMENT '项目id',
`name` varchar(40) NOT NULL COMMENT '项目分割模块名字',
`tag` json DEFAULT NULL COMMENT '模块标签',
`engineering` tinyint UNSIGNED NOT NULL DEFAULT '1' COMMENT '工程量计算',
`estimated_time` int UNSIGNED NOT NULL DEFAULT '3' COMMENT '预估时间(小时)',
`real_time` timestamp NULL DEFAULT NULL COMMENT '实际时间',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT NULL COMMENT '修改时间'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci COMMENT ='项目切割';
-- --------------------------------------------------------
--
-- 表的结构 `oa_project_type`
--
CREATE TABLE `oa_project_type`
(
`id` int UNSIGNED NOT NULL COMMENT '项目类型id',
`name` varchar(50) NOT NULL COMMENT '类型名字',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT NULL COMMENT '修改时间'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci COMMENT ='项目类型';
-- --------------------------------------------------------
--
-- 表的结构 `oa_project_user`
--
CREATE TABLE `oa_project_user`
(
`id` bigint UNSIGNED NOT NULL COMMENT '主键id',
`uid` bigint UNSIGNED NOT NULL COMMENT '用户id',
`pid` bigint UNSIGNED NOT NULL COMMENT '接到分割项目内容',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT NULL COMMENT '修改时间'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci COMMENT ='用户项目表';
-- --------------------------------------------------------
--
-- 表的结构 `oa_role`
--
CREATE TABLE `oa_role`
(
`id` int UNSIGNED NOT NULL COMMENT '角色id',
`role_name` varchar(20) NOT NULL COMMENT '角色名称',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT NULL COMMENT '修改时间'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci COMMENT ='角色表';
-- --------------------------------------------------------
--
-- 表的结构 `oa_role_permissions`
--
CREATE TABLE `oa_role_permissions`
(
`rid` int UNSIGNED NOT NULL COMMENT '角色id',
`pid` bigint UNSIGNED NOT NULL COMMENT '权限id',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci COMMENT ='角色权限表';
-- --------------------------------------------------------
--
-- 表的结构 `oa_role_user`
--
CREATE TABLE `oa_role_user`
(
`uid` bigint UNSIGNED NOT NULL COMMENT '用户id',
`rid` int UNSIGNED NOT NULL COMMENT '角色id',
`createdt_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT NULL COMMENT '修改时间'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci COMMENT ='角色用户表';
-- --------------------------------------------------------
--
-- 表的结构 `oa_user`
--
CREATE TABLE `oa_user`
(
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
`job_id` char(10) NOT NULL COMMENT '工作ID正则表达 "^[STU|TEA|OTH][0-9]{7}"',
`username` varchar(40) NOT NULL COMMENT '用户名',
`password` varchar(255) NOT NULL COMMENT '密码',
`address` varchar(255) NOT NULL COMMENT '用户家庭地址',
`phone` varchar(11) NOT NULL COMMENT '电话',
`email` varchar(100) NOT NULL COMMENT '邮箱',
`age` tinyint UNSIGNED NOT NULL COMMENT '年龄',
`signature` varchar(50) DEFAULT NULL COMMENT '一句话描述自己',
`sex` tinyint UNSIGNED NOT NULL DEFAULT '0' COMMENT '0/1/2:保密/男/女',
`avatar` text COMMENT '头像地址',
`nickname` varchar(20) DEFAULT NULL COMMENT '昵称',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '账户是否可用',
`account_no_expired` tinyint(1) NOT NULL DEFAULT '1' COMMENT '账户是否过期',
`credentials_no_expired` tinyint(1) NOT NULL DEFAULT '0' COMMENT '密码是否过期',
`recommend` tinyint(1) NOT NULL DEFAULT '0' COMMENT '账户是否被推荐',
`account_no_locked` tinyint(1) NOT NULL DEFAULT '1' COMMENT '账户是否被锁定',
`description` text COMMENT '个人简介',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci COMMENT ='用户表';
--
-- 转储表的索引
--
--
-- 表的索引 `oa_config`
--
ALTER TABLE `oa_config`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `oa_config_value_uindex` (`value`);
--
-- 表的索引 `oa_permissions`
--
ALTER TABLE `oa_permissions`
ADD PRIMARY KEY (`id`),
ADD KEY `oa_permissions_oa_permissions_id_fk` (`pid`);
--
-- 表的索引 `oa_project`
--
ALTER TABLE `oa_project`
ADD PRIMARY KEY (`id`),
ADD KEY `oa_project_oa_project_type_id_fk` (`type`);
--
-- 表的索引 `oa_project_cutting`
--
ALTER TABLE `oa_project_cutting`
ADD PRIMARY KEY (`id`),
ADD KEY `oa_project_cutting_oa_project_id_fk` (`pid`);
--
-- 表的索引 `oa_project_type`
--
ALTER TABLE `oa_project_type`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `oa_project_user`
--
ALTER TABLE `oa_project_user`
ADD PRIMARY KEY (`id`),
ADD KEY `oa_project_user_oa_project_cutting_id_fk` (`pid`),
ADD KEY `oa_user_project_oa_user_id_fk` (`uid`);
--
-- 表的索引 `oa_role`
--
ALTER TABLE `oa_role`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `oa_role_permissions`
--
ALTER TABLE `oa_role_permissions`
ADD PRIMARY KEY (`rid`),
ADD KEY `oa_role_permissions_oa_permissions_id_fk` (`pid`);
--
-- 表的索引 `oa_role_user`
--
ALTER TABLE `oa_role_user`
ADD PRIMARY KEY (`uid`),
ADD KEY `oa_role_user_oa_role_id_fk` (`rid`);
--
-- 表的索引 `oa_user`
--
ALTER TABLE `oa_user`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `oa_user_job_id_uindex` (`job_id`),
ADD UNIQUE KEY `oa_user_email_uindex` (`email`),
ADD UNIQUE KEY `oa_user_phone_uindex` (`phone`),
ADD UNIQUE KEY `oa_user_username_uindex` (`username`);
--
-- 在导出的表使用AUTO_INCREMENT
--
--
-- 使用表AUTO_INCREMENT `oa_config`
--
ALTER TABLE `oa_config`
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键';
--
-- 使用表AUTO_INCREMENT `oa_permissions`
--
ALTER TABLE `oa_permissions`
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键';
--
-- 使用表AUTO_INCREMENT `oa_project`
--
ALTER TABLE `oa_project`
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '项目id';
--
-- 使用表AUTO_INCREMENT `oa_project_cutting`
--
ALTER TABLE `oa_project_cutting`
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键';
--
-- 使用表AUTO_INCREMENT `oa_project_type`
--
ALTER TABLE `oa_project_type`
MODIFY `id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '项目类型id';
--
-- 使用表AUTO_INCREMENT `oa_project_user`
--
ALTER TABLE `oa_project_user`
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键id';
--
-- 使用表AUTO_INCREMENT `oa_role`
--
ALTER TABLE `oa_role`
MODIFY `id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '角色id';
--
-- 使用表AUTO_INCREMENT `oa_user`
--
ALTER TABLE `oa_user`
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键';
--
-- 限制导出的表
--
--
-- 限制表 `oa_permissions`
--
ALTER TABLE `oa_permissions`
ADD CONSTRAINT `oa_permissions_oa_permissions_id_fk` FOREIGN KEY (`pid`) REFERENCES `oa_permissions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- 限制表 `oa_project`
--
ALTER TABLE `oa_project`
ADD CONSTRAINT `oa_project_oa_project_type_id_fk` FOREIGN KEY (`type`) REFERENCES `oa_project_type` (`id`) ON UPDATE CASCADE;
--
-- 限制表 `oa_project_cutting`
--
ALTER TABLE `oa_project_cutting`
ADD CONSTRAINT `oa_project_cutting_oa_project_id_fk` FOREIGN KEY (`pid`) REFERENCES `oa_project` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- 限制表 `oa_project_user`
--
ALTER TABLE `oa_project_user`
ADD CONSTRAINT `oa_project_user_oa_project_cutting_id_fk` FOREIGN KEY (`pid`) REFERENCES `oa_project_cutting` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `oa_user_project_oa_user_id_fk` FOREIGN KEY (`uid`) REFERENCES `oa_user` (`id`);
--
-- 限制表 `oa_role_permissions`
--
ALTER TABLE `oa_role_permissions`
ADD CONSTRAINT `oa_role_permissions_oa_permissions_id_fk` FOREIGN KEY (`pid`) REFERENCES `oa_permissions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `oa_role_permissions_oa_role_id_fk` FOREIGN KEY (`rid`) REFERENCES `oa_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- 限制表 `oa_role_user`
--
ALTER TABLE `oa_role_user`
ADD CONSTRAINT `oa_role_user_oa_role_id_fk` FOREIGN KEY (`rid`) REFERENCES `oa_role` (`id`),
ADD CONSTRAINT `oa_role_user_oa_user_id_fk` FOREIGN KEY (`uid`) REFERENCES `oa_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION */;

146
pom.xml
View File

@ -1,146 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jsl</groupId>
<artifactId>oa</artifactId>
<version>1.1.0</version>
<name>JSL_OrganizeInternalOA</name>
<description>JSL_OrganizeInternalOA</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>annotationProcessor</scope>
</dependency>
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- BCrypt 校验 -->
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
<!-- SpringBoot Validation(校验) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.1.5</version>
</dependency>
<!-- SpringBoot Aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.9.1</version>
</dependency>
<!-- Jwt Token -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.3</version>
<scope>runtime</scope>
</dependency>
<!-- SpringBoot Test -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.8.0</version>
</dependency>
<!-- SpringBoot Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Jetbrains -->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.1.0</version>
<scope>compile</scope>
</dependency>
<!-- SpringBoot Mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!-- SpringBoot Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,13 +0,0 @@
package com.jsl.oa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JslOrganizeInternalOaApplication {
public static void main(String[] args) {
SpringApplication.run(JslOrganizeInternalOaApplication.class, args);
}
}

View File

@ -1,80 +0,0 @@
package com.jsl.oa.aspect;
import com.jsl.oa.utils.ErrorCode;
import com.jsl.oa.utils.ResultUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* <h1>用户控制器切面</h1>
* <hr/>
* 用于用户控制器的切面
*
* @since v1.0.0
* @version v1.0.0
* @author 筱锋xiao_lfeng
*/
@Aspect
@Component
public class UserControllerAspect {
/**
* <h1>用户控制器切面</h1>
* <hr/>
* 用于用户控制器的切面
*
* @since v1.0.0
* @param pjp ProceedingJoinPoint对象
* @return {@link Object}
* @throws Throwable 异常
*/
@Around("execution(* com.jsl.oa.controllers.AuthController.*(..))")
public Object controllerAround(ProceedingJoinPoint pjp) throws Throwable {
// 获取HttpServletRequest对象
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
// 时间戳检查
if (checkTimestamp(request)) {
// TODO: 2023/12/21 0001 后期固定业务日志处理
return pjp.proceed();
} else {
return ResultUtil.error(ErrorCode.TIMESTAMP_ERROR);
}
}
/**
* <h1>时间戳检查</h1>
* <hr/>
* 用于检查时间戳是否合法合法时间范围正负5秒
*
* @since v1.0.0
* @param request HttpServletRequest对象
* @return {@link Boolean}
*/
public Boolean checkTimestamp(@NotNull HttpServletRequest request) {
// 获取请求头中的时间戳
String getTimestamp = request.getHeader("Timestamp");
// 判断是否为空
if (getTimestamp == null || getTimestamp.isEmpty()) {
return false;
} else {
if (getTimestamp.length() == 10) {
getTimestamp += "000";
}
}
// 获取当前时间戳
long nowTimestamp = System.currentTimeMillis();
// 时间误差允许前后五秒钟
return nowTimestamp - Long.parseLong(getTimestamp) <= 5000 && nowTimestamp - Long.parseLong(getTimestamp) >= -5000;
}
}

View File

@ -1,19 +0,0 @@
package com.jsl.oa.common.constant;
import lombok.Getter;
/**
*
*/
@Getter
public enum BusinessConstants {
BUSINESS_LOGIN("login:", "登陆实现");
private final String value;
private final String description;
BusinessConstants(String value, String description) {
this.value = value;
this.description = description;
}
}

View File

@ -1,27 +0,0 @@
package com.jsl.oa.common.constant;
import com.jsl.oa.utils.EmailRedisUtil;
/**
* <h1>Redis常量类</h1>
* <hr/>
* 用于存放Redis常量
*
* @version v1.1.0
* @since v1.1.0
* @see EmailRedisUtil
* @author 筱锋xiao_lfeng
*/
public class RedisConstant {
/*
* 类型分类
*/
public static final String TYPE_USER = "user:"; // 用户相关
public static final String TYPE_EMAIL = "mail:"; // 邮件相关
/*
* 表分类
*/
public static final String TABLE_USER = "user:"; // 用户表
public static final String TABLE_EMAIL = "code:"; // 邮箱验证码
}

View File

@ -1,106 +0,0 @@
package com.jsl.oa.config;
import com.google.gson.Gson;
import com.jsl.oa.utils.ErrorCode;
import com.jsl.oa.utils.JwtUtil;
import com.jsl.oa.utils.ResultUtil;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* <h1>JWT过滤器</h1>
* <hr/>
* 用于JWT的过滤器
*
* @author 筱锋xiao_lfeng
* @version v1.1.0
* @since v1.1.0
*/
public class JwtFilter extends BasicHttpAuthenticationFilter {
/**
* <h2>判断用户Token</h2>
* <hr/>
* 判断用户Token是否存在如果存在则进行验证
*
* @param request 请求
* @param response 响应
* @param mappedValue 映射值
* @return {@link Boolean}
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// 判断是否存在Authorization Header
String token = getAuthzHeader(request);
if (token == null || token.isEmpty()) {
return false; // 未提供Token拒绝访问
} else {
// 解析Bearer后面的令牌
token = token.replace("Bearer ", "");
System.out.println(token);
return JwtUtil.verify(token);
}
}
/**
* <h2>访问被拒绝时</h2>
* <hr/>
* 当访问被拒绝时会调用此方法
*
* @param request 请求
* @param response 响应
* @param mappedValue 映射值
* @return {@link Boolean}
* @throws Exception 异常
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
try {
// 尝试获取Authorization Header
String token = getAuthzHeader(request);
if (token == null || token.isEmpty()) {
// 未提供Token拒绝访问
Gson gson = new Gson();
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(gson.toJson(ResultUtil.error(ErrorCode.UNAUTHORIZED)));
return false;
} else {
// 解析Bearer后面的令牌
token = token.replace("Bearer ", "");
System.out.println(token);
if (JwtUtil.verify(token)) {
// Token验证通过
return true;
} else {
// Token验证失败抛出异常
throw new ExpiredCredentialsException("Token已过期");
}
}
} catch (ExpiredCredentialsException e) {
// 处理Token过期异常返回自定义的JSON信息
Gson gson = new Gson();
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(gson.toJson(ResultUtil.error(ErrorCode.TOKEN_EXPIRED)));
return false;
}
}
/**
* <h2>获取Authorization Header</h2>
* <hr/>
* 用于获取Authorization Header
*
* @param request 请求
* @return {@link String}
*/
@Override
protected String getAuthzHeader(ServletRequest request) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
return httpRequest.getHeader("Authorization");
}
}

View File

@ -1,47 +0,0 @@
package com.jsl.oa.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import java.util.Properties;
/**
* <h1>邮件配置类</h1>
* <hr/>
* 用于配置邮件发送相关信息
*
* @since v1.1.0
* @version v1.1.0
* @author 筱锋xiao_lfeng
*/
@Configuration
public class MailConfiguration {
@Value("${spring.mail.host}")
private String emailHost;
@Value("${spring.mail.username}")
private String emailUsername;
@Value("${spring.mail.password}")
private String emailPassword;
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setDefaultEncoding("UTF-8");
mailSender.setHost(emailHost);
mailSender.setPort(25); // 你的邮件服务器端口
mailSender.setUsername(emailUsername);
mailSender.setPassword(emailPassword);
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.debug", "true");
return mailSender;
}
}

View File

@ -1,46 +0,0 @@
package com.jsl.oa.config.redis;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* <h1>Redis配置类</h1>
* <hr/>
* 用于配置Redis
*
* @author 筱锋xiao_lfeng
* @version v1.1.0
* @since v1.1.0
*/
@Configuration
public class RedisConfiguration {
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("localhost");
return new JedisConnectionFactory(config);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// 配置Redis编码格式
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(jsonSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(jsonSerializer);
return redisTemplate;
}
}

View File

@ -1,59 +0,0 @@
package com.jsl.oa.config.redis;
import com.jsl.oa.common.constant.BusinessConstants;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
@RequiredArgsConstructor
public abstract class RedisOperating<R> {
protected final RedisTemplate<String, R> redisTemplate;
protected final StringRedisTemplate stringRedisTemplate;
public abstract Long getExpiredAt(BusinessConstants businessConstants, String field);
public abstract Boolean delData(BusinessConstants businessConstants, String field);
public abstract R getData(BusinessConstants businessConstants, String field);
public abstract Boolean setData(BusinessConstants businessConstants, String field, R value, Integer time);
/**
* <h2>获取Redis中元素过期时间</h2>
* <hr/>
* 基础方法用于添加String元素到Redis<br/>
*
* @param key 索引
* @return 返回过期时间
*/
public Long getExpiredAt(String key) {
return redisTemplate.getExpire(key);
}
/**
* <h2>基础添加String元素到Redis</h2>
* <hr/>
* 基础方法用于添加String元素到Redis<br/>
* 默认处理时间单位时间秒
*
* @param key
* @param value
*/
public void set(String key, String value, Integer time) {
stringRedisTemplate.opsForValue().set(key, value);
stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
}
/**
* <h2>基础添加元素到Redis</h2>
* <hr/>
* 基础方法用于添加元素元素到Redis<br/>
* 默认处理时间单位时间秒
*
* @param key
* @param value
*/
public void set(String key, R value, Integer time) {
redisTemplate.opsForValue().set(key, value);
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
}

View File

@ -1,40 +0,0 @@
package com.jsl.oa.config.shiro;
import com.jsl.oa.services.UserService;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
public class MyRealm extends AuthorizingRealm {
private final UserService userService;
/**
* 授权
*
* @return 授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(@NotNull PrincipalCollection principals) {
return null;
}
/**
* 认证
*
* @param authenticationToken 令牌
* @return 认证信息
* @throws AuthenticationException 认证异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
return null;
}
}

View File

@ -1,56 +0,0 @@
package com.jsl.oa.config.shiro;
import com.jsl.oa.config.JwtFilter;
import com.jsl.oa.services.UserService;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
@RequiredArgsConstructor
public class ShiroConfiguration {
private final UserService userService;
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 配置过滤器规则
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/auth/**", "anon"); // 登录接口允许匿名访问
filterChainDefinitionMap.put("/unauthorized", "anon"); // 未授权接口允许匿名访问
filterChainDefinitionMap.put("/", "anon"); // 首页允许匿名访问
filterChainDefinitionMap.put("/**/**", "jwt"); // 其他接口一律拦截(需要Token)
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 设置未登陆响应接口
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
// 添加JWT过滤器
Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("jwt", new JwtFilter()); // 配置自定义的JWT过滤器
shiroFilterFactoryBean.setFilters(filters);
return shiroFilterFactoryBean;
}
@Bean
public DefaultWebSecurityManager securityManager(MyRealm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
@Bean
public MyRealm myRealm() {
return new MyRealm(userService);
}
}

View File

@ -1,125 +0,0 @@
package com.jsl.oa.controllers;
import com.jsl.oa.model.voData.UserLoginVO;
import com.jsl.oa.model.voData.UserRegisterVO;
import com.jsl.oa.services.AuthService;
import com.jsl.oa.utils.BaseResponse;
import com.jsl.oa.utils.ErrorCode;
import com.jsl.oa.utils.Processing;
import com.jsl.oa.utils.ResultUtil;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.text.ParseException;
import java.util.regex.Pattern;
/**
* <h1>用户认证控制器</h1>
* <hr/>
* 用户认证控制器包含用户注册用户登录用户登出接口
*
* @version v1.1.0
* @see AuthService
* @see UserRegisterVO
* @see UserLoginVO
* @see BaseResponse
* @see ErrorCode
* @see Processing
* @see ResultUtil
* @since v1.0.0
*/
@RestController
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
/**
* <h2>用户注册</h2>
* <hr/>
* 用户注册接口
*
* @return {@link BaseResponse}
* @author 筱锋xiao_lfeng
* @since v1.0.0
*/
@PostMapping("/auth/register")
public BaseResponse authRegister(@RequestBody @Validated UserRegisterVO userRegisterVO, @NotNull BindingResult bindingResult) throws ParseException {
// 判断是否有参数错误
if (bindingResult.hasErrors()) {
return ResultUtil.error(ErrorCode.REQUEST_BODY_ERROR, Processing.getValidatedErrorList(bindingResult));
}
return authService.authRegister(userRegisterVO);
}
/**
* <h2>用户登录</h2>
* <hr/>
* 用户登录接口
*
* @param userLoginVO 用户登录信息
* @param bindingResult 参数校验结果
* @return {@link BaseResponse}
* @author 176yunxuan
* @since v1.0.0
*/
@GetMapping("/auth/login")
public BaseResponse authLogin(@RequestBody @Validated UserLoginVO userLoginVO, @NotNull BindingResult bindingResult) {
// 判断是否有参数错误
if (bindingResult.hasErrors()) {
return ResultUtil.error(ErrorCode.REQUEST_BODY_ERROR, Processing.getValidatedErrorList(bindingResult));
}
return authService.authLogin(userLoginVO);
}
/**
* <h2>用户邮箱登录</h2>
* <hr/>
* 用户邮箱登录接口
*
* @param email 用户登陆邮箱
* @return {@link BaseResponse}
* @author 筱锋xiao_lfeng
* @since v1.1.0
*/
@GetMapping("/auth/login/email/code")
public BaseResponse authLoginSendEmailCode(@RequestParam String email) {
if (email != null) {
if (Pattern.matches("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$", email)) {
return authService.authLoginSendEmailCode(email);
} else {
return ResultUtil.error(ErrorCode.PARAMETER_ERROR);
}
} else {
return ResultUtil.error(ErrorCode.PARAMETER_ERROR);
}
}
@GetMapping("/auth/login/email")
public BaseResponse authLoginByEmail(@RequestParam String email, @RequestParam Integer code) {
if (email != null && code != null) {
if (Pattern.matches("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$", email)) {
return authService.authLoginByEmail(email, code);
} else {
return ResultUtil.error(ErrorCode.PARAMETER_ERROR);
}
} else {
return ResultUtil.error(ErrorCode.PARAMETER_ERROR);
}
}
/**
* <h2>用户登出</h2>
* <hr/>
* 用户登出接口
*
* @return {@link BaseResponse}
* @since v1.1.0
*/
@GetMapping("/auth/logout")
public BaseResponse authLogout() {
return null;
}
}

View File

@ -1,32 +0,0 @@
package com.jsl.oa.controllers;
import com.jsl.oa.services.MailService;
import com.jsl.oa.utils.BaseResponse;
import com.jsl.oa.utils.ResultUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class CustomController implements ErrorController {
private final MailService mailService;
@RequestMapping("/")
public BaseResponse index() {
return ResultUtil.success("欢迎使用JSL-OA系统服务器处于正常状态");
}
@RequestMapping("/error")
public ResponseEntity<BaseResponse> handleError() {
return ResultUtil.error("PageNotFound", 404, "请求资源不存在");
}
@RequestMapping("/unauthorized")
public ResponseEntity<BaseResponse> handleUnauthorized() {
return ResultUtil.error("Unauthorized", 401, "未授权");
}
}

View File

@ -1,9 +0,0 @@
package com.jsl.oa.controllers;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class InfoController {
}

View File

@ -1,9 +0,0 @@
package com.jsl.oa.controllers;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class PermissionController {
}

View File

@ -1,9 +0,0 @@
package com.jsl.oa.controllers;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class ProjectController {
}

View File

@ -1,10 +0,0 @@
package com.jsl.oa.controllers;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class RoleController {
}

View File

@ -1,66 +0,0 @@
package com.jsl.oa.dao;
import com.jsl.oa.mapper.UserMapper;
import com.jsl.oa.model.doData.UserDO;
import com.jsl.oa.model.voData.UserDeleteVO;
import com.jsl.oa.model.voData.UserEditProfile;
import com.jsl.oa.model.voData.UserLockVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class UserDAO {
private final UserMapper userMapper;
/**
* <h2>用户名获取用户信息</h2>
* <hr/>
* 根据用户名获取用户信息
*
* @param username 用户名
* @author 筱锋xiao_lfeng
* @return {@link UserDO}
*/
public UserDO getUserInfoByUsername(String username) {
UserDO userDO = null;
// Redis 获取数据
// TODO: 10000-Redis: Redis 获取数据
// 从数据库获取用户信息
if (userDO == null) {
userDO = userMapper.getUserInfoByUsername(username);
}
return userDO;
}
/**
* 根据id判断用户是否存在
* @param id
* @return
*/
public Boolean isExistUser(Long id){
if(userMapper.getUserById(id)==null) {
return false;
}else return true;
}
/**
* 用户账号删除
* @param userDeleteVO
*/
public void userDelete(UserDeleteVO userDeleteVO) {
userMapper.userDelete(userDeleteVO);
}
/**
* 用户账号锁定
* @param userLockVO
*/
public void userLock(UserLockVO userLockVO) {
userMapper.userLock(userLockVO);
}
public void userEditProfile(UserEditProfile userEditProfile) {
userMapper.userEditProfile(userEditProfile);
}
}

View File

@ -1,20 +0,0 @@
package com.jsl.oa.exception;
import com.jsl.oa.utils.ErrorCode;
/**
* <h1>业务异常类</h1>
* <hr/>
* 用于处理业务异常
*
* @since v1.0.0
* @version v1.0.0
* @author 筱锋xiao_lfeng
* @see RuntimeException
*/
public class BusinessException extends RuntimeException {
public BusinessException(ErrorCode errorCode) {
super(errorCode.getOutput() + "|" + errorCode.getMessage());
}
}

View File

@ -1,38 +0,0 @@
package com.jsl.oa.exception;
import com.jsl.oa.utils.BaseResponse;
import com.jsl.oa.utils.ResultUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.regex.Pattern;
@ControllerAdvice
public class ProcessException {
@ExceptionHandler(value = Exception.class)
public ResponseEntity<BaseResponse> businessException(@NotNull Exception e) {
e.printStackTrace();
return ResultUtil.error("Exception", 500, "服务器异常");
}
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
public ResponseEntity<BaseResponse> businessMethodNotAllowedException() {
return ResultUtil.error("MethodNotAllowed", 405, "请求方法错误");
}
@ExceptionHandler(value = SQLIntegrityConstraintViolationException.class)
public ResponseEntity<BaseResponse> businessSQLIntegrityConstraintViolationException(@NotNull SQLIntegrityConstraintViolationException e) {
if (Pattern.matches(".*Duplicate entry.*", e.getMessage())) {
return ResultUtil.error("DuplicateEntry", 400, "数据重复");
} else if (Pattern.matches(".*Cannot delete or update a parent row: a foreign key constraint fails.*", e.getMessage())) {
return ResultUtil.error("DataAssociation", 400, "数据存在关联,无法删除");
} else {
return ResultUtil.error("DatabaseError", 400, "数据库异常");
}
}
}

View File

@ -1,52 +0,0 @@
package com.jsl.oa.mapper;
import com.jsl.oa.model.doData.UserDO;
import com.jsl.oa.model.voData.UserDeleteVO;
import com.jsl.oa.model.voData.UserEditProfile;
import com.jsl.oa.model.voData.UserLockVO;
import com.jsl.oa.model.voData.UserLoginVO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
@Mapper
public interface UserMapper {
@Select("SELECT * FROM organize_oa.oa_user WHERE username = #{username}")
UserDO getUserInfoByUsername(String username);
@Select("SELECT * FROM organize_oa.oa_user WHERE job_id = #{jobId}")
UserDO getUserByUserNum(String jobId);
@Insert("INSERT INTO organize_oa.oa_user " +
"(job_id, username, password, address, phone, email, age, sex) " +
"VALUES (#{jobId}, #{username}, #{password}, #{address}, #{phone}, #{email}, #{age}, #{sex})")
boolean insertUser(UserDO userDO);
@Select("SELECT password FROM organize_oa.oa_user WHERE job_id = #{jobId}")
String loginPassword(UserLoginVO userLoginVO);
@Select("SELECT * FROM organize_oa.oa_user WHERE job_id = #{jobId}")
UserDO login(UserLoginVO userLoginVO);
@Update("UPDATE organize_oa.oa_user SET enabled = 0 WHERE id = #{id} ")
void userDelete(UserDeleteVO userDeleteVO);
@Update("UPDATE organize_oa.oa_user SET account_no_locked = 1 WHERE id = #{id} ")
void userLock(UserLockVO userLockVO);
@Select("SELECT * FROM organize_oa.oa_user WHERE id = #{id}")
UserDO getUserById(Long id);
@Select("SELECT * FROM organize_oa.oa_user WHERE email = #{email}")
UserDO getUserInfoByEmail(String email);
@Select("SELECT * FROM organize_oa.oa_user WHERE phone = #{phone}")
UserDO getUserInfoByPhone(String user);
@Select("SELECT * FROM organize_oa.oa_user WHERE job_id = #{jobId}")
UserDO getUserByJobId(String user);
void userEditProfile(UserEditProfile userEditProfile);
}

View File

@ -1,27 +0,0 @@
package com.jsl.oa.model.doData;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
import java.sql.Timestamp;
/**
* <h1>config 数据表</h1>
* <hr/>
* 映射 oa_config 数据表内容进入自定义实体类
*
* @author 筱锋xiao_lfeng
* @since v1.1.0
* @version v1.1.0
*/
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ConfigDO {
private Long id;
private String value;
private String data;
private Timestamp createdAt;
private Timestamp updatedAt;
}

View File

@ -1,28 +0,0 @@
package com.jsl.oa.model.doData;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
import java.sql.Timestamp;
/**
* <h1>permission 数据表</h1>
* <hr/>
* 映射 oa_permission 数据表内容进入自定义实体类
*
* @author 筱锋xiao_lfeng
* @since v1.1.0
* @version v1.1.0
*/
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PermissionDO {
private Long id;
private Long pid;
private String name;
private String code;
private Short type;
private Timestamp deletedAt;
}

View File

@ -1,31 +0,0 @@
package com.jsl.oa.model.doData;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
import java.sql.Timestamp;
/**
* <h1>project_cutting 数据表</h1>
* <hr/>
* 映射 oa_project_cutting 数据表内容进入自定义实体类
*
* @author 筱锋xiao_lfeng
* @since v1.1.0
* @version v1.1.0
*/
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProjectCuttingDO {
private Long id;
private Long pid;
private String name;
private String tag;
private Short engineering;
private Integer estimatedTime;
private Integer realTime;
private Timestamp createdAt;
private Timestamp updatedAt;
}

View File

@ -1,31 +0,0 @@
package com.jsl.oa.model.doData;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* <h1>project 数据表</h1>
* <hr/>
* 映射 oa_project 数据表内容进入自定义实体类
*
* @author 筱锋xiao_lfeng
* @since v1.1.0
* @version v1.1.0
*/
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProjectDO {
private Long id;
private String name;
private String description;
private String introduction;
private Boolean codeOpen;
private String coreCode;
private String git;
private Short difficultyLevel;
private Integer type;
private Long reward;
private Short status;
}

View File

@ -1,26 +0,0 @@
package com.jsl.oa.model.doData;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
import java.sql.Timestamp;
/**
* <h1>project_type 数据表</h1>
* <hr/>
* 映射 oa_project_type 数据表内容进入自定义实体类
*
* @author 筱锋xiao_lfeng
* @since v1.1.0
* @version v1.1.0
*/
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProjectTypeDO {
private Long id;
private String name;
private Timestamp createdAt;
private Timestamp updatedAt;
}

View File

@ -1,27 +0,0 @@
package com.jsl.oa.model.doData;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
import java.sql.Timestamp;
/**
* <h1>project_user 数据表</h1>
* <hr/>
* 映射 oa_project_user 数据表内容进入自定义实体类
*
* @author 筱锋xiao_lfeng
* @since v1.1.0
* @version v1.1.0
*/
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProjectUserDO {
private Long id;
private Long pid;
private Long uid;
private Timestamp createdAt;
private Timestamp updatedAt;
}

View File

@ -1,26 +0,0 @@
package com.jsl.oa.model.doData;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
import java.sql.Timestamp;
/**
* <h1>role 数据表</h1>
* <hr/>
* 映射 oa_role 数据表内容进入自定义实体类
*
* @author 筱锋xiao_lfeng
* @since v1.1.0
* @version v1.1.0
*/
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RoleDO {
private Long id;
private String roleName;
private Timestamp createdAt;
private Timestamp updatedAt;
}

View File

@ -1,25 +0,0 @@
package com.jsl.oa.model.doData;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
import java.sql.Timestamp;
/**
* <h1>role 数据表</h1>
* <hr/>
* 映射 oa_role 数据表内容进入自定义实体类
*
* @author 筱锋xiao_lfeng
* @since v1.1.0
* @version v1.1.0
*/
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RolePermissionDO {
private Long rid;
private Long pid;
private Timestamp createdAt;
}

View File

@ -1,26 +0,0 @@
package com.jsl.oa.model.doData;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
import java.sql.Timestamp;
/**
* <h1>project_user 数据表</h1>
* <hr/>
* 映射 oa_project_user 数据表内容进入自定义实体类
*
* @author 筱锋xiao_lfeng
* @since v1.1.0
* @version v1.1.0
*/
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RoleUserDO {
private Long uid;
private Long rid;
private Timestamp createdAt;
private Timestamp updatedAt;
}

View File

@ -1,42 +0,0 @@
package com.jsl.oa.model.doData;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;
import java.sql.Timestamp;
/**
* <h1>user 数据表</h1>
* <hr/>
* 映射 oa_user 数据表内容进入自定义实体类
*
* @author 筱锋xiao_lfeng
* @version v1.1.0
* @since v1.0.0
*/
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserDO {
private Long id;
private String jobId;
private String username;
private String password;
private String address;
private String phone;
private String email;
private Short age;
private String signature;
private String avatar;
private String nickname;
private Short sex;
private Boolean enabled;
private Boolean accountNoExpired;
private Boolean credentialsNoExpired;
private Boolean recommend;
private Boolean accountNoLocked;
private String description;
private Timestamp createdAt;
private Timestamp updatedAt;
}

View File

@ -1,12 +0,0 @@
package com.jsl.oa.model.voData;
import lombok.Getter;
import javax.validation.constraints.NotNull;
@Getter
public class UserDeleteVO {
@NotNull(message = "id不能为空")
private Long id;
}

View File

@ -1,12 +0,0 @@
package com.jsl.oa.model.voData;
import lombok.Getter;
import javax.validation.constraints.NotNull;
@Getter
public class UserLockVO {
@NotNull(message = "id不能为空")
private Long id;
}

View File

@ -1,24 +0,0 @@
package com.jsl.oa.model.voData;
import lombok.Getter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
/**
* <h1>用户登陆自定义实体类</h1>
* <hr/>
* 用于处理用户登陆表单输入的数据
*
* @author 175yunxuan
* @version v1.0.0
* @since v1.0.0
*/
@Getter
public class UserLoginVO {
@Pattern(regexp = "^[0-9A-Za-z_]+$", message = "支持用户名/手机号/工号登陆")
@NotBlank(message = "用户名不能为空")
private String user;
@NotBlank(message = "密码不能为空")
private String password;
}

View File

@ -1,45 +0,0 @@
package com.jsl.oa.model.voData;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.*;
/**
* <h1>用户注册自定义实体类</h1>
* <hr/>
* 用于处理用户注册表单输入的数据
*
* @author 筱锋xiao_lfeng
* @version v1.0.0
* @since v1.1.0
*/
@Getter
@Setter
public class UserRegisterVO {
@NotBlank(message = "用户名不能为空")
@Pattern(regexp = "^[0-9A-Za-z_]{3,40}$", message = "用户名只能为字母、数字或下划线")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
@NotBlank(message = "家乡不能为空")
private String address;
@NotBlank(message = "电话不能为空")
@Pattern(regexp = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$", message = "电话格式错误")
private String phone;
@NotBlank(message = "邮箱不能为空")
@Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$", message = "邮箱格式错误")
private String email;
@Min(value = 0, message = "保密:0,男:1,女:2")
@Max(value = 2, message = "保密:0,男:1,女:2")
@NotNull(message = "性别不能为空")
private Short sex;
@NotNull(message = "年龄不能为空")
private Short age;
}

View File

@ -1,28 +0,0 @@
package com.jsl.oa.model.voData;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* <h1>用户注册成功UserDO自定义实体类</h1>
* <hr/>
* 用于处理用户注册表单输出的数据
*
* @author 筱锋xiao_lfeng
* @version v1.1.0
* @since v1.1.0
*/
@Getter
@Setter
@Accessors(chain = true)
public class UserReturnBackVO {
private String jobId;
private String username;
private String address;
private String phone;
private String email;
private Short age;
private Short sex;
private String token;
}

View File

@ -1,58 +0,0 @@
package com.jsl.oa.services;
import com.jsl.oa.model.voData.UserLoginVO;
import com.jsl.oa.model.voData.UserRegisterVO;
import com.jsl.oa.utils.BaseResponse;
/**
* <h1>用户认证服务接口</h1>
* <hr/>
* 用户认证服务接口包含用户注册用户登录用户登出接口
*
* @version v1.1.0
* @since v1.0.0
*/
public interface AuthService {
/**
* <h2>用户注册</h2>
* <hr/>
* 用户注册服务类操作
*
* @param userRegisterVO 用户注册信息
* @return {@link BaseResponse}
* @author 筱锋xiao_lfeng
*/
BaseResponse authRegister(UserRegisterVO userRegisterVO);
/**
* <h2>用户登录</h2>
* <hr/>
* 用户登录服务类操作
*
* @param userLoginVO 用户登录信息
* @return {@link BaseResponse}
* @author 176yunxuan
*/
BaseResponse authLogin(UserLoginVO userLoginVO);
/**
* <h2>邮箱登陆</h2>
* <hr/>
* 用户邮箱登陆服务类操作
*
* @param email 邮箱
* @param code 验证码
* @return {@link BaseResponse}
*/
BaseResponse authLoginByEmail(String email, Integer code);
/**
* <h2>发送邮箱验证码</h2>
* <hr/>
* 用户邮箱登陆服务类操作
*
* @param email 邮箱
* @return {@link BaseResponse}
*/
BaseResponse authLoginSendEmailCode(String email);
}

View File

@ -1,47 +0,0 @@
package com.jsl.oa.services;
/**
* <h1>邮件服务接口</h1>
* <hr/>
* 用于发送邮件
*
* @author 筱锋xiao_lfeng
* @version v1.1.0
* @since v1.1.0
*/
public interface MailService {
/**
* <h2>发送邮件通用模板</h2>
* <hr/>
* 更为广泛的内容发送用于发送普通文本邮件
*
* @param sendTo 收件人
* @param subject 主题
* @param text 内容
* @return 是否发送成功
*/
boolean sendMail(String sendTo, String subject, String text);
/**
* <h2>发送邮件通用模板</h2>
* <hr/>
* 发送邮件通用模板用于发送具有模板HTML邮件
*
* @param sendTo 收件人
* @param model 模板
* @return 是否发送成功
*/
boolean sendMail(String sendTo, String model);
/**
* <h2>邮件登陆模块</h2>
* <hr/>
* 用于发送用户登陆邮件
*
* @param email 邮箱
* @param code 验证码
* @return 是否发送成功
*/
boolean sendMailAboutUserLogin(String email, Integer code);
}

View File

@ -1,45 +0,0 @@
package com.jsl.oa.services;
import com.jsl.oa.model.doData.UserDO;
import com.jsl.oa.model.voData.UserDeleteVO;
import com.jsl.oa.model.voData.UserEditProfile;
import com.jsl.oa.model.voData.UserLockVO;
import com.jsl.oa.utils.BaseResponse;
/**
* <h1>用户控制器接口</h1>
* <hr/>
*
* <p>该接口用于定义用户控制器的方法</p>
*
* @version 1.1.0
* @since v1.1.0
* @author 筱锋xiao_lfeng
*/
public interface UserService {
/**
* <h2>根据用户名获取用户信息</h2>
*
* <p>该方法用于根据用户名获取用户信息</p>
*
* @param username 用户名
* @return 用户信息
*/
UserDO getUserInfoByUsername(String username);
/**
* 用户账号删除
* @param userDeleteVO
* @return
*/
BaseResponse userDelete(UserDeleteVO userDeleteVO);
/**
* 用户账号锁定
* @param userLockVO
* @return
*/
BaseResponse userLock(UserLockVO userLockVO);
BaseResponse userEditProfile(UserEditProfile userEditProfile);
}

View File

@ -1,161 +0,0 @@
package com.jsl.oa.services.impl;
import com.jsl.oa.common.constant.BusinessConstants;
import com.jsl.oa.exception.BusinessException;
import com.jsl.oa.mapper.UserMapper;
import com.jsl.oa.model.doData.UserDO;
import com.jsl.oa.model.voData.UserLoginVO;
import com.jsl.oa.model.voData.UserRegisterVO;
import com.jsl.oa.model.voData.UserReturnBackVO;
import com.jsl.oa.services.AuthService;
import com.jsl.oa.services.MailService;
import com.jsl.oa.utils.*;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.stereotype.Service;
import java.util.regex.Pattern;
/**
* <h1>用户认证服务实现类</h1>
* <hr/>
* 用户认证服务实现类包含用户注册用户登录用户登出接口
*
* @version v1.1.0
* @see AuthService
* @since v1.0.0
*/
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final UserMapper userMapper;
private final MailService mailService;
private final EmailRedisUtil<Integer> emailRedisUtil;
@Override
public BaseResponse authRegister(@NotNull UserRegisterVO userRegisterVO) {
// 检查用户说是否存在
UserDO getUserByUsername = userMapper.getUserInfoByUsername(userRegisterVO.getUsername());
// 用户名已存在
if (getUserByUsername != null) {
return ResultUtil.error(ErrorCode.USER_EXIST);
}
// 生成工号
String userNum;
do {
userNum = Processing.createJobNumber((short) 2);
} while (userMapper.getUserByUserNum(userNum) != null);
// 数据上传
UserDO userDO = new UserDO();
userDO.setJobId(userNum)
.setUsername(userRegisterVO.getUsername())
.setPassword(BCrypt.hashpw(userRegisterVO.getPassword(), BCrypt.gensalt()))
.setAddress(userRegisterVO.getAddress())
.setPhone(userRegisterVO.getPhone())
.setEmail(userRegisterVO.getEmail())
.setAge(userRegisterVO.getAge())
.setSex(userRegisterVO.getSex());
// 插入数据
if (userMapper.insertUser(userDO)) {
userDO.setPassword(null);
return ResultUtil.success("注册成功", userDO);
} else {
throw new BusinessException(ErrorCode.DATABASE_INSERT_ERROR);
}
}
@Override
public BaseResponse authLogin(@NotNull UserLoginVO userLoginVO) {
// 检查用户是否存在
UserDO userDO;
if (Pattern.matches("^[0-9A-Za-z_]{3,40}$", userLoginVO.getUser())) {
// 是否为用户名
userDO = userMapper.getUserInfoByUsername(userLoginVO.getUser());
} else if (Pattern.matches("^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$", userLoginVO.getUser())) {
// 是否为手机号
userDO = userMapper.getUserInfoByPhone(userLoginVO.getUser());
} else if (Pattern.matches("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$", userLoginVO.getUser())) {
// 是否为邮箱
return ResultUtil.error(ErrorCode.EMAIL_LOGIN_NOT_SUPPORT);
} else {
// 工号
userDO = userMapper.getUserByJobId(userLoginVO.getUser());
}
if (userDO != null) {
// 获取用户并登陆
if (BCrypt.checkpw(userLoginVO.getPassword(), userDO.getPassword())) {
UserReturnBackVO userReturnBackVO = new UserReturnBackVO();
// 授权 Token
String token = JwtUtil.generateToken(userDO.getUsername());
userReturnBackVO.setAddress(userDO.getAddress())
.setAge(userDO.getAge())
.setEmail(userDO.getEmail())
.setJobId(userDO.getJobId())
.setPhone(userDO.getPhone())
.setSex(userDO.getSex())
.setUsername(userDO.getUsername())
.setToken(token);
return ResultUtil.success("登陆成功", userReturnBackVO);
} else {
return ResultUtil.error(ErrorCode.WRONG_PASSWORD);
}
} else {
return ResultUtil.error(ErrorCode.USER_NOT_EXIST);
}
}
@Override
public BaseResponse authLoginByEmail(String email, Integer code) {
// 获取验证码是否有效
Integer redisCode = emailRedisUtil.getData(BusinessConstants.BUSINESS_LOGIN, email);
if (redisCode != null) {
if (redisCode.equals(code)) {
// 删除验证码
if (emailRedisUtil.delData(BusinessConstants.BUSINESS_LOGIN, email)) {
// 邮箱获取用户
UserDO userDO = userMapper.getUserInfoByEmail(email);
// 授权 Token
String token = JwtUtil.generateToken(userDO.getUsername());
UserReturnBackVO userReturnBackVO = new UserReturnBackVO();
userReturnBackVO.setAddress(userDO.getAddress())
.setAge(userDO.getAge())
.setEmail(userDO.getEmail())
.setJobId(userDO.getJobId())
.setPhone(userDO.getPhone())
.setSex(userDO.getSex())
.setUsername(userDO.getUsername())
.setToken(token);
return ResultUtil.success("登陆成功", userReturnBackVO);
} else {
return ResultUtil.error(ErrorCode.DATABASE_DELETE_ERROR);
}
}
}
return ResultUtil.error(ErrorCode.VERIFICATION_INVALID);
}
@Override
public BaseResponse authLoginSendEmailCode(String email) {
// 获取用户信息
UserDO userDO = userMapper.getUserInfoByEmail(email);
if (userDO != null) {
// 生成验证码
Integer code = Processing.createCode();
// 存储验证码
if (emailRedisUtil.setData(BusinessConstants.BUSINESS_LOGIN, email, code, 5)) {
// 发送邮件
if (mailService.sendMailAboutUserLogin(email, code)) {
return ResultUtil.success("验证码已发送");
} else {
return ResultUtil.error(ErrorCode.EMAIL_LOGIN_NOT_SUPPORT);
}
} else {
return ResultUtil.error(ErrorCode.DATABASE_INSERT_ERROR);
}
} else {
return ResultUtil.error(ErrorCode.USER_NOT_EXIST);
}
}
}

View File

@ -1,85 +0,0 @@
package com.jsl.oa.services.impl;
import com.jsl.oa.services.MailService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
/**
* <h1>邮件服务实现类</h1>
* <hr/>
* 用于发送邮件
*
* @since v1.1.0
* @version v1.1.0
* @author 筱锋xiao_lfeng
* @see MailService
* @see JavaMailSender
* @see MimeMessageHelper
*/
@Service
@RequiredArgsConstructor
public class MailServiceImpl implements MailService {
private final JavaMailSender javaMailSender;
private final TemplateEngine templateEngine;
@Value("${spring.mail.username}")
private String from;
@Override
public boolean sendMail(String sendTo, String subject, String text) {
//发送多媒体邮件
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(sendTo);
helper.setSubject(subject);
helper.setText(text, true);
javaMailSender.send(message);
return true;
} catch (MessagingException e) {
//TODO: 10001-发送邮件失败处理
return false;
}
}
@Override
public boolean sendMail(String sendTo, String model) {
return false;
}
@Override
public boolean sendMailAboutUserLogin(String email, Integer code) {
// 发送邮件带HTML模块部分
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessage = new MimeMessageHelper(message, true);
mimeMessage.setFrom(from);
mimeMessage.setTo(email);
mimeMessage.setSubject("用户登陆邮件");
Context context = new Context();
context.setVariable("code", code);
context.setVariable("email", email);
String emailContent = templateEngine.process("/mail/user-login.html", context);
mimeMessage.setText(emailContent, true);
javaMailSender.send(message);
return true;
} catch (MessagingException e) {
//TODO: 10001-发送邮件失败处理
return false;
}
}
}

View File

@ -1,56 +0,0 @@
package com.jsl.oa.services.impl;
import com.jsl.oa.dao.UserDAO;
import com.jsl.oa.model.doData.UserDO;
import com.jsl.oa.model.voData.UserDeleteVO;
import com.jsl.oa.model.voData.UserEditProfile;
import com.jsl.oa.model.voData.UserLockVO;
import com.jsl.oa.services.UserService;
import com.jsl.oa.utils.BaseResponse;
import com.jsl.oa.utils.ErrorCode;
import com.jsl.oa.utils.ResultUtil;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserDAO userDAO;
@Override
public UserDO getUserInfoByUsername(String username) {
return userDAO.getUserInfoByUsername(username);
}
@Override
public BaseResponse userDelete(UserDeleteVO userDeleteVO) {
//判断用户是否存在
if(userDAO.isExistUser(userDeleteVO.getId())) {
userDAO.userDelete(userDeleteVO);
return ResultUtil.success("删除成功");
}else return ResultUtil.error(ErrorCode.USER_NOT_EXIST);
}
@Override
public BaseResponse userLock(@NotNull UserLockVO userLockVO) {
//判断用户是否存在
if(userDAO.isExistUser(userLockVO.getId())) {
userDAO.userLock(userLockVO);
return ResultUtil.success("锁定成功");
}else return ResultUtil.error(ErrorCode.USER_NOT_EXIST);
}
@Override
public BaseResponse userEditProfile(@NotNull UserEditProfile userEditProfile) {
if(userDAO.isExistUser(userEditProfile.getId())) {
if(userEditProfile.getPassword()!=null){
userEditProfile.setPassword(BCrypt.hashpw(userEditProfile.getPassword(), BCrypt.gensalt()));
}
userDAO.userEditProfile(userEditProfile);
return ResultUtil.success("修改成功");
}else return ResultUtil.error(ErrorCode.USER_NOT_EXIST);
}
}

View File

@ -1,27 +0,0 @@
package com.jsl.oa.utils;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
@Getter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BaseResponse {
private final String output;
private final Integer code;
private final String message;
private final Object data;
public BaseResponse(String output, Integer code, String message, Object data) {
this.output = output;
this.code = code;
this.message = message;
this.data = data;
}
public BaseResponse(String output, Integer code, String message) {
this.output = output;
this.code = code;
this.message = message;
this.data = null;
}
}

View File

@ -1,92 +0,0 @@
package com.jsl.oa.utils;
import com.jsl.oa.common.constant.BusinessConstants;
import com.jsl.oa.common.constant.RedisConstant;
import com.jsl.oa.config.redis.RedisConfiguration;
import com.jsl.oa.config.redis.RedisOperating;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* <h1>Redis工具类</h1>
* <hr/>
* 用于操作Redis
*
* @author 筱锋xiao_lfeng
* @version v1.1.0
* @see RedisConfiguration
* @see com.jsl.oa.common.constant.RedisConstant
* @since v1.1.0
*/
@Component
public class EmailRedisUtil<R> extends RedisOperating<R> {
public EmailRedisUtil(RedisTemplate<String, R> redisTemplate, StringRedisTemplate stringRedisTemplate) {
super(redisTemplate, stringRedisTemplate);
}
/**
* <h2>获取邮箱验证码过期时间</h2>
* <hr/>
* 用于 AuthController 中的 authLoginByEmail 方法<br/>
* 用于获取邮箱验证码过期时间
*
* @param email 邮箱
* @return 返回邮箱验证码过期时间戳
*/
@Override
public Long getExpiredAt(@NotNull BusinessConstants businessConstants, String email) {
String key = RedisConstant.TYPE_EMAIL + RedisConstant.TABLE_EMAIL + businessConstants.getValue() + email;
return redisTemplate.getExpire(key);
}
/**
* <h2>删除邮箱验证码</h2>
* <hr/>
* 用于 AuthController 中的 authLoginByEmail 方法<br/>
* 用于删除邮箱验证码
*
* @param email 邮箱
* @return 返回是否删除成功
*/
public Boolean delData(@NotNull BusinessConstants businessConstants, String email) {
String key = RedisConstant.TYPE_EMAIL + RedisConstant.TABLE_EMAIL + businessConstants.getValue() + email;
return redisTemplate.delete(key);
}
/**
* <h2>获取邮箱验证码</h2>
* <hr/>
* 用于 AuthController 中的 authLoginByEmail 方法<br/>
* 用于获取邮箱验证码
*
* @param email 邮箱
* @return 返回邮箱验证码
*/
public R getData(@NotNull BusinessConstants businessConstants, String email) {
String key = RedisConstant.TYPE_EMAIL + RedisConstant.TABLE_EMAIL + businessConstants.getValue() + email;
return redisTemplate.opsForValue().get(key);
}
/**
* <h2>设置邮箱验证码</h2>
* <hr/>
* 用于 AuthController 中的 authLoginByEmail 方法<br/>
* 用于设置邮箱验证码
*
* @param email 邮箱
* @param value 验证码
* @return 返回是否添加成功
*/
public Boolean setData(@NotNull BusinessConstants businessConstants, String email, R value, Integer time) {
// 处理数据
String key = RedisConstant.TYPE_EMAIL + RedisConstant.TABLE_EMAIL + businessConstants.getValue() + email;
redisTemplate.opsForValue().set(key, value);
redisTemplate.expire(key, time, TimeUnit.MINUTES);
return true;
}
}

View File

@ -1,30 +0,0 @@
package com.jsl.oa.utils;
import lombok.Getter;
@Getter
public enum ErrorCode {
WRONG_PASSWORD("WrongPassword", 40010, "密码错误"),
PARAMETER_ERROR("ParameterError", 40011, "参数错误"),
REQUEST_BODY_ERROR("RequestBodyError", 40012, "请求体错误"),
USER_EXIST("UserExist", 40013, "用户名已存在"),
TIMESTAMP_ERROR("TimestampError", 40014, "时间戳错误"),
USER_NOT_EXIST("UserNotExist", 40015, "用户不存在"),
UNAUTHORIZED("Unauthorized", 40100, "未授权"),
TOKEN_EXPIRED("TokenExpired", 40101, "Token已过期"),
VERIFICATION_INVALID("VerificationInvalid", 40102, "验证码无效"),
EMAIL_LOGIN_NOT_SUPPORT("EmailLoginNotSupport", 40300, "请使用邮箱登陆"),
DATABASE_INSERT_ERROR("DatabaseInsertError", 50010, "数据库插入错误"),
DATABASE_UPDATE_ERROR("DatabaseUpdateError", 50011, "数据库更新错误"),
DATABASE_DELETE_ERROR("DatabaseDeleteError", 50012, "数据库删除错误");
private final String output;
private final Integer code;
private final String message;
ErrorCode(String output, Integer code, String message) {
this.output = output;
this.code = code;
this.message = message;
}
}

View File

@ -1,49 +0,0 @@
package com.jsl.oa.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.regex.Pattern;
public class JwtUtil {
// 替换为实际的密钥建议使用足够长的随机字符串
private static final String SECRET_KEY = "238542310128901753637022851772455105464283332917211091531086967815273100806759714250034263888525489008903447113697698540563820710887668094087054975808574632265678643370464260078072153369247242449569221118098938297741582538222826493707667477115117609126233";
// Token 有效期这里设置为一天可以根据实际需求调整
private static final long EXPIRATION_TIME = 86400000;
// 生成Token
public static String generateToken(String username) {
Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
return Jwts.builder()
.setSubject(username)
.setExpiration(new java.util.Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
// 验证Token
public static boolean verify(String token) {
try {
Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
// 从JWT中获取用户名进行匹配
String tokenUsername = claimsJws.getBody().getSubject();
// 验证用户名是否匹配
return Pattern.matches("^[0-9A-Za-z_]{3,40}$", tokenUsername);
} catch (Exception e) {
// 验证失败
return false;
}
}
}

View File

@ -1,89 +0,0 @@
package com.jsl.oa.utils;
import org.jetbrains.annotations.NotNull;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import java.util.ArrayList;
import java.util.Random;
/**
* <h1>自定义快捷工具类</h1>
* <hr/>
*
* @author 筱锋xiao_lfeng
* @since v1.0.0
* @version v1.0.0
*/
public class Processing {
/**
* <h1>获取参数校验错误信息</h1>
* <hr/>
* 用于获取参数校验错误信息
*
* @since v1.0.0
* @param bindingResult 参数校验结果
* @return {@link ArrayList<String>}
*/
public static @NotNull ArrayList<String> getValidatedErrorList(BindingResult bindingResult) {
ArrayList<String> arrayList = new ArrayList<>();
for (ObjectError objectError : bindingResult.getAllErrors()) {
arrayList.add(objectError.getDefaultMessage());
}
return arrayList;
}
/**
* <h1>生成工号</h1>
* <hr/>
* 用于生成工号默认长度为10
*
* @since v1.0.0
* @param type 0:学生 1:教师 2:其他
* @return {@link String}
*/
public static @NotNull String createJobNumber(Short type) {
return createJobNumber(type, (short) 10);
}
/**
* <h1>生成工号</h1>
* <hr/>
* 用于生成工号
*
* @since v1.0.0
* @param type 0:学生 1:教师 2:其他
* @param size 工号长度
* @return {@link String}
*/
public static @NotNull String createJobNumber(Short type, Short size) {
StringBuilder stringBuilder = new StringBuilder();
if (type == 0) {
stringBuilder.append("STU");
} else if (type == 1) {
stringBuilder.append("TCH");
} else {
stringBuilder.append("OTH");
}
// 生成工号
Random random = new Random();
for (int i = 0; i < size-3; i++) {
stringBuilder.append(random.nextInt(10));
}
return stringBuilder.toString();
}
/**
*
*/
public static @NotNull Integer createCode() {
StringBuilder stringBuilder = new StringBuilder();
// 生成验证码
Random random = new Random();
for (int i = 0; i < 6; i++) {
stringBuilder.append(random.nextInt(10));
}
return Integer.valueOf(stringBuilder.toString());
}
}

View File

@ -1,39 +0,0 @@
package com.jsl.oa.utils;
import org.springframework.http.ResponseEntity;
public class ResultUtil {
public static BaseResponse success() {
return new BaseResponse("Success", 200, "操作成功", null);
}
public static BaseResponse success(String message) {
return new BaseResponse("Success", 200, message, null);
}
public static BaseResponse success(Object data) {
return new BaseResponse("Success", 200, "操作成功", data);
}
public static BaseResponse success(String message, Object data) {
return new BaseResponse("Success", 200, message, data);
}
public static BaseResponse error(ErrorCode errorCode) {
return new BaseResponse(errorCode.getOutput(), errorCode.getCode(), errorCode.getMessage());
}
public static BaseResponse error(ErrorCode errorCode, Object data) {
return new BaseResponse(errorCode.getOutput(), errorCode.getCode(), errorCode.getMessage(), data);
}
public static BaseResponse error(String output, Integer code, String message, Object data) {
return new BaseResponse(output, code, message, data);
}
public static ResponseEntity<BaseResponse> error(String output, Integer code, String message) {
return ResponseEntity.status(code)
.body(new BaseResponse(output, code, message));
}
}

View File

@ -1,16 +0,0 @@
spring:
datasource:
url: jdbc:mysql://localhost:3306
username: organize_oa
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
database: 0
host: localhost
port: 6379
profiles:
active: dev
mybatis:
configuration:
map-underscore-to-camel-case: true

View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jsl.oa.mapper.UserMapper">
<update id="userEditProfile">
update organize_oa.oa_user
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="password != null and password != ''">
password = #{password},
</if>
<if test="address != null and address != ''">
address = #{address},
</if>
<if test="phone != null and phone != ''">
phone = #{phone},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
<if test="age != null and age != ''">
age = #{age},
</if>
<if test="signature != null and signature != ''">
signature = #{signature},
</if>
<if test="sex != null and sex != ''">
sex = #{sex},
</if>
<if test="avatar != null and avatar != ''">
avatar = #{avatar},
</if>
<if test="nickname != null and nickname != ''">
nickname = #{nickname},
</if>
<if test="description != null and description != ''">
description = #{description}
</if>
</set>
where id = #{id}
</update>
</mapper>

View File

@ -1,90 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh-cn">
<head>
<title>登录邮件模板</title>
<style>
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
.container {
width: 80%;
margin: 0 auto;
overflow: hidden;
}
header {
background: #ffffff;
padding: 10px 0;
box-shadow: 0 5px 5px #888888;
}
header img {
width: 100px;
height: auto;
}
.main {
padding: 20px 0;
}
.main h2 {
color: #333;
}
.main p {
color: #555;
}
.code {
background: #4CAF50;
color: #ffffff;
padding: 10px;
text-align: center;
font-size: 24px;
margin: 20px 0;
}
footer {
background: #ffffff;
padding: 10px 0;
box-shadow: 0 -5px 5px #888888;
}
footer p {
text-align: center;
color: #888;
}
</style>
</head>
<body>
<div class="container">
<header>
<!--<img src="path/to/your/logo.png" alt="Logo">-->
测试
</header>
<div class="main">
<h2>登录您的账户</h2>
<p>
你好,<b th:text="${email}">[收件人]</b>
<br>
使用以下验证码登录您的账户:
</p>
<div class="code" th:text="${code}">000000</div>
<p>
如果您未请求此验证码,请忽略此邮件。
<br>
感谢您使用我们的服务。
</p>
</div>
<footer>
<p>© 2024 您的公司。保留所有权利。</p>
</footer>
</div>
</body>
</html>

View File

@ -1,20 +0,0 @@
package com.jsl.oa;
import com.jsl.oa.utils.JwtUtil;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class JslOrganizeInternalOaApplicationTests {
@Test
void contextLoads() {
String token = JwtUtil.generateToken("admin");
if (JwtUtil.verify(token)) {
System.out.println("验证通过");
} else {
System.out.println("验证失败");
}
}
}