Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee6c8e8253 | ||
|
|
5b387cffd9 | ||
|
|
8a11774d98 | ||
|
|
ac88526613 | ||
|
|
5fe3ab01b4 | ||
|
|
e03d33ff83 | ||
|
|
d0624ea0b3 | ||
|
|
ef578a697b | ||
|
|
4dad99ec39 | ||
|
|
c092044954 | ||
|
|
0677c36d13 | ||
|
|
d8b0439057 | ||
|
|
24222b0b41 | ||
|
|
29404d88c9 | ||
|
|
b4b9cca357 | ||
|
|
3982d1961f | ||
|
|
b1ed9c687b | ||
|
|
d37219bb6c | ||
|
|
95515754cc | ||
|
|
25f86068d0 | ||
|
|
c3fe99245c | ||
|
|
1d6d4ead0b | ||
|
|
228164fd07 | ||
|
|
d3c0e1c58a | ||
|
|
06c4b47eb1 | ||
|
|
ab81f1f86c | ||
|
|
0d2b118e52 | ||
|
|
9ef75e4377 |
@ -15,7 +15,6 @@ __pycache__/
|
|||||||
.gitignore
|
.gitignore
|
||||||
|
|
||||||
# 忽略依赖文件(除非你希望它们存在于镜像中)
|
# 忽略依赖文件(除非你希望它们存在于镜像中)
|
||||||
requirements.txt
|
|
||||||
Pipfile
|
Pipfile
|
||||||
Pipfile.lock
|
Pipfile.lock
|
||||||
|
|
||||||
|
|||||||
20
.github/workflows/test.yaml
vendored
20
.github/workflows/test.yaml
vendored
@ -6,27 +6,19 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: test
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: https://git.bellacocool.com/actions/checkout@v4
|
uses: http://gitea.xkkxyy.com/actions/checkout@main
|
||||||
- name: Docker Build and Push
|
- name: Docker Build and Push
|
||||||
run: |
|
run: |
|
||||||
docker build -t git.bellacocool.com/jsy/epr:latest .
|
docker build -t my-django:latest .
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: test
|
runs-on: self-hosted
|
||||||
needs: build
|
needs: build
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to Test
|
- name: Deploy to Test
|
||||||
run: cd /opt/docker && docker compose -f docker-compose.yaml -f epr/docker-compose.yaml up -d
|
run: cd /opt/docker && docker-compose -f docker-compose.yaml -f my-django/docker-compose.yaml up -d
|
||||||
|
|
||||||
release:
|
|
||||||
runs-on: test
|
|
||||||
needs: build
|
|
||||||
steps:
|
|
||||||
- name: Release Package
|
|
||||||
run: |
|
|
||||||
docker run --rm -e GITHUB_RUN_NUMBER=$GITHUB_RUN_NUMBER git.bellacocool.com/jsy/epr sh release.sh
|
|
||||||
|
|
||||||
|
|||||||
115
CLAUDE.md
Normal file
115
CLAUDE.md
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Running the Application
|
||||||
|
```bash
|
||||||
|
# Start development server (for local development)
|
||||||
|
python manage.py runserver
|
||||||
|
|
||||||
|
# Start with uvicorn (production-like)
|
||||||
|
uvicorn mysite.asgi:application --host=0.0.0.0 --port=8000 --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Operations
|
||||||
|
```bash
|
||||||
|
# Run migrations
|
||||||
|
python manage.py makemigrations
|
||||||
|
python manage.py migrate
|
||||||
|
|
||||||
|
# Create superuser
|
||||||
|
python manage.py createsuperuser
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Development
|
||||||
|
```bash
|
||||||
|
# Build Docker image
|
||||||
|
docker build -t my-django:latest .
|
||||||
|
|
||||||
|
# Run with Docker Compose (see deploy/compose-test.yaml)
|
||||||
|
docker-compose -f deploy/compose-test.yaml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
# Run tests
|
||||||
|
python manage.py test
|
||||||
|
|
||||||
|
# Run tests for specific app
|
||||||
|
python manage.py test apps.role
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
This is a Django 5.1 REST API project with the following key components:
|
||||||
|
|
||||||
|
- **mysite/**: Main Django project configuration
|
||||||
|
- **apps/**: Modular Django applications
|
||||||
|
- `area/`: Geographic area management
|
||||||
|
- `dictionary/`: Data dictionary/configuration
|
||||||
|
- `menu/`: Navigation menu system
|
||||||
|
- `menu_button/`: Menu button permissions
|
||||||
|
- `role/`: Role-based access control
|
||||||
|
- `system_user/`: User management
|
||||||
|
- **utils/**: Shared utilities and middleware
|
||||||
|
|
||||||
|
### Key Configuration Details
|
||||||
|
|
||||||
|
#### Database Setup
|
||||||
|
- **Primary Database**: MySQL (host: 47.108.232.131:3306, database: ky)
|
||||||
|
- **Secondary Database**: MongoDB (host: 47.108.232.131:27017, database: wk)
|
||||||
|
- **Caching**: Redis with multiple databases (0: default, 1: queue, 2: base, 3: cache)
|
||||||
|
|
||||||
|
#### REST Framework Configuration
|
||||||
|
- Uses Django REST Framework 3.15.2
|
||||||
|
- JSON response format with custom response wrapper
|
||||||
|
- Exception handling via `utils.exception.CustomExceptionMiddleware`
|
||||||
|
- Filtering support via django-filter
|
||||||
|
- API documentation with CoreAPI
|
||||||
|
|
||||||
|
#### Internationalization
|
||||||
|
- Primary language: Chinese (zh-hans)
|
||||||
|
- Timezone: Asia/Shanghai
|
||||||
|
- Localization support enabled
|
||||||
|
|
||||||
|
### API Structure
|
||||||
|
All APIs follow the pattern:
|
||||||
|
- `/area/` - Area management endpoints
|
||||||
|
- `/role/` - Role management endpoints
|
||||||
|
- `/menu/` - Menu management endpoints
|
||||||
|
- `/menu_button/` - Menu button endpoints
|
||||||
|
- `/dictionary/` - Dictionary endpoints
|
||||||
|
- `/system_user/` - User management endpoints
|
||||||
|
|
||||||
|
### Custom Utilities
|
||||||
|
- `utils.json_response.Response`: Standardized API response wrapper
|
||||||
|
- `utils.exception.CustomExceptionMiddleware`: Global exception handling
|
||||||
|
- `utils.paginator`: Custom pagination logic
|
||||||
|
- `utils.redis`: Redis connection utilities
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
- Containerized with Docker (Python 3.11-slim base)
|
||||||
|
- Uses uvicorn ASGI server
|
||||||
|
- CI/CD via GitHub Actions with self-hosted runners
|
||||||
|
- Deployment to test environment via Docker Compose
|
||||||
|
|
||||||
|
## Development Notes
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
- Core: Django 5.1.5, Django REST Framework 3.15.2
|
||||||
|
- Database: PyMySQL, mongoengine
|
||||||
|
- Caching: django-redis
|
||||||
|
- Server: uvicorn
|
||||||
|
|
||||||
|
### Security Considerations
|
||||||
|
- CSRF middleware is disabled (line 55 in settings.py)
|
||||||
|
- Custom exception middleware catches all unhandled exceptions
|
||||||
|
- Debug mode enabled (should be disabled in production)
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
- Comprehensive logging setup with rotation
|
||||||
|
- Separate log files for general logs and error logs
|
||||||
|
- File-based logging with console output for development
|
||||||
@ -2,7 +2,7 @@ FROM python:3.11-slim
|
|||||||
|
|
||||||
WORKDIR /opt/my-django
|
WORKDIR /opt/my-django
|
||||||
|
|
||||||
ADD requirements.txt requirements.txt
|
COPY requirements.txt .
|
||||||
|
|
||||||
RUN pip install --upgrade pip && pip install -r requirements.txt --timeout=1000 -i https://mirrors.cloud.tencent.com/pypi/simple
|
RUN pip install --upgrade pip && pip install -r requirements.txt --timeout=1000 -i https://mirrors.cloud.tencent.com/pypi/simple
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
# Create your views here.
|
# Create your views here.
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
from utils.json_response import Response
|
||||||
from .models import Area
|
from .models import Area
|
||||||
from .serializers import AreaSerializer
|
from .serializers import AreaSerializer
|
||||||
from utils.paginator import BasicSetPagination
|
from utils.paginator import BasicSetPagination
|
||||||
@ -8,9 +10,7 @@ from django_filters import rest_framework
|
|||||||
|
|
||||||
class AreaFilter(rest_framework.FilterSet):
|
class AreaFilter(rest_framework.FilterSet):
|
||||||
level = rest_framework.NumberFilter(field_name="level", lookup_expr="exact")
|
level = rest_framework.NumberFilter(field_name="level", lookup_expr="exact")
|
||||||
parent_code = rest_framework.CharFilter(
|
parent_code = rest_framework.CharFilter(field_name="parent_code", lookup_expr="exact")
|
||||||
field_name="parent_code", lookup_expr="exact"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Area
|
model = Area
|
||||||
@ -25,3 +25,6 @@ class AreaViewSet(viewsets.ModelViewSet):
|
|||||||
rest_framework.DjangoFilterBackend,
|
rest_framework.DjangoFilterBackend,
|
||||||
] # 过滤器
|
] # 过滤器
|
||||||
filterset_class = AreaFilter
|
filterset_class = AreaFilter
|
||||||
|
|
||||||
|
def list(self, request):
|
||||||
|
return Response("test")
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import Menu
|
from .models import Menu
|
||||||
|
|
||||||
|
|
||||||
class MenuSerializer(serializers.ModelSerializer):
|
class MenuSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Menu
|
model = Menu
|
||||||
|
|||||||
@ -19,9 +19,7 @@ class MenuButton(BaseModel):
|
|||||||
(2, "PUT"),
|
(2, "PUT"),
|
||||||
(3, "DELETE"),
|
(3, "DELETE"),
|
||||||
]
|
]
|
||||||
method = models.IntegerField(
|
method = models.IntegerField(choices=METHOD_CHOICES, default=0, verbose_name="接口请求方式")
|
||||||
choices=METHOD_CHOICES, default=0, verbose_name="接口请求方式"
|
|
||||||
)
|
|
||||||
status = models.BooleanField(default=True, verbose_name="状态")
|
status = models.BooleanField(default=True, verbose_name="状态")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import Role
|
from .models import Role
|
||||||
|
|
||||||
|
|
||||||
class RoleSerializer(serializers.ModelSerializer):
|
class RoleSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Role
|
model = Role
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|||||||
@ -17,7 +17,7 @@ class RoleViewSet(viewsets.ModelViewSet):
|
|||||||
queryset = Role.objects.all() # 查询集
|
queryset = Role.objects.all() # 查询集
|
||||||
serializer_class = RoleSerializer # 序列化器
|
serializer_class = RoleSerializer # 序列化器
|
||||||
pagination_class = BasicSetPagination
|
pagination_class = BasicSetPagination
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
rest_framework.DjangoFilterBackend,
|
rest_framework.DjangoFilterBackend,
|
||||||
] # 过滤器
|
] # 过滤器
|
||||||
filterset_class = RoleFilter
|
filterset_class = RoleFilter
|
||||||
|
|||||||
14
deploy/compose-test.yaml
Normal file
14
deploy/compose-test.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
my-django:
|
||||||
|
image: my-django:latest
|
||||||
|
command: ["uvicorn", "mysite.asgi:application", "--host=0.0.0.0", "--port=80", "--reload"]
|
||||||
|
ports:
|
||||||
|
- 8080:80
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
replicas: 1
|
||||||
|
environment:
|
||||||
|
env: test
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ../../my-django:/opt/my-django
|
||||||
@ -190,9 +190,7 @@ connect(db="wk", host="47.108.232.131", port=27017, username="wk", password="moo
|
|||||||
# rest_framework配置
|
# rest_framework配置
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
"DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S", # 时间格式
|
"DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S", # 时间格式
|
||||||
"DEFAULT_FILTER_BACKENDS": (
|
"DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",), # 过滤器
|
||||||
"django_filters.rest_framework.DjangoFilterBackend", # 过滤器
|
|
||||||
),
|
|
||||||
"DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema", # 自动生成api文档
|
"DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema", # 自动生成api文档
|
||||||
"DEFAULT_RENDERER_CLASSES": (
|
"DEFAULT_RENDERER_CLASSES": (
|
||||||
"rest_framework.renderers.JSONRenderer", # 返回json格式
|
"rest_framework.renderers.JSONRenderer", # 返回json格式
|
||||||
@ -209,12 +207,8 @@ if not os.path.exists(os.path.join(BASE_DIR, "logs")):
|
|||||||
|
|
||||||
# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
|
# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
|
||||||
# 格式:[日期][模块.函数名称():行号] [级别] 信息
|
# 格式:[日期][模块.函数名称():行号] [级别] 信息
|
||||||
STANDARD_LOG_FORMAT = (
|
STANDARD_LOG_FORMAT = "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
|
||||||
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
|
CONSOLE_LOG_FORMAT = "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
|
||||||
)
|
|
||||||
CONSOLE_LOG_FORMAT = (
|
|
||||||
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
|
|
||||||
)
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"disable_existing_loggers": False,
|
"disable_existing_loggers": False,
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
uvicorn
|
||||||
|
cryptography
|
||||||
Django~=5.1.5
|
Django~=5.1.5
|
||||||
djangorestframework~=3.15.2
|
djangorestframework~=3.15.2
|
||||||
django-filter~=25.1
|
django-filter~=25.1
|
||||||
|
|||||||
@ -13,9 +13,7 @@ class BaseModel(models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
id = models.BigAutoField(primary_key=True, verbose_name="Id", help_text="主键id")
|
id = models.BigAutoField(primary_key=True, verbose_name="Id", help_text="主键id")
|
||||||
description = models.TextField(
|
description = models.TextField(null=True, blank=True, verbose_name="描述", help_text="描述")
|
||||||
null=True, blank=True, verbose_name="描述", help_text="描述"
|
|
||||||
)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
||||||
is_deleted = models.BooleanField(default=False, verbose_name="是否删除")
|
is_deleted = models.BooleanField(default=False, verbose_name="是否删除")
|
||||||
@ -40,11 +38,7 @@ class BaseModel(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def get_need_fields_names(self):
|
def get_need_fields_names(self):
|
||||||
return [
|
return [field.name for field in self.get_all_fields() if field.name not in self.exclude_fields]
|
||||||
field.name
|
|
||||||
for field in self.get_all_fields()
|
|
||||||
if field.name not in self.exclude_fields
|
|
||||||
]
|
|
||||||
|
|
||||||
def to_dict_data(self):
|
def to_dict_data(self):
|
||||||
"""
|
"""
|
||||||
@ -60,9 +54,7 @@ class BaseModel(models.Model):
|
|||||||
"""
|
"""
|
||||||
插入模型
|
插入模型
|
||||||
"""
|
"""
|
||||||
assert (
|
assert self.pk is None, f"模型{self.__class__.__name__}还没有保存到数据中,不能手动指定ID"
|
||||||
self.pk is None
|
|
||||||
), f"模型{self.__class__.__name__}还没有保存到数据中,不能手动指定ID"
|
|
||||||
return self.__class__._default_manager.create(**self.DICT_DATA)
|
return self.__class__._default_manager.create(**self.DICT_DATA)
|
||||||
|
|
||||||
def update(self, request, update_data: dict[str, any] = None):
|
def update(self, request, update_data: dict[str, any] = None):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user