guci 5 months ago
parent dacd605b4b
commit 97f91d38f7

@ -1,8 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="DjangoProject/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (DjangoProject) (2)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/../DjangoProject\templates" />
</list>
</option>
</component>
</module>

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="sw.sqlite" uuid="166e5de7-2427-48a7-b532-3707147a661a">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:identifier.sqlite</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="identifier" uuid="9e468e66-19e3-4806-802d-3f430c902f99">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:E:\Python_study\DjangoProject\identifier.sqlite3</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
<libraries>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
</library>
</libraries>
</data-source>
</component>
</project>

@ -1,16 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="3">
<item index="0" class="java.lang.String" itemvalue="django" />
<item index="1" class="java.lang.String" itemvalue="djangorestframework" />
<item index="2" class="java.lang.String" itemvalue="django-cors-headers" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 (DjangoProject) (2)" />
</component>
</project>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/DjangoProject.iml" filepath="$PROJECT_DIR$/.idea/DjangoProject.iml" />
</modules>
</component>
</project>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../" vcs="Git" />
</component>
</project>

@ -1,6 +0,0 @@
projectKey=Notes-master2
serverUrl=http://localhost:9000
serverVersion=25.3.0.104237
dashboardUrl=http://localhost:9000/dashboard?id=Notes-master2
ceTaskId=a33d0039-99a1-489c-8e22-20146e609d81
ceTaskUrl=http://localhost:9000/api/ce/task?id=a33d0039-99a1-489c-8e22-20146e609d81

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

@ -1,16 +0,0 @@
"""
ASGI config for DjangoProject project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DjangoProject.settings')
application = get_asgi_application()

@ -1,95 +0,0 @@
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'django-insecure-your-secret-key-here'
DEBUG = True
ALLOWED_HOSTS = ['*']
# 在INSTALLED_APPS中添加corsheaders
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders', # 新增这一行
'project',
'django_crontab', # 定时任务应用
]
# 在MIDDLEWARE最前面添加CorsMiddleware
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 新增,必须放在最前面
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'DjangoProject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'DjangoProject.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'identifier.sqlite3',
}
}
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True
STATIC_URL = 'static/'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# AI服务配置
AI_API_KEY = "4agUQAXAv79GrBnxQy5Wh1sn"
AI_SECRET_KEY = "OZ9hyVAQdKbmG08oq5iMFH5zx8AoNAdv"
# 静态文件配置
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
# 媒体文件配置
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# ========== 新增CORS配置 ==========
CORS_ALLOW_ALL_ORIGINS = True # 允许所有域名跨域
CORS_ALLOW_CREDENTIALS = True # 允许携带cookie
# 定时匹配功能
CRONJOBS = [
('0 6,12,18,0 * * *', 'project.management.commands.scheduled_match.Command'),
]

@ -1,14 +0,0 @@
# DjangoProject/urls.py - 添加媒体文件服务
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('project.urls')),
]
# 开发环境下提供媒体文件服务
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

@ -1,16 +0,0 @@
"""
WSGI config for DjangoProject project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DjangoProject.settings')
application = get_wsgi_application()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

@ -1,22 +0,0 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DjangoProject.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

@ -1,26 +0,0 @@
from django.contrib import admin
from .models import T_User, T_Object_Info, T_Description
# 注册 T_User 模型到管理员界面
@admin.register(T_User)
class T_UserAdmin(admin.ModelAdmin):
list_display = ['user_id', 'wechat_id', 'nickname'] # 列表中显示的字段
list_display_links = ['user_id', 'nickname'] # 可点击进入编辑页的字段
search_fields = ['nickname', 'wechat_id'] # 可搜索的字段
list_per_page = 20 # 每页显示的数量
# 或者使用简单注册方式:
# admin.site.register(T_User)
@admin.register(T_Object_Info)
class T_Object_InfoAdmin(admin.ModelAdmin):
list_display = ['object', 'color', 'brand', 'feature','create_time', 'state', 'claim_phone'] # 添加状态和认领手机号
search_fields = ['object', 'color', 'brand','feature']
list_filter = ['state'] # 添加状态过滤器
@admin.register(T_Description)
class T_DescriptionAdmin(admin.ModelAdmin):
list_display = ['desc_id', 'wechat', 'description']
search_fields = ['wechat__wechat_id', 'wechat__nickname', 'description']
list_select_related = ['wechat']

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

