initial commit

This commit is contained in:
2025-04-29 18:09:00 +08:00
commit 4faed52de5
690 changed files with 13481 additions and 0 deletions

0
apps/user/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

6
apps/user/admin.py Normal file
View File

@@ -0,0 +1,6 @@
from django.contrib import admin
from apps.user.models import Users
# 在这里将数据库表注册到admin中
admin.site.register([Users])

5
apps/user/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class UserConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.user'

182
apps/user/controllers.py Normal file
View File

@@ -0,0 +1,182 @@
from django.contrib.auth import get_user_model
from datetime import datetime, timedelta, timezone
from ninja_extra import api_controller, ControllerBase, route
from ninja.pagination import paginate
from utils.chen_pagination import MyPagination
from ninja_extra.permissions import IsAuthenticated, IsAdminUser
from ninja import Query
from django.db import transaction
from django.contrib.auth import authenticate
from django.shortcuts import get_object_or_404
from ninja_jwt.tokens import RefreshToken
from ninja_jwt.authentication import JWTAuth
from ninja_jwt.controller import TokenObtainPairController
from ninja_jwt import schema
from typing import List
from utils.chen_response import ChenResponse
from apps.user.schema import UserInfoOutSchema, CreateUserSchema, CreateUserOutSchema, UserRetrieveInputSchema, \
UserRetrieveOutSchema, UpdateDeleteUserSchema, UpdateDeleteUserOutSchema, DeleteUserSchema, LogOutSchema, \
LogInputSchema, LogDeleteInSchema, AdminModifyPasswordSchema
from apps.user.models import TableOperationLog, Users as UserClass
from apps.project.models import Project
# 工具函数
from utils.chen_crud import update, multi_delete
from apps.user.tools.ldap_tools import load_ldap_users
# 导入登录日志函数
from utils.log_util.request_util import save_login_log
Users: UserClass = get_user_model() # type:ignore
# 定义用户登录接口包含token刷新和生成
@api_controller("/system", tags=['用户token控制和登录接口'])
class UserTokenController(TokenObtainPairController):
auto_import = True
@route.post("/login", url_name='login')
def obtain_token(self, user_token: schema.TokenObtainPairSerializer):
"""新版本有特性,后期修改"""
# 注意TokenObtainPairSerializer是老版本所以兼容本质是TokenObtainPairInputSchema
user: UserClass = user_token._user
if user:
# 判断是否为启用状态
if user.status == '2':
return ChenResponse(status=500, code=500, message='账号已被禁用,请联系管理员...')
save_login_log(request=self.context.request, user=user) # 保存登录日志
refresh = RefreshToken.for_user(user)
token = refresh.access_token # type:ignore
return ChenResponse(code=200,
data={'token': str(token), 'refresh': str(refresh),
'token_exp_data': datetime.fromtimestamp(token["exp"], tz=timezone.utc)})
@route.get("/getInfo", response=UserInfoOutSchema, url_name="get_info", auth=JWTAuth())
def get_user_info(self):
# 直接按照Schema返回
return self.context.request.auth
@route.post("/logout", url_name="logout", auth=JWTAuth())
def logout(self):
return ChenResponse(code=200, message='退出登录成功')
# 定义system/user用户管理接口
@api_controller("/system/user", tags=['用户管理'], auth=JWTAuth())
class UserManageController(ControllerBase):
# 用户创建接口
@route.post("/save", response=CreateUserOutSchema, url_name="user_create", auth=JWTAuth(),
permissions=[IsAuthenticated, IsAdminUser])
def create_user(self, user_schema: CreateUserSchema):
user = user_schema.create()
return user
# 给前端传所有用户当做字典
@route.get('/list', response=List[UserRetrieveOutSchema], url_name="user_list", auth=None)
@transaction.atomic
def list_user(self, project_id: int = None):
"""如果传了project_id则返回项目中的成员而非全部用户"""
qs = Users.objects.all()
if project_id is not None:
project_obj = get_object_or_404(Project, id=project_id)
all_member: list = project_obj.member
# 将member和duty_person联合
if project_obj.duty_person not in project_obj.member:
all_member.append(project_obj.duty_person)
qs = qs.filter(name__in=all_member)
return qs
# 用户检索接口
@route.get("/index", response=List[UserRetrieveOutSchema])
@paginate(MyPagination)
def index_user(self, filters: UserRetrieveInputSchema = Query(...)):
# 重要处理前端不传值为None的情况
for attr, value in filters.__dict__.items():
if getattr(filters, attr) is None:
setattr(filters, attr, '')
start_time = self.context.request.GET.get('create_datetime[0]')
if start_time is None:
start_time = "2000-01-01"
end_time = self.context.request.GET.get('create_datetime[1]')
if end_time is None:
end_time = '8000-01-01'
date_list = [start_time, end_time]
qs = Users.objects.filter(name__icontains=filters.name, username__icontains=filters.username,
phone__icontains=filters.phone, status__contains=filters.status,
create_datetime__range=date_list).order_by('-create_datetime')
return qs
@route.put("/update/{user_id}", response=UpdateDeleteUserOutSchema, permissions=[IsAuthenticated, IsAdminUser],
url_name="user-update")
def update_user(self, user_id: int, payload: UpdateDeleteUserSchema):
if payload.username == "superAdmin":
return ChenResponse(code=400, status=400, message="无法编辑,唯一管理员账号")
payload.validate_unique_username(user_id)
update_user = update(self.context.request, user_id, payload, Users)
return {"message": "用户更新成功"}
@route.delete("/delete", permissions=[IsAuthenticated, IsAdminUser], url_name="user-delete")
def delete_user(self, data: DeleteUserSchema):
ids = data.ids
# 去掉删除创始人
for item in ids:
if item == 1:
ids.pop(item)
multi_delete(ids, Users)
return ChenResponse(code=200, status=200, message="删除成功")
# 管理员改变用户状态是否停用/启用
@route.get('/change_status', auth=JWTAuth(), permissions=[IsAuthenticated, IsAdminUser], url_name='user-change')
def change_user_status(self, user_status: str, userId: int):
user = Users.objects.filter(id=userId).first()
if not user:
return ChenResponse(status=400, code=400, message='用户未找到')
if user.id == 1:
return ChenResponse(status=400, code=400, message='管理员不能被禁用,此操作无效')
user.status = user_status
user.save()
return user.status
@route.post("/modifyPassword", auth=JWTAuth(), permissions=[IsAuthenticated, IsAdminUser])
def modify_password(self, payload: AdminModifyPasswordSchema):
user: UserClass = self.context.request.user # type:ignore
if user:
# 判断就密码是否正确
user_old = authenticate(username=user.username, password=payload.oldPassword)
if not user_old:
return ChenResponse(status=500, code=500, message='旧密码错误,请检查')
user.set_password(payload.newPassword)
user.save()
return ChenResponse(status=200, code=200, message='管理员修改密码成功')
# 用户登录后动态读取LDAP用户录入数据
@route.get("/ldap", url_name='user-ldap')
def load_ldap(self):
try:
load_ldap_users()
return ChenResponse(status=200, code=200, message='连接LDAP服务器成功同步用户数据')
except Exception as exc:
print(exc)
return ChenResponse(status=200, code=200, message='欢迎您,正在外网访问')
# 操作日志接口
@api_controller("/system/log", tags=['日志记录'], auth=JWTAuth())
class LogController(ControllerBase):
@route.get("/operation_list", url_name="log_list", response=List[LogOutSchema], auth=None)
@paginate(MyPagination)
def log_list(self, data: Query[LogInputSchema]):
for attr, value in data.model_dump().items():
if getattr(data, attr) is None:
setattr(data, attr, '')
logs = TableOperationLog.objects.values('id', 'user__username', 'operate_obj', 'create_datetime',
'operate_des').order_by(
'-create_datetime')
# 根据条件搜索
logs = logs.filter(user__username__icontains=data.user, create_datetime__range=data.create_datetime)
return logs
@route.get('/operation_delete', url_name='log_delete', permissions=[IsAuthenticated, IsAdminUser], auth=JWTAuth())
def log_delete(self, data: LogDeleteInSchema = Query(...)):
time = datetime.now() - timedelta(days=data.day)
log_qs = TableOperationLog.objects.filter(create_datetime__lt=time)
log_qs.delete()
if data.day > 0:
return ChenResponse(message=f'删除{data.day}天前数据成功')
else:
return ChenResponse(message='全部日志删除成功')

