init
This commit is contained in:
13
.devcontainer/README.md
Normal file
13
.devcontainer/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# VS Code Docker Dev
|
||||||
|
|
||||||
|
This repository provides two VS Code Dev Container modes:
|
||||||
|
|
||||||
|
- `external`: connect to externally managed PostgreSQL and Redis
|
||||||
|
- `internal`: create PostgreSQL and Redis inside the dev environment
|
||||||
|
|
||||||
|
Open the wanted config in VS Code:
|
||||||
|
|
||||||
|
- `.devcontainer/external/devcontainer.json`
|
||||||
|
- `.devcontainer/internal/devcontainer.json`
|
||||||
|
|
||||||
|
Both modes mount the repository into `/workspaces/K12study`.
|
||||||
8
.devcontainer/external/.env.example
vendored
Normal file
8
.devcontainer/external/.env.example
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
K12STUDY_DB_HOST=host.docker.internal
|
||||||
|
K12STUDY_DB_PORT=5432
|
||||||
|
K12STUDY_DB_NAME=k12study
|
||||||
|
K12STUDY_DB_USER=k12study
|
||||||
|
K12STUDY_DB_PASSWORD=k12study
|
||||||
|
K12STUDY_REDIS_HOST=host.docker.internal
|
||||||
|
K12STUDY_REDIS_PORT=6379
|
||||||
|
K12STUDY_REDIS_PASSWORD=
|
||||||
31
.devcontainer/external/devcontainer.json
vendored
Normal file
31
.devcontainer/external/devcontainer.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "K12Study External DB Dev",
|
||||||
|
"dockerComposeFile": [
|
||||||
|
"docker-compose.yml"
|
||||||
|
],
|
||||||
|
"service": "workspace",
|
||||||
|
"workspaceFolder": "/workspaces/K12study",
|
||||||
|
"shutdownAction": "stopCompose",
|
||||||
|
"forwardPorts": [5173, 8080, 8081, 8082, 8088, 9000],
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"settings": {
|
||||||
|
"terminal.integrated.defaultProfile.linux": "bash"
|
||||||
|
},
|
||||||
|
"extensions": [
|
||||||
|
"vscjava.vscode-java-pack",
|
||||||
|
"redhat.java",
|
||||||
|
"vmware.vscode-boot-dev-pack",
|
||||||
|
"ms-azuretools.vscode-docker",
|
||||||
|
"ms-python.python",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"remoteEnv": {
|
||||||
|
"SPRING_PROFILES_ACTIVE": "dev",
|
||||||
|
"K12STUDY_DEV_MODE": "external"
|
||||||
|
},
|
||||||
|
"postCreateCommand": "cd /workspaces/K12study/frontend && pnpm install"
|
||||||
|
}
|
||||||
19
.devcontainer/external/docker-compose.yml
vendored
Normal file
19
.devcontainer/external/docker-compose.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
services:
|
||||||
|
workspace:
|
||||||
|
image: mcr.microsoft.com/devcontainers/universal:2
|
||||||
|
volumes:
|
||||||
|
- ../../:/workspaces/K12study:cached
|
||||||
|
working_dir: /workspaces/K12study
|
||||||
|
command: sleep infinity
|
||||||
|
environment:
|
||||||
|
K12STUDY_DB_HOST: ${K12STUDY_DB_HOST:-host.docker.internal}
|
||||||
|
K12STUDY_DB_PORT: ${K12STUDY_DB_PORT:-5432}
|
||||||
|
K12STUDY_DB_NAME: ${K12STUDY_DB_NAME:-k12study}
|
||||||
|
K12STUDY_DB_USER: ${K12STUDY_DB_USER:-k12study}
|
||||||
|
K12STUDY_DB_PASSWORD: ${K12STUDY_DB_PASSWORD:-k12study}
|
||||||
|
K12STUDY_REDIS_HOST: ${K12STUDY_REDIS_HOST:-host.docker.internal}
|
||||||
|
K12STUDY_REDIS_PORT: ${K12STUDY_REDIS_PORT:-6379}
|
||||||
|
K12STUDY_REDIS_PASSWORD: ${K12STUDY_REDIS_PASSWORD:-}
|
||||||
|
PYTHONUNBUFFERED: "1"
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
32
.devcontainer/internal/devcontainer.json
Normal file
32
.devcontainer/internal/devcontainer.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "K12Study Internal DB Dev",
|
||||||
|
"dockerComposeFile": [
|
||||||
|
"docker-compose.yml"
|
||||||
|
],
|
||||||
|
"service": "workspace",
|
||||||
|
"workspaceFolder": "/workspaces/K12study",
|
||||||
|
"runServices": ["workspace", "postgres", "redis"],
|
||||||
|
"shutdownAction": "stopCompose",
|
||||||
|
"forwardPorts": [5173, 8080, 8081, 8082, 8088, 9000, 5432, 6379],
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"settings": {
|
||||||
|
"terminal.integrated.defaultProfile.linux": "bash"
|
||||||
|
},
|
||||||
|
"extensions": [
|
||||||
|
"vscjava.vscode-java-pack",
|
||||||
|
"redhat.java",
|
||||||
|
"vmware.vscode-boot-dev-pack",
|
||||||
|
"ms-azuretools.vscode-docker",
|
||||||
|
"ms-python.python",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"remoteEnv": {
|
||||||
|
"SPRING_PROFILES_ACTIVE": "dev",
|
||||||
|
"K12STUDY_DEV_MODE": "internal"
|
||||||
|
},
|
||||||
|
"postCreateCommand": "cd /workspaces/K12study/frontend && pnpm install"
|
||||||
|
}
|
||||||
46
.devcontainer/internal/docker-compose.yml
Normal file
46
.devcontainer/internal/docker-compose.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
services:
|
||||||
|
workspace:
|
||||||
|
image: mcr.microsoft.com/devcontainers/universal:2
|
||||||
|
volumes:
|
||||||
|
- ../../:/workspaces/K12study:cached
|
||||||
|
working_dir: /workspaces/K12study
|
||||||
|
command: sleep infinity
|
||||||
|
environment:
|
||||||
|
K12STUDY_DB_HOST: postgres
|
||||||
|
K12STUDY_DB_PORT: 5432
|
||||||
|
K12STUDY_DB_NAME: k12study
|
||||||
|
K12STUDY_DB_USER: k12study
|
||||||
|
K12STUDY_DB_PASSWORD: k12study
|
||||||
|
K12STUDY_REDIS_HOST: redis
|
||||||
|
K12STUDY_REDIS_PORT: 6379
|
||||||
|
K12STUDY_REDIS_PASSWORD: ""
|
||||||
|
PYTHONUNBUFFERED: "1"
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:16
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: k12study
|
||||||
|
POSTGRES_USER: k12study
|
||||||
|
POSTGRES_PASSWORD: k12study
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres-dev-data:/var/lib/postgresql/data
|
||||||
|
- ../../init/pg:/docker-entrypoint-initdb.d
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
volumes:
|
||||||
|
- redis-dev-data:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-dev-data:
|
||||||
|
redis-dev-data:
|
||||||
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
[*.{js,ts,jsx,tsx,json,java,py,yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.{md,txt}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
urbanLifeline
|
||||||
|
Tik
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
.tmp-run/
|
||||||
|
|
||||||
|
**/target/
|
||||||
|
**/node_modules/
|
||||||
|
**/dist/
|
||||||
|
**/.turbo/
|
||||||
|
**/.cache/
|
||||||
|
**/.pytest_cache/
|
||||||
|
**/__pycache__/
|
||||||
|
**/*.pyc
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
frontend/pnpm-lock.yaml
|
||||||
71
README.md
Normal file
71
README.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# K12Study
|
||||||
|
|
||||||
|
K12Study 是首版 K12 智能学习系统项目骨架,当前聚焦“可运行基础框架”,用于承接多区域、多租户、多角色的后续业务建设。
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
- `backend`: Java 多模块后端、网关、本地聚合启动、Python AI 占位服务
|
||||||
|
- `frontend`: 单一 React 管理端项目,保留 `api / types / utils / components / dynamic route`
|
||||||
|
- `app`: 微信小程序项目骨架,独立于 Web 管理端
|
||||||
|
- `init/pg`: PostgreSQL 初始化脚本,按模块拆分维护
|
||||||
|
- `docs`: 产品、架构和实施计划文档
|
||||||
|
|
||||||
|
## 运行模式
|
||||||
|
|
||||||
|
- 分布式模式:独立启动 `gateway`、`auth`、`upms`、`python-ai`
|
||||||
|
- 本地模式:通过 `backend/boot-dev` 单进程暴露 `/api/auth/**`、`/api/upms/**`
|
||||||
|
|
||||||
|
## VS Code Docker Dev
|
||||||
|
|
||||||
|
提供两套 dev container:
|
||||||
|
|
||||||
|
- `.devcontainer/external/devcontainer.json`
|
||||||
|
连接外部 PostgreSQL / Redis
|
||||||
|
- `.devcontainer/internal/devcontainer.json`
|
||||||
|
在开发环境内部创建 PostgreSQL / Redis
|
||||||
|
|
||||||
|
代码与本地目录同步方式是 Docker bind mount,不是额外复制:
|
||||||
|
|
||||||
|
- 宿主机项目目录直接挂载到容器工作目录
|
||||||
|
- 本地改代码,容器内实时可见
|
||||||
|
- 容器里安装的依赖和运行环境与本地源码共用同一份工程目录
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
mvn -q -DskipTests package
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
pnpm install
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### App
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd app
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
然后使用微信开发者工具打开 `app/project.config.json`。
|
||||||
|
|
||||||
|
### Python AI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend/python-ai
|
||||||
|
pip install -r requirements.txt
|
||||||
|
uvicorn app.main:app --reload --port 9000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d postgres redis
|
||||||
|
```
|
||||||
17
app/README.md
Normal file
17
app/README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# K12Study App
|
||||||
|
|
||||||
|
`app` 目录用于微信小程序项目骨架,占位承接家长端、学生端或轻量移动端能力。
|
||||||
|
|
||||||
|
## 当前结构
|
||||||
|
|
||||||
|
- `src/app.*`: 小程序全局入口
|
||||||
|
- `src/pages/home`: 首页占位
|
||||||
|
- `src/pages/profile`: 我的页占位
|
||||||
|
- `src/api`: 调用后端 `/api/auth`、`/api/upms` 的接口封装预留
|
||||||
|
- `src/utils/request.js`: 小程序请求基础封装
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
1. 使用微信开发者工具打开 [project.config.json](/f:/Project/K12study/app/project.config.json)
|
||||||
|
2. 确认 `miniprogramRoot` 指向 `src`
|
||||||
|
3. 按实际环境修改 `src/utils/request.js` 中的 `BASE_URL`
|
||||||
9
app/package.json
Normal file
9
app/package.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "k12study-app",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "echo Open this project with WeChat DevTools",
|
||||||
|
"lint": "echo Add miniprogram lint rules when business code grows"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/project.config.json
Normal file
16
app/project.config.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"description": "K12Study 微信小程序骨架",
|
||||||
|
"compileType": "miniprogram",
|
||||||
|
"miniprogramRoot": "src/",
|
||||||
|
"srcMiniprogramRoot": "src/",
|
||||||
|
"appid": "touristappid",
|
||||||
|
"setting": {
|
||||||
|
"urlCheck": false,
|
||||||
|
"es6": true,
|
||||||
|
"enhance": true,
|
||||||
|
"postcss": true,
|
||||||
|
"minified": false
|
||||||
|
},
|
||||||
|
"simulatorType": "wechat",
|
||||||
|
"libVersion": "trial"
|
||||||
|
}
|
||||||
13
app/src/api/auth.js
Normal file
13
app/src/api/auth.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const { request } = require("../utils/request");
|
||||||
|
|
||||||
|
function login(data) {
|
||||||
|
return request({
|
||||||
|
url: "/api/auth/login",
|
||||||
|
method: "POST",
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
login
|
||||||
|
};
|
||||||
12
app/src/api/upms.js
Normal file
12
app/src/api/upms.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const { request } = require("../utils/request");
|
||||||
|
|
||||||
|
function getRouteMeta() {
|
||||||
|
return request({
|
||||||
|
url: "/api/upms/routes",
|
||||||
|
method: "GET"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getRouteMeta
|
||||||
|
};
|
||||||
5
app/src/app.js
Normal file
5
app/src/app.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
App({
|
||||||
|
globalData: {
|
||||||
|
userInfo: null
|
||||||
|
}
|
||||||
|
});
|
||||||
14
app/src/app.json
Normal file
14
app/src/app.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
"pages/home/index",
|
||||||
|
"pages/profile/index"
|
||||||
|
],
|
||||||
|
"window": {
|
||||||
|
"navigationBarTitleText": "K12Study",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"backgroundColor": "#f5f7fb"
|
||||||
|
},
|
||||||
|
"style": "v2",
|
||||||
|
"sitemapLocation": "sitemap.json"
|
||||||
|
}
|
||||||
17
app/src/app.wxss
Normal file
17
app/src/app.wxss
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
page {
|
||||||
|
background: #f5f7fb;
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 32rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 12rpx 40rpx rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
6
app/src/pages/home/index.js
Normal file
6
app/src/pages/home/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
title: "K12Study 小程序骨架",
|
||||||
|
description: "这里先放首页占位,后续可扩展为家长端或学生端入口。"
|
||||||
|
}
|
||||||
|
});
|
||||||
3
app/src/pages/home/index.json
Normal file
3
app/src/pages/home/index.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"navigationBarTitleText": "首页"
|
||||||
|
}
|
||||||
6
app/src/pages/home/index.wxml
Normal file
6
app/src/pages/home/index.wxml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<view class="page">
|
||||||
|
<view class="card">
|
||||||
|
<view>{{title}}</view>
|
||||||
|
<view style="margin-top: 16rpx; color: #64748b;">{{description}}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
3
app/src/pages/home/index.wxss
Normal file
3
app/src/pages/home/index.wxss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
view {
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
6
app/src/pages/profile/index.js
Normal file
6
app/src/pages/profile/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
title: "我的",
|
||||||
|
description: "这里预留账号中心、学校切换、消息入口等能力。"
|
||||||
|
}
|
||||||
|
});
|
||||||
3
app/src/pages/profile/index.json
Normal file
3
app/src/pages/profile/index.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"navigationBarTitleText": "我的"
|
||||||
|
}
|
||||||
6
app/src/pages/profile/index.wxml
Normal file
6
app/src/pages/profile/index.wxml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<view class="page">
|
||||||
|
<view class="card">
|
||||||
|
<view>{{title}}</view>
|
||||||
|
<view style="margin-top: 16rpx; color: #64748b;">{{description}}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
3
app/src/pages/profile/index.wxss
Normal file
3
app/src/pages/profile/index.wxss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
view {
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
4
app/src/sitemap.json
Normal file
4
app/src/sitemap.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"desc": "K12Study sitemap",
|
||||||
|
"rules": []
|
||||||
|
}
|
||||||
21
app/src/utils/request.js
Normal file
21
app/src/utils/request.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const BASE_URL = "http://localhost:8088";
|
||||||
|
|
||||||
|
function request(options) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.request({
|
||||||
|
url: `${BASE_URL}${options.url}`,
|
||||||
|
method: options.method || "GET",
|
||||||
|
data: options.data,
|
||||||
|
header: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...(options.header || {})
|
||||||
|
},
|
||||||
|
success: (response) => resolve(response.data),
|
||||||
|
fail: reject
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
request
|
||||||
|
};
|
||||||
11
backend/.env.example
Normal file
11
backend/.env.example
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
K12STUDY_DB_HOST=localhost
|
||||||
|
K12STUDY_DB_PORT=5432
|
||||||
|
K12STUDY_DB_NAME=k12study
|
||||||
|
K12STUDY_DB_USER=k12study
|
||||||
|
K12STUDY_DB_PASSWORD=k12study
|
||||||
|
|
||||||
|
K12STUDY_REDIS_HOST=localhost
|
||||||
|
K12STUDY_REDIS_PORT=6379
|
||||||
|
K12STUDY_REDIS_PASSWORD=
|
||||||
|
|
||||||
|
AI_CLIENT_BASE_URL=http://localhost:9000
|
||||||
23
backend/ai-client/pom.xml
Normal file
23
backend/ai-client/pom.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>ai-client</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>api-ai</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.k12study.aiclient.client;
|
||||||
|
|
||||||
|
import com.k12study.api.ai.dto.AiHealthDto;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
public class HttpPythonAiClient implements PythonAiClient {
|
||||||
|
private final RestClient restClient;
|
||||||
|
|
||||||
|
public HttpPythonAiClient(RestClient restClient) {
|
||||||
|
this.restClient = restClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AiHealthDto health() {
|
||||||
|
try {
|
||||||
|
AiHealthDto response = restClient.get()
|
||||||
|
.uri("/health")
|
||||||
|
.retrieve()
|
||||||
|
.body(AiHealthDto.class);
|
||||||
|
return response == null ? new AiHealthDto("python-ai", "UNKNOWN", "0.1.0") : response;
|
||||||
|
} catch (Exception exception) {
|
||||||
|
return new AiHealthDto("python-ai", "UNREACHABLE", "0.1.0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.k12study.aiclient.client;
|
||||||
|
|
||||||
|
import com.k12study.api.ai.dto.AiHealthDto;
|
||||||
|
|
||||||
|
public interface PythonAiClient {
|
||||||
|
AiHealthDto health();
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.k12study.aiclient.config;
|
||||||
|
|
||||||
|
import com.k12study.aiclient.client.HttpPythonAiClient;
|
||||||
|
import com.k12study.aiclient.client.PythonAiClient;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(AiClientProperties.class)
|
||||||
|
public class AiClientAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestClient aiRestClient(AiClientProperties properties) {
|
||||||
|
return RestClient.builder().baseUrl(properties.getBaseUrl()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PythonAiClient pythonAiClient(RestClient aiRestClient) {
|
||||||
|
return new HttpPythonAiClient(aiRestClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.k12study.aiclient.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "ai.client")
|
||||||
|
public class AiClientProperties {
|
||||||
|
private String baseUrl = "http://localhost:9000";
|
||||||
|
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseUrl(String baseUrl) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
backend/apis/api-ai/pom.xml
Normal file
25
backend/apis/api-ai/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>api-ai</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.k12study.api.ai.dto;
|
||||||
|
|
||||||
|
public record AiHealthDto(
|
||||||
|
String name,
|
||||||
|
String status,
|
||||||
|
String version
|
||||||
|
) {
|
||||||
|
}
|
||||||
25
backend/apis/api-auth/pom.xml
Normal file
25
backend/apis/api-auth/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>api-auth</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.k12study.api.auth.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record CurrentUserResponse(
|
||||||
|
String userId,
|
||||||
|
String username,
|
||||||
|
String displayName,
|
||||||
|
String provinceCode,
|
||||||
|
String areaCode,
|
||||||
|
String tenantId,
|
||||||
|
String deptId,
|
||||||
|
List<String> roles
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.k12study.api.auth.dto;
|
||||||
|
|
||||||
|
public record LoginRequest(
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
String provinceCode,
|
||||||
|
String areaCode,
|
||||||
|
String tenantId
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.k12study.api.auth.dto;
|
||||||
|
|
||||||
|
public record TokenResponse(
|
||||||
|
String accessToken,
|
||||||
|
String refreshToken,
|
||||||
|
String tokenType,
|
||||||
|
long expiresIn
|
||||||
|
) {
|
||||||
|
}
|
||||||
25
backend/apis/api-upms/pom.xml
Normal file
25
backend/apis/api-upms/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>api-upms</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.k12study.api.upms.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record AreaNodeDto(
|
||||||
|
String areaCode,
|
||||||
|
String areaName,
|
||||||
|
String areaLevel,
|
||||||
|
String provinceCode,
|
||||||
|
List<AreaNodeDto> children
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.k12study.api.upms.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record CurrentRouteUserDto(
|
||||||
|
String userId,
|
||||||
|
String username,
|
||||||
|
String displayName,
|
||||||
|
String tenantId,
|
||||||
|
String deptId,
|
||||||
|
List<String> permissionCodes
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.k12study.api.upms.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record DeptNodeDto(
|
||||||
|
String deptId,
|
||||||
|
String deptName,
|
||||||
|
String deptType,
|
||||||
|
String tenantId,
|
||||||
|
String deptPath,
|
||||||
|
List<DeptNodeDto> children
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.k12study.api.upms.dto;
|
||||||
|
|
||||||
|
public enum LayoutType {
|
||||||
|
DEFAULT,
|
||||||
|
SIDEBAR
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.k12study.api.upms.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record RouteMetaDto(
|
||||||
|
String title,
|
||||||
|
String icon,
|
||||||
|
List<String> permissionCodes,
|
||||||
|
boolean hidden
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.k12study.api.upms.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record RouteNodeDto(
|
||||||
|
String id,
|
||||||
|
String path,
|
||||||
|
String name,
|
||||||
|
String component,
|
||||||
|
LayoutType layout,
|
||||||
|
RouteMetaDto meta,
|
||||||
|
List<RouteNodeDto> children
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.k12study.api.upms.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record TenantNodeDto(
|
||||||
|
String tenantId,
|
||||||
|
String tenantName,
|
||||||
|
String tenantType,
|
||||||
|
String provinceCode,
|
||||||
|
String areaCode,
|
||||||
|
String tenantPath,
|
||||||
|
List<TenantNodeDto> children
|
||||||
|
) {
|
||||||
|
}
|
||||||
21
backend/apis/pom.xml
Normal file
21
backend/apis/pom.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>apis-parent</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>api-auth</module>
|
||||||
|
<module>api-upms</module>
|
||||||
|
<module>api-ai</module>
|
||||||
|
</modules>
|
||||||
|
</project>
|
||||||
50
backend/auth/pom.xml
Normal file
50
backend/auth/pom.xml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>auth</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-web</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-security</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-redis</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>api-auth</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.k12study.auth;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication(scanBasePackages = {"com.k12study.auth", "com.k12study.common"})
|
||||||
|
public class AuthApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AuthApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.k12study.auth.config;
|
||||||
|
|
||||||
|
import com.k12study.auth.AuthApplication;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.FilterType;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan(
|
||||||
|
basePackages = "com.k12study.auth",
|
||||||
|
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AuthApplication.class)
|
||||||
|
)
|
||||||
|
public class AuthModuleConfiguration {
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.k12study.auth.controller;
|
||||||
|
|
||||||
|
import com.k12study.api.auth.dto.CurrentUserResponse;
|
||||||
|
import com.k12study.api.auth.dto.LoginRequest;
|
||||||
|
import com.k12study.api.auth.dto.TokenResponse;
|
||||||
|
import com.k12study.auth.service.AuthService;
|
||||||
|
import com.k12study.common.api.response.ApiResponse;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/auth")
|
||||||
|
public class AuthController {
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
public AuthController(AuthService authService) {
|
||||||
|
this.authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ApiResponse<TokenResponse> login(@RequestBody LoginRequest request) {
|
||||||
|
return ApiResponse.success("登录成功", authService.login(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/refresh")
|
||||||
|
public ApiResponse<TokenResponse> refresh(@RequestParam("refreshToken") String refreshToken) {
|
||||||
|
return ApiResponse.success("刷新成功", authService.refresh(refreshToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/current-user")
|
||||||
|
public ApiResponse<CurrentUserResponse> currentUser(
|
||||||
|
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
|
||||||
|
return ApiResponse.success(authService.currentUser(authorizationHeader));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.k12study.auth.service;
|
||||||
|
|
||||||
|
import com.k12study.api.auth.dto.CurrentUserResponse;
|
||||||
|
import com.k12study.api.auth.dto.LoginRequest;
|
||||||
|
import com.k12study.api.auth.dto.TokenResponse;
|
||||||
|
import com.k12study.common.security.context.RequestUserContextHolder;
|
||||||
|
import com.k12study.common.security.jwt.JwtTokenProvider;
|
||||||
|
import com.k12study.common.security.jwt.JwtUserPrincipal;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class AuthService {
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
|
||||||
|
public AuthService(JwtTokenProvider jwtTokenProvider) {
|
||||||
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenResponse login(LoginRequest request) {
|
||||||
|
String username = request.username() == null || request.username().isBlank() ? "admin" : request.username();
|
||||||
|
JwtUserPrincipal principal = new JwtUserPrincipal(
|
||||||
|
"U10001",
|
||||||
|
username,
|
||||||
|
"K12Study 管理员",
|
||||||
|
request.tenantId() == null || request.tenantId().isBlank() ? "SCH-HQ" : request.tenantId(),
|
||||||
|
"DEPT-HQ-ADMIN"
|
||||||
|
);
|
||||||
|
String accessToken = jwtTokenProvider.createAccessToken(principal);
|
||||||
|
String refreshToken = jwtTokenProvider.createAccessToken(principal);
|
||||||
|
return new TokenResponse(accessToken, refreshToken, "Bearer", 12 * 60 * 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenResponse refresh(String refreshToken) {
|
||||||
|
JwtUserPrincipal principal = jwtTokenProvider.parse(refreshToken);
|
||||||
|
String accessToken = jwtTokenProvider.createAccessToken(principal);
|
||||||
|
return new TokenResponse(accessToken, refreshToken, "Bearer", 12 * 60 * 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CurrentUserResponse currentUser(String authorizationHeader) {
|
||||||
|
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
|
||||||
|
JwtUserPrincipal principal = jwtTokenProvider.parse(authorizationHeader.substring("Bearer ".length()));
|
||||||
|
return new CurrentUserResponse(
|
||||||
|
principal.userId(),
|
||||||
|
principal.username(),
|
||||||
|
principal.displayName(),
|
||||||
|
"330000",
|
||||||
|
"330100",
|
||||||
|
principal.tenantId(),
|
||||||
|
principal.deptId(),
|
||||||
|
List.of("SUPER_ADMIN", "ORG_ADMIN")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var context = RequestUserContextHolder.get();
|
||||||
|
return new CurrentUserResponse(
|
||||||
|
context == null ? "U10001" : context.userId(),
|
||||||
|
context == null ? "admin" : context.username(),
|
||||||
|
context == null ? "K12Study 管理员" : context.displayName(),
|
||||||
|
"330000",
|
||||||
|
"330100",
|
||||||
|
context == null ? "SCH-HQ" : context.tenantId(),
|
||||||
|
context == null ? "DEPT-HQ-ADMIN" : context.deptId(),
|
||||||
|
List.of("SUPER_ADMIN", "ORG_ADMIN")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
backend/auth/src/main/resources/application.yml
Normal file
28
backend/auth/src/main/resources/application.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
server:
|
||||||
|
port: 8081
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: k12study-auth
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: ${K12STUDY_REDIS_HOST:localhost}
|
||||||
|
port: ${K12STUDY_REDIS_PORT:6379}
|
||||||
|
password: ${K12STUDY_REDIS_PASSWORD:}
|
||||||
|
|
||||||
|
management:
|
||||||
|
health:
|
||||||
|
redis:
|
||||||
|
enabled: false
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: health,info
|
||||||
|
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
gateway-mode: true
|
||||||
|
whitelist:
|
||||||
|
- /auth/login
|
||||||
|
- /auth/refresh
|
||||||
|
- /actuator/**
|
||||||
41
backend/boot-dev/pom.xml
Normal file
41
backend/boot-dev/pom.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>boot-dev</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>auth</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>upms</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>ai-client</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.k12study.bootdev;
|
||||||
|
|
||||||
|
import com.k12study.aiclient.config.AiClientAutoConfiguration;
|
||||||
|
import com.k12study.auth.config.AuthModuleConfiguration;
|
||||||
|
import com.k12study.upms.config.UpmsModuleConfiguration;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
@SpringBootApplication(scanBasePackages = {"com.k12study.bootdev", "com.k12study.common"})
|
||||||
|
@Import({AuthModuleConfiguration.class, UpmsModuleConfiguration.class, AiClientAutoConfiguration.class})
|
||||||
|
public class BootDevApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(BootDevApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
backend/boot-dev/src/main/resources/application.yml
Normal file
39
backend/boot-dev/src/main/resources/application.yml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
server:
|
||||||
|
port: 8088
|
||||||
|
servlet:
|
||||||
|
context-path: /api
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: k12study-boot-dev
|
||||||
|
autoconfigure:
|
||||||
|
exclude:
|
||||||
|
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
|
||||||
|
- org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
|
||||||
|
- com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: ${K12STUDY_REDIS_HOST:localhost}
|
||||||
|
port: ${K12STUDY_REDIS_PORT:6379}
|
||||||
|
password: ${K12STUDY_REDIS_PASSWORD:}
|
||||||
|
|
||||||
|
management:
|
||||||
|
health:
|
||||||
|
redis:
|
||||||
|
enabled: false
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: health,info
|
||||||
|
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
gateway-mode: false
|
||||||
|
whitelist:
|
||||||
|
- /auth/login
|
||||||
|
- /auth/refresh
|
||||||
|
- /actuator/**
|
||||||
|
|
||||||
|
ai:
|
||||||
|
client:
|
||||||
|
base-url: http://localhost:9000
|
||||||
25
backend/common/common-api/pom.xml
Normal file
25
backend/common/common-api/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>common-api</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.k12study.common.api.response;
|
||||||
|
|
||||||
|
import com.k12study.common.core.utils.TraceIdHolder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class ApiResponse<T> {
|
||||||
|
private final int code;
|
||||||
|
private final String message;
|
||||||
|
private final T data;
|
||||||
|
private final String traceId;
|
||||||
|
|
||||||
|
private ApiResponse(int code, String message, T data, String traceId) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
this.data = data;
|
||||||
|
this.traceId = traceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> success(T data) {
|
||||||
|
return new ApiResponse<>(0, "OK", data, TraceIdHolder.getOrCreate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> success(String message, T data) {
|
||||||
|
return new ApiResponse<>(0, message, data, TraceIdHolder.getOrCreate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> failure(int code, String message) {
|
||||||
|
return new ApiResponse<>(code, message, null, TraceIdHolder.getOrCreate());
|
||||||
|
}
|
||||||
|
}
|
||||||
24
backend/common/common-core/pom.xml
Normal file
24
backend/common/common-core/pom.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>common-core</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.k12study.common.core.constants;
|
||||||
|
|
||||||
|
public final class SecurityConstants {
|
||||||
|
public static final String TRACE_ID = "X-Trace-Id";
|
||||||
|
public static final String HEADER_USER_ID = "X-User-Id";
|
||||||
|
public static final String HEADER_USERNAME = "X-Username";
|
||||||
|
public static final String HEADER_DISPLAY_NAME = "X-Display-Name";
|
||||||
|
public static final String HEADER_TENANT_ID = "X-Tenant-Id";
|
||||||
|
public static final String HEADER_DEPT_ID = "X-Dept-Id";
|
||||||
|
|
||||||
|
private SecurityConstants() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.k12study.common.core.domain;
|
||||||
|
|
||||||
|
public record RouteKey(
|
||||||
|
String provinceCode,
|
||||||
|
String areaCode,
|
||||||
|
String tenantId,
|
||||||
|
String tenantPath,
|
||||||
|
String deptId,
|
||||||
|
String deptPath
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.k12study.common.core.utils;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public final class TraceIdHolder {
|
||||||
|
private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
|
||||||
|
|
||||||
|
private TraceIdHolder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getOrCreate() {
|
||||||
|
String value = TRACE_ID.get();
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
value = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
TRACE_ID.set(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void set(String traceId) {
|
||||||
|
TRACE_ID.set(traceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
TRACE_ID.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
28
backend/common/common-mybatis/pom.xml
Normal file
28
backend/common/common-mybatis/pom.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>common-mybatis</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.k12study.common.mybatis.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class MybatisPlusConfiguration {
|
||||||
|
}
|
||||||
19
backend/common/common-redis/pom.xml
Normal file
19
backend/common/common-redis/pom.xml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>common-redis</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.k12study.common.redis.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class RedisConfiguration {
|
||||||
|
}
|
||||||
38
backend/common/common-security/pom.xml
Normal file
38
backend/common/common-security/pom.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>common-security</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.k12study.common.security.config;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "auth")
|
||||||
|
public class AuthProperties {
|
||||||
|
private boolean enabled = true;
|
||||||
|
private boolean gatewayMode = false;
|
||||||
|
private String tokenHeader = "Authorization";
|
||||||
|
private String tokenPrefix = "Bearer ";
|
||||||
|
private String secret = "k12study-dev-secret-k12study-dev-secret";
|
||||||
|
private Duration accessTokenTtl = Duration.ofHours(12);
|
||||||
|
private Duration refreshTokenTtl = Duration.ofDays(7);
|
||||||
|
private List<String> whitelist = new ArrayList<>(List.of("/actuator/**", "/auth/login", "/auth/refresh"));
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGatewayMode() {
|
||||||
|
return gatewayMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGatewayMode(boolean gatewayMode) {
|
||||||
|
this.gatewayMode = gatewayMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenHeader() {
|
||||||
|
return tokenHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTokenHeader(String tokenHeader) {
|
||||||
|
this.tokenHeader = tokenHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenPrefix() {
|
||||||
|
return tokenPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTokenPrefix(String tokenPrefix) {
|
||||||
|
this.tokenPrefix = tokenPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecret() {
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecret(String secret) {
|
||||||
|
this.secret = secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getAccessTokenTtl() {
|
||||||
|
return accessTokenTtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccessTokenTtl(Duration accessTokenTtl) {
|
||||||
|
this.accessTokenTtl = accessTokenTtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getRefreshTokenTtl() {
|
||||||
|
return refreshTokenTtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefreshTokenTtl(Duration refreshTokenTtl) {
|
||||||
|
this.refreshTokenTtl = refreshTokenTtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getWhitelist() {
|
||||||
|
return whitelist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWhitelist(List<String> whitelist) {
|
||||||
|
this.whitelist = whitelist;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.k12study.common.security.config;
|
||||||
|
|
||||||
|
import com.k12study.common.security.jwt.JwtTokenProvider;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(AuthProperties.class)
|
||||||
|
public class SecurityAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtTokenProvider jwtTokenProvider(AuthProperties authProperties) {
|
||||||
|
return new JwtTokenProvider(authProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.k12study.common.security.context;
|
||||||
|
|
||||||
|
public record RequestUserContext(
|
||||||
|
String userId,
|
||||||
|
String username,
|
||||||
|
String displayName,
|
||||||
|
String tenantId,
|
||||||
|
String deptId
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.k12study.common.security.context;
|
||||||
|
|
||||||
|
public final class RequestUserContextHolder {
|
||||||
|
private static final ThreadLocal<RequestUserContext> CONTEXT = new ThreadLocal<>();
|
||||||
|
|
||||||
|
private RequestUserContextHolder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void set(RequestUserContext context) {
|
||||||
|
CONTEXT.set(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RequestUserContext get() {
|
||||||
|
return CONTEXT.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
CONTEXT.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.k12study.common.security.jwt;
|
||||||
|
|
||||||
|
import com.k12study.common.security.config.AuthProperties;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Date;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
public class JwtTokenProvider {
|
||||||
|
private final AuthProperties authProperties;
|
||||||
|
private final SecretKey secretKey;
|
||||||
|
|
||||||
|
public JwtTokenProvider(AuthProperties authProperties) {
|
||||||
|
this.authProperties = authProperties;
|
||||||
|
this.secretKey = Keys.hmacShaKeyFor(authProperties.getSecret().getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String createAccessToken(JwtUserPrincipal principal) {
|
||||||
|
Instant now = Instant.now();
|
||||||
|
return Jwts.builder()
|
||||||
|
.subject(principal.userId())
|
||||||
|
.claim("username", principal.username())
|
||||||
|
.claim("displayName", principal.displayName())
|
||||||
|
.claim("tenantId", principal.tenantId())
|
||||||
|
.claim("deptId", principal.deptId())
|
||||||
|
.issuedAt(Date.from(now))
|
||||||
|
.expiration(Date.from(now.plus(authProperties.getAccessTokenTtl())))
|
||||||
|
.signWith(secretKey)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JwtUserPrincipal parse(String token) {
|
||||||
|
Claims claims = Jwts.parser()
|
||||||
|
.verifyWith(secretKey)
|
||||||
|
.build()
|
||||||
|
.parseSignedClaims(token)
|
||||||
|
.getPayload();
|
||||||
|
return new JwtUserPrincipal(
|
||||||
|
claims.getSubject(),
|
||||||
|
claims.get("username", String.class),
|
||||||
|
claims.get("displayName", String.class),
|
||||||
|
claims.get("tenantId", String.class),
|
||||||
|
claims.get("deptId", String.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.k12study.common.security.jwt;
|
||||||
|
|
||||||
|
public record JwtUserPrincipal(
|
||||||
|
String userId,
|
||||||
|
String username,
|
||||||
|
String displayName,
|
||||||
|
String tenantId,
|
||||||
|
String deptId
|
||||||
|
) {
|
||||||
|
}
|
||||||
34
backend/common/common-web/pom.xml
Normal file
34
backend/common/common-web/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>common-web</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-security</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.k12study.common.web.config;
|
||||||
|
|
||||||
|
import com.k12study.common.core.constants.SecurityConstants;
|
||||||
|
import com.k12study.common.core.utils.TraceIdHolder;
|
||||||
|
import com.k12study.common.security.context.RequestUserContext;
|
||||||
|
import com.k12study.common.security.context.RequestUserContextHolder;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CommonWebMvcConfiguration extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
try {
|
||||||
|
String traceId = request.getHeader(SecurityConstants.TRACE_ID);
|
||||||
|
TraceIdHolder.set(traceId == null || traceId.isBlank() ? TraceIdHolder.getOrCreate() : traceId);
|
||||||
|
|
||||||
|
RequestUserContextHolder.set(new RequestUserContext(
|
||||||
|
request.getHeader(SecurityConstants.HEADER_USER_ID),
|
||||||
|
request.getHeader(SecurityConstants.HEADER_USERNAME),
|
||||||
|
request.getHeader(SecurityConstants.HEADER_DISPLAY_NAME),
|
||||||
|
request.getHeader(SecurityConstants.HEADER_TENANT_ID),
|
||||||
|
request.getHeader(SecurityConstants.HEADER_DEPT_ID)
|
||||||
|
));
|
||||||
|
response.setHeader(SecurityConstants.TRACE_ID, TraceIdHolder.getOrCreate());
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
} finally {
|
||||||
|
TraceIdHolder.clear();
|
||||||
|
RequestUserContextHolder.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.k12study.common.web.exception;
|
||||||
|
|
||||||
|
public class BizException extends RuntimeException {
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
public BizException(int code, String message) {
|
||||||
|
super(message);
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.k12study.common.web.exception;
|
||||||
|
|
||||||
|
import com.k12study.common.api.response.ApiResponse;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(BizException.class)
|
||||||
|
public ApiResponse<Void> handleBizException(BizException exception) {
|
||||||
|
return ApiResponse.failure(exception.getCode(), exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ApiResponse<Void> handleException(Exception exception) {
|
||||||
|
return ApiResponse.failure(500, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
24
backend/common/pom.xml
Normal file
24
backend/common/pom.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>common-parent</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>common-api</module>
|
||||||
|
<module>common-core</module>
|
||||||
|
<module>common-web</module>
|
||||||
|
<module>common-security</module>
|
||||||
|
<module>common-mybatis</module>
|
||||||
|
<module>common-redis</module>
|
||||||
|
</modules>
|
||||||
|
</project>
|
||||||
35
backend/gateway/pom.xml
Normal file
35
backend/gateway/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>gateway</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-security</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.k12study.gateway;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication(scanBasePackages = {"com.k12study.gateway", "com.k12study.common"})
|
||||||
|
public class GatewayApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(GatewayApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.k12study.gateway.filter;
|
||||||
|
|
||||||
|
import com.k12study.common.core.constants.SecurityConstants;
|
||||||
|
import com.k12study.common.security.config.AuthProperties;
|
||||||
|
import com.k12study.common.security.jwt.JwtTokenProvider;
|
||||||
|
import com.k12study.common.security.jwt.JwtUserPrincipal;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtRelayFilter implements GlobalFilter, Ordered {
|
||||||
|
private final AuthProperties authProperties;
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
|
||||||
|
|
||||||
|
public JwtRelayFilter(AuthProperties authProperties, JwtTokenProvider jwtTokenProvider) {
|
||||||
|
this.authProperties = authProperties;
|
||||||
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||||
|
String path = exchange.getRequest().getURI().getPath();
|
||||||
|
if (!authProperties.isEnabled() || matches(path, authProperties.getWhitelist())) {
|
||||||
|
return chain.filter(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
String authorization = exchange.getRequest().getHeaders().getFirst(authProperties.getTokenHeader());
|
||||||
|
if (authorization == null || !authorization.startsWith(authProperties.getTokenPrefix())) {
|
||||||
|
return unauthorized(exchange, "Missing token");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String token = authorization.substring(authProperties.getTokenPrefix().length());
|
||||||
|
JwtUserPrincipal principal = jwtTokenProvider.parse(token);
|
||||||
|
var mutatedRequest = exchange.getRequest().mutate()
|
||||||
|
.header(SecurityConstants.HEADER_USER_ID, principal.userId())
|
||||||
|
.header(SecurityConstants.HEADER_USERNAME, principal.username())
|
||||||
|
.header(SecurityConstants.HEADER_DISPLAY_NAME, principal.displayName())
|
||||||
|
.header(SecurityConstants.HEADER_TENANT_ID, principal.tenantId())
|
||||||
|
.header(SecurityConstants.HEADER_DEPT_ID, principal.deptId())
|
||||||
|
.build();
|
||||||
|
return chain.filter(exchange.mutate().request(mutatedRequest).build());
|
||||||
|
} catch (Exception exception) {
|
||||||
|
return unauthorized(exchange, "Invalid token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return -100;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matches(String path, List<String> patterns) {
|
||||||
|
return patterns.stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
|
||||||
|
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||||
|
exchange.getResponse().getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
byte[] body = ("{\"code\":401,\"message\":\"" + message + "\",\"data\":null}").getBytes();
|
||||||
|
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse()
|
||||||
|
.bufferFactory()
|
||||||
|
.wrap(body)));
|
||||||
|
}
|
||||||
|
}
|
||||||
34
backend/gateway/src/main/resources/application.yml
Normal file
34
backend/gateway/src/main/resources/application.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: k12study-gateway
|
||||||
|
cloud:
|
||||||
|
gateway:
|
||||||
|
routes:
|
||||||
|
- id: auth
|
||||||
|
uri: http://localhost:8081
|
||||||
|
predicates:
|
||||||
|
- Path=/api/auth/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
- id: upms
|
||||||
|
uri: http://localhost:8082
|
||||||
|
predicates:
|
||||||
|
- Path=/api/upms/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
|
||||||
|
management:
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: health,info
|
||||||
|
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
whitelist:
|
||||||
|
- /api/auth/login
|
||||||
|
- /api/auth/refresh
|
||||||
|
- /actuator/**
|
||||||
115
backend/pom.xml
Normal file
115
backend/pom.xml
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<name>k12study-backend</name>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>common</module>
|
||||||
|
<module>apis</module>
|
||||||
|
<module>ai-client</module>
|
||||||
|
<module>auth</module>
|
||||||
|
<module>upms</module>
|
||||||
|
<module>gateway</module>
|
||||||
|
<module>boot-dev</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.release>${java.version}</maven.compiler.release>
|
||||||
|
|
||||||
|
<spring.boot.version>3.3.5</spring.boot.version>
|
||||||
|
<spring.cloud.version>2023.0.3</spring.cloud.version>
|
||||||
|
<mybatis.plus.version>3.5.7</mybatis.plus.version>
|
||||||
|
<postgresql.version>42.7.4</postgresql.version>
|
||||||
|
<jjwt.version>0.12.6</jjwt.version>
|
||||||
|
<springdoc.version>2.6.0</springdoc.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
<version>${spring.boot.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-dependencies</artifactId>
|
||||||
|
<version>${spring.cloud.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
<version>${mybatis.plus.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>${postgresql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>${springdoc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.13.0</version>
|
||||||
|
<configuration>
|
||||||
|
<release>${maven.compiler.release}</release>
|
||||||
|
<encoding>${project.build.sourceEncoding}</encoding>
|
||||||
|
<parameters>true</parameters>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring.boot.version}</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>aliyun-public</id>
|
||||||
|
<name>aliyun-public</name>
|
||||||
|
<url>https://maven.aliyun.com/repository/public</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
</project>
|
||||||
14
backend/python-ai/README.md
Normal file
14
backend/python-ai/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Python AI Placeholder
|
||||||
|
|
||||||
|
This service is the placeholder for OCR, grading and speech evaluation capabilities.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
uvicorn app.main:app --reload --port 9000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
- `GET /health`
|
||||||
13
backend/python-ai/app/main.py
Normal file
13
backend/python-ai/app/main.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(title="K12Study Python AI", version="0.1.0")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health():
|
||||||
|
return {
|
||||||
|
"name": "python-ai",
|
||||||
|
"status": "UP",
|
||||||
|
"version": "0.1.0",
|
||||||
|
}
|
||||||
2
backend/python-ai/requirements.txt
Normal file
2
backend/python-ai/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fastapi==0.115.12
|
||||||
|
uvicorn==0.34.0
|
||||||
50
backend/upms/pom.xml
Normal file
50
backend/upms/pom.xml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?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>com.k12study</groupId>
|
||||||
|
<artifactId>k12study-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>upms</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-web</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-security</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>common-mybatis</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.k12study</groupId>
|
||||||
|
<artifactId>api-upms</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.k12study.upms;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication(scanBasePackages = {"com.k12study.upms", "com.k12study.common"})
|
||||||
|
public class UpmsApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(UpmsApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.k12study.upms.config;
|
||||||
|
|
||||||
|
import com.k12study.upms.UpmsApplication;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.FilterType;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan(
|
||||||
|
basePackages = "com.k12study.upms",
|
||||||
|
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = UpmsApplication.class)
|
||||||
|
)
|
||||||
|
public class UpmsModuleConfiguration {
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.k12study.upms.controller;
|
||||||
|
|
||||||
|
import com.k12study.api.upms.dto.AreaNodeDto;
|
||||||
|
import com.k12study.api.upms.dto.CurrentRouteUserDto;
|
||||||
|
import com.k12study.api.upms.dto.DeptNodeDto;
|
||||||
|
import com.k12study.api.upms.dto.RouteNodeDto;
|
||||||
|
import com.k12study.api.upms.dto.TenantNodeDto;
|
||||||
|
import com.k12study.common.api.response.ApiResponse;
|
||||||
|
import com.k12study.upms.service.UpmsQueryService;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/upms")
|
||||||
|
public class UpmsController {
|
||||||
|
private final UpmsQueryService upmsQueryService;
|
||||||
|
|
||||||
|
public UpmsController(UpmsQueryService upmsQueryService) {
|
||||||
|
this.upmsQueryService = upmsQueryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/routes")
|
||||||
|
public ApiResponse<List<RouteNodeDto>> routes() {
|
||||||
|
return ApiResponse.success(upmsQueryService.routes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/areas/tree")
|
||||||
|
public ApiResponse<List<AreaNodeDto>> areas() {
|
||||||
|
return ApiResponse.success(upmsQueryService.areas());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/tenants/tree")
|
||||||
|
public ApiResponse<List<TenantNodeDto>> tenants() {
|
||||||
|
return ApiResponse.success(upmsQueryService.tenants());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/depts/tree")
|
||||||
|
public ApiResponse<List<DeptNodeDto>> departments() {
|
||||||
|
return ApiResponse.success(upmsQueryService.departments());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/current-user")
|
||||||
|
public ApiResponse<CurrentRouteUserDto> currentUser() {
|
||||||
|
return ApiResponse.success(upmsQueryService.currentUser());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.k12study.upms.domain;
|
||||||
|
|
||||||
|
public record SysArea(
|
||||||
|
String areaCode,
|
||||||
|
String parentCode,
|
||||||
|
String areaName,
|
||||||
|
String areaLevel,
|
||||||
|
String provinceCode
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.k12study.upms.domain;
|
||||||
|
|
||||||
|
public record SysDept(
|
||||||
|
String deptId,
|
||||||
|
String parentDeptId,
|
||||||
|
String tenantId,
|
||||||
|
String deptName,
|
||||||
|
String deptType,
|
||||||
|
String deptPath
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.k12study.upms.domain;
|
||||||
|
|
||||||
|
public record SysTenant(
|
||||||
|
String tenantId,
|
||||||
|
String parentTenantId,
|
||||||
|
String tenantName,
|
||||||
|
String tenantType,
|
||||||
|
String provinceCode,
|
||||||
|
String areaCode,
|
||||||
|
String tenantPath
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.k12study.upms.service;
|
||||||
|
|
||||||
|
import com.k12study.api.upms.dto.AreaNodeDto;
|
||||||
|
import com.k12study.api.upms.dto.CurrentRouteUserDto;
|
||||||
|
import com.k12study.api.upms.dto.DeptNodeDto;
|
||||||
|
import com.k12study.api.upms.dto.LayoutType;
|
||||||
|
import com.k12study.api.upms.dto.RouteMetaDto;
|
||||||
|
import com.k12study.api.upms.dto.RouteNodeDto;
|
||||||
|
import com.k12study.api.upms.dto.TenantNodeDto;
|
||||||
|
import com.k12study.common.security.context.RequestUserContextHolder;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UpmsQueryService {
|
||||||
|
|
||||||
|
public List<RouteNodeDto> routes() {
|
||||||
|
return List.of(
|
||||||
|
new RouteNodeDto(
|
||||||
|
"dashboard",
|
||||||
|
"/",
|
||||||
|
"dashboard",
|
||||||
|
"dashboard",
|
||||||
|
LayoutType.SIDEBAR,
|
||||||
|
new RouteMetaDto("控制台", "layout-dashboard", List.of("dashboard:view"), false),
|
||||||
|
List.of()
|
||||||
|
),
|
||||||
|
new RouteNodeDto(
|
||||||
|
"tenant-management",
|
||||||
|
"/tenant",
|
||||||
|
"tenant-management",
|
||||||
|
"tenant",
|
||||||
|
LayoutType.SIDEBAR,
|
||||||
|
new RouteMetaDto("租户组织", "building-2", List.of("tenant:view"), false),
|
||||||
|
List.of()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AreaNodeDto> areas() {
|
||||||
|
return List.of(
|
||||||
|
new AreaNodeDto(
|
||||||
|
"330000",
|
||||||
|
"浙江省",
|
||||||
|
"province",
|
||||||
|
"330000",
|
||||||
|
List.of(
|
||||||
|
new AreaNodeDto("330100", "杭州市", "city", "330000", List.of())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TenantNodeDto> tenants() {
|
||||||
|
return List.of(
|
||||||
|
new TenantNodeDto(
|
||||||
|
"SCH-HQ",
|
||||||
|
"K12Study 总校",
|
||||||
|
"head_school",
|
||||||
|
"330000",
|
||||||
|
"330100",
|
||||||
|
"/SCH-HQ/",
|
||||||
|
List.of(
|
||||||
|
new TenantNodeDto(
|
||||||
|
"SCH-ZJ-HZ-01",
|
||||||
|
"杭州分校",
|
||||||
|
"city_school",
|
||||||
|
"330000",
|
||||||
|
"330100",
|
||||||
|
"/SCH-HQ/SCH-ZJ-HZ-01/",
|
||||||
|
List.of()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DeptNodeDto> departments() {
|
||||||
|
return List.of(
|
||||||
|
new DeptNodeDto(
|
||||||
|
"DEPT-HQ",
|
||||||
|
"总校教学部",
|
||||||
|
"grade",
|
||||||
|
"SCH-HQ",
|
||||||
|
"/DEPT-HQ/",
|
||||||
|
List.of(
|
||||||
|
new DeptNodeDto(
|
||||||
|
"DEPT-HQ-MATH",
|
||||||
|
"数学学科组",
|
||||||
|
"subject",
|
||||||
|
"SCH-HQ",
|
||||||
|
"/DEPT-HQ/DEPT-HQ-MATH/",
|
||||||
|
List.of()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CurrentRouteUserDto currentUser() {
|
||||||
|
var context = RequestUserContextHolder.get();
|
||||||
|
return new CurrentRouteUserDto(
|
||||||
|
context == null ? "U10001" : context.userId(),
|
||||||
|
context == null ? "admin" : context.username(),
|
||||||
|
context == null ? "K12Study 管理员" : context.displayName(),
|
||||||
|
context == null ? "SCH-HQ" : context.tenantId(),
|
||||||
|
context == null ? "DEPT-HQ-ADMIN" : context.deptId(),
|
||||||
|
List.of("dashboard:view", "tenant:view", "dept:view")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
backend/upms/src/main/resources/application.yml
Normal file
23
backend/upms/src/main/resources/application.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
server:
|
||||||
|
port: 8082
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: k12study-upms
|
||||||
|
autoconfigure:
|
||||||
|
exclude:
|
||||||
|
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
|
||||||
|
- org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
|
||||||
|
- com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
|
||||||
|
|
||||||
|
management:
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: health,info
|
||||||
|
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
gateway-mode: true
|
||||||
|
whitelist:
|
||||||
|
- /actuator/**
|
||||||
28
docker-compose.yml
Normal file
28
docker-compose.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16
|
||||||
|
container_name: k12study-postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: k12study
|
||||||
|
POSTGRES_USER: k12study
|
||||||
|
POSTGRES_PASSWORD: k12study
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
- ./init/pg:/docker-entrypoint-initdb.d
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7
|
||||||
|
container_name: k12study-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
redis-data:
|
||||||
BIN
docs/AI智能学习系统功能清单.pdf
Normal file
BIN
docs/AI智能学习系统功能清单.pdf
Normal file
Binary file not shown.
158
docs/architecture/数据流图(多角色).drawio
Normal file
158
docs/architecture/数据流图(多角色).drawio
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<mxfile host="app.diagrams.net" modified="2026-04-14T09:05:00.000Z" agent="Codex GPT-5" version="24.7.17">
|
||||||
|
<diagram id="multi-role-flow" name="数据流图(多角色)">
|
||||||
|
<mxGraphModel dx="1800" dy="1200" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
<mxCell id="2" value="多角色数据流图" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="30" y="20" width="220" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" value="入口:微信小程序 / React 后台,处理链路:Java 分布式服务 -> Python AI 服务 -> Redis / PostgreSQL" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="30" y="52" width="860" height="22" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="70" y="140" width="160" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="70" y="350" width="160" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" value="机构管理员" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="70" y="560" width="160" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20" value="P1 接入与身份认证<br>微信小程序 / React -> Java Gateway" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="340" y="80" width="290" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="21" value="P2 作业 / 资料管理<br>Java 作业服务" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="340" y="205" width="290" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="22" value="P3 AI批改与错因分析<br>Java 编排服务" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="340" y="330" width="290" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="23" value="P4 错题本 / 复习计划<br>Java 复习服务" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="340" y="455" width="290" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="24" value="P5 推荐与消息推送<br>Java 推荐服务" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="340" y="580" width="290" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="25" value="P6 Python AI处理服务<br>OCR / 批改 / 语音评测" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="760" y="250" width="300" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="26" value="P7 讲解评估 / 教学干预 / 运营监控<br>Java 教学与运营服务" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="760" y="470" width="330" height="90" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="30" value="D1 PostgreSQL 用户 / 班级库<br>支持分库分表 / 分区" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1180" y="70" width="260" height="75" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="31" value="D2 PostgreSQL 作业 / 批改库<br>支持分库分表 / 分区" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1180" y="190" width="260" height="75" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="32" value="D3 PostgreSQL 错题 / 复习库<br>支持分区归档" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1180" y="310" width="260" height="75" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="33" value="D4 知识库 / 题库 / 对象存储" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1180" y="430" width="260" height="75" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="34" value="D5 Redis Cluster<br>会话、缓存、热数据、推荐结果" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1180" y="550" width="260" height="75" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="35" value="D6 日志 / 审计 / BI" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1180" y="670" width="260" height="75" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="登录 / 绑定" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="10" target="20">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="登录 / 班级关系" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="11" target="20">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="高权限登录" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" edge="1" parent="1" source="12" target="20">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="用户 / 班级 / 权限" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="20" target="30">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" value="会话 / Token" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="20" target="34">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" value="发布作业 / 资料" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="11" target="21">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" value="上传作业 / 答题" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="10" target="21">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" value="作业 / 资料 / 提交" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="21" target="31">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" value="文件 / 课件" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="21" target="33">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" value="批改请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="21" target="22">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="调用 Python AI" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="22" target="25">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="解析结果 / 错因 / 评分" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="25" target="22">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="知识点 / 题库匹配" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="25" target="33">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="批改结果" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="22" target="31">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="错题 / 薄弱项" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="22" target="23">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="55" value="批改结果 / 错因" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="22" target="10">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="56" value="复核任务 / 学情" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="22" target="11">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" value="错题 / 复习节点" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="23" target="32">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="画像 / 复习信号" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="23" target="24">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" value="错题本 / 复习计划" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="23" target="10">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="微课 / 变式题召回" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="24" target="33">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="缓存推荐结果" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="24" target="34">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="推荐内容 / 提醒" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="24" target="10">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="语音讲解提交" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="10" target="26">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="点评 / 评分 / 学情查询" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="11" target="26">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="65" value="机构监控 / 审核" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" edge="1" parent="1" source="12" target="26">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="讲解评估调用" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="26" target="25">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="67" value="讲解记录 / 教学数据" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="26" target="31">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="68" value="报表 / 审计" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="26" target="35">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="69" value="班级学情 / 教学建议" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="26" target="11">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="70" value="机构看板 / 质量报表" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" edge="1" parent="1" source="26" target="12">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
450
docs/architecture/系统架构与代码架构.drawio
Normal file
450
docs/architecture/系统架构与代码架构.drawio
Normal file
@@ -0,0 +1,450 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="sys-arch" name="系统架构">
|
||||||
|
<mxGraphModel dx="1267" dy="1104" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="2" value="AI智能学习系统系统架构" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="30" y="20" width="420" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" value="技术栈:微信小程序 + React + Java + Python(AI处理供 Java 调用) + Redis + PostgreSQL" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="30" y="52" width="760" height="22" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="角色与前端接入层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="100" width="240" height="600" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="学生端<br>微信小程序<br>学习执行 / 作业上传 / 推荐接收" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="70" y="155" width="180" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" value="教师端<br>React 管理后台<br>班级管理 / 作业发布 / 教学干预" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="70" y="345" width="180" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="13" value="机构端<br>React 管理后台<br>运营管控 / 质控 / 知识库审核" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="70" y="535" width="180" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20" value="接入层<br>SLB / Nginx / Java Gateway(BFF) 集群<br>统一鉴权 / Token / RBAC / 限流 / 聚合接口" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="340" y="315" width="230" height="120" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="30" value="Java 分布式业务服务集群" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="640" y="80" width="760" height="420" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="31" value="认证与用户中心<br>登录、绑定、机构 / 班级 / 角色权限" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="690" y="140" width="220" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="32" value="作业与内容服务<br>作业发布、上传、文件元数据、提交记录" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="940" y="140" width="220" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="33" value="批改编排与学情服务<br>任务编排、错因分析、讲解评估、学情画像" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1190" y="140" width="220" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="34" value="错题 / 复习 / 推荐服务<br>错题本、艾宾浩斯计划、推荐触达" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="690" y="280" width="220" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="35" value="知识库与机构运营服务<br>知识库审核、运营看板、质量监控、报表导出" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="940" y="280" width="220" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="36" value="分布式治理能力<br>服务注册发现 / 配置中心 / 链路追踪 / 灰度发布" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1190" y="280" width="220" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="Python AI处理层(供 Java 调用)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1460" y="80" width="300" height="260" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="Python OCR 服务<br>文档解析 / 图片增强 / 结构化提取" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1490" y="135" width="240" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="Python 批改与生成服务<br>LLM批改 / 错因标签 / 变式题生成" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1490" y="215" width="240" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="Python 语音评测服务<br>ASR / 讲解评分 / 教学建议" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1490" y="295" width="240" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="公共支撑层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1460" y="390" width="300" height="210" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="知识库 / 题库 / 课程资源<br>学科 / 年级 / 知识点内容组织" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1490" y="445" width="240" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="画像与推荐策略<br>召回、排序、效果回流" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1490" y="525" width="240" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="数据层与基础设施" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="550" y="625" width="1200" height="250" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="PostgreSQL 集群<br>业务主库 + 分库分表 + 分区表" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="620" y="650" width="200" height="85" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="Redis Cluster<br>缓存、会话、排行榜、热数据" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="860" y="650" width="200" height="85" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="对象存储<br>图片 / PDF / 音频 / 课件 / 讲解录音" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1100" y="650" width="200" height="85" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="MQ / 任务调度<br>异步批改、推荐推送、报表任务" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1340" y="650" width="200" height="85" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="65" value="日志 / 审计 / BI<br>操作日志、链路日志、运营分析" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1580" y="650" width="160" height="85" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="70" value="外部能力" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="740" width="500" height="150" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="71" value="微信登录 / 订阅消息 / 短信" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="80" y="795" width="180" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="72" value="第三方 OCR / LLM / ASR 模型接口" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="795" width="200" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="80" value="学习接口" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="11" target="20" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="81" value="教学接口" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="12" target="20" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="82" value="运营接口" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="13" target="20" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="83" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="20" target="31" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="84" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="20" target="32" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="85" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="20" target="33" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="86" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="20" target="34" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="87" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="20" target="35" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="88" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="20" target="36" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="89" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="31" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="90" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="31" target="62" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="91" value="登录 / 通知" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="31" target="71" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="92" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="93" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="63" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="94" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="64" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="95" value="Java RPC / HTTP" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="41" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="96" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="42" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="97" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="43" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="98" value="知识点 / 课程匹配" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="33" target="51" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="99" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="100" value="画像 / 策略" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="34" target="52" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="101" value="复习内容" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="34" target="51" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="102" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="34" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="103" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="34" target="62" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="104" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="35" target="51" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="105" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="106" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="65" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="107" value="监控 / 链路" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="36" target="65" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="108" value="模型调用" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="41" target="72" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="109" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="42" target="72" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="110" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="43" target="72" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="111" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="51" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="112" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="51" target="63" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="113" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="52" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="114" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="52" target="62" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
<diagram id="code-arch" name="代码架构">
|
||||||
|
<mxGraphModel dx="887" dy="773" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="2" value="AI智能学习系统代码架构" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="30" y="20" width="420" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" value="前端:微信小程序 + React,后端:Java 分布式服务,AI:Python 服务,数据:Redis + PostgreSQL" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="30" y="52" width="780" height="22" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="前端应用层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="90" width="1700" height="150" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="apps/weapp-student<br>Taro / UniApp 风格小程序<br>作业上传、错题本、推荐页" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="90" y="145" width="270" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" value="apps/react-teacher-admin<br>React 教师后台<br>班级管理、作业发布、教学点评" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="145" width="270" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="13" value="apps/react-org-admin<br>React 机构后台<br>权限配置、运营看板、质量监控" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="750" y="145" width="270" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20" value="Java 接入层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="270" width="1700" height="150" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="21" value="gateway/java-bff-cluster<br>REST API、聚合接口、限流、灰度、鉴权" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="90" y="325" width="290" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="22" value="security/auth-center<br>JWT、RBAC、登录态、审计中间件" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="430" y="325" width="290" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="23" value="integration/openapi-adapter<br>微信、短信、对象存储、消息回调" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="770" y="325" width="290" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="24" value="governance/distributed-kit<br>注册发现、配置中心、链路追踪、熔断重试" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1110" y="325" width="320" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="30" value="Java 领域服务层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="450" width="1700" height="250" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="31" value="service/user-class-center<br>用户、班级、机构、角色权限" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="70" y="530" width="240" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="32" value="service/homework-content<br>作业、资料、上传、文件元数据" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="340" y="530" width="240" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="33" value="service/grading-orchestrator<br>批改编排、错因分析、学情画像" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="610" y="530" width="240" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="34" value="service/wrongbook-review<br>错题本、复习计划、掌握状态" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="880" y="530" width="240" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="35" value="service/recommendation<br>召回排序、内容推荐、消息触达" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1150" y="530" width="240" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="36" value="service/ops-knowledge<br>机构运营、质控、知识库审核、报表" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1420" y="530" width="260" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="Python AI服务层(独立部署,供 Java 调用)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="730" width="980" height="180" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="python/ocr-service<br>文档解析、图片增强、结构化输出" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="80" y="795" width="210" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="python/grading-llm-service<br>主观题批改、错因标签、变式题生成" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="320" y="795" width="240" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="python/speech-eval-service<br>ASR、讲解评分、反馈建议" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="590" y="795" width="210" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" value="python/feature-worker<br>画像特征、推荐特征、离线计算" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="830" y="795" width="160" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="共享库 / 契约层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1070" y="730" width="670" height="180" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="contracts/api-schema<br>DTO、API Schema、OpenAPI" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1100" y="795" width="140" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="contracts/domain-model<br>实体、值对象、枚举、状态机" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1260" y="795" width="140" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="common/toolkit<br>日志、异常、配置、工具组件" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1420" y="795" width="140" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="client/ai-bridge<br>Java 调 Python AI RPC / HTTP SDK" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1580" y="795" width="130" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="数据与基础设施层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="940" width="1700" height="190" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="infra/postgresql-cluster<br>业务主库、分库分表、分区表、读写分离" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="70" y="1010" width="250" height="75" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="infra/redis-cluster<br>缓存、会话、排行榜、热点数据" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="360" y="1010" width="250" height="75" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="infra/object-storage<br>图片、PDF、音频、视频、课件" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="650" y="1010" width="250" height="75" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="infra/mq-scheduler<br>异步批改、推荐任务、定时报表" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="940" y="1010" width="250" height="75" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="65" value="infra/search-bi<br>检索索引、统计聚合、运营分析" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1230" y="1010" width="250" height="75" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="infra/open-platform<br>微信、短信、OCR、LLM、ASR" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1520" y="1010" width="190" height="75" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="80" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="11" target="21" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="81" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="12" target="21" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="82" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="13" target="21" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="83" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="21" target="22" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="84" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="21" target="23" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="85" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="21" target="24" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="86" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="22" target="31" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="87" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="21" target="32" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="88" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="21" target="33" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="89" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="21" target="34" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="90" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="21" target="35" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="91" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="21" target="36" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="92" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="41" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="93" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="42" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="94" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="43" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="95" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="44" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="96" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="31" target="51" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="97" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="32" target="52" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="98" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="33" target="54" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="99" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="34" target="52" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="100" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="35" target="51" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="101" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="36" target="53" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="102" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="31" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="103" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="31" target="62" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="104" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="105" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="63" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="106" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="64" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="107" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="108" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="64" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="109" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="34" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="110" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="34" target="62" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="111" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="112" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="62" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="113" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="64" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="114" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="36" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="115" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="36" target="65" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="116" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="23" target="63" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="117" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="23" target="66" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="118" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="24" target="65" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="119" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="41" target="66" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="120" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="42" target="66" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="121" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="43" target="66" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="122" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="44" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="123" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="44" target="62" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user