├── .gitignore ├── LICENSE ├── README.md ├── development.js ├── hiolabsDB.sql.zip ├── package-lock.json ├── package.json ├── pm2.json ├── production.js ├── src ├── admin │ ├── config │ │ └── config.js │ ├── controller │ │ ├── ad.js │ │ ├── admin.js │ │ ├── auth.js │ │ ├── base.js │ │ ├── category.js │ │ ├── delivery.js │ │ ├── goods.js │ │ ├── index.js │ │ ├── keywords.js │ │ ├── notice.js │ │ ├── order.js │ │ ├── shipper.js │ │ ├── shopcart.js │ │ ├── specification.js │ │ ├── user.js │ │ └── wap.js │ ├── logic │ │ └── auth.js │ ├── model │ │ ├── order.js │ │ ├── order_express.js │ │ └── region.js │ └── service │ │ ├── express.js │ │ ├── qiniu.js │ │ └── token.js ├── api │ ├── config │ │ └── config.js │ ├── controller │ │ ├── address.js │ │ ├── auth.js │ │ ├── base.js │ │ ├── cart.js │ │ ├── catalog.js │ │ ├── crontab.js │ │ ├── footprint.js │ │ ├── goods.js │ │ ├── index.js │ │ ├── order.js │ │ ├── pay.js │ │ ├── qrcode.js │ │ ├── region.js │ │ ├── search.js │ │ ├── settings.js │ │ ├── upload.js │ │ ├── user.js │ │ └── weChat.js │ ├── model │ │ ├── cart.js │ │ ├── category.js │ │ ├── footprint.js │ │ ├── goods.js │ │ ├── order.js │ │ ├── order_express.js │ │ ├── region.js │ │ └── shipper.js │ └── service │ │ ├── express.js │ │ ├── qiniu.js │ │ ├── token.js │ │ └── weixin.js └── common │ └── config │ ├── adapter.js │ ├── config.js │ ├── config.production.js │ ├── crontab.js │ ├── database.js │ ├── extend.js │ ├── middleware.js │ ├── node-crontab.js │ └── router.js ├── view └── api │ └── index_index.html └── www └── static ├── css └── style.css ├── images ├── background.jpg └── default_avatar.png └── upload └── avatar └── 25f45a40-9447-41bc-9d53-4c5fc17f3dff.jpeg /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage/ 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Dependency directory 23 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 24 | node_modules/ 25 | 26 | # IDE config 27 | .idea 28 | 29 | # output 30 | output/ 31 | output.tar.gz 32 | 33 | runtime/ 34 | app/ 35 | 36 | config.development.js 37 | adapter.development.js 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 iamdarcy 黑亮(sligxl@163.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 海风小店,开源商城(服务端) 2 | 3 | + 基于开源项目NideShop重建,精简了一些功能的同时完善了一些功能,并重新设计了UI 4 | + 测试数据来自上述开源项目 5 | + 服务端api基于Node.js+ThinkJS+MySQL 6 | 7 | ### 基于海风小店开发上线的小程序 8 | 9 | 10 | ### 视频教程 11 | https://www.bilibili.com/video/av89567916 12 | 13 | ### 本项目需要配合 14 | 微信小程序项目:GitHub: https://github.com/iamdarcy/hioshop-miniprogram 15 | electron版管理后台:https://github.com/iamdarcy/hioshop-admin 16 | web版管理后台:https://github.com/iamdarcy/hioshop-admin-web 17 | 18 | 线上demo:[https://demo.qilelab.com/hioshop](https://demo.qilelab.com/hioshop/) 19 | 用户名:qilelab.com 20 | 密码:qilelab.com 21 | 22 | 23 | 24 | 阿里云主机:低至2折 立即去看看 25 | 26 | ### 本地开发环境配置 27 | + 克隆项目到本地 28 | ``` 29 | git clone https://github.com/iamdarcy/hioshop-server 30 | ``` 31 | + 创建数据库hiolabsDB并导入项目根目录下的hiolabsDB.sql 32 | 推荐使用软件Navicat创建和管理数据库,也可以用以下命令创建: 33 | ``` 34 | CREATE SCHEMA `hiolabsDB` DEFAULT CHARACTER SET utf8mb4 ; 35 | ``` 36 | > 注意数据库字符编码为utf8mb4 37 | + 更改数据库配置 38 | src/common/config/database.js 39 | ``` 40 | const mysql = require('think-model-mysql'); 41 | 42 | module.exports = { 43 | handle: mysql, 44 | database: 'hiolabsDB', 45 | prefix: 'hiolabs_', 46 | encoding: 'utf8mb4', 47 | host: '127.0.0.1', 48 | port: '3306', 49 | user: 'root', 50 | password: '123123', //你的密码 51 | dateStrings: true 52 | }; 53 | ``` 54 | 55 | + 填写微信登录和微信支付配置和其他设置,比如七牛,阿里云快递等等 56 | 57 | src/common/config/config.js 58 | ``` 59 | // default config 60 | module.exports = { 61 | default_module: 'api', 62 | weixin: { 63 | appid: '', // 小程序 appid 64 | secret: '', // 小程序密钥 65 | mch_id: '', // 商户帐号ID 66 | partner_key: '', // 微信支付密钥 67 | notify_url: '' // 微信异步通知,例:https://www.hiolabs.com/api/pay/notify 68 | } 69 | }; 70 | ``` 71 | 72 | + 安装依赖并启动 73 | ``` 74 | npm install 75 | npm start 76 | ``` 77 | 78 | 如果安装不成功,百度搜索cnpm,用淘宝源代替,替换后,用cnpm i进行安装依赖 79 | 80 | 启动后,本地访问 http://127.0.0.1:8360/ 81 | 82 | ### 上线需要以下准备工作: 83 | + 一个微信服务公众号 84 | + 阿里云服务器 85 | + 注册小程序 86 | + 完成认证的七牛 87 | + 完成API安全设置的微信商户,并绑定好小程序id(支付) 88 | + 阿里云物流api 89 | + 备案后的域名 90 | + 如果卖食品,还需要《食品经营许可证》 91 | 92 | 也不一定用七牛云的服务,可以用本地存储,不过要自己开发上传功能,可以参考项目中的upload.js 93 | 94 | 客服使用微信小程序官方提供的客服功能即可 95 | 96 | 97 | ### 功能列表 98 | + 首页:搜索、Banner、公告、分类Icons、分类商品列表 99 | + 详情页:加入购物车、立即购买、选择规格 100 | + 搜索页:排序 101 | + 分类页:分页加载商品 102 | + 我的页面:订单(待付款,待发货,待收货),足迹,收货地址 103 | 104 | ### 项目截图 105 | 请参考微信小程序项目:https://github.com/iamdarcy/hioshop-miniprogram 106 | 107 | ### 最近更新 108 | - 新增生成分享图的功能 109 | 在src/common/config/config.js需要设置好已经开通https的七牛bucket的参数 110 | 111 | 112 | - 项目地址 113 | 服务端: https://github.com/iamdarcy/hioshop-server 114 | 后台管理:https://github.com/iamdarcy/hioshop-admin 115 | 微信小程序:https://github.com/iamdarcy/hioshop-miniprogram 116 | 117 | - 本项目会持续更新和维护,喜欢别忘了 Star,有问题可通过微信、QQ群联系我,谢谢您的关注。 118 | - 我的微信号是lookgxl,加群时回答这个问题即可入群。 119 | 海风小店小程序商城1群 824781955(已满) 120 | 海风小店小程序商城2群 932101372(已满) 121 | 海风小店小程序商城3群 1130172339(已满) 122 | 海风小店小程序商城4群 652317079 123 | 124 | -------------------------------------------------------------------------------- /development.js: -------------------------------------------------------------------------------- 1 | const Application = require('thinkjs'); 2 | const babel = require('think-babel'); 3 | const watcher = require('think-watcher'); 4 | const notifier = require('node-notifier'); 5 | 6 | const instance = new Application({ 7 | ROOT_PATH: __dirname, 8 | watcher: watcher, 9 | transpiler: [babel, { 10 | presets: ['think-node'] 11 | }], 12 | notifier: notifier.notify.bind(notifier), 13 | env: 'development' 14 | }); 15 | 16 | instance.run(); 17 | -------------------------------------------------------------------------------- /hiolabsDB.sql.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamdarcy/hioshop-server/112b61ebdbc97ee193903dc57b2bbf8041541aa3/hiolabsDB.sql.zip -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hioshop", 3 | "description": "hioshop - open source mini program shop", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "start": "node development.js", 7 | "compile": "babel --no-babelrc src/ --presets think-node --out-dir app/", 8 | "lint": "eslint src/", 9 | "lint-fix": "eslint --fix src/" 10 | }, 11 | "dependencies": { 12 | "gm": "^1.23.0", 13 | "jsonwebtoken": "^8.0.0", 14 | "jushuitan": "^1.0.2", 15 | "kcors": "^2.2.1", 16 | "lodash": "^4.17.4", 17 | "md5": "^2.2.1", 18 | "mime-types": "^2.1.24", 19 | "moment": "^2.18.1", 20 | "nanoid": "^2.1.1", 21 | "node-wget": "^0.4.3", 22 | "pinyin": "^2.9.0", 23 | "qiniu": "^7.2.1", 24 | "querystring": "^0.2.0", 25 | "request": "^2.81.0", 26 | "request-promise": "^4.2.1", 27 | "think-cache": "^1.0.0", 28 | "think-cache-file": "^1.0.8", 29 | "think-logger3": "^1.0.0", 30 | "think-model": "^1.0.0", 31 | "think-model-mysql": "^1.0.0", 32 | "think-view": "^1.0.11", 33 | "think-view-nunjucks": "^1.0.7", 34 | "thinkjs": "^3.0.0", 35 | "weixinpay": "^1.0.12", 36 | "xml2js": "^0.4.19" 37 | }, 38 | "devDependencies": { 39 | "babel-cli": "^6.24.1", 40 | "babel-preset-think-node": "^1.0.0", 41 | "node-notifier": "^5.0.2", 42 | "think-watcher": "^3.0.0", 43 | "think-inspect": "0.0.2", 44 | "think-babel": "^1.0.3", 45 | "eslint": "^4.2.0", 46 | "eslint-config-think": "^1.0.0" 47 | }, 48 | "repository": "", 49 | "license": "MIT", 50 | "engines": { 51 | "node": ">=6.0.0" 52 | }, 53 | "readmeFilename": "README.md" 54 | } 55 | -------------------------------------------------------------------------------- /pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [{ 3 | "name": "hiolabs", 4 | "script": "production.js", 5 | "cwd": "/var/www/hioshop-server", 6 | "exec_mode": "fork", 7 | "max_memory_restart": "1G", 8 | "autorestart": true, 9 | "node_args": [], 10 | "args": [], 11 | "env": { 12 | 13 | } 14 | }] 15 | } 16 | -------------------------------------------------------------------------------- /production.js: -------------------------------------------------------------------------------- 1 | const Application = require('thinkjs'); 2 | 3 | const instance = new Application({ 4 | ROOT_PATH: __dirname, 5 | proxy: true, // use proxy 6 | env: 'production' 7 | }); 8 | 9 | instance.run(); 10 | -------------------------------------------------------------------------------- /src/admin/config/config.js: -------------------------------------------------------------------------------- 1 | // default config test 2 | module.exports = { 3 | 4 | }; 5 | -------------------------------------------------------------------------------- /src/admin/controller/ad.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | module.exports = class extends Base { 4 | /** 5 | * index action 6 | * @return {Promise} [] 7 | */ 8 | async indexAction() { 9 | const page = this.get('page') || 1; 10 | const size = this.get('size') || 10; 11 | const model = this.model('ad'); 12 | const data = await model.where({ 13 | is_delete: 0 14 | }).order(['id ASC']).page(page, size).countSelect(); 15 | for (const item of data.data) { 16 | if (item.end_time != 0) { 17 | item.end_time = moment.unix(item.end_time).format('YYYY-MM-DD HH:mm:ss'); 18 | } 19 | if (item.enabled == 1) { 20 | item.enabled = true; 21 | } else { 22 | item.enabled = false; 23 | } 24 | } 25 | return this.success(data); 26 | } 27 | async updateSortAction() { 28 | const id = this.post('id'); 29 | const sort = this.post('sort'); 30 | const model = this.model('ad'); 31 | const data = await model.where({ 32 | id: id 33 | }).update({ 34 | sort_order: sort 35 | }); 36 | return this.success(data); 37 | } 38 | async infoAction() { 39 | const id = this.get('id'); 40 | const model = this.model('ad'); 41 | const data = await model.where({ 42 | id: id 43 | }).find(); 44 | return this.success(data); 45 | } 46 | async storeAction() { 47 | if (!this.isPost) { 48 | return false; 49 | } 50 | const values = this.post(); 51 | console.log(values); 52 | values.end_time = parseInt(new Date(values.end_time).getTime() / 1000); 53 | const id = this.post('id'); 54 | const model = this.model('ad'); 55 | if (id > 0) { 56 | await model.where({ 57 | id: id 58 | }).update(values); 59 | } else { 60 | let ex = await model.where({ 61 | goods_id: values.goods_id, 62 | is_delete:0 63 | }).find(); 64 | if (think.isEmpty(ex)) { 65 | delete values.id; 66 | if (values.link_type == 0) { 67 | values.link = ''; 68 | } else { 69 | values.goods_id = 0; 70 | } 71 | await model.add(values); 72 | } else { 73 | return this.fail(100, '发生错误'); 74 | } 75 | } 76 | return this.success(values); 77 | } 78 | async getallrelateAction() { 79 | let data = await this.model('goods').where({ 80 | is_on_sale: 1, 81 | is_delete: 0 82 | }).field('id,name,list_pic_url').select(); 83 | return this.success(data); 84 | } 85 | async destoryAction() { 86 | const id = this.post('id'); 87 | await this.model('ad').where({ 88 | id: id 89 | }).limit(1).update({ 90 | is_delete: 1 91 | }); 92 | return this.success(); 93 | } 94 | async saleStatusAction() { 95 | const id = this.get('id'); 96 | const status = this.get('status'); 97 | let sale = 0; 98 | if (status == 'true') { 99 | sale = 1; 100 | } 101 | const model = this.model('ad'); 102 | await model.where({ 103 | id: id 104 | }).update({ 105 | enabled: sale 106 | }); 107 | } 108 | }; -------------------------------------------------------------------------------- /src/admin/controller/admin.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | const md5 = require('md5'); 4 | module.exports = class extends Base { 5 | /** 6 | * index action 7 | * @return {Promise} [] 8 | */ 9 | async indexAction() { 10 | const data = await this.model('admin').where({ 11 | // is_show: 1, 12 | is_delete: 0 13 | }).select(); 14 | for (const item of data) { 15 | if (item.last_login_time != 0) { 16 | item.last_login_time = moment.unix(item.last_login_time).format('YYYY-MM-DD HH:mm:ss'); 17 | } else { 18 | item.last_login_time = '还没登录过' 19 | } 20 | item.password = ''; 21 | } 22 | return this.success(data); 23 | } 24 | async adminDetailAction() { 25 | let id = this.post('id') 26 | let info = await this.model('admin').where({ 27 | id: id 28 | }).find(); 29 | return this.success(info); 30 | } 31 | async adminAddAction() { 32 | let user = this.post('user'); 33 | let password = user.password; 34 | let upData = { 35 | username: info.username, 36 | password_salt: 'HIOLABS' 37 | }; 38 | if (password.replace(/(^\s*)|(\s*$)/g, "").length != 0) { 39 | password = md5(info.password + '' + upData.password_salt); 40 | upData.password = password; 41 | } 42 | await this.model('admin').add(upData); 43 | return this.success(); 44 | } 45 | async adminSaveAction() { 46 | let user = this.post('user'); 47 | let change = this.post('change'); 48 | let upData = { 49 | username: user.username, 50 | }; 51 | if (change == true) { 52 | let newPassword = user.newpassword; 53 | if (newPassword.replace(/(^\s*)|(\s*$)/g, "").length != 0) { 54 | newPassword = md5(user.newpassword + '' + user.password_salt); 55 | upData.password = newPassword; 56 | } 57 | } 58 | let ex = await this.model('admin').where({ 59 | username: user.username, 60 | id: ['<>', user.id] 61 | }).find(); 62 | if (!think.isEmpty(ex)) { 63 | return this.fail(400, '重名了') 64 | } 65 | // if (user.id == 14) { 66 | // return this.fail(400, '演示版后台的管理员密码不能修改!本地开发,删除这个判断') 67 | // } 68 | await this.model('admin').where({ 69 | id: user.id 70 | }).update(upData); 71 | return this.success(); 72 | } 73 | async infoAction() { 74 | const id = this.get('id'); 75 | const model = this.model('user'); 76 | const data = await model.where({ 77 | id: id 78 | }).find(); 79 | return this.success(data); 80 | } 81 | async storeAction() { 82 | if (!this.isPost) { 83 | return false; 84 | } 85 | const values = this.post(); 86 | const id = this.post('id'); 87 | const model = this.model('user'); 88 | values.is_show = values.is_show ? 1 : 0; 89 | values.is_new = values.is_new ? 1 : 0; 90 | if (id > 0) { 91 | await model.where({ 92 | id: id 93 | }).update(values); 94 | } else { 95 | delete values.id; 96 | await model.add(values); 97 | } 98 | return this.success(values); 99 | } 100 | async deleAdminAction() { 101 | const id = this.post('id'); 102 | await this.model('admin').where({ 103 | id: id 104 | }).limit(1).delete(); 105 | return this.success(); 106 | } 107 | async showsetAction() { 108 | const model = this.model('show_settings'); 109 | let data = await model.find(); 110 | return this.success(data); 111 | } 112 | async showsetStoreAction() { 113 | let id = 1; 114 | const values = this.post(); 115 | const model = this.model('show_settings'); 116 | await model.where({ 117 | id: id 118 | }).update(values); 119 | return this.success(values); 120 | } 121 | async changeAutoStatusAction() { 122 | const status = this.post('status'); 123 | await this.model('settings').where({ 124 | id: 1 125 | }).update({ 126 | autoDelivery: status 127 | }); 128 | return this.success(); 129 | } 130 | async storeShipperSettingsAction() { 131 | const values = this.post(); 132 | await this.model('settings').where({ 133 | id: values.id 134 | }).update(values); 135 | return this.success(); 136 | } 137 | async senderInfoAction() { 138 | let info = await this.model('settings').where({ 139 | id: 1 140 | }).find(); 141 | return this.success(info); 142 | } 143 | }; -------------------------------------------------------------------------------- /src/admin/controller/auth.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | module.exports = class extends Base { 3 | async loginAction() { 4 | const username = this.post('username'); 5 | const password = this.post('password'); 6 | const admin = await this.model('admin').where({ 7 | username: username 8 | }).find(); 9 | if (think.isEmpty(admin)) { 10 | return this.fail(401, '用户名或密码不正确!'); 11 | } 12 | console.log(think.md5(password + '' + admin.password_salt)); 13 | console.log(admin.password); 14 | if (think.md5(password + '' + admin.password_salt) !== admin.password) { 15 | return this.fail(400, '用户名或密码不正确!!'); 16 | } 17 | // 更新登录信息 18 | await this.model('admin').where({ 19 | id: admin.id 20 | }).update({ 21 | last_login_time: parseInt(Date.now() / 1000), 22 | last_login_ip: this.ctx.ip 23 | }); 24 | const TokenSerivce = this.service('token', 'admin'); 25 | let sessionData = {} 26 | sessionData.user_id = admin.id 27 | const sessionKey = await TokenSerivce.create(sessionData); 28 | if (think.isEmpty(sessionKey)) { 29 | return this.fail('登录失败'); 30 | } 31 | const userInfo = { 32 | id: admin.id, 33 | username: admin.username, 34 | name:admin.name 35 | }; 36 | return this.success({ 37 | token: sessionKey, 38 | userInfo: userInfo 39 | }); 40 | } 41 | }; -------------------------------------------------------------------------------- /src/admin/controller/base.js: -------------------------------------------------------------------------------- 1 | module.exports = class extends think.Controller { 2 | async __before() { 3 | // 根据token值获取用户id 4 | think.token = this.ctx.header['x-hioshop-token'] || ''; 5 | const tokenSerivce = think.service('token', 'admin'); 6 | think.userId = await tokenSerivce.getUserId(); 7 | // 只允许登录操作 8 | if (this.ctx.controller != 'auth') { 9 | if (think.userId <= 0 || think.userId == undefined) { 10 | return this.fail(401, '请先登录'); 11 | } 12 | } 13 | } 14 | }; -------------------------------------------------------------------------------- /src/admin/controller/category.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | module.exports = class extends Base { 3 | /** 4 | * index action 5 | * @return {Promise} [] 6 | */ 7 | async indexAction() { 8 | const model = this.model('category'); 9 | const data = await model.order(['sort_order ASC']).select(); 10 | const topCategory = data.filter((item) => { 11 | return item.parent_id === 0; 12 | }); 13 | const categoryList = []; 14 | topCategory.map((item) => { 15 | item.level = 1; 16 | categoryList.push(item); 17 | data.map((child) => { 18 | if (child.parent_id === item.id) { 19 | child.level = 2; 20 | categoryList.push(child); 21 | } 22 | if (child.is_show == 1) { 23 | child.is_show = true; 24 | } else { 25 | child.is_show = false; 26 | } 27 | if (child.is_channel == 1) { 28 | child.is_channel = true; 29 | } else { 30 | child.is_channel = false; 31 | } 32 | if (child.is_category == 1) { 33 | child.is_category = true; 34 | } else { 35 | child.is_category = false; 36 | } 37 | }); 38 | }); 39 | return this.success(categoryList); 40 | } 41 | async updateSortAction() { 42 | const id = this.post('id'); 43 | const sort = this.post('sort'); 44 | const model = this.model('category'); 45 | const data = await model.where({ 46 | id: id 47 | }).update({ 48 | sort_order: sort 49 | }); 50 | return this.success(data); 51 | } 52 | async topCategoryAction() { 53 | const model = this.model('category'); 54 | const data = await model.where({ 55 | parent_id: 0 56 | }).order(['id ASC']).select(); 57 | return this.success(data); 58 | } 59 | async infoAction() { 60 | const id = this.get('id'); 61 | const model = this.model('category'); 62 | const data = await model.where({ 63 | id: id 64 | }).find(); 65 | return this.success(data); 66 | } 67 | async storeAction() { 68 | if (!this.isPost) { 69 | return false; 70 | } 71 | const values = this.post(); 72 | const id = this.post('id'); 73 | const model = this.model('category'); 74 | values.is_show = values.is_show ? 1 : 0; 75 | values.is_channel = values.is_channel ? 1 : 0; 76 | values.is_category = values.is_category ? 1 : 0; 77 | if (id > 0) { 78 | await model.where({ 79 | id: id 80 | }).update(values); 81 | } else { 82 | delete values.id; 83 | await model.add(values); 84 | } 85 | return this.success(values); 86 | } 87 | async destoryAction() { 88 | const id = this.post('id'); 89 | let data = await this.model('category').where({ 90 | parent_id: id 91 | }).select(); 92 | if (data.length > 0) { 93 | return this.fail(); 94 | } else { 95 | await this.model('category').where({ 96 | id: id 97 | }).limit(1).delete(); 98 | return this.success(); 99 | } 100 | } 101 | async showStatusAction() { 102 | const id = this.get('id'); 103 | const status = this.get('status'); 104 | let ele = 0; 105 | if (status == 'true') { 106 | ele = 1; 107 | } 108 | const model = this.model('category'); 109 | await model.where({ 110 | id: id 111 | }).update({ 112 | is_show: ele 113 | }); 114 | } 115 | async channelStatusAction() { 116 | const id = this.get('id'); 117 | const status = this.get('status'); 118 | let stat = 0; 119 | if (status == 'true') { 120 | stat = 1; 121 | } 122 | const model = this.model('category'); 123 | await model.where({ 124 | id: id 125 | }).update({ 126 | is_channel: stat 127 | }); 128 | } 129 | async categoryStatusAction() { 130 | const id = this.get('id'); 131 | const status = this.get('status'); 132 | let stat = 0; 133 | if (status == 'true') { 134 | stat = 1; 135 | } 136 | const model = this.model('category'); 137 | await model.where({ 138 | id: id 139 | }).update({ 140 | is_category: stat 141 | }); 142 | } 143 | async deleteBannerImageAction() { 144 | let id = this.post('id'); 145 | await this.model('category').where({ 146 | id: id 147 | }).update({ 148 | img_url: '' 149 | }); 150 | return this.success(); 151 | } 152 | async deleteIconImageAction() { 153 | let id = this.post('id'); 154 | await this.model('category').where({ 155 | id: id 156 | }).update({ 157 | icon_url: '' 158 | }); 159 | return this.success(); 160 | } 161 | }; -------------------------------------------------------------------------------- /src/admin/controller/delivery.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | module.exports = class extends Base { 3 | /** 4 | * index action 5 | * @return {Promise} [] 6 | */ 7 | async indexAction() { 8 | const data = await this.model('shipper').where({ 9 | enabled:1 10 | }).select(); 11 | return this.success(data); 12 | } 13 | }; -------------------------------------------------------------------------------- /src/admin/controller/index.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | 4 | module.exports = class extends Base { 5 | async checkLoginAction(){ 6 | if(think.userId == 0){ 7 | return this.fail(404,'请登录'); 8 | } 9 | } 10 | async indexAction() { 11 | const goodsOnsale = await this.model('goods').where({is_on_sale: 1,is_delete:0}).count(); 12 | const orderToDelivery = await this.model('order').where({order_status: 300}).count(); 13 | const user = await this.model('user').count(); 14 | let data = await this.model('settings').field('countdown').find(); 15 | let timestamp = data.countdown; 16 | let info = { 17 | user: user, 18 | goodsOnsale: goodsOnsale, 19 | timestamp:timestamp, 20 | orderToDelivery: orderToDelivery, 21 | } 22 | return this.success(info); 23 | } 24 | async getQiniuTokenAction(){ 25 | const TokenSerivce = this.service('qiniu'); // 服务里返回token 26 | let data = await TokenSerivce.getQiniuToken(); // 取得token值 goods 27 | let qiniuToken = data.uploadToken; 28 | let domain = data.domain; 29 | let info ={ 30 | token:qiniuToken, 31 | url:domain 32 | }; 33 | return this.success(info); 34 | } 35 | async mainAction() { 36 | const index = this.get('pindex'); 37 | console.log('index:' + index); 38 | let todayTimeStamp = new Date(new Date().setHours(0, 0, 0, 0)) / 1000; //今天零点的时间戳 39 | let yesTimeStamp = todayTimeStamp - 86400; //昨天零点的时间戳 40 | let sevenTimeStamp = todayTimeStamp - 86400 * 7; //7天前零点的时间戳 41 | let thirtyTimeStamp = todayTimeStamp - 86400 * 30; //30天前零点的时间戳 42 | let newUser = 1; 43 | let oldUser = 0; 44 | let addCart = 0; 45 | let addOrderNum = 0; 46 | let addOrderSum = 0; 47 | let payOrderNum = 0; 48 | let payOrderSum = 0; 49 | let newData = []; 50 | let oldData = []; 51 | if (index == 0) { 52 | newData = await this.model('user').where({ 53 | id: ['>', 0], 54 | register_time: ['>', todayTimeStamp] 55 | }).select(); 56 | newUser = newData.length; 57 | for(const item of newData){ 58 | item.nickname = Buffer.from(item.nickname, 'base64').toString(); 59 | } 60 | oldData = await this.model('user').where({ 61 | id: ['>', 0], 62 | register_time: ['<', todayTimeStamp], 63 | last_login_time: ['>', todayTimeStamp] 64 | }).select(); 65 | for(const item of oldData){ 66 | item.nickname = Buffer.from(item.nickname, 'base64').toString(); 67 | } 68 | oldUser = oldData.length; 69 | addCart = await this.model('cart').where({is_delete: 0, add_time: ['>', todayTimeStamp]}).count(); 70 | addOrderNum = await this.model('order').where({ 71 | is_delete: 0, 72 | add_time: ['>', todayTimeStamp] 73 | }).count(); 74 | addOrderSum = await this.model('order').where({ 75 | is_delete: 0, 76 | add_time: ['>', todayTimeStamp] 77 | }).sum('actual_price'); 78 | payOrderNum = await this.model('order').where({ 79 | is_delete: 0, 80 | add_time: ['>', todayTimeStamp], 81 | order_status: ['IN', [201, 802, 300, 301]] 82 | }).count(); 83 | payOrderSum = await this.model('order').where({ 84 | is_delete: 0, 85 | add_time: ['>', todayTimeStamp], 86 | order_status: ['IN', [201, 802, 300, 301]] 87 | }).sum('actual_price'); 88 | } 89 | else if (index == 1) { 90 | newData = await this.model('user').where({ 91 | id: ['>', 0], 92 | register_time: ['BETWEEN', yesTimeStamp, todayTimeStamp] 93 | }).select(); 94 | for(const item of newData){ 95 | item.nickname = Buffer.from(item.nickname, 'base64').toString(); 96 | } 97 | newUser = newData.length; 98 | oldData = await this.model('user').where({ 99 | id: ['>', 0], 100 | register_time: ['<', yesTimeStamp], 101 | last_login_time: ['BETWEEN', yesTimeStamp, todayTimeStamp] 102 | }).select(); 103 | for(const item of oldData){ 104 | item.nickname = Buffer.from(item.nickname, 'base64').toString(); 105 | } 106 | oldUser = oldData.length; 107 | addCart = await this.model('cart').where({ 108 | is_delete: 0, 109 | add_time: ['BETWEEN', yesTimeStamp, todayTimeStamp] 110 | }).count(); 111 | addOrderNum = await this.model('order').where({ 112 | is_delete: 0, 113 | add_time: ['BETWEEN', yesTimeStamp, todayTimeStamp] 114 | }).count(); 115 | addOrderSum = await this.model('order').where({ 116 | is_delete: 0, 117 | add_time: ['BETWEEN', yesTimeStamp, todayTimeStamp] 118 | }).sum('actual_price'); 119 | payOrderNum = await this.model('order').where({ 120 | is_delete: 0, 121 | add_time: ['BETWEEN', yesTimeStamp, todayTimeStamp], 122 | order_status: ['IN', [201, 802, 300, 301]] 123 | }).count(); 124 | console.log('------------321----------'); 125 | console.log(payOrderNum); 126 | console.log('-----------3321-----------'); 127 | payOrderSum = await this.model('order').where({ 128 | is_delete: 0, 129 | add_time: ['BETWEEN', yesTimeStamp, todayTimeStamp], 130 | order_status: ['IN', [201, 802, 300, 301]] 131 | }).sum('actual_price'); 132 | console.log('-----------123-----------'); 133 | console.log(payOrderSum); 134 | console.log('-----------123-----------'); 135 | 136 | } 137 | else if (index == 2) { 138 | newData = await this.model('user').where({ 139 | id: ['>', 0], 140 | register_time: ['>', sevenTimeStamp] 141 | }).select(); 142 | for(const item of newData){ 143 | item.nickname = Buffer.from(item.nickname, 'base64').toString(); 144 | } 145 | newUser = newData.length; 146 | oldData = await this.model('user').where({ 147 | id: ['>', 0], 148 | register_time: ['<', sevenTimeStamp], 149 | last_login_time: ['>', sevenTimeStamp] 150 | }).select(); 151 | for(const item of oldData){ 152 | item.nickname = Buffer.from(item.nickname, 'base64').toString(); 153 | } 154 | oldUser = oldData.length; 155 | addCart = await this.model('cart').where({ 156 | is_delete: 0, 157 | add_time: ['>', sevenTimeStamp] 158 | }).count(); 159 | addOrderNum = await this.model('order').where({ 160 | is_delete: 0, 161 | add_time: ['>', sevenTimeStamp] 162 | }).count(); 163 | addOrderSum = await this.model('order').where({ 164 | is_delete: 0, 165 | add_time: ['>', sevenTimeStamp] 166 | }).sum('actual_price'); 167 | payOrderNum = await this.model('order').where({ 168 | is_delete: 0, 169 | add_time: ['>', sevenTimeStamp], 170 | order_status: ['IN', [201, 802, 300, 301]] 171 | }).count(); 172 | payOrderSum = await this.model('order').where({ 173 | is_delete: 0, 174 | add_time: ['>', sevenTimeStamp], 175 | order_status: ['IN', [201, 802, 300, 301]] 176 | }).sum('actual_price'); 177 | } 178 | else if (index == 3) { 179 | newData = await this.model('user').where({ 180 | id: ['>', 0], 181 | register_time: ['>', thirtyTimeStamp] 182 | }).select(); 183 | for(const item of newData){ 184 | item.nickname = Buffer.from(item.nickname, 'base64').toString(); 185 | } 186 | newUser = newData.length; 187 | oldData = await this.model('user').where({ 188 | id: ['>', 0], 189 | register_time: ['<', thirtyTimeStamp], 190 | last_login_time: ['>', thirtyTimeStamp] 191 | }).select(); 192 | for(const item of oldData){ 193 | item.nickname = Buffer.from(item.nickname, 'base64').toString(); 194 | } 195 | oldUser = oldData.length; 196 | addCart = await this.model('cart').where({ 197 | is_delete: 0, 198 | add_time: ['>', thirtyTimeStamp] 199 | }).count(); 200 | addOrderNum = await this.model('order').where({ 201 | is_delete: 0, 202 | add_time: ['>', thirtyTimeStamp] 203 | }).count(); 204 | addOrderSum = await this.model('order').where({ 205 | is_delete: 0, 206 | add_time: ['>', thirtyTimeStamp] 207 | }).sum('actual_price'); 208 | payOrderNum = await this.model('order').where({ 209 | is_delete: 0, 210 | add_time: ['>', thirtyTimeStamp], 211 | order_status: ['IN', [201, 802, 300, 301]] 212 | }).count(); 213 | payOrderSum = await this.model('order').where({ 214 | is_delete: 0, 215 | add_time: ['>', thirtyTimeStamp], 216 | order_status: ['IN', [201, 802, 300, 301]] 217 | }).sum('actual_price'); 218 | } 219 | if (addOrderSum == null) { 220 | addOrderSum = 0; 221 | } 222 | if (payOrderSum == null) { 223 | payOrderSum = 0; 224 | } 225 | if(newData.length > 0){ 226 | for(const item of newData){ 227 | item.register_time = moment.unix(item.register_time).format('YYYY-MM-DD HH:mm:ss'); 228 | item.last_login_time = moment.unix(item.last_login_time).format('YYYY-MM-DD HH:mm:ss'); 229 | } 230 | } 231 | 232 | if(oldData.length > 0){ 233 | for(const item of oldData){ 234 | item.register_time = moment.unix(item.register_time).format('YYYY-MM-DD HH:mm:ss'); 235 | item.last_login_time = moment.unix(item.last_login_time).format('YYYY-MM-DD HH:mm:ss'); 236 | } 237 | } 238 | 239 | let info = { 240 | newUser: newUser, 241 | oldUser: oldUser, 242 | addCart: addCart, 243 | newData: newData, 244 | oldData: oldData, 245 | addOrderNum: addOrderNum, 246 | addOrderSum: addOrderSum, 247 | payOrderNum: payOrderNum, 248 | payOrderSum: payOrderSum 249 | } 250 | return this.success(info); 251 | } 252 | 253 | 254 | }; 255 | -------------------------------------------------------------------------------- /src/admin/controller/keywords.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | module.exports = class extends Base { 4 | /** 5 | * index action 6 | * @return {Promise} [] 7 | */ 8 | async indexAction() { 9 | const page = this.get('page') || 1; 10 | const size = this.get('size') || 10; 11 | const name = this.get('name') || ''; 12 | 13 | const model = this.model('cart'); 14 | const data = await model.where({goods_name: ['like', `%${name}%`]}).order(['id DESC']).page(page, size).countSelect(); 15 | 16 | for (const item of data.data) { 17 | item.add_time = moment.unix(item.add_time).format('YYYY-MM-DD HH:mm:ss'); 18 | } 19 | 20 | return this.success(data); 21 | } 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /src/admin/controller/notice.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | 4 | module.exports = class extends Base { 5 | /** 6 | * index action 7 | * @return {Promise} [] 8 | */ 9 | async indexAction() { 10 | const model = this.model('notice'); 11 | const data = await model.select(); 12 | 13 | for (const item of data) { 14 | item.end_time = moment.unix(item.end_time).format('YYYY-MM-DD HH:mm:ss'); 15 | } 16 | 17 | return this.success(data); 18 | } 19 | 20 | async updateContentAction() { 21 | const id = this.post('id'); 22 | const content = this.post('content'); 23 | const model = this.model('notice'); 24 | const data = await model.where({id: id}).update({content: content}); 25 | return this.success(data); 26 | } 27 | 28 | 29 | async addAction() { 30 | const content = this.post('content'); 31 | let end_time = this.post('time'); 32 | 33 | end_time = parseInt(new Date(end_time).getTime() / 1000); 34 | 35 | let info = { 36 | content:content, 37 | end_time:end_time 38 | } 39 | const model = this.model('notice'); 40 | const data = await model.add(info); 41 | return this.success(data); 42 | } 43 | 44 | 45 | async updateAction() { 46 | const content = this.post('content'); 47 | let end_time = this.post('time'); 48 | let id = this.post('id'); 49 | 50 | end_time = parseInt(new Date(end_time).getTime() / 1000); 51 | const currentTime = parseInt(new Date().getTime() / 1000); 52 | 53 | 54 | let info = { 55 | content:content, 56 | end_time:end_time 57 | }; 58 | 59 | if(end_time > currentTime){ 60 | info.is_delete = 0; 61 | } 62 | else{ 63 | info.is_delete = 1; 64 | } 65 | const model = this.model('notice'); 66 | const data = await model.where({id:id}).update(info); 67 | return this.success(data); 68 | } 69 | 70 | 71 | async destoryAction() { 72 | const id = this.post('id'); 73 | await this.model('notice').where({id: id}).limit(1).delete(); 74 | return this.success(); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /src/admin/controller/shipper.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | 3 | module.exports = class extends Base { 4 | /** 5 | * index action 6 | * @return {Promise} [] 7 | */ 8 | async indexAction() { 9 | const model = this.model('shipper'); 10 | const info = await model.where({ 11 | enabled: 1 12 | }).select(); 13 | const set = await this.model('settings').where({ 14 | id: 1 15 | }).find(); 16 | let data = { 17 | info: info, 18 | set: set 19 | } 20 | return this.success(data); 21 | } 22 | 23 | async listAction() { 24 | const page = this.get('page') || 1; 25 | const size = this.get('size') || 10; 26 | const name = this.get('name') || ''; 27 | const model = this.model('shipper'); 28 | const data = await model.where({ 29 | 'name|code': ['like', `%${name}%`] 30 | }).order('sort_order ASC').page(page, size).countSelect(); 31 | return this.success(data); 32 | } 33 | 34 | async enabledStatusAction() { 35 | const id = this.get('id'); 36 | const status = this.get('status'); 37 | let sale = 0; 38 | if (status == 'true') { 39 | sale = 1; 40 | } 41 | const model = this.model('shipper'); 42 | await model.where({ 43 | id: id 44 | }).update({ 45 | enabled: sale 46 | }); 47 | return this.success(); 48 | } 49 | 50 | async updateSortAction() { 51 | const id = this.post('id'); 52 | const sort = this.post('sort'); 53 | const model = this.model('shipper'); 54 | const data = await model.where({ 55 | id: id 56 | }).update({ 57 | sort_order: sort 58 | }); 59 | return this.success(data); 60 | } 61 | 62 | async infoAction() { 63 | const id = this.get('id'); 64 | const model = this.model('shipper'); 65 | const data = await model.where({ 66 | id: id 67 | }).find(); 68 | return this.success(data); 69 | } 70 | 71 | async storeAction() { 72 | if (!this.isPost) { 73 | return false; 74 | } 75 | const values = this.post(); 76 | const id = this.post('id'); 77 | 78 | const model = this.model('shipper'); 79 | if (id > 0) { 80 | await model.where({ 81 | id: id 82 | }).update(values); 83 | } else { 84 | delete values.id; 85 | await model.add(values); 86 | } 87 | return this.success(values); 88 | } 89 | 90 | 91 | async destoryAction() { 92 | const id = this.post('id'); 93 | await this.model('shipper').where({ 94 | id: id 95 | }).limit(1).delete(); 96 | return this.success(); 97 | } 98 | 99 | async freightAction() { 100 | const model = this.model('freight_template'); 101 | const data = await model.where({ 102 | is_delete: 0 103 | }).select(); 104 | return this.success(data); 105 | } 106 | 107 | async getareadataAction() { 108 | let all = await this.model('region').where({ 109 | type: 1 110 | }).field('id,name').select(); 111 | return this.success(all); 112 | } 113 | 114 | 115 | async freightdetailAction() { 116 | let id = this.post('id'); 117 | 118 | const model = this.model('freight_template_group'); 119 | let data = await model.where({ 120 | template_id: id, 121 | is_delete: 0, 122 | area: ['<>', 0] 123 | }).select(); 124 | 125 | for (const item of data) { 126 | let area = item.area; 127 | if (item.free_by_money > 0) { 128 | item.freeByMoney = false 129 | } 130 | if (item.free_by_number > 0) { 131 | item.freeByNumber = false 132 | } 133 | let areaData = area.split(','); 134 | let info = await this.model('region').where({ 135 | id: ['IN', areaData] 136 | }).getField('name'); 137 | item.areaName = info.join(','); 138 | } 139 | 140 | let defaultData = await model.where({ 141 | template_id: id, 142 | area: 0, 143 | is_delete: 0 144 | }).select(); 145 | 146 | let freight = await this.model('freight_template').where({ 147 | id: id 148 | }).find(); 149 | 150 | let info = { 151 | freight: freight, 152 | data: data, 153 | defaultData: defaultData 154 | }; 155 | 156 | return this.success(info); 157 | } 158 | 159 | 160 | async saveTableAction() { 161 | let data = this.post('table'); 162 | let def = this.post('defaultData'); 163 | let info = this.post('info'); 164 | let idInfo = []; // 是已存在的id。如果大于零,则去循环。等于零,则先将已存在的data删除,然后判断,1,data的length > 0.则,说明有新的数据 165 | for (const item of data) { 166 | if (item.id > 0) { 167 | idInfo.push(item.id); 168 | } 169 | } 170 | 171 | if (idInfo.length != 0) { 172 | let deleData = await this.model('freight_template_group').where({ 173 | id: ['NOTIN', idInfo], 174 | template_id: info.id, 175 | is_default: 0, 176 | is_delete: 0 177 | }).getField('id'); 178 | 179 | for (const ele of deleData) { 180 | await this.model('freight_template_detail').where({ 181 | template_id: info.id, 182 | group_id: ele, 183 | is_delete: 0 184 | }).update({ 185 | is_delete: 1 186 | }); 187 | } 188 | 189 | let dbTable = await this.model('freight_template_group').where({ 190 | id: ['NOTIN', idInfo], 191 | template_id: info.id, 192 | is_default: 0, 193 | is_delete: 0 194 | }).update({ 195 | is_delete: 1 196 | }); 197 | 198 | for (const item of data) { 199 | let id = item.id; // 这个是group_id 200 | if (id > 0) { 201 | 202 | let template_id = info.id; 203 | 204 | let val = { 205 | area: item.area, 206 | start: item.start, 207 | start_fee: item.start_fee, 208 | add: item.add, 209 | add_fee: item.add_fee, 210 | free_by_money: item.free_by_money, 211 | free_by_number: item.free_by_number 212 | }; 213 | await this.model('freight_template_group').where({ 214 | id: id, 215 | template_id: template_id, 216 | is_delete: 0 217 | }).update(val); 218 | 219 | // 这里要根据area去notin更新 220 | 221 | 222 | let area = item.area; 223 | let arr = area.split(','); 224 | 225 | await this.model('freight_template_detail').where({ 226 | area: ['NOTIN', arr], 227 | template_id: template_id, 228 | group_id: id 229 | }).update({ 230 | is_delete: 1 231 | }); 232 | for (const item of arr) { 233 | let e = await this.model('freight_template_detail').where({ 234 | template_id: template_id, 235 | area: item, 236 | group_id: id 237 | }).find(); 238 | if (think.isEmpty(e)) { 239 | await this.model('freight_template_detail').add({ 240 | template_id: template_id, 241 | group_id: id, 242 | area: item 243 | }); 244 | } 245 | } 246 | } else { 247 | let template_id = info.id; 248 | let area = item.area.substring(2); 249 | let val = { 250 | area: area, 251 | start: item.start, 252 | start_fee: item.start_fee, 253 | add: item.add, 254 | add_fee: item.add_fee, 255 | template_id: template_id, 256 | free_by_money: item.free_by_money, 257 | free_by_number: item.free_by_number 258 | }; 259 | let groupId = await this.model('freight_template_group').add(val); 260 | let areaArr = area.split(','); 261 | for (const item of areaArr) { 262 | await this.model('freight_template_detail').add({ 263 | template_id: template_id, 264 | group_id: groupId, 265 | area: item 266 | }); 267 | } 268 | } 269 | } 270 | } else { 271 | // 这里前台将table全删除了,所以要将原先的数据都删除 272 | let dbTable = await this.model('freight_template_group').where({ 273 | template_id: info.id, 274 | is_default: 0, 275 | is_delete: 0 276 | }).update({ 277 | is_delete: 1 278 | }); 279 | // 将detail表也要删除!!! 280 | 281 | if (data.length != 0) { 282 | for (const item of data) { 283 | let area = item.area.substring(2); 284 | let template_id = info.id; 285 | let val = { 286 | area: area, 287 | start: item.start, 288 | start_fee: item.start_fee, 289 | add: item.add, 290 | add_fee: item.add_fee, 291 | template_id: template_id, 292 | free_by_money: item.free_by_money, 293 | free_by_number: item.free_by_number 294 | }; 295 | let groupId = await this.model('freight_template_group').add(val); 296 | //根据area 去循环一下另一张detail表 297 | let areaArr = area.split(','); 298 | for (const item of areaArr) { 299 | await this.model('freight_template_detail').add({ 300 | template_id: template_id, 301 | group_id: groupId, 302 | area: item 303 | }); 304 | } 305 | 306 | } 307 | } 308 | } 309 | 310 | let upData = { 311 | start: def[0].start, 312 | start_fee: def[0].start_fee, 313 | add: def[0].add, 314 | add_fee: def[0].add_fee, 315 | free_by_money: def[0].free_by_money, 316 | free_by_number: def[0].free_by_number 317 | }; 318 | 319 | await this.model('freight_template_group').where({ 320 | id: def[0].id, 321 | template_id: info.id, 322 | is_default: 1 323 | }).update(upData); 324 | 325 | // await this.model('freight_template_detail').where({ 326 | // group_id: def[0].id, 327 | // template_id: info.id, 328 | // }).update(upData); 329 | 330 | 331 | let tempData = { 332 | name: info.name, 333 | package_price: info.package_price, 334 | freight_type: info.freight_type 335 | }; 336 | 337 | await this.model('freight_template').where({ 338 | id: info.id 339 | }).update(tempData); 340 | return this.success(); 341 | } 342 | 343 | async addTableAction() { 344 | let info = this.post('info'); 345 | let data = this.post('table'); 346 | let def = this.post('defaultData'); 347 | // return false; 348 | let temp_id = await this.model('freight_template').add(info); 349 | 350 | if (temp_id > 0) { 351 | let upData = { 352 | start: def[0].start, 353 | start_fee: def[0].start_fee, 354 | add: def[0].add, 355 | add_fee: def[0].add_fee, 356 | free_by_money: def[0].free_by_money, 357 | free_by_number: def[0].free_by_number, 358 | template_id: temp_id, 359 | is_default: 1 360 | }; 361 | 362 | let groupId = await this.model('freight_template_group').add(upData); 363 | if (groupId > 0) { 364 | await this.model('freight_template_detail').add({ 365 | template_id: temp_id, 366 | group_id: groupId, 367 | area: 0 368 | }); 369 | } 370 | 371 | if (data.length > 0) { 372 | for (const item of data) { 373 | let area = item.area.substring(2); 374 | let template_id = temp_id; 375 | let info = { 376 | area: area, 377 | start: item.start, 378 | start_fee: item.start_fee, 379 | add: item.add, 380 | add_fee: item.add_fee, 381 | template_id: temp_id, 382 | free_by_money: item.free_by_money, 383 | free_by_number: item.free_by_number 384 | }; 385 | let groupId = await this.model('freight_template_group').add(info); 386 | let areaArr = area.split(','); 387 | for (const item of areaArr) { 388 | await this.model('freight_template_detail').add({ 389 | template_id: template_id, 390 | group_id: groupId, 391 | area: item 392 | }); 393 | } 394 | } 395 | } 396 | } 397 | 398 | return this.success(); 399 | } 400 | 401 | async exceptareaAction() { 402 | const model = this.model('except_area'); 403 | const data = await model.where({ 404 | is_delete: 0 405 | }).select(); 406 | 407 | for (const item of data) { 408 | let area = item.area; 409 | let areaData = area.split(','); 410 | let info = await this.model('region').where({ 411 | id: ['IN', areaData] 412 | }).getField('name'); 413 | item.areaName = info.join(','); 414 | } 415 | 416 | return this.success(data); 417 | } 418 | 419 | async exceptAreaDeleteAction() { 420 | const id = this.post('id'); 421 | await this.model('except_area').where({ 422 | id: id 423 | }).limit(1).update({ 424 | is_delete: 1 425 | }); 426 | await this.model('except_area_detail').where({ 427 | except_area_id: id 428 | }).update({ 429 | is_delete: 1 430 | }); 431 | return this.success(); 432 | } 433 | 434 | 435 | async exceptAreaDetailAction() { 436 | let id = this.post('id'); 437 | const model = this.model('except_area'); 438 | let data = await model.where({ 439 | id: id, 440 | is_delete: 0, 441 | }).find(); 442 | // let areaData = {} 443 | let area = data.area; 444 | let areaData = area.split(','); 445 | let info = await this.model('region').where({ 446 | id: ['IN', areaData] 447 | }).getField('name'); 448 | data.areaName = info.join(','); 449 | return this.success(data); 450 | } 451 | 452 | async saveExceptAreaAction() { 453 | let table = this.post('table'); 454 | let info = this.post('info'); 455 | let data = { 456 | area: table[0].area, 457 | content: info.content 458 | }; 459 | await this.model('except_area').where({ 460 | id: info.id 461 | }).update(data); 462 | let area = table[0].area; 463 | let arr = area.split(','); 464 | await this.model('except_area_detail').where({ 465 | area: ['NOTIN', arr], 466 | except_area_id: info.id, 467 | is_delete: 0 468 | }).update({ 469 | is_delete: 1 470 | }); 471 | for (const item of arr) { 472 | let e = await this.model('except_area_detail').where({ 473 | except_area_id: info.id, 474 | area: item, 475 | is_delete: 0 476 | }).find(); 477 | if (think.isEmpty(e)) { 478 | await this.model('except_area_detail').add({ 479 | except_area_id: info.id, 480 | area: item 481 | }); 482 | } 483 | } 484 | return this.success(); 485 | } 486 | 487 | async addExceptAreaAction() { 488 | let table = this.post('table'); 489 | let info = this.post('info'); 490 | let data = { 491 | area: table[0].area.substring(2), 492 | content: info.content 493 | }; 494 | let id = await this.model('except_area').add(data); 495 | let area = table[0].area.substring(2); 496 | let arr = area.split(','); 497 | for (const item of arr) { 498 | await this.model('except_area_detail').add({ 499 | except_area_id: id, 500 | area: item 501 | }); 502 | } 503 | return this.success(); 504 | } 505 | }; 506 | -------------------------------------------------------------------------------- /src/admin/controller/shopcart.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | module.exports = class extends Base { 4 | /** 5 | * index action 6 | * @return {Promise} [] 7 | */ 8 | async indexAction() { 9 | const page = this.get('page') || 1; 10 | const size = this.get('size') || 10; 11 | const name = this.get('name') || ''; 12 | const model = this.model('cart'); 13 | const data = await model.where({goods_name: ['like', `%${name}%`]}).order(['id DESC']).page(page, size).countSelect(); 14 | for (const item of data.data) { 15 | item.add_time = moment.unix(item.add_time).format('YYYY-MM-DD HH:mm:ss'); 16 | let userInfo = await this.model('user').where({id:item.user_id}).find(); 17 | if(!think.isEmpty(userInfo)){ 18 | item.nickname = Buffer.from(userInfo.nickname, 'base64').toString(); 19 | } 20 | else{ 21 | item.nickname = '已删除' 22 | } 23 | } 24 | 25 | return this.success(data); 26 | } 27 | 28 | }; 29 | -------------------------------------------------------------------------------- /src/admin/controller/specification.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | module.exports = class extends Base { 3 | /** 4 | * index action 5 | * @return {Promise} [] 6 | */ 7 | async indexAction() { 8 | const model = this.model('specification'); 9 | const data = await model.where({ 10 | id: ['>', 0] 11 | }).select(); 12 | return this.success(data); 13 | } 14 | async getGoodsSpecAction() { 15 | const id = this.post('id'); 16 | const model = this.model('product'); 17 | const data = await model.where({ 18 | goods_id: id, 19 | is_delete: 0 20 | }).select(); 21 | //TODO 这里只有一层,以后如果有多重型号,如一件商品既有颜色又有尺寸时,这里的代码是不对的。以后再写。 22 | let specData = []; 23 | let specification_id = 0; 24 | for (const item of data) { 25 | let goods_spec_id = item.goods_specification_ids; 26 | let specValueData = await this.model('goods_specification').where({ 27 | id: goods_spec_id, 28 | is_delete: 0 29 | }).find(); 30 | specification_id = specValueData.specification_id; 31 | item.value = specValueData.value; 32 | } 33 | let dataInfo = { 34 | specData: data, 35 | specValue: specification_id 36 | }; 37 | return this.success(dataInfo); 38 | } 39 | async productUpdateAction() { 40 | const goods_number = this.post('goods_number'); 41 | const goods_weight = this.post('goods_weight'); 42 | const goods_sn = this.post('goods_sn'); 43 | const retail_price = this.post('retail_price'); 44 | const cost = this.post('cost'); 45 | const value = this.post('value'); 46 | let updateInfo = { 47 | goods_number: goods_number, 48 | goods_weight: goods_weight, 49 | cost: cost, 50 | retail_price: retail_price 51 | } 52 | await this.model('cart').where({ 53 | goods_sn: goods_sn 54 | }).update({ 55 | retail_price: retail_price 56 | }); 57 | const model = this.model('product'); 58 | await model.where({ 59 | goods_sn: goods_sn 60 | }).update(updateInfo); 61 | let idData = await model.where({ 62 | goods_sn: goods_sn 63 | }).field('goods_specification_ids,goods_id').find(); 64 | let goods_specification_id = idData.goods_specification_ids 65 | let info = await this.model('goods_specification').where({ 66 | id: goods_specification_id 67 | }).update({ 68 | value: value 69 | }); 70 | let goods_id = idData.goods_id; 71 | // todo 价格显示为区间 72 | let pro = await this.model('product').where({ 73 | goods_id: goods_id 74 | }).select(); 75 | if (pro.length > 1) { 76 | let goodsNum = await this.model('product').where({ 77 | goods_id: goods_id 78 | }).sum('goods_number'); 79 | let maxPrice = await this.model('product').where({ 80 | goods_id: goods_id 81 | }).max('retail_price'); 82 | let minPrice = await this.model('product').where({ 83 | goods_id: goods_id 84 | }).min('retail_price'); 85 | let maxCost = await this.model('product').where({ 86 | goods_id: goods_id 87 | }).max('cost'); 88 | let minCost = await this.model('product').where({ 89 | goods_id: goods_id 90 | }).min('cost'); 91 | let goodsPrice = minPrice + '-' + maxPrice; 92 | let costPrice = minCost + '-' + maxCost; 93 | await this.model('goods').where({ 94 | id: goods_id 95 | }).update({ 96 | goods_number: goodsNum, 97 | retail_price: goodsPrice, 98 | cost_price: costPrice, 99 | min_retail_price: minPrice, 100 | min_cost_price: minCost, 101 | }); 102 | } else { 103 | await this.model('goods').where({ 104 | id: goods_id 105 | }).update({ 106 | goods_number: goods_number, 107 | retail_price: retail_price, 108 | cost_price: cost, 109 | min_retail_price: retail_price, 110 | min_cost_price: cost, 111 | }); 112 | } 113 | return this.success(info); 114 | } 115 | async productDeleAction() { 116 | const productId = this.post('id'); 117 | const model = this.model('product'); 118 | let idData = await model.where({ 119 | id: productId 120 | }).field('goods_specification_ids,goods_id').find(); 121 | let goods_specification_id = idData.goods_specification_ids; 122 | let goods_id = idData.goods_id; 123 | await model.where({ 124 | id: productId 125 | }).limit(1).delete(); 126 | let info = await this.model('goods_specification').where({ 127 | id: goods_specification_id 128 | }).limit(1).delete(); 129 | let lastData = await model.where({ 130 | goods_id: goods_id 131 | }).select(); 132 | if (lastData.length != 0) { 133 | let goodsNum = await this.model('product').where({ 134 | goods_id: goods_id 135 | }).sum('goods_number'); 136 | let goodsPrice = await this.model('product').where({ 137 | goods_id: goods_id 138 | }).min('retail_price'); 139 | await this.model('goods').where({ 140 | id: goods_id 141 | }).update({ 142 | goods_number: goodsNum, 143 | retail_price: goodsPrice 144 | }); 145 | } 146 | return this.success(info); 147 | } 148 | async delePrimarySpecAction() { 149 | const goods_id = this.post('id'); 150 | const model = this.model('product'); 151 | await model.where({ 152 | goods_id: goods_id 153 | }).delete(); 154 | let info = await this.model('goods_specification').where({ 155 | goods_id: goods_id 156 | }).delete(); 157 | await this.model('goods').where({ 158 | id: goods_id 159 | }).update({ 160 | goods_number: 0, 161 | retail_price: 0 162 | }); 163 | return this.success(info); 164 | } 165 | async detailAction(){ 166 | let id = this.post('id'); 167 | let info = await this.model('specification').where({ 168 | id:id 169 | }).find(); 170 | return this.success(info); 171 | } 172 | async addAction() { 173 | const value = this.post('name'); 174 | const sort = this.post('sort_order'); 175 | let info = { 176 | name: value, 177 | sort_order: sort 178 | } 179 | const model = this.model('specification'); 180 | const data = await model.add(info); 181 | return this.success(data); 182 | } 183 | async checkSnAction() { 184 | const sn = this.post('sn'); 185 | const model = this.model('product'); 186 | const data = await model.where({ 187 | goods_sn: sn 188 | }).select(); 189 | if (data.length > 0) { 190 | return this.fail('sn已存在'); 191 | } else { 192 | return this.success(data); 193 | } 194 | } 195 | async updateAction() { 196 | const id = this.post('id'); 197 | const value = this.post('name'); 198 | const sort = this.post('sort_order'); 199 | let info = { 200 | name: value, 201 | sort_order: sort 202 | } 203 | const model = this.model('specification'); 204 | const data = await model.where({ 205 | id: id 206 | }).update(info); 207 | return this.success(data); 208 | } 209 | async deleteAction() { 210 | const id = this.post('id'); 211 | const goods_spec = await this.model('goods_specification').where({ 212 | specification_id: id, 213 | is_delete: 0 214 | }).select(); 215 | console.log(goods_spec); 216 | if (goods_spec.length > 0) { 217 | return this.fail('该型号下有商品,暂不能删除') 218 | } else { 219 | const model = this.model('specification'); 220 | const data = await model.where({ 221 | id: id 222 | }).limit(1).delete(); 223 | return this.success(data); 224 | } 225 | } 226 | }; -------------------------------------------------------------------------------- /src/admin/controller/user.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | module.exports = class extends Base { 4 | /** 5 | * index action 6 | * @return {Promise} [] 7 | */ 8 | async indexAction() { 9 | const page = this.get('page') || 1; 10 | const size = this.get('size') || 10; 11 | let nickname = this.get('nickname') || ''; 12 | const buffer = Buffer.from(nickname); 13 | nickname = buffer.toString('base64'); 14 | const model = this.model('user'); 15 | const data = await model.where({ 16 | nickname: ['like', `%${nickname}%`], 17 | }).order(['id DESC']).page(page, size).countSelect(); 18 | for (const item of data.data) { 19 | item.register_time = moment.unix(item.register_time).format('YYYY-MM-DD HH:mm:ss'); 20 | item.last_login_time = moment.unix(item.last_login_time).format('YYYY-MM-DD HH:mm:ss'); 21 | item.nickname = Buffer.from(item.nickname, 'base64').toString(); 22 | } 23 | let info = { 24 | userData: data, 25 | } 26 | return this.success(info); 27 | } 28 | async infoAction() { 29 | const id = this.get('id'); 30 | const model = this.model('user'); 31 | let info = await model.where({ 32 | id: id 33 | }).find(); 34 | info.register_time = moment.unix(info.register_time).format('YYYY-MM-DD HH:mm:ss'); 35 | info.last_login_time = moment.unix(info.last_login_time).format('YYYY-MM-DD HH:mm:ss'); 36 | info.nickname = Buffer.from(info.nickname, 'base64').toString(); 37 | return this.success(info); 38 | } 39 | async datainfoAction() { 40 | const id = this.get('id'); 41 | let info = {}; 42 | info.orderSum = await this.model('order').where({ 43 | user_id: id, 44 | order_type: ['<', 8], 45 | is_delete: 0 46 | }).count(); 47 | info.orderDone = await this.model('order').where({ 48 | user_id: id, 49 | order_status: ['IN', '302,303,401'], 50 | order_type: ['<', 8], 51 | is_delete: 0 52 | }).count(); 53 | info.orderMoney = await this.model('order').where({ 54 | user_id: id, 55 | order_status: ['IN', '302,303,401'], 56 | order_type: ['<', 8], 57 | is_delete: 0 58 | }).sum('actual_price'); 59 | info.cartSum = await this.model('cart').where({ 60 | user_id: id, 61 | is_delete: 0 62 | }).sum('number'); 63 | return this.success(info); 64 | } 65 | async addressAction() { 66 | const id = this.get('id'); 67 | const page = this.get('page') || 1; 68 | const size = this.get('size') || 10; 69 | let addr = await this.model('address').where({ 70 | user_id: id 71 | }).page(page, size).countSelect(); 72 | for (const item of addr.data) { 73 | let province_name = await this.model('region').where({ 74 | id: item.province_id 75 | }).getField('name', true); 76 | let city_name = await this.model('region').where({ 77 | id: item.city_id 78 | }).getField('name', true); 79 | let district_name = await this.model('region').where({ 80 | id: item.district_id 81 | }).getField('name', true); 82 | item.full_region = province_name + city_name + district_name + item.address; 83 | } 84 | return this.success(addr); 85 | } 86 | async saveaddressAction() { 87 | const id = this.post('id'); 88 | const user_id = this.post('user_id'); 89 | const name = this.post('name'); 90 | const mobile = this.post('mobile'); 91 | const address = this.post('address'); 92 | const addOptions = this.post('addOptions'); 93 | const province = addOptions[0]; 94 | const city = addOptions[1]; 95 | const district = addOptions[2]; 96 | let info = { 97 | name: name, 98 | mobile: mobile, 99 | address: address, 100 | province_id: province, 101 | district_id: district, 102 | city_id: city 103 | } 104 | await this.model('address').where({ 105 | user_id: user_id, 106 | id: id 107 | }).update(info); 108 | return this.success(); 109 | } 110 | async cartdataAction() { 111 | const id = this.get('id'); 112 | const page = this.get('page') || 1; 113 | const size = this.get('size') || 10; 114 | const model = this.model('cart'); 115 | const data = await model.where({ 116 | user_id: id 117 | }).order(['add_time DESC']).page(page, size).countSelect(); 118 | for (const item of data.data) { 119 | item.add_time = moment.unix(item.add_time).format('YYYY-MM-DD HH:mm:ss'); 120 | } 121 | return this.success(data); 122 | } 123 | async footAction() { 124 | const id = this.get('id'); 125 | const page = this.get('page') || 1; 126 | const size = this.get('size') || 10; 127 | const model = this.model('footprint'); 128 | const data = await model.alias('f').join({ 129 | table: 'goods', 130 | join: 'left', 131 | as: 'g', 132 | on: ['f.goods_id', 'g.id'] 133 | }).where({ 134 | user_id: id 135 | }).page(page, size).countSelect(); 136 | console.log(data); 137 | return this.success(data); 138 | } 139 | async orderAction() { 140 | const page = this.get('page') || 1; 141 | const size = this.get('size') || 10; 142 | const user_id = this.get('id'); 143 | const model = this.model('order'); 144 | const data = await model.where({ 145 | user_id: user_id, 146 | order_type: ['<', 8], 147 | }).order(['id DESC']).page(page, size).countSelect(); 148 | console.log(data.count); 149 | for (const item of data.data) { 150 | item.goodsList = await this.model('order_goods').field('goods_name,list_pic_url,number,goods_specifition_name_value,retail_price').where({ 151 | order_id: item.id, 152 | is_delete: 0 153 | }).select(); 154 | item.goodsCount = 0; 155 | item.goodsList.forEach(v => { 156 | item.goodsCount += v.number; 157 | }); 158 | let province_name = await this.model('region').where({ 159 | id: item.province 160 | }).getField('name', true); 161 | let city_name = await this.model('region').where({ 162 | id: item.city 163 | }).getField('name', true); 164 | let district_name = await this.model('region').where({ 165 | id: item.district 166 | }).getField('name', true); 167 | item.full_region = province_name + city_name + district_name; 168 | item.postscript = Buffer.from(item.postscript, 'base64').toString(); 169 | item.add_time = moment.unix(item.add_time).format('YYYY-MM-DD HH:mm:ss'); 170 | item.order_status_text = await this.model('order').getOrderStatusText(item.id); 171 | item.button_text = await this.model('order').getOrderBtnText(item.id); 172 | } 173 | return this.success(data); 174 | } 175 | async getOrderStatusText(orderInfo) { 176 | let statusText = '待付款'; 177 | switch (orderInfo.order_status) { 178 | case 101: 179 | statusText = '待付款'; 180 | break; 181 | case 102: 182 | statusText = '交易关闭'; 183 | break; 184 | case 103: 185 | statusText = '交易关闭'; //到时间系统自动取消 186 | break; 187 | case 201: 188 | statusText = '待发货'; 189 | break; 190 | case 202: 191 | statusText = '退款中'; 192 | break; 193 | case 203: 194 | statusText = '已退款'; 195 | break; 196 | case 300: 197 | statusText = '已备货'; 198 | break; 199 | case 301: 200 | statusText = '已发货'; 201 | break; 202 | case 302: 203 | statusText = '待评价'; 204 | break; 205 | case 303: 206 | statusText = '待评价'; //到时间,未收货的系统自动收货、 207 | break; 208 | case 401: 209 | statusText = '交易成功'; //到时间,未收货的系统自动收货、 210 | break; 211 | case 801: 212 | statusText = '拼团待付款'; 213 | break; 214 | case 802: 215 | statusText = '拼团中'; // 如果sum变为0了。则,变成201待发货 216 | break; 217 | } 218 | return statusText; 219 | } 220 | async updateInfoAction() { 221 | const id = this.post('id'); 222 | let nickname = this.post('nickname'); 223 | const buffer = Buffer.from(nickname); 224 | nickname = buffer.toString('base64'); 225 | const model = this.model('user'); 226 | const data = await model.where({ 227 | id: id 228 | }).update({ 229 | nickname: nickname 230 | }); 231 | return this.success(data); 232 | } 233 | async updateNameAction() { 234 | const id = this.post('id'); 235 | const name = this.post('name'); 236 | const model = this.model('user'); 237 | const data = await model.where({ 238 | id: id 239 | }).update({ 240 | name: name 241 | }); 242 | return this.success(data); 243 | } 244 | async updateMobileAction() { 245 | const id = this.post('id'); 246 | const mobile = this.post('mobile'); 247 | const model = this.model('user'); 248 | const data = await model.where({ 249 | id: id 250 | }).update({ 251 | mobile: mobile 252 | }); 253 | return this.success(data); 254 | } 255 | async storeAction() { 256 | if (!this.isPost) { 257 | return false; 258 | } 259 | const values = this.post(); 260 | const id = this.post('id'); 261 | const model = this.model('user'); 262 | values.is_show = values.is_show ? 1 : 0; 263 | values.is_new = values.is_new ? 1 : 0; 264 | if (id > 0) { 265 | await model.where({ 266 | id: id 267 | }).update(values); 268 | } else { 269 | delete values.id; 270 | await model.add(values); 271 | } 272 | return this.success(values); 273 | } 274 | async destoryAction() { 275 | const id = this.post('id'); 276 | await this.model('user').where({ 277 | id: id 278 | }).limit(1).delete(); 279 | return this.success(); 280 | } 281 | }; -------------------------------------------------------------------------------- /src/admin/controller/wap.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | module.exports = class extends Base { 3 | /** 4 | * index action 5 | * @return {Promise} [] 6 | */ 7 | async indexAction() { 8 | // const product = await this.model('product').where({is_delete:1}).delete() 9 | const product = await this.model('product').field(['c.goods_sn', 'c.goods_id', 'c.goods_specification_ids', 'c.retail_price', 'g.value']).alias('c').join({ 10 | table: 'goods_specification', 11 | join: 'left', 12 | as: 'g', 13 | on: ['c.goods_specification_ids', 'g.id'] 14 | }).select(); // 如果出错了,不会更新数据的 15 | console.log(product); 16 | // const goods = await this.model('goods').where({is_delete:0}).select(); 17 | const goods = await this.model('goods').where({ 18 | is_delete: 0 19 | }).select(); 20 | for (const item of product) { 21 | let goods_id = item.goods_id; 22 | for (const jtem of goods) { 23 | if (goods_id == jtem.id) { 24 | // const product = await this.model('product').where({goods_id:jtem.id}).update({is_delete:0}) 25 | item.name = jtem.name + '-' + item.value; 26 | item.is_on_sale = jtem.is_on_sale; 27 | item.list_pic_url = jtem.list_pic_url; 28 | if (item.is_on_sale == 1) { 29 | item.is_on_sale = true; 30 | } else { 31 | item.is_on_sale = false; 32 | } 33 | } 34 | } 35 | } 36 | return this.success(product); 37 | } 38 | async onsaleAction() { 39 | const product = await this.model('product').field(['c.goods_sn', 'c.goods_id', 'c.goods_specification_ids', 'c.retail_price', 'g.value']).alias('c').join({ 40 | table: 'goods_specification', 41 | join: 'left', 42 | as: 'g', 43 | on: ['c.goods_specification_ids', 'g.id'] 44 | }).select(); // 如果出错了,不会更新数据的 45 | const goods = await this.model('goods').where({ 46 | is_on_sale: 1, 47 | is_delete: 0 48 | }).select(); 49 | console.log(goods); 50 | let info = []; 51 | for (const item of product) { 52 | let goods_id = item.goods_id; 53 | for (const jtem of goods) { 54 | if (goods_id == jtem.id) { 55 | item.name = jtem.name + '-' + item.value; 56 | item.is_on_sale = jtem.is_on_sale; 57 | item.list_pic_url = jtem.list_pic_url; 58 | if (item.is_on_sale == 1) { 59 | item.is_on_sale = true; 60 | info.push(item); 61 | } 62 | } 63 | } 64 | } 65 | console.log(product); 66 | return this.success(info); 67 | } 68 | async outsaleAction() { 69 | const product = await this.model('product').field(['c.goods_sn', 'c.goods_id', 'c.goods_specification_ids', 'c.retail_price', 'g.value']).alias('c').join({ 70 | table: 'goods_specification', 71 | join: 'left', 72 | as: 'g', 73 | on: ['c.goods_specification_ids', 'g.id'] 74 | }).select(); // 如果出错了,不会更新数据的 75 | let info = []; 76 | const goods = await this.model('goods').where({ 77 | is_on_sale: 0, 78 | is_delete: 0 79 | }).select(); 80 | console.log(goods); 81 | for (const item of product) { 82 | let goods_id = item.goods_id; 83 | for (const jtem of goods) { 84 | if (goods_id == jtem.id) { 85 | item.name = jtem.name + '-' + item.value; 86 | item.is_on_sale = jtem.is_on_sale; 87 | item.list_pic_url = jtem.list_pic_url; 88 | if (item.is_on_sale == 0) { 89 | item.is_on_sale = false; 90 | info.push(item); 91 | } 92 | } 93 | } 94 | } 95 | console.log(product); 96 | return this.success(info); 97 | } 98 | async updatePriceAction() { 99 | const sn = this.post('sn'); 100 | const id = this.post('id'); 101 | const price = this.post('price'); 102 | const info = await this.model('product').where({ 103 | goods_sn: sn 104 | }).update({ 105 | retail_price: price 106 | }); 107 | let min = await this.model('product').where({ 108 | goods_id: id 109 | }).min('retail_price'); 110 | await this.model('cart').where({ 111 | goods_sn: sn 112 | }).update({ 113 | retail_price: price 114 | }); 115 | await this.model('goods').where({ 116 | id: id 117 | }).update({ 118 | retail_price: min 119 | }); 120 | return this.success(); 121 | } 122 | async outAction() { 123 | const page = this.get('page') || 1; 124 | const size = this.get('size') || 10; 125 | const model = this.model('goods'); 126 | const data = await model.where({ 127 | is_delete: 0, 128 | goods_number: ['<=', 0] 129 | }).order(['id DESC']).page(page, size).countSelect(); 130 | for (const item of data.data) { 131 | const info = await this.model('category').where({ 132 | id: item.category_id 133 | }).find(); 134 | item.category_name = info.name; 135 | if (info.parent_id != 0) { 136 | const parentInfo = await this.model('category').where({ 137 | id: info.parent_id 138 | }).find(); 139 | item.category_p_name = parentInfo.name; 140 | } 141 | if (item.is_on_sale == 1) { 142 | item.is_on_sale = true; 143 | } else { 144 | item.is_on_sale = false; 145 | } 146 | } 147 | return this.success(data); 148 | } 149 | async dropAction() { 150 | const page = this.get('page') || 1; 151 | const size = this.get('size') || 10; 152 | const model = this.model('goods'); 153 | const data = await model.where({ 154 | is_delete: 0, 155 | is_on_sale: 0 156 | }).order(['id DESC']).page(page, size).countSelect(); 157 | for (const item of data.data) { 158 | const info = await this.model('category').where({ 159 | id: item.category_id 160 | }).find(); 161 | item.category_name = info.name; 162 | if (info.parent_id != 0) { 163 | const parentInfo = await this.model('category').where({ 164 | id: info.parent_id 165 | }).find(); 166 | item.category_p_name = parentInfo.name; 167 | } 168 | if (item.is_on_sale == 1) { 169 | item.is_on_sale = true; 170 | } else { 171 | item.is_on_sale = false; 172 | } 173 | } 174 | return this.success(data); 175 | } 176 | async sortAction() { 177 | const page = this.get('page') || 1; 178 | const size = this.get('size') || 10; 179 | const model = this.model('goods'); 180 | const index = this.get('index'); 181 | if (index == 1) { 182 | const data = await model.where({ 183 | is_delete: 0 184 | }).order(['sell_volume DESC']).page(page, size).countSelect(); 185 | for (const item of data.data) { 186 | const info = await this.model('category').where({ 187 | id: item.category_id 188 | }).find(); 189 | item.category_name = info.name; 190 | if (info.parent_id != 0) { 191 | const parentInfo = await this.model('category').where({ 192 | id: info.parent_id 193 | }).find(); 194 | item.category_p_name = parentInfo.name; 195 | } 196 | if (item.is_on_sale == 1) { 197 | item.is_on_sale = true; 198 | } else { 199 | item.is_on_sale = false; 200 | } 201 | } 202 | return this.success(data); 203 | } else if (index == 2) { 204 | const data = await model.where({ 205 | is_delete: 0 206 | }).order(['retail_price DESC']).page(page, size).countSelect(); 207 | for (const item of data.data) { 208 | const info = await this.model('category').where({ 209 | id: item.category_id 210 | }).find(); 211 | item.category_name = info.name; 212 | if (info.parent_id != 0) { 213 | const parentInfo = await this.model('category').where({ 214 | id: info.parent_id 215 | }).find(); 216 | item.category_p_name = parentInfo.name; 217 | } 218 | if (item.is_on_sale == 1) { 219 | item.is_on_sale = true; 220 | } else { 221 | item.is_on_sale = false; 222 | } 223 | } 224 | return this.success(data); 225 | } else if (index == 3) { 226 | const data = await model.where({ 227 | is_delete: 0 228 | }).order(['goods_number DESC']).page(page, size).countSelect(); 229 | for (const item of data.data) { 230 | const info = await this.model('category').where({ 231 | id: item.category_id 232 | }).find(); 233 | item.category_name = info.name; 234 | if (info.parent_id != 0) { 235 | const parentInfo = await this.model('category').where({ 236 | id: info.parent_id 237 | }).find(); 238 | item.category_p_name = parentInfo.name; 239 | } 240 | if (item.is_on_sale == 1) { 241 | item.is_on_sale = true; 242 | } else { 243 | item.is_on_sale = false; 244 | } 245 | } 246 | return this.success(data); 247 | } 248 | } 249 | async saleStatusAction() { 250 | const id = this.get('id'); 251 | const status = this.get('status'); 252 | let sale = 0; 253 | if (status == 'true') { 254 | sale = 1; 255 | } 256 | const model = this.model('goods'); 257 | await model.where({ 258 | id: id 259 | }).update({ 260 | is_on_sale: sale 261 | }); 262 | } 263 | async infoAction() { 264 | const id = this.get('id'); 265 | const model = this.model('goods'); 266 | const data = await model.where({ 267 | id: id 268 | }).find(); 269 | console.log(data); 270 | let category_id = data.category_id; 271 | let cateData = []; 272 | const c_data = await this.model('category').where({ 273 | id: category_id 274 | }).find(); 275 | const f_data = await this.model('category').where({ 276 | id: c_data.parent_id 277 | }).find(); 278 | cateData.push(f_data.id, c_data.id); 279 | let productInfo = await this.model('product').where({ 280 | goods_id: id 281 | }).select(); 282 | if (productInfo.length > 1) {} 283 | let infoData = { 284 | info: data, 285 | cateData: cateData, 286 | }; 287 | return this.success(infoData); 288 | } 289 | async getAllCategory1Action() { // 我写的算法 290 | const model = this.model('category'); 291 | const data = await model.where({ 292 | is_show: 1, 293 | level: 'L1' 294 | }).select(); 295 | const c_data = await model.where({ 296 | is_show: 1, 297 | level: 'L2' 298 | }).select(); 299 | let newData = []; 300 | for (const item of data) { 301 | let children = []; 302 | for (const citem of c_data) { 303 | if (citem.parent_id == item.id) { 304 | children.push({ 305 | value: citem.id, 306 | label: citem.name 307 | }) 308 | } 309 | } 310 | newData.push({ 311 | value: item.id, 312 | label: item.name, 313 | children: children 314 | }); 315 | } 316 | return this.success(newData); 317 | } 318 | async getAllCategoryAction() { // 老婆的算法 319 | const model = this.model('category'); 320 | const data = await model.where({ 321 | is_show: 1, 322 | level: 'L1' 323 | }).field('id,name').select(); 324 | let newData = []; 325 | for (const item of data) { 326 | let children = []; 327 | const c_data = await model.where({ 328 | is_show: 1, 329 | level: 'L2', 330 | parent_id: item.id 331 | }).field('id,name').select(); 332 | for (const c_item of c_data) { 333 | children.push({ 334 | value: c_item.id, 335 | label: c_item.name 336 | }) 337 | } 338 | newData.push({ 339 | value: item.id, 340 | label: item.name, 341 | children: children 342 | }); 343 | } 344 | return this.success(newData); 345 | } 346 | async getGoodsSnNameAction() { 347 | const cateId = this.get('cateId'); 348 | const model = this.model('goods'); 349 | const data = await model.where({ 350 | category_id: cateId, 351 | is_delete: 0 352 | }).field('goods_sn,name').order({ 353 | 'goods_sn': 'DESC' 354 | }).select(); 355 | return this.success(data); 356 | } 357 | async storeAction() { 358 | const values = this.post('info'); 359 | const model = this.model('goods'); 360 | let picUrl = values.list_pic_url; 361 | let goods_id = values.id; 362 | await this.model('cart').where({ 363 | goods_id: goods_id 364 | }).update({ 365 | list_pic_url: picUrl 366 | }); 367 | values.is_new = values.is_new ? 1 : 0; 368 | let id = values.id; 369 | if (id > 0) { 370 | await model.where({ 371 | id: id 372 | }).update(values); 373 | } else { 374 | delete values.id; 375 | let goods_id = await model.add(values); 376 | await model.where({ 377 | id: goods_id 378 | }).update({ 379 | goods_sn: goods_id 380 | }); 381 | } 382 | return this.success(values); 383 | } 384 | 385 | async destoryAction() { 386 | const id = this.post('id'); 387 | await this.model('goods').where({ 388 | id: id 389 | }).limit(1).delete(); 390 | return this.success(); 391 | } 392 | }; -------------------------------------------------------------------------------- /src/admin/logic/auth.js: -------------------------------------------------------------------------------- 1 | module.exports = class extends think.Logic { 2 | loginAction() { 3 | this.allowMethods = 'post'; 4 | this.rules = { 5 | username: { required: true, string: true }, 6 | password: { required: true, string: true } 7 | }; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/admin/model/order.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | module.exports = class extends think.Model { 3 | /** 4 | * 生成订单的编号order_sn 5 | * @returns {string} 6 | */ 7 | generateOrderNumber() { 8 | const date = new Date(); 9 | return date.getFullYear() + _.padStart(date.getMonth(), 2, '0') + _.padStart(date.getDay(), 2, '0') + _.padStart(date.getHours(), 2, '0') + _.padStart(date.getMinutes(), 2, '0') + _.padStart(date.getSeconds(), 2, '0') + _.random(100000, 999999); 10 | } 11 | /** 12 | * 获取订单可操作的选项 13 | * @param orderId 14 | */ 15 | async getOrderHandleOption(orderId) { 16 | const handleOption = { 17 | cancel: false, // 取消操作 18 | delete: false, // 删除操作 19 | pay: false, // 支付操作 20 | delivery: false, // 确认收货操作 21 | confirm: false, // 完成订单操作 22 | buy: false // 再次购买 23 | }; 24 | const orderInfo = await this.where({ 25 | id: orderId 26 | }).find(); 27 | // 订单流程:下单成功-》支付订单-》发货-》收货-》评论 28 | // 订单相关状态字段设计,采用单个字段表示全部的订单状态 29 | // 1xx表示订单取消和删除等状态 0订单创建成功等待付款,101订单已取消,102订单已删除 30 | // 2xx表示订单支付状态,201订单已付款,等待发货 31 | // 3xx表示订单物流相关状态,300订单已发货,301用户确认收货 32 | // 4xx表示订单退换货相关的状态,401没有发货,退款402,已收货,退款退货 33 | // 如果订单已经取消或是已完成,则可删除和再次购买 34 | if (orderInfo.order_status === 101) { 35 | handleOption.delete = true; 36 | handleOption.buy = true; 37 | } 38 | // 如果订单没有被取消,且没有支付,则可支付,可取消 39 | if (orderInfo.order_status === 0) { 40 | handleOption.cancel = true; 41 | handleOption.pay = true; 42 | } 43 | // 如果订单已经发货,没有收货,则可收货操作和退款、退货操作 44 | if (orderInfo.order_status === 300) { 45 | handleOption.cancel = true; 46 | handleOption.pay = true; 47 | } 48 | // 如果订单已经支付,且已经收货,则可完成交易、评论和再次购买 49 | if (orderInfo.order_status === 301) { 50 | handleOption.delete = true; 51 | handleOption.buy = true; 52 | } 53 | return handleOption; 54 | } 55 | async getOrderStatusText(orderId) { 56 | const orderInfo = await this.where({ 57 | id: orderId 58 | }).find(); 59 | let statusText = ''; 60 | switch (orderInfo.order_status) { 61 | case 101: 62 | statusText = '待付款'; 63 | break; 64 | case 102: 65 | statusText = '交易关闭'; 66 | break; 67 | case 103: 68 | statusText = '交易关闭'; //到时间系统自动取消 69 | break; 70 | case 201: 71 | statusText = '待备货'; 72 | break; 73 | case 300: 74 | statusText = '待发货'; 75 | break; 76 | case 301: 77 | statusText = '已发货'; 78 | break; 79 | case 302: 80 | statusText = '待评价'; 81 | break; 82 | case 303: 83 | statusText = '待评价'; //到时间,未收货的系统自动收货、 84 | break; 85 | case 401: 86 | statusText = '交易成功'; //到时间,未收货的系统自动收货、 87 | break; 88 | } 89 | return statusText; 90 | } 91 | async getOrderBtnText(orderId) { 92 | const orderInfo = await this.where({ 93 | id: orderId 94 | }).find(); 95 | let statusText = ''; 96 | switch (orderInfo.order_status) { 97 | case 101: 98 | statusText = '修改价格'; 99 | break; 100 | case 102: 101 | statusText = '查看详情'; 102 | break; 103 | case 103: 104 | statusText = '查看详情'; //到时间系统自动取消 105 | break; 106 | case 201: 107 | statusText = '备货'; 108 | break; 109 | case 202: 110 | statusText = '查看详情'; 111 | break; 112 | case 203: 113 | statusText = '查看详情'; 114 | break; 115 | case 300: 116 | statusText = '打印快递单'; 117 | break; 118 | case 301: 119 | statusText = '查看详情'; 120 | break; 121 | case 302: 122 | statusText = '查看详情'; 123 | break; 124 | case 303: 125 | statusText = '查看详情'; //到时间,未收货的系统自动收货、 126 | break; 127 | case 401: 128 | statusText = '查看详情'; //到时间,未收货的系统自动收货、 129 | break; 130 | } 131 | if (orderInfo.order_status == 301) { 132 | statusText = '确认收货' 133 | } 134 | return statusText; 135 | } 136 | }; -------------------------------------------------------------------------------- /src/admin/model/order_express.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const _ = require('lodash'); 3 | const rp = require('request-promise'); 4 | const fs = require('fs'); 5 | const http = require("http"); 6 | const path = require('path'); 7 | 8 | module.exports = class extends think.Model { 9 | get tableName() { 10 | return this.tablePrefix + 'order_express'; 11 | } 12 | /** 13 | * 获取最新的订单物流信息 快递鸟,不能查顺丰 14 | * @param orderId 15 | * @returns {Promise.<*>} 16 | */ 17 | async getLatestOrderExpress(orderId) { // 这个是快递鸟的接口,查不了顺丰。顺丰用阿里云的付费接口,在order.js中已经写好 18 | const returnExpressInfo = { 19 | shipper_code: '', 20 | shipper_name: '', 21 | logistic_code: '', 22 | is_finish: 0, 23 | request_time: 0, 24 | traces: [] 25 | }; 26 | const orderExpress = await this.where({ 27 | order_id: orderId 28 | }).find(); 29 | if (think.isEmpty(orderExpress)) { 30 | return returnExpressInfo; 31 | } 32 | if (think.isEmpty(orderExpress.shipper_code) || think.isEmpty(orderExpress.logistic_code)) { 33 | return returnExpressInfo; 34 | } 35 | returnExpressInfo.shipper_code = orderExpress.shipper_code; 36 | returnExpressInfo.shipper_name = orderExpress.shipper_name; 37 | returnExpressInfo.logistic_code = orderExpress.logistic_code; 38 | returnExpressInfo.is_finish = orderExpress.is_finish; 39 | returnExpressInfo.request_time = think.datetime(orderExpress.request_time * 1000); 40 | returnExpressInfo.traces = think.isEmpty(orderExpress.traces) ? [] : JSON.parse(orderExpress.traces); 41 | // 如果物流配送已完成,直接返回 42 | if (orderExpress.is_finish) { 43 | return returnExpressInfo; 44 | } 45 | // 查询最新物流信息 46 | const ExpressSerivce = think.service('express', 'api'); 47 | const latestExpressInfo = await ExpressSerivce.queryExpress(orderExpress.shipper_code, orderExpress.logistic_code); 48 | const nowTime = parseInt(new Date().getTime() / 1000); 49 | const updateData = { 50 | request_time: nowTime, 51 | update_time: nowTime, 52 | request_count: ['EXP', 'request_count+1'] 53 | }; 54 | if (latestExpressInfo.success) { 55 | returnExpressInfo.traces = latestExpressInfo.traces; 56 | returnExpressInfo.is_finish = latestExpressInfo.isFinish; 57 | // 查询成功则更新订单物流信息 58 | updateData.traces = JSON.stringify(latestExpressInfo.traces); 59 | returnExpressInfo.request_time = think.datetime(nowTime * 1000); 60 | updateData.is_finish = latestExpressInfo.isFinish; 61 | } 62 | await this.where({ 63 | id: orderExpress.id 64 | }).update(updateData); 65 | return returnExpressInfo; 66 | } 67 | /** 68 | * 获取最新的订单物流信息 阿里云快递api 收费 69 | * @param orderId 70 | * @returns {Promise.<*>} 71 | */ 72 | async getLatestOrderExpressByAli(orderId) { 73 | // let aliexpress = think.config('aliexpress'); 74 | 75 | const currentTime = parseInt(new Date().getTime() / 1000); 76 | console.log('==============orderExpress==============='); 77 | let info = await this.model('order_express').where({ 78 | order_id: orderId 79 | }).find(); 80 | if (think.isEmpty(info)) { 81 | return this.fail(400, '暂无物流信息'); 82 | } 83 | const expressInfo = await this.model('order_express').where({ 84 | order_id: orderId 85 | }).find(); 86 | // 如果is_finish == 1;或者 updateTime 小于 10分钟, 87 | let updateTime = info.update_time; 88 | let com = (currentTime - updateTime) / 60; 89 | let is_finish = info.is_finish; 90 | if (is_finish == 1) { 91 | return expressInfo; 92 | } else if (updateTime != 0 && com < 20) { 93 | return expressInfo; 94 | } else { 95 | let shipperCode = expressInfo.shipper_code; 96 | let expressNo = expressInfo.logistic_code; 97 | let code = shipperCode.substring(0, 2); 98 | let shipperName = ''; 99 | let sfLastNo = think.config('aliexpress.sfLastNo'); 100 | if (code == "SF") { 101 | shipperName = "SFEXPRESS"; 102 | expressNo = expressNo + ':'+sfLastNo; // 这个要根据自己的寄件时的手机末四位 103 | } else { 104 | shipperName = shipperCode; 105 | } 106 | let lastExpressInfo = await this.getExpressInfo(shipperName, expressNo); 107 | console.log(lastExpressInfo); 108 | let deliverystatus = lastExpressInfo.deliverystatus; 109 | let newUpdateTime = lastExpressInfo.updateTime; 110 | newUpdateTime = parseInt(new Date(newUpdateTime).getTime() / 1000); 111 | deliverystatus = await this.getDeliverystatus(deliverystatus); 112 | console.log(deliverystatus); 113 | let issign = lastExpressInfo.issign; 114 | let traces = lastExpressInfo.list; 115 | traces = JSON.stringify(traces); 116 | console.log(traces); 117 | let dataInfo = { 118 | express_status: deliverystatus, 119 | is_finish: issign, 120 | traces: traces, 121 | update_time: newUpdateTime 122 | } 123 | console.log('出发1222222221'); 124 | await this.model('order_express').where({ 125 | order_id: orderId 126 | }).update(dataInfo); 127 | let express = await this.model('order_express').where({ 128 | order_id: orderId 129 | }).find(); 130 | return express; 131 | } 132 | // return this.success(latestExpressInfo); 133 | } 134 | async getExpressInfo(shipperName, expressNo) { 135 | console.log('出发1111111'); 136 | let appCode = "APPCODE "+ think.config('aliexpress.appcode'); 137 | const options = { 138 | method: 'GET', 139 | url: 'http://wuliu.market.alicloudapi.com/kdi?no=' + expressNo + '&type=' + shipperName, 140 | headers: { 141 | "Content-Type": "application/json; charset=utf-8", 142 | "Authorization": appCode 143 | } 144 | }; 145 | let sessionData = await rp(options); 146 | sessionData = JSON.parse(sessionData); 147 | return sessionData.result; 148 | } 149 | async getDeliverystatus(status) { 150 | if (status == 0) { 151 | return '快递收件(揽件)'; 152 | } else if (status == 1) { 153 | return '在途中'; 154 | } else if (status == 2) { 155 | return '正在派件'; 156 | } else if (status == 3) { 157 | return '已签收'; 158 | } else if (status == 4) { 159 | return '派送失败(无法联系到收件人或客户要求择日派送,地址不详或手机号不清)'; 160 | } else if (status == 5) { 161 | return '疑难件(收件人拒绝签收,地址有误或不能送达派送区域,收费等原因无法正常派送)'; 162 | } else if (status == 6) { 163 | return '退件签收'; 164 | } 165 | } 166 | async getMianExpress(orderId, senderInfo, receiverInfo, expressType) { 167 | // 开始了 168 | let ShipperCode = ''; 169 | let ExpType = 1; 170 | let CustomerName = ''; // 圆通需要 171 | let MonthCode = ''; // 圆通需要浙江省舟山市普陀区沈家门分部 172 | let GoodsName = ''; 173 | // 测试开关 start 174 | let testSwitch = 1; // 正式的时候,将这个设置为0 175 | if (testSwitch == 1) { 176 | if (expressType < 4) { 177 | MonthCode = '' 178 | } else { 179 | CustomerName = 'testyto'; 180 | MonthCode = 'testytomonthcode'; 181 | } 182 | } else { 183 | if (expressType < 4) { 184 | if (expressType == 1 || expressType == 2) { 185 | let info = await this.model('shipper').where({ 186 | name: '顺丰速运' 187 | }).find(); 188 | MonthCode = info.MonthCode; 189 | } else if (expressType == 3) { 190 | let info = await this.model('shipper').where({ 191 | name: '顺丰特惠' 192 | }).find(); 193 | MonthCode = info.MonthCode; 194 | } 195 | } else { 196 | let info = await this.model('shipper').where({ 197 | code: 'YTO' 198 | }).find(); 199 | CustomerName = info.CustomerName; 200 | MonthCode = info.MonthCode; 201 | } 202 | } 203 | // 测试开关 end 204 | // 根据expressType 设置不同都属性 205 | let returnExpressInfo = {}; 206 | if (expressType == 1) { // 顺丰保价:海鲜、梭子蟹 207 | ShipperCode = 'SF'; 208 | GoodsName = '海鲜'; 209 | } else if (expressType == 2) { // 顺丰不保价:外省腌制品等 210 | ShipperCode = 'SF'; 211 | GoodsName = '海产品'; 212 | } else if (expressType == 3) { // 顺丰特惠,江浙沪皖干货 213 | ExpType = 2; 214 | ShipperCode = 'SF'; 215 | GoodsName = '海干货'; 216 | } else if (expressType == 4) { // 圆通 217 | ShipperCode = 'YTO'; 218 | GoodsName = '海产品'; 219 | } 220 | returnExpressInfo = { 221 | MemberID: '', //ERP系统、电商平台等系统或平台类型用户的会员ID或店铺账号等唯一性标识,用于区分其用户 222 | // 电子面单客户号,需要下载《快递鸟电子面单客户号参数对照表.xlsx》,参考对应字段传值 223 | CustomerPwd: '', 224 | SendSite: '', 225 | SendStaff: '', 226 | // 圆通要传这个两个值 227 | CustomerName: CustomerName, 228 | MonthCode: MonthCode, 229 | CustomArea: '', //商家自定义区域 230 | WareHouseID: '', //发货仓编码 231 | TransType: 1, // 1陆运,2空运,默认不填为1 232 | ShipperCode: ShipperCode, // 必填项!!!------------- 233 | LogisticCode: '', //快递单号(仅宅急送可用) 234 | ThrOrderCode: '', //第三方订单号 (ShipperCode为JD且ExpType为1时必填) 235 | OrderCode: '', //订单编号(自定义,不可重复) 必填项!!!---------- 236 | PayType: 3, // 邮费支付方式:1-现付,2-到付,3-月结,4-第三方支付(仅SF支持) 必填项!!!---------- 237 | ExpType: ExpType, // 快递类型:1-标准快件 2-特惠,干货用这个 ,详细快递类型参考《快递公司快递业务类型.xlsx》 必填项!!!---------- 238 | IsReturnSignBill: '', //是否要求签回单 1- 要求 0-不要求 239 | OperateRequire: '', // 签回单操作要求(如:签名、盖章、身份证复印件等) 240 | Cost: 0, // 快递运费 241 | OtherCost: 0, // 其他费用 242 | Receiver: { 243 | Company: '', //收件人公司 244 | Name: '', //收件人 必填项!!!------------- 245 | Tel: '', // 电话与手机,必填一个 246 | Mobile: '', //电话与手机,必填一个 247 | PostCode: '', // 收件人邮编 248 | ProvinceName: '', //收件省 必填项!!!-------------(如广东省,不要缺少“省”;如是直辖市,请直接传北京、上海等; 如是自治区,请直接传广西壮族自治区等) 249 | CityName: '', //收件市 必填项!!!------------- (如深圳市,不要缺少“市”; 如果是市辖区,请直接传北京市、上海市等) 250 | ExpAreaName: '', //收件区/县 必填项!!!-------------(如福田区,不要缺少“区”或“县”) 251 | Address: '' // 收件人详细地址 必填项!!!------------- 252 | }, 253 | Sender: { 254 | Company: '', // 发件人公司 255 | Name: '', //发件人 必填项!!!------------- 256 | Tel: '', //电话与手机,必填一个 必填项!!!------------- 257 | Mobile: '', //电话与手机,必填一个 必填项!!!------------- 258 | PostCode: '', //发件地邮编(ShipperCode为EMS、YZPY、YZBK时必填) 259 | ProvinceName: '', //发件省 必填项!!!------------- 260 | CityName: '', //发件市 必填项!!!------------- 261 | ExpAreaName: '', //发件区/县 必填项!!!------------ 262 | Address: '' /// 发件人详细地址 必填项!!!------ 263 | }, 264 | IsNotice: 1, //是否通知快递员上门揽件 0- 通知 1- 不通知 不填则默认为1 265 | Quantity: 1, //必填项!!!------包裹数(最多支持30件) 一个包裹对应一个运单号,如果是大于1个包裹,返回则按照子母件的方式返回母运单号和子运单号 266 | Remark: '', //备注 267 | Commodity: [{ 268 | GoodsName: GoodsName, // 商品名称 必填项!!!------ 269 | GoodsCode: '', //商品编码 270 | Goodsquantity: 0, //商品数量 271 | GoodsPrice: 0, //商品价格 272 | GoodsWeight: 0, //商品重量kg 273 | GoodsDesc: '', //商品描述 274 | GoodsVol: 0 //商品体积m3 275 | }], 276 | IsReturnPrintTemplate: 0, //返回电子面单模板:0-不需要;1-需要 todo 277 | IsSendMessage: 0, //是否订阅短信:0-不需要;1-需要 278 | TemplateSize: '', //模板规格(默认的模板无需传值,非默认模板传对应模板尺寸) 279 | PackingType: 0, //包装类型(快运字段)默认为0; 0- 纸 1- 纤 2- 木 3- 托膜 4- 木托 99-其他 280 | DeliveryMethod: 2 //送货方式(快运字段)默认为0; 0- 自提 1- 送货上门(不含上楼) 2- 送货上楼 281 | }; 282 | const orderExpress = await this.model('order').where({ 283 | id: orderId 284 | }).find(); 285 | if (think.isEmpty(orderExpress)) { 286 | let error = 400; 287 | return error; 288 | } 289 | returnExpressInfo.Remark = orderExpress.remark; 290 | let expressValue = '0'; 291 | if (expressType == 1) { // 顺丰保价:海鲜、梭子蟹 292 | expressValue = orderExpress.express_value; 293 | returnExpressInfo.AddService = [{ 294 | "Name": "INSURE", //保价 295 | "Value": expressValue, 296 | "CustomerID": '0' 297 | }, ] 298 | } 299 | // 这里就是重新生成订单号,然后记得存入order表中去 todo,这里有点问题,不应该去直接生成新的订单号,而应该让打单员选择 300 | returnExpressInfo.OrderCode = orderExpress.order_sn; 301 | // 这里就是重新生成订单号,然后记得存入order表中去 todo,这里有点问题,不应该去直接生成新的订单号,而应该让打单员选择 302 | returnExpressInfo.Receiver = receiverInfo; 303 | returnExpressInfo.Sender = senderInfo; 304 | const ExpressSerivce = think.service('express', 'api'); 305 | const latestExpressInfo = await ExpressSerivce.mianExpress(returnExpressInfo); 306 | // 将保价金额和时间回传。 307 | const currentTime = parseInt(new Date().getTime() / 1000); 308 | latestExpressInfo.send_time = moment.unix(currentTime).format('YYYY-MM-DD'); 309 | latestExpressInfo.expressValue = expressValue; 310 | latestExpressInfo.remark = orderExpress.remark; 311 | latestExpressInfo.expressType = expressType; 312 | latestExpressInfo.MonthCode = MonthCode; 313 | return latestExpressInfo; 314 | } 315 | async printExpress() { 316 | const ExpressSerivce = think.service('express', 'api'); 317 | const latestExpressInfo = await ExpressSerivce.buildForm(); 318 | return latestExpressInfo; 319 | } 320 | }; -------------------------------------------------------------------------------- /src/admin/model/region.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | module.exports = class extends think.Model { 3 | /** 4 | * 获取完整的省市区名称组成的字符串 5 | * @param provinceId 6 | * @param cityId 7 | * @param districtId 8 | * @returns {Promise.<*>} 9 | */ 10 | async getFullRegionName(provinceId, cityId, districtId) { 11 | const isFullRegion = await this.checkFullRegion(provinceId, cityId, districtId); 12 | if (!isFullRegion) { 13 | return ''; 14 | } 15 | const regionList = await this.limit(3).order({ 16 | 'id': 'asc' 17 | }).where({ 18 | id: { 19 | 'in': [provinceId, cityId, districtId] 20 | } 21 | }).select(); 22 | if (think.isEmpty(regionList) || regionList.length !== 3) { 23 | return ''; 24 | } 25 | return _.flatMap(regionList, 'name').join(''); 26 | } 27 | /** 28 | * 检查省市区信息是否完整和正确 29 | * @param provinceId 30 | * @param cityId 31 | * @param districtId 32 | * @returns {Promise.} 33 | */ 34 | async checkFullRegion(provinceId, cityId, districtId) { 35 | if (think.isEmpty(provinceId) || think.isEmpty(cityId) || think.isEmpty(districtId)) { 36 | return false; 37 | } 38 | const regionList = await this.limit(3).order({ 39 | 'id': 'asc' 40 | }).where({ 41 | id: { 42 | 'in': [provinceId, cityId, districtId] 43 | } 44 | }).select(); 45 | if (think.isEmpty(regionList) || regionList.length !== 3) { 46 | return false; 47 | } 48 | // 上下级关系检查 49 | if (_.get(regionList, ['0', 'id']) !== _.get(regionList, ['1', 'parent_id'])) { 50 | return false; 51 | } 52 | if (_.get(regionList, ['1', 'id']) !== _.get(regionList, ['2', 'parent_id'])) { 53 | return false; 54 | } 55 | return true; 56 | } 57 | /** 58 | * 获取区域的名称 59 | * @param regionId 60 | * @returns {Promise.<*>} 61 | */ 62 | async getRegionName(regionId) { 63 | return this.where({ 64 | id: regionId 65 | }).getField('name', true); 66 | } 67 | /** 68 | * 获取下级的地区列表 69 | * @param parentId 70 | * @returns {Promise.<*>} 71 | */ 72 | async getRegionList(parentId) { 73 | return this.where({ 74 | parent_id: parentId 75 | }).select(); 76 | } 77 | /** 78 | * 获取区域的信息 79 | * @param regionId 80 | * @returns {Promise.<*>} 81 | */ 82 | async getRegionInfo(regionId) { 83 | return this.where({ 84 | id: regionId 85 | }).find(); 86 | } 87 | }; -------------------------------------------------------------------------------- /src/admin/service/express.js: -------------------------------------------------------------------------------- 1 | const rp = require('request-promise'); 2 | const _ = require('lodash'); 3 | module.exports = class extends think.Service { 4 | async queryExpress(shipperCode, logisticCode, orderCode = '') { 5 | // 最终得到的数据,初始化 6 | let expressInfo = { 7 | success: false, 8 | shipperCode: shipperCode, 9 | shipperName: '', 10 | logisticCode: logisticCode, 11 | isFinish: 0, 12 | traces: [] 13 | }; 14 | // 要post的数据,进行编码,签名 15 | const fromData = this.generateFromData(shipperCode, logisticCode, orderCode); 16 | if (think.isEmpty(fromData)) { 17 | return expressInfo; 18 | } 19 | // post的参数 20 | const sendOptions = { 21 | method: 'POST', 22 | url: think.config('express.request_url'), 23 | headers: { 24 | 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' 25 | }, 26 | form: fromData 27 | }; 28 | // post请求 29 | try { 30 | const requestResult = await rp(sendOptions); 31 | if (think.isEmpty(requestResult)) { 32 | return expressInfo; 33 | } 34 | expressInfo = this.parseExpressResult(requestResult); 35 | expressInfo.shipperCode = shipperCode; 36 | expressInfo.logisticCode = logisticCode; 37 | return expressInfo; 38 | } catch (err) { 39 | return expressInfo; 40 | } 41 | } 42 | // 快递物流信息请求系统级参数 要post的数据,进行编码,签名 43 | generateFromData(shipperCode, logisticCode, orderCode) { 44 | const requestData = this.generateRequestData(shipperCode, logisticCode, orderCode); 45 | const fromData = { 46 | RequestData: encodeURI(requestData), // 把字符串作为 URI 进行编码 47 | EBusinessID: think.config('express.appid'), // 客户号 48 | RequestType: '1002', // 请求代码 49 | DataSign: this.generateDataSign(requestData), // 签名 50 | DataType: '2' //数据类型:2 51 | }; 52 | return fromData; 53 | } 54 | // JavaScript 值转换为 JSON 字符串。 55 | generateRequestData(shipperCode, logisticCode, orderCode = '') { 56 | // 参数验证 57 | const requestData = { 58 | OrderCode: orderCode, 59 | ShipperCode: shipperCode, 60 | LogisticCode: logisticCode 61 | }; 62 | return JSON.stringify(requestData); 63 | } 64 | // 编码加密 65 | generateDataSign(requestData) { 66 | return encodeURI(Buffer.from(think.md5(requestData + think.config('express.appkey'))).toString('base64')); 67 | } 68 | parseExpressResult(requestResult) { 69 | const expressInfo = { 70 | success: false, 71 | shipperCode: '', 72 | shipperName: '', 73 | logisticCode: '', 74 | isFinish: 0, 75 | traces: [] 76 | }; 77 | if (think.isEmpty(requestResult)) { 78 | return expressInfo; 79 | } 80 | try { 81 | if (_.isString(requestResult)) { 82 | requestResult = JSON.parse(requestResult); // 将一个 JSON 字符串转换为对象。 83 | } 84 | } catch (err) { 85 | return expressInfo; 86 | } 87 | if (think.isEmpty(requestResult.Success)) { 88 | return expressInfo; 89 | } 90 | // 判断是否已签收 91 | if (Number.parseInt(requestResult.State) === 3) { 92 | expressInfo.isFinish = 1; 93 | } 94 | expressInfo.success = true; 95 | if (!think.isEmpty(requestResult.Traces) && Array.isArray(requestResult.Traces)) { 96 | expressInfo.traces = _.map(requestResult.Traces, item => { 97 | return { 98 | datetime: item.AcceptTime, 99 | content: item.AcceptStation 100 | }; 101 | }); 102 | _.reverse(expressInfo.traces); 103 | } 104 | return expressInfo; 105 | } 106 | // 电子面单开始 107 | async mianExpress(data = {}) { 108 | // 从前台传过来的数据 109 | let expressInfo = data; 110 | // 进行编码,签名 111 | const fromData = this.mianFromData(data); 112 | if (think.isEmpty(fromData)) { 113 | return expressInfo; 114 | } 115 | // 请求的参数设置 116 | const sendOptions = { 117 | method: 'POST', 118 | url: think.config('mianexpress.request_url'), 119 | headers: { 120 | 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' 121 | }, 122 | form: fromData 123 | }; 124 | // post请求 125 | try { 126 | const requestResult = await rp(sendOptions); 127 | if (think.isEmpty(requestResult)) { 128 | return expressInfo; 129 | } 130 | expressInfo = this.parseMianExpressResult(requestResult); 131 | let htmldata = expressInfo.PrintTemplate; 132 | let html = htmldata.toString(); 133 | return expressInfo; 134 | } catch (err) { 135 | return expressInfo; 136 | } 137 | } 138 | // 电子面单信息请求系统级参数 要post的数据 进行编码,签名 139 | mianFromData(data) { 140 | const requestData = JSON.stringify(data); // data:post进来的 // JavaScript 值转换为 JSON 字符串。 141 | const fromData = { 142 | RequestData: encodeURI(requestData), 143 | EBusinessID: think.config('mianexpress.appid'), 144 | RequestType: '1007', 145 | DataSign: this.mianDataSign(requestData), 146 | DataType: '2' 147 | }; 148 | // console.log('fromdata======'); 149 | return fromData; 150 | } 151 | // 加密签名 152 | mianDataSign(requestData) { 153 | return encodeURI(Buffer.from(think.md5(requestData + think.config('mianexpress.appkey'))).toString('base64')); 154 | } 155 | // 返回数据 156 | parseMianExpressResult(requestResult) { 157 | const expressInfo = { 158 | success: false, 159 | shipperCode: '', 160 | shipperName: '', 161 | logisticCode: '', 162 | isFinish: 0, 163 | traces: [] 164 | }; 165 | if (think.isEmpty(requestResult)) { 166 | return expressInfo; 167 | } 168 | try { 169 | if (_.isString(requestResult)) { 170 | requestResult = JSON.parse(requestResult); 171 | } 172 | return requestResult; 173 | } catch (err) { 174 | return expressInfo; 175 | } 176 | return expressInfo; 177 | } 178 | // 电子面单结束 179 | // 批量打印开始 180 | // build_form(); 181 | /** 182 | * 组装POST表单用于调用快递鸟批量打印接口页面 183 | */ 184 | async buildForm(data = {}) { 185 | let requestData = data; 186 | requestData = '[{"OrderCode":"234351215333113311353","PortName":"打印机名称一"}]'; 187 | //OrderCode:需要打印的订单号,和调用快递鸟电子面单的订单号一致,PortName:本地打印机名称,请参考使用手册设置打印机名称。支持多打印机同时打印。 188 | // $request_data = '[{"OrderCode":"234351215333113311353","PortName":"打印机名称一"},{"OrderCode":"234351215333113311354","PortName":"打印机名称二"}]'; 189 | let requestDataEncode = encodeURI(requestData); 190 | let APIKey = think.config('mianexpress.appkey'); 191 | let API_URL = think.config('mianexpress.print_url'); 192 | let dataSign = this.printDataSign(this.get_ip(), requestDataEncode); 193 | //是否预览,0-不预览 1-预览 194 | let is_priview = '0'; 195 | let EBusinessID = think.config('mianexpress.appid'); 196 | //组装表单 197 | console.log('hahaaaaaaaaaa '); 198 | let form = '
'; 199 | console.log(form); 200 | return form; 201 | } 202 | // 加密签名 203 | printDataSign(ip, requestData) { 204 | return encodeURI(Buffer.from(ip + think.md5(requestData + think.config('mianexpress.appkey'))).toString('base64')); 205 | } 206 | /** 207 | * 判断是否为内网IP 208 | * @param ip IP 209 | * @return 是否内网IP 210 | */ 211 | // function is_private_ip($ip) { 212 | // return !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE); 213 | // } 214 | /** 215 | * 获取客户端IP(非用户服务器IP) 216 | * @return 客户端IP 217 | */ 218 | async get_ip() { 219 | const sendOptions = { 220 | method: 'GET', 221 | url: think.config('mianexpress.ip_server_url'), 222 | headers: { 223 | 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' 224 | } 225 | }; 226 | // post请求 227 | try { 228 | const requestResult = await rp(sendOptions); 229 | if (think.isEmpty(requestResult)) { 230 | let i = 0 231 | return i; 232 | } 233 | var ip = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/; 234 | var text = ip.exec(requestResult); 235 | console.log(text[0]); 236 | return text[0]; 237 | } catch (err) { 238 | return 0; 239 | } 240 | } 241 | // 批量打印结束 242 | }; -------------------------------------------------------------------------------- /src/admin/service/qiniu.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | module.exports = class extends think.Service { 3 | async getQiniuToken() { 4 | let accessKey = think.config('qiniu.access_key'); 5 | let secretKey = think.config('qiniu.secret_key'); 6 | let bucket = think.config('qiniu.bucket'); 7 | let domain = think.config('qiniu.domain'); 8 | let mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 9 | let currentTime = parseInt(new Date().getTime() / 1000) + 600; 10 | let key = think.uuid(32); 11 | let options = { 12 | scope: bucket, 13 | deadline: currentTime, 14 | saveKey: key 15 | }; 16 | let putPolicy = new qiniu.rs.PutPolicy(options); 17 | let uploadToken = putPolicy.uploadToken(mac); 18 | let data = { 19 | uploadToken: uploadToken, 20 | domain: domain 21 | }; 22 | return data; 23 | } 24 | }; -------------------------------------------------------------------------------- /src/admin/service/token.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const secret = 'SLDLKKDS323ssdd@#@@gf'; 3 | 4 | const moment = require('moment'); 5 | const rp = require('request-promise'); 6 | const fs = require('fs'); 7 | const http = require("http"); 8 | 9 | module.exports = class extends think.Service { 10 | /** 11 | * 根据header中的x-hioshop-token值获取用户id 12 | */ 13 | async getUserId() { 14 | const token = think.token; 15 | if (!token) { 16 | return 0; 17 | } 18 | 19 | const result = await this.parse(); 20 | if (think.isEmpty(result) || result.user_id <= 0) { 21 | return 0; 22 | } 23 | 24 | return result.user_id; 25 | } 26 | 27 | /** 28 | * 根据值获取用户信息 29 | */ 30 | async getUserInfo() { 31 | const userId = await this.getUserId(); 32 | if (userId <= 0) { 33 | return null; 34 | } 35 | 36 | const userInfo = await this.model('admin').where({id: userId}).find(); 37 | 38 | return think.isEmpty(userInfo) ? null : userInfo; 39 | } 40 | 41 | async create(userInfo) { 42 | const token = jwt.sign(userInfo, secret); 43 | return token; 44 | } 45 | 46 | async parse() { 47 | if (think.token) { 48 | try { 49 | return jwt.verify(think.token, secret); 50 | } catch (err) { 51 | return null; 52 | } 53 | } 54 | return null; 55 | } 56 | 57 | async verify() { 58 | const result = await this.parse(); 59 | if (think.isEmpty(result)) { 60 | return false; 61 | } 62 | 63 | return true; 64 | } 65 | 66 | async getAccessToken() { 67 | const options = { 68 | method: 'POST', 69 | url: 'https://api.weixin.qq.com/cgi-bin/token', 70 | qs: { 71 | grant_type: 'client_credential', 72 | secret: think.config('weixin.secret'), 73 | appid: think.config('weixin.appid') 74 | } 75 | }; 76 | let sessionData = await rp(options); 77 | sessionData = JSON.parse(sessionData); 78 | let token = sessionData.access_token; 79 | return token; 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /src/api/config/config.js: -------------------------------------------------------------------------------- 1 | // default config 2 | module.exports = { 3 | // 可以公开访问的Controller 4 | publicController: [ 5 | // 格式为controller 6 | 'index', 7 | 'catalog', 8 | 'auth', 9 | 'goods', 10 | 'search', 11 | 'region', 12 | 'address' 13 | ], 14 | 15 | // 可以公开访问的Action 16 | publicAction: [ 17 | // 格式为: controller+action 18 | 'cart/index', 19 | 'cart/add', 20 | 'cart/checked', 21 | 'cart/update', 22 | 'cart/delete', 23 | 'cart/goodscount', 24 | 'pay/notify' 25 | ] 26 | }; 27 | -------------------------------------------------------------------------------- /src/api/controller/address.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const pinyin = require("pinyin"); 3 | const generate = require('nanoid/generate'); 4 | module.exports = class extends Base { 5 | async getAddressesAction() { 6 | const userId = this.getLoginUserId();; 7 | const addressList = await this.model('address').where({ 8 | user_id: userId, 9 | is_delete: 0 10 | }).order('id desc').select(); 11 | let itemKey = 0; 12 | for (const addressItem of addressList) { 13 | addressList[itemKey].province_name = await this.model('region').getRegionName(addressItem.province_id); 14 | addressList[itemKey].city_name = await this.model('region').getRegionName(addressItem.city_id); 15 | addressList[itemKey].district_name = await this.model('region').getRegionName(addressItem.district_id); 16 | addressList[itemKey].full_region = addressList[itemKey].province_name + addressList[itemKey].city_name + addressList[itemKey].district_name; 17 | itemKey += 1; 18 | } 19 | return this.success(addressList); 20 | } 21 | async saveAddressAction() { 22 | let addressId = this.post('id'); 23 | const userId = this.getLoginUserId();; 24 | const addressData = { 25 | name: this.post('name'), 26 | mobile: this.post('mobile'), 27 | province_id: this.post('province_id'), 28 | city_id: this.post('city_id'), 29 | district_id: this.post('district_id'), 30 | address: this.post('address'), 31 | user_id: this.getLoginUserId(), 32 | is_default: this.post('is_default') 33 | }; 34 | if (think.isEmpty(addressId)) { 35 | addressId = await this.model('address').add(addressData); 36 | } else { 37 | await this.model('address').where({ 38 | id: addressId, 39 | user_id: userId 40 | }).update(addressData); 41 | } 42 | // 如果设置为默认,则取消其它的默认 43 | if (this.post('is_default') == 1) { 44 | await this.model('address').where({ 45 | id: ['<>', addressId], 46 | user_id: userId 47 | }).update({ 48 | is_default: 0 49 | }); 50 | } 51 | const addressInfo = await this.model('address').where({ 52 | id: addressId 53 | }).find(); 54 | return this.success(addressInfo); 55 | } 56 | async deleteAddressAction() { 57 | const id = this.post('id'); 58 | const userId = this.getLoginUserId();; 59 | let d = await this.model('address').where({ 60 | user_id: userId, 61 | id: id 62 | }).update({ 63 | is_delete: 1 64 | }); 65 | return this.success(d); 66 | } 67 | async addressDetailAction() { 68 | const addressId = this.get('id'); 69 | const userId = this.getLoginUserId();; 70 | const addressInfo = await this.model('address').where({ 71 | user_id: userId, 72 | id: addressId 73 | }).find(); 74 | if (!think.isEmpty(addressInfo)) { 75 | addressInfo.province_name = await this.model('region').getRegionName(addressInfo.province_id); 76 | addressInfo.city_name = await this.model('region').getRegionName(addressInfo.city_id); 77 | addressInfo.district_name = await this.model('region').getRegionName(addressInfo.district_id); 78 | addressInfo.full_region = addressInfo.province_name + addressInfo.city_name + addressInfo.district_name; 79 | } 80 | return this.success(addressInfo); 81 | } 82 | }; -------------------------------------------------------------------------------- /src/api/controller/auth.js: -------------------------------------------------------------------------------- 1 | const Base = require("./base.js"); 2 | const rp = require("request-promise"); 3 | module.exports = class extends Base { 4 | async loginByWeixinAction() { 5 | // const code = this.post('code'); 6 | const code = this.post("code"); 7 | let currentTime = parseInt(new Date().getTime() / 1000); 8 | const clientIp = ""; // 暂时不记录 ip test git 9 | // 获取openid 10 | const options = { 11 | method: "GET", 12 | url: "https://api.weixin.qq.com/sns/jscode2session", 13 | qs: { 14 | grant_type: "authorization_code", 15 | js_code: code, 16 | secret: think.config("weixin.secret"), 17 | appid: think.config("weixin.appid"), 18 | }, 19 | }; 20 | let sessionData = await rp(options); 21 | sessionData = JSON.parse(sessionData); 22 | if (!sessionData.openid) { 23 | return this.fail("登录失败,openid无效"); 24 | } 25 | // 根据openid查找用户是否已经注册 26 | let userId = await this.model("user") 27 | .where({ 28 | weixin_openid: sessionData.openid, 29 | }) 30 | .getField("id", true); 31 | let is_new = 0; 32 | const buffer = Buffer.from('微信用户'); 33 | let nickname = buffer.toString("base64"); 34 | if (think.isEmpty(userId)) { 35 | // 注册 36 | userId = await this.model("user").add({ 37 | username: "微信用户" + think.uuid(6), 38 | password: sessionData.openid, 39 | register_time: currentTime, 40 | register_ip: clientIp, 41 | last_login_time: currentTime, 42 | last_login_ip: clientIp, 43 | mobile: "", 44 | weixin_openid: sessionData.openid, 45 | nickname: nickname, 46 | avatar:'/static/images/default_avatar.png' 47 | }); 48 | is_new = 1; 49 | } 50 | sessionData.user_id = userId; 51 | // 更新登录信息 52 | await this.model("user") 53 | .where({ 54 | id: userId, 55 | }) 56 | .update({ 57 | last_login_time: currentTime, 58 | last_login_ip: clientIp, 59 | }); 60 | const newUserInfo = await this.model("user") 61 | .field("id,username,nickname, avatar") 62 | .where({ 63 | id: userId, 64 | }) 65 | .find(); 66 | newUserInfo.nickname = Buffer.from( 67 | newUserInfo.nickname, 68 | "base64" 69 | ).toString(); 70 | const TokenSerivce = this.service("token", "api"); 71 | const sessionKey = await TokenSerivce.create(sessionData); 72 | if (think.isEmpty(newUserInfo) || think.isEmpty(sessionKey)) { 73 | return this.fail("登录失败4"); 74 | } 75 | return this.success({ 76 | token: sessionKey, 77 | userInfo: newUserInfo, 78 | is_new: is_new, 79 | }); 80 | } 81 | async logoutAction() { 82 | return this.success(); 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /src/api/controller/base.js: -------------------------------------------------------------------------------- 1 | module.exports = class extends think.Controller { 2 | async __before() { 3 | // 根据token值获取用户id 4 | const token = this.ctx.header['x-hioshop-token'] || ''; 5 | const tokenSerivce = think.service('token', 'api'); 6 | think.userId = tokenSerivce.getUserId(token); 7 | } 8 | /** 9 | * 获取时间戳 10 | * @returns {Number} 11 | */ 12 | getTime() { 13 | return parseInt(Date.now() / 1000); 14 | } 15 | /** 16 | * 获取当前登录用户的id 17 | * @returns {*} 18 | */ 19 | getLoginUserId() { 20 | const token = this.ctx.header['x-hioshop-token'] || ''; 21 | const tokenSerivce = think.service('token', 'api'); 22 | return tokenSerivce.getUserId(token); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/api/controller/catalog.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | module.exports = class extends Base { 3 | /** 4 | * 获取分类栏目数据 5 | * @returns {Promise.} 6 | */ 7 | async indexAction() { 8 | const categoryId = this.get('id'); 9 | const model = this.model('category'); 10 | const data = await model.limit(10).where({ 11 | parent_id: 0, 12 | is_category: 1 13 | }).order('sort_order ASC').select(); 14 | let currentCategory = null; 15 | if (categoryId) { 16 | currentCategory = await model.where({ 17 | 'id': categoryId 18 | }).find(); 19 | } 20 | if (think.isEmpty(currentCategory)) { 21 | currentCategory = data[0]; 22 | } 23 | return this.success({ 24 | categoryList: data, 25 | }); 26 | } 27 | async currentAction() { 28 | const categoryId = this.get('id'); 29 | let data = await this.model('category').where({ 30 | id: categoryId 31 | }).field('id,name,img_url,p_height').find(); 32 | return this.success(data); 33 | } 34 | async currentlistAction() { 35 | const page = this.post('page'); 36 | const size = this.post('size'); 37 | const categoryId = this.post('id'); 38 | if (categoryId == 0) { 39 | let list = await this.model('goods').where({ 40 | is_on_sale: 1, 41 | is_delete: 0 42 | }).order({ 43 | sort_order: 'asc' 44 | }).field('name,id,goods_brief,min_retail_price,list_pic_url,goods_number').page(page, size).countSelect(); 45 | return this.success(list); 46 | } else { 47 | let list = await this.model('goods').where({ 48 | is_on_sale: 1, 49 | is_delete: 0, 50 | category_id: categoryId 51 | }).order({ 52 | sort_order: 'asc' 53 | }).field('name,id,goods_brief,min_retail_price,list_pic_url,goods_number').page(page, size).countSelect(); 54 | return this.success(list); 55 | } 56 | } 57 | }; -------------------------------------------------------------------------------- /src/api/controller/crontab.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | const rp = require('request-promise'); 4 | const http = require("http"); 5 | module.exports = class extends Base { 6 | async timetaskAction() { 7 | console.log("=============开始============"); 8 | let currentTime = parseInt(new Date().getTime() / 1000); 9 | let newday = new Date(new Date().setHours(3, 0, 0, 0)) / 1000; 10 | let newday_over = new Date(new Date().setHours(3, 0, 59, 0)) / 1000; 11 | if (currentTime > newday && currentTime < newday_over) { 12 | } 13 | // 将公告下掉 14 | let notice = await this.model('notice').where({ 15 | is_delete: 0 16 | }).select(); 17 | if (notice.length > 0) { 18 | for (const noticeItem of notice) { 19 | let notice_exptime = noticeItem.end_time; 20 | if (currentTime > notice_exptime) { 21 | await this.model('notice').where({ 22 | id: noticeItem.id 23 | }).update({ 24 | is_delete: 1 25 | }); 26 | } 27 | } 28 | } 29 | const expiretime = parseInt(new Date().getTime() / 1000) - 24 * 60 * 60; 30 | let orderList = await this.model('order').where({ 31 | order_status: ['IN', '101,801'], 32 | add_time: ['<', expiretime], 33 | is_delete: 0, 34 | }).select(); 35 | if (orderList.length != 0) { 36 | // await this.model('order').where({id: ['IN', orderList.map((ele) => ele.id)]}).update({order_status: 102}); 37 | for (const item of orderList) { 38 | 39 | let orderId = item.id; 40 | await this.model('order').where({ 41 | id: orderId 42 | }).update({ 43 | order_status: 102 44 | }); 45 | } 46 | } 47 | // 定时将到期的广告停掉 48 | let ad_info = await this.model('ad').where({ 49 | end_time: ['<', currentTime], 50 | enabled: 1 51 | }).select(); 52 | if (ad_info.length != 0) { 53 | await this.model('ad').where({ 54 | id: ['IN', ad_info.map((ele) => ele.id)] 55 | }).update({ 56 | enabled: 0 57 | }); 58 | } 59 | //定时将长时间没收货的订单确认收货 60 | const noConfirmTime = parseInt(new Date().getTime() / 1000) - 5 * 24 * 60 * 60; 61 | // 5天没确认收货就自动确认 62 | let noConfirmList = await this.model('order').where({ 63 | order_status: 301, 64 | shipping_time: { 65 | '<=': noConfirmTime, 66 | '<>': 0 67 | }, 68 | is_delete: 0, 69 | }).select(); 70 | if (noConfirmList.length != 0) { 71 | for (const citem of noConfirmList) { 72 | let orderId = citem.id; 73 | await this.model('order').where({ 74 | id: orderId 75 | }).update({ 76 | order_status: 401, 77 | confirm_time: currentTime 78 | }); 79 | } 80 | } 81 | } 82 | async resetSqlAction() { 83 | let time = parseInt(new Date().getTime() / 1000 + 300); 84 | let info = await this.model('settings').where({id:1}).find(); 85 | if(info.reset == 0){ 86 | await this.model('settings').where({id:1}).update({countdown:time,reset:1}); 87 | console.log('重置了!'); 88 | } 89 | console.log('还没到呢!'); 90 | } 91 | }; -------------------------------------------------------------------------------- /src/api/controller/footprint.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | const _ = require('lodash'); 4 | module.exports = class extends Base { 5 | /** 6 | * 7 | * @returns {Promise} 8 | */ 9 | async deleteAction() { 10 | const footprintId = this.post('footprintId'); 11 | const userId = this.getLoginUserId();; 12 | // 删除当天的同一个商品的足迹 13 | await this.model('footprint').where({ 14 | user_id: userId, 15 | id: footprintId 16 | }).delete(); 17 | return this.success('删除成功'); 18 | } 19 | /** 20 | * list action 21 | * @return {Promise} [] 22 | */ 23 | async listAction() { 24 | const page = this.get('page'); 25 | const size = this.get('size'); 26 | const userId = this.getLoginUserId();; 27 | const list = await this.model('footprint').alias('f').join({ 28 | table: 'goods', 29 | join: 'left', 30 | as: 'g', 31 | on: ['f.goods_id', 'g.id'] 32 | }).where({ 33 | user_id: userId 34 | }).page(page, size).order({ 35 | add_time: 'desc' 36 | }).field('id,goods_id,add_time').countSelect(); 37 | for (const item of list.data) { 38 | let goods = await this.model('goods').where({ 39 | id:item.goods_id 40 | }).field('name,goods_brief,retail_price,list_pic_url,goods_number,min_retail_price').find(); 41 | item.add_time = moment.unix(item.add_time).format('YYYY-MM-DD'); 42 | item.goods = goods; 43 | if (moment().format('YYYY-MM-DD') == item.add_time) { 44 | item.add_time = '今天'; 45 | } 46 | } 47 | return this.success(list); 48 | } 49 | }; -------------------------------------------------------------------------------- /src/api/controller/goods.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | module.exports = class extends Base { 4 | async indexAction() { 5 | const model = this.model('goods'); 6 | const goodsList = await model.select(); 7 | return this.success(goodsList); 8 | } 9 | /** 10 | * 商品详情页数据 11 | * @returns {Promise.} 12 | */ 13 | async detailAction() { 14 | const goodsId = this.get('id'); 15 | const userId = this.getLoginUserId();; 16 | const model = this.model('goods'); 17 | let info = await model.where({ 18 | id: goodsId, 19 | is_delete:0 20 | }).find(); 21 | if(think.isEmpty(info)){ 22 | return this.fail('该商品不存在或已下架'); 23 | } 24 | const gallery = await this.model('goods_gallery').where({ 25 | goods_id: goodsId, 26 | is_delete: 0, 27 | }).order('sort_order').limit(6).select(); 28 | await this.model('footprint').addFootprint(userId, goodsId); 29 | let productList = await model.getProductList(goodsId); 30 | let goodsNumber = 0; 31 | for (const item of productList) { 32 | if (item.goods_number > 0) { 33 | goodsNumber = goodsNumber + item.goods_number; 34 | } 35 | } 36 | let specificationList = await model.getSpecificationList(goodsId); 37 | info.goods_number = goodsNumber; 38 | return this.success({ 39 | info: info, 40 | gallery: gallery, 41 | specificationList: specificationList, 42 | productList: productList 43 | }); 44 | } 45 | async goodsShareAction() { 46 | const goodsId = this.get('id'); 47 | const info = await this.model('goods').where({ 48 | id: goodsId 49 | }).field('name,retail_price').find(); 50 | return this.success(info); 51 | } 52 | /** 53 | * 获取商品列表 54 | * @returns {Promise.<*>} 55 | */ 56 | async listAction() { 57 | const userId = this.getLoginUserId();; 58 | const keyword = this.get('keyword'); 59 | const sort = this.get('sort'); 60 | const order = this.get('order'); 61 | const sales = this.get('sales'); 62 | const model = this.model('goods'); 63 | const whereMap = { 64 | is_on_sale: 1, 65 | is_delete: 0, 66 | }; 67 | if (!think.isEmpty(keyword)) { 68 | whereMap.name = ['like', `%${keyword}%`]; 69 | // 添加到搜索历史 70 | await this.model('search_history').add({ 71 | keyword: keyword, 72 | user_id: userId, 73 | add_time: parseInt(new Date().getTime() / 1000) 74 | }); 75 | // TODO 之后要做个判断,这个词在搜索记录中的次数,如果大于某个值,则将他存入keyword 76 | } 77 | // 排序 78 | let orderMap = {}; 79 | if (sort === 'price') { 80 | // 按价格 81 | orderMap = { 82 | retail_price: order 83 | }; 84 | } else if (sort === 'sales') { 85 | // 按价格 86 | orderMap = { 87 | sell_volume: sales 88 | }; 89 | } else { 90 | // 按商品添加时间 91 | orderMap = { 92 | sort_order: 'asc' 93 | }; 94 | } 95 | const goodsData = await model.where(whereMap).order(orderMap).select(); 96 | return this.success(goodsData); 97 | } 98 | /** 99 | * 在售的商品总数 100 | * @returns {Promise.} 101 | */ 102 | async countAction() { 103 | const goodsCount = await this.model('goods').where({ 104 | is_delete: 0, 105 | is_on_sale: 1 106 | }).count('id'); 107 | return this.success({ 108 | goodsCount: goodsCount 109 | }); 110 | } 111 | }; -------------------------------------------------------------------------------- /src/api/controller/index.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | // const view = require('think-view'); 3 | const moment = require('moment'); 4 | const Jushuitan = require('jushuitan'); 5 | const rp = require('request-promise'); 6 | const http = require("http"); 7 | module.exports = class extends Base { 8 | async indexAction() { 9 | //auto render template file index_index.html 10 | return this.display(); 11 | } 12 | async appInfoAction() { 13 | const banner = await this.model('ad').where({ 14 | enabled: 1, 15 | is_delete: 0 16 | }).field('link_type,goods_id,image_url,link').order('sort_order asc').select(); 17 | const notice = await this.model('notice').where({ 18 | is_delete: 0 19 | }).field('content').select(); 20 | const channel = await this.model('category').where({ 21 | is_channel: 1, 22 | parent_id: 0, 23 | }).field('id,icon_url,name,sort_order').order({ 24 | sort_order: 'asc' 25 | }).select(); 26 | const categoryList = await this.model('category').where({ 27 | parent_id: 0, 28 | is_show: 1 29 | }).field('id,name,img_url as banner, p_height as height').order({ 30 | sort_order: 'asc' 31 | }).select(); 32 | for (const categoryItem of categoryList) { 33 | const categoryGoods = await this.model('goods').where({ 34 | category_id: categoryItem.id, 35 | goods_number: ['>=', 0], 36 | is_on_sale: 1, 37 | is_index: 1, 38 | is_delete: 0 39 | }).field('id,list_pic_url,is_new,goods_number,name,min_retail_price').order({ 40 | sort_order: 'asc' 41 | }).select(); 42 | categoryItem.goodsList = categoryGoods; 43 | } 44 | const userId = this.getLoginUserId(); 45 | let cartCount = await this.model('cart').where({ 46 | user_id: userId, 47 | is_delete: 0 48 | }).sum('number'); 49 | if(cartCount == null){ 50 | cartCount = 0; 51 | } 52 | let data = { 53 | channel: channel, 54 | banner: banner, 55 | notice: notice, 56 | categoryList: categoryList, 57 | cartCount: cartCount, 58 | } 59 | return this.success(data); 60 | } 61 | }; -------------------------------------------------------------------------------- /src/api/controller/pay.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const moment = require('moment'); 3 | const generate = require('nanoid/generate'); 4 | const Jushuitan = require('jushuitan'); 5 | module.exports = class extends Base { 6 | /** 7 | * 获取支付的请求参数 8 | * @returns {Promise} 9 | */ 10 | // 测试时付款,将真实接口注释。 在小程序的services/pay.js中按照提示注释和打开 11 | async preWeixinPayaAction() { 12 | const orderId = this.get('orderId'); 13 | const orderInfo = await this.model('order').where({ 14 | id: orderId 15 | }).find(); 16 | let userId = orderInfo.user_id; 17 | let result = { 18 | transaction_id: 123123123123, 19 | time_end: parseInt(new Date().getTime() / 1000), 20 | } 21 | const orderModel = this.model('order'); 22 | await orderModel.updatePayData(orderInfo.id, result); 23 | this.afterPay(orderInfo); 24 | return this.success(); 25 | } 26 | // 真实的付款接口 27 | async preWeixinPayAction() { 28 | const orderId = this.get('orderId'); 29 | const orderInfo = await this.model('order').where({ 30 | id: orderId 31 | }).find(); 32 | // 再次确认库存和价格 33 | let orderGoods = await this.model('order_goods').where({ 34 | order_id:orderId, 35 | is_delete:0 36 | }).select(); 37 | let checkPrice = 0; 38 | let checkStock = 0; 39 | for(const item of orderGoods){ 40 | let product = await this.model('product').where({ 41 | id:item.product_id 42 | }).find(); 43 | if(item.number > product.goods_number){ 44 | checkStock++; 45 | } 46 | if(item.retail_price != product.retail_price){ 47 | checkPrice++; 48 | } 49 | } 50 | if(checkStock > 0){ 51 | return this.fail(400, '库存不足,请重新下单'); 52 | } 53 | if(checkPrice > 0){ 54 | return this.fail(400, '价格发生变化,请重新下单'); 55 | } 56 | if (think.isEmpty(orderInfo)) { 57 | return this.fail(400, '订单已取消'); 58 | } 59 | if (parseInt(orderInfo.pay_status) !== 0) { 60 | return this.fail(400, '订单已支付,请不要重复操作'); 61 | } 62 | const openid = await this.model('user').where({ 63 | id: orderInfo.user_id 64 | }).getField('weixin_openid', true); 65 | if (think.isEmpty(openid)) { 66 | return this.fail(400, '微信支付失败,没有openid'); 67 | } 68 | const WeixinSerivce = this.service('weixin', 'api'); 69 | try { 70 | const returnParams = await WeixinSerivce.createUnifiedOrder({ 71 | openid: openid, 72 | body: '[海风小店]:' + orderInfo.order_sn, 73 | out_trade_no: orderInfo.order_sn, 74 | total_fee: parseInt(orderInfo.actual_price * 100), 75 | spbill_create_ip: '' 76 | }); 77 | return this.success(returnParams); 78 | } catch (err) { 79 | return this.fail(400, '微信支付失败?'); 80 | } 81 | } 82 | async notifyAction() { 83 | const WeixinSerivce = this.service('weixin', 'api'); 84 | const data = this.post('xml'); 85 | const result = WeixinSerivce.payNotify(this.post('xml')); 86 | 87 | if (!result) { 88 | let echo = 'FAIL'; 89 | return this.json(echo); 90 | } 91 | const orderModel = this.model('order'); 92 | const orderInfo = await orderModel.getOrderByOrderSn(result.out_trade_no); 93 | if (think.isEmpty(orderInfo)) { 94 | let echo = 'FAIL'; 95 | return this.json(echo); 96 | } 97 | let bool = await orderModel.checkPayStatus(orderInfo.id); 98 | if (bool == true) { 99 | if (orderInfo.order_type == 0) { //普通订单和秒杀订单 100 | await orderModel.updatePayData(orderInfo.id, result); 101 | this.afterPay(orderInfo); 102 | } 103 | } else { 104 | return ''; 105 | } 106 | let echo = 'SUCCESS' 107 | return this.json(echo); 108 | } 109 | async afterPay(orderInfo) { 110 | if (orderInfo.order_type == 0) { 111 | let orderGoodsList = await this.model('order_goods').where({ 112 | order_id: orderInfo.id 113 | }).select(); 114 | for (const cartItem of orderGoodsList) { 115 | let goods_id = cartItem.goods_id; 116 | let product_id = cartItem.product_id; 117 | let number = cartItem.number; 118 | let specification = cartItem.goods_specifition_name_value; 119 | await this.model('goods').where({ 120 | id: goods_id 121 | }).decrement('goods_number', number); 122 | await this.model('goods').where({ 123 | id: goods_id 124 | }).increment('sell_volume', number); 125 | await this.model('product').where({ 126 | id: product_id 127 | }).decrement('goods_number', number); 128 | } 129 | // version 1.01 130 | } 131 | } 132 | }; -------------------------------------------------------------------------------- /src/api/controller/qrcode.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const rp = require('request-promise'); 3 | const fs = require('fs'); 4 | const http = require("https"); 5 | const path = require('path'); 6 | // const mineType = require('mime-types'); 7 | module.exports = class extends Base { 8 | async getBase64Action() { 9 | let goodsId = this.post('goodsId'); 10 | let page = "pages/goods/goods"; 11 | let sceneData = goodsId; 12 | const options = { 13 | method: 'POST', 14 | url: 'https://api.weixin.qq.com/cgi-bin/token', 15 | qs: { 16 | grant_type: 'client_credential', 17 | secret: think.config('weixin.secret'), 18 | appid: think.config('weixin.appid') 19 | } 20 | }; 21 | let sessionData = await rp(options); 22 | sessionData = JSON.parse(sessionData); 23 | let token = sessionData.access_token; 24 | let data = { 25 | "scene": sceneData, //第一个参数是抽奖ID,第二个是userId,第三个是share=1 26 | "page": page, 27 | "width": 200 28 | }; 29 | data = JSON.stringify(data); 30 | var options2 = { 31 | method: "POST", 32 | host: "api.weixin.qq.com", 33 | path: "/wxa/getwxacodeunlimit?access_token=" + token, 34 | headers: { 35 | "Content-Type": "application/json", 36 | "Content-Length": data.length 37 | } 38 | }; 39 | const uploadFunc = async () => { 40 | return new Promise((resolve, reject) => { 41 | try { 42 | var req = http.request(options2, function(res) { 43 | res.setEncoding("base64"); 44 | var imgData = ""; 45 | res.on('data', function(chunk) { 46 | imgData += chunk; 47 | }); 48 | res.on("end", function() { 49 | return resolve(imgData); 50 | }); 51 | }); 52 | req.write(data); 53 | req.end(); 54 | } catch (e) { 55 | return resolve(null); 56 | } 57 | }) 58 | }; 59 | const url = await uploadFunc(); 60 | return this.success(url); 61 | } 62 | } -------------------------------------------------------------------------------- /src/api/controller/region.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | module.exports = class extends Base { 3 | async infoAction() { 4 | const region = await this.model('region').getRegionInfo(this.get('regionId')); 5 | return this.success(region); 6 | } 7 | async listAction() { 8 | const regionList = await this.model('region').getRegionList(this.get('parentId')); 9 | return this.success(regionList); 10 | } 11 | async dataAction() { 12 | let parentId = this.post('parent_id'); 13 | let info = await this.model('region').where({ 14 | parent_id: parentId 15 | }).getField('id,name'); 16 | return this.success(info); 17 | } 18 | async codeAction() { 19 | let province = this.post('Province'); 20 | let city = this.post('City'); 21 | let country = this.post('Country'); 22 | let provinceInfo = await this.model('region').where({ 23 | name: province 24 | }).field('id').find(); 25 | let province_id = provinceInfo.id; 26 | let cityInfo = await this.model('region').where({ 27 | name: city 28 | }).field('id').find(); 29 | let city_id = cityInfo.id; 30 | let countryInfo = await this.model('region').where({ 31 | name: country 32 | }).field('id').find(); 33 | let country_id = countryInfo.id; 34 | let data = { 35 | province_id: province_id, 36 | city_id: city_id, 37 | country_id: country_id 38 | } 39 | return this.success(data); 40 | } 41 | }; -------------------------------------------------------------------------------- /src/api/controller/search.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | //TODO 在后台搜索那里完善,采用专门的搜索开发库 3 | module.exports = class extends Base { 4 | async indexAction() { 5 | // 取出输入框默认的关键词 6 | let userId = this.getLoginUserId();; 7 | const defaultKeyword = await this.model('keywords').where({ 8 | is_default: 1 9 | }).limit(1).find(); 10 | // 取出热闹关键词 11 | const hotKeywordList = await this.model('keywords').distinct('keyword').field(['keyword', 'is_hot']).limit(10).select(); 12 | const historyKeywordList = await this.model('search_history').distinct('keyword').where({ 13 | user_id: userId 14 | }).limit(10).getField('keyword'); 15 | return this.success({ 16 | defaultKeyword: defaultKeyword, 17 | historyKeywordList: historyKeywordList, 18 | hotKeywordList: hotKeywordList 19 | }); 20 | } 21 | async helperAction() { 22 | const keyword = this.get('keyword'); 23 | const keywords = await this.model('keywords').distinct('keyword').where({ 24 | keyword: ['like', keyword + '%'] 25 | }).getField('keyword', 10); 26 | return this.success(keywords); 27 | } 28 | async clearHistoryAction() { 29 | let userId = this.getLoginUserId();; 30 | await this.model('search_history').where({ 31 | user_id: userId 32 | }).delete(); 33 | return this.success(); 34 | } 35 | }; -------------------------------------------------------------------------------- /src/api/controller/settings.js: -------------------------------------------------------------------------------- 1 | const Base = require("./base.js"); 2 | module.exports = class extends Base { 3 | async showSettingsAction() { 4 | let info = await this.model("show_settings") 5 | .where({ 6 | id: 1, 7 | }) 8 | .find(); 9 | return this.success(info); 10 | } 11 | async saveAction() { 12 | let userId = this.getLoginUserId(); 13 | let name = this.post("name"); 14 | let mobile = this.post("mobile"); 15 | let nickName = this.post("nickName"); 16 | let avatar = this.post("avatar"); 17 | let name_mobile = 0; 18 | if (name != "" && mobile != "") { 19 | name_mobile = 1; 20 | } 21 | const newbuffer = Buffer.from(nickName); 22 | let nickname = newbuffer.toString("base64"); 23 | let data = { 24 | name: name, 25 | mobile: mobile, 26 | nickname: nickname, 27 | avatar: avatar, 28 | name_mobile: name_mobile, 29 | }; 30 | let info = await this.model("user") 31 | .where({ 32 | id: userId, 33 | }) 34 | .update(data); 35 | return this.success(info); 36 | } 37 | async userDetailAction() { 38 | let userId = this.getLoginUserId(); 39 | if (userId != 0) { 40 | let info = await this.model("user") 41 | .where({ 42 | id: userId, 43 | }) 44 | .field("id,mobile,name,nickname,avatar") 45 | .find(); 46 | info.nickname = Buffer.from(info.nickname, "base64").toString(); 47 | return this.success(info); 48 | } 49 | else{ 50 | return this.fail(100,'未登录') 51 | } 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /src/api/controller/upload.js: -------------------------------------------------------------------------------- 1 | const Base = require("./base.js"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | 5 | module.exports = class extends Base { 6 | async uploadAvatarAction() { 7 | const file = this.file('upload_file'); 8 | let fileType = file.type; 9 | const spliceLength = fileType.lastIndexOf("/"); 10 | let fileTypeText = fileType.slice(spliceLength + 1); 11 | if (think.isEmpty(file)) { 12 | return this.fail("保存失败"); 13 | } 14 | const that = this; 15 | let name = think.uuid(32) + "." + fileTypeText; 16 | const filename = "/static/upload/avatar/" + name; 17 | const is = fs.createReadStream(file.path); 18 | const os = fs.createWriteStream(think.ROOT_PATH + "/www" + filename); 19 | is.pipe(os); 20 | return that.success({ 21 | name: name, 22 | fileUrl: filename, 23 | }); 24 | } 25 | 26 | // async deleteFileAction() { 27 | // const url = this.post('para'); 28 | // let newUrl = url.lastIndexOf("/"); 29 | // let fileName = url.substring(newUrl + 1); 30 | // let delePath = './www/static/upload/goods/detail/' + fileName; 31 | // fs.unlink(delePath, function (err) { 32 | // if (err) throw err; 33 | // return false; 34 | // }); 35 | // return this.success('文件删除成功'); 36 | // } 37 | }; 38 | -------------------------------------------------------------------------------- /src/api/controller/user.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base.js'); 2 | const fs = require('fs'); 3 | const _ = require('lodash'); 4 | const moment = require('moment'); 5 | module.exports = class extends Base { 6 | 7 | }; -------------------------------------------------------------------------------- /src/api/controller/weChat.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-multi-spaces */ 2 | const Base = require('./base.js'); 3 | const crypto = require("crypto"); 4 | /** 5 | * 检验signature对请求进行校验 6 | */ 7 | function checkSignature(params) { 8 | //token 就是自己填写的令牌 9 | var key = ['huixianshuichanhuixianshuichan', params.timestamp, params.nonce].sort().join(''); 10 | //将token (自己设置的) 、timestamp(时间戳)、nonce(随机数)三个参数进行字典排序 11 | var sha1 = crypto.createHash('sha1'); 12 | //将上面三个字符串拼接成一个字符串再进行sha1加密 13 | sha1.update(key); 14 | let a = sha1.digest('hex'); 15 | let b = params.signature; 16 | if (a == b) { 17 | return true; 18 | } 19 | //将加密后的字符串与signature进行对比,若成功,返回success。如果通过验证,则,注释掉这个函数 20 | } 21 | module.exports = class extends Base { 22 | async receiveAction() { 23 | // const value = this.post('value'); 24 | await this.model('rules').add({ 25 | name: 9, 26 | rule_content: '哈哈' 27 | }); 28 | return this.success('haha'); 29 | } 30 | async notifyAction() { 31 | /** 32 | * 服务器配置校验,校验后,注释掉! 33 | */ 34 | // const info = this.get(""); 35 | // if (checkSignature(info)){ 36 | // return this.json(info.echostr); 37 | // } 38 | // else { 39 | // return this.fail("error"); 40 | // } 41 | await this.model('rules').add({ 42 | name: 9, 43 | rule_content: '哈哈' 44 | }); 45 | return this.success('haha'); 46 | const data = this.post(""); 47 | const { 48 | ToUserName, 49 | FromUserName, 50 | CreateTime, 51 | MsgType, 52 | Content, 53 | MsgId 54 | } = data; 55 | // console.log("data: ", JSON.stringify(data)); 56 | const tokenServer = think.service('weixin', 'api'); 57 | const token = await tokenServer.getAccessToken(); 58 | // console.log(token); 59 | switch (data.MsgType) { 60 | case 'text': 61 | { //用户在客服会话中发送文本消息 62 | await tokenServer.sendTextMessage(data, token); 63 | break; 64 | } 65 | // case 'image': { //用户在客服会话中发送图片消息 66 | // await sendImageMessage(data.MediaId, data, access_token); 67 | // await tokenServer.sendImageMessage(data, token); 68 | // break; 69 | // } 70 | case 'event': 71 | { 72 | if (data.Event == 'user_enter_tempsession') { //用户在小程序“客服会话按钮”进入客服会话,在聊天框进入不会有此事件 73 | // await tokenServer.sendTextMessage(data, token); 74 | // await sendTextMessage("您有什么问题吗?", data, access_token); 75 | } else if (data.Event == 'kf_create_session') { //网页客服进入回话 76 | console.log('网页客服进入回话'); 77 | } 78 | break; 79 | } 80 | } 81 | // https://www.jianshu.com/p/3d59ae5e69ab 82 | } 83 | }; -------------------------------------------------------------------------------- /src/api/model/cart.js: -------------------------------------------------------------------------------- 1 | module.exports = class extends think.Model { 2 | /** 3 | * 获取购物车的商品 4 | * @returns {Promise.<*>} 5 | */ 6 | async getGoodsList() { 7 | const goodsList = await this.model('cart').where({ 8 | user_id: think.userId, 9 | is_delete: 0 10 | }).select(); 11 | return goodsList; 12 | } 13 | /** 14 | * 获取购物车的选中的商品 15 | * @returns {Promise.<*>} 16 | */ 17 | async getCheckedGoodsList() { 18 | const goodsList = await this.model('cart').where({ 19 | user_id: think.userId, 20 | checked: 1, 21 | is_delete: 0 22 | }).select(); 23 | return goodsList; 24 | } 25 | /** 26 | * 清空已购买的商品 27 | * @returns {Promise.<*>} 28 | */ 29 | async clearBuyGoods() { 30 | const $res = await this.model('cart').where({ 31 | user_id: think.userId, 32 | checked: 1, 33 | is_delete: 0 34 | }).update({ 35 | is_delete: 1 36 | }); 37 | return $res; 38 | } 39 | }; -------------------------------------------------------------------------------- /src/api/model/category.js: -------------------------------------------------------------------------------- 1 | module.exports = class extends think.Model { 2 | async getChildCategoryId(parentId) { 3 | const childIds = await this.where({parent_id: parentId}).getField('id', 10000); 4 | return childIds; 5 | } 6 | 7 | async getCategoryWhereIn(categoryId) { 8 | const childIds = await this.getChildCategoryId(categoryId); 9 | childIds.push(categoryId); 10 | return childIds; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/api/model/footprint.js: -------------------------------------------------------------------------------- 1 | module.exports = class extends think.Model { 2 | async addFootprint(userId, goodsId) { 3 | // 用户已经登录才可以添加到足迹 4 | 5 | const currentTime = parseInt(new Date().getTime() / 1000); 6 | 7 | if (userId > 0 && goodsId > 0) { 8 | let info = await this.where({ 9 | goods_id: goodsId, 10 | user_id: userId 11 | }).find(); 12 | if (think.isEmpty(info)) { 13 | await this.add({ 14 | goods_id: goodsId, 15 | user_id: userId, 16 | add_time: currentTime 17 | }); 18 | } 19 | else { 20 | const add_time = currentTime; 21 | await this.where({ 22 | goods_id: goodsId, 23 | user_id: userId 24 | }).update({add_time: add_time}); 25 | } 26 | } 27 | 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/api/model/goods.js: -------------------------------------------------------------------------------- 1 | module.exports = class extends think.Model { 2 | /** 3 | * 获取商品的product 4 | * @param goodsId 5 | * @returns {Promise.<*>} 6 | */ 7 | async getProductList(goodsId) { 8 | const goods = await this.model('product').where({goods_id: goodsId,is_delete:0}).select(); 9 | return goods; 10 | } 11 | 12 | /** 13 | * 获取商品的规格信息 14 | * @param goodsId 15 | * @returns {Promise.} 16 | */ 17 | async getSpecificationList(goodsId) { 18 | // 根据sku商品信息,查找规格值列表 19 | let info = await this.model('goods_specification').where({goods_id:goodsId,is_delete:0}).select(); 20 | for(const item of info){ 21 | let product = await this.model('product').where({ 22 | goods_specification_ids:item.id, 23 | is_delete:0 24 | }).find(); 25 | item.goods_number = product.goods_number; 26 | } 27 | let spec_id = info[0].specification_id; 28 | let specification = await this.model('specification').where({id:spec_id}).find(); 29 | let name = specification.name; 30 | let data = { 31 | specification_id:spec_id, 32 | name:name, 33 | valueList:info 34 | } 35 | return data; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/api/model/order.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | module.exports = class extends think.Model { 3 | /** 4 | * 生成订单的编号order_sn 5 | * @returns {string} 6 | */ 7 | // TODO 这里应该产生一个唯一的订单,但是实际上这里仍然存在两个订单相同的可能性 8 | generateOrderNumber() { 9 | const date = new Date(); 10 | return date.getFullYear() + _.padStart(date.getMonth(), 2, '0') + _.padStart(date.getDay(), 2, '0') + _.padStart(date.getHours(), 2, '0') + _.padStart(date.getMinutes(), 2, '0') + _.padStart(date.getSeconds(), 2, '0') + _.random(100000, 999999); 11 | } 12 | getOrderStatus(showType) { 13 | let status = []; 14 | if (showType == 0) { 15 | status.push(101, 102, 103, 201, 202, 203, 300, 301, 302, 303, 401); 16 | // TODO 这里会不会效率不高? 17 | } else if (showType == 1) { 18 | // 待付款订单 19 | status.push(101); 20 | } else if (showType == 2) { 21 | // 待发货订单 22 | status.push(300); 23 | } else if (showType == 3) { 24 | // 待收货订单 25 | status.push(301); 26 | } else if (showType == 4) { 27 | // 待评价订单 28 | status.push(302, 303); 29 | } else { 30 | return null; 31 | } 32 | return status; 33 | } 34 | /** 35 | * 获取订单可操作的选项 36 | * @param orderId 37 | */ 38 | async getOrderHandleOption(orderId) { 39 | const handleOption = { 40 | cancel: false, // 取消操作 41 | delete: false, // 删除操作 42 | pay: false, // 支付操作 43 | confirm: false, // 确认收货完成订单操作 44 | cancel_refund: false 45 | }; 46 | const orderInfo = await this.where({ 47 | id: orderId 48 | }).find(); 49 | // 订单流程:下单成功-》支付订单-》发货-》收货-》评论 50 | // 订单相关状态字段设计,采用单个字段表示全部的订单状态 51 | // 1xx表示订单取消和删除等状态: 101订单创建成功等待付款、102订单已取消、103订单已取消(自动) 52 | // 2xx表示订单支付状态: 201订单已付款,等待发货、202订单取消,退款中、203已退款 53 | // 3xx表示订单物流相关状态: 300订单待发货,301订单已发货,302用户确认收货、303系统自动收货 54 | // 4xx表示订单完成的状态: 401已收货已评价 55 | // 5xx表示订单退换货相关的状态: 501已收货,退款退货 TODO 56 | // 如果订单已经取消或是已完成,则可删除和再次购买 57 | // if (status == 101) "未付款"; 58 | // if (status == 102) "已取消"; 59 | // if (status == 103) "已取消(系统)"; 60 | // if (status == 201) "已付款"; 61 | // if (status == 300) "待发货"; 62 | // if (status == 301) "已发货"; 63 | // if (status == 302) "已收货"; 64 | // if (status == 303) "已收货(系统)"; 65 | // TODO 设置一个定时器,自动将有些订单设为完成 66 | // 订单刚创建,可以取消订单,可以继续支付 67 | if (orderInfo.order_status === 101 || orderInfo.order_status === 801) { 68 | handleOption.cancel = true; 69 | handleOption.pay = true; 70 | } 71 | // 如果订单被取消 72 | if (orderInfo.order_status === 102 || orderInfo.order_status === 103) { 73 | handleOption.delete = true; 74 | } 75 | // 如果订单已付款,没有发货,则可退款操作 76 | if (orderInfo.order_status === 201) { 77 | // handleOption.return = true; 78 | } 79 | // 如果订单申请退款中,没有相关操作 80 | if (orderInfo.order_status === 202) { 81 | handleOption.cancel_refund = true; 82 | } 83 | if (orderInfo.order_status === 300) {} 84 | // 如果订单已经退款,则可删除 85 | if (orderInfo.order_status === 203) { 86 | handleOption.delete = true; 87 | } 88 | // 如果订单已经发货,没有收货,则可收货操作, 89 | // 此时不能取消订单 90 | if (orderInfo.order_status === 301) { 91 | handleOption.confirm = true; 92 | } 93 | if (orderInfo.order_status === 401) { 94 | handleOption.delete = true; 95 | } 96 | return handleOption; 97 | } 98 | async getOrderTextCode(orderId) { 99 | const textCode = { 100 | pay: false, 101 | close: false, 102 | delivery: false, 103 | receive: false, 104 | success: false, 105 | countdown: false, 106 | }; 107 | const orderInfo = await this.where({ 108 | id: orderId 109 | }).find(); 110 | if (orderInfo.order_status === 101) { 111 | textCode.pay = true; 112 | textCode.countdown = true; 113 | } 114 | if (orderInfo.order_status === 102 || orderInfo.order_status === 103) { 115 | textCode.close = true; 116 | } 117 | if (orderInfo.order_status === 201 || orderInfo.order_status === 300) { //待发货 118 | textCode.delivery = true; 119 | } 120 | if (orderInfo.order_status === 301) { //已发货 121 | textCode.receive = true; 122 | } 123 | if (orderInfo.order_status === 401) { 124 | textCode.success = true; 125 | } 126 | return textCode; 127 | } 128 | // if (status == 101) "未付款"; 129 | // if (status == 102) "已取消"; 130 | // if (status == 103) "已取消(系统)"; 131 | // if (status == 201) "已付款"; 132 | // if (status == 301) "已发货"; 133 | // if (status == 302) "已收货"; 134 | // if (status == 303) "已收货(系统)"; 135 | // if (status == 401) "已完成"; 136 | async getOrderStatusText(orderId) { 137 | const orderInfo = await this.where({ 138 | id: orderId 139 | }).find(); 140 | let statusText = '待付款'; 141 | switch (orderInfo.order_status) { 142 | case 101: 143 | statusText = '待付款'; 144 | break; 145 | case 102: 146 | statusText = '交易关闭'; 147 | break; 148 | case 103: 149 | statusText = '交易关闭'; //到时间系统自动取消 150 | break; 151 | case 201: 152 | statusText = '待发货'; 153 | break; 154 | case 300: 155 | statusText = '待发货'; 156 | break; 157 | case 301: 158 | statusText = '已发货'; 159 | break; 160 | case 401: 161 | statusText = '交易成功'; //到时间,未收货的系统自动收货、 162 | break; 163 | } 164 | return statusText; 165 | } 166 | // 返回创建时间 167 | async getOrderAddTime(orderId) { 168 | const orderInfo = await this.where({ 169 | id: orderId 170 | }).find(); 171 | let add_time = orderInfo.add_time; 172 | return add_time; 173 | } 174 | // 支付时间 175 | async setOrderPayTime(orderId, payTime) { 176 | const orderInfo = await this.where({ 177 | id: orderId 178 | }).update({ 179 | pay_time, 180 | payTime 181 | }); 182 | return orderInfo; 183 | } 184 | // 删除订单,将is_delete置为0 185 | async orderDeleteById(orderId) { 186 | return this.where({ 187 | id: orderId 188 | }).update({ 189 | is_delete: 1 190 | }); 191 | } 192 | /** 193 | * check订单状态 194 | * @param orderId 195 | * @param payStatus 196 | * @returns {Promise.} 197 | */ 198 | async checkPayStatus(orderId) { 199 | let info = await this.where({ 200 | id: orderId, 201 | pay_status: 2 202 | }).select(); 203 | let _length = info.length; 204 | if (_length > 0) { 205 | return false; 206 | } else { 207 | return true; 208 | } 209 | } 210 | /** 211 | * 更改订单支付状态 212 | * @param orderId 213 | * @param payStatus 214 | * @returns {Promise.} 215 | */ 216 | async updatePayStatus(orderId, payStatus = 0) { 217 | return this.where({ 218 | id: orderId 219 | }).limit(1).update({ 220 | pay_status: parseInt(payStatus) 221 | }); 222 | } 223 | /** 224 | * 更改订单状态 225 | * @param orderId 226 | * @param order_Status 227 | * @returns {Promise.} 228 | */ 229 | async updateOrderStatus(orderId, orderStatus = 0) { 230 | return this.where({ 231 | id: orderId 232 | }).limit(1).update({ 233 | order_status: parseInt(orderStatus) 234 | }); 235 | } 236 | /** 237 | * 根据订单编号查找订单信息 238 | * @param orderSn 239 | * @returns {Promise.|T|*>} 240 | */ 241 | async getOrderByOrderSn(orderSn) { 242 | if (think.isEmpty(orderSn)) { 243 | return {}; 244 | } 245 | return this.where({ 246 | order_sn: orderSn 247 | }).find(); 248 | } 249 | /** 250 | * 更新订单状态,包括更新库存数据,现在可以开始写了 251 | * @param orderId 252 | * @param 253 | * @returns {Promise.} 254 | */ 255 | async updatePayData(orderId, info) { 256 | let data = { 257 | pay_status: 2, 258 | order_status: 300, 259 | pay_id: info.transaction_id, 260 | pay_time: info.time_end 261 | } 262 | return this.where({ 263 | id: orderId 264 | }).limit(1).update(data); 265 | } 266 | }; -------------------------------------------------------------------------------- /src/api/model/order_express.js: -------------------------------------------------------------------------------- 1 | module.exports = class extends think.Model { 2 | get tableName() { 3 | return this.tablePrefix + 'order_express'; 4 | } 5 | /** 6 | * 获取最新的订单物流信息 7 | * @param orderId 8 | * @returns {Promise.<*>} 9 | */ 10 | async getLatestOrderExpress(orderId) { 11 | const returnExpressInfo = { 12 | shipper_code: '', 13 | shipper_name: '', 14 | logistic_code: '', 15 | is_finish: 0, 16 | request_time: 0, 17 | traces: [] 18 | }; 19 | const orderExpress = await this.where({ 20 | order_id: orderId 21 | }).find(); // 根据orderid得到order_express的info 22 | if (think.isEmpty(orderExpress)) { // 如果是空的,说明还没发货 23 | return returnExpressInfo; 24 | } 25 | if (think.isEmpty(orderExpress.shipper_code) || think.isEmpty(orderExpress.logistic_code)) { 26 | return returnExpressInfo; // 如果是空的,说明还没发货 27 | } 28 | // 如果不空,则将里面的登录的快递号等信息复制给returnExpressInfo 29 | returnExpressInfo.shipper_code = orderExpress.shipper_code; 30 | returnExpressInfo.shipper_name = orderExpress.shipper_name; 31 | returnExpressInfo.logistic_code = orderExpress.logistic_code; 32 | returnExpressInfo.is_finish = orderExpress.is_finish; 33 | returnExpressInfo.request_time = think.datetime(orderExpress.request_time * 1000); 34 | returnExpressInfo.traces = think.isEmpty(orderExpress.traces) ? [] : JSON.parse(orderExpress.traces); 35 | // 如果物流配送已完成,直接返回 36 | if (orderExpress.is_finish) { 37 | return returnExpressInfo; 38 | } 39 | // 查询最新物流信息 40 | const ExpressSerivce = think.service('express', 'api'); // 引入api 41 | // 调用api里的queryExpress方法 最重要 42 | const latestExpressInfo = await ExpressSerivce.queryExpress(orderExpress.shipper_code, orderExpress.logistic_code); 43 | const nowTime = Number.parseInt(Date.now() / 1000); 44 | const updateData = { 45 | request_time: nowTime, 46 | update_time: nowTime, 47 | request_count: ['EXP', 'request_count+1'] 48 | }; 49 | if (latestExpressInfo.success) { 50 | returnExpressInfo.traces = latestExpressInfo.traces; 51 | returnExpressInfo.is_finish = latestExpressInfo.isFinish; 52 | // 查询成功则更新订单物流信息 53 | updateData.traces = JSON.stringify(latestExpressInfo.traces); 54 | returnExpressInfo.request_time = think.datetime(nowTime * 1000); 55 | updateData.is_finish = latestExpressInfo.isFinish; 56 | } 57 | await this.where({ 58 | id: orderExpress.id 59 | }).update(updateData); 60 | return returnExpressInfo; 61 | } 62 | }; -------------------------------------------------------------------------------- /src/api/model/region.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | module.exports = class extends think.Model { 3 | /** 4 | * 获取完整的省市区名称组成的字符串 5 | * @param provinceId 6 | * @param cityId 7 | * @param districtId 8 | * @returns {Promise.<*>} 9 | */ 10 | async getFullRegionName(provinceId, cityId, districtId) { 11 | const isFullRegion = await this.checkFullRegion(provinceId, cityId, districtId); 12 | if (!isFullRegion) { 13 | return ''; 14 | } 15 | const regionList = await this.limit(3).order({ 16 | 'id': 'asc' 17 | }).where({ 18 | id: { 19 | 'in': [provinceId, cityId, districtId] 20 | } 21 | }).select(); 22 | if (think.isEmpty(regionList) || regionList.length !== 3) { 23 | return ''; 24 | } 25 | return _.flatMap(regionList, 'name').join(''); 26 | } 27 | /** 28 | * 检查省市区信息是否完整和正确 29 | * @param provinceId 30 | * @param cityId 31 | * @param districtId 32 | * @returns {Promise.} 33 | */ 34 | async checkFullRegion(provinceId, cityId, districtId) { 35 | if (think.isEmpty(provinceId) || think.isEmpty(cityId) || think.isEmpty(districtId)) { 36 | return false; 37 | } 38 | const regionList = await this.limit(3).order({ 39 | 'id': 'asc' 40 | }).where({ 41 | id: { 42 | 'in': [provinceId, cityId, districtId] 43 | } 44 | }).select(); 45 | if (think.isEmpty(regionList) || regionList.length !== 3) { 46 | return false; 47 | } 48 | // 上下级关系检查 49 | if (_.get(regionList, ['0', 'id']) !== _.get(regionList, ['1', 'parent_id'])) { 50 | return false; 51 | } 52 | if (_.get(regionList, ['1', 'id']) !== _.get(regionList, ['2', 'parent_id'])) { 53 | return false; 54 | } 55 | return true; 56 | } 57 | /** 58 | * 获取区域的名称 59 | * @param regionId 60 | * @returns {Promise.<*>} 61 | */ 62 | async getRegionName(regionId) { 63 | return this.where({ 64 | id: regionId 65 | }).getField('name', true); 66 | } 67 | /** 68 | * 获取下级的地区列表 69 | * @param parentId 70 | * @returns {Promise.<*>} 71 | */ 72 | async getRegionList(parentId) { 73 | return this.where({ 74 | parent_id: parentId 75 | }).select(); 76 | } 77 | /** 78 | * 获取区域的信息 79 | * @param regionId 80 | * @returns {Promise.<*>} 81 | */ 82 | async getRegionInfo(regionId) { 83 | return this.where({ 84 | id: regionId 85 | }).find(); 86 | } 87 | }; -------------------------------------------------------------------------------- /src/api/model/shipper.js: -------------------------------------------------------------------------------- 1 | module.exports = class extends think.Model { 2 | /** 3 | * 根据快递公司编码获取名称 4 | * @param shipperCode 5 | * @returns {Promise.<*>} 6 | */ 7 | async getShipperNameByCode(shipperCode) { 8 | return this.where({ 9 | code: shipperCode 10 | }).getField('name', true); 11 | } 12 | /** 13 | * 根据 id 获取快递公司信息 14 | * @param shipperId 15 | * @returns {Promise.<*>} 16 | */ 17 | async getShipperById(shipperId) { 18 | return this.where({ 19 | id: shipperId 20 | }).find(); 21 | } 22 | }; -------------------------------------------------------------------------------- /src/api/service/express.js: -------------------------------------------------------------------------------- 1 | const rp = require('request-promise'); 2 | const _ = require('lodash'); 3 | module.exports = class extends think.Service { 4 | async queryExpress(shipperCode, logisticCode, orderCode = '') { 5 | // 最终得到的数据,初始化 6 | let expressInfo = { 7 | success: false, 8 | shipperCode: shipperCode, 9 | shipperName: '', 10 | logisticCode: logisticCode, 11 | isFinish: 0, 12 | traces: [] 13 | }; 14 | // 要post的数据,进行编码,签名 15 | const fromData = this.generateFromData(shipperCode, logisticCode, orderCode); 16 | if (think.isEmpty(fromData)) { 17 | return expressInfo; 18 | } 19 | // post的参数 20 | const sendOptions = { 21 | method: 'POST', 22 | url: think.config('express.request_url'), 23 | headers: { 24 | 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' 25 | }, 26 | form: fromData 27 | }; 28 | // post请求 29 | try { 30 | const requestResult = await rp(sendOptions); 31 | if (think.isEmpty(requestResult)) { 32 | return expressInfo; 33 | } 34 | expressInfo = this.parseExpressResult(requestResult); 35 | expressInfo.shipperCode = shipperCode; 36 | expressInfo.logisticCode = logisticCode; 37 | return expressInfo; 38 | } catch (err) { 39 | return expressInfo; 40 | } 41 | } 42 | // 快递物流信息请求系统级参数 要post的数据,进行编码,签名 43 | generateFromData(shipperCode, logisticCode, orderCode) { 44 | const requestData = this.generateRequestData(shipperCode, logisticCode, orderCode); 45 | const fromData = { 46 | RequestData: encodeURI(requestData), // 把字符串作为 URI 进行编码 47 | EBusinessID: think.config('express.appid'), // 客户号 48 | RequestType: '1002', // 请求代码 49 | DataSign: this.generateDataSign(requestData), // 签名 50 | DataType: '2' //数据类型:2 51 | }; 52 | return fromData; 53 | } 54 | // JavaScript 值转换为 JSON 字符串。 55 | generateRequestData(shipperCode, logisticCode, orderCode = '') { 56 | // 参数验证 57 | const requestData = { 58 | OrderCode: orderCode, 59 | ShipperCode: shipperCode, 60 | LogisticCode: logisticCode 61 | }; 62 | return JSON.stringify(requestData); 63 | } 64 | // 编码加密 65 | generateDataSign(requestData) { 66 | return encodeURI(Buffer.from(think.md5(requestData + think.config('express.appkey'))).toString('base64')); 67 | } 68 | parseExpressResult(requestResult) { 69 | const expressInfo = { 70 | success: false, 71 | shipperCode: '', 72 | shipperName: '', 73 | logisticCode: '', 74 | isFinish: 0, 75 | traces: [] 76 | }; 77 | if (think.isEmpty(requestResult)) { 78 | return expressInfo; 79 | } 80 | try { 81 | if (_.isString(requestResult)) { 82 | requestResult = JSON.parse(requestResult); // 将一个 JSON 字符串转换为对象。 83 | } 84 | } catch (err) { 85 | return expressInfo; 86 | } 87 | if (think.isEmpty(requestResult.Success)) { 88 | return expressInfo; 89 | } 90 | // 判断是否已签收 91 | if (Number.parseInt(requestResult.State) === 3) { 92 | expressInfo.isFinish = 1; 93 | } 94 | expressInfo.success = true; 95 | if (!think.isEmpty(requestResult.Traces) && Array.isArray(requestResult.Traces)) { 96 | expressInfo.traces = _.map(requestResult.Traces, item => { 97 | return { 98 | datetime: item.AcceptTime, 99 | content: item.AcceptStation 100 | }; 101 | }); 102 | _.reverse(expressInfo.traces); 103 | } 104 | return expressInfo; 105 | } 106 | // 电子面单开始 107 | async mianExpress(data = {}) { 108 | // 从前台传过来的数据 109 | let expressInfo = data; 110 | // 进行编码,签名 111 | const fromData = this.mianFromData(data); 112 | if (think.isEmpty(fromData)) { 113 | return expressInfo; 114 | } 115 | // 请求的参数设置 116 | const sendOptions = { 117 | method: 'POST', 118 | url: think.config('mianexpress.request_url'), 119 | headers: { 120 | 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' 121 | }, 122 | form: fromData 123 | }; 124 | // post请求 125 | try { 126 | const requestResult = await rp(sendOptions); 127 | if (think.isEmpty(requestResult)) { 128 | return expressInfo; 129 | } 130 | expressInfo = this.parseMianExpressResult(requestResult); 131 | let htmldata = expressInfo.PrintTemplate; 132 | let html = htmldata.toString(); 133 | return expressInfo; 134 | } catch (err) { 135 | return expressInfo; 136 | } 137 | } 138 | // 电子面单信息请求系统级参数 要post的数据 进行编码,签名 139 | mianFromData(data) { 140 | const requestData = JSON.stringify(data); // data:post进来的 // JavaScript 值转换为 JSON 字符串。 141 | const fromData = { 142 | RequestData: encodeURI(requestData), 143 | EBusinessID: think.config('mianexpress.appid'), 144 | RequestType: '1007', 145 | DataSign: this.mianDataSign(requestData), 146 | DataType: '2' 147 | }; 148 | // console.log('fromdata======'); 149 | return fromData; 150 | } 151 | // 加密签名 152 | mianDataSign(requestData) { 153 | return encodeURI(Buffer.from(think.md5(requestData + think.config('mianexpress.appkey'))).toString('base64')); 154 | } 155 | // 返回数据 156 | parseMianExpressResult(requestResult) { 157 | const expressInfo = { 158 | success: false, 159 | }; 160 | if (think.isEmpty(requestResult)) { 161 | return expressInfo; 162 | } 163 | try { 164 | if (_.isString(requestResult)) { 165 | requestResult = JSON.parse(requestResult); 166 | } 167 | return requestResult; 168 | } catch (err) { 169 | return expressInfo; 170 | } 171 | return expressInfo; 172 | } 173 | // 电子面单结束 174 | // 批量打印开始 175 | // build_form(); 176 | /** 177 | * 组装POST表单用于调用快递鸟批量打印接口页面 178 | */ 179 | async buildForm(data = {}) { 180 | let requestData = data; 181 | requestData = '[{"OrderCode":"234351215333113311353","PortName":"打印机名称一"}]'; 182 | //OrderCode:需要打印的订单号,和调用快递鸟电子面单的订单号一致,PortName:本地打印机名称,请参考使用手册设置打印机名称。支持多打印机同时打印。 183 | // $request_data = '[{"OrderCode":"234351215333113311353","PortName":"打印机名称一"},{"OrderCode":"234351215333113311354","PortName":"打印机名称二"}]'; 184 | let requestDataEncode = encodeURI(requestData); 185 | let APIKey = think.config('mianexpress.appkey'); 186 | let API_URL = think.config('mianexpress.print_url'); 187 | let dataSign = this.printDataSign(this.get_ip(), requestDataEncode); 188 | //是否预览,0-不预览 1-预览 189 | let is_priview = '0'; 190 | let EBusinessID = think.config('mianexpress.appid'); 191 | //组装表单 192 | let form = '
'; 193 | console.log(form); 194 | return form; 195 | } 196 | // 加密签名 197 | printDataSign(ip, requestData) { 198 | return encodeURI(Buffer.from(ip + think.md5(requestData + think.config('mianexpress.appkey'))).toString('base64')); 199 | } 200 | /** 201 | * 判断是否为内网IP 202 | * @param ip IP 203 | * @return 是否内网IP 204 | */ 205 | // function is_private_ip($ip) { 206 | // return !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE); 207 | // } 208 | /** 209 | * 获取客户端IP(非用户服务器IP) 210 | * @return 客户端IP 211 | */ 212 | async get_ip() { 213 | const sendOptions = { 214 | method: 'GET', 215 | url: think.config('mianexpress.ip_server_url'), 216 | headers: { 217 | 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' 218 | } 219 | }; 220 | // post请求 221 | try { 222 | const requestResult = await rp(sendOptions); 223 | if (think.isEmpty(requestResult)) { 224 | let i = 0 225 | return i; 226 | } 227 | var ip = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/; 228 | var text = ip.exec(requestResult); 229 | console.log(text[0]); 230 | return text[0]; 231 | } catch (err) { 232 | return 0; 233 | } 234 | } 235 | // 批量打印结束 236 | }; -------------------------------------------------------------------------------- /src/api/service/qiniu.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | module.exports = class extends think.Service { 3 | async getQiniuToken() { 4 | let accessKey = think.config('qiniu.access_key'); 5 | let secretKey = think.config('qiniu.secret_key'); 6 | let bucket = think.config('qiniu.bucket'); 7 | let domain = think.config('qiniu.domain'); 8 | let mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 9 | let currentTime = parseInt(new Date().getTime() / 1000) + 600; 10 | let key = think.uuid(32); 11 | let options = { 12 | scope:bucket, 13 | deadline:currentTime, 14 | saveKey:key 15 | }; 16 | let putPolicy = new qiniu.rs.PutPolicy(options); 17 | let uploadToken=putPolicy.uploadToken(mac); 18 | let data = { 19 | uploadToken:uploadToken, 20 | domain:domain 21 | }; 22 | return data; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/api/service/token.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const secret = 'sdfsdfsdf123123!ASDasdasdasdasda'; 3 | module.exports = class extends think.Service { 4 | /** 5 | * 根据header中的x-hioshop-token值获取用户id 6 | */ 7 | getUserId(token) { 8 | if (!token) { 9 | return 0; 10 | } 11 | const result = this.parse(token); 12 | if (think.isEmpty(result) || result.user_id <= 0) { 13 | return 0; 14 | } 15 | return result.user_id; 16 | } 17 | parse(token) { 18 | if (token) { 19 | try { 20 | return jwt.verify(token, secret); 21 | } catch (err) { 22 | return null; 23 | } 24 | } 25 | return null; 26 | } 27 | async create(userInfo) { 28 | const token = jwt.sign(userInfo, secret); 29 | return token; 30 | } 31 | /** 32 | * 根据值获取用户信息 33 | */ 34 | async getUserInfo() { 35 | const userId = await this.getUserId(); 36 | if (userId <= 0) { 37 | return null; 38 | } 39 | const userInfo = await this.model('user').field(['id', 'username', 'nickname', 'gender', 'avatar', 'birthday']).where({ 40 | id: userId 41 | }).find(); 42 | return think.isEmpty(userInfo) ? null : userInfo; 43 | } 44 | async verify() { 45 | const result = await this.parse(); 46 | if (think.isEmpty(result)) { 47 | return false; 48 | } 49 | return true; 50 | } 51 | }; -------------------------------------------------------------------------------- /src/api/service/weixin.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const md5 = require('md5'); 3 | const moment = require('moment'); 4 | const rp = require('request-promise'); 5 | const fs = require('fs'); 6 | const http = require("http"); 7 | module.exports = class extends think.Service { 8 | /** 9 | * 解析微信登录用户数据 10 | * @param sessionKey 11 | * @param encryptedData 12 | * @param iv 13 | * @returns {Promise.} 14 | */ 15 | async decryptUserInfoData(sessionKey, encryptedData, iv) { 16 | // base64 decode 17 | const _sessionKey = Buffer.from(sessionKey, 'base64'); 18 | encryptedData = Buffer.from(encryptedData, 'base64'); 19 | iv = Buffer.from(iv, 'base64'); 20 | let decoded = ''; 21 | try { 22 | // 解密 23 | const decipher = crypto.createDecipheriv('aes-128-cbc', _sessionKey, iv); 24 | // 设置自动 padding 为 true,删除填充补位 25 | decipher.setAutoPadding(true); 26 | decoded = decipher.update(encryptedData, 'binary', 'utf8'); 27 | decoded += decipher.final('utf8'); 28 | decoded = JSON.parse(decoded); 29 | } catch (err) { 30 | return ''; 31 | } 32 | if (decoded.watermark.appid !== think.config('weixin.appid')) { 33 | return ''; 34 | } 35 | return decoded; 36 | } 37 | /** 38 | * 统一下单 39 | * @param payInfo 40 | * @returns {Promise} 41 | */ 42 | async createUnifiedOrder(payInfo) { 43 | const WeiXinPay = require('weixinpay'); 44 | const weixinpay = new WeiXinPay({ 45 | appid: think.config('weixin.appid'), // 微信小程序appid 46 | openid: payInfo.openid, // 用户openid 47 | mch_id: think.config('weixin.mch_id'), // 商户帐号ID 48 | partner_key: think.config('weixin.partner_key') // 秘钥 49 | }); 50 | return new Promise((resolve, reject) => { 51 | // let total_fee = this.getTotalFee(payInfo.out_trade_no); 52 | weixinpay.createUnifiedOrder({ 53 | body: payInfo.body, 54 | out_trade_no: payInfo.out_trade_no, 55 | total_fee: payInfo.total_fee, 56 | // total_fee: total_fee, 57 | spbill_create_ip: payInfo.spbill_create_ip, 58 | notify_url: think.config('weixin.notify_url'), 59 | trade_type: 'JSAPI' 60 | }, (res) => { 61 | console.log(res); 62 | if (res.return_code === 'SUCCESS' && res.result_code === 'SUCCESS') { 63 | const returnParams = { 64 | 'appid': res.appid, 65 | 'timeStamp': parseInt(Date.now() / 1000) + '', 66 | 'nonceStr': res.nonce_str, 67 | 'package': 'prepay_id=' + res.prepay_id, 68 | 'signType': 'MD5' 69 | }; 70 | const paramStr = `appId=${returnParams.appid}&nonceStr=${returnParams.nonceStr}&package=${returnParams.package}&signType=${returnParams.signType}&timeStamp=${returnParams.timeStamp}&key=` + think.config('weixin.partner_key'); 71 | returnParams.paySign = md5(paramStr).toUpperCase(); 72 | let order_sn = payInfo.out_trade_no; 73 | resolve(returnParams); 74 | } else { 75 | reject(res); 76 | } 77 | }); 78 | }); 79 | } 80 | async getTotalFee(sn) { 81 | let total_fee = await this.model('order').where({ 82 | order_sn: sn 83 | }).field('actual_price').find(); 84 | let res = total_fee.actual_price; 85 | return res; 86 | } 87 | /** 88 | * 生成排序后的支付参数 query 89 | * @param queryObj 90 | * @returns {Promise.} 91 | */ 92 | buildQuery(queryObj) { 93 | const sortPayOptions = {}; 94 | for (const key of Object.keys(queryObj).sort()) { 95 | sortPayOptions[key] = queryObj[key]; 96 | } 97 | let payOptionQuery = ''; 98 | for (const key of Object.keys(sortPayOptions).sort()) { 99 | payOptionQuery += key + '=' + sortPayOptions[key] + '&'; 100 | } 101 | payOptionQuery = payOptionQuery.substring(0, payOptionQuery.length - 1); 102 | return payOptionQuery; 103 | } 104 | /** 105 | * 对 query 进行签名 106 | * @param queryStr 107 | * @returns {Promise.} 108 | */ 109 | signQuery(queryStr) { 110 | queryStr = queryStr + '&key=' + think.config('weixin.partner_key'); 111 | const md5 = require('md5'); 112 | const md5Sign = md5(queryStr); 113 | return md5Sign.toUpperCase(); 114 | } 115 | /** 116 | * 处理微信支付回调 117 | * @param notifyData 118 | * @returns {{}} 119 | */ 120 | payNotify(notifyData) { 121 | if (think.isEmpty(notifyData)) { 122 | return false; 123 | } 124 | const notifyObj = {}; 125 | let sign = ''; 126 | for (const key of Object.keys(notifyData)) { 127 | if (key !== 'sign') { 128 | notifyObj[key] = notifyData[key][0]; 129 | } else { 130 | sign = notifyData[key][0]; 131 | } 132 | } 133 | if (notifyObj.return_code !== 'SUCCESS' || notifyObj.result_code !== 'SUCCESS') { 134 | return false; 135 | } 136 | const signString = this.signQuery(this.buildQuery(notifyObj)); 137 | if (think.isEmpty(sign) || signString !== sign) { 138 | return false; 139 | } 140 | let timeInfo = notifyObj.time_end; 141 | let pay_time = moment(timeInfo, 'YYYYMMDDHHmmss'); 142 | notifyObj.time_end = new Date(Date.parse(pay_time)).getTime() / 1000 143 | return notifyObj; 144 | } 145 | /** 146 | * 申请退款 147 | * @param refundInfo 148 | * @returns {Promise} 149 | */ 150 | createRefund(payInfo) { 151 | const WeiXinPay = require('weixinpay'); 152 | const weixinpay = new WeiXinPay({ 153 | appid: think.config('weixin.appid'), // 微信小程序appid 154 | openid: payInfo.openid, // 用户openid 155 | mch_id: think.config('weixin.mch_id'), // 商户帐号ID 156 | partner_key: think.config('weixin.partner_key') // 秘钥 157 | }); 158 | return new Promise((resolve, reject) => { 159 | weixinpay.createUnifiedOrder({ 160 | body: payInfo.body, 161 | out_trade_no: payInfo.out_trade_no, 162 | total_fee: payInfo.total_fee, 163 | spbill_create_ip: payInfo.spbill_create_ip, 164 | notify_url: think.config('weixin.notify_url'), 165 | trade_type: 'JSAPI' 166 | }, (res) => { 167 | if (res.return_code === 'SUCCESS' && res.result_code === 'SUCCESS') { 168 | const returnParams = { 169 | 'appid': res.appid, 170 | 'timeStamp': parseInt(Date.now() / 1000) + '', 171 | 'nonceStr': res.nonce_str, 172 | 'package': 'prepay_id=' + res.prepay_id, 173 | 'signType': 'MD5' 174 | }; 175 | const paramStr = `appId=${returnParams.appid}&nonceStr=${returnParams.nonceStr}&package=${returnParams.package}&signType=${returnParams.signType}&timeStamp=${returnParams.timeStamp}&key=` + think.config('weixin.partner_key'); 176 | returnParams.paySign = md5(paramStr).toUpperCase(); 177 | resolve(returnParams); 178 | } else { 179 | reject(res); 180 | } 181 | }); 182 | }); 183 | } 184 | async getAccessToken() { 185 | const options = { 186 | method: 'POST', 187 | // url: 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=', 188 | url: 'https://api.weixin.qq.com/cgi-bin/token', 189 | qs: { 190 | grant_type: 'client_credential', 191 | secret: think.config('weixin.secret'), 192 | appid: think.config('weixin.appid') 193 | } 194 | }; 195 | let sessionData = await rp(options); 196 | sessionData = JSON.parse(sessionData); 197 | let token = sessionData.access_token; 198 | return token; 199 | } 200 | async getSelfToken(params) { 201 | var key = ['meiweiyuxianmeiweiyuxian', params.timestamp, params.nonce].sort().join(''); 202 | //将token (自己设置的) 、timestamp(时间戳)、nonce(随机数)三个参数进行字典排序 203 | var sha1 = crypto.createHash('sha1'); 204 | //将上面三个字符串拼接成一个字符串再进行sha1加密 205 | sha1.update(key); 206 | //将加密后的字符串与signature进行对比,若成功,返回success。如果通过验证,则,注释掉这个函数 207 | let a = sha1.digest('hex'); 208 | let b = params.signature; 209 | if (a == b) { 210 | return true; 211 | } 212 | } 213 | async sendMessage(token, data) { 214 | const sendInfo = { 215 | method: 'POST', 216 | url: 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=' + token, 217 | body: data, 218 | json: true 219 | }; 220 | let posting = await rp(sendInfo); 221 | console.log(posting); 222 | return posting; 223 | } 224 | async getMessageATempId(type) { 225 | switch (type) { 226 | case 1: 227 | return 'TXWzXjO4C0odXCwQk4idgBtGcgSKBEWXJETYBZcRAzE'; 228 | break; 229 | // 支付成功 230 | case 2: 231 | return 'COiQGBTzTtz_us5qYeJf0K-pFAyubBuWQh40sV1eAuw'; 232 | break; 233 | // 发货通知 234 | default: 235 | return '400'; 236 | } 237 | } 238 | async getMessageTempId(type) { 239 | switch (type) { 240 | case 1: 241 | return 'TXWzXjO4C0odXCwQk4idgBtGcgSKBEWXJETYBZcRAzE'; 242 | break; 243 | // 支付成功 244 | case 2: 245 | return 'COiQGBTzTtz_us5qYeJf0K-pFAyubBuWQh40sV1eAuw'; 246 | break; 247 | // 发货通知 248 | default: 249 | return '400'; 250 | } 251 | } 252 | async sendTextMessage(data, access_token) { 253 | const sendInfo = { 254 | method: 'POST', 255 | url: 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=' + access_token, 256 | body: { 257 | touser: data.FromUserName, 258 | msgtype: "text", 259 | text: { 260 | content: data.Content 261 | } 262 | }, 263 | json: true 264 | }; 265 | let posting = await rp(sendInfo); 266 | return posting; 267 | } 268 | async sendImageMessage(media_id, data, access_token) { 269 | const sendInfo = { 270 | method: 'POST', 271 | url: 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=' + access_token, 272 | body: { 273 | touser: data.FromUserName, 274 | msgtype: "image", 275 | image: { 276 | media_id: media_id 277 | } 278 | }, 279 | json: true 280 | }; 281 | let posting = await rp(sendInfo); 282 | return posting; 283 | } 284 | }; -------------------------------------------------------------------------------- /src/common/config/adapter.js: -------------------------------------------------------------------------------- 1 | const fileCache = require('think-cache-file'); 2 | const { 3 | Console, 4 | File, 5 | DateFile 6 | } = require('think-logger3'); 7 | const path = require('path'); 8 | const database = require('./database.js'); 9 | const nunjucks = require('think-view-nunjucks'); 10 | const isDev = think.env === 'development'; 11 | /** 12 | * cache adapter config 13 | * @type {Object} 14 | */ 15 | exports.cache = { 16 | type: 'file', 17 | common: { 18 | timeout: 24 * 60 * 60 * 1000 // millisecond 19 | }, 20 | file: { 21 | handle: fileCache, 22 | cachePath: path.join(think.ROOT_PATH, 'runtime/cache'), // absoulte path is necessarily required 23 | pathDepth: 1, 24 | gcInterval: 24 * 60 * 60 * 1000 // gc interval 25 | } 26 | }; 27 | /** 28 | * model adapter config 29 | * @type {Object} 30 | */ 31 | exports.model = { 32 | type: 'mysql', 33 | common: { 34 | logConnect: isDev, 35 | logSql: isDev, 36 | logger: msg => think.logger.info(msg) 37 | }, 38 | mysql: database 39 | }; 40 | /** 41 | * logger adapter config 42 | * @type {Object} 43 | */ 44 | exports.logger = { 45 | type: isDev ? 'console' : 'dateFile', 46 | console: { 47 | handle: Console 48 | }, 49 | file: { 50 | handle: File, 51 | backups: 10, // max chunk number 52 | absolute: true, 53 | maxLogSize: 50 * 1024, // 50M 54 | filename: path.join(think.ROOT_PATH, 'logs/app.log') 55 | }, 56 | dateFile: { 57 | handle: DateFile, 58 | level: 'ALL', 59 | absolute: true, 60 | pattern: '-yyyy-MM-dd', 61 | alwaysIncludePattern: true, 62 | filename: path.join(think.ROOT_PATH, 'logs/app.log') 63 | } 64 | }; 65 | exports.view = { 66 | type: 'nunjucks', // 这里指定默认的模板引擎是 nunjucks 67 | common: { 68 | viewPath: path.join(think.ROOT_PATH, 'view'), //模板文件的根目录 69 | sep: '_', //Controller 与 Action 之间的连接符 70 | extname: '.html' //模板文件扩展名 71 | }, 72 | nunjucks: { 73 | handle: nunjucks, 74 | beforeRender: () => {}, // 模板渲染预处理 75 | options: { // 模板引擎额外的配置参数 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/common/config/config.js: -------------------------------------------------------------------------------- 1 | // default config 2 | module.exports = { 3 | default_module: 'api', 4 | port: 8360, //服务端口,可自定义 5 | weixin: { 6 | appid: 'wx7af111110000000', // 小程序 appid 7 | secret: 'cb8e5adce569f9bddce5b8123123e1115aaddce505', // 小程序密钥 8 | mch_id: '15988888888', // 商户帐号ID 9 | partner_key: 'asdasdasdasdasdasdasd', // 微信支付密钥 10 | notify_url: 'https://www.您的域名.com/api/pay/notify' // 微信支付异步通知 11 | }, 12 | express: { 13 | // 已废弃,之后考虑改回来,做成和阿里云的物流查询可以切换,方便大家的使用 14 | // 免费的,但是顺丰的话,要配合快递鸟的电子面单 15 | // 快递物流信息查询使用的是快递鸟接口,申请地址:http://www.kdniao.com/ 16 | appid: '12312312', // 对应快递鸟用户后台 用户ID 17 | appkey: '123123123123123123123123', // 对应快递鸟用户后台 API key 18 | request_url: 'http://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx' 19 | }, 20 | mianexpress:{ 21 | appid: '123123', // 对应快递鸟用户后台 用户ID 22 | appkey: '123123-4e61236-94cb5297309a', // 对应快递鸟用户后台 API key 23 | request_url: 'http://testapi.kdniao.com:8081/api/EOrderService', 24 | print_url: 'http://sandboxapi.kdniao.com:8080/kdniaosandbox/gateway/exterfaceInvoke.json', 25 | ip_server_url:'http://www.kdniao.com/External/GetIp.aspx' 26 | }, 27 | qiniu: { 28 | access_key: 'asdlakjsdlajlajsdlas', // 在七牛密钥管理中获取 29 | secret_key: 'alskdjalksjdlasjdlajsd', // 在七牛密钥管理中获取 30 | bucket: 'bucketname', // 请填自己的bucket的名称 31 | domain: 'domain/' // 请填自己的domain域名 32 | }, 33 | // 在七牛新建一个https的空间,这个是用来存储分享图片的https图片,对应的是goods表中的https_pic_url 34 | qiniuHttps: { 35 | access_key: 'asdlakjsdlajlajsdlasasdla', // 在七牛密钥管理中获取 36 | secret_key: 'aaaaaaaaaaasdasdasdasd', // 在七牛密钥管理中获取 37 | bucket: 'bucketname', // 自己设置的 38 | domain: 'domain/', // 自己设置,例如:'http://img.你的域名.com/',别忘了这个”/“ 39 | // https://developer.qiniu.com/kodo/manual/1671/region-endpoint 40 | zoneNum: 0 // 这个自己根据地区设置:华东 0;华北 1;华南 2; 北美 3;东南亚 4 41 | }, 42 | aliexpress:{ 43 | // https://market.aliyun.com/products/56928004/cmapi021863.html?spm=5176.730005.productlist.d_cmapi021863.6ba73524uQjLqE&innerSource=search_%E5%85%A8%E5%9B%BD%E5%BF%AB%E9%80%92%E7%89%A9%E6%B5%81%E6%9F%A5%E8%AF%A2-%E5%BF%AB%E9%80%92%E6%9F%A5%E8%AF%A2%E6%8E%A5%E5%8F%A3#sku=yuncode1586300000 44 | url:'http://wuliu.market.alicloudapi.com/kdi', //阿里云的物流查询api,收费的 45 | appcode: 'asldjalsjdlasjdla' ,// 阿里云后台获取 46 | }, 47 | templateId:{ 48 | deliveryId:'w6AMCJ0nVWTsFasdasdgnlNlmCf9TTDmG6_U' // 模板id。在订阅消息里设置好后就可以得到 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /src/common/config/config.production.js: -------------------------------------------------------------------------------- 1 | // production config, it will load in production enviroment 2 | module.exports = { 3 | workers: 0 4 | }; 5 | -------------------------------------------------------------------------------- /src/common/config/crontab.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | module.exports = [{ 3 | interval: '60s', 4 | enable: true, 5 | immediate: true, 6 | handle: "crontab/timetask" 7 | }, 8 | { 9 | interval: '10s', 10 | enable: false, 11 | immediate: true, 12 | handle: "crontab/resetSql" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /src/common/config/database.js: -------------------------------------------------------------------------------- 1 | const mysql = require('think-model-mysql'); 2 | 3 | module.exports = { 4 | handle: mysql, 5 | database: 'hiolabsDB', 6 | prefix: 'hiolabs_', 7 | encoding: 'utf8mb4', 8 | host: '127.0.0.1', 9 | port: '3306', 10 | user: 'root', 11 | password: '123123123', 12 | dateStrings: true 13 | }; 14 | -------------------------------------------------------------------------------- /src/common/config/extend.js: -------------------------------------------------------------------------------- 1 | const model = require('think-model'); 2 | const cache = require('think-cache'); 3 | const view = require('think-view'); 4 | module.exports = [ 5 | model(think.app), 6 | cache, 7 | view 8 | ]; 9 | -------------------------------------------------------------------------------- /src/common/config/middleware.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const isDev = think.env === 'development'; 3 | const kcors = require('kcors'); 4 | module.exports = [{ 5 | handle: kcors, // 处理跨域 6 | options: {} 7 | }, { 8 | handle: 'meta', 9 | options: { 10 | logRequest: isDev, 11 | sendResponseTime: isDev 12 | } 13 | }, { 14 | handle: 'resource', 15 | // enable: isDev, 16 | enable: true, 17 | options: { 18 | root: path.join(think.ROOT_PATH, 'www'), 19 | publicPath: /^\/(static|favicon\.ico)/ 20 | } 21 | }, { 22 | handle: 'trace', 23 | enable: !think.isCli, 24 | options: { 25 | debug: isDev 26 | } 27 | }, { 28 | handle: 'payload', 29 | options: {} 30 | }, { 31 | handle: 'router', 32 | options: { 33 | defaultModule: 'api', 34 | defaultController: 'index', 35 | defaultAction: 'index' 36 | } 37 | }, 'logic', 'controller']; -------------------------------------------------------------------------------- /src/common/config/node-crontab.js: -------------------------------------------------------------------------------- 1 | import crontab from 'node-crontab'; 2 | 3 | -------------------------------------------------------------------------------- /src/common/config/router.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 3 | ]; 4 | -------------------------------------------------------------------------------- /view/api/index_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 海鸥飞啊飞 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
网站建设中
15 |
16 |
17 | 曾经沧海难为水,除却巫山不是云 18 |
19 | 22 | 23 |
24 | -------------------------------------------------------------------------------- /www/static/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | width: 100%; 5 | height: 100%; 6 | } 7 | .wrap { 8 | width: 100vw; 9 | height: 100vh; 10 | background: url("/static/images/background.jpg"); 11 | } 12 | 13 | .top { 14 | height: 140px; 15 | padding: 20px 40px; 16 | color: #fff; 17 | } 18 | 19 | .top .logo { 20 | font-size: 20px; 21 | margin-bottom: 20px; 22 | } 23 | 24 | .top .tip { 25 | font-size: 13px; 26 | } 27 | 28 | .bottom { 29 | position: fixed; 30 | width: 100%; 31 | bottom: 20px; 32 | margin-bottom: 20px; 33 | color: #fff; 34 | font-size: 16px; 35 | color: #fff; 36 | display: flex; 37 | justify-content: center; 38 | } 39 | .middle { 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | font-size: 32px; 44 | color: #fff; 45 | } 46 | a:link { 47 | color: #fff; 48 | text-decoration: none; 49 | } 50 | a:visited { 51 | text-decoration: none; 52 | color: #fff; 53 | } 54 | 55 | a:hover { 56 | text-decoration: underline; 57 | color: #ccc; 58 | } 59 | -------------------------------------------------------------------------------- /www/static/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamdarcy/hioshop-server/112b61ebdbc97ee193903dc57b2bbf8041541aa3/www/static/images/background.jpg -------------------------------------------------------------------------------- /www/static/images/default_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamdarcy/hioshop-server/112b61ebdbc97ee193903dc57b2bbf8041541aa3/www/static/images/default_avatar.png -------------------------------------------------------------------------------- /www/static/upload/avatar/25f45a40-9447-41bc-9d53-4c5fc17f3dff.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamdarcy/hioshop-server/112b61ebdbc97ee193903dc57b2bbf8041541aa3/www/static/upload/avatar/25f45a40-9447-41bc-9d53-4c5fc17f3dff.jpeg --------------------------------------------------------------------------------