关于eggjs:eggjs-连接远程-mysql-数据库

作为一个正在学习后端常识的菜鸟,最后存储数据的形式是通过 fs 模块存到本地的 JSON 文件中的。起初感觉太 low 还是得用上 mysql。 服务器 mysql 装置我是在腾讯云的宝塔面板界面化装置的,本地装置能够去这里下载并装置。装置实现后,试了一些命令确定装置胜利。 # 查看 mysql 的状态$ systemctl status mysqld# 启动 mysql$ systemctl start mysqld# 应用 root 权限登录 mysql$ mysql -u root -p题外话,忽然想起来无论是 nginx 还是 mysql 的启动和查看状态都是用的 systemctl 命令。 $ systemctl start nginx$ systemctl status nginxmysql 连贯我的 node 我的项目用到的是 egg.js。文档十分敌对,很粗疏的阐明了如何连贯数据库,详见MySQL - Egg。 装置依赖库$ npm i --save egg-mysql开启插件// config/plugin.jsexports.mysql = { enable: true, package: 'egg-mysql',};配置数据库信息// config/config.${env}.jsexports.mysql = { // 单数据库信息配置 client: { // host host: 'mysql.com', // 端口号 port: '3306', // 用户名 user: 'test_user', // 明码 password: 'test_password', // 数据库名 database: 'test', }, // 是否加载到 app 上,默认开启 app: true, // 是否加载到 agent 上,默认敞开 agent: false,};遇到的问题无法访问近程服务器发现是我的腾讯云服务器的 3306 端口未凋谢,关上之后就好了。 ...

February 27, 2023 · 1 min · jiezi

关于eggjs:初始Egg框架

前言作为一名前端从业者不会点后端的常识怎么能够。node.js成为了前端理解后端的首选。工欲善其事,必先利其器本。一款好的框架。是提效的基本。这是一篇从0到1入门egg的文章。 三者区别与分割Express是基于 Node.js平台,疾速、凋谢、极简的 Web 开发框架,老牌框架,很多风行的框架的基石,简略且扩展性强,适宜做集体我的项目,本身短少束缚。Koa是基于Node.js平台的下一代web框架,由Express原班人马打造。特点:玲珑灵便简略易用。作为企业级开发过于根底。Egg为企业级框架和利用而生,奉行约定优于配置。Egg 继承于 Koa特点: 提供基于 Egg [定制下层框架]的能力高度可扩大的[插件机制]内置[多过程治理]基于 [Koa]开发,性能优异框架稳固,测试覆盖率高[渐进式开发] 起步初始化我的项目:(脚手架) $ mkdir project-name//创立一个空的文件夹$ npm init egg --type=simple//simple示意骨架类型 $ npm install || i //装置依赖初始化我的项目后构造目录如图所示启动我的项目 $ npm run dev//开发环境中应用$ npm run start//生产环境中应用文件目录介绍次要文件目录介绍 |-app//次要开发的文件| |-- controller//解析用户的输出,解决后返回相应的后果| |-- db//启动mongodb数据库的dbpath门路(可选)| |--extend//框架的扩大(内置对象扩大)| | |---application.js//(固定名称)| | |---context.js//(固定名称)| | |---request.js//(固定名称)| | |---response.js//(固定名称)| | |---helper.js//(固定名称)| |--middleware//编写中间件| |--model//数据库中表的模型| |--publie//动态资源| |--schedule//定时工作| |--service//编写业务逻辑层| |--view//模板文件| |---router.js//配置 URL 路由|-config//寄存配置文件| |--config.default.js//用于编写配置文件| |--plugin.js//配置须要加载的插件|-test//寄存单元测试|-logs//日志文件|-package.json//我的项目形容内置对象Application//全局利用对象=》继承于koaContext//申请级别对象=》继承于koaRequest//申请级别对象=》继承于koaResponse//申请级别对象=》继承于koaController//基类Service//基类Helper//提供一些实用的 utility 函数,本身是一个类Config//配置Logger//功能强大的日志性能Subscription//基类定时工作 路由(router)路由的作用:用来形容申请 URL 和具体承当执行动作的 Controller 的对应关系,用于对立所有路由规定。根本应用办法:在app/router.js中定义url的规定 'use strict';module.exports = app => { const { router, controller } = app; //注册接口 router.get('/logon', controller.logon.Logon);/***路由参数阐明*1.get申请*2.url为/logon*3.执行controller文件夹下的logon文件中的Logon办法**/ };路由实战1.参数获取如果是get申请 ...

March 1, 2022 · 2 min · jiezi

关于eggjs:解决eggmysql插件连接不上mysql问题

解决egg-mysql连贯不上MySql服务器报错:Client does not support authentication protocol requested by server; consider upgrading MySQL client 问题起因通过相干问题查阅,发现是因为navicat版本的问题造成连贯失败。mysql8 之前的版本中加密规定是mysql_native_password,而在mysql8之后,加密规定是caching_sha2_password MySql查看版本号-1 LITING:~ liting$ mysql -uroot -p // 进入mysqlEnter password: //输出mysql明码,如下提醒示意登录胜利Welcome to the MySQL monitor. Commands end with ; or \g.Your MySQL connection id is 24Server version: 8.0.14 MySQL Community Server - GPLCopyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> MySql查看版本号-2(能够进入mysql后通过mysql命令查看) ...

February 23, 2022 · 2 min · jiezi

关于eggjs:Eggjs使用redis实现跨域缓存Fetch发送跨域请求

前后端拆散开发时,咱们的前端申请是跨域申请,会造成session和cookie生效的问题。在浏览多种解决办法后,我抉择了应用redis来实现session的解决方案,确保前端应用跨域申请的状况下,后端能够维持用户session. 起因为什么抉择redis来实现跨域下的session呢?我浏览了多种跨域session失落的解决办法,但都没有失效,于是最初抉择了redis才解决了这个问题。 前后端增加credential后,浏览器无奈主动加载第三方cookie, 服务端的session和cookie依然失落。 前端Fetch跨域申请const data = {key:"value"};// 参数const myHeaders = new Headers();myHeaders.append('Access-Control-Allow-Origin', 'http://localhost:3000/'); // 设置Access-Control-Allow-Credentials,跨域申请带受权myHeaders.append('Access-Control-Allow-Credentials', 'true'); // 设置mode为cors,进行跨域申请myHeaders.append('mode', 'cors'); // 设置Content-Type, 参数格局myHeaders.append('Content-Type', 'application/x-www-form-urlencoded')var urlencoded = new URLSearchParams()for (let key in data) { urlencoded.append(key, data[key])}const requestOptions = { method: 'POST', headers: myHeaders, body: urlencoded, redirect: 'follow', // 设置为include确保跨域申请放弃cookie credentials: 'include', }fetch(api, requestOptions).then(response=>response.json());Egg跨域配置// config.default.js'use strict'const path = require('path')module.exports = appInfo => { const config = { mode: 'file', errorHandler: { match: '/' }, } // 设置security里的csrf敞开,容许跨域申请 config.security = { domainWhiteList: ['*'], csrf: { enable: false } } // 设置cors插件配置, origin为前端的起源(带credentials无奈设置为*) // 设置credentials为true,返回的Header带有Access-Control-Allow-Credentials为true config.cors = { origin: 'http://localhost:3000', credentials: true, allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH' } // 这里设置egg自带的session,但跨域申请依然失落session config.session = { key: 'WheelFit', maxAge: 30 * 24 * 3600 * 1000, // 30 days httpOnly: true, encrypt: true, renew: true } // 自定义中间件, sessionToken为鉴权中间件,在收到申请后获取session里存储的token config.middleware = ['sessionToken', 'errorHandler'] return { ...config, }}// plugin.js'use strict';// 增加cors插件用于回复跨域申请// npm i --save egg-corsmodule.exports = { cors: { enable: true, package: 'egg-cors', }};这个配置是在其余的解决跨域session失落问题的文章中找到的,实践上增加credientals即可放弃session,但我这里并没有失效,跨域申请依然会失落session,并且给response设置的cookie也不能被浏览器获取到。然而应用postman,发送非跨域申请,是能够记录的session的。 ...

November 23, 2021 · 2 min · jiezi

关于eggjs:eggjs-框架安全

