You've already forked opc-backend
开发了多角色登录与鉴权接口:实现了普通用户、企业和管理员的登录分流,并支持Token验证。
开发了权限控制接口:实现了通过数据库分配菜单权限节点,控制接口访问安全。 开发了实名认证中心:实现了个人身份证信息与企业营业执照的提交与审核接口。 开发了任务与协作大厅核心业务:实现了任务的发布、接单、状态流转以及专家邀约接口。 配置了全局环境变量与数据库引擎:集成了 PostgreSQL 数据库、Redis 缓存与 MinIO 对象存储。
This commit is contained in:
0
opc_cert/__init__.py
Normal file
0
opc_cert/__init__.py
Normal file
3
opc_cert/admin.py
Normal file
3
opc_cert/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
opc_cert/apps.py
Normal file
6
opc_cert/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OpcCertConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'opc_cert'
|
||||
40
opc_cert/migrations/0001_initial.py
Normal file
40
opc_cert/migrations/0001_initial.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Generated by Django 4.2.30 on 2026-04-25 09:46
|
||||
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OpcCertification',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('real_name', models.CharField(max_length=64)),
|
||||
('id_card', models.TextField(blank=True, null=True)),
|
||||
('skills', models.JSONField(default=list)),
|
||||
('experience', models.TextField(blank=True, null=True)),
|
||||
('resume_url', models.CharField(blank=True, max_length=512, null=True)),
|
||||
('attachments', models.JSONField(default=list)),
|
||||
('status', models.CharField(choices=[('PENDING', '待审核'), ('APPROVED', '已通过'), ('REJECTED', '已驳回')], default='PENDING', max_length=16)),
|
||||
('reject_reason', models.TextField(blank=True, null=True)),
|
||||
('reviewed_at', models.DateTimeField(blank=True, null=True)),
|
||||
('rating', models.SmallIntegerField(blank=True, null=True)),
|
||||
('rating_tags', models.JSONField(default=list)),
|
||||
('rating_note', models.TextField(blank=True, null=True)),
|
||||
('rated_at', models.DateTimeField(blank=True, null=True)),
|
||||
('version', models.SmallIntegerField(default=1)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'opc_certifications',
|
||||
},
|
||||
),
|
||||
]
|
||||
33
opc_cert/migrations/0002_initial.py
Normal file
33
opc_cert/migrations/0002_initial.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.2.30 on 2026-04-25 09:46
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('opc_cert', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='opccertification',
|
||||
name='rated_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rated_certifications', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='opccertification',
|
||||
name='reviewer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reviewed_certifications', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='opccertification',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opc_certifications', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
0
opc_cert/migrations/__init__.py
Normal file
0
opc_cert/migrations/__init__.py
Normal file
32
opc_cert/models.py
Normal file
32
opc_cert/models.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import uuid
|
||||
from django.db import models
|
||||
|
||||
class CertStatus(models.TextChoices):
|
||||
PENDING = 'PENDING', '待审核'
|
||||
APPROVED = 'APPROVED', '已通过'
|
||||
REJECTED = 'REJECTED', '已驳回'
|
||||
|
||||
class OpcCertification(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
user = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='opc_certifications')
|
||||
real_name = models.CharField(max_length=64)
|
||||
id_card = models.TextField(null=True, blank=True)
|
||||
skills = models.JSONField(default=list)
|
||||
experience = models.TextField(null=True, blank=True)
|
||||
resume_url = models.CharField(max_length=512, null=True, blank=True)
|
||||
attachments = models.JSONField(default=list)
|
||||
status = models.CharField(max_length=16, choices=CertStatus.choices, default=CertStatus.PENDING)
|
||||
reject_reason = models.TextField(null=True, blank=True)
|
||||
reviewer = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='reviewed_certifications')
|
||||
reviewed_at = models.DateTimeField(null=True, blank=True)
|
||||
rating = models.SmallIntegerField(null=True, blank=True)
|
||||
rating_tags = models.JSONField(default=list)
|
||||
rating_note = models.TextField(null=True, blank=True)
|
||||
rated_by = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='rated_certifications')
|
||||
rated_at = models.DateTimeField(null=True, blank=True)
|
||||
version = models.SmallIntegerField(default=1)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'opc_certifications'
|
||||
21
opc_cert/serializers.py
Normal file
21
opc_cert/serializers.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from rest_framework import serializers
|
||||
from .models import OpcCertification
|
||||
|
||||
class OpcCertificationSerializer(serializers.ModelSerializer):
|
||||
user_detail = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = OpcCertification
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id', 'user', 'status', 'reviewer', 'reviewed_at', 'rating', 'rating_tags', 'rating_note', 'rated_by', 'rated_at', 'created_at', 'updated_at']
|
||||
|
||||
def get_user_detail(self, obj):
|
||||
user = obj.user
|
||||
return {
|
||||
'id': user.id,
|
||||
'username': user.username,
|
||||
'nickname': user.nickname,
|
||||
'avatar_url': user.avatar_url,
|
||||
'email': user.email,
|
||||
'phone': user.phone
|
||||
}
|
||||
3
opc_cert/tests.py
Normal file
3
opc_cert/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
10
opc_cert/urls.py
Normal file
10
opc_cert/urls.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import OpcCertificationViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register('certifications', OpcCertificationViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
91
opc_cert/views.py
Normal file
91
opc_cert/views.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from rest_framework import viewsets, permissions, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import action
|
||||
from .models import OpcCertification, CertStatus
|
||||
from .serializers import OpcCertificationSerializer
|
||||
from django.utils import timezone
|
||||
|
||||
class OpcCertificationViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
@author: xujl
|
||||
Api说明: OPC(One Person Company)专家认证申请接口视图。提供普通用户提交资质申请、管理员审核(approve)、驳回(reject)等核心流程。认证通过后自动授予OPC_USER角色。
|
||||
"""
|
||||
queryset = OpcCertification.objects.all()
|
||||
serializer_class = OpcCertificationSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
required_permissions = {
|
||||
'GET': 'api:certs:read',
|
||||
'POST': 'api:certs:write',
|
||||
'PUT': 'api:certs:write',
|
||||
'PATCH': 'api:certs:write',
|
||||
'DELETE': 'api:certs:delete'
|
||||
}
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action in ['create', 'destroy']:
|
||||
return [permissions.IsAuthenticated()]
|
||||
if self.action in ['list', 'retrieve']:
|
||||
# Either it's their own, or they have admin permission
|
||||
return [permissions.IsAuthenticated()]
|
||||
if self.action in ['approve', 'reject']:
|
||||
from users.permissions import HasAPIPermission
|
||||
return [HasAPIPermission()]
|
||||
return super().get_permissions()
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
from users.permissions import HasAPIPermission
|
||||
|
||||
# If user has the certs read permission or is superuser, show all
|
||||
has_admin_perm = False
|
||||
if user.is_superuser:
|
||||
has_admin_perm = True
|
||||
else:
|
||||
from users.models import RolePermission
|
||||
perms = set(RolePermission.objects.filter(role__userrole__user=user).values_list('permission__code', flat=True))
|
||||
if 'api:certs:read' in perms or '*' in perms:
|
||||
has_admin_perm = True
|
||||
|
||||
if has_admin_perm:
|
||||
return OpcCertification.objects.all().order_by('-created_at')
|
||||
return OpcCertification.objects.filter(user=user).order_by('-created_at')
|
||||
|
||||
def perform_create(self, serializer):
|
||||
# 提交新申请前,只删除该用户的 PENDING 或 REJECTED 记录,保留已有的 APPROVED 记录
|
||||
OpcCertification.objects.filter(user=self.request.user).exclude(status=CertStatus.APPROVED).delete()
|
||||
serializer.save(user=self.request.user, status=CertStatus.PENDING)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def approve(self, request, pk=None):
|
||||
cert = self.get_object()
|
||||
if cert.status != CertStatus.PENDING:
|
||||
return Response({'detail': '状态不允许该操作'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
cert.status = CertStatus.APPROVED
|
||||
cert.reviewer = request.user
|
||||
cert.reviewed_at = timezone.now()
|
||||
cert.save()
|
||||
|
||||
# 管理员通过后,删除该用户所有的旧认证记录(包括旧的 APPROVED 记录),确保数据库只有最新的一条
|
||||
OpcCertification.objects.filter(user=cert.user).exclude(id=cert.id).delete()
|
||||
|
||||
# 自动追加 OPC_USER 角色
|
||||
from users.models import Role, UserRole
|
||||
role_opc, _ = Role.objects.get_or_create(code='OPC_USER', defaults={'name': '认证专家', 'is_system': True})
|
||||
UserRole.objects.get_or_create(user=cert.user, role=role_opc, defaults={'granted_by': request.user})
|
||||
|
||||
return Response({'status': '认证已通过,用户角色已更新'})
|
||||
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def reject(self, request, pk=None):
|
||||
cert = self.get_object()
|
||||
reason = request.data.get('reject_reason', '不符合要求')
|
||||
|
||||
cert.status = CertStatus.REJECTED
|
||||
cert.reject_reason = reason
|
||||
cert.reviewer = request.user
|
||||
cert.reviewed_at = timezone.now()
|
||||
cert.save()
|
||||
return Response({'status': '认证已驳回'})
|
||||
Reference in New Issue
Block a user