View File

@@ -0,0 +1,79 @@
# Generated by Django 4.2.13 on 2024-07-03 10:30
from django.conf import settings
import django.contrib.auth.models
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='Users',
fields=[
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')),
('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')),
('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')),
('username', models.CharField(db_index=True, help_text='用户账号', max_length=150, unique=True, verbose_name='用户账号')),
('name', models.CharField(help_text='姓名', max_length=40, verbose_name='姓名')),
('avatar', models.TextField(blank=True, help_text='头像', null=True, verbose_name='头像')),
('email', models.EmailField(blank=True, help_text='邮箱', max_length=255, null=True, verbose_name='邮箱')),
('status', models.CharField(default='1', help_text='status', max_length=15, verbose_name='启用状态')),
('job', models.CharField(blank=True, help_text='工作', max_length=255, null=True, verbose_name='工作')),
('jobName', models.CharField(blank=True, help_text='工作名称', max_length=255, null=True, verbose_name='工作名称')),
('organization', models.CharField(blank=True, help_text='工作组织', max_length=255, null=True, verbose_name='工作组织')),
('location', models.CharField(blank=True, help_text='住地', max_length=255, null=True, verbose_name='住地')),
('locationName', models.CharField(blank=True, help_text='住地名称', max_length=255, null=True, verbose_name='住地名称')),
('introduction', models.CharField(blank=True, help_text='自我介绍', max_length=255, null=True, verbose_name='自我介绍')),
('personalWebsite', models.CharField(blank=True, help_text='个人网站', max_length=255, null=True, verbose_name='个人网站')),
('phone', models.CharField(blank=True, default='18888888888', help_text='电话', max_length=255, null=True, verbose_name='电话')),
('accountId', models.CharField(blank=True, default='1', help_text='用户标识', max_length=255, null=True, verbose_name='用户标识')),
('role', models.CharField(blank=True, default='user', help_text='角色', max_length=64, null=True, verbose_name='角色')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': '用户表',
'verbose_name_plural': '用户表',
'db_table': 'user_user',
'ordering': ('-create_datetime',),
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='TableOperationLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
('operate_obj', models.CharField(default='未关联对象', help_text='操作对象', max_length=256, verbose_name='操作对象')),
('operate_des', models.CharField(default='未有操作详情', help_text='操作详情', max_length=1024, verbose_name='操作详情')),
('user', models.ForeignKey(blank=True, db_constraint=False, help_text='操作人员', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ruser', related_query_name='quser', to=settings.AUTH_USER_MODEL, verbose_name='操作人员')),
],
options={
'verbose_name': '用户操作日志表',
'verbose_name_plural': '用户操作日志表',
'db_table': 'operation_log',
'ordering': ('-create_datetime',),
},
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 4.2.13 on 2024-07-15 13:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='users',
name='email',
field=models.EmailField(blank=True, default=1, max_length=254, verbose_name='email address'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.2.13 on 2024-07-15 13:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('user', '0002_alter_users_email'),
]
operations = [
migrations.RemoveField(
model_name='users',
name='email',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.13 on 2024-07-15 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0003_remove_users_email'),
]
operations = [
migrations.AddField(
model_name='users',
name='email',
field=models.EmailField(blank=True, max_length=254, verbose_name='email address'),
),
]

View File

56
apps/user/models.py Normal file
View File

@@ -0,0 +1,56 @@
from django.contrib.auth.models import AbstractUser, Group
from django.db import models
from utils.models import CoreModel
STATUS_CHOICES = (
(0, '禁用'),
(1, '启用')
)
class Users(AbstractUser, CoreModel):
username = models.CharField(max_length=150, unique=True, db_index=True, verbose_name='用户账号',
help_text="用户账号")
name = models.CharField(max_length=40, verbose_name="姓名", help_text="姓名")
avatar = models.TextField(verbose_name="头像", null=True, blank=True, help_text="头像")
status = models.CharField(max_length=15, verbose_name='启用状态', help_text="status", default='1')
job = models.CharField(max_length=255, verbose_name='工作', null=True, blank=True, help_text='工作')
jobName = models.CharField(max_length=255, verbose_name='工作名称', null=True, blank=True, help_text='工作名称')
organization = models.CharField(max_length=255, verbose_name='工作组织', null=True, blank=True,
help_text='工作组织')
location = models.CharField(max_length=255, verbose_name='住地', null=True, blank=True, help_text='住地')
locationName = models.CharField(max_length=255, verbose_name='住地名称', null=True, blank=True,
help_text='住地名称')
introduction = models.CharField(max_length=255, verbose_name='自我介绍', null=True, blank=True,
help_text='自我介绍')
personalWebsite = models.CharField(max_length=255, verbose_name='个人网站', null=True, blank=True,
help_text='个人网站')
phone = models.CharField(max_length=255, verbose_name="电话", null=True, blank=True, help_text="电话", default='18888888888')
accountId = models.CharField(max_length=255, verbose_name="用户标识", null=True, blank=True, help_text="用户标识", default='1')
role = models.CharField(max_length=64, verbose_name="角色", null=True, blank=True, help_text="角色", default='user')
def __str__(self):
return f'用户账号:{self.username}-用户名:{self.name}'
class Meta:
db_table = 'user_user'
verbose_name = '用户表'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)
class TableOperationLog(models.Model):
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
verbose_name="创建时间")
user = models.ForeignKey(to="Users", db_constraint=False, related_name="ruser", on_delete=models.CASCADE,
verbose_name='操作人员', help_text='操作人员', related_query_name='quser', null=True,
blank=True)
# 2.操作的对象
operate_obj = models.CharField(max_length=256, verbose_name='操作对象', default='未关联对象', help_text='操作对象')
# 3.操作详情
operate_des = models.CharField(max_length=1024, verbose_name='操作详情', default='未有操作详情',
help_text='操作详情')
class Meta:
db_table = 'operation_log'
verbose_name = '用户操作日志表'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)