/** * security options * @member Config#security * @property {String} defaultMiddleware - default open security middleware * @property {Object} csrf - whether defend csrf attack * @property {Object} xframe - whether enable X-Frame-Options response header, default SAMEORIGIN * @property {Object} hsts - whether enable Strict-Transport-Security response header, default is one year * @property {Object} methodnoallow - whether enable Http Method filter * @property {Object} noopen - whether enable IE automaticlly download open * @property {Object} nosniff - whether enable IE8 automaticlly dedect mime * @property {Object} xssProtection - whether enable IE8 XSS Filter, default is open * @property {Object} csp - content security policy config * @property {Object} referrerPolicy - referrer policy config * @property {Object} dta - auto avoid directory traversal attack * @property {Array} domainWhiteList - domain white list * @property {Array} protocolWhiteList - protocal white list */ exports.security = { domainWhiteList: [], protocolWhiteList: [], defaultMiddleware: 'csrf,hsts,methodnoallow,noopen,nosniff,csp,xssProtection,xframe,dta', csrf: { enable: true, // can be ctoken or referer or all type: 'ctoken', ignoreJSON: false, // These config works when using ctoken type useSession: false, // can be function(ctx) or String cookieDomain: undefined, cookieName: 'csrfToken', sessionName: 'csrfToken', headerName: 'x-csrf-token', bodyName: '_csrf', queryName: '_csrf', // These config works when using referer type refererWhiteList: [ // 'eggjs.org' ], }, xframe: { enable: true, // 'SAMEORIGIN', 'DENY' or 'ALLOW-FROM http://example.jp' value: 'SAMEORIGIN', }, hsts: { enable: false, maxAge: 365 * 24 * 3600, includeSubdomains: false, }, dta: { enable: true, }, methodnoallow: { enable: true, }, noopen: { enable: true, }, nosniff: { enable: true, }, referrerPolicy: { enable: false, value: 'no-referrer-when-downgrade', }, xssProtection: { enable: true, value: '1; mode=block', }, csp: { enable: false, policy: {}, }, ssrf: { ipBlackList: null, checkAddress: null, }, };版权申明:本文为CSDN博主「beginnboyer」的原创文章,遵循CC 4.0 BY-SA版权协定,转载请附上原文出处链接及本申明。原文链接:https://blog.csdn.net/wenrenn... ...

July 2, 2021 · 2 min · jiezi

关于eggjs:eggmonoose-Cannot-read-property-Schema-of-undefined

问题这周有个需要是应用 egg.js 接入 MongoDB 数据库,于是先依照官网举荐的形式应用 egg-mongoose,然而在依照文档进行操作写 demo 时呈现了十分诡异的谬误TypeError: Cannot read property 'Schema' of undefined,app.mongoose app 下的 mongoose 没有被失常挂载上。 // config/plugin.jsmodule.exports = { ... 省略其余不相干配置 mongoose: { enable: true, package: 'egg-mongoose', },};// config/config.default.jsmodule.exports = appInfo => { /** * built-in config * @type {Egg.EggAppConfig} **/ const config = exports = {}; ... 省略其余不相干配置 config.mongoose = { client: { url: 'mongodb://xxxx:30000', options: { mongos: true, dbName: 'xxx', user: 'xxx', pass: 'xxx', }, }, }; return { ...config, };}// {app_root}/app/model/user.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const UserSchema = new Schema({ userName: { type: String }, password: { type: String }, }); return mongoose.model('User', UserSchema);}排查思路首先确认有没有拼写错误,没有的话间接用最小化形式应用 egg-mongoose 是否能够复现来确认是不是文档呈现了问题。测试后发现失常。接下来就是排查本地代码,发现是因为 egg-sequelize 导致的,而局部业务接口之前配置了应用 MySQL 数据库。 egg-sequelize 和 egg-mongoose 默认都是加载 app/model 下的文件,具体逻辑后续详说。 ...

January 2, 2021 · 1 min · jiezi

关于eggjs:如何用Eggjs从零开始开发一个项目2

在上一篇文章,咱们曾经应用Sequelize连贯上了数据库,并能进行简略的数据库操作,在此基础上,咱们试着来开发一个残缺的我的项目。这篇文章咱们从用户的注册、登录着手,试着开发用户模块的相干的代码。 用户注册1. 注册逻辑用户注册的逻辑很简略: 客户端:用户输出输出账号,明码等信息进行用户注册;服务端:接管到客户端提交的注册信息后,进行字段的测验(是否必填、字段长度等),字段符合要求后,依据用户注册的账号查询数据库,依据返回后果判断该用户是否是新用户,如果是新用户,将用户信息写入到数据库,实现注册流程。2. 用户明码解决客户端用户提交数据后,服务端验证通过进行数据库写入,然而其中用户明码是敏感信息,为了服务平安思考,不能间接将明文明码写入到数据库,避免数据库被攻打,用户明码泄露。所以个别在存储用户明码时,会先对用户明码进行加盐加密解决,这样哪怕数据库存储的明码泄露,其他人也无奈通过解决后的明码进行登录。这里应用哈希算法对明码进行加密,因为哈希的个性是不可逆,具体的细节能够参考为什么说 MD5 是不可逆的?。阐明:哈希能够被暴力破解,加盐能够很大水平上减少破解难度。 废话不多说,咱们上面来写代码。 首先,咱们装置一下bcryptjs,咱们应用它对明码进行加盐加密和比对: npm install bcryptjs --save而后咱们把这两个办法写到app/extend/helper.js: const bcrypt = require('bcryptjs');module.exports = { encrypt(password) { const salt = bcrypt.genSaltSync(5); //加盐 const hash = bcrypt.hashSync(password, salt); //哈希(同步调用) return hash; }, compare(password, hash) { return bcrypt.compareSync(password, hash); //比对 }};这样咱们在我的项目里就能够通过this.ctx.helop.encrypt和this.ctx.helop.compare的形式去应用这些专用的办法了。 而后,在UserController中增加一个register办法: async register() { const params = this.ctx.request.body; // 参数校验 if (!params.name || !params.password || !params.phone || !params.email) { this.ctx.body = { code: '500', msg: '参数不非法' }; } // 查问该用户是否曾经注册 const user = await this.ctx.model.User.findOne({ where: { name: params.name } }); if (user) { this.ctx.body = { code: '500', msg: '该用户已存在' }; } // 插入数据库 const result = await this.ctx.model.User.create({ ...params, password: this.ctx.helper.encrypt(params.password) }); if (result) { this.ctx.body = { code: '200', msg: '注册胜利' }; }}最初,增加路由: ...

December 23, 2020 · 2 min · jiezi

关于eggjs:实战搭建完整的IM即时通讯应用2

即时通讯应用服务,整套蕴含服务端、治理端和客户端,欢送Star反对和查看源码。 现已部署上线,欢送体验客户端和治理端 咱们书接上文,持续实现残缺的即时通讯服务,这篇着重讲下Server端我的项目中我认为几个重要的点,大部分内容须要去我的仓库源码和 egg 官网查看。 server 端具体阐明应用脚手架npm init egg --type=simple初始化 server 我的项目,装置 mysql(我的是 8.0 版本),配置上 sequelize 所需的数据库链接明码等,就能够启动了着重讲下 Server 端我的项目中我认为几个重要的点,大部分内容须要去 egg 官网查看。 // 目录构造阐明├── package.json // 我的项目信息├── app.js // 启动文件,其中有一些钩子函数├── app| ├── router.js // 路由│ ├── controller│ ├── service│ ├── middleware // 中间件│ ├── model // 实体模型│ └── io // socket.io 相干│ ├── controller│ └── middleware // io独有的中间件├── config // 配置文件| ├── plugin.js // 插件配置文件| └── config.default.js // 默认的配置文件├── logs // server运行期间产生的log文件└── public // 动态文件和上传文件目录路由Router 次要用来形容申请 URL 和具体承当执行动作的 Controller 的对应关系,即 app/router ...

July 20, 2020 · 4 min · jiezi

Egg-React-SSR-服务端渲染-Webpack-构建流程

1. 本地Egg项目启动 首先执行node index.js 或者 npm run dev 启动 Egg应用在 Egg Agent 里面启动koa服务, 同时在koa服务里面启动Webpack编译服务挂载Webpack内存文件读取方法覆盖本地文件读取的逻辑app.react.render = (name, locals, options) => { const filePath = path.isAbsolute(name) ? name : path.join(app.config.view.root[0], name); const promise = app.webpack.fileSystem.readWebpackMemoryFile(filePath, name); return co(function* () { const code = yield promise; if (!code) { throw new Error(`read webpack memory file[${filePath}] content is empty, please check if the file exists`); } // dynamic execute javascript const wrapper = NativeModule.wrap(code); vm.runInThisContext(wrapper)(exports, require, module, __filename, __dirname); const reactClass = module.exports; if (options && options.markup) { return Promise.resolve(app.react.renderToStaticMarkup(reactClass, locals)); } return Promise.resolve(app.react.renderToString(reactClass, locals)); });};Worker 监听Webpack编译状态, 检测Webpack 编译是否完成, 如果未完成, 显示Webpack 编译Loading, 如果编译完成, 自动打开浏览器Webpack编译完成, Agent 发送消息给Worker,  Worker检测到编译完成, 自动打开浏览器, Egg服务正式可用 ...

November 3, 2019 · 1 min · jiezi

基于阿里egg框架搭建博客2HelloWorld

相关文章基于阿里egg框架搭建博客(1)——开发准备基于阿里egg框架搭建博客(2)——Hello World基于阿里egg框架搭建博客(3)——注册与登录基于阿里egg框架搭建博客(4)——权限控制基于阿里egg框架搭建博客(5)——置顶导航条基于阿里egg框架搭建博客(6)——浏览、发表文章基于阿里egg框架搭建博客(7)——编辑文章 githttps://github.com/ZzzSimon/e...喜欢就点个赞吧! 正文这是必备的Hello World章节,本章节将不使用脚手架,逐步创建一个hello world web应用。 初始化项目先来初始化下目录结构: $ mkdir egg-hello-world$ cd egg-hello-world$ npm init$ npm i egg --save$ npm i egg-bin --save-dev执行完成后如下图所示:除了下载的node模块以外什么都没有,不要着急,我们接下来会一点点创建。 添加 npm scripts 到 package.json: { "name": "egg-example", "scripts": { "dev": "egg-bin dev" }}编写Controller上一节我们知道,controller是需要放在app/controller/目录下的,所以我创建helloWorld.js文件: // app/controller/home.jsconst Controller = require('egg').Controller;class HelloWorldController extends Controller { async index() { this.ctx.body = 'Hello World'; }}module.exports = HelloWorldController;编写路由规则egg将路由交由app/router.js管理,于是我们在app/目录下创建router.js文件: // app/router.jsmodule.exports = app => { const { router, controller } = app; router.get('/', controller.helloWorld.index);};配置文件最后加上一个配置config/config.default.js文件:注意:config与app为同级目录! ...

October 14, 2019 · 1 min · jiezi

eggjs-eggtx-接口级事务管理插件

egg-tx 一个 egg 事务插件,它支持 Mysql、Mongo 数据库,它能做到请求接口级别的事务管理。 依赖的插件对于使用 Mysql 数据库你需要开启 egg-sequelize 插件。对于使用 Mongo 数据库你需要开启 egg-mongoose 插件。安装$ npm i egg-tx --save开启插件// {app_root}/config/plugin.jsexports.tx = { enable: true, package: 'egg-tx',};配置// {app_root}/config/config.default.jsexports.tx = { reqAction:['POST','PUT','DELETE'], dbType:'mysql'};reqAction:将为指定动作的所有请求进行事务管理,该数组的值可为 GET、POST、PUT、DELETE、HEAD、PATCH、OPTIONS(默认值为 POST、PUT、DELETE)dbType:所使用的数据库类型,该值可为 mysql 或 mongo (默认值为 mysql)使用例子你可以通过 ctx.tx.session 获取到本次请求的事务会话对象,前提是它已经被事务管理器所管理。 mysqlawait this.ctx.model.User.create(user, { transaction: this.ctx.tx.session,});mongoawait this.ctx.model.User.insertMany([ { username: 'lyTongXue', password: '123456' },], { session: this.ctx.tx.session });注解@tx使用该注解的接口方法将会进行事务管理,即便 reqAction 配置项中未包含该动作类型的请求。 // {app_root}/app/controller/{controller_name}.js/*** @tx*/async create(){}@txIgnore即便 reqAction 配置项中包含了该动作类型的请求,使用了该注解的接口方法将不会进行事务管理。 // {app_root}/app/controller/{controller_name}.js/*** @txIgnore*/async index(){}提问交流1、接口方法的 jsDoc 是否有一定要求? ...

August 19, 2019 · 1 min · jiezi

小程序egg后台简要文档

小程序egg后台简要文档更多 blog如果不需要后端java或者其他语言支持,对于小型的小程序后台,可以使用eggjs框架快速搭建简要的数据后台。 如果未接触过node编写接口,首先还是需要基本过一下egg官方文档,至少得把快速入门看完。 不会从头开始把每一步都详细写下来,只针对微信对接的一些处理列出来。 数据库使用mongo,示例通过egg-mongoose进行连接处理。 安装插件后,在/config/plugin.js进行基本配置: mongoose: { enable: true, package: 'egg-mongoose'}在/config/config.default.js文件中配置mongodb的连接(保证本地测试环境数据库连接好): // connect mongo config.mongoose = { client: { url: 'mongodb://127.0.0.1/fulishe', options: {}, } }在/app/models文件夹编写相关的model,在程序运行时会自动在mongo上创建对应的表。也可以优先创建好数据库和表设计等。 编写接口在controller写主要的业务逻辑,接受接口请求参数并返回。对于入参,需要进行验证的可以做验证处理,需要处理返回结果,即使请求出错也不要返回非200的状态码。可以将处理结果设置为一个函数,如: // data: 返回给前端的数据,code: 状态,1为成功,0为失败,message:状态信息formatResponse: function (data, code, message) { return { code, data, message }}!> 为避免出现出现问题而导致程序中断,最好在每一个容易出现问题的地方进行try catch将异常抛出并返回到前端。 在service编写数据库操作函数等,通过在controller进行调用,统一管理数据库数据进出。注意在数据新增的时候需要进行save操作: const addUser = await this.ctx.model.Users(data)addUser.save() // 不要遗漏相关的增删改查操作,需要直接点的可以看仓库app/service下的写法。 小程序接口相关以下是eggjs对小程序包括获取openId、获取unionId、获取手机号码、判断用户是否关注公众号、客服信息发送进行编写说明。 如果某个对象不知道是什么,一般都是可以根据名字找到对于的js文件或者通过npm引入,不再表述引入什么了。 获取openId参数说明: APPID: 小程序的appIdSECRET: 小程序的secret,跟appId在同一个地方能找到CODE:小程序在前端通过wx.login()获取的jscodeconst openIdRes = await rp(`https://api.weixin.qq.com/sns/jscode2session?appid=${APPID}&secret=${SECRET}&js_code=${CODE}&grant_type=authorization_code`)const openId = JSON.parse(openIdRes).openId // 在处理错误判断后,返回的数据是json字符串,需要转化获取unionIdunionId,属于微信端通用的账号唯一标识,举个例子就是同一个微信号,唯一对应一个unionId。而在每一个小程序上,用户openId都不一样。可以用于判断在小程序上的用户是否关注公众号等。 ...

July 5, 2019 · 2 min · jiezi

eggjs框架开发环境搭建

1.创建工程目录1.1 创建一个文件夹叫 my-egg-application$ mkdir my-egg-application不熟悉命令行的同学也可以手动创建文件夹 2.初始化项目模板2.1 进入工程目录$ cd my-egg-application2.2 下载项目模板$ npm init egg --type=simple这里的type参数是需要下载的模板名字,具体的值可以参考egg在github上的模板egg-boilerplate-simple。在egg的项目主页中找类似格式的项目名字,如egg-boilerplate-[命令行中type的值] 3.安装项目依赖$ npm install --registry=https://registry.npm.taobao.org在这里下载比较慢的同学可以使用淘宝的cnpm,具体的cnpm安装方法请自己网上搜索,很多类似的教程。我为了方便直接就指定了淘宝仓库地址 https://registry.npm.taobao.org 4.运行服务运行项目就比较简单了,运行npm的命令 $ npm run dev在浏览器中打开地址:http://localhost:7001,就能看到服务已经启动完成。是不是特别简单!!! 5.其它npm run [命令]这是npm相关的使用方法,可以打开项目根目录下的package.json文件 // 这是一个最简单的package.json{ "name": "my-egg-application", "version": "1.0.0", "description": "", "scripts": { "dev": "egg-bin dev", }, "author": "mufeng", "license": "ISC"}在script下面看到了dev命令,它其实运行了egg-bin dev这个命令,egg-bin这个模块以后我会再写一篇文章介绍一下

June 30, 2019 · 1 min · jiezi

nodejs的Web开发框架的选择

node.js的Web开发框架的选择?这个问题貌似在其它的后端开发领域不存在。没错,我说的就是隔壁的Java。我要是写java的应用,可以毫不犹豫的选择Spring。但是node可选择的余地多的多。 现有node服务端框架1. Express、Koaexpress框架肯定不用说了,写node服务这块的同学肯定是非常熟悉的框架了。我早期的时候也是express的粉丝。 优点:express的框架结构非常的简单。经过短暂的学习就可以用来开发一个项目。非常适合作为node新手的入门框架。 缺点:开发阶段:Express的缺点也很明显,由于结构简单,自由度高。每个人会有不同的文件编排方式。前期设计阶段需要人工的把项目约定做好。但是团队来新人了,又要重新学习项目约定,无形中增加了学习成本。说到底还是缺乏项目的工程化约束。在项目的开发初期需要自己手工的搭建一些通用的脚手架代码,来方便的之后的开发工作。开发流程会拖的比较长。 运维阶段:由于node单进程,js主线程运行的机制。如果在js主线程中没有做好错误的处理。会导致进程意外退出的问题。这在项目运行阶段是不可接受的问题。需要进程守护的机制来保证程序的健壮性。Express和Koa需要依赖第三方的工具来实现。如PM2。讲道理这些功能应该是一个web开发框架应该具备的基础功能。 总结:不管是Express还是Koa框架。还是处于比较简单的基于http模块的封装。在Reuest和Response这两个对象基础上进行扩展开发。我们业务开发团队需要的是稳定、快速的开发框架。实际开发中往往需要在Express和Koa的基础上封装大量的代码,来适应不同的业务场景,这对追求快速开发的互联网行业是不受欢迎的。 2. Egg.js我在2018年3月份开始接触egg框架。发现这是一个具备较完善功能的web开发框架。 优点:方便、好用、少写很多的脚手架级别的代码。专注于业务逻辑的开发。内建插件机制,兼容koa插件。约定大于配置。内置多进程管理。阿里巴巴开源。文档是中文的。估计没点自虐倾向的同学一般都会选择母语版本的文档来看吧。 缺点:由于目前的使用层面还不够深入。除了对应用配置方式的不太满意外,没有发现大的开发痛点。项目开发实践下来,开发效率杠杠的。 总结:估计写到这里,应该能看出我对egg框架的喜爱程度了。那么下面学习一下egg入门。

June 30, 2019 · 1 min · jiezi

学习nodejs服务开发这一篇就够了持续更新中

开头要说的话接触node.js后端开发也有几年时间了。经过几个项目的实践。不可否认,在后端服务领域。node.js还是有一定的用武之地的。当然在平时的实践中也发现了node体系的一些问题和不足。所以写篇文章分享一下我探索node服务的经历。 目录由于工作繁忙,不定时的会更新的。。。 0. JavaScript语言的学习1. 框架的选择node.js的Web开发框架的选择?Egg.js官网了解一下2. 快速入门3. web综合开发4. 模板引擎Nunjuck的使用5. redis的使用6. 持久层框架的使用---Sequelize7. 持久层框架的使用---Mongoose8. 如何优雅的使用Sequelize9. 如何优雅的开发Router10. egg-security插件的使用11. 定时任务12. 多进程模式开发13. 日志开发的实践14. 全局同一异常处理实践15. 中间件(过滤器)开发实践16. 框架的启动自定义实践17. Service层开发实践18. 一些方便开发的小技巧19. 从Java那边借鉴过来的优秀实践20. 服务的监控21. 容器化部署22. 使用TypeScript开发服务

June 30, 2019 · 1 min · jiezi

基于前端技术生成PDF方案

需求背景业务系统需要预览报告(如产品周报,体检报告等)并生成pdf格式供用户下载,或者定期发送给指定用户报告格式相对固定,由文本,图片和图表组成,基本与前端页面保持一致解决方案需求分为两步:报告预览和报告生成。 报告预览在前端进行展示,可使用前端技术,如React/Vue等技术栈对其进行还原,数据从服务端获取。报告生成需要对第一步生成的HTML进行PDF的转换生成,HTML2PDF的方式又分为两种: 基于canvas的客户端生成方案基于nodejs + puppeteer的服务端生成方案一个完整的案例下面以一个体检报告的案例进行这两种方案的说明:体检报告展示形式如下,格式相对固定,分为四个页面:个人信息页,建议页,原理页,个人信息页与建议页数据来源于服务器。report.png 基于canvas的客户端生成方案canvas是HTML5标准中新增的元素,可用于通过使用JS的脚本来绘制图形。canvas提供了toDataURL/toBlob方法,用于把canvas中的内容转换为图片,API文档如下(来源于MDN): 由于HTML文档再浏览器中是以DOM树的形式存在,所以我们可以通过三步完成HTML到PDF的转换: 将DOM树转换为canvas对象,可使用html2canvas完成将canvas转换为图片,可使用canvas.toDataURL完成将图片转换为PDF,可使用jsPDF完成完整代码实现:https://github.com/simonwoo/d... 截图如下,点击下载按钮可进行pdf生产: 该方案完全基于客户端的方式生成,不需要服务器进行支持。在使用该方案的过程中,发现了一些问题: 生产的PDF比较模糊,质量不高如果HTML中有外链图片,无法生成由于第一步是通过DOM去生成canvas,所以针对特别长的报告,DOM尚未加载完便点击下载时,会造成报告生成问题因为是客户端方案,所以需要用户主动触发生成,但对于一些定期发送给用户的报告,该方案无法使用基于nodejs + puppeteer的服务端生成方案puppeteer是google推出的headless浏览器,即没有图形界面的浏览器,但又可以实现普通浏览器HTML/JS/CSS的渲染,以及其他基本浏览器功能。你可以理解为一个没有界面的Chrome浏览器。主要有以下几种使用场景: 生成页面的截图和PDF抓取SPA并生成预先呈现的内容(即“SSR”)爬虫,从网站抓取你需要的内容自动化测试,自动表单提交,UI测试,键盘输入等创建一个最新的自动化测试环境。使用最新的JavaScript和浏览器功能,直接在最新版本的Chrome中运行测试通过理解puppeteer的功能,我们可以开启一个实例去渲染HTML报告,然后再利用其提供的转换PDF功能进行PDF的生成。 两个重要的API: page.goto(url, [options]) - 打开指定url的文件,可以是本地文件(file://)也可以是网络文件(http://)page.pdf([options]) - 转换页面成PDF文件puppeteer使用一个小例子,将百度网页转换为pdf: 完整代码如下: 前端部分:https://github.com/simonwoo/d...后端部分:https://github.com/simonwoo/d...项目启动流程如下: 进入到webapp目录,使用npm install和npm run start启动前端服务器,地址为:localhost:3000进入到server目录,使用npm install和npm run dev启动node服务器,地址为:localhost:7001整个服务架构如下: node服务器通过路由增加一个pdf生成的controller,该controller通过启动puppeteer实例去加载localhost:3000的页面并生成pdf。直接在浏览器中通过http://localhost:7001/pdf即可访问到生成的pdf. 在实际环境中,前端页面可部署在nginx服务器上或者直接放在Node服务器上,puppeteer也支持使用cookie的操作,这样可以避免一些需要身份认证的问题。 相比客户端生成方式,使用puppeteer生成的pdf质量比较高,可满足生产要求。 本文中提到的两种方案中,均省去了ajax后端请求数据部分,读者可根据需要自行增加。 Referencehtml2canvas - https://html2canvas.hertzen.c...jsPDF - https://github.com/MrRio/jsPDFpuppeteer - https://zhaoqize.github.io/pu...Eggjs - https://eggjs.org/zh-cn/原文链接:https://juejin.im/post/5d036a...

June 15, 2019 · 1 min · jiezi

基于Egg框架的日志链路追踪实践分享

快速导航[Logger-Custom] 需求背景[Logger-Custom] 自定义日志插件开发[Logger-Custom] 项目扩展[Logger-Custom] 项目应用[ContextFormatter] contextFormatter自定义日志格式[Logrotator] 日志切割需求背景实现全链路日志追踪,便于日志监控、问题排查、接口响应耗时数据统计等,首先 API 接口服务接收到调用方请求,根据调用方传的 traceId,在该次调用链中处理业务时,如需打印日志的,日志信息按照约定的规范进行打印,并记录 traceId,实现日志链路追踪。 日志路径约定/var/logs/${projectName}/bizLog/${projectName}-yyyyMMdd.log日志格式约定日志时间[]traceId[]服务端IP[]客户端IP[]日志级别[]日志内容采用 Egg.js 框架 egg-logger 中间件,在实现过程中发现对于按照以上日志格式打印是无法满足需求的(至少目前我还没找到可实现方式),如果要自己实现,可能要自己造轮子了,好在官方的 egg-logger 中间件提供了自定义日志扩展功能,参考 高级自定义日志,本身也提供了日志分割、多进程日志处理等功能。 egg-logger 提供了多种传输通道,我们的需求主要是对请求的业务日志自定义格式存储,主要用到 fileTransport 和 consoleTransport 两个通道,分别打印日志到文件和终端。 自定义日志插件开发基于 egg-logger 定制开发一个插件项目,参考 插件开发,以下以 egg-logger-custom 为项目,展示核心代码编写 编写logger.jsegg-logger-custom/lib/logger.jsconst moment = require('moment');const FileTransport = require('egg-logger').FileTransport;const utils = require('./utils');const util = require('util');/** * 继承 FileTransport */class AppTransport extends FileTransport { constructor(options, ctx) { super(options); this.ctx = ctx; // 得到每次请求的上下文 } log(level, args, meta) { // 获取自定义格式消息 const customMsg = this.messageFormat({ level, }); // 针对 Error 消息打印出错误的堆栈 if (args[0] instanceof Error) { const err = args[0] || {}; args[0] = util.format('%s: %s\n%s\npid: %s\n', err.name, err.message, err.stack, process.pid); } else { args[0] = util.format(customMsg, args[0]); } // 这个是必须的,否则日志文件不会写入 super.log(level, args, meta); } /** * 自定义消息格式 * 可以根据自己的业务需求自行定义 * @param { String } level */ messageFormat({ level }) { const { ctx } = this; const params = JSON.stringify(Object.assign({}, ctx.request.query, ctx.body)); return [ moment().format('YYYY/MM/DD HH:mm:ss'), ctx.request.get('traceId'), utils.serviceIPAddress, utils.clientIPAddress(ctx.req), level, ].join(utils.loggerDelimiter) + utils.loggerDelimiter; }}module.exports = AppTransport;工具egg-logger-custom/lib/utils.jsconst interfaces = require('os').networkInterfaces();module.exports = { /** * 日志分隔符 */ loggerDelimiter: '[]', /** * 获取当前服务器IP */ serviceIPAddress: (() => { for (const devName in interfaces) { const iface = interfaces[devName]; for (let i = 0; i < iface.length; i++) { const alias = iface[i]; if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { return alias.address; } } } })(), /** * 获取当前请求客户端IP * 不安全的写法 */ clientIPAddress: req => { const address = req.headers['x-forwarded-for'] || // 判断是否有反向代理 IP req.connection.remoteAddress || // 判断 connection 的远程 IP req.socket.remoteAddress || // 判断后端的 socket 的 IP req.connection.socket.remoteAddress; return address.replace(/::ffff:/ig, ''); }, clientIPAddress: ctx => { return ctx.ip; },}注意:以上获取当前请求客户端IP的方式,如果你需要对用户的 IP 做限流、防刷限制,请不要使用如上方式,参见 科普文:如何伪造和获取用户真实 IP ?,在 Egg.js 里你也可以通过 ctx.ip 来获取,参考 前置代理模式。 ...

June 5, 2019 · 3 min · jiezi

APubPlat-一款Devops自动化部署持续集成堡垒机开源项目友好的Web-Terminal

嗨、很高心你能进入这里,我是zane,  在这里给你介绍一款完整的Devops自动化部署工具 APubPlat - 一款完整的Devops自动化部署、持续集成、堡垒机、并且友好的Web Terminal开源项目。 如果你对它感兴趣,就给一个小小的关注吧,一款好的产品更需要碰撞和火花。: github address : https://github.com/wangweianger/APubPlat document : http://apub-wiki.seosiwei.com 接下来我还会持续的更新和迭代。 功能描述资产管理: 方便快捷的管理资产,可为资产分组,为应用分配不同的资产,快捷控制台管理等。应用管理:可建立各种应用任务,前端,后端发布任务,可同时执行单机和多机任务,并实时显示任务日志。WEB控制台: 一套强大的Web Terminal,可直接替代Xshell等工具,可单个或批量打开窗口或执行命令(已支持linux系统,后期版本支持windows系统)。脚本管理:可为单个或者多个资产预装各种软装或者执行各种命令,可自由自定义各种预装脚本,例如安装nginx单|多机脚本生成:可同时为单机或者多机器同时生成shell脚本到指定的目录,方便统一管理和操作。备份还原:单多机可同时备份,并按详细日期进行备份,可随时随意一键恢复任意历史版本。应用场景各种前端静态发布(例如:vue,react,jquery之类的纯前端持续集成)前端中间层发布(例如:使用node.js开发的前端中间层之类的服务持续集成)后端发布 (不限制后端语言,只依赖于shell脚本)单机 | 多台机器 同时发布、备份、还原web版本的xshell,让你不管何时何地都能方便的管理服务器资源强大的权限管理能力,为不同角色分配不同的管理权限,让我们的持续集成更灵活更方便安装环境APubPlat依赖的环境并不复杂,对软硬件的要求也并不高,一台1G双核的服务器都能搞定。 APubPlat 开发技术基于egg.js、vue.js, 因此只需要安装node环境,node.js版本推荐 8.9.0 ~ 10.15.1 之间 数据库基于mongudb、环境数据库基于redis、web服务器基于nginx,所有的软件和服务你都可以安装在一台机器中。 如果想了解更多你可以选择去查看项目文档: http://apub-wiki.seosiwei.com 项目预览登录界面、第一次使用时请注册admin账号,其他账号在后台中进行新增和编辑管理 你可以自定义任何适合你的项目环境 资产管理是项目的一个核心能力,所有持续集成都依赖于资产,也是Web Terminal的入口之一 你可以新建任何需要发布和管理的应用,分配相应的资产,可以选择单机部署、部分部署或者全量部署 在这里你可以查看任何时候的应用构建状态、备份状态、生成配置状态 一切的部署都依赖于shell脚本,脚本的正确与否,决定了你的应用是否能部署成功 友好的web化界面部署日志,支持多机,你可以随时掌控部署状态,也可随时终端某台机器的发布 强大的Web Terminal能力,跟xshell工具一样的体验,随时随地管理你的资产吧 感兴趣如果你有那么一点感兴趣,别犹豫先star或者watch,我会持续的更新和迭代,让它成为你开发中的神器吧 github address: https://github.com/wangweianger/APubPlat 如果你也认可我,那也可以给我一个following额 你还可以加入QQ群来尽情的交流吧,一款好的产品更需要碰撞和火花。

June 5, 2019 · 1 min · jiezi

VuejsEggjsMongodb前后端分离的个人网站

AntVueBlogFrontVue.js+Egg.js+Mongodb前后端分离的个人网站博客。博客地址:ANT (ssr渲染参考AntVueBlogFrontSSR) 主要技术栈前端:vue.js、vue-router、vuex AntVueBlogFront后端:Egg.js、Mongodb AntEggBlogService后台管理: vue.js、vue-router、vuex AntVueBlogAdmin博客功能前台页面文档列表分类标签归档评论(暂时关闭)文章检索后台管理发布文章、存为草稿文章管理标签管理分类管理登录验证Setup运行环境 node.jsmongoDB克隆远程库 git clone git@github.com:antbaobao/AntVueBlogFront.git安装依赖 cd AntVueBlogFrontnpm i运行 npm run dev目录│ .babelrc babel配置│ .editorconfig 编辑器配置│ .eslintignore eslint忽略│ .eslintrc.js eslintrc配置│ .gitignore git上传忽略│ .postcssrc.js│ Dockerfile docker 配置│ index.html 打包模板│ package.json│ README.md│├─build├─src│ │ main.js 项目入口│ │ App.vue 根组件│ │ index.css 全局样式│ ││ ├─api api 请求接口│ ││ ├─assets 外部引用文件│ │ ├─css│ │ └─js│ ││ ├─components vue组件│ ││ ├─ layout 页面公共结构│ ││ ├─store vuex文件│ ││ ├─utils 工具函数│ ││ └─views 页面vue文件│├─test└─static 静态文件部署部署流程可以参考使用docker部署网站 ...

May 31, 2019 · 1 min · jiezi

造轮子-EGGJS的MySQL操作库

最近学习eggjs,学习过程中使用官方推荐的MySQL库,感觉官方库不太好用,基础的CURD没问题。但是复杂点的操作就不行了,虽然官方还有一个egg-sequelize,但是这个这并不妨碍我造轮子。下面介绍一下我的这个轮子。 介绍这个轮子其实是很早以前就造好的,主要参考THINKPHP的数据库操作方式。将设置表名(table)、设置查询字段(field)、联表(join)等操作进行链式操作,给人一种语义化操作数据库的感觉。 比如从用户表查找id为1的用户的名字,则只需要这样操作: mysql.table('user').field('name').where({ id: 1 }).find();// SELECT name FROM user where id=1 limit 1是不是很简单呢? 造的轮子名字叫@hyoga/egg-mysql,所以只需要: 安装npm i @hyoga/egg-mysql --save配置// {app_root}/config/config.default.jsexports.mysql = { mysql: { client: { host: '127.0.0.1', port: '3306', user: 'root', password: '', database: 'db', }, // 是否加载到 app 上,默认开启 app: true, // 是否加载到 agent 上,默认关闭 agent: false, },};使用// {app_root}/config/plugin.jsexports.mysql = { enable: true, package: '@hyoga/egg-mysql',};// {app_root}/app/service/user.jsexport default class User extends Service { private table = 'user'; public async list() { // sql = SELECT * FROM user WHERE status = 1 return this.app.mysql.table(this.table).where({ status: 1 }).select(); }} 这时候mysql就挂载到egg的app对象了,接下来就可以到处链式操作了。 ...

May 15, 2019 · 1 min · jiezi

技术栈为什么-Node-是小菜前端团队的核心技术栈

著作权归作者所有。商业转载请联系 Scott 获得授权,非商业转载请注明出处[务必保留全文,勿做删减]。Scott 近两年无论是面试还是线下线上的技术分享,遇到许许多多前端同学,由于团队原因,个人原因,职业成长,技术方向,甚至家庭等等原因,在理想国与现实之间,在放弃与坚守之间,摇摆不停,心酸硬抗,大家可以找我聊聊南聊聊北,对工程师的宿命有更多的了解,有更多的看见与听见,Scott 微信: codingdream。 本系列共 15 篇,此为第二篇,大家看完转发下朋友圈我就心满意足了。 任何可以用 JavaScript 来写的应用,最终都将用 JavaScript 来写。  -- 阿特伍德定律 这篇文章向大家介绍下小菜前端的基建在一步步走过来的过程中,NodeJS 是如何使用的及扮演了哪些角色,它对于工程师个人,团队能力,公司研发效率,业务支撑,技术的探索与突破等等到底有什么实际的意义,以及为什么是它而不是 Python/C++/PHP/Java 成为了前端团队的核心技术栈。 被 NodeJS 加速的框架演进速度2019 年的前端与 2009 年的前端早已是君住长江头我住长江尾,短短十年,人是物非,React/Vue 一统天下,Webpack 标配江湖,单纯看近 2 年的 【Npm Trends】: 或者参考近 10 年的【Google Trends】: 热度一定程度反映了社区活跃,和占用市场的体量,可以发现 AngularJS 也是经历了过山车,市场被 React/Vue 不断侵蚀,那再把 jQuery 加进来看下: 令人瞠目结舌,即便 React/Vue(绿色和紫色) 如日中天的今天,在整个网络的搜索热度上,也远远低于 jQuery 和 NodeJS,尤其是 jQuery,虽然它的热度在持续降低,但依然是整个互联网中不能忽视的重要组成部分, 虽然早期与 jQuery 同时代还有很多其他框架类库,比如 ExtJS/Mooltools/Dojo/Yui/Kissy 等等等等,但它们的体量比起 jQuery 都差之甚远不再比较,如果大家把近十年听到的看到的框架罗列起来,几十上百都不成问题,生命周期能超过 5 年却寥寥无几,尤其是在 2012 年 NodeJS 在全球推广到一定规模后,框架的诞生迭代替换更为快速,所以从框架的生命力来看,jQuery 目前为止依然是赢家,那它跟 NodeJS 有什么关系呢? ...

April 26, 2019 · 2 min · jiezi

Node.js - 阿里Egg的多进程模型和进程间通讯

前言最近用Egg作为底层框架开发项目,好奇其多进程模型的管理实现,于是学习了解了一些东西,顺便记录下来。文章如有错误, 请轻喷为什么需要多进程伴随科技的发展, 现在的服务器基本上都是多核cpu的了。然而,Node是一个单进程单线程语言(对于开发者来说是单线程,实际上不是)。我们都知道,cpu的调度单位是线程,而基于Node的特性,那么我们每次只能利用一个cpu。这样不仅仅利用率极低,而且容错更是不能接受(出错时会崩溃整个程序)。所以,Node有了cluster来协助我们充分利用服务器的资源。 cluster工作原理 关于cluster的工作原理推荐大家看这篇文章,这里简单总结一下:子进程的端口监听会被hack掉,而是统一由master的内部TCP监听,所以不会出现多个子进程监听同一端口而报错的现象。请求统一经过master的内部TCP,TCP的请求处理逻辑中,会挑选一个worker进程向其发送一个newconn内部消息,随消息发送客户端句柄。(这里的挑选有两种方式,第一种是除Windows外所有平台的默认方法循环法,即由主进程负责监听端口,接收新连接后再将连接循环分发给工作进程。在分发中使用了一些内置技巧防止工作进程任务过载。第二种是主进程创建监听socket后发送给感兴趣的工作进程,由工作进程负责直接接收连接。)worker进程收到句柄后,创建客户端实例(net.socket)执行具体的业务逻辑,然后返回。如图: 图引用出处多进程模型先看一下Egg官方文档的进程模型 +——–+ +——-+ | Master |<——–>| Agent | +——–+ +——-+ ^ ^ ^ / | \ / | \ / | \ v v v+———-+ +———-+ +———-+| Worker 1 | | Worker 2 | | Worker 3 |+———-+ +———-+ +———-+类型进程数量作用稳定性是否运行业务代码Master1进程管理,进程间消息转发非常高否Agent1后台运行工作(长连接客户端)高少量Worker一般为cpu核数执行业务代码一般是大致上就是利用Master作为主线程,启动Agent作为秘书进程协助Worker处理一些公共事务(日志之类),启动Worker进程执行真正的业务代码。多进程的实现流程相关代码首先从Master入手,这里暂时认为Master是最顶级的进程(事实上还有一个parent进程,待会再说)。/** * start egg app * @method Egg#startCluster * @param {Object} options {@link Master} * @param {Function} callback start success callback */exports.startCluster = function(options, callback) { new Master(options).ready(callback);};先从Master的构造函数看起constructor(options) { super(); // 初始化参数 this.options = parseOptions(options); // worker进程的管理类 详情见 Manager及Messenger篇 this.workerManager = new Manager(); // messenger类, 详情见 Manager及Messenger篇 this.messenger = new Messenger(this); // 设置一个ready事件 详情见get-ready npm包 ready.mixin(this); // 是否为生产环境 this.isProduction = isProduction(); this.agentWorkerIndex = 0; // 是否关闭 this.closed = false; … 接下来看的是ready的回调函数及注册的各类事件: this.ready(() => { // 将开始状态设置为true this.isStarted = true; const stickyMsg = this.options.sticky ? ’ with STICKY MODE!’ : ‘’; this.logger.info(’[master] %s started on %s (%sms)%s’, frameworkPkg.name, this[APP_ADDRESS], Date.now() - startTime, stickyMsg); // 发送egg-ready至各个进程并触发相关事件 const action = ’egg-ready’; this.messenger.send({ action, to: ‘parent’, data: { port: this[REALPORT], address: this[APP_ADDRESS] } }); this.messenger.send({ action, to: ‘app’, data: this.options }); this.messenger.send({ action, to: ‘agent’, data: this.options }); // start check agent and worker status this.workerManager.startCheck(); }); // 注册各类事件 this.on(‘agent-exit’, this.onAgentExit.bind(this)); this.on(‘agent-start’, this.onAgentStart.bind(this)); … // 检查端口并 Fork一个Agent detectPort((err, port) => { … this.forkAgentWorker(); } });}综上, 可以看到Master的构造函数主要是初始化和注册各类相应的事件, 最后运行的是forkAgentWorker函数, 该函数的关键代码可以看到:const agentWorkerFile = path.join(__dirname, ‘agent_worker.js’);// 通过child_process执行一个Agentconst agentWorker = childprocess.fork(agentWorkerFile, args, opt);继续到agent_worker.js上面看,agent_worker实例化一个agent对象,agent_worker.js有一句关键代码:agent.ready(() => { agent.removeListener(’error’, startErrorHandler); // 清除错误监听的事件 process.send({ action: ‘agent-start’, to: ‘master’ }); // 向master发送一个agent-start的动作});可以看到, agent_worker.js中的代码向master发出了一个信息, 动作为agent-start, 再回到Master中, 可以看到其注册了两个事件, 分别为once的forkAppWorkers和 on的onAgentStartthis.on(‘agent-start’, this.onAgentStart.bind(this));this.once(‘agent-start’, this.forkAppWorkers.bind(this));先看onAgentStart函数, 这个函数相对简单, 就是一些信息的传递:onAgentStart() { this.agentWorker.status = ‘started’; // Send egg-ready when agent is started after launched if (this.isAllAppWorkerStarted) { this.messenger.send({ action: ’egg-ready’, to: ‘agent’, data: this.options }); } this.messenger.send({ action: ’egg-pids’, to: ‘app’, data: [ this.agentWorker.pid ] }); // should send current worker pids when agent restart if (this.isStarted) { this.messenger.send({ action: ’egg-pids’, to: ‘agent’, data: this.workerManager.getListeningWorkerIds() }); } this.messenger.send({ action: ‘agent-start’, to: ‘app’ }); this.logger.info(’[master] agent_worker#%s:%s started (%sms)’, this.agentWorker.id, this.agentWorker.pid, Date.now() - this.agentStartTime); }然后会执行forkAppWorkers函数,该函数主要是借助cfork包fork对应的工作进程, 并注册一系列相关的监听事件,…cfork({ exec: this.getAppWorkerFile(), args, silent: false, count: this.options.workers, // don’t refork in local env refork: this.isProduction,});…// 触发app-start事件cluster.on(’listening’, (worker, address) => { this.messenger.send({ action: ‘app-start’, data: { workerPid: worker.process.pid, address }, to: ‘master’, from: ‘app’, });});可以看到forkAppWorkers函数在监听Listening事件时,会触发master上的app-start事件。this.on(‘app-start’, this.onAppStart.bind(this));…// master ready回调触发if (this.options.sticky) { this.startMasterSocketServer(err => { if (err) return this.ready(err); this.ready(true); });} else { this.ready(true);}// ready回调 发送egg-ready状态到各个进程const action = ’egg-ready’;this.messenger.send({ action, to: ‘parent’, data: { port: this[REALPORT], address: this[APP_ADDRESS] } });this.messenger.send({ action, to: ‘app’, data: this.options });this.messenger.send({ action, to: ‘agent’, data: this.options });// start check agent and worker statusif (this.isProduction) { this.workerManager.startCheck();}总结下:Master.constructor: 先执行Master的构造函数, 里面有个detect函数被执行Detect: Detect => forkAgentWorker()forkAgentWorker: 获取Agent进程, 向master触发agent-start事件执行onAgentStart函数, 执行forkAppWorker函数(once)onAgentStart => 发送各类信息, forkAppWorker => 向master触发 app-start事件App-start事件 触发 onAppStart()方法onAppStart => 设置ready(true) => 执行ready的回调函数Ready() = > 发送egg-ready到各个进程并触发相关事件, 执行startCheck()函数+———+ +———+ +———+| Master | | Agent | | Worker |+———+ +—-+—-+ +—-+—-+ | fork agent | | +——————–>| | | agent ready | | |<——————–+ | | | fork worker | +—————————————–>| | worker ready | | |<—————————————–+ | Egg ready | | +——————–>| | | Egg ready | | +—————————————–>|进程守护根据官方文档,进程守护主要是依赖于graceful和egg-cluster这两个库。 未捕获异常关闭异常 Worker 进程所有的 TCP Server(将已有的连接快速断开,且不再接收新的连接),断开和 Master 的 IPC 通道,不再接受新的用户请求。Master 立刻 fork 一个新的 Worker 进程,保证在线的『工人』总数不变。异常 Worker 等待一段时间,处理完已经接受的请求后退出。+———+ +———+| Worker | | Master |+———+ +—-+—-+ | uncaughtException | +————+ | | | | +———+ | <———-+ | | Worker | | | +—-+—-+ | disconnect | fork a new worker | +————————-> + ———————> | | wait… | | | exit | | +————————-> | | | | | die | | | | | |由执行的app文件可知, app实际上是继承于Application类, 该类下面调用了graceful()。onServer(server) { …… graceful({ server: [ server ], error: (err, throwErrorCount) => { …… }, }); …… }继续看graceful, 可以看到它捕获了process.on(‘uncaughtException’)事件, 并在回调函数里面关闭TCP连接, 关闭本身进程, 断开与master的IPC通道。process.on(‘uncaughtException’, function (err) { …… // 对http连接设置 Connection: close响应头 servers.forEach(function (server) { if (server instanceof http.Server) { server.on(‘request’, function (req, res) { // Let http server set Connection: close header, and close the current request socket. req.shouldKeepAlive = false; res.shouldKeepAlive = false; if (!res._header) { res.setHeader(‘Connection’, ‘close’); } }); } }); // 设置一个定时函数关闭子进程, 并退出本身进程 // make sure we close down within killTimeout seconds var killtimer = setTimeout(function () { console.error(’[%s] [graceful:worker:%s] kill timeout, exit now.’, Date(), process.pid); if (process.env.NODE_ENV !== ’test’) { // kill children by SIGKILL before exit killChildren(function() { // 退出本身进程 process.exit(1); }); } }, killTimeout); // But don’t keep the process open just for that! // If there is no more io waitting, just let process exit normally. if (typeof killtimer.unref === ‘function’) { // only worked on node 0.10+ killtimer.unref(); } var worker = options.worker || cluster.worker; // cluster mode if (worker) { try { // 关闭TCP连接 for (var i = 0; i < servers.length; i++) { var server = servers[i]; server.close(); } } catch (er1) { …… } try { // 关闭ICP通道 worker.disconnect(); } catch (er2) { …… } } });ok, 关闭了IPC通道后, 我们继续看cfork文件, 即上面提到的fork worker的包, 里面监听了子进程的disconnect事件, 他会根据条件判断是否重新fork一个新的子进程cluster.on(‘disconnect’, function (worker) { …… // 存起该pid disconnects[worker.process.pid] = utility.logDate(); if (allow()) { // fork一个新的子进程 newWorker = forkWorker(worker._clusterSettings); newWorker._clusterSettings = worker._clusterSettings; } else { …… } });一般来说, 这个时候会继续等待一会然后就执行了上面说到的定时函数了, 即退出进程。 OOM、系统异常关于这种系统异常, 有时候在子进程中是不能捕获到的, 我们只能在master中进行处理, 也就是cfork包。cluster.on(’exit’, function (worker, code, signal) { // 是程序异常的话, 会通过上面提到的uncatughException重新fork一个子进程, 所以这里就不需要了 var isExpected = !!disconnects[worker.process.pid]; if (isExpected) { delete disconnects[worker.process.pid]; // worker disconnect first, exit expected return; } // 是master杀死的子进程, 无需fork if (worker.disableRefork) { // worker is killed by master return; } if (allow()) { newWorker = forkWorker(worker._clusterSettings); newWorker._clusterSettings = worker._clusterSettings; } else { …… } cluster.emit(‘unexpectedExit’, worker, code, signal); });进程间通信(IPC)上面一直提到各种进程间通信,细心的你可能已经发现 cluster 的 IPC 通道只存在于 Master 和 Worker/Agent 之间,Worker 与 Agent 进程互相间是没有的。那么 Worker 之间想通讯该怎么办呢?是的,通过 Master 来转发。广播消息: agent => all workers +——–+ +——-+ | Master |<———| Agent | +——–+ +——-+ / | \ / | \ / | \ / | \ v v v +———-+ +———-+ +———-+ | Worker 1 | | Worker 2 | | Worker 3 | +———-+ +———-+ +———-+指定接收方: one worker => another worker +——–+ +——-+ | Master |———-| Agent | +——–+ +——-+ ^ | send to / | worker 2 / | / | / v +———-+ +———-+ +———-+ | Worker 1 | | Worker 2 | | Worker 3 | +———-+ +———-+ +———-+在master中, 可以看到当agent和app被fork时, 会监听他们的信息, 同时将信息转化成一个对象:agentWorker.on(‘message’, msg => { if (typeof msg === ‘string’) msg = { action: msg, data: msg }; msg.from = ‘agent’; this.messenger.send(msg);});worker.on(‘message’, msg => { if (typeof msg === ‘string’) msg = { action: msg, data: msg }; msg.from = ‘app’; this.messenger.send(msg);});可以看到最后调用的是messenger.send, 而messengeer.send就是根据from和to来决定将信息发送到哪里send(data) { if (!data.from) { data.from = ‘master’; } …… // app -> master // agent -> master if (data.to === ‘master’) { debug(’%s -> master, data: %j’, data.from, data); // app/agent to master this.sendToMaster(data); return; } // master -> parent // app -> parent // agent -> parent if (data.to === ‘parent’) { debug(’%s -> parent, data: %j’, data.from, data); this.sendToParent(data); return; } // parent -> master -> app // agent -> master -> app if (data.to === ‘app’) { debug(’%s -> %s, data: %j’, data.from, data.to, data); this.sendToAppWorker(data); return; } // parent -> master -> agent // app -> master -> agent,可能不指定 to if (data.to === ‘agent’) { debug(’%s -> %s, data: %j’, data.from, data.to, data); this.sendToAgentWorker(data); return; } }master则是直接根据action信息emit对应的注册事件sendToMaster(data) { this.master.emit(data.action, data.data);}而agent和worker则是通过一个sendmessage包, 实际上就是调用下面类似的方法 // 将信息传给子进程 agent.send(data) worker.send(data)最后, 在agent和app都继承的基础类EggApplication上, 调用了Messenger类, 该类内部的构造函数如下:constructor() { super(); …… this._onMessage = this._onMessage.bind(this); process.on(‘message’, this._onMessage); }_onMessage(message) { if (message && is.string(message.action)) { // 和master一样根据action信息emit对应的注册事件 this.emit(message.action, message.data); } }总结一下: 思路就是利用事件机制和IPC通道来达到各个进程之间的通信。其他学习过程中有遇到一个timeout.unref()的函数, 关于该函数推荐大家参考这个问题的6楼回答总结从前端思维转到后端思维其实还是很吃力的,加上Egg的进程管理实现确实非常厉害, 所以花了很多时间在各种api和思路思考上。参考与引用多进程模型和进程间通讯 Egg 源码解析之 egg-cluster ...

April 17, 2019 · 6 min · jiezi

Egg集成Mybatis

Egg集成Mybatis就一张图稍后再放出关于分页查询的处理吧,目前部分代码在express的一个小项目中,搬过来之后再分享一下,本人刚刚接触Egg这套框架,希望大家多提提意见。

April 5, 2019 · 1 min · jiezi

如何给Egg.js项目开启80端口访问

为什么要因某些特殊情况,项目cdn做了防盗链,然后本地开发也收到了限制,在cdn设置了一些本地ip的白名单,居然对端口支持不好,结果有时候可以访问cdn资源,有时候又不行,无奈,和运维、后端商量讲开发地址暂时改成80端口,以便正常开发Egg.js的项目改端口很简单啦。但是也有一些要注意的,本文面向MacOS,当然Linux系统应该同样适用。如何做这里有几个方案,供君参考:修改package.json跑哪个命令就在这个命令后面加–port=80,例如:“start”: “egg-scripts start –daemon –title=egg-server-51la-web-egg –workers=2 –port=80”,…dev同理。配置config.local.js如果没有这个文件自己创建一个,当然这个对应的是开发模式下使用。部分配置如下:‘use strict’;module.exports = app => { const exports = {}; exports.cluster = { listen: { port: 80, hostname: ‘127.0.0.1’, }, }; return exports;};Nginx大法比较麻烦,不过看了下官方文档,应该也是可以很好的支持的。有兴趣请阅读该节:部署Warning如果你不看这部分,很遗憾,你肯定跑不起来。你可能会遇到下面这些异常情况:ERROR 3810 nodejs.AppWorkerDiedError: [master] app_worker#1:3813 died (code: 0, signal: null, suicide: false, state: dead), current workers: []原因是Node.js的服务器端默认是无法使用1024以下的端口的。咋办呢?使用sudo哈哈哈。就是这样:sudo npm start或者sodu npm run dev。也有可能是:ERROR 3709 [app_worker] server got error: bind EADDRINUSE null:80, code: EADDRINUSE端口被占用了!node.js的server服务无法在ctrl+c后立刻终止。比如默认7001未能正常关闭,通过config.local.js文件修改的80端口也没有能够生效,每次启动服务都是启动了新的端口7002,此时:需要查出占用7001的端口的pid,将它终止。操作效果大致如下:P750TM:51la-web-egg whidy$ lsof -i:7001COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEnode 2982 whidy 25u IPv6 0x2f7a1da313a05e4d 0t0 TCP *:afs3-callback (LISTEN)P750TM:51la-web-egg whidy$ killkill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec … or kill -l [sigspec]P750TM:51la-web-egg whidy$ kill 2982P750TM:51la-web-egg whidy$ lsof -i:7001上面用到两个命令:lsof -i:[端口号],kill [进程的PID],最后重新查询7001就没有任何返回,说明Ok了,再次执行sudo npm run dev,那么就很好的使用80端口了。参考egg.js启动命令Node.js EACCES error when listening on most ports本文仅作为总结形式,未能重新完整的实践整个流程,如果有操作跳跃性或错误欢迎提出 ...

March 15, 2019 · 1 min · jiezi

egg学习笔记-1

入门笔记请按照官方文档初始化一个simple工程项目,按照如下步骤学习即可,如有不懂的请参考官方文档。禁止csrf验证根据如下步骤进行控制的创建,在执行Post操作的时候会出现invalid csrf token错误。需要禁止此特性。创建路由/** * @param {Egg.Application} app - egg application /module.exports = app => { const { router, controller, } = app; router.get(’/’, controller.home.index); // 新建资源路由 router.resources(‘SystemUser’, ‘/system/user’, controller.system.user);};控制器controller.system.user,对应的是controller目录下的system目录里面的user.js文件创建对应的控制器const Controller = require(’egg’).Controller;class SystemUserController extends Controller { async index() { const { ctx, } = this; ctx.body = ‘hello ! this is SystemUserController’; }}module.exports = SystemUserController; 访问地址:localhost:7001/system/user创建可以提交数据的控制器方法const Controller = require(’egg’).Controller;class SystemUserController extends Controller { async index() { const { ctx, } = this; ctx.body = ‘hello ! this is SystemUserController’; } async create() { const ctx = this.ctx; const user = ctx.request.body; ctx.body = user; }}module.exports = SystemUserController;POST方法访问网址:localhost:7001/system/user 会提示invalid csrf token错误。关闭csrf验证 // config\config.default.js // 关闭csrf验证 config.security = { csrf: { enable: false, }, };开启验证在上面的例子中,我们通过表单提交了任何数据,都会回显到界面上,如果是数据需要存储或者做其他业务处理,则需要对用户输入的数据进行验证。egg提供了egg-validate插件进行表单验证安装插件cnpm install egg-validate –save配置启用插件/* @type Egg.EggPlugin /module.exports = { // had enabled by egg // static: { // enable: true, // } validate: { enable: true, package: ’egg-validate’, },};使用验证组件首先创建一个规则,这里我们定义username是字符串而且是必须的,最大长度为8个字符。定义password为字符串且必须,最小长度为6个字符。ctx.validate(createRule, ctx.request.body);通过上述语句进行参数检查更多规则请参考:https://github.com/node-modul…const Controller = require(’egg’).Controller;// 定义本接口的请求参数的验证规则const createRule = { username: { type: ‘string’, required: true, max: 8, }, password: { type: ‘string’, required: true, min: 6, },};class SystemUserController extends Controller { async index() { const { ctx, } = this; ctx.body = ‘hello ! this is SystemUserController’; } async create() { const ctx = this.ctx; // 验证输入参数是否符合预期格式 ctx.validate(createRule, ctx.request.body); const user = ctx.request.body; ctx.body = user; }}module.exports = SystemUserController;测试使用POSTMAN提交任意字段,则会提示Validation Failed (code: invalid_param)如何查看具体错误信息呢。下面将定义统一错误处理的中间件进行错误处理。统一错误处理中间件在项目的app目录中创建middleware目录,此目录中可以创建中间件。创建错误处理中间件’use strict’;module.exports = () => { return async function errorHandler(ctx, next) { try { await next(); } catch (err) { // 控制台输出 console.error(‘MiddleWare errorHandler’, err); // 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志 ctx.app.emit(’error’, err, ctx); // status 如果没有,则统一为500 const status = err.status || 500; // 如果是500错误,且是生产环境,则统一显示“Internal Server Error” const error = status === 500 && ctx.app.config.env === ‘prod’ ? ‘Internal Server Error’ : err; // 改变上下文状态代码 ctx.status = status; // 从 error 对象上读出各个属性,设置到响应中 ctx.body = { error, }; } };};启用中间件文件configconfig.default.js中 // add your middleware config here config.middleware = [];将其修改为包含你创建的中间件 // add your middleware config here // errorHandler 统一错误处理 config.middleware = [ ’errorHandler’ ]; // errorHandler 只在/api上生效 config.errorHandler = { match: ‘/api’, };如上所示,可以设置错误处理中间件在什么URL上面起作用。这里我们使用/api测试路由先使用原来的路由访问:localhost:7001/system/user 错误提示依旧变更路由:打开文件:approuter.js // router.resources(‘SystemUser’, ‘/system/user’, controller.system.user); router.resources(‘SystemUser’, ‘/api/v1/system/user’, controller.system.user);再次访问:localhost:7001/api/v1/system/user提示信息会变成:{“error”:{“message”:“Validation Failed”,“code”:“invalid_param”,“errors”:[{“message”:“required”,“field”:“username”,“code”:“missing_field”},{“message”:“required”,“field”:“password”,“code”:“missing_field”}]}}格式化接口返回的数据结构在上面显示错误信息中,可以看到规范的Json数据,如果开发接口,我们就需要定义统一的数据结构,以便客户端进行解析。在API开发中,可以定义接口的标准返回格式。通过框架扩展方式定义返回数据格式是一个非常方便的方法。在app目录中创建extend目录,对egg的内置对象Helper进行扩展即可。创建Helper扩展文件:app/extend/helper.js’use strict’;module.exports = { /* * 调用正常情况的返回数据封装 * @param {Object} ctx - context * @param {} msg - message * @param {} data - 数据 / success(ctx, msg, data) { ctx.body = { code: 0, msg, data, }; ctx.status = 200; }, /* * 处理失败,处理传入的失败原因 * @param {*} ctx - context * @param {Object} res - 返回的状态数据 */ fail(ctx, res) { ctx.body = { code: res.code, msg: res.msg, data: res.data, }; ctx.status = 200; },};改造统一错误处理// 从 error 对象上读出各个属性,设置到响应中// ctx.body = {// error,// };// 格式化返回错误信息ctx.helper.fail(ctx, { code: status, msg: error.message, data: error.errors,});测试再次访问:localhost:7001/api/v1/system/user提示信息会变成:{ “code”: 422, “msg”: “Validation Failed”, “data”: [ { “message”: “required”, “field”: “username”, “code”: “missing_field” }, { “message”: “required”, “field”: “password”, “code”: “missing_field” } ]}将控制器user中的返回改为标准格式async create() { const ctx = this.ctx; // 验证输入参数是否符合预期格式 ctx.validate(createRule, ctx.request.body); const user = ctx.request.body; // ctx.body = user; this.ctx.helper.success(this.ctx, ‘ok’, user);}提交满足要求的数据,测试正确的返回数据username testuser password 1234567890提交后将返回标准的数据格式{ “code”: 0, “msg”: “ok”, “data”: { “username”: “testuser”, “password”: “1234567890” }} ...

February 26, 2019 · 3 min · jiezi

egg(114)--egg之订单详情

controllerappcontrollerdefaultuser.js async orderinfo() { // this.ctx.body = ‘用户订单’; const uid = this.ctx.service.cookies.get(‘userinfo’)._id; const id = this.ctx.request.query.id; var orderResult = await this.ctx.model.Order.find({ “uid”: uid, “_id”: id }); //不可扩展对象的解决方法 orderResult = JSON.parse(JSON.stringify(orderResult)); orderResult[0].orderItems = await this.ctx.model.OrderItem.find({ “order_id”: id }); await this.ctx.render(‘default/user/order_info.html’, { orderInfo: orderResult[0] }); }viewappviewdefaultuserorder_info.html<% include ../public/header.html%> <!–end header –> <!– start banner_x –> <% include ../public/banner.html%> <!– end banner_x –> <link rel=“stylesheet” href="/public/default/css/order.css" /> <!– self_info –> <div class=“grzxbj”> <div class=“selfinfo center”> <div class=“lfnav fl”> <% include ./user_left.html%> </div> <div class=“rtcont fr”> <h1>订单详情</h1> <div class=“uc-content-box”> <div class=“uc-box uc-main-box”> <div class=“uc-content-box order-view-box”> <div class=“box-hd”> <div class=“more clearfix”> <h2 class=“subtitle”>订单号: <%=orderInfo.order_id%> <span class=“tag tag-subsidy”></span> </h2> <div class=“actions”> <a title=“申请售后” href="#" class=“btn btn-small btn-line-gray” data-stat-id=“12e905752ea93db8” onclick="_msq.push([’trackEvent’, ‘4a854694de3347de-12e905752ea93db8’, ‘http://service.order.mi.com/apply/order/id/1160529723001145’, ‘pcpid’, ‘’]);">申请售后</a> </div> </div> </div> <div class=“box-bd”> <div class=“uc-order-item uc-order-item-finish”> <div class=“order-detail”> <div class=“order-summary”> <div class=“order-status”> <%if(orderInfo.order_status==0){%> 已下单 未支付 <%}else if(orderInfo.order_status==1){%> 已付款 <%}else if(orderInfo.order_status==2){%> 已配货 <%}else if(orderInfo.order_status==3){%> 已发货 <%}else if(orderInfo.order_status==4){%> 交易成功 <%}else if(orderInfo.order_status==5){%> 已退货 <%}else if(orderInfo.order_status==6){%> 无效 已取消 <%}%> </div> <div class=“order-progress”> <ol class=“progress-list clearfix progress-list-5”> <li class=“step step-first <%if(orderInfo.order_status>=0){%> step-done <%}%> “> <div class=“progress”><span class=“text”>下单</span></div> <div class=“info”> <%=helper.formatTime(orderInfo.add_time) %> </div> </li> <li class=“step <%if(orderInfo.order_status>=1){%> step-done <%}%>"> <div class=“progress”><span class=“text”>付款</span></div> <div class=“info”> <%=helper.formatTime(orderInfo.add_time) %> </div> </li> <li class=“step <%if(orderInfo.order_status>=2){%> step-done <%}%>"> <div class=“progress”><span class=“text”>配货</span></div> <div class=“info”> <%=helper.formatTime(orderInfo.add_time) %> </div> </li> <li class=“step <%if(orderInfo.order_status>=3){%> step-done <%}%>"> <div class=“progress”><span class=“text”>出库</span></div> <div class=“info”> <%=helper.formatTime(orderInfo.add_time) %> </div> </li> <li class=“step step-active step-last”> <div class=“progress”><span class=“text”>交易成功</span></div> <div class=“info”>2016年06月04日 13:58</div> </li> </ol> </div> <div class=“order-delivery order-delivery-detail” style=“display:block;border:none;"> <p class=“delivery-num”> 物流公司: <a href=”##” target="_blank” data-stat-id=“d4af14ade0c175da”>顺丰(北京) </a> 运单号:199384067236 </p> </div> </div> <table class=“order-items-table”> <tbody> <%for(var j=0;j<orderInfo.orderItems.length;j++){%> <tr> <td class=“col col-thumb”> <div class=“figure figure-thumb”> <a target="_blank” href=”#"> <img src="<%=orderInfo.orderItems[j].product_img%>” width=“80” height=“80” alt=""> </a> </div> </td> <td class=“col col-name”> <p class=“name”> <a target="_blank" href="#"> <%=orderInfo.orderItems[j].product_title%> </a> </p> </td> <td class=“col col-price”> <p class=“price”> <%=orderInfo.orderItems[j].product_price%>元 × <%=orderInfo.orderItems[j].product_num%> </p> </td> <td class=“col col-actions”> </td> </tr> <%}%> </tbody> </table> </div> <!– 订金盲约订单 –> <div id=“editAddr” class=“order-detail-info”> <h3>收货信息</h3> <table class=“info-table”> <tbody> <tr> <th>姓&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;名:</th> <td> <%=orderInfo.name%> </td> </tr> <tr> <th>联系电话:</th> <td> <%=orderInfo.phone%> </td> </tr> <tr> <th>收货地址:</th> <td> <%=orderInfo.address%> </td> </tr> </tbody> </table> <div class=“actions”> </div> </div> <div id=“editTime” class=“order-detail-info”> <h3>支付方式</h3> <table class=“info-table”> <tbody> <tr> <th>支付方式:</th> <td>在线支付</td> </tr> </tbody> </table> <div class=“actions”> </div> </div> <div class=“order-detail-info”> <h3>发票信息</h3> <table class=“info-table”> <tbody> <tr> <th>发票类型:</th> <td>电子发票</td> </tr> <tr> <th>发票内容:</th> <td>购买商品明细</td> </tr> <tr> <th>发票抬头:</th> <td>个人</td> </tr> </tbody> </table> </div> <div class=“order-detail-total”> <table class=“total-table”> <tbody> <tr> <th>商品总价:</th> <td><span class=“num”><%=orderInfo.all_price%></span>元</td> </tr> <tr> <th>运费:</th> <td><span class=“num”>0</span>元</td> </tr> <tr> <th class=“total”>实付金额:</th> <td class=“total”><span class=“num”><%=orderInfo.all_price%></span>元</td> </tr> </tbody> </table> </div> </div> </div> </div> </div> </div> </div> <div class=“clear”></div> </div> </div> <!– self_info –> <footer class=“mt20 center”> <div class=“mt20”>小米商城|MIUI|米聊|多看书城|小米路由器|视频电话|小米天猫店|小米淘宝直营店|小米网盟|小米移动|隐私政策|Select Region</div> <div>©mi.com 京ICP证110507号 京ICP备10046444号 京公网安备11010802020134号 京网文[2014]0059-0009号</div> <div>违法和不良信息举报电话:185-0130-1238,本网站所列数据,除特殊说明,所有数据均出自我司实验室测试</div> </footer> </body> </html>效果 ...

February 22, 2019 · 3 min · jiezi

egg(115)--egg之订单页面筛选,搜索

model修改order_item,增加uidappmodelorder_item.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const d = new Date(); const OrderItem = new Schema({ uid: { type: Schema.Types.ObjectId }, order_id: { type: Schema.Types.ObjectId }, product_title: { type: String }, product_id: { type: Schema.Types.ObjectId }, product_img: { type: String }, product_price: { type: Number }, product_num: { type: Number }, add_time: { type: Number, default: d.getTime(), } }); return mongoose.model(‘OrderItem’, OrderItem, ‘order_item’);};controllerappcontrollerdefaultbuy.js’use strict’;const Controller = require(’egg’).Controller;class BuyController extends Controller { // 去结算 async checkout() { // 获取购物车选中的商品 let orderList = []; let allPrice = 0; let cartList = this.service.cookies.get(‘cartList’); //签名防止重复提交订单 var orderSign = await this.service.tools.md5(await this.service.tools.getRandomNum()); this.ctx.session.orderSign = orderSign; if (cartList && cartList.length > 0) { for (let i = 0; i < cartList.length; i++) { if (cartList[i].checked) { orderList.push(cartList[i]); allPrice += cartList[i].price * cartList[i].num; } } // 获取当前用户的所有收货地址 const uid = this.ctx.service.cookies.get(‘userinfo’)._id; const addressList = await this.ctx.model.Address.find({ uid }).sort({ default_address: -1 }); await this.ctx.render(‘default/checkout.html’, { orderList, allPrice, addressList, orderSign: orderSign }); } else { // 恶意操作 this.ctx.redirect(’/cart’); } } //提交订单 async doOrder() { /* 1、获取收货地址信息 2、需要获取购买商品的信息 3、把这些信息 放在订单表 4、删除购物车里面的数据 / /防止提交重复订单/ var orderSign = this.ctx.request.body.orderSign; if (orderSign != this.ctx.session.orderSign) { return false; } this.ctx.session.orderSign = null; const uid = this.ctx.service.cookies.get(‘userinfo’)._id; let addressResult = await this.ctx.model.Address.find({ “uid”: uid, “default_address”: 1 }); let cartList = this.service.cookies.get(‘cartList’); if (addressResult && addressResult.length > 0 && cartList && cartList.length > 0) { var all_price = 0; let orderList = cartList.filter((value) => { if (value.checked) { all_price += value.price * value.num; return value; } }) //执行提交订单的操作 let order_id = await this.service.tools.getOrderId(); let name = addressResult[0].name; let phone = addressResult[0].phone; let address = addressResult[0].address; let zipcode = addressResult[0].zipcode; let pay_status = 0; let pay_type = ‘’; let order_status = 0; let orderModel = new this.ctx.model.Order({ order_id, uid, name, phone, address, zipcode, pay_status, pay_type, order_status, all_price }); let orderResult = await orderModel.save(); if (orderResult && orderResult._id) { //增加商品信息 for (let i = 0; i < orderList.length; i++) { let json = { “uid”: uid, “order_id”: orderResult._id, //订单id “product_title”: orderList[i].title, “product_id”: orderList[i]._id, “product_img”: orderList[i].goods_img, “product_price”: orderList[i].price, “product_num”: orderList[i].num } let orderItemModel = new this.ctx.model.OrderItem(json); await orderItemModel.save(); } //删除购物车中已经购买的商品 var unCheckedCartList = cartList.filter((value) => { if (!value.checked) { return value; } }) this.service.cookies.set(‘cartList’, unCheckedCartList); this.ctx.redirect(’/buy/confirm?id=’ + orderResult._id); } else { this.ctx.redirect(’/buy/checkout’); } } else { this.ctx.redirect(’/buy/checkout’); } console.log(‘提交订单’); } // 确认订单 支付 async confirm() { var id = this.ctx.request.query.id; var orderResult = await this.ctx.model.Order.find({ “_id”: id }); if (orderResult && orderResult.length > 0) { //获取商品 var orderItemResult = await this.ctx.model.OrderItem.find({ “order_id”: id }); await this.ctx.render(‘default/confirm.html’, { orderResult: orderResult[0], orderItemResult: orderItemResult, id: id }); } else { //错误 this.ctx.redirect(’/’); } } //执行多次 async getOrderPayStatus() { / 1、获取订单号 2、查询当前订单的支付状态 3、如果支付 返回成功 如果没有支付返回失败信息 / var id = this.ctx.request.query.id; if (id) { try { var orderReuslt = await this.ctx.model.Order.find({ “_id”: id }); if (orderReuslt && orderReuslt[0].pay_status == 1 && orderReuslt[0].order_status == 1) { this.ctx.body = { success: true, message: ‘已支付’ } } else { this.ctx.body = { success: false, message: ‘未支付’ } } } catch (error) { this.ctx.body = { success: false, message: ‘未支付’ } } } else { this.ctx.body = { success: false, message: ‘未支付’ } } }}module.exports = BuyController;appcontrollerdefaultuser.js’use strict’;const Controller = require(’egg’).Controller;class UserController extends Controller { async welcome() { await this.ctx.render(‘default/user/welcome.html’); } async order() { const uid = this.ctx.service.cookies.get(‘userinfo’)._id; const page = this.ctx.request.query.page || 1; var order_status = this.ctx.request.query.order_status || -1; var keywords = this.ctx.request.query.keywords; var json = { “uid”: this.app.mongoose.Types.ObjectId(uid) }; //查询当前用户下面的所有订单 //筛选 if (order_status != -1) { json = Object.assign(json, { “order_status”: parseInt(order_status) }); } //搜索 if (keywords) { var orderItemJson = Object.assign({ “uid”: this.app.mongoose.Types.ObjectId(uid) }, { “product_title”: { $regex: new RegExp(keywords) } }); var orderItemResult = await this.ctx.model.OrderItem.find(orderItemJson); if (orderItemResult.length > 0) { var tempArr = []; orderItemResult.forEach(value => { tempArr.push({ _id: value.order_id }); }); json = Object.assign(json, { $or: tempArr }) / { uid: 5c10c2dfd702ac47bc58ab45, ‘$or’: [ { _id: 5c41955b10f6400bb0c850ab }, { _id: 5c42a48be6389d22a4396833 } ] } */ } else { json = Object.assign(json, { $or: [{ 1: -1 }] }) } } console.log(“aaa”) console.log(JSON.stringify(json)); const pageSize = 5; // 总数量 const totalNum = await this.ctx.model.Order.find(json).countDocuments(); //聚合管道要注意顺序 const result = await this.ctx.model.Order.aggregate([{ $lookup: { from: ‘order_item’, localField: ‘_id’, foreignField: ‘order_id’, as: ‘orderItems’, }, }, { $sort: { “add_time”: -1 } }, { $match: json //条件 }, { $skip: (page - 1) * pageSize, }, { $limit: pageSize, } ]); await this.ctx.render(‘default/user/order.html’, { list: result, totalPages: Math.ceil(totalNum / pageSize), page, order_status: order_status }); } async orderinfo() { // this.ctx.body = ‘用户订单’; await this.ctx.render(‘default/user/order_info.html’); } async address() { this.ctx.body = ‘收货地址’; }}module.exports = UserController;viewappviewdefaultuserorder.html<% include ../public/header.html%> <!–end header –> <!– start banner_x –> <% include ../public/banner.html%> <!– end banner_x –> <script src="/public/default/js/jqPaginator.js"></script> <link rel=“stylesheet” href="/public/default/css/order.css" /> <!– self_info –> <div class=“grzxbj”> <div class=“selfinfo center”> <div class=“lfnav fl”> <% include ./user_left.html%> </div> <div class=“rtcont fr”> <h1>我的订单</h1> <div class=“uc-content-box”> <div class=“box-hd”> <div class=“more clearfix”> <ul class=“filter-list J_orderType”> <li class=“first active”><a href="/user/order">全部有效订单</a></li> <li><a href="/user/order?page=<%=page%>&order_status=0">待支付</a></li> <li><a href="/user/order?page=<%=page%>&order_status=1">已支付</a></li> <li><a href="/user/order?page=<%=page%>&order_status=3">待收货</a></li> <li><a href="/user/order?page=<%=page%>&order_status=6">已关闭</a></li> </ul> <form id=“J_orderSearchForm” class=“search-form clearfix” action="#" method=“get”> <input class=“search-text” type=“search” id=“J_orderSearchKeywords” name=“keywords” autocomplete=“off” placeholder=“输入商品名称、商品编号、订单号”> <input type=“submit” class=“search-btn iconfont” value=“搜索”> </form> </div> </div> <div class=“box-bd”> <%if(list.length>0){%> <table class=“table”> <%for(var i=0;i<list.length;i++){%> <tr <%if(list[i].pay_status==0){%>class=“order_pay” <%}%>> <td colspan=“2”> <div class=“order-summary”> <h2> <%if(list[i].order_status==0){%> 已下单 未支付 <%}else if(list[i].order_status==1){%> 已付款 <%}else if(list[i].order_status==2){%> 已配货 <%}else if(list[i].order_status==3){%> 已发货 <%}else if(list[i].order_status==4){%> 交易成功 <%}else if(list[i].order_status==5){%> 已退货 <%}else if(list[i].order_status==6){%> 无效 已取消 <%}%> </h2> <p> <%=helper.formatTime(list[i].add_time) %> | <%=list[i].name%> | 订单号: <%=list[i].order_id%> | 在线支付 实付金额: <%=list[i].all_price%>元</p> </div> <%for(var j=0;j<list[i].orderItems.length;j++){%> <div class=“order-info clearfix”> <div class=“col_pic”> <img src="<%=list[i].orderItems[j].product_img%>" /> </div> <div class=“col_title”> <p> <%=list[i].orderItems[j].product_title%> </p> <p> <%=list[i].orderItems[j].product_price%>元 × <%=list[i].orderItems[j].product_num%> </p> </div> </div> <%}%> </td> <td> <span> <%if(list[i].pay_status==1){%> <a class=“delete btn” href="/user/orderinfo?id=<%=list[i]._id%>">订单详情</a> <br> <br> <a class=“delete btn” href="#">申请售后</a> <%}else{%> <a class=“delete btn btn-primary” href="/buy/confirm?id=<%=list[i]._id%>">去支付</a> <br> <br> <a class=“delete btn” href="/user/orderinfo?id=<%=list[i]._id%>">订单详情</a> <%}%> </span> </td> </tr> <%}%> </table> <div id=“page” class=“pagination fr”></div> <%}else{%> <p style=“text-align:center; padding-top:100px;">没有查找到任何订到</p> <%}%> </div> </div> <script> $(’#page’).jqPaginator({ totalPages: <%=totalPages%>, visiblePages: 8, currentPage: <%=page%>, onPageChange: function(num, type) { console.log(‘当前第’ + num + ‘页’, type); if (type == ‘change’) { location.href = “/user/order?page=” + num + ‘&order_status=’ + <%=order_status%>; } } }); </script> </div> <div class=“clear”></div> </div> </div> <!– self_info –> <footer class=“mt20 center”> <div class=“mt20”>小米商城|MIUI|米聊|多看书城|小米路由器|视频电话|小米天猫店|小米淘宝直营店|小米网盟|小米移动|隐私政策|Select Region</div> <div>©mi.com 京ICP证110507号 京ICP备10046444号 京公网安备11010802020134号 京网文[2014]0059-0009号</div> <div>违法和不良信息举报电话:185-0130-1238,本网站所列数据,除特殊说明,所有数据均出自我司实验室测试</div> </footer> </body> </html>效果显示订单支付状态根据订单状态筛选搜索 ...

February 21, 2019 · 5 min · jiezi

egg(114)--egg之订单页面

router router.get(’/user/welcome’, initMiddleware, userauthMiddleware, controller.default.user.welcome); router.get(’/user/order’, initMiddleware, userauthMiddleware, controller.default.user.order); router.get(’/user/orderinfo’, initMiddleware, userauthMiddleware, controller.default.user.orderinfo);中间件传递当前urlappmiddlewareuserauth.jsctx.state.url = url.parse(ctx.request.url).pathname;controllerappcontrollerdefaultuser.js’use strict’;const Controller = require(’egg’).Controller;class UserController extends Controller { async welcome() { await this.ctx.render(‘default/user/welcome.html’); } async order() { const uid = this.ctx.service.cookies.get(‘userinfo’)._id; const page = this.ctx.request.query.page || 1; var json = { “uid”: uid }; //查询当前用户下面的所有订单 const pageSize = 2; // 总数量 const totalNum = await this.ctx.model.Order.find(json).countDocuments(); //聚合管道要注意顺序 const result = await this.ctx.model.Order.aggregate([{ $lookup: { from: ‘order_item’, localField: ‘_id’, foreignField: ‘order_id’, as: ‘orderItems’, }, }, { $sort: { “add_time”: -1 } }, { $match: { “uid”: this.app.mongoose.Types.ObjectId(uid) } //条件 }, { $skip: (page - 1) * pageSize, }, { $limit: pageSize, } ]); await this.ctx.render(‘default/user/order.html’, { list: result, totalPages: Math.ceil(totalNum / pageSize), page, }); } async orderinfo() { // this.ctx.body = ‘用户订单’; await this.ctx.render(‘default/user/order_info.html’); } async address() { this.ctx.body = ‘收货地址’; }}module.exports = UserController;view左侧导航appviewdefaultuseruser_left.html<div class=“ddzx”>用户中心</div><div class=“subddzx”> <ul> <li <%if(url==’/user/welcome’ ){%> class=“active” <%}%>><a href="/user/welcome">欢迎页面</a></li> <li <%if(url==’/user/order’ || url==’/user/orderinfo’ ){%> class=“active” <%}%>><a href="/user/order">我的订单</a></li> <li><a href="#">用户信息</a></li> <li><a href="#">我的收藏</a></li> <li><a href="#">我的评论</a></li> </ul></div>欢迎页appviewdefaultuserwelcome.html<% include ./user_left.html%> <div class=“grzxbj”> <div class=“selfinfo center”> <div class=“lfnav fl”> <% include ./user_left.html%> </div> <div class=“rtcont fr”> <div class=“portal-content-box”> <div class=“box-bd”> <div class=“portal-main clearfix”> <div class=“user-card”> <h2 class=“username”>2152135723</h2> <p class=“tip”>晚上好</p> <a class=“link” href=“https://account.xiaomi.com/pass/userInfo" target="_blank” data-stat-id=“30d619c492c43471” onclick="_msq.push([’trackEvent’, ‘f4f3444fdfa3d27a-30d619c492c43471’, ‘https://account.xiaomi.com/pass/userInfo’, ‘pcpid’, ‘’]);">修改个人信息 &gt;</a> <img class=“avatar” src=“https://account.xiaomi.com/static/img/passport/photo.jpg" width=“150” height=“150” alt=“2152135723”> </div> <div class=“user-actions”> <ul class=“action-list”> <li>账户安全:<span class=“level level-2”>普通</span></li> <li>绑定手机:<span class=“tel”>150********01</span></li> <li>绑定邮箱:<span class=“tel”></span><a class=“btn btn-small btn-primary” href=“https://account.xiaomi.com/pass/userInfo" target="_blank” data-stat-id=“d36648c4ef44cb77” onclick="_msq.push([’trackEvent’, ‘f4f3444fdfa3d27a-d36648c4ef44cb77’, ‘https://account.xiaomi.com/pass/userInfo’, ‘pcpid’, ‘’]);">绑定</a></li> </ul> </div> </div> <div class=“portal-sub”> <ul class=“info-list clearfix”> <li> <h3>待支付的订单:<span class=“num”>0</span></h3> <a href=”//static.mi.com/order/?type=7" data-stat-id=“ff48b3f50874dae7”>查看待支付订单</a> <img src="//s01.mifile.cn/i/user/portal-icon-1.png" alt=""> </li> <li> <h3>待收货的订单:<span class=“num”>0</span></h3> <a href="//static.mi.com/order/?type=8" data-stat-id=“f7b15ff4b8710895”>查看待收货订单</a> <img src="//s01.mifile.cn/i/user/portal-icon-2.png" alt=""> </li> </ul> </div> </div> </div> </div> <div class=“clear”></div> </div> </div>订单页 <% include ./user_left.html%>订单详情页<% include ./user_left.html%>效果欢迎页订单页订单详情页 ...

February 21, 2019 · 2 min · jiezi

egg(113)--egg之登录成功跳转到登录之前的页面

流程点击登录按钮,前端把当前的url,传给后台后台把url传给,登录页面登录页面,点击登录,跳转到登录前的页面效果第一步第二步第三步router router.get(’/login’, initMiddleware, controller.default.pass.login);点击登录按钮viewappviewdefaultpublicheader.html<li><a href=“javascript:void(0)” id=“loginButton” target="_blank">登录</a></li>apppublicdefaultjsbase.js initLogin: function() { $("#loginButton").click(function() { // alert(location.href); location.href = ‘/login?returnUrl=’ + encodeURIComponent(location.href); }) }controllerappcontrollerdefaultpass.js async login() { //获取returnUrl var returnUrl = this.ctx.request.query.returnUrl; returnUrl = returnUrl ? decodeURIComponent(returnUrl) : ‘/’; await this.ctx.render(‘default/pass/login.html’, { returnUrl: returnUrl }); }登录页面appviewdefaultpasslogin.html<input type=“hidden” name=“returnUrl” id=“returnUrl” value="<%=returnUrl%>" /> $("#doLogin").click(function(e) { var returnUrl = $(’#returnUrl’).val(); var username = $(’#username’).val(); var password = $(’#password’).val(); var identify_code = $(’#identify_code’).val(); var reg = /^[\d]{11}$/; if (!reg.test(username)) { alert(‘手机号输入错误’); return false; } if (identify_code.length < 4) { alert(‘验证码长度不合法’); return false; } //ajax请求 $.post(’/pass/doLogin’, { username: username, identify_code: identify_code, password: password }, function(response) { console.log(response); if (response.success == true) { // location.href = “/”; location.href = returnUrl; } else { $("#identify_code_img").attr(‘src’, ‘/verify?mt=’ + Math.random()); alert(response.msg); } }) })去结算的登录跳转中间件appmiddlewareuserauth.jsmodule.exports = (options, app) => { return async function init(ctx, next) { //判断前台用户是否登录 如果登录可以进入 ( 去结算 用户中心) 如果没有登录直接跳转到登录 var userinfo = ctx.service.cookies.get(‘userinfo’); var prevPage = ctx.request.headers.referer; //上一个页面的地址 if (userinfo && userinfo._id && userinfo.phone) { //判断数据库里面有没有当前用户 var userResutl = await ctx.model.User.find({ “_id”: userinfo._id, “phone”: userinfo.phone }); if (userResutl && userResutl.length > 0) { //注意 await next(); } else { // ctx.redirect(’/login’); ctx.redirect(’/login?returnUrl=’ + encodeURIComponent(prevPage)); } } else { // ctx.redirect(’/login’); ctx.redirect(’/login?returnUrl=’ + encodeURIComponent(prevPage)); } };};效果 ...

February 20, 2019 · 1 min · jiezi

egg(109)--egg之微信支付

微信支付前的准备工作准备工作准备工作:个体工商户、企业、政府及事业单位。需要获取内容appid:应用 APPID(必须配置,开户邮件中可查看)MCHID:微信支付商户号(必须配置,开户邮件中可查看)KEY:API 密钥,参考开户邮件设置(必须配置,登录商户平台自行设置)express支付(测试)向微信发送带金额和标题参数的请求//引入统一下单的apivar wechatPay = require(’./module/wechatPay’);var express = require(’express’);var bodyParser = require(‘body-parser’);var xmlparser = require(’express-xml-bodyparser’);var app = new express();//xmlparserapp.use(xmlparser());app.use(express.static(’./public’));//使用中间件body-parser获取post参数 app.use(bodyParser.urlencoded({ extended: false }));app.use(bodyParser.json());app.set(‘view engine’, ’ejs’);app.get(’/order’, function(req, res) { var openid = ‘’; var config = { mch_id: ‘1502539541’, wxappid: “wx7bf3787c783116e4”, wxpaykey: ‘zhongyuantengitying6666666666666’ } var pay = new wechatPay(config); pay.createOrder({ openid: openid, notify_url: ‘http://118.123.14.36:8000/notifyUrl’, //微信支付完成后的回调 out_trade_no: new Date().getTime(), //订单号 attach: ‘名称’, body: ‘购买信息’, total_fee: ‘1’, // 此处的额度为分 spbill_create_ip: req.connection.remoteAddress.replace(/::ffff:/, ‘’) }, function(error, responseData) { console.log(‘11111111’); console.log(responseData); if (error) { console.log(error); } res.json(responseData); /签名字段/ });})app.listen(8000, function() { console.log(‘port 8000 is running!’);});回调里有支付url把url转成二维码手机扫码支付支付页面有金额和标题信息 ...

February 19, 2019 · 1 min · jiezi

eggjs实现一个较为完整的后台管理系统

包含功能登录/注册首页菜单管理角色管理用户管理字典管理使用npm install / yarn install 下载依赖导入sql 本项目使用mysql 基础sql文件在db目录结尾-用了一个星期业余时间开发的,大家可以在此基础上开发!github地址https://github.com/wxbing1207…

January 23, 2019 · 1 min · jiezi

Nunjucks使用正则表达式示例

我在使用egg.js时,他用的模板引擎是Nunjucks,其中有个地方需要用到正则,可是官方文档基本上写了跟没写一样,官方的正则表达式。于是我便去找例子了。正则表达式在Nunjucks中使用正则表达式的示例:{% set regExp = r/^foo.*/g %}{% if regExp.test(‘foo’) %} Foo in the house!{% endif %}那么这个就会被正常显示。其他的表达式也是可以的。例如:<!– 有个后台存储的未验证的手机号码(mobile)在前端显示,如果格式正确则显示,不正确则显示“暂无” –>{% set regExp = r/^\d{11}$/g %}<span>号码:{{mobile if regExp.test(mobile) else ‘暂无’}}</span>这两个例子应该看得懂吧。正则这块我并没有看源码,因为搜索出来了,我这里参考的regex exmaple?后来发现其实很多方法文档并没有写出来,这时候可能真的需要看看源码了,有兴趣的话可以阅读下filter的源码https://github.com/mozilla/nu…

January 15, 2019 · 1 min · jiezi

egg(109)--egg之支付宝支付

router //支付 router.get(’/alipay/pay’, initMiddleware, controller.default.alipay.pay); //支付成功回调 router.get(’/alipay/alipayReturn’, initMiddleware, controller.default.alipay.alipayReturn); //支付成功异步通知 注意关闭csrf验证 router.post(’/alipay/alipayNotify’, initMiddleware, xmlparseMiddleware, controller.default.alipay.alipayNotify);config配置config/config.default.js //支付宝支付的配置 exports.alipayOptions = { app_id: ‘2018122062672017’, appPrivKeyFile: “MIIEowIBAAKCAQEAytRAWUJE+t7Xg62PFPpwCxaIBwO942bZX2ehlHbdLSs0i1H3xHlIGTF/0pAYksLuXq8ovyGW263MqvAjt5n97JPjD1ip9eFuIZhZ2wbrOac+MerE+x7agDOBgmJGwdffxbGRRkjz/OtwrIfIVf7TcAm/MPSAuD3RAIkvVXzQO16x6BnBnev1JR+HybeyUssMCk1y6JZ2pZ4H62gGKGvDQcV8NW0q7g4qu2CwQKMVhbnMpG/wRuIla/MOB9MPZiV4CINsxNGya5mmzkXTemjheRl9me6dEZEgKU+tcgTH5Y36faRphbQQ9ATAt3EZQXw4gkoO9vHyEsf7mAAOofQyVQIDAQABAoIBAG+PpUEzKRvPnDyqJuwD/8KphvJMxZIhjOhj6MTvSCJDBGipEh24E8b/qe3YIhv/KftcXo4aXI7CHrPa19px0e/hO9/CBeHfN6M02B+Xw6P3cEcmeWgihU5EhjR/96lBIqzrSRuensz7dwL+wFtEiWmzgrzbjz1HiwC/dBCSUTqFlT7+M+xx8N6w3gQbZ8bpW33XY4KB26C5G8/g6ImEMUbNez8p+24qVztszHWfDHmGJp//Z4g6dgyd2RNrNZdzCyNmlsFRYRXgKa1WbC7qi2ihPUMmYLhlD5OFwZkGbk7bPy+GTYfAK6JjHRjONtdcE06pVFPUMlr7OL96MsABt1UCgYEA8YEPuvbE0Y4i+YlHrYxJ8iJ8WceWL1mNp6QFG6cNQ06+ohPhVVjEdKSFLQIz03aRGQI+E6Kuki5JVwlUeUmqJM1LJxA7NEm9b+YNQG+Y/23FcYAbuaiJp22qpo45XNLSs6QoKcqyJkZwM/Niv07mLP907PSt6WzM4a10vVs+pu8CgYEA1wDpWdxPpUsVfggCrfel6DveLzHvtX4DWPbL9NXj2j39B6Y0+1BuHMw5WZ/GoZxC0Gi/buuspwaqws9HiGg5ttMJqz23YOKUwHkpZrIpZjyVAjDoRduKcaX3q4/NCs5CPzWoGjfcJXoxcoZuYos5/kg+tXhmsH/DA0jmTfyR2vsCgYATZ71t1npGJFenGWLLDSS78g1v4Vut/lIlkEZgzHGCYQdsWpCWnQVcIgQZc73aVgKesdFvHnlMga+e8L765/Jl9qD9SI6ZSvuPzDpwXQc8LwPYdOTFbEdzTpqRu4fcb4xCpwQbJ5BdBvfpFLtwh9Ry9SveBmMbCIUF9TwWIwjLvQKBgFcQpG5iO9J4zFREFCm0rneTvs6nzyVUyTA+iKs17lYTYiK12KComl6JCPRVMk+BgsD4mgTl5P2iQoYvAA2p/y0c2r6AeIEAYDJtHinbHc6r27+OZJDdbXvGNLxBuEuW6NbF+LPdSQXYLKvu6kZ3kN17DgHYpuT0Z9ktrS2JiNr/AoGBAMBMG/25ioZMVuMFL2D1NqW221NLGMnHtFSATT1o6XGNEd5/PABCei7l2KSSrv93wyXllnk6cVauSn32zmKNWC0i6Ei89wYIUlgF7grRxBWHm0H8hgbss4YHqEf19t8Fu9QW0g48VXWsZaDmoNQotxytWx3rJ5jIuvz8xWz+UkbH”, alipayPubKeyFile: “MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhb/KlxYfhRE8KRp92MQM8ZB8NVjoM9LYFOnPIuNtcMZVA8ld7ybDP2FiA+QEE7wLGqMImwl1Y4xzkrTLCjHVC8fdR8ZvzZR2I3ZOrARerI9+RbkCfT+7YLv55+A+WTHEyiB+v7PfXVTT28s0CHNLPXMyQD1u8UVEQEpbMSs8hH3pJF55Li7kc5VvJpV3RVO9TXZTVAA5mSp9FvO3u+47IJDgFVLnqqHh6ETL1nHVpxiAY2LGer+RWpVYD8v+We+VWsrfJP7bO0xr2pwizldepo8YNYPgcIAIwd7KiveypL1pA0xWgSjUHzrkVh1j/nSnvJgKSdydU/VRcaVt/Mt8wwIDAQAB” } exports.alipayBasicParams = { return_url: ‘http://127.0.0.1:7001/alipay/alipayReturn’, //支付成功返回地址 notify_url: ‘http://127.0.0.1:7001/alipay/alipayNotify’ //支付成功异步通知地址 }支付前controllerapp/controller/default/alipay.js async pay() { // this.ctx.body=‘支付宝支付’; var d = new Date(); const data = { subject: ‘辣条111’, out_trade_no: d.getTime().toString(), total_amount: ‘0.1’ } var url = await this.service.alipay.doPay(data); this.ctx.redirect(url); }serviceapp/service/alipay.js async doPay(orderData, ) { return new Promise((resolve, reject) => { //实例化 alipay const service = new Alipay(this.config.alipayOptions); //获取返回的参数 // this.config.alipayBasicParams service.createPageOrderURL(orderData, this.config.alipayBasicParams) .then(result => { console.log(result); resolve(result.data); }) }) }支付后支付后跳转的地址controllerapp/controller/default/alipay.js async alipayReturn() { this.ctx.body = ‘支付成功’; //接收异步通知 }支付宝支付成功异步通知引入koa-xml-body解析支付成功后的回调xml格式数据app/middleware/xmlparse.jsmodule.exports = require(‘koa-xml-body’);controller支付成功以后更新订单 必须正式上线app/controller/default/alipay.js async alipayNotify() { const params = this.ctx.request.body; //接收 post 提交的 XML console.log(params); var result = await this.service.alipay.alipayNotify(params); console.log(’————-’); console.log(result); if (result.code == 0) { if (params.trade_status == ‘TRADE_SUCCESS’) { //更新订单 } } //接收异步通知 }service验证异步通知的数据是否正确app/service/alipay.js alipayNotify(params) { //实例化 alipay const service = new Alipay(this.config.alipayOptions); return service.makeNotifyResponse(params); }流程点击 支付宝支付订单信息传入订单名称,订单号,总价 const data = { subject: ‘辣条111’, out_trade_no: d.getTime().toString(), total_amount: ‘0.1’ }点击去支付支付宝把我们的订单参数,加密封装,之后返回一个url,我们重定向,跳转到这个url,就是支付的页面支付宝支付页面自动跳转到支付页面,url为支付宝自动生成扫码支付支付成功后,跳转的地址支付成功后,返回的xml参数,解析成json ...

January 14, 2019 · 1 min · jiezi

egg(107)--egg之提交订单收货地址判断、egg后端防止提交重复订单、去支付页面显示订单信息

提交订单收货地址判断没有地址,提示输入地址,才能提交订单app/view/default/checkout.html<form action="/buy/doOrder?_csrf=<%=csrf%>" method=“POST” id=“checkoutForm”></form> //提交数据监测是否有默认收货地址 var flag = true; $("#checkoutForm").submit(function() { if (flag) { flag = false; var addressCount = $(’#addressList .selected’).length; if (!addressCount) { alert(‘请填写默认收货地址’); return false; } return true; } else { return false; } })效果图egg后端防止单个用户提交重复订单传签名orderSign给checkout.html页面app/controller/default/buy.jsasync checkout() { //签名防止重复提交订单 var orderSign = await this.service.tools.md5(await this.service.tools.getRandomNum()); this.ctx.session.orderSign = orderSign; await this.ctx.render(‘default/checkout.html’, { orderSign: orderSign }); }checkout.html页面接收签名orderSignapp/view/default/checkout.html <form action="/buy/doOrder?_csrf=<%=csrf%>" method=“POST” id=“checkoutForm”> <input type=“hidden” name=“orderSign” value="<%=orderSign%>" /> </form>提交订单请求,接收签名orderSign和后台session的`orderSign对比app/controller/default/buy.js async doOrder() { var orderSign = this.ctx.request.body.orderSign; if (orderSign != this.ctx.session.orderSign) { return false; } this.ctx.session.orderSign = null; //todo…. }效果去支付页面显示订单信息controllerapp/controller/default/buy.js async confirm() { var id = this.ctx.request.query.id; var orderResult = await this.ctx.model.Order.find({ “_id”: id }); if (orderResult && orderResult.length > 0) { //获取商品 var orderItemResult = await this.ctx.model.OrderItem.find({ “order_id”: id }); await this.ctx.render(‘default/confirm.html’, { orderResult: orderResult[0], orderItemResult: orderItemResult }); } else { //错误 this.ctx.redirect(’/’); } }viewapp/view/default/confirm.html <ul> <li class=“clearfix”> <div class=“label”>订单号:</div> <div class=“content”> <span class=“order-num”> <%=orderResult.order_id%> </span> </div> </li> <li class=“clearfix”> <div class=“label”>收货信息:</div> <div class=“content”> <%=orderResult.name%> <%=orderResult.phone%>&nbsp;&nbsp; <%=orderResult.address%> </div> </li> <li class=“clearfix”> <div class=“label”>商品名称:</div> <div class=“content”> <%for(var i=0;i<orderItemResult.length;i++){%> <p> <%=orderItemResult[i].product_title%> </p> <%}%> </div> </li> </ul>效果 ...

January 12, 2019 · 1 min · jiezi

egg(106)--egg之106提交订单,生成订单和子订单,删除购物车

流程购物车中选中商品,点击去结算获取收货地址信息需要获取购买商品的信息把这些信息 放在订单表删除购物车里面的数据modelorderapp/model/order.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const d = new Date(); const Order = new Schema({ uid: { type: Schema.Types.ObjectId }, all_price: { type: Number }, order_id: { type: Number }, name: { type: String }, phone: { type: Number }, address: { type: String }, zipcode: { type: String }, pay_status: { type: Number }, // 支付状态: 0 表示未支付 1 已经支付 pay_type: { type: String }, // 支付类型: alipay wechat order_status: { // 订单状态: 0 已下单 1 已付款 2 已配货 3、发货 4、交易成功 5、退货 6、取消 type: Number }, add_time: { type: Number, default: d.getTime() } }); return mongoose.model(‘Order’, Order, ‘order’);};order_itemapp/model/order_item.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const d = new Date(); const OrderItem = new Schema({ order_id: { type: Schema.Types.ObjectId }, product_title: { type: String }, product_id: { type: Schema.Types.ObjectId }, product_img: { type: String }, product_price: { type: Number }, product_num: { type: Number }, add_time: { type: Number, default: d.getTime(), } }); return mongoose.model(‘OrderItem’, OrderItem, ‘order_item’);};router //去结算 router.get(’/buy/checkout’, initMiddleware, userauthMiddleware, controller.default.buy.checkout); //确认订单去支付 router.get(’/buy/confirm’, initMiddleware, userauthMiddleware, controller.default.buy.confirm); //提交订单 router.post(’/buy/doOrder’, initMiddleware, userauthMiddleware, controller.default.buy.doOrder);去结算controllerapp/controller/default/buy.js async checkout() { // 获取购物车选中的商品 let orderList = []; let allPrice = 0; let cartList = this.service.cookies.get(‘cartList’); if (cartList && cartList.length > 0) { for (let i = 0; i < cartList.length; i++) { if (cartList[i].checked) { orderList.push(cartList[i]); allPrice += cartList[i].price * cartList[i].num; } } // 获取当前用户的所有收货地址 const uid = this.ctx.service.cookies.get(‘userinfo’)._id; const addressList = await this.ctx.model.Address.find({ uid }).sort({ default_address: -1 }); await this.ctx.render(‘default/checkout.html’, { orderList, allPrice, addressList, }); } else { // 恶意操作 this.ctx.redirect(’/cart’); } }提交订单controllerapp/controller/default/buy.js async doOrder() { const uid = this.ctx.service.cookies.get(‘userinfo’)._id; let addressResult = await this.ctx.model.Address.find({ “uid”: uid, “default_address”: 1 }); let cartList = this.service.cookies.get(‘cartList’); if (addressResult && addressResult.length > 0 && cartList && cartList.length > 0) { var all_price = 0; let orderList = cartList.filter((value) => { if (value.checked) { all_price += value.price * value.num; return value; } }) //执行提交订单的操作 let order_id = await this.service.tools.getOrderId(); let name = addressResult[0].name; let phone = addressResult[0].phone; let address = addressResult[0].address; let zipcode = addressResult[0].zipcode; let pay_status = 0; let pay_type = ‘’; let order_status = 0; let orderModel = new this.ctx.model.Order({ order_id, name, phone, address, zipcode, pay_status, pay_type, order_status, all_price }); let orderResult = await orderModel.save(); if (orderResult && orderResult._id) { //增加商品信息 for (let i = 0; i < orderList.length; i++) { let json = { “order_id”: orderResult._id, //订单id “product_title”: orderList[i].title, “product_id”: orderList[i]._id, “product_img”: orderList[i].goods_img, “product_price”: orderList[i].price, “product_num”: orderList[i].num } let orderItemModel = new this.ctx.model.OrderItem(json); await orderItemModel.save(); } //删除购物车中已经购买的商品 var unCheckedCartList = cartList.filter((value) => { if (!value.checked) { return value; } }) this.service.cookies.set(‘cartList’, unCheckedCartList); this.ctx.redirect(’/buy/confirm?id=’ + orderResult._id); } else { this.ctx.redirect(’/checkout’); } } else { this.ctx.redirect(’/checkout’); } }确认订单,去支付app/controller/default/buy.js async confirm() { await this.ctx.render(‘default/confirm.html’); }效果购物车中选中商品,点击去结算订单确认,点击立即下单生成订单,选择付款购物车中,删除刚才选择的商品,留下没有选中的商品第一步第二步第三步第四步数据库中查看数据orderorder_item ...

January 12, 2019 · 3 min · jiezi

egg(105)--egg之去结算页面收货地址的增加 修改 显示(下)

修改默认的收货地址获取用户当前收货地址id 以及用户id更新当前用户的所有收货地址的默认收货地址状态为0更新当前收货地址的默认收货地址状态为1routerrouter.get(’/user/changeDefaultAddress’, initMiddleware, userauthMiddleware, controller.default.address.changeDefaultAddress);controllerapp/controller/default/address.js async changeDefaultAddress() { const uid = this.ctx.service.cookies.get(‘userinfo’)._id; var id = this.ctx.request.query.id; await this.ctx.model.Address.updateMany({ uid: uid }, { default_address: 0 }); await this.ctx.model.Address.updateMany({ uid: uid, “_id”: id }, { default_address: 1 }); this.ctx.body = { success: true, msg: ‘更新默认收货地址成功’ }; }viewapp/view/default/checkout.html $("#addressList .J_addressItem").click(function() { $(this).addClass(‘selected’).siblings().removeClass(‘selected’); var id = $(this).attr(‘data-id’); //收货地址的_id $.get(’/user/changeDefaultAddress?id=’ + id, function(response) { //console.log(response); }) })效果修改收货地址routerrouter.get(’/user/getOneAddressList’, initMiddleware, userauthMiddleware, controller.default.address.getOneAddressList);router.post(’/user/editAddress’, initMiddleware, userauthMiddleware, controller.default.address.editAddress);controllerapp/controller/default/address.js获取一个收货地址 async getOneAddressList() { const uid = this.ctx.service.cookies.get(‘userinfo’)._id; var id = this.ctx.request.query.id; var result = await this.ctx.model.Address.find({ uid: uid, “_id”: id }); this.ctx.body = { success: true, result: result, }; }编辑收货地址 async editAddress() { const uid = this.ctx.service.cookies.get(‘userinfo’)._id; const id = this.ctx.request.body.id; const name = this.ctx.request.body.name; const phone = this.ctx.request.body.phone; const address = this.ctx.request.body.address; const zipcode = this.ctx.request.body.zipcode; await this.ctx.model.Address.updateOne({ “_id”: id, “uid”: uid }, { name, phone, address, zipcode }); const addressList = await this.ctx.model.Address.find({ uid }).sort({ default_address: -1 }); this.ctx.body = { success: true, result: addressList, }; }效果 ...

January 12, 2019 · 1 min · jiezi

egg(104)--egg之去结算页面收货地址的增加 修改 显示(上)

routerrouter.post(’/user/addAddress’, initMiddleware, userauthMiddleware, controller.default.address.addAddress);modelmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const d = new Date(); const AddressSchema = new Schema({ uid: { type: Schema.Types.ObjectId }, name: { type: String }, phone: { type: Number }, address: { type: String }, zipcode: { type: String }, default_address: { type: Number, default: 1 }, add_time: { type: Number, default: d.getTime(), }, }); return mongoose.model(‘Address’, AddressSchema, ‘address’);};controller增加地址app/controller/default/address.js async addAddress() { const uid = this.ctx.service.cookies.get(‘userinfo’)._id; const name = this.ctx.request.body.name; const phone = this.ctx.request.body.phone; const address = this.ctx.request.body.address; const zipcode = this.ctx.request.body.zipcode; const addressCount = await this.ctx.model.Address.find({ uid }).count(); if (addressCount > 20) { this.ctx.body = { success: false, result: ‘增加收货地址失败 收货地址数量超过限制’, }; } else { await this.ctx.model.Address.updateMany({ uid }, { default_address: 0 }); const addressModel = new this.ctx.model.Address({ uid, name, phone, address, zipcode }); await addressModel.save(); const addressList = await this.ctx.model.Address.find({ uid }).sort({ default_address: -1 }); this.ctx.body = { success: true, result: addressList, }; } }显示地址app/controller/default/buy.js async checkout() { // 获取购物车选中的商品 const orderList = []; let allPrice = 0; const cartList = this.service.cookies.get(‘cartList’); if (cartList && cartList.length > 0) { for (let i = 0; i < cartList.length; i++) { if (cartList[i].checked) { orderList.push(cartList[i]); allPrice += cartList[i].price * cartList[i].num; } } // 获取当前用户的所有收货地址 const uid = this.ctx.service.cookies.get(‘userinfo’)._id; const addressList = await this.ctx.model.Address.find({ uid }).sort({ default_address: -1 }); await this.ctx.render(‘default/checkout.html’, { orderList, allPrice, addressList, }); } else { // 恶意操作 this.ctx.redirect(’/cart’); } }viewapp/view/default/checkout.html <div id=“addressList”> <%for(var i=0;i<addressList.length;i++){%> <div class=“address-item J_addressItem <%if(addressList[i].default_address){%>selected <%}%>” data-id="<%=addressList[i]._id%>" data-name="<%=addressList[i].name%>" data-phone="<%=addressList[i].phone%>" data-address="<%=addressList[i].address%>"> <dl> <dt> <em class=“uname”><%=addressList[i].name%></em> </dt> <dd class=“utel”> <%=addressList[i].phone%> </dd> <dd class=“uaddress”> <%=addressList[i].address%> </dd> </dl> <div class=“actions” data-id="<%=addressList[i]._id%>"> <a href=“javascript:void(0);” class=“modify addressModify”>修改</a> </div> </div> <%}%> </div> <script> $(function() { $(’#addAddressButton’).click(function() { var name = $(’#add_name’).val(); var phone = $(’#add_phone’).val(); var address = $(’#add_address’).val(); var zipcode = $(’#add_zipcode’).val(); //判断格式是否正确 -自己完善 if (name == ’’ || phone == "" || address == “”) { alert(‘格式不正确’) return false; } //csrf $.post(’/user/addAddress’, { name: name, phone: phone, address: address, zipcode: zipcode }, function(response) { console.log(response); var addressList = response.result; var str = ‘’; if (response.success == true) { for (var i = 0; i < addressList.length; i++) { if (addressList[i].default_address) { str += ‘<div class=“address-item J_addressItem selected” data-id="’ + addressList[i]._id + ‘" data-name="’ + addressList[i].name + ‘" data-phone="’ + addressList[i].phone + ‘" data-address="’ + addressList[i].address + ‘" > ‘; str += ‘<dl>’; str += ‘<dt> <em class=“uname”>’ + addressList[i].name + ‘</em> </dt>’; str += ‘<dd class=“utel”>’ + addressList[i].phone + ‘</dd>’; str += ‘<dd class=“uaddress”>’ + addressList[i].address + ‘</dd>’; str += ‘</dl>’; str += ‘<div class=“actions”>’; str += ‘<a href=“javascript:void(0);” data-id="’ + addressList[i]._id + ‘" class=“modify addressModify”>修改</a>’; str += ‘</div>’; str += ‘</div>’; } else { str += ‘<div class=“address-item J_addressItem” data-id="’ + addressList[i]._id + ‘" data-name="’ + addressList[i].name + ‘" data-phone="’ + addressList[i].phone + ‘" data-address="’ + addressList[i].address + ‘" > ‘; str += ‘<dl>’; str += ‘<dt> <em class=“uname”>’ + addressList[i].name + ‘</em> </dt>’; str += ‘<dd class=“utel”>’ + addressList[i].phone + ‘</dd>’; str += ‘<dd class=“uaddress”>’ + addressList[i].address + ‘</dd>’; str += ‘</dl>’; str += ‘<div class=“actions”>’; str += ‘<a href=“javascript:void(0);” data-id="’ + addressList[i]._id + ‘" class=“modify addressModify”>修改</a>’; str += ‘</div>’; str += ‘</div>’; } } //增加 $("#addressList").html(str); } else { alert(‘增加失败’); } $(’#addAddress’).modal(‘hide’); }) }) }) </script>效果 ...

January 11, 2019 · 3 min · jiezi

egg(102)--egg之用户登录 以及登录时候涉及的一些安全问题

router router.get(’/login’, initMiddleware, controller.default.pass.login); router.post(’/pass/doLogin’, initMiddleware, controller.default.pass.doLogin); router.get(’/pass/loginOut’, initMiddleware, controller.default.pass.loginOut);登录controllerapp/controller/default/pass.js async login() { await this.ctx.render(‘default/pass/login.html’); } async doLogin() { var username = this.ctx.request.body.username; var password = this.ctx.request.body.password; var identify_code = this.ctx.request.body.identify_code; if (identify_code != this.ctx.session.identify_code) { //重新生成验证码 为了安全 var captcha = await this.service.tools.captcha(120, 50); this.ctx.session.identify_code = captcha.text; this.ctx.body = { success: false, msg: ‘输入的图形验证码不正确’ } } else { password = await this.service.tools.md5(password); var userResult = await this.ctx.model.User.find({ “phone”: username, password: password }, ‘_id phone last_ip add_time email status’); if (userResult.length) { //cookies 安全 加密 this.service.cookies.set(‘userinfo’, userResult[0]); this.ctx.body = { success: true, msg: ‘登录成功’ } } else { //重新生成验证码 var captcha = await this.service.tools.captcha(120, 50); this.ctx.session.identify_code = captcha.text; this.ctx.body = { success: false, msg: ‘用户名或者密码错误’ } } } }配置请求不经过csrf验证config/config.default.js config.security = { csrf: { // 判断是否需要 ignore 的方法,请求上下文 context 作为第一个参数 ignore: ctx => { if (ctx.request.url == ‘/pass/doLogin’) { return true; } return false; } } }viewapp/view/default/pass/login.html <div class=“form”> <div class=“login”> <div class=“login_center”> <div class=“login_top”> <div class=“left fl”>会员登录</div> <div class=“right fr”>您还不是我们的会员?<a href="/register/registerStep1" target="_self">立即注册</a></div> <div class=“clear”></div> <div class=“xian center”></div> </div> <div class=“login_main center”> <div class=“username”>用户名:<input class=“shurukuang” id=“username” type=“text” name=“username” placeholder=“请输入你的手机号” /></div> <div class=“username”>密 码:<input class=“shurukuang” id=“password” type=“password” name=“password” placeholder=“请输入你的密码” /></div> <div class=“username”> <div class=“left fl”>验证码:<input class=“yanzhengma” id=“identify_code” type=“text” name=“identify_code” placeholder=“请输入验证码” /></div> <div class=“right fl”> <img id=“identify_code_img” src="/verify" title=“看不清?点击刷新” onclick=“javascript:this.src=’/verify?mt=’+Math.random()"> </div> <div class=“clear”></div> </div> </div> <div class=“login_submit”> <button class=“submit” id=“doLogin”>立即登录</button> </div> </div> </div> </div> $(function() { $("#doLogin”).click(function(e) { var username = $(’#username’).val(); var password = $(’#password’).val(); var identify_code = $(’#identify_code’).val(); var reg = /^[\d]{11}$/; if (!reg.test(username)) { alert(‘手机号输入错误’); return false; } if (identify_code.length < 4) { alert(‘验证码长度不合法’); return false; } //ajax请求 $.post(’/pass/doLogin’, { username: username, identify_code: identify_code, password: password }, function(response) { console.log(response); if (response.success == true) { location.href = “/”; } else { $("#identify_code_img").attr(‘src’, ‘/verify?mt=’ + Math.random()); alert(response.msg); } }) }) })效果当输入的验证码不正确的时候,返回新的验证码退出controllerapp/controller/default/pass.js async loginOut() { this.service.cookies.set(‘userinfo’, ‘’); this.ctx.redirect(’/’); }viewapp/view/default/public/header.html<li><a href="/pass/loginOut">退出登录</a></li>效果 ...

January 11, 2019 · 2 min · jiezi

egg(103)--egg之定义公共的中间件判断用户是否登录以及去结算页面制作

判断用户是否登录中间件app/middleware/userauth.jsmodule.exports = (options, app) => { return async function init(ctx, next) { //判断前台用户是否登录 如果登录可以进入 ( 去结算 用户中心) 如果没有登录直接跳转到登录 var userinfo = ctx.service.cookies.get(‘userinfo’); if (userinfo && userinfo._id && userinfo.phone) { //判断数据库里面有没有当前用户 var userResutl = await ctx.model.User.find({ “_id”: userinfo._id, “phone”: userinfo.phone }); if (userResutl && userResutl.length > 0) { //注意 await next(); } else { ctx.redirect(’/login’); } } else { ctx.redirect(’/login’); } };};router var userauthMiddleware = app.middleware.userauth({}, app); router.get(’/buy/checkout’, initMiddleware, userauthMiddleware, controller.default.buy.checkout);}效果点击结算,如果没有登录,就跳转到登录页面购物车到结算页面controllerapp/controller/default/buy.js async checkout() { var orderList = []; var allPrice = 0; var cartList = this.service.cookies.get(‘cartList’); if (cartList && cartList.length > 0) { for (var i = 0; i < cartList.length; i++) { if (cartList[i].checked) { orderList.push(cartList[i]); allPrice += cartList[i].price * cartList[i].num; } } await this.ctx.render(’/default/checkout.html’, { orderList: orderList, allPrice: allPrice }) } else { this.ctx.redirect(’/cart’) } }viewapp/view/default/checkout.html <%for(var i=0;i<orderList.length;i++){%> <li class=“clearfix”> <div class=“col col-img”> <img src="<%=orderList[i].goods_img%>" width=“30” height=“30”> </div> <div class=“col col-name”> <a href="#" target="_blank"> <%=orderList[i].title%> </a> </div> <div class=“col col-price”> <%=orderList[i].price%>元 x <%=orderList[i].num%> </div> <div class=“col col-status”> &nbsp; </div> <div class=“col col-total”> <%=orderList[i].num * orderList[i].price%>元 </div> </li> <%}%> <li class=“clearfix total-price”> <label>应付总额:</label> <span class=“val”><em data-id=“J_totalPrice”><%=allPrice%></em>元</span> </li>效果 ...

January 11, 2019 · 1 min · jiezi

egg(101)--egg之注册 输入密码完成注册 完成注册后实现登录

router.js router.get(’/register/registerStep3’, initMiddleware, controller.default.pass.registerStep3); router.post(’/pass/doRegister’, initMiddleware, controller.default.pass.doRegister);modelapp/model/user.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d = new Date(); const User = new Schema({ password: { type: String }, phone: { type: Number }, last_ip: { type: String }, add_time: { type: Number, default: d.getTime() }, email: { type: String }, status: { type: Number, default: d.getTime() } }); return mongoose.model(‘User’, User, ‘user’);}注册页面controllerapp/controller/default/pass.js async registerStep3() { var sign = this.ctx.request.query.sign; var phone_code = this.ctx.request.query.phone_code; var msg = this.ctx.request.query.msg || ‘’; var add_day = await this.service.tools.getDay(); //年月日 var userTempResult = await this.ctx.model.UserTemp.find({ “sign”: sign, add_day: add_day }); if (userTempResult.length == 0) { this.ctx.redirect(’/register/registerStep1’); } else { await this.ctx.render(‘default/pass/register_step3.html’, { sign: sign, phone_code: phone_code, msg: msg }); } }viewappviewdefaultpassregister_step3.html <form action="/pass/doRegister?_csrf=<%=csrf%>" method=“post” id=“form”> <input type=“hidden” name=“sign” value="<%=sign%>" /> <input type=“hidden” name=“phone_code” value="<%=phone_code%>" /> <div> <input class=“form_input” type=“password” id=“password” name=“password” placeholder=“请输入密码” /> </div> <div> <input class=“form_input” type=“password” id=“rpassword” name=“rpassword” placeholder=“请输入确认密码” /> </div> <%if(msg){%> <p style=“color:red”> <%=msg%> </p> <%}%> <div class=“regist_submit”> <input class=“submit” id=“nextStep” type=“submit” name=“submit” value=“下一步”> </div> </form> $(function() { $("#form").submit(function() { var password = $(’#password’).val(); var rpassword = $(’#rpassword’).val(); if (password.length < 6) { alert(‘用户名的长度不能小于6位’); return false; } if (password != rpassword) { alert(‘密码和确认密码不一致’); return false; } return true; }) })效果提交注册,跳转到首页controllerapp/controller/default/pass.js async doRegister() { var sign = this.ctx.request.body.sign; var phone_code = this.ctx.request.body.phone_code; var add_day = await this.service.tools.getDay(); //年月日 var password = this.ctx.request.body.password; var rpassword = this.ctx.request.body.rpassword; var ip = this.ctx.request.ip.replace(/::ffff:/, ‘’); if (this.ctx.session.phone_code != phone_code) { //非法操作 this.ctx.redirect(’/pass/registerStep1’); } var userTempResult = await this.ctx.model.UserTemp.find({ “sign”: sign, add_day: add_day }); if (userTempResult.length == 0) { //非法操作 this.ctx.redirect(’/pass/registerStep1’); } else { //传入参数正确 执行增加操作 if (password.length < 6 || password != rpassword) { var msg = ‘密码不能小于6位并且密码和确认密码必须一致’; this.ctx.redirect(’/register/registerStep3?sign=’ + sign + ‘&phone_code=’ + phone_code + ‘&msg=’ + msg); } else { var userModel = new this.ctx.model.User({ phone: userTempResult[0].phone, password: await this.service.tools.md5(password), last_ip: ip }) //保存用户 var userReuslt = await userModel.save(); if (userReuslt) { //获取用户信息 var userinfo = await this.ctx.model.User.find({ “phone”: userTempResult[0].phone }, ‘_id phone last_ip add_time email status’) //用户注册成功以后默认登录 //cookies 安全 加密 this.service.cookies.set(‘userinfo’, userinfo[0]); this.ctx.redirect(’/’); } } } }中间件app/middleware/init.js ctx.state.csrf = ctx.csrf; //全局变量 //获取用户信息 ctx.state.userinfo = ctx.service.cookies.get(‘userinfo’);viewapp/view/default/public/header.html <%if(userinfo && userinfo.phone){%> <li> <a href="#"> <%=userinfo.phone%> </a> </li> <%}%>效果 ...

January 9, 2019 · 2 min · jiezi

egg(98)--egg之注册 倒计时 发送验证码 验证验证码

modelapp/model/user.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d = new Date(); const User = new Schema({ password: { type: String }, phone: { type: Number }, last_ip: { type: String }, add_time: { type: Number, default: d.getTime() }, email: { type: String }, status: { type: Number, default: d.getTime() } }); return mongoose.model(‘User’, User, ‘user’);}router router.get(’/pass/sendCode’, initMiddleware, controller.default.pass.sendCode); router.get(’/pass/validatePhoneCode’, initMiddleware, controller.default.pass.validatePhoneCode);controllerapp/controller/default/pass.js async registerStep2() { var sign = this.ctx.request.query.sign; var identify_code = this.ctx.request.query.identify_code; var add_day = await this.service.tools.getDay(); //年月日 var userTempResult = await this.ctx.model.UserTemp.find({ “sign”: sign, add_day: add_day }); if (userTempResult.length == 0) { this.ctx.redirect(’/register/registerStep1’); } else { await this.ctx.render(‘default/pass/register_step2.html’, { sign: sign, phone: userTempResult[0].phone, identify_code: identify_code }); } } async validatePhoneCode() { var sign = this.ctx.request.query.sign; var phone_code = this.ctx.request.query.phone_code; var add_day = await this.service.tools.getDay(); //年月日 if (this.ctx.session.phone_code != phone_code) { this.ctx.body = { success: false, msg: ‘您输入的手机验证码错误’ } } else { var userTempResult = await this.ctx.model.UserTemp.find({ “sign”: sign, add_day: add_day }); if (userTempResult.length <= 0) { this.ctx.body = { success: false, msg: ‘参数错误’ } } else { //判断验证码是否超时 var nowTime = await this.service.tools.getTime(); if ((userTempResult[0].add_time - nowTime) / 1000 / 60 > 30) { this.ctx.body = { success: false, msg: ‘验证码已经过期’ } } else { //用户表有没有当前这个手机号 手机号有没有注册 var userResult = await this.ctx.model.User.find({ “phone”: userTempResult[0].phone }); if (userResult.length > 0) { this.ctx.body = { success: false, msg: ‘此用户已经存在’ } } else { this.ctx.body = { success: true, msg: ‘验证码输入正确’, sign: sign } } } } } }viewapp/view/default/pass/register_step2.html <div class=“yzm”> <input type=“hidden” id=“identify_code” name=“identify_code” value="<%=identify_code%>" /> <input type=“hidden” id=“phone” name=“phone” value="<%=phone%>" /> <input type=“hidden” id=“sign” name=“sign” value="<%=sign%>" /> <input type=“text” id=“phone_code” name=“phone_code” placeholder=“请输入验证码” /> <button id=“sendCode”>重新发送</button> </div> <div class=“regist_submit”> <input class=“submit” id=“nextStep” type=“button” name=“submit” value=“下一步”> <br> <input class=“return” id=“returnButton” type=“button” name=“return” value=“返回”> </div> $(function() { var timer = 10; function Countdown() { if (timer >= 1) { timer -= 1; $("#sendCode").attr(‘disabled’, true); $("#sendCode").html(‘重新发送(’ + timer + ‘)’); setTimeout(function() { Countdown(); }, 1000); } else { $("#sendCode").attr(‘disabled’, false) $("#sendCode").html(‘重新发送’); } } Countdown(); //重新发送验证码 $("#sendCode").click(function() { Countdown(); var phone = $(’#phone’).val(); var identify_code = $(’#identify_code’).val(); $.get(’/pass/sendCode’, { phone: phone, identify_code: identify_code }, function(response) { console.log(response); if (response.success == true) { alert(‘发送验证码成功’); } else { alert(response.msg); } }) }) //验证验证码 $("#nextStep").click(function(e) { var sign = $(’#sign’).val(); var phone_code = $(’#phone_code’).val(); $.get(’/pass/validatePhoneCode’, { sign: sign, phone_code: phone_code }, function(response) { console.log(response); if (response.success == true) { location.href = “/register/registerStep3?sign=” + response.sign; } else { alert(response.msg); } }) }) })效果 ...

January 8, 2019 · 2 min · jiezi

egg(98)--egg之注册 发送手机短信验证码 以及验证当前ip、手机号发送验证码的次数

model 用户注册临时表app/model/user_temp.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d = new Date(); const UserTempSchema = new Schema({ phone: { type: Number }, send_count: { type: Number }, sign: { type: String }, ip: { type: String, }, add_time: { type: Number, default: d.getTime() }, }); return mongoose.model(‘UserTemp’, UserTempSchema, ‘user_temp’);}router.js router.get(’/register/registerStep1’, initMiddleware, controller.default.pass.registerStep1); router.get(’/verify’, initMiddleware, controller.default.base.verify);controller验证码app/controller/default/base.js’use strict’;const Controller = require(’egg’).Controller;class BaseController extends Controller { //验证码 async verify() { var captcha = await this.service.tools.captcha(120, 50); //服务里面的方法 this.ctx.session.identify_code = captcha.text; /验证码的信息/ console.log(captcha.text) this.ctx.response.type = ‘image/svg+xml’; /指定返回的类型/ this.ctx.body = captcha.data; /给页面返回一张图片/ }}module.exports = BaseController;验证码验证码是否正确验证当前ip、手机号发送验证码的次数app/controller/default/pass.js async sendCode() { var phone = this.ctx.request.query.phone; var identify_code = this.ctx.request.query.identify_code; //用户输入的验证码 if (identify_code != this.ctx.session.identify_code) { this.ctx.body = { success: false, msg: ‘输入的图形验证码不正确’ } } else { //判断手机格式是否合法 var reg = /^[\d]{11}$/; if (!reg.test(phone)) { this.ctx.body = { success: false, msg: ‘手机号不合法’ } } else { var add_day = await this.service.tools.getDay(); //年月日 var sign = await this.service.tools.md5(phone + add_day); //签名 var ip = this.ctx.request.ip.replace(/::ffff:/, ‘’); //获取客户端ip var phone_code = await this.service.tools.getRandomNum(); //发送短信的随机码 var userTempResult = await this.ctx.model.UserTemp.find({ “sign”: sign, add_day: add_day }); //1个ip 一天只能发20个手机号 var ipCount = await this.ctx.model.UserTemp.find({ “ip”: ip, add_day: add_day }).count(); console.log(ip) if (userTempResult.length > 0) { if (userTempResult[0].send_count < 6 && ipCount < 10) { //执行发送 var send_count = userTempResult[0].send_count + 1; await this.ctx.model.UserTemp.updateOne({ “_id”: userTempResult[0]._id }, { “send_count”: send_count }); //发送短信 // this.service.sendCode.send(phone,‘随机验证码’) this.ctx.session.phone_code = phone_code; console.log(’———————————’) console.log(phone_code, ipCount); this.ctx.body = { success: true, msg: ‘短信发送成功’, sign: sign } } else { this.ctx.body = { “success”: false, msg: ‘当前手机号码发送次数达到上限,明天重试’ }; } } else { var userTmep = new this.ctx.model.UserTemp({ phone, add_day, sign, ip, send_count: 1 }); userTmep.save(); //发送短信 // this.service.sendCode.send(phone,‘随机验证码’) this.ctx.session.phone_code = phone_code; this.ctx.body = { success: true, msg: ‘短信发送成功’, sign: sign } } } } }serviceapp/service/tools.js async getTime() { var d = new Date(); return d.getTime(); } async getDay() { var day = sd.format(new Date(), ‘YYYYMMDD’); return day; } async getRandomNum() { var random_str = ‘’; for (var i = 0; i < 4; i++) { random_str += Math.floor(Math.random() * 10); } return random_str; }效果 ...

January 7, 2019 · 2 min · jiezi

egg(96下)--egg之购物车之删除商品

router.jsrouter.get(’/removeCart’, initMiddleware, controller.default.cart.removeCart)controllerapp/controller/default/cart.js async removeCart() { var goods_id = this.ctx.request.query.goods_id; var color = this.ctx.request.query.color; var goodsResult = await this.ctx.model.Goods.find({ “_id”: goods_id }); if (!goodsResult || goodsResult.length == 0) { this.ctx.redirect(’/cart’); } else { var cartList = this.service.cookies.get(‘cartList’); for (var i = 0; i < cartList.length; i++) { if (cartList[i]._id == goods_id && cartList[i].color == color) { cartList.splice(i, 1); } } this.service.cookies.set(‘cartList’, cartList); this.ctx.redirect(’/cart’); } }view<span><a class=“delete” href="/removeCart?goods_id=<%=cartList[i]._id%>&color=<%=cartList[i].color%>"> 删除</a></span>效果删除前删除后

January 4, 2019 · 1 min · jiezi

egg(95下)--egg之购物车之异步改变数量

router.js router.get(’/incCart’, initMiddleware, controller.default.cart.incCart); router.get(’/decCart’, initMiddleware, controller.default.cart.decCart);controllerapp/controller/default/cart.js增加 async incCart() { var goods_id = this.ctx.request.query.goods_id; var color = this.ctx.request.query.color; var goodsResult = await this.ctx.model.Goods.find({ “_id”: goods_id }); if (goodsResult.length == 0) { this.ctx.body = { “success”: false, msg: ‘修改数量失败’ } } else { var cartList = this.service.cookies.get(‘cartList’); var currentNum = 0; //当前数量 var allPrice = 0; //总价格 for (var i = 0; i < cartList.length; i++) { if (cartList[i]._id == goods_id && cartList[i].color == color) { cartList[i].num += 1; currentNum = cartList[i].num; } if (cartList[i].checked) { allPrice += cartList[i].price * cartList[i].num; } } this.service.cookies.set(‘cartList’, cartList); this.ctx.body = { “success”: true, num: currentNum, allPrice: allPrice } } }减少 async decCart() { var goods_id = this.ctx.request.query.goods_id; var color = this.ctx.request.query.color; var goodsResult = await this.ctx.model.Goods.find({ “_id”: goods_id }); if (goodsResult.length == 0) { this.ctx.body = { “success”: false, msg: ‘修改数量失败’ } } else { var cartList = this.service.cookies.get(‘cartList’); var currentNum = 0; //当前数量 var allPrice = 0; //总价格 for (var i = 0; i < cartList.length; i++) { if (cartList[i]._id == goods_id && cartList[i].color == color) { if (cartList[i].num > 1) { cartList[i].num -= 1; } currentNum = cartList[i].num; } if (cartList[i].checked) { allPrice += cartList[i].price * cartList[i].num; } } this.service.cookies.set(‘cartList’, cartList); this.ctx.body = { “success”: true, num: currentNum, allPrice: allPrice } } }viewapp/view/default/cart.html <div class=“cart_number”> <div class=“input_left decCart” goods_id="<%=cartList[i]._id%>" color="<%=cartList[i].color%>">-</div> <div class=“input_center”> <input id=“num” name=“num” readonly=“readonly” type=“text” value="<%=cartList[i].num%>" /> </div> <div class=“input_right incCart” goods_id="<%=cartList[i]._id%>" color="<%=cartList[i].color%>">+</div> </div>前端jsapp/public/default/js/cart.js(function($) { var app = { init: function() { this.initCheckBox(); this.changeCartNum(); }, initCheckBox() { $("#checkAll").click(function() { if (this.checked) { $(":checkbox").prop(“checked”, true); } else { $(":checkbox").prop(“checked”, false); } }); $(".cart_list input:checkbox").click(function() { var chknum = $(".cart_list input:checkbox").size(); //checkbox总个数 var chk = 0; //checkbox checked=true总个数 $(".cart_list input:checkbox").each(function() { if ($(this).prop(“checked”) == true) { chk++; } }); if (chknum == chk) { //全选 $("#checkAll").prop(“checked”, true); } else { //不全选 $("#checkAll").prop(“checked”, false); } }); }, changeCartNum() { $(’.decCart’).click(function() { var goods_id = $(this).attr(‘goods_id’); var color = $(this).attr(‘color’); // alert(color); $.get(’/decCart?goods_id=’ + goods_id + ‘&color=’ + color, function(response) { console.log(response); if (response.success) { $("#allPrice").html(response.allPrice + ‘元’); $(this).siblings(’.input_center’).find(‘input’).val(response.num); var price = parseFloat($(this).parent().parent().siblings(’.price’).html()); $(this).parent().parent().siblings(’.totalPrice’).html(response.num * price + “元”); } }.bind(this)); //注意this指向 }) $(’.incCart’).click(function() { var goods_id = $(this).attr(‘goods_id’); var color = $(this).attr(‘color’); $.get(’/incCart?goods_id=’ + goods_id + ‘&color=’ + color, function(response) { if (response.success) { $("#allPrice").html(response.allPrice + ‘元’); $(this).siblings(’.input_center’).find(‘input’).val(response.num); var price = parseFloat($(this).parent().parent().siblings(’.price’).html()); $(this).parent().parent().siblings(’.totalPrice’).html(response.num * price + ‘元’); } }.bind(this)); }) } } $(function() { app.init(); })})($)效果 ...

January 4, 2019 · 2 min · jiezi

egg(95中)--egg之购物车列表

商品列表router.get(’/cart’, initMiddleware, controller.default.cart.cartList);controllerapp/controller/default/cart.js async cartList() { var cartList = this.service.cookies.get(‘cartList’); var allPrice = 0; for (var i = 0; i < cartList.length; i++) { if (cartList[i].checked) { allPrice += cartList[i].price * cartList[i].num; } } await this.ctx.render(‘default/cart.html’, { cartList: cartList, allPrice: allPrice }); }viewapp/view/default/product_list.html<% include ./public/header.html%> <!–end header –> <!– start banner_x –> <% include ./public/banner.html%> <!– end banner_x –> <!– start danpin –> <div class=“danpin center”> <div class=“biaoti center”>小米手机</div> <div class=“main center”> <%for(var i=0;i<goodsList.length;i++){%> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href="/pinfo?id=<%=goodsList[i]._id%>” target="_blank"><img src="<%=goodsList[i].goods_img%>" alt="" /></a> </div> <div class=“pinpai”> <a href="./xiangqing.html" target="_blank"> <%=goodsList[i].title%> </a> </div> <div class=“jiage”> <%=goodsList[i].shop_price%>元</div> </div> <%}%> </div> </div> <footer class=“mt20 center”> <div class=“mt20”>小米商城|MIUI|米聊|多看书城|小米路由器|视频电话|小米天猫店|小米淘宝直营店|小米网盟|小米移动|隐私政策|Select Region</div> <div>©mi.com 京ICP证110507号 京ICP备10046444号 京公网安备11010802020134号 京网文[2014]0059-0009号</div> <div>违法和不良信息举报电话:185-0130-1238,本网站所列数据,除特殊说明,所有数据均出自我司实验室测试</div> </footer> <!– end danpin –> </body> </html>效果 ...

January 4, 2019 · 1 min · jiezi

egg(96上)--egg之购物车之check选中

rotuer.js router.get(’/changeOneCart’, initMiddleware, controller.default.cart.changeOneCart); router.get(’/changeAllCart’, initMiddleware, controller.default.cart.changeAllCart);controllerapp/controller/default/cart.js单个check async changeOneCart() { var goods_id = this.ctx.request.query.goods_id; var color = this.ctx.request.query.color; var goodsResult = await this.ctx.model.Goods.find({ “_id”: goods_id }); if (!goodsResult || goodsResult.length == 0) { this.ctx.body = { “success”: false, msg: ‘改变状态失败’ } } else { var cartList = this.service.cookies.get(‘cartList’); var allPrice = 0; //总价格 for (var i = 0; i < cartList.length; i++) { if (cartList[i]._id == goods_id && cartList[i].color == color) { cartList[i].checked = !cartList[i].checked; } //计算总价 if (cartList[i].checked) { allPrice += cartList[i].price * cartList[i].num; } } this.service.cookies.set(‘cartList’, cartList); this.ctx.body = { “success”: true, allPrice: allPrice } } }全选 async changeAllCart() { var type = this.ctx.request.query.type; var cartList = this.service.cookies.get(‘cartList’); var allPrice = 0; //总价格 for (var i = 0; i < cartList.length; i++) { if (type == 1) { cartList[i].checked = true; } else { cartList[i].checked = false; } //计算总价 if (cartList[i].checked) { allPrice += cartList[i].price * cartList[i].num; } } this.service.cookies.set(‘cartList’, cartList); this.ctx.body = { “success”: true, allPrice: allPrice } }viewapp/view/default/cart.html<% include ./public/header.html%> <!–end header –> <link rel=“stylesheet” href="/public/default/css/cart.css" /> <script src="/public/default/js/cart.js"> </script> <!– start banner_x –> <div class=“banner_x center”> <a href="./index.html" target="_blank"> <div class=“logo fl”></div> </a> <div class=“wdgwc fl ml40”>我的购物车</div> <div class=“wxts fl ml20”>温馨提示:产品是否购买成功,以最终下单为准哦,请尽快结算</div> <div class=“dlzc fr”> <ul> <li><a href="./login.html" target="_blank">登录</a></li> <li>|</li> <li><a href="./register.html" target="_blank">注册</a></li> </ul> </div> <div class=“clear”></div> </div> <div class=“xiantiao”></div> <div class=“gwcxqbj”> <div class=“gwcxd center”> <table class=“table”> <tr class=“th”> <th> <input type=“checkbox” id=“checkAll” /> 全选 </th> <th> 商品名称 </th> <th>单价</th> <th>数量</th> <th>小计</th> <th>操作</th> </tr> <%for(var i=0;i<cartList.length;i++){%> <tr class=“cart_list”> <td> <input type=“checkbox” goods_id="<%=cartList[i]._id%>" color="<%=cartList[i].color%>" <%if(cartList[i].checked){%>checked <%}%> /> </td> <td> <div class=“col_pic”> <img src="<%=cartList[i].goods_img%>" /> </div> <div class=“col_title”> <%=cartList[i].title%> </div> </td> <td class=“price”> <%=cartList[i].price%>元 </td> <td> <div class=“cart_number”> <div class=“input_left decCart” goods_id="<%=cartList[i]._id%>" color="<%=cartList[i].color%>">-</div> <div class=“input_center”> <input id=“num” name=“num” readonly=“readonly” type=“text” value="<%=cartList[i].num%>" /> </div> <div class=“input_right incCart” goods_id="<%=cartList[i]._id%>" color="<%=cartList[i].color%>">+</div> </div> </td> <td class=“totalPrice”> <%=parseFloat(cartList[i].price)*cartList[i].num%>元 </td> <td> <span><a class=“delete” href="/removeCart?goods_id=<%=cartList[i]._id%>&color=<%=cartList[i].color%>"> 删除</a></span> </td> </tr> <%}%> </table> </div> <div class=“jiesuandan mt20 center”> <div class=“tishi fl ml20”> <ul> <li><a href="./liebiao.html">继续购物</a></li> <li>|</li> <li>共<span>2</span>件商品,已选择<span>1</span>件</li> <div class=“clear”></div> </ul> </div> <div class=“jiesuan fr”> <div class=“jiesuanjiage fl”>合计(不含运费):<span id=“allPrice”><%=allPrice%>元</span></div> <div class=“jsanniu fr”><input class=“jsan” type=“submit” name=“jiesuan” value=“去结算” /></div> <div class=“clear”></div> </div> <div class=“clear”></div> </div> </div> <!– footer –> <footer class=“center”> <div class=“mt20”>小米商城|MIUI|米聊|多看书城|小米路由器|视频电话|小米天猫店|小米淘宝直营店|小米网盟|小米移动|隐私政策|Select Region</div> <div>©mi.com 京ICP证110507号 京ICP备10046444号 京公网安备11010802020134号 京网文[2014]0059-0009号</div> <div>违法和不良信息举报电话:185-0130-1238,本网站所列数据,除特殊说明,所有数据均出自我司实验室测试</div> </footer> </body> </html>前端jsapp/public/default/js/cart.js(function($) { var app = { init: function() { this.initCheckBox(); this.changeCartNum(); this.isCheckedAll(); this.deleteConfirm(); }, deleteConfirm: function() { $(’.delete’).click(function() { var flag = confirm(‘您确定要删除吗?’); return flag; }) }, initCheckBox() { $("#checkAll").click(function() { if (this.checked) { $(":checkbox").prop(“checked”, true); $.get(’/changeAllCart?type=1’, function(response) { if (response.success) { $("#allPrice").html(response.allPrice + ‘元’); } }); } else { $(":checkbox").prop(“checked”, false); $.get(’/changeAllCart?type=0’, function(response) { if (response.success) { $("#allPrice").html(response.allPrice + ‘元’); } }); } }); var _that = this; $(".cart_list input:checkbox").click(function() { _that.isCheckedAll(); var goods_id = $(this).attr(‘goods_id’); var color = $(this).attr(‘color’); $.get(’/changeOneCart?goods_id=’ + goods_id + ‘&color=’ + color, function(response) { if (response.success) { $("#allPrice").html(response.allPrice + ‘元’); } }); }); }, //判断全选是否选择 isCheckedAll() { var chknum = $(".cart_list input:checkbox").size(); //checkbox总个数 var chk = 0; //checkbox checked=true总个数 $(".cart_list input:checkbox").each(function() { if ($(this).prop(“checked”) == true) { chk++; } }); if (chknum == chk) { //全选 $("#checkAll").prop(“checked”, true); } else { //不全选 $("#checkAll").prop(“checked”, false); } }, changeCartNum() { $(’.decCart’).click(function() { var goods_id = $(this).attr(‘goods_id’); var color = $(this).attr(‘color’); // alert(color); $.get(’/decCart?goods_id=’ + goods_id + ‘&color=’ + color, function(response) { if (response.success) { $("#allPrice").html(response.allPrice + ‘元’); $(this).siblings(’.input_center’).find(‘input’).val(response.num); var price = parseFloat($(this).parent().parent().siblings(’.price’).html()); $(this).parent().parent().siblings(’.totalPrice’).html(response.num * price + “元”); } }.bind(this)); //注意this指向 }) $(’.incCart’).click(function() { var goods_id = $(this).attr(‘goods_id’); var color = $(this).attr(‘color’); $.get(’/incCart?goods_id=’ + goods_id + ‘&color=’ + color, function(response) { if (response.success) { $("#allPrice").html(response.allPrice + ‘元’); $(this).siblings(’.input_center’).find(‘input’).val(response.num); var price = parseFloat($(this).parent().parent().siblings(’.price’).html()); $(this).parent().parent().siblings(’.totalPrice’).html(response.num * price + ‘元’); } }.bind(this)); }) } } $(function() { app.init(); })})($)效果 ...

January 4, 2019 · 3 min · jiezi

egg(95上)--egg之购物车列表

router.js router.get(’/addCartSuccess’, initMiddleware, controller.default.cart.addCartSuccess);controller.jsapp/controller/default/cart.js async addCartSuccess() { var goods_id = this.ctx.request.query.goods_id; var color_id = this.ctx.request.query.color_id; var goodsResult = await this.ctx.model.Goods.find({ “_id”: goods_id }); var colorResult = await this.ctx.model.GoodsColor.find({ “_id”: color_id }); if (goodsResult.length == 0 || colorResult.length == 0) { this.ctx.status = 404; this.ctx.body = ‘错误404’; //404 } else { var title = goodsResult[0].title + ‘–’ + goodsResult[0].goods_version + “–” + colorResult[0].color_name; await this.ctx.render(‘default/add_cart_success.html’, { title: title, goods_id: goods_id }); } }viewapp/view/default/add_cart_success.html<% include ./public/header.html%> <!–end header –> <!– start banner_x –> <% include ./public/banner.html%> <!– end banner_x –> <style> .buy-succ-box { margin-bottom: 26px; padding: 40px 0 40px 0; height: 68px; border-bottom: 1px solid #e0e0e0; } .buy-succ-box .goods-content { float: left; } .buy-succ-box .actions { float: right; } .buy-succ-box .goods-img { float: left; width: 64px; height: 64px; } .buy-succ-box .goods-info { float: left; margin-left: 20px; } .buy-succ-box .goods-info h3 { margin: 0; color: #424242; font-size: 24px; font-weight: normal; margin-top: 3px; } .buy-succ-box .goods-info .name, .buy-succ-box .goods-info .price { margin-right: 15px; font-size: 14px; color: #757575; } .buy-succ-box .actions .btn { width: 180px; margin-left: 12px; margin-top: 5px; } .btn-line-gray { border-color: #b0b0b0; background: #fff; color: #757575; } .btn { display: inline-block; *zoom: 1; *display: inline; width: 158px; height: 38px; padding: 0; margin: 0; border: 1px solid #b0b0b0; font-size: 14px; line-height: 38px; text-align: center; color: #b0b0b0; cursor: pointer; -webkit-transition: all .4s; transition: all .4s; } .btn-primary { background: #ff6700; border-color: #ff6700; color: #fff; } .xm-recommend-title { text-align: center; padding: 20px 0px; background-color: #f5f5f5; margin-bottom: 10px; } .xm-recommend-title span { position: static; margin-bottom: 20px; width: 100%; height: auto; font-size: 28px; } </style> <div class=“container”> <div class=“buy-succ-box clearfix”> <div class=“goods-content” id=“J_goodsBox”> <div class=“goods-img”> <img src="/public/default/image/success.png" width=“64” height=“64”> </div> <div class=“goods-info”> <h3>已成功加入购物车!</h3> <span class=“name”><%=title%> </span> </div> </div> <div class=“actions J_actBox”> <a href="/pinfo?id=<%=goods_id%>" class=“btn btn-line-gray J_goBack”>返回上一级</a> <a href="/cart" class=“btn btn-primary”>去购物车结算</a> </div> </div> <h2 class=“xm-recommend-title”><span>买购物车中商品的人还买了</span></h2> <div class=“danpin center”> <div class=“main center”> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href=”./xiangqing.html" target="_blank"><img src="/public/default/image/liebiao_xiaomi6.jpg" alt=""></a> </div> <div class=“pinpai”><a href="./xiangqing.html" target="_blank">小米6</a></div> <div class=“youhui”>5.16早10点开售</div> <div class=“jiage”>2499.00元</div> </div> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href=”"><img src="/public/default/image/liebiao_xiaomi5c.jpg" alt=""></a> </div> <div class=“pinpai”><a href="">小米手机5c</a></div> <div class=“youhui”>搭载澎湃S1 八核高性能处理器</div> <div class=“jiage”>1499.00元</div> </div> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href=”"><img src="/public/default/image/liebiao_xiaomint2.jpg" alt=""></a> </div> <div class=“pinpai”><a href="">小米Note 2</a></div> <div class=“youhui”>5月9日-20日 小米Note 2 享花呗12期分期免息</div> <div class=“jiage”>2799.00元</div> </div> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href=”"><img src="/public/default/image/liebiao_xiaomimix.jpg" alt=""></a> </div> <div class=“pinpai”><a href="">小米MIX</a></div> <div class=“youhui”>5月9日-20日小米MIX 享花呗12期分期免息</div> <div class=“jiage”>3499.00元</div> </div> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href=”"><img src="/public/default/image/liebiao_xiaomi5s.jpg" alt=""></a> </div> <div class=“pinpai”><a href="">小米5s</a></div> <div class=“youhui”>“暗夜之眼”超感光相机 / 无孔式超声波</div> <div class=“jiage”>1999.00元</div> </div> <div class=“clear”></div> </div> <div class=“main center mb20”> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href=”"><img src="/public/default/image/liebiao_xiaomi5.jpg" alt=""></a> </div> <div class=“pinpai”><a href="">小米手机5</a></div> <div class=“youhui”>骁龙820处理器 / UFS 2.0 闪存</div> <div class=“jiage”>1799.00元</div> </div> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href=”"><img src="/public/default/image/liebiao_hongmin4.jpg" alt=""></a> </div> <div class=“pinpai”><a href="">红米Note 4</a></div> <div class=“youhui”>十核旗舰处理器 / 全金属一体化机身 </div> <div class=“jiage”>1399.00元</div> </div> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href=”"><img src="/public/default/image/pinpai3.png" alt=""></a> </div> <div class=“pinpai”><a href="">小米手机5 64GB</a></div> <div class=“youhui”>5月9日-10日,下单立减100元</div> <div class=“jiage”>1799元</div> </div> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href=”"><img src="/public/default/image/liebiao_hongmin42.jpg" alt=""></a> </div> <div class=“pinpai”><a href="">红米4</a></div> <div class=“youhui”>2.5D 玻璃,金属一体化机身</div> <div class=“jiage”>999.00元</div> </div> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href=”"><img src="/public/default/image/liebiao_hongmin4x.jpg" alt=""></a> </div> <div class=“pinpai”><a href="">红米Note 4X 全网通版</a></div> <div class=“youhui”>多彩金属 / 4100mAh 超长续航</div> <div class=“jiage”>1299.00元</div> </div> <div class=“clear”></div> </div> </div> </div> <footer class=“mt20 center”> <div class=“mt20”>小米商城|MIUI|米聊|多看书城|小米路由器|视频电话|小米天猫店|小米淘宝直营店|小米网盟|小米移动|隐私政策|Select Region</div> <div>©mi.com 京ICP证110507号 京ICP备10046444号 京公网安备11010802020134号 京网文[2014]0059-0009号</div> <div>违法和不良信息举报电话:185-0130-1238,本网站所列数据,除特殊说明,所有数据均出自我司实验室测试</div> </footer> <!– end danpin –> </body> </html>效果 ...

January 4, 2019 · 3 min · jiezi

egg(95)--egg之商品加入购物车

流程选择版本,选择颜色,加入购物车router.jsrouter.get(’/addCart’, controller.default.cart.addCart);viewapp/view/default/product_info.html选中版本,选中颜色,然后点击加入购物车<input class=“jrgwc” type=“button” name=“addCart” id=“addCart” value=“加入购物车” /> $(function() { $(’#addCart’).click(function() { var goods_id = $(’#color_list .active’).attr(‘goods_id’); var color_id = $(’#color_list .active’).attr(‘color_id’); location.href = “/addCart?goods_id=” + goods_id + ‘&color_id=’ + color_id; }) })controllerapp/controller/default/cart.js根据传过来的good_id和color_id,查找商品和商品颜色 async addCart() { var goods_id = this.ctx.request.query.goods_id; var color_id = this.ctx.request.query.color_id; //1、获取商品信息 var goodsResult = await this.ctx.model.Goods.find({ “_id”: goods_id }); var colorResult = await this.ctx.model.GoodsColor.find({ “_id”: color_id }); console.log(goodsResult); if (goodsResult.length == 0 || colorResult.length == 0) { this.ctx.status = 404; this.ctx.body = ‘错误404’; //404 } else { // 赠品 var goodsGiftIds = this.ctx.service.goods.strToArray(goodsResult[0].goods_gift); var goodsGift = await this.ctx.model.Goods.find({ $or: goodsGiftIds }); var currentData = { _id: goods_id, title: goodsResult[0].title, price: goodsResult[0].shop_price, goods_version: goodsResult[0].goods_version, num: 1, color: colorResult[0].color_name, goods_img: goodsResult[0].goods_img, goods_gift: goodsGift, /赠品/ checked: true /默认选中/ } //2、判断购物车有没有数据 var cartList = this.service.cookies.get(‘cartList’); if (cartList && cartList.length > 0) { //存在 //4、判断购物车有没有当前数据 if (this.service.cart.cartHasData(cartList, currentData)) { for (var i = 0; i < cartList.length; i++) { if (cartList[i]._id == currentData._id) { cartList[i].num = cartList[i].num + 1; } } this.service.cookies.set(‘cartList’, cartList); } else { //如果购物车里面没有当前数据 把购物车以前的数据和当前数据拼接 然后重新写入 var tempArr = cartList; tempArr.push(currentData); this.service.cookies.set(‘cartList’, tempArr); } } else { // 3、如果购物车没有任何数据 直接把当前数据写入cookies var tempArr = []; tempArr.push(currentData); this.service.cookies.set(‘cartList’, tempArr); } this.ctx.body = ‘加入购物车成功’; } }serviceapp/service/cookies.jsclass CookiesService extends Service { set(key, value, expires) { expires = expires ? expires : 24 * 3600 * 1000; this.ctx.cookies.set(key, JSON.stringify(value), { maxAge: expires, httpOnly: true, encrypt: true }) } get(key) { var data = this.ctx.cookies.get(key, { encrypt: true }); if (data) { return JSON.parse(data); } return null; }}效果 ...

January 4, 2019 · 2 min · jiezi

egg(93)--egg之商品详情数据渲染 关联商品 关联赠品 关联配件 关联颜色

router.js router.get(’/pinfo’,initMiddleware, controller.default.product.info);controller.jsapp/controller/default/product.js async info() { // 1、获取商品信息 var id=this.ctx.request.query.id; var productInfo=await this.ctx.model.Goods.find({"_id":id}); //2、关联商品 var relationGoodsIds=this.ctx.service.goods.strToArray(productInfo[0].relation_goods); var relationGoods=await this.ctx.model.Goods.find({ $or:relationGoodsIds },‘goods_version shop_price’); //3、获取关联颜色 var goodsColorIds=this.ctx.service.goods.strToArray(productInfo[0].goods_color); var goodsColor=await this.ctx.model.GoodsColor.find({ $or:goodsColorIds }); //4、关联赠品 var goodsGiftIds=this.ctx.service.goods.strToArray(productInfo[0].goods_gift); var goodsGift=await this.ctx.model.Goods.find({ $or:goodsGiftIds }); //5、关联配件 var goodsFittingIds=this.ctx.service.goods.strToArray(productInfo[0].goods_fitting); var goodsFitting=await this.ctx.model.Goods.find({ $or:goodsFittingIds }); await this.ctx.render(‘default/product_info.html’,{ productInfo:productInfo[0], relationGoods:relationGoods, goodsColor:goodsColor, goodsGift:goodsGift, goodsFitting:goodsFitting }); }serviceapp/service/goods.js strToArray(str){ try { var tempIds=[]; if(str){ var idsArr=str.replace(/,/g,’,’).split(’,’); for(var i=0;i<idsArr.length;i++){ tempIds.push({ “_id”:this.app.mongoose.Types.ObjectId(idsArr[i]) }); } }else{ tempIds=[{“1”:-1}] } return tempIds; } catch (error) { return [{“1”:-1}]; //返回一个不成立的条件 }}viewapp/view/default/product_info.html<% include ./public/header.html%><!–end header –><!– start banner_x –><% include ./public/banner.html%><!– end banner_x –> <!– xiangqing –> <div class=“jieshao mt20 w”> <div class=“left fl”> <div class=“swiper-container”> <div class=“swiper-wrapper”> <div class=“swiper-slide”> <img src="/public/default/image/liebiao_xiaomi6.jpg"> </div> <div class=“swiper-slide”> <img src="/public/default/image/liebiao_xiaomi6.jpg"> </div> <div class=“swiper-slide”> <img src="/public/default/image/liebiao_xiaomi6.jpg"> </div> </div> <div class=“swiper-pagination”></div> <!– Add Arrows –> <div class=“swiper-button-next”></div> <div class=“swiper-button-prev”></div> </div> </div> <div class=“right fr”> <div class=“h3 ml20 mt20”><%=productInfo.title%></div> <div class=“jianjie mr40 ml20 mt10”><%=productInfo.sub_title%></div> <div class=“jiage ml20 mt10”><%=productInfo.shop_price%>元</div> <%if(relationGoods.length>0){%> <div class=“ft20 ml20 mt20”>选择版本</div> <div class=“xzbb ml20 mt10 clearfix”> <%for(var i=0;i<relationGoods.length;i++){%> <div class=“banben fl <%if(productInfo._id.toString()==relationGoods[i]._id.toString()){%>active <%}%>” > <a href="/pinfo?id=<%=relationGoods[i]._id%>"> <span><%=relationGoods[i].goods_version%></span> <span><%=relationGoods[i].shop_price%>元</span> </a> </div> <%}%> </div> <%}%> <%if(goodsColor.length>0){%> <div class=“ft20 ml20 mt20 clear”>选择颜色</div> <div class=“xzbb ml20 mt10 clearfix”> <%for(var i=0;i<goodsColor.length;i++){%> <div class=“banben fl”> <a> <span class=“yuandian” style=“background:<%=goodsColor[i].color_value%>"></span> <span class=“yanse”><%=goodsColor[i].color_name%></span> </a> </div> <%}%> </div> <%}%> <div class=“xqxq mt20 ml20 clear”> <div class=“top1 mt10”> <div class=“left1 fl”>小米6 全网通版 6GB内存 64GB 亮黑色</div> <div class=“right1 fr”>2499.00元</div> <div class=“clear”></div> </div> <div class=“bot mt20 ft20 ftbc”>总计:2499元</div> </div> <div class=“xiadan ml20 mt20”> <input class=“jrgwc” type=“button” name=“jrgwc” value=“加入购物车” /> </div> </div> <div class=“clear”></div> </div> <div class=“container clearfix”> <div class=“c_left”> <h2>看了又看</h2> <div class=“item”> <a target="_blank” href="#"> <img src="//img10.360buyimg.com/N1/jfs/t24550/324/210570328/559386/cc08975f/5b2910bbNfaceb0b4.jpg" /> <p class=“price recommendLookPrice4183081”>¥31.90</p> <p>三利 纯棉A类标准简约素雅大浴巾 70×140cm 男女同款 柔软舒适吸水裹身巾 豆绿</p> </a> </div> <div class=“item”> <a target="_blank" href="#"> <img src="//img10.360buyimg.com/N1/jfs/t24550/324/210570328/559386/cc08975f/5b2910bbNfaceb0b4.jpg" /> <p class=“price recommendLookPrice4183081”>¥31.90</p> <p>三利 纯棉A类标准简约素雅大浴巾 70×140cm 男女同款 柔软舒适吸水裹身巾 豆绿</p> </a> </div> <div class=“item”> <a target="_blank" href="#"> <img src="//img10.360buyimg.com/N1/jfs/t24550/324/210570328/559386/cc08975f/5b2910bbNfaceb0b4.jpg" /> <p class=“price recommendLookPrice4183081”>¥31.90</p> <p>三利 纯棉A类标准简约素雅大浴巾 70×140cm 男女同款 柔软舒适吸水裹身巾 豆绿</p> </a> </div> </div> <div class=“c_right”> <ul class=“detail_list clearfix”> <li class="">详情描述</li> <li class="">规格参数</li> <li class="">用户评价</li> </ul> <div class=“detail_info”> <div class=“detail_info_item”> <%-productInfo.goods_content%> </div> <div class=“detail_info_item”> <ul> <li class=“row clearfix”> <div class=“span5”> <h2>充电与电池</h2> </div> <div class=“span15”> 有线充电支持 QC 4.0+ 快充<br> USB Type-C 双面充电接口<br> 内置电池,免更换<br> 标配 18W QC3.0 充电器 &amp; 附赠 10W 无线充电器 </div> </li> <li class=“row clearfix”> <div class=“span5”> <h2>充电与电池</h2> </div> <div class=“span15”> 有线充电支持 QC 4.0+ 快充<br> USB Type-C 双面充电接口<br> 内置电池,免更换<br> 标配 18W QC3.0 充电器 &amp; 附赠 10W 无线充电器 </div> </li> </ul> </div> <div class=“detail_info_item”> <ul class=“comment_list”> <li> <div> <img src="//static.i360mall.com/mall/images/eval-stars.png"> </div> <p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p> <p class=“eval-order-info”> <span class=“eval-time”>2018-11-18 14:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p> </li> <li> <div> <img src="//static.i360mall.com/mall/images/eval-stars.png"> </div> <p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p> <p class=“eval-order-info”> <span class=“eval-time”>2018-11-18 14:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p> </li> <li> <div> <img src="//static.i360mall.com/mall/images/eval-stars.png"> </div> <p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p> <p class=“eval-order-info”> <span class=“eval-time”>2018-11-18 14:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p> </li> <li> <div> <img src="//static.i360mall.com/mall/images/eval-stars.png"> </div> <p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p> <p class=“eval-order-info”> <span class=“eval-time”>2018-11-18 14:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p> </li> </ul> </div> </div> </div> </div> <!– footer –> <footer class=“mt20 center”> <div class=“mt20”>小米商城|MIUI|米聊|多看书城|小米路由器|视频电话|小米天猫店|小米淘宝直营店|小米网盟|小米移动|隐私政策|Select Region</div> <div>©mi.com 京ICP证110507号 京ICP备10046444号 京公网安备11010802020134号 京网文[2014]0059-0009号</div> <div>违法和不良信息举报电话:185-0130-1238,本网站所列数据,除特殊说明,所有数据均出自我司实验室测试</div> </footer> </body></html>效果 ...

January 2, 2019 · 3 min · jiezi

egg(94)--egg之商品详情数据渲染 商品颜色选择

router.js router.get(’/getImagelist’,initMiddleware, controller.default.product.getImagelist);controller.jsapp/controller/default/product.js async getImagelist(){ try { var color_id=this.ctx.request.query.color_id; var goods_id=this.ctx.request.query.goods_id; var goodsImages=await this.ctx.model.GoodsImage.find({“goods_id”:goods_id,“color_id”:this.app.mongoose.Types.ObjectId(color_id)}); if(goodsImages.length==0){ var goodsImages=await this.ctx.model.GoodsImage.find({“goods_id”:goods_id}).limit(8); } // console.log(goodsImages); this.ctx.body={“success”:true,“result”:goodsImages}; } catch (error) { this.ctx.body={“success”:false,“result”:[]}; } }extendapp/extend/helper.js var showdown = require(‘showdown’); formatAttr(str){ var converter = new showdown.Converter(); return converter.makeHtml(str); }viewapp/view/default/product_info.html<% include ./public/header.html%><!–end header –><!– start banner_x –><% include ./public/banner.html%><!– end banner_x –> <!– xiangqing –> <div class=“jieshao mt20 w”> <div class=“left fl”> <div class=“swiper-container”> <div class=“swiper-wrapper” id=“swiper-wrapper”> <%for(var i=0;i<goodsImageResult.length;i++){%> <div class=“swiper-slide”> <img src="<%=goodsImageResult[i].img_url%>"> </div> <%}%> </div> <div class=“swiper-pagination”></div> <!– Add Arrows –> <div class=“swiper-button-next”></div> <div class=“swiper-button-prev”></div> </div> </div> <div class=“right fr”> <div class=“h3 ml20 mt20”><%=productInfo.title%></div> <div class=“jianjie mr40 ml20 mt10”><%=productInfo.sub_title%></div> <div class=“jiage ml20 mt10”><%=productInfo.shop_price%>元</div> <%if(relationGoods.length>0){%> <div class=“ft20 ml20 mt20”>选择版本</div> <div class=“xzbb ml20 mt10 clearfix”> <%for(var i=0;i<relationGoods.length;i++){%> <div class=“banben fl <%if(productInfo._id.toString()==relationGoods[i]._id.toString()){%>active <%}%>” > <a href="/pinfo?id=<%=relationGoods[i]._id%>"> <span><%=relationGoods[i].goods_version%></span> <span><%=relationGoods[i].shop_price%>元</span> </a> </div> <%}%> </div> <%}%> <%if(goodsColor.length>0){%> <div class=“ft20 ml20 mt20 clear”>选择颜色</div> <div class=“xzbb ml20 mt10 clearfix” id=“color_list”> <%for(var i=0;i<goodsColor.length;i++){%> <div class=“banben fl” goods_id="<%=productInfo._id%>" goods_color="<%=goodsColor[i]._id%>"> <a> <span class=“yuandian” style=“background:<%=goodsColor[i].color_value%>"></span> <span class=“yanse”><%=goodsColor[i].color_name%></span> </a> </div> <%}%> </div> <%}%> <div class=“xqxq mt20 ml20 clear”> <div class=“top1 mt10”> <div class=“left1 fl”>小米6 全网通版 6GB内存 64GB 亮黑色</div> <div class=“right1 fr”>2499.00元</div> <div class=“clear”></div> </div> <div class=“bot mt20 ft20 ftbc”>总计:2499元</div> </div> <div class=“xiadan ml20 mt20”> <input class=“jrgwc” type=“button” name=“jrgwc” value=“加入购物车” /> </div> </div> <div class=“clear”></div> </div> <div class=“container clearfix”> <div class=“c_left”> <h2>看了又看</h2> <div class=“item”> <a target="_blank” href="#"> <img src="//img10.360buyimg.com/N1/jfs/t24550/324/210570328/559386/cc08975f/5b2910bbNfaceb0b4.jpg" /> <p class=“price recommendLookPrice4183081”>¥31.90</p> <p>三利 纯棉A类标准简约素雅大浴巾 70×140cm 男女同款 柔软舒适吸水裹身巾 豆绿</p> </a> </div> <div class=“item”> <a target="_blank" href="#"> <img src="//img10.360buyimg.com/N1/jfs/t24550/324/210570328/559386/cc08975f/5b2910bbNfaceb0b4.jpg" /> <p class=“price recommendLookPrice4183081”>¥31.90</p> <p>三利 纯棉A类标准简约素雅大浴巾 70×140cm 男女同款 柔软舒适吸水裹身巾 豆绿</p> </a> </div> <div class=“item”> <a target="_blank" href="#"> <img src="//img10.360buyimg.com/N1/jfs/t24550/324/210570328/559386/cc08975f/5b2910bbNfaceb0b4.jpg" /> <p class=“price recommendLookPrice4183081”>¥31.90</p> <p>三利 纯棉A类标准简约素雅大浴巾 70×140cm 男女同款 柔软舒适吸水裹身巾 豆绿</p> </a> </div> </div> <div class=“c_right”> <ul class=“detail_list clearfix”> <li class="">详情描述</li> <li class="">规格参数</li> <li class="">用户评价</li> </ul> <div class=“detail_info”> <div class=“detail_info_item”> <%-productInfo.goods_content%> </div> <div class=“detail_info_item”> <ul> <%for(var i=0;i<goodsAttr.length;i++){%> <%if(goodsAttr[i].attribute_value){%> <li class=“row clearfix”> <div class=“span5”> <h2><%=goodsAttr[i].attribute_title%></h2> </div> <div class=“span15”> <%-helper.formatAttr(goodsAttr[i].attribute_value)%> </div> </li> <%}%> <%}%> </ul> </div> <div class=“detail_info_item”> <ul class=“comment_list”> <li> <div> <img src="//static.i360mall.com/mall/images/eval-stars.png"> </div> <p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p> <p class=“eval-order-info”> <span class=“eval-time”>2018-11-18 14:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p> </li> <li> <div> <img src="//static.i360mall.com/mall/images/eval-stars.png"> </div> <p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p> <p class=“eval-order-info”> <span class=“eval-time”>2018-11-18 14:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p> </li> <li> <div> <img src="//static.i360mall.com/mall/images/eval-stars.png"> </div> <p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p> <p class=“eval-order-info”> <span class=“eval-time”>2018-11-18 14:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p> </li> <li> <div> <img src="//static.i360mall.com/mall/images/eval-stars.png"> </div> <p>这已经是第六部了,一如既往地好用。美中不足得是,尾插和数据线的链接口,用过一段时间,就会有充电接触不良的问题,希望厂家将来有改进。</p> <p class=“eval-order-info”> <span class=“eval-time”>2018-11-18 14:00:35</span><span>月岩白</span><span>6GB+64GB</span><span></span></p> </li> </ul> </div> </div> </div> </div> <!– footer –> <footer class=“mt20 center”> <div class=“mt20”>小米商城|MIUI|米聊|多看书城|小米路由器|视频电话|小米天猫店|小米淘宝直营店|小米网盟|小米移动|隐私政策|Select Region</div> <div>©mi.com 京ICP证110507号 京ICP备10046444号 京公网安备11010802020134号 京网文[2014]0059-0009号</div> <div>违法和不良信息举报电话:185-0130-1238,本网站所列数据,除特殊说明,所有数据均出自我司实验室测试</div> </footer> </body></html>效果点击版本显示不同的商品点击颜色出现不同的图片 ...

January 2, 2019 · 2 min · jiezi

egg(91)--egg之商品列表数据渲染

router.jsrouter.get(’/plist’,initMiddleware, controller.default.product.list);controller.jsapp/controller/default/product.js’use strict’;const Controller = require(’egg’).Controller;class ProductController extends Controller { async list() { var cid=this.ctx.request.query.cid; //根据分类id获取当前的分类新 var curentCate=await this.ctx.model.GoodsCate.find({"_id":cid}); console.log(JSON.stringify(curentCate)) //判断是否是顶级分类 if(curentCate[0].pid!=0){ // 二级分类 var goodsList=await this.ctx.model.Goods.find({“cate_id”:cid},’_id title price sub_title goods_img shop_price’); console.log(goodsList); }else{ //顶级分类 获取当前顶级分类下面的所有的子分类 var subCatesIds=await this.ctx.model.GoodsCate.find({“pid”:this.app.mongoose.Types.ObjectId(cid)},’_id’); var tempArr=[]; for(var i=0;i<subCatesIds.length;i++){ tempArr.push({ “cate_id”:subCatesIds[i]._id }) } var goodsList=await this.ctx.model.Goods.find({ $or:tempArr },’_id title price sub_title goods_img shop_price’); } var tpl=curentCate[0].template?curentCate[0].template:‘default/product_list.html’; await this.ctx.render(tpl,{ goodsList:goodsList }); } async info() { await this.ctx.render(‘default/product_info.html’); }}module.exports = ProductController;viewapp/view/default/index.html <a href="/plist?cid=<%=goodsCate[i]._id%>"><%=goodsCate[i].title%></a>app/view/default/product_list.html<% include ./public/header.html%><!–end header –><!– start banner_x –><% include ./public/banner.html%><!– end banner_x –> <!– start danpin –> <div class=“danpin center”> <div class=“biaoti center”>小米手机</div> <div class=“main center”> <%for(var i=0;i<goodsList.length;i++){%> <div class=“mingxing fl mb20” style=“border:2px solid #fff;width:230px;cursor:pointer;” onmouseout=“this.style.border=‘2px solid #fff’” onmousemove=“this.style.border=‘2px solid red’"> <div class=“sub_mingxing”> <a href="/pinfo?id=<%=goodsList[i]._id%>” target="_blank"><img src="<%=goodsList[i].goods_img%>" alt="" /></a> </div> <div class=“pinpai”><a href="./xiangqing.html" target="_blank"><%=goodsList[i].title%></a></div> <div class=“jiage”><%=goodsList[i].shop_price%>元</div> </div> <%}%> </div> </div> <footer class=“mt20 center”> <div class=“mt20”>小米商城|MIUI|米聊|多看书城|小米路由器|视频电话|小米天猫店|小米淘宝直营店|小米网盟|小米移动|隐私政策|Select Region</div> <div>©mi.com 京ICP证110507号 京ICP备10046444号 京公网安备11010802020134号 京网文[2014]0059-0009号</div> <div>违法和不良信息举报电话:185-0130-1238,本网站所列数据,除特殊说明,所有数据均出自我司实验室测试</div> </footer> <!– end danpin –> </body></html>效果 ...

January 2, 2019 · 1 min · jiezi

egg(91)--egg之配置前台公共中间件获取数据(系统设置、公共导航、公共nav)

controllerapp/controller/default/index.js去掉请求顶部导航,中间导航,商品分类的请求’use strict’;const Controller = require(’egg’).Controller;class IndexController extends Controller { async index() { console.time(‘indextime’); //轮播图 var focus=await this.ctx.service.cache.get(‘index_focus’); if(!focus){ focus=await this.ctx.model.Focus.find({“type”:1}); await this.ctx.service.cache.set(‘index_focus’,focus,6060); } //手机 var shoujiResult=await this.ctx.service.cache.get(‘index_shoujiResult’); if(!shoujiResult){ shoujiResult=await this.service.goods.get_category_recommend_goods(‘5bbf058f9079450a903cb77b’,‘best’,8); await this.ctx.service.cache.set(‘index_shoujiResult’,shoujiResult,6060); } //电视 var dianshiResult=await this.service.goods.get_category_recommend_goods(‘5bbf05ac9079450a903cb77c’,‘best’,10); console.timeEnd(‘indextime’); await this.ctx.render(‘default/index’,{ focus:focus, shoujiResult:shoujiResult }); }}module.exports = IndexController;中间件middlewareapp/middleware/init.jsmodule.exports = (options,app) => { return async function init(ctx, next) { // console.log(app); //获取顶部导航的数据 var topNav=await ctx.service.cache.get(‘index_topNav’); if(!topNav){ topNav=await ctx.model.Nav.find({“position”:1}); await ctx.service.cache.set(‘index_topNav’,topNav,6060); } //商品分类 var goodsCate=await ctx.service.cache.get(‘index_goodsCate’); if(!goodsCate){ goodsCate=await ctx.model.GoodsCate.aggregate([ { $lookup:{ from:‘goods_cate’, localField:’_id’, foreignField:‘pid’, as:‘items’ } }, { $match:{ “pid”:‘0’ } } ]) await ctx.service.cache.set(‘index_goodsCate’,goodsCate,6060); } //中间导航以及关联商品 var middleNav=await ctx.service.cache.get(‘index_middleNav’); if(!middleNav){ middleNav=await ctx.model.Nav.find({“position”:2}); middleNav=JSON.parse(JSON.stringify(middleNav)); //1、不可扩展对象 for(var i=0;i<middleNav.length;i++){ if(middleNav[i].relation){ //数据库查找relation对应的商品 try{ var tempArr=middleNav[i].relation.replace(/,/g,’,’).split(’,’); var tempRelationIds=[]; tempArr.forEach((value)=>{ tempRelationIds.push({ “_id”:app.mongoose.Types.ObjectId(value) }) }) var relationGoods=await ctx.model.Goods.find({ $or:tempRelationIds },’title goods_img’); middleNav[i].subGoods=relationGoods; }catch(err){ //2、如果用户输入了错误的ObjectID(商品id) middleNav[i].subGoods=[]; } }else{ middleNav[i].subGoods=[]; } } await ctx.service.cache.set(‘index_middleNav’,middleNav,60*60); } // console.log(middleNav); ctx.state.topNav=topNav; ctx.state.goodsCate=goodsCate; ctx.state.middleNav=middleNav; //注意 await next(); };};router.jsapp/router/default.js给需要使用顶部导航,中间导航,商品分类,增加中间件过滤module.exports = app => { const { router, controller } = app; //配置路由中间件 var initMiddleware=app.middleware.init({},app); router.get(’/’, initMiddleware,controller.default.index.index); router.get(’/plist’,initMiddleware, controller.default.product.list); router.get(’/pinfo’,initMiddleware, controller.default.product.info); router.get(’/pinfo’,initMiddleware, controller.default.product.info); router.get(’/cart’, initMiddleware,controller.default.flow.cart); //用户中心 router.get(’/login’, controller.default.user.login); router.get(’/register’, controller.default.user.register); router.get(’/user’, controller.default.user.welcome); router.get(’/user/order’, controller.default.user.order);}效果顶部导航,中间导航,商品分类的数据 ...

December 28, 2018 · 1 min · jiezi

egg(90)--egg之redis在项目中的实际应用

依赖配置cnpm install egg-redis –saveconfig/plugin.jsexports.redis = { enable: false, package: ’egg-redis’,};config/config.default.js config.redis = { client: { port: 6379, // Redis port host: ‘127.0.0.1’, // Redis host password: ‘’, db: 0 } }不用redis之前的请求时间设置redisserviceapp/service/cache.js’use strict’;const Service = require(’egg’).Service;class CacheService extends Service { async set(key,value,seconds) { value = JSON.stringify(value); if(this.app.redis){ if(!seconds){ await this.app.redis.set(key,value); }else{ await this.app.redis.set(key,value,‘EX’,seconds) } } } async get(key){ if(this.app.redis){ var data = await this.app.redis.get(key); if(!data) return; return JSON.parse(data) } }}module.exports = CacheService;controllerapp/controller/default/index.js如果没有设置redis缓存,就去请求数据,再设置缓存 var topNav = await this.ctx.service.cache.get(‘index_topNav’); if(!topNav){ topNav=await this.ctx.model.Nav.find({“position”:1}); await this.ctx.service.cache.set(‘index_topNav’,topNav,6060); }全部代码’use strict’;const Controller = require(’egg’).Controller;class IndexController extends Controller { async index() { console.time(‘index_time’) //获取顶部导航的数据 var topNav = await this.ctx.service.cache.get(‘index_topNav’); if(!topNav){ topNav=await this.ctx.model.Nav.find({“position”:1}); await this.ctx.service.cache.set(‘index_topNav’,topNav,6060); } //轮播图 var focus=await this.ctx.service.cache.get(‘index_focus’); if(!focus){ focus=await this.ctx.model.Focus.find({“type”:1}); await this.ctx.service.cache.set(‘index_focus’,focus,6060); } //商品分类 var goodsCate=await this.ctx.service.cache.get(‘index_goodsCate’); if(!goodsCate){ goodsCate=await this.ctx.model.GoodsCate.aggregate([ { $lookup:{ from:‘goods_cate’, localField:’_id’, foreignField:‘pid’, as:‘items’ } }, { $match:{ “pid”:‘0’ } } ]) await this.ctx.service.cache.set(‘index_goodsCate’,goodsCate,6060); } // console.log(topNav); var middleNav=await this.ctx.service.cache.get(‘index_middleNav’); if(!middleNav){ middleNav=await this.ctx.model.Nav.find({“position”:2}); middleNav=JSON.parse(JSON.stringify(middleNav)); //1、不可扩展对象 for(var i=0;i<middleNav.length;i++){ if(middleNav[i].relation){ //数据库查找relation对应的商品 try{ var tempArr=middleNav[i].relation.replace(/,/g,’,’).split(’,’); var tempRelationIds=[]; tempArr.forEach((value)=>{ tempRelationIds.push({ “_id”:this.app.mongoose.Types.ObjectId(value) }) }) var relationGoods=await this.ctx.model.Goods.find({ $or:tempRelationIds },’title goods_img’); middleNav[i].subGoods=relationGoods; }catch(err){ //2、如果用户输入了错误的ObjectID(商品id) middleNav[i].subGoods=[]; } }else{ middleNav[i].subGoods=[]; } } await this.ctx.service.cache.set(‘index_middleNav’,middleNav,6060); } // console.log(JSON.stringify(middleNav)) var shoujiResult=await this.ctx.service.cache.get(‘index_shoujiResult’); if(!shoujiResult){ shoujiResult=await this.service.goods.get_category_recommend_goods(‘5bbf058f9079450a903cb77b’,‘best’,8); await this.ctx.service.cache.set(‘index_shoujiResult’,shoujiResult,6060); } console.timeEnd(‘index_time’) await this.ctx.render(‘default/index’,{ topNav:topNav, focus:focus, goodsCate:goodsCate, middleNav:middleNav, shoujiResult:shoujiResult }); }}module.exports = IndexController;设置redis缓存后的时间 ...

December 27, 2018 · 1 min · jiezi

egg(89)--egg之redis的发布和订阅

gitbhubgithub目录文件内容redis-push.jsvar redis = require(‘redis’);var client = redis.createClient(6379,’localhost’);client.publish(’testPublish’,‘message from publish.js’)redis-sub1.jsvar redis = require(‘redis’);var client = redis.createClient(6379,’localhost’);client.subscribe(’testPublish’)client.on(‘message’,(channel,msg) => { console.log(channel) console.log(msg)})redis-sub2.jsvar redis = require(‘redis’);var client = redis.createClient(6379,’localhost’);client.subscribe(’testPublish’)client.on(‘message’,(channel,msg) => { console.log(channel) console.log(msg)})先订阅再发布,订阅者就能看到发布者发布过来的信息

December 27, 2018 · 1 min · jiezi

egg(88,89)--egg之redis的(字符串,列表,集合,哈希)的curd

字符串string常用命令查看所有的 key: keys 普通设置: set key value设置并加过期时间: set key value EX 30 表示 30 秒后过期获取数据: get key删除指定数据: del key删除全部数据: flushall查看类型: type key设置过期时间: expire key 20 表示指定的 key5 秒后过期终端nodevar redis = require(‘redis’);var client = redis.createClient(6379,’localhost’);client.set(‘username1’,‘jie’);client.set(‘username2’,‘biao’,‘Ex’,‘5’);client.set(‘username3’,’nine’);client.get(‘username3’);client.type(‘username3’,(err,value) => { console.log(value)})client.del(‘username3’,(err,value) => { console.log(value)})client.keys(’’,(err,value) => { console.log(value)})client.keys(‘flushall’,(err,value) => { console.log(value)})列表(数组)列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)常用命令列表右侧增加值: rpush key value列表左侧增加值: lpush key value右侧删除值: rpop key左侧删除值: lpop key获取数据: lrange key删除指定数据: del key删除全部数据: flushall查看类型: type key终端nodevar redis = require(‘redis’);var client = redis.createClient(6379,’localhost’);client.lpush(’name’,‘a’);client.lpush(’name’,‘b’);client.rpush(’name’,‘c’);client.rpush(’name’,’d’);client.type(’name’,(err,value) => { console.log(value)})client.lrange(’name’,0,-1,(err,value) => { console.log(value)})client.del(’name’,(err,value) => { console.log(value)})client.flushall(’name’,(err,value) => { console.log(value)})client.lrange(’name’,0,-1,(err,value) => { console.log(value)})集合setSet 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。 它和列表的最主要区别就是没法增加重复值常用命令给集合增数据: sadd key value删除集合中的一个值: srem key value获取数据: smembers key删除指定数据: del key删除全部数据: flushall终端nodevar redis = require(‘redis’);var client = redis.createClient(6379,’localhost’);client.sadd(’name’,‘a’);client.sadd(’name’,‘b’);client.sadd(’name’,‘c’);client.sadd(’name’,’d’);client.type(’name’,(err,value) => { console.log(value)})client.smembers(’name’,(err,value) => { console.log(value)})client.srem(’name’,‘a’,(err,value) => { console.log(value)})client.smembers(’name’,(err,value) => { console.log(value)})client.flushall(’name’,(err,value) => { console.log(value)})client.smembers(’name’,(err,value) => { console.log(value)})Redis哈希Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。常用命令设置值 hmset : hmset zhangsan name “张三” age 20 sex “男”设置值 hset : hset zhangsan name “张三"获取数据: hgetall key删除指定数据: del key删除全部数据: flushall终端nodevar redis = require(‘redis’);var client = redis.createClient(6379,’localhost’);client.hmset(‘userinfo’,’name’,‘jie’,‘age’,20,‘sex’,‘男’);client.type(‘userinfo’,(err,value) => { console.log(value)})client.hgetall(‘userinfo’,(err,value) => { console.log(value)})client.del(‘userinfo’,(err,value) => { console.log(value)})client.hgetall(‘userinfo’,(err,value) => { console.log(value)}) ...

December 27, 2018 · 1 min · jiezi

egg(87)--egg之redis的使用

Redis的简单介绍Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key-value 数据库,它也属于 nosql。Redis 和 Memcached 类似,都是内存级别的数据缓存,主要用户数据缓存,它支持存储的 value 类型相对更多,包括 string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和 hash(哈希类型)。Redis 不仅有丰富的特性(数据持久化到硬盘、 publish/subscribe、 key 过期),还有极高性能,经测试Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s下载安装下载地址https://github.com/MicrosoftArchive/redis/releases安装运行redis常用命令查找所有key keys *设置key set username “jie"获取key get usernamenode中使用redis目录创建项目mkdir rediscd redisnpm init -ycnpm install redis –save文件内容redis.jsvar redis = require(‘redis’);var client = redis.createClient(6379,’localhost’);client.set(‘username1’,‘jie’);client.set(‘username2’,‘biao’,‘Ex’,‘5’);client.get(‘username1’,(err,value) => { console.log(value)})client.get(‘username2’,(err,value) => { console.log(value)})setTimeout(function(){ client.get(‘username2’,(err,value) => { console.log(value) })},6000)运行

December 26, 2018 · 1 min · jiezi

egg(83,84,85,86)--egg之商城首页数据渲染

controllerapp/controller/default/index.js’use strict’;const Controller = require(’egg’).Controller;class IndexController extends Controller { async index() { //获取顶部导航的数据 var topNav=await this.ctx.model.Nav.find({“position”:1}); //轮播图 var focus=await this.ctx.model.Focus.find({“type”:1}); //商品分类 var goodsCate=await this.ctx.model.GoodsCate.aggregate([ { $lookup:{ from:‘goods_cate’, localField:’_id’, foreignField:‘pid’, as:‘items’ } }, { $match:{ “pid”:‘0’ } } ]) // console.log(topNav); var middleNav=await this.ctx.model.Nav.find({“position”:2}); middleNav=JSON.parse(JSON.stringify(middleNav)); //1、不可扩展对象 for(var i=0;i<middleNav.length;i++){ if(middleNav[i].relation){ //数据库查找relation对应的商品 try{ var tempArr=middleNav[i].relation.replace(/,/g,’,’).split(’,’); var tempRelationIds=[]; tempArr.forEach((value)=>{ tempRelationIds.push({ “_id”:this.app.mongoose.Types.ObjectId(value) }) }) var relationGoods=await this.ctx.model.Goods.find({ $or:tempRelationIds },’title goods_img’); middleNav[i].subGoods=relationGoods; }catch(err){ //2、如果用户输入了错误的ObjectID(商品id) middleNav[i].subGoods=[]; } }else{ middleNav[i].subGoods=[]; } } console.log(JSON.stringify(middleNav)) var shoujiResult=await this.service.goods.get_category_recommend_goods(‘5bbf058f9079450a903cb77b’,‘best’,8); await this.ctx.render(‘default/index’,{ topNav:topNav, focus:focus, goodsCate:goodsCate, middleNav:middleNav, shoujiResult:shoujiResult }); }}module.exports = IndexController;viewapp/view/default/index.html<% include ./public/header.html%><!–end header –><!– start banner_x –> <div class=“banner_x center”> <a href="./index.html" target="_blank"><div class=“logo fl”></div></a> <a href=""><div class=“ad_top fl”></div></a> <div class=“nav fl”> <ul class=“clearfix” id=“nav_list”> <%for(var i=0;i<middleNav.length;i++){ %> <li> <a href="#" target="_blank"><%=middleNav[i].title%></a> <%if(middleNav[i].subGoods.length>0) {%> <ol class=“children-list clearfix”> <%for(var j=0;j<middleNav[i].subGoods.length;j++){ %> <li> <a href="#"> <img src="<%=middleNav[i].subGoods[j].goods_img%>" /> <p><%=middleNav[i].subGoods[j].price%></p> </a> </li> <%}%> </ol> <%}%> </li> <%} %> </ul> </div> <div class=“search fr”> <form action="" method=“post”> <div class=“text fl”> <input type=“text” class=“shuru” placeholder=“小米6&nbsp;小米MIX现货”> </div> <div class=“submit fl”> <input type=“submit” class=“sousuo” value=“搜索”/> </div> <div class=“clear”></div> </form> <div class=“clear”></div> </div> </div><!– end banner_x –> <!– start banner_y –> <div class=“banner_y center”> <div class=“nav”> <ul> <%for(var i=0;i<goodsCate.length;i++){ %> <li> <a href="#"><%=goodsCate[i].title%></a> <div class=“pop”> <ol class=“cate_list clear”> <%for(var j=0;j<goodsCate[i].items.length;j++){ %> <li> <div class=“xuangou_left”> <a href="#"> <div class=“img fl”><img src="<%=goodsCate[i].items[j].cate_img%>" alt="<%=goodsCate[i].items[j].title%>"></div> <span class=“fl”><%=goodsCate[i].items[j].title%></span> </a> </div> </li> <%}%> </ol> </div> </li> <%}%> </ul> </div> <div class=“swiper-container”> <div class=“swiper-wrapper”> <%for(var i=0;i<focus.length;i++){ %> <div class=“swiper-slide”> <a href="<%=focus[i].link%>" target="_blank"> <img src="<%=focus[i].focus_img%>" alt="<%=focus[i].title%>" /> </a> </div> <%}%> </div> <!– Add Arrows –> <div class=“swiper-button-next”></div> <div class=“swiper-button-prev”></div> </div> </div> <div class=“sub_banner center”> <div class=“sidebar fl”> <div class=“fl”><a href=""><img src="/public/default/image/hjh_01.gif"></a></div> <div class=“fl”><a href=""><img src="/public/default/image/hjh_02.gif"></a></div> <div class=“fl”><a href=""><img src="/public/default/image/hjh_03.gif"></a></div> <div class=“fl”><a href=""><img src="/public/default/image/hjh_04.gif"></a></div> <div class=“fl”><a href=""><img src="/public/default/image/hjh_05.gif"></a></div> <div class=“fl”><a href=""><img src="/public/default/image/hjh_06.gif"></a></div> <div class=“clear”></div> </div> <div class=“datu fl”><a href=""><img src="/public/default/image/hongmi4x.png" alt=""></a></div> <div class=“datu fl”><a href=""><img src="/public/default/image/xiaomi5.jpg" alt=""></a></div> <div class=“datu fr”><a href=""><img src="/public/default/image/pinghengche.jpg" alt=""></a></div> <div class=“clear”></div> </div> <!– end banner –> <!– start danpin –> <div class=“danpin center”> <div class=“title center”>小米明星单品</div> <div class=“main center”> <div class=“mingxing fl”> <div class=“sub_mingxing”><a href=""><img src="/public/default/image/pinpai1.png" alt=""></a></div> <div class=“pinpai”><a href="">小米MIX</a></div> <div class=“youhui”>5月9日-21日享花呗12期分期免息</div> <div class=“jiage”>3499元起</div> </div> <div class=“mingxing fl”> <div class=“sub_mingxing”><a href=""><img src="/public/default/image/pinpai2.png" alt=""></a></div> <div class=“pinpai”><a href="">小米5s</a></div> <div class=“youhui”>5月9日-10日,下单立减200元</div> <div class=“jiage”>1999元</div> </div> <div class=“mingxing fl”> <div class=“sub_mingxing”><a href=""><img src="/public/default/image/pinpai3.png" alt=""></a></div> <div class=“pinpai”><a href="">小米手机5 64GB</a></div> <div class=“youhui”>5月9日-10日,下单立减100元</div> <div class=“jiage”>1799元</div> </div> <div class=“mingxing fl”> <div class=“sub_mingxing”><a href=""><img src="/public/default/image/pinpai4.png" alt=""></a></div> <div class=“pinpai”><a href="">小米电视3s 55英寸</a></div> <div class=“youhui”>5月9日,下单立减200元</div> <div class=“jiage”>3999元</div> </div> <div class=“mingxing fl”> <div class=“sub_mingxing”><a href=""><img src="/public/default/image/pinpai5.png" alt=""></a></div> <div class=“pinpai”><a href="">小米笔记本</a></div> <div class=“youhui”>更轻更薄,像杂志一样随身携带</div> <div class=“jiage”>3599元起</div> </div> <div class=“clear”></div> </div> </div> <!– 手机 –> <div class=“category_item w”> <div class=“title center”>手机</div> <div class=“main center”> <div class=“category_item_left”> <img src="/public/default/image/shouji.jpg" alt=“手机”> </div> <div class=“category_item_right”> <%for(var i=0;i<shoujiResult.length;i++){ %> <div class=“hot fl”> <div class=“xinpin”><span>新品</span></div> <div class=“tu”><a href=""><img src="<%=shoujiResult[i].goods_img%>"></a></div> <div class=“miaoshu”><a href="#"><%=shoujiResult[i].title%></a></div> <div class=“jiage”><%=shoujiResult[i].price%>元</div> <!– <div class=“pingjia”>372人评价</div> –> <div class=“piao”> <span><%=shoujiResult[i].sub_title%></span> </div> </div> <%} %> </div> </div> </div> <!– 配件 –> <div class=“category_item w”> <div class=“title center”>配件</div> <div class=“main center”> <div class=“content”> <div class=“hot fl”><a href=""><img src="/public/default/image/peijian1.jpg"></a> </div> <div class=“hot fl”> <div class=“xinpin”><span>新品</span></div> <div class=“tu”><a href=""><img src="/public/default/image/peijian2.jpg"></a></div> <div class=“miaoshu”><a href="">小米6 硅胶保护套</a></div> <div class=“jiage”>49元</div> <div class=“pingjia”>372人评价</div> <div class=“piao”> <a href=""> <span>发货速度很快!很配小米6!</span> <span>来至于mi狼牙的评价</span> </a> </div> </div> <div class=“hot fl”> <div class=“xinpin”><span style=“background:#fff”></span></div> <div class=“tu”><a href=""><img src="/public/default/image/peijian3.jpg"></a></div> <div class=“miaoshu”><a href="">小米手机4c 小米4c 智能</a></div> <div class=“jiage”>29元</div> <div class=“pingjia”>372人评价</div> </div> <div class=“hot fl”> <div class=“xinpin”><span style=“background:red”>享6折</span></div> <div class=“tu”><a href=""><img src="/public/default/image/peijian4.jpg"></a></div> <div class=“miaoshu”><a href="">红米NOTE 4X 红米note4X</a></div> <div class=“jiage”>19元</div> <div class=“pingjia”>372人评价</div> <div class=“piao”> <a href=""> <span>发货速度很快!很配小米6!</span> <span>来至于mi狼牙的评价</span> </a> </div> </div> <div class=“hot fl”> <div class=“xinpin”><span style=“background:#fff”></span></div> <div class=“tu”><a href=""><img src="/public/default/image/peijian5.jpg"></a></div> <div class=“miaoshu”><a href="">小米支架式自拍杆</a></div> <div class=“jiage”>89元</div> <div class=“pingjia”>372人评价</div> <div class=“piao”> <a href=""> <span>发货速度很快!很配小米6!</span> <span>来至于mi狼牙的评价</span> </a> </div> </div> <div class=“clear”></div> </div> <div class=“content”> <div class=“hot fl”><a href=""><img src="/public/default/image/peijian6.png"></a> </div> <div class=“hot fl”> <div class=“xinpin”><span style=“background:#fff”></span></div> <div class=“tu”><a href=""><img src="/public/default/image/peijian7.jpg"></a></div> <div class=“miaoshu”><a href="">小米指环支架</a></div> <div class=“jiage”>19元</div> <div class=“pingjia”>372人评价</div> <div class=“piao”> <a href=""> <span>发货速度很快!很配小米6!</span> <span>来至于mi狼牙的评价</span> </a> </div> </div> <div class=“hot fl”> <div class=“xinpin”><span style=“background:#fff”></span></div> <div class=“tu”><a href=""><img src="/public/default/image/peijian8.jpg"></a></div> <div class=“miaoshu”><a href="">米家随身风扇</a></div> <div class=“jiage”>19.9元</div> <div class=“pingjia”>372人评价</div> </div> <div class=“hot fl”> <div class=“xinpin”><span style=“background:#fff”></span></div> <div class=“tu”><a href=""><img src="/public/default/image/peijian9.jpg"></a></div> <div class=“miaoshu”><a href="">红米4X 高透软胶保护套</a></div> <div class=“jiage”>59元</div> <div class=“pingjia”>775人评价</div> </div> <div class=“hotlast fr”> <div class=“hongmi”><a href=""><img src="/public/default/image/hongmin4.png" alt=""></a></div> <div class=“liulangengduo”><a href=""><img src="/public/default/image/liulangengduo.png" alt=""></a></div> </div> <div class=“clear”></div> </div> </div> </div> <footer class=“mt20 center”> <div class=“mt20”>小米商城|MIUI|米聊|多看书城|小米路由器|视频电话|小米天猫店|小米淘宝直营店|小米网盟|小米移动|隐私政策|Select Region</div> <div>©mi.com 京ICP证110507号 京ICP备10046444号 京公网安备11010802020134号 京网文[2014]0059-0009号</div> <div>违法和不良信息举报电话:185-0130-1238,本网站所列数据,除特殊说明,所有数据均出自我司实验室测试</div> </footer> </body></html>效果 ...

December 26, 2018 · 4 min · jiezi

mongodb数据的导入与导出

先连接数据库mongo eggxiaomi -u admin -p 123456打开另一个窗口操作导出-h 端口-d 数据库名字-u 账号-p 密码-o 导入到哪里(路径)mongodump -h 127.0.0.1 -d eggxiaomi -u admin -p 123456 -o D:\test导入路径不能太多要输入密码mongorestore -h 127.0.0.1 -d eggxiaomi -u admin -p 123456 D:\test\eggxiaomi

December 25, 2018 · 1 min · jiezi

egg(81)--egg之网站信息

modelapp/model/setting.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const SettingSchema = new Schema({ site_title: { type: String }, site_logo: { type: String }, site_keywords: { type: String }, site_description:{ type: String }, no_picture:{ type: String }, site_icp:{ type: String }, site_tel: { type: String }, search_keywords: { type: String }, tongji_code: { type: String } }); return mongoose.model(‘Setting’, SettingSchema,‘setting’);}router.js router.get(’/admin/setting’, controller.admin.setting.index); router.post(’/admin/setting/doEdit’, controller.admin.setting.doEdit);controllerapp/controller/admin/setting.js’use strict’;const fs=require(‘fs’);const pump = require(‘mz-modules/pump’);var BaseController =require(’./base.js’);class SettingController extends BaseController { async index() { //提前给setting表增加一条数据 var result=await this.ctx.model.Setting.find({}); console.log(JSON.stringify(result)) if(result){ await this.ctx.render(‘admin/setting/index’,{ list:result[0] }); }else{ await this.ctx.render(‘admin/setting/index’); } } async doEdit() { let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) } //修改操作 var updateResult=Object.assign(files,parts.field); await this.ctx.model.Setting.updateOne({},updateResult); await this.success(’/admin/setting’,‘修改系统设置成功’); } }module.exports = SettingController;viewapp/view/admin/setting/index.html<%- include ../public/page_header.html %> <div class=“panel panel-default”> <div class=“panel-heading”> 商店设置 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/setting/doEdit?_csrf=<%=csrf%>" method=“post” enctype=“multipart/form-data”> <ul class=“form_input”> <li> <span>网站标题:</span> <input type=“text” class=“input” name=“site_title” value="<%=list.site_title%>"/></li> <li> <span>网站logo:</span> <input type=“file” name=“site_logo”/> <br /> <span> </span> <img class=“pic” src="<%=list.site_logo%>" /> </li> <li> <span>网站关键词:</span> <input type=“text” class=“input” name=“site_keywords” value="<%=list.site_keywords%>"/></li> <li> <span>网站描述:</span> <textarea name=“site_description” style=“width:600px;"><%=list.site_description%></textarea></li> <li> <span>商品默认图片:</span> <input type=“file” name=“no_picture” /> <br /> <span> </span> <img class=“pic” src="<%=list.no_picture%>” /> </li> <li> <span>备案信息:</span> <input type=“text” class=“input” name=“site_icp” value="<%=list.site_icp%>"/></li> <li> <span>网站电话:</span> <input type=“text” class=“input” name=“site_tel” value="<%=list.site_tel%>"/></li> <li> <span>搜索关键词:</span> <input type=“text” class=“input” name=“search_keywords” value="<%=list.search_keywords%>"/></li> <li> <span>统计代码:</span> <input type=“text” class=“input” name=“tongji_code” value="<%=list.tongji_code%>"/></li> <li> <br/> <button type=“submit” class=“btn btn-primary”>提交</button> </li> </ul> </form> </div> </div> </div></body></html>效果 ...

December 21, 2018 · 2 min · jiezi

egg(80)--egg之文章修改后返回到当前分页,模糊搜索

文章修改后返回到当前分页middlewareapp/middleware/adminauth.js获取上一页的urlctx.state.prevPage = ctx.request.headers[‘referer’]; //获取上一页的urlviewapp/view/admin/nav/edit.html在edit的from中把上一页的url提交给后台<input type=“hidden” name=“prevPage” value="<%= prevPage %>">controllerapp/controller/admin/nav.js async doEdit() { var _id=this.ctx.request.body._id; var prevPage = this.ctx.request.body.prevPage; await this.ctx.model.Nav.updateOne({"_id":_id},this.ctx.request.body) await this.success(prevPage,‘编辑导航成功’); } 效果模糊搜索viewapp/view/admin/goods/index.html <form role=“form” class=“form-inline” method=“get” action="/admin/goods"> <div class=“form-group”> <label for=“name”>输入关键词</label> <input type=“text” class=“form-control” value="<%=keyword%>" id=“name” name=“keyword” placeholder=“请输入名称”> </div> <div class=“form-group”> <button type=“submit” class=“btn btn-default”>开始搜索</button> </div> </form>controllerapp/controller/admin/goods.js async index() { var page=this.ctx.request.query.page || 1; var keyword=this.ctx.request.query.keyword; //注意 var json={}; if(keyword){ json=Object.assign({“title”:{$regex:new RegExp(keyword)}}); } var pageSize=3; //获取当前数据表的总数量 var totalNum=await this.ctx.model.Goods.find(json).count(); var goodsResult=await this.ctx.model.Goods.find(json).skip((page-1)*pageSize).limit(pageSize); await this.ctx.render(‘admin/goods/index’,{ list:goodsResult, totalPages:Math.ceil(totalNum/pageSize), page:page, keyword:keyword }); }效果 ...

December 21, 2018 · 1 min · jiezi

egg(77)--egg之文章curd,文章与文章分类相关联

modelapp/model/article.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d=new Date(); const ArticleSchema = new Schema({ title: { type: String }, cate_id:{type:Schema.Types.ObjectId }, article_img: { type: String }, link:{ type: String }, content: { type: String }, keywords: { type: String }, description: { type: String }, sort: { type: Number,default:100 }, add_time: { type:Number, default: d.getTime() }, status: { type: Number,default:1 } }); return mongoose.model(‘Article’,ArticleSchema,‘article’);}router.js router.get(’/admin/article’, controller.admin.article.index); router.get(’/admin/article/add’, controller.admin.article.add); router.get(’/admin/article/edit’, controller.admin.article.edit); router.post(’/admin/article/doEdit’, controller.admin.article.doEdit); router.post(’/admin/article/doAdd’, controller.admin.article.doAdd);controller.jsaggregate聚合管道分页const fs=require(‘fs’);const pump = require(‘mz-modules/pump’);var BaseController =require(’./base.js’);class ArticleController extends BaseController { async index() { var page=this.ctx.request.query.page || 1; var pageSize=3; //总数量 var totalNum=await this.ctx.model.Article.find({}).count(); /* var goodsResult=await this.ctx.model.Goods.find({}).skip((page-1)*pageSize).limit(pageSize); */ //让文章和分类进行关联 var result=await this.ctx.model.Article.aggregate([ { $lookup:{ from:‘article_cate’, localField:‘cate_id’, foreignField:’_id’, as:‘catelist’ } }, { $skip:(page-1)*pageSize }, { $limit:pageSize } ]) console.log(result); await this.ctx.render(‘admin/article/index’,{ list:result, totalPages:Math.ceil(totalNum/pageSize), page:page }); } async add() { //获取所有的分类 var cateResult=await this.ctx.model.ArticleCate.aggregate([ { $lookup:{ from:‘article_cate’, localField:’_id’, foreignField:‘pid’, as:‘items’ } }, { $match:{ “pid”:‘0’ } } ]) await this.ctx.render(‘admin/article/add’,{ cateList:cateResult }); } async doAdd() { let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) //生成缩略图 this.service.tools.jimpImg(target); } let article =new this.ctx.model.Article(Object.assign(files,parts.field)); await article.save(); await this.success(’/admin/article’,‘增加文章成功’); } async edit() { var id=this.ctx.request.query.id; //当前id对应的数据 var result=await this.ctx.model.Article.find({"_id":id}); //获取所有的分类 var cateResult=await this.ctx.model.ArticleCate.aggregate([ { $lookup:{ from:‘article_cate’, localField:’_id’, foreignField:‘pid’, as:‘items’ } }, { $match:{ “pid”:‘0’ } } ]); await this.ctx.render(‘admin/article/edit’,{ cateList:cateResult, list:result[0] }); } async doEdit() { let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) //生成缩略图 this.service.tools.jimpImg(target); } var id=parts.field.id; var updateResult=Object.assign(files,parts.field); await this.ctx.model.Article.updateOne({"_id":id},updateResult); await this.success(’/admin/article’,‘修改数据成功’); } }module.exports = ArticleController;view增加<%- include ../public/page_header.html %> <!– 富文本编辑器 –> <link href="/public/admin/wysiwyg-editor/css/font-awesome.min.css" rel=“stylesheet” type=“text/css” /> <!– Include Editor style. –> <link href="/public/admin/wysiwyg-editor/css/froala_editor.pkgd.min.css" rel=“stylesheet” type=“text/css” /> <link href="/public/admin/wysiwyg-editor/css/froala_style.min.css" rel=“stylesheet” type=“text/css” /> <!– 引入jquery –> <!– Include Editor JS files. –> <script type=“text/javascript” src="/public/admin/wysiwyg-editor/js/froala_editor.pkgd.min.js"></script> <script type=“text/javascript” src="/public/admin/wysiwyg-editor/js/zh_cn.js"></script> <div class=“panel panel-default”> <div class=“panel-heading”> 增加文章 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/article/doAdd?_csrf=<%=csrf%>" method=“post” enctype=“multipart/form-data” class=“news_content”> <!– Nav tabs –> <ul class=“nav nav-tabs” role=“tablist”> <li role=“presentation” class=“active”><a href="#general" role=“tab” data-toggle=“tab”>通用信息</a></li> <li role=“presentation”><a href="#detail" role=“tab” data-toggle=“tab”>详细描述</a></li> </ul> <!– Tab panes –> <div class=“tab-content”> <div role=“tabpanel” class=“tab-pane active” id=“general”> <ul class=“form_input”> <li> <span>文章标题:</span> <input type=“text” name=“title” class=“input”/></li> <li> <span>所属分类:</span> <select name=“cate_id” id=“cate_id”> <%for(var i=0;i<cateList.length;i++){%> <option value="<%=cateList[i]._id%>"><%=cateList[i].title%></option> <%for(var j=0;j<cateList[i].items.length;j++){%> <option value="<%=cateList[i].items[j]._id%>">—-<%=cateList[i].items[j].title%></option> <%}%> <%}%> </select> </li> <li> <span>封面图片:</span> <input type=“file” name=“article_img”/></li> <li> <span>跳转地址:</span> <input type=“text” name=“link” class=“input”/></li> <li> <span>Seo关键词: </span><input type=“text” name=“keywords” class=“input”/></li> <li> <span>Seo描述:</span> <textarea name=“description” id=“description” cols=“84” rows=“4”></textarea></li> <li> <span>排 序:</span> <input type=“text” name=“sort” value=“100”/></li> <li> <span>状 态:</span> <input type=“radio” name=“status” checked value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” name=“status” value=“0” id=“b”/><label for=“b”>隐藏</label> </li> </ul> </div> <div role=“tabpanel” class=“tab-pane” id=“detail”> <textarea name=“content” id=“content” cols=“100” rows=“8”></textarea> </div> <br /> <button type=“submit” class=“btn btn-success goods_content_btn”>提交</button> </div> </form> </div> </div> <script> $(function() { $(’#content’).froalaEditor({ height: 300, //给编辑器设置默认的高度 language: ‘zh_cn’, imageUploadURL: ‘/admin/goods/goodsUploadImage’, //根据不同的分辨率加载不同的配置 toolbarButtons: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’], toolbarButtonsMD: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’], toolbarButtonsSM: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’] }); }); </script> </div></body></html>查找<%- include ../public/page_header.html %> <!– 分页插件 –> <script src="/public/admin/js/jqPaginator.js"></script> <div class=“panel panel-default”> <div class=“panel-heading clear”> <span>文章分类列表</span> <a href="/admin/article/add" class=“btn btn-primary fr”>增加文章</a> </div> <div class=“panel-body”> <!– 列表展示 –> <div class=“table-responsive”> <table class=“table table-bordered”> <thead> <tr class=“th”> <th>文章名称</th> <th>文章图片</th> <th>所属分类</th> <th>增加日期</th> <th class=“text-center”>排序</th> <th class=“text-center”>状态</th> <th class=“text-center”>操作</th> </tr> </thead> <tbody> <%for(var i=0;i<list.length;i++){%> <tr> <td><%=list[i].title%></td> <td><img class=“pic” src="<%=list[i].article_img%>" /></td> <td><%=list[i].catelist[0].title%></td> <td><%=helper.formatTime(list[i].add_time)%></td> <td class=“text-center”><span onclick=“app.editNum(this,‘Article’,‘sort’,’<%=list[i]._id%>’)"><%=list[i].sort%></span></td> <td class=“text-center”> <%if(list[i].status==1){%> <img src="/public/admin/images/yes.gif” onclick=“app.changeStatus(this,‘Article’,‘status’,’<%=list[i]._id%>’)” /> <%}else{%> <img src="/public/admin/images/no.gif" onclick=“app.changeStatus(this,‘Article’,‘status’,’<%=list[i]._id%>’)” /> <%}%> </td> <td class=“text-center”> <a href="/admin/article/edit?id=<%=list[i]._id%>">修改</a> <a class=“delete” href="/admin/delete?model=Article&id=<%=list[i]._id%>">删除</a></td> </tr> <%}%> </tbody> </table> <div id=“page” class=“pagination fr”></div> </div> </div> </div> <script> $(’#page’).jqPaginator({ totalPages: <%=totalPages%>, visiblePages: 8, currentPage: <%=page%>, onPageChange: function (num, type) { console.log(‘当前第’ + num + ‘页’,type); if(type==‘change’){ location.href="/admin/article?page="+num; } } }); </script></body></html>编辑<%- include ../public/page_header.html %> <!– 富文本编辑器 –> <link href="/public/admin/wysiwyg-editor/css/font-awesome.min.css" rel=“stylesheet” type=“text/css” /> <!– Include Editor style. –> <link href="/public/admin/wysiwyg-editor/css/froala_editor.pkgd.min.css" rel=“stylesheet” type=“text/css” /> <link href="/public/admin/wysiwyg-editor/css/froala_style.min.css" rel=“stylesheet” type=“text/css” /> <!– 引入jquery –> <!– Include Editor JS files. –> <script type=“text/javascript” src="/public/admin/wysiwyg-editor/js/froala_editor.pkgd.min.js"></script> <script type=“text/javascript” src="/public/admin/wysiwyg-editor/js/zh_cn.js"></script> <div class=“panel panel-default”> <div class=“panel-heading”> 修改文章 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/article/doEdit?_csrf=<%=csrf%>" method=“post” enctype=“multipart/form-data” class=“news_content”> <!– Nav tabs –> <ul class=“nav nav-tabs” role=“tablist”> <li role=“presentation” class=“active”><a href="#general" role=“tab” data-toggle=“tab”>通用信息</a></li> <li role=“presentation”><a href="#detail" role=“tab” data-toggle=“tab”>详细描述</a></li> </ul> <!– Tab panes –> <div class=“tab-content”> <div role=“tabpanel” class=“tab-pane active” id=“general”> <input type=“hidden” name=“id” class=“input” value="<%=list._id%>"/> <ul class=“form_input”> <li> <span>文章标题:</span> <input type=“text” name=“title” class=“input” value="<%=list.title%>"/></li> <li> <span>所属分类:</span> <select name=“cate_id” id=“cate_id”> <%for(var i=0;i<cateList.length;i++){%> <option value="<%=cateList[i]._id%>" <%if(list.cate_id.toString()==cateList[i]._id.toString()){%>selected<%}%> ><%=cateList[i].title%></option> <%for(var j=0;j<cateList[i].items.length;j++){%> <option value="<%=cateList[i].items[j]._id%>" <%if(list.cate_id.toString()==cateList[i].items[j]._id.toString()){%>selected<%}%>>—-<%=cateList[i].items[j].title%></option> <%}%> <%}%> </select> </li> <li> <span>封面图片:</span> <input type=“file” name=“article_img”/> <br /> <span> </span> <img class=“pic” src="<%=list.article_img%>" /> </li> <li> <span>跳转地址:</span> <input type=“text” name=“link” class=“input” value="<%=list.link%>"/></li> <li> <span>Seo关键词: </span><input type=“text” name=“keywords” class=“input” value="<%=list.keywords%>"/></li> <li> <span>Seo描述:</span> <textarea name=“description” id=“description” cols=“84” rows=“4”><%=list.description%></textarea></li> <li> <span>排 序:</span> <input type=“text” name=“sort” value="<%=list.sort%>"/></li> <li> <span>状 态:</span> <input type=“radio” name=“status” <%if(list.status==1){%> checked <%}%> value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” <%if(list.status==0){%> checked <%}%> name=“status” value=“0” id=“b”/><label for=“b”>隐藏</label> </li> </ul> </div> <div role=“tabpanel” class=“tab-pane” id=“detail”> <textarea name=“content” id=“content” cols=“100” rows=“8”><%=list.content%></textarea> </div> <br /> <button type=“submit” class=“btn btn-success goods_content_btn”>提交</button> </div> </form> </div> </div> <script> $(function() { $(’#content’).froalaEditor({ height: 300, //给编辑器设置默认的高度 language: ‘zh_cn’, imageUploadURL: ‘/admin/goods/goodsUploadImage’, //根据不同的分辨率加载不同的配置 toolbarButtons: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’], toolbarButtonsMD: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’], toolbarButtonsSM: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’] }); }); </script> </div></body></html> ...

December 20, 2018 · 6 min · jiezi

egg(77)--egg之文章分类curd

modelapp/model/article_cate.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d=new Date(); const ArticleCateSchema = new Schema({ title: { type: String }, cate_img: { type: String }, link:{ type: String }, pid:{ type:Schema.Types.Mixed //混合类型 }, sub_title: { type: String }, /seo相关的标题 关键词 描述/ keywords: { type: String }, description: { type: String }, status: { type: Number,default:1 }, sort: { type: Number,default:100 }, add_time: { type:Number, default: d.getTime() } }); return mongoose.model(‘ArticleCate’,ArticleCateSchema,‘article_cate’);}router.js router.get(’/admin/articleCate’, controller.admin.articleCate.index); router.get(’/admin/articleCate/add’, controller.admin.articleCate.add); router.get(’/admin/articleCate/edit’, controller.admin.articleCate.edit); router.post(’/admin/articleCate/doEdit’, controller.admin.articleCate.doEdit); router.post(’/admin/articleCate/doAdd’, controller.admin.articleCate.doAdd);controllerapp/controller/admin/articleCate.jsconst fs=require(‘fs’);const pump = require(‘mz-modules/pump’);var BaseController =require(’./base.js’);class ArticleCateController extends BaseController { async index() { var result=await this.ctx.model.ArticleCate.aggregate([ { $lookup:{ from:‘article_cate’, localField:’_id’, foreignField:‘pid’, as:‘items’ } }, { $match:{ “pid”:‘0’ } } ]) await this.ctx.render(‘admin/articleCate/index’,{ list:result }); } async add() { var result=await this.ctx.model.ArticleCate.find({“pid”:‘0’}); await this.ctx.render(‘admin/articleCate/add’,{ cateList:result }); } async doAdd() { let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) //生成缩略图 this.service.tools.jimpImg(target); } if(parts.field.pid!=0){ parts.field.pid=this.app.mongoose.Types.ObjectId(parts.field.pid); //调用mongoose里面的方法把字符串转换成ObjectId } let articleCate =new this.ctx.model.ArticleCate(Object.assign(files,parts.field)); await articleCate.save(); await this.success(’/admin/articleCate’,‘增加分类成功’); } async edit() { var id=this.ctx.request.query.id; var result=await this.ctx.model.ArticleCate.find({"_id":id}); var cateList=await this.ctx.model.ArticleCate.find({“pid”:‘0’}); await this.ctx.render(‘admin/articleCate/edit’,{ cateList:cateList, list:result[0] }); } async doEdit() { let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) //生成缩略图 this.service.tools.jimpImg(target); } if(parts.field.pid!=0){ parts.field.pid=this.app.mongoose.Types.ObjectId(parts.field.pid); //调用mongoose里面的方法把字符串转换成ObjectId } var id=parts.field.id; var updateResult=Object.assign(files,parts.field); await this.ctx.model.ArticleCate.updateOne({"_id":id},updateResult); await this.success(’/admin/articleCate’,‘修改分类成功’); } }module.exports = ArticleCateController;view增加<%- include ../public/page_header.html %> <div class=“panel panel-default”> <div class=“panel-heading”> 增加文章分类 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/articleCate/doAdd?_csrf=<%=csrf%>" method=“post” enctype=“multipart/form-data”> <ul class=“form_input”> <li> <span>分类名称:</span> <input type=“text” name=“title” class=“input”/></li> <li> <span>上级分类:</span> <select name=“pid” id=“pid”> <option value=“0”>顶级分类</option> <%for(var i=0;i<cateList.length;i++){%> <option value="<%=cateList[i]._id%>"><%=cateList[i].title%></option> <%}%> </select> </li> <li> <span>分类图片:</span> <input type=“file” name=“cate_img”/></li> <li> <span>跳转地址:</span> <input type=“text” name=“link” class=“input”/></li> <li> <span>Seo标题:</span> <input type=“text” name=“sub_title” class=“input”/></li> <li> <span>Seo关键词: </span><input type=“text” name=“keywords” class=“input”/></li> <li> <span>Seo描述:</span> <textarea name=“description” id=“description” cols=“84” rows=“4”></textarea></li> <li> <span>排 序:</span> <input type=“text” name=“sort” value=“100”/></li> <li> <span>状 态:</span> <input type=“radio” name=“status” checked value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” name=“status” value=“0” id=“b”/><label for=“b”>隐藏</label> </li> <li> <br/> <button type=“submit” class=“btn btn-primary”>提交</button> </li> </ul> </form> </div> </div> </div></body></html>查找<%- include ../public/page_header.html %> <div class=“panel panel-default”> <div class=“panel-heading clear”> <span>文章分类列表</span> <a href="/admin/articleCate/add" class=“btn btn-primary fr”>增加文章分类</a> </div> <div class=“panel-body”> <!– 列表展示 –> <div class=“table-responsive”> <table class=“table table-bordered”> <thead> <tr class=“th”> <th>分类名称</th> <th>分类图片</th> <th class=“text-center”>排序</th> <th class=“text-center”>状态</th> <th class=“text-center”>操作</th> </tr> </thead> <tbody> <%for(var i=0;i<list.length;i++){%> <tr> <td><%=list[i].title%></td> <td><img class=“pic” src="<%=list[i].cate_img%>" /></td> <td class=“text-center”><span onclick=“app.editNum(this,‘ArticleCate’,‘sort’,’<%=list[i]._id%>’)"><%=list[i].sort%></span></td> <td class=“text-center”> <%if(list[i].status==1){%> <img src="/public/admin/images/yes.gif” onclick=“app.changeStatus(this,‘ArticleCate’,‘status’,’<%=list[i]._id%>’)” /> <%}else{%> <img src="/public/admin/images/no.gif" onclick=“app.changeStatus(this,‘ArticleCate’,‘status’,’<%=list[i]._id%>’)” /> <%}%> </td> <td class=“text-center”> <a href="/admin/articleCate/edit?id=<%=list[i]._id%>">修改</a> <a class=“delete” href="/admin/delete?model=ArticleCate&id=<%=list[i]._id%>">删除</a></td> </tr> <%for(var j=0;j<list[i].items.length;j++){%> <tr> <td>—–<%=list[i].items[j].title%></td> <td><img class=“pic” src="<%=list[i].items[j].cate_img%>" /></td> <td class=“text-center”><span onclick=“app.editNum(this,‘ArticleCate’,‘sort’,’<%=list[i].items[j]._id%>’)"><%=list[i].items[j].sort%></span></td> <td class=“text-center”> <%if(list[i].status==1){%> <img src="/public/admin/images/yes.gif” onclick=“app.changeStatus(this,‘ArticleCate’,‘status’,’<%=list[i].items[j]._id%>’)” /> <%}else{%> <img src="/public/admin/images/no.gif" onclick=“app.changeStatus(this,‘ArticleCate’,‘status’,’<%=list[i].items[j]._id%>’)” /> <%}%> </td> <td class=“text-center”> <a href="/admin/articleCate/edit?id=<%=list[i].items[j]._id%>">修改</a> <a class=“delete” href="/admin/delete?model=ArticleCate&id=<%=list[i].items[j]._id%>">删除</a></td> </tr> <%}%> <%}%> </tbody> </table> </div> </div> </div></body></html>编辑<%- include ../public/page_header.html %> <div class=“panel panel-default”> <div class=“panel-heading”> 编辑文章分类 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/articleCate/doEdit?_csrf=<%=csrf%>" method=“post” enctype=“multipart/form-data”> <ul class=“form_input”> <input type=“hidden” name=“id” value="<%=list._id%>" /> <li> <span>分类名称:</span> <input type=“text” name=“title” class=“input” value="<%=list.title%>"/></li> <li> <span>上级分类:</span> <select name=“pid” id=“pid”> <option value=“0”>顶级分类</option> <%for(var i=0;i<cateList.length;i++){%> <option value="<%=cateList[i]._id%>" <%if(cateList[i]._id.toString()==list.pid){%> selected <%}%>><%=cateList[i].title%></option> <%}%> </select> </li> <li> <span>分类图片:</span> <input type=“file” name=“cate_img”/> <br /> <span> </span> <img class=“pic” src="<%=list.cate_img%>" /> </li> <li> <span>跳转地址:</span> <input type=“text” name=“link” class=“input” value="<%=list.link%>" /></li> <li> <span>Seo标题:</span> <input type=“text” name=“sub_title” class=“input” value="<%=list.sub_title%>"/></li> <li> <span>Seo关键词: </span><input type=“text” name=“keywords” class=“input” value="<%=list.keywords%>"/></li> <li> <span>Seo描述:</span> <textarea name=“description” id=“description” cols=“84” rows=“4”><%=list.description%></textarea></li> <li> <span>排 序:</span> <input type=“text” name=“sort” value="<%=list.sort%>"/></li> <li> <span>状 态:</span> <input type=“radio” name=“status” <%if(list.status==1){%> checked <%}%> value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” <%if(list.status==0){%> checked <%}%> name=“status” value=“0” id=“b”/><label for=“b”>隐藏</label> </li> <li> <br/> <button type=“submit” class=“btn btn-primary”>提交</button> </li> </ul> </form> </div> </div> </div></body></html> ...

December 20, 2018 · 4 min · jiezi

egg(75)--egg之前端jqPaginator分页插件

jqPaginator.jscontrollerapp/controller/admin/goods.jspage 当前页pageSize 每页多少条数据totalPages 总页数totalPages:Math.ceil(totalNum/pageSize) 向上取整,如 3.2 => 4 async index() { var page=this.ctx.request.query.page || 1; var pageSize=2; //获取当前数据表的总数量 var totalNum=await this.ctx.model.Goods.find({}).count(); var goodsResult=await this.ctx.model.Goods.find({}).skip((page-1)*pageSize).limit(pageSize); await this.ctx.render(‘admin/goods/index’,{ list:goodsResult, totalPages:Math.ceil(totalNum/pageSize), page:page }); } viewapp/view/admin/goods/index.html<script src="/public/admin/js/jqPaginator.js"></script><div id=“page” class=“pagination”></div> <script> $(’#page’).jqPaginator({ totalPages: <%=totalPages%>, visiblePages: 8, currentPage: <%=page%>, onPageChange: function (num, type) { console.log(‘当前第’ + num + ‘页’,type); if(type==‘change’){ location.href="/admin/goods?page="+num; } } }); </script>效果

December 20, 2018 · 1 min · jiezi

egg(76)--egg之商品导航curd

效果modelapp/model/nav.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d=new Date(); const NavSchema = new Schema({ title: { type: String }, link: { type: String }, position: { type:Number, default:2 //1最顶部 2中间 3底部 }, is_opennew:{ type:Number, default:1 //1、本窗口 2、新窗口 }, sort:{ type:Number, default:100 }, relation:{ // 1,2,3 type:String, default:’’ }, status: { type: Number,default:1 }, add_time: { type:Number, default: d.getTime() } }); return mongoose.model(‘Nav’, NavSchema,’nav’);}router.js router.get(’/admin/nav’, controller.admin.nav.index); router.get(’/admin/nav/add’, controller.admin.nav.add); router.get(’/admin/nav/edit’, controller.admin.nav.edit); router.post(’/admin/nav/doEdit’, controller.admin.nav.doEdit); router.post(’/admin/nav/doAdd’, controller.admin.nav.doAdd);controllerapp/controller/admin/nav.js’use strict’;var BaseController =require(’./base.js’);class NavController extends BaseController { async index() { var page=this.ctx.request.query.page || 1; var pageSize=5; //获取当前数据表的总数量 var totalNum=await this.ctx.model.Nav.find({}).count(); //分页查询 var result=await this.ctx.model.Nav.find({}).skip((page-1)*pageSize).limit(pageSize); await this.ctx.render(‘admin/nav/index’,{ list:result, totalPages:Math.ceil(totalNum/pageSize), page:page }); } async add() { await this.ctx.render(‘admin/nav/add’); } async doAdd() { // console.log(); var nav=new this.ctx.model.Nav(this.ctx.request.body) await nav.save(); //注意 await this.success(’/admin/nav’,‘增加导航成功’); } async edit() { var id=this.ctx.query.id; var result=await this.ctx.model.Nav.find({"_id":id}); await this.ctx.render(‘admin/nav/edit’,{ list:result[0] }); } async doEdit() { var _id=this.ctx.request.body._id; await this.ctx.model.Nav.updateOne({"_id":_id},this.ctx.request.body) await this.success(’/admin/nav’,‘编辑导航成功’); } }module.exports = NavController;view查找app/view/admin/nav/index.html<%- include ../public/page_header.html %> <!– 引入分页插件 –> <script src="/public/admin/js/jqPaginator.js"></script> <div class=“panel panel-default”> <div class=“panel-heading clear”> <span>网站导航</span> <a href="/admin/nav/add" class=“btn btn-primary fr”>增加导航</a> </div> <div class=“panel-body”> <!– 列表展示 –> <div class=“table-responsive”> <table class=“table table-bordered”> <thead> <tr class=“th”> <th>分类名称</th> <th>关联商品</th> <th>跳转地址</th> <th>位置</th> <th class=“text-center”>排序</th> <th class=“text-center”>状态</th> <th class=“text-center”>操作</th> </tr> </thead> <tbody> <%for(var i=0;i<list.length;i++){%> <tr> <td><%=list[i].title%></td> <td><%=list[i].relation%></td> <td><%=list[i].link%></td> <td> <%if(list[i].position==1){%> 顶部 <%}else if(list[i].position==2){%> 中间 <%}else{%> 底部 <%}%> </td> <td class=“text-center”><span onclick=“app.editNum(this,‘Nav’,‘sort’,’<%=list[i]._id%>’)"><%=list[i].sort%></span></td> <td class=“text-center”> <%if(list[i].status==1){%> <img src="/public/admin/images/yes.gif” onclick=“app.changeStatus(this,‘Nav’,‘status’,’<%=list[i]._id%>’)” /> <%}else{%> <img src="/public/admin/images/no.gif" onclick=“app.changeStatus(this,‘Nav’,‘status’,’<%=list[i]._id%>’)” /> <%}%> </td> <td class=“text-center”> <a href="/admin/nav/edit?id=<%=list[i]._id%>">修改</a> <a class=“delete” href="/admin/delete?model=Nav&id=<%=list[i]._id%>">删除</a></td> </tr> <%}%> </tbody> </table> <div id=“page” class=“pagination fr”></div> </div> </div> </div> <script> $(’#page’).jqPaginator({ totalPages: <%=totalPages%>, visiblePages: 8, currentPage: <%=page%>, onPageChange: function (num, type) { console.log(‘当前第’ + num + ‘页’,type); if(type==‘change’){ location.href="/admin/nav?page="+num; } } }); </script></body></html>增加app/view/admin/nav/add.html<%- include ../public/page_header.html %> <div class=“panel panel-default”> <div class=“panel-heading”> 增加导航 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/nav/doAdd" method=“post” > <ul class=“form_input”> <input type=“hidden” name="_csrf" value="<%=csrf%>"> <li> <span>导航名称:</span> <input type=“text” name=“title”/></li> <li> <span>导航位置: </span> <select name=“positon” id=“positon”> <option value=“1”>顶部</option> <option value=“2” selected>中间</option> <option value=“3”>底部</option> </select> </li> <li> <span>关联商品:</span> <input type=“text” name=“relation”/></li> <li> <span>导航连接地址:</span> <input type=“text” name=“link”/></li> <li> <span>新窗口打开: </span> <select name=“is_opennew” id=“is_opennew”> <option value=“1”>否</option> <option value=“2”>是</option> </select> <li> <span>排序:</span> <input type=“text” name=“sort” value=“10”/></li> <li> <span>状态: </span> <input type=“radio” name=“status” checked value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” name=“status” value=“0” id=“b”/><label for=“b”>隐藏</label> </li> <li> <br/> <button type=“submit” class=“btn btn-primary”>提交</button> </li> </ul> </form> </div> </div> </div></body></html>编辑app/view/admin/nav/edit.html<%- include ../public/page_header.html %> <div class=“panel panel-default”> <div class=“panel-heading”> 修改导航 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/nav/doEdit" method=“post” > <ul class=“form_input”> <input type=“hidden” name="_csrf" value="<%=csrf%>"> <input type=“hidden” name="_id" value="<%=list._id%>"/> <li> <span>导航名称:</span> <input type=“text” name=“title” value="<%=list.title%>"/></li> <li> <span>导航位置: </span> <select name=“position” id=“position”> <option value=“1” <%if(list.position==1){%>selected<%}%>>顶部</option> <option value=“2” <%if(list.position==2){%>selected<%}%>>中间</option> <option value=“3” <%if(list.position==3){%>selected<%}%>>底部</option> </select> </li> <li> <span>关联商品:</span> <input type=“text” name=“relation” value="<%=list.relation%>"/></li> <li> <span>导航连接地址:</span> <input type=“text” name=“link” value="<%=list.link%>"/></li> <li> <span>新窗口打开: </span> <select name=“is_opennew” id=“is_opennew”> <option value=“2” <%if(list.is_opennew==1){%>selected<%}%>>否</option> <option value=“1” <%if(list.is_opennew==2){%>selected<%}%>>是</option> </select> <li> <span>排序:</span> <input type=“text” name=“sort” value=“10” value="<%=list.sort%>"/></li> <li> <span>状 态:</span> <input type=“radio” name=“status” <%if(list.status==1){%> checked <%}%> value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” <%if(list.status==0){%> checked <%}%> name=“status” value=“0” id=“b”/><label for=“b”>隐藏</label> </li> <li> <br/> <button type=“submit” class=“btn btn-primary”>提交</button> </li> </ul> </form> </div> </div> </div></body></html> ...

December 20, 2018 · 3 min · jiezi

egg(72,73)--egg之商品curd的编辑

controllerapp/controller/admin/goods.jsedit async edit() { //获取修改数据的id var id=this.ctx.request.query.id; //获取所有的颜色值 var colorResult=await this.ctx.model.GoodsColor.find({}); //获取所有的商品类型 var goodsType=await this.ctx.model.GoodsType.find({}); //获取商品分类 var goodsCate=await this.ctx.model.GoodsCate.aggregate([ { $lookup:{ from:‘goods_cate’, localField:’_id’, foreignField:‘pid’, as:‘items’ } }, { $match:{ “pid”:‘0’ } } ]) //获取修改的商品 var goodsResult=await this.ctx.model.Goods.find({’_id’:id}); //获取当前商品的颜色 // 5bbb68dcfe498e2346af9e4a,5bbb68effe498e2346af9e4b,5bc067d92e5f889dc864aa96 var colorArrTemp=goodsResult[0].goods_color.split(’,’); // console.log(colorArrTemp); var goodsColorArr=[]; colorArrTemp.forEach((value)=>{ goodsColorArr.push({"_id":value}) }) var goodsColorReulst=await this.ctx.model.GoodsColor.find({ $or:goodsColorArr }) // console.log(colorReulst); //获取规格信息 var goodsAttsResult=await this.ctx.model.GoodsAttr.find({“goods_id”:goodsResult[0]._id}); var goodsAttsStr=’’; goodsAttsResult.forEach(async (val)=>{ if(val.attribute_type==1){ goodsAttsStr+=&lt;li&gt;&lt;span&gt;${val.attribute_title}: &lt;/span&gt;&lt;input type="hidden" name="attr_id_list" value="${val.attribute_id}" /&gt; &lt;input type="text" name="attr_value_list" value="${val.attribute_value}" /&gt;&lt;/li&gt;; }else if(val.attribute_type==2){ goodsAttsStr+=&lt;li&gt;&lt;span&gt;${val.attribute_title}: &lt;/span&gt;&lt;input type="hidden" name="attr_id_list" value="${val.attribute_id}" /&gt; &lt;textarea cols="50" rows="3" name="attr_value_list"&gt;${val.attribute_value}&lt;/textarea&gt;&lt;/li&gt;; }else{ //获取 attr_value 获取可选值列表 var oneGoodsTypeAttributeResult=await this.ctx.model.GoodsTypeAttribute.find({ _id:val.attribute_id }) var arr=oneGoodsTypeAttributeResult[0].attr_value.split(’\n’); goodsAttsStr+=&lt;li&gt;&lt;span&gt;${val.attribute_title}: &lt;/span&gt;&lt;input type="hidden" name="attr_id_list" value="${val.attribute_id}" /&gt;; goodsAttsStr+=&lt;select name="attr_value_list"&gt;; for(var j=0;j<arr.length;j++){ if(arr[j]==val.attribute_value){ goodsAttsStr+=&lt;option value="${arr[j]}" selected &gt;${arr[j]}&lt;/option&gt;; }else{ goodsAttsStr+=&lt;option value="${arr[j]}" &gt;${arr[j]}&lt;/option&gt;; } } goodsAttsStr+=&lt;/select&gt;; goodsAttsStr+=&lt;/li&gt;; } }) //商品的图库信息 var goodsImageResult=await this.ctx.model.GoodsImage.find({“goods_id”:goodsResult[0]._id}); console.log(goodsImageResult); await this.ctx.render(‘admin/goods/edit’,{ colorResult:colorResult, goodsType:goodsType, goodsCate:goodsCate, goods:goodsResult[0], goodsAtts:goodsAttsStr, goodsImage:goodsImageResult, goodsColor:goodsColorReulst }); } doEdit async doEdit() { let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) } var formFields=Object.assign(files,parts.field); //修改商品的id var goods_id=parts.field.id; //修改商品信息 await this.ctx.model.Goods.updateOne({"_id":goods_id},formFields); //修改图库信息 (增加) var goods_image_list=formFields.goods_image_list; if(goods_id && goods_image_list){ if(typeof(goods_image_list)==‘string’){ goods_image_list=new Array(goods_image_list); } for(var i=0;i<goods_image_list.length;i++){ let goodsImageRes =new this.ctx.model.GoodsImage({ goods_id:goods_id, img_url:goods_image_list[i] }); await goodsImageRes.save(); } } //修改商品类型数据 1、删除以前的类型数据 2、重新增加新的商品类型数据 //1、删除以前的类型数据 await this.ctx.model.GoodsAttr.deleteOne({“goods_id”:goods_id}); //2、重新增加新的商品类型数据 var attr_value_list=formFields.attr_value_list; var attr_id_list=formFields.attr_id_list; if(goods_id && attr_id_list && attr_value_list){ //解决只有一个属性的时候存在的bug if(typeof(attr_id_list)==‘string’){ attr_id_list=new Array(attr_id_list); attr_value_list=new Array(attr_value_list); } for(var i=0;i<attr_value_list.length;i++){ //查询goods_type_attribute if(attr_value_list[i]){ var goodsTypeAttributeResutl=await this.ctx.model.GoodsTypeAttribute.find({"_id":attr_id_list[i]}) let goodsAttrRes =new this.ctx.model.GoodsAttr({ goods_id:goods_id, cate_id:formFields.cate_id, attribute_id:attr_id_list[i], attribute_type:goodsTypeAttributeResutl[0].attr_type, attribute_title:goodsTypeAttributeResutl[0].title, attribute_value:attr_value_list[i] }); await goodsAttrRes.save(); } } } await this.success(’/admin/goods’,‘修改商品数据成功’); } viewapp/view/admin/goods/edit.html<%- include ../public/page_header.html %> <!– 富文本编辑器 –> <link href="/public/admin/wysiwyg-editor/css/font-awesome.min.css" rel=“stylesheet” type=“text/css” /> <!– Include Editor style. –> <link href="/public/admin/wysiwyg-editor/css/froala_editor.pkgd.min.css" rel=“stylesheet” type=“text/css” /> <link href="/public/admin/wysiwyg-editor/css/froala_style.min.css" rel=“stylesheet” type=“text/css” /> <!– 引入jquery –> <!– Include Editor JS files. –> <script type=“text/javascript” src="/public/admin/wysiwyg-editor/js/froala_editor.pkgd.min.js"></script> <script type=“text/javascript” src="/public/admin/wysiwyg-editor/js/zh_cn.js"></script> <!– 批量上传图片插件 –> <link rel=“stylesheet” type=“text/css” href="/public/admin/webuploader/css/webuploader.css"> <link rel=“stylesheet” type=“text/css” href="/public/admin/webuploader/css/diyUpload.css"> <script type=“text/javascript” src="/public/admin/webuploader/js/webuploader.html5only.min.js"></script> <script type=“text/javascript” src="/public/admin/webuploader/js/diyUpload.js"></script> <div class=“panel panel-default”> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/goods/doEdit?_csrf=<%=csrf%>" method=“post” class=“goods_content” enctype=“multipart/form-data”> <!– Nav tabs –> <ul class=“nav nav-tabs” role=“tablist”> <li role=“presentation” class=“active”><a href="#general" role=“tab” data-toggle=“tab”>通用信息</a></li> <li role=“presentation”><a href="#detail" role=“tab” data-toggle=“tab”>详细描述</a></li> <li role=“presentation”><a href="#mix" role=“tab” data-toggle=“tab”>其他信息</a></li> <li role=“presentation”><a href="#attribute" role=“tab” data-toggle=“tab”>规格与包装</a></li> <li role=“presentation”><a href="#photo" role=“tab” data-toggle=“tab”>商品相册</a></li> </ul> <!– Tab panes –> <div class=“tab-content”> <div role=“tabpanel” class=“tab-pane active” id=“general”> <input type=“hidden” name=“id” value="<%=goods._id%>" /> <ul class=“form_input”> <li> <span> 商品标题:</span> <input type=“text” name=“title” class=“input” value="<%=goods.title%>" /></li> <li> <span> 附属标题:</span> <input type=“text” name=“sub_title” class=“input” value="<%=goods.sub_title%>"/></li> <li> <span>商品版本:</span> <input type=“text” name=“goods_version” class=“input"value="<%=goods.goods_version%>” /></li> <li> <span>所属分类:</span> <select name=“cate_id” id=“cate_id”> <%for(var i=0;i<goodsCate.length;i++){%> <option value="<%=goodsCate[i]._id%>" <%if(goods.cate_id.toString()==goodsCate[i]._id.toString()){%>selected<%}%> ><%=goodsCate[i].title%></option> <%for(var j=0;j<goodsCate[i].items.length;j++){%> <option value="<%=goodsCate[i].items[j]._id%>" <%if(goods.cate_id.toString()==goodsCate[i].items[j]._id.toString()){%>selected<%}%>>—-<%=goodsCate[i].items[j].title%></option> <%}%> <%}%> </select> <input type=“hidden” name=“cname” id=“cname” /> </li> <li> <span> 商品图片:</span> <input type=“file” name=“goods_img”/> <span> </span> <img class=“pic” src="<%=goods.goods_img%>" /> </li> <li> <span>商品价格:</span> <input type=“text” name=“shop_price” value="<%=goods.shop_price%>"/></li> <li> <span>商品原价:</span> <input type=“text” name=“market_price” value="<%=goods.market_price%>"/></li> <li> <span>商品状态:</span> <input type=“radio” name=“status” <%if(goods.status==1){%> checked <%}%> value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” name=“status” <%if(goods.status==0){%> checked <%}%> value=“0” id=“b”/><label for=“b”>隐藏</label> </li> <li> <span>加入推荐:</span> <input type=“checkbox” value=“1” <%if(goods.is_best==1){%> checked <%}%> name=“is_best”/> 精品 <input type=“checkbox” value=“1” name=“is_hot” <%if(goods.is_hot==1){%> checked <%}%> /> 热销 <input type=“checkbox” value=“1” name=“is_new” <%if(goods.is_new==1){%> checked <%}%> /> 新品 </li> </ul> </div> <div role=“tabpanel” class=“tab-pane” id=“detail”> <textarea name=“goods_content” id=“content” cols=“100” rows=“8”><%=goods.goods_content%></textarea> </div> <div role=“tabpanel” class=“tab-pane” id=“mix”> <ul class=“form_input”> <li> <span>商品颜色:</span> <%for(var i=0;i<colorResult.length;i++){%> <%if(goods.goods_color.indexOf(colorResult[i]._id.toString())!=-1){%> <input type=“checkbox” checked name=“goods_color” value="<%=colorResult[i].id%>" id=“color<%=colorResult[i].id%>” /> <label for=“color<%=colorResult[i]._id%>"><%=colorResult[i].color_name%></label> <%}else{%> <input type=“checkbox” name=“goods_color” value="<%=colorResult[i].id%>” id=“color<%=colorResult[i].id%>” /> <label for=“color<%=colorResult[i]._id%>"><%=colorResult[i].color_name%></label> <%}%> <%}%> </li> <li> <span>关联商品:</span> <input type=“text” name=“relation_goods” class=“relation_goods” value="<%=goods.relation_goods%>”/> <i>填写关联商品的id 多个以逗号隔开 格式:23,24,39</i> </li> <li> <span>关联赠品:</span> <input type=“text” name=“goods_gift” class=“goods_gift” value="<%=goods.goods_gift%>"/> <i>可为空 格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量</i> </li> <li> <span>关联配件:</span> <input type=“text” name=“goods_fitting” class=“goods_fitting” value="<%=goods.goods_fitting%>"/> <i>可为空 格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量</i> </li> <li> <span>更多属性:</span> <input type=“text” name=“goods_attr” class=“goods_attr” value="<%=goods.goods_attr%>"/> <i> 格式: 颜色:红色,白色,黄色 | 尺寸:41,42,43</i> </li> <li> <span>Seo关键词:</span> <input type=“text” name=“goods_keywords” class=“goods_keywords” value="<%=goods.goods_keywords%>"/> </li> <li> <span>Seo描述:</span> <textarea name=“goods_desc” id=“goods_desc” cols=“100” rows=“2”><%=goods.goods_desc%></textarea> </li> </ul> </div> <div role=“tabpanel” class=“tab-pane” id=“attribute”> <ul class=“form_input”> <li> <span>商品类型: </span> <select name=“goods_type_id” id=“goods_type_id”> <option value=“0”>–请选择商品类型–</option> <%for(var i=0;i<goodsType.length;i++){%> <option value="<%=goodsType[i]._id%>" <%if(goods.goods_type_id.toString()==goodsType[i]._id.toString()){%>selected<%}%> ><%=goodsType[i].title%></option> <%}%> </select> </li> </ul> <ul class=“form_input” id=“goods_type_attribute”> <%-goodsAtts%> </ul> </div> <div role=“tabpanel” class=“tab-pane” id=“photo”> <div id=“goods_image”> <ul id=“goods_image_list” class=“goods_image_list clear”> <%for(var i=0;i<goodsImage.length;i++){%> <li> <img class=“pic” src="<%=goodsImage[i].img_url%>" /> <div class=“color_list”> <select class=“relation_goods_color” goods_image_id="<%=goodsImage[i]._id%>"> <option value=“0”>关联颜色</option> <%for(var j=0;j<goodsColor.length;j++){%> <option value="<%=goodsColor[j]._id%>" <%if(goodsImage[i].color_id.toString()==goodsColor[j]._id){%>selected<%}%> ><%=goodsColor[j].color_name%></option> <%}%> </select> </div> <div class=“goods_image_delete” goods_image_id="<%=goodsImage[i]._id%>"></div> </li> <%}%> </ul> </div> <div id=“photoLib” class=“photoLib”></div> <div id=“photoList”> </div> </div> </div> <button type=“submit” class=“btn btn-success goods_content_btn”>提交</button> </form> </div> </div> </div> <!– 缓存当前的属性信息 –> <div id=“goods_attr_value” style=“display:none”> <%-goodsAtts%> </div><script> $(function(){ //关联商品类型 $(’#goods_type_id’).change(function(){ // alert($(this).val()); var cate_id=$(this).val(); if(cate_id==’<%=goods.goods_type_id.toString()%>’){ $(’#goods_type_attribute’).html($(’#goods_attr_value’).html()); }else{ var data=’’; $.get(’/admin/goods/goodsTypeAttribute?cate_id=’+cate_id,function(response){ data=response.result; var str=""; for(var i=0;i<data.length;i++){ if(data[i].attr_type==1){ str+=’<li><span>’+data[i].title+’: </span><input type=“hidden” name=“attr_id_list” value="’+data[i]._id+’" /> <input type=“text” name=“attr_value_list” /></li>’ }else if(data[i].attr_type==2){ str+=’<li><span>’+data[i].title+’: </span> <input type=“hidden” name=“attr_id_list” value="’+data[i]._id+’"> <textarea cols=“50” rows=“3” name=“attr_value_list”></textarea></li>’ }else{ var arr=data[i].attr_value.split(’\n’); str+=’<li><span>’+data[i].title+’: </span><input type=“hidden” name=“attr_id_list” value="’+data[i]._id+’">’; str+=’<select name=“attr_value_list”>’; for(var j=0;j<arr.length;j++){ str+=’<option value="’+arr[j]+’">’+arr[j]+’</option>’; } str+=’</select>’; str+=’</li>’; } } $(’#goods_type_attribute’).html(str); }) } }) }) //富文本编辑器 $(function() { $(’#content’).froalaEditor({ height: 300, //给编辑器设置默认的高度 language: ‘zh_cn’, imageUploadURL: ‘/admin/goods/goodsUploadImage’, //根据不同的分辨率加载不同的配置 toolbarButtons: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’], toolbarButtonsMD: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’], toolbarButtonsSM: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’] }); }); //批量上传图片 $(function(){ var photoStr=’’; $(’#photoLib’).diyUpload({ url:’/admin/goods/goodsUploadPhoto’, success:function( response ) { // console.info( data ); photoStr=’<input type=“hidden” name=“goods_image_list” value=’+response.link+’ />’; $(’#photoList’).append(photoStr); }, error:function( err ) { console.info( err ); } }); }) $(function(){ //改变颜色 $(’.relation_goods_color’).change(function(){ var color_id=$(this).val(); var goods_image_id=$(this).attr(‘goods_image_id’); console.log(color_id,goods_image_id); $.post(’/admin/goods/changeGoodsImageColor?_csrf=<%=csrf%>’,{color_id:color_id,goods_image_id:goods_image_id},function(response){ console.log(response); }) }) //删除图片 $(’.goods_image_delete’).click(function(){ var _that=this; var flag=confirm(‘您确定要删除吗?’); if(flag){ var goods_image_id=$(this).attr(‘goods_image_id’); $.post(’/admin/goods/goodsImageRemove?_csrf=<%=csrf%>’,{goods_image_id:goods_image_id},function(response){ console.log(response); if(response.success){ $(_that).parent().remove(); } }) } }) }) </script></body></html>效果 ...

December 19, 2018 · 5 min · jiezi

egg(74)--egg之商品的图库关联颜色表,以及图库的curd

modelapp/model/goods_image.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d=new Date(); const GoodsImageSchema = new Schema({ goods_id: {type:Schema.Types.ObjectId }, img_url: { type: String }, color_id:{ type:Schema.Types.Mixed, //混合类型 default: ’’ }, status: { type: Number,default:1 }, add_time: { type:Number, default: d.getTime() } }); return mongoose.model(‘GoodsImage’, GoodsImageSchema,‘goods_image’);}app/model/goods_color.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const GoodsColorSchema = new Schema({ color_name:{ type:String }, color_value: { type: String }, status: { type: Number,default:1 }, }); return mongoose.model(‘GoodsColor’, GoodsColorSchema,‘goods_color’); }router.js router.post(’/admin/goods/changeGoodsImageColor’, controller.admin.goods.changeGoodsImageColor); router.post(’/admin/goods/goodsImageRemove’, controller.admin.goods.goodsImageRemove);controllerapp/controller/admin/goods.js async changeGoodsImageColor() { var color_id=this.ctx.request.body.color_id; var goods_image_id=this.ctx.request.body.goods_image_id; console.log(this.ctx.request.body); if(color_id){ color_id=this.app.mongoose.Types.ObjectId(color_id); } var result= await this.ctx.model.GoodsImage.updateOne({"_id":goods_image_id},{ color_id:color_id }) if(result){ this.ctx.body={‘success’: true,‘message’:‘更新数据成功’}; }else{ this.ctx.body={‘success’: false,‘message’:‘更新数据失败’}; } }viewapp/view/admin/goods/edit.html <div role=“tabpanel” class=“tab-pane” id=“photo”> <div id=“goods_image”> <ul id=“goods_image_list” class=“goods_image_list clear”> <%for(var i=0;i<goodsImage.length;i++){%> <li> <img class=“pic” src="<%=goodsImage[i].img_url%>" /> <div class=“color_list”> <select class=“relation_goods_color” goods_image_id="<%=goodsImage[i]._id%>"> <option value=“0”>关联颜色</option> <%for(var j=0;j<goodsColor.length;j++){%> <option value="<%=goodsColor[j]._id%>" <%if(goodsImage[i].color_id.toString()==goodsColor[j]._id){%>selected<%}%> ><%=goodsColor[j].color_name%></option> <%}%> </select> </div> <div class=“goods_image_delete” goods_image_id="<%=goodsImage[i]._id%>"></div> </li> <%}%> </ul> </div> <div id=“photoLib” class=“photoLib”></div> <div id=“photoList”> </div> </div> $(’.relation_goods_color’).change(function(){ var color_id=$(this).val(); var goods_image_id=$(this).attr(‘goods_image_id’); console.log(color_id,goods_image_id); $.post(’/admin/goods/changeGoodsImageColor?_csrf=<%=csrf%>’,{color_id:color_id,goods_image_id:goods_image_id},function(response){ console.log(response); }) })效果 ...

December 19, 2018 · 1 min · jiezi

egg(70)--egg之商品curd的增加

modelapp/model/goods_attr.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d=new Date(); const GoodsAttrSchema = new Schema({ goods_id: {type:Schema.Types.ObjectId }, cate_id:{ type:Schema.Types.ObjectId }, attribute_id:{ type:Schema.Types.ObjectId }, attribute_type:{ type: String }, attribute_title:{ type: String }, attribute_value:{ type: String }, status: { type: Number,default:1 }, add_time: { type:Number, default: d.getTime() } }); return mongoose.model(‘GoodsAttr’, GoodsAttrSchema,‘goods_attr’);}app/model/goods_image.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d=new Date(); const GoodsImageSchema = new Schema({ goods_id: {type:Schema.Types.ObjectId }, img_url: { type: String }, color_id:{ type:Schema.Types.Mixed, //混合类型 default: ’’ }, status: { type: Number,default:1 }, add_time: { type:Number, default: d.getTime() } }); return mongoose.model(‘GoodsImage’, GoodsImageSchema,‘goods_image’);}viewapp/view/admin/goods/add.html<%- include ../public/page_header.html %> <!– 富文本编辑器 –> <link href="/public/admin/wysiwyg-editor/css/font-awesome.min.css" rel=“stylesheet” type=“text/css” /> <!– Include Editor style. –> <link href="/public/admin/wysiwyg-editor/css/froala_editor.pkgd.min.css" rel=“stylesheet” type=“text/css” /> <link href="/public/admin/wysiwyg-editor/css/froala_style.min.css" rel=“stylesheet” type=“text/css” /> <!– 引入jquery –> <!– Include Editor JS files. –> <script type=“text/javascript” src="/public/admin/wysiwyg-editor/js/froala_editor.pkgd.min.js"></script> <script type=“text/javascript” src="/public/admin/wysiwyg-editor/js/zh_cn.js"></script> <!– 批量上传图片插件 –> <link rel=“stylesheet” type=“text/css” href="/public/admin/webuploader/css/webuploader.css"> <link rel=“stylesheet” type=“text/css” href="/public/admin/webuploader/css/diyUpload.css"> <script type=“text/javascript” src="/public/admin/webuploader/js/webuploader.html5only.min.js"></script> <script type=“text/javascript” src="/public/admin/webuploader/js/diyUpload.js"></script> <div class=“panel panel-default”> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/goods/doAdd?_csrf=<%=csrf%>" method=“post” class=“goods_content” enctype=“multipart/form-data”> <!– Nav tabs –> <ul class=“nav nav-tabs” role=“tablist”> <li role=“presentation” class=“active”><a href="#general" role=“tab” data-toggle=“tab”>通用信息</a></li> <li role=“presentation”><a href="#detail" role=“tab” data-toggle=“tab”>详细描述</a></li> <li role=“presentation”><a href="#mix" role=“tab” data-toggle=“tab”>商品属性</a></li> <li role=“presentation”><a href="#attribute" role=“tab” data-toggle=“tab”>规格与包装</a></li> <li role=“presentation”><a href="#photo" role=“tab” data-toggle=“tab”>商品相册</a></li> </ul> <!– Tab panes –> <div class=“tab-content”> <div role=“tabpanel” class=“tab-pane active” id=“general”> <ul class=“form_input”> <li> <span> 商品标题:</span> <input type=“text” name=“title” class=“input”/></li> <li> <span> 附属标题:</span> <input type=“text” name=“sub_title” class=“input”/></li> <li> <span>商品版本:</span> <input type=“text” name=“goods_version” class=“input”/></li> <li> <span>所属分类:</span> <select name=“cid” id=“cid”> <%for(var i=0;i<goodsCate.length;i++){%> <option value="<%=goodsCate[i]._id%>"><%=goodsCate[i].title%></option> <%for(var j=0;j<goodsCate[i].items.length;j++){%> <option value="<%=goodsCate[i].items[j]._id%>">—-<%=goodsCate[i].items[j].title%></option> <%}%> <%}%> </select> <input type=“hidden” name=“cname” id=“cname” /> </li> <li> <span> 商品图片:</span> <input type=“file” name=“pic”/></li> <li> <span>商品价格:</span> <input type=“text” name=“price”/></li> <li> <span>商品原价:</span> <input type=“text” name=“old_price”/></li> <li> <span>商品状态:</span> <input type=“radio” value=“1” name=“status” checked/> 显示 <input type=“radio” value=“0” name=“status”/> 隐藏 </li> <li> <span>加入推荐:</span> <input type=“checkbox” value=“1” name=“is_best”/> 精品 <input type=“checkbox” value=“1” name=“is_hot”/> 热销 <input type=“checkbox” value=“1” name=“is_new”/> 新品 </li> </ul> </div> <div role=“tabpanel” class=“tab-pane” id=“detail”> <textarea name=“goods_content” id=“content” cols=“100” rows=“8”></textarea> </div> <div role=“tabpanel” class=“tab-pane” id=“mix”> <ul class=“form_input”> <li> <span>商品颜色:</span> <%for(var i=0;i<colorResult.length;i++){%> <input type=“checkbox” name=“goods_color” value="<%=colorResult[i].id%>" id=“color<%=colorResult[i].id%>” /> <label for=“color<%=colorResult[i]._id%>"><%=colorResult[i].color_name%></label> <%}%> </li> <li> <span>关联商品:</span> <input type=“text” name=“relation_goods” class=“relation_goods”/> <i>填写关联商品的id 多个以逗号隔开 格式:23,24,39</i> </li> <li> <span>关联赠品:</span> <input type=“text” name=“goods_gift” class=“goods_gift”/> <i>可为空 格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量</i> </li> <li> <span>关联配件:</span> <input type=“text” name=“goods_fitting” class=“goods_fitting”/> <i>可为空 格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量</i> </li> <li> <span>更多属性:</span> <input type=“text” name=“goods_attr” class=“goods_attr”/> <i> 格式: 颜色:红色,白色,黄色 | 尺寸:41,42,43</i> </li> </ul> </div> <div role=“tabpanel” class=“tab-pane” id=“attribute”> <ul class=“form_input”> <li> <span>商品类型: </span> <select name=“goods_type_id” id=“goods_type_id”> <option value=“0”>–请选择商品类型–</option> <%for(var i=0;i<goodsType.length;i++){%> <option value="<%=goodsType[i]._id%>"><%=goodsType[i].title%></option> <%}%> </select> </li> </ul> <ul class=“form_input” id=“goods_type_attribute”> </ul> </div> <div role=“tabpanel” class=“tab-pane” id=“photo”> <div id=“photoLib” class=“photoLib”></div> <div id=“photoList”> </div> </div> </div> <button type=“submit” class=“btn btn-success goods_content_btn”>提交</button> </form> </div> </div> </div><script> $(function(){ //关联商品类型 $(’#goods_type_id’).change(function(){ // alert($(this).val()); var cate_id=$(this).val(); var data=’’; $.get(’/admin/goods/goodsTypeAttribute?cate_id=’+cate_id,function(response){ console.log(response.result); data=response.result; console.log(data.length); var str=”"; for(var i=0;i<data.length;i++){ if(data[i].attr_type==1){ str+=’<li><span>’+data[i].title+’: </span><input type=“hidden” name=“attr_id_list” value="’+data[i]._id+’" /> <input type=“text” name=“attr_value_list” /></li>’ }else if(data[i].attr_type==2){ str+=’<li><span>’+data[i].title+’: </span> <input type=“hidden” name=“attr_id_list” value="’+data[i]._id+’"> <textarea cols=“50” rows=“3” name=“attr_value_list”></textarea></li>’ }else{ var arr=data[i].attr_value.split(’\n’); str+=’<li><span>’+data[i].title+’: </span><input type=“hidden” name=“attr_id_list” value="’+data[i]._id+’">’; str+=’<select name=“attr_value_list”>’; for(var j=0;j<arr.length;j++){ str+=’<option value="’+arr[j]+’">’+arr[j]+’</option>’; } str+=’</select>’; str+=’</li>’; } } $(’#goods_type_attribute’).html(str); }) }) }) //富文本编辑器 $(function() { $(’#content’).froalaEditor({ height: 300, //给编辑器设置默认的高度 language: ‘zh_cn’, imageUploadURL: ‘/admin/goods/goodsUploadImage’, //根据不同的分辨率加载不同的配置 toolbarButtons: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’], toolbarButtonsMD: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’], toolbarButtonsSM: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’] }); }); //批量上传图片 $(function(){ var photoStr=’’; $(’#photoLib’).diyUpload({ url:’/admin/goods/goodsUploadPhoto’, success:function( response ) { // console.info( data ); photoStr=’<input type=“hidden” name=“goods_image_list” value=’+response.link+’ />’; $(’#photoList’).append(photoStr); }, error:function( err ) { console.info( err ); } }); }) </script></body></html>controllerapp/controller/admin/goods.js async add() { //获取所有的颜色值 var colorResult = await this.ctx.model.GoodsColor.find({}); //获取所有的商品类型,对应包装与规格 var goodsType = await this.ctx.model.GoodsType.find({}); //获取商品分类 var goodsCate=await this.ctx.model.GoodsCate.aggregate([ { $lookup:{ from:‘goods_cate’, localField:’_id’, foreignField:‘pid’, as:‘items’ } }, { $match:{ “pid”:‘0’ } } ]) await this.ctx.render(‘admin/goods/add’,{ colorResult:colorResult, goodsType:goodsType, goodsCate:goodsCate }); } async doAdd() { let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) } var formFields=Object.assign(files,parts.field); console.log(formFields); //增加商品信息 let goodsRes =new this.ctx.model.Goods(formFields); var result=await goodsRes.save(); // console.log(result._id); //增加图库信息 if(result._id){ var goods_image_list=formFields.goods_image_list; for(var i=0;i<goods_image_list.length;i++){ let goodsImageRes =new this.ctx.model.GoodsImage({ goods_id:result._id, img_url:goods_image_list[i] }); await goodsImageRes.save(); } } //增加商品类型数据 if(result._id){ /* attr_id_list: [ ‘5bbac2f646f01a08f4a82e7c’, ‘5bbd7ea7e723b71e5815b7dd’, ‘5bbd7eb0e723b71e5815b7de’, ‘5bbd805ee723b71e5815b7df’ ], attr_value_list: [ ‘windows’, ‘8g’, ‘1t’, ‘1440*720\r\n’ ] } */ var attr_value_list=formFields.attr_value_list; var attr_id_list=formFields.attr_id_list; for(var i=0;i<attr_value_list.length;i++){ //查询goods_type_attribute if(attr_value_list[i]){ var goodsTypeAttributeResutl=await this.ctx.model.GoodsTypeAttribute.find({"_id":attr_id_list[i]}) let goodsAttrRes =new this.ctx.model.GoodsAttr({ goods_id:result._id, cate_id:formFields.cate_id, attribute_id:attr_id_list[i], attribute_type:goodsTypeAttributeResutl[0].attr_type, attribute_title:goodsTypeAttributeResutl[0].title, attribute_value:attr_value_list[i] }); await goodsAttrRes.save(); } } } await this.success(’/admin/goods’,‘增加商品数据成功’); } 表关联 ...

December 18, 2018 · 4 min · jiezi

egg(70)--egg之前端图片批量上传插件webuploader

引入webuploader <link rel=“stylesheet” type=“text/css” href="/public/admin/webuploader/css/webuploader.css"> <link rel=“stylesheet” type=“text/css” href="/public/admin/webuploader/css/diyUpload.css"> <script type=“text/javascript” src="/public/admin/webuploader/js/webuploader.html5only.min.js"></script> <script type=“text/javascript” src="/public/admin/webuploader/js/diyUpload.js"></script>前端app/view/admin/goods/add.html <div role=“tabpanel” class=“tab-pane” id=“photo”> <div id=“photoLib” class=“photoLib”></div> <div id=“photoList”> </div> </div> $(function(){ var photoStr = “”; $("#photoLib").diyUpload({ url:’/admin/goods/goodsUploadPhoto’, success:function(response){ photoStr = ‘<input type=“hidden” name=“goods_image_list[]” value=’+response.link+’ />’; $(“photoList”).append(photoStr); }, error:function(err){ console.log(err) } }) })后台router.jsrouter.post(’/admin/goods/goodsUploadPhoto’, controller.admin.goods.goodsUploadPhoto);controllerapp/controller/admin/goods.js async goodsUploadPhoto() { //实现图片上传 let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) //生成缩略图 this.service.tools.jimpImg(target); } //图片的地址转化成 {link: ‘path/to/image.jpg’} this.ctx.body={link: files.file}; } 效果 ...

December 18, 2018 · 1 min · jiezi

egg(68,69)--egg引用wysiwyg-editor富文本

引入wysiwyg-editor字体,css,jquery,js,汉化包 <link href="/public/admin/wysiwyg-editor/css/font-awesome.min.css" rel=“stylesheet” type=“text/css” /> <!– Include Editor style. –> <link href="/public/admin/wysiwyg-editor/css/froala_editor.pkgd.min.css" rel=“stylesheet” type=“text/css” /> <link href="/public/admin/wysiwyg-editor/css/froala_style.min.css" rel=“stylesheet” type=“text/css” /> <!– 引入jquery –> <!– Include Editor JS files. –> <script type=“text/javascript” src="/public/admin/wysiwyg-editor/js/froala_editor.pkgd.min.js"></script> <script type=“text/javascript” src="/public/admin/wysiwyg-editor/js/zh_cn.js"></script>前端配置wysiwyg-editorheight 高度language 语言imageUploadURL 图片上传地址toolbarButtons ,toolbarButtonsMD ,toolbarButtonsSM 不同分辨率显示工具 <script> $(function() { $(’#content’).froalaEditor({ height: 300, //给编辑器设置默认的高度 language: ‘zh_cn’, imageUploadURL: ‘/admin/goods/goodsUploadImage’, //根据不同的分辨率加载不同的配置 toolbarButtons: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’], toolbarButtonsMD: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’], toolbarButtonsSM: [‘fullscreen’, ‘bold’, ‘italic’, ‘underline’, ‘strikeThrough’, ‘subscript’, ‘superscript’, ‘|’, ‘fontFamily’, ‘fontSize’, ‘color’, ‘inlineStyle’, ‘paragraphStyle’, ‘|’, ‘paragraphFormat’, ‘align’, ‘formatOL’, ‘formatUL’, ‘outdent’, ‘indent’, ‘quote’, ‘-’, ‘insertLink’, ‘insertImage’, ‘insertVideo’, ’embedly’, ‘insertFile’, ‘insertTable’, ‘|’, ’emoticons’, ‘specialCharacters’, ‘insertHR’, ‘selectAll’, ‘clearFormatting’, ‘|’, ‘print’, ‘spellChecker’, ‘help’, ‘html’, ‘|’, ‘undo’, ‘redo’] }); }); </script>后台配置图片上传router.jsrouter.post(’/admin/goods/goodsUploadImage’, controller.admin.goods.goodsUploadImage);controllerapp/controller/admin/goods.js async goodsUploadImage() { //实现图片上传 let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) } console.log(files); //图片的地址转化成 {link: ‘path/to/image.jpg’} this.ctx.body={link: files.file}; } 图片上传不经过csrf报错解决办法config/config.default.js config.security = { csrf: { // 判断是否需要 ignore 的方法,请求上下文 context 作为第一个参数 ignore: ctx => { if(ctx.request.url==’/admin/goods/goodsUploadImage’){ return true; } return false; } } }成功效果 ...

December 17, 2018 · 2 min · jiezi

egg(67)--商品curd之商品属性与颜色关联,商品与商品类型相关联

商品属性与颜色关联modelapp/model/goods_color.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const GoodsColorSchema = new Schema({ color_name:{ type:String }, color_value: { type: String }, status: { type: Number,default:1 }, }); return mongoose.model(‘GoodsColor’, GoodsColorSchema,‘goods_color’); }controllerapp/controller/admin/goods.js async add() { //获取所有的颜色值 var colorResult = await this.ctx.model.GoodsColor.find({}); await this.ctx.render(‘admin/goods/add’,{ colorResult:colorResult, }); } viewapp/view/admin/goods/add.html <li> <span>商品颜色:</span> <% for(var i =0;i<colorResult.length;i++){ %> <input type=“checkbox” name=“colors[]” value="<%=colorResult[i].id%>" id=“color<%=colorResult[i].id%>” /> <label for=“color<%=colorResult[i]._id%>"><%=colorResult[i].color_name%></label> <%}%> </li>效果在增加商品属性的时候,查找到所有的商品颜色商品与商品类型相关联router.js router.get(’/admin/goods’, controller.admin.goods.index); router.get(’/admin/goods/add’, controller.admin.goods.add); router.get(’/admin/goods/goodsTypeAttribute’, controller.admin.goods.goodsTypeAttribute); router.post(’/admin/goods/doAdd’, controller.admin.goods.doAdd);controllerapp/controller/admin/goods.js async add() { //获取所有的商品类型,对应包装与规格 var goodsType = await this.ctx.model.GoodsType.find({}); await this.ctx.render(‘admin/goods/add’,{ goodsType:goodsType }); } viewapp/view/admin/goods/add.html根据attr_type显示同的展示形式attr_type== 1 蓝色 inputattr_type== 2 红色 textareaattr_type== 3 绿色 selecthtml <div role=“tabpanel” class=“tab-pane” id=“attribute”> <ul class=“form_input”> <li> <span>商品类型: </span> <select name=“goods_type_id” id=“goods_type_id”> <option value=“0”>–请选择商品类型–</option> <%for(var i=0;i<goodsType.length;i++){%> <option value="<%=goodsType[i]._id%>"><%=goodsType[i].title%></option> <%}%> </select> </li> </ul> <ul class=“form_input” id=“goods_type_attribute”> </ul> </div>js$(function(){ $(’#goods_type_id’).change(function(){ // alert($(this).val()); var cate_id=$(this).val(); var data=’’; $.get(’/admin/goods/goodsTypeAttribute?cate_id=’+cate_id,function(response){ console.log(response.result); data=response.result; console.log(data.length); var str=”"; for(var i=0;i<data.length;i++){ if(data[i].attr_type==1){ str+=’<li><span>’+data[i].title+’: </span><input type=“hidden” name=“attr_id_list[]” value="’+data[i]._id+’" /> <input type=“text” name=“attr_value_list[]” /></li>’ }else if(data[i].attr_type==2){ str+=’<li><span>’+data[i].title+’: </span> <input type=“hidden” name=“attr_id_list[]” value="’+data[i]._id+’"> <textarea cols=“50” rows=“3” name=“attr_value_list[]"></textarea></li>’ }else{ var arr=data[i].attr_value.split(’\n’); str+=’<li><span>’+data[i].title+’: </span><input type=“hidden” name=“attr_id_list[]” value=”’+data[i]._id+’">’; str+=’<select name=“attr_value_list[]">’; for(var j=0;j<arr.length;j++){ str+=’<option value=”’+arr[j]+’">’+arr[j]+’</option>’; } str+=’</select>’; str+=’</li>’; } } $(’#goods_type_attribute’).html(str); }) })})效果 ...

December 17, 2018 · 1 min · jiezi

egg(63,64,65)--商品分类的curd,上传图片时生成多种分辨率的图片,表单最多提交个数

modelapp/model/goods_cate.jsmodule.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; var d=new Date(); const GoodsCateSchema = new Schema({ title:{ type:String }, cate_img: { type: String }, filter_attr: { type: String }, //筛选id link:{ type: String }, template:{ /指定当前分类的模板/ type:String }, pid:{ type:Schema.Types.Mixed //混合类型 }, sub_title:{ /seo相关的标题 关键词 描述/ type:String }, keywords:{ type:String }, description:{ type:String }, status: { type: Number,default:1 }, add_time: { type:Number, default: d.getTime() } }); return mongoose.model(‘GoodsCate’, GoodsCateSchema,‘goods_cate’); }router.js router.get(’/admin/goodsCate’, controller.admin.goodsCate.index); router.get(’/admin/goodsCate/add’, controller.admin.goodsCate.add); router.post(’/admin/goodsCate/doAdd’, controller.admin.goodsCate.doAdd);增加controllerapp/controller/admin/goodsCate.js async add(){ var result = await this.ctx.model.GoodsCate.find({‘pid’:‘0’}) await this.ctx.render(‘admin/goodsCate/add’,{ cateList:result }); } async doAdd(){ let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) } if(parts.field.pid!=0){ parts.field.pid=this.app.mongoose.Types.ObjectId(parts.field.pid); //调用mongoose里面的方法把字符串转换成ObjectId } let goodsCate =new this.ctx.model.GoodsCate(Object.assign(files,parts.field)); await goodsCate.save(); await this.success(’/admin/goodsCate’,‘增加分类成功’); }viewapp/view/admin/goodsCate/add.html一级分类的pid是0,顶级分类二级分类的pid是_id,一级分类(如下图的手机/电话卡)<%- include ../public/page_header.html %><div class=“panel panel-default”> <div class=“panel-heading”> 增加商品分类 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/goodsCate/doAdd?_csrf=<%=csrf%>" method=“post” enctype=“multipart/form-data”> <ul class=“form_input”> <li> <span>分类名称:</span> <input type=“text” name=“title” class=“input”/></li> <li> <span>上级分类:</span> <select name=“pid” id=“pid”> <option value=“0”>顶级分类</option> <%for(var i=0;i<cateList.length;i++){%> <option value="<%=cateList[i]._id%>"><%=cateList[i].title%></option> <%}%> </select> </li> <li> <span>分类图片:</span> <input type=“file” name=“cate_img”/></li> <li> <span>筛选属性:</span> <input type=“text” name=“filter_attr” class=“input”/></li> <li> <span>跳转地址:</span> <input type=“text” name=“link” class=“input”/></li> <li> <span>分类模板:</span> <input type=“text” name=“template” class=“input”/><span>空表示默认模板</span></li> <li> <span>Seo标题:</span> <input type=“text” name=“sub_title” class=“input”/></li> <li> <span>Seo关键词: </span><input type=“text” name=“keywords” class=“input”/></li> <li> <span>Seo描述:</span> <textarea name=“description” id=“description” cols=“84” rows=“4”></textarea></li> <li> <span>排 序:</span> <input type=“text” name=“sort”/></li> <li> <span>状 态:</span> <input type=“radio” name=“status” checked value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” name=“status” value=“0” id=“b”/><label for=“b”>隐藏</label> </li> <li> <br/> <button type=“submit” class=“btn btn-primary”>提交</button> </li> </ul> </form> </div> </div></div></body></html>效果查找controllerapp/controller/admin/goodsCate.js通过pid关联自己查询match筛选pid为0的数据 async index() { var result=await this.ctx.model.GoodsCate.aggregate([ { $lookup:{ from:‘goods_cate’, localField:’_id’, foreignField:‘pid’, as:‘items’ } }, { $match:{ “pid”:‘0’ } } ]) console.log(JSON.stringify(result)); await this.ctx.render(‘admin/goodsCate/index’,{ list:result }); }viewapp/view/admin/goodsCate/index.html<%- include ../public/page_header.html %> <div class=“panel panel-default”> <div class=“panel-heading clear”> <span>商品分类列表</span> <a href="/admin/goodsCate/add" class=“btn btn-primary fr”>增加商品分类</a> </div> <div class=“panel-body”> <div class=“table-responsive”> <table class=“table table-bordered”> <thead> <tr class=“th”> <th>分类名称</th> <th>分类图片</th> <th class=“text-center”>排序</th> <th class=“text-center”>状态</th> <th class=“text-center”>操作</th> </tr> </thead> <tbody> <%for(var i=0;i<list.length;i++){%> <tr> <td><%=list[i].title%></td> <td><img class=“pic” src="<%=list[i].cate_img%>" /></td> <td class=“text-center”><span onclick=“app.editNum(this,‘GoodsCate’,‘sort’,’<%=list[i]._id%>’)"><%=list[i].sort%></span></td> <td class=“text-center”> <%if(list[i].status==1){%> <img src="/public/admin/images/yes.gif” onclick=“app.changeStatus(this,‘GoodsCate’,‘status’,’<%=list[i]._id%>’)” /> <%}else{%> <img src="/public/admin/images/no.gif" onclick=“app.changeStatus(this,‘GoodsCate’,‘status’,’<%=list[i]._id%>’)” /> <%}%> </td> <td class=“text-center”> <a href="/admin/goodsCate/edit?id=<%=list[i]._id%>">修改</a> <a class=“delete” href="/admin/delete?model=GoodsCate&id=<%=list[i]._id%>">删除</a></td> </tr> <%for(var j=0;j<list[i].items.length;j++){%> <tr> <td>—–<%=list[i].items[j].title%></td> <td><img class=“pic” src="<%=list[i].items[j].cate_img%>" /></td> <td class=“text-center”><span onclick=“app.editNum(this,‘GoodsCate’,‘sort’,’<%=list[i].items[j]._id%>’)"><%=list[i].items[j].sort%></span></td> <td class=“text-center”> <%if(list[i].status==1){%> <img src="/public/admin/images/yes.gif” onclick=“app.changeStatus(this,‘GoodsCate’,‘status’,’<%=list[i].items[j]._id%>’)” /> <%}else{%> <img src="/public/admin/images/no.gif" onclick=“app.changeStatus(this,‘GoodsCate’,‘status’,’<%=list[i].items[j]._id%>’)” /> <%}%> </td> <td class=“text-center”> <a href="/admin/goodsCate/edit?id=<%=list[i].items[j]._id%>">修改</a> <a class=“delete” href="/admin/delete?model=GoodsCate&id=<%=list[i].items[j]._id%>">删除</a></td> </tr> <%}%> <%}%> </tbody> </table> </div> </div> </div></body></html>效果红色为一级分类蓝色为二级分类上传图片时,生成多种分辨率的图片安装依赖cnpm install jimp –saveserviceapp/service/tools.jsresize 尺寸quality 质量write 名字const Jimp = require(“jimp”); //生成缩略图的模块 async jimpImg(target){ //上传图片成功以后生成缩略图 Jimp.read(target, (err, lenna) => { if (err) throw err; lenna.resize(200, 200) // 尺寸 .quality(90) // 质量 .write(target+’_200x200’+path.extname(target)); // save lenna.resize(400, 400) // 尺寸 .quality(90) // 质量 .write(target+’_400x400’+path.extname(target)); // save }); }controllerapp/controller/admin/goodsCate.js增加一行this.service.tools.jimpImg(target); async doAdd(){ let parts = this.ctx.multipart({ autoFields: true }); let files = {}; let stream; while ((stream = await parts()) != null) { if (!stream.filename) { break; } let fieldname = stream.fieldname; //file表单的名字 //上传图片的目录 let dir=await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); files=Object.assign(files,{ [fieldname]:dir.saveDir }) this.service.tools.jimpImg(target); } if(parts.field.pid!=0){ parts.field.pid=this.app.mongoose.Types.ObjectId(parts.field.pid); //调用mongoose里面的方法把字符串转换成ObjectId } let goodsCate =new this.ctx.model.GoodsCate(Object.assign(files,parts.field)); await goodsCate.save(); await this.success(’/admin/goodsCate’,‘增加分类成功’); }效果编辑controllerapp/controller/admin/goodsCate.jsviewapp/view/admin/goodsCate/edit.html<%- include ../public/page_header.html %> <div class=“panel panel-default”> <div class=“panel-heading”> 编辑商品分类 </div> <div class=“panel-body”> <div class=“table-responsive input-form”> <form action="/admin/goodsCate/doEdit?_csrf=<%=csrf%>" method=“post” enctype=“multipart/form-data”> <ul class=“form_input”> <input type=“hidden” name=“id” value="<%=list._id%>" /> <li> <span>分类名称:</span> <input type=“text” name=“title” class=“input” value="<%=list.title%>"/></li> <li> <span>上级分类:</span> <select name=“pid” id=“pid”> <option value=“0”>顶级分类</option> <%for(var i=0;i<cateList.length;i++){%> <option value="<%=cateList[i]._id%>" <%if(cateList[i]._id.toString()==list.pid){%> selected <%}%>><%=cateList[i].title%></option> <%}%> </select> </li> <li> <span>分类图片:</span> <input type=“file” name=“cate_img”/> <br /> <span> </span> <img class=“pic” src="<%=list.cate_img%>" /> </li> <li> <span>筛选属性:</span> <input type=“text” name=“filter_attr” class=“input” value="<%=list.filter_attr%>"/></li> <li> <span>跳转地址:</span> <input type=“text” name=“link” class=“input” value="<%=list.link%>" /></li> <li> <span>分类模板:</span> <input type=“text” name=“template” class=“input” value="<%=list.template%>" /><span>空表示默认模板</span></li> <li> <span>Seo标题:</span> <input type=“text” name=“sub_title” class=“input” value="<%=list.sub_title%>"/></li> <li> <span>Seo关键词: </span><input type=“text” name=“keywords” class=“input” value="<%=list.keywords%>"/></li> <li> <span>Seo描述:</span> <textarea name=“description” id=“description” cols=“84” rows=“4”><%=list.description%></textarea></li> <li> <span>排 序:</span> <input type=“text” name=“sort” value="<%=list.sort%>"/></li> <li> <span>状 态:</span> <input type=“radio” name=“status” <%if(list.status==1){%> checked <%}%> value=“1” id=“a”/> <label for=“a”>显示</label> <input type=“radio” <%if(list.status==0){%> checked <%}%> name=“status” value=“0” id=“b”/><label for=“b”>隐藏</label> </li> <li> <br/> <button type=“submit” class=“btn btn-primary”>提交</button> </li> </ul> </form> </div> </div> </div></body></html> ...

December 13, 2018 · 4 min · jiezi