@ -1,56 +0,0 @@
# project/crontab.py
from django.utils import timezone
from .models import T_Description, T_Object_Info
from .utils import SearchService
def auto_match_descriptions():
"""
自动匹配描述任务 - 改进版本
"""
print(f"开始执行自动匹配任务... {timezone.now()}")
# 获取所有需要匹配的描述
descriptions = T_Description.objects.all()
all_items = T_Object_Info.objects.all()
match_count = 0
match_results = []
for desc in descriptions:
# 使用搜索服务进行匹配
search_service = SearchService()
result = search_service.search_objects_by_description(desc.description, threshold=0.3)
if result['success']:
for match in result['results']:
item = match['object_info']
match_count += 1
match_result = {
'desc_id': desc.desc_id,
'wechat_id': desc.wechat.wechat_id,
'nickname': desc.wechat.nickname,
'user_description': desc.description,
'object_id': item.object_id,
'item_name': item.name,
'item_color': item.color,
'item_location': item.location,
'contact_number': item.contact_number,
'similarity': round(match['similarity'] * 100, 2), # 转换为百分比
'match_time': timezone.now().strftime('%Y-%m-%d %H:%M:%S')
}
match_results.append(match_result)
print(f"匹配成功: {desc.wechat.nickname}的描述 '{desc.description}'")
print(f" 与物品 '{item.name}({item.color})' 相似度 {match['similarity']:.2%}")
print(f"自动匹配完成,找到 {match_count} 个匹配结果")
# 保存匹配结果到数据库或发送通知
if match_results:
print("匹配结果汇总:")
for result in match_results:
print(
f"- {result['nickname']}: {result['user_description']} -> {result['item_name']}({result['item_color']}) 相似度{result['similarity']}%")
return match_results

@ -1,31 +0,0 @@
# project/management/commands/scheduled_match.py
from django.core.management.base import BaseCommand
from django.utils import timezone
from project.utils import SearchService
from project.models import T_Description
class Command(BaseCommand):
help = '定时匹配任务 - 每天6点、12点、18点、24点执行'
def handle(self, *args, **options):
current_hour = timezone.now().hour
self.stdout.write(f'开始定时匹配任务,当前时间: {current_hour}')
search_service = SearchService()
# 获取所有描述
descriptions = T_Description.objects.all().select_related('wechat')
match_count = 0
for desc in descriptions:
result = search_service.search_objects_by_description(desc.description)
if result['success'] and result['count'] > 0:
match_count += 1
self.stdout.write(
f"用户 {desc.wechat.nickname}({desc.wechat.wechat_id}) 匹配到 {result['count']} 个物品"
)
# 这里可以添加发送微信通知的逻辑
self.stdout.write(
self.style.SUCCESS(f'定时匹配任务完成,共处理 {descriptions.count()} 个描述,匹配到 {match_count} 个用户')
)

@ -1,22 +0,0 @@
from django.http import HttpResponse
import json
class SimpleCorsMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 处理预检请求
if request.method == "OPTIONS":
response = HttpResponse()
response["Access-Control-Allow-Origin"] = "*"
response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS, PUT, DELETE"
response["Access-Control-Allow-Headers"] = "Content-Type, Authorization, X-Requested-With"
response["Access-Control-Max-Age"] = "86400"
return response
response = self.get_response(request)
response["Access-Control-Allow-Origin"] = "*"
response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS, PUT, DELETE"
response["Access-Control-Allow-Headers"] = "Content-Type, Authorization, X-Requested-With"
return response

@ -1,34 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-01 15:07
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='T_Object_Info',
fields=[
('number', models.AutoField(primary_key=True, serialize=False, verbose_name='编号')),
('object', models.CharField(max_length=100, verbose_name='物品名称')),
('color', models.CharField(max_length=50, verbose_name='物品颜色')),
('feature', models.TextField(verbose_name='物品特征')),
('brand', models.CharField(max_length=100, verbose_name='物品品牌')),
('telephone', models.CharField(max_length=11, verbose_name='联系电话')),
('location', models.CharField(max_length=200, verbose_name='位置')),
('image_path', models.CharField(blank=True, max_length=500, null=True, verbose_name='图片路径')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
],
options={
'verbose_name': '物品信息',
'verbose_name_plural': '物品信息',
'db_table': 'T_Object_Info',
},
),
]

