├── .gitignore ├── Readme.md ├── doc ├── doc.md └── 表结构.docx ├── server ├── manage.py ├── myapp │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── auth │ │ ├── MyRateThrottle.py │ │ ├── __init__.py │ │ └── authentication.py │ ├── cf │ │ ├── UserCF.py │ │ └── __init__.py │ ├── handler.py │ ├── middlewares │ │ ├── LogMiddleware.py │ │ └── __init__.py │ ├── models.py │ ├── permission │ │ ├── __init__.py │ │ └── permission.py │ ├── serializers.py │ ├── urls.py │ ├── utils.py │ └── views │ │ ├── __init__.py │ │ └── index │ │ ├── __init__.py │ │ ├── classification.py │ │ ├── comment.py │ │ ├── feedback.py │ │ ├── notice.py │ │ ├── order.py │ │ ├── thing.py │ │ └── user.py ├── python_movie.sql ├── readme.md ├── requirements.txt ├── server │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── upload │ ├── ad │ ├── 1717248427687.jpeg │ ├── 1718280455795.jpeg │ └── 1718280462895.jpeg │ ├── avatar │ ├── 1716017756768.png │ ├── 1716017945368.jpeg │ ├── 1716017953831.png │ ├── 1716018100946.png │ ├── 1716018106970.jpeg │ ├── 1717249502324.jpeg │ └── 1718280917105.jpeg │ ├── cover │ ├── 1716043254213.jpeg │ ├── 1716083189595.jpeg │ ├── 1716083199860.jpeg │ ├── 1716083209737.jpeg │ ├── 1716083281670.jpeg │ ├── 1716083301393.jpeg │ ├── 1716087863555.jpeg │ ├── 1717249309482.jpeg │ ├── 1717249816837.jpeg │ ├── 1717297292698.jpeg │ ├── 1717297298858.jpeg │ ├── 1717297305179.jpeg │ ├── 1717297311519.jpeg │ ├── 1717297318288.jpeg │ ├── 1717297652697.jpeg │ ├── 1718195752664.jpeg │ ├── 1718197771289.jpeg │ ├── 1718198003099.jpeg │ ├── 1718198097347.jpeg │ ├── 1718198791310.jpeg │ ├── 1718198803762.jpeg │ ├── 1718198812032.jpeg │ ├── 1718198819617.jpeg │ ├── 1718198828791.jpeg │ └── 1718198852834.jpeg │ └── img │ ├── 111.jpg │ ├── 222.jpg │ ├── 333.jpg │ ├── 444.jpg │ ├── Wechat.jpeg │ └── weixin.png ├── web ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .stylelintignore ├── README.md ├── build │ ├── constant.ts │ └── vite │ │ └── plugins │ │ ├── autoImport.ts │ │ ├── component.ts │ │ ├── compress.ts │ │ ├── imagemin.ts │ │ ├── index.ts │ │ ├── progress.ts │ │ ├── restart.ts │ │ ├── unocss.ts │ │ └── visualizer.ts ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── prettier.config.js ├── public │ ├── ico.png │ └── images │ │ ├── admin-login-bg.jpg │ │ ├── admin-login-bg2.jpg │ │ └── demo.jpg ├── src │ ├── App.vue │ ├── api │ │ └── index │ │ │ ├── ad.js │ │ │ ├── address.js │ │ │ ├── classification.js │ │ │ ├── comment.js │ │ │ ├── feedback.js │ │ │ ├── notice.js │ │ │ ├── order.js │ │ │ ├── thing.js │ │ │ ├── thingCollect.js │ │ │ ├── thingWish.js │ │ │ └── user.js │ ├── assets │ │ ├── fonts │ │ │ ├── Blimone-ExtraBold.woff │ │ │ ├── Blimone-ExtraLight.woff │ │ │ ├── Blimone-Light.woff │ │ │ └── Blimone-Regular.woff │ │ ├── icons │ │ │ ├── logo.png │ │ │ └── svg │ │ │ │ ├── github.svg │ │ │ │ ├── logo.svg │ │ │ │ ├── marks.svg │ │ │ │ ├── test.svg │ │ │ │ ├── ts.svg │ │ │ │ └── twitter.svg │ │ ├── images │ │ │ ├── add.svg │ │ │ ├── address-right-icon.svg │ │ │ ├── ali-pay-icon.svg │ │ │ ├── avatar.jpg │ │ │ ├── background.svg │ │ │ ├── banner-02.webp │ │ │ ├── banner2.svg │ │ │ ├── cart-icon.svg │ │ │ ├── clear-search.svg │ │ │ ├── code-icon.svg │ │ │ ├── delete-icon.svg │ │ │ ├── ebook-download-icon.svg │ │ │ ├── ic-admin-logo.png │ │ │ ├── ic-message.png │ │ │ ├── k-logo.png │ │ │ ├── login-banner.png │ │ │ ├── login.png │ │ │ ├── logo.png │ │ │ ├── mail-icon.svg │ │ │ ├── message-icon.svg │ │ │ ├── order-address-icon.svg │ │ │ ├── order-icon.svg │ │ │ ├── order-point-icon.svg │ │ │ ├── order-thing-icon.svg │ │ │ ├── pwd-hidden.svg │ │ │ ├── pwd-icon.svg │ │ │ ├── read-online-icon.svg │ │ │ ├── recommend-hover.png │ │ │ ├── recommend-hover.svg │ │ │ ├── register-name.svg │ │ │ ├── search-icon.svg │ │ │ ├── searchIcon.svg │ │ │ ├── setting-card-icon.svg │ │ │ ├── setting-icon.svg │ │ │ ├── setting-msg-icon.svg │ │ │ ├── setting-push-icon.svg │ │ │ ├── setting-safe-icon.svg │ │ │ ├── share-icon.svg │ │ │ ├── tel-icon.svg │ │ │ ├── want-read-hover.png │ │ │ ├── want-read-hover.svg │ │ │ ├── wb-share.svg │ │ │ └── wx-pay-icon.svg │ │ └── styles │ │ │ └── base.less │ ├── core │ │ └── bootstrap.js │ ├── main.js │ ├── router │ │ ├── index.js │ │ └── root.js │ ├── store │ │ ├── constants.js │ │ ├── index.js │ │ └── modules │ │ │ ├── app │ │ │ ├── index.ts │ │ │ └── types.ts │ │ │ └── user │ │ │ ├── index.ts │ │ │ └── types.ts │ ├── styles │ │ ├── index.less │ │ └── reset.less │ └── utils │ │ ├── auth.ts │ │ ├── http │ │ └── axios │ │ │ ├── index.ts │ │ │ ├── status.ts │ │ │ └── type.ts │ │ ├── index.ts │ │ └── result.ts ├── stylelint.config.js ├── tsconfig.json ├── types │ ├── auto-imports.d.ts │ ├── components.d.ts │ └── env.d.ts ├── vite.config.ts └── yarn.lock └── 代码说明.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /server/myapp/migrations 2 | /server/myapp/views/__pycache__/ 3 | /server/.idea 4 | /web/.idea 5 | /web/dist 6 | /web/node_modules 7 | /server/.idea/ 8 | /.idea/ 9 | .idea 10 | .idea/workspace.xml 11 | __pycache__ 12 | server/.idea 13 | .DS_Store 14 | server/.DS_Store 15 | web/.DS_Store -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## 项目简介 3 | 4 | 5 | >该项目是基于Python+Vue开发的电影订票管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。 6 | 该系统旨在帮助大学生学习并掌握Python编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Python的电影订票管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。如需完整源码,可以联系客服微信购买:lengqin1024 7 | 8 | ## 主要功能 9 | 10 | - 影片管理:管理系统可以录入、修改和查询影片的基本信息,如名称、价格、语言、地区、简介等。 11 | - 类型管理:系统可以管理影片的类型信息,包括类型的名称等。 12 | - 评论管理:管理和浏览整个网站的评论信息。 13 | - 用户管理:管理和浏览网站的用户信息,可以新增、编辑和删除用户。 14 | - 统计分析:系统可以根据影片的活动数据和用户参与度进行统计和分析,帮助管理员了解整个系统的状况。 15 | - 消息管理:影片管理员可以在系统上发布消息,整个网站的用户都能收到。 16 | - 广告管理:影片管理员可以在系统上发布广告消息,然后在详情页面右侧展示。 17 | - 意见反馈:影片管理员可以在后台查看浏览用户提交的意见反馈信息。 18 | - 系统信息:管理员可以查看系统的基本信息,包括系统名称、服务器信息、内存信息、cpu信息、软件信息等。 19 | - 注册登录:用户通过注册和登录后,才能使用网站。 20 | - 门户浏览:用户进入首页后,可以浏览影片列表信息,包括最新、最热。 21 | - 影片推荐:基于协同过滤推荐算法的热门推荐。 22 | - 用户中心:包括用户基本资料修改、用户基本信息、密码、收藏点赞等。 23 | - 我的订单:包括我购买的影片的门票信息。 24 | - 意见反馈:包括用户提交意见反馈的入口页面。 25 | - 模糊搜索:顶部搜索功能,支持模糊搜索影片信息。 26 | - 影片评论:详情页下侧用户可以评论影片。 27 | 28 | ## 开发环境 29 | 30 | - 后端: Python 3.8 + Django 3.2 31 | - 前端: Javascript + Vue 32 | - 数据库:MySQL 5.7 33 | - 开发平台:Pycharm + vscode 34 | - 运行环境:Windows 10/11 35 | 36 | ## 关键技术 37 | 38 | - 前端技术栈 ES6、vue、vuex、vue-router、vue-cli、axios、antd 39 | - 后端技术栈 Python、Django、pip 40 | 41 | 42 | 43 | ## 在线演示 44 | 45 | 演示地址:[https://movie.gitapp.cn](https://movie.gitapp.cn) 46 | 47 | ## 运行步骤 48 | 49 | ### 软件准备 50 | 51 | 1. Python 3.8 [下载地址](https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe) 52 | 2. MySQL 5.7 [下载地址](https://dev.mysql.com/get/Downloads/MySQLInstaller/mysql-installer-community-5.7.44.0.msi) 53 | 3. Node [下载地址](https://nodejs.org/dist/v18.20.2/node-v18.20.2-x64.msi) 54 | 55 | ### 后端运行步骤 56 | 57 | (1) 安装依赖,cd进入server目录下,执行 58 | ``` 59 | pip install -r requirements.txt 60 | ``` 61 | 62 | (2) 创建数据库,创建SQL如下: 63 | ``` 64 | CREATE DATABASE IF NOT EXISTS python_db[your dbname] DEFAULT CHARSET utf8 COLLATE utf8_general_ci 65 | ``` 66 | (3) 恢复数据库数据。在mysql下依次执行如下命令: 67 | 68 | ``` 69 | mysql> use xxx(数据库名); 70 | mysql> source D:/xxx/xxx/xxx.sql; 71 | ``` 72 | 73 | (4) 配置数据库。在server目录下的server下的settings.py中配置您的数据库账号密码 74 | 75 | ``` 76 | DATABASES = { 77 | 'default': { 78 | 'ENGINE': 'django.db.backends.mysql', 79 | 'NAME': 'python_db', # 您的数据库 80 | 'USER': 'root', # 您的用户名 81 | 'PASSWORD': '4643830', # 您的密码 82 | 'HOST': '127.0.0.1', 83 | 'PORT': '3306', 84 | 'OPTIONS': { 85 | "init_command": "SET foreign_key_checks = 0;", 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | (5) 启动django服务。在server目录下执行: 92 | ``` 93 | python manage.py runserver 94 | ``` 95 | 96 | ### 前端运行步骤 97 | 98 | (1) 安装依赖,cd到web目录,执行: 99 | ``` 100 | npm install 101 | ``` 102 | (2) 运行项目 103 | ``` 104 | npm run dev 105 | ``` 106 | 107 | 然后访问前端地址。即可 108 | 109 | 110 | ## 开发文档 111 | 112 | [点击进入](doc/doc.md) 113 | 114 | 115 | ## 付费咨询 116 | 117 | 微信(Lengqin1024) 118 | 119 | ## 常见问题 120 | 121 | **1. 数据库版本有什么要求?** 122 | 123 | 答:mysql 5.7及以上版本即可 124 | 125 | **2. 项目的代码结构?** 126 | 127 | 答:server目录是后端代码,web目录是前端代码。 128 | 129 | **3. 需要学习哪些技术知识?** 130 | 131 | 答:需要学习[python编程知识](https://www.runoob.com/python3/python3-tutorial.html)、[django框架知识](https://docs.djangoproject.com/zh-hans/3.2/)、[vue编程知识](https://cn.vuejs.org/guide/introduction.html) 132 | 133 | **4. 后台管理的默认账号密码是?** 134 | 135 | 答:管理员账号密码是:admin123 / admin123 136 | 137 | -------------------------------------------------------------------------------- /doc/表结构.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/doc/表结构.docx -------------------------------------------------------------------------------- /server/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /server/myapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/myapp/__init__.py -------------------------------------------------------------------------------- /server/myapp/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from myapp.models import Classification, Thing, User, Comment 5 | 6 | admin.site.register(Classification) 7 | admin.site.register(Thing) 8 | admin.site.register(User) 9 | admin.site.register(Comment) 10 | -------------------------------------------------------------------------------- /server/myapp/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MyappConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'myapp' 7 | -------------------------------------------------------------------------------- /server/myapp/auth/MyRateThrottle.py: -------------------------------------------------------------------------------- 1 | from rest_framework.throttling import AnonRateThrottle 2 | 3 | 4 | class MyRateThrottle(AnonRateThrottle): 5 | # 限流 每分钟2次 6 | THROTTLE_RATES = {"anon": "2/min"} 7 | 8 | 9 | class UserRateThrottle(AnonRateThrottle): 10 | # 限流 每分钟5次 11 | THROTTLE_RATES = {"anon": "5/min"} 12 | -------------------------------------------------------------------------------- /server/myapp/auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/myapp/auth/__init__.py -------------------------------------------------------------------------------- /server/myapp/auth/authentication.py: -------------------------------------------------------------------------------- 1 | from rest_framework import exceptions 2 | from rest_framework.authentication import BaseAuthentication 3 | 4 | from myapp.models import User 5 | 6 | 7 | # 后台接口认证 8 | class AdminTokenAuthtication(BaseAuthentication): 9 | def authenticate(self, request): 10 | adminToken = request.META.get("HTTP_ADMINTOKEN") 11 | 12 | print("检查adminToken==>" + adminToken) 13 | users = User.objects.filter(admin_token=adminToken) 14 | """ 15 | 判定条件: 16 | 1. 传了adminToken 17 | 2. 查到了该帐号 18 | 3. 该帐号是管理员或演示帐号 19 | """ 20 | if not adminToken or len(users) == 0 or users[0].role == '2': 21 | raise exceptions.AuthenticationFailed("AUTH_FAIL_END") 22 | else: 23 | print('adminToken验证通过') 24 | 25 | 26 | # 前台接口认证 27 | class TokenAuthtication(BaseAuthentication): 28 | def authenticate(self, request): 29 | token = request.META.get("HTTP_TOKEN", "") 30 | if token is not None: 31 | print("检查token==>" + token) 32 | users = User.objects.filter(token=token) 33 | # print(users) 34 | """ 35 | 判定条件: 36 | 1. 传了token 37 | 2. 查到了该帐号 38 | 3. 该帐号是普通用户 39 | """ 40 | if not token or len(users) == 0 or (users[0].role in ['1', '3']): 41 | raise exceptions.AuthenticationFailed("AUTH_FAIL_FRONT") 42 | else: 43 | print('token验证通过') 44 | else: 45 | print("检查token==>token 为空") 46 | raise exceptions.AuthenticationFailed("AUTH_FAIL_FRONT") 47 | -------------------------------------------------------------------------------- /server/myapp/cf/UserCF.py: -------------------------------------------------------------------------------- 1 | from math import sqrt, pow 2 | import operator 3 | 4 | 5 | # 基于用户的协同过滤推荐算法 6 | # 算法参考:https://blog.csdn.net/net19880504/article/details/137772131 7 | 8 | class UserCf: 9 | 10 | # 获得初始化数据 11 | def __init__(self, data): 12 | self.data = data 13 | 14 | # 计算两个用户的皮尔逊相关系数 15 | def pearson(self, user1, user2): 16 | sumXY = 0.0 17 | n = 1 18 | sumX = 0.1 19 | sumY = 0.1 20 | sumX2 = 0.1 21 | sumY2 = 0.1 22 | try: 23 | for movie1, score1 in user1.items(): 24 | if movie1 in user2.keys(): # 计算公共的thing的评分 25 | n += 1 26 | sumXY += score1 * user2[movie1] 27 | sumX += score1 28 | sumY += user2[movie1] 29 | sumX2 += pow(score1, 2) 30 | sumY2 += pow(user2[movie1], 2) 31 | 32 | molecule = sumXY - (sumX * sumY) / n 33 | denominator = sqrt((sumX2 - pow(sumX, 2) / n) * (sumY2 - pow(sumY, 2) / n)) 34 | r = molecule / denominator 35 | except Exception as e: 36 | print("异常信息:", str(e)) 37 | return None 38 | return r 39 | 40 | # 计算与当前用户的距离,获得最临近的用户 41 | def nearstUser(self, username, n=1): 42 | distances = {} # 用户,相似度 43 | for otherUser, items in self.data.items(): # 遍历整个数据集 44 | if otherUser not in username: # 非当前的用户 45 | distance = self.pearson(self.data[username], self.data[otherUser]) # 计算两个用户的相似度 46 | distances[otherUser] = distance 47 | sortedDistance = sorted(distances.items(), key=operator.itemgetter(1), reverse=True) # 最相似的N个用户 48 | # print("排序后的用户为:", sortedDistance) 49 | return sortedDistance[:n] 50 | 51 | # 给用户推荐物品(比如电影) 52 | def recomand(self, username, n=1): 53 | recommand = [] # 待推荐的物品 54 | for user, score in dict(self.nearstUser(username, n)).items(): # 最相近的n个用户 55 | # print("推荐的用户:", (user, score)) 56 | for movies, scores in self.data[user].items(): # 推荐的用户的物品列表 57 | if movies not in self.data[username].keys(): # 当前username没有看过 58 | # print("%s为该用户推荐的物品:%s" % (user, movies)) 59 | if movies not in recommand: 60 | recommand.append(movies) 61 | 62 | return recommand 63 | -------------------------------------------------------------------------------- /server/myapp/cf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/myapp/cf/__init__.py -------------------------------------------------------------------------------- /server/myapp/handler.py: -------------------------------------------------------------------------------- 1 | from rest_framework.response import Response 2 | 3 | 4 | class APIResponse(Response): 5 | def __init__(self, code=0, msg='', data=None, status=200, headers=None, content_type=None, **kwargs): 6 | dic = {'code': code, 'msg': msg} 7 | if data is not None: 8 | dic['data'] = data 9 | 10 | dic.update(kwargs) # 这里使用update 11 | super().__init__(data=dic, status=status, 12 | template_name=None, headers=headers, 13 | exception=False, content_type=content_type) 14 | -------------------------------------------------------------------------------- /server/myapp/middlewares/LogMiddleware.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import time 3 | import json 4 | 5 | from django.utils.deprecation import MiddlewareMixin 6 | 7 | from myapp import utils 8 | from myapp.serializers import OpLogSerializer 9 | 10 | 11 | class OpLogs(MiddlewareMixin): 12 | 13 | def __init__(self, *args): 14 | super(OpLogs, self).__init__(*args) 15 | 16 | self.start_time = None # 开始时间 17 | self.end_time = None # 响应时间 18 | self.data = {} # dict数据 19 | 20 | def process_request(self, request): 21 | 22 | self.start_time = time.time() # 开始时间 23 | 24 | re_ip = utils.get_ip(request) 25 | re_method = request.method 26 | re_content = request.GET if re_method == 'GET' else request.POST 27 | if re_content: 28 | re_content = json.dumps(re_content) 29 | else: 30 | re_content = None 31 | 32 | self.data.update( 33 | { 34 | 're_url': request.path, 35 | 're_method': re_method, 36 | 're_ip': re_ip, 37 | # 're_content': re_content, 38 | } 39 | ) 40 | # print(self.data) 41 | 42 | def process_response(self, request, response): 43 | 44 | # 耗时毫秒/ms 45 | self.end_time = time.time() # 响应时间 46 | access_time = self.end_time - self.start_time 47 | self.data['access_time'] = round(access_time * 1000) 48 | 49 | # 入库 50 | serializer = OpLogSerializer(data=self.data) 51 | if serializer.is_valid(): 52 | serializer.save() 53 | 54 | return response 55 | -------------------------------------------------------------------------------- /server/myapp/middlewares/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/myapp/middlewares/__init__.py -------------------------------------------------------------------------------- /server/myapp/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class User(models.Model): 5 | GENDER_CHOICES = ( 6 | ('M', '男'), 7 | ('F', '女'), 8 | ) 9 | STATUS_CHOICES = ( 10 | ('0', '正常'), 11 | ('1', '封号'), 12 | ) 13 | id = models.BigAutoField(primary_key=True) 14 | username = models.CharField(max_length=50, null=True) 15 | password = models.CharField(max_length=50, null=True) 16 | role = models.CharField(max_length=2, blank=True, null=True) 17 | status = models.CharField(max_length=1, choices=STATUS_CHOICES, default='0') 18 | nickname = models.CharField(blank=True, null=True, max_length=20) 19 | avatar = models.FileField(upload_to='avatar/', null=True) 20 | mobile = models.CharField(max_length=13, blank=True, null=True) 21 | email = models.CharField(max_length=50, blank=True, null=True) 22 | gender = models.CharField(max_length=1, choices=GENDER_CHOICES, blank=True, null=True) 23 | description = models.TextField(max_length=200, null=True) 24 | create_time = models.DateTimeField(auto_now_add=True, null=True) 25 | score = models.IntegerField(default=0, blank=True, null=True) 26 | push_email = models.CharField(max_length=40, blank=True, null=True) 27 | push_switch = models.BooleanField(blank=True, null=True, default=False) 28 | admin_token = models.CharField(max_length=32, blank=True, null=True) 29 | token = models.CharField(max_length=32, blank=True, null=True) 30 | 31 | class Meta: 32 | db_table = "b_user" 33 | 34 | 35 | class Classification(models.Model): 36 | list_display = ("title", "id") 37 | id = models.BigAutoField(primary_key=True) 38 | title = models.CharField(max_length=100, blank=True, null=True) 39 | create_time = models.DateTimeField(auto_now_add=True, null=True) 40 | 41 | def __str__(self): 42 | return self.title 43 | 44 | class Meta: 45 | db_table = "b_classification" 46 | 47 | 48 | class Thing(models.Model): 49 | STATUS_CHOICES = ( 50 | ('0', '上架'), 51 | ('1', '下架'), 52 | ) 53 | id = models.BigAutoField(primary_key=True) 54 | classification = models.ForeignKey(Classification, on_delete=models.CASCADE, blank=True, null=True, 55 | related_name='classification_thing') 56 | user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True, related_name='user_thing') 57 | title = models.CharField(max_length=100, blank=True, null=True) 58 | cover = models.ImageField(upload_to='cover/', null=True) 59 | description = models.TextField(max_length=1000, blank=True, null=True) 60 | price = models.CharField(max_length=50, blank=True, null=True) 61 | daoyan = models.CharField(max_length=50, blank=True, null=True) # 导演 62 | shichang = models.CharField(max_length=50, blank=True, null=True) # 时长 63 | location = models.CharField(max_length=50, blank=True, null=True) # 地区 64 | yuyan = models.CharField(max_length=50, blank=True, null=True) # 语言 65 | nianfen = models.CharField(max_length=50, blank=True, null=True) # 年份 66 | shangying = models.CharField(max_length=50, blank=True, null=True) # 上映时间 67 | fangying = models.CharField(max_length=50, blank=True, null=True) # 放映 68 | fangyingting = models.CharField(max_length=50, blank=True, null=True) # 放映厅 69 | renyuan = models.CharField(max_length=50, blank=True, null=True) # 人员表 70 | repertory = models.IntegerField(default=0) # 余票数 71 | status = models.CharField(max_length=1, choices=STATUS_CHOICES, default='0') 72 | create_time = models.DateTimeField(auto_now_add=True, null=True) 73 | pv = models.IntegerField(default=0) 74 | rate = models.IntegerField(default=3) # 评分 75 | recommend_count = models.IntegerField(default=0) 76 | wish = models.ManyToManyField(User, blank=True, related_name="wish_things") 77 | wish_count = models.IntegerField(default=0) 78 | collect = models.ManyToManyField(User, blank=True, related_name="collect_things") 79 | collect_count = models.IntegerField(default=0) 80 | 81 | class Meta: 82 | db_table = "b_thing" 83 | 84 | 85 | class Comment(models.Model): 86 | id = models.BigAutoField(primary_key=True) 87 | content = models.CharField(max_length=200, blank=True, null=True) 88 | user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='user_comment') 89 | thing = models.ForeignKey(Thing, on_delete=models.CASCADE, null=True, related_name='thing_comment') 90 | comment_time = models.DateTimeField(auto_now_add=True, null=True) 91 | like_count = models.IntegerField(default=0) 92 | 93 | class Meta: 94 | db_table = "b_comment" 95 | 96 | 97 | class Record(models.Model): 98 | id = models.BigAutoField(primary_key=True) 99 | ip = models.CharField(max_length=100, blank=True, null=True) # ip地址 100 | thing = models.ForeignKey(Thing, on_delete=models.CASCADE, null=True, related_name='thing_record') 101 | score = models.IntegerField(default=0) 102 | 103 | class Meta: 104 | db_table = "b_record" 105 | 106 | 107 | class LoginLog(models.Model): 108 | id = models.BigAutoField(primary_key=True) 109 | username = models.CharField(max_length=50, blank=True, null=True) 110 | ip = models.CharField(max_length=100, blank=True, null=True) 111 | ua = models.CharField(max_length=200, blank=True, null=True) 112 | log_time = models.DateTimeField(auto_now_add=True, null=True) 113 | 114 | class Meta: 115 | db_table = "b_login_log" 116 | 117 | 118 | class OpLog(models.Model): 119 | id = models.BigAutoField(primary_key=True) 120 | re_ip = models.CharField(max_length=100, blank=True, null=True) 121 | re_time = models.DateTimeField(auto_now_add=True, null=True) 122 | re_url = models.CharField(max_length=200, blank=True, null=True) 123 | re_method = models.CharField(max_length=10, blank=True, null=True) 124 | re_content = models.CharField(max_length=200, blank=True, null=True) 125 | access_time = models.CharField(max_length=10, blank=True, null=True) 126 | 127 | class Meta: 128 | db_table = "b_op_log" 129 | 130 | 131 | class ErrorLog(models.Model): 132 | id = models.BigAutoField(primary_key=True) 133 | ip = models.CharField(max_length=100, blank=True, null=True) 134 | url = models.CharField(max_length=200, blank=True, null=True) 135 | method = models.CharField(max_length=10, blank=True, null=True) 136 | content = models.CharField(max_length=200, blank=True, null=True) 137 | log_time = models.DateTimeField(auto_now_add=True, null=True) 138 | 139 | class Meta: 140 | db_table = "b_error_log" 141 | 142 | 143 | class Order(models.Model): 144 | id = models.BigAutoField(primary_key=True) 145 | order_number = models.CharField(max_length=13, blank=True, null=True) 146 | user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='user_order') 147 | thing = models.ForeignKey(Thing, on_delete=models.CASCADE, null=True, related_name='thing_order') 148 | count = models.IntegerField(default=0) 149 | status = models.CharField(max_length=2, blank=True, null=True) # 1正常 2取消 150 | order_time = models.DateTimeField(auto_now_add=True, null=True) 151 | receiver_name = models.CharField(max_length=20, blank=True, null=True) 152 | receiver_address = models.CharField(max_length=50, blank=True, null=True) 153 | receiver_phone = models.CharField(max_length=20, blank=True, null=True) 154 | remark = models.CharField(max_length=30, blank=True, null=True) 155 | 156 | class Meta: 157 | db_table = "b_order" 158 | 159 | 160 | class Ad(models.Model): 161 | id = models.BigAutoField(primary_key=True) 162 | image = models.ImageField(upload_to='ad/', null=True) 163 | link = models.CharField(max_length=500, blank=True, null=True) 164 | create_time = models.DateTimeField(auto_now_add=True, null=True) 165 | 166 | class Meta: 167 | db_table = "b_ad" 168 | 169 | 170 | class Notice(models.Model): 171 | id = models.BigAutoField(primary_key=True) 172 | title = models.CharField(max_length=100, blank=True, null=True) 173 | content = models.CharField(max_length=1000, blank=True, null=True) 174 | create_time = models.DateTimeField(auto_now_add=True, null=True) 175 | 176 | class Meta: 177 | db_table = "b_notice" 178 | 179 | 180 | class Feedback(models.Model): 181 | id = models.BigAutoField(primary_key=True) 182 | title = models.CharField(max_length=100, blank=True, null=True) 183 | content = models.CharField(max_length=200, blank=True, null=True) 184 | name = models.CharField(max_length=100, blank=True, null=True) 185 | email = models.CharField(max_length=100, blank=True, null=True) 186 | mobile = models.CharField(max_length=20, blank=True, null=True) 187 | create_time = models.DateTimeField(auto_now_add=True, null=True) 188 | 189 | class Meta: 190 | db_table = "b_feedback" 191 | -------------------------------------------------------------------------------- /server/myapp/permission/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/myapp/permission/__init__.py -------------------------------------------------------------------------------- /server/myapp/permission/permission.py: -------------------------------------------------------------------------------- 1 | from myapp.models import User 2 | 3 | 4 | def isDemoAdminUser(request): 5 | adminToken = request.META.get("HTTP_ADMINTOKEN") 6 | users = User.objects.filter(admin_token=adminToken) 7 | if len(users) > 0: 8 | user = users[0] 9 | if user.role == '3': # (角色3)表示演示帐号 10 | print('演示帐号===>') 11 | return True 12 | return False 13 | -------------------------------------------------------------------------------- /server/myapp/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from myapp.models import Thing, Classification, User, Comment, Record, LoginLog, Order, OpLog, \ 4 | Ad, Notice, ErrorLog, Feedback 5 | 6 | 7 | class ThingSerializer(serializers.ModelSerializer): 8 | # 额外字段 9 | classification_title = serializers.ReadOnlyField(source='classification.title') 10 | 11 | class Meta: 12 | model = Thing 13 | fields = '__all__' 14 | 15 | 16 | class DetailThingSerializer(serializers.ModelSerializer): 17 | # 额外字段 18 | classification_title = serializers.ReadOnlyField(source='classification.title') 19 | 20 | class Meta: 21 | model = Thing 22 | # 排除多对多字段 23 | exclude = ('wish', 'collect',) 24 | 25 | 26 | class UpdateThingSerializer(serializers.ModelSerializer): 27 | # 额外字段 28 | classification_title = serializers.ReadOnlyField(source='classification.title') 29 | 30 | class Meta: 31 | model = Thing 32 | # 排除多对多字段 33 | exclude = ('wish', 'collect',) 34 | 35 | 36 | class ListThingSerializer(serializers.ModelSerializer): 37 | # 额外字段 38 | classification_title = serializers.ReadOnlyField(source='classification.title') 39 | 40 | class Meta: 41 | model = Thing 42 | # 排除字段 43 | exclude = ('wish', 'collect',) 44 | 45 | 46 | class ClassificationSerializer(serializers.ModelSerializer): 47 | class Meta: 48 | model = Classification 49 | fields = '__all__' 50 | 51 | 52 | class UserSerializer(serializers.ModelSerializer): 53 | create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 54 | 55 | class Meta: 56 | model = User 57 | fields = '__all__' 58 | # exclude = ('password',) 59 | 60 | 61 | class CommentSerializer(serializers.ModelSerializer): 62 | comment_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 63 | # 额外字段 64 | title = serializers.ReadOnlyField(source='thing.title') 65 | username = serializers.ReadOnlyField(source='user.username') 66 | 67 | class Meta: 68 | model = Comment 69 | fields = ['id', 'content', 'comment_time', 'like_count', 'thing', 'user', 'title', 'username'] 70 | 71 | 72 | class RecordSerializer(serializers.ModelSerializer): 73 | class Meta: 74 | model = Record 75 | fields = '__all__' 76 | 77 | 78 | class LoginLogSerializer(serializers.ModelSerializer): 79 | log_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 80 | 81 | class Meta: 82 | model = LoginLog 83 | fields = '__all__' 84 | 85 | 86 | class OpLogSerializer(serializers.ModelSerializer): 87 | re_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 88 | 89 | class Meta: 90 | model = OpLog 91 | fields = '__all__' 92 | 93 | 94 | class ErrorLogSerializer(serializers.ModelSerializer): 95 | log_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 96 | 97 | class Meta: 98 | model = ErrorLog 99 | fields = '__all__' 100 | 101 | 102 | class OrderSerializer(serializers.ModelSerializer): 103 | order_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 104 | expect_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 105 | return_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 106 | # extra 107 | username = serializers.ReadOnlyField(source='user.username') 108 | title = serializers.ReadOnlyField(source='thing.title') 109 | price = serializers.ReadOnlyField(source='thing.price') 110 | cover = serializers.FileField(source='thing.cover', required=False) 111 | 112 | class Meta: 113 | model = Order 114 | fields = '__all__' 115 | 116 | 117 | class AdSerializer(serializers.ModelSerializer): 118 | create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 119 | 120 | class Meta: 121 | model = Ad 122 | fields = '__all__' 123 | 124 | 125 | class NoticeSerializer(serializers.ModelSerializer): 126 | create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 127 | 128 | class Meta: 129 | model = Notice 130 | fields = '__all__' 131 | 132 | 133 | class FeedbackSerializer(serializers.ModelSerializer): 134 | create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False) 135 | 136 | class Meta: 137 | model = Feedback 138 | fields = '__all__' 139 | -------------------------------------------------------------------------------- /server/myapp/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from myapp import views 4 | 5 | app_name = 'myapp' 6 | urlpatterns = [ 7 | # 后台管理api 8 | path('admin/overview/count', views.admin.overview.count), 9 | path('admin/overview/sysInfo', views.admin.overview.sysInfo), 10 | path('admin/thing/list', views.admin.thing.list_api), 11 | path('admin/thing/detail', views.admin.thing.detail), 12 | path('admin/thing/create', views.admin.thing.create), 13 | path('admin/thing/update', views.admin.thing.update), 14 | path('admin/thing/delete', views.admin.thing.delete), 15 | path('admin/thing/upload', views.admin.thing.upload), 16 | path('admin/comment/list', views.admin.comment.list_api), 17 | path('admin/comment/create', views.admin.comment.create), 18 | path('admin/comment/update', views.admin.comment.update), 19 | path('admin/comment/delete', views.admin.comment.delete), 20 | path('admin/classification/list', views.admin.classification.list_api), 21 | path('admin/classification/create', views.admin.classification.create), 22 | path('admin/classification/update', views.admin.classification.update), 23 | path('admin/classification/delete', views.admin.classification.delete), 24 | path('admin/feedback/list', views.admin.feedback.list_api), 25 | path('admin/feedback/delete', views.admin.feedback.delete), 26 | path('admin/record/list', views.admin.record.list_api), 27 | path('admin/record/create', views.admin.record.create), 28 | path('admin/record/update', views.admin.record.update), 29 | path('admin/record/delete', views.admin.record.delete), 30 | path('admin/ad/list', views.admin.ad.list_api), 31 | path('admin/ad/create', views.admin.ad.create), 32 | path('admin/ad/update', views.admin.ad.update), 33 | path('admin/ad/delete', views.admin.ad.delete), 34 | path('admin/notice/list', views.admin.notice.list_api), 35 | path('admin/notice/create', views.admin.notice.create), 36 | path('admin/notice/update', views.admin.notice.update), 37 | path('admin/notice/delete', views.admin.notice.delete), 38 | path('admin/order/list', views.admin.order.list_api), 39 | path('admin/order/create', views.admin.order.create), 40 | path('admin/order/update', views.admin.order.update), 41 | path('admin/order/cancel_order', views.admin.order.cancel_order), 42 | path('admin/order/delay', views.admin.order.delay), 43 | path('admin/order/delete', views.admin.order.delete), 44 | path('admin/loginLog/list', views.admin.loginLog.list_api), 45 | path('admin/loginLog/create', views.admin.loginLog.create), 46 | path('admin/loginLog/update', views.admin.loginLog.update), 47 | path('admin/loginLog/delete', views.admin.loginLog.delete), 48 | path('admin/loginLog/clear', views.admin.loginLog.clear), 49 | path('admin/opLog/list', views.admin.opLog.list_api), 50 | path('admin/opLog/clear', views.admin.opLog.clear), 51 | path('admin/errorLog/list', views.admin.errorLog.list_api), 52 | path('admin/errorLog/clear', views.admin.errorLog.clear), 53 | path('admin/user/list', views.admin.user.list_api), 54 | path('admin/user/create', views.admin.user.create), 55 | path('admin/user/update', views.admin.user.update), 56 | path('admin/user/updatePwd', views.admin.user.updatePwd), 57 | path('admin/user/delete', views.admin.user.delete), 58 | path('admin/user/info', views.admin.user.info), 59 | path('admin/adminLogin', views.admin.user.admin_login), 60 | 61 | 62 | # 前台管理api 63 | path('index/classification/list', views.index.classification.list_api), 64 | path('index/user/login', views.index.user.login), 65 | path('index/user/register', views.index.user.register), 66 | path('index/user/info', views.index.user.info), 67 | path('index/user/update', views.index.user.update), 68 | path('index/user/updatePwd', views.index.user.updatePwd), 69 | path('index/notice/list_api', views.index.notice.list_api), 70 | path('index/thing/list', views.index.thing.list_api), 71 | path('index/thing/detail', views.index.thing.detail), 72 | path('index/thing/getRecommend', views.index.thing.get_recommend), 73 | path('index/thing/increaseWishCount', views.index.thing.increaseWishCount), 74 | path('index/thing/addWishUser', views.index.thing.addWishUser), 75 | path('index/thing/removeWishUser', views.index.thing.removeWishUser), 76 | path('index/thing/getWishThingList', views.index.thing.getWishThingList), 77 | path('index/thing/addCollectUser', views.index.thing.addCollectUser), 78 | path('index/thing/removeCollectUser', views.index.thing.removeCollectUser), 79 | path('index/thing/getCollectThingList', views.index.thing.getCollectThingList), 80 | path('index/thing/increaseRecommendCount', views.index.thing.increaseRecommendCount), 81 | path('index/thing/listUserThing', views.index.thing.list_user_thing_api), 82 | path('index/thing/create', views.index.thing.create), 83 | path('index/thing/update', views.index.thing.update), 84 | path('index/thing/rate', views.index.thing.rate), 85 | path('index/comment/list', views.index.comment.list_api), 86 | path('index/comment/listMyComments', views.index.comment.list_my_comment), 87 | path('index/comment/create', views.index.comment.create), 88 | path('index/comment/delete', views.index.comment.delete), 89 | path('index/comment/like', views.index.comment.like), 90 | path('index/order/list', views.index.order.list_api), 91 | path('index/order/create', views.index.order.create), 92 | path('index/order/cancel_order', views.index.order.cancel_order), 93 | path('index/feedback/create', views.index.feedback.create), 94 | 95 | 96 | ] 97 | -------------------------------------------------------------------------------- /server/myapp/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import hashlib 3 | import time 4 | 5 | from myapp.cf.UserCF import UserCf 6 | from myapp.models import Record, Thing 7 | from myapp.serializers import ErrorLogSerializer 8 | 9 | 10 | def get_timestamp(): 11 | return int(round(time.time() * 1000)) 12 | 13 | 14 | def md5value(key): 15 | input_name = hashlib.md5() 16 | input_name.update(key.encode("utf-8")) 17 | md5str = (input_name.hexdigest()).lower() 18 | print('计算md5:', md5str) 19 | return md5str 20 | 21 | 22 | def dict_fetchall(cursor): # cursor是执行sql_str后的记录,作入参 23 | columns = [col[0] for col in cursor.description] # 得到域的名字col[0],组成List 24 | return [ 25 | dict(zip(columns, row)) for row in cursor.fetchall() 26 | ] 27 | 28 | 29 | def get_ip(request): 30 | """ 31 | 获取请求者的IP信息 32 | """ 33 | x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') 34 | if x_forwarded_for: 35 | ip = x_forwarded_for.split(',')[0] 36 | else: 37 | ip = request.META.get('REMOTE_ADDR') 38 | return ip 39 | 40 | 41 | def get_ua(request): 42 | """ 43 | 获取请求者的IP信息 44 | """ 45 | ua = request.META.get('HTTP_USER_AGENT') 46 | return ua[0:200] 47 | 48 | 49 | def getWeekDays(): 50 | """ 51 | 获取近一周的日期 52 | """ 53 | week_days = [] 54 | now = datetime.datetime.now() 55 | for i in range(7): 56 | day = now - datetime.timedelta(days=i) 57 | week_days.append(day.strftime('%Y-%m-%d %H:%M:%S.%f')[:10]) 58 | week_days.reverse() # 逆序 59 | return week_days 60 | 61 | 62 | def get_monday(): 63 | """ 64 | 获取本周周一日期 65 | """ 66 | now = datetime.datetime.now() 67 | monday = now - datetime.timedelta(now.weekday()) 68 | return monday.strftime('%Y-%m-%d %H:%M:%S.%f')[:10] 69 | 70 | 71 | def log_error(request, content): 72 | """ 73 | 记录错误日志 74 | """ 75 | ip = get_ip(request) 76 | method = request.method 77 | url = request.path 78 | 79 | data = { 80 | 'ip': ip, 81 | 'method': method, 82 | 'url': url, 83 | 'content': content 84 | } 85 | 86 | # 入库 87 | serializer = ErrorLogSerializer(data=data) 88 | if serializer.is_valid(): 89 | serializer.save() 90 | 91 | 92 | def get_recommend(request): 93 | # 协同过滤推荐 94 | ips = Record.objects.values_list("ip", flat=True).distinct() 95 | data = {} 96 | # 构建字典 97 | for ip in ips: 98 | dd = {} 99 | records = Record.objects.filter(ip=ip) 100 | for record in records: 101 | dd[str(record.thing_id)] = record.score 102 | data[ip] = dd 103 | if len(data) > 30: 104 | break 105 | 106 | # print('构建字典', data) 107 | 108 | userCf = UserCf(data=data) 109 | re_ip = get_ip(request) 110 | # print(re_ip) 111 | recommandList = userCf.recomand(re_ip, 10) # 核心推荐方法 112 | # print(recommandList) 113 | 114 | things = Thing.objects.filter(id__in=recommandList) 115 | # print(things) 116 | if not things or len(things) <= 0: 117 | # 若推荐太少,则pv推荐 118 | things = Thing.objects.filter(status='0').order_by('-pv') 119 | return things 120 | -------------------------------------------------------------------------------- /server/myapp/views/__init__.py: -------------------------------------------------------------------------------- 1 | from myapp.views.admin import * 2 | from myapp.views.index import * 3 | -------------------------------------------------------------------------------- /server/myapp/views/index/__init__.py: -------------------------------------------------------------------------------- 1 | from myapp.views.index.classification import * 2 | from myapp.views.index.user import * 3 | from myapp.views.index.thing import * 4 | from myapp.views.index.comment import * 5 | from myapp.views.index.order import * 6 | from myapp.views.index.notice import * 7 | from myapp.views.index.feedback import * 8 | -------------------------------------------------------------------------------- /server/myapp/views/index/classification.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | from django.db import connection 3 | from rest_framework.decorators import api_view 4 | 5 | from myapp.handler import APIResponse 6 | from myapp.models import Classification 7 | from myapp.serializers import ClassificationSerializer 8 | 9 | 10 | @api_view(['GET']) 11 | def list_api(request): 12 | if request.method == 'GET': 13 | classifications = Classification.objects.all().order_by('-create_time') 14 | serializer = ClassificationSerializer(classifications, many=True) 15 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /server/myapp/views/index/comment.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | from rest_framework.decorators import api_view, authentication_classes, throttle_classes 3 | 4 | from myapp.auth.MyRateThrottle import MyRateThrottle 5 | from myapp.auth.authentication import AdminTokenAuthtication 6 | from myapp.handler import APIResponse 7 | from myapp.models import Comment 8 | from myapp.permission.permission import isDemoAdminUser 9 | from myapp.serializers import CommentSerializer 10 | 11 | 12 | @api_view(['GET']) 13 | def list_api(request): 14 | if request.method == 'GET': 15 | thingId = request.GET.get("thingId", None) 16 | order = request.GET.get("order", 'recent') 17 | 18 | if thingId: 19 | if order == 'recent': 20 | orderBy = '-comment_time' 21 | else: 22 | orderBy = '-like_count' 23 | 24 | comments = Comment.objects.select_related("thing").filter(thing=thingId).order_by(orderBy) 25 | # print(comments) 26 | serializer = CommentSerializer(comments, many=True) 27 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 28 | else: 29 | return APIResponse(code=1, msg='thingId不能为空') 30 | 31 | 32 | @api_view(['GET']) 33 | def list_my_comment(request): 34 | if request.method == 'GET': 35 | userId = request.GET.get("userId", None) 36 | order = request.GET.get("order", 'recent') 37 | 38 | if userId: 39 | if order == 'recent': 40 | orderBy = '-comment_time' 41 | else: 42 | orderBy = '-like_count' 43 | 44 | comments = Comment.objects.select_related("thing").filter(user=userId).order_by(orderBy) 45 | # print(comments) 46 | serializer = CommentSerializer(comments, many=True) 47 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 48 | else: 49 | return APIResponse(code=1, msg='userId不能为空') 50 | 51 | 52 | @api_view(['POST']) 53 | @throttle_classes([MyRateThrottle]) 54 | def create(request): 55 | serializer = CommentSerializer(data=request.data) 56 | if serializer.is_valid(): 57 | serializer.save() 58 | return APIResponse(code=0, msg='创建成功', data=serializer.data) 59 | else: 60 | print(serializer.errors) 61 | 62 | return APIResponse(code=1, msg='创建失败') 63 | 64 | 65 | @api_view(['POST']) 66 | def delete(request): 67 | try: 68 | ids = request.GET.get('ids') 69 | ids_arr = ids.split(',') 70 | Comment.objects.filter(id__in=ids_arr).delete() 71 | except Comment.DoesNotExist: 72 | return APIResponse(code=1, msg='对象不存在') 73 | 74 | return APIResponse(code=0, msg='删除成功') 75 | 76 | 77 | @api_view(['POST']) 78 | def like(request): 79 | try: 80 | commentId = request.GET.get('commentId') 81 | comment = Comment.objects.get(pk=commentId) 82 | comment.like_count += 1 83 | comment.save() 84 | except Comment.DoesNotExist: 85 | return APIResponse(code=1, msg='对象不存在') 86 | 87 | return APIResponse(code=0, msg='推荐成功') 88 | -------------------------------------------------------------------------------- /server/myapp/views/index/feedback.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | from rest_framework.decorators import api_view, throttle_classes, authentication_classes 3 | 4 | from myapp.auth.MyRateThrottle import MyRateThrottle 5 | from myapp.auth.authentication import AdminTokenAuthtication 6 | from myapp.handler import APIResponse 7 | import datetime 8 | 9 | from myapp.models import Feedback 10 | from myapp.permission.permission import isDemoAdminUser 11 | from myapp.serializers import FeedbackSerializer 12 | 13 | 14 | @api_view(['POST']) 15 | @throttle_classes([MyRateThrottle]) 16 | def create(request): 17 | data = request.data.copy() 18 | if data['title'] is None or data['content'] is None: 19 | return APIResponse(code=1, msg='不能为空') 20 | 21 | create_time = datetime.datetime.now() 22 | data['create_time'] = create_time 23 | serializer = FeedbackSerializer(data=data) 24 | if serializer.is_valid(): 25 | serializer.save() 26 | 27 | return APIResponse(code=0, msg='提交成功', data=serializer.data) 28 | else: 29 | print(serializer.errors) 30 | return APIResponse(code=1, msg='提交失败') 31 | -------------------------------------------------------------------------------- /server/myapp/views/index/notice.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | from rest_framework.decorators import api_view 3 | 4 | from myapp.handler import APIResponse 5 | from myapp.models import Notice 6 | from myapp.serializers import NoticeSerializer 7 | 8 | 9 | @api_view(['GET']) 10 | def list_api(request): 11 | if request.method == 'GET': 12 | notices = Notice.objects.all().order_by('-create_time') 13 | serializer = NoticeSerializer(notices, many=True) 14 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 15 | 16 | -------------------------------------------------------------------------------- /server/myapp/views/index/order.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | import datetime 3 | 4 | from rest_framework.decorators import api_view, authentication_classes 5 | 6 | from myapp import utils 7 | from myapp.auth.authentication import TokenAuthtication 8 | from myapp.handler import APIResponse 9 | from myapp.models import Order, Thing 10 | from myapp.serializers import OrderSerializer 11 | 12 | 13 | @api_view(['GET']) 14 | def list_api(request): 15 | if request.method == 'GET': 16 | userId = request.GET.get('userId', -1) 17 | orderStatus = request.GET.get('orderStatus', '') 18 | 19 | orders = Order.objects.all().filter(user=userId).filter(status__contains=orderStatus).order_by('-order_time') 20 | serializer = OrderSerializer(orders, many=True) 21 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 22 | 23 | 24 | @api_view(['POST']) 25 | @authentication_classes([TokenAuthtication]) 26 | def create(request): 27 | 28 | data = request.data.copy() 29 | if data['user'] is None or data['thing'] is None or data['count'] is None: 30 | return APIResponse(code=1, msg='参数错误') 31 | 32 | thing = Thing.objects.get(pk=data['thing']) 33 | count = data['count'] 34 | if thing.repertory < int(count): 35 | return APIResponse(code=1, msg='库存不足') 36 | 37 | create_time = datetime.datetime.now() 38 | data['create_time'] = create_time 39 | data['order_number'] = str(utils.get_timestamp()) 40 | data['status'] = '1' 41 | serializer = OrderSerializer(data=data) 42 | if serializer.is_valid(): 43 | serializer.save() 44 | # 减库存 45 | thing.repertory = thing.repertory - int(count) 46 | thing.save() 47 | 48 | return APIResponse(code=0, msg='创建成功', data=serializer.data) 49 | else: 50 | print(serializer.errors) 51 | return APIResponse(code=1, msg='创建失败') 52 | 53 | 54 | @api_view(['POST']) 55 | @authentication_classes([TokenAuthtication]) 56 | def cancel_order(request): 57 | """ 58 | cancal 59 | """ 60 | try: 61 | pk = request.GET.get('id', -1) 62 | order = Order.objects.get(pk=pk) 63 | except Order.DoesNotExist: 64 | return APIResponse(code=1, msg='对象不存在') 65 | 66 | data = { 67 | 'status': 2 68 | } 69 | serializer = OrderSerializer(order, data=data) 70 | if serializer.is_valid(): 71 | serializer.save() 72 | # 加库存 73 | # thingId = request.data['thing'] 74 | # thing = Thing.objects.get(pk=thingId) 75 | # thing.repertory = thing.repertory + 1 76 | # thing.save() 77 | 78 | return APIResponse(code=0, msg='取消成功', data=serializer.data) 79 | else: 80 | print(serializer.errors) 81 | return APIResponse(code=1, msg='更新失败') 82 | -------------------------------------------------------------------------------- /server/myapp/views/index/thing.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | from django.db.models import Q 3 | from rest_framework.decorators import api_view 4 | 5 | from myapp import utils 6 | from myapp.handler import APIResponse 7 | from myapp.models import Thing, User, Record 8 | from myapp.serializers import ThingSerializer, ListThingSerializer, DetailThingSerializer, \ 9 | UpdateThingSerializer 10 | 11 | 12 | @api_view(['GET']) 13 | def list_api(request): 14 | if request.method == 'GET': 15 | keyword = request.GET.get("keyword", None) 16 | c = request.GET.get("c", None) 17 | cc = request.GET.get("cc", None) 18 | sort = request.GET.get("sort", 'recent') 19 | 20 | # 排序方式 21 | order = '-create_time' 22 | if sort == 'recent': 23 | order = '-create_time' 24 | elif sort == 'hot': 25 | order = '-pv' 26 | 27 | # 搜索 28 | if keyword: 29 | things = Thing.objects.filter(title__contains=keyword).filter(status='0').order_by(order) 30 | serializer = ListThingSerializer(things, many=True) 31 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 32 | 33 | # 过滤 34 | query = Q(status='0') 35 | if c != '-1': 36 | # 分类 37 | query = query & Q(classification_id=c) 38 | if cc != '全部': 39 | query = query & Q(yuyan=cc) 40 | 41 | things = Thing.objects.filter(query).order_by(order) 42 | 43 | serializer = ListThingSerializer(things, many=True) 44 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 45 | 46 | 47 | @api_view(['GET']) 48 | def get_recommend(request): 49 | # 推荐(协同过滤) 50 | things = utils.get_recommend(request) 51 | serializer = ListThingSerializer(things, many=True) 52 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 53 | 54 | 55 | @api_view(['POST']) 56 | def rate(request): 57 | try: 58 | thing = request.GET.get('thing', -1) 59 | rate = request.GET.get('rate', 0) 60 | thing = Thing.objects.get(pk=thing) 61 | thing.rate = int((thing.rate + int(rate)) / 2) 62 | thing.save() 63 | 64 | except Thing.DoesNotExist: 65 | utils.log_error(request, '对象不存在') 66 | return APIResponse(code=1, msg='对象不存在') 67 | 68 | return APIResponse(code=0, msg='操作成功') 69 | 70 | 71 | @api_view(['GET']) 72 | def detail(request): 73 | try: 74 | pk = request.GET.get('id', -1) 75 | thing = Thing.objects.get(pk=pk) 76 | thing.pv = thing.pv + 1 77 | thing.save() 78 | 79 | # 保存浏览记录(协同过滤) 80 | ip = utils.get_ip(request) 81 | if len(ip) > 0 and pk != -1: 82 | record = Record.objects.filter(ip=ip, thing_id=pk).first() 83 | if record: 84 | record.score = record.score + 1 85 | record.save() 86 | else: 87 | Record.objects.create(ip=ip, thing_id=pk, score=1) 88 | 89 | except Thing.DoesNotExist: 90 | utils.log_error(request, '对象不存在') 91 | return APIResponse(code=1, msg='对象不存在') 92 | 93 | if request.method == 'GET': 94 | serializer = ThingSerializer(thing) 95 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 96 | 97 | 98 | @api_view(['POST']) 99 | def increaseWishCount(request): 100 | try: 101 | pk = request.GET.get('id', -1) 102 | thing = Thing.objects.get(pk=pk) 103 | # wish_count加1 104 | thing.wish_count = thing.wish_count + 1 105 | thing.save() 106 | except Thing.DoesNotExist: 107 | utils.log_error(request, '对象不存在') 108 | return APIResponse(code=1, msg='对象不存在') 109 | 110 | serializer = ThingSerializer(thing) 111 | return APIResponse(code=0, msg='操作成功', data=serializer.data) 112 | 113 | 114 | @api_view(['POST']) 115 | def increaseRecommendCount(request): 116 | try: 117 | pk = request.GET.get('id', -1) 118 | thing = Thing.objects.get(pk=pk) 119 | # recommend_count加1 120 | thing.recommend_count = thing.recommend_count + 1 121 | thing.save() 122 | except Thing.DoesNotExist: 123 | utils.log_error(request, '对象不存在') 124 | return APIResponse(code=1, msg='对象不存在') 125 | 126 | serializer = ThingSerializer(thing) 127 | return APIResponse(code=0, msg='操作成功', data=serializer.data) 128 | 129 | 130 | @api_view(['POST']) 131 | def addWishUser(request): 132 | try: 133 | username = request.GET.get('username', None) 134 | thingId = request.GET.get('thingId', None) 135 | 136 | if username and thingId: 137 | user = User.objects.get(username=username) 138 | thing = Thing.objects.get(pk=thingId) 139 | 140 | if user not in thing.wish.all(): 141 | thing.wish.add(user) 142 | thing.wish_count += 1 143 | thing.save() 144 | 145 | except Thing.DoesNotExist: 146 | utils.log_error(request, '操作失败') 147 | return APIResponse(code=1, msg='操作失败') 148 | 149 | serializer = ThingSerializer(thing) 150 | return APIResponse(code=0, msg='操作成功', data=serializer.data) 151 | 152 | 153 | @api_view(['POST']) 154 | def removeWishUser(request): 155 | try: 156 | username = request.GET.get('username', None) 157 | thingId = request.GET.get('thingId', None) 158 | 159 | if username and thingId: 160 | user = User.objects.get(username=username) 161 | thing = Thing.objects.get(pk=thingId) 162 | 163 | if user in thing.wish.all(): 164 | thing.wish.remove(user) 165 | thing.wish_count -= 1 166 | thing.save() 167 | 168 | except Thing.DoesNotExist: 169 | utils.log_error(request, '操作失败') 170 | return APIResponse(code=1, msg='操作失败') 171 | 172 | return APIResponse(code=0, msg='操作成功') 173 | 174 | 175 | @api_view(['GET']) 176 | def getWishThingList(request): 177 | try: 178 | username = request.GET.get('username', None) 179 | if username: 180 | user = User.objects.get(username=username) 181 | things = user.wish_things.all() 182 | serializer = ListThingSerializer(things, many=True) 183 | return APIResponse(code=0, msg='操作成功', data=serializer.data) 184 | else: 185 | return APIResponse(code=1, msg='username不能为空') 186 | 187 | except Exception as e: 188 | utils.log_error(request, '操作失败' + str(e)) 189 | return APIResponse(code=1, msg='获取心愿单失败') 190 | 191 | 192 | @api_view(['POST']) 193 | def addCollectUser(request): 194 | try: 195 | username = request.GET.get('username', None) 196 | thingId = request.GET.get('thingId', None) 197 | 198 | if username and thingId: 199 | user = User.objects.get(username=username) 200 | thing = Thing.objects.get(pk=thingId) 201 | 202 | if user not in thing.collect.all(): 203 | thing.collect.add(user) 204 | thing.collect_count += 1 205 | thing.save() 206 | 207 | except Thing.DoesNotExist: 208 | utils.log_error(request, '操作失败') 209 | return APIResponse(code=1, msg='操作失败') 210 | 211 | serializer = DetailThingSerializer(thing) 212 | return APIResponse(code=0, msg='操作成功', data=serializer.data) 213 | 214 | 215 | @api_view(['POST']) 216 | def removeCollectUser(request): 217 | try: 218 | username = request.GET.get('username', None) 219 | thingId = request.GET.get('thingId', None) 220 | 221 | if username and thingId: 222 | user = User.objects.get(username=username) 223 | thing = Thing.objects.get(pk=thingId) 224 | 225 | if user in thing.collect.all(): 226 | thing.collect.remove(user) 227 | thing.collect_count -= 1 228 | thing.save() 229 | 230 | except Thing.DoesNotExist: 231 | utils.log_error(request, '操作失败') 232 | return APIResponse(code=1, msg='操作失败') 233 | 234 | return APIResponse(code=0, msg='操作成功') 235 | 236 | 237 | @api_view(['GET']) 238 | def getCollectThingList(request): 239 | try: 240 | username = request.GET.get('username', None) 241 | if username: 242 | user = User.objects.get(username=username) 243 | things = user.collect_things.all() 244 | serializer = ListThingSerializer(things, many=True) 245 | return APIResponse(code=0, msg='操作成功', data=serializer.data) 246 | else: 247 | return APIResponse(code=1, msg='username不能为空') 248 | 249 | except Exception as e: 250 | utils.log_error(request, '操作失败' + str(e)) 251 | return APIResponse(code=1, msg='获取收藏失败') 252 | 253 | 254 | @api_view(['GET']) 255 | def list_user_thing_api(request): 256 | if request.method == 'GET': 257 | user = request.GET.get("user", None) 258 | 259 | if user: 260 | things = Thing.objects.filter(user=user) 261 | serializer = ListThingSerializer(things, many=True) 262 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 263 | else: 264 | return APIResponse(code=1, msg='user不能为空') 265 | 266 | 267 | @api_view(['POST']) 268 | def create(request): 269 | data = request.data.copy() 270 | data['status'] = '1' 271 | serializer = ThingSerializer(data=data) 272 | if serializer.is_valid(): 273 | serializer.save() 274 | return APIResponse(code=0, msg='创建成功', data=serializer.data) 275 | else: 276 | print(serializer.errors) 277 | utils.log_error(request, '参数错误') 278 | 279 | return APIResponse(code=1, msg='创建失败') 280 | 281 | 282 | @api_view(['POST']) 283 | def update(request): 284 | try: 285 | pk = request.GET.get('id', -1) 286 | thing = Thing.objects.get(pk=pk) 287 | except Thing.DoesNotExist: 288 | return APIResponse(code=1, msg='对象不存在') 289 | 290 | serializer = UpdateThingSerializer(thing, data=request.data) 291 | if serializer.is_valid(): 292 | serializer.save() 293 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 294 | else: 295 | print(serializer.errors) 296 | utils.log_error(request, '参数错误') 297 | 298 | return APIResponse(code=1, msg='更新失败') 299 | -------------------------------------------------------------------------------- /server/myapp/views/index/user.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | import datetime 3 | 4 | from rest_framework.decorators import api_view, authentication_classes, throttle_classes 5 | 6 | from myapp import utils 7 | from myapp.auth.MyRateThrottle import UserRateThrottle 8 | from myapp.auth.authentication import TokenAuthtication 9 | from myapp.handler import APIResponse 10 | from myapp.models import User 11 | from myapp.serializers import UserSerializer, LoginLogSerializer 12 | from myapp.utils import md5value 13 | 14 | 15 | def make_login_log(request): 16 | try: 17 | username = request.data['username'] 18 | data = { 19 | "username": username, 20 | "ip": utils.get_ip(request), 21 | "ua": utils.get_ua(request) 22 | } 23 | serializer = LoginLogSerializer(data=data) 24 | if serializer.is_valid(): 25 | serializer.save() 26 | else: 27 | print(serializer.errors) 28 | except Exception as e: 29 | print(e) 30 | 31 | 32 | @api_view(['POST']) 33 | def login(request): 34 | username = request.data['username'] 35 | password = utils.md5value(request.data['password']) 36 | 37 | users = User.objects.filter(username=username, password=password) 38 | if len(users) > 0: 39 | user = users[0] 40 | 41 | if user.role in ['1', '3']: 42 | return APIResponse(code=1, msg='该帐号为后台管理员帐号') 43 | 44 | if user.status == '1': 45 | return APIResponse(code=1, msg='该帐号已禁用') 46 | 47 | data = { 48 | 'username': username, 49 | 'password': password, 50 | 'token': md5value(username) # 生成令牌 51 | } 52 | serializer = UserSerializer(user, data=data) 53 | if serializer.is_valid(): 54 | serializer.save() 55 | make_login_log(request) 56 | return APIResponse(code=0, msg='登录成功', data=serializer.data) 57 | else: 58 | print(serializer.errors) 59 | 60 | return APIResponse(code=1, msg='用户名或密码错误') 61 | 62 | 63 | @api_view(['POST']) 64 | def register(request): 65 | print(request.data) 66 | username = request.data.get('username', None) 67 | password = request.data.get('password', None) 68 | repassword = request.data.get('repassword', None) 69 | if not username or not password or not repassword: 70 | return APIResponse(code=1, msg='用户名或密码不能为空') 71 | if password != repassword: 72 | return APIResponse(code=1, msg='密码不一致') 73 | users = User.objects.filter(username=username) 74 | if len(users) > 0: 75 | return APIResponse(code=1, msg='该用户名已存在') 76 | 77 | data = { 78 | 'username': username, 79 | 'password': password, 80 | 'role': 2, # 角色2 81 | 'status': 0, 82 | } 83 | data.update({'password': utils.md5value(request.data['password'])}) 84 | serializer = UserSerializer(data=data) 85 | if serializer.is_valid(): 86 | serializer.save() 87 | return APIResponse(code=0, msg='创建成功', data=serializer.data) 88 | else: 89 | print(serializer.errors) 90 | 91 | return APIResponse(code=1, msg='创建失败') 92 | 93 | 94 | @api_view(['GET']) 95 | def info(request): 96 | if request.method == 'GET': 97 | pk = request.GET.get('id', -1) 98 | user = User.objects.get(pk=pk) 99 | serializer = UserSerializer(user) 100 | return APIResponse(code=0, msg='查询成功', data=serializer.data) 101 | 102 | 103 | @api_view(['POST']) 104 | @throttle_classes([UserRateThrottle]) 105 | @authentication_classes([TokenAuthtication]) 106 | def update(request): 107 | try: 108 | pk = request.GET.get('id', -1) 109 | user = User.objects.get(pk=pk) 110 | except User.DoesNotExist: 111 | return APIResponse(code=1, msg='对象不存在') 112 | 113 | content_length = int(request.META.get('CONTENT_LENGTH', 0)) 114 | if content_length > 2 * 1024 * 1024: 115 | return APIResponse(code=1, msg='文件太大') 116 | 117 | data = request.data.copy() 118 | if 'username' in data.keys(): 119 | del data['username'] 120 | if 'password' in data.keys(): 121 | del data['password'] 122 | if 'role' in data.keys(): 123 | del data['role'] 124 | serializer = UserSerializer(user, data=data) 125 | print(serializer.is_valid()) 126 | if serializer.is_valid(): 127 | serializer.save() 128 | return APIResponse(code=0, msg='更新成功', data=serializer.data) 129 | else: 130 | print(serializer.errors) 131 | 132 | return APIResponse(code=1, msg='更新失败') 133 | 134 | 135 | @api_view(['POST']) 136 | @authentication_classes([TokenAuthtication]) 137 | def updatePwd(request): 138 | 139 | try: 140 | pk = request.GET.get('id', -1) 141 | user = User.objects.get(pk=pk) 142 | except User.DoesNotExist: 143 | return APIResponse(code=1, msg='对象不存在') 144 | 145 | print(user.role) 146 | if user.role != '2': 147 | return APIResponse(code=1, msg='参数非法') 148 | 149 | password = request.data.get('password', None) 150 | newPassword1 = request.data.get('newPassword1', None) 151 | newPassword2 = request.data.get('newPassword2', None) 152 | 153 | if not password or not newPassword1 or not newPassword2: 154 | return APIResponse(code=1, msg='不能为空') 155 | 156 | if user.password != utils.md5value(password): 157 | return APIResponse(code=1, msg='原密码不正确') 158 | 159 | if newPassword1 != newPassword2: 160 | return APIResponse(code=1, msg='两次密码不一致') 161 | 162 | data = request.data.copy() 163 | data.update({'password': utils.md5value(newPassword1)}) 164 | serializer = UserSerializer(user, data=data) 165 | if serializer.is_valid(): 166 | serializer.save() 167 | return APIResponse(code=0, msg='更新成功', data=serializer.data) 168 | else: 169 | print(serializer.errors) 170 | 171 | return APIResponse(code=1, msg='更新失败') -------------------------------------------------------------------------------- /server/readme.md: -------------------------------------------------------------------------------- 1 | ### 后端部署步骤 2 | 3 | > 部署过程中,如遇问题可咨询作者:lengqin1024(微信) 4 | 5 | 1. 安装mysql数据库,启动服务 6 | 2. 打开cmd命令行,进入mysql,并新建数据库 7 | ``` 8 | mysql -u root -p 9 | CREATE DATABASE IF NOT EXISTS python_one DEFAULT CHARSET utf8 COLLATE utf8_general_ci; 10 | ``` 11 | 3. 恢复sql数据 12 | ``` 13 | use xxx 14 | source xxxx.sql 15 | ``` 16 | 4. 修改settings.py中的配置信息 17 | 5. 复制资源,将upload文件夹复制到server目录下 18 | 6. 安装python 3.8 19 | 7. 安装依赖包 20 | ``` 21 | pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple 22 | ``` 23 | 运行项目 24 | ``` 25 | python manage.py runserver 26 | ``` 27 | 7. 后期维护改动 28 | 29 | 将修改的py文件覆盖服务器的py文件即可,重启django 30 | 31 | ### 删除数据库 32 | 33 | drop database if exists xxx; 34 | 35 | ### 创建数据库 36 | 37 | CREATE DATABASE IF NOT EXISTS xxx DEFAULT CHARSET utf8 COLLATE utf8_general_ci; 38 | 39 | 40 | ### 迁移数据库表 41 | 42 | ``` 43 | python manage.py makemigrations; 44 | 45 | python manage.py migrate; 46 | 47 | python manage.py makemigrations myapp; 48 | 49 | python manage.py migrate myapp; 50 | ``` 51 | 52 | 53 | ### 协同过滤 54 | 55 | 第一步:保存记录,经过detail接口,保存浏览记录,并记录score分数。 56 | 57 | 第二步:推荐方法,经utils中的get_recommend()调用CF算法向用户推荐。 58 | 59 | 60 | ### 跨域配置 61 | 62 | django-cors-headers 63 | 64 | ### 多对多技术参考 65 | 66 | https://www.cnblogs.com/SunshineKimi/p/14140900.html 67 | 68 | ### 二级分类设计 69 | https://blog.csdn.net/weixin_47971206/article/details/124199978 70 | 71 | ### 常见问题 72 | 73 | 多对多的查询可通过related_name别名查询 74 | join查询 75 | ForeignKey的时候字段会自动加_id后缀 76 | 学习SerializerMethodField 77 | 跨域配置 django-cors-headers 78 | 数据库备份命令: 79 | mysqldump -u root -p --databases 数据库名称 > xxx.sql 80 | 数据库还原命令: 81 | source D:/xxx/xxx/shop.sql; 82 | 创建管理员命令: 83 | insert into b_user(username,password,role,status) values('admin111',md5('admin111'),1,'0'); 84 | 85 | 接口请求频次限制 86 | 87 | 88 | ### 登录接口 89 | 90 | 调login -> 生成token 91 | 92 | ### 注意 93 | 94 | update接口的时候,如果model里面存在多对多字段,则需要设置explode 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==3.2.11 2 | PyMySQL==1.0.2 3 | djangorestframework==3.13.0 4 | django-cors-headers==3.13.0 5 | Pillow==9.1.1 6 | psutil==5.9.4 -------------------------------------------------------------------------------- /server/server/__init__.py: -------------------------------------------------------------------------------- 1 | import pymysql 2 | pymysql.install_as_MySQLdb() 3 | 4 | print("===============install pymysql==============") -------------------------------------------------------------------------------- /server/server/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for server project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /server/server/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for server project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.1.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.1/ref/settings/ 11 | """ 12 | import os 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | # BASE_DIR = Path(__file__).resolve().parent.parent 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'django-insecure-sz@madp0ifx!b)^lg_g!f+5s*w7w_=sjgq-k+erzb%x42$^r!d' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ['*'] 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = [ 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 'rest_framework', 40 | 'corsheaders', # 跨域 41 | 'myapp' 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'corsheaders.middleware.CorsMiddleware', # 跨域配置 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.csrf.CsrfViewMiddleware', 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | 'myapp.middlewares.LogMiddleware.OpLogs' 54 | ] 55 | 56 | CORS_ORIGIN_ALLOW_ALL = True # 允许跨域 57 | 58 | ROOT_URLCONF = 'server.urls' 59 | 60 | TEMPLATES = [ 61 | { 62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 63 | 'DIRS': [], 64 | 'APP_DIRS': True, 65 | 'OPTIONS': { 66 | 'context_processors': [ 67 | 'django.template.context_processors.debug', 68 | 'django.template.context_processors.request', 69 | 'django.contrib.auth.context_processors.auth', 70 | 'django.contrib.messages.context_processors.messages', 71 | ], 72 | }, 73 | }, 74 | ] 75 | 76 | WSGI_APPLICATION = 'server.wsgi.application' 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 80 | 81 | DATABASES = { 82 | 'default': { 83 | 'ENGINE': 'django.db.backends.mysql', 84 | 'NAME': 'python_movie', 85 | 'USER': 'root', 86 | 'PASSWORD': '4643830', 87 | 'HOST': '127.0.0.1', 88 | 'PORT': '3306', 89 | 'OPTIONS': { 90 | "init_command": "SET foreign_key_checks = 0;", 91 | } 92 | } 93 | } 94 | 95 | # Password validation 96 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 97 | 98 | AUTH_PASSWORD_VALIDATORS = [ 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 107 | }, 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 110 | }, 111 | ] 112 | 113 | # Internationalization 114 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 115 | 116 | 117 | LANGUAGE_CODE = 'zh-hans' 118 | 119 | # 时区 120 | TIME_ZONE = 'Asia/Shanghai' 121 | 122 | USE_I18N = True 123 | 124 | USE_L10N = True 125 | 126 | USE_TZ = False 127 | 128 | # 日期时间格式 129 | DATE_FORMAT = 'Y-m-d' 130 | DATETIME_FORMAT = 'Y-m-d H:i:s' 131 | 132 | # 上传文件路径 133 | # 并在urls.py配置+static 134 | MEDIA_ROOT = os.path.join(BASE_DIR, 'upload/') 135 | MEDIA_URL = '/upload/' 136 | 137 | # Static files (CSS, JavaScript, Images) 138 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 139 | 140 | STATIC_URL = 'static/' 141 | 142 | # Default primary key field type 143 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 144 | 145 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 146 | 147 | # 跨域配置 148 | CORS_ALLOW_CREDENTIALS = True 149 | CORS_ALLOW_ALL_ORIGINS = True 150 | CORS_ALLOW_HEADERS = '*' 151 | -------------------------------------------------------------------------------- /server/server/urls.py: -------------------------------------------------------------------------------- 1 | """server URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls.static import static 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | 20 | from server import settings 21 | 22 | urlpatterns = [ 23 | path('admin/', admin.site.urls), 24 | path('myapp/', include('myapp.urls')), 25 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 26 | -------------------------------------------------------------------------------- /server/server/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for server project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /server/upload/ad/1717248427687.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/ad/1717248427687.jpeg -------------------------------------------------------------------------------- /server/upload/ad/1718280455795.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/ad/1718280455795.jpeg -------------------------------------------------------------------------------- /server/upload/ad/1718280462895.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/ad/1718280462895.jpeg -------------------------------------------------------------------------------- /server/upload/avatar/1716017756768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/avatar/1716017756768.png -------------------------------------------------------------------------------- /server/upload/avatar/1716017945368.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/avatar/1716017945368.jpeg -------------------------------------------------------------------------------- /server/upload/avatar/1716017953831.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/avatar/1716017953831.png -------------------------------------------------------------------------------- /server/upload/avatar/1716018100946.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/avatar/1716018100946.png -------------------------------------------------------------------------------- /server/upload/avatar/1716018106970.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/avatar/1716018106970.jpeg -------------------------------------------------------------------------------- /server/upload/avatar/1717249502324.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/avatar/1717249502324.jpeg -------------------------------------------------------------------------------- /server/upload/avatar/1718280917105.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/avatar/1718280917105.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1716043254213.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1716043254213.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1716083189595.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1716083189595.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1716083199860.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1716083199860.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1716083209737.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1716083209737.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1716083281670.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1716083281670.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1716083301393.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1716083301393.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1716087863555.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1716087863555.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1717249309482.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1717249309482.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1717249816837.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1717249816837.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1717297292698.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1717297292698.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1717297298858.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1717297298858.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1717297305179.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1717297305179.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1717297311519.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1717297311519.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1717297318288.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1717297318288.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1717297652697.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1717297652697.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1718195752664.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1718195752664.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1718197771289.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1718197771289.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1718198003099.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1718198003099.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1718198097347.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1718198097347.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1718198791310.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1718198791310.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1718198803762.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1718198803762.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1718198812032.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1718198812032.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1718198819617.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1718198819617.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1718198828791.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1718198828791.jpeg -------------------------------------------------------------------------------- /server/upload/cover/1718198852834.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/cover/1718198852834.jpeg -------------------------------------------------------------------------------- /server/upload/img/111.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/img/111.jpg -------------------------------------------------------------------------------- /server/upload/img/222.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/img/222.jpg -------------------------------------------------------------------------------- /server/upload/img/333.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/img/333.jpg -------------------------------------------------------------------------------- /server/upload/img/444.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/img/444.jpg -------------------------------------------------------------------------------- /server/upload/img/Wechat.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/img/Wechat.jpeg -------------------------------------------------------------------------------- /server/upload/img/weixin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net936/python_movie/a5b3fd1d8c445b0954cce89860cfe279ce91b0e4/server/upload/img/weixin.png -------------------------------------------------------------------------------- /web/.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | *.sh 3 | node_modules 4 | *.md 5 | *.woff 6 | *.ttf 7 | .vscode 8 | .idea 9 | dist 10 | /public 11 | /build 12 | /docs 13 | .husky 14 | .local 15 | /bin 16 | Dockerfile 17 | -------------------------------------------------------------------------------- /web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | es6: true, 7 | }, 8 | parser: 'vue-eslint-parser', 9 | parserOptions: { 10 | parser: '@typescript-eslint/parser', 11 | ecmaVersion: 2020, 12 | sourceType: 'module', 13 | jsxPragma: 'React', 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | }, 18 | extends: ['plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended'], 19 | rules: { 20 | 'vue/script-setup-uses-vars': 'error', 21 | '@typescript-eslint/ban-ts-ignore': 'off', 22 | '@typescript-eslint/explicit-function-return-type': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | '@typescript-eslint/no-var-requires': 'off', 25 | '@typescript-eslint/no-empty-function': 'off', 26 | 'vue/custom-event-name-casing': 'off', 27 | 'no-use-before-define': 'off', 28 | '@typescript-eslint/no-use-before-define': 'off', 29 | '@typescript-eslint/ban-ts-comment': 'off', 30 | '@typescript-eslint/ban-types': 'off', 31 | '@typescript-eslint/no-non-null-assertion': 'off', 32 | '@typescript-eslint/explicit-module-boundary-types': 'off', 33 | '@typescript-eslint/no-unused-vars': 'off', 34 | 'no-unused-vars': 'off', 35 | 'space-before-function-paren': 'off', 36 | 37 | 'vue/attributes-order': 'off', 38 | 'vue/one-component-per-file': 'off', 39 | 'vue/html-closing-bracket-newline': 'off', 40 | 'vue/max-attributes-per-line': 'off', 41 | 'vue/multiline-html-element-content-newline': 'off', 42 | 'vue/singleline-html-element-content-newline': 'off', 43 | 'vue/attribute-hyphenation': 'off', 44 | 'vue/require-default-prop': 'off', 45 | 'vue/require-explicit-emits': 'off', 46 | 'vue/html-self-closing': [ 47 | 'error', 48 | { 49 | html: { 50 | void: 'always', 51 | normal: 'never', 52 | component: 'always', 53 | }, 54 | svg: 'always', 55 | math: 'always', 56 | }, 57 | ], 58 | 'vue/multi-word-component-names': 'off', 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | .local 6 | .history 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | .eslintcache 11 | .github 12 | .husky 13 | .vscode 14 | 15 | # Log files 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | pnpm-debug.log* 20 | pnpm-lock.yaml* 21 | 22 | # Editor directories and files 23 | .idea 24 | # .vscode 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | ./packages 31 | ./history 32 | -------------------------------------------------------------------------------- /web/.stylelintignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /public/* 3 | public/* 4 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | ### 学习文档 2 | 3 | #### 运行步骤 4 | 5 | npm run dev 6 | 7 | #### 部署步骤 8 | 9 | 1. 修改constants.ts中的BASE_URL 10 | 2. 修改源码客服 11 | 3. vite build 12 | 4. 将dist部署到nginx 13 | 14 | 15 | #### 配置解释 16 | 17 | 1. env.development 开发环境配置 18 | 2. eslintrc.js 代码规范化提示 19 | 3. vite.config.js vite 开发服务器配置 20 | 21 | #### 常见问题 22 | 23 | ##### node下载地址 24 | https://nodejs.org/dist/v18.20.2/node-v18.20.2-x64.msi 25 | 26 | ##### antd的css引入方式 27 | 在index.html里面引入的cdn 28 | 29 | ##### cdn 30 | https://cdn.jsdelivr.net/npm/ant-design-vue@3.2.20/dist/ 31 | https://cdn.staticfile.org/ant-design-vue/3.2.20/antd.min.css 32 | 33 | #### public文件夹内容在build后会自动打到dist中 34 | -------------------------------------------------------------------------------- /web/build/constant.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name Config 3 | * @description 项目配置 4 | */ 5 | 6 | // 本地服务端口 7 | export const VITE_PORT = 8080; 8 | 9 | // 包依赖分析 10 | export const ANALYSIS = true; 11 | 12 | // 代码压缩 13 | export const COMPRESSION = true; 14 | -------------------------------------------------------------------------------- /web/build/vite/plugins/autoImport.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name AutoImportDeps 3 | * @description 按需加载,自动引入 4 | */ 5 | import AutoImport from 'unplugin-auto-import/vite'; 6 | // import { AntDesignVueResolver} from 'unplugin-vue-components/resolvers'; 7 | 8 | export const AutoImportDeps = () => { 9 | return AutoImport({ 10 | dts: 'types/auto-imports.d.ts', 11 | imports: [ 12 | 'vue', 13 | 'pinia', 14 | 'vue-router', 15 | { 16 | '@vueuse/core': [], 17 | }, 18 | { 19 | 'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'], 20 | }, 21 | ], 22 | resolvers: [ 23 | // AntDesignVueResolver(), 24 | ], 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /web/build/vite/plugins/component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name AutoRegistryComponents 3 | * @description 按需加载,自动引入组件 4 | */ 5 | import Components from 'unplugin-vue-components/vite'; 6 | import { 7 | ElementPlusResolver, 8 | VueUseComponentsResolver, 9 | AntDesignVueResolver, 10 | TDesignResolver, 11 | NaiveUiResolver, 12 | } from 'unplugin-vue-components/resolvers'; 13 | export const AutoRegistryComponents = () => { 14 | return Components({ 15 | dirs: ['src/components'], 16 | extensions: ['vue'], 17 | deep: true, 18 | dts: 'types/components.d.ts', 19 | directoryAsNamespace: false, 20 | globalNamespaces: [], 21 | directives: true, 22 | importPathTransform: (v) => v, 23 | allowOverrides: false, 24 | include: [/\.vue$/, /\.vue\?vue/], 25 | exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/], 26 | resolvers: [ 27 | ElementPlusResolver(), 28 | VueUseComponentsResolver(), 29 | AntDesignVueResolver(), 30 | TDesignResolver({ 31 | library: 'vue-next', 32 | }), 33 | NaiveUiResolver(), 34 | ], 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /web/build/vite/plugins/compress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name ConfigCompressPlugin 3 | * @description 开启.gz压缩 4 | */ 5 | import viteCompression from 'vite-plugin-compression'; 6 | import { COMPRESSION } from '../../constant'; 7 | 8 | export const ConfigCompressPlugin = () => { 9 | if (COMPRESSION) { 10 | return viteCompression({ 11 | verbose: true, // 默认即可 12 | disable: false, //开启压缩(不禁用),默认即可 13 | deleteOriginFile: false, //删除源文件 14 | threshold: 10240, //压缩前最小文件大小 15 | algorithm: 'gzip', //压缩算法 16 | ext: '.gz', //文件类型 17 | }); 18 | } 19 | return []; 20 | }; 21 | -------------------------------------------------------------------------------- /web/build/vite/plugins/imagemin.ts: -------------------------------------------------------------------------------- 1 | import viteImagemin from 'vite-plugin-imagemin'; 2 | 3 | export function ConfigImageminPlugin() { 4 | const plugin = viteImagemin({ 5 | gifsicle: { 6 | optimizationLevel: 7, 7 | interlaced: false, 8 | }, 9 | mozjpeg: { 10 | quality: 20, 11 | }, 12 | optipng: { 13 | optimizationLevel: 7, 14 | }, 15 | pngquant: { 16 | quality: [0.8, 0.9], 17 | speed: 4, 18 | }, 19 | svgo: { 20 | plugins: [ 21 | { 22 | name: 'removeViewBox', 23 | }, 24 | { 25 | name: 'removeEmptyAttrs', 26 | active: false, 27 | }, 28 | ], 29 | }, 30 | }); 31 | return plugin; 32 | } 33 | -------------------------------------------------------------------------------- /web/build/vite/plugins/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name createVitePlugins 3 | * @description 封装plugins数组统一调用 4 | */ 5 | import { PluginOption } from 'vite'; 6 | import vue from '@vitejs/plugin-vue'; 7 | import vueJsx from '@vitejs/plugin-vue-jsx'; 8 | import { AutoImportDeps } from './autoImport'; 9 | import { ConfigCompressPlugin } from './compress'; 10 | import { ConfigRestartPlugin } from './restart'; 11 | import { ConfigProgressPlugin } from './progress'; 12 | import { ConfigVisualizerConfig } from './visualizer'; 13 | 14 | export function createVitePlugins(isBuild: boolean) { 15 | const vitePlugins = [ 16 | // vue支持 17 | vue(), 18 | // JSX支持 19 | vueJsx(), 20 | // setup语法糖组件名支持 21 | // vueSetupExtend(), 22 | // 提供https证书 23 | // VitePluginCertificate({ 24 | // source: 'coding', 25 | // }) as PluginOption, 26 | ]; 27 | 28 | // 自动按需引入组件 29 | // vitePlugins.push(AutoRegistryComponents()); 30 | 31 | // 自动按需引入依赖 32 | vitePlugins.push(AutoImportDeps()); 33 | 34 | // 自动生成路由 35 | // vitePlugins.push(ConfigPagesPlugin()); 36 | 37 | // 开启.gz压缩 rollup-plugin-gzip 38 | vitePlugins.push(ConfigCompressPlugin()); 39 | 40 | // 监听配置文件改动重启 41 | vitePlugins.push(ConfigRestartPlugin()); 42 | 43 | // 构建时显示进度条 44 | vitePlugins.push(ConfigProgressPlugin()); 45 | 46 | // 构建时显示进度条 47 | vitePlugins.push(ConfigVisualizerConfig()); 48 | 49 | return vitePlugins; 50 | } 51 | -------------------------------------------------------------------------------- /web/build/vite/plugins/progress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name ConfigProgressPlugin 3 | * @description 构建显示进度条 4 | */ 5 | 6 | import progress from 'vite-plugin-progress'; 7 | export const ConfigProgressPlugin = () => { 8 | return progress(); 9 | }; 10 | -------------------------------------------------------------------------------- /web/build/vite/plugins/restart.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name ConfigRestartPlugin 3 | * @description 监听配置文件修改自动重启Vite 4 | */ 5 | import ViteRestart from 'vite-plugin-restart'; 6 | export const ConfigRestartPlugin = () => { 7 | return ViteRestart({ 8 | restart: ['*.config.[jt]s', '**/config/*.[jt]s'], 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /web/build/vite/plugins/unocss.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name ConfigUnocssPlugin 3 | * @description 监听配置文件修改自动重启Vite 4 | */ 5 | 6 | // Unocss 7 | import Unocss from 'unocss/vite'; 8 | 9 | export const ConfigUnocssPlugin = () => { 10 | return Unocss(); 11 | }; 12 | -------------------------------------------------------------------------------- /web/build/vite/plugins/visualizer.ts: -------------------------------------------------------------------------------- 1 | import visualizer from 'rollup-plugin-visualizer'; 2 | import { ANALYSIS } from '../../constant'; 3 | 4 | export function ConfigVisualizerConfig() { 5 | if (ANALYSIS) { 6 | return visualizer({ 7 | filename: 'dist/report.html', 8 | open: true, 9 | gzipSize: true, 10 | emitFile: false 11 | }); 12 | } 13 | return []; 14 | } 15 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |