doc: sql in README #2

Merged
SOR2171 merged 5 commits from backend-dev into main 2026-07-25 14:41:42 +08:00
54 changed files with 951 additions and 147 deletions
+1
View File
@@ -5,6 +5,7 @@
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$/src/beckend" /> <option name="externalProjectPath" value="$PROJECT_DIR$/src/beckend" />
<option name="gradleJvm" value="25" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$/src/beckend" /> <option value="$PROJECT_DIR$/src/beckend" />
+12
View File
@@ -0,0 +1,12 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyShadowingBuiltinsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredNames">
<list>
<option value="id" />
</list>
</option>
</inspection_tool>
</profile>
</component>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/schema.sql" dialect="MariaDB" />
</component>
</project>
+123 -83
View File
@@ -5,122 +5,123 @@
[完整信息](./API.md) [完整信息](./API.md)
- /api - /api
- /test - /test
- /hello - /hello
- /v1 - /v1
- /auth - /auth
- /login - /login
- /logout - /logout
- /relogin - /relogin
- /ask-code - /ask-code
- /register - /register
- /reset - /reset
- /departments - /departments
- /email - /email
- /evaluation-records - /evaluation-records
- /finance - /finance
- /items - /items
- /memos - /memos
- /personnel - /personnel
- /project-groups - /project-groups
- /proof-images - /proof-images
- /usages - /usages
- /users - /users
## 预计完成的 API 列表 ## 预计完成的 API 列表
### 1. **物品管理 (Items)** - `/api/` ### 1. **物品管理 (Items)**
| 端点 | 方法 | 功能说明 | | 端点 | 方法 | 功能说明 |
|---------------------------------|---------------------|----------------| |----------------------------------------------|---------------------|--------------------------|
| `items/` | GET/POST/PUT/DELETE | 物品 CRUD 操作 | | `items/` | GET/POST/PUT/DELETE | 物品 CRUD 操作 |
| `items/{id}/upload_images/` | POST | 上传物品图片 | | `items/{id}/upload_images/` | POST | 上传物品图片 |
| `items/{id}/set_primary_image/` | POST | 设置主图片 | | `items/{id}/set_primary_image/` | POST | 设置主图片 |
| `items/{id}/borrow/` | POST | 借用物品 | | `items/{id}/borrow/` | POST | 借用物品 |
| `items/{id}/return_item/` | POST | 归还物品 | | `items/{id}/return_item/` | POST | 归还物品 |
| `items/available/` | GET | 获取可用物品列表 | | `items/available/` | GET | 获取可用物品列表 |
| `items/in_use/` | GET | 获取使用中的物品列表 | | `items/in_use/` | GET | 获取使用中的物品列表 |
| `usages/` | GET/POST/PUT/DELETE | 物品使用记录 CRUD 操作 | | `usages/` | GET/POST/PUT/DELETE | 物品使用记录 CRUD 操作 |
| `usages/{id}/upload_images/` | POST | 上传使用记录图片 | | `usages/{id}/upload_images/` | POST | 上传使用记录图片 |
| `usages/current/` | GET | 获取当前使用中的记录 | | `usages/current/` | GET | 获取当前使用中的记录 |
| `usages/by_user/` | GET | 根据用户姓名获取使用记录 | | `usages/by_user/` | GET | 根据用户姓名获取使用记录 |
| `items/categories/`(原 `item_categories/`) | GET/POST/PUT/DELETE | 物品类别 CRUD 操作 | | `items/categories/`(原 `item_categories/`) | GET/POST/PUT/DELETE | 物品类别 CRUD 操作 |
| `users/` | GET | 获取用户列表(只读) | | `users/` | GET | 获取用户列表(只读) |
| `items/images/`(原 `item-images/`) | GET/POST/PUT/DELETE | 物品图片 CRUD 操作 | | `items/images/`(原 `item-images/`) | GET/POST/PUT/DELETE | 物品图片 CRUD 操作 |
| `usages/images/`(原 `usage-images/`) | GET/POST/PUT/DELETE | 使用记录图片 CRUD 操作 | | `usages/images/`(原 `usage-images/`) | GET/POST/PUT/DELETE | 使用记录图片 CRUD 操作 |
--- ---
### 2. **人员管理 (Personnel)** - `/api/` ### 2. **人员管理 (Personnel)**
| 端点 | 方法 | 功能说明 | | 端点 | 方法 | 功能说明 |
|---------------------------------|---------------------|------------------| |---------------------------------|---------------------|----------------------------------|
| `personnel/` | GET/POST/PUT/DELETE | 人员信息 CRUD 操作 | | `personnel/` | GET/POST/PUT/DELETE | 人员信息 CRUD 操作 |
| `personnel/statistics/` | GET | 获取人员统计信息(按部门和职位) | | `personnel/statistics/` | GET | 获取人员统计信息(按部门和职位) |
| `personnel/{id}/set_inactive/` | POST | 设置人员为已卸任状态 | | `personnel/{id}/set_inactive/` | POST | 设置人员为已卸任状态 |
| `personnel/{id}/set_active/` | POST | 设置人员为在职状态 | | `personnel/{id}/set_active/` | POST | 设置人员为在职状态 |
| `personnel/{id}/` | DELETE | 删除人员(发送邮件通知) | | `personnel/{id}/` | DELETE | 删除人员(发送邮件通知) |
| `personnel/check_expired/` | POST | 检查并更新已到期的人员状态 | | `personnel/check_expired/` | POST | 检查并更新已到期的人员状态 |
| `project-groups/` | GET/POST/PUT/DELETE | 项目组 CRUD 操作 | | `project-groups/` | GET/POST/PUT/DELETE | 项目组 CRUD 操作 |
| `project-groups/by_department/` | GET | 按部门获取项目组 | | `project-groups/by_department/` | GET | 按部门获取项目组 |
| `project-groups/{id}/` | DELETE | 删除项目组(发送邮件通知) | | `project-groups/{id}/` | DELETE | 删除项目组(发送邮件通知) |
**搜索/过滤字段**: `name`, `student_id`, `phone`, `email`, `grade_major` **搜索/过滤字段**: `name`, `student_id`, `phone`, `email`, `grade_major`
--- ---
### 3. **财务管理 (Finance)** - `/api/` ### 3. **财务管理 (Finance)**
| 端点 | 方法 | 功能说明 | | 端点 | 方法 | 功能说明 |
|-------------------------------|---------------------|------------------| |---------------------------------------------------|---------------------|----------------------------------|
| `finance/` | GET/POST/PUT/DELETE | 财务记录 CRUD 操作 | | `finance/` | GET/POST/PUT/DELETE | 财务记录 CRUD 操作 |
| `finance/{id}/upload_images/` | POST | 上传财务凭证图片 | | `finance/{id}/upload_images/` | POST | 上传财务凭证图片 |
| `finance/{id}/` | DELETE | 删除财务记录(发送邮件通知) | | `finance/{id}/` | DELETE | 删除财务记录(发送邮件通知) |
| `departments/` | GET/POST/PUT/DELETE | 部门 CRUD 操作 | | `departments/` | GET/POST/PUT/DELETE | 部门 CRUD 操作 |
| `departments/{id}/` | DELETE | 删除部门(发送邮件通知) | | `departments/{id}/` | DELETE | 删除部门(发送邮件通知) |
| `finance/categories/`(原 `finance_categories/`) | GET/POST/PUT/DELETE | 财务分类 CRUD 操作 | | `finance/categories/`(原 `finance_categories/`) | GET/POST/PUT/DELETE | 财务分类 CRUD 操作 |
| `proof-images/` | GET/POST/PUT/DELETE | 财务凭证图片 CRUD 操作 | | `proof-images/` | GET/POST/PUT/DELETE | 财务凭证图片 CRUD 操作 |
| `proof-images/{id}/` | DELETE | 删除凭证图片(自动删除物理文件) | | `proof-images/{id}/` | DELETE | 删除凭证图片(自动删除物理文件) |
--- ---
### 4. **邮件通知 (Email Notice)** - `/api/` ### 4. **邮件通知 (Email Notice)**
| 端点 | 方法 | 功能说明 | | 端点 | 方法 | 功能说明 |
|--------------------------|----------|----------------| |--------------------------|----------|-----------------------------|
| `notification-settings/` | GET/POST | 获取/更新通知设置和邮箱列表 | | `notification-settings/` | GET/POST | 获取/更新通知设置和邮箱列表 |
| `toggle-email-status/` | POST | 切换邮箱启用/禁用状态 | | `toggle-email-status/` | POST | 切换邮箱启用/禁用状态 |
**功能**: 管理邮件通知的全局开关和收件人列表 **功能**: 管理邮件通知的全局开关和收件人列表
--- ---
### 5. **备忘录 (Memo)** - `/api/` ### 5. **备忘录 (Memo)**
| 端点 | 方法 | 功能说明 | | 端点 | 方法 | 功能说明 |
|----------------------------|---------------------|-------------| |----------------------------|---------------------|------------------|
| `memos/` | GET/POST/PUT/DELETE | 备忘录 CRUD 操作 | | `memos/` | GET/POST/PUT/DELETE | 备忘录 CRUD 操作 |
| `memos/{id}/upload_image/` | POST | 上传备忘录图片 | | `memos/{id}/upload_image/` | POST | 上传备忘录图片 |
| `memos/{id}/delete_image/` | DELETE | 删除备忘录图片 | | `memos/{id}/delete_image/` | DELETE | 删除备忘录图片 |
**搜索字段**: `title`, `content` **搜索字段**: `title`, `content`
**默认排序**: 按更新时间倒序 **默认排序**: 按更新时间倒序
--- ---
### 6. **考评系统 (Evaluation)** - `/api/` ### 6. **考评系统 (Evaluation)**
| 端点 | 方法 | 功能说明 | | 端点 | 方法 | 功能说明 |
|-----------------------------------------|---------------------|------------------------| |-----------------------------------------|---------------------|--------------------------------------------|
| `evaluation-records/` | GET/POST/PUT/DELETE | 考评记录 CRUD 操作 | | `evaluation-records/` | GET/POST/PUT/DELETE | 考评记录 CRUD 操作 |
| `evaluation-records/personnel-summary/` | GET | 获取人员考评汇总(按人员、部门、年级分组) | | `evaluation-records/personnel-summary/` | GET | 获取人员考评汇总(按人员、部门、年级分组) |
| `evaluation-records/personnel-records/` | GET | 获取某个人员的所有考评记录 | | `evaluation-records/personnel-records/` | GET | 获取某个人员的所有考评记录 |
| `evaluation-records/delete-personnel/` | DELETE | 删除某个人员的所有考评记录 | | `evaluation-records/delete-personnel/` | DELETE | 删除某个人员的所有考评记录 |
| `evaluation-records/export/` | GET | 导出考评记录为 Excel(每个人一个表格) | | `evaluation-records/export/` | GET | 导出考评记录为 Excel(每个人一个表格) |
| `evaluation-records/download-template/` | GET | 下载人员导入样表 | | `evaluation-records/download-template/` | GET | 下载人员导入样表 |
| `evaluation-records/import/` | POST | 导入考评记录(支持 Excel/CSV) | | `evaluation-records/import/` | POST | 导入考评记录(支持 Excel/CSV) |
**功能**: **功能**:
- 支持批量导入和导出 - 支持批量导入和导出
- 样表格式:只需部门、年级、姓名(初始分39分) - 样表格式:只需部门、年级、姓名(初始分39分)
- 完整格式:包含详细的加分/扣分记录 - 完整格式:包含详细的加分/扣分记录
@@ -130,17 +131,56 @@
### 7. **认证相关** ### 7. **认证相关**
| 端点 | 方法 | 功能说明 | 这是原先的 Django REST Framework JWT 认证端点,主要用于获取和刷新 JWT 令牌。
|----------------------|----------|-------------| 现在改用 `/api/v1/auth/` 端点来处理登录、注册、重置密码等操作。
| 端点 | 方法 | 功能说明 |
|----------------------|----------|-----------------|
| `api/token/` | POST | JWT 令牌获取 | | `api/token/` | POST | JWT 令牌获取 |
| `api/token/refresh/` | POST | JWT 令牌刷新 | | `api/token/refresh/` | POST | JWT 令牌刷新 |
| `admin/` | GET/POST | Django 管理后台 | | `admin/` | GET/POST | Django 管理后台 |
--- ---
## 认证方式 #### 认证方式
所有 API 均使用 **JWT 认证**(除了需要特殊处理的端点): 所有 API 均使用 **JWT 认证**(除了需要特殊处理的端点):
``` ```
Authorization: Bearer <token> Authorization: Bearer <token>
``` ```
## 数据库结构
[schema.sql](./schema.sql)。
- users (系统用户/认证信息)
- departments (组织/部门)
- parent_id -> departments.id (自关联父级)
- personnel (人员信息)
- user_id -> users.id
- department_id -> departments.id
- categories (物品类别)
- parent_id -> categories.id (自关联父级)
- items (物品基本信息)
- category_id -> categories.id
- created_by -> personnel.id
- item_transactions (物品出入库流水)
- item_id -> items.id (ON DELETE CASCADE)
- operator_id -> personnel.id
- borrow_records (借用/归还记录)
- item_id -> items.id (ON DELETE CASCADE)
- borrower_id -> personnel.id (ON DELETE CASCADE)
- approver_id -> personnel.id
- memos (备忘/备注)
- author_id -> personnel.id
- related_item_id -> items.id
- finance_records (财务相关记录)
- related_item_id -> items.id
- recorded_by -> personnel.id
- email_notices (邮件通知规则/历史)
- evaluations (物品/服务评价)
- related_item_id -> items.id
- evaluator_id -> personnel.id
- attachments (文件附件存储索引表)
- uploaded_by -> personnel.id
+228
View File
@@ -0,0 +1,228 @@
-- 用户表(若项目使用 Django auth,可忽略或与 auth_user 对应)
DROP TABLE IF EXISTS account;
CREATE TABLE account
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(150) NOT NULL UNIQUE,
password VARCHAR(128) NOT NULL,
email VARCHAR(254),
full_name VARCHAR(200),
is_active TINYINT(1) NOT NULL DEFAULT 1,
is_staff TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='系统用户/认证信息';
-- 部门表
DROP TABLE IF EXISTS departments;
CREATE TABLE departments
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
code VARCHAR(50),
parent_id BIGINT UNSIGNED,
description TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_departments_parent FOREIGN KEY (parent_id) REFERENCES departments (id) ON DELETE SET NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='组织/部门';
-- 人员表(可映射到 Django 的 profile)
DROP TABLE IF EXISTS personnel;
CREATE TABLE personnel
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED, -- 关联 account 表(可为空)
employee_no VARCHAR(64) UNIQUE,
name VARCHAR(100) NOT NULL,
department_id BIGINT UNSIGNED,
phone VARCHAR(50),
email VARCHAR(254),
role VARCHAR(100),
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_personnel_user FOREIGN KEY (user_id) REFERENCES account (id) ON DELETE SET NULL,
CONSTRAINT fk_personnel_department FOREIGN KEY (department_id) REFERENCES departments (id) ON DELETE SET NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='人员信息';
-- 物品类别
DROP TABLE IF EXISTS categories;
CREATE TABLE categories
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(150) NOT NULL,
parent_id BIGINT UNSIGNED,
description TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_categories_parent FOREIGN KEY (parent_id) REFERENCES categories (id) ON DELETE SET NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='物品类别';
-- 物品(物品主表)
DROP TABLE IF EXISTS items;
CREATE TABLE items
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(100) NOT NULL UNIQUE, -- 物品编号/资产编号
name VARCHAR(200) NOT NULL,
category_id BIGINT UNSIGNED,
model VARCHAR(150),
brand VARCHAR(150),
location VARCHAR(200),
total_quantity INT NOT NULL DEFAULT 0,
available_quantity INT NOT NULL DEFAULT 0,
unit VARCHAR(30) DEFAULT '件',
price DECIMAL(12, 2) DEFAULT NULL,
purchase_date DATE DEFAULT NULL,
supplier VARCHAR(200) DEFAULT NULL,
status VARCHAR(50) DEFAULT 'normal', -- 如 normal/disabled/lost/repair
description TEXT,
image_path VARCHAR(500),
created_by BIGINT UNSIGNED,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_items_category FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE SET NULL,
CONSTRAINT fk_items_creator FOREIGN KEY (created_by) REFERENCES personnel (id) ON DELETE SET NULL,
INDEX (name),
INDEX (code)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='物品基本信息';
-- 物品库存/流水(每次入库/出库/调整都记录)
DROP TABLE IF EXISTS item_transactions;
CREATE TABLE item_transactions
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
item_id BIGINT UNSIGNED NOT NULL,
trans_type ENUM ('IN','OUT','ADJUST','TRANSFER') NOT NULL,
quantity INT NOT NULL,
from_location VARCHAR(200),
to_location VARCHAR(200),
related_id BIGINT UNSIGNED, -- 可关联采购单/借用记录/调拨单 id
operator_id BIGINT UNSIGNED, -- personnel 操作者
note TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_trans_item FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE,
CONSTRAINT fk_trans_operator FOREIGN KEY (operator_id) REFERENCES personnel (id) ON DELETE SET NULL,
INDEX (item_id),
INDEX (trans_type)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='物品出入库与调整流水';
-- 借用记录(借/还)
DROP TABLE IF EXISTS borrow_records;
CREATE TABLE borrow_records
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
item_id BIGINT UNSIGNED NOT NULL,
borrower_id BIGINT UNSIGNED NOT NULL, -- personnel id
quantity INT NOT NULL DEFAULT 1,
borrow_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
due_date DATETIME DEFAULT NULL,
return_date DATETIME DEFAULT NULL,
status ENUM ('BORROWED','RETURNED','OVERDUE','CANCELED') NOT NULL DEFAULT 'BORROWED',
approver_id BIGINT UNSIGNED DEFAULT NULL,
note TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_borrow_item FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE,
CONSTRAINT fk_borrow_borrower FOREIGN KEY (borrower_id) REFERENCES personnel (id) ON DELETE CASCADE,
CONSTRAINT fk_borrow_approver FOREIGN KEY (approver_id) REFERENCES personnel (id) ON DELETE SET NULL,
INDEX (borrower_id),
INDEX (status)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='借用/归还记录';
-- 备忘/便签
DROP TABLE IF EXISTS memos;
CREATE TABLE memos
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
author_id BIGINT UNSIGNED,
related_item_id BIGINT UNSIGNED,
is_public TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_memo_author FOREIGN KEY (author_id) REFERENCES personnel (id) ON DELETE SET NULL,
CONSTRAINT fk_memo_item FOREIGN KEY (related_item_id) REFERENCES items (id) ON DELETE SET NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='备忘/备注';
-- 财务记录(采购/报销等)
DROP TABLE IF EXISTS finance_records;
CREATE TABLE finance_records
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
record_no VARCHAR(100) UNIQUE,
type ENUM ('PURCHASE','REFUND','REPAIR','DEPRECIATION','OTHER') NOT NULL,
related_item_id BIGINT UNSIGNED,
amount DECIMAL(14, 2) NOT NULL DEFAULT 0.00,
currency VARCHAR(10) DEFAULT 'CNY',
vendor VARCHAR(200),
occurred_at DATETIME,
recorded_by BIGINT UNSIGNED,
note TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_finance_item FOREIGN KEY (related_item_id) REFERENCES items (id) ON DELETE SET NULL,
CONSTRAINT fk_finance_user FOREIGN KEY (recorded_by) REFERENCES personnel (id) ON DELETE SET NULL,
INDEX (record_no)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='财务相关记录';
-- 邮件通知规则/历史
DROP TABLE IF EXISTS email_notices;
CREATE TABLE email_notices
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200),
target_emails TEXT, -- 以逗号分隔或 JSON 数组
subject VARCHAR(300),
body TEXT,
enabled TINYINT(1) NOT NULL DEFAULT 1,
schedule_cron VARCHAR(100), -- 若使用定时任务
last_sent_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='邮件通知规则/历史';
-- 评价/反馈(evaluation 模块对应)
DROP TABLE IF EXISTS evaluations;
CREATE TABLE evaluations
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
related_item_id BIGINT UNSIGNED,
evaluator_id BIGINT UNSIGNED,
score TINYINT UNSIGNED, -- 1-5
comment TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_evaluation_item FOREIGN KEY (related_item_id) REFERENCES items (id) ON DELETE SET NULL,
CONSTRAINT fk_evaluation_user FOREIGN KEY (evaluator_id) REFERENCES personnel (id) ON DELETE SET NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='物品/服务评价';
-- 附件表(文件存储路径)
DROP TABLE IF EXISTS attachments;
CREATE TABLE attachments
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
related_table VARCHAR(100), -- 指示关联表,如 'items','memos'
related_id BIGINT UNSIGNED, -- 关联记录 ID
file_name VARCHAR(300),
file_path VARCHAR(1000),
file_size BIGINT UNSIGNED,
content_type VARCHAR(200),
uploaded_by BIGINT UNSIGNED,
uploaded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_attachments_user FOREIGN KEY (uploaded_by) REFERENCES personnel (id) ON DELETE SET NULL,
INDEX (related_table, related_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='文件附件存储索引表';
@@ -1,56 +1,39 @@
package club.itstudio.itmanager.entity package club.itstudio.itmanager.entity
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KProperty import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
import kotlin.reflect.full.createInstance import kotlin.reflect.full.createInstance
import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.memberProperties import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.isAccessible
interface DataCopy { interface DataCopy {
fun <T : Any> toAnotherObject( fun <T : Any> toAnotherObject(
toClass: KClass<T>, toClass: KClass<T>,
otherProperties: Map<String, Any?> otherProperties: Map<String, Any?> = emptyMap()
): T { ): T {
try { @Suppress("UNCHECKED_CAST")
val voProperties = toClass.declaredMemberProperties val sourceProps = this::class.memberProperties
.associate { it.name to (it as KProperty1<Any, *>).get(this) }
return try {
val vo = toClass.createInstance() val vo = toClass.createInstance()
for (voProperty in voProperties) for (prop in toClass.declaredMemberProperties) {
copyFieldData(voProperty, otherProperties, vo) @Suppress("UNCHECKED_CAST")
return vo val mutableProp = prop as? KMutableProperty1<Any, Any?> ?: continue
val value = when {
sourceProps.containsKey(prop.name) -> sourceProps[prop.name]
otherProperties.containsKey(prop.name) -> otherProperties[prop.name]
else -> continue
}
mutableProp.isAccessible = true
mutableProp.set(vo, value)
}
vo
} catch (e: ReflectiveOperationException) { } catch (e: ReflectiveOperationException) {
throw RuntimeException( throw RuntimeException(
"${toClass.simpleName} has no fields.", "Failed to instantiate ${toClass.simpleName}. Does it have a no-arg constructor?", e
e
) )
} }
} }
private fun copyFieldData(
toProperty: KProperty<*>,
otherProperties: Map<String, Any?>,
another: Any
) {
try {
val thisProperty = this::class.memberProperties
.firstOrNull { it.name == toProperty.name }
val value =
if (thisProperty != null) {
thisProperty.getter.call(this)
} else if (otherProperties.containsKey(toProperty.name))
otherProperties[toProperty.name]
else
throw RuntimeException(
"Property ${toProperty.name} not found."
)
val field = toProperty.javaField!!
field.isAccessible = true
field.set(another, value)
} catch (e: Exception) {
e.printStackTrace()
}
}
} }
@@ -11,10 +11,13 @@ import kotlinx.serialization.Serializable
@TableName("account") @TableName("account")
data class Account( data class Account(
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
val id: Int? = null, val id: Long? = null,
val username: String = "", val username: String = "",
val password: String = "", val password: String = "",
val email: String = "", val email: String? = null,
val role: String = "", val fullName: String? = null,
val registerTime: LocalDateTime? = null val isActive: Boolean = true,
val isStaff: Boolean = false,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy ) : DataCopy
@@ -0,0 +1,23 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("attachments")
data class Attachment(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val relatedTable: String? = null,
val relatedId: Long? = null,
val fileName: String? = null,
val filePath: String? = null,
val fileSize: Long? = null,
val contentType: String? = null,
val uploadedBy: Long? = null,
val uploadedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,26 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("borrow_records")
data class BorrowRecord(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val itemId: Long = 0,
val borrowerId: Long = 0,
val quantity: Int = 1,
val borrowDate: LocalDateTime? = null,
val dueDate: LocalDateTime? = null,
val returnDate: LocalDateTime? = null,
val status: String = "BORROWED", // BORROWED / RETURNED / OVERDUE / CANCELED
val approverId: Long? = null,
val note: String? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,20 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("categories")
data class Category(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val name: String = "",
val parentId: Long? = null,
val description: String? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,21 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("departments")
data class Department(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val name: String = "",
val code: String? = null,
val parentId: Long? = null,
val description: String? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,24 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("email_notices")
data class EmailNotice(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val name: String? = null,
val targetEmails: String? = null,
val subject: String? = null,
val body: String? = null,
val enabled: Boolean = true,
val scheduleCron: String? = null,
val lastSentAt: LocalDateTime? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,20 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("evaluations")
data class Evaluation(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val relatedItemId: Long? = null,
val evaluatorId: Long? = null,
val score: Short? = null, // 1-5
val comment: String? = null,
val createdAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,26 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
import java.math.BigDecimal
@Serializable
@TableName("finance_records")
data class FinanceRecord(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val recordNo: String? = null,
val type: String = "", // PURCHASE / REFUND / REPAIR / DEPRECIATION / OTHER
val relatedItemId: Long? = null,
val amount: BigDecimal = BigDecimal.ZERO,
val currency: String = "CNY",
val vendor: String? = null,
val occurredAt: LocalDateTime? = null,
val recordedBy: Long? = null,
val note: String? = null,
val createdAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,35 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
import java.math.BigDecimal
@Serializable
@TableName("items")
data class Item(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val code: String = "",
val name: String = "",
val categoryId: Long? = null,
val model: String? = null,
val brand: String? = null,
val location: String? = null,
val totalQuantity: Int = 0,
val availableQuantity: Int = 0,
val unit: String = "件",
val price: BigDecimal? = null,
val purchaseDate: LocalDate? = null,
val supplier: String? = null,
val status: String = "normal",
val description: String? = null,
val imagePath: String? = null,
val createdBy: Long? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,24 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("item_transactions")
data class ItemTransaction(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val itemId: Long = 0,
val transType: String = "", // IN / OUT / ADJUST / TRANSFER
val quantity: Int = 0,
val fromLocation: String? = null,
val toLocation: String? = null,
val relatedId: Long? = null,
val operatorId: Long? = null,
val note: String? = null,
val createdAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,22 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("memos")
data class Memo(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val title: String? = null,
val content: String? = null,
val authorId: Long? = null,
val relatedItemId: Long? = null,
val isPublic: Boolean = false,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,25 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("personnel")
data class Personnel(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val userId: Long? = null,
val employeeNo: String? = null,
val name: String = "",
val departmentId: Long? = null,
val phone: String? = null,
val email: String? = null,
val role: String? = null,
val isActive: Boolean = true,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Attachment
import org.apache.ibatis.annotations.Mapper
@Mapper
interface AttachmentMapper : BaseMapper<Attachment>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.BorrowRecord
import org.apache.ibatis.annotations.Mapper
@Mapper
interface BorrowRecordMapper : BaseMapper<BorrowRecord>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Category
import org.apache.ibatis.annotations.Mapper
@Mapper
interface CategoryMapper : BaseMapper<Category>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Department
import org.apache.ibatis.annotations.Mapper
@Mapper
interface DepartmentMapper : BaseMapper<Department>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.EmailNotice
import org.apache.ibatis.annotations.Mapper
@Mapper
interface EmailNoticeMapper : BaseMapper<EmailNotice>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Evaluation
import org.apache.ibatis.annotations.Mapper
@Mapper
interface EvaluationMapper : BaseMapper<Evaluation>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.FinanceRecord
import org.apache.ibatis.annotations.Mapper
@Mapper
interface FinanceRecordMapper : BaseMapper<FinanceRecord>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Item
import org.apache.ibatis.annotations.Mapper
@Mapper
interface ItemMapper : BaseMapper<Item>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.ItemTransaction
import org.apache.ibatis.annotations.Mapper
@Mapper
interface ItemTransactionMapper : BaseMapper<ItemTransaction>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Memo
import org.apache.ibatis.annotations.Mapper
@Mapper
interface MemoMapper : BaseMapper<Memo>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Personnel
import org.apache.ibatis.annotations.Mapper
@Mapper
interface PersonnelMapper : BaseMapper<Personnel>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Attachment
interface AttachmentService : IService<Attachment>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.BorrowRecord
interface BorrowRecordService : IService<BorrowRecord>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Category
interface CategoryService : IService<Category>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Department
interface DepartmentService : IService<Department>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.EmailNotice
interface EmailNoticeService : IService<EmailNotice>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Evaluation
interface EvaluationService : IService<Evaluation>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.FinanceRecord
interface FinanceRecordService : IService<FinanceRecord>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Item
interface ItemService : IService<Item>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.ItemTransaction
interface ItemTransactionService : IService<ItemTransaction>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Memo
interface MemoService : IService<Memo>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Personnel
interface PersonnelService : IService<Personnel>
@@ -10,7 +10,7 @@ import club.itstudio.itmanager.entity.vo.response.ReloginVO
import club.itstudio.itmanager.mapper.AccountMapper import club.itstudio.itmanager.mapper.AccountMapper
import club.itstudio.itmanager.service.AccountService import club.itstudio.itmanager.service.AccountService
import club.itstudio.itmanager.utils.Const import club.itstudio.itmanager.utils.Const
import club.itstudio.itmanager.utils.DateUtils.getCurrentDateTime
import club.itstudio.itmanager.utils.FlowUtils import club.itstudio.itmanager.utils.FlowUtils
import club.itstudio.itmanager.utils.JwtUtils import club.itstudio.itmanager.utils.JwtUtils
import org.springframework.amqp.core.AmqpTemplate import org.springframework.amqp.core.AmqpTemplate
@@ -31,7 +31,6 @@ class AccountServiceImpl(
private val amqpTemplate: AmqpTemplate, private val amqpTemplate: AmqpTemplate,
private val stringRedisTemplate: ReactiveStringRedisTemplate, private val stringRedisTemplate: ReactiveStringRedisTemplate,
private val encoder: PasswordEncoder private val encoder: PasswordEncoder
) : ServiceImpl<AccountMapper, Account>(), AccountService { ) : ServiceImpl<AccountMapper, Account>(), AccountService {
override fun findByUsername(username: String): Mono<UserDetails> { override fun findByUsername(username: String): Mono<UserDetails> {
@@ -40,7 +39,7 @@ class AccountServiceImpl(
.map { account -> .map { account ->
User.withUsername(username) User.withUsername(username)
.password(account.password) .password(account.password)
.roles(account.role) .roles(if (account.isStaff) "STAFF" else "USER")
.build() .build()
} }
} }
@@ -82,7 +81,7 @@ class AccountServiceImpl(
val (email, code, username, password) = vo val (email, code, username, password) = vo
return verifyCode(email, code).flatMap { error -> return verifyCode(email, code).flatMap { error ->
if (!error.isBlank()) { if (error.isNotBlank()) {
return@flatMap Mono.just(error) return@flatMap Mono.just(error)
} }
@@ -98,12 +97,11 @@ class AccountServiceImpl(
val encodedPassword = encoder.encode(password) val encodedPassword = encoder.encode(password)
val account = Account( val account = Account(
null, username = username,
username, password = encodedPassword!!,
encodedPassword!!, email = email,
email, isActive = true,
"user", isStaff = false
getCurrentDateTime()
) )
Mono.fromCallable { this.save(account) } Mono.fromCallable { this.save(account) }
@@ -124,7 +122,7 @@ class AccountServiceImpl(
val (email, code, password) = vo val (email, code, password) = vo
return verifyCode(email, code).flatMap { error -> return verifyCode(email, code).flatMap { error ->
if (!error.isBlank()) { if (error.isNotBlank()) {
return@flatMap Mono.just(error) return@flatMap Mono.just(error)
} }
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Attachment
import club.itstudio.itmanager.mapper.AttachmentMapper
import club.itstudio.itmanager.service.AttachmentService
import org.springframework.stereotype.Service
@Service
class AttachmentServiceImpl : ServiceImpl<AttachmentMapper, Attachment>(), AttachmentService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.BorrowRecord
import club.itstudio.itmanager.mapper.BorrowRecordMapper
import club.itstudio.itmanager.service.BorrowRecordService
import org.springframework.stereotype.Service
@Service
class BorrowRecordServiceImpl : ServiceImpl<BorrowRecordMapper, BorrowRecord>(), BorrowRecordService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Category
import club.itstudio.itmanager.mapper.CategoryMapper
import club.itstudio.itmanager.service.CategoryService
import org.springframework.stereotype.Service
@Service
class CategoryServiceImpl : ServiceImpl<CategoryMapper, Category>(), CategoryService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Department
import club.itstudio.itmanager.mapper.DepartmentMapper
import club.itstudio.itmanager.service.DepartmentService
import org.springframework.stereotype.Service
@Service
class DepartmentServiceImpl : ServiceImpl<DepartmentMapper, Department>(), DepartmentService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.EmailNotice
import club.itstudio.itmanager.mapper.EmailNoticeMapper
import club.itstudio.itmanager.service.EmailNoticeService
import org.springframework.stereotype.Service
@Service
class EmailNoticeServiceImpl : ServiceImpl<EmailNoticeMapper, EmailNotice>(), EmailNoticeService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Evaluation
import club.itstudio.itmanager.mapper.EvaluationMapper
import club.itstudio.itmanager.service.EvaluationService
import org.springframework.stereotype.Service
@Service
class EvaluationServiceImpl : ServiceImpl<EvaluationMapper, Evaluation>(), EvaluationService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.FinanceRecord
import club.itstudio.itmanager.mapper.FinanceRecordMapper
import club.itstudio.itmanager.service.FinanceRecordService
import org.springframework.stereotype.Service
@Service
class FinanceRecordServiceImpl : ServiceImpl<FinanceRecordMapper, FinanceRecord>(), FinanceRecordService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Item
import club.itstudio.itmanager.mapper.ItemMapper
import club.itstudio.itmanager.service.ItemService
import org.springframework.stereotype.Service
@Service
class ItemServiceImpl : ServiceImpl<ItemMapper, Item>(), ItemService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.ItemTransaction
import club.itstudio.itmanager.mapper.ItemTransactionMapper
import club.itstudio.itmanager.service.ItemTransactionService
import org.springframework.stereotype.Service
@Service
class ItemTransactionServiceImpl : ServiceImpl<ItemTransactionMapper, ItemTransaction>(), ItemTransactionService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Memo
import club.itstudio.itmanager.mapper.MemoMapper
import club.itstudio.itmanager.service.MemoService
import org.springframework.stereotype.Service
@Service
class MemoServiceImpl : ServiceImpl<MemoMapper, Memo>(), MemoService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Personnel
import club.itstudio.itmanager.mapper.PersonnelMapper
import club.itstudio.itmanager.service.PersonnelService
import org.springframework.stereotype.Service
@Service
class PersonnelServiceImpl : ServiceImpl<PersonnelMapper, Personnel>(), PersonnelService
@@ -4,13 +4,14 @@ import reactor.core.publisher.Mono
object Const { object Const {
const val FLOW_LIMIT_ORDER = -101 const val FLOW_LIMIT_ORDER = -101
const val JWT_BLACK_LIST = "jwt:blacklist:" const val JWT_BLACK_LIST = "jwt:blacklist:"
const val VERIFY_EMAIL_LIMIT = "verify:email:limit:" const val VERIFY_EMAIL_LIMIT = "verify:email:limit:"
const val VERIFY_EMAIL_DATA = "verify:email:data:" const val VERIFY_EMAIL_DATA = "verify:email:data:"
const val FLOW_LIMIT_COUNTER = "flow:counter:" const val FLOW_LIMIT_COUNTER = "flow:counter:"
const val FLOW_LIMIT_BLOCK = "flow:block:" const val FLOW_LIMIT_BLOCK = "flow:block:"
val INTERNAL_ERROR_MONO = Mono.just("something went wrong. Please contact the administrator.") const val INTERNAL_ERROR_STR = "something went wrong. Please contact the administrator."
val INTERNAL_ERROR_MONO = Mono.just(INTERNAL_ERROR_STR)
} }
@@ -4,6 +4,7 @@ import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.interfaces.DecodedJWT import com.auth0.jwt.interfaces.DecodedJWT
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.data.redis.core.StringRedisTemplate import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.User
@@ -16,14 +17,15 @@ import java.util.concurrent.TimeUnit
class JwtUtils( class JwtUtils(
@param:Value($$"${spring.security.jwt.key}") @param:Value($$"${spring.security.jwt.key}")
private val key: String, private val key: String,
@param:Value($$"${spring.security.jwt.expire-hours}") @param:Value($$"${spring.security.jwt.expire-hours}")
private val expireHours: Int, private val expireHours: Int,
private val template: StringRedisTemplate, private val template: StringRedisTemplate
private val algorithm: Algorithm = Algorithm.HMAC256(key),
private val jwtVerifier: JWTVerifier = JWT.require(algorithm).build()
) { ) {
private val algorithm: Algorithm = Algorithm.HMAC256(key)
private val jwtVerifier: JWTVerifier = JWT.require(algorithm).build()
private val logger = LoggerFactory.getLogger(this::class.java)
fun invalidateJwt(headerToken: String?): Boolean { fun invalidateJwt(headerToken: String?): Boolean {
val token = this.convertToToken(headerToken) ?: return false val token = this.convertToToken(headerToken) ?: return false
try { try {
@@ -31,7 +33,7 @@ class JwtUtils(
val id = jwt.id val id = jwt.id
return deleteToken(id, jwt.expiresAt) return deleteToken(id, jwt.expiresAt)
} catch (e: Exception) { } catch (e: Exception) {
println(e.message) logger.error("Error occurred while invalidating JWT", e)
return false return false
} }
} }
@@ -62,14 +64,14 @@ class JwtUtils(
else decodedJWT else decodedJWT
} catch (e: Exception) { } catch (e: Exception) {
println(e.message) logger.error("Error occurred while resolving JWT", e)
return null return null
} }
} }
fun createJwt( fun createJwt(
details: UserDetails, details: UserDetails,
id: Int, id: Long,
username: String username: String
): String { ): String {
return JWT.create() return JWT.create()
@@ -94,7 +96,7 @@ class JwtUtils(
.build() .build()
} }
fun toId(jwt: DecodedJWT) = jwt.claims["id"]?.asInt() ?: -1 fun toId(jwt: DecodedJWT) = jwt.claims["id"]?.asLong() ?: -1L
fun expiresTime(): Date { fun expiresTime(): Date {
val cal = Calendar.getInstance() val cal = Calendar.getInstance()