115
apps/user/schema.py Normal file
View File

@@ -0,0 +1,115 @@
from apps.user.models import Users
from django.contrib.auth.models import Group
from ninja_schema import ModelSchema, model_validator, Schema
from ninja_extra.exceptions import APIException
from ninja_extra import status
from datetime import datetime
from typing import List
from ninja import Field
UserModel = Users
# 定义用户名异常
class UsernameException(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "用户名已存在,注册失败"
default_code = 400
class GroupSchema(ModelSchema):
class Config:
# 因为保证唯一性所以ninja_schema使用set集合
model = Group
# 注意ninja_schema(include)和ninja(model_fields)
include = ("name",)
# schema作用于创建用户请求
class CreateUserSchema(ModelSchema):
class Config:
model = UserModel
include = ('username', 'name', 'password', 'phone', 'status',)
# username判重
@model_validator("username")
@classmethod
def unique_username(cls, value):
if UserModel.objects.filter(username__icontains=value).exists():
raise UsernameException()
return value
def create(self):
# 注意这里使用exclude_nonedict()方式属于pydantic
return UserModel.objects.create_user(**self.dict(exclude_none=True), email='xxx@qq.com')
# schema:作用于创建用户后response
class CreateUserOutSchema(ModelSchema):
class Config:
model = UserModel
exclude = ('password',)
# schema:定义前端查询用户信息的输出
class UserInfoOutSchema(ModelSchema):
class Config:
model = UserModel
exclude = ("password",)
# schema:作用于用户检索以及其他
class UserRetrieveInputSchema(ModelSchema):
class Config:
model = UserModel
include = ("name", "username", "phone", "status",)
# ninja_schema的可选字段
optional = ("name", "username", "phone", "status",)
# schema:作用于检索后的输出定义
class UserRetrieveOutSchema(ModelSchema):
class Config:
model = UserModel
exclude = ("password",)
# 删除和更新用户
class UpdateDeleteUserSchema(ModelSchema):
class Config:
model = UserModel
include = ("name", "username", "phone", "status")
def validate_unique_username(self, id: int):
user_filters = UserModel.objects.filter(username=self.username)
if len(user_filters) > 1:
raise UsernameException()
elif len(user_filters) == 1:
if user_filters[0].id == id:
return
else:
raise UsernameException()
else:
return
class UpdateDeleteUserOutSchema(Schema):
message: str
class DeleteUserSchema(Schema):
ids: List[int]
# ~~~~~~~~~~~~~~~~~~~~日志schema~~~~~~~~~~~~~~~~~~~~
# 操作日志的schema
class LogOutSchema(Schema):
id: int
user: str = Field(..., alias='user__username')
operate_obj: str
create_datetime: datetime
operate_des: str
# 操作日志的查询
class LogInputSchema(Schema):
user: str = Field("", alias='user')
create_datetime: List = ['2000-01-01', '9999-01-01']
# 操作日志的删除输入
class LogDeleteInSchema(Schema):
day: int = Field(7, ge=0, description='删除多少天前的数据')
# 管理员修改密码
class AdminModifyPasswordSchema(Schema):
newPassword: str
newPassword_confirmation: str
oldPassword: str

3
apps/user/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,56 @@
import ldap
from django.contrib.auth import get_user_model
def load_ldap_users(url='ldap://dns.paisat.cn:389',
dn="CN=Administrator,CN=Users,DC=sstc,DC=ctu",
pwd="WXWX2019!!!!!!",
search_dn="OU=ALL,DC=sstc,DC=ctu",
search_filter='(&(sAMAccountName=*))'):
Users = get_user_model()
ldap_server = ldap.initialize(url)
ldap_server.simple_bind_s(dn, pwd)
ldap_users = ldap_server.search_ext_s(search_dn,
ldap.SCOPE_SUBTREE,
search_filter)
temp_users = []
for user in ldap_users:
username_field = user[-1]['sAMAccountName'][0]
email_field = user[-1].get('mail', username_field + b'@sstc.ctu')[0]
if isinstance(email_field, int):
email_field = username_field + b'@sstc.ctu'
user_dict = {
'username': username_field.decode(),
'name': user[-1]['name'][0].decode(),
'email': email_field.decode(),
}
temp_users.append(user_dict)
db_user = Users.objects.filter(username=user_dict['username'])
exsits = db_user.exists()
if exsits:
# 如果存在则更新
update_flag = False
c_user = db_user.first()
if c_user != user_dict['username']:
c_user.username = user_dict['username']
update_flag = True
if c_user.name != user_dict['name']:
c_user.name = user_dict['name']
update_flag = True
if c_user.email != user_dict['email']:
c_user.email = user_dict['email']
update_flag = True
if update_flag:
c_user.save()
else:
user_dict['remark'] = '自动同步LDAP数据用户'
user_dict['status'] = '1'
user_dict['phone'] = '18888888888'
user_dict['role'] = 'user'
user_dict['accountId'] = 'user'
user_single = Users.objects.create(**user_dict)
user_single.set_password('wxwx2018!!!')
user_single.save()
# 6月3日新增组别

3
apps/user/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.