@ -1,26 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-02 05:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='T_User',
fields=[
('user_id', models.AutoField(primary_key=True, serialize=False, verbose_name='用户序号')),
('wechat_id', models.CharField(max_length=100, unique=True, verbose_name='微信号')),
('nickname', models.CharField(max_length=100, verbose_name='微信昵称')),
],
options={
'verbose_name': '用户信息',
'verbose_name_plural': '用户信息',
'db_table': 'T_User',
},
),
]

@ -1,27 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-02 07:41
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project', '0002_t_user'),
]
operations = [
migrations.CreateModel(
name='T_Description',
fields=[
('desc_id', models.AutoField(primary_key=True, serialize=False, verbose_name='描述序号')),
('description', models.TextField(verbose_name='用户描述')),
('wechat', models.ForeignKey(db_column='wechat_id', on_delete=django.db.models.deletion.CASCADE, to='project.t_user', to_field='wechat_id', verbose_name='微信号')),
],
options={
'verbose_name': '用户描述',
'verbose_name_plural': '用户描述',
'db_table': 'T_Description',
},
),
]

@ -1,28 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-02 12:09
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project', '0003_t_description'),
]
operations = [
migrations.AlterModelOptions(
name='t_description',
options={'verbose_name': '用户描述'},
),
migrations.RemoveField(
model_name='t_object_info',
name='image_path',
),
migrations.AddField(
model_name='t_description',
name='created_time',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='创建时间'),
preserve_default=False,
),
]

@ -1,17 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-04 13:53
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('project', '0004_alter_t_description_options_and_more'),
]
operations = [
migrations.RemoveField(
model_name='t_description',
name='created_time',
),
]

@ -1,23 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-16 07:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project', '0005_remove_t_description_created_time'),
]
operations = [
migrations.AddField(
model_name='t_object_info',
name='claim_phone',
field=models.CharField(blank=True, max_length=11, null=True, verbose_name='认领人手机号'),
),
migrations.AddField(
model_name='t_object_info',
name='state',
field=models.CharField(choices=[('未领取', '未领取'), ('已领取', '已领取')], default='未领取', max_length=20, verbose_name='物品状态'),
),
]

@ -1,18 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-16 10:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project', '0006_t_object_info_claim_phone_t_object_info_state'),
]
operations = [
migrations.AddField(
model_name='t_object_info',
name='photo',
field=models.CharField(blank=True, default='', max_length=500, null=True, verbose_name='识别照片路径'),
),
]

@ -1,72 +0,0 @@
# models.py - 修复字段名称
from django.db import models
from django.utils import timezone
# 物品信息表
class T_Object_Info(models.Model):
number = models.AutoField(primary_key=True, verbose_name="编号")
object = models.CharField(max_length=100, verbose_name="物品名称")
color = models.CharField(max_length=50, verbose_name="物品颜色")
feature = models.TextField(verbose_name="物品特征")
brand = models.CharField(max_length=100, verbose_name="物品品牌")
telephone = models.CharField(max_length=11, verbose_name="联系电话")
location = models.CharField(max_length=200, verbose_name="位置")
# 修改使用统一的photo字段名
photo = models.CharField(
max_length=500,
verbose_name="识别照片路径",
blank=True,
null=True,
default=''
)
create_time = models.DateTimeField(default=timezone.now, verbose_name="创建时间")
# 状态信息
state = models.CharField(
max_length=20,
default='未领取',
choices=[('未领取', '未领取'), ('已领取', '已领取')],
verbose_name="物品状态"
)
claim_phone = models.CharField(
max_length=11,
blank=True,
null=True,
verbose_name="认领人手机号"
)
class Meta:
db_table = 'T_Object_Info'
verbose_name = '物品信息'
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.object} - {self.create_time.strftime('%Y-%m-%d %H:%M')}"
# 用户表
class T_User(models.Model):
user_id = models.AutoField(primary_key=True, verbose_name="用户序号")
wechat_id = models.CharField(max_length=100, unique=True, verbose_name="微信号")
nickname = models.CharField(max_length=100, verbose_name="微信昵称")
class Meta:
db_table = 'T_User'
verbose_name = '用户信息'
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.nickname}({self.wechat_id})"
class T_Description(models.Model):
desc_id = models.AutoField(primary_key=True, verbose_name="描述序号")
wechat = models.ForeignKey(T_User, on_delete=models.CASCADE, verbose_name="微信号", to_field='wechat_id',
db_column='wechat_id')
description = models.TextField(verbose_name="用户描述")
class Meta:
db_table = 'T_Description'
verbose_name = '用户描述'
def __str__(self):
return f"描述{self.desc_id}({self.wechat.nickname})"

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

