├── admin └── personal_pay │ ├── static │ └── .gitkeep │ ├── .eslintignore │ ├── config │ ├── prod.env.js │ ├── dev.env.js │ └── index.js │ ├── dist │ └── favicon.ico │ ├── src │ ├── images │ │ └── logo.png │ ├── App.vue │ ├── store │ │ └── store.js │ ├── css │ │ └── common.css │ ├── main.js │ ├── util │ │ ├── common.js │ │ ├── qcloud.js │ │ └── api.js │ ├── components │ │ ├── common │ │ │ ├── 404.vue │ │ │ ├── sbUpload.vue │ │ │ ├── login.vue │ │ │ └── reg.vue │ │ └── admin │ │ │ ├── merchant │ │ │ └── detail.vue │ │ │ ├── product │ │ │ ├── addStock.vue │ │ │ ├── stockList.vue │ │ │ ├── addProduct.vue │ │ │ └── productList.vue │ │ │ ├── admin.vue │ │ │ └── order │ │ │ └── orderList.vue │ └── router │ │ └── index.js │ ├── .editorconfig │ ├── .postcssrc.js │ ├── index.html │ ├── build │ ├── dev-client.js │ ├── vue-loader.conf.js │ ├── build.js │ ├── webpack.dev.conf.js │ ├── check-versions.js │ ├── webpack.base.conf.js │ ├── utils.js │ ├── dev-server.js │ └── webpack.prod.conf.js │ ├── .babelrc │ ├── .eslintrc.js │ └── package.json ├── api ├── api_personal_pay │ ├── libs │ │ ├── __init__.py │ │ └── qcloud_cos │ │ │ ├── __init__.py │ │ │ ├── cos_cred.py │ │ │ ├── cos_auth.py │ │ │ └── cos_params_check.py │ ├── model │ │ ├── __init__.py │ │ └── response.py │ ├── table │ │ ├── __init__.py │ │ └── model.py │ ├── util │ │ ├── __init__.py │ │ ├── db.py │ │ ├── safedict.py │ │ ├── log.py │ │ ├── valid.py │ │ ├── convertUtil.py │ │ ├── redis.py │ │ ├── store.py │ │ ├── checkUtil.py │ │ ├── emailUtil.py │ │ ├── validImage.py │ │ └── commonUtil.py │ ├── config │ │ ├── __init__.py │ │ └── config.py │ ├── resources │ │ ├── __init__.py │ │ ├── baseApi.py │ │ ├── common.py │ │ ├── confirm.py │ │ ├── merchant.py │ │ ├── client.py │ │ ├── order.py │ │ └── product.py │ ├── src │ │ └── font │ │ │ └── Verdana Italic.ttf │ ├── requirements │ ├── .idea │ │ ├── misc.xml │ │ ├── inspectionProfiles │ │ │ └── profiles_settings.xml │ │ ├── modules.xml │ │ └── site1.iml │ └── index.py └── personal_pay_2018-05-23.sql ├── home ├── favicon.ico ├── index.html ├── confirm.html └── buy.html └── README.md /admin/personal_pay/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/api_personal_pay/libs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/api_personal_pay/model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/api_personal_pay/table/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/api_personal_pay/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/api_personal_pay/resources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admin/personal_pay/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /home/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichDaddyCashMany/PersonalPay/HEAD/home/favicon.ico -------------------------------------------------------------------------------- /admin/personal_pay/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /admin/personal_pay/dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichDaddyCashMany/PersonalPay/HEAD/admin/personal_pay/dist/favicon.ico -------------------------------------------------------------------------------- /admin/personal_pay/src/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichDaddyCashMany/PersonalPay/HEAD/admin/personal_pay/src/images/logo.png -------------------------------------------------------------------------------- /api/api_personal_pay/src/font/Verdana Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichDaddyCashMany/PersonalPay/HEAD/api/api_personal_pay/src/font/Verdana Italic.ttf -------------------------------------------------------------------------------- /admin/personal_pay/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /admin/personal_pay/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /admin/personal_pay/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /api/api_personal_pay/requirements: -------------------------------------------------------------------------------- 1 | Flask == 0.12.2 2 | pymysql == 0.8.0 3 | flask_restful == 0.3.6 4 | flask_sqlalchemy == 2.3 5 | flask-cors == 3.0.3 6 | pillow == 5.0.0 7 | redis == 2.10.6 8 | jpush == 3.2.9 9 | pycryptodome 10 | bs4 11 | lxml 12 | zmail 13 | -------------------------------------------------------------------------------- /admin/personal_pay/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /admin/personal_pay/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台管理系统 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /api/api_personal_pay/model/response.py: -------------------------------------------------------------------------------- 1 | class Response: 2 | code = 0 3 | message = "" 4 | data = None 5 | 6 | def __init__(self, code=0, message="", data=None): 7 | self.code = code 8 | self.message = message 9 | self.data = data 10 | -------------------------------------------------------------------------------- /api/api_personal_pay/config/config.py: -------------------------------------------------------------------------------- 1 | class Config: 2 | MYSQL_HOST = '127.0.0.1' 3 | MYSQL_USER = 'root' 4 | MYSQL_PASSWORD = '' 5 | MYSQL_DBNAME = 'personal_pay' 6 | APP_DEBUG = True 7 | ROOT_PATH = None 8 | NOTIFY_ROOT_URL = 'http://www.51shuaba.xyz' 9 | -------------------------------------------------------------------------------- /api/api_personal_pay/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /api/api_personal_pay/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /api/api_personal_pay/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /admin/personal_pay/src/store/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | Vue.use(Vuex) 4 | 5 | export const store = new Vuex.Store({ 6 | state: { 7 | menuIndex: '' 8 | }, 9 | mutations: { 10 | changeMenuIndex: (state, arg) => { 11 | state.menuIndex = arg 12 | } 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | 4 | class DB: 5 | session = None 6 | 7 | @classmethod 8 | def init(cls, app): 9 | db = SQLAlchemy() 10 | db.init_app(app) 11 | db.app = app 12 | db.create_all() 13 | cls.session = db.session 14 | -------------------------------------------------------------------------------- /admin/personal_pay/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 'use strict' 3 | require('eventsource-polyfill') 4 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 5 | 6 | hotClient.subscribe(function (event) { 7 | if (event.action === 'reload') { 8 | window.location.reload() 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/safedict.py: -------------------------------------------------------------------------------- 1 | # 可以使用点语法访问 2 | class SafeDict(dict): 3 | def __getattr__(self, name): 4 | try: 5 | return self[name] 6 | except KeyError: 7 | return None 8 | # raise AttributeError(name) 9 | 10 | def __setattr__(self, name, value): 11 | self[name] = value -------------------------------------------------------------------------------- /admin/personal_pay/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /api/api_personal_pay/libs/qcloud_cos/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from libs.qcloud_cos.cos_auth import Auth 4 | from libs.qcloud_cos.cos_cred import CredInfo 5 | 6 | 7 | import logging 8 | 9 | try: 10 | from logging import NullHandler 11 | except ImportError: 12 | class NullHandler(logging.Handler): 13 | def emit(self, record): 14 | pass 15 | 16 | logging.getLogger(__name__).addHandler(NullHandler()) 17 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/log.py: -------------------------------------------------------------------------------- 1 | from config.config import Config 2 | import time 3 | 4 | 5 | class Logger: 6 | @classmethod 7 | def log(cls, string): 8 | t = time.localtime(time.time()) 9 | file_name = time.strftime("%Y%m%d", t) 10 | file = Config.ROOT_PATH + "/log/" + file_name + ".log" 11 | with open(file, 'a') as f: 12 | string = "\n" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + " " + string 13 | f.write(string) 14 | print(string) 15 | -------------------------------------------------------------------------------- /admin/personal_pay/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | 6 | module.exports = { 7 | loaders: utils.cssLoaders({ 8 | sourceMap: isProduction 9 | ? config.build.productionSourceMap 10 | : config.dev.cssSourceMap, 11 | extract: isProduction 12 | }), 13 | transformToRequire: { 14 | video: 'src', 15 | source: 'src', 16 | img: 'src', 17 | image: 'xlink:href' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /admin/personal_pay/src/css/common.css: -------------------------------------------------------------------------------- 1 | .el-breadcrumb { 2 | margin-bottom: 40px; 3 | margin-top: 10px; 4 | } 5 | 6 | .el-message-box { 7 | width: auto; 8 | min-width: 420px; 9 | } 10 | 11 | .pagination { 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | } 16 | 17 | .pagination p.count { 18 | font-size: 14px; 19 | margin-right: 10px; 20 | color: #666; 21 | } 22 | 23 | .el-select .el-input { 24 | width: 130px; 25 | } 26 | .input-with-select .el-input-group__prepend { 27 | background-color: #fff; 28 | } -------------------------------------------------------------------------------- /api/api_personal_pay/util/valid.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | # 验证类 5 | class Valid: 6 | # 账号 7 | @classmethod 8 | def is_username(cls, value): 9 | return re.match("[a-zA-Z0-9_-]{6,16}", value) 10 | 11 | # 密码 12 | @classmethod 13 | def is_password(cls, value): 14 | return re.match("[a-zA-Z0-9_-]{6,16}", value) 15 | 16 | # 是否是非空字符 17 | @classmethod 18 | def is_non_empty_str(cls, value): 19 | if isinstance(value, str): 20 | return len(value) > 0 21 | else: 22 | return False 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /admin/personal_pay/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import {store} from './store/store' 7 | import ElementUI from 'element-ui' 8 | import 'element-ui/lib/theme-chalk/index.css' 9 | 10 | Vue.config.productionTip = false 11 | Vue.use(ElementUI) 12 | 13 | /* eslint-disable no-new */ 14 | new Vue({ 15 | el: '#app', 16 | router, 17 | store, 18 | template: '', 19 | components: { App } 20 | }) 21 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/convertUtil.py: -------------------------------------------------------------------------------- 1 | from flask_restful import fields 2 | from util.commonUtil import CommonUtil 3 | import time 4 | 5 | 6 | # 时间戳转时间,用于marshal函数,过滤返回字典 7 | class ConvertTimeStamp(fields.Raw): 8 | def format(self, value): 9 | ret = CommonUtil.timestamp_to_time(value) 10 | if ret is None: 11 | return '' 12 | else: 13 | return ret 14 | 15 | 16 | # 比如20170102080808转成2017-01-02 08:08:08 17 | class ConvertFormatTime(fields.Raw): 18 | def format(self, value): 19 | t = time.strptime(value, "%Y%m%d%H%M%S") 20 | return time.strftime("%Y-%m-%d %H:%M:%S", t) 21 | 22 | -------------------------------------------------------------------------------- /api/api_personal_pay/.idea/site1.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/redis.py: -------------------------------------------------------------------------------- 1 | import redis 2 | 3 | 4 | class Redis: 5 | @classmethod 6 | def setex(cls, name, time, value): 7 | r = redis.StrictRedis(host='127.0.0.1', port=6379, db=0) 8 | r.setex(name, time, value) 9 | 10 | @classmethod 11 | def get(cls, name): 12 | r = redis.StrictRedis(host='127.0.0.1', port=6379, db=0) 13 | value = r.get(name) 14 | if value is None: 15 | return None 16 | else: 17 | # pytyhon3 redis默认返回的是bytes 18 | return bytes.decode(value) 19 | 20 | @classmethod 21 | def delete(cls, name): 22 | r = redis.StrictRedis(host='127.0.0.1', port=6379, db=0) 23 | r.delete(name) # 看源码明明可以支持多参数的,但是之前把参数封装成*names会删除失败 24 | -------------------------------------------------------------------------------- /admin/personal_pay/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /admin/personal_pay/src/util/common.js: -------------------------------------------------------------------------------- 1 | function isInArray (arr, val) { 2 | for (var i = 0; i < arr.length; i++) { 3 | if (arr[i] === val) { 4 | return true 5 | } 6 | } 7 | } 8 | 9 | function safeJson (json) { 10 | let dic = json 11 | for (var key in json) { 12 | if (json[key] === null || json[key] === undefined) { 13 | dic[key] = '' 14 | } 15 | } 16 | return dic 17 | } 18 | 19 | function safeKeyValueAssign (oldDic, newDic) { 20 | let dic = oldDic 21 | for (var key in newDic) { 22 | if (newDic[key] !== null && newDic[key] !== undefined) { 23 | dic[key] = newDic[key] 24 | } 25 | } 26 | return dic 27 | } 28 | 29 | const common = { 30 | isInArray: isInArray, 31 | safeJson: safeJson, 32 | safeKeyValueAssign 33 | } 34 | 35 | export default common 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 个人收款解决方案 2 | 3 | **官网**:[http://123.206.186.27](http://123.206.186.27) 4 | 5 | > 免费注册商户,收款到个人支付宝或微信,无手续费。 6 | 7 | # 收款流程 8 | 9 | ### 1.买家下单 10 | 11 | ![](http://github-1252137158.file.myqcloud.com/PersonalPay/pay_1.png) 12 | 13 | ### 2.商家收到邮件 14 | 15 | ![](http://github-1252137158.file.myqcloud.com/PersonalPay/pay_2.png) 16 | 17 | ### 3.买家付款 18 | --- 19 | 20 | ![](http://github-1252137158.file.myqcloud.com/PersonalPay/pay_3.png) 21 | 22 | ### 4.商家确认收款 23 | 24 | ![](http://github-1252137158.file.myqcloud.com/PersonalPay/pay_4.png) 25 | 26 | ### 5.买家收到邮件 27 | 28 | ![](http://github-1252137158.file.myqcloud.com/PersonalPay/pay_5.png) 29 | 30 | # 项目结构 31 | 32 | **官网、下单页、付款页、确认收款页:** `静态HTML` 33 | 34 | **管理台:** `node`、`Vue`、`Vue-router`、`VueX`、`Element-UI` 35 | 36 | **API:** `python3.6`、`Flask`、`SQLAlchemy`、`uwsgi`、`nginx` 37 | 38 | 39 | # LICENSE 40 | 41 | MIT 42 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/store.py: -------------------------------------------------------------------------------- 1 | from table.model import Config 2 | from util.db import DB 3 | 4 | 5 | # config表的保存和读取,返回的值是字符串类型,需要自己转换 6 | class Store: 7 | @classmethod 8 | def save(cls, key, value): 9 | config = DB.session.query(Config).filter(Config.key == key).first() 10 | if config is None: 11 | config = Config( 12 | key=key, 13 | value=value 14 | ) 15 | DB.session.add(config) 16 | DB.session.commit() 17 | else: 18 | config.value = value 19 | DB.session.commit() 20 | 21 | @classmethod 22 | def read(cls, key): 23 | config = DB.session.query(Config).filter(Config.key == key).first() 24 | if config is None: 25 | return '' 26 | else: 27 | return config.value 28 | 29 | @classmethod 30 | def read_all(cls): 31 | configs = DB.session.query(Config).all() 32 | dic = dict() 33 | for item in configs: 34 | dic[item.key] = item.value 35 | return dic 36 | -------------------------------------------------------------------------------- /admin/personal_pay/src/components/common/404.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 35 | 36 | -------------------------------------------------------------------------------- /api/api_personal_pay/resources/baseApi.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from flask import request, session, make_response 3 | import os 4 | import hashlib 5 | from util.log import Logger 6 | import json 7 | from util.commonUtil import CommonUtil 8 | import time 9 | 10 | def output_json(data, code, headers=None): 11 | """Makes a Flask response with a JSON encoded body""" 12 | # 如果是app接口,且不是支付回调,加密后返回 13 | Logger.log("请求id:%s 响应\n返回JSON:%s\n" % (session['requestId'], data)) 14 | resp = make_response(json.dumps(data), code) 15 | resp.headers.extend(headers or {}) 16 | return resp 17 | 18 | 19 | # 这个是Api基类,可以做统一处理 20 | class BaseApi(Resource): 21 | def __init__(self): 22 | md5 = hashlib.md5() 23 | md5.update(os.urandom(24)) 24 | session['requestId'] = md5.hexdigest() 25 | 26 | Logger.log(">>>>>>>>>>>>>>>>>>>>>>> 请求 请求id:%s >>>>>>>>>>>>>>>>>>>>>>>\n%s|%s|%s|%s|%s" % (session['requestId'], time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), request.environ['REMOTE_ADDR'], request.environ['REQUEST_METHOD'], request.url, request.get_data())) 27 | Resource.__init__(self) 28 | -------------------------------------------------------------------------------- /api/api_personal_pay/libs/qcloud_cos/cos_cred.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | 4 | from libs.qcloud_cos.cos_params_check import ParamCheck 5 | 6 | 7 | class CredInfo(object): 8 | """CredInfo用户的身份信息""" 9 | def __init__(self, appid, secret_id, secret_key): 10 | self._appid = appid 11 | self._secret_id = secret_id 12 | self._secret_key = secret_key 13 | self._param_check = ParamCheck() 14 | 15 | def get_appid(self): 16 | return self._appid 17 | 18 | def get_secret_id(self): 19 | return self._secret_id 20 | 21 | def get_secret_key(self): 22 | return self._secret_key 23 | 24 | def check_params_valid(self): 25 | if not self._param_check.check_param_int('appid', self._appid): 26 | return False 27 | if not self._param_check.check_param_unicode('secret_id', self._secret_id): 28 | return False 29 | return self._param_check.check_param_unicode('secret_key', self._secret_key) 30 | 31 | def get_err_tips(self): 32 | """获取错误信息 33 | 34 | :return: 35 | """ 36 | return self._param_check.get_err_tips() 37 | -------------------------------------------------------------------------------- /admin/personal_pay/src/util/qcloud.js: -------------------------------------------------------------------------------- 1 | import CosCloud from 'cos-js-sdk-v4' 2 | import api from './api.js' 3 | 4 | const upload = (event, success, failed) => { 5 | const file = event.target.files[0] 6 | // 签名 7 | const cos = new CosCloud({ 8 | appid: '', 9 | bucket: '', 10 | region: '', 11 | getAppSign: function (callback) { 12 | api.qcloudToken(null, (res) => { 13 | if (res.status === 200) { 14 | const data = res.data 15 | if (data.code === 0) { 16 | console.log('sign:' + data.data) 17 | callback(data.data.sign) 18 | } else { 19 | this.$message(data.message) 20 | } 21 | } else { 22 | this.$message('请求超时') 23 | } 24 | }) 25 | }, 26 | getAppSignOnce: function (callback) { 27 | } 28 | }) 29 | // 上传 30 | cos.uploadFile((result) => { 31 | console.log('成功' + JSON.stringify(result)) 32 | success(result.data.access_url) 33 | }, (result) => { 34 | result = result || {} 35 | console.log(result) 36 | failed() 37 | }, null, 'test', '/myFloder/' + new Date().getTime() + '_' + file.name, file, 1) 38 | } 39 | 40 | const qcloud = { 41 | upload: upload 42 | } 43 | 44 | export default qcloud 45 | -------------------------------------------------------------------------------- /admin/personal_pay/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, function (err, stats) { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /admin/personal_pay/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const baseWebpackConfig = require('./webpack.base.conf') 7 | const HtmlWebpackPlugin = require('html-webpack-plugin') 8 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 9 | 10 | // add hot-reload related code to entry chunks 11 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 12 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 13 | }) 14 | 15 | module.exports = merge(baseWebpackConfig, { 16 | module: { 17 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 18 | }, 19 | // cheap-module-eval-source-map is faster for development 20 | devtool: '#cheap-module-eval-source-map', 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': config.dev.env 24 | }), 25 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 26 | new webpack.HotModuleReplacementPlugin(), 27 | new webpack.NoEmitOnErrorsPlugin(), 28 | // https://github.com/ampedandwired/html-webpack-plugin 29 | new HtmlWebpackPlugin({ 30 | filename: 'index.html', 31 | template: 'index.html', 32 | inject: true 33 | }), 34 | new FriendlyErrorsPlugin() 35 | ] 36 | }) 37 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/checkUtil.py: -------------------------------------------------------------------------------- 1 | from util.redis import Redis 2 | from util.db import DB 3 | from model.response import Response 4 | from table.model import Merchant 5 | from util.commonUtil import CommonUtil 6 | from flask import request 7 | 8 | class CheckUtil: 9 | # 效验图片验证码 10 | @classmethod 11 | def check_valid_image(cls, valid_id, valid_value): 12 | code = Redis.get(valid_id) 13 | 14 | if code is None: 15 | return Response(-1, '验证码不存在') 16 | elif code != valid_value: 17 | return Response(-1, '验证码错误') 18 | else: 19 | Redis.delete(valid_id) 20 | return Response() 21 | 22 | # 效验token 23 | 24 | @classmethod 25 | def check_merchant_token(cls, token): 26 | if token is None: 27 | return Response(1001, '身份信息不存在') 28 | else: 29 | admin = DB.session.query(Merchant).filter(Merchant.token == token).first() 30 | if admin is None: 31 | return Response(1001, '请登录') 32 | elif admin.token != token: 33 | return Response(1001, '身份信息已过期') 34 | elif admin.is_frozen == 1: 35 | return Response(1001, '账号异常') 36 | else: 37 | admin.login_at = CommonUtil.time_format_str() 38 | admin.login_ip = request.environ['REMOTE_ADDR'] 39 | DB.session.commit() 40 | return Response(0, '', admin) 41 | -------------------------------------------------------------------------------- /admin/personal_pay/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | function exec (cmd) { 7 | return require('child_process').execSync(cmd).toString().trim() 8 | } 9 | 10 | const versionRequirements = [ 11 | { 12 | name: 'node', 13 | currentVersion: semver.clean(process.version), 14 | versionRequirement: packageConfig.engines.node 15 | } 16 | ] 17 | 18 | if (shell.which('npm')) { 19 | versionRequirements.push({ 20 | name: 'npm', 21 | currentVersion: exec('npm --version'), 22 | versionRequirement: packageConfig.engines.npm 23 | }) 24 | } 25 | 26 | module.exports = function () { 27 | const warnings = [] 28 | for (let i = 0; i < versionRequirements.length; i++) { 29 | const mod = versionRequirements[i] 30 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 31 | warnings.push(mod.name + ': ' + 32 | chalk.red(mod.currentVersion) + ' should be ' + 33 | chalk.green(mod.versionRequirement) 34 | ) 35 | } 36 | } 37 | 38 | if (warnings.length) { 39 | console.log('') 40 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 41 | console.log() 42 | for (let i = 0; i < warnings.length; i++) { 43 | const warning = warnings[i] 44 | console.log(' ' + warning) 45 | } 46 | console.log() 47 | process.exit(1) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /admin/personal_pay/config/index.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict' 3 | // Template version: 1.1.3 4 | // see http://vuejs-templates.github.io/webpack for documentation. 5 | 6 | const path = require('path') 7 | 8 | module.exports = { 9 | build: { 10 | env: require('./prod.env'), 11 | index: path.resolve(__dirname, '../dist/index.html'), 12 | assetsRoot: path.resolve(__dirname, '../dist'), 13 | assetsSubDirectory: 'static', 14 | assetsPublicPath: '/', 15 | productionSourceMap: true, 16 | // Gzip off by default as many popular static hosts such as 17 | // Surge or Netlify already gzip all static assets for you. 18 | // Before setting to `true`, make sure to: 19 | // npm install --save-dev compression-webpack-plugin 20 | productionGzip: false, 21 | productionGzipExtensions: ['js', 'css'], 22 | // Run the build command with an extra argument to 23 | // View the bundle analyzer report after build finishes: 24 | // `npm run build --report` 25 | // Set to `true` or `false` to always turn it on or off 26 | bundleAnalyzerReport: process.env.npm_config_report 27 | }, 28 | dev: { 29 | env: require('./dev.env'), 30 | port: process.env.PORT || 8080, 31 | autoOpenBrowser: true, 32 | assetsSubDirectory: 'static', 33 | assetsPublicPath: '/', 34 | cssSourceMap: false, 35 | proxyTable: { 36 | // proxy all requests starting with /api to jsonplaceholder 37 | '/api': { 38 | target: 'pei zhi le ye mei yong', 39 | changeOrigin: true, 40 | pathRewrite: { 41 | '^/api': '' 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/emailUtil.py: -------------------------------------------------------------------------------- 1 | from email.mime.multipart import MIMEMultipart 2 | from email.mime.text import MIMEText 3 | import smtplib 4 | from email import utils 5 | import socket 6 | 7 | class EmailUtil: 8 | @classmethod 9 | def send_html_email(cls, title, html, receiver): 10 | socket.setdefaulttimeout(10) 11 | 12 | sender = '' 13 | server = 'smtp.exmail.qq.com' 14 | smtp_port = 465 15 | user = sender 16 | passwd = '' 17 | 18 | # 设定root信息 19 | msg_root = MIMEMultipart('related') 20 | msg_root['Subject'] = title 21 | msg_root['From'] = sender 22 | msg_root['To'] = receiver 23 | 24 | msg_alternative = MIMEMultipart('alternative') 25 | msg_root.attach(msg_alternative) 26 | 27 | # 构造MIMEMultipart对象做为根容器 28 | main_msg = MIMEMultipart() 29 | 30 | html_msg = MIMEText( 31 | html, 32 | 'html', 33 | 'utf-8' 34 | ) 35 | 36 | main_msg.attach(html_msg) 37 | # 设置根容器属性 38 | main_msg['From'] = sender 39 | main_msg['To'] = receiver 40 | main_msg['Subject'] = title 41 | main_msg['Date'] = utils.formatdate() 42 | 43 | # 得到格式化后的完整文本 44 | full_text = main_msg.as_string() 45 | 46 | try: 47 | # 发送邮件 48 | smtp = smtplib.SMTP_SSL(server, smtp_port) 49 | smtp.login(user, passwd) 50 | smtp.sendmail(sender, [receiver], full_text) 51 | smtp.quit() 52 | print("邮件发送成功!") 53 | return True 54 | except BaseException as e: 55 | print("邮件发送失败!") 56 | print(e) 57 | return False 58 | -------------------------------------------------------------------------------- /admin/personal_pay/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | module.exports = { 12 | entry: { 13 | app: './src/main.js' 14 | }, 15 | output: { 16 | path: config.build.assetsRoot, 17 | filename: '[name].js', 18 | publicPath: process.env.NODE_ENV === 'production' 19 | ? config.build.assetsPublicPath 20 | : config.dev.assetsPublicPath 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.vue', '.json'], 24 | alias: { 25 | 'vue$': 'vue/dist/vue.esm.js', 26 | '@': resolve('src') 27 | } 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.(js|vue)$/, 33 | loader: 'eslint-loader', 34 | enforce: 'pre', 35 | include: [resolve('src'), resolve('test')], 36 | options: { 37 | formatter: require('eslint-friendly-formatter') 38 | } 39 | }, 40 | { 41 | test: /\.vue$/, 42 | loader: 'vue-loader', 43 | options: vueLoaderConfig 44 | }, 45 | { 46 | test: /\.js$/, 47 | loader: 'babel-loader', 48 | include: [resolve('src'), resolve('test')] 49 | }, 50 | { 51 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 52 | loader: 'url-loader', 53 | options: { 54 | limit: 10000, 55 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 56 | } 57 | }, 58 | { 59 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 60 | loader: 'url-loader', 61 | options: { 62 | limit: 10000, 63 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 64 | } 65 | }, 66 | { 67 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 68 | loader: 'url-loader', 69 | options: { 70 | limit: 10000, 71 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 72 | } 73 | } 74 | ] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /admin/personal_pay/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | 6 | exports.assetsPath = function (_path) { 7 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 8 | ? config.build.assetsSubDirectory 9 | : config.dev.assetsSubDirectory 10 | return path.posix.join(assetsSubDirectory, _path) 11 | } 12 | 13 | exports.cssLoaders = function (options) { 14 | options = options || {} 15 | 16 | const cssLoader = { 17 | loader: 'css-loader', 18 | options: { 19 | minimize: process.env.NODE_ENV === 'production', 20 | sourceMap: options.sourceMap 21 | } 22 | } 23 | 24 | // generate loader string to be used with extract text plugin 25 | function generateLoaders (loader, loaderOptions) { 26 | const loaders = [cssLoader] 27 | if (loader) { 28 | loaders.push({ 29 | loader: loader + '-loader', 30 | options: Object.assign({}, loaderOptions, { 31 | sourceMap: options.sourceMap 32 | }) 33 | }) 34 | } 35 | 36 | // Extract CSS when that option is specified 37 | // (which is the case during production build) 38 | if (options.extract) { 39 | return ExtractTextPlugin.extract({ 40 | use: loaders, 41 | fallback: 'vue-style-loader' 42 | }) 43 | } else { 44 | return ['vue-style-loader'].concat(loaders) 45 | } 46 | } 47 | 48 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 49 | return { 50 | css: generateLoaders(), 51 | postcss: generateLoaders(), 52 | less: generateLoaders('less'), 53 | sass: generateLoaders('sass', { indentedSyntax: true }), 54 | scss: generateLoaders('sass'), 55 | stylus: generateLoaders('stylus'), 56 | styl: generateLoaders('stylus') 57 | } 58 | } 59 | 60 | // Generate loaders for standalone style files (outside of .vue) 61 | exports.styleLoaders = function (options) { 62 | const output = [] 63 | const loaders = exports.cssLoaders(options) 64 | for (const extension in loaders) { 65 | const loader = loaders[extension] 66 | output.push({ 67 | test: new RegExp('\\.' + extension + '$'), 68 | use: loader 69 | }) 70 | } 71 | return output 72 | } 73 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/validImage.py: -------------------------------------------------------------------------------- 1 | import string 2 | from random import randint, sample 3 | from PIL import Image, ImageDraw, ImageFont, ImageFilter 4 | from config.config import Config 5 | import base64 6 | from io import BytesIO 7 | from config.config import Config 8 | 9 | 10 | class ValidImage: 11 | # word_type=0生成4个数字,wordtype=1生成4个字母 12 | @classmethod 13 | def create(cls, word_type=0): 14 | # 定义变量 15 | img_size = (110, 50) # 定义画布大小 16 | img_rgb = (248, 248, 248) # 定义画布颜色,白色 17 | img = Image.new("RGB", img_size, img_rgb) 18 | 19 | img_text = '' 20 | if word_type == 0: 21 | img_text = " ".join(sample(string.digits, 4)) 22 | else: 23 | img_text = " ".join(sample(string.ascii_letters, 4)) 24 | # print(img_text.replace(' ','')) 25 | code = img_text.replace(' ','') 26 | 27 | # 画图 28 | drow = ImageDraw.Draw(img) 29 | for i in range(10): 30 | # 随机画线 31 | drow.line([tuple(sample(range(img_size[0]), 2)), tuple(sample(range(img_size[0]), 2))], fill=(0, 0, 0)) 32 | for i in range(99): 33 | # 随机画点 34 | drow.point(tuple(sample(range(img_size[0]), 2)), fill=(0, 0, 0)) 35 | 36 | # 文字 37 | font = ImageFont.truetype(Config.ROOT_PATH + "/src/font/Verdana Italic.ttf", 24) # 定义文字字体和大小 38 | drow.text((6, 6), img_text, font=font, fill="green") 39 | 40 | # 扭曲图片和滤镜 41 | params = [ 42 | 1 - float(randint(1, 2)) / 100, 43 | 0, 44 | 0, 45 | 0, 46 | 1 - float(randint(1, 10)) / 100, 47 | float(randint(1, 2)) / 500, 48 | 0.001, 49 | float(randint(1, 2)) / 500 50 | ] 51 | img = img.transform(img_size, Image.PERSPECTIVE, params) 52 | img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) 53 | 54 | # 模糊: 55 | # image.filter(ImageFilter.BLUR) 56 | 57 | # code_name = '{}.jpg'.format('valid') 58 | # save_dir = '/{}'.format(code_name) 59 | # img.save(Config.ROOT_PATH + save_dir, 'jpeg') 60 | # print("已保存图片: {}".format(save_dir)) 61 | 62 | buffered = BytesIO() 63 | img.save(buffered, format="JPEG") 64 | img_str = base64.b64encode(buffered.getvalue()) 65 | 66 | return { 67 | "img": 'data:image/jpeg;base64,' + bytes.decode(img_str), 68 | "code": code 69 | } 70 | -------------------------------------------------------------------------------- /admin/personal_pay/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import notFound from '@/components/common/404' 4 | import admin from '@/components/admin/admin' 5 | import reg from '@/components/common/reg' 6 | import login from '@/components/common/login' 7 | import merchantDetail from '@/components/admin/merchant/detail' 8 | import productList from '@/components/admin/product/productList' 9 | import addProduct from '@/components/admin/product/addProduct' 10 | import addStock from '@/components/admin/product/addStock' 11 | import stockList from '@/components/admin/product/stockList' 12 | import orderList from '@/components/admin/order/orderList' 13 | 14 | Vue.use(Router) 15 | 16 | const dic = { 17 | routes: [ 18 | { 19 | path: '/admin', 20 | title: '管理台', 21 | name: 'admin', 22 | component: admin, 23 | redirect: { name: 'merchantDetail' }, 24 | children: [ 25 | { 26 | path: 'productList', 27 | title: '商品列表', 28 | name: 'productList', 29 | component: productList 30 | }, 31 | { 32 | path: 'merchantDetail', 33 | name: 'merchantDetail', 34 | component: merchantDetail 35 | }, 36 | { 37 | path: 'addProduct', 38 | name: 'addProduct', 39 | component: addProduct 40 | }, 41 | { 42 | path: 'addStock', 43 | name: 'addStock', 44 | component: addStock 45 | }, 46 | { 47 | path: 'stockList', 48 | name: 'stockList', 49 | component: stockList 50 | }, 51 | { 52 | path: 'orderList', 53 | name: 'orderList', 54 | component: orderList 55 | } 56 | ] 57 | }, 58 | { 59 | path: '/login', 60 | title: '登录', 61 | name: 'login', 62 | component: login 63 | }, 64 | { 65 | path: '/reg', 66 | title: '注册', 67 | name: 'reg', 68 | component: reg 69 | }, 70 | { 71 | path: '/wrongway', 72 | component: notFound, 73 | name: 'notFound', 74 | hidden: true 75 | }, 76 | { 77 | path: '*', 78 | hidden: true, 79 | redirect: { name: 'notFound' } 80 | }, 81 | { 82 | path: '/', 83 | hidden: true, 84 | redirect: { name: 'merchantDetail' } 85 | } 86 | ], 87 | mode: 'history' 88 | } 89 | 90 | export default new Router(dic) 91 | -------------------------------------------------------------------------------- /api/api_personal_pay/libs/qcloud_cos/cos_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import random 5 | import time 6 | import urllib.request 7 | import hmac 8 | import hashlib 9 | import binascii 10 | import base64 11 | 12 | 13 | class Auth(object): 14 | def __init__(self, cred): 15 | self.cred = cred 16 | 17 | def app_sign(self, bucket, cos_path, expired, upload_sign=True): 18 | appid = self.cred.get_appid() 19 | bucket = bucket 20 | secret_id = self.cred.get_secret_id() 21 | now = int(time.time()) 22 | rdm = random.randint(0, 999999999) 23 | cos_path = urllib.request.quote(cos_path.encode('utf8'), '~/') 24 | if upload_sign: 25 | fileid = '/%s/%s%s' % (appid, bucket, cos_path) 26 | else: 27 | fileid = cos_path 28 | 29 | if expired != 0 and expired < now: 30 | expired = now + expired 31 | 32 | sign_tuple = (appid, secret_id, expired, now, rdm, fileid, bucket) 33 | 34 | plain_text = 'a=%s&k=%s&e=%d&t=%d&r=%d&f=%s&b=%s' % sign_tuple 35 | secret_key = self.cred.get_secret_key().encode('utf8') 36 | sha1_hmac = hmac.new(secret_key, plain_text.encode('utf-8'), hashlib.sha1) 37 | hmac_digest = sha1_hmac.hexdigest() 38 | hmac_digest = binascii.unhexlify(hmac_digest) 39 | sign_hex = hmac_digest + plain_text.encode('utf-8') 40 | sign_base64 = base64.b64encode(sign_hex) 41 | return sign_base64.decode('utf-8') 42 | 43 | def sign_once(self, bucket, cos_path): 44 | """单次签名(针对删除和更新操作) 45 | 46 | :param bucket: bucket名称 47 | :param cos_path: 要操作的cos路径, 以'/'开始 48 | :return: 签名字符串 49 | """ 50 | return self.app_sign(bucket, cos_path, 0) 51 | 52 | def sign_more(self, bucket, cos_path, expired): 53 | """多次签名(针对上传文件,创建目录, 获取文件目录属性, 拉取目录列表) 54 | 55 | :param bucket: bucket名称 56 | :param cos_path: 要操作的cos路径, 以'/'开始 57 | :param expired: 签名过期时间, UNIX时间戳, 如想让签名在30秒后过期, 即可将expired设成当前时间加上30秒 58 | :return: 签名字符串 59 | """ 60 | return self.app_sign(bucket, cos_path, expired) 61 | 62 | def sign_download(self, bucket, cos_path, expired): 63 | """下载签名(用于获取后拼接成下载链接,下载私有bucket的文件) 64 | 65 | :param bucket: bucket名称 66 | :param cos_path: 要下载的cos文件路径, 以'/'开始 67 | :param expired: 签名过期时间, UNIX时间戳, 如想让签名在30秒后过期, 即可将expired设成当前时间加上30秒 68 | :return: 签名字符串 69 | """ 70 | return self.app_sign(bucket, cos_path, expired, False) 71 | -------------------------------------------------------------------------------- /admin/personal_pay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PersonalPay", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "HJaycee <>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js", 11 | "lint": "eslint --ext .js,.vue src" 12 | }, 13 | "dependencies": { 14 | "vue": "^2.5.2", 15 | "vue-router": "^3.0.1", 16 | "element-ui": "2.0", 17 | "axios": "0.17", 18 | "js-md5": "^0.4.2", 19 | "vuex": "^3.0.0" 20 | }, 21 | "devDependencies": { 22 | "autoprefixer": "^7.1.2", 23 | "axios": "0.17", 24 | "babel-core": "^6.22.1", 25 | "babel-eslint": "^7.1.1", 26 | "babel-loader": "^7.1.1", 27 | "babel-plugin-transform-runtime": "^6.22.0", 28 | "babel-preset-env": "^1.3.2", 29 | "babel-preset-stage-2": "^6.22.0", 30 | "babel-register": "^6.22.0", 31 | "chalk": "^2.0.1", 32 | "connect-history-api-fallback": "^1.3.0", 33 | "copy-webpack-plugin": "^4.0.1", 34 | "cos-js-sdk-v4": "^1.1.10", 35 | "css-loader": "^0.28.0", 36 | "element-ui": "2.0", 37 | "eslint": "^3.19.0", 38 | "eslint-config-standard": "^10.2.1", 39 | "eslint-friendly-formatter": "^3.0.0", 40 | "eslint-loader": "^1.7.1", 41 | "eslint-plugin-html": "^3.0.0", 42 | "eslint-plugin-import": "^2.7.0", 43 | "eslint-plugin-node": "^5.2.0", 44 | "eslint-plugin-promise": "^3.4.0", 45 | "eslint-plugin-standard": "^3.0.1", 46 | "eventsource-polyfill": "^0.9.6", 47 | "express": "^4.14.1", 48 | "extract-text-webpack-plugin": "^3.0.0", 49 | "file-loader": "^1.1.4", 50 | "friendly-errors-webpack-plugin": "^1.6.1", 51 | "html-webpack-plugin": "^2.30.1", 52 | "http-proxy-middleware": "^0.17.3", 53 | "node-sass": "^4.6.0", 54 | "opn": "^5.1.0", 55 | "optimize-css-assets-webpack-plugin": "^3.2.0", 56 | "ora": "^1.2.0", 57 | "portfinder": "^1.0.13", 58 | "rimraf": "^2.6.0", 59 | "semver": "^5.3.0", 60 | "shelljs": "^0.7.6", 61 | "url-loader": "^0.5.8", 62 | "vue-loader": "^13.3.0", 63 | "vue-style-loader": "^3.0.1", 64 | "vue-template-compiler": "^2.5.2", 65 | "webpack": "^3.6.0", 66 | "webpack-bundle-analyzer": "^2.9.0", 67 | "webpack-dev-middleware": "^1.12.0", 68 | "webpack-hot-middleware": "^2.18.2", 69 | "webpack-merge": "^4.1.0" 70 | }, 71 | "engines": { 72 | "node": ">= 4.0.0", 73 | "npm": ">= 3.0.0" 74 | }, 75 | "browserslist": [ 76 | "> 1%", 77 | "last 2 versions", 78 | "not ie <= 8" 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /api/api_personal_pay/util/commonUtil.py: -------------------------------------------------------------------------------- 1 | """ 2 | 这里封装了一些常用的方法 3 | """ 4 | import hashlib 5 | import time 6 | import base64 7 | from Crypto.Cipher import AES 8 | from config.config import Config 9 | import json 10 | from util.safedict import SafeDict 11 | from flask_restful import reqparse 12 | from util.log import Logger 13 | from flask import session 14 | import random 15 | 16 | 17 | class CommonUtil: 18 | # 返回json数据 19 | @classmethod 20 | def json_response(cls, code, msg, data={}): 21 | res = { 22 | "code": code, 23 | "message": msg, 24 | "data": data 25 | } 26 | return res 27 | 28 | # python3.6比如md5经常提示参数类型不对 29 | @classmethod 30 | def utf8str(cls, arg): 31 | ret = "{}".format(arg).encode('utf-8').decode() 32 | if ret != "{}": 33 | return ret 34 | else: 35 | raise AssertionError('convert {} to utf-8 error'.format(arg)) 36 | 37 | # md5 38 | @classmethod 39 | def md5(cls, arg): 40 | obj = hashlib.md5() 41 | obj.update(cls.utf8str(arg).encode('utf-8')) 42 | return obj.hexdigest() 43 | 44 | @classmethod 45 | def random_id(cls): 46 | return random.randint(1000000000, 1999999999) 47 | 48 | # 创建管理员token 49 | @classmethod 50 | def create_admin_token(cls, value): 51 | return cls.md5('admin_'+ value + str(time.time())) 52 | 53 | # 创建用户token 54 | @classmethod 55 | def create_user_token(cls, value): 56 | return cls.md5('user_' + value + str(time.time())) 57 | 58 | # 用户密码 59 | @classmethod 60 | def create_user_password(cls, username, password): 61 | return cls.md5(username + password + '_pwd') 62 | 63 | # 时间戳转时间 64 | @classmethod 65 | def timestamp_to_time(cls, value): 66 | time_local = time.localtime(value) 67 | time_str = time.strftime("%Y-%m-%d %H:%M:%S", time_local) 68 | if time_str: 69 | return time_str 70 | else: 71 | return '' 72 | 73 | # 时间转时间戳 74 | @classmethod 75 | def time_to_timestamp(cls, value, formatter="%Y-%m-%d %H:%M:%S"): 76 | time_local = time.strptime(value, formatter) 77 | return int(time.mktime(time_local)) 78 | 79 | # 14位格式化无符号时间 80 | @classmethod 81 | def time_format_str(cls): 82 | return time.strftime('%Y%m%d%H%M%S', time.localtime()) 83 | 84 | @classmethod 85 | def sql_result_to_json(cls, result): 86 | if type(result) is dict: 87 | return result._asdict() 88 | else: 89 | arr = [] 90 | for item in result: 91 | arr.append(item._asdict()) 92 | return arr 93 | 94 | -------------------------------------------------------------------------------- /api/api_personal_pay/resources/common.py: -------------------------------------------------------------------------------- 1 | from flask_restful import reqparse 2 | from flask import request 3 | from util.commonUtil import CommonUtil 4 | from resources.baseApi import BaseApi 5 | from util.validImage import ValidImage 6 | from util.redis import Redis 7 | import hashlib 8 | from util.log import Logger 9 | from libs.qcloud_cos.cos_cred import CredInfo 10 | from libs.qcloud_cos.cos_auth import Auth 11 | import time 12 | from util.checkUtil import CheckUtil 13 | 14 | 15 | # 生成验证码图片 16 | class ValidImageCreate(BaseApi): 17 | def get(self): 18 | return self.handle() 19 | 20 | def post(self): 21 | return self.handle() 22 | 23 | def handle(self): 24 | parser = reqparse.RequestParser() 25 | parser.add_argument('type') 26 | args = parser.parse_args() 27 | 28 | usage = '' 29 | if args.type is None: 30 | return CommonUtil.json_response(-1, '缺少参数:type') 31 | elif args.type == '0': 32 | usage = 'regAccount' # 注册账号 33 | elif args.type == '1': 34 | usage = 'findPassword' # 找回密码 35 | elif args.type == '2': 36 | usage = 'adminLogin' # 管理台登录 37 | else: 38 | return CommonUtil.json_response(-1, 'type参数格式错误') 39 | 40 | # 用客户端ip来作为sendId是为了使频繁请求时可以替换这个key下面原来的验证码 41 | md5 = hashlib.md5() 42 | md5.update("validimage_{}_{}".format(request.environ['REMOTE_ADDR'], usage).encode('utf-8')) 43 | sendId = md5.hexdigest() 44 | validImage = ValidImage.create() 45 | 46 | Redis.setex(sendId, 60, validImage['code']) 47 | 48 | data = { 49 | "img": validImage['img'], 50 | "sendId": sendId 51 | } 52 | 53 | Logger.log("生成图片验证码 ip:{} sendId:{} code:{}".format(request.environ['REMOTE_ADDR'], sendId, validImage['code'])) 54 | 55 | return CommonUtil.json_response(0, "success", data) 56 | 57 | 58 | # 生成腾讯云cos的多次签名 59 | class QCloudCosSign(BaseApi): 60 | def get(self): 61 | return self.handle() 62 | 63 | def post(self): 64 | return self.handle() 65 | 66 | def handle(self): 67 | parser = reqparse.RequestParser() 68 | parser.add_argument('token', required=True) 69 | args = parser.parse_args() 70 | 71 | # 效验token 72 | result = CheckUtil.check_merchant_token(args.token) 73 | if result.code != 0: 74 | return CommonUtil.json_response(result.code, result.message) 75 | 76 | cred = CredInfo(1252137158, u'密钥', u'密钥') 77 | auth_obj = Auth(cred) 78 | sign_str = auth_obj.sign_more(u'bucket', u'/文件夹/', int(time.time()) + 60) 79 | return CommonUtil.json_response(0, '获取成功', { 80 | 'sign': sign_str 81 | }) -------------------------------------------------------------------------------- /admin/personal_pay/src/components/common/sbUpload.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 47 | 48 | -------------------------------------------------------------------------------- /api/api_personal_pay/index.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from config.config import Config 4 | import os 5 | from flask_cors import CORS 6 | from util.db import DB 7 | from resources.baseApi import output_json 8 | # merchant 9 | from resources.common import ValidImageCreate, QCloudCosSign 10 | from resources.merchant import MerchantReg, MerchantLogin, MerchantInfo, MerchantInfoSave 11 | from resources.product import ProductAdd, ProductList, ProductDelete, ProductStockAdd, ProductStockList, \ 12 | ProductStockDelete, ProductStockOrderNo 13 | from resources.client import ClientProductList, ClientOrderCreate 14 | from resources.order import OrderList, OrderConfirm 15 | from resources.confirm import ConfirmSend 16 | 17 | # 设置根目录 18 | Config.ROOT_PATH = os.path.dirname(os.path.realpath(__file__)) 19 | 20 | app = Flask(__name__) 21 | 22 | # 开启cors 23 | CORS(app) 24 | 25 | # 初使化SQLAlchemy 26 | app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://' + Config.MYSQL_USER + ':' + Config.MYSQL_PASSWORD + '@' + \ 27 | Config.MYSQL_HOST+ '/' + Config.MYSQL_DBNAME 28 | # 不配置会报错提示。如果设置成 True (默认情况),Flask-SQLAlchemy 将会追踪对象的修改并且发送信号。这需要额外的内存, 如果不必要的可以禁用它。 29 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True 30 | # 密钥,不配置无法对session字典赋值 31 | app.config['SECRET_KEY'] = os.urandom(24) 32 | # 显示SQLAlchemy的sql语句日志 33 | app.config["SQLALCHEMY_ECHO"] = True 34 | 35 | # 缓存全局db对象,每次重新创建会导致事务不是同一个 36 | DB.init(app) 37 | 38 | # 自定义返回,flask_restful默认只支持json 39 | api = Api(app, default_mediatype='application/json') 40 | # 根据请求头的的Accept参数,返回对应的格式 41 | api.representations = { 42 | 'application/json': output_json, 43 | } 44 | # 45 | api.add_resource(ValidImageCreate, '/common/validImage/create') # 生成图片验证码 46 | api.add_resource(QCloudCosSign, '/common/QCloud/sign') # 腾讯云COS多次签名 47 | api.add_resource(MerchantReg, '/merchant/reg') 48 | api.add_resource(MerchantLogin, '/merchant/login') 49 | api.add_resource(MerchantInfo, '/merchant/info') 50 | api.add_resource(MerchantInfoSave, '/merchant/info/save') 51 | api.add_resource(ProductAdd, '/product/add') 52 | api.add_resource(ProductList, '/product/list') 53 | api.add_resource(ProductDelete, '/product/delete') 54 | api.add_resource(ProductStockAdd, '/product/stock/add') 55 | api.add_resource(ProductStockList, '/product/stock/list') 56 | api.add_resource(ProductStockDelete, '/product/stock/delete') 57 | api.add_resource(ClientProductList, '/client/product/list') 58 | api.add_resource(ProductStockOrderNo, '/product/stock/orderno') 59 | api.add_resource(ClientOrderCreate, '/client/order/create') 60 | api.add_resource(OrderList, '/order/list') 61 | api.add_resource(OrderConfirm, '/order/confirm') 62 | api.add_resource(ConfirmSend, '/confirm/send') 63 | 64 | if __name__ == '__main__': 65 | # threaded=True 防止开发服务器阻塞 66 | app.run(host='0.0.0.0', threaded=True, debug=True) 67 | -------------------------------------------------------------------------------- /api/api_personal_pay/table/model.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | 5 | 6 | # 商户 7 | class Merchant(db.Model): 8 | id = db.Column(db.Integer, primary_key=True) 9 | merchant_no = db.Column(db.Integer) 10 | username = db.Column(db.String(16)) 11 | password = db.Column(db.String(32)) 12 | confirm_password = db.Column(db.String(32)) 13 | phone = db.Column(db.Integer) 14 | email = db.Column(db.String(50)) 15 | avatar = db.Column(db.String(200)) 16 | parent_no = db.Column(db.Integer) 17 | online_from = db.Column(db.String(14)) 18 | online_to = db.Column(db.String(14)) 19 | is_frozen = db.Column(db.Integer) 20 | create_at = db.Column(db.String(14)) 21 | create_ip = db.Column(db.String(15)) 22 | login_at = db.Column(db.String(14)) 23 | login_ip = db.Column(db.String(15)) 24 | token = db.Column(db.String(32)) 25 | alipay_name = db.Column(db.String(4)) 26 | alipay_account = db.Column(db.String(100)) 27 | wechat_name = db.Column(db.String(4)) 28 | wechat_account = db.Column(db.String(100)) 29 | 30 | 31 | # 订单 32 | class Order(db.Model): 33 | id = db.Column(db.Integer, primary_key=True) 34 | merchant_id = db.Column(db.Integer, db.ForeignKey('merchant.id'), nullable=False) 35 | product_id = db.Column(db.Integer, db.ForeignKey('product.id'), nullable=False) 36 | order_no = db.Column(db.String(32)) 37 | platform_order_no = db.Column(db.String(32)) 38 | platform = db.Column(db.Integer) 39 | create_at = db.Column(db.String(14)) 40 | confirm_at = db.Column(db.String(14)) 41 | cost = db.Column(db.BigInteger) 42 | from_account = db.Column(db.String(20)) 43 | from_nickname = db.Column(db.String(20)) 44 | from_email = db.Column(db.String(100)) 45 | message = db.Column(db.Text) 46 | confirm_secret_key = db.Column(db.String(32)) 47 | 48 | 49 | # 产品 50 | class Product(db.Model): 51 | id = db.Column(db.Integer, primary_key=True) 52 | merchant_id = db.Column(db.Integer, db.ForeignKey('merchant.id'), nullable=False) 53 | record_id = db.Column(db.String(32)) 54 | name = db.Column(db.String(20)) 55 | desc = db.Column(db.String(200)) 56 | price = db.Column(db.BigInteger) 57 | is_on_sell = db.Column(db.Integer) 58 | create_at = db.Column(db.String(14)) 59 | modify_at = db.Column(db.String(14)) 60 | alipay_qrcode = db.Column(db.String(200)) 61 | wechat_qrcode = db.Column(db.String(200)) 62 | 63 | 64 | # 库存 65 | class ProductStock(db.Model): 66 | id = db.Column(db.Integer, primary_key=True) 67 | product_id = db.Column(db.Integer, db.ForeignKey('product.id'), nullable=False) 68 | record_id = db.Column(db.String(32)) 69 | content = db.Column(db.Text) 70 | create_at = db.Column(db.String(14)) 71 | sold_at = db.Column(db.String(14)) 72 | order_id = db.Column(db.Integer, db.ForeignKey('order.id'), nullable=False) 73 | 74 | -------------------------------------------------------------------------------- /api/api_personal_pay/resources/confirm.py: -------------------------------------------------------------------------------- 1 | from flask_restful import reqparse, fields, marshal 2 | from util.db import DB 3 | from table.model import Product, ProductStock, Merchant, Order 4 | from util.commonUtil import CommonUtil 5 | from resources.baseApi import BaseApi 6 | from util.checkUtil import CheckUtil 7 | from util.valid import Valid 8 | import time 9 | from util.convertUtil import ConvertFormatTime 10 | import math 11 | from util.emailUtil import EmailUtil 12 | 13 | 14 | class ConfirmSend(BaseApi): 15 | def post(self): 16 | return self.handle() 17 | 18 | def handle(self): 19 | parser = reqparse.RequestParser() 20 | parser.add_argument('secret_key', required=True) 21 | parser.add_argument('order_no', required=True) 22 | parser.add_argument('password', required=True) 23 | args = parser.parse_args() 24 | 25 | order = DB.session.query(Order).filter(Order.order_no == args.order_no).first() 26 | if order is None: 27 | return CommonUtil.json_response(-1, '订单不存在') 28 | 29 | if order.confirm_secret_key != args.secret_key: 30 | return CommonUtil.json_response(-1, '订单密钥错误') 31 | 32 | if order.confirm_at: 33 | return CommonUtil.json_response(-1, '订单已确认过') 34 | 35 | merchant = DB.session.query(Merchant).filter(Merchant.id == order.merchant_id).first() 36 | # 二次密码核对 37 | if merchant and merchant.password == CommonUtil.create_user_password(merchant.username, args.password): 38 | stock = DB.session.query(ProductStock).\ 39 | filter(order.product_id == ProductStock.product_id).\ 40 | filter(ProductStock.sold_at == None). \ 41 | first() 42 | if stock: 43 | stock.sold_at = CommonUtil.time_format_str() 44 | stock.order_id = order.id 45 | DB.session.commit() 46 | 47 | order.confirm_at = CommonUtil.time_format_str() 48 | DB.session.commit() 49 | 50 | info = '

%s

Copyright@2018 51shuaba.xyz All Rights Reseved.
' % ( 51 | stock.content 52 | ) 53 | 54 | result = EmailUtil.send_html_email('订单' + args.order_no + '发货通知', info, order.from_email) 55 | 56 | if result is True: 57 | return CommonUtil.json_response(0, '确认成功,已邮件通知买家') 58 | else: 59 | return CommonUtil.json_response(0, '确认成功,但是发货邮件未能送达,请联系买家') 60 | else: 61 | return CommonUtil.json_response(-1, '库存不足') 62 | 63 | return CommonUtil.json_response(-1, '密码错误') 64 | -------------------------------------------------------------------------------- /admin/personal_pay/build/dev-server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | const config = require('../config') 5 | if (!process.env.NODE_ENV) { 6 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 7 | } 8 | 9 | const opn = require('opn') 10 | const path = require('path') 11 | const express = require('express') 12 | const webpack = require('webpack') 13 | const proxyMiddleware = require('http-proxy-middleware') 14 | const webpackConfig = require('./webpack.dev.conf') 15 | 16 | // default port where dev server listens for incoming traffic 17 | const port = process.env.PORT || config.dev.port 18 | // automatically open browser, if not set will be false 19 | const autoOpenBrowser = !!config.dev.autoOpenBrowser 20 | // Define HTTP proxies to your custom API backend 21 | // https://github.com/chimurai/http-proxy-middleware 22 | const proxyTable = config.dev.proxyTable 23 | 24 | const app = express() 25 | const compiler = webpack(webpackConfig) 26 | 27 | const devMiddleware = require('webpack-dev-middleware')(compiler, { 28 | publicPath: webpackConfig.output.publicPath, 29 | quiet: true 30 | }) 31 | 32 | const hotMiddleware = require('webpack-hot-middleware')(compiler, { 33 | log: false, 34 | heartbeat: 2000 35 | }) 36 | // force page reload when html-webpack-plugin template changes 37 | // currently disabled until this is resolved: 38 | // https://github.com/jantimon/html-webpack-plugin/issues/680 39 | // compiler.plugin('compilation', function (compilation) { 40 | // compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 41 | // hotMiddleware.publish({ action: 'reload' }) 42 | // cb() 43 | // }) 44 | // }) 45 | 46 | // enable hot-reload and state-preserving 47 | // compilation error display 48 | app.use(hotMiddleware) 49 | 50 | // proxy api requests 51 | Object.keys(proxyTable).forEach(function (context) { 52 | let options = proxyTable[context] 53 | if (typeof options === 'string') { 54 | options = { target: options } 55 | } 56 | app.use(proxyMiddleware(options.filter || context, options)) 57 | }) 58 | 59 | // handle fallback for HTML5 history API 60 | app.use(require('connect-history-api-fallback')()) 61 | 62 | // serve webpack bundle output 63 | app.use(devMiddleware) 64 | 65 | // serve pure static assets 66 | const staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 67 | app.use(staticPath, express.static('./static')) 68 | 69 | const uri = 'http://localhost:' + port 70 | 71 | var _resolve 72 | var _reject 73 | var readyPromise = new Promise((resolve, reject) => { 74 | _resolve = resolve 75 | _reject = reject 76 | }) 77 | 78 | var server 79 | var portfinder = require('portfinder') 80 | portfinder.basePort = port 81 | 82 | console.log('> Starting dev server...') 83 | devMiddleware.waitUntilValid(() => { 84 | portfinder.getPort((err, port) => { 85 | if (err) { 86 | _reject(err) 87 | } 88 | process.env.PORT = port 89 | var uri = 'http://localhost:' + port 90 | console.log('NODE_ENV=' + process.env.NODE_ENV) 91 | console.log('> Listening at ' + uri + '\n') 92 | // when env is testing, don't need open it 93 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 94 | opn(uri) 95 | } 96 | server = app.listen(port) 97 | _resolve() 98 | }) 99 | }) 100 | 101 | module.exports = { 102 | ready: readyPromise, 103 | close: () => { 104 | server.close() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /admin/personal_pay/src/components/common/login.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 87 | 88 | -------------------------------------------------------------------------------- /admin/personal_pay/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | 13 | const env = config.build.env 14 | 15 | const webpackConfig = merge(baseWebpackConfig, { 16 | module: { 17 | rules: utils.styleLoaders({ 18 | sourceMap: config.build.productionSourceMap, 19 | extract: true 20 | }) 21 | }, 22 | devtool: config.build.productionSourceMap ? '#source-map' : false, 23 | output: { 24 | path: config.build.assetsRoot, 25 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 26 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 27 | }, 28 | plugins: [ 29 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 30 | new webpack.DefinePlugin({ 31 | 'process.env': env 32 | }), 33 | // UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | }, 38 | sourceMap: true 39 | }), 40 | // extract css into its own file 41 | new ExtractTextPlugin({ 42 | filename: utils.assetsPath('css/[name].[contenthash].css') 43 | }), 44 | // Compress extracted CSS. We are using this plugin so that possible 45 | // duplicated CSS from different components can be deduped. 46 | new OptimizeCSSPlugin({ 47 | cssProcessorOptions: { 48 | safe: true 49 | } 50 | }), 51 | // generate dist index.html with correct asset hash for caching. 52 | // you can customize output by editing /index.html 53 | // see https://github.com/ampedandwired/html-webpack-plugin 54 | new HtmlWebpackPlugin({ 55 | filename: config.build.index, 56 | template: 'index.html', 57 | inject: true, 58 | minify: { 59 | removeComments: true, 60 | collapseWhitespace: true, 61 | removeAttributeQuotes: true 62 | // more options: 63 | // https://github.com/kangax/html-minifier#options-quick-reference 64 | }, 65 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 66 | chunksSortMode: 'dependency' 67 | }), 68 | // keep module.id stable when vender modules does not change 69 | new webpack.HashedModuleIdsPlugin(), 70 | // split vendor js into its own file 71 | new webpack.optimize.CommonsChunkPlugin({ 72 | name: 'vendor', 73 | minChunks: function (module) { 74 | // any required modules inside node_modules are extracted to vendor 75 | return ( 76 | module.resource && 77 | /\.js$/.test(module.resource) && 78 | module.resource.indexOf( 79 | path.join(__dirname, '../node_modules') 80 | ) === 0 81 | ) 82 | } 83 | }), 84 | // extract webpack runtime and module manifest to its own file in order to 85 | // prevent vendor hash from being updated whenever app bundle is updated 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'manifest', 88 | chunks: ['vendor'] 89 | }), 90 | // copy custom static assets 91 | new CopyWebpackPlugin([ 92 | { 93 | from: path.resolve(__dirname, '../static'), 94 | to: config.build.assetsSubDirectory, 95 | ignore: ['.*'] 96 | } 97 | ]) 98 | ] 99 | }) 100 | 101 | if (config.build.productionGzip) { 102 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 103 | 104 | webpackConfig.plugins.push( 105 | new CompressionWebpackPlugin({ 106 | asset: '[path].gz[query]', 107 | algorithm: 'gzip', 108 | test: new RegExp( 109 | '\\.(' + 110 | config.build.productionGzipExtensions.join('|') + 111 | ')$' 112 | ), 113 | threshold: 10240, 114 | minRatio: 0.8 115 | }) 116 | ) 117 | } 118 | 119 | if (config.build.bundleAnalyzerReport) { 120 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 121 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 122 | } 123 | 124 | module.exports = webpackConfig 125 | -------------------------------------------------------------------------------- /admin/personal_pay/src/components/admin/merchant/detail.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 134 | 135 | -------------------------------------------------------------------------------- /admin/personal_pay/src/components/admin/product/addStock.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 155 | 156 | -------------------------------------------------------------------------------- /admin/personal_pay/src/components/admin/product/stockList.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 152 | 153 | -------------------------------------------------------------------------------- /admin/personal_pay/src/components/admin/product/addProduct.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 159 | 160 | -------------------------------------------------------------------------------- /home/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 51刷吧 - 免费个人收款 9 | 10 | 11 | 12 | 13 | 61 | 62 | 63 |
64 | 65 | 68 | 69 |
70 | 76 |
77 | 78 | 92 |
93 |
94 |

收款流程

95 | 109 |
110 |
111 | ¥0.01体验 112 |
113 | 118 | 119 | -------------------------------------------------------------------------------- /admin/personal_pay/src/components/admin/admin.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 142 | 143 | -------------------------------------------------------------------------------- /api/personal_pay_2018-05-23.sql: -------------------------------------------------------------------------------- 1 | # ************************************************************ 2 | # Sequel Pro SQL dump 3 | # Version 4541 4 | # 5 | # http://www.sequelpro.com/ 6 | # https://github.com/sequelpro/sequelpro 7 | # 8 | # Host: 123.206.186.27 (MySQL 5.7.20-log) 9 | # Database: personal_pay 10 | # Generation Time: 2018-05-23 07:23:44 +0000 11 | # ************************************************************ 12 | 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8 */; 18 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 19 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 20 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 21 | 22 | 23 | # Dump of table merchant 24 | # ------------------------------------------------------------ 25 | 26 | DROP TABLE IF EXISTS `merchant`; 27 | 28 | CREATE TABLE `merchant` ( 29 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 30 | `merchant_no` int(11) DEFAULT NULL COMMENT '商户号', 31 | `username` varchar(16) DEFAULT NULL, 32 | `password` varchar(32) DEFAULT NULL, 33 | `confirm_password` varchar(32) DEFAULT NULL COMMENT '二级密码', 34 | `phone` int(11) DEFAULT NULL, 35 | `email` varchar(50) DEFAULT NULL, 36 | `avatar` varchar(200) DEFAULT NULL, 37 | `parent_no` int(11) DEFAULT NULL COMMENT '上级id', 38 | `online_from` varchar(14) DEFAULT NULL COMMENT '上线时间开始', 39 | `online_to` varchar(14) DEFAULT NULL COMMENT '上线时间结束', 40 | `is_frozen` tinyint(1) DEFAULT NULL, 41 | `create_at` varchar(14) DEFAULT NULL COMMENT '是否冻结', 42 | `create_ip` varchar(15) DEFAULT NULL COMMENT '注册ip', 43 | `login_at` varchar(14) DEFAULT NULL, 44 | `login_ip` varchar(15) DEFAULT NULL, 45 | `token` varchar(32) DEFAULT NULL, 46 | `alipay_name` varchar(20) DEFAULT NULL COMMENT '支付宝姓名', 47 | `alipay_account` varchar(100) DEFAULT NULL COMMENT '支付宝账号', 48 | `wechat_name` varchar(20) DEFAULT NULL COMMENT '微信姓名', 49 | `wechat_account` varchar(100) DEFAULT NULL COMMENT '微信账号', 50 | PRIMARY KEY (`id`), 51 | UNIQUE KEY `merchant_no` (`merchant_no`) 52 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商户'; 53 | 54 | 55 | 56 | # Dump of table order 57 | # ------------------------------------------------------------ 58 | 59 | DROP TABLE IF EXISTS `order`; 60 | 61 | CREATE TABLE `order` ( 62 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 63 | `merchant_id` int(11) unsigned DEFAULT NULL, 64 | `product_id` int(11) unsigned DEFAULT NULL, 65 | `order_no` varchar(32) DEFAULT NULL COMMENT '订单号', 66 | `platform_order_no` varchar(40) DEFAULT NULL COMMENT '支付平台订单号', 67 | `platform` int(11) DEFAULT NULL COMMENT '0支付宝 1微信', 68 | `create_at` varchar(14) DEFAULT NULL, 69 | `confirm_at` varchar(14) DEFAULT NULL, 70 | `cost` bigint(11) DEFAULT NULL COMMENT '金额', 71 | `from_account` varchar(20) DEFAULT NULL COMMENT '来源用户账号', 72 | `from_nickname` varchar(20) DEFAULT NULL COMMENT '来源用户昵称', 73 | `from_email` varchar(100) DEFAULT NULL COMMENT '来源邮箱', 74 | `message` varchar(200) DEFAULT NULL COMMENT '留言', 75 | `confirm_secret_key` varchar(32) DEFAULT NULL COMMENT '确认密钥', 76 | PRIMARY KEY (`id`), 77 | KEY `order_merchant_id` (`merchant_id`), 78 | KEY `order_product_id` (`product_id`), 79 | CONSTRAINT `order_merchant_id` FOREIGN KEY (`merchant_id`) REFERENCES `merchant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 80 | CONSTRAINT `order_product_id` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 81 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 82 | 83 | 84 | 85 | # Dump of table product 86 | # ------------------------------------------------------------ 87 | 88 | DROP TABLE IF EXISTS `product`; 89 | 90 | CREATE TABLE `product` ( 91 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 92 | `merchant_id` int(11) unsigned DEFAULT NULL COMMENT '商户', 93 | `record_id` varchar(32) DEFAULT NULL, 94 | `name` varchar(20) DEFAULT NULL, 95 | `desc` varchar(100) DEFAULT NULL COMMENT '描述', 96 | `price` bigint(20) DEFAULT NULL COMMENT '价格', 97 | `is_on_sell` tinyint(1) DEFAULT NULL COMMENT '0下架 1上架', 98 | `create_at` varchar(15) DEFAULT NULL, 99 | `modify_at` varchar(15) DEFAULT NULL COMMENT '修改时间', 100 | `alipay_qrcode` varchar(200) DEFAULT NULL COMMENT '支付宝收款码', 101 | `wechat_qrcode` varchar(200) DEFAULT NULL COMMENT '微信收款码', 102 | PRIMARY KEY (`id`), 103 | KEY `product_merchant_id` (`merchant_id`), 104 | CONSTRAINT `product_merchant_id` FOREIGN KEY (`merchant_id`) REFERENCES `merchant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 105 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 106 | 107 | 108 | 109 | # Dump of table product_stock 110 | # ------------------------------------------------------------ 111 | 112 | DROP TABLE IF EXISTS `product_stock`; 113 | 114 | CREATE TABLE `product_stock` ( 115 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 116 | `product_id` int(11) unsigned DEFAULT NULL, 117 | `record_id` varchar(32) DEFAULT NULL, 118 | `content` text, 119 | `create_at` varchar(14) DEFAULT NULL, 120 | `sold_at` varchar(14) DEFAULT NULL, 121 | `order_id` int(11) unsigned DEFAULT NULL, 122 | PRIMARY KEY (`id`), 123 | KEY `stock_product_id` (`product_id`), 124 | KEY `stock_order_id` (`order_id`), 125 | CONSTRAINT `stock_order_id` FOREIGN KEY (`order_id`) REFERENCES `order` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 126 | CONSTRAINT `stock_product_id` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 127 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 128 | 129 | 130 | 131 | 132 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 133 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 134 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 135 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 136 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 137 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 138 | -------------------------------------------------------------------------------- /admin/personal_pay/src/components/admin/order/orderList.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 154 | 155 | -------------------------------------------------------------------------------- /admin/personal_pay/src/components/admin/product/productList.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 170 | 171 | -------------------------------------------------------------------------------- /admin/personal_pay/src/components/common/reg.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 162 | 163 | -------------------------------------------------------------------------------- /api/api_personal_pay/resources/merchant.py: -------------------------------------------------------------------------------- 1 | from flask_restful import reqparse, fields, marshal, request 2 | from util.db import DB 3 | from table.model import Merchant 4 | from util.commonUtil import CommonUtil 5 | from resources.baseApi import BaseApi 6 | from util.checkUtil import CheckUtil 7 | from util.valid import Valid 8 | from config.config import Config 9 | 10 | 11 | # 登录 12 | class MerchantLogin(BaseApi): 13 | def post(self): 14 | return self.handle() 15 | 16 | def handle(self): 17 | parser = reqparse.RequestParser() 18 | parser.add_argument('username', required=True) 19 | parser.add_argument('password', required=True) 20 | args = parser.parse_args() 21 | 22 | merchant = DB.session.query(Merchant).filter(Merchant.username == args.username).first() 23 | if merchant is None: 24 | return CommonUtil.json_response(-1, "用户名不存在") 25 | 26 | if merchant.password == CommonUtil.create_user_password(args.username, args.password): 27 | # 生成新token 28 | merchant.token = CommonUtil.create_admin_token(args.username) 29 | DB.session.commit() 30 | 31 | merchant = DB.session.query(Merchant).filter(Merchant.username == args.username).first() 32 | dic = { 33 | 'token': fields.String 34 | } 35 | 36 | return CommonUtil.json_response(0, "登录成功", marshal(merchant, dic)) 37 | else: 38 | print(merchant.password) 39 | print(CommonUtil.create_user_password(args.username, args.password)) 40 | return CommonUtil.json_response(-1, "密码错误") 41 | 42 | 43 | # 注册 44 | class MerchantReg(BaseApi): 45 | def post(self): 46 | return self.handle() 47 | 48 | def handle(self): 49 | parser = reqparse.RequestParser() 50 | parser.add_argument('username', required=True) 51 | parser.add_argument('password', required=True) 52 | parser.add_argument('password2', required=True) 53 | parser.add_argument('validId', required=True) 54 | parser.add_argument('validValue', required=True) 55 | args = parser.parse_args() 56 | 57 | # 效验验证码 58 | result = CheckUtil.check_valid_image(args.validId, args.validValue) 59 | if result.code != 0: 60 | return CommonUtil.json_response(result.code, result.message) 61 | 62 | if Valid.is_username(args.username) is None: 63 | return CommonUtil.json_response(-1, "用户名必须是6-16位英文或数字") 64 | 65 | if Valid.is_password(args.password) is None: 66 | return CommonUtil.json_response(-1, "密码必须是6-16位英文或数字") 67 | 68 | if args.password != args.password2: 69 | return CommonUtil.json_response(-1, "两次密码不一致") 70 | 71 | merchant = DB.session.query(Merchant).filter(Merchant.username == args.username).first() 72 | if merchant: 73 | return CommonUtil.json_response(-1, "用户名已存在") 74 | 75 | # 生成唯一的商户id 76 | merchant_no = None 77 | while merchant_no is None: 78 | random_id = CommonUtil.random_id() 79 | merchant = DB.session.query(Merchant).filter(Merchant.merchant_no == random_id).first() 80 | if merchant is None: 81 | merchant_no = random_id 82 | 83 | merchant = Merchant( 84 | merchant_no=merchant_no, 85 | username=args.username, 86 | password=CommonUtil.create_user_password(args.username, args.password), 87 | create_at=CommonUtil.time_format_str(), 88 | create_ip=request.environ['REMOTE_ADDR'], 89 | is_frozen=0 90 | ) 91 | DB.session.add(merchant) 92 | DB.session.commit() 93 | return CommonUtil.json_response(0, "注册成功") 94 | 95 | 96 | class MerchantInfo(BaseApi): 97 | def post(self): 98 | return self.handle() 99 | 100 | def handle(self): 101 | parser = reqparse.RequestParser() 102 | parser.add_argument('token', required=True) 103 | args = parser.parse_args() 104 | 105 | # 效验token 106 | result = CheckUtil.check_merchant_token(args.token) 107 | if result.code != 0: 108 | return CommonUtil.json_response(result.code, result.message) 109 | 110 | merchant = DB.session.query(Merchant).filter(Merchant.id == result.data.id).first() 111 | 112 | dic = { 113 | 'email': fields.String, 114 | 'online_from': fields.String, 115 | 'online_to': fields.String, 116 | 'alipay_name': fields.String, 117 | 'alipay_account': fields.String, 118 | 'wechat_name': fields.String, 119 | 'wechat_account': fields.String 120 | } 121 | 122 | result = marshal(merchant, dic) 123 | result['mch_url'] = Config.NOTIFY_ROOT_URL + '/buy.html?mch=' + str(merchant.merchant_no) 124 | 125 | return CommonUtil.json_response(0, '获取成功', result) 126 | 127 | 128 | class MerchantInfoSave(BaseApi): 129 | def post(self): 130 | return self.handle() 131 | 132 | def handle(self): 133 | parser = reqparse.RequestParser() 134 | parser.add_argument('token', required=True) 135 | parser.add_argument('email', required=True) 136 | parser.add_argument('online_from', required=True) 137 | parser.add_argument('online_to', required=True) 138 | parser.add_argument('alipay_name', required=True) 139 | parser.add_argument('alipay_account', required=True) 140 | parser.add_argument('wechat_name', required=True) 141 | parser.add_argument('wechat_account', required=True) 142 | args = parser.parse_args() 143 | 144 | # 效验token 145 | result = CheckUtil.check_merchant_token(args.token) 146 | if result.code != 0: 147 | return CommonUtil.json_response(result.code, result.message) 148 | 149 | if Valid.is_non_empty_str(args.email) is False: 150 | return CommonUtil.json_response(-1, '确认邮箱不能为空') 151 | 152 | merchant = DB.session.query(Merchant).filter(Merchant.id == result.data.id).first() 153 | merchant.email = args.email 154 | merchant.online_from = args.online_from 155 | merchant.online_to = args.online_to 156 | merchant.alipay_name = args.alipay_name 157 | merchant.alipay_account = args.alipay_account 158 | merchant.wechat_name = args.wechat_name 159 | merchant.wechat_account = args.wechat_account 160 | 161 | DB.session.commit() 162 | 163 | return CommonUtil.json_response(0, '保存成功') 164 | -------------------------------------------------------------------------------- /home/confirm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 51刷吧 - 免费个人收款 5 | 6 | 7 | 8 | 9 | 138 | 139 | 140 |
141 |
142 | 确认收款 143 |
144 | 145 |
146 | 147 |
148 | 订单号 149 |
150 | 151 |
152 |
153 | 154 |
155 | 156 |
157 | 密 码 158 |
159 | 160 |
161 |
162 | 163 |
164 |

确认收款后,买家将会收到收货邮件

165 |
166 | 167 |
168 |
169 |

提交

170 |
171 |
172 | 173 |
174 |
175 | 176 | 177 | 178 | 258 | -------------------------------------------------------------------------------- /admin/personal_pay/src/util/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import router from '../router' 3 | 4 | /** 5 | * 因为在index.js中的proxyTable配置了代理没效果,所幸在这里配置 6 | */ 7 | let MAIN_URL = '' 8 | if (process.env.NODE_ENV === 'development') { 9 | MAIN_URL = 'http://127.0.0.1:5000' 10 | } else { 11 | MAIN_URL = 'http://123.206.186.27:82' 12 | } 13 | 14 | const request = (url, data, callback) => { 15 | axios.post(MAIN_URL + '/' + url, data) 16 | .then((response) => { 17 | if (response && 18 | response.status === 200 && 19 | response.data.code === 1001) { 20 | // need login 21 | router.push({ 22 | name: 'login' 23 | }) 24 | } 25 | callback(response) 26 | }) 27 | } 28 | 29 | /** 30 | * reg 31 | */ 32 | const reg = (params, callback) => { 33 | var data = new URLSearchParams() 34 | data.append('username', params.username) 35 | data.append('password', params.password) 36 | data.append('password2', params.password2) 37 | data.append('validId', params.validId) 38 | data.append('validValue', params.validValue) 39 | 40 | request('merchant/reg', data, (response) => { 41 | callback(response) 42 | }) 43 | } 44 | 45 | /** 46 | * login 47 | */ 48 | const login = (params, callback) => { 49 | var data = new URLSearchParams() 50 | data.append('username', params.username) 51 | data.append('password', params.password) 52 | 53 | request('merchant/login', data, (response) => { 54 | if (response && 55 | response.status === 200 && 56 | response.data.code === 0) { 57 | localStorage.token = response.data.data.token // save token to localStorage 58 | } 59 | callback(response) 60 | }) 61 | } 62 | 63 | const merchantInfo = (params, callback) => { 64 | // console.log(params) 65 | var data = new URLSearchParams() 66 | data.append('token', localStorage.token) 67 | 68 | request('merchant/info', data, (response) => { 69 | callback(response) 70 | }) 71 | } 72 | 73 | const merchantInfoSave = (params, callback) => { 74 | // console.log(params) 75 | var data = new URLSearchParams() 76 | data.append('token', localStorage.token) 77 | data.append('email', params.email ? params.email : '') 78 | data.append('online_from', params.online_from ? params.online_from : '') 79 | data.append('online_to', params.online_to ? params.online_to : '') 80 | data.append('alipay_name', params.alipay_name ? params.alipay_name : '') 81 | data.append('alipay_account', params.alipay_account ? params.alipay_account : '') 82 | data.append('wechat_name', params.wechat_name ? params.wechat_name : '') 83 | data.append('wechat_account', params.wechat_account ? params.wechat_account : '') 84 | 85 | request('merchant/info/save', data, (response) => { 86 | callback(response) 87 | }) 88 | } 89 | 90 | const addProduct = (params, callback) => { 91 | // console.log(params) 92 | var data = new URLSearchParams() 93 | data.append('token', localStorage.token) 94 | data.append('name', params.name) 95 | data.append('desc', params.desc) 96 | data.append('price', params.price * 100) 97 | data.append('alipay_qrcode', params.alipay_qrcode) 98 | data.append('wechat_qrcode', params.wechat_qrcode) 99 | data.append('productId', params.productId) 100 | data.append('is_on_sell', params.is_on_sell) 101 | 102 | request('product/add', data, (response) => { 103 | callback(response) 104 | }) 105 | } 106 | 107 | const productList = (params, callback) => { 108 | // console.log(params) 109 | var data = new URLSearchParams() 110 | data.append('token', localStorage.token) 111 | data.append('page', params.page) 112 | data.append('size', params.size) 113 | data.append('searchType', params.searchType) 114 | data.append('searchWords', params.searchWords) 115 | 116 | request('product/list', data, (response) => { 117 | callback(response) 118 | }) 119 | } 120 | 121 | const deleteProduct = (params, callback) => { 122 | // console.log(params) 123 | var data = new URLSearchParams() 124 | data.append('token', localStorage.token) 125 | data.append('productId', params.productId) 126 | 127 | request('product/delete', data, (response) => { 128 | callback(response) 129 | }) 130 | } 131 | 132 | const addStock = (params, callback) => { 133 | // console.log(params) 134 | var data = new URLSearchParams() 135 | data.append('token', localStorage.token) 136 | data.append('productId', params.productId) 137 | data.append('content', params.content) 138 | 139 | request('product/stock/add', data, (response) => { 140 | callback(response) 141 | }) 142 | } 143 | 144 | const stockList = (params, callback) => { 145 | // console.log(params) 146 | var data = new URLSearchParams() 147 | data.append('token', localStorage.token) 148 | data.append('productId', params.productId) 149 | data.append('page', params.page) 150 | data.append('size', params.size) 151 | data.append('searchType', params.searchType) 152 | data.append('searchWords', params.searchWords) 153 | 154 | request('product/stock/list', data, (response) => { 155 | callback(response) 156 | }) 157 | } 158 | 159 | const deleteStock = (params, callback) => { 160 | // console.log(params) 161 | var data = new URLSearchParams() 162 | data.append('token', localStorage.token) 163 | data.append('stockId', params.stockId) 164 | 165 | request('product/stock/delete', data, (response) => { 166 | callback(response) 167 | }) 168 | } 169 | 170 | const stockOrderNo = (params, callback) => { 171 | // console.log(params) 172 | var data = new URLSearchParams() 173 | data.append('token', localStorage.token) 174 | data.append('stockId', params.stockId) 175 | 176 | request('product/stock/orderno', data, (response) => { 177 | callback(response) 178 | }) 179 | } 180 | 181 | const orderList = (params, callback) => { 182 | // console.log(params) 183 | var data = new URLSearchParams() 184 | data.append('token', localStorage.token) 185 | data.append('page', params.page) 186 | data.append('size', params.size) 187 | data.append('searchType', params.searchType) 188 | data.append('searchWords', params.searchWords) 189 | 190 | request('order/list', data, (response) => { 191 | callback(response) 192 | }) 193 | } 194 | 195 | const orderConfirm = (params, callback) => { 196 | // console.log(params) 197 | var data = new URLSearchParams() 198 | data.append('token', localStorage.token) 199 | data.append('order_no', params.order_no) 200 | 201 | request('order/confirm', data, (response) => { 202 | callback(response) 203 | }) 204 | } 205 | 206 | const qcloudToken = (params, callback) => { 207 | var data = new URLSearchParams() 208 | data.append('token', localStorage.token) 209 | 210 | request('common/QCloud/sign', data, (response) => { 211 | if (response && 212 | response.status === 200 && 213 | response.data.code === 0) { 214 | callback(response) 215 | } 216 | callback(response) 217 | }) 218 | } 219 | 220 | const getValidImage = (params, callback) => { 221 | // console.log(params) 222 | request('common/validImage/create', params, (response) => { 223 | callback(response) 224 | }) 225 | } 226 | 227 | const api = { 228 | reg: reg, 229 | login: login, 230 | qcloudToken: qcloudToken, 231 | getValidImage: getValidImage, 232 | addProduct: addProduct, 233 | productList: productList, 234 | deleteProduct: deleteProduct, 235 | addStock: addStock, 236 | stockList: stockList, 237 | deleteStock: deleteStock, 238 | orderList: orderList, 239 | orderConfirm: orderConfirm, 240 | stockOrderNo: stockOrderNo, 241 | merchantInfo: merchantInfo, 242 | merchantInfoSave: merchantInfoSave 243 | } 244 | 245 | export default api 246 | -------------------------------------------------------------------------------- /api/api_personal_pay/libs/qcloud_cos/cos_params_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import re 5 | 6 | 7 | class ParamCheck(object): 8 | """BaseRequest基本类型的请求""" 9 | def __init__(self): 10 | self._err_tips = '' 11 | 12 | def get_err_tips(self): 13 | """获取错误信息 14 | 15 | :return: 16 | """ 17 | return self._err_tips 18 | 19 | def check_param_unicode(self, param_name, param_value): 20 | """检查参数是否是unicode 21 | 22 | :param param_name: param_name 参数名 23 | :param param_value: param_value 参数值 24 | :return: 25 | """ 26 | if param_value is None: 27 | self._err_tips = param_name + ' is None!' 28 | return False 29 | if not isinstance(param_value, str): 30 | self._err_tips = param_name + ' is not unicode!' 31 | return False 32 | return True 33 | 34 | def check_param_int(self, param_name, param_value): 35 | """检查参数是否是int 36 | 37 | :param param_name: param_name 参数名 38 | :param param_value: param_value 参数值 39 | :return: 40 | """ 41 | if param_value is None: 42 | self._err_tips = param_name + ' is None!' 43 | return False 44 | if not isinstance(param_value, int): 45 | self._err_tips = param_name + ' is not int!' 46 | return False 47 | return True 48 | 49 | def check_cos_path_valid(self, cos_path, is_file_path): 50 | """检查cos_path是否合法 51 | 52 | 路径必须以/开始,文件路径则不能以/结束, 目录路径必须以/结束 53 | 54 | :param cos_path: 55 | :param is_file_path: 56 | :return: True for valid path, other False 57 | """ 58 | if cos_path[0] != '/': 59 | self._err_tips = 'cos path must start with /' 60 | return False 61 | 62 | last_letter = cos_path[len(cos_path) - 1] 63 | if is_file_path and last_letter == '/': 64 | self._err_tips = 'for file operation, cos_path must not end with /' 65 | return False 66 | elif not is_file_path and last_letter != '/': 67 | self._err_tips = 'for folder operation, cos_path must end with /' 68 | return False 69 | else: 70 | pass 71 | 72 | illegal_letters = ['?', '*', ':', '|', '\\', '<', '>', '"'] 73 | for illegal_letter in illegal_letters: 74 | if cos_path.find(illegal_letter) != -1: 75 | self._err_tips = 'cos path contain illegal letter %s' % illegal_letter 76 | return False 77 | 78 | pattern = re.compile(r'/(\s*)/') 79 | if pattern.search(cos_path): 80 | self._err_tips = 'cos path contain illegal letter / /' 81 | return False 82 | return True 83 | 84 | def check_not_cos_root(self, cos_path): 85 | """检查不是cos的根路径 86 | 87 | 不能对根路径操作的有 1 update 2 create 3 delete 88 | :param cos_path: 89 | :return: 90 | """ 91 | if cos_path == '/': 92 | self._err_tips = 'bucket operation is not supported by sdk,' 93 | ' please use cos console: https://console.qcloud.com/cos' 94 | return False 95 | else: 96 | return True 97 | 98 | def check_local_file_valid(self, local_path): 99 | """检查本地文件有效(存在并且可读) 100 | 101 | :param local_path: 102 | :return: 103 | """ 104 | if not os.path.exists(local_path): 105 | self._err_tips = 'local_file %s not exist!' % local_path 106 | return False 107 | if not os.path.isfile(local_path): 108 | self._err_tips = 'local_file %s is not regular file!' % local_path 109 | return False 110 | if not os.access(local_path, os.R_OK): 111 | self._err_tips = 'local_file %s is not readable!' % local_path 112 | return False 113 | return True 114 | 115 | def check_slice_size(self, slice_size): 116 | """检查分片大小有效 117 | 118 | :param slice_size: 119 | :return: 120 | """ 121 | min_size = 64 * 1024 # 512KB 122 | max_size = 3 * 1024 * 1024 # 20MB 123 | 124 | if max_size >= slice_size >= min_size: 125 | return True 126 | else: 127 | self._err_tips = 'slice_size is invalid, only accept [%d, %d]' \ 128 | % (min_size, max_size) 129 | return False 130 | 131 | def check_insert_only(self, insert_only): 132 | """检查文件上传的insert_only参数 133 | 134 | :param insert_only: 135 | :return: 136 | """ 137 | if insert_only != 1 and insert_only != 0: 138 | self._err_tips = 'insert_only only support 0 and 1' 139 | return False 140 | else: 141 | return True 142 | 143 | def check_move_over_write(self, to_over_write): 144 | """检查move的over write标志 145 | 146 | :param to_over_write: 147 | :return: 148 | """ 149 | if to_over_write != 1 and to_over_write != 0: 150 | self._err_tips = 'to_over_write only support 0 and 1' 151 | return False 152 | else: 153 | return True 154 | 155 | def check_file_authority(self, authority): 156 | """检查文件的authority属性 157 | 158 | 合法的取值只有eInvalid, eWRPrivate, eWPrivateRPublic和空值 159 | :param authority: 160 | :return: 161 | """ 162 | if authority != ''and authority != 'eInvalid' and authority != 'eWRPrivate' and authority != 'eWPrivateRPublic': 163 | self._err_tips = 'file authority valid value is: eInvalid, eWRPrivate, eWPrivateRPublic' 164 | return False 165 | else: 166 | return True 167 | 168 | def check_x_cos_meta_dict(self, x_cos_meta_dict): 169 | """检查x_cos_meta_dict, key和value都必须是UTF8编码 170 | 171 | :param x_cos_meta_dict: 172 | :return: 173 | """ 174 | prefix_len = len('x-cos-meta-') 175 | for key in x_cos_meta_dict.keys(): 176 | if not self.check_param_unicode('x-cos-meta-key', key): 177 | return False 178 | if not self.check_param_unicode('x-cos-meta-value', x_cos_meta_dict[key]): 179 | return False 180 | if key[0:prefix_len] != 'x-cos-meta-': 181 | self._err_tips = 'x-cos-meta key must start with x-cos-meta-' 182 | return False 183 | if len(key) == prefix_len: 184 | self._err_tips = 'x-cos-meta key must not just be x-cos-meta-' 185 | return False 186 | if len(x_cos_meta_dict[key]) == 0: 187 | self._err_tips = 'x-cos-meta value must not be empty' 188 | return False 189 | return True 190 | 191 | def check_update_flag(self, flag): 192 | """检查更新文件的flag 193 | 194 | :param flag: 195 | :return: 196 | """ 197 | if flag == 0: 198 | self._err_tips = 'no any attribute to be updated!' 199 | return False 200 | else: 201 | return True 202 | 203 | def check_list_order(self, list_order): 204 | """ 检查list folder的order 205 | 206 | :param list_order: 合法取值0(正序), 1(逆序) 207 | :return: 208 | """ 209 | if list_order != 0 and list_order != 1: 210 | self._err_tips = 'list order is invalid, please use 0(positive) or 1(reverse)!' 211 | return False 212 | else: 213 | return True 214 | 215 | def check_list_pattern(self, list_pattern): 216 | """检查list folder的pattern 217 | 218 | :param list_pattern: 合法取值eListBoth, eListDirOnly, eListFileOnly 219 | :return: 220 | """ 221 | if list_pattern != 'eListBoth' and list_pattern != 'eListDirOnly' and list_pattern != 'eListFileOnly': 222 | self._err_tips = 'list pattern is invalid, please use eListBoth or eListDirOnly or eListFileOnly' 223 | return False 224 | else: 225 | return True 226 | -------------------------------------------------------------------------------- /api/api_personal_pay/resources/client.py: -------------------------------------------------------------------------------- 1 | from flask_restful import reqparse, fields, marshal 2 | from util.db import DB 3 | from table.model import Product, ProductStock, Merchant, Order 4 | from util.commonUtil import CommonUtil 5 | from resources.baseApi import BaseApi 6 | from util.checkUtil import CheckUtil 7 | from util.valid import Valid 8 | import time 9 | from util.convertUtil import ConvertFormatTime 10 | import math 11 | from util.emailUtil import EmailUtil 12 | from config.config import Config 13 | 14 | 15 | # 商品列表 16 | class ClientProductList(BaseApi): 17 | def post(self): 18 | return self.handle() 19 | 20 | def handle(self): 21 | parser = reqparse.RequestParser() 22 | parser.add_argument('mch', required=True) 23 | args = parser.parse_args() 24 | 25 | merchant = DB.session.query(Merchant).filter(Merchant.merchant_no == args.mch).first() 26 | if merchant is None: 27 | return CommonUtil.json_response(-1, '商户不存在') 28 | 29 | products = DB.session.query(Product).join(Merchant).filter(Product.merchant_id == merchant.id).order_by(Product.create_at.desc()).all() 30 | 31 | dic = { 32 | 'productId': fields.String(attribute='record_id'), 33 | 'is_on_sell': fields.Integer, 34 | 'name': fields.String, 35 | 'desc': fields.String, 36 | 'price': fields.String, 37 | 'alipay_qrcode': fields.String, 38 | 'wechat_qrcode': fields.String 39 | } 40 | 41 | mch_dic = { 42 | 'online_from': fields.String, 43 | 'online_to': fields.String, 44 | 'alipay_name': fields.String, 45 | 'alipay_account': fields.String, 46 | 'wechat_name': fields.String, 47 | 'wechat_account': fields.String 48 | } 49 | 50 | data = { 51 | 'list': marshal(products, dic), 52 | 'mch': marshal(merchant, mch_dic) 53 | } 54 | 55 | return CommonUtil.json_response(0, '获取成功', data) 56 | 57 | 58 | class ClientOrderCreate(BaseApi): 59 | def post(self): 60 | return self.handle() 61 | 62 | def handle(self): 63 | parser = reqparse.RequestParser() 64 | parser.add_argument('productId', required=True) 65 | parser.add_argument('from_account', required=True) 66 | parser.add_argument('from_email', required=True) 67 | parser.add_argument('from_nickname', required=True) 68 | parser.add_argument('message', required=True) 69 | parser.add_argument('platform', required=True) 70 | args = parser.parse_args() 71 | 72 | product = DB.session.query(Product).filter(Product.record_id == args.productId).first() 73 | merchant = DB.session.query(Merchant).filter(Merchant.id == product.merchant_id).first() 74 | 75 | if product is None or merchant is None: 76 | return CommonUtil.json_response(-1, '商品查询失败') 77 | 78 | stock = DB.session.query(ProductStock).filter(ProductStock.product_id == product.id).first() 79 | if stock is None: 80 | return CommonUtil.json_response(-1, '商品库存不足') 81 | 82 | if product.is_on_sell == 0: 83 | return CommonUtil.json_response(-1, '商品已下架') 84 | 85 | if Valid.is_non_empty_str(args.from_account) is False: 86 | return CommonUtil.json_response(-1, '支付账号不能为空') 87 | 88 | if Valid.is_non_empty_str(args.from_email) is False: 89 | return CommonUtil.json_response(-1, '收货邮箱不能为空') 90 | 91 | secret_key = CommonUtil.md5(str(time.time()) + args.from_account + args.productId + 'secret_key') 92 | 93 | order_no = CommonUtil.md5(str(time.time()) + args.from_account + args.productId) 94 | 95 | if int(args.platform) == 0: 96 | payment = '支付宝' 97 | else: 98 | payment = '微信支付' 99 | 100 | email_head = '
' 101 | email_tail = '
我已收到转账,点击确认收款
Copyright@2018 51shuaba.xyz All Rights Reseved.
' % ( 102 | Config.NOTIFY_ROOT_URL + '/confirm.html?secretkey=' + secret_key + '&orderno=' + order_no 103 | ) 104 | email_order_no = '
%s %s
' % ( 105 | '订单号', order_no) 106 | email_time = '
%s %s
' % ( 107 | '提交时间', CommonUtil.timestamp_to_time(int(time.time()))) 108 | email_payment = '
%s %s
' % ( 109 | '支付方式', payment) 110 | email_product_name = '
%s %s
' % ( 111 | '商品名称', product.name) 112 | email_product_price = '
%s %s
' % ( 113 | '商品价格', str(product.price / 100) + '元') 114 | email_account = '
%s %s
' % ( 115 | '支付账号', args.from_account) 116 | email_email = '
%s %s
' % ( 117 | '收货邮箱', args.from_email) 118 | email_nickname = '
%s %s
' % ( 119 | '支付昵称', args.from_nickname) 120 | email_message = '
%s %s
' % ( 121 | '买家留言', args.message) 122 | 123 | info = '%s%s%s%s%s%s%s%s%s%s%s' % (email_head, email_order_no, email_time, email_payment, email_product_name, email_product_price, email_account, email_email, email_nickname, email_message, email_tail) 124 | 125 | result = EmailUtil.send_html_email('收到新的商品订单,买家正在付款中~', info, merchant.email) 126 | 127 | if result is True: 128 | order = Order( 129 | merchant_id=merchant.id, 130 | product_id=product.id, 131 | order_no=order_no, 132 | platform=args.platform, 133 | create_at=CommonUtil.time_format_str(), 134 | cost=product.price, 135 | from_account=args.from_account, 136 | from_nickname=args.from_nickname, 137 | from_email=args.from_email, 138 | message=args.message, 139 | confirm_secret_key=secret_key 140 | ) 141 | 142 | DB.session.add(order) 143 | DB.session.commit() 144 | 145 | return CommonUtil.json_response(0, '下单成功') 146 | else: 147 | return CommonUtil.json_response(-1, '邮件通知商户失败,请重试') 148 | -------------------------------------------------------------------------------- /api/api_personal_pay/resources/order.py: -------------------------------------------------------------------------------- 1 | from flask_restful import reqparse, fields, marshal 2 | from util.db import DB 3 | from table.model import Product, ProductStock, Merchant, Order 4 | from util.commonUtil import CommonUtil 5 | from resources.baseApi import BaseApi 6 | from util.checkUtil import CheckUtil 7 | from util.valid import Valid 8 | import time 9 | from util.convertUtil import ConvertFormatTime 10 | import math 11 | from util.emailUtil import EmailUtil 12 | 13 | 14 | class OrderList(BaseApi): 15 | def post(self): 16 | return self.handle() 17 | 18 | def handle(self): 19 | parser = reqparse.RequestParser() 20 | parser.add_argument('token', required=True) 21 | parser.add_argument('page', required=True) 22 | parser.add_argument('size', required=True) 23 | parser.add_argument('searchType') 24 | parser.add_argument('searchWords') 25 | args = parser.parse_args() 26 | 27 | # 效验token 28 | result = CheckUtil.check_merchant_token(args.token) 29 | if result.code != 0: 30 | return CommonUtil.json_response(result.code, result.message) 31 | 32 | page = int(args.page) 33 | size = int(args.size) 34 | 35 | if Valid.is_non_empty_str(args.searchType) and Valid.is_non_empty_str(args.searchWords): 36 | if args.searchType == 'order_no': 37 | orders = DB.session.query(Order.order_no, Order.platform_order_no, Order.platform, Order.create_at, 38 | Order.confirm_at, Order.cost, Order.from_account, Order.from_email, 39 | Order.from_nickname, Order.message, Product.name, Product.record_id).\ 40 | join(Product) .\ 41 | filter(Product.id == Order.product_id) .\ 42 | filter(Order.merchant_id == result.data.id). \ 43 | filter(Order.order_no.like('%' + args.searchWords + '%')). \ 44 | order_by(Order.create_at.desc()).limit(size).offset((page - 1) * size).\ 45 | all() 46 | count = DB.session.query(Order).\ 47 | filter(Order.merchant_id == result.data.id). \ 48 | filter(Order.order_no.like('%' + args.searchWords + '%')). \ 49 | count() 50 | orders = CommonUtil.sql_result_to_json(orders) 51 | elif args.searchType == 'from_account': 52 | orders = DB.session.query(Order.order_no, Order.platform_order_no, Order.platform, Order.create_at, 53 | Order.confirm_at, Order.cost, Order.from_account, Order.from_email, 54 | Order.from_nickname, Order.message, Product.name, Product.record_id).\ 55 | join(Product) .\ 56 | filter(Product.id == Order.product_id) .\ 57 | filter(Order.merchant_id == result.data.id). \ 58 | filter(Order.from_account.like('%' + args.searchWords + '%')). \ 59 | order_by(Order.create_at.desc()).limit(size).offset((page - 1) * size). \ 60 | all() 61 | count = DB.session.query(Order). \ 62 | filter(Order.merchant_id == result.data.id). \ 63 | filter(Order.from_account.like('%' + args.searchWords + '%')). \ 64 | count() 65 | orders = CommonUtil.sql_result_to_json(orders) 66 | elif args.searchType == 'from_email': 67 | orders = DB.session.query(Order.order_no, Order.platform_order_no, Order.platform, Order.create_at, 68 | Order.confirm_at, Order.cost, Order.from_account, Order.from_email, 69 | Order.from_nickname, Order.message, Product.name, Product.record_id).\ 70 | join(Product) .\ 71 | filter(Product.id == Order.product_id) .\ 72 | filter(Order.merchant_id == result.data.id). \ 73 | filter(Order.from_email.like('%' + args.searchWords + '%')). \ 74 | order_by(Order.create_at.desc()).limit(size).offset((page - 1) * size). \ 75 | all() 76 | count = DB.session.query(Order). \ 77 | filter(Order.merchant_id == result.data.id). \ 78 | filter(Order.from_email.like('%' + args.searchWords + '%')). \ 79 | count() 80 | orders = CommonUtil.sql_result_to_json(orders) 81 | else: 82 | orders = DB.session.query(Order.order_no, Order.platform_order_no, Order.platform, Order.create_at, 83 | Order.confirm_at, Order.cost, Order.from_account, Order.from_email, 84 | Order.from_nickname, Order.message, Product.name, Product.record_id).\ 85 | join(Product) .\ 86 | filter(Product.id == Order.product_id) .\ 87 | filter(Order.merchant_id == result.data.id). \ 88 | order_by(Order.create_at.desc()).limit(size).offset((page - 1) * size).all() 89 | count = DB.session.query(Order). \ 90 | filter(Order.merchant_id == result.data.id). \ 91 | count() 92 | orders = CommonUtil.sql_result_to_json(orders) 93 | else: 94 | orders = DB.session.query(Order.order_no, Order.platform_order_no, Order.platform, Order.create_at, 95 | Order.confirm_at, Order.cost, Order.from_account, Order.from_email, 96 | Order.from_nickname, Order.message, Product.name, Product.record_id).\ 97 | join(Product) .\ 98 | filter(Product.id == Order.product_id) .\ 99 | filter(Order.merchant_id == result.data.id). \ 100 | order_by(Order.create_at.desc()).limit(size).offset((page - 1) * size).all() 101 | count = DB.session.query(Order).\ 102 | filter(Order.merchant_id == result.data.id).\ 103 | count() 104 | orders = CommonUtil.sql_result_to_json(orders) 105 | 106 | dic = { 107 | 'order_no': fields.String, 108 | 'platform_order_no': fields.String, 109 | 'platform': fields.Integer, 110 | 'create_at': ConvertFormatTime(), 111 | 'confirm_at': ConvertFormatTime(), 112 | 'cost': fields.String, 113 | 'from_account': fields.String, 114 | 'from_email': fields.String, 115 | 'from_nickname': fields.String, 116 | 'message': fields.String, 117 | 'product_name': fields.String(attribute='name'), 118 | 'productId': fields.String(attribute='record_id') 119 | } 120 | 121 | data = { 122 | 'list': marshal(orders, dic), 123 | 'totalCount': math.ceil(count) 124 | } 125 | 126 | return CommonUtil.json_response(0, '获取成功', data) 127 | 128 | 129 | class OrderConfirm(BaseApi): 130 | def post(self): 131 | return self.handle() 132 | 133 | def handle(self): 134 | parser = reqparse.RequestParser() 135 | parser.add_argument('token', required=True) 136 | parser.add_argument('order_no', required=True) 137 | args = parser.parse_args() 138 | 139 | # 效验token 140 | result = CheckUtil.check_merchant_token(args.token) 141 | if result.code != 0: 142 | return CommonUtil.json_response(result.code, result.message) 143 | 144 | order = DB.session.query(Order).filter(Order.order_no == args.order_no).first() 145 | if order is None: 146 | return CommonUtil.json_response(-1, '订单不存在') 147 | 148 | if order.confirm_at: 149 | return CommonUtil.json_response(-1, '订单已确认过') 150 | 151 | stock = DB.session.query(ProductStock).\ 152 | filter(order.product_id == ProductStock.product_id). \ 153 | filter(ProductStock.sold_at == None). \ 154 | first() 155 | if stock: 156 | stock.sold_at = CommonUtil.time_format_str() 157 | stock.order_id = order.id 158 | DB.session.commit() 159 | 160 | order.confirm_at = CommonUtil.time_format_str() 161 | DB.session.commit() 162 | 163 | info = '

%s

Copyright@2018 51shuaba.xyz All Rights Reseved.
' % ( 164 | stock.content 165 | ) 166 | 167 | result = EmailUtil.send_html_email('订单' + args.order_no + '发货通知', info, order.from_email) 168 | 169 | if result is True: 170 | return CommonUtil.json_response(0, '确认成功,已邮件通知买家') 171 | else: 172 | return CommonUtil.json_response(0, '确认成功,但是发货邮件未能送达,请联系买家') 173 | 174 | return CommonUtil.json_response(-1, '库存不足') 175 | -------------------------------------------------------------------------------- /api/api_personal_pay/resources/product.py: -------------------------------------------------------------------------------- 1 | from flask_restful import reqparse, fields, marshal 2 | from util.db import DB 3 | from table.model import Product, ProductStock, Order 4 | from util.commonUtil import CommonUtil 5 | from resources.baseApi import BaseApi 6 | from util.checkUtil import CheckUtil 7 | from util.valid import Valid 8 | import time 9 | from util.convertUtil import ConvertFormatTime 10 | import math 11 | from sqlalchemy import func 12 | 13 | 14 | # 商品新增 15 | class ProductAdd(BaseApi): 16 | def post(self): 17 | return self.handle() 18 | 19 | def handle(self): 20 | parser = reqparse.RequestParser() 21 | parser.add_argument('token', required=True) 22 | parser.add_argument('name', required=True) 23 | parser.add_argument('desc', required=True) 24 | parser.add_argument('price', required=True) 25 | parser.add_argument('alipay_qrcode', required=True) 26 | parser.add_argument('wechat_qrcode', required=True) 27 | parser.add_argument('productId', required=True) 28 | parser.add_argument('is_on_sell', required=True) 29 | args = parser.parse_args() 30 | 31 | # 效验token 32 | result = CheckUtil.check_merchant_token(args.token) 33 | if result.code != 0: 34 | return CommonUtil.json_response(result.code, result.message) 35 | 36 | if Valid.is_non_empty_str(args.name) is False: 37 | return CommonUtil.json_response(-1, '商品名称不能为空') 38 | 39 | if Valid.is_non_empty_str(args.price) is False: 40 | return CommonUtil.json_response(-1, '商品单价不能为空') 41 | 42 | if len(args.productId) == 0: 43 | product = DB.session.query(Product).filter(Product.name == args.name).filter( 44 | Product.merchant_id == result.data.id).first() 45 | if product: 46 | return CommonUtil.json_response(-1, '商品名称已存在') 47 | 48 | product = Product( 49 | merchant_id=result.data.id, 50 | record_id=CommonUtil.md5(args.name + args.token + str(time.time())), 51 | name=args.name, 52 | desc=args.desc, 53 | price=args.price, 54 | is_on_sell='1', 55 | create_at=CommonUtil.time_format_str(), 56 | alipay_qrcode=args.alipay_qrcode, 57 | wechat_qrcode=args.wechat_qrcode 58 | ) 59 | DB.session.add(product) 60 | DB.session.commit() 61 | 62 | return CommonUtil.json_response(0, '新增成功') 63 | else: 64 | product = DB.session.query(Product).filter(Product.record_id == args.productId).filter( 65 | Product.merchant_id == result.data.id).first() 66 | if product: 67 | product.price = args.price 68 | product.desc = args.desc 69 | product.alipay_qrcode= args.alipay_qrcode 70 | product.wechat_qrcode = args.wechat_qrcode 71 | product.is_on_sell = args.is_on_sell 72 | 73 | DB.session.commit() 74 | 75 | return CommonUtil.json_response(0, '修改成功') 76 | 77 | return CommonUtil.json_response(-1, '未知错误') 78 | 79 | 80 | # 商品列表 81 | class ProductList(BaseApi): 82 | def post(self): 83 | return self.handle() 84 | 85 | def handle(self): 86 | parser = reqparse.RequestParser() 87 | parser.add_argument('token', required=True) 88 | parser.add_argument('page', required=True) 89 | parser.add_argument('size', required=True) 90 | parser.add_argument('searchType') 91 | parser.add_argument('searchWords') 92 | args = parser.parse_args() 93 | 94 | # 效验token 95 | result = CheckUtil.check_merchant_token(args.token) 96 | if result.code != 0: 97 | return CommonUtil.json_response(result.code, result.message) 98 | 99 | page = int(args.page) 100 | size = int(args.size) 101 | 102 | if Valid.is_non_empty_str(args.searchType) and Valid.is_non_empty_str(args.searchWords): 103 | if args.searchType == 'product_name': 104 | products = DB.session.query(Product). \ 105 | filter(Product.merchant_id == result.data.id). \ 106 | filter(Product.name.like('%' + args.searchWords + '%')). \ 107 | order_by(Product.create_at.desc()).limit(size).offset((page - 1) * size). \ 108 | all() 109 | count = DB.session.query(Product).\ 110 | filter(Product.merchant_id == result.data.id). \ 111 | filter(Product.name.like('%' + args.searchWords + '%')). \ 112 | count() 113 | else: 114 | products = DB.session.query(Product). \ 115 | filter(Product.merchant_id == result.data.id). \ 116 | order_by(Product.create_at.desc()).limit(size).offset((page - 1) * size). \ 117 | all() 118 | count = DB.session.query(Product).filter(Product.merchant_id == result.data.id).count() 119 | 120 | dic = { 121 | 'productId': fields.String(attribute='record_id'), 122 | 'create_at': ConvertFormatTime(), 123 | 'is_on_sell': fields.Integer, 124 | 'name': fields.String, 125 | 'desc': fields.String, 126 | 'price': fields.String, 127 | 'alipay_qrcode': fields.String, 128 | 'wechat_qrcode': fields.String 129 | } 130 | 131 | data = { 132 | 'list': marshal(products, dic), 133 | 'totalCount': math.ceil(count) 134 | } 135 | 136 | return CommonUtil.json_response(0, '获取成功', data) 137 | 138 | 139 | # 商品删除 140 | class ProductDelete(BaseApi): 141 | def post(self): 142 | return self.handle() 143 | 144 | def handle(self): 145 | parser = reqparse.RequestParser() 146 | parser.add_argument('token', required=True) 147 | parser.add_argument('productId', required=True) 148 | args = parser.parse_args() 149 | 150 | # 效验token 151 | result = CheckUtil.check_merchant_token(args.token) 152 | if result.code != 0: 153 | return CommonUtil.json_response(result.code, result.message) 154 | 155 | DB.session.query(Product).\ 156 | filter(Product.record_id == args.productId).\ 157 | filter(Product.merchant_id == result.data.id).\ 158 | delete() 159 | DB.session.commit() 160 | 161 | return CommonUtil.json_response(0, '删除成功') 162 | 163 | 164 | # 商品库存新增 165 | class ProductStockAdd(BaseApi): 166 | def post(self): 167 | return self.handle() 168 | 169 | def handle(self): 170 | parser = reqparse.RequestParser() 171 | parser.add_argument('token', required=True) 172 | parser.add_argument('productId', required=True) 173 | parser.add_argument('content', required=True) 174 | args = parser.parse_args() 175 | 176 | # 效验token 177 | result = CheckUtil.check_merchant_token(args.token) 178 | if result.code != 0: 179 | return CommonUtil.json_response(result.code, result.message) 180 | 181 | if Valid.is_non_empty_str(args.content) is False: 182 | return CommonUtil.json_response(-1, '内容不能为空') 183 | 184 | product = DB.session.query(Product).filter(Product.record_id == args.productId).filter(Product.merchant_id == result.data.id).first() 185 | if product is None: 186 | return CommonUtil.json_response(-1, '商品不存在') 187 | if product.is_on_sell == 0: 188 | return CommonUtil.json_response(-1, '商品已下架') 189 | 190 | contents = args.content.split('#separator#') 191 | create_at = CommonUtil.time_format_str() 192 | 193 | for index in range(len(contents)): 194 | content = contents[index] 195 | # 去首尾回车 196 | if len(content) > 2: 197 | if content[:1] == '\n': 198 | content = content[1:] 199 | if len(content) > 2: 200 | if content[-1:] == '\n': 201 | content = content[:-1] 202 | if len(content) > 0 and content != '\n': 203 | productStock = ProductStock( 204 | product_id=product.id, 205 | record_id=CommonUtil.md5(args.productId + args.token + create_at + str(index)), 206 | content=content, 207 | create_at=create_at 208 | ) 209 | DB.session.add(productStock) 210 | DB.session.commit() 211 | 212 | return CommonUtil.json_response(0, '新增成功') 213 | 214 | 215 | # 商品库存列表 216 | class ProductStockList(BaseApi): 217 | def post(self): 218 | return self.handle() 219 | 220 | def handle(self): 221 | parser = reqparse.RequestParser() 222 | parser.add_argument('token', required=True) 223 | parser.add_argument('productId', required=True) 224 | parser.add_argument('page', required=True) 225 | parser.add_argument('size', required=True) 226 | parser.add_argument('searchType') 227 | parser.add_argument('searchWords') 228 | args = parser.parse_args() 229 | 230 | # 效验token 231 | result = CheckUtil.check_merchant_token(args.token) 232 | if result.code != 0: 233 | return CommonUtil.json_response(result.code, result.message) 234 | 235 | page = int(args.page) 236 | size = int(args.size) 237 | 238 | product = DB.session.query(Product).filter(Product.record_id == args.productId).filter( 239 | Product.merchant_id == result.data.id).first() 240 | if product is None: 241 | if Valid.is_non_empty_str(args.searchType) and Valid.is_non_empty_str(args.searchWords): 242 | if args.searchType == 'content': 243 | stocks = DB.session.query(ProductStock.record_id, ProductStock.content, ProductStock.create_at, 244 | ProductStock.sold_at, Product.name). \ 245 | join(Product). \ 246 | filter(Product.merchant_id == result.data.id). \ 247 | filter(Product.id == ProductStock.product_id). \ 248 | filter(ProductStock.content.like('%' + args.searchWords + '%')). \ 249 | order_by(ProductStock.create_at.desc()).limit(size).offset((page - 1) * size). \ 250 | all() 251 | count = DB.session.query(ProductStock). \ 252 | join(Product). \ 253 | filter(Product.merchant_id == result.data.id). \ 254 | filter(Product.id == ProductStock.product_id). \ 255 | filter(ProductStock.content.like('%' + args.searchWords + '%')). \ 256 | count() 257 | stocks = CommonUtil.sql_result_to_json(stocks) 258 | else: 259 | stocks = DB.session.query(ProductStock.record_id, ProductStock.content, ProductStock.create_at, 260 | ProductStock.sold_at, Product.name). \ 261 | join(Product). \ 262 | filter(Product.merchant_id == result.data.id). \ 263 | filter(Product.id == ProductStock.product_id). \ 264 | order_by(ProductStock.create_at.desc()).limit(size).offset((page - 1) * size). \ 265 | all() 266 | count = DB.session.query(ProductStock). \ 267 | join(Product). \ 268 | filter(Product.merchant_id == result.data.id). \ 269 | filter(Product.id == ProductStock.product_id). \ 270 | count() 271 | stocks = CommonUtil.sql_result_to_json(stocks) 272 | else: 273 | if Valid.is_non_empty_str(args.searchType) and Valid.is_non_empty_str(args.searchWords): 274 | if args.searchType == 'content': 275 | stocks = DB.session.query(ProductStock.record_id, ProductStock.content, ProductStock.create_at, 276 | ProductStock.sold_at, Product.name). \ 277 | join(Product). \ 278 | filter(Product.merchant_id == result.data.id). \ 279 | filter(ProductStock.product_id == product.id). \ 280 | filter(ProductStock.content.like('%' + args.searchWords + '%')). \ 281 | order_by(ProductStock.create_at.desc()).limit(size).offset((page - 1) * size). \ 282 | all() 283 | count = DB.session.query(ProductStock). \ 284 | filter(Product.merchant_id == result.data.id). \ 285 | filter(ProductStock.product_id == product.id). \ 286 | filter(ProductStock.content.like('%' + args.searchWords + '%')). \ 287 | count() 288 | stocks = CommonUtil.sql_result_to_json(stocks) 289 | else: 290 | stocks = DB.session.query(ProductStock.record_id, ProductStock.content, ProductStock.create_at, 291 | ProductStock.sold_at, Product.name). \ 292 | join(Product). \ 293 | filter(Product.merchant_id == result.data.id). \ 294 | filter(ProductStock.product_id == product.id). \ 295 | order_by(ProductStock.create_at.desc()).limit(size).offset((page - 1) * size). \ 296 | all() 297 | count = DB.session.query(ProductStock).\ 298 | filter(ProductStock.product_id == product.id). \ 299 | filter(Product.merchant_id == result.data.id). \ 300 | count() 301 | stocks = CommonUtil.sql_result_to_json(stocks) 302 | 303 | dic = { 304 | 'stockId': fields.String(attribute='record_id'), 305 | 'content': fields.String, 306 | 'create_at': ConvertFormatTime(), 307 | 'sold_at': ConvertFormatTime(), 308 | 'order_no': fields.String, 309 | 'product_name': fields.String(attribute='name') 310 | } 311 | 312 | data = { 313 | 'list': marshal(stocks, dic), 314 | 'totalCount': math.ceil(count) 315 | } 316 | 317 | return CommonUtil.json_response(0, '获取成功', data) 318 | 319 | 320 | # 商品库存的订单号 321 | class ProductStockOrderNo(BaseApi): 322 | def post(self): 323 | return self.handle() 324 | 325 | def handle(self): 326 | parser = reqparse.RequestParser() 327 | parser.add_argument('token', required=True) 328 | parser.add_argument('stockId', required=True) 329 | args = parser.parse_args() 330 | 331 | # 效验token 332 | result = CheckUtil.check_merchant_token(args.token) 333 | if result.code != 0: 334 | return CommonUtil.json_response(result.code, result.message) 335 | 336 | stock = DB.session.query(ProductStock).filter(ProductStock.record_id == args.stockId).first() 337 | order = DB.session.query(Order).filter(Order.id == stock.order_id).first() 338 | if stock and order: 339 | return CommonUtil.json_response(0, '获取成功', { 340 | 'order_no': order.order_no 341 | }) 342 | else: 343 | return CommonUtil.json_response(-1, '获取失败') 344 | 345 | 346 | # 库存删除 347 | class ProductStockDelete(BaseApi): 348 | def post(self): 349 | return self.handle() 350 | 351 | def handle(self): 352 | parser = reqparse.RequestParser() 353 | parser.add_argument('token', required=True) 354 | parser.add_argument('stockId', required=True) 355 | args = parser.parse_args() 356 | 357 | # 效验token 358 | result = CheckUtil.check_merchant_token(args.token) 359 | if result.code != 0: 360 | return CommonUtil.json_response(result.code, result.message) 361 | 362 | stock = DB.session.query(ProductStock).filter(ProductStock.record_id == args.stockId).first() 363 | if stock: 364 | product = DB.session.query(Product).filter(Product.id == stock.product_id).filter(Product.merchant_id == result.data.id).first() 365 | if product: 366 | DB.session.query(ProductStock).filter(ProductStock.record_id == args.stockId).delete() 367 | DB.session.commit() 368 | return CommonUtil.json_response(0, '删除成功') 369 | 370 | return CommonUtil.json_response(-1, '删除失败') 371 | -------------------------------------------------------------------------------- /home/buy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 51刷吧 - 免费个人收款 5 | 6 | 7 | 8 | 9 | 319 | 320 | 321 |
322 |
323 | 购买 324 |
325 | 326 |
327 | 328 |
329 |
330 |

支付宝扫一扫,向{{mch.alipay_name}}付款

331 |

微信扫一扫,向{{mch.wechat_name}}付款

332 | 333 | 334 |

商户确认收款后您会收到收货邮件,请耐心等待

335 |
336 |
337 | 338 |
339 |
340 | 商品选择 341 |
342 | {{selectedProduct ? selectedProduct.name : '请选择'}} 343 | 344 |
345 |
346 | 347 |
348 | 349 |
350 | 商户在线时间 351 |
352 | {{mch.online_from + ' ~ ' + mch.online_to}} 353 | 354 |
355 |
356 | 357 |
358 |

{{selectedProduct.desc}}

359 |
360 | 361 |
362 | 363 |
364 |

必填信息

365 |
366 | 367 |
368 | 支付账号 369 |
370 | 371 |
372 |
373 | 374 |
375 | 376 |
377 | 收货邮箱 378 |
379 | 380 |
381 |
382 | 383 |
384 | 385 |
386 |

选填信息

387 |
388 | 389 |
390 | 支付昵称 391 |
392 | 393 |
394 |
395 | 396 |
397 | 398 |
399 | 留言 400 |
401 | 402 |
403 |
404 | 405 |
406 | 407 |
408 |

支付方式

409 |
410 | 411 |
412 |
413 | 414 | 支付宝 415 |
416 |
417 | 418 | 419 |
420 |
421 | 422 |
423 | 424 |
425 |
426 | 427 | 微信支付 428 |
429 |
430 | 431 | 432 |
433 |
434 | 435 |
436 | 437 |
438 |
439 |

¥{{selectedProduct ? (selectedProduct.price / 100).toFixed(2) : ''}}

440 |

{{buttonDisable ? '请稍等' : '扫码付款'}}

441 |
442 |
443 | 444 |
445 |
446 |
447 |
448 |

商品选择

449 |
450 |

+

451 |
452 |
453 |
454 |

{{product.name}}

455 |

¥{{(product.price / 100).toFixed(2)}}

456 |
457 |
458 |
459 |
460 | 461 |
462 | 463 |
464 |
465 | 466 | 467 | 468 | 592 | --------------------------------------------------------------------------------