这个笔记管理系统采用了典型的前后端分离架构,前端使用React构建单页应用(SPA),后端基于Flask框架提供RESTful API,数据存储使用PostgreSQL关系型数据库。整个系统遵循三层架构设计原则,清晰地划分了表现层、业务逻辑层和数据层。
在实际开发中,我选择了这套技术栈主要基于以下几个考量:
系统采用经典的三层架构设计,各层职责明确:
表现层:
业务逻辑层:
数据层:
系统各组件间的完整交互流程如下:
提示:在生产环境中,建议在后端前面再加一层Gunicorn或uWSGI作为应用服务器,Nginx只做反向代理和静态文件服务。
系统核心实体包括用户(User)、笔记本(Notebook)和笔记(Note),它们之间的关系如下:
这种设计确保了数据的完整性和一致性,同时支持常见的笔记管理场景。
使用Flask-SQLAlchemy定义的数据模型如下:
python复制class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
created_at = db.Column(db.DateTime, server_default=db.func.now())
notebooks = db.relationship('Notebook', backref='owner', lazy='dynamic')
class Notebook(db.Model):
__tablename__ = 'notebooks'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
created_at = db.Column(db.DateTime, server_default=db.func.now())
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
notes = db.relationship('Note', backref='notebook', lazy='dynamic')
class Note(db.Model):
__tablename__ = 'notes'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text)
created_at = db.Column(db.DateTime, server_default=db.func.now())
updated_at = db.Column(db.DateTime, onupdate=db.func.now())
notebook_id = db.Column(db.Integer, db.ForeignKey('notebooks.id'), nullable=False)
为常用查询字段添加索引:
python复制# 在用户模型中添加
username = db.Column(db.String(64), unique=True, nullable=False, index=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
考虑使用数据库连接池:
python复制from sqlalchemy.pool import QueuePool
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
'poolclass': QueuePool,
'pool_size': 10,
'max_overflow': 20,
'pool_timeout': 30
}
对于大型文本内容,可以考虑单独存储或使用全文检索功能。
使用Flask-JWT-Extended实现基于JWT的身份认证:
python复制@auth_bp.route('/login', methods=['POST'])
def login():
data = request.get_json()
user = User.query.filter_by(email=data['email']).first()
if user and user.check_password(data['password']):
access_token = create_access_token(identity=user.id)
return jsonify({
'access_token': access_token,
'user': user.to_dict()
}), 200
return jsonify({'msg': 'Invalid credentials'}), 401
实现CRUD操作的笔记API:
python复制@notes_bp.route('/notebooks/<int:notebook_id>/notes', methods=['POST'])
@jwt_required()
def create_note(notebook_id):
current_user = get_jwt_identity()
notebook = Notebook.query.get_or_404(notebook_id)
if notebook.user_id != current_user:
return jsonify({'msg': 'Not authorized'}), 403
data = request.get_json()
note = Note(
title=data.get('title', 'Untitled'),
content=data.get('content', ''),
notebook_id=notebook_id
)
db.session.add(note)
db.session.commit()
return jsonify(note.to_dict()), 201
前端采用模块化组件设计:
code复制src/
├── components/
│ ├── common/ # 通用UI组件
│ │ ├── Button.js
│ │ ├── Input.js
│ │ └── ...
│ ├── layout/ # 布局组件
│ │ ├── Header.js
│ │ ├── Sidebar.js
│ │ └── ...
│ └── ...
├── pages/ # 页面级组件
│ ├── Auth/
│ │ ├── Login.js
│ │ └── Register.js
│ ├── Dashboard.js # 主界面
│ ├── NoteEditor.js # 笔记编辑器
│ └── ...
└── ...
对于中小型应用,可以使用React Context + useReducer:
javascript复制// contexts/NoteContext.js
const NoteContext = createContext();
export const NoteProvider = ({ children }) => {
const [state, dispatch] = useReducer(noteReducer, initialState);
const fetchNotes = async (notebookId) => {
try {
const notes = await getNotes(notebookId);
dispatch({ type: 'SET_NOTES', payload: notes });
} catch (error) {
// 错误处理
}
};
return (
<NoteContext.Provider value={{ ...state, fetchNotes }}>
{children}
</NoteContext.Provider>
);
};
使用axios封装统一的请求处理:
javascript复制// utils/request.js
const request = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL || '/api',
timeout: 10000,
});
// 请求拦截器
request.interceptors.request.use(config => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器
request.interceptors.response.use(
response => response.data,
error => {
if (error.response?.status === 401) {
// 处理未授权情况
}
return Promise.reject(error);
}
);
使用Docker Compose编排三个服务:
yaml复制version: '3.8'
services:
db:
image: postgres:13
environment:
POSTGRES_DB: notekeep
POSTGRES_USER: notekeep
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
backend:
build:
context: ./backend
dockerfile: Dockerfile.backend
environment:
DATABASE_URL: postgresql://notekeep:${DB_PASSWORD}@db:5432/notekeep
depends_on:
- db
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.frontend
ports:
- "80:80"
- "443:443"
depends_on:
- backend
前端:
后端:
数据库:
后端使用pytest编写单元测试:
python复制def test_create_note(client, auth_headers):
notebook = Notebook(title='Test Notebook', user_id=1)
db.session.add(notebook)
db.session.commit()
response = client.post(
f'/api/notebooks/{notebook.id}/notes',
json={'title': 'Test Note', 'content': 'Test content'},
headers=auth_headers
)
assert response.status_code == 201
assert response.json['title'] == 'Test Note'
测试整个API调用链:
python复制def test_note_lifecycle(client, test_user):
# 注册用户
client.post('/api/auth/register', json=test_user)
# 登录获取token
login_res = client.post('/api/auth/login', json=test_user)
token = login_res.json['access_token']
headers = {'Authorization': f'Bearer {token}'}
# 创建笔记本
notebook_res = client.post('/api/notebooks',
json={'title': 'Test Notebook'},
headers=headers
)
# 创建笔记
note_res = client.post(
f'/api/notebooks/{notebook_res.json['id']}/notes',
json={'title': 'Test Note'},
headers=headers
)
assert note_res.status_code == 201
使用Cypress进行端到端测试:
javascript复制describe('Note Management', () => {
beforeEach(() => {
cy.login('test@example.com', 'password');
});
it('should create a new note', () => {
cy.get('[data-testid="new-notebook-btn"]').click();
cy.get('[data-testid="notebook-title-input"]').type('Test Notebook');
cy.get('[data-testid="save-notebook-btn"]').click();
cy.get('[data-testid="new-note-btn"]').click();
cy.get('[data-testid="note-title-input"]').type('Test Note');
cy.get('[data-testid="save-note-btn"]').click();
cy.contains('Test Note').should('exist');
});
});
笔记分享功能:
富文本编辑器:
标签系统:
实时协作:
全文检索:
移动端适配:
在实际项目中,我通常会先实现核心功能,然后根据用户反馈和业务需求逐步扩展。这种渐进式的开发方式可以确保系统始终围绕真实需求演进,避免过度设计。