@ -1,23 +0,0 @@
from django.urls import path
from . import views
from django.conf import settings
from django.conf.urls.static import static
from django.urls import path, include
urlpatterns = [
path('identify/', views.identify_item, name='identify_item'),
path('submit/', views.submit_item, name='submit_item'),
path('items/', views.get_items, name='get_items'),
path('wechat/login/', views.wechat_login, name='wechat_login'),
path('users/', views.get_user_list, name='get_user_list'),
path('search/objects', views.search_objects, name='search_objects'),
path('save-description/', views.save_description, name='save_description'),
path('saved-descriptions/', views.saved_descriptions, name='saved_descriptions'),
path('delete-description/', views.delete_description, name='delete_description'),
path('check-auto-match/', views.check_auto_match, name='check_auto_match'),
path('claim-item/', views.claim_item, name='claim_item'), # 新增认领接口
path('items/<int:item_id>/', views.get_item_detail, name='get_item_detail'),
path('upload-photo/', views.upload_photo, name='upload_photo'), #上传照片
# 兼容原有接口
path('search/', views.search_objects, name='search'),
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

@ -1,279 +0,0 @@
import jieba
import jieba.posseg as pseg
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
class ObjectMatchService:
def __init__(self):
# 初始化中文关键词词典 - 扩展颜色关键词,包含单字和完整词
self.color_keywords = {
'': ['', '黑色'],
'': ['', '白色'],
'': ['', '红色'],
'': ['', '蓝色'],
'绿': ['绿', '绿色'],
'': ['', '黄色'],
'': ['', '紫色'],
'': ['', '灰色'],
'': ['', '粉色'],
'': ['', '橙色'],
'': ['', '棕色'],
'': ['', '金色'],
'': ['', '银色']
}
self.brand_keywords = ['耐克', '阿迪', '苹果', '三星', '小米', '华为', '联想', '戴尔', '索尼', '惠普','倍思','美的','康铭','sky','品诺']
# 初始化jieba
jieba.initialize()
def normalize_color(self, color_word):
"""将颜色词归一化为标准形式"""
for standard_color, variants in self.color_keywords.items():
if color_word in variants:
return standard_color
return color_word
def extract_features(self, description):
"""
使用jieba进行中文特征提取
"""
features = {
'object': '',
'color': '',
'brand': '',
'feature': ''
}
# 常见物品名称列表(中文)
common_objects = [
'保温杯', '水杯', '杯子', '茶杯', '咖啡杯', '玻璃杯',
'充电宝', '充电器', '充电头', '充电线', '数据线', '电源适配器', '遥控器',
'耳机', '耳塞', '蓝牙耳机', '有线耳机', '头戴耳机',
'手机', '智能手机', '电话', '移动电话',
'手表', '腕表', '电子表', '机械表', '智能手表',
'钱包', '卡包', '零钱包', '皮夹',
'钥匙', '钥匙串', '钥匙扣', '钥匙链',
'校园卡', '学生证', '一卡通', '身份证', '银行卡',
'书本', '书籍', '教科书', '笔记本', '练习本',
'', '圆珠笔', '钢笔', '铅笔', '马克笔',
'眼镜', '太阳镜', '墨镜', '近视镜',
'雨伞', '折叠伞', '雨具', '阳伞',
'书包', '背包', '双肩包', '手提包', '电脑包',
'U盘', '闪存盘', '优盘', '移动硬盘',
'鼠标', '电脑鼠标', '无线鼠标', '有线鼠标',
'纸巾', '卫生纸', '卷纸', '湿巾',
'电脑', '笔记本电脑', '平板电脑', 'iPad',
'水壶', '运动水壶', '塑料杯',
'牙刷'
]
# 使用jieba进行分词和词性标注
words = pseg.cut(description)
word_list = list(words)
print(f"分词结果: {[(word, flag) for word, flag in word_list]}") # word是分词结果flag是词性
# 第一轮:精确匹配物品名称
for word, flag in word_list:
word = word.strip()
if not word:
continue
# 优先匹配常见物品名称
if word in common_objects and not features['object']:
features['object'] = word
continue
# 第二轮:如果第一轮没匹配到,使用包含匹配
if not features['object']:
for word, flag in word_list:
word = word.strip()
for obj in common_objects:
if obj in word or word in obj:
features['object'] = obj
break
if features['object']:
break
# 第三轮:使用名词作为备选
if not features['object']:
for word, flag in word_list:
if flag.startswith('n') and len(word) >= 2:
features['object'] = word
break
# 颜色匹配
for word, flag in word_list:
word = word.strip()
# 检查是否是颜色词(包括单字和完整词)
normalized_color = self.normalize_color(word)
if normalized_color != word and not features['color']: # 如果归一化后不同,说明是颜色词
features['color'] = normalized_color
break
# 直接检查颜色关键词
for standard_color, variants in self.color_keywords.items():
if word in variants and not features['color']:
features['color'] = standard_color
break
if features['color']:
break
# 品牌匹配
for word, flag in word_list:
word = word.strip()
if word in self.brand_keywords and not features['brand']:
features['brand'] = word
break
# 特征提取(形容词和其他描述性词汇)
feature_words = []
for word, flag in word_list:
word = word.strip()
# 提取形容词、动词和其他描述词
if (flag.startswith('a') or flag.startswith('v') or
(flag.startswith('n') and word not in common_objects and
word not in [color for variants in self.color_keywords.values() for color in variants] and
word not in self.brand_keywords)):
feature_words.append(word)
if feature_words:
features['feature'] = ' '.join(feature_words)
print(f"提取的特征: {features}")
return features
def calculate_similarity(self, description, obj_info):
"""相似度计算:物品名称匹配为基础"""
try:
# 从描述中提取特征
desc_features = self.extract_features(description)
# 首先检查物品名称是否匹配
desc_object = desc_features['object']
obj_object = obj_info.object if obj_info.object else ""
print(f"描述物品: '{desc_object}', 数据库物品: '{obj_object}'")
# 如果物品名称不匹配直接返回0相似度
if not desc_object or not obj_object:
return 0
# 检查物品名称是否匹配(包含关系或相同)
name_matched = False
name_similarity = 0
if desc_object == obj_object:
name_matched = True
name_similarity = 1.0
elif desc_object in obj_object or obj_object in desc_object:
name_matched = True
name_similarity = 0.8
else:
# 物品名称不匹配直接返回0
return 0
# 只有物品名称匹配的情况下,才计算其他特征的匹配度
if not name_matched:
return 0
# 计算特征匹配得分
match_score = name_similarity # 基础分
# 颜色匹配加分
desc_color = desc_features['color']
obj_color = obj_info.color if obj_info.color else ""
# 归一化数据库中的颜色
if obj_color:
obj_color_normalized = self.normalize_color(obj_color)
else:
obj_color_normalized = ""
if desc_color and obj_color_normalized:
# 检查颜色是否匹配
if desc_color == obj_color_normalized:
match_score += 0.15
print(
f"颜色匹配加分 +0.15 (描述颜色: {desc_color}, 数据库颜色: {obj_color}->{obj_color_normalized})")
else:
# 颜色不匹配,减分
match_score -= 0.1
print(
f"颜色不匹配减分 -0.1 (描述颜色: {desc_color}, 数据库颜色: {obj_color}->{obj_color_normalized})")
# 品牌匹配加分
desc_brand = desc_features['brand']
obj_brand = obj_info.brand if obj_info.brand else ""
if desc_brand and obj_brand and desc_brand == obj_brand:
match_score += 0.1
print(f"品牌匹配加分 +0.1")
# 特征匹配加分
desc_feature = desc_features['feature']
obj_feature = obj_info.feature if obj_info.feature else ""
if desc_feature and obj_feature:
# 计算特征词重叠
desc_feature_words = set(desc_feature.split())
obj_feature_words = set(obj_feature.split())
common_features = desc_feature_words & obj_feature_words
if common_features:
feature_bonus = len(common_features) * 0.05
match_score += min(0.1, feature_bonus)
print(f"特征匹配加分 +{min(0.1, feature_bonus):.3f}")
# 确保相似度在合理范围内
final_similarity = max(0, min(1.0, match_score))
print(f"最终相似度: {final_similarity:.3f}")
return final_similarity
except Exception as e:
print(f"相似度计算错误: {e}")
return 0
class SearchService:
def __init__(self):
self.match_service = ObjectMatchService()
def search_objects_by_description(self, description, threshold=0.3):
"""
根据描述搜索匹配的物品
"""
from .models import T_Object_Info
try:
# 获取所有物品信息
all_objects = T_Object_Info.objects.all()
results = []
for obj in all_objects:
# 计算相似度
similarity = self.match_service.calculate_similarity(description, obj)
# 相似度大于阈值的加入结果
if similarity >= threshold:
results.append({
'object_info': obj,
'similarity': round(similarity, 4)
})
# 按相似度降序排序
results.sort(key=lambda x: x['similarity'], reverse=True)
print(f"搜索 '{description}' 找到 {len(results)} 个匹配物品")
return {
'success': True,
'results': results,
'count': len(results)
}
except Exception as e:
print(f"搜索错误: {e}")
return {
'success': False,
'error': str(e)
}

File diff suppressed because it is too large Load Diff

@ -1,29 +0,0 @@
@echo off
echo ========================================
echo 开始执行失物招领自动匹配任务
echo 时间:%date% %time%
echo ========================================
REM 切换到项目目录
cd /d E:\Python_study\DjangoProject
REM 激活虚拟环境
call .venv\Scripts\activate.bat
REM 执行匹配命令
echo 正在执行匹配任务...
python manage.py run_match
REM 检查执行结果
if %errorlevel% == 0 (
echo 匹配任务执行成功!
) else (
echo 匹配任务执行失败!
)
echo ========================================
echo 任务执行完成
echo ========================================
REM 暂停以便查看结果(可选)
REM pause

@ -1,7 +0,0 @@
sonar.projectKey=Notes-master2
sonar.projectName=Notes-master2
sonar.sources=.
sonar.sourceEncoding=UTF-8
sonar.python.version=3
sonar.host.url=http://localhost:9000
sonar.token=sqp_52f5140964a42f8b4068d07aba6382cd0489c512

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

@ -1 +0,0 @@
Subproject commit eee2e57e1eb685bca14b8b50817c562fba1303d5

@ -1,28 +0,0 @@
系统简介:
整体目标:开发一个面向中国民航大学东丽校区的智能校园失物招领平台,通过数字化、智能化的方式解决校园内物品丢失寻找困难的问题。
软件系统的独特创意和特色: 系统无需额外服务器及硬件投入,通过图像识别接口自动生成物品信息,缩短发布时间;基于地图的物品位置可视化展示。
1.解决“信息发布低效杂乱”问题通过AI识别与模板化发布确保信息规范、准确提升发布效率与质量。
2.解决“寻找过程耗时费力”问题:通过地图可视化,将“漫无目的寻找”变为“有的放矢的领取”,极大缩短寻找路径和时间。
所需结合的其他设备、系统和服务:
1.第三方AI服务接口调用百度AI图像识别API
2.地图API集成腾讯地图API实现地图展示功能。
主要功能:
功能1智能拾物登记用户拍照上传拾物信息AI自动识别物品特征及物品类别如书包、水杯、钥匙生成物品卡片并显示在界面。
功能2搜索匹配系统根据用户描述匹配物品。
功能3地图显示 ):在地图上显示物品的当前位置。
配置环境:
一、前端开发环境
安装微信开发者工具:
前往微信公众平台下载并安装最新稳定版的微信开发者工具。
获取项目源码。
打开微信开发者工具。
选择「导入项目」,定位到本项目根目录。
填入 AppID。
点击「确定」,即可在模拟器中预览和调试小程序。
二、后端开发环境
1.安装 Python和Pycharm专业版
2.安装项目依赖
3.打开项目
4.启动后端服务
Loading…
Cancel
Save