├── .gitignore ├── .idea ├── Django_Store.iml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── README.md ├── front_end_pc ├── cart.html ├── css │ ├── main.css │ └── reset.css ├── goods_judge.html ├── images │ ├── QQ-weixin.png │ ├── adv01.jpg │ ├── adv02.jpg │ ├── banner01.jpg │ ├── banner02.jpg │ ├── banner03.jpg │ ├── bar_code.jpg │ ├── cat.jpg │ ├── clock.jpeg │ ├── clock.jpg │ ├── down.png │ ├── edit.png │ ├── find-password.png │ ├── goods │ │ ├── goods001.jpg │ │ ├── goods002.jpg │ │ ├── goods003.jpg │ │ ├── goods004.jpg │ │ ├── goods005.jpg │ │ ├── goods006.jpg │ │ ├── goods007.jpg │ │ ├── goods008.jpg │ │ └── goods009.jpg │ ├── goods_450.jpg │ ├── icons.png │ ├── icons02.png │ ├── interval_line.png │ ├── left_bg.jpg │ ├── login_banner.png │ ├── logo.png │ ├── logo02.png │ ├── pay_icons.png │ ├── pic_code.jpg │ ├── register_banner.png │ ├── selected.png │ ├── shine.png │ ├── shop_cart.png │ ├── slide01.jpg │ ├── slide02.jpg │ ├── slide03.jpg │ ├── slide04.jpg │ ├── stars.png │ ├── success.png │ └── time_count_bg.png ├── index.html ├── js │ ├── axios-0.18.0.min.js │ ├── cart.js │ ├── detail.js │ ├── goods_judge.js │ ├── host.js │ ├── index.js │ ├── jquery-1.12.4.min.js │ ├── list.js │ ├── login.js │ ├── oauth_callback.js │ ├── order_success.js │ ├── place_order.js │ ├── register.js │ ├── search.js │ ├── slide.js │ ├── user_center_info.js │ ├── user_center_pass.js │ ├── user_center_site.js │ └── vue-2.5.16.js ├── list.html ├── login.html ├── oauth_callback.html ├── order_success.html ├── pay_success.html ├── place_order.html ├── register.html ├── search.html ├── success_verify_email.html ├── user_center_info.html ├── user_center_order.html ├── user_center_pass.html └── user_center_site.html ├── requirements.txt └── tb_store ├── __init__.py ├── celery_tasks ├── __init__.py ├── config.py ├── email │ ├── __init__.py │ └── tasks.py ├── html │ ├── __init__.py │ └── tasks.py ├── main.py └── sms │ ├── __init__.py │ ├── tasks.py │ └── yuntongxun │ ├── CCPRestSDK.py │ ├── __init__.py │ ├── sms.py │ └── xmltojson.py ├── manage.py ├── scripts ├── areas.sql ├── import_areas_data_to_db.sh ├── regenerate_detail_html.py └── regenerate_index_html.py └── tb_store ├── __init__.py ├── apps ├── __init__.py ├── areas │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── carts │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── constants.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── contents │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── crons.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── goods │ ├── __init__.py │ ├── admin.py │ ├── adminx.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── search_indexes.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── oauth │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── constants.py │ ├── exceptions.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── orders │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── payment │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── keys │ │ ├── alipay_public_key.pem │ │ ├── app_private_key.pem │ │ └── app_public_key.pem │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── users │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── constants.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py └── verifications │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── constants.py │ ├── migrations │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── libs ├── __init__.py └── captcha │ ├── __init__.py │ ├── captcha.py │ └── fonts │ ├── Arial.ttf │ ├── Georgia.ttf │ └── actionj.ttf ├── settings ├── __init__.py └── dev.py ├── templates ├── detail.html ├── index.html └── search │ └── indexes │ └── goods │ └── sku_text.txt ├── urls.py ├── utils ├── __init__.py ├── db_router.py ├── exceptions.py ├── fastdfs │ ├── client.conf │ └── fdfs_storage.py ├── models.py ├── pagination.py └── yuntongxun │ ├── CCPRestSDK.py │ ├── __init__.py │ ├── sms.py │ └── xmltojson.py └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | .idea -------------------------------------------------------------------------------- /.idea/Django_Store.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django_Store 2 | 本项目基于Django1.11.11等来开发一个购物商城Web程序,本项目集成注册,登录,购物,购物车,评论,搜索,第三方qq登录,微信登录,手机号登录,支付宝支付等功能 3 | 4 | 5 | ## 重点内容有: 6 | 7 | - Redis实现购物车记录存储 8 | - Redis实现最近浏览记录存储 9 | - 发送手机短信验证码以Celery异步操作实现 10 | - 发送注册邮件以Celery异步操作实现 11 | - 网站优化之首页动态页面静态化——以Celery异步操作实现 12 | - 网站优化之首页缓存——Redis存储 13 | - 分布式存储系统FastDFS存储网站商品图片——自定义存储器类 14 | - 商品搜索框架`Elasticsearch` 15 | - 订单并发库存问题之悲观锁与乐观锁 16 | - 自定义管理器实现快速查询数据 17 | - 采用Django内置的认证系统进行登录校验——自定义用户类、校验装饰器 18 | - session基于Redis存储 19 | - 支付宝接口 20 | 21 | 22 | ## 项目展示: 23 | ![](https://raw.githubusercontent.com/Sjj1024/image-all/master/Django_store/%E9%A6%96%E9%A1%B5.png) 24 | 25 | ![](https://raw.githubusercontent.com/Sjj1024/image-all/master/Django_store/%E6%B3%A8%E5%86%8C%E9%A1%B5%E9%9D%A2.png) 26 | 27 | ![](https://raw.githubusercontent.com/Sjj1024/image-all/master/Django_store/%E7%99%BB%E5%BD%95%E9%A1%B5%E9%9D%A2.png) 28 | 29 | ![](https://raw.githubusercontent.com/Sjj1024/image-all/master/Django_store/%E5%88%97%E8%A1%A8%E9%A1%B5%E9%9D%A2.png) 30 | 31 | ![](https://raw.githubusercontent.com/Sjj1024/image-all/master/Django_store/%E4%B8%AA%E4%BA%BA%E4%BF%A1%E6%81%AF%E9%A1%B5.png) 32 | 33 | ![](https://raw.githubusercontent.com/Sjj1024/image-all/master/Django_store/QQ%E7%99%BB%E5%BD%95%E9%A1%B5%E9%9D%A2.png) 34 | 35 | ![](https://raw.githubusercontent.com/Sjj1024/image-all/master/Django_store/%E6%94%AF%E4%BB%98%E9%A1%B5%E9%9D%A2.png) 36 | 37 | ![](https://raw.githubusercontent.com/Sjj1024/image-all/master/Django_store/%E7%A1%AE%E8%AE%A4%E8%AE%A2%E5%8D%95%E9%A1%B5.png) 38 | -------------------------------------------------------------------------------- /front_end_pc/cart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 美多商城-购物车 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
欢迎来到美多商城!
17 |
18 | 23 | 28 | 36 |
37 |
38 |
39 | 40 | 48 | 49 |
全部商品{{total_count}}
50 | 57 | 72 | 73 | 79 | 80 | 93 |
94 | 95 | 96 | -------------------------------------------------------------------------------- /front_end_pc/css/reset.css: -------------------------------------------------------------------------------- 1 | /* 把标签默认的间距设为0 */ 2 | body,ul,ol,p,h1,h2,h3,h4,h5,h6,dl,dd,select,input,textarea,form{margin:0;padding:0} 3 | 4 | /* 让h标签文字大小继承body的文字设置 */ 5 | h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;} 6 | 7 | /* 去掉列表默认的图标 */ 8 | ul,ol{list-style:none;} 9 | 10 | /* 去掉em默认的斜体 */ 11 | em{font-style: normal;} 12 | 13 | /* 去掉a标签默认的下划线 */ 14 | a{text-decoration:none;} 15 | 16 | 17 | /* 去掉加链接时产生的框线 */ 18 | img{border:0;} 19 | 20 | /* 清除浮动 */ 21 | .clearfix:before,.clearfix:after{content:"";display:table} 22 | .clearfix:after{clear:both;} 23 | .clearfix{zoom:1} 24 | 25 | /* 浮动 */ 26 | .fl{float:left} 27 | .fr{float:right} -------------------------------------------------------------------------------- /front_end_pc/goods_judge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 美多商城-商品评价 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
欢迎来到美多商城!
15 |
16 | 19 | 24 | 32 |
33 |
34 |
35 | 36 | 44 | 45 |
46 |
47 | 54 |
55 |
56 |
57 | 58 |
59 | 60 | 61 | 62 | 63 | 64 |
65 |
{{display_score}}分
66 |
67 |
68 | 69 | 70 |
71 |
72 | 73 | 匿名评价 74 |
75 |
76 |
77 | 78 | 79 | 92 |
93 | 94 | 95 | -------------------------------------------------------------------------------- /front_end_pc/images/QQ-weixin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/QQ-weixin.png -------------------------------------------------------------------------------- /front_end_pc/images/adv01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/adv01.jpg -------------------------------------------------------------------------------- /front_end_pc/images/adv02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/adv02.jpg -------------------------------------------------------------------------------- /front_end_pc/images/banner01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/banner01.jpg -------------------------------------------------------------------------------- /front_end_pc/images/banner02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/banner02.jpg -------------------------------------------------------------------------------- /front_end_pc/images/banner03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/banner03.jpg -------------------------------------------------------------------------------- /front_end_pc/images/bar_code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/bar_code.jpg -------------------------------------------------------------------------------- /front_end_pc/images/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/cat.jpg -------------------------------------------------------------------------------- /front_end_pc/images/clock.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/clock.jpeg -------------------------------------------------------------------------------- /front_end_pc/images/clock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/clock.jpg -------------------------------------------------------------------------------- /front_end_pc/images/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/down.png -------------------------------------------------------------------------------- /front_end_pc/images/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/edit.png -------------------------------------------------------------------------------- /front_end_pc/images/find-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/find-password.png -------------------------------------------------------------------------------- /front_end_pc/images/goods/goods001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/goods/goods001.jpg -------------------------------------------------------------------------------- /front_end_pc/images/goods/goods002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/goods/goods002.jpg -------------------------------------------------------------------------------- /front_end_pc/images/goods/goods003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/goods/goods003.jpg -------------------------------------------------------------------------------- /front_end_pc/images/goods/goods004.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/goods/goods004.jpg -------------------------------------------------------------------------------- /front_end_pc/images/goods/goods005.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/goods/goods005.jpg -------------------------------------------------------------------------------- /front_end_pc/images/goods/goods006.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/goods/goods006.jpg -------------------------------------------------------------------------------- /front_end_pc/images/goods/goods007.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/goods/goods007.jpg -------------------------------------------------------------------------------- /front_end_pc/images/goods/goods008.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/goods/goods008.jpg -------------------------------------------------------------------------------- /front_end_pc/images/goods/goods009.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/goods/goods009.jpg -------------------------------------------------------------------------------- /front_end_pc/images/goods_450.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/goods_450.jpg -------------------------------------------------------------------------------- /front_end_pc/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/icons.png -------------------------------------------------------------------------------- /front_end_pc/images/icons02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/icons02.png -------------------------------------------------------------------------------- /front_end_pc/images/interval_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/interval_line.png -------------------------------------------------------------------------------- /front_end_pc/images/left_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/left_bg.jpg -------------------------------------------------------------------------------- /front_end_pc/images/login_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/login_banner.png -------------------------------------------------------------------------------- /front_end_pc/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/logo.png -------------------------------------------------------------------------------- /front_end_pc/images/logo02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/logo02.png -------------------------------------------------------------------------------- /front_end_pc/images/pay_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/pay_icons.png -------------------------------------------------------------------------------- /front_end_pc/images/pic_code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/pic_code.jpg -------------------------------------------------------------------------------- /front_end_pc/images/register_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/register_banner.png -------------------------------------------------------------------------------- /front_end_pc/images/selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/selected.png -------------------------------------------------------------------------------- /front_end_pc/images/shine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/shine.png -------------------------------------------------------------------------------- /front_end_pc/images/shop_cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/shop_cart.png -------------------------------------------------------------------------------- /front_end_pc/images/slide01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/slide01.jpg -------------------------------------------------------------------------------- /front_end_pc/images/slide02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/slide02.jpg -------------------------------------------------------------------------------- /front_end_pc/images/slide03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/slide03.jpg -------------------------------------------------------------------------------- /front_end_pc/images/slide04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/slide04.jpg -------------------------------------------------------------------------------- /front_end_pc/images/stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/stars.png -------------------------------------------------------------------------------- /front_end_pc/images/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/success.png -------------------------------------------------------------------------------- /front_end_pc/images/time_count_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/front_end_pc/images/time_count_bg.png -------------------------------------------------------------------------------- /front_end_pc/js/detail.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | delimiters: ['[[', ']]'], 4 | data: { 5 | host, 6 | username: sessionStorage.username || localStorage.username, 7 | user_id: sessionStorage.user_id || localStorage.user_id, 8 | token: sessionStorage.token || localStorage.token, 9 | tab_content: { 10 | detail: true, 11 | pack: false, 12 | comment: false, 13 | service: false 14 | }, 15 | sku_id: '', 16 | sku_count: 1, 17 | sku_price: price, 18 | cart_total_count: 0, // 购物车总数量 19 | cart: [], // 购物车数据 20 | hots: [], // 热销商品 21 | cat: cat, // 商品类别 22 | comments: [], // 评论信息 23 | score_classes: { 24 | 1: 'stars_one', 25 | 2: 'stars_two', 26 | 3: 'stars_three', 27 | 4: 'stars_four', 28 | 5: 'stars_five', 29 | } 30 | }, 31 | computed: { 32 | sku_amount: function(){ 33 | return (this.sku_price * this.sku_count).toFixed(2); 34 | } 35 | }, 36 | mounted: function(){ 37 | // 添加用户浏览历史记录 38 | this.get_sku_id(); 39 | if (this.user_id) { 40 | axios.post(this.host+'/browse_histories/', { 41 | sku_id: this.sku_id 42 | }, { 43 | headers: { 44 | 'Authorization': 'JWT ' + this.token 45 | } 46 | }) 47 | } 48 | this.get_cart(); 49 | this.get_hot_goods(); 50 | this.get_comments(); 51 | }, 52 | methods: { 53 | // 退出 54 | logout: function(){ 55 | sessionStorage.clear(); 56 | localStorage.clear(); 57 | location.href = '/login.html'; 58 | }, 59 | // 控制页面标签页展示 60 | on_tab_content: function(name){ 61 | this.tab_content = { 62 | detail: false, 63 | pack: false, 64 | comment: false, 65 | service: false 66 | }; 67 | this.tab_content[name] = true; 68 | }, 69 | // 从路径中提取sku_id 70 | get_sku_id: function(){ 71 | var re = /^\/goods\/(\d+).html$/; 72 | this.sku_id = document.location.pathname.match(re)[1]; 73 | }, 74 | // 减小数值 75 | on_minus: function(){ 76 | if (this.sku_count > 1) { 77 | this.sku_count--; 78 | } 79 | }, 80 | // 添加购物车 81 | add_cart: function(){ 82 | axios.post(this.host+'/cart/', { 83 | sku_id: parseInt(this.sku_id), 84 | count: this.sku_count 85 | }, { 86 | headers: { 87 | 'Authorization': 'JWT ' + this.token 88 | }, 89 | responseType: 'json', 90 | withCredentials: true 91 | }) 92 | .then(response => { 93 | alert('添加购物车成功'); 94 | this.cart_total_count += response.data.count; 95 | }) 96 | .catch(error => { 97 | if ('non_field_errors' in error.response.data) { 98 | alert(error.response.data.non_field_errors[0]); 99 | } else { 100 | alert('添加购物车失败'); 101 | } 102 | console.log(error.response.data); 103 | }) 104 | }, 105 | // 获取购物车数据 106 | get_cart: function(){ 107 | 108 | }, 109 | // 获取热销商品数据 110 | get_hot_goods: function(){ 111 | 112 | }, 113 | // 获取商品评价信息 114 | get_comments: function(){ 115 | 116 | } 117 | } 118 | }); -------------------------------------------------------------------------------- /front_end_pc/js/goods_judge.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | data: { 4 | score: 0, 5 | display_score: 0, 6 | final_score: 0 7 | }, 8 | methods: { 9 | on_stars_mouseover: function(score){ 10 | this.score = score; 11 | this.display_score = score * 20; 12 | }, 13 | on_stars_mouseout: function() { 14 | this.score = this.final_score; 15 | this.display_score = this.final_score * 20; 16 | }, 17 | on_stars_click: function(score) { 18 | this.final_score = score; 19 | }, 20 | } 21 | }); -------------------------------------------------------------------------------- /front_end_pc/js/host.js: -------------------------------------------------------------------------------- 1 | var host = 'http://api.store.site:8000'; -------------------------------------------------------------------------------- /front_end_pc/js/index.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | delimiters: ['[[', ']]'], 4 | data: { 5 | host, 6 | username: sessionStorage.username || localStorage.username, 7 | user_id: sessionStorage.user_id || localStorage.user_id, 8 | token: sessionStorage.token || localStorage.token, 9 | cart_total_count: 0, // 购物车总数量 10 | cart: [], // 购物车数据, 11 | f1_tab: 1, // 1F 标签页控制 12 | f2_tab: 1, // 2F 标签页控制 13 | f3_tab: 1, // 3F 标签页控制 14 | }, 15 | mounted: function(){ 16 | this.get_cart(); 17 | }, 18 | methods: { 19 | // 退出 20 | logout: function(){ 21 | sessionStorage.clear(); 22 | localStorage.clear(); 23 | location.href = '/login.html'; 24 | }, 25 | // 获取购物车数据 26 | get_cart: function(){ 27 | 28 | } 29 | } 30 | }); -------------------------------------------------------------------------------- /front_end_pc/js/login.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | data: { 4 | host: host, 5 | error_username: false, 6 | error_pwd: false, 7 | error_pwd_message: '请填写密码', 8 | username: '', 9 | password: '', 10 | remember: false 11 | }, 12 | methods: { 13 | // 获取url路径参数 14 | get_query_string: function(name){ 15 | var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); 16 | var r = window.location.search.substr(1).match(reg); 17 | if (r != null) { 18 | return decodeURI(r[2]); 19 | } 20 | return null; 21 | }, 22 | // 检查数据 23 | check_username: function(){ 24 | if (!this.username) { 25 | this.error_username = true; 26 | } else { 27 | this.error_username = false; 28 | } 29 | }, 30 | check_pwd: function(){ 31 | if (!this.password) { 32 | this.error_pwd_message = '请填写密码'; 33 | this.error_pwd = true; 34 | } else { 35 | this.error_pwd = false; 36 | } 37 | }, 38 | // 表单提交 39 | on_submit: function(){ 40 | this.check_username(); 41 | this.check_pwd(); 42 | 43 | if (this.error_username == false && this.error_pwd == false) { 44 | axios.post(this.host+'/authorizations/', { 45 | username: this.username, 46 | password: this.password 47 | }, { 48 | responseType: 'json', 49 | withCredentials: true 50 | }) 51 | .then(response => { 52 | // 使用浏览器本地存储保存token 53 | if (this.remember) { 54 | // 记住登录 55 | sessionStorage.clear(); 56 | localStorage.token = response.data.token; 57 | localStorage.user_id = response.data.user_id; 58 | localStorage.username = response.data.username; 59 | } else { 60 | // 未记住登录 61 | localStorage.clear(); 62 | sessionStorage.token = response.data.token; 63 | sessionStorage.user_id = response.data.user_id; 64 | sessionStorage.username = response.data.username; 65 | } 66 | 67 | // 跳转页面 68 | var return_url = this.get_query_string('next'); 69 | if (!return_url) { 70 | return_url = '/index.html'; 71 | } 72 | location.href = return_url; 73 | }) 74 | .catch(error => { 75 | if (error.response.status == 400) { 76 | this.error_pwd_message = '用户名或密码错误'; 77 | } else { 78 | this.error_pwd_message = '服务器错误'; 79 | } 80 | this.error_pwd = true; 81 | }) 82 | } 83 | }, 84 | // qq登录 85 | qq_login: function(){ 86 | var next = this.get_query_string('next') || '/'; 87 | axios.get(this.host + '/oauth/qq/authorization/?next=' + next, { 88 | responseType: 'json' 89 | }) 90 | .then(response => { 91 | location.href = response.data.login_url; 92 | }) 93 | .catch(error => { 94 | console.log(error.response.data); 95 | }) 96 | } 97 | } 98 | }); -------------------------------------------------------------------------------- /front_end_pc/js/order_success.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | data: { 4 | host, 5 | username: sessionStorage.username || localStorage.username, 6 | user_id: sessionStorage.user_id || localStorage.user_id, 7 | token: sessionStorage.token || localStorage.token, 8 | order_id: '', 9 | amount: 0, 10 | pay_method: '', 11 | }, 12 | computed: { 13 | operate: function(){ 14 | if (this.pay_method==1){ 15 | return '继续购物'; 16 | } else { 17 | return '去支付'; 18 | } 19 | } 20 | }, 21 | mounted: function(){ 22 | this.order_id = this.get_query_string('order_id'); 23 | this.amount = this.get_query_string('amount'); 24 | this.pay_method = this.get_query_string('pay'); 25 | }, 26 | methods: { 27 | // 退出 28 | logout: function(){ 29 | sessionStorage.clear(); 30 | localStorage.clear(); 31 | location.href = '/login.html'; 32 | }, 33 | // 获取url路径参数 34 | get_query_string: function(name){ 35 | var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); 36 | var r = window.location.search.substr(1).match(reg); 37 | if (r != null) { 38 | return decodeURI(r[2]); 39 | } 40 | return null; 41 | }, 42 | // 去支付 43 | next_operate: function(){ 44 | if (this.pay_method == 1) { 45 | location.href = '/index.html'; 46 | } else { 47 | // 发起支付 48 | axios.get(this.host+'/orders/'+this.order_id+'/payment/', { 49 | headers: { 50 | 'Authorization': 'JWT ' + this.token 51 | }, 52 | responseType: 'json' 53 | }) 54 | .then(response => { 55 | // 跳转到支付宝支付 56 | location.href = response.data.alipay_url; 57 | }) 58 | .catch(error => { 59 | console.log(error.response.data); 60 | }) 61 | } 62 | } 63 | } 64 | }); -------------------------------------------------------------------------------- /front_end_pc/js/place_order.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | data: { 4 | host, 5 | username: sessionStorage.username || localStorage.username, 6 | user_id, 7 | token, 8 | skus: [], 9 | freight: 0, // 运费 10 | total_count: 0, 11 | total_amount: 0, 12 | payment_amount: 0, 13 | order_submitting: false, // 正在提交订单标志 14 | pay_method: 1, // 支付方式, 15 | nowsite:0, // 默认地址 16 | addresses: [] 17 | }, 18 | mounted: function(){ 19 | // 获取地址信息 20 | axios.get(this.host + '/addresses/', { 21 | headers: { 22 | 'Authorization': 'JWT ' + this.token 23 | }, 24 | responseType: 'json' 25 | }) 26 | .then(response => { 27 | this.addresses = response.data.addresses; 28 | this.nowsite = response.data.default_address_id; 29 | }) 30 | .catch(error => { 31 | status = error.response.status; 32 | if (status == 401 || status == 403) { 33 | location.href = 'login.html?next=/user_center_site.html'; 34 | } else { 35 | alert(error.response.data.detail); 36 | } 37 | }) 38 | // 获取结算商品信息 39 | axios.get(this.host+'/orders/settlement/', { 40 | headers: { 41 | 'Authorization': 'JWT ' + this.token 42 | }, 43 | responseType: 'json' 44 | }) 45 | .then(response => { 46 | this.skus = response.data.skus; 47 | this.freight = response.data.freight; 48 | this.total_count = 0; 49 | this.total_amount = 0; 50 | for(var i=0; i { 61 | if (error.response.status == 401){ 62 | location.href = '/login.html?next=/cart.html'; 63 | } else{ 64 | console.log(error.response.data); 65 | } 66 | }) 67 | }, 68 | methods: { 69 | // 退出 70 | logout: function(){ 71 | sessionStorage.clear(); 72 | localStorage.clear(); 73 | location.href = '/login.html'; 74 | }, 75 | // 提交订单 76 | on_order_submit: function(){ 77 | if (this.order_submitting == false){ 78 | this.order_submitting = true; 79 | axios.post(this.host+'/orders/', { 80 | address: this.nowsite, 81 | pay_method: this.pay_method 82 | }, { 83 | headers: { 84 | 'Authorization': 'JWT ' + this.token 85 | }, 86 | responseType: 'json' 87 | }) 88 | .then(response => { 89 | location.href = '/order_success.html?order_id='+response.data.order_id 90 | +'&amount='+this.payment_amount 91 | +'&pay='+this.pay_method; 92 | }) 93 | .catch(error => { 94 | this.order_submitting = false; 95 | alert(error.response.data[0]); 96 | }) 97 | } 98 | } 99 | } 100 | }); -------------------------------------------------------------------------------- /front_end_pc/js/search.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | delimiters: ['[[', ']]'], // 修改vue模板符号,防止与django冲突 4 | data: { 5 | host: host, 6 | username: sessionStorage.username || localStorage.username, 7 | user_id: sessionStorage.user_id || localStorage.user_id, 8 | token: sessionStorage.token || localStorage.token, 9 | page: 1, // 当前页数 10 | page_size: 6, // 每页数量 11 | count: 0, // 总数量 12 | skus: [], // 数据 13 | query: '', // 查询关键字 14 | cart_total_count: 0, // 购物车总数量 15 | cart: [], // 购物车数据 16 | }, 17 | computed: { 18 | total_page: function(){ // 总页数 19 | return Math.ceil(this.count/this.page_size); 20 | }, 21 | next: function(){ // 下一页 22 | if (this.page >= this.total_page) { 23 | return 0; 24 | } else { 25 | return this.page + 1; 26 | } 27 | }, 28 | previous: function(){ // 上一页 29 | if (this.page <= 0 ) { 30 | return 0; 31 | } else { 32 | return this.page - 1; 33 | } 34 | }, 35 | page_nums: function(){ // 页码 36 | // 分页页数显示计算 37 | // 1.如果总页数<=5 38 | // 2.如果当前页是前3页 39 | // 3.如果当前页是后3页, 40 | // 4.既不是前3页,也不是后3页 41 | var nums = []; 42 | if (this.total_page <= 5) { 43 | for (var i=1; i<=this.total_page; i++){ 44 | nums.push(i); 45 | } 46 | } else if (this.page <= 3) { 47 | nums = [1, 2, 3, 4, 5]; 48 | } else if (this.total_page - this.page <= 2) { 49 | for (var i=this.total_page; i>this.total_page-5; i--) { 50 | nums.push(i); 51 | } 52 | } else { 53 | for (var i=this.page-2; i { 91 | this.count = response.data.count; 92 | this.skus = response.data.results; 93 | for(var i=0; i { 98 | console.log(error.response.data); 99 | }) 100 | }, 101 | // 点击页数 102 | on_page: function(num){ 103 | if (num != this.page){ 104 | this.page = num; 105 | this.get_search_result(); 106 | } 107 | }, 108 | // 获取购物车数据 109 | get_cart: function(){ 110 | 111 | } 112 | } 113 | }); -------------------------------------------------------------------------------- /front_end_pc/js/slide.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $slides = $('.slide li'); 3 | var len = $slides.length; 4 | var nowli = 0; 5 | var prevli = 0; 6 | var $prev = $('.prev'); 7 | var $next = $('.next'); 8 | var timer = null; 9 | $slides.not(':first').css({'opacity':0}); 10 | 11 | $slides.each(function(index, el) { 12 | var $li = $('
  • '); 13 | if(index==0) 14 | { 15 | $li.addClass('active'); 16 | } 17 | $li.appendTo($('.points')); 18 | }); 19 | 20 | $points = $('.points li'); 21 | timer = setInterval(autoplay,4000); 22 | 23 | $('.pos_center_con').mouseenter(function() { 24 | clearInterval(timer); 25 | }); 26 | 27 | $('.pos_center_con').mouseleave(function() { 28 | timer = setInterval(autoplay,4000); 29 | }); 30 | 31 | function autoplay(){ 32 | nowli++; 33 | move(); 34 | $points.eq(nowli).addClass('active').siblings().removeClass('active'); 35 | }; 36 | 37 | $points.click(function() { 38 | nowli = $(this).index(); 39 | $(this).addClass('active').siblings().removeClass('active'); 40 | move(); 41 | }); 42 | $prev.click(function() { 43 | nowli--; 44 | move(); 45 | $points.eq(nowli).addClass('active').siblings().removeClass('active'); 46 | }); 47 | $next.click(function() { 48 | nowli++; 49 | move(); 50 | $points.eq(nowli).addClass('active').siblings().removeClass('active'); 51 | 52 | }); 53 | 54 | function move(){ 55 | if(nowli==prevli) 56 | { 57 | return; 58 | } 59 | 60 | if(nowli<0) 61 | { 62 | nowli=len-1; 63 | prevli = 0 64 | $slides.eq(nowli).animate({'opacity':1},800); 65 | $slides.eq(prevli).animate({'opacity':0},800); 66 | prevli=nowli; 67 | return; 68 | } 69 | 70 | if(nowli>len-1) 71 | { 72 | nowli = 0; 73 | prevli = len-1; 74 | $slides.eq(nowli).animate({'opacity':1},800); 75 | $slides.eq(prevli).animate({'opacity':0},800); 76 | prevli=nowli; 77 | return; 78 | } 79 | 80 | if(prevli { 28 | // 加载用户数据 29 | this.user_id = response.data.id; 30 | this.username = response.data.username; 31 | this.mobile = response.data.mobile; 32 | this.email = response.data.email; 33 | this.email_active = response.data.email_active; 34 | // 补充请求浏览历史 35 | axios.get(this.host + '/browse_histories/', { 36 | headers: { 37 | 'Authorization': 'JWT ' + this.token 38 | }, 39 | responseType: 'json' 40 | }) 41 | .then(response => { 42 | this.histories = response.data; 43 | for(var i=0; i { 49 | if (error.response.status==401 || error.response.status==403) { 50 | location.href = '/login.html?next=/user_center_info.html'; 51 | } 52 | }); 53 | } else { 54 | location.href = '/login.html?next=/user_center_info.html'; 55 | } 56 | }, 57 | methods: { 58 | // 退出 59 | logout: function(){ 60 | sessionStorage.clear(); 61 | localStorage.clear(); 62 | location.href = '/login.html'; 63 | }, 64 | // 保存email 65 | save_email: function(){ 66 | var re = /^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$/; 67 | if(re.test(this.email)) { 68 | this.email_error = false; 69 | } else { 70 | this.email_error = true; 71 | return; 72 | } 73 | axios.put(this.host + '/email/', 74 | { email: this.email }, 75 | { 76 | headers: { 77 | 'Authorization': 'JWT ' + this.token 78 | }, 79 | responseType: 'json' 80 | }) 81 | .then(response => { 82 | this.set_email = false; 83 | this.send_email_btn_disabled = true; 84 | this.send_email_tip = '已发送验证邮件' 85 | }) 86 | .catch(error => { 87 | alert(error.data); 88 | }); 89 | } 90 | } 91 | }); -------------------------------------------------------------------------------- /front_end_pc/js/user_center_pass.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | data: { 4 | host, 5 | error_ipassword: false, 6 | error_password: false, 7 | error_check_password: false, 8 | user_id: sessionStorage.user_id || localStorage.user_id, 9 | token: sessionStorage.token || localStorage.token, 10 | username: '', 11 | ipassword:'', 12 | password:'', 13 | password2:'', 14 | }, 15 | mounted: function(){ 16 | // 判断用户的登录状态 17 | if (this.user_id && this.token) { 18 | axios.get(this.host + '/user/', { 19 | // 向后端传递JWT token的方法 20 | headers: { 21 | 'Authorization': 'JWT ' + this.token 22 | }, 23 | responseType: 'json', 24 | }) 25 | .then(response => { 26 | // 加载用户数据 27 | this.user_id = response.data.id; 28 | this.username = response.data.username; 29 | }) 30 | .catch(error => { 31 | if (error.response.status==401 || error.response.status==403) { 32 | location.href = '/login.html?next=/user_center_info.html'; 33 | } 34 | }); 35 | } else { 36 | location.href = '/login.html?next=/user_center_info.html'; 37 | } 38 | }, 39 | methods: { 40 | // 退出 41 | logout: function(){ 42 | sessionStorage.clear(); 43 | localStorage.clear(); 44 | location.href = '/login.html'; 45 | }, 46 | check_pwd: function (){ 47 | var len = this.password.length; 48 | if(len<8||len>20){ 49 | this.error_password = true; 50 | } else { 51 | this.error_password = false; 52 | } 53 | }, 54 | check_cpwd: function (){ 55 | if(this.password!=this.password2) { 56 | this.error_check_password = true; 57 | } else { 58 | this.error_check_password = false; 59 | } 60 | }, 61 | // 表单提交,修改密码方法 62 | on_submit: function(){ 63 | // this.check_username(); 64 | // this.check_ipwd(); 65 | this.check_pwd(); 66 | this.check_cpwd(); 67 | if (this.error_password == false && this.error_check_password == false) { 68 | axios.put(this.host+'/password/', 69 | { 70 | // 向后端传递JWT token的方法 71 | headers: { 72 | 'Authorization': 'JWT ' + this.token 73 | }, 74 | ipassword: this.ipassword, 75 | password: this.password, 76 | password2: this.password2, 77 | user_id:this.user_id, 78 | responseType: 'json', 79 | withCredentials: true 80 | }) 81 | .then(response => { 82 | // 弹出修改成功,跳转到登录页面 83 | alert("修改成功!"); 84 | return_url = '/login.html'; 85 | location.href = return_url; 86 | }) 87 | .catch(error => { 88 | if (error.response.status == 400) { 89 | this.error_pwd_message = '原始密码错误'; 90 | } else { 91 | this.error_pwd_message = '服务器错误'; 92 | } 93 | this.error_pwd = true; 94 | }) 95 | } 96 | }, 97 | 98 | } 99 | }); -------------------------------------------------------------------------------- /front_end_pc/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 美多商城-登录 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 49 | 50 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /front_end_pc/oauth_callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 美多商城-绑定用户 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 |
    请稍后...
    15 |
    16 |
    17 |
    18 | 19 |
    商品美 · 种类多 · 欢迎光临
    20 |
    21 |
    22 | 23 |
    24 |
    25 |

    绑定用户

    26 |
    27 |
    28 |
    29 |
      30 |
    • 31 | 32 | 33 | {{ error_phone_message }} 34 |
    • 35 |
    • 36 | 37 | 38 | 密码最少8位,最长20位 39 |
    • 40 |
    • 41 | 42 | 43 | 图形验证码 44 | {{ error_image_code_message }} 45 |
    • 46 |
    • 47 | 48 | 49 | {{ sms_code_tip }} 50 | {{ error_sms_code_message }} 51 |
    • 52 |
    • 53 | 54 |
    • 55 |
    56 |
    57 |
    58 |
    59 |
    60 | 61 | 74 |
    75 |
    76 | 77 | 78 | -------------------------------------------------------------------------------- /front_end_pc/order_success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 美多商城-订单提交成功 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 |
    21 |
    22 |
    23 |
    欢迎来到美多商城!
    24 |
    25 | 30 | 38 |
    39 |
    40 |
    41 | 42 | 50 | 51 | 52 | 53 |
    54 |
    55 |

    订单提交成功,订单总价¥{{amount}}

    56 |

    您的订单已成功生成,选择您想要的支付方式,订单号:{{order_id}}

    57 |

    您可以在用户中心中我的订单中查看该订单

    58 |
    59 |
    60 | 61 |
    62 | {{ operate }} 63 |
    64 | 65 | 78 |
    79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /front_end_pc/pay_success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 美多商城-支付成功 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 |
    15 |
    16 |
    欢迎来到美多商城!
    17 |
    18 | 26 |
    27 |
    28 |
    29 | 30 | 38 | 39 | 40 | 41 |
    42 |
    43 |

    订单支付成功

    44 |

    您的订单已成功支付,支付交易号:{{trade_id}}

    45 |

    您可以在用户中心中我的订单中查看该订单

    46 |
    47 |
    48 | 49 | 62 |
    63 | 83 | 84 | -------------------------------------------------------------------------------- /front_end_pc/place_order.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 美多商城-提交订单 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 |
    21 |
    22 |
    23 |
    欢迎来到美多商城!
    24 |
    25 | 30 | 38 |
    39 |
    40 |
    41 | 42 | 50 | 51 |

    确认收货地址

    52 | 53 |
    54 |
    55 |
    寄送到:
    56 |
    {{ address.province }} {{address.city}} {{ address.district }} {{ address.place }} ({{ address.receiver }} 收) {{ address.mobile }}
    57 |
    58 | 编辑收货地址 59 |
    60 | 61 |

    支付方式

    62 |
    63 |
    64 | 65 | 66 | 67 | 68 |
    69 |
    70 | 71 |

    商品列表

    72 | 73 |
    74 |
      75 |
    • 商品名称
    • 76 |
    • 商品价格
    • 77 |
    • 数量
    • 78 |
    • 小计
    • 79 |
    80 |
      81 |
    • {{index+1}}
    • 82 |
    • 83 |
    • {{ sku.name }}
    • 84 |
    • {{ sku.price }}元
    • 85 |
    • {{ sku.count }}
    • 86 |
    • {{ sku.amount }}元
    • 87 |
    88 |
    89 | 90 |

    总金额结算

    91 | 92 |
    93 |
    94 |
    {{ total_count }}件商品,总金额{{ total_amount }}元
    95 |
    运费:{{ freight }}元
    96 |
    实付款:{{ payment_amount }}元
    97 |
    98 |
    99 | 100 |
    101 | 提交订单 102 |
    103 | 104 | 117 |
    118 | 119 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /front_end_pc/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 美多商城-注册 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 |
    15 | 16 |
    商品美 · 种类多 · 欢迎光临
    17 |
    18 |
    19 | 20 |
    21 |
    22 |

    用户注册

    23 | 登录 24 |
    25 |
    26 |
    27 |
      28 |
    • 29 | 30 | 31 | {{ error_name_message }} 32 |
    • 33 |
    • 34 | 35 | 36 | 密码最少8位,最长20位 37 |
    • 38 |
    • 39 | 40 | 41 | 两次输入的密码不一致 42 |
    • 43 |
    • 44 | 45 | 46 | {{ error_phone_message }} 47 |
    • 48 |
    • 49 | 50 | 51 | 图形验证码 52 | {{ error_image_code_message }} 53 |
    • 54 |
    • 55 | 56 | 57 | {{ sms_code_tip }} 58 | {{ error_sms_code_message }} 59 |
    • 60 |
    • 61 | 62 | 63 | 请勾选同意 64 |
    • 65 |
    • 66 | 67 |
    • 68 |
    69 |
    70 |
    71 | 72 |
    73 | 74 |
    75 | 76 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /front_end_pc/success_verify_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 美多商城-邮箱验证成功 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 | 15 | |    邮箱验证成功 16 |
    17 | 18 |
    19 |
    恭喜您,邮箱验证成功!
    返回主页
    20 |
    链接已失效,验证失败,请重新验证!
    返回主页
    21 |
    22 | 23 | 36 | 54 | 55 | -------------------------------------------------------------------------------- /front_end_pc/user_center_info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 东京商城-用户中心 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 |
    21 |
    22 |
    23 |
    欢迎来到东京商城!
    24 |
    25 | 30 | 38 |
    39 |
    40 |
    41 | 42 | 50 | 51 |
    52 |
    53 |

    用户中心

    54 | 60 |
    61 |
    62 |
    63 |

    基本信息

    64 | 87 |
    88 | 89 |

    最近浏览

    90 |
    91 |
      92 |
    • 93 | 94 |

      {{sku.name}}

      95 |
      96 | ¥{{sku.price}} 97 | {{sku.comments}}评价 98 |
      99 |
    • 100 |
    101 |
    102 |
    103 |
    104 | 117 |
    118 | 119 | 120 | -------------------------------------------------------------------------------- /front_end_pc/user_center_order.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 美多商城-用户中心 6 | 7 | 8 | 9 | 10 |
    11 |
    12 |
    欢迎来到美多商城!
    13 |
    14 | 17 | 22 | 30 |
    31 |
    32 |
    33 | 34 | 42 | 43 |
    44 |
    45 |

    用户中心

    46 | 52 |
    53 |
    54 |

    全部订单

    55 |
      56 |
    • 2016-8-21 17:36:24
    • 57 |
    • 订单号:56872934
    • 58 |
    59 | 60 | 61 | 62 | 63 | 77 | 78 | 79 | 80 | 81 | 82 |
    64 |
      65 |
    • 66 |
    • 360手机 N6 Pro 全网通2688.00元
    • 67 |
    • 1
    • 68 |
    • 2688.00元
    • 69 |
    70 |
      71 |
    • 72 |
    • 360手机 N6 Pro 全网通2688.00元
    • 73 |
    • 1
    • 74 |
    • 2688.00元
    • 75 |
    76 |
    5276.00元支付宝去付款
    83 | 84 |
      85 |
    • 2016-8-21 17:36:24
    • 86 |
    • 订单号:56872934
    • 87 |
    88 | 89 | 90 | 91 | 105 | 106 | 107 | 108 | 109 | 110 |
    92 |
      93 |
    • 94 |
    • iphoneX N6 Pro 全网通7899.00元
    • 95 |
    • 1
    • 96 |
    • 7899.00元
    • 97 |
    98 |
      99 |
    • 100 |
    • iphoneX N6 Pro 全网通7899.00元
    • 101 |
    • 1
    • 102 |
    • 7899.00元
    • 103 |
    104 |
    15798.00元货到付款去评价
    111 | 112 |
    113 | <上一页 114 | 1 115 | 2 116 | 3 117 | 4 118 | 5 119 | 下一页> 120 |
    121 |
    122 |
    123 | 124 | 125 | 126 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /front_end_pc/user_center_pass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 东京商城-用户中心 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 |
    15 |
    16 |
    欢迎来到东京商城!
    17 |
    18 | 23 | 31 |
    32 |
    33 |
    34 | 35 | 43 | 44 |
    45 |
    46 |

    用户中心

    47 | 53 |
    54 |
    55 |
    56 |

    修改密码

    57 |
    58 |
    59 |
    60 | 61 | 62 | 原密码不正确,请重新输入 63 |
    64 |
    65 | 66 | 67 | 密码最少8位,最长20位 68 |
    69 |
    70 | 71 | 72 | 两次输入的密码不一致 73 |
    74 | 75 |
    76 |
    77 |
    78 | 79 |
    80 |
    81 |
    82 | 83 | 84 | 85 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | amqp==2.5.0 2 | backports.csv==1.0.7 3 | billiard==3.6.0.0 4 | celery==4.3.0 5 | certifi==2019.6.16 6 | chardet==3.0.4 7 | defusedxml==0.6.0 8 | diff-match-patch==20181111 9 | Django==1.11.11 10 | django-ckeditor==5.7.1 11 | django-cors-headers==2.2.0 12 | django-crispy-forms==1.7.2 13 | django-crontab==0.7.1 14 | django-formtools==2.1 15 | django-haystack==2.8.1 16 | django-import-export==1.2.0 17 | django-js-asset==1.2.2 18 | django-redis==4.10.0 19 | django-reversion==3.0.4 20 | djangorestframework==3.9.4 21 | djangorestframework-jwt==1.11.0 22 | drf-extensions==0.5.0 23 | drf-haystack==1.8.5 24 | elasticsearch==2.4.1 25 | et-xmlfile==1.0.1 26 | fdfs-client-py==1.2.6 27 | future==0.17.1 28 | httplib2==0.9.2 29 | idna==2.8 30 | itsdangerous==1.1.0 31 | jdcal==1.4.1 32 | kombu==4.6.3 33 | lxml==4.3.4 34 | mutagen==1.42.0 35 | odfpy==1.4.0 36 | openpyxl==2.6.2 37 | Pillow==6.0.0 38 | pycryptodomex==3.7.2 39 | PyJWT==1.7.1 40 | PyMySQL==0.9.3 41 | python-alipay-sdk==1.10.1 42 | python-dateutil==2.8.0 43 | pytz==2019.1 44 | PyYAML==5.1.1 45 | redis==3.2.1 46 | requests==2.22.0 47 | six==1.12.0 48 | tablib==0.13.0 49 | urllib3==1.25.3 50 | vine==1.3.0 51 | xadmin==0.6.1 52 | xlrd==1.2.0 53 | xlwt==1.3.0 54 | -------------------------------------------------------------------------------- /tb_store/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/__init__.py -------------------------------------------------------------------------------- /tb_store/celery_tasks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/celery_tasks/__init__.py -------------------------------------------------------------------------------- /tb_store/celery_tasks/config.py: -------------------------------------------------------------------------------- 1 | broker_url = "redis://127.0.0.1/15" -------------------------------------------------------------------------------- /tb_store/celery_tasks/email/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/celery_tasks/email/__init__.py -------------------------------------------------------------------------------- /tb_store/celery_tasks/email/tasks.py: -------------------------------------------------------------------------------- 1 | from celery_tasks.main import celery_app 2 | from django.core.mail import send_mail 3 | from django.conf import settings 4 | 5 | 6 | @celery_app.task(name="send_active_email") 7 | def send_verify_email(to_email, verify_url): 8 | """ 9 | 发送验证码邮件 10 | :return: 11 | """ 12 | subject = "淘宝商城邮箱验证" 13 | html_message = '

    尊敬的用户您好!

    ' \ 14 | '

    感谢您使用淘宝商城。

    ' \ 15 | '

    您的邮箱为:%s 。请点击此链接激活您的邮箱:

    ' \ 16 | '

    请点击链接以确认

    ' % (to_email, verify_url) 17 | send_mail(subject, "", settings.EMAIL_FROM, [to_email], html_message=html_message) 18 | print("邮箱发送成功") 19 | -------------------------------------------------------------------------------- /tb_store/celery_tasks/html/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/celery_tasks/html/__init__.py -------------------------------------------------------------------------------- /tb_store/celery_tasks/html/tasks.py: -------------------------------------------------------------------------------- 1 | from celery_tasks.main import celery_app 2 | from django.template import loader 3 | from django.conf import settings 4 | import os 5 | 6 | from goods.utils import get_categories 7 | from goods.models import SKU 8 | 9 | 10 | @celery_app.task(name='generate_static_sku_detail_html') 11 | def generate_static_sku_detail_html(sku_id): 12 | """ 13 | 生成静态商品详情页面 14 | :param sku_id: 商品sku id 15 | """ 16 | # 商品分类菜单 17 | categories = get_categories() 18 | 19 | # 获取当前sku的信息 20 | sku = SKU.objects.get(id=sku_id) 21 | sku.images = sku.skuimage_set.all() 22 | 23 | # 面包屑导航信息中的频道 24 | goods = sku.goods 25 | goods.channel = goods.category1.goodschannel_set.all()[0] 26 | 27 | # 构建当前商品的规格键 28 | # sku_key = [规格1参数id, 规格2参数id, 规格3参数id, ...] 29 | sku_specs = sku.skuspecification_set.order_by('spec_id') 30 | sku_key = [] 31 | for spec in sku_specs: 32 | sku_key.append(spec.option.id) 33 | 34 | # 获取当前商品的所有SKU 35 | skus = goods.sku_set.all() 36 | 37 | # 构建不同规格参数(选项)的sku字典 38 | # spec_sku_map = { 39 | # (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id, 40 | # (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id, 41 | # ... 42 | # } 43 | spec_sku_map = {} 44 | for s in skus: 45 | # 获取sku的规格参数 46 | s_specs = s.skuspecification_set.order_by('spec_id') 47 | # 用于形成规格参数-sku字典的键 48 | key = [] 49 | for spec in s_specs: 50 | key.append(spec.option.id) 51 | # 向规格参数-sku字典添加记录 52 | spec_sku_map[tuple(key)] = s.id 53 | 54 | # 获取当前商品的规格信息 55 | #specs = [ 56 | # { 57 | # 'name': '屏幕尺寸', 58 | # 'options': [ 59 | # {'value': '13.3寸', 'sku_id': xxx}, 60 | # {'value': '15.4寸', 'sku_id': xxx}, 61 | # ] 62 | # }, 63 | # { 64 | # 'name': '颜色', 65 | # 'options': [ 66 | # {'value': '银色', 'sku_id': xxx}, 67 | # {'value': '黑色', 'sku_id': xxx} 68 | # ] 69 | # }, 70 | # ... 71 | #] 72 | specs = goods.goodsspecification_set.order_by('id') 73 | # 若当前sku的规格信息不完整,则不再继续 74 | if len(sku_key) < len(specs): 75 | return 76 | for index, spec in enumerate(specs): 77 | # 复制当前sku的规格键 78 | key = sku_key[:] 79 | # 该规格的选项 80 | options = spec.specificationoption_set.all() 81 | for option in options: 82 | # 在规格参数sku字典中查找符合当前规格的sku 83 | key[index] = option.id 84 | option.sku_id = spec_sku_map.get(tuple(key)) 85 | 86 | spec.options = options 87 | 88 | # 渲染模板,生成静态html文件 89 | context = { 90 | 'categories': categories, 91 | 'goods': goods, 92 | 'specs': specs, 93 | 'sku': sku 94 | } 95 | 96 | template = loader.get_template('detail.html') 97 | html_text = template.render(context) 98 | file_path = os.path.join(settings.GENERATED_STATIC_HTML_FILES_DIR, 'goods/'+str(sku_id)+'.html') 99 | with open(file_path, 'w') as f: 100 | f.write(html_text) -------------------------------------------------------------------------------- /tb_store/celery_tasks/main.py: -------------------------------------------------------------------------------- 1 | from celery import Celery 2 | 3 | # 为celery使用django配置文件进行设置 4 | import os 5 | if not os.getenv('DJANGO_SETTINGS_MODULE'): 6 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tb_store.settings.dev' 7 | 8 | 9 | # 创建celery对象 10 | celery_app = Celery("store") 11 | 12 | # 导入celery配置 13 | celery_app.config_from_object("celery_tasks.config") 14 | 15 | # 导入任务 16 | celery_app.autodiscover_tasks(["celery_tasks.sms", "celery_tasks.email", "celery_tasks.html"]) -------------------------------------------------------------------------------- /tb_store/celery_tasks/sms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/celery_tasks/sms/__init__.py -------------------------------------------------------------------------------- /tb_store/celery_tasks/sms/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from celery_tasks.main import celery_app 4 | from .yuntongxun.sms import CCP 5 | 6 | logger = logging.getLogger("django") 7 | 8 | # 验证码短信模板 9 | SMS_CODE_TEMP_ID = 1 10 | 11 | @celery_app.task(name='send_sms_code') 12 | def send_sms_code(mobile, code, expires): 13 | """ 14 | 发送短信验证码 15 | :param mobile: 手机号 16 | :param code: 验证码 17 | :param expires: 有效期 18 | :return: None 19 | """ 20 | 21 | try: 22 | ccp = CCP() 23 | result = ccp.send_template_sms(mobile, [code, expires], SMS_CODE_TEMP_ID) 24 | except Exception as e: 25 | logger.error("发送验证码短信[异常][ mobile: %s, message: %s ]" % (mobile, e)) 26 | else: 27 | if result == 0: 28 | logger.info("发送验证码短信[正常][ mobile: %s ]" % mobile) 29 | else: 30 | # print(result) 31 | logger.warning("发送验证码短信[失败][ mobile: %s ]" % mobile) -------------------------------------------------------------------------------- /tb_store/celery_tasks/sms/yuntongxun/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/celery_tasks/sms/yuntongxun/__init__.py -------------------------------------------------------------------------------- /tb_store/celery_tasks/sms/yuntongxun/sms.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from .CCPRestSDK import REST 4 | 5 | # 说明:主账号,登陆云通讯网站后,可在"控制台-应用"中看到开发者主账号ACCOUNT SID 6 | _accountSid = '8a216da86b2bc78f016b361c93d8025f' 7 | 8 | # 说明:主账号Token,登陆云通讯网站后,可在控制台-应用中看到开发者主账号AUTH TOKEN 9 | _accountToken = '78637b84e5544705bda2aca3426c88f5' 10 | 11 | # 请使用管理控制台首页的APPID或自己创建应用的APPID 12 | _appId = '8a216da86b2bc78f016b361c943e0266' 13 | 14 | # 说明:请求地址,生产环境配置成app.cloopen.com 15 | _serverIP = 'app.cloopen.com' 16 | 17 | # 说明:请求端口 ,生产环境为8883 18 | _serverPort = "8883" 19 | 20 | # 说明:REST API版本号保持不变 21 | _softVersion = '2013-12-26' 22 | 23 | # 云通讯官方提供的发送短信代码实例 24 | # # 发送模板短信 25 | # # @param to 手机号码 26 | # # @param datas 内容数据 格式为数组 例如:{'12','34'},如不需替换请填 '' 27 | # # @param $tempId 模板Id 28 | # 29 | # def sendTemplateSMS(to, datas, tempId): 30 | # # 初始化REST SDK 31 | # rest = REST(serverIP, serverPort, softVersion) 32 | # rest.setAccount(accountSid, accountToken) 33 | # rest.setAppId(appId) 34 | # 35 | # result = rest.sendTemplateSMS(to, datas, tempId) 36 | # for k, v in result.iteritems(): 37 | # 38 | # if k == 'templateSMS': 39 | # for k, s in v.iteritems(): 40 | # print '%s:%s' % (k, s) 41 | # else: 42 | # print '%s:%s' % (k, v) 43 | 44 | 45 | class CCP(object): 46 | """发送短信的辅助类""" 47 | 48 | def __new__(cls, *args, **kwargs): 49 | # 判断是否存在类属性_instance,_instance是类CCP的唯一对象,即单例 50 | if not hasattr(CCP, "_instance"): 51 | cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs) 52 | cls._instance.rest = REST(_serverIP, _serverPort, _softVersion) 53 | cls._instance.rest.setAccount(_accountSid, _accountToken) 54 | cls._instance.rest.setAppId(_appId) 55 | return cls._instance 56 | 57 | def send_template_sms(self, to, datas, temp_id): 58 | """发送模板短信""" 59 | # @param to 手机号码 60 | # @param datas 内容数据 格式为数组 例如:{'12','34'},如不需替换请填 '' 61 | # @param temp_id 模板Id 62 | result = self.rest.sendTemplateSMS(to, datas, temp_id) 63 | # 如果云通讯发送短信成功,返回的字典数据result中statuCode字段的值为"000000" 64 | if result.get("statusCode") == "000000": 65 | # 返回0 表示发送短信成功 66 | return 0 67 | else: 68 | # 返回-1 表示发送失败 69 | return -1 70 | 71 | 72 | if __name__ == '__main__': 73 | ccp = CCP() 74 | # 注意: 测试的短信模板编号为1 75 | ccp.send_template_sms('15670339118', ['1234', 5], 1) 76 | -------------------------------------------------------------------------------- /tb_store/celery_tasks/sms/yuntongxun/xmltojson.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # python xml.etree.ElementTree 3 | 4 | import os 5 | import xml.etree.ElementTree as ET 6 | from xml.dom import minidom 7 | 8 | 9 | class xmltojson: 10 | # global var 11 | # show log 12 | SHOW_LOG = True 13 | # XML file 14 | XML_PATH = None 15 | a = {} 16 | m = [] 17 | 18 | def get_root(self, path): 19 | '''parse the XML file,and get the tree of the XML file 20 | finally,return the root element of the tree. 21 | if the XML file dose not exist,then print the information''' 22 | # if os.path.exists(path): 23 | # if SHOW_LOG: 24 | # print('start to parse the file : [{}]'.format(path)) 25 | tree = ET.fromstring(path) 26 | return tree 27 | # else: 28 | # print('the path [{}] dose not exist!'.format(path)) 29 | 30 | def get_element_tag(self, element): 31 | '''return the element tag if the element is not None.''' 32 | if element is not None: 33 | 34 | return element.tag 35 | else: 36 | print('the element is None!') 37 | 38 | def get_element_attrib(self, element): 39 | '''return the element attrib if the element is not None.''' 40 | if element is not None: 41 | 42 | return element.attrib 43 | else: 44 | print('the element is None!') 45 | 46 | def get_element_text(self, element): 47 | '''return the text of the element.''' 48 | if element is not None: 49 | return element.text 50 | else: 51 | print('the element is None!') 52 | 53 | def get_element_children(self, element): 54 | '''return the element children if the element is not None.''' 55 | if element is not None: 56 | 57 | return [c for c in element] 58 | else: 59 | print('the element is None!') 60 | 61 | def get_elements_tag(self, elements): 62 | '''return the list of tags of element's tag''' 63 | if elements is not None: 64 | tags = [] 65 | for e in elements: 66 | tags.append(e.tag) 67 | return tags 68 | else: 69 | print('the elements is None!') 70 | 71 | def get_elements_attrib(self, elements): 72 | '''return the list of attribs of element's attrib''' 73 | if elements is not None: 74 | attribs = [] 75 | for a in elements: 76 | attribs.append(a.attrib) 77 | return attribs 78 | else: 79 | print('the elements is None!') 80 | 81 | def get_elements_text(self, elements): 82 | '''return the dict of element''' 83 | if elements is not None: 84 | text = [] 85 | for t in elements: 86 | text.append(t.text) 87 | return dict(zip(self.get_elements_tag(elements), text)) 88 | else: 89 | print('the elements is None!') 90 | 91 | def main(self, xml): 92 | # root 93 | root = self.get_root(xml) 94 | 95 | # children 96 | children = self.get_element_children(root) 97 | 98 | children_tags = self.get_elements_tag(children) 99 | 100 | children_attribs = self.get_elements_attrib(children) 101 | 102 | i = 0 103 | 104 | # 获取二级元素的每一个子节点的名称和值 105 | for c in children: 106 | p = 0 107 | c_children = self.get_element_children(c) 108 | dict_text = self.get_elements_text(c_children) 109 | if dict_text: 110 | # print (children_tags[i]) 111 | if children_tags[i] == 'TemplateSMS': 112 | self.a['templateSMS'] = dict_text 113 | else: 114 | if children_tags[i] == 'SubAccount': 115 | k = 0 116 | 117 | for x in children: 118 | if children_tags[k] == 'totalCount': 119 | self.m.append(dict_text) 120 | self.a['SubAccount'] = self.m 121 | p = 1 122 | k = k + 1 123 | if p == 0: 124 | self.a[children_tags[i]] = dict_text 125 | else: 126 | self.a[children_tags[i]] = dict_text 127 | 128 | 129 | else: 130 | self.a[children_tags[i]] = c.text 131 | i = i + 1 132 | return self.a 133 | 134 | def main2(self, xml): 135 | # root 136 | root = self.get_root(xml) 137 | 138 | # children 139 | children = self.get_element_children(root) 140 | 141 | children_tags = self.get_elements_tag(children) 142 | 143 | children_attribs = self.get_elements_attrib(children) 144 | 145 | i = 0 146 | 147 | # 获取二级元素的每一个子节点的名称和值 148 | for c in children: 149 | p = 0 150 | c_children = self.get_element_children(c) 151 | dict_text = self.get_elements_text(c_children) 152 | if dict_text: 153 | if children_tags[i] == 'TemplateSMS': 154 | k = 0 155 | 156 | for x in children: 157 | if children_tags[k] == 'totalCount': 158 | self.m.append(dict_text) 159 | self.a['TemplateSMS'] = self.m 160 | p = 1 161 | k = k + 1 162 | if p == 0: 163 | self.a[children_tags[i]] = dict_text 164 | else: 165 | self.a[children_tags[i]] = dict_text 166 | 167 | else: 168 | self.a[children_tags[i]] = c.text 169 | i = i + 1 170 | return self.a 171 | -------------------------------------------------------------------------------- /tb_store/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 启动redis和celery 3 | # redis-server /etc/redis/redis.conf 4 | # celery -A celery_tasks.main worker -l info 5 | # docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker 6 | # docker run -dti --network=host --name storage -e TRACKER_SERVER=192.168.11.68:22122 -v /var/fdfs/storage:/var/fdfs delron/fastdfs storage 7 | import os 8 | import sys 9 | 10 | 11 | if __name__ == "__main__": 12 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tb_store.settings.dev") 13 | try: 14 | from django.core.management import execute_from_command_line 15 | except ImportError: 16 | # The above import may fail for some other reason. Ensure that the 17 | # issue is really that Django is missing to avoid masking other 18 | # exceptions on Python 2. 19 | try: 20 | import django 21 | except ImportError: 22 | raise ImportError( 23 | "Couldn't import Django. Are you sure it's installed and " 24 | "available on your PYTHONPATH environment variable? Did you " 25 | "forget to activate a virtual environment?" 26 | ) 27 | raise 28 | execute_from_command_line(sys.argv) 29 | -------------------------------------------------------------------------------- /tb_store/scripts/import_areas_data_to_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mysql -uroot -pmysql tb_store < areas.sql -------------------------------------------------------------------------------- /tb_store/scripts/regenerate_detail_html.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | 功能:手动生成所有SKU的静态detail html文件 5 | 使用方法: 6 | ./regenerate_detail_html.py 7 | """ 8 | import sys 9 | sys.path.insert(0, '../') 10 | 11 | import os 12 | if not os.getenv('DJANGO_SETTINGS_MODULE'): 13 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tb_store.settings.dev' 14 | 15 | import django 16 | django.setup() 17 | 18 | from django.template import loader 19 | from django.conf import settings 20 | 21 | from goods.utils import get_categories 22 | from goods.models import SKU 23 | 24 | 25 | def generate_static_sku_detail_html(sku_id): 26 | """ 27 | 生成静态商品详情页面 28 | :param sku_id: 商品sku id 29 | """ 30 | # 商品分类菜单 31 | categories = get_categories() 32 | 33 | # 获取当前sku的信息 34 | sku = SKU.objects.get(id=sku_id) 35 | sku.images = sku.skuimage_set.all() 36 | 37 | # 面包屑导航信息中的频道 38 | goods = sku.goods 39 | goods.channel = goods.category1.goodschannel_set.all()[0] 40 | 41 | # 构建当前商品的规格键 42 | sku_specs = sku.skuspecification_set.order_by('spec_id') 43 | sku_key = [] 44 | for spec in sku_specs: 45 | sku_key.append(spec.option.id) 46 | 47 | # 获取当前商品的所有SKU 48 | skus = goods.sku_set.all() 49 | 50 | # 构建不同规格参数(选项)的sku字典 51 | # spec_sku_map = { 52 | # (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id, 53 | # (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id, 54 | # ... 55 | # } 56 | spec_sku_map = {} 57 | for s in skus: 58 | # 获取sku的规格参数 59 | s_specs = s.skuspecification_set.order_by('spec_id') 60 | # 用于形成规格参数-sku字典的键 61 | key = [] 62 | for spec in s_specs: 63 | key.append(spec.option.id) 64 | # 向规格参数-sku字典添加记录 65 | spec_sku_map[tuple(key)] = s.id 66 | 67 | # 获取当前商品的规格信息 68 | specs = goods.goodsspecification_set.order_by('id') 69 | # 若当前sku的规格信息不完整,则不再继续 70 | if len(sku_key) < len(specs): 71 | return 72 | for index, spec in enumerate(specs): 73 | # 复制当前sku的规格键 74 | key = sku_key[:] 75 | # 该规格的选项 76 | options = spec.specificationoption_set.all() 77 | for option in options: 78 | # 在规格参数sku字典中查找符合当前规格的sku 79 | key[index] = option.id 80 | option.sku_id = spec_sku_map.get(tuple(key)) 81 | 82 | spec.options = options 83 | 84 | # 渲染模板,生成静态html文件 85 | context = { 86 | 'categories': categories, 87 | 'goods': goods, 88 | 'specs': specs, 89 | 'sku': sku 90 | } 91 | 92 | template = loader.get_template('detail.html') 93 | html_text = template.render(context) 94 | file_path = os.path.join(settings.GENERATED_STATIC_HTML_FILES_DIR, 'goods/'+str(sku_id)+'.html') 95 | with open(file_path, 'w') as f: 96 | f.write(html_text) 97 | 98 | 99 | if __name__ == '__main__': 100 | skus = SKU.objects.all() 101 | for sku in skus: 102 | print(sku.id) 103 | generate_static_sku_detail_html(sku.id) -------------------------------------------------------------------------------- /tb_store/scripts/regenerate_index_html.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | 功能:手动生成所有SKU的静态detail html文件 5 | 使用方法: 6 | ./regenerate_index_html.py 7 | """ 8 | import sys 9 | sys.path.insert(0, '../') 10 | 11 | import os 12 | if not os.getenv('DJANGO_SETTINGS_MODULE'): 13 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tb_store.settings.dev' 14 | 15 | # 让django进行初始化设置 16 | import django 17 | django.setup() 18 | 19 | 20 | from contents.crons import generate_static_index_html 21 | 22 | 23 | if __name__ == '__main__': 24 | generate_static_index_html() -------------------------------------------------------------------------------- /tb_store/tb_store/__init__.py: -------------------------------------------------------------------------------- 1 | import pymysql 2 | 3 | pymysql.install_as_MySQLdb() -------------------------------------------------------------------------------- /tb_store/tb_store/apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/areas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/areas/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/areas/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/areas/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AreasConfig(AppConfig): 5 | name = 'areas' 6 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/areas/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/areas/migrations/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/areas/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | # Create your models here. 5 | from tb_store.utils.models import BaseModel 6 | from users.models import User 7 | 8 | 9 | class Area(models.Model): 10 | """ 11 | 收货地址模型类 12 | """ 13 | name = models.CharField(max_length=20, verbose_name='名称') 14 | parent = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='subs', null=True, blank=True, 15 | verbose_name='上级行政区划') 16 | 17 | class Meta: 18 | db_table = "tb_areas" 19 | verbose_name = "行政区划" 20 | verbose_name_plural = "行政区划" 21 | 22 | def __str__(self): 23 | return self.name 24 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/areas/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from .models import Area 4 | 5 | 6 | class AreaSerializer(serializers.ModelSerializer): 7 | """ 8 | 行政区域信息序列化器 9 | """ 10 | class Meta: 11 | model = Area 12 | fields = ("id", "name") 13 | 14 | 15 | class SubAreaSerializer(serializers.ModelSerializer): 16 | """ 17 | 子行政规划信息序列化器 18 | """ 19 | subs = AreaSerializer(many=True, read_only=True) 20 | 21 | class Meta: 22 | model = Area 23 | fields = ("id", "name", "subs") -------------------------------------------------------------------------------- /tb_store/tb_store/apps/areas/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/areas/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from rest_framework.routers import DefaultRouter 3 | 4 | from . import views 5 | 6 | urlpatterns = [ 7 | 8 | ] 9 | 10 | router = DefaultRouter() 11 | router.register(r'areas', views.AreasViewSet, base_name='areas') 12 | 13 | urlpatterns += router.urls 14 | 15 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/areas/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from rest_framework.viewsets import ReadOnlyModelViewSet 3 | 4 | from .models import Area 5 | from .serializers import AreaSerializer, SubAreaSerializer 6 | 7 | # Create your views here. 8 | 9 | 10 | class AreasViewSet(ReadOnlyModelViewSet): 11 | """ 12 | 行政区划信息 13 | """ 14 | pagination_class = None # 区划信息不分页 15 | 16 | def get_queryset(self): 17 | """ 18 | 提供数据集 19 | """ 20 | if self.action == 'list': 21 | return Area.objects.filter(parent=None) 22 | else: 23 | return Area.objects.all() 24 | 25 | def get_serializer_class(self): 26 | """ 27 | 提供序列化器 28 | """ 29 | if self.action == 'list': 30 | return AreaSerializer 31 | else: 32 | return SubAreaSerializer -------------------------------------------------------------------------------- /tb_store/tb_store/apps/carts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/carts/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/carts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/carts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CartsConfig(AppConfig): 5 | name = 'carts' 6 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/carts/constants.py: -------------------------------------------------------------------------------- 1 | CART_COOKIE_EXPIRES = 365 * 24 * 60 * 60 -------------------------------------------------------------------------------- /tb_store/tb_store/apps/carts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/carts/migrations/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/carts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/carts/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from goods.models import SKU 4 | 5 | 6 | class CartSerializer(serializers.Serializer): 7 | """ 8 | 购物车数据序列化器 9 | """ 10 | sku_id = serializers.IntegerField(label="sku_id", min_value=1) 11 | count = serializers.IntegerField(label="数量", min_value=1) 12 | selected = serializers.BooleanField(label="是否勾选", default=True) 13 | 14 | def validate(self, data): 15 | try: 16 | sku = SKU.objects.get(id=data["sku_id"]) 17 | except SKU.DoesNotExist: 18 | raise serializers.ValidationError("商品库存不足") 19 | 20 | if data["count"] > sku.stock: 21 | raise serializers.ValidationError("商品库存不足") 22 | 23 | return data 24 | 25 | class CartSKUSerializer(serializers.ModelSerializer): 26 | """ 27 | 购物车商品数据序列化器 28 | """ 29 | count = serializers.IntegerField(label='数量') 30 | selected = serializers.BooleanField(label='是否勾选') 31 | 32 | class Meta: 33 | model = SKU 34 | fields = ('id', 'count', 'name', 'default_image_url', 'price', 'selected') 35 | 36 | class CartDeletSerializer(serializers.Serializer): 37 | """ 38 | 删除购物车数据序列化器 39 | """ 40 | sku_id = serializers.IntegerField(label="商品id", min_value=1) 41 | 42 | def validate_sku_id(self, value): 43 | try: 44 | sku = SKU.objects.get(id=value) 45 | except SKU.DoesNotExist: 46 | raise serializers.ValidationError("商品不存在") 47 | 48 | return value 49 | 50 | class CartSelectAllSerializer(serializers.Serializer): 51 | """ 52 | 购物车全选 53 | """ 54 | selected = serializers.BooleanField(label="全选") 55 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/carts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/carts/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from rest_framework.routers import DefaultRouter 3 | 4 | from . import views 5 | 6 | 7 | urlpatterns = [ 8 | url(r'^cart/$', views.CartView.as_view()), 9 | url(r"^cart/selection/$", views.CartSelectAllView.as_view()) 10 | ] -------------------------------------------------------------------------------- /tb_store/tb_store/apps/carts/utils.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import base64 3 | from django_redis import get_redis_connection 4 | 5 | 6 | def merge_cart_cookie_to_redis(request, user, response): 7 | """ 8 | 合并请求用户的购物车数据,将未登录保存的cookie数据保存到redis中 9 | :param request: 10 | :param user: 11 | :param response: 12 | :return: 13 | :return: 14 | """ 15 | cookie_cart = request.COOKIES.get('cart') 16 | if not cookie_cart: 17 | return response 18 | 19 | # 解析cookie购物车数据 20 | cookie_cart = pickle.loads(base64.b64decode(cookie_cart.encode())) 21 | 22 | # 获取redis中购物车数据 23 | redis_conn = get_redis_connection('cart') 24 | redis_cart = redis_conn.hgetall('cart_%s' % user.id) 25 | 26 | # 用于保存最终购物车数据的字典 27 | cart = {} 28 | 29 | # 将redis中购物车数据的键值对转换为整型 30 | for sku_id, count in redis_cart.items(): 31 | cart[int(sku_id)] = int(count) 32 | 33 | # 记录redis勾选状态中应该增加的sku_id 34 | redis_cart_selected_add = [] 35 | 36 | # 记录redis勾选状态中应该删除的sku_id 37 | redis_cart_selected_remove = [] 38 | 39 | # 合并cookie购物车与redis购物车,保存到cart字典中 40 | for sku_id, count_selected_dict in cookie_cart.items(): 41 | # 处理商品数量 42 | cart[sku_id] = count_selected_dict['count'] 43 | 44 | if count_selected_dict['selected']: 45 | redis_cart_selected_add.append(sku_id) 46 | else: 47 | redis_cart_selected_remove.append(sku_id) 48 | 49 | if cart: 50 | pl = redis_conn.pipeline() 51 | pl.hmset('cart_%s' % user.id, cart) 52 | if redis_cart_selected_add: 53 | pl.sadd('cart_selected_%s' % user.id, *redis_cart_selected_add) 54 | if redis_cart_selected_remove: 55 | pl.srem('cart_selected_%s' % user.id, *redis_cart_selected_remove) 56 | pl.execute() 57 | 58 | response.delete_cookie('cart') 59 | 60 | return response -------------------------------------------------------------------------------- /tb_store/tb_store/apps/contents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/contents/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/contents/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from contents import models 5 | 6 | admin.site.register(models.ContentCategory) 7 | admin.site.register(models.Content) -------------------------------------------------------------------------------- /tb_store/tb_store/apps/contents/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ContentsConfig(AppConfig): 5 | name = 'contents' 6 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/contents/crons.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from django.conf import settings 3 | from django.template import loader 4 | import os 5 | import time 6 | 7 | from goods.models import GoodsChannel 8 | from .models import ContentCategory 9 | 10 | 11 | def generate_static_index_html(): 12 | """ 13 | 生成静态的主页html文件 14 | """ 15 | print('%s: generate_static_index_html' % time.ctime()) 16 | # 商品频道及分类菜单 17 | # 使用有序字典保存类别的顺序 18 | # categories = { 19 | # 1: { # 组1 20 | # 'channels': [{'id':, 'name':, 'url':},{}, {}...], 21 | # 'sub_cats': [{'id':, 'name':, 'sub_cats':[{},{}]}, {}, {}, ..] 22 | # }, 23 | # 2: { # 组2 24 | # 25 | # } 26 | # } 27 | categories = OrderedDict() 28 | channels = GoodsChannel.objects.order_by('group_id', 'sequence') 29 | for channel in channels: 30 | group_id = channel.group_id # 当前组 31 | 32 | if group_id not in categories: 33 | categories[group_id] = {'channels': [], 'sub_cats': []} 34 | 35 | cat1 = channel.category # 当前频道的类别 36 | 37 | # 追加当前频道 38 | categories[group_id]['channels'].append({ 39 | 'id': cat1.id, 40 | 'name': cat1.name, 41 | 'url': channel.url 42 | }) 43 | # 构建当前类别的子类别 44 | for cat2 in cat1.goodscategory_set.all(): 45 | cat2.sub_cats = [] 46 | for cat3 in cat2.goodscategory_set.all(): 47 | cat2.sub_cats.append(cat3) 48 | categories[group_id]['sub_cats'].append(cat2) 49 | 50 | # 广告内容 51 | contents = {} 52 | content_categories = ContentCategory.objects.all() 53 | for cat in content_categories: 54 | contents[cat.key] = cat.content_set.filter(status=True).order_by('sequence') 55 | 56 | # 渲染模板 57 | context = { 58 | 'categories': categories, 59 | 'contents': contents 60 | } 61 | template = loader.get_template('index.html') 62 | html_text = template.render(context) 63 | file_path = os.path.join(settings.GENERATED_STATIC_HTML_FILES_DIR, 'index.html') 64 | with open(file_path, 'w', encoding='utf-8') as f: 65 | f.write(html_text) -------------------------------------------------------------------------------- /tb_store/tb_store/apps/contents/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/contents/migrations/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/contents/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | from tb_store.utils.models import BaseModel 5 | 6 | 7 | class ContentCategory(BaseModel): 8 | """ 9 | 广告内容类别 10 | """ 11 | name = models.CharField(max_length=50, verbose_name='名称') 12 | key = models.CharField(max_length=50, verbose_name='类别键名') 13 | 14 | class Meta: 15 | db_table = 'tb_content_category' 16 | verbose_name = '广告内容类别' 17 | verbose_name_plural = verbose_name 18 | 19 | def __str__(self): 20 | return self.name 21 | 22 | 23 | class Content(BaseModel): 24 | """ 25 | 广告内容 26 | """ 27 | category = models.ForeignKey(ContentCategory, on_delete=models.PROTECT, verbose_name='类别') 28 | title = models.CharField(max_length=100, verbose_name='标题') 29 | url = models.CharField(max_length=300, verbose_name='内容链接') 30 | image = models.ImageField(null=True, blank=True, verbose_name='图片') 31 | text = models.TextField(null=True, blank=True, verbose_name='内容') 32 | sequence = models.IntegerField(verbose_name='排序') 33 | status = models.BooleanField(default=True, verbose_name='是否展示') 34 | 35 | class Meta: 36 | db_table = 'tb_content' 37 | verbose_name = '广告内容' 38 | verbose_name_plural = verbose_name 39 | 40 | def __str__(self): 41 | return self.category.name + ': ' + self.title -------------------------------------------------------------------------------- /tb_store/tb_store/apps/contents/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/contents/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/goods/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from goods import models 5 | 6 | from django.contrib import admin 7 | 8 | # Register your models here. 9 | 10 | from . import models 11 | 12 | 13 | class SKUAdmin(admin.ModelAdmin): 14 | def save_model(self, request, obj, form, change): 15 | obj.save() 16 | from celery_tasks.html.tasks import generate_static_sku_detail_html 17 | generate_static_sku_detail_html.delay(obj.id) 18 | 19 | 20 | class SKUSpecificationAdmin(admin.ModelAdmin): 21 | def save_model(self, request, obj, form, change): 22 | obj.save() 23 | from celery_tasks.html.tasks import generate_static_sku_detail_html 24 | generate_static_sku_detail_html.delay(obj.sku.id) 25 | 26 | def delete_model(self, request, obj): 27 | sku_id = obj.sku.id 28 | obj.delete() 29 | from celery_tasks.html.tasks import generate_static_sku_detail_html 30 | generate_static_sku_detail_html.delay(sku_id) 31 | 32 | 33 | class SKUImageAdmin(admin.ModelAdmin): 34 | def save_model(self, request, obj, form, change): 35 | obj.save() 36 | from celery_tasks.html.tasks import generate_static_sku_detail_html 37 | generate_static_sku_detail_html.delay(obj.sku.id) 38 | 39 | # 设置SKU默认图片 40 | sku = obj.sku 41 | if not sku.default_image_url: 42 | sku.default_image_url = obj.image.url 43 | sku.save() 44 | 45 | def delete_model(self, request, obj): 46 | sku_id = obj.sku.id 47 | obj.delete() 48 | from celery_tasks.html.tasks import generate_static_sku_detail_html 49 | generate_static_sku_detail_html.delay(sku_id) 50 | 51 | 52 | admin.site.register(models.GoodsCategory) 53 | admin.site.register(models.GoodsChannel) 54 | admin.site.register(models.Goods) 55 | admin.site.register(models.Brand) 56 | admin.site.register(models.GoodsSpecification) 57 | admin.site.register(models.SpecificationOption) 58 | admin.site.register(models.SKU, SKUAdmin) 59 | admin.site.register(models.SKUSpecification, SKUSpecificationAdmin) 60 | admin.site.register(models.SKUImage, SKUImageAdmin) -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/adminx.py: -------------------------------------------------------------------------------- 1 | import xadmin 2 | from xadmin import views 3 | 4 | from . import models 5 | 6 | 7 | class BaseSetting(object): 8 | """xadmin的基本配置""" 9 | enable_themes = True # 开启主题切换功能 10 | use_bootswatch = True 11 | 12 | 13 | xadmin.site.register(views.BaseAdminView, BaseSetting) 14 | 15 | 16 | class GlobalSettings(object): 17 | """xadmin的全局配置""" 18 | site_title = "东京商城运营管理系统" # 设置站点标题 19 | site_footer = "东京商城集团有限公司" # 设置站点的页脚 20 | menu_style = "accordion" # 设置菜单折叠 21 | 22 | 23 | xadmin.site.register(views.CommAdminView, GlobalSettings) 24 | 25 | 26 | class SKUAdmin(object): 27 | model_icon = 'fa fa-gift' 28 | list_display = ['id', 'name', 'price', 'stock', 'sales', 'comments'] 29 | list_editable = ['price', 'stock'] 30 | 31 | 32 | xadmin.site.register(models.SKU, SKUAdmin) 33 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class GoodsConfig(AppConfig): 5 | name = 'goods' 6 | verbose_name = "商品管理" 7 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/goods/migrations/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/models.py: -------------------------------------------------------------------------------- 1 | from ckeditor.fields import RichTextField 2 | from ckeditor_uploader.fields import RichTextUploadingField 3 | from django.db import models 4 | 5 | # Create your models here. 6 | from tb_store.utils.models import BaseModel 7 | 8 | 9 | class GoodsCategory(BaseModel): 10 | """ 11 | 商品类别 12 | """ 13 | name = models.CharField(max_length=10, verbose_name='名称') 14 | parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, verbose_name='父类别') 15 | 16 | class Meta: 17 | db_table = 'tb_goods_category' 18 | verbose_name = '商品类别' 19 | verbose_name_plural = verbose_name 20 | 21 | def __str__(self): 22 | return self.name 23 | 24 | 25 | class GoodsChannel(BaseModel): 26 | """ 27 | 商品频道 28 | """ 29 | group_id = models.IntegerField(verbose_name='组号') 30 | category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, verbose_name='顶级商品类别') 31 | url = models.CharField(max_length=50, verbose_name='频道页面链接') 32 | sequence = models.IntegerField(verbose_name='组内顺序') 33 | 34 | class Meta: 35 | db_table = 'tb_goods_channel' 36 | verbose_name = '商品频道' 37 | verbose_name_plural = verbose_name 38 | 39 | def __str__(self): 40 | return self.category.name 41 | 42 | 43 | class Brand(BaseModel): 44 | """ 45 | 品牌 46 | """ 47 | name = models.CharField(max_length=20, verbose_name='名称') 48 | logo = models.ImageField(verbose_name='Logo图片') 49 | first_letter = models.CharField(max_length=1, verbose_name='品牌首字母') 50 | 51 | class Meta: 52 | db_table = 'tb_brand' 53 | verbose_name = '品牌' 54 | verbose_name_plural = verbose_name 55 | 56 | def __str__(self): 57 | return self.name 58 | 59 | 60 | class Goods(BaseModel): 61 | """ 62 | 商品SPU 63 | """ 64 | name = models.CharField(max_length=50, verbose_name='名称') 65 | brand = models.ForeignKey(Brand, on_delete=models.PROTECT, verbose_name='品牌') 66 | category1 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat1_goods', verbose_name='一级类别') 67 | category2 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat2_goods', verbose_name='二级类别') 68 | category3 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat3_goods', verbose_name='三级类别') 69 | sales = models.IntegerField(default=0, verbose_name='销量') 70 | comments = models.IntegerField(default=0, verbose_name='评价数') 71 | desc_detail = RichTextUploadingField(default='', verbose_name='详细介绍') 72 | desc_pack = RichTextField(default='', verbose_name='包装信息') 73 | desc_service = RichTextUploadingField(default='', verbose_name='售后服务') 74 | 75 | class Meta: 76 | db_table = 'tb_goods' 77 | verbose_name = '商品' 78 | verbose_name_plural = verbose_name 79 | 80 | def __str__(self): 81 | return self.name 82 | 83 | 84 | class GoodsSpecification(BaseModel): 85 | """ 86 | 商品规格 87 | """ 88 | goods = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name='商品') 89 | name = models.CharField(max_length=20, verbose_name='规格名称') 90 | 91 | class Meta: 92 | db_table = 'tb_goods_specification' 93 | verbose_name = '商品规格' 94 | verbose_name_plural = verbose_name 95 | 96 | def __str__(self): 97 | return '%s: %s' % (self.goods.name, self.name) 98 | 99 | 100 | class SpecificationOption(BaseModel): 101 | """ 102 | 规格选项 103 | """ 104 | spec = models.ForeignKey(GoodsSpecification, on_delete=models.CASCADE, verbose_name='规格') 105 | value = models.CharField(max_length=20, verbose_name='选项值') 106 | 107 | class Meta: 108 | db_table = 'tb_specification_option' 109 | verbose_name = '规格选项' 110 | verbose_name_plural = verbose_name 111 | 112 | def __str__(self): 113 | return '%s - %s' % (self.spec, self.value) 114 | 115 | 116 | class SKU(BaseModel): 117 | """ 118 | 商品SKU 119 | """ 120 | name = models.CharField(max_length=50, verbose_name='名称') 121 | caption = models.CharField(max_length=100, verbose_name='副标题') 122 | goods = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name='商品') 123 | category = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, verbose_name='从属类别') 124 | price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价') 125 | cost_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='进价') 126 | market_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='市场价') 127 | stock = models.IntegerField(default=0, verbose_name='库存') 128 | sales = models.IntegerField(default=0, verbose_name='销量') 129 | comments = models.IntegerField(default=0, verbose_name='评价数') 130 | is_launched = models.BooleanField(default=True, verbose_name='是否上架销售') 131 | default_image_url = models.CharField(max_length=200, default='', null=True, blank=True, verbose_name='默认图片') 132 | 133 | class Meta: 134 | db_table = 'tb_sku' 135 | verbose_name = '商品SKU' 136 | verbose_name_plural = verbose_name 137 | 138 | def __str__(self): 139 | return '%s: %s' % (self.id, self.name) 140 | 141 | 142 | class SKUImage(BaseModel): 143 | """ 144 | SKU图片 145 | """ 146 | sku = models.ForeignKey(SKU, on_delete=models.CASCADE, verbose_name='sku') 147 | image = models.ImageField(verbose_name='图片') 148 | 149 | class Meta: 150 | db_table = 'tb_sku_image' 151 | verbose_name = 'SKU图片' 152 | verbose_name_plural = verbose_name 153 | 154 | def __str__(self): 155 | return '%s %s' % (self.sku.name, self.id) 156 | 157 | 158 | class SKUSpecification(BaseModel): 159 | """ 160 | SKU具体规格 161 | """ 162 | sku = models.ForeignKey(SKU, on_delete=models.CASCADE, verbose_name='sku') 163 | spec = models.ForeignKey(GoodsSpecification, on_delete=models.PROTECT, verbose_name='规格名称') 164 | option = models.ForeignKey(SpecificationOption, on_delete=models.PROTECT, verbose_name='规格值') 165 | 166 | class Meta: 167 | db_table = 'tb_sku_specification' 168 | verbose_name = 'SKU规格' 169 | verbose_name_plural = verbose_name 170 | 171 | def __str__(self): 172 | return '%s: %s - %s' % (self.sku, self.spec.name, self.option.value) -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/search_indexes.py: -------------------------------------------------------------------------------- 1 | from haystack import indexes 2 | from .models import SKU 3 | 4 | 5 | class SKUIndex(indexes.SearchIndex, indexes.Indexable): 6 | """ 7 | 建立SKU索引数据模型类 8 | """ 9 | text = indexes.CharField(document=True, use_template=True) 10 | id = indexes.IntegerField(model_attr="id") 11 | name = indexes.CharField(model_attr="name") 12 | price = indexes.DecimalField(model_attr="price") 13 | default_image_url = indexes.CharField(model_attr='default_image_url') 14 | comments = indexes.IntegerField(model_attr="comments") 15 | 16 | def get_model(self): 17 | """返回建立索引的模型类""" 18 | return SKU 19 | 20 | def index_queryset(self, using=None): 21 | return self.get_model().objects.filter(is_launched=True) 22 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from goods.models import SKU 4 | from drf_haystack.serializers import HaystackSerializer 5 | 6 | from goods.search_indexes import SKUIndex 7 | 8 | 9 | class SKUSerializer(serializers.ModelSerializer): 10 | class Meta: 11 | model = SKU 12 | fields = ("id", "name", "price", "default_image_url", "comments") 13 | 14 | 15 | class SKUIndexSerializer(HaystackSerializer): 16 | """ 17 | SKU索引结果数据序列化器 18 | """ 19 | class Meta: 20 | index_classes = [SKUIndex] 21 | fields = ("text", "id", "name", "price", "default_image_url", "comments") -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from rest_framework.routers import DefaultRouter 3 | 4 | from . import views 5 | 6 | 7 | urlpatterns = [ 8 | url(r'^categories/(?P\d+)/skus/$', views.SKUListView.as_view()), 9 | ] 10 | 11 | router = DefaultRouter() 12 | router.register("skus/search", views.SKUSearchViewSet, base_name="skus_search") 13 | urlpatterns += router.urls 14 | 15 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/utils.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .models import GoodsChannel 4 | 5 | 6 | def get_categories(): 7 | """ 8 | 获取商城商品分类菜单 9 | :return 菜单字典 10 | """ 11 | # 商品频道及分类菜单 12 | # 使用有序字典保存类别的顺序 13 | # categories = { 14 | # 1: { # 组1 15 | # 'channels': [{'id':, 'name':, 'url':},{}, {}...], 16 | # 'sub_cats': [{'id':, 'name':, 'sub_cats':[{},{}]}, {}, {}, ..] 17 | # }, 18 | # 2: { # 组2 19 | # 20 | # } 21 | # } 22 | categories = OrderedDict() 23 | channels = GoodsChannel.objects.order_by('group_id', 'sequence') 24 | for channel in channels: 25 | group_id = channel.group_id # 当前组 26 | 27 | if group_id not in categories: 28 | categories[group_id] = {'channels': [], 'sub_cats': []} 29 | 30 | cat1 = channel.category # 当前频道的类别 31 | 32 | # 追加当前频道 33 | categories[group_id]['channels'].append({ 34 | 'id': cat1.id, 35 | 'name': cat1.name, 36 | 'url': channel.url 37 | }) 38 | # 构建当前类别的子类别 39 | for cat2 in cat1.goodscategory_set.all(): 40 | cat2.sub_cats = [] 41 | for cat3 in cat2.goodscategory_set.all(): 42 | cat2.sub_cats.append(cat3) 43 | categories[group_id]['sub_cats'].append(cat2) 44 | return categories -------------------------------------------------------------------------------- /tb_store/tb_store/apps/goods/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from drf_haystack.viewsets import HaystackViewSet 3 | from rest_framework.filters import OrderingFilter 4 | # Create your views here. 5 | from rest_framework.generics import ListAPIView 6 | 7 | from goods.models import SKU 8 | from goods.serializers import SKUSerializer, SKUIndexSerializer 9 | 10 | 11 | class SKUListView(ListAPIView): 12 | """ 13 | sku列表数据 14 | """ 15 | serializer_class = SKUSerializer 16 | filter_backends = (OrderingFilter,) 17 | ordering_fields = ("create_time", "price", "sales") 18 | 19 | def get_queryset(self): 20 | category_id = self.kwargs["category_id"] 21 | return SKU.objects.filter(category_id=category_id, is_launched=True) 22 | 23 | 24 | class SKUSearchViewSet(HaystackViewSet): 25 | """ 26 | SKU搜索 27 | """ 28 | index_models = [SKU] 29 | serializer_class = SKUIndexSerializer 30 | 31 | 32 | class SKUSpecificationAdmin(object): 33 | def save_models(self): 34 | # 保存数据对象 35 | obj = self.new_obj 36 | obj.save() 37 | 38 | # 补充自定义行为 39 | from celery_tasks.html.tasks import generate_static_sku_detail_html 40 | generate_static_sku_detail_html.delay(obj.sku.id) 41 | 42 | def delete_model(self): 43 | # 删除数据对象 44 | obj = self.obj 45 | sku_id = obj.sku.id 46 | obj.delete() 47 | 48 | # 补充自定义行为 49 | from celery_tasks.html.tasks import generate_static_sku_detail_html 50 | generate_static_sku_detail_html.delay(sku_id) -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/oauth/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class OauthConfig(AppConfig): 5 | name = 'oauth' 6 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/constants.py: -------------------------------------------------------------------------------- 1 | # 绑定用户的有效期 2 | SAVE_QQ_USER_TOKEN_EXPIRES = 10 * 60 -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/exceptions.py: -------------------------------------------------------------------------------- 1 | class OAuthQQAPIError(Exception): 2 | pass -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/oauth/migrations/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | from tb_store.utils.models import BaseModel 5 | 6 | 7 | class OAuthQQUser(BaseModel): 8 | """ 9 | QQ登录用户数据 10 | """ 11 | user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户') 12 | openid = models.CharField(max_length=64, verbose_name='openid', db_index=True) 13 | 14 | class Meta: 15 | db_table = 'tb_oauth_qq' 16 | verbose_name = 'QQ登录用户数据' 17 | verbose_name_plural = verbose_name 18 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/serializers.py: -------------------------------------------------------------------------------- 1 | from django_redis import get_redis_connection 2 | from rest_framework import serializers 3 | from rest_framework_jwt.settings import api_settings 4 | 5 | from oauth.models import OAuthQQUser 6 | from oauth.utils import OAuthQQ 7 | from users.models import User 8 | 9 | 10 | class OAuthQQUserSerializer(serializers.ModelSerializer): 11 | """ 12 | 保存QQ用户序列化器 13 | """ 14 | sms_code = serializers.CharField(label='短信验证码', write_only=True) 15 | access_token = serializers.CharField(label='操作凭证', write_only=True) 16 | token = serializers.CharField(read_only=True) 17 | mobile = serializers.RegexField(label='手机号', regex=r'^1[3-9]\d{9}$') 18 | 19 | class Meta: 20 | model = User 21 | fields = ('mobile', 'password', 'sms_code', 'access_token', 'id', 'username', 'token') 22 | extra_kwargs = { 23 | 'username': { 24 | 'read_only': True 25 | }, 26 | 'password': { 27 | 'write_only': True, 28 | 'min_length': 8, 29 | 'max_length': 20, 30 | 'error_messages': { 31 | 'min_length': '仅允许8-20个字符的密码', 32 | 'max_length': '仅允许8-20个字符的密码', 33 | } 34 | } 35 | } 36 | 37 | def validate(self, attrs): 38 | # 检验access_token 39 | access_token = attrs['access_token'] 40 | 41 | openid = OAuthQQ.check_save_user_token(access_token) 42 | if not openid: 43 | raise serializers.ValidationError('无效的access_token') 44 | 45 | attrs['openid'] = openid 46 | 47 | # 检验短信验证码 48 | mobile = attrs['mobile'] 49 | sms_code = attrs['sms_code'] 50 | redis_conn = get_redis_connection('verify_codes') 51 | real_sms_code = redis_conn.get('sms_%s' % mobile) 52 | if real_sms_code.decode() != sms_code: 53 | raise serializers.ValidationError('短信验证码错误') 54 | 55 | # 如果用户存在,检查用户密码 56 | try: 57 | user = User.objects.get(mobile=mobile) 58 | except User.DoesNotExist: 59 | pass 60 | else: 61 | password = attrs['password'] 62 | if not user.check_password(password): 63 | raise serializers.ValidationError('密码错误') 64 | attrs['user'] = user 65 | return attrs 66 | 67 | def create(self, validated_data): 68 | openid = validated_data['openid'] 69 | user = validated_data.get('user') 70 | mobile = validated_data['mobile'] 71 | password = validated_data['password'] 72 | 73 | if not user: 74 | # 如果用户不存在,创建用户,绑定openid(创建了OAuthQQUser数据) 75 | user = User.objects.create_user(username=mobile, mobile=mobile, password=password) 76 | 77 | OAuthQQUser.objects.create(user=user, openid=openid) 78 | 79 | # 签发jwt token 80 | jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER 81 | jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER 82 | 83 | payload = jwt_payload_handler(user) 84 | token = jwt_encode_handler(payload) 85 | 86 | user.token = token 87 | 88 | # 向视图对象中补充user对象属性,以便在视图中使用user 89 | self.context['view'].user = user 90 | 91 | return user 92 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | url(r'^qq/authorization/$', views.QQAuthURLView.as_view()), 7 | url(r'^qq/user/$', views.QQAuthUserView.as_view()) 8 | 9 | ] -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import urllib 4 | from urllib.parse import urlencode, parse_qs 5 | from urllib.request import urlopen 6 | 7 | from django.conf import settings 8 | from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadData 9 | 10 | from oauth.exceptions import OAuthQQAPIError 11 | from . import constants 12 | 13 | logger = logging.getLogger("django") 14 | 15 | 16 | class OAuthQQ(object): 17 | """ 18 | 添加qq认证辅助工具类 19 | """ 20 | 21 | def __init__(self, client_id=None, client_secret=None, redirect_uri=None, state=None): 22 | self.client_id = client_id or settings.QQ_CLIENT_ID 23 | self.client_secret = client_secret or settings.QQ_CLIENT_SECRET 24 | self.redirect_uri = redirect_uri or settings.QQ_REDIRECT_URI 25 | self.state = state or settings.QQ_STATE # 用于保存登录后跳转页面的路径 26 | 27 | def get_qq_login_url(self): 28 | """ 29 | 获取qq登录的网址 30 | return:url网址 31 | :return: 32 | """ 33 | params = { 34 | 'response_type': "code", 35 | 'client_id': self.client_id, 36 | 'redirect_uri': self.redirect_uri, 37 | 'state': self.state, 38 | 'scope': "get_user_info", 39 | } 40 | url = 'https://graph.qq.com/oauth2.0/authorize?' + urlencode(params) 41 | return url 42 | 43 | # 验证开发者身份 44 | def get_access_token(self, code): 45 | url = 'https://graph.qq.com/oauth2.0/token?' 46 | params = { 47 | "grant_type": "authorization_code", 48 | "client_id": self.client_id, 49 | "client_secret": self.client_secret, 50 | "code": code, 51 | "redirect_uri": self.redirect_uri, 52 | } 53 | url += urllib.parse.urlencode(params) 54 | try: 55 | # 发送请求 56 | resp = urlopen(url) 57 | # 读取相应体数据,字节转成字符串 58 | resp_data = resp.read().decode() 59 | # 解析access_token 60 | resp_dict = urllib.parse.parse_qs(resp_data) 61 | except Exception as e: 62 | logger.error("获取access_token异常:%s" % e) 63 | raise OAuthQQAPIError 64 | else: 65 | access_token = resp_dict.get("access_token") 66 | return access_token[0] 67 | 68 | def get_openid(self, access_token): 69 | """ 70 | 获取用户的openid 71 | :param access_token: qq提供的access_token 72 | :return: open_id 73 | """ 74 | url = 'https://graph.qq.com/oauth2.0/me?access_token=' + access_token 75 | response = urlopen(url) 76 | response_data = response.read().decode() 77 | try: 78 | # 返回的数据 callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n; 79 | data = json.loads(response_data[10:-4]) 80 | except Exception: 81 | data = parse_qs(response_data) 82 | logger.error('code=%s msg=%s' % (data.get('code'), data.get('msg'))) 83 | raise OAuthQQAPIError 84 | openid = data.get('openid', None) 85 | return openid 86 | 87 | @staticmethod 88 | def generate_save_user_token(openid): 89 | """ 90 | 生成保存用户数据的token 91 | :param openid: 用户的openid 92 | :return: token 93 | """ 94 | serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES) 95 | data = {'openid': openid} 96 | token = serializer.dumps(data) 97 | return token.decode() 98 | 99 | @staticmethod 100 | def check_save_user_token(token): 101 | """ 102 | 检验保存用户数据的token 103 | :param token: token 104 | :return: openid or None 105 | """ 106 | serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES) 107 | try: 108 | data = serializer.loads(token) 109 | except BadData: 110 | return None 111 | else: 112 | return data.get('openid') -------------------------------------------------------------------------------- /tb_store/tb_store/apps/oauth/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | from rest_framework import status 5 | from rest_framework.generics import CreateAPIView 6 | from rest_framework.response import Response 7 | from rest_framework.views import APIView 8 | from rest_framework_jwt.settings import api_settings 9 | 10 | from carts.utils import merge_cart_cookie_to_redis 11 | from oauth.exceptions import OAuthQQAPIError 12 | from oauth.models import OAuthQQUser 13 | from oauth.serializers import OAuthQQUserSerializer 14 | from oauth.utils import OAuthQQ 15 | 16 | 17 | class QQAuthURLView(APIView): 18 | """ 19 | 获取qq登录的url 20 | """ 21 | 22 | def get(self, request): 23 | """ 24 | 提供qq登录的url 25 | :param request: 26 | :return: 27 | """ 28 | next = request.query_params.get("next") 29 | oauth = OAuthQQ(state=next) 30 | login_url = oauth.get_qq_login_url() 31 | return Response({'login_url': login_url}) 32 | 33 | 34 | class QQAuthUserView(CreateAPIView): 35 | """ 36 | 用与qq登录的用户:?code=XXX 37 | """ 38 | serializer_class = OAuthQQUserSerializer 39 | 40 | def get(self, request): 41 | # 获取code 42 | code = request.query_params.get("code") 43 | if not code: 44 | return Response({"message": "缺少code"}, status=status.HTTP_400_BAD_REQUEST) 45 | oauth_qq = OAuthQQ() 46 | 47 | try: 48 | # 凭借code获取access_token 49 | access_token = oauth_qq.get_access_token(code) 50 | # 凭借access_token获取openid 51 | openid = oauth_qq.get_openid(access_token) 52 | except OAuthQQAPIError: 53 | return Response({"message": "QQ服务异常"}, status=status.HTTP_503_SERVICE_UNAVAILABLE) 54 | 55 | try: 56 | qq_user = OAuthQQUser.objects.get(openid=openid) 57 | except OAuthQQUser.DoesNotExist: 58 | # 如果数据不存在,处理openid并返回 59 | token = oauth_qq.generate_save_user_token(openid) 60 | return Response({"access_token": token}) 61 | else: 62 | # 如果数据存在,表示用户已经绑定过身份,签发JWT token 63 | # 找到用户, 生成token 64 | user = qq_user.user 65 | jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER 66 | jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER 67 | 68 | payload = jwt_payload_handler(user) 69 | token = jwt_encode_handler(payload) 70 | 71 | response = Response({ 72 | 'token': token, 73 | 'user_id': user.id, 74 | 'username': user.username 75 | }) 76 | # 合并购物车 77 | response = merge_cart_cookie_to_redis(request, user, response) 78 | return response 79 | 80 | def post(self, request, *args, **kwargs): 81 | response = super().post(request, *args, **kwargs) 82 | 83 | # 合并购物车 84 | response = merge_cart_cookie_to_redis(request, self.user, response) 85 | return response 86 | 87 | 88 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/orders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/orders/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/orders/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/orders/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class OrdersConfig(AppConfig): 5 | name = 'orders' 6 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/orders/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/orders/migrations/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/orders/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from tb_store.utils.models import BaseModel 3 | from users.models import User, Address 4 | from goods.models import SKU 5 | 6 | 7 | # Create your models here. 8 | 9 | 10 | class OrderInfo(BaseModel): 11 | """ 12 | 订单信息 13 | """ 14 | PAY_METHODS_ENUM = { 15 | "CASH": 1, 16 | "ALIPAY": 2 17 | } 18 | 19 | PAY_METHOD_CHOICES = ( 20 | (1, "货到付款"), 21 | (2, "支付宝"), 22 | ) 23 | 24 | ORDER_STATUS_ENUM = { 25 | "UNPAID": 1, 26 | "UNSEND": 2, 27 | "UNRECEIVED": 3, 28 | "UNCOMMENT": 4, 29 | "FINISHED": 5 30 | } 31 | 32 | ORDER_STATUS_CHOICES = ( 33 | (1, "待支付"), 34 | (2, "待发货"), 35 | (3, "待收货"), 36 | (4, "待评价"), 37 | (5, "已完成"), 38 | (6, "已取消"), 39 | ) 40 | 41 | order_id = models.CharField(max_length=64, primary_key=True, verbose_name="订单号") 42 | user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="下单用户") 43 | address = models.ForeignKey(Address, on_delete=models.PROTECT, verbose_name="收获地址") 44 | total_count = models.IntegerField(default=1, verbose_name="商品总数") 45 | total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="商品总金额") 46 | freight = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="运费") 47 | pay_method = models.SmallIntegerField(choices=PAY_METHOD_CHOICES, default=1, verbose_name="支付方式") 48 | status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES, default=1, verbose_name="订单状态") 49 | 50 | class Meta: 51 | db_table = "tb_order_info" 52 | verbose_name = '订单基本信息' 53 | verbose_name_plural = verbose_name 54 | 55 | 56 | class OrderGoods(BaseModel): 57 | """ 58 | 订单商品 59 | """ 60 | SCORE_CHOICES = ( 61 | (0, '0分'), 62 | (1, '20分'), 63 | (2, '40分'), 64 | (3, '60分'), 65 | (4, '80分'), 66 | (5, '100分'), 67 | ) 68 | order = models.ForeignKey(OrderInfo, related_name='skus', on_delete=models.CASCADE, verbose_name="订单") 69 | sku = models.ForeignKey(SKU, on_delete=models.PROTECT, verbose_name="订单商品") 70 | count = models.IntegerField(default=1, verbose_name="数量") 71 | price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="单价") 72 | comment = models.TextField(default="", verbose_name="评价信息") 73 | score = models.SmallIntegerField(choices=SCORE_CHOICES, default=5, verbose_name='满意度评分') 74 | is_anonymous = models.BooleanField(default=False, verbose_name='是否匿名评价') 75 | is_commented = models.BooleanField(default=False, verbose_name='是否评价了') 76 | 77 | class Meta: 78 | db_table = "tb_order_goods" 79 | verbose_name = '订单商品' 80 | verbose_name_plural = verbose_name 81 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/orders/serializers.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from django.db import transaction 3 | from django.utils import timezone 4 | from django_redis import get_redis_connection 5 | from rest_framework import serializers 6 | from rest_framework.exceptions import ValidationError 7 | 8 | from goods.models import SKU 9 | from orders.models import OrderInfo, OrderGoods 10 | from tb_store.utils.exceptions import logger 11 | 12 | 13 | class CartSKUSerializer(serializers.ModelSerializer): 14 | """ 15 | 购物车商品数据序列化器 16 | """ 17 | count = serializers.IntegerField(label='数量') 18 | 19 | class Meta: 20 | model = SKU 21 | fields = ('id', 'name', 'default_image_url', 'price', 'count') 22 | 23 | 24 | class OrderSettlementSerializer(serializers.Serializer): 25 | """ 26 | 订单结算数据序列化器 27 | """ 28 | freight = serializers.DecimalField(label='运费', max_digits=10, decimal_places=2) 29 | skus = CartSKUSerializer(many=True) 30 | 31 | 32 | class SaveOrderSerializer(serializers.ModelSerializer): 33 | """ 34 | 下单数据序列化器 35 | """ 36 | 37 | class Meta: 38 | model = OrderInfo 39 | fields = ('order_id', 'address', 'pay_method') 40 | read_only_fields = ('order_id',) 41 | extra_kwargs = { 42 | 'address': { 43 | 'write_only': True, 44 | 'required': True, 45 | }, 46 | 'pay_method': { 47 | 'write_only': True, 48 | 'required': True 49 | } 50 | } 51 | 52 | def create(self, validated_data): 53 | """ 54 | 保存订单 55 | """ 56 | # 获取当前下单用户 57 | user = self.context['request'].user 58 | 59 | # 组织订单编号 20170903153611+user.id 60 | # timezone.now() -> datetime 61 | order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id) 62 | 63 | address = validated_data['address'] 64 | pay_method = validated_data['pay_method'] 65 | 66 | # 生成订单 67 | with transaction.atomic(): 68 | # 创建一个保存点 69 | save_id = transaction.savepoint() 70 | 71 | try: 72 | # 创建订单信息 73 | order = OrderInfo.objects.create( 74 | order_id=order_id, 75 | user=user, 76 | address=address, 77 | total_count=0, 78 | total_amount=Decimal(0), 79 | freight=Decimal(10), 80 | pay_method=pay_method, 81 | status=OrderInfo.ORDER_STATUS_ENUM['UNSEND'] if pay_method == OrderInfo.PAY_METHODS_ENUM[ 82 | 'CASH'] else OrderInfo.ORDER_STATUS_ENUM['UNPAID'] 83 | ) 84 | # 获取购物车信息 85 | redis_conn = get_redis_connection("cart") 86 | redis_cart = redis_conn.hgetall("cart_%s" % user.id) 87 | cart_selected = redis_conn.smembers('cart_selected_%s' % user.id) 88 | 89 | # 将bytes类型转换为int类型 90 | cart = {} 91 | for sku_id in cart_selected: 92 | cart[int(sku_id)] = int(redis_cart[sku_id]) 93 | 94 | # # 一次查询出所有商品数据 95 | # skus = SKU.objects.filter(id__in=cart.keys()) 96 | 97 | # 处理订单商品 98 | sku_id_list = cart.keys() 99 | for sku_id in sku_id_list: 100 | while True: 101 | sku = SKU.objects.get(id=sku_id) 102 | 103 | sku_count = cart[sku.id] 104 | 105 | # 判断库存 106 | origin_stock = sku.stock # 原始库存 107 | origin_sales = sku.sales # 原始销量 108 | 109 | if sku_count > origin_stock: 110 | transaction.savepoint_rollback(save_id) 111 | raise serializers.ValidationError('商品库存不足') 112 | 113 | # 用于演示并发下单 114 | # import time 115 | # time.sleep(5) 116 | 117 | # 减少库存 118 | # sku.stock -= sku_count 119 | # sku.sales += sku_count 120 | # sku.save() 121 | new_stock = origin_stock - sku_count 122 | new_sales = origin_sales + sku_count 123 | 124 | # 根据原始库存条件更新,返回更新的条目数,乐观锁 125 | ret = SKU.objects.filter(id=sku.id, stock=origin_stock).update(stock=new_stock, sales=new_sales) 126 | if ret == 0: 127 | continue 128 | 129 | # 累计商品的SPU 销量信息 130 | sku.goods.sales += sku_count 131 | sku.goods.save() 132 | 133 | # 累计订单基本信息的数据 134 | order.total_count += sku_count # 累计总金额 135 | order.total_amount += (sku.price * sku_count) # 累计总额 136 | 137 | # 保存订单商品 138 | OrderGoods.objects.create( 139 | order=order, 140 | sku=sku, 141 | count=sku_count, 142 | price=sku.price, 143 | ) 144 | 145 | # 更新成功 146 | break 147 | 148 | # 更新订单的金额数量信息 149 | order.total_amount += order.freight 150 | order.save() 151 | 152 | except serializers.ValidationError: 153 | raise 154 | except Exception as e: 155 | logger.error(e) 156 | transaction.savepoint_rollback(save_id) 157 | raise 158 | 159 | # 提交事务 160 | transaction.savepoint_commit(save_id) 161 | 162 | # 更新redis中保存的购物车数据 163 | pl = redis_conn.pipeline() 164 | pl.hdel('cart_%s' % user.id, *cart_selected) 165 | pl.srem('cart_selected_%s' % user.id, *cart_selected) 166 | pl.execute() 167 | return order 168 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/orders/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/orders/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | url(r'^orders/settlement/$', views.OrderSettlementView.as_view()), 7 | url(r"^orders/$", views.SaveOrderView.as_view()), 8 | ] -------------------------------------------------------------------------------- /tb_store/tb_store/apps/orders/views.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from django.shortcuts import render 3 | 4 | # Create your views here. 5 | from django_redis import get_redis_connection 6 | from rest_framework.generics import CreateAPIView 7 | from rest_framework.permissions import IsAuthenticated 8 | from rest_framework.response import Response 9 | from rest_framework.views import APIView 10 | 11 | from goods.models import SKU 12 | from orders.serializers import OrderSettlementSerializer 13 | from orders.serializers import SaveOrderSerializer 14 | 15 | 16 | class OrderSettlementView(APIView): 17 | """ 18 | 订单结算 19 | """ 20 | permission_classes = [IsAuthenticated] 21 | 22 | def get(self, request): 23 | """ 24 | 获取 25 | """ 26 | user = request.user 27 | 28 | # 从购物车中获取用户勾选要结算的商品信息 29 | redis_conn = get_redis_connection('cart') 30 | redis_cart = redis_conn.hgetall('cart_%s' % user.id) 31 | cart_selected = redis_conn.smembers('cart_selected_%s' % user.id) 32 | 33 | cart = {} 34 | for sku_id in cart_selected: 35 | cart[int(sku_id)] = int(redis_cart[sku_id]) 36 | 37 | # 查询商品信息 38 | skus = SKU.objects.filter(id__in=cart.keys()) 39 | for sku in skus: 40 | sku.count = cart[sku.id] 41 | 42 | # 运费 43 | freight = Decimal('10.00') 44 | 45 | serializer = OrderSettlementSerializer({'freight': freight, 'skus': skus}) 46 | return Response(serializer.data) 47 | 48 | 49 | class SaveOrderView(CreateAPIView): 50 | """ 51 | 保存订单 52 | """ 53 | permission_classes = [IsAuthenticated] 54 | serializer_class = SaveOrderSerializer 55 | 56 | 57 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/payment/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PaymentConfig(AppConfig): 5 | name = 'payment' 6 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/keys/alipay_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIFrkflOuab8sqGEaKPLAQHjMPLfGYmH2fUUjceNUWFoGrtshVtvu+Ah1qjiqWteCEQNrACKmCiPlyWfKlk5rhyKPnuqK2HLktTQ5n1fCcfK3+T03oDIEtqPiU+MTQxZna9Is5p5Jwb0s+ztCkvXTZ0N/yuTb1wrQVQ1QbWuTeev9LNQvdkNMjDESJy8/rTI20QaGyACjk5APkakx/MG4OlqwFVsTelBaivp2cH1PuNLpC9kCBYcwgaIWJarLq6qV3IH/aR2iQXDQOahNnd8NwQYrjK2gbkJ4/GYyf4XisTiGDCslntjhotag96fJaruIHmkcAZ9hFAn+6fSxlbqUQIDAQAB 3 | -----END PUBLIC KEY----- 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/keys/app_private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA79B/fWkq6h4C+Mgp+RvFoiOu3e9PoNsvIcHx7j2VsaQiO6Zk 3 | m+8DzwdbX0o1dAN6oEJ5KGxXljdTsFzBcwuXgi1kl85lMRdpn7jU0JfVLhkPb5qo 4 | DulPpBDaq/OOkxMBkEm9Fy6pkHNyL55SFSzMVEb/LJKfdtjk/wdO/wcNnAyZ/25f 5 | rUJ9Q5vSfDX76CcDR2k0mE/iMQOGuU80okyLjbTiP2CmK3flQYFgvrGmbVLipNT9 6 | Ngl1qp51/Tlm8qUuQjNb2cOn9oXAlEMH5tgmW+sg2plupXbm2xJGWe5cRIGfjRdr 7 | /Tkwshm4Yh1f4egj7GJriFsJOsUIlqRqHOqZFQIDAQABAoIBAH5h2O5UwEMFcfd+ 8 | NXCMzEi4mW1oswRIvrwBo+g0GHG32DqrZBYxjkZK0EKVrznmo3dV0NgW0MnfeoFV 9 | QLXKhBwcpAjEwttuRUHhfHY5riVPG61rSeoh1tDV+QfpoVetCoPp+HcBJmd0D9c8 10 | jnGOXFiF2fC5jMRrZbNpPJ52LxvvGu+/lOUZpO6q2Hxek+eGHfTGtb3Io/BOgyaa 11 | lpLwAxJj9LvFvXILC6Ycpz841jBIhQ9hiq5EbDymNl+tW7774NrPsEsRs9U/nmxV 12 | 6Iwttrt3/GtGBtby4aMjLD+iBRzRj6BFcoy/IGFJIp/tTqug9lsEBQDEP+3y3qed 13 | RnmFpwECgYEA/qIxjaKSQspyYnOLfYJJB7x0iMG4bD0SLbFabhm5M1oLeNifbr36 14 | hks9GewPSwqZ30YtB82u3A+0pOqUUGsWepn2bfjO8mWr8rdEwJHdHfqnjj4abcOH 15 | D8r7TVq27Etbmyw2VCpPXprSLxJcrfL2/w+BZuvox+HqpGrPUqroTLUCgYEA8Rny 16 | S1BbvqipEN4zklIqYDUpsZnpxF29d4f3KxcEd+SgRYslEEsUOY1DqKYhlUQa+gf9 17 | Mpa3StX/3hqqsiMccAnladksVoU/Wksxvezh/OHjf1/67vAgMUSuIBFUbZhbIQy+ 18 | Yia3ng4Ek226VL2+NT5nqqMMc7Qevc9UXzftNuECgYAjQW2/AI1jtggUXY8ot4tJ 19 | COzRqpDQW7xSm0W8DM+5rnP8LXIVsTQot+4j0q1jZHpdkafWdsIJMht+4gPbBpi8 20 | LOkT3Ok6Yp1mI73fM9L7vaLJbQvXusTOiPo2gaLmIKD0YTC8gocuwBzt64rtgsB0 21 | HD98cpluY3dLgIZoGcXEKQKBgQC04OFczVG3DPOhmwIXwRrXiKrZ+f1d+3GcTl4J 22 | bsBEbjorUkOhIKJwtuFOkixZvnl6oSm8nUOvmjLWXy02bY71IweAzJQt6NNpm0Yb 23 | Fa9JN/Kj6c7pGS8dp0f2+OldO1MKzXO7UQ6qPhwCuuxyjNM5QLMnWiGKYFQ71GKY 24 | +O03IQKBgCTuPjC2d7ra3PaIQNuXVKq9yKidBlVMAH5QxEfrSjodwVHtpob685Op 25 | gNa0yWEbgSKFiOIwcgbz2OVV3PD9NL+51AtGJ7cxmuJ6L4XntzaFe/EFMxqVeq4u 26 | XFZO1yBnWpADMcIL+4iu+1cPg/bRu6JPnguOamjN28ziNRC6v1sQ 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/keys/app_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA79B/fWkq6h4C+Mgp+RvF 3 | oiOu3e9PoNsvIcHx7j2VsaQiO6Zkm+8DzwdbX0o1dAN6oEJ5KGxXljdTsFzBcwuX 4 | gi1kl85lMRdpn7jU0JfVLhkPb5qoDulPpBDaq/OOkxMBkEm9Fy6pkHNyL55SFSzM 5 | VEb/LJKfdtjk/wdO/wcNnAyZ/25frUJ9Q5vSfDX76CcDR2k0mE/iMQOGuU80okyL 6 | jbTiP2CmK3flQYFgvrGmbVLipNT9Ngl1qp51/Tlm8qUuQjNb2cOn9oXAlEMH5tgm 7 | W+sg2plupXbm2xJGWe5cRIGfjRdr/Tkwshm4Yh1f4egj7GJriFsJOsUIlqRqHOqZ 8 | FQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/payment/migrations/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from tb_store.utils.models import BaseModel 4 | from orders.models import OrderInfo 5 | 6 | # Create your models here. 7 | 8 | 9 | class Payment(BaseModel): 10 | """ 11 | 支付信息 12 | """ 13 | order = models.ForeignKey(OrderInfo, on_delete=models.CASCADE, verbose_name='订单') 14 | trade_id = models.CharField(max_length=100, unique=True, null=True, blank=True, verbose_name="支付编号") 15 | 16 | class Meta: 17 | db_table = 'tb_payment' 18 | verbose_name = '支付信息' 19 | verbose_name_plural = verbose_name -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | url(r'^orders/(?P\d+)/payment/$', views.PaymentView.as_view()), 7 | url(r'^payment/status/$', views.PaymentStatusView.as_view()) 8 | ] -------------------------------------------------------------------------------- /tb_store/tb_store/apps/payment/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | from alipay import AliPay 3 | from rest_framework import status 4 | from rest_framework.permissions import IsAuthenticated 5 | from rest_framework.response import Response 6 | from rest_framework.views import APIView 7 | 8 | from orders.models import OrderInfo 9 | from payment.models import Payment 10 | from tb_store.settings import dev 11 | 12 | 13 | class PaymentView(APIView): 14 | """ 15 | 支付 16 | """ 17 | permission_classes = (IsAuthenticated,) 18 | 19 | def get(self, request, order_id): 20 | """ 21 | 获取支付链接 22 | """ 23 | # 判断订单信息是否正确 24 | try: 25 | order = OrderInfo.objects.get(order_id=order_id, user=request.user, 26 | pay_method=OrderInfo.PAY_METHODS_ENUM["ALIPAY"], 27 | status=OrderInfo.ORDER_STATUS_ENUM["UNPAID"]) 28 | except OrderInfo.DoesNotExist: 29 | return Response({'message': '订单信息有误'}, status=status.HTTP_400_BAD_REQUEST) 30 | 31 | # 构造支付宝支付链接地址 32 | alipay = AliPay( 33 | appid=dev.ALIPAY_APPID, 34 | app_notify_url=None, # 默认回调url 35 | app_private_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/app_private_key.pem"), 36 | alipay_public_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), 37 | "keys/alipay_public_key.pem"), # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 38 | sign_type="RSA2", # RSA 或者 RSA2 39 | debug=dev.ALIPAY_DEBUG # 默认False 40 | ) 41 | 42 | order_string = alipay.api_alipay_trade_page_pay( 43 | out_trade_no=order_id, 44 | total_amount=str(order.total_amount), 45 | subject="东京商城%s" % order_id, 46 | return_url="http://www.meiduo.site:8080/pay_success.html", 47 | ) 48 | # 需要跳转到https://openapi.alipay.com/gateway.do? + order_string 49 | # 拼接链接返回前端 50 | alipay_url = dev.ALIPAY_URL + "?" + order_string 51 | return Response({'alipay_url': alipay_url}) 52 | 53 | 54 | # 支付宝回调函数接口 put /payment/status/?支付宝参数 55 | class PaymentStatusView(APIView): 56 | def put(self, request): 57 | # 接收参数,效验参数 58 | # 构造支付宝支付链接地址 59 | alipay_req_data = request.query_params 60 | if not alipay_req_data: 61 | return Response({"message": "缺啥参数"}, status=status.HTTP_400_BAD_REQUEST) 62 | alipay_req_dict = alipay_req_data.dict() 63 | sign = alipay_req_dict.pop("sign") 64 | 65 | alipay = AliPay( 66 | appid=dev.ALIPAY_APPID, 67 | app_notify_url=None, # 默认回调url 68 | app_private_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/app_private_key.pem"), 69 | alipay_public_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), 70 | "keys/alipay_public_key.pem"), # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 71 | sign_type="RSA2", # RSA 或者 RSA2 72 | debug=dev.ALIPAY_DEBUG # 默认False 73 | ) 74 | 75 | result = alipay.verify(alipay_req_dict, sign) 76 | # 保存数据,保存支付结果数据 77 | if result: 78 | order_id = alipay_req_dict.get("out_trade_no") 79 | trade_id = alipay_req_dict.get("trade_no") 80 | # 修改订单状态 81 | Payment.objects.create( 82 | order_id=order_id, 83 | trade_id=trade_id 84 | ) 85 | OrderInfo.objects.filter(order_id=order_id).update(status=OrderInfo.ORDER_STATUS_ENUM["UNCOMMENT"]) 86 | return Response({"trade_id": trade_id}) 87 | else: 88 | return Response({"message": "参数有误"}, status=status.HTTP_400_BAD_REQUEST) -------------------------------------------------------------------------------- /tb_store/tb_store/apps/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/users/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/users/constants.py: -------------------------------------------------------------------------------- 1 | VERIFY_EMAIL_TOKEN_EXPIRES = 24 * 60 * 60 2 | 3 | USER_ADDRESS_COUNTS_LIMIT = 20 4 | 5 | USER_BROWSING_HISTORY_COUNTS_LIMIT = 5 -------------------------------------------------------------------------------- /tb_store/tb_store/apps/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/users/migrations/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/users/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | from django.contrib.auth.models import AbstractUser 4 | from itsdangerous import TimedJSONWebSignatureSerializer as TJWSSerializer, BadData 5 | 6 | # Create your models here. 7 | from tb_store.utils.models import BaseModel 8 | from users import constants 9 | 10 | 11 | class User(AbstractUser): 12 | """ 13 | 用户模型类 14 | """ 15 | mobile = models.CharField(max_length=11, unique=True, verbose_name="手机号") 16 | email_active = models.BooleanField(default=False, verbose_name='邮箱验证状态') 17 | default_address = models.ForeignKey('Address', related_name='users', null=True, blank=True, 18 | on_delete=models.SET_NULL, verbose_name='默认地址') 19 | 20 | # 添加额外说明,flask中的__table__也是这个作用 21 | class Meta: 22 | db_table = "tb_users" 23 | verbose_name = "用户" 24 | # 显示有用户名的时候,不以复数形式显示 25 | verbose_name_plural = verbose_name 26 | 27 | def generate_verify_email_url(self): 28 | """ 29 | 生成验证邮箱的url 30 | """ 31 | serializer = TJWSSerializer(settings.SECRET_KEY, expires_in=constants.VERIFY_EMAIL_TOKEN_EXPIRES) 32 | data = {'user_id': self.id, 'email': self.email} 33 | token = serializer.dumps(data).decode() 34 | verify_url = 'http://www.meiduo.site:8080/success_verify_email.html?token=' + token 35 | return verify_url 36 | 37 | @staticmethod 38 | def check_verify_email_token(token): 39 | """ 40 | 检查验证邮件的token 41 | :return: 42 | """ 43 | serializer = TJWSSerializer(settings.SECRET_KEY, expires_in=constants.VERIFY_EMAIL_TOKEN_EXPIRES) 44 | try: 45 | data = serializer.loads(token) 46 | except BadData: 47 | return None 48 | else: 49 | email = data.get("email") 50 | user_id = data.get("user_id") 51 | try: 52 | user = User.objects.get(id=user_id, email=email) 53 | except User.DoesNotExist: 54 | return None 55 | else: 56 | return user 57 | 58 | 59 | class Address(BaseModel): 60 | """ 61 | 用户地址 62 | """ 63 | user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='addresses', verbose_name='用户') 64 | title = models.CharField(max_length=20, verbose_name='地址名称') 65 | receiver = models.CharField(max_length=20, verbose_name='收货人') 66 | province = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='province_addresses', 67 | verbose_name='省') 68 | city = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='city_addresses', verbose_name='市') 69 | district = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='district_addresses', 70 | verbose_name='区') 71 | place = models.CharField(max_length=50, verbose_name='地址') 72 | mobile = models.CharField(max_length=11, verbose_name='手机') 73 | tel = models.CharField(max_length=20, null=True, blank=True, default='', verbose_name='固定电话') 74 | email = models.CharField(max_length=30, null=True, blank=True, default='', verbose_name='电子邮箱') 75 | is_deleted = models.BooleanField(default=False, verbose_name='逻辑删除') 76 | 77 | class Meta: 78 | db_table = 'tb_address' 79 | verbose_name = '用户地址' 80 | verbose_name_plural = verbose_name 81 | ordering = ['-update_time'] 82 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from rest_framework import routers 3 | 4 | from . import views 5 | from rest_framework_jwt.views import obtain_jwt_token 6 | 7 | urlpatterns = [ 8 | url(r'^usernames/(?P\w{5,20})/count/$', views.UsernameCountView.as_view()), 9 | url(r'^mobiles/(?P1[3-9]\d{9})/count/$', views.MobileCountView.as_view()), 10 | url(r'^usernames/$', views.UserView.as_view()), 11 | # url(r'^authorizations/$', obtain_jwt_token), 12 | url(r"^user/$", views.UserDetailView.as_view()), 13 | url(r"^email/$", views.EmailView.as_view()), 14 | url(r'^emails/verification/$', views.VerifyEmailView.as_view()), 15 | url(r'^browse_histories/$', views.UserBrowsingHistoryView.as_view()), 16 | url(r'^password/$', views.PassWord2.as_view()), 17 | url(r'^authorizations/$', views.UserAuthorizeView.as_view()), 18 | ] 19 | router = routers.DefaultRouter() 20 | router.register(r'addresses', views.AddressViewSet, base_name='addresses') 21 | 22 | urlpatterns += router.urls 23 | # POST /addresses/ 新建 -> create 24 | # PUT /addresses// 修改 -> update 25 | # GET /addresses/ 查询 -> list 26 | # DELETE /addresses// 删除 -> destroy 27 | # PUT /addresses//status/ 设置默认 -> status 28 | # PUT /addresses//title/ 设置标题 -> title -------------------------------------------------------------------------------- /tb_store/tb_store/apps/users/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.contrib.auth.backends import ModelBackend 4 | 5 | from users.models import User 6 | 7 | 8 | def jwt_response_payload_handler(token, user=None, request=None): 9 | """ 10 | 自定义jwt认证成功返回数据 11 | """ 12 | return { 13 | 'token': token, 14 | 'user_id': user.id, 15 | 'username': user.username 16 | } 17 | 18 | 19 | def get_user_by_account(account): 20 | """ 21 | 根据账号获取user对象 22 | :param account 账号,可以是用户名或手机号 23 | :return user 对象 24 | :return: 25 | """ 26 | try: 27 | if re.match('^1[3-9]\d{9}$', account): 28 | # 用户名是手机号 29 | print(account) 30 | user = User.objects.get(mobile=account) 31 | else: 32 | # 账号是用户名 33 | print(account) 34 | user = User.objects.get(username=account) 35 | except User.DoesNotExist: 36 | return None 37 | else: 38 | return user 39 | 40 | class UsernameMobileAuthBackend(ModelBackend): 41 | """ 42 | 自定义用户名或手机号认证 43 | """ 44 | def authenticate(self, request, username=None, password=None, **kwargs): 45 | user = get_user_by_account(username) 46 | print(username, password) 47 | if user is not None and user.check_password(password): 48 | return user -------------------------------------------------------------------------------- /tb_store/tb_store/apps/verifications/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/verifications/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/verifications/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/verifications/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class VerificationsConfig(AppConfig): 5 | name = 'verifications' 6 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/verifications/constants.py: -------------------------------------------------------------------------------- 1 | # 图片验证码过期时间 2 | IMAGE_CODE_REDIS_EXPIRES = 5 * 60 3 | 4 | # 短信验证码过期时间 5 | SMS_CODE_REDIS_EXPIRES = 5 * 60 6 | 7 | # 发送间隔 8 | SEND_SMS_CODE_INTERVAL = 60 9 | 10 | # 短信验证码模板常量 11 | SMS_CODE_TEMP_ID = 1 -------------------------------------------------------------------------------- /tb_store/tb_store/apps/verifications/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/apps/verifications/migrations/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/apps/verifications/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here.a 4 | 5 | 6 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/verifications/serializers.py: -------------------------------------------------------------------------------- 1 | from django_redis import get_redis_connection 2 | from rest_framework import serializers 3 | 4 | 5 | class ImageCodeCheckSerializer(serializers.Serializer): 6 | """ 7 | 图片验证码校验序列化器 8 | """ 9 | image_code_id = serializers.UUIDField() 10 | text = serializers.CharField(max_length=4, min_length=4) 11 | 12 | def validate(self, attrs): 13 | """ 14 | 校验 15 | """ 16 | image_code_id = attrs['image_code_id'] 17 | text = attrs['text'] 18 | # 查询真实图片验证码 19 | redis_conn = get_redis_connection('verify_codes') 20 | real_image_code_text = redis_conn.get('img_%s' % image_code_id) 21 | if not real_image_code_text: 22 | raise serializers.ValidationError('图片验证码无效') 23 | 24 | # 删除图片验证码 25 | redis_conn.delete('img_%s' % image_code_id) 26 | 27 | # 比较图片验证码 28 | real_image_code_text = real_image_code_text.decode() 29 | if real_image_code_text.lower() != text.lower(): 30 | raise serializers.ValidationError('图片验证码错误') 31 | 32 | # 判断是否在60s内 33 | mobile = self.context['view'].kwargs['mobile'] 34 | send_flag = redis_conn.get("send_flag_%s" % mobile) 35 | if send_flag: 36 | raise serializers.ValidationError('请求次数过于频繁') 37 | 38 | return attrs -------------------------------------------------------------------------------- /tb_store/tb_store/apps/verifications/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tb_store/tb_store/apps/verifications/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from . import views 3 | 4 | 5 | urlpatterns = [ 6 | url(r'^image_codes/(?P[\w-]+)/$', views.ImageCodeView.as_view()), 7 | url(r'^sms_codes/(?P1[3-9]\d{9})/$', views.SMSCodeView.as_view()) 8 | ] -------------------------------------------------------------------------------- /tb_store/tb_store/apps/verifications/views.py: -------------------------------------------------------------------------------- 1 | import random 2 | import logging 3 | from django.shortcuts import render 4 | from django.http import HttpResponse 5 | from django_redis import get_redis_connection 6 | from rest_framework import status 7 | from rest_framework.generics import GenericAPIView 8 | from rest_framework.response import Response 9 | from rest_framework.views import APIView 10 | from tb_store.libs.captcha.captcha import captcha 11 | from tb_store.utils.yuntongxun.sms import CCP 12 | 13 | from verifications import constants, serializers 14 | from celery_tasks.sms.tasks import send_sms_code 15 | 16 | 17 | # Create your views here. 18 | logger = logging.getLogger('django') 19 | 20 | class ImageCodeView(APIView): 21 | def get(self, request, image_code_id): 22 | # 接收参数校验参数,这一步已经在url匹配中实现了 23 | # 生成图片验证码 24 | text, image = captcha.generate_captcha() 25 | redis_conn = get_redis_connection('verify_codes') 26 | redis_conn.setex('img_%s' % image_code_id, constants.IMAGE_CODE_REDIS_EXPIRES, text) 27 | print(text) 28 | 29 | # 返回给前端数据 30 | return HttpResponse(image, content_type='image/jpg') 31 | 32 | 33 | class SMSCodeView(GenericAPIView): 34 | """ 35 | 短信验证码接口 36 | """ 37 | serializer_class = serializers.ImageCodeCheckSerializer 38 | 39 | def get(self, request, mobile): 40 | """ 41 | 创建短信验证码 42 | """ 43 | # 判断图片验证码, 判断是否在60s内 44 | serializer = self.get_serializer(data=request.query_params) 45 | serializer.is_valid(raise_exception=True) 46 | 47 | # 生成短信验证码 48 | sms_code = "%06d" % random.randint(0, 999999) 49 | 50 | # 保存短信验证码与发送记录,使用redis管道 51 | redis_conn = get_redis_connection('verify_codes') 52 | pl = redis_conn.pipeline() 53 | pl.setex("sms_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code) 54 | pl.setex("send_flag_%s" % mobile, constants.SEND_SMS_CODE_INTERVAL, 1) 55 | # 让管道通知redis执行命令 56 | pl.execute() 57 | 58 | # 发送短信验证码 59 | # try: 60 | # sms_code_expires = str(constants.SMS_CODE_REDIS_EXPIRES // 60) 61 | # ccp = CCP() 62 | # result = ccp.send_template_sms(mobile, [sms_code, sms_code_expires], constants.SMS_CODE_TEMP_ID) 63 | # except Exception as e: 64 | # logger.info("发送验证码异常mobile:%s, message:%s" % (mobile, e), status=status.HTTP_500_INTERNAL_SERVER_ERROR) 65 | # else: 66 | # if result == 0: 67 | # logger.info("发送验证码短信正常mobile:%s" % mobile) 68 | # return Response({"message": "OK"}) 69 | # else: 70 | # logger.warning("发送短信验证码失败mobile:%s" % mobile) 71 | # return Response({"message": "failed"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) 72 | # 使用celery队列发送短信验证码 73 | sms_code_expires = str(constants.SMS_CODE_REDIS_EXPIRES // 60) 74 | send_sms_code.delay(mobile, sms_code, sms_code_expires) 75 | print(sms_code) 76 | return Response({"message": "OK"}) -------------------------------------------------------------------------------- /tb_store/tb_store/libs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/libs/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/libs/captcha/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/libs/captcha/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/libs/captcha/fonts/Arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/libs/captcha/fonts/Arial.ttf -------------------------------------------------------------------------------- /tb_store/tb_store/libs/captcha/fonts/Georgia.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/libs/captcha/fonts/Georgia.ttf -------------------------------------------------------------------------------- /tb_store/tb_store/libs/captcha/fonts/actionj.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/libs/captcha/fonts/actionj.ttf -------------------------------------------------------------------------------- /tb_store/tb_store/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/settings/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/templates/search/indexes/goods/sku_text.txt: -------------------------------------------------------------------------------- 1 | {{ object.name }} 2 | {{ object.caption }} 3 | {{ object.id }} -------------------------------------------------------------------------------- /tb_store/tb_store/urls.py: -------------------------------------------------------------------------------- 1 | """tb_store URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/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: url(r'^$', 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: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | from django.contrib import admin 18 | import xadmin 19 | 20 | urlpatterns = [ 21 | url(r'xadmin/', include(xadmin.site.urls)), 22 | url(r'^admin/', admin.site.urls), 23 | url(r'', include('verifications.urls')), 24 | url(r'', include('users.urls')), 25 | url(r'^oauth/', include('oauth.urls')), 26 | url(r"", include("areas.urls")), 27 | url(r'^ckeditor/', include('ckeditor_uploader.urls')), 28 | url(r'', include("goods.urls")), 29 | url(r'', include("carts.urls")), 30 | url(r'', include("orders.urls")), 31 | url(r'', include("payment.urls")), 32 | ] 33 | -------------------------------------------------------------------------------- /tb_store/tb_store/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/utils/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/utils/db_router.py: -------------------------------------------------------------------------------- 1 | class MasterSlaveDBRouter(object): 2 | """数据库主从读写分离路由""" 3 | 4 | def db_for_read(self, model, **hints): 5 | """读数据库""" 6 | return "slave" 7 | 8 | def db_for_write(self, model, **hints): 9 | """写数据库""" 10 | return "default" 11 | 12 | def allow_relation(self, obj1, obj2, **hints): 13 | """是否运行关联操作""" 14 | return True 15 | -------------------------------------------------------------------------------- /tb_store/tb_store/utils/exceptions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.views import exception_handler as drf_exception_handler 2 | import logging 3 | from django.db import DatabaseError 4 | from redis.exceptions import RedisError 5 | from rest_framework.response import Response 6 | from rest_framework import status 7 | 8 | # 获取在配置文件中定义的logger,用来记录日志 9 | logger = logging.getLogger('django') 10 | 11 | def exception_handler(exc, context): 12 | """ 13 | 自定义异常处理 14 | :param exc: 异常 15 | :param context: 抛出异常的上下文 16 | :return: Response响应对象 17 | """ 18 | # 调用drf框架原生的异常处理方法,如果不是404和视图异常,就返回None,说明是服务器错误 19 | response = drf_exception_handler(exc, context) 20 | 21 | # 这里只捕获服务器错误 22 | if response is None: 23 | view = context['view'] 24 | if isinstance(exc, DatabaseError) or isinstance(exc, RedisError): 25 | # 数据库异常 26 | logger.error('[%s] %s' % (view, exc)) 27 | response = Response({'message': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE) 28 | 29 | return response -------------------------------------------------------------------------------- /tb_store/tb_store/utils/fastdfs/client.conf: -------------------------------------------------------------------------------- 1 | # connect timeout in seconds 2 | # default value is 30s 3 | connect_timeout=30 4 | 5 | # network timeout in seconds 6 | # default value is 30s 7 | network_timeout=60 8 | 9 | # the base path to store log files 10 | base_path=/home/python/Desktop/Django-Store/tb_store/logs 11 | 12 | # tracker_server can ocur more than once, and tracker_server format is 13 | # "host:port", host can be hostname or ip address 14 | tracker_server=192.168.11.68:22122 15 | 16 | #standard log level as syslog, case insensitive, value list: 17 | ### emerg for emergency 18 | ### alert 19 | ### crit for critical 20 | ### error 21 | ### warn for warning 22 | ### notice 23 | ### info 24 | ### debug 25 | log_level=info 26 | 27 | # if use connection pool 28 | # default value is false 29 | # since V4.05 30 | use_connection_pool = false 31 | 32 | # connections whose the idle time exceeds this time will be closed 33 | # unit: second 34 | # default value is 3600 35 | # since V4.05 36 | connection_pool_max_idle_time = 3600 37 | 38 | # if load FastDFS parameters from tracker server 39 | # since V4.05 40 | # default value is false 41 | load_fdfs_parameters_from_tracker=false 42 | 43 | # if use storage ID instead of IP address 44 | # same as tracker.conf 45 | # valid only when load_fdfs_parameters_from_tracker is false 46 | # default value is false 47 | # since V4.05 48 | use_storage_id = false 49 | 50 | # specify storage ids filename, can use relative or absolute path 51 | # same as tracker.conf 52 | # valid only when load_fdfs_parameters_from_tracker is false 53 | # since V4.05 54 | storage_ids_filename = storage_ids.conf 55 | 56 | 57 | #HTTP settings 58 | http.tracker_server_port=80 59 | 60 | #use "#include" directive to include HTTP other settiongs 61 | ##include http.conf 62 | -------------------------------------------------------------------------------- /tb_store/tb_store/utils/fastdfs/fdfs_storage.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.files.storage import Storage 3 | from django.utils.deconstruct import deconstructible 4 | from fdfs_client.client import Fdfs_client 5 | 6 | 7 | @deconstructible 8 | class FastDFSStorage(Storage): 9 | def __init__(self, base_url=None, client_conf=None): 10 | """ 11 | 初始化 12 | :param base_url: 用于构造图片完整路径使用,图片服务器的域名 13 | :param client_conf: FastDFS客户端配置文件的路径 14 | """ 15 | if base_url is None: 16 | base_url = settings.FDFS_URL 17 | self.base_url = base_url 18 | if client_conf is None: 19 | client_conf = settings.FDFS_CLIENT_CONF 20 | self.client_conf = client_conf 21 | 22 | def _open(self, name, mode='rb'): 23 | """ 24 | 用不到打开文件,所以省略 25 | """ 26 | pass 27 | 28 | def _save(self, name, content): 29 | """ 30 | 在FastDFS中保存文件 31 | :param name: 传入的文件名 32 | :param content: 文件内容 33 | :return: 保存到数据库中的FastDFS的文件名 34 | """ 35 | client = Fdfs_client(self.client_conf) 36 | ret = client.upload_by_buffer(content.read()) 37 | if ret.get("Status") != "Upload successed.": 38 | raise Exception("upload file failed") 39 | file_name = ret.get("Remote file_id") 40 | return file_name 41 | 42 | def url(self, name): 43 | """ 44 | 返回文件的完整URL路径 45 | :param name: 数据库中保存的文件名 46 | :return: 完整的URL 47 | """ 48 | return self.base_url + name 49 | 50 | def exists(self, name): 51 | """ 52 | 判断文件是否存在,FastDFS可以自行解决文件的重名问题 53 | 所以此处返回False,告诉Django上传的都是新文件 54 | :param name: 文件名 55 | :return: False 56 | """ 57 | return False -------------------------------------------------------------------------------- /tb_store/tb_store/utils/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class BaseModel(models.Model): 5 | """ 6 | 为模型类补充字段 7 | """ 8 | create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") 9 | update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") 10 | 11 | class Meta: 12 | abstract = True # 指明是抽象类模型,用于集成 13 | 14 | -------------------------------------------------------------------------------- /tb_store/tb_store/utils/pagination.py: -------------------------------------------------------------------------------- 1 | from rest_framework.pagination import PageNumberPagination 2 | 3 | 4 | class StandardResultsSetPagination(PageNumberPagination): 5 | page_size = 2 6 | page_size_query_param = "page_size" 7 | max_page_size = 20 -------------------------------------------------------------------------------- /tb_store/tb_store/utils/yuntongxun/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sjj1024/Django-Store/f68b7dd657b2a9c5769a3a657be083dad5b2ff11/tb_store/tb_store/utils/yuntongxun/__init__.py -------------------------------------------------------------------------------- /tb_store/tb_store/utils/yuntongxun/sms.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from .CCPRestSDK import REST 4 | 5 | # 说明:主账号,登陆云通讯网站后,可在"控制台-应用"中看到开发者主账号ACCOUNT SID 6 | _accountSid = '8a216da86b2bc78f016b361c93d8025f' 7 | 8 | # 说明:主账号Token,登陆云通讯网站后,可在控制台-应用中看到开发者主账号AUTH TOKEN 9 | _accountToken = '78637b84e5544705bda2aca3426c88f5' 10 | 11 | # 请使用管理控制台首页的APPID或自己创建应用的APPID 12 | _appId = '8a216da86b2bc78f016b361c943e0266' 13 | 14 | # 说明:请求地址,生产环境配置成app.cloopen.com 15 | _serverIP = 'sandboxapp.cloopen.com' 16 | 17 | # 说明:请求端口 ,生产环境为8883 18 | _serverPort = "8883" 19 | 20 | # 说明:REST API版本号保持不变 21 | _softVersion = '2013-12-26' 22 | 23 | # 云通讯官方提供的发送短信代码实例 24 | # # 发送模板短信 25 | # # @param to 手机号码 26 | # # @param datas 内容数据 格式为数组 例如:{'12','34'},如不需替换请填 '' 27 | # # @param $tempId 模板Id 28 | # 29 | # def sendTemplateSMS(to, datas, tempId): 30 | # # 初始化REST SDK 31 | # rest = REST(serverIP, serverPort, softVersion) 32 | # rest.setAccount(accountSid, accountToken) 33 | # rest.setAppId(appId) 34 | # 35 | # result = rest.sendTemplateSMS(to, datas, tempId) 36 | # for k, v in result.iteritems(): 37 | # 38 | # if k == 'templateSMS': 39 | # for k, s in v.iteritems(): 40 | # print '%s:%s' % (k, s) 41 | # else: 42 | # print '%s:%s' % (k, v) 43 | 44 | 45 | class CCP(object): 46 | """发送短信的辅助类""" 47 | 48 | def __new__(cls, *args, **kwargs): 49 | # 判断是否存在类属性_instance,_instance是类CCP的唯一对象,即单例 50 | if not hasattr(CCP, "_instance"): 51 | cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs) 52 | cls._instance.rest = REST(_serverIP, _serverPort, _softVersion) 53 | cls._instance.rest.setAccount(_accountSid, _accountToken) 54 | cls._instance.rest.setAppId(_appId) 55 | return cls._instance 56 | 57 | def send_template_sms(self, to, datas, temp_id): 58 | """发送模板短信""" 59 | # @param to 手机号码 60 | # @param datas 内容数据 格式为数组 例如:{'12','34'},如不需替换请填 '' 61 | # @param temp_id 模板Id 62 | result = self.rest.sendTemplateSMS(to, datas, temp_id) 63 | # 如果云通讯发送短信成功,返回的字典数据result中statuCode字段的值为"000000" 64 | if result.get("statusCode") == "000000": 65 | # 返回0 表示发送短信成功 66 | return 0 67 | else: 68 | # 返回-1 表示发送失败 69 | return -1 70 | 71 | 72 | if __name__ == '__main__': 73 | ccp = CCP() 74 | # 注意: 测试的短信模板编号为1 75 | ccp.send_template_sms('15670339118', ['1234', 5], 1) 76 | -------------------------------------------------------------------------------- /tb_store/tb_store/utils/yuntongxun/xmltojson.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # python xml.etree.ElementTree 3 | 4 | import os 5 | import xml.etree.ElementTree as ET 6 | from xml.dom import minidom 7 | 8 | 9 | class xmltojson: 10 | # global var 11 | # show log 12 | SHOW_LOG = True 13 | # XML file 14 | XML_PATH = None 15 | a = {} 16 | m = [] 17 | 18 | def get_root(self, path): 19 | '''parse the XML file,and get the tree of the XML file 20 | finally,return the root element of the tree. 21 | if the XML file dose not exist,then print the information''' 22 | # if os.path.exists(path): 23 | # if SHOW_LOG: 24 | # print('start to parse the file : [{}]'.format(path)) 25 | tree = ET.fromstring(path) 26 | return tree 27 | # else: 28 | # print('the path [{}] dose not exist!'.format(path)) 29 | 30 | def get_element_tag(self, element): 31 | '''return the element tag if the element is not None.''' 32 | if element is not None: 33 | 34 | return element.tag 35 | else: 36 | print('the element is None!') 37 | 38 | def get_element_attrib(self, element): 39 | '''return the element attrib if the element is not None.''' 40 | if element is not None: 41 | 42 | return element.attrib 43 | else: 44 | print('the element is None!') 45 | 46 | def get_element_text(self, element): 47 | '''return the text of the element.''' 48 | if element is not None: 49 | return element.text 50 | else: 51 | print('the element is None!') 52 | 53 | def get_element_children(self, element): 54 | '''return the element children if the element is not None.''' 55 | if element is not None: 56 | 57 | return [c for c in element] 58 | else: 59 | print('the element is None!') 60 | 61 | def get_elements_tag(self, elements): 62 | '''return the list of tags of element's tag''' 63 | if elements is not None: 64 | tags = [] 65 | for e in elements: 66 | tags.append(e.tag) 67 | return tags 68 | else: 69 | print('the elements is None!') 70 | 71 | def get_elements_attrib(self, elements): 72 | '''return the list of attribs of element's attrib''' 73 | if elements is not None: 74 | attribs = [] 75 | for a in elements: 76 | attribs.append(a.attrib) 77 | return attribs 78 | else: 79 | print('the elements is None!') 80 | 81 | def get_elements_text(self, elements): 82 | '''return the dict of element''' 83 | if elements is not None: 84 | text = [] 85 | for t in elements: 86 | text.append(t.text) 87 | return dict(zip(self.get_elements_tag(elements), text)) 88 | else: 89 | print('the elements is None!') 90 | 91 | def main(self, xml): 92 | # root 93 | root = self.get_root(xml) 94 | 95 | # children 96 | children = self.get_element_children(root) 97 | 98 | children_tags = self.get_elements_tag(children) 99 | 100 | children_attribs = self.get_elements_attrib(children) 101 | 102 | i = 0 103 | 104 | # 获取二级元素的每一个子节点的名称和值 105 | for c in children: 106 | p = 0 107 | c_children = self.get_element_children(c) 108 | dict_text = self.get_elements_text(c_children) 109 | if dict_text: 110 | # print (children_tags[i]) 111 | if children_tags[i] == 'TemplateSMS': 112 | self.a['templateSMS'] = dict_text 113 | else: 114 | if children_tags[i] == 'SubAccount': 115 | k = 0 116 | 117 | for x in children: 118 | if children_tags[k] == 'totalCount': 119 | self.m.append(dict_text) 120 | self.a['SubAccount'] = self.m 121 | p = 1 122 | k = k + 1 123 | if p == 0: 124 | self.a[children_tags[i]] = dict_text 125 | else: 126 | self.a[children_tags[i]] = dict_text 127 | 128 | 129 | else: 130 | self.a[children_tags[i]] = c.text 131 | i = i + 1 132 | return self.a 133 | 134 | def main2(self, xml): 135 | # root 136 | root = self.get_root(xml) 137 | 138 | # children 139 | children = self.get_element_children(root) 140 | 141 | children_tags = self.get_elements_tag(children) 142 | 143 | children_attribs = self.get_elements_attrib(children) 144 | 145 | i = 0 146 | 147 | # 获取二级元素的每一个子节点的名称和值 148 | for c in children: 149 | p = 0 150 | c_children = self.get_element_children(c) 151 | dict_text = self.get_elements_text(c_children) 152 | if dict_text: 153 | if children_tags[i] == 'TemplateSMS': 154 | k = 0 155 | 156 | for x in children: 157 | if children_tags[k] == 'totalCount': 158 | self.m.append(dict_text) 159 | self.a['TemplateSMS'] = self.m 160 | p = 1 161 | k = k + 1 162 | if p == 0: 163 | self.a[children_tags[i]] = dict_text 164 | else: 165 | self.a[children_tags[i]] = dict_text 166 | 167 | else: 168 | self.a[children_tags[i]] = c.text 169 | i = i + 1 170 | return self.a 171 | -------------------------------------------------------------------------------- /tb_store/tb_store/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for tb_store 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/1.11/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", "tb_store.settings") 15 | 16 | application = get_wsgi_application() 17 | --------------------------------------------------------------------------------