├── .autod.conf.js ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── .vscode └── last.sql ├── README.md ├── README.zh-CN.md ├── app.js ├── app ├── common │ ├── alipayConfig.js │ ├── alipayConst.js │ ├── cart.js │ ├── orderStatus.js │ ├── payPlatform.js │ ├── paymentType.js │ ├── product.js │ ├── property.js │ ├── responseCode.js │ ├── role.js │ ├── serverResponse.js │ ├── type.js │ └── user.js ├── controller │ ├── backend │ │ ├── CategoryManageController.js │ │ ├── ManageController.js │ │ ├── OrderManageController.js │ │ └── ProductManageController.js │ ├── home.js │ └── portal │ │ ├── CartController.js │ │ ├── OrderController.js │ │ ├── ProductController.js │ │ ├── ShippingController.js │ │ └── userController.js ├── dao │ └── dao.js ├── extend │ ├── helper.js │ └── response.js ├── middleware │ └── checkLogin.js ├── model │ ├── CartModel.js │ ├── CategoryModel.js │ ├── OrderItemModel.js │ ├── OrderModel.js │ ├── PayInfoModel.js │ ├── ProductModel.js │ ├── ShippingModel.js │ └── UserModel.js ├── public │ ├── qr_order_1519615543540timestamps_1519620893829.png │ ├── qr_order_1519615543540timestamps_1519621090956.png │ ├── qr_order_1519639098998timestamps_1519639356810.png │ └── shipping1519636340300.jpeg ├── router.js ├── router │ ├── backend │ │ ├── categoryManageRouter.js │ │ ├── manageRouter.js │ │ ├── orderManageRouter.js │ │ └── productManageRouter.js │ └── portal │ │ ├── cartRouter.js │ │ ├── orderRouter.js │ │ ├── productRouter.js │ │ ├── shippingRouter.js │ │ └── userRouter.js └── service │ ├── CartService.js │ ├── CategoryManageService.js │ ├── OrderService.js │ ├── ProductManageService.js │ ├── ProductService.js │ ├── ShippingService.js │ └── UserService.js ├── appveyor.yml ├── config ├── config.default.js └── plugin.js ├── package.json ├── server.js └── test └── app └── controller └── home.test.js /.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | prefix: '^', 6 | plugin: 'autod-egg', 7 | test: [ 8 | 'test', 9 | 'benchmark', 10 | ], 11 | dep: [ 12 | 'egg', 13 | 'egg-scripts', 14 | ], 15 | devdep: [ 16 | 'egg-ci', 17 | 'egg-bin', 18 | 'egg-mock', 19 | 'autod', 20 | 'autod-egg', 21 | 'eslint', 22 | 'eslint-config-egg', 23 | 'webstorm-disable-index', 24 | ], 25 | exclude: [ 26 | './test/fixtures', 27 | './dist', 28 | ], 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | yarn-error.log 4 | node_modules/ 5 | package-lock.json 6 | yarn.lock 7 | coverage/ 8 | .idea/ 9 | run/ 10 | .DS_Store 11 | *.sw* 12 | *.un~ 13 | config/config.default.js 14 | config/plugin.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | install: 6 | - npm i npminstall && npminstall 7 | script: 8 | - npm run ci 9 | after_script: 10 | - npminstall codecov && codecov 11 | -------------------------------------------------------------------------------- /.vscode/last.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfyr111/egg-commerce/e7f6af82e464d05bf13351688a83c609fb75edd3/.vscode/last.sql -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egg-commerce 2 | 3 | 线上测试地址 4 | ``` 5 | 13.229.236.130:3000 6 | ``` 7 | 8 | egg / mysql 开发电商平台 9 | 集成支付宝面对面支付、手机网站唤醒APP支付 10 | 11 | ### 相关栈 12 | node / mysql / egg / sequelizejs / redis / 支付宝支付 13 | 14 | ### 本地开发 15 | 16 | 1.找到config/config.default.js 和config/plugin.js 17 | 确保alinode 配置和插件已经注释 18 | 19 | 2.然后找到在config/config.default.js 配置你的数据库环境 20 | 21 | ```bash 22 | $ npm i 23 | $ npm run dev 24 | $ open http://localhost:7071/ 25 | ``` 26 | 27 | ### API 文档 28 | [用户模块](https://github.com/sfyr111/egg-commerce/wiki/%E7%94%A8%E6%88%B7%E6%A8%A1%E5%9D%97) 29 | 30 | [前台商品展示及购物车](https://github.com/sfyr111/egg-commerce/wiki/%E5%89%8D%E5%8F%B0%E5%95%86%E5%93%81%E5%B1%95%E7%A4%BA%E5%8F%8A%E8%B4%AD%E7%89%A9%E8%BD%A6) 31 | 32 | [前台收货地址](https://github.com/sfyr111/egg-commerce/wiki/%E5%89%8D%E5%8F%B0%E6%94%B6%E8%B4%A7%E5%9C%B0%E5%9D%80) 33 | 34 | [前台订单及支付](https://github.com/sfyr111/egg-commerce/wiki/%E5%89%8D%E5%8F%B0%E8%AE%A2%E5%8D%95%E5%8F%8A%E6%94%AF%E4%BB%98) 35 | 36 | [后台商品分类管理](https://github.com/sfyr111/egg-commerce/wiki/%E5%90%8E%E5%8F%B0%E5%95%86%E5%93%81%E5%88%86%E7%B1%BB%E7%AE%A1%E7%90%86) 37 | 38 | [后台商品管理](https://github.com/sfyr111/egg-commerce/wiki/%E5%90%8E%E5%8F%B0%E5%95%86%E5%93%81%E7%AE%A1%E7%90%86) 39 | 40 | [后台管理订单](https://github.com/sfyr111/egg-commerce/wiki/%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86%E8%AE%A2%E5%8D%95) 41 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # egg-commerce 2 | 3 | egg / mysql 开发电商平台 4 | 5 | ### 相关栈 6 | node / mysql / egg / redis / 支付宝支付 7 | 8 | ### 本地开发 9 | 10 | ```bash 11 | $ npm i 12 | $ npm run dev 13 | $ open http://localhost:7071/ 14 | ``` 15 | 16 | ### API 文档 17 | [用户模块](https://github.com/sfyr111/egg-commerce/wiki/%E7%94%A8%E6%88%B7%E6%A8%A1%E5%9D%97) 18 | 19 | [前台商品展示及购物车](https://github.com/sfyr111/egg-commerce/wiki/%E5%89%8D%E5%8F%B0%E5%95%86%E5%93%81%E5%B1%95%E7%A4%BA%E5%8F%8A%E8%B4%AD%E7%89%A9%E8%BD%A6) 20 | 21 | [前台收货地址](https://github.com/sfyr111/egg-commerce/wiki/%E5%89%8D%E5%8F%B0%E6%94%B6%E8%B4%A7%E5%9C%B0%E5%9D%80) 22 | 23 | [前台订单及支付](https://github.com/sfyr111/egg-commerce/wiki/%E5%89%8D%E5%8F%B0%E8%AE%A2%E5%8D%95%E5%8F%8A%E6%94%AF%E4%BB%98) 24 | 25 | [后台商品分类管理](https://github.com/sfyr111/egg-commerce/wiki/%E5%90%8E%E5%8F%B0%E5%95%86%E5%93%81%E5%88%86%E7%B1%BB%E7%AE%A1%E7%90%86) 26 | 27 | [后台商品管理](https://github.com/sfyr111/egg-commerce/wiki/%E5%90%8E%E5%8F%B0%E5%95%86%E5%93%81%E7%AE%A1%E7%90%86) 28 | 29 | [后台管理订单](https://github.com/sfyr111/egg-commerce/wiki/%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86%E8%AE%A2%E5%8D%95) 30 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.beforeStart(async function() { 3 | // 应用会等待这个函数执行完成才启动 4 | // await app.model.sync({ force: true }); // 开发环境使用 5 | await app.model.sync({}); 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /app/common/alipayConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | /* 更多参数请翻源码 */ 4 | 5 | /* 应用AppID */ 6 | appid: 2016091200492877, 7 | 8 | /* 通知URL */ 9 | notifyUrl: 'http://9bqaiq.natappfree.cc/order/alipaycallback', 10 | 11 | /* 应用RSA私钥 请勿忘记 -----BEGIN RSA PRIVATE KEY----- 与 -----END RSA PRIVATE KEY----- */ 12 | merchantPrivateKey: '-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEApsBXKCC0pZmKAAosGCgPfrWKpmyCG32LzZO3XJhuROpgfLYBCPF2t1k3i3o7E6ViCT7nlhZu8/vDszdCyIJhD2fLZEKy0knPTykzpGtp4S7H4wiDuPsQRsJ2qktd8knNzEthN/CXQu+AUj3ggrnkiqDxQ5EbdXQ4sK1VlgJ6t+gWLfepXC4KwtkVwJVIt3s67xghRiOpl2Y+AZL1HIfZvLQJUWzP92Ts/pNnvgotaywKM535kK8CcFrehmXObvYQ7zSauZTcWNcWI1qaKkjG1LGvCU2J4VQU43B2NY9blTpyuL9qxJ9hexP/fAAXToslf2aPAmdeSvvB5xoDZf2X6QIDAQABAoIBAEPoNlY4I3kA8wsbGWPpBI5kXgdyTvXlBcb9bgG+bcGQ9SQ0dm1u8BqwsYcSivZwNmFvhZ5AmoSvtb3JNmAzgFVmvpSg+PPcbRlevRIrUB4NEAfsEsCFNdarIOou8R5XYgDdfcTrLJ5srIRRgJmcHG88JaSPdnA5mVCR9jW14sX7jKrF+mE9zuVlxzEQFBEIKu9pGsqdutsvhfPwZH2kp1ji6Ltd1OxPgymFu3MQW+I7agZB8lqvOQql3/EbOaw6vy1uaqR4qSD6la2ckVlLxdyeDjBZZOKsR0R8SOkzJi9BBDnmx12aDatW4DwZI4uwOBJL4lhJN5ys/+D34wCI5oUCgYEA0InpKu3eoo6h+ghMeGRJ5buKbWjfmI8yUl39eOQ/ip3rO75N/TAFDKSCo3TtaA3ftfhn13MU6EEjDhoyix3NhLGndLHfrrtMM7umYq5P8AafEDjHiHqqo12Wvtg2pw943AFGg8V1EV+luB1m/qbWnA4tRQk1INbQ+A+ulyudrqsCgYEAzLPFi6WCmRoSGMlHX60E7B2nmVXrEVb80qg2qh34ZgLXLOqLt9pQgWkCw0LersKWIwIrFkhd9CTZk7ccpbAYfKlPuIxo4L+zxzqq1g9Z90A3yoVxCQG+RKdl2eGoUwuIj1TnubCy8DPxTyOm3TwJo50i4D/HNEtixXyujqZJA7sCgYBxoeRjFxDMpUoP03vP0l4OB74rVg0YtVanWT3oJP+WyexHNrCKeSMXO4FQDkPbAkxXfM8gsD3BPNUcNxw5f/jgCGoGBXKsZLTmL6c/eFpooUMFdNsNPEJFGJcu0OQe7iheQXeqD+t1lxfXFnZr5n9ks7jpOFYx2bwun2T0TLj0VwKBgC2tb8dZh2rihmdBgsu2sAKAG4X7xhh4cLIRFyGezm70807yh3rfHFfENvmbUlVs1lO5iCPQwiZYkrSDh8DxKoWmwkNMEZsVK+ipDrX1dv3VNp3aaP65hNuM/w0/bXAagr55E7w70bIH5TDjo7h6TSxVRBMGKE1jBQdMaycps+FBAoGAKe7TNAohYjde+Hc6DMad7FPX1vZnMad8khWmw2vE+KAJe2I5VflBMPk6OekUa3l8GLbecIl9+dICbyqpVo9u3c4JI9MkwJGzx6qdEmqT6cumgBay6CE7eX/wIkZQ+/VZVEt2zgrReMNx1NtbHxp8ushGpblQcUKdwqn063gmnOg=\n-----END RSA PRIVATE KEY-----', 13 | 14 | /* 支付宝公钥 如果为空会使用默认值 请勿忘记 -----BEGIN PUBLIC KEY----- 与 -----END PUBLIC KEY----- */ 15 | alipayPublicKey: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs1qN9Jc9Sf+wnRxVHcHruXQq/YjWIe47Ay1e0ujNb8Ga+GS5Vlvg9UoISI1J8GUbNQR7gK2j81h6icbBVbMc4s5ZvPJTIOD0xODxdRH3daY04mkfYnshbu+ri5GhwrlGS9wAFKa57Ksb8P+EUZumNQL7rIJwFlW1UEiF/x252xG+1zplTnLnKkf1ERcPb4fo/kdtBD53bW5TFsY7IJ4NPqj9AnnfDdpFTZsXZ3ixPVz8uY8J5qganVXEieKpH1OSR9us9Egsh2OfR8AvLl1u2Py/qBecvXbggqPEzbNWLbNyJY0iK/KPXiDYiOV+MN/HHgCRXAEqR3g7Whh6ARswZwIDAQAB\n-----END PUBLIC KEY-----', 16 | 17 | /* 支付宝支付网关 如果为空会使用沙盒网关 */ 18 | gatewayUrl: 'https://openapi.alipaydev.com/gateway.do', 19 | }; 20 | -------------------------------------------------------------------------------- /app/common/alipayConst.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 交易创建,等待买家付款 3 | WAIT_BUYER_PAY: 'WAIT_BUYER_PAY', 4 | // 未付款交易超时关闭,或支付完成后全额退款 5 | TRADE_CLOSED: 'TRADE_CLOSED', 6 | // 交易支付成功 7 | TRADE_SUCCESS: 'TRADE_SUCCESS', 8 | // 交易结束,不可退款 9 | TRADE_FINISHED: 'TRADE_FINISHED', 10 | // success 11 | RESPONSE_SUCCESS: 'success', 12 | // failed 13 | RESPONSE_FAILED: 'failed', 14 | }; 15 | -------------------------------------------------------------------------------- /app/common/cart.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 购物车选中 3 | CHECKED: 1, 4 | // 购物车未选中 5 | UN_CHECKED: 0, 6 | }; 7 | -------------------------------------------------------------------------------- /app/common/orderStatus.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | CANCELED: { CODE: 0, VALUE: '已取消' }, 3 | NO_PAY: { CODE: 10, VALUE: '未支付' }, 4 | PAID: { CODE: 20, VALUE: '已支付' }, 5 | SHIPPED: { CODE: 40, VALUE: '已发货' }, 6 | ORDER_SUCCESS: { CODE: 50, VALUE: '订单已完成' }, 7 | ORDER_CLOSE: { CODE: 60, VALUE: '订单关闭' }, 8 | }; 9 | -------------------------------------------------------------------------------- /app/common/payPlatform.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ALIPAY: { CODE: 1, VALUE: '支付宝' }, 3 | }; 4 | -------------------------------------------------------------------------------- /app/common/paymentType.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ONLINE_PAY: { CODE: 1, VALUE: '在线支付' } 3 | } -------------------------------------------------------------------------------- /app/common/product.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ON_SALE: { 3 | CODE: 1, 4 | VALUE: '在售', 5 | }, 6 | NO_SALE: { 7 | CODE: 2, 8 | VALUE: '下架', 9 | }, 10 | DEL_SALE: { 11 | CODE: 3, 12 | VALUE: '删除', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /app/common/property.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | salt: 'egg-commerce', 3 | }; 4 | -------------------------------------------------------------------------------- /app/common/responseCode.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | SUCCESS: 0, 3 | ERROR: 1, 4 | NEED_LOGIN: 10, 5 | NO_AUTH: 20, 6 | ILLEGAL_ARGUMENT: 2, 7 | }; 8 | -------------------------------------------------------------------------------- /app/common/role.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 普通用户 3 | ROLE_CUSTOMER: 0, 4 | // 管理员 5 | ROLE_ADMAIN: 1, 6 | }; 7 | -------------------------------------------------------------------------------- /app/common/serverResponse.js: -------------------------------------------------------------------------------- 1 | const { SUCCESS, ERROR } = require('./responseCode'); 2 | 3 | module.exports = class ServerResponse { 4 | constructor(status, msg, data) { 5 | this.status = status; 6 | this.msg = msg; 7 | this.data = data; 8 | } 9 | 10 | isSuccess() { 11 | return this.status === SUCCESS; 12 | } 13 | 14 | getStatus() { 15 | return this.status; 16 | } 17 | 18 | getData() { 19 | return this.data; 20 | } 21 | 22 | getMsg() { 23 | return this.msg; 24 | } 25 | 26 | static createBySuccess() { 27 | return new ServerResponse(SUCCESS); 28 | } 29 | 30 | static createBySuccessMsg(msg) { 31 | return new ServerResponse(SUCCESS, msg, null); 32 | } 33 | 34 | static createBySuccessData(data) { 35 | return new ServerResponse(SUCCESS, null, data); 36 | } 37 | 38 | static createBySuccessMsgAndData(msg, data) { 39 | return new ServerResponse(SUCCESS, msg, data); 40 | } 41 | 42 | static createByError() { 43 | return new ServerResponse(ERROR, 'error', null); 44 | } 45 | 46 | static createByErrorMsg(errorMsg) { 47 | return new ServerResponse(ERROR, errorMsg, null); 48 | } 49 | 50 | static createByErrorCodeMsg(errorCode, errorMsg) { 51 | return new ServerResponse(errorCode, errorMsg, null); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /app/common/type.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | USERNAME: 'username', 3 | EMAIL: 'email', 4 | }; 5 | -------------------------------------------------------------------------------- /app/common/user.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | CURRENT_USER: 'CURRENT_USER', 3 | }; 4 | -------------------------------------------------------------------------------- /app/controller/backend/CategoryManageController.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller; 2 | const _ = require('lodash'); 3 | 4 | class CategoryManageController extends Controller { 5 | constructor(ctx) { 6 | super(ctx); 7 | this.session = ctx.session; 8 | this.resquest = ctx.request; 9 | this.UserService = ctx.service.userService; 10 | this.ResponseCode = ctx.response.ResponseCode; 11 | this.ServerResponse = ctx.response.ServerResponse; 12 | this.CategoryManageService = ctx.service.categoryManageService; 13 | } 14 | 15 | // 添加类别 16 | async addCategory() { 17 | // 默认0 根节点 18 | const { name, parentId = 0 } = this.resquest.body; 19 | const response = await this.CategoryManageService.addCategory(name, parentId); 20 | this.ctx.body = response; 21 | } 22 | 23 | // 更新品类的name 24 | async updateCategoryName() { 25 | const { name, id } = this.resquest.body; 26 | const response = await this.CategoryManageService.updateCategoryName(name, id); 27 | this.ctx.body = response; 28 | } 29 | 30 | // 获取某分类下平级子分类 31 | async getChildParallelCagtegory() { 32 | const { parentId = 0 } = this.ctx.params; 33 | const response = await this.CategoryManageService.getChildParallelCagtegory(parentId); 34 | this.ctx.body = response; 35 | } 36 | 37 | // 获取某分类下的递归子分类, 返回的是Array 38 | async getCategoryAndDeepChildCategory() { 39 | const { parentId = 0 } = this.ctx.params; 40 | const response = await this.CategoryManageService.getCategoryAndDeepChildCategory(parentId); 41 | this.ctx.body = response; 42 | } 43 | 44 | } 45 | 46 | 47 | module.exports = CategoryManageController; 48 | -------------------------------------------------------------------------------- /app/controller/backend/ManageController.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller; 2 | const _ = require('lodash'); 3 | const { ROLE_ADMAIN } = require('../../common/role'); 4 | 5 | class ManageController extends Controller { 6 | constructor(ctx) { 7 | super(ctx); 8 | this.session = ctx.session; 9 | this.UserModel = ctx.model.UserModel; 10 | this.OrderModel = ctx.model.OrderModel; 11 | this.ProductModel = ctx.model.ProductModel; 12 | this.UserService = ctx.service.userService; 13 | this.ResponseCode = ctx.response.ResponseCode; 14 | this.ServerResponse = ctx.response.ServerResponse; 15 | } 16 | 17 | async login() { 18 | let user, 19 | response; 20 | const { username, password } = this.ctx.request.body; 21 | response = await this.UserService.login(username, password); 22 | if (response.isSuccess()) { 23 | user = response.getData(); 24 | if (!user) return this.ctx.body = response 25 | if (user.role === ROLE_ADMAIN) this.session.currentUser = user; 26 | else response = this.ServerResponse.createByErrorMsg('无法登录,不是管理员'); 27 | } 28 | this.ctx.body = response; 29 | } 30 | 31 | async count() { 32 | const [ userCount, orderCount, productCount ] = await Promise.all([ this.UserModel.count({}), this.OrderModel.count({}), this.ProductModel.count({}) ]) 33 | this.ctx.body = this.ServerResponse.createBySuccessMsgAndData('总数', { 34 | userCount, 35 | orderCount, 36 | productCount, 37 | }) 38 | } 39 | } 40 | 41 | 42 | module.exports = ManageController; 43 | -------------------------------------------------------------------------------- /app/controller/backend/OrderManageController.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller; 2 | const _ = require('lodash'); 3 | 4 | module.exports = app => class OrderController extends Controller { 5 | constructor(ctx) { 6 | super(ctx); 7 | this.session = ctx.session; 8 | this.request = ctx.request; 9 | this.UserService = ctx.service.userService; 10 | this.OrderService = ctx.service.orderService; 11 | this.ResponseCode = ctx.response.ResponseCode; 12 | this.ProductService = ctx.service.productService; 13 | this.ServerResponse = ctx.response.ServerResponse; 14 | this.ShippingService = ctx.service.shippingService; 15 | } 16 | 17 | async list() { 18 | const response = await this.OrderService.getList(this.request.query); 19 | this.ctx.body = response; 20 | } 21 | 22 | async search() { 23 | const response = await this.OrderService.search(this.request.query); 24 | this.ctx.body = response; 25 | } 26 | 27 | async detail() { 28 | const { orderNum } = this.request.query 29 | const response = await this.OrderService.getDetail(orderNum); 30 | this.ctx.body = response; 31 | } 32 | 33 | async sendGood() { 34 | const { orderNum } = this.request.body 35 | const response = await this.OrderService.manageSendGood(orderNum); 36 | this.ctx.body = response; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /app/controller/backend/ProductManageController.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller; 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const sendToWormhole = require('stream-wormhole'); 5 | const _ = require('lodash'); 6 | 7 | class ProductManageController extends Controller { 8 | constructor(ctx) { 9 | super(ctx); 10 | this.session = ctx.session; 11 | this.resquest = ctx.request; 12 | this.UserService = ctx.service.userService; 13 | this.ResponseCode = ctx.response.ResponseCode; 14 | this.ServerResponse = ctx.response.ServerResponse; 15 | this.ProductManageService = ctx.service.productManageService; 16 | this.CategoryManageService = ctx.service.categoryManageService; 17 | } 18 | 19 | // 添加产品 20 | async saveOrUpdateProduct() { 21 | const product = this.resquest.body; 22 | const response = await this.ProductManageService.saveOrUpdateProduct(product); 23 | this.ctx.body = response; 24 | } 25 | 26 | // 产品上下架 27 | async setSaleStatus() { 28 | const { id, status } = this.resquest.body; 29 | const response = await this.ProductManageService.setSaleStatus(id, status); 30 | this.ctx.body = response; 31 | } 32 | 33 | // 获取产品详情 34 | async getDetail() { 35 | const { id } = this.ctx.params; 36 | const response = await this.ProductManageService.getDetail(id); 37 | this.ctx.body = response; 38 | } 39 | // 获取产品列表 40 | async getProductList() { 41 | const response = await this.ProductManageService.getProductList(this.resquest.query); 42 | this.ctx.body = response; 43 | } 44 | 45 | // 后台产品搜索 46 | async productSearch() { 47 | const response = await this.ProductManageService.productSearch(this.resquest.query); 48 | this.ctx.body = response; 49 | } 50 | 51 | // 上传图片 52 | async upload() { 53 | let response 54 | const stream = await this.ctx.getFileStream(); 55 | const extname = path.extname(stream.filename); 56 | const name = path.basename(stream.filename, extname); 57 | const filename = name + Date.now() + extname; 58 | let result; 59 | try { 60 | // 本地上传 61 | const ws = fs.createWriteStream(path.resolve('app/public/' + filename)); 62 | stream.pipe(ws); 63 | // oss 服务 64 | // result = await this.ctx.oss.put(name + now, stream) 65 | } catch (e) { 66 | await sendToWormhole(stream); 67 | throw new Error(e); 68 | response = this.ServerResponse.createByError('上传图片失败'); 69 | } finally { await sendToWormhole(stream); } 70 | response = this.ServerResponse.createBySuccessMsgAndData('上传图片成功', { 71 | filename, 72 | url: result ? result.url : 'localhost:7001/public/' + filename, 73 | fields: stream.fields, 74 | }); 75 | this.ctx.body = response; 76 | } 77 | 78 | // 富文本图片上传,对返回值有特别要求 simditor 79 | // async richUpload() { 80 | // this.ctx.body = { 81 | // success: true / false, 82 | // msg: 'error message', 83 | // file_path: '[real file path]' 84 | // } 85 | // } 86 | } 87 | 88 | 89 | module.exports = ProductManageController; 90 | -------------------------------------------------------------------------------- /app/controller/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | 5 | class HomeController extends Controller { 6 | async index() { 7 | this.ctx.body = 'hi, egg'; 8 | } 9 | } 10 | 11 | module.exports = HomeController; 12 | -------------------------------------------------------------------------------- /app/controller/portal/CartController.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller; 2 | const _ = require('lodash'); 3 | const { CHECKED, UN_CHECKED } = require('../../common/cart'); 4 | 5 | module.exports = app => class CartController extends Controller { 6 | constructor(ctx) { 7 | super(ctx); 8 | this.session = ctx.session; 9 | this.request = ctx.request; 10 | this.CartService = ctx.service.cartService; 11 | this.UserService = ctx.service.userService; 12 | this.ResponseCode = ctx.response.ResponseCode; 13 | this.ServerResponse = ctx.response.ServerResponse; 14 | this.ProductService = ctx.service.productService; 15 | } 16 | 17 | // 添加或更新购物车 18 | async addOrUpdate() { 19 | const response = await this.CartService.addOrUpdate(this.request.body); 20 | this.ctx.body = response; 21 | } 22 | 23 | // 获取购物车列表 24 | async getCartList() { 25 | const response = await this.CartService.getCartListByUserId(); 26 | this.ctx.body = response; 27 | } 28 | 29 | // 删除购物车 30 | async deleteCart() { 31 | const response = await this.CartService.deleteCartByproductIdList(this.request.query); 32 | this.ctx.body = response; 33 | } 34 | 35 | // 全选购物车 36 | async selectAll() { 37 | const response = await this.CartService.selectOrUnselectAll(CHECKED); 38 | this.ctx.body = response; 39 | } 40 | 41 | // 全反选购物车 42 | async unSelectAll() { 43 | const response = await this.CartService.selectOrUnselectAll(UN_CHECKED); 44 | this.ctx.body = response; 45 | } 46 | 47 | // 选择购物车 48 | async select() { 49 | const { productId } = this.ctx.params; 50 | const response = await this.CartService.selectOrUnselectByProductId(CHECKED, productId); 51 | this.ctx.body = response; 52 | } 53 | 54 | // 反选购物车 55 | async unSelect() { 56 | const { productId } = this.ctx.params; 57 | const response = await this.CartService.selectOrUnselectByProductId(UN_CHECKED, productId); 58 | this.ctx.body = response; 59 | } 60 | 61 | // 获取购物车产品数量 62 | async getCartProductCount() { 63 | const response = await this.CartService.getCartProductCount(); 64 | this.ctx.body = response; 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /app/controller/portal/OrderController.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller; 2 | const _ = require('lodash'); 3 | 4 | module.exports = app => class OrderController extends Controller { 5 | constructor(ctx) { 6 | super(ctx); 7 | this.session = ctx.session; 8 | this.request = ctx.request; 9 | this.UserService = ctx.service.userService; 10 | this.OrderService = ctx.service.orderService; 11 | this.ResponseCode = ctx.response.ResponseCode; 12 | this.ProductService = ctx.service.productService; 13 | this.ServerResponse = ctx.response.ServerResponse; 14 | this.ShippingService = ctx.service.shippingService; 15 | } 16 | 17 | async pay() { 18 | const { orderNum } = this.request.body; 19 | const response = await this.OrderService.pay(orderNum); 20 | this.ctx.body = response; 21 | } 22 | 23 | async mobilePay() { 24 | const { orderNum } = this.request.body 25 | const response = await this.OrderService.mobilePay(orderNum) 26 | this.ctx.body = response 27 | } 28 | 29 | async queryOrderPayStatus() { 30 | const { orderNum } = this.request.query; 31 | const response = await this.OrderService.queryOrderPayStatus(orderNum); 32 | this.ctx.body = response; 33 | } 34 | 35 | async alipayCallback() { 36 | // 验证回调是否为支付宝发起,避免重复 37 | const body = this.ctx.request.body; 38 | const response = await this.OrderService.alipayCallback(body); 39 | this.ctx.body = response; 40 | } 41 | 42 | async create() { 43 | const { shippingId } = this.request.body 44 | const response = await this.OrderService.createOrder(shippingId); 45 | this.ctx.body = response; 46 | } 47 | 48 | async cancel() { 49 | const { orderNum } = this.request.body 50 | const response = await this.OrderService.cancel(orderNum); 51 | this.ctx.body = response; 52 | } 53 | 54 | async getOrderCartProduct() { 55 | // 获取购物车中已经选中的商品详情 56 | const response = await this.OrderService.getOrderCartProduct(); 57 | this.ctx.body = response; 58 | } 59 | 60 | async list() { 61 | const response = await this.OrderService.getList(this.request.query); 62 | this.ctx.body = response; 63 | } 64 | 65 | async detail() { 66 | const { orderNum } = this.request.query 67 | const response = await this.OrderService.getDetail(orderNum); 68 | this.ctx.body = response; 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /app/controller/portal/ProductController.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller; 2 | const _ = require('lodash'); 3 | 4 | class ProductController extends Controller { 5 | constructor(ctx) { 6 | super(ctx); 7 | this.session = ctx.session; 8 | this.resquest = ctx.request; 9 | this.UserService = ctx.service.userService; 10 | this.ResponseCode = ctx.response.ResponseCode; 11 | this.ServerResponse = ctx.response.ServerResponse; 12 | this.ProductService = ctx.service.productService; 13 | } 14 | 15 | // 获取商品详情 16 | async getDetail() { 17 | const { id } = this.ctx.params; 18 | const response = await this.ProductService.getDetail(id); 19 | this.ctx.body = response; 20 | } 21 | 22 | // 根据产品名称搜索 23 | async productSearch() { 24 | const response = await this.ProductService.productSearch(this.ctx.query); 25 | this.ctx.body = response; 26 | } 27 | 28 | // 根据分类id 进行搜索 29 | async getProductListByCategoryId() { 30 | const response = await this.ProductService.getProductListByCategoryId(this.ctx.query); 31 | this.ctx.body = response; 32 | } 33 | } 34 | 35 | 36 | module.exports = ProductController; 37 | -------------------------------------------------------------------------------- /app/controller/portal/ShippingController.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller; 2 | const _ = require('lodash'); 3 | 4 | module.exports = app => class ShippingController extends Controller { 5 | constructor(ctx) { 6 | super(ctx); 7 | this.session = ctx.session; 8 | this.request = ctx.request; 9 | this.UserService = ctx.service.userService; 10 | this.ResponseCode = ctx.response.ResponseCode; 11 | this.ProductService = ctx.service.productService; 12 | this.ShippingService = ctx.service.shippingService; 13 | this.ServerResponse = ctx.response.ServerResponse; 14 | } 15 | 16 | async add() { 17 | const response = await this.ShippingService.add(this.request.body); 18 | this.ctx.body = response; 19 | } 20 | 21 | async update() { 22 | const { id } = this.ctx.params; 23 | const response = await this.ShippingService.update(this.request.body, id); 24 | this.ctx.body = response; 25 | } 26 | 27 | async delete() { 28 | const { id } = this.ctx.params; 29 | const response = await this.ShippingService.delete(id); 30 | this.ctx.body = response; 31 | } 32 | 33 | async getAllShipping() { 34 | const response = await this.ShippingService.getAllShipping(); 35 | this.ctx.body = response; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /app/controller/portal/userController.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller; 2 | const _ = require('lodash'); 3 | 4 | class UserController extends Controller { 5 | constructor(ctx) { 6 | super(ctx); 7 | this.session = ctx.session; 8 | this.UserModel = ctx.model.UserModel; 9 | this.UserService = ctx.service.userService; 10 | this.ResponseCode = ctx.response.ResponseCode; 11 | this.ServerResponse = ctx.response.ServerResponse; 12 | } 13 | 14 | // 登录 15 | async login() { 16 | const { username, password } = this.ctx.request.body; 17 | const response = await this.UserService.login(username, password); 18 | 19 | if (response.isSuccess()) { 20 | this.session.currentUser = response.getData(); 21 | } 22 | 23 | this.ctx.body = response; 24 | } 25 | 26 | // 登出 27 | async logout() { 28 | this.ctx.session = null; 29 | this.ctx.body = this.ServerResponse.createBySuccess(); 30 | } 31 | 32 | // 注册 33 | async register() { 34 | const user = this.ctx.request.body; 35 | const respponse = await this.UserService.register(user); 36 | this.ctx.body = respponse; 37 | } 38 | 39 | // 校验 40 | async checkValid() { 41 | const { value, type } = this.ctx.params; 42 | const response = await this.UserService.checkValid(type, value); 43 | this.ctx.body = response; 44 | } 45 | 46 | // 获取用户信息 47 | async getUserSession() { 48 | let response; 49 | const user = this.session.currentUser; 50 | if (!user) response = this.ServerResponse.createByErrorMsg('用户未登录,无法获取用户信息'); 51 | else response = this.ServerResponse.createBySuccessMsgAndData('用户已登录', user); 52 | this.ctx.body = response; 53 | } 54 | 55 | // 获取密码提示问题 56 | async forgetGetQuestion() { 57 | const { username } = this.ctx.params; 58 | const response = await this.UserService.selectQuestion(username); 59 | this.ctx.body = response; 60 | } 61 | 62 | // 校验找回密码返回token 63 | async forgetCheckAnswer() { 64 | const { username, question, answer } = this.ctx.request.body; 65 | const response = await this.UserService.checkAnswer(username, question, answer); 66 | this.ctx.body = response; 67 | } 68 | 69 | // 忘记密码用token 重置密码 70 | async forgetRestPassword() { 71 | const { username, paswordNew, forgetToken } = this.ctx.request.body; 72 | const response = await this.UserService.forgetRestPassword(username, paswordNew, forgetToken); 73 | this.ctx.body = response; 74 | } 75 | 76 | // 登录状态的重置密码 77 | async resetPassword() { 78 | let response; 79 | const { passwordOld, passwordNew } = this.ctx.request.body; 80 | const user = this.session.currentUser; 81 | if (!user) response = this.ServerResponse.createByErrorMsg('用户未登录'); 82 | else response = await this.UserService.resetPassword(passwordOld, passwordNew, user); 83 | this.ctx.body = response; 84 | } 85 | 86 | // 修改用户信息 87 | async updateUserInfo() { 88 | const userInfo = this.ctx.request.body; 89 | const user = this.session.currentUser; 90 | const response = await this.UserService.updateUserInfo(userInfo, user); 91 | this.session.currentUser = response.getData(); 92 | this.ctx.body = response; 93 | } 94 | 95 | // 获取用户详细信息 96 | async getUserInfo() { 97 | let response; 98 | const user = this.session.currentUser; 99 | if (!user) response = this.ServerResponse.createByErrorCodeMsg(this.ResponseCode.NEED_LOGIN, '需要强制登录status=10'); 100 | else response = await this.UserService.getUserInfo(user.id); 101 | this.ctx.body = response; 102 | } 103 | } 104 | 105 | 106 | module.exports = UserController; 107 | -------------------------------------------------------------------------------- /app/dao/dao.js: -------------------------------------------------------------------------------- 1 | module.exports = (app) => app.model.OrderModel -------------------------------------------------------------------------------- /app/extend/helper.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | exports.relativeTime = time => moment(new Date(time * 1000)).fromNow(); 3 | -------------------------------------------------------------------------------- /app/extend/response.js: -------------------------------------------------------------------------------- 1 | const ServerResponse = require('../common/serverResponse'); 2 | const ResponseCode = require('../common/responseCode'); 3 | 4 | module.exports = { 5 | ResponseCode, 6 | ServerResponse, 7 | }; 8 | -------------------------------------------------------------------------------- /app/middleware/checkLogin.js: -------------------------------------------------------------------------------- 1 | const { ROLE_ADMAIN } = require('../common/role') 2 | module.exports = options => { 3 | 4 | return async function checkLogin(ctx, next) { 5 | const user = ctx.session.currentUser; 6 | if (!user) return ctx.body = ctx.response.ServerResponse.createByErrorCodeMsg(ctx.response.ResponseCode.NEED_LOGIN, '用户未登录'); 7 | else { 8 | if (options.checkAdmin && user.role !== ROLE_ADMAIN) return ctx.body = ctx.response.ServerResponse.createByErrorCodeMsg(ctx.response.ResponseCode.NO_AUTH, '用户不是管理员无权操作'); 9 | // else await next(); 10 | } 11 | await next() 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /app/model/CartModel.js: -------------------------------------------------------------------------------- 1 | const { UN_CHECKED } = require('../common/cart'); 2 | 3 | module.exports = app => { 4 | const { INTEGER, DATE, UUID, UUIDV4 } = app.Sequelize; 5 | 6 | const CartModel = app.model.define('cart', { 7 | id: { 8 | type: UUID, 9 | defaultValue: UUIDV4, 10 | allowNull: false, 11 | primaryKey: true, 12 | // autoIncrement: true, 13 | }, 14 | // 用户id 15 | userId: { 16 | type: UUID, 17 | allowNull: false, 18 | }, 19 | // 产品id 20 | productId: { 21 | type: UUID, 22 | allowNull: true, 23 | }, 24 | // 数量 25 | quantity: { 26 | type: INTEGER(11), 27 | allowNull: true, 28 | }, 29 | // 是否选择, 1已勾选, 0未勾选 30 | checked: { 31 | type: INTEGER(11), 32 | allowNull: true, 33 | defaultValue: UN_CHECKED, 34 | }, 35 | createTime: { 36 | type: DATE, 37 | allowNull: false, 38 | defaultValue: new Date(), 39 | }, 40 | updateTime: { 41 | type: DATE, 42 | allowNull: false, 43 | defaultValue: new Date(), 44 | }, 45 | }, { 46 | timestamps: false, 47 | tablseName: 'cart', 48 | }, { 49 | indexes: [ 50 | { fields: [ 'userId' ] }, 51 | ], 52 | }, { 53 | classMethods: { 54 | associate() { 55 | CartModel.belongsTo(app.model.ProductModel, { foreignKey: 'productId' }); 56 | }, 57 | }, 58 | }); 59 | // ProductModel.belongsTo(app.model.categoryModel) 60 | CartModel.beforeBulkUpdate(cart => { 61 | cart.attributes.updateTime = new Date(); 62 | return cart; 63 | }); 64 | 65 | return CartModel; 66 | }; 67 | -------------------------------------------------------------------------------- /app/model/CategoryModel.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const { INTEGER, STRING, DATE, UUID, UUIDV4 } = app.Sequelize; 3 | 4 | const CategoryModel = app.model.define('category', { 5 | id: { 6 | type: UUID, 7 | defaultValue: UUIDV4, 8 | allowNull: false, 9 | primaryKey: true, 10 | // autoIncrement: true, 11 | }, 12 | // 父类别id 为0时为根节点,一级类别 13 | parentId: { 14 | type: UUID, 15 | allowNull: true, 16 | }, 17 | // 类别名称 18 | name: { 19 | type: STRING(50), 20 | allowNull: true, 21 | }, 22 | // 类别状态1-正常,2-废弃 23 | status: { 24 | type: INTEGER(1), 25 | allowNull: true, 26 | defaultValue: 1, 27 | }, 28 | // 排序编号,同类展示顺序,相等则自然排序 29 | sortOrder: { 30 | type: INTEGER(4), 31 | allowNull: true, 32 | }, 33 | createTime: { 34 | type: DATE, 35 | allowNull: false, 36 | defaultValue: new Date(), 37 | }, 38 | updateTime: { 39 | type: DATE, 40 | allowNull: false, 41 | defaultValue: new Date(), 42 | }, 43 | }, { 44 | timestamps: false, 45 | tablseName: 'category', 46 | }, { 47 | classMethods: { 48 | associate() {}, 49 | }, 50 | }); 51 | 52 | CategoryModel.beforeBulkUpdate(category => { 53 | category.attributes.updateTime = new Date(); 54 | return category; 55 | }); 56 | 57 | return CategoryModel; 58 | }; 59 | -------------------------------------------------------------------------------- /app/model/OrderItemModel.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const { INTEGER, DATE, BIGINT, DECIMAL, STRING, UUID, UUIDV4 } = app.Sequelize; 3 | 4 | const OrderItemModel = app.model.define('orderItem', { 5 | id: { 6 | type: UUID, 7 | defaultValue: UUIDV4, 8 | allowNull: false, 9 | primaryKey: true, 10 | // autoIncrement: true, 11 | }, 12 | // 用户id 13 | userId: { 14 | type: UUID, 15 | allowNull: false, 16 | }, 17 | // 订单号 18 | orderNum: { 19 | type: BIGINT(20), 20 | allowNull: true, 21 | }, 22 | // 商品id 23 | productId: { 24 | type: UUID, 25 | allowNull: true, 26 | }, 27 | // 商品名称 28 | productName: { 29 | type: STRING(100), 30 | allowNull: true, 31 | }, 32 | // 商品图片地址 33 | productImage: { 34 | type: STRING(500), 35 | allowNull: true, 36 | }, 37 | // 生成订单时的商品单价,单位元,保留2位小数 38 | currentUnitPrice: { 39 | type: DECIMAL(20, 2), 40 | allowNull: true, 41 | }, 42 | // 商品数量 43 | quantity: { 44 | type: INTEGER(10), 45 | allowNull: true, 46 | }, 47 | // 商品总价,单位元,保留2位小数 48 | totalPrice: { 49 | type: DECIMAL(20, 2), 50 | allowNull: true, 51 | }, 52 | createTime: { 53 | type: DATE, 54 | allowNull: false, 55 | defaultValue: new Date(), 56 | }, 57 | updateTime: { 58 | type: DATE, 59 | allowNull: false, 60 | defaultValue: new Date(), 61 | }, 62 | }, { 63 | timestamps: false, 64 | tablseName: 'orderItem', 65 | }, { 66 | indexes: [ 67 | { fields: [ 'userId' ] }, 68 | ], 69 | }, { 70 | classMethods: { 71 | associate() {}, 72 | }, 73 | }); 74 | 75 | OrderItemModel.beforeBulkUpdate(orderItem => { 76 | orderItem.attributes.updateTime = new Date(); 77 | return orderItem; 78 | }); 79 | 80 | return OrderItemModel; 81 | }; 82 | -------------------------------------------------------------------------------- /app/model/OrderModel.js: -------------------------------------------------------------------------------- 1 | const { NO_PAY } = require('../common/orderStatus') 2 | const { ONLINE_PAY } = require('../common/paymentType') 3 | 4 | module.exports = app => { 5 | const { INTEGER, DATE, BIGINT, DECIMAL, UUID, UUIDV4 } = app.Sequelize; 6 | 7 | const OrderModel = app.model.define('order', { 8 | id: { 9 | type: UUID, 10 | defaultValue: UUIDV4, 11 | allowNull: false, 12 | primaryKey: true, 13 | // autoIncrement: true, 14 | }, 15 | // 用户id 16 | userId: { 17 | type: UUID, 18 | allowNull: false, 19 | }, 20 | // 订单号 21 | orderNum: { 22 | type: BIGINT(20), 23 | allowNull: false, 24 | }, 25 | shippingId: { 26 | type: UUID, 27 | allowNull: true, 28 | }, 29 | // 实际付款金额,单位元,保留2位小数 30 | payment: { 31 | type: DECIMAL(20, 2), 32 | allowNull: true, 33 | }, 34 | // 支付类型, 1-在线支付 35 | paymentType: { 36 | type: INTEGER(4), 37 | allowNull: true, 38 | defaultValue: ONLINE_PAY.CODE, 39 | }, 40 | // 运费,单位元 41 | postage: { 42 | type: INTEGER(10), 43 | allowNull: true, 44 | defaultValue: 0 45 | }, 46 | // 订单状态 0已取消 10未支付 20已支付 40已发货 50订单完成 60订单关闭 47 | status: { 48 | type: INTEGER(10), 49 | allowNull: true, 50 | defaultValue: NO_PAY.CODE 51 | }, 52 | // 支付时间 53 | paymentTime: { 54 | type: DATE, 55 | allowNull: true, 56 | }, 57 | // 发货时间 58 | sendTime: { 59 | type: DATE, 60 | allowNull: true, 61 | }, 62 | // 交易完成时间 63 | endTime: { 64 | type: DATE, 65 | allowNull: true, 66 | }, 67 | // 交易关闭时间 68 | closeTime: { 69 | type: DATE, 70 | allowNull: true, 71 | }, 72 | createTime: { 73 | type: DATE, 74 | allowNull: false, 75 | defaultValue: new Date(), 76 | }, 77 | updateTime: { 78 | type: DATE, 79 | allowNull: false, 80 | defaultValue: new Date(), 81 | }, 82 | }, { 83 | timestamps: false, 84 | tablseName: 'order', 85 | }, { 86 | indexes: [ 87 | { fields: [ 'userId' ] }, 88 | { fields: [ 'orderNum' ] }, 89 | ], 90 | }, { 91 | classMethods: { 92 | associate() {}, 93 | }, 94 | }); 95 | 96 | OrderModel.beforeBulkUpdate(order => { 97 | order.attributes.updateTime = new Date(); 98 | return order; 99 | }); 100 | 101 | return OrderModel; 102 | }; 103 | -------------------------------------------------------------------------------- /app/model/PayInfoModel.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const { INTEGER, DATE, STRING, BIGINT, UUID, UUIDV4 } = app.Sequelize; 3 | 4 | const PayInfoModel = app.model.define('payInfo', { 5 | id: { 6 | type: UUID, 7 | defaultValue: UUIDV4, 8 | allowNull: false, 9 | primaryKey: true, 10 | // autoIncrement: true, 11 | }, 12 | // 用户id 13 | userId: { 14 | type: UUID, 15 | allowNull: false, 16 | }, 17 | // 订单号 18 | orderNum: { 19 | type: BIGINT(20), 20 | allowNull: false, 21 | }, 22 | // 支付平台: 1-支付宝, 2-微信 23 | payPlatform: { 24 | type: INTEGER(10), 25 | allowNull: false, 26 | }, 27 | // 支付宝支付流水号 28 | platformNumber: { 29 | type: STRING(200), 30 | allowNull: false, 31 | }, 32 | // 支付宝支付状态 33 | platformStatus: { 34 | type: STRING(20), 35 | allowNull: false, 36 | }, 37 | createTime: { 38 | type: DATE, 39 | allowNull: false, 40 | defaultValue: new Date(), 41 | }, 42 | updateTime: { 43 | type: DATE, 44 | allowNull: false, 45 | defaultValue: new Date(), 46 | }, 47 | }, { 48 | timestamps: false, 49 | tablseName: 'payInfo', 50 | }, { 51 | indexes: [ 52 | { fields: [ 'userId' ] }, 53 | ], 54 | }, { 55 | classMethods: { 56 | associate() {}, 57 | }, 58 | }); 59 | 60 | PayInfoModel.beforeBulkUpdate(payInfo => { 61 | payInfo.attributes.updateTime = new Date(); 62 | return payInfo; 63 | }); 64 | 65 | return PayInfoModel; 66 | }; 67 | -------------------------------------------------------------------------------- /app/model/ProductModel.js: -------------------------------------------------------------------------------- 1 | const { ON_SALE } = require('../common/product'); 2 | 3 | module.exports = app => { 4 | const { INTEGER, STRING, DATE, TEXT, DECIMAL, UUID, UUIDV4 } = app.Sequelize; 5 | 6 | const ProductModel = app.model.define('product', { 7 | id: { 8 | type: UUID, 9 | defaultValue: UUIDV4, 10 | allowNull: false, 11 | primaryKey: true, 12 | // autoIncrement: true, 13 | }, 14 | // 分类id 15 | categoryId: { 16 | type: UUID, 17 | allowNull: false, 18 | }, 19 | // 商品名称 20 | name: { 21 | type: STRING(50), 22 | allowNull: false, 23 | }, 24 | // 商品副标题 25 | subtitle: { 26 | type: STRING(200), 27 | allowNull: true, 28 | }, 29 | // 产品主图,url相对地址 30 | mainImage: { 31 | type: STRING(500), 32 | allowNull: true, 33 | }, 34 | // 图片地址,json格式,扩展用 35 | subImages: { 36 | type: TEXT, 37 | allowNull: true, 38 | }, 39 | // 商品详情 40 | detail: { 41 | type: TEXT, 42 | allowNull: true, 43 | }, 44 | // 价格,保留两位小数 45 | price: { 46 | type: DECIMAL(20, 2), 47 | allowNull: false, 48 | }, 49 | // 库存 50 | stock: { 51 | type: INTEGER(11), 52 | allowNull: false, 53 | }, 54 | // 商品状态 1-在售,2-下架,3-删除 55 | status: { 56 | type: INTEGER(6), 57 | allowNull: true, 58 | defaultValue: ON_SALE.CODE, 59 | }, 60 | createTime: { 61 | type: DATE, 62 | allowNull: false, 63 | defaultValue: new Date(), 64 | }, 65 | updateTime: { 66 | type: DATE, 67 | allowNull: false, 68 | defaultValue: new Date(), 69 | }, 70 | }, { 71 | timestamps: false, 72 | tablseName: 'product', 73 | }, { 74 | indexes: [ 75 | { fields: [ 'categoryId' ] }, 76 | ], 77 | }, { 78 | classMethods: { 79 | associate() { 80 | ProductModel.hasOne(app.model.CartModel, { foreignKey: 'id' }); 81 | }, 82 | }, 83 | }); 84 | // ProductModel.belongsTo(app.model.categoryModel) 85 | ProductModel.beforeBulkUpdate(product => { 86 | product.attributes.updateTime = new Date(); 87 | return product; 88 | }); 89 | 90 | return ProductModel; 91 | }; 92 | -------------------------------------------------------------------------------- /app/model/ShippingModel.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const { INTEGER, DATE, STRING, UUID, UUIDV4 } = app.Sequelize; 3 | 4 | const ShippingModel = app.model.define('shipping', { 5 | id: { 6 | type: UUID, 7 | defaultValue: UUIDV4, 8 | allowNull: false, 9 | primaryKey: true, 10 | // autoIncrement: true, 11 | }, 12 | // 用户id 13 | userId: { 14 | type: UUID, 15 | allowNull: false, 16 | }, 17 | // 收货姓名 18 | receiverName: { 19 | type: STRING(20), 20 | allowNull: false, 21 | }, 22 | // 收货电话 23 | receiverPhone: { 24 | type: STRING(20), 25 | allowNull: false, 26 | }, 27 | // 收货手机 28 | receiverMobile: { 29 | type: STRING(20), 30 | allowNull: false, 31 | }, 32 | // 省份 33 | receiverProvince: { 34 | type: STRING(20), 35 | allowNull: false, 36 | }, 37 | // 城市 38 | receiverCity: { 39 | type: STRING(20), 40 | allowNull: false, 41 | }, 42 | // 区/县 43 | receiverDistrict: { 44 | type: STRING(20), 45 | allowNull: false, 46 | }, 47 | // 详细地址 48 | receiverAddress: { 49 | type: STRING(200), 50 | allowNull: false, 51 | }, 52 | // 邮编 53 | receiverZip: { 54 | type: STRING(6), 55 | allowNull: false, 56 | }, 57 | createTime: { 58 | type: DATE, 59 | allowNull: false, 60 | defaultValue: new Date(), 61 | }, 62 | updateTime: { 63 | type: DATE, 64 | allowNull: false, 65 | defaultValue: new Date(), 66 | }, 67 | }, { 68 | timestamps: false, 69 | tablseName: 'shipping', 70 | }, { 71 | indexes: [ 72 | { fields: [ 'userId' ] }, 73 | ], 74 | }, { 75 | classMethods: { 76 | associate() {}, 77 | }, 78 | }); 79 | 80 | ShippingModel.beforeBulkUpdate(shipping => { 81 | shipping.attributes.updateTime = new Date(); 82 | return shipping; 83 | }); 84 | 85 | return ShippingModel; 86 | }; 87 | -------------------------------------------------------------------------------- /app/model/UserModel.js: -------------------------------------------------------------------------------- 1 | const { ROLE_CUSTOMER } = require('../common/role'); 2 | 3 | module.exports = app => { 4 | const { INTEGER, STRING, DATE, UUID, UUIDV4 } = app.Sequelize; 5 | 6 | const UserModel = app.model.define('user', { 7 | id: { 8 | type: UUID, 9 | defaultValue: UUIDV4, 10 | allowNull: false, 11 | primaryKey: true, 12 | // autoIncrement: true, 13 | }, 14 | username: { 15 | type: STRING(50), 16 | allowNull: false, 17 | unique: true, 18 | }, 19 | password: { 20 | type: STRING(50), 21 | allowNull: false, 22 | }, 23 | email: { 24 | type: STRING(50), 25 | allowNull: true, 26 | }, 27 | phone: { 28 | type: STRING(20), 29 | allowNull: true, 30 | }, 31 | question: { 32 | type: STRING(100), 33 | allowNull: true, 34 | }, 35 | answer: { 36 | type: STRING(100), 37 | allowNull: true, 38 | }, 39 | role: { 40 | type: INTEGER(4), 41 | allowNull: false, 42 | defaultValue: ROLE_CUSTOMER, 43 | }, 44 | createTime: { 45 | type: DATE, 46 | allowNull: false, 47 | defaultValue: new Date(), 48 | }, 49 | updateTime: { 50 | type: DATE, 51 | allowNull: false, 52 | defaultValue: new Date(), 53 | }, 54 | }, { 55 | timestamps: false, 56 | tablseName: 'user', 57 | }); 58 | 59 | UserModel.beforeBulkUpdate(user => { 60 | user.attributes.updateTime = new Date(); 61 | return user; 62 | }); 63 | 64 | // UserModel.beforeCreate((user) => { 65 | // console.log(user) 66 | // return user 67 | // }) 68 | 69 | return UserModel; 70 | }; 71 | -------------------------------------------------------------------------------- /app/public/qr_order_1519615543540timestamps_1519620893829.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfyr111/egg-commerce/e7f6af82e464d05bf13351688a83c609fb75edd3/app/public/qr_order_1519615543540timestamps_1519620893829.png -------------------------------------------------------------------------------- /app/public/qr_order_1519615543540timestamps_1519621090956.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfyr111/egg-commerce/e7f6af82e464d05bf13351688a83c609fb75edd3/app/public/qr_order_1519615543540timestamps_1519621090956.png -------------------------------------------------------------------------------- /app/public/qr_order_1519639098998timestamps_1519639356810.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfyr111/egg-commerce/e7f6af82e464d05bf13351688a83c609fb75edd3/app/public/qr_order_1519639098998timestamps_1519639356810.png -------------------------------------------------------------------------------- /app/public/shipping1519636340300.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfyr111/egg-commerce/e7f6af82e464d05bf13351688a83c609fb75edd3/app/public/shipping1519636340300.jpeg -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {Egg.Application} app - egg application 5 | */ 6 | module.exports = app => { 7 | const { router, controller } = app; 8 | router.get('/', controller.home.index); 9 | require('./router/portal/cartRouter')(app); 10 | require('./router/portal/userRouter')(app); 11 | require('./router/portal/orderRouter')(app); 12 | require('./router/portal/productRouter')(app); 13 | require('./router/portal/shippingRouter')(app); 14 | require('./router/backend/manageRouter')(app); 15 | require('./router/backend/orderManageRouter')(app); 16 | require('./router/backend/productManageRouter')(app); 17 | require('./router/backend/categoryManageRouter')(app); 18 | }; 19 | -------------------------------------------------------------------------------- /app/router/backend/categoryManageRouter.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const checkLogin = app.middleware.checkLogin({ checkAdmin: true }); 3 | app.router.post('/manage/category/addCategory', checkLogin, app.controller.backend.categoryManageController.addCategory); 4 | 5 | app.router.put('/manage/category/updateCategoryName', checkLogin, app.controller.backend.categoryManageController.updateCategoryName); 6 | 7 | app.router.get('/manage/category/parentId/:parentId', checkLogin, app.controller.backend.categoryManageController.getChildParallelCagtegory); 8 | 9 | app.router.get('/manage/category/deep/parentId/:parentId', checkLogin, app.controller.backend.categoryManageController.getCategoryAndDeepChildCategory); 10 | }; 11 | -------------------------------------------------------------------------------- /app/router/backend/manageRouter.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.router.post('/manage/user/login', app.controller.backend.manageController.login); 3 | app.router.get('/manage/count', app.controller.backend.manageController.count); 4 | }; 5 | -------------------------------------------------------------------------------- /app/router/backend/orderManageRouter.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const checkLogin = app.middleware.checkLogin({ checkAdmin: true }); 3 | app.router.get('/manage/order/detail', checkLogin, app.controller.backend.orderManageController.detail); 4 | 5 | app.router.get('/manage/order/list', checkLogin, app.controller.backend.orderManageController.list); 6 | 7 | app.router.get('/manage/order/search', checkLogin, app.controller.backend.orderManageController.search); 8 | 9 | app.router.put('/manage/order/sendGood', checkLogin, app.controller.backend.orderManageController.sendGood); 10 | }; 11 | -------------------------------------------------------------------------------- /app/router/backend/productManageRouter.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const checkLogin = app.middleware.checkLogin({ checkAdmin: true }); 3 | 4 | app.router.post('/manage/product/saveOrUpdate', checkLogin, app.controller.backend.productManageController.saveOrUpdateProduct); 5 | 6 | app.router.post('/manage/product/setSaleStatus', checkLogin, app.controller.backend.productManageController.setSaleStatus); 7 | 8 | app.router.put('/manage/product/setSaleStatus', checkLogin, app.controller.backend.productManageController.setSaleStatus); 9 | 10 | app.router.get('/manage/product/detail/:id', checkLogin, app.controller.backend.productManageController.getDetail); 11 | 12 | app.router.get('/manage/product/list', checkLogin, app.controller.backend.productManageController.getProductList); 13 | 14 | app.router.get('/manage/product/search', checkLogin, app.controller.backend.productManageController.productSearch); 15 | 16 | app.router.put('/manage/upload', checkLogin, app.controller.backend.productManageController.upload); 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /app/router/portal/cartRouter.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const checkLogin = app.middleware.checkLogin({}); 3 | app.router.post('/cart/update', checkLogin, app.controller.portal.cartController.addOrUpdate); 4 | 5 | app.router.get('/cart/list', checkLogin, app.controller.portal.cartController.getCartList); 6 | 7 | app.router.delete('/cart/delete', checkLogin, app.controller.portal.cartController.deleteCart); 8 | 9 | app.router.put('/cart/selectAll', checkLogin, app.controller.portal.cartController.selectAll); 10 | 11 | app.router.put('/cart/unSelectAll', checkLogin, app.controller.portal.cartController.unSelectAll); 12 | 13 | app.router.put('/cart/select/:productId', checkLogin, app.controller.portal.cartController.select); 14 | 15 | app.router.put('/cart/unSelect/:productId', checkLogin, app.controller.portal.cartController.unSelect); 16 | 17 | app.router.get('/cart/count', checkLogin, app.controller.portal.cartController.getCartProductCount); 18 | }; 19 | -------------------------------------------------------------------------------- /app/router/portal/orderRouter.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const checkLogin = app.middleware.checkLogin({}); 3 | app.router.post('/order/pay', checkLogin, app.controller.portal.orderController.pay); 4 | 5 | app.router.get('/order/queryOrderPayStatus', checkLogin, app.controller.portal.orderController.queryOrderPayStatus); 6 | 7 | app.router.post('/order/alipaycallback', app.controller.portal.orderController.alipayCallback); 8 | 9 | app.router.post('/order/mobilePay', checkLogin, app.controller.portal.orderController.mobilePay); 10 | 11 | app.router.post('/order/create', checkLogin, app.controller.portal.orderController.create); 12 | 13 | app.router.put('/order/cancel', checkLogin, app.controller.portal.orderController.cancel); 14 | 15 | app.router.get('/order/getOrderCartProduct', checkLogin, app.controller.portal.orderController.getOrderCartProduct); 16 | 17 | app.router.get('/order/list', checkLogin, app.controller.portal.orderController.list); 18 | 19 | app.router.get('/order/detail', checkLogin, app.controller.portal.orderController.detail); 20 | }; 21 | -------------------------------------------------------------------------------- /app/router/portal/productRouter.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.router.get('/product/detail/:id', app.controller.portal.productController.getDetail); 3 | 4 | app.router.get('/product/name/search', app.controller.portal.productController.productSearch); 5 | 6 | app.router.get('/product/categoryId/search', app.controller.portal.productController.getProductListByCategoryId); 7 | }; 8 | -------------------------------------------------------------------------------- /app/router/portal/shippingRouter.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const checkLogin = app.middleware.checkLogin({}); 3 | app.router.get('/shipping/list', checkLogin, app.controller.portal.shippingController.getAllShipping); 4 | 5 | app.router.post('/shipping/add', checkLogin, app.controller.portal.shippingController.add); 6 | 7 | app.router.put('/shipping/update/:id', checkLogin, app.controller.portal.shippingController.update); 8 | 9 | app.router.delete('/shipping/delete/:id', checkLogin, app.controller.portal.shippingController.delete); 10 | }; 11 | -------------------------------------------------------------------------------- /app/router/portal/userRouter.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const checkLogin = app.middleware.checkLogin({}); 3 | app.router.post('/user/login', app.controller.portal.userController.login); 4 | 5 | app.router.get('/user/logout', app.controller.portal.userController.logout); 6 | 7 | app.router.post('/user/register', app.controller.portal.userController.register); 8 | 9 | app.router.get('/user/checkValid/:type/:value', app.controller.portal.userController.checkValid); 10 | 11 | app.router.get('/user/getUserSession', checkLogin, app.controller.portal.userController.getUserSession); 12 | 13 | app.router.get('/user/forgetGetQuestion/:username', app.controller.portal.userController.forgetGetQuestion); 14 | 15 | app.router.post('/user/forgetCheckAnswer', app.controller.portal.userController.forgetCheckAnswer); 16 | 17 | app.router.put('/user/forgetRestPassword', app.controller.portal.userController.forgetRestPassword); 18 | 19 | app.router.put('/user/resetPassword', checkLogin, app.controller.portal.userController.resetPassword); 20 | 21 | app.router.put('/user/updateUserInfo', checkLogin, app.controller.portal.userController.updateUserInfo); 22 | 23 | app.router.get('/user/getUserInfo', checkLogin, app.controller.portal.userController.getUserInfo); 24 | }; 25 | -------------------------------------------------------------------------------- /app/service/CartService.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service; 2 | const _ = require('lodash'); 3 | const { CHECKED } = require('../common/cart'); 4 | 5 | module.exports = app => class CartService extends Service { 6 | constructor(ctx) { 7 | super(ctx); 8 | this.session = ctx.session; 9 | this.CartModel = ctx.model.CartModel; 10 | this.ProductModel = ctx.model.ProductModel; 11 | this.CategoryModel = ctx.model.CategoryModel; 12 | this.ResponseCode = ctx.response.ResponseCode; 13 | this.ServerResponse = ctx.response.ServerResponse; 14 | this.ProductModel.hasOne(this.CartModel, { foreignKey: 'id' }); 15 | this.CartModel.belongsTo(this.ProductModel, { foreignKey: 'productId' }); 16 | } 17 | 18 | /** 19 | * @feature 添加产品到购物车 20 | * @param userId {Number} 21 | * @param productId {Number} 产品id 22 | * @param cout {Number} 总数 23 | * @return {Promise.} 24 | */ 25 | async addOrUpdate({ productId, count }) { 26 | if (!productId || !count) return this.ServerResponse.createByErrorMsg(this.ResponseCode.ILLEGAL_ARGUMENT, 'ILLEGAL_ARGUMENT'); 27 | const { id: userId } = this.session.currentUser; 28 | count = Number(count); 29 | const productRow = await this.ProductModel.findOne({ where: { id: productId } }); 30 | if (!productRow) return this.ServerResponse.createByErrorMsg('商品不存在'); 31 | 32 | const cartRow = await this.CartModel.findOne({ where: { userId, productId } }); 33 | let msg; 34 | if (!cartRow) { 35 | // TODO 不存在 进行添加 36 | await this.CartModel.create({ 37 | userId, 38 | productId, 39 | quantity: count < 0 ? 0 : count, 40 | checked: CHECKED, 41 | }); 42 | msg = '添加'; 43 | } else { 44 | // TODO 已存在 增加数量 45 | const stock = productRow.get('stock'); 46 | const quantity = cartRow.get('quantity'); 47 | count = quantity + count > stock ? stock : quantity + count; 48 | await cartRow.update({ quantity: count < 0 ? 0 : count }, { individualHooks: true }); 49 | msg = '更新'; 50 | } 51 | return await this.getCartListByUserId(`${msg}购物车成功`, userId); 52 | } 53 | 54 | /** 55 | * @feature 根据产品id 删除购物车 56 | * @param productIdList {String} 1,2,3,4 57 | * @return {Promise.<*>} 58 | */ 59 | async deleteCartByproductIdList({ productIdList }) { 60 | const productIdArr = productIdList.split(','); 61 | if (productIdArr.length < 1) return this.ServerResponse.createByErrorMsg(this.ResponseCode.ILLEGAL_ARGUMENT, 'ILLEGAL_ARGUMENT'); 62 | const { id: userId } = this.session.currentUser; 63 | const deleteCount = await this.CartModel.destroy({ where: { userId, productId: { $in: productIdArr } } }); 64 | if (deleteCount > 0) return await this.getCartListByUserId('删除购物车成功'); 65 | return this.ServerResponse.createBySuccessMsg('购物车已删除或不存在'); 66 | } 67 | 68 | /** 69 | * @feature 根据用户id 返回购物车列表 70 | * @param userId {Number} 71 | * @param msg {String} 返回的msg 72 | * @return {Promise.<*>} 73 | */ 74 | async getCartListByUserId(msg, userId = this.session.currentUser.id) { 75 | const cartArr = await this.CartModel.findAll({ 76 | where: { userId }, 77 | include: [{ model: this.ProductModel, where: { id: app.Sequelize.col('productId') } }], 78 | }).map(rows => rows && rows.toJSON()); 79 | const totalPrice = cartArr.reduce((prePrice, curItem) => { 80 | return curItem.checked ? Number(Number(prePrice) + Number(curItem.quantity) * Number(curItem.product.price)).toFixed(2) : 0; 81 | }, 0); 82 | const allChecked = cartArr.every(item => item.checked === CHECKED); 83 | if (isNaN(totalPrice)) return this.ServerResponse.createByErrorMsg('价格或数量错误'); 84 | return this.ServerResponse.createBySuccessMsgAndData(msg, { 85 | totalPrice, 86 | allChecked, 87 | list: cartArr, 88 | host: this.config.oss.client.endpoint, 89 | }); 90 | } 91 | 92 | /** 93 | * @feature 选中或反选购物车 94 | * @param checked {Boolean} 95 | * @param productId {Number} 96 | * @return {Promise.<*>} 97 | */ 98 | async selectOrUnselectByProductId(checked, productId) { 99 | const productRow = await this.ProductModel.findOne({ where: { id: productId } }); 100 | if (!productRow) return this.ServerResponse.createByErrorMsg('商品不存在'); 101 | 102 | const { id: userId } = this.session.currentUser; 103 | const [ updateCount ] = await this.CartModel.update({ checked }, { 104 | where: { userId, productId }, 105 | individualHooks: true, 106 | }); 107 | if (updateCount > 0) return await this.getCartListByUserId('', userId); 108 | } 109 | 110 | /** 111 | * @feature 全选或全反选购物车 112 | * @param checked {Boolean} 113 | * @return {Promise.<*>} 114 | */ 115 | async selectOrUnselectAll(checked) { 116 | const { id: userId } = this.session.currentUser; 117 | const [ updateCount ] = await this.CartModel.update({ checked }, { 118 | where: { userId }, 119 | individualHooks: true, 120 | }); 121 | if (updateCount > 0) return await this.getCartListByUserId('选择', userId); 122 | } 123 | 124 | async getCartProductCount() { 125 | const { id: userId } = this.session.currentUser; 126 | const [ countRow ] = await this.CartModel.findAll({ 127 | where: { userId }, 128 | attributes: [ 129 | [ app.Sequelize.fn('sum', app.Sequelize.col('quantity')), 'count' ], 130 | ], 131 | }); 132 | const count = countRow.toJSON().count ? countRow.toJSON() : { count: 0 }; 133 | return this.ServerResponse.createBySuccessData(count); 134 | } 135 | }; 136 | -------------------------------------------------------------------------------- /app/service/CategoryManageService.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service; 2 | const _ = require('lodash'); 3 | 4 | class CategoryManageService extends Service { 5 | constructor(ctx) { 6 | super(ctx); 7 | this.CategoryModel = ctx.model.CategoryModel; 8 | this.ServerResponse = ctx.response.ServerResponse; 9 | } 10 | 11 | /** 12 | * @feature 添加分类 13 | * @param name {String} 类别名称 14 | * @param parentId {Number} 父类别id 15 | * @return {*} 16 | */ 17 | async addCategory(name, parentId = 0) { 18 | if (!name.trim()) return this.ServerResponse.createByErrorMsg('添加品类参数错误'); 19 | const categoryRow = await this.CategoryModel.create({ name, parentId }); 20 | if (!categoryRow) return this.ServerResponse.createByErrorMsg('添加品类失败'); 21 | const category = categoryRow.toJSON(); 22 | return this.ServerResponse.createBySuccessMsgAndData('添加品类成功', category); 23 | } 24 | 25 | /** 26 | * @feature 更新类别名称 27 | * @param name {String} 类别名称 28 | * @param id {Number} 类别id 29 | * @return {Promise.} 30 | */ 31 | async updateCategoryName(name, id) { 32 | if (!name.trim() || !id) return this.ServerResponse.createByErrorMsg('更新品类名称参数错误'); 33 | const [ updateCount, [ updateRow ]] = await this.CategoryModel.update({ name }, { where: { id }, individualHooks: true }); 34 | if (updateCount < 1) return this.ServerResponse.createByErrorMsg('更新品类名称错误'); 35 | const category = updateRow.toJSON(); 36 | return this.ServerResponse.createBySuccessMsgAndData('更新品类名称成功', category); 37 | } 38 | 39 | /** 40 | * @feature 获取某分类下的平级子分类 41 | * @param parentId 42 | * @return {Promise.<*>} 43 | */ 44 | async getChildParallelCagtegory(parentId = 0) { 45 | const cagtegoryRows = await this.CategoryModel.findAll({ 46 | attributes: [ 'id', 'parentId', 'name', 'status' ], 47 | where: { parentId }, 48 | }).then(rows => rows && rows.map(r => r.toJSON())); 49 | if (cagtegoryRows.length < 1) { 50 | // return this.ServerResponse.createByErrorMsg('未找到当前分类的子分类') 51 | this.ctx.logger.info('getChildParallelCagtegory: 未找到当前分类的子分类'); 52 | } 53 | return this.ServerResponse.createBySuccessData(cagtegoryRows); 54 | } 55 | 56 | /** 57 | * @feature 递归查询本节点的id及孩子父节点的id 58 | * @param {Number} categoryId 59 | */ 60 | async getCategoryAndDeepChildCategory(categoryId = 0) { 61 | const categoryIdSet = new Set(); 62 | const categoryList = await this._findChildCategory(categoryId); 63 | const categoryUniqList = _.uniqWith(categoryList, _.isEqual); 64 | await categoryUniqList.forEach(item => categoryIdSet.add(item.id)); 65 | return this.ServerResponse.createBySuccessData(Array.from(categoryIdSet)); 66 | } 67 | 68 | async _findChildCategory(categoryId, arr = []) { 69 | const category = await this.CategoryModel.findOne({ 70 | attributes: [ 'id', 'parentId', 'name', 'status' ], 71 | where: { id: categoryId }, 72 | }).then(row => row && row.toJSON()); 73 | if (category) arr.push(category); 74 | const categoryList = await this.CategoryModel.findAll({ 75 | attributes: [ 'id', 'parentId', 'name', 'status' ], 76 | where: { parentId: categoryId }, 77 | }); 78 | if (categoryList.length < 1) return arr; 79 | let i = 0; 80 | while (i < categoryList.length) { 81 | await this._findChildCategory(categoryList[i++].get('id'), arr); 82 | } 83 | return arr; 84 | } 85 | } 86 | 87 | module.exports = CategoryManageService; 88 | -------------------------------------------------------------------------------- /app/service/OrderService.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service; 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const _ = require('lodash'); 5 | const qr = require('qr-image'); 6 | const alipayf2fConfig = require('../common/alipayConfig'); 7 | const alipayftof = require('alipay-ftof'); 8 | const alipayf2f = new alipayftof(alipayf2fConfig); 9 | 10 | // 要求在env = test 环境下 11 | process.env.NODE_ENV = 'test' 12 | const Alipay = require('alipay-mobile'); 13 | const options = { 14 | app_id: alipayf2fConfig.appid.toString(), 15 | appPrivKeyFile: alipayf2fConfig.merchantPrivateKey, 16 | alipayPubKeyFile: alipayf2fConfig.alipayPublicKey, 17 | } 18 | const alipayService = new Alipay(options); 19 | 20 | 21 | const OrderStatus = require('../common/orderStatus'); 22 | const PayPlatform = require('../common/payPlatform'); 23 | const AlipayConst = require('../common/alipayConst'); 24 | 25 | const { CHECKED } = require('../common/cart'); 26 | const { ON_SALE } = require('../common/product'); 27 | const { ROLE_ADMAIN } = require('../common/role'); 28 | const PAYMENT_TYPE_MAP = require('../common/paymentType'); 29 | const ORDER_STATUS_MAP = require('../common/orderStatus'); 30 | 31 | module.exports = app => { 32 | 33 | return class OrderService extends Service { 34 | constructor(ctx) { 35 | super(ctx); 36 | this.session = ctx.session; 37 | this.CartModel = ctx.model.CartModel; 38 | this.OrderModel = ctx.model.OrderModel; 39 | this.ProductModel = ctx.model.ProductModel; 40 | this.PayInfoModel = ctx.model.PayInfoModel; 41 | this.CategoryModel = ctx.model.CategoryModel; 42 | this.ShippingModel = ctx.model.ShippingModel; 43 | this.OrderItemModel = ctx.model.OrderItemModel; 44 | this.ResponseCode = ctx.response.ResponseCode; 45 | this.ServerResponse = ctx.response.ServerResponse; 46 | this.ProductModel.hasOne(this.CartModel, { foreignKey: 'id' }); 47 | this.CartModel.belongsTo(this.ProductModel, { foreignKey: 'productId' }); 48 | 49 | this.OrderItemModel.hasMany(this.OrderModel, { foreignKey: 'orderNum', targetKey: 'orderNum' }) 50 | this.OrderModel.belongsTo(this.OrderItemModel, { foreignKey: 'orderNum', targetKey: 'orderNum' }) 51 | 52 | this.ShippingModel.hasOne(this.OrderModel, { foreignKey: 'id' }) 53 | this.OrderModel.belongsTo(this.ShippingModel, { foreignKey: 'shippingId' }) 54 | } 55 | /** 56 | * @feature 生成支付二维码 用到orderNum、userId、qrcode 57 | * @param orderNum {Number} 订单号 58 | * @return {Promise.} 59 | */ 60 | async pay(orderNum) { 61 | const { id: userId } = this.session.currentUser; 62 | const order = await this.OrderModel.findOne({ where: { userId, orderNum } }).then(row => row && row.toJSON()); 63 | if (!order) this.ServerResponse.createByErrorMsg('用户没有该订单'); 64 | if (order.status >= OrderStatus.PAID.CODE) return this.ServerResponse.createByErrorMsg('该订单不可支付'); 65 | // const orderGoodsDetail = await this.OrderItemModel.findAll({ where: { userId, orderNum }}).map(row => row && row.toJSON()) 66 | 67 | const result = await alipayf2f.createQRPay(this.alipayData(order)); 68 | if (result.code !== '10000') return this.ServerResponse.createByErrorMsg('创建支付宝失败'); 69 | 70 | const { filename, url } = this.saveQrCode(result); 71 | return this.ServerResponse.createBySuccessMsgAndData('支付宝二维码生成成功', { filename, url }); 72 | } 73 | 74 | /** 75 | * @feature 手机支付宝唤醒APP 支付 76 | * @param orderNum {number} 77 | * @returns {Promise.<*>} 78 | */ 79 | async mobilePay(orderNum) { 80 | // 要求在env = test 环境下 81 | const { id: userId } = this.session.currentUser; 82 | const order = await this.OrderModel.findOne({ where: { userId, orderNum } }).then(row => row && row.toJSON()); 83 | if (!order) this.ServerResponse.createByErrorMsg('用户没有该订单'); 84 | if (order.status >= OrderStatus.PAID.CODE) return this.ServerResponse.createByErrorMsg('该订单不可支付'); 85 | 86 | const data = { 87 | subject: `COOLHEADEDYANG扫码支付,订单号: ${order.orderNum}`, 88 | out_trade_no: order.orderNum.toString(), 89 | total_amount: order.payment, 90 | body: `订单${order.orderNum}购买商品共${order.payment}元` 91 | }; 92 | const basicParams = { return_url: 'http://localhost:7071', notify_url: alipayf2fConfig.notifyUrl }; 93 | const result = await alipayService.createWebOrderURL(data, basicParams); 94 | if (result.code !== 0) return this.ServerResponse.createByErrorMsg('创建支付订单错误') 95 | console.log(result.data) 96 | return this.ServerResponse.createBySuccessMsgAndData('支付宝手机支付地址创建成功', result) 97 | } 98 | 99 | /** 100 | * @feature 处理支付宝支付回调 101 | * @param body {Object} 支付宝支付回调的请求body 102 | * @return {Promise.<*>} 103 | */ 104 | async alipayCallback(body) { 105 | const signStatus = alipayf2f.verifyCallback(body); 106 | if (!signStatus) return this.ServerResponse.createByErrorMsg('非法请求验证不通过'); 107 | // TODO 处理支付回调 1更新订单状态 2判断支付状态 108 | const { /* 支付宝支付状态*/ 109 | trade_status: tradeStatus, 110 | /* 支付宝外部订单号 === 我们的订单号*/ 111 | out_trade_no: orderNum, 112 | /* 支付宝内部订单号*/ 113 | trade_no: tradeNo, 114 | /* 支付宝支付时间*/ 115 | gmt_payment: paymentTime } = body; 116 | const orderRow = await this.OrderModel.findOne({ where: { orderNum } }); 117 | if (!orderRow) return this.ServerResponse.createByErrorMsg('非本系统支付单,回调忽略'); 118 | const order = orderRow.toJSON(); 119 | if (order.status >= OrderStatus.PAID.CODE) return this.ServerResponse.createBySuccessMsg('支付宝支付重复调用'); 120 | 121 | if (tradeStatus === AlipayConst.TRADE_SUCCESS) await orderRow.update({ status: OrderStatus.PAID.CODE, paymentTime }, { individualHooks: true }); 122 | return this.createPayInfo(order.userId, order.orderNum, PayPlatform.ALIPAY.CODE, tradeNo, tradeStatus); 123 | } 124 | 125 | /** 126 | * @feature 查询订单状态 127 | * @param orderNum {Number} 订单号 128 | * @return {Promise.<*>} 129 | */ 130 | async queryOrderPayStatus(orderNum) { 131 | const { id: userId } = this.session.currentUser; 132 | const order = await this.OrderModel.findOne({ where: { userId, orderNum } }).then(row => row && row.toJSON()); 133 | if (!order) return this.ServerResponse.createByErrorMsg('用户不存在该订单'); 134 | const key = Object.keys(OrderStatus).find(k => OrderStatus[k].CODE === order.status); 135 | 136 | return this.ServerResponse.createBySuccessMsgAndData('订单状态', OrderStatus[key]); 137 | } 138 | 139 | async createOrder(shippingId) { 140 | const { id: userId } = this.session.currentUser 141 | const shipping = await this.ShippingModel.findOne({ where: { id: shippingId, userId }}).then(r => r && r.toJSON()) 142 | if (!shipping) return this.ServerResponse.createByErrorMsg('用户无该收货地址') 143 | // 购物车中获取数据 144 | const response = await this._getCartListWithProduct(userId) 145 | if (!response.isSuccess()) return response 146 | const cartListWithProduct = response.getData() 147 | 148 | const orderNum = this._createAOrderNum() 149 | const orderItemArr = await this._cartListToOrderItemArr(cartListWithProduct) 150 | const orderTotalPrice = orderItemArr.reduce((total, item) => total + item.totalPrice, 0) 151 | const order = await this._createPayOrder(userId, shippingId, orderTotalPrice, orderNum) 152 | if (!order) return this.ServerResponse.createByErrorMsg('创建订单错误') 153 | // 批量插入orderItem 154 | const orderItemList = await this._bulkCreateOrderItemArr(orderItemArr, orderNum) 155 | // 更新库存 156 | await this._reduceUpdateProductStock(orderItemList) 157 | // 清空购物车 158 | await this._cleanCart(cartListWithProduct) 159 | 160 | // 组装及处理返回数据, 返回订单详情,收货地址,订单的各产品 161 | const orderDetail = await this._createOrderDetail(order, orderItemList, shippingId) 162 | return this.ServerResponse.createBySuccessMsgAndData('创建订单成功', orderDetail) 163 | } 164 | 165 | /** 166 | * @feature 取消订单 167 | * @param orderNum {number} 订单号 168 | * @returns {Promise.} 169 | */ 170 | async cancel(orderNum) { 171 | const { id: userId } = this.session.currentUser 172 | const orderRow = await this.OrderModel.findOne({ where: { userId, orderNum }}) 173 | if (!orderRow) return this.ServerResponse.createByErrorMsg('用户不存在该订单') 174 | if (orderRow.get('status') !== ORDER_STATUS_MAP.NO_PAY.CODE) return this.ServerResponse.createByErrorMsg('订单无法取消') 175 | const updateRow = await orderRow.update({ status: ORDER_STATUS_MAP.CANCELED.CODE, closeTime: new Date() }, { individualHooks: true }) 176 | if (!updateRow) return this.ServerResponse.createByErrorMsg('取消订单失败') 177 | return this.ServerResponse.createBySuccessMsgAndData('取消订单成功', updateRow.toJSON()) 178 | } 179 | 180 | /** 181 | * @feature 查看选中购物车的订单快照 182 | * @returns {Promise.<*>} 183 | */ 184 | async getOrderCartProduct() { 185 | const { id: userId } = this.session.currentUser 186 | // 购物车中获取数据 187 | const response = await this._getCartListWithProduct(userId) 188 | if (!response.isSuccess()) return response 189 | const cartListWithProduct = response.getData() 190 | 191 | const orderItemArr = await this._cartListToOrderItemArr(cartListWithProduct) 192 | const orderTotalPrice = orderItemArr.reduce((total, item) => total + item.totalPrice, 0).toFixed(2) 193 | return this.ServerResponse.createBySuccessMsgAndData('订单快照', { orderItemArr, orderTotalPrice, host: 'localhost:7071'}) 194 | } 195 | 196 | /** 197 | * @feature 获取订单详情 198 | * @param orderNum {number} 订单号 199 | * @returns {Promise.} 200 | */ 201 | async getDetail(orderNum) { 202 | const { id: userId, role} = this.session.currentUser 203 | const order = await this.OrderModel.findOne({ where: { orderNum, userId: role === ROLE_ADMAIN ? { $regexp: '[0-9a-zA-Z]' } : userId }}).then(r => r && r.toJSON()) 204 | if (!order) return this.ServerResponse.createByErrorMsg('订单不存在') 205 | const orderItem = await this.OrderItemModel.findAll({ where: { orderNum, userId: role === ROLE_ADMAIN ? { $regexp: '[0-9a-zA-Z]' } : userId }}).then(rows => rows && rows.map(r => r.toJSON())) 206 | if (orderItem.length < 1) return this.ServerResponse.createByErrorMsg('订单不存在') 207 | const orderDetail = await this._createOrderDetail(order, orderItem, order.shippingId) 208 | return this.ServerResponse.createBySuccessMsgAndData('订单详情', orderDetail) 209 | } 210 | 211 | async manageSendGood(orderNum) { 212 | const orderRow = await this.OrderModel.findOne({ where: { orderNum }}) 213 | if (!orderRow) return this.ServerResponse.createByErrorMsg('订单不存在1') 214 | 215 | const orderItem = await this.OrderItemModel.findAll({ where: { orderNum } }).then(rows => rows && rows.map(r => r.toJSON())) 216 | if (orderItem.length < 1) return this.ServerResponse.createByErrorMsg('订单不存在2') 217 | 218 | if (orderRow.get('status') !== ORDER_STATUS_MAP.PAID.CODE) return this.ServerResponse.createByErrorMsg('此订单未完成交易, 不能发货') 219 | const updateRow = await orderRow.update({ status: ORDER_STATUS_MAP.SHIPPED.CODE, sendTime: new Date() }, { individualHooks: true }) 220 | if (!updateRow) return this.ServerResponse.createByErrorMsg('订单发货失败') 221 | 222 | 223 | const orderDetail = await this._createOrderDetail(updateRow.toJSON(), orderItem, updateRow.get('shippingId')) 224 | return this.ServerResponse.createBySuccessMsgAndData('发货成功', orderDetail) 225 | } 226 | 227 | /** 228 | * @feature 获取订单详情列表 229 | * @param pageNum {number} 230 | * @param pageSize {number} 231 | * @returns {Promise.} 232 | */ 233 | async getList({ pageNum = 1, pageSize = 10 }) { 234 | const { id: userId, role } = this.session.currentUser 235 | // 循环查询解决 236 | const { count, rows } = await this.OrderModel.findAndCount({ 237 | where: { userId: role === ROLE_ADMAIN ? { $regexp: '[0-9a-zA-Z]' } : userId }, 238 | order: [[ 'id', 'DESC' ]], 239 | limit: Number(pageSize | 0), 240 | offset: Number(pageNum - 1 | 0) * Number(pageSize | 0), 241 | }); 242 | if (rows.length < 1) this.ServerResponse.createBySuccessMsg('已无订单数据'); 243 | const orderList = rows.map(row => row && row.toJSON()); 244 | 245 | const orderListWithOrderItemsAndShipping = await Promise.all(orderList.map(async item => { 246 | const orderItemList = await this.OrderItemModel.findAll({ where: { orderNum: item.orderNum }}).then(rows => rows && rows.map(r => r.toJSON())) 247 | const shipping = await this.ShippingModel.findOne({ where: { id: item.shippingId }}).then(r => r && r.toJSON()) 248 | 249 | return { ...item, orderItemList, shipping } 250 | })) 251 | const list = this._createOrderDetailList(orderListWithOrderItemsAndShipping) 252 | // 关联查询解决 bug 253 | // const orderListWithOrderItemsAndShipping = await this.OrderModel.findAll({ 254 | // where: { userId: role === ROLE_ADMAIN ? { $regexp: '[0-9a-zA-Z]' } : userId }, 255 | // order: [[ 'id', 'DESC' ]], 256 | // limit: Number(pageSize | 0), 257 | // offset: Number(pageNum - 1 | 0) * Number(pageSize | 0), 258 | // include: [ 259 | // { model: this.OrderItemModel }, 260 | // { model: this.ShippingModel, where: { id: app.Sequelize.literal('order.shippingId = shipping.id') } } 261 | // ], 262 | // }).then(rows => rows && rows.map(r => r.toJSON())) 263 | // 264 | // const groupList = this._groupList(orderListWithOrderItemsAndShipping) 265 | // const list = this._createOrderDetailList(groupList) 266 | 267 | return this.ServerResponse.createBySuccessData({ 268 | list, 269 | pageNum, 270 | pageSize, 271 | total: count, 272 | host: this.config.oss.client.endpoint, 273 | }); 274 | } 275 | 276 | /** 277 | * @feature 后台管理搜索, 现只支持订单号搜索 278 | * @param orderNum {number} 279 | * @param pageNum {number} 280 | * @param pageSize {number} 281 | * @returns {Promise.} 282 | */ 283 | async search({ orderNum, pageNum = 1, pageSize = 10 }) { 284 | const { count, rows } = await this.OrderModel.findAndCount({ 285 | where: { orderNum }, 286 | order: [[ 'id', 'DESC' ]], 287 | limit: Number(pageSize | 0), 288 | offset: Number(pageNum - 1 | 0) * Number(pageSize | 0), 289 | }); 290 | if (rows.length < 1) this.ServerResponse.createBySuccessMsg('已无订单搜索数据'); 291 | const orderList = rows.map(row => row && row.toJSON()); 292 | 293 | const orderListWithOrderItemsAndShipping = await Promise.all(orderList.map(async item => { 294 | const orderItemList = await this.OrderItemModel.findAll({ where: { orderNum: item.orderNum }}).then(rows => rows && rows.map(r => r.toJSON())) 295 | const shipping = await this.ShippingModel.findOne({ where: { id: item.shippingId }}).then(r => r && r.toJSON()) 296 | 297 | return { ...item, orderItemList, shipping } 298 | })) 299 | 300 | const list = this._createOrderDetailList(orderListWithOrderItemsAndShipping) 301 | 302 | return this.ServerResponse.createBySuccessData({ 303 | list, 304 | pageNum, 305 | pageSize, 306 | total: count, 307 | host: this.config.oss.client.endpoint, 308 | }); 309 | } 310 | 311 | // 对order 组装orderItem 数组 312 | _groupList(arr) { 313 | return arr.reduce((arr, item, index) => { 314 | item.orderItemList = [] 315 | if (!arr[index - 1]) item.orderItemList.push(item.orderItem) 316 | else { 317 | if (arr[index - 1].orderItem.orderNum === item.orderItem.orderNum) { 318 | arr[index - 1].orderItemList.push(item.orderItem) 319 | _.unset(arr[index - 1], 'orderItem') 320 | return arr 321 | } 322 | } 323 | return [ ...arr, item ] 324 | }, []) 325 | } 326 | 327 | /** 328 | * @feature 补全订单列表详情数据, 关联查询已经携带orderLIst shipping 329 | * @param list {array} 订单列表,包含orderItem shipping 330 | * @returns {Array|*|{}} 331 | * @private 332 | */ 333 | _createOrderDetailList(list) { 334 | return list.map(order => { 335 | const paymentType = this._getEnumValueByCode(PAYMENT_TYPE_MAP, order.paymentType) 336 | const orderStatus = this._getEnumValueByCode(ORDER_STATUS_MAP, order.status) 337 | return { ...order, payment: Number(order.payment).toFixed(2), paymentType, orderStatus } 338 | }) 339 | } 340 | 341 | /** 342 | * @feature 组装及处理订单详情 343 | * @param order {object} 344 | * @param orderItemList {array} 345 | * @param shippingId {number} 346 | * @returns {Promise.<{order: {paymentType: (string|string|string|string|string|string|*), orderStatus: (string|string|string|string|string|string|*)}, orderItemList: *, shipping: TResult, host: string}>} 347 | * @private 348 | */ 349 | async _createOrderDetail(order, orderItemList, shippingId) { 350 | let shipping 351 | if (shippingId) shipping = await this.ShippingModel.findOne({ where: { id: shippingId }}).then(r => r && r.toJSON()) 352 | 353 | const paymentType = this._getEnumValueByCode(PAYMENT_TYPE_MAP, order.paymentType) 354 | const orderStatus = this._getEnumValueByCode(ORDER_STATUS_MAP, order.status) 355 | 356 | return { order: { ...order, payment: Number(order.payment).toFixed(2), paymentType, orderStatus }, orderItemList, shipping, host: 'localhost:7071' } 357 | } 358 | 359 | _getEnumValueByCode(mapper, code) { 360 | return mapper[_.findKey(mapper, item => item.CODE === code)].VALUE 361 | } 362 | 363 | async _getCartListWithProduct(userId) { 364 | const arr = await this.CartModel.findAll({ 365 | where: { userId, checked: CHECKED }, 366 | include: [{ model: this.ProductModel, where: { id: app.Sequelize.col('productId'), status: ON_SALE.CODE } }], 367 | }).then(rows => rows && rows.map(r => r.toJSON())) 368 | 369 | if (arr.length === 0) return this.ServerResponse.createByErrorMsg('购物车为空') 370 | if (!this._checkStock(arr).hasStock) return this.ServerResponse.createByErrorMsg(`${this._checkStock(arr).noStockList[0]}商品库存不足`) 371 | return this.ServerResponse.createBySuccessData(arr) 372 | } 373 | 374 | // 清空购物车 375 | async _cleanCart(cartList) { 376 | cartList.forEach(async item => { 377 | await this.CartModel.destroy({ where: { id: item.id } }) 378 | }) 379 | } 380 | 381 | // 批量更新库存 382 | async _reduceUpdateProductStock(orderItemList) { 383 | orderItemList.forEach(async item => { 384 | await this.ProductModel.update({ stock: app.Sequelize.literal(`stock - ${item.quantity}`) }, { where: { id: item.productId }}) 385 | }) 386 | } 387 | 388 | async _bulkCreateOrderItemArr(orderItemArr, orderNum) { 389 | orderItemArr = orderItemArr.map(item => ({ ...item, orderNum })) 390 | return await this.OrderItemModel.bulkCreate(orderItemArr).then(rows => rows && rows.map(r => r.toJSON())) 391 | } 392 | 393 | _createAOrderNum() { 394 | const orderNum = Date.now() + _.random(100) 395 | return orderNum 396 | } 397 | /** 398 | * @feature 创建最终支付订单 399 | * @param userId {number} 400 | * @param shippingId {number} 收货地址 401 | * @param payment {string} 支付总价 402 | * @returns {Promise.} 403 | */ 404 | async _createPayOrder(userId, shippingId, payment, orderNum) { 405 | const order = { 406 | userId, 407 | payment, 408 | orderNum, 409 | shippingId, 410 | } 411 | // 发货时间,支付时间 412 | const result = await this.OrderModel.create(order).then(r => r && r.toJSON()) 413 | if (!result) return null 414 | return result 415 | } 416 | 417 | /** 418 | * @feature 创建OrderItems 快照 419 | * @param cartList 420 | * @returns {Promise.} 421 | */ 422 | async _cartListToOrderItemArr(cartList) { 423 | return cartList.map(item => ({ 424 | userId: item.userId, 425 | quantity: item.quantity, 426 | productId: item.product.id, 427 | productName: item.product.name, 428 | productImage: item.product.mainImage, 429 | currentUnitPrice: item.product.price, 430 | totalPrice: item.product.price * item.quantity 431 | })) 432 | // return await this.OrderItemModel.bulkCreate(orderItemArr).then(rows => rows && rows.map(r => r.toJSON())) 433 | } 434 | 435 | // 生成支付信息 436 | alipayData(order) { 437 | let tradeNo = order.orderNum, 438 | subject = `COOLHEADEDYANG扫码支付,订单号: ${tradeNo}`, 439 | totalAmount = order.payment, 440 | body = `订单${tradeNo}购买商品共${totalAmount}元`; 441 | return { tradeNo, subject, totalAmount, body }; 442 | } 443 | 444 | /** 445 | * @feature 生成二维码并保存,返回二维码地址 446 | * @param result {Object} 447 | * @return {{filename: string, url: string}} 448 | */ 449 | saveQrCode(result) { 450 | const imgStream = qr.image(result.qr_code, { size: 10 }); 451 | const filename = 'qr_order_' + result.out_trade_no + 'timestamps_' + Date.now() + '.png'; 452 | const ws = fs.createWriteStream(path.resolve('app/public/' + filename)); 453 | imgStream.pipe(ws); 454 | const url = 'localhost:7001/public/' + filename; 455 | return { filename, url }; 456 | } 457 | 458 | /** 459 | * @feature 生成一个支付信息并存库 460 | * @param userId 461 | * @param orderNum 462 | * @param payPlatform 463 | * @param platformNumber 464 | * @param platformStatus 465 | * @return {Promise.<*>} 466 | */ 467 | async createPayInfo(userId, orderNum, payPlatform, platformNumber, platformStatus) { 468 | // TODO 创建payInfo 469 | const payInfo = { userId, orderNum, payPlatform, platformNumber, platformStatus }; 470 | const payInfoRow = await this.PayInfoModel.create(payInfo); 471 | app.logger.info(`\n创建支付信息\n${JSON.stringify(payInfoRow.toJSON())}`); 472 | return this.ServerResponse.createBySuccess(); 473 | } 474 | 475 | // 检查库存 476 | _checkStock(list) { 477 | let arr = [] 478 | list.forEach(item => { 479 | if (item.product.stock < item.quantity) { 480 | arr.push(item.product.name) 481 | } 482 | }) 483 | return { hasStock : arr.length === 0, noStockList: arr } 484 | } 485 | }; 486 | }; 487 | -------------------------------------------------------------------------------- /app/service/ProductManageService.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service; 2 | const _ = require('lodash'); 3 | 4 | class ProductManageService extends Service { 5 | constructor(ctx) { 6 | super(ctx); 7 | this.ProductModel = ctx.model.ProductModel; 8 | this.CategoryModel = ctx.model.CategoryModel; 9 | this.ResponseCode = ctx.response.ResponseCode; 10 | this.ServerResponse = ctx.response.ServerResponse; 11 | } 12 | 13 | /** 14 | * @feature 增加或更新产品 15 | * @param product {Object} -id 有id 更新,没id 增加 16 | * @return {Promise.<*>} 17 | */ 18 | async saveOrUpdateProduct(product) { 19 | if (!product) return this.ServerResponse.createByErrorMsg('新增或更新产品参数不正确'); 20 | const subImgArr = product.subImages.split(','); 21 | if (subImgArr.length > 0) product.mainImage = subImgArr[0]; 22 | const resultRow = await this.ProductModel.findOne({ where: { id: product.id } }); 23 | let productRow, 24 | addOrUpdate; 25 | if (!resultRow) { 26 | // TODO 添加 27 | productRow = await this.ProductModel.create(product); 28 | addOrUpdate = '添加'; 29 | if (!productRow) return this.ServerResponse.createByErrorMsg('添加产品失败'); 30 | } else { 31 | // TODO 更新 32 | const [ updateCount, [ updateRow ]] = await this.ProductModel.update(product, { 33 | where: { id: product.id }, 34 | individualHooks: true, 35 | }); 36 | addOrUpdate = '更新'; 37 | if (updateCount < 1) return this.ServerResponse.createByErrorMsg('更新产品失败'); 38 | productRow = updateRow; 39 | } 40 | return this.ServerResponse.createBySuccessMsgAndData(`${addOrUpdate}产品成功`, productRow.toJSON()); 41 | } 42 | 43 | /** 44 | * @feature 修改产品销售状态 45 | * @param id {Number} 产品id 46 | * @param status {Number} 产品销售状态 47 | * @return {Promise.<*>} 48 | */ 49 | async setSaleStatus(id, status) { 50 | if (!id || !status) return this.ServerResponse.createByErrorCodeMsg(this.ResponseCode.ILLEGAL_ARGUMENT, 'ILLEGAL_ARGUMENT'); 51 | const [ updateCount, [ updateRow ]] = await this.ProductModel.update({ status }, { where: { id }, individualHooks: true }); 52 | if (updateCount < 1) return this.ServerResponse.createByErrorMsg('修改产品销售状态失败'); 53 | return this.ServerResponse.createBySuccessMsgAndData('修改产品销售状态成功', updateRow.toJSON()); 54 | } 55 | 56 | /** 57 | * @feature 获取商品详情 58 | * @param id {Number} 商品id 59 | * @return {Promise.<*>} 60 | */ 61 | async getDetail(id) { 62 | if (!id) return this.ServerResponse.createByErrorCodeMsg(this.ResponseCode.ILLEGAL_ARGUMENT, 'ILLEGAL_ARGUMENT'); 63 | const productRow = await this.ProductModel.findOne({ 64 | // attributes: { exclude: ['createTime', 'updateTime'] }, 65 | where: { id }, 66 | // include: [ 67 | // { model: this.CategoryModel, as: 'categoryId', attributes: ['name'] } 68 | // ] 69 | }); 70 | if (!productRow) this.ServerResponse.createByErrorMsg('产品已下架或删除'); 71 | return this.ServerResponse.createBySuccessData(productRow.toJSON()); 72 | } 73 | 74 | /** 75 | * @feature 产品列表获取 76 | * @param pageNum {Number} 页数 77 | * @param pageSize {Number} limit 78 | * @return {Promise.<*>} 79 | */ 80 | async getProductList({ pageNum = 1, pageSize = 10 }) { 81 | const { count, rows } = await this.ProductModel.findAndCount({ 82 | // attributes: { exclude: ['createTime', 'updateTime'] }, 83 | order: [[ 'id', 'ASC' ]], 84 | limit: Number(pageSize | 0), 85 | offset: Number(pageNum - 1 | 0) * Number(pageSize | 0), 86 | }); 87 | if (rows.length < 1) this.ServerResponse.createBySuccessMsg('已无产品数据'); 88 | rows.forEach(row => row && row.toJSON()); 89 | return this.ServerResponse.createBySuccessData({ 90 | pageNum, 91 | pageSize, 92 | list: rows, 93 | total: count, 94 | host: this.config.oss.client.endpoint, 95 | }); 96 | } 97 | 98 | /** 99 | * @feature 后台产品搜索 100 | * @param pageNum {Number} 101 | * @param pageSize {Number} 102 | * @param productName {String} 103 | * @param productId {Number} 104 | * @return {Promise.} 105 | */ 106 | async productSearch({ pageNum = 1, pageSize = 10, productName, productId }) { 107 | if (productId && !productName) { 108 | // TODO 按id 搜索 返回一条产品 109 | const product = await this.ProductModel.findOne({ where: { id: productId } }).then(row => row && row.toJSON()); 110 | if (!product) return this.ServerResponse.createByErrorMsg('产品id错误'); 111 | return this.ServerResponse.createBySuccessData({ 112 | product, 113 | host: this.config.oss.client.endpoint, 114 | }); 115 | } else if (productName && !productId) { 116 | // TODO 按名称分页搜索 返回产品列表 117 | const { count, rows } = await this.ProductModel.findAndCount({ 118 | // attributes: { exclude: ['createTime', 'updateTime'] }, 119 | where: { name: { $like: `%${productName}%` } }, 120 | order: [[ 'id', 'ASC' ]], 121 | limit: Number(pageSize | 0), 122 | offset: Number(pageNum - 1 | 0) * Number(pageSize | 0), 123 | }); 124 | if (rows.length < 1) this.ServerResponse.createBySuccessMsg('无产品数据'); 125 | rows.forEach(row => row && row.toJSON()); 126 | return this.ServerResponse.createBySuccessData({ 127 | pageNum, 128 | pageSize, 129 | list: rows, 130 | total: count, 131 | host: this.config.oss.client.endpoint, 132 | }); 133 | } 134 | return this.ServerResponse.createByErrorCodeMsg(this.ResponseCode.ILLEGAL_ARGUMENT, 'ILLEGAL_ARGUMENT'); 135 | } 136 | 137 | } 138 | 139 | module.exports = ProductManageService; 140 | -------------------------------------------------------------------------------- /app/service/ProductService.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service; 2 | const _ = require('lodash'); 3 | const { ON_SALE } = require('../common/product'); 4 | 5 | class ProductService extends Service { 6 | constructor(ctx) { 7 | super(ctx); 8 | this.ProductModel = ctx.model.ProductModel; 9 | this.CategoryModel = ctx.model.CategoryModel; 10 | this.ResponseCode = ctx.response.ResponseCode; 11 | this.ServerResponse = ctx.response.ServerResponse; 12 | } 13 | 14 | /** 15 | * @feature 返回商品详情 16 | * @param id {Number} 商品id 17 | * @return {Promise.<*>} 18 | */ 19 | async getDetail(id) { 20 | if (!id) return this.ServerResponse.createByErrorCodeMsg(this.ResponseCode.ILLEGAL_ARGUMENT, 'ILLEGAL_ARGUMENT'); 21 | const productRow = await this.ProductModel.findOne({ where: { id } }); 22 | if (!productRow) return this.ServerResponse.createByErrorMsg('不存在或已删除'); 23 | if (productRow.get('status') !== ON_SALE.CODE) return this.ServerResponse.createByErrorMsg('产品已下架'); 24 | return this.ServerResponse.createBySuccessData(productRow.toJSON()); 25 | } 26 | 27 | /** 28 | * @feature 根据产品名称搜索 29 | * @param productName {String} 30 | * @param pageNum {Number} 31 | * @param pageSize {Number} 32 | * @return {Promise.<*>} 33 | */ 34 | async productSearch({ productName, pageNum = 1, pageSize = 10, sortBy = 'asc' }) { 35 | const { count, rows } = await this.ProductModel.findAndCount({ 36 | // attributes: { exclude: ['createTime', 'updateTime'] }, 37 | where: { name: { $like: `%${productName}%` }, status: ON_SALE.CODE }, 38 | order: [[ 'price', sortBy ]], 39 | limit: Number(pageSize | 0), 40 | offset: Number(pageNum - 1 | 0) * Number(pageSize | 0), 41 | }); 42 | if (rows.length < 1) this.ServerResponse.createBySuccessMsg('无产品数据'); 43 | rows.forEach(row => row && row.toJSON()); 44 | return this.ServerResponse.createBySuccessData({ 45 | pageNum, 46 | pageSize, 47 | list: rows, 48 | total: count, 49 | host: this.config.oss.client.endpoint, 50 | }); 51 | } 52 | 53 | /** 54 | * @feature 根据分类信息搜索产品 55 | * @param categoryName {String} 56 | * @param categoryId {Number} 57 | * @param pageNum {Number} 58 | * @param pageSize {Number} 59 | * @return {Promise.<*>} 60 | */ 61 | async getProductListByCategoryId({ categoryName, categoryId, pageNum = 1, pageSize = 10, sortBy = 'asc' }) { 62 | if (!categoryName && !categoryId) return this.ServerResponse.createByErrorCodeMsg(this.ResponseCode.ILLEGAL_ARGUMENT, 'ILLEGAL_ARGUMENT'); 63 | if (categoryName || !categoryId) { 64 | const { id } = this.CategoryModel.findOne({ where: { $like: categoryName } }).then(row => row && row.toJSON()); 65 | categoryId = id; 66 | } 67 | const { data: categoryIdArr } = await this.ctx.service.categoryManageService.getCategoryAndDeepChildCategory(categoryId); 68 | const { count, rows } = await this.ProductModel.findAndCount({ 69 | where: { categoryId: { $in: categoryIdArr }, status: ON_SALE.CODE }, 70 | order: [[ 'price', sortBy ]], 71 | limit: Number(pageSize | 0), 72 | offset: Number(pageNum - 1 | 0) * Number(pageSize | 0), 73 | }); 74 | if (rows.length < 1) this.ServerResponse.createBySuccessMsg('无产品数据'); 75 | rows.forEach(row => row && row.toJSON()); 76 | return this.ServerResponse.createBySuccessData({ 77 | pageNum, 78 | pageSize, 79 | categoryName, 80 | list: rows, 81 | total: count, 82 | host: this.config.oss.client.endpoint, 83 | }); 84 | } 85 | } 86 | 87 | module.exports = ProductService; 88 | -------------------------------------------------------------------------------- /app/service/ShippingService.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service 2 | const _ = require('lodash') 3 | const { isMobilePhone } = require('validator'); 4 | 5 | module.exports = app => class ShippingService extends Service { 6 | constructor (ctx) { 7 | super(ctx) 8 | this.session = ctx.session 9 | this.CartModel = ctx.model.CartModel 10 | this.ProductModel = ctx.model.ProductModel 11 | this.CategoryModel = ctx.model.CategoryModel 12 | this.ShippingModel = ctx.model.ShippingModel 13 | this.ResponseCode = ctx.response.ResponseCode 14 | this.ServerResponse = ctx.response.ServerResponse 15 | } 16 | 17 | /** 18 | * @feature 新建收货地址 19 | * @param shipping {Object} 20 | */ 21 | async add(shipping) { 22 | // 可以用schema 验证数据完整性, 验证手机 23 | // console.log(isMobilePhone(shipping.receiverMobile, 'zh-CN')) 24 | if (!Object.keys(shipping).every(k => !!shipping[k])) return this.ServerResponse.createByErrorCodeMsg(this.ResponseCode.ILLEGAL_ARGUMENT, 'ILLEGAL_ARGUMENT') 25 | const { id: userId } = this.session.currentUser 26 | shipping = { ...shipping, userId } 27 | const shippingRow = await this.ShippingModel.create(shipping) 28 | if (!shippingRow) return this.ServerResponse.createByErrorMsg('新建地址失败') 29 | return this.getAllShipping('新建收货地址成功', shippingRow.toJSON()) 30 | } 31 | 32 | /** 33 | * @feature 更新收货地址 34 | * @param id {Number} 收货地址id 35 | * @param shipping {Object} 36 | */ 37 | async update(shipping, id) { 38 | if (!Object.keys(shipping).every(k => !!shipping[k])) return this.ServerResponse.createByErrorCodeMsg(this.ResponseCode.ILLEGAL_ARGUMENT, 'ILLEGAL_ARGUMENT') 39 | const { id: userId } = this.session.currentUser 40 | const [updateCount, [updateRow]] = await this.ShippingModel.update(shipping, { where: { id, userId }, individualHooks: true }) 41 | if (updateCount < 1) return this.ServerResponse.createByErrorMsg('更新收货地址失败') 42 | return this.getAllShipping('更新收货地址成功', updateRow.toJSON()) 43 | } 44 | 45 | /** 46 | * @feature 删除收货地址 47 | * @param id {Number} 48 | * @returns {Promise.<*>} 49 | */ 50 | async delete(id) { 51 | if (!id) return this.ServerResponse.createByErrorCodeMsg(this.ResponseCode.ILLEGAL_ARGUMENT, 'ILLEGAL_ARGUMENT') 52 | const { id: userId } = this.session.currentUser 53 | const deleteCount = await this.ShippingModel.destroy({ where: { userId, id } }) 54 | if (deleteCount < 1) return this.ServerResponse.createByErrorMsg('删除收货地址失败') 55 | return this.getAllShipping('删除收货地址成功') 56 | } 57 | 58 | /** 59 | * @feature 获取收货地址列表 60 | * @param msg? {String} 返回msg 61 | * @returns {Promise.<*>} 62 | */ 63 | async getAllShipping(msg) { 64 | const { id: userId } = this.session.currentUser 65 | const allShippingArr = await this.ShippingModel.findAll({ 66 | where: { userId } 67 | }).map(r => r && r.toJSON()) 68 | return this.ServerResponse.createBySuccessMsgAndData(msg, allShippingArr) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/service/UserService.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service; 2 | const md5 = require('md5'); 3 | const _ = require('lodash'); 4 | const uuidv1 = require('uuid/v1'); 5 | const { salt } = require('../common/property'); 6 | const { USERNAME, EMAIL } = require('../common/type'); 7 | const { ROLE_ADMAIN, ROLE_CUSTOMER } = require('../common/role'); 8 | 9 | const TOKEN = 'token_'; 10 | 11 | class UserService extends Service { 12 | constructor(ctx) { 13 | super(ctx); 14 | this.session = ctx.session; 15 | this.UserModel = ctx.model.UserModel; 16 | this.ResponseCode = ctx.response.ResponseCode; 17 | this.ServerResponse = ctx.response.ServerResponse; 18 | } 19 | /** 20 | * 21 | * @param field {String} 22 | * @param value {String} 23 | * @return {Promise.} 24 | */ 25 | async _checkExistColByField(field, value) { 26 | const data = await this.UserModel.findOne({ 27 | attributes: [ field ], 28 | where: { [field]: value }, 29 | }); 30 | return !!data; 31 | } 32 | 33 | /** 34 | * @feature 校验 username email 35 | * @param value {String} 36 | * @param type {String} 37 | * @return ServerResponse.msg 38 | */ 39 | async checkValid(type, value) { 40 | if (type.trim()) { 41 | if (USERNAME === type) { 42 | return await this._checkExistColByField(USERNAME, value) 43 | ? this.ServerResponse.createByErrorMsg('用户名已存在') 44 | : this.ServerResponse.createBySuccessMsg('用户名不存在'); 45 | } 46 | if (EMAIL === type) { 47 | return await this._checkExistColByField(EMAIL, value) 48 | ? this.ServerResponse.createByErrorMsg('邮箱已存在') 49 | : this.ServerResponse.createBySuccessMsg('邮箱不存在'); 50 | } 51 | } 52 | return this.ServerResponse.createByErrorMsg('参数错误'); 53 | } 54 | 55 | async login(username, password) { 56 | // 用户名存在报错 57 | const validResponse = await this.checkValid(USERNAME, username); 58 | if (validResponse.isSuccess()) return validResponse; 59 | 60 | // 检查密码是否正确 61 | const user = await this.UserModel.findOne({ 62 | attributes: [ 'id', 'username', 'email', 'phone', 'role' ], 63 | where: { 64 | username, 65 | password: md5(password + salt), 66 | }, 67 | }); 68 | 69 | if (!user) return this.ServerResponse.createByErrorMsg('密码错误'); 70 | 71 | const userInfo = user.toJSON() 72 | let redirectTo 73 | if (userInfo.role === ROLE_ADMAIN) redirectTo = '/' 74 | else redirectTo = '' 75 | 76 | return this.ServerResponse.createBySuccessMsgAndData('登录成功', { ...userInfo, redirectTo }); 77 | } 78 | 79 | /** 80 | * @feature 注册, 只能注册为ROLE_CUSTOMER, ROLE_ADMAIN 需要管理员授权 81 | * @param user {Object} { username, password, ... } 82 | * @return {Promise.} 83 | */ 84 | async register(user) { 85 | // 用户名存在报错 86 | const validUsernameResponse = await this.checkValid(USERNAME, user.username); 87 | if (!validUsernameResponse.isSuccess()) return validUsernameResponse; 88 | // 邮箱存在报错 89 | const validEmailResponse = await this.checkValid(EMAIL, user.email); 90 | if (!validEmailResponse.isSuccess()) return validEmailResponse; 91 | 92 | try { 93 | user.role = ROLE_CUSTOMER; 94 | user.password = md5(user.password + salt); 95 | user = await this.UserModel.create(user, { 96 | attributes: { exclude: [ 'password', 'role', 'answer' ] }, 97 | }); 98 | if (!user) return this.ServerResponse.createByErrorMsg('注册失败'); 99 | 100 | user = user.toJSON(); 101 | _.unset(user, 'password'); 102 | 103 | return this.ServerResponse.createBySuccessMsgAndData('注册成功', user); 104 | } catch (e) { 105 | console.log(e); 106 | return this.ServerResponse.createByErrorMsg('注册失败'); 107 | } 108 | } 109 | 110 | async selectQuestion(username) { 111 | const validResponse = await this.checkValid(USERNAME, username); 112 | if (validResponse.isSuccess()) return this.ServerResponse.createByErrorMsg('用户不存在'); 113 | const question = await this.UserModel.findOne({ 114 | attributes: [ 'question' ], 115 | where: { username }, 116 | }); 117 | if (question) return this.ServerResponse.createBySuccessData(question); 118 | return this.ServerResponse.createByErrorMsg('找回密码的问题是空的'); 119 | } 120 | 121 | // 找回密码答案验证tokan 122 | async checkAnswer(username, question, answer) { 123 | const user = await this.UserModel.findOne({ 124 | attributes: [ 'username', 'question', 'answer' ], 125 | where: { username, question, answer }, 126 | }); 127 | if (user) { 128 | // 答案正确 129 | const forgetToken = uuidv1(); 130 | await this.app.redis.set(TOKEN + username, forgetToken); 131 | await this.app.redis.expire(TOKEN + username, 12 * 60 * 60); 132 | return this.ServerResponse.createBySuccessMsgAndData('问题回答正确', { token: forgetToken }); 133 | } 134 | return this.ServerResponse.createByErrorMsg('问题的答案错误'); 135 | } 136 | 137 | /** 138 | * @feature 重置密码 139 | * @param username {String} 140 | * @param passwordNew {String} 141 | * @param forgetToken {String} 142 | * @return ServerResponse 143 | */ 144 | async forgetRestPassword(username, passwordNew, forgetToken) { 145 | if (!forgetToken) return this.ServerResponse.createByErrorMsg('参数错误,token必须传递'); 146 | // 用户不存在 147 | const validResponse = await this.checkValid(USERNAME, username); 148 | if (validResponse.isSuccess()) return this.ServerResponse.createByErrorMsg('用户不存在'); 149 | // token缓存 150 | const token = await this.app.redis.get(TOKEN + username); 151 | if (!token) return this.ServerResponse.createByErrorMsg('token无效或者过期'); 152 | // 比较token 153 | if (_.eq(token, forgetToken)) { 154 | // 修改密码 155 | const [ rowCount ] = await this.UserModel.update({ 156 | password: md5(passwordNew + salt), 157 | }, { where: { username }, individualHooks: true }); 158 | if (rowCount > 0) return this.ServerResponse.createBySuccessMsg('修改密码成功'); 159 | return this.ServerResponse.createBySuccessMsg('修改密码失败'); 160 | } return this.ServerResponse.createBySuccessMsg('token错误, 请重新获取充值密码的token'); 161 | } 162 | 163 | /** 164 | * @feature 在线修改密码 165 | * @param passwordOld {String} 166 | * @param passwordNew {String} 167 | * @param currentUser {Object} [id]: 防止横向越权 168 | * @return ServerResponse 169 | */ 170 | async resetPassword(passwordOld, passwordNew, currentUser) { 171 | const result = await this.UserModel.findOne({ 172 | attributes: [ 'username' ], 173 | where: { id: currentUser.id, password: md5(passwordOld + salt) }, 174 | }); 175 | if (!result) return this.ServerResponse.createByErrorMsg('旧密码错误'); 176 | const [ rowCount ] = await this.UserModel.update({ 177 | password: md5(passwordNew + salt), 178 | }, { where: { username: currentUser.username }, individualHooks: true }); 179 | if (rowCount > 0) return this.ServerResponse.createBySuccessMsg('修改密码成功'); 180 | return this.ServerResponse.createByErrorMsg('更新密码失败'); 181 | } 182 | 183 | /** 184 | * @feature 更新用户信息 185 | * @param userInfo 186 | * @param currentUser 187 | * @return {Promise.} 188 | */ 189 | async updateUserInfo(userInfo, currentUser) { 190 | // username 不能被更新 191 | // email 校验email 是否存在,并且email 存在不是当前currentUser 192 | const result = await this.UserModel.findOne({ 193 | attributes: [ 'email' ], 194 | where: { 195 | email: userInfo.email, 196 | id: { $not: currentUser.id }, 197 | // '$not': [ { id: currentUser.id } ] 198 | }, 199 | }); 200 | if (result) return this.ServerResponse.createByErrorMsg('email已经存在, 请更换'); 201 | const [ updateCount, [ updateRow ]] = await this.UserModel.update(userInfo, { 202 | where: { id: currentUser.id }, 203 | individualHooks: true, 204 | }); 205 | const user = _.pickBy(updateRow.toJSON(), (value, key) => { 206 | return [ 'id', 'username', 'email', 'phone' ].find(item => key === item); 207 | }); 208 | if (updateCount > 0) return this.ServerResponse.createBySuccessMsgAndData('更新个人信息成功', user); 209 | return this.ServerResponse.createByError('更新个人信息失败'); 210 | } 211 | 212 | /** 213 | * 获取用户信息 214 | * @param {String} userId session下的 id 215 | * @return {Promise.} 216 | */ 217 | async getUserInfo(userId) { 218 | const user = await this.UserModel.findOne({ 219 | attributes: [ 'id', 'username', 'email', 'phone', 'question' ], 220 | where: { id: userId }, 221 | }); 222 | if (!user) return this.ServerResponse.createByErrorMsg('找不到当前用户'); 223 | return this.ServerResponse.createBySuccessData(user.toJSON()); 224 | } 225 | 226 | /** 227 | * @Reconstruction 中间件 228 | * @featrue 后台管理校验管理员 229 | * @param user {Object} 230 | * @return {Promise.} 231 | */ 232 | async checkAdminRole(user) { 233 | if (user && user.role === ROLE_ADMAIN) return this.ServerResponse.createBySuccess(); 234 | return this.ServerResponse.createByError(); 235 | } 236 | 237 | /** 238 | * @Reconstruction 中间件 239 | * @featrue 检查是否登录、是否为管理员 240 | * @return {Promise.<*>} 241 | */ 242 | async checkAdminAndLogin() { 243 | const user = this.session.currentUser; 244 | if (!user) return this.ServerResponse.createByErrorCodeMsg(this.ResponseCode.NEED_LOGIN, '用户未登录, 请登录'); 245 | const response = await this.checkAdminRole(user); 246 | if (!response.isSuccess()) return this.ServerResponse.createByErrorMsg('无权限操作, 需要管理员权限'); 247 | return this.ServerResponse.createBySuccess(); 248 | } 249 | } 250 | 251 | module.exports = UserService; 252 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /config/config.default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = appInfo => { 4 | const config = exports = {}; 5 | 6 | // use for cookie sign key, should change to your own and keep security 7 | config.keys = appInfo.name + '_Yid'; 8 | 9 | // add your config here 10 | config.middleware = []; 11 | 12 | config.sequelize = { 13 | dialect: 'mysql', // support: mysql, mariadb, postgres, mssql 14 | database: 'egg_commerce', 15 | host: 'localhost', 16 | port: '3306', 17 | username: 'root', 18 | password: '', 19 | timezone: '+08:00', // 东八时区 20 | }; 21 | 22 | config.redis = { 23 | client: { 24 | port: 6379, // Redis port 25 | host: '127.0.0.1', // Redis host 26 | password: '', 27 | db: 0, 28 | }, 29 | agent: true, 30 | }; 31 | 32 | config.sessionRedis = { 33 | key: 'EGG_SESSION', 34 | maxAge: 24 * 3600 * 1000, 35 | httpOnly: true, 36 | encrypt: false, 37 | }; 38 | 39 | config.security = { 40 | csrf: { 41 | enable: false, 42 | }, 43 | }; 44 | 45 | config.oss = { 46 | client: { 47 | accessKeyId: 'LTAItynAEvcPJHkE', 48 | accessKeySecret: '5cZb18s6ZeBxY6K9duVavWL6Aup7T5', 49 | bucket: 'egg-commerce', 50 | endpoint: 'oss-cn-hangzhou.aliyuncs.com', 51 | timeout: '60s', 52 | }, 53 | }; 54 | 55 | config.multipart = { 56 | // fileSize: '50mb', // default 10M 57 | // whitelist: [ 58 | // '.png' 59 | // ] 60 | }; 61 | 62 | return config; 63 | }; 64 | -------------------------------------------------------------------------------- /config/plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // had enabled by egg 4 | // exports.static = true; 5 | 6 | exports.sequelize = { 7 | enable: true, 8 | package: 'egg-sequelize', 9 | }; 10 | 11 | exports.redis = { 12 | enable: true, 13 | package: 'egg-redis', 14 | }; 15 | 16 | exports.sessionRedis = { 17 | enable: true, 18 | package: 'egg-session-redis', 19 | }; 20 | 21 | exports.oss = { 22 | enable: true, 23 | package: 'egg-oss', 24 | }; 25 | 26 | exports.alinode = { 27 | enable: true, 28 | package: 'egg-alinode', 29 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egg-commerce", 3 | "version": "1.0.0", 4 | "description": "yangran", 5 | "private": true, 6 | "dependencies": { 7 | "alipay-ftof": "^0.1.5", 8 | "alipay-mobile": "^2.2.1", 9 | "egg": "^2.2.1", 10 | "egg-alinode": "^2.0.1", 11 | "egg-multipart": "^2.0.0", 12 | "egg-oss": "^1.1.0", 13 | "egg-redis": "^1.0.2", 14 | "egg-scripts": "^2.5.0", 15 | "egg-sequelize": "^3.1.1", 16 | "egg-session-redis": "^1.0.0", 17 | "lodash": "^4.17.5", 18 | "lru-cache": "^4.1.1", 19 | "md5": "^2.2.1", 20 | "moment": "^2.20.1", 21 | "mysql2": "^1.5.2", 22 | "qr-image": "^3.2.0", 23 | "stream-wormhole": "^1.0.3", 24 | "uuid": "^3.2.1", 25 | "validator": "^9.4.1" 26 | }, 27 | "devDependencies": { 28 | "autod": "^3.0.1", 29 | "autod-egg": "^1.0.0", 30 | "egg-bin": "^4.3.5", 31 | "egg-ci": "^1.8.0", 32 | "egg-mock": "^3.14.0", 33 | "eslint": "^4.11.0", 34 | "eslint-config-egg": "^6.0.0", 35 | "sequelize-auto": "^0.4.29", 36 | "webstorm-disable-index": "^1.2.0" 37 | }, 38 | "engines": { 39 | "node": ">=8.9.0" 40 | }, 41 | "scripts": { 42 | "start": "ENABLE_NODE_LOG=YES egg-scripts start --daemon --title=egg-server-egg-commerce -p 3000", 43 | "stop": "egg-scripts stop --title=egg-server-egg-commerce", 44 | "dev": "egg-bin dev --port 7001", 45 | "debug": "egg-bin debug", 46 | "test": "npm run lint -- --fix && npm run test-local", 47 | "test-local": "egg-bin test", 48 | "cov": "egg-bin cov", 49 | "lint": "eslint .", 50 | "ci": "npm run lint && npm run cov", 51 | "autod": "autod", 52 | "migrate:new": "egg-sequelize migration:create", 53 | "migrate:up": "egg-sequelize db:migrate", 54 | "migrate:down": "egg-sequelize db:migrate:undo" 55 | }, 56 | "ci": { 57 | "version": "8" 58 | }, 59 | "repository": { 60 | "type": "git", 61 | "url": "" 62 | }, 63 | "author": "yangran", 64 | "license": "MIT" 65 | } 66 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const egg = require('egg'); 2 | 3 | const workers = Number(process.argv[2] || require('os').cpus().length); 4 | egg.startCluster({ 5 | workers, 6 | baseDir: __dirname, 7 | }); -------------------------------------------------------------------------------- /test/app/controller/home.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { app, assert } = require('egg-mock/bootstrap'); 4 | 5 | describe('test/app/controller/home.test.js', () => { 6 | 7 | it('should assert', function* () { 8 | const pkg = require('../../../package.json'); 9 | assert(app.config.keys.startsWith(pkg.name)); 10 | 11 | // const ctx = app.mockContext({}); 12 | // yield ctx.service.xx(); 13 | }); 14 | 15 | it('should GET /', () => { 16 | return app.httpRequest() 17 | .get('/') 18 | .expect('hi, egg') 19 | .expect(200); 20 | }); 21 | }); 22 | --------------------------------------------------------------------------------