├── .gitignore ├── LICENSE ├── README.md ├── doc ├── changelog.md ├── deploy-srv.md └── deploy-web.md ├── golang-srv ├── .gitkeep ├── app.go ├── framework │ └── web │ │ ├── core.go │ │ ├── middleware │ │ └── recover.go │ │ ├── request.go │ │ └── response.go ├── go.mod └── go.sum ├── html-web ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .postcssrc.js ├── build │ ├── build.js │ ├── check-versions.js │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── config │ ├── dev.env.js │ ├── index.js │ └── prod.env.js ├── gulpfile.js ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── App.vue │ ├── assets │ │ ├── images │ │ │ ├── bg.jpg │ │ │ └── favicon.ico │ │ ├── scripts │ │ │ └── common.js │ │ └── styles │ │ │ ├── animate.min.css │ │ │ ├── cropper.css │ │ │ ├── iconfonts │ │ │ ├── iconfont.css │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ ├── iconfont.woff │ │ │ └── iconfont.woff2 │ │ │ ├── index.css │ │ │ └── reset.css │ ├── components │ │ ├── Category.vue │ │ ├── Index.vue │ │ ├── Login.vue │ │ ├── Person.vue │ │ ├── Share.vue │ │ └── common │ │ │ ├── CommonFooter.vue │ │ │ ├── CommonHeader.vue │ │ │ ├── ImgEdit.vue │ │ │ ├── ImgItem.vue │ │ │ ├── ImgUpload.vue │ │ │ ├── Person │ │ │ ├── HeadImg.vue │ │ │ ├── MailCode.vue │ │ │ └── Token.vue │ │ │ ├── Search.vue │ │ │ ├── Timer.vue │ │ │ ├── UserReg.vue │ │ │ └── UserReset.vue │ ├── main.js │ ├── router │ │ └── index.js │ └── store │ │ └── index.js ├── static │ └── favicon.ico └── update.html └── node-srv ├── app.js ├── config └── mailTemplate │ ├── ForgetCode.html │ ├── RegisteCode.html │ └── UpdateMailCode.html ├── const ├── allowedMimeType.js ├── code.js ├── cookiesName.js ├── mailType.js ├── redisKey.js ├── reg.js ├── unlogPathname.js └── whitePathname.js ├── controller ├── baseController.js ├── common.js ├── imgs.js ├── share.js ├── sort.js ├── tag.js ├── token.js └── user.js ├── extends ├── date.js └── index.js ├── lib ├── algorithm10to64.js ├── asyncRedis.js ├── busboyUpload.js ├── httpRequest.js ├── mailSender.js ├── mongodb.js └── util.js ├── middleware ├── auth.js └── basic.js ├── models ├── authToken.js ├── basic.js ├── defaultLoadSortId.js ├── images.js ├── shareImg.js ├── shareList.js ├── sorts.js └── user.js ├── package-lock.json ├── package.json └── routers ├── common.js ├── imgs.js ├── routerScanner.js ├── share.js ├── sort.js ├── tag.js ├── token.js └── user.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc html coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | ### ExtJs template 65 | .architect 66 | bootstrap.css 67 | bootstrap.js 68 | bootstrap.json 69 | bootstrap.jsonp 70 | dist/ 71 | classic.json 72 | classic.jsonp 73 | ext/ 74 | modern.json 75 | modern.jsonp 76 | resources/sass/.sass-cache/ 77 | resources/.arch-internal-preview.css 78 | .arch-internal-preview.css 79 | 80 | 81 | # Editor directories and files 82 | .vscode 83 | *.suo 84 | *.ntvs* 85 | *.njsproj 86 | *.sln 87 | 88 | # 自定义 89 | .idea 90 | .DS_Store 91 | node-srv/config/*.json 92 | pm2.json 93 | 94 | html-web/www/ 95 | html-web/rev/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![license](https://img.shields.io/badge/license-GNU-100000.svg) 2 | ![vue](https://img.shields.io/badge/>-vue-lightred.svg) 3 | ![node](https://img.shields.io/badge/>-nodejs-green.svg) 4 | ![vue](https://img.shields.io/badge/>-koa2-blue.svg) 5 | 6 | > #### 图床服务 7 | 8 | - Why 9 | 10 | ``` 11 | 1. 一款以管理图片为主导的图床 12 | 2. 为自己的图片分类,贴标签以便后面找到它们 13 | 3. 要建立几张图片集用于分享 14 | 4. 想要为图片提供在线外链 15 | TODO: 5. 想要安安静静存储个人图片(其他人不能访问) 16 | ... 17 | ``` 18 | 19 | - #### CHANGELOG **为支持正常运行(防攻击)部分图片浏览代码未开源** 20 | 21 | - 新功能 22 | 23 | - 为防止非法图片在微信中分享传输,已全面禁止图片在微信中浏览 24 | 25 | - 1.0.7 bugfix 26 | 27 | - 调整了对图片进行分级时,若无法正确分级时的相关数据记录问题 28 | - 修复一个图片分级API调用时可能导致的传参异常问题 29 | 30 | - 1.0.7 viewer 31 | 32 | - 图片浏览支持webp格式 33 | 34 | - 1.0.7 35 | 36 | - UI小姐姐更新了一波前端展示效果 37 | - 缩略图展示正式上线,加速个人中心图片展示 38 | - 图片分级判断上线(不限制;限制未成年;成人图片),后续将按照相关分级限制违规图片 39 | - 修复一个内部图床访问原图加载失败的问题,取消内部webp处理 40 | - 图片截取界面新增所截区域大小值展示 41 | 42 | - [历史记录](https://github.com/lazy-koala/imgs-upload-srv/blob/master/doc/changelog.md) 43 | --- 44 | > #### 技术栈 45 | - Server: 46 | 47 | - Node 48 | - Koa2 49 | 50 | - WEB: 51 | 52 | - HTML5 53 | - Vue 54 | 55 | - DataBase: 56 | 57 | - MongoDB 58 | - Redis 59 | - 自建部署指南 60 | 61 | - [前端部署指南](https://github.com/lazy-koala/imgs-upload-srv/blob/master/doc/deploy-web.md) 62 | 63 | - [后端部署指南](https://github.com/lazy-koala/imgs-upload-srv/blob/master/doc/deploy-srv.md) 64 | --- 65 | > #### 关于我们 66 | 67 | [![org](https://img.shields.io/badge/org-@LazyKoala-yellow.svg)](https://github.com/lazy-koala/) 68 | 69 | [![author](https://img.shields.io/badge/author-@qazyuan-blue.svg)](https://github.com/qazyuan/) [![author](https://img.shields.io/badge/author-@acexy-blue.svg)](https://github.com/acexy/) 70 | 71 | > #### Thanks [Jetbrains Ides](https://www.jetbrains.com/?from=imgs-upload-srv) 72 | jetbrains logo 73 | -------------------------------------------------------------------------------- /doc/changelog.md: -------------------------------------------------------------------------------- 1 | - 1.0.6 2 | 3 | - 升级node服务端所有第三方依赖至最新 4 | - 上传图片的相关异常反馈处理 5 | - 限制了上传图片的大小 6 | 7 | - 1.0.5.patch 8 | 9 | - 调整&闭源图片访问控制模块以用于解决来自网络的恶意攻击(如: DDos) 10 | - 图片访问控制增加相关纬度监控,预设阈值完善访问模块控制请求 11 | - 部分基础资源调整至云存储节点 12 | 13 | - 1.0.5 14 | 15 | - 分享界面展示分享者标注的标签信息 16 | 17 | - 1.0.4 18 | 19 | - 部分界面优化 20 | - 支持已分享的分类自动分享后续上传的图片 21 | - 新增设置默认加载分类的功能,进入主界面可自定义首选分类 22 | 23 | - 1.0.3 24 | 25 | - 用户可分享单张/多张/某一分类的图片 26 | - 用户可查询自己创建的分享清单,并可以删除自己创建的分享 27 | - 访问者可以通过访问有效的分享链接,浏览所分享的图片列表 28 | 29 | - 1.0.2 30 | 31 | - 升级了包含安全漏洞的模块 32 | - 新增图片分类功能,可以对图片分类管理,并可以给图片打上标签备注 33 | - 新增图片检索功能,支持分类|标签查询 34 | - 新增图片分类维护 35 | - 调整了图片短链生成策略,防止图片地址连续可猜测 36 | - bufix: #12 37 | - 调整密码找回流程,账号/邮箱轻松找回 38 | 39 | - 1.0.1-bugfix 40 | 41 | - 图片上传问题 #8 42 | - 首页打包引号缺失问题修正 43 | 44 | - 1.0.1-dependencies-upgrade 45 | 46 | - 升级了包含安全漏洞的模块 47 | 48 | - 1.0.1 49 | 50 | - 新增图片加载过渡动画 51 | - 优化分页大小: 分页大小根据屏幕大小动态计算 52 | - 优化图片uri为短连接形式,调整图片的访问方式,预留更多图片访问控制处理方案 53 | - bufix: #5 #6 54 | - 在线图片服务文案变更为图床服务 55 | - 优化了非长期登录token的有效期: #7 56 | - 调整了可配置部分uri不打印出入参数, 敏感信息不予输出 57 | 58 | - 1.0.0-bugfix 59 | 60 | - bugfix: #1 #2 #3 #4 61 | 62 | - 1.0.0 63 | 64 | - 基础标准版本上线 65 | -------------------------------------------------------------------------------- /doc/deploy-srv.md: -------------------------------------------------------------------------------- 1 | > #### 服务端部署指南 2 | - 环境服务 3 | 4 | ``` 5 | node >= 8.9.0 6 | mongodb 7 | redis 8 | ``` 9 | - 步骤 10 | 11 | - git clone https://github.com/lazy-koala/imgs-upload-srv.git 12 | 13 | - node-srv/config 新建以下相关配置 14 | - basic.json 基本配置 15 | ``` 16 | imsDomain: 图片可访问的域名配置 17 | ``` 18 | ```json 19 | { 20 | "port": 8005, 21 | "imsDomain": "" 22 | } 23 | ``` 24 | - mongo.json mongodb数据库配置 25 | ```json 26 | { 27 | "host": "", 28 | "port": 6330, 29 | "username": "", 30 | "password": "", 31 | "dbname": "" 32 | } 33 | ``` 34 | - redis.json redis缓存配置 35 | ```json 36 | { 37 | "host": "", 38 | "port": 6379, 39 | "password": "", 40 | "no_ready_check": true 41 | } 42 | ``` 43 | - upload.json 图片上传配置 44 | ``` 45 | path: 图片上传的物理服务器路径 (文件夹) 46 | ``` 47 | ```json 48 | { 49 | "path": "" 50 | } 51 | ``` 52 | 53 | - 切换至 node-srv 目录执行 npm install 安装服务端依赖的相关组件 54 | 55 | - node app.js 启动后端服务 (推荐使用nohup或采用pm2管理) 56 | -------------------------------------------------------------------------------- /doc/deploy-web.md: -------------------------------------------------------------------------------- 1 | #### 前端部署指南 2 | - 主要依赖 3 | ``` 4 | vue 2.0 5 | webpack ^3.6.0 6 | element-ui ^2.4.1 7 | vuex ^3.0.1 8 | vue-router^3.0.1 9 | ``` 10 | 11 | - 安装打包资源 12 | 13 | - 切换到目录 html-web&安装依赖 14 | ```bash 15 | npm install 16 | ``` 17 | 18 | - 本地启动Http服务 http://localhost:8080 (可选测试) 19 | ```bash 20 | npm run dev 21 | ``` 22 | 23 | - 项目打包生成dist目录下的http应用资源文件 24 | ```bash 25 | npm run build 26 | ``` 27 | 28 | - Nginx配置 29 | ``` 30 | 目前部署采用nginx做http服务层,并做后端接口调用映射 31 | 32 | server{ 33 | 34 | listen 80; 35 | server_name yourdomain.com; 36 | 37 | # 配置请求node服务端接口映射 38 | location /api/ { 39 | proxy_set_header X-Real-IP $remote_addr; 40 | proxy_set_header Host $host; 41 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 42 | proxy_pass http://127.0.0.1:8005/; 43 | } 44 | 45 | # 配置指定dist目录http资源位置映射 46 | location / { 47 | try_files $uri $uri/ /index.html; 48 | root **/dist/; # 编译生产的dist资源目录位置 49 | } 50 | 51 | # 由于部署的图片服务器域名和服务域名不一致 该配置用于将图片服务器的下载资源映射到当前域名下以解决前端跨域下载 52 | location /download/ { 53 | alias */*/; # 服务图片上传的物理位置目录 同 node-srv/config/upload.json 54 | autoindex off; 55 | autoindex_exact_size on; 56 | autoindex_localtime on; 57 | } 58 | } 59 | ``` -------------------------------------------------------------------------------- /golang-srv/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/imgs-upload-srv/a14f57f9f9ee7bac6f939a74017a4d0ef6a58731/golang-srv/.gitkeep -------------------------------------------------------------------------------- /golang-srv/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /golang-srv/framework/web/core.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | type GinWrap struct { 9 | gRouter *gin.RouterGroup 10 | } 11 | 12 | type RouterConfig struct { 13 | GroupPath string 14 | } 15 | 16 | type Routers interface { 17 | RouterInfo() *RouterConfig 18 | Register(h *GinWrap) 19 | } 20 | 21 | type HandlerWrap func(request *Request) (*Response, error) 22 | 23 | type AllRouters func() []Routers 24 | 25 | func LoadRouter(gine *gin.Engine, routers AllRouters) { 26 | for _, v := range routers() { 27 | r := v.RouterInfo() 28 | wrap := &GinWrap{ 29 | gRouter: gine.Group(r.GroupPath), 30 | } 31 | v.Register(wrap) 32 | } 33 | } 34 | 35 | func (r *GinWrap) handle(method string, path string, handler HandlerWrap) { 36 | r.gRouter.Handle(method, path, func(context *gin.Context) { 37 | response, err := handler(&Request{context}) 38 | if err == nil { 39 | if response == nil { 40 | response = &Response{ 41 | Message: RtMsgSuccess, 42 | Code: RtCdSuccess, 43 | } 44 | } else { 45 | if response.Code == RtCdSuccess && response.Message == "" { 46 | response.Message = RtMsgSuccess 47 | } 48 | } 49 | context.JSON(http.StatusOK, response) 50 | } else { 51 | _ = context.Error(err) 52 | context.JSON(http.StatusOK, NewExceptionResp()) 53 | } 54 | }) 55 | } 56 | 57 | func (r *GinWrap) POST(path string, handler HandlerWrap) { 58 | r.handle(http.MethodPost, path, handler) 59 | } 60 | func (r *GinWrap) GET(path string, handler HandlerWrap) { 61 | r.handle(http.MethodGet, path, handler) 62 | } 63 | func (r *GinWrap) HEAD(path string, handler HandlerWrap) { 64 | r.handle(http.MethodHead, path, handler) 65 | } 66 | func (r *GinWrap) PUT(path string, handler HandlerWrap) { 67 | r.handle(http.MethodPut, path, handler) 68 | } 69 | func (r *GinWrap) PATCH(path string, handler HandlerWrap) { 70 | r.handle(http.MethodPatch, path, handler) 71 | } 72 | func (r *GinWrap) DELETE(path string, handler HandlerWrap) { 73 | r.handle(http.MethodDelete, path, handler) 74 | } 75 | func (r *GinWrap) OPTIONS(path string, handler HandlerWrap) { 76 | r.handle(http.MethodOptions, path, handler) 77 | } 78 | func (r *GinWrap) TRACE(path string, handler HandlerWrap) { 79 | r.handle(http.MethodTrace, path, handler) 80 | } 81 | -------------------------------------------------------------------------------- /golang-srv/framework/web/middleware/recover.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "runtime/debug" 6 | ) 7 | 8 | func Recover() gin.HandlerFunc { 9 | return func(ctx *gin.Context) { 10 | defer func() { 11 | if r := recover(); r != nil { 12 | debug.PrintStack() 13 | ctx.Abort() 14 | } else { 15 | ctx.Next() 16 | } 17 | }() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /golang-srv/framework/web/request.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "mime/multipart" 6 | "path/filepath" 7 | ) 8 | 9 | type Request struct { 10 | ctx *gin.Context 11 | } 12 | 13 | func (r *Request) GetGinContext() *gin.Context { 14 | return r.ctx 15 | } 16 | 17 | // Param 获取url部分中的参数 18 | func (r *Request) Param(key string) string { 19 | return r.ctx.Param(key) 20 | } 21 | 22 | func (r *Request) Params(keys ...string) map[string]string { 23 | result := make(map[string]string) 24 | if len(keys) > 0 { 25 | for _, value := range keys { 26 | result[value] = r.Param(value) 27 | } 28 | } 29 | return result 30 | } 31 | 32 | // urlPath 获取url中作为参数的数据 33 | 34 | func (r *Request) ShouldBindUri(object any) error { 35 | return r.ctx.ShouldBindUri(object) 36 | } 37 | 38 | // 将json类型body数据绑定到结构体中 39 | 40 | func (r *Request) ShouldBindJson(object any) error { 41 | return r.ctx.ShouldBindJSON(object) 42 | } 43 | 44 | func (r *Request) GetRawData() ([]byte, error) { 45 | return r.ctx.GetRawData() 46 | } 47 | 48 | // 获取请求中的原始数据 string类型 49 | 50 | func (r *Request) GetRawDataString() (content string, err error) { 51 | bytes, err := r.GetRawData() 52 | if err != nil { 53 | return 54 | } 55 | content = string(bytes) 56 | return 57 | } 58 | 59 | func (r *Request) GetFormFile(name string) (*multipart.FileHeader, error) { 60 | return r.ctx.FormFile(name) 61 | } 62 | 63 | // SaveUploadedFile 保存文件 dist 如果是目录则将以原始文件名存储在指定目录 64 | func (r *Request) SaveUploadedFile(name string, dirPath string, fileName ...string) error { 65 | file, err := r.GetFormFile(name) 66 | if err != nil { 67 | return err 68 | } 69 | var dist string 70 | if len(fileName) != 0 { 71 | // 未指定文件名,传入的应当是带文件名的绝对路径 72 | dist = dirPath + string(filepath.Separator) + fileName[0] 73 | } else { 74 | dist = dirPath + string(filepath.Separator) + file.Filename 75 | } 76 | return r.ctx.SaveUploadedFile(file, dist) 77 | } 78 | 79 | // GetHeaderValue 获取指定的Head值 80 | func (r *Request) GetHeaderValue(key string) string { 81 | return r.ctx.GetHeader(key) 82 | } 83 | -------------------------------------------------------------------------------- /golang-srv/framework/web/response.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | type Code int 4 | type Message string 5 | 6 | const ( 7 | RtCdSuccess Code = 0 8 | RtCdException Code = 9999 9 | 10 | RtCdUnauthorized = 8999 11 | RtCdCommonFailed = 7999 12 | ) 13 | 14 | const ( 15 | RtMsgException Message = "System Error" 16 | RtMsgUnauthorized = "Unauthorized" 17 | RtMsgSuccess = "Success" 18 | 19 | RtMsgCommonFailed = "Execution Failed" 20 | ) 21 | 22 | type Response struct { 23 | Code Code `json:"code"` 24 | Message Message `json:"message"` 25 | Data any `json:"data"` 26 | } 27 | 28 | var ( 29 | successResp = &Response{ 30 | Code: RtCdSuccess, 31 | Message: RtMsgSuccess, 32 | } 33 | 34 | exceptionResp = &Response{ 35 | Code: RtCdException, 36 | Message: RtMsgException, 37 | } 38 | 39 | commonFailedResp = &Response{ 40 | Code: RtCdCommonFailed, 41 | Message: RtMsgCommonFailed, 42 | } 43 | ) 44 | 45 | var rtConfig = map[Code]Message{ 46 | RtCdException: RtMsgException, 47 | RtCdUnauthorized: RtMsgUnauthorized, 48 | RtCdSuccess: RtMsgSuccess, 49 | RtCdCommonFailed: RtMsgCommonFailed, 50 | } 51 | 52 | func NewSuccessResp(data ...any) *Response { 53 | resp := *successResp 54 | if len(data) > 0 { 55 | resp.Data = data[0] 56 | } 57 | return &resp 58 | } 59 | 60 | func NewExceptionResp(message ...string) *Response { 61 | if len(message) > 0 { 62 | resp := *exceptionResp 63 | resp.Message = Message(message[0]) 64 | return &resp 65 | } else { 66 | return exceptionResp 67 | } 68 | } 69 | 70 | func NewCommonFailedResp(message ...string) *Response { 71 | if len(message) > 0 { 72 | resp := *commonFailedResp 73 | resp.Message = Message(message[0]) 74 | return &resp 75 | } else { 76 | return commonFailedResp 77 | } 78 | } 79 | 80 | func NewCommonFailedWithDataResp(any any, message ...string) *Response { 81 | resp := *commonFailedResp 82 | resp.Data = any 83 | if len(message) > 0 { 84 | resp.Message = Message(message[0]) 85 | } 86 | return &resp 87 | } 88 | 89 | func NewResp(rtCode Code, rtMsg ...string) *Response { 90 | if len(rtMsg) > 0 { 91 | return &Response{ 92 | Code: rtCode, 93 | Message: Message(rtMsg[0]), 94 | } 95 | } 96 | return &Response{ 97 | Code: rtCode, 98 | Message: rtConfig[rtCode], 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /golang-srv/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lazy-koala/imgs-upload-srv/tree/master/golang-srv 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/bytedance/sonic v1.9.1 // indirect 7 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 8 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 9 | github.com/gin-contrib/sse v0.1.0 // indirect 10 | github.com/gin-gonic/gin v1.9.1 // indirect 11 | github.com/go-playground/locales v0.14.1 // indirect 12 | github.com/go-playground/universal-translator v0.18.1 // indirect 13 | github.com/go-playground/validator/v10 v10.14.0 // indirect 14 | github.com/goccy/go-json v0.10.2 // indirect 15 | github.com/json-iterator/go v1.1.12 // indirect 16 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 17 | github.com/leodido/go-urn v1.2.4 // indirect 18 | github.com/mattn/go-isatty v0.0.19 // indirect 19 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 20 | github.com/modern-go/reflect2 v1.0.2 // indirect 21 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 22 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 23 | github.com/ugorji/go/codec v1.2.11 // indirect 24 | golang.org/x/arch v0.3.0 // indirect 25 | golang.org/x/crypto v0.9.0 // indirect 26 | golang.org/x/net v0.10.0 // indirect 27 | golang.org/x/sys v0.8.0 // indirect 28 | golang.org/x/text v0.9.0 // indirect 29 | google.golang.org/protobuf v1.30.0 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /golang-srv/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 3 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 10 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 11 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 12 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 13 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 14 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 15 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 16 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 17 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 18 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 19 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 20 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 21 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 22 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 23 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 24 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 25 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 26 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 27 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 28 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 29 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 30 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 31 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 32 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 33 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 34 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 35 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 36 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 37 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 38 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 39 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 40 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 41 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 42 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 43 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 44 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 45 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 46 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 47 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 48 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 49 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 50 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 51 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 52 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 53 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 54 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 55 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 56 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 57 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 58 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 59 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 60 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 61 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 62 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 63 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 64 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 65 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 66 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 67 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 68 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 69 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 70 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 71 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 72 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 73 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 74 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 75 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 76 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 77 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 78 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 79 | -------------------------------------------------------------------------------- /html-web/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": [ 12 | [ 13 | "component", 14 | { 15 | "libraryName": "element-ui", 16 | "styleLibraryName": "theme-chalk" 17 | } 18 | ], 19 | "transform-vue-jsx", 20 | "transform-runtime" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /html-web/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /html-web/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /src/ 6 | -------------------------------------------------------------------------------- /html-web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 28 | "no-mixed-spaces-and-tabs": 0, 29 | "no-tabs": 2, 30 | "indent": [0, 4, {"SwitchCase": 1}], 31 | "semi": [0, "always"], 32 | "eol-last": 0, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /html-web/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /html-web/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /html-web/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /html-web/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const sassLoader = { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | const postcssLoader = { 33 | loader: 'postcss-loader', 34 | options: { 35 | sourceMap: options.sourceMap 36 | } 37 | } 38 | 39 | // generate loader string to be used with extract text plugin 40 | function generateLoaders (loader, loaderOptions) { 41 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader, sassLoader] : [cssLoader, sassLoader] 42 | 43 | if (loader) { 44 | loaders.push({ 45 | loader: loader + '-loader', 46 | options: Object.assign({}, loaderOptions, { 47 | sourceMap: options.sourceMap 48 | }) 49 | }) 50 | } 51 | 52 | // Extract CSS when that option is specified 53 | // (which is the case during production build) 54 | if (options.extract) { 55 | return ExtractTextPlugin.extract({ 56 | use: loaders, 57 | fallback: 'vue-style-loader' 58 | }) 59 | } else { 60 | return ['vue-style-loader'].concat(loaders) 61 | } 62 | } 63 | 64 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 65 | return { 66 | css: generateLoaders(), 67 | postcss: generateLoaders(), 68 | less: generateLoaders('less'), 69 | sass: generateLoaders('sass', { indentedSyntax: true }), 70 | scss: generateLoaders('sass'), 71 | stylus: generateLoaders('stylus'), 72 | styl: generateLoaders('stylus') 73 | } 74 | } 75 | 76 | // Generate loaders for standalone style files (outside of .vue) 77 | exports.styleLoaders = function (options) { 78 | const output = [] 79 | const loaders = exports.cssLoaders(options) 80 | 81 | for (const extension in loaders) { 82 | const loader = loaders[extension] 83 | output.push({ 84 | test: new RegExp('\\.' + extension + '$'), 85 | use: loader 86 | }) 87 | } 88 | 89 | return output 90 | } 91 | 92 | exports.createNotifierCallback = () => { 93 | const notifier = require('node-notifier') 94 | 95 | return (severity, errors) => { 96 | if (severity !== 'error') return 97 | 98 | const error = errors[0] 99 | const filename = error.file && error.file.split('!').pop() 100 | 101 | notifier.notify({ 102 | title: packageConfig.name, 103 | message: severity + ': ' + error.name, 104 | subtitle: filename || '', 105 | icon: path.join(__dirname, 'logo.png') 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /html-web/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /html-web/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json', '.css'], 36 | alias: { 37 | 'vue$': 'vue/dist/vue.esm.js', 38 | '@': resolve('src'), 39 | '~styles': resolve('src/assets/styles') 40 | } 41 | }, 42 | module: { 43 | rules: [ 44 | ...(config.dev.useEslint ? [createLintingRule()] : []), 45 | { 46 | test: /\.vue$/, 47 | loader: 'vue-loader', 48 | options: vueLoaderConfig 49 | }, 50 | { 51 | test: /\.js$/, 52 | loader: 'babel-loader', 53 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 54 | }, 55 | { 56 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 57 | loader: 'url-loader', 58 | options: { 59 | limit: 10000, 60 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 61 | } 62 | }, 63 | { 64 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 65 | loader: 'url-loader', 66 | options: { 67 | limit: 10000, 68 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 69 | } 70 | }, 71 | { 72 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 73 | loader: 'url-loader', 74 | options: { 75 | limit: 10000, 76 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 77 | } 78 | } 79 | ] 80 | }, 81 | node: { 82 | // prevent webpack from injecting useless setImmediate polyfill because Vue 83 | // source contains it (although only uses it if it's native). 84 | setImmediate: false, 85 | // prevent webpack from injecting mocks to Node native modules 86 | // that does not make sense for the client 87 | dgram: 'empty', 88 | fs: 'empty', 89 | net: 'empty', 90 | tls: 'empty', 91 | child_process: 'empty' 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /html-web/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay ? { warnings: false, errors: true } : false, 38 | publicPath: config.dev.assetsPublicPath, 39 | proxy: config.dev.proxyTable, 40 | quiet: true, // necessary for FriendlyErrorsPlugin 41 | watchOptions: { 42 | poll: config.dev.poll, 43 | }, 44 | 45 | disableHostCheck: true 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([{ 62 | from: path.resolve(__dirname, '../static'), 63 | to: config.dev.assetsSubDirectory, 64 | ignore: ['.*'] 65 | }]) 66 | ] 67 | }) 68 | 69 | module.exports = new Promise((resolve, reject) => { 70 | portfinder.basePort = process.env.PORT || config.dev.port 71 | portfinder.getPort((err, port) => { 72 | if (err) { 73 | reject(err) 74 | } else { 75 | // publish the new Port, necessary for e2e tests 76 | process.env.PORT = port 77 | // add port to devServer config 78 | devWebpackConfig.devServer.port = port 79 | 80 | // Add FriendlyErrorsPlugin 81 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 82 | compilationSuccessInfo: { 83 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 84 | }, 85 | onErrors: config.dev.notifyOnErrors ? 86 | utils.createNotifierCallback() : undefined 87 | })) 88 | 89 | resolve(devWebpackConfig) 90 | } 91 | }) 92 | }) -------------------------------------------------------------------------------- /html-web/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true, 21 | usePostCSS: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new UglifyJsPlugin({ 36 | uglifyOptions: { 37 | compress: { 38 | warnings: false 39 | } 40 | }, 41 | sourceMap: config.build.productionSourceMap, 42 | parallel: true 43 | }), 44 | // extract css into its own file 45 | new ExtractTextPlugin({ 46 | filename: utils.assetsPath('css/[name].[contenthash].css'), 47 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 51 | allChunks: true, 52 | }), 53 | // Compress extracted CSS. We are using this plugin so that possible 54 | // duplicated CSS from different components can be deduped. 55 | new OptimizeCSSPlugin({ 56 | cssProcessorOptions: config.build.productionSourceMap 57 | ? { safe: true, map: { inline: false } } 58 | : { safe: true } 59 | }), 60 | // generate dist index.html with correct asset hash for caching. 61 | // you can customize output by editing /index.html 62 | // see https://github.com/ampedandwired/html-webpack-plugin 63 | new HtmlWebpackPlugin({ 64 | filename: config.build.index, 65 | template: 'index.html', 66 | inject: true, 67 | minify: { 68 | removeComments: true, 69 | collapseWhitespace: true, 70 | removeAttributeQuotes: false 71 | // more options: 72 | // https://github.com/kangax/html-minifier#options-quick-reference 73 | }, 74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 75 | chunksSortMode: 'dependency' 76 | }), 77 | // keep module.id stable when vendor modules does not change 78 | new webpack.HashedModuleIdsPlugin(), 79 | // enable scope hoisting 80 | new webpack.optimize.ModuleConcatenationPlugin(), 81 | // split vendor js into its own file 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'vendor', 84 | minChunks (module) { 85 | // any required modules inside node_modules are extracted to vendor 86 | return ( 87 | module.resource && 88 | /\.js$/.test(module.resource) && 89 | module.resource.indexOf( 90 | path.join(__dirname, '../node_modules') 91 | ) === 0 92 | ) 93 | } 94 | }), 95 | // extract webpack runtime and module manifest to its own file in order to 96 | // prevent vendor hash from being updated whenever app bundle is updated 97 | new webpack.optimize.CommonsChunkPlugin({ 98 | name: 'manifest', 99 | minChunks: Infinity 100 | }), 101 | // This instance extracts shared chunks from code splitted chunks and bundles them 102 | // in a separate chunk, similar to the vendor chunk 103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'app', 106 | async: 'vendor-async', 107 | children: true, 108 | minChunks: 3 109 | }), 110 | 111 | // copy custom static assets 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.resolve(__dirname, '../static'), 115 | to: config.build.assetsSubDirectory, 116 | ignore: ['.*'] 117 | } 118 | ]) 119 | ] 120 | }) 121 | 122 | if (config.build.productionGzip) { 123 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 124 | 125 | webpackConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | module.exports = webpackConfig 146 | -------------------------------------------------------------------------------- /html-web/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /html-web/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | // '/api': { //代理地址 15 | // changeOrigin: true, //是否跨域 16 | // secure: false, 17 | // pathRewrite: { 18 | // '^/api': '/mock/27/api' //本身的接口地址没有 '/api' 这种通用前缀,所以要rewrite,如果本身有则去掉 19 | // } 20 | // } 21 | // testimg测试环境 22 | // '/api': { //代理地址 23 | // target: 'https://testimgs.acexy.cn', //需要代理的地址 24 | // changeOrigin: true, //是否跨域 25 | // secure: false, 26 | // baseURL: 'https://testimgs.acexy.cn' 27 | // } 28 | 29 | // 线上环境 30 | '/api': { //代理地址 31 | target: 'https://imgs.acexy.cn', //需要代理的地址 32 | changeOrigin: true, //是否跨域 33 | secure: false, 34 | baseURL: 'https://imgs.acexy.cn' 35 | } 36 | 37 | // '/api': { //代理地址 38 | // target: 'http://aorakipet.com', //需要代理的地址 39 | // changeOrigin: true, //是否跨域 40 | // secure: false, 41 | // baseURL: 'http://aorakipet.com', 42 | // pathRewrite: { 43 | // '^/api': '/' 44 | // } 45 | // } 46 | }, 47 | 48 | // Various Dev Server settings 49 | host: 'localhost', // can be overwritten by process.env.HOST 50 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 51 | autoOpenBrowser: false, 52 | errorOverlay: true, 53 | notifyOnErrors: true, 54 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 55 | 56 | // Use Eslint Loader? 57 | // If true, your code will be linted during bundling and 58 | // linting errors and warnings will be shown in the console. 59 | useEslint: true, 60 | // If true, eslint errors and warnings will also be shown in the error overlay 61 | // in the browser. 62 | showEslintErrorsInOverlay: false, 63 | 64 | /** 65 | * Source Maps 66 | */ 67 | 68 | // https://webpack.js.org/configuration/devtool/#development 69 | devtool: 'cheap-module-eval-source-map', 70 | 71 | // If you have problems debugging vue-files in devtools, 72 | // set this to false - it *may* help 73 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 74 | cacheBusting: true, 75 | 76 | cssSourceMap: true 77 | }, 78 | 79 | build: { 80 | // Template for index.html 81 | index: path.resolve(__dirname, '../dist/index.html'), 82 | 83 | // Paths 84 | assetsRoot: path.resolve(__dirname, '../dist'), 85 | assetsSubDirectory: 'static', 86 | assetsPublicPath: '/', 87 | 88 | /** 89 | * Source Maps 90 | */ 91 | 92 | productionSourceMap: false, 93 | // https://webpack.js.org/configuration/devtool/#production 94 | devtool: '#source-map', 95 | 96 | // Gzip off by default as many popular static hosts such as 97 | // Surge or Netlify already gzip all static assets for you. 98 | // Before setting to `true`, make sure to: 99 | // npm install --save-dev compression-webpack-plugin 100 | productionGzip: false, 101 | productionGzipExtensions: ['js', 'css'], 102 | 103 | // Run the build command with an extra argument to 104 | // View the bundle analyzer report after build finishes: 105 | // `npm run build --report` 106 | // Set to `true` or `false` to always turn it on or off 107 | bundleAnalyzerReport: process.env.npm_config_report 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /html-web/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"', 4 | API_ROOT: "http://imgs.acexy.cn" //后台请求地址域名 5 | } 6 | -------------------------------------------------------------------------------- /html-web/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var rev = require('gulp-rev'); 3 | var revCollector = require('gulp-rev-collector'); 4 | var clean = require('gulp-clean'); 5 | var date = new Date(); 6 | var month = date.getMonth() + 1; 7 | month = month > 9 ? month : '0'+month; 8 | var day = date.getDate(); 9 | var time = month + day + '/'; 10 | var prdPath = "https://acexy-1251164268.cos.ap-shanghai.myqcloud.com/imgs/static/" + time; 11 | gulp.task('css', function () { 12 | return gulp.src('dist/static/css/*.css') 13 | .pipe(rev()) 14 | .pipe(gulp.dest('www/static/' + time + 'css')) 15 | .pipe(rev.manifest()) 16 | .pipe(gulp.dest('rev/css')); 17 | }); 18 | 19 | gulp.task('js', function () { 20 | return gulp.src('dist/static/js/*.js') 21 | .pipe(rev()) 22 | .pipe(gulp.dest('www/static/' + time + 'js')) 23 | .pipe(rev.manifest()) 24 | .pipe(gulp.dest('rev/js')); 25 | }); 26 | 27 | gulp.task('fonts', function () { 28 | // return gulp.src('dist/static/fonts/*') 29 | // .pipe(gulp.dest('www/static/fonts')) 30 | return gulp.src('dist/static/fonts/*') 31 | .pipe(rev()) 32 | .pipe(gulp.dest('www/static/' + time + 'fonts')) 33 | .pipe(rev.manifest()) 34 | .pipe(gulp.dest('rev/fonts')); 35 | }); 36 | 37 | gulp.task('img', function () { 38 | return gulp.src('dist/static/img/*') 39 | .pipe(rev()) 40 | .pipe(gulp.dest('www/static/' + time + 'img')) 41 | .pipe(rev.manifest()) 42 | .pipe(gulp.dest('rev/img')); 43 | }); 44 | 45 | // gulp.task('revhtml', function () { 46 | // return gulp.src(['rev/**/*.json', 'dist/*.html']) 47 | // .pipe(revCollector({ 48 | // replaceReved: true,//允许替换, 已经被替换过的文件 49 | // dirReplacements: { 50 | // '/static/css': prdPath + 'css', 51 | // '/static/js': prdPath + 'js' 52 | // } 53 | // })) 54 | // .pipe(gulp.dest('www')); 55 | // }); 56 | gulp.task('revcss', gulp.series('fonts', 'img', 'css', function () { 57 | return gulp.src(['rev/**/*.json', 'www/static/' + time + 'css/*']) 58 | .pipe(revCollector({ 59 | replaceReved: true, //允许替换, 已经被替换过的文件 60 | dirReplacements: { 61 | '/static/img/': prdPath + 'img', 62 | '/static/fonts/': prdPath + 'fonts' 63 | } 64 | })) 65 | .pipe(gulp.dest('www/static/' + time + 'css')); 66 | })); 67 | gulp.task('revhtml', gulp.series('js', 'revcss', function () { 68 | return gulp.src(['rev/**/*.json', 'dist/*.html']) 69 | .pipe(revCollector({ 70 | replaceReved: true, //允许替换, 已经被替换过的文件 71 | dirReplacements: { 72 | '/static/css': prdPath + 'css', 73 | '/static/js': prdPath + 'js' 74 | } 75 | })) 76 | .pipe(gulp.dest('www' )); 77 | })); 78 | // gulp.task('revhtml', function () { 79 | // return gulp.src(['rev/**/*.json', 'dist/*.html']) 80 | // .pipe(revCollector({ 81 | // replaceReved: true, //允许替换, 已经被替换过的文件 82 | // dirReplacements: { 83 | // '/static/css': prdPath + 'css', 84 | // '/static/js': prdPath + 'js' 85 | // } 86 | // })) 87 | // .pipe(gulp.dest('www')); 88 | // }); 89 | 90 | gulp.task('clean', function () { 91 | return gulp.src(['www', 'rev'], { 92 | read: false, 93 | allowEmpty: true 94 | }) 95 | .pipe(clean()); 96 | }); 97 | // gulp.task('revhtml', ['clean', 'fonts', 'img'], done => { 98 | // done(); 99 | // }); 100 | 101 | // gulp.task('replace', gulp.parallel('clean', 'revhtml', 'revcss', function (done) { 102 | // done(); 103 | // })); 104 | 105 | gulp.task('default', gulp.series('clean', 'revhtml', function (done) { 106 | done(); 107 | })); 108 | -------------------------------------------------------------------------------- /html-web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 在线图床服务 10 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /html-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-web", 3 | "version": "1.0.7", 4 | "description": "图床服务UI", 5 | "author": "ZhiyuanYang ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js", 12 | "replace": "./node_modules/gulp/bin/gulp.js", 13 | "prd": "npm run build && npm run replace" 14 | }, 15 | "dependencies": { 16 | "element-ui": "^2.4.1", 17 | "qs": "^6.7.0", 18 | "vue": "^2.5.17", 19 | "vue-router": "^3.0.1", 20 | "vuex": "^3.0.1" 21 | }, 22 | "devDependencies": { 23 | "autoprefixer": "^7.1.2", 24 | "axios": "^0.18.0", 25 | "babel-core": "^6.22.1", 26 | "babel-eslint": "^8.2.1", 27 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 28 | "babel-loader": "^7.1.1", 29 | "babel-plugin-component": "^1.1.1", 30 | "babel-plugin-syntax-jsx": "^6.18.0", 31 | "babel-plugin-transform-runtime": "^6.22.0", 32 | "babel-plugin-transform-vue-jsx": "^3.5.0", 33 | "babel-preset-env": "^1.3.2", 34 | "babel-preset-stage-2": "^6.22.0", 35 | "chalk": "^2.0.1", 36 | "copy-webpack-plugin": "^4.0.1", 37 | "cropper": "^4.0.0", 38 | "css-loader": "^0.28.0", 39 | "eslint": "^4.15.0", 40 | "eslint-config-standard": "^10.2.1", 41 | "eslint-friendly-formatter": "^3.0.0", 42 | "eslint-loader": "^2.1.0", 43 | "eslint-plugin-import": "^2.7.0", 44 | "eslint-plugin-node": "^5.2.0", 45 | "eslint-plugin-promise": "^3.4.0", 46 | "eslint-plugin-standard": "^3.0.1", 47 | "eslint-plugin-vue": "^4.0.0", 48 | "extract-text-webpack-plugin": "^3.0.2", 49 | "file-loader": "^1.1.4", 50 | "friendly-errors-webpack-plugin": "^1.6.1", 51 | "html-webpack-plugin": "^2.30.1", 52 | "jquery": "^3.3.1", 53 | "js-cookie": "^2.2.0", 54 | "node-notifier": "^5.1.2", 55 | "node-sass": "^4.13.1", 56 | "optimize-css-assets-webpack-plugin": "^3.2.0", 57 | "ora": "^1.2.0", 58 | "portfinder": "^1.0.13", 59 | "postcss-import": "^11.0.0", 60 | "postcss-loader": "^2.0.8", 61 | "postcss-url": "^7.2.1", 62 | "rimraf": "^2.6.0", 63 | "sass-loader": "^7.1.0", 64 | "semver": "^5.3.0", 65 | "shelljs": "^0.7.6", 66 | "uglifyjs-webpack-plugin": "^1.1.1", 67 | "url-loader": "^1.0.1", 68 | "vue-loader": "^13.3.0", 69 | "vue-style-loader": "^3.0.1", 70 | "vue-template-compiler": "^2.5.17", 71 | "webpack": "^3.6.0", 72 | "webpack-bundle-analyzer": "^2.9.0", 73 | "webpack-dev-server": "^2.9.1", 74 | "webpack-merge": "^4.1.4", 75 | "gulp": "^4.0.2", 76 | "gulp-rev-collector": "^1.3.1", 77 | "gulp-clean": "^0.4.0", 78 | "gulp-rev": "^9.0.0" 79 | }, 80 | "engines": { 81 | "node": ">= 6.0.0", 82 | "npm": ">= 3.0.0" 83 | }, 84 | "browserslist": [ 85 | "> 1%", 86 | "last 2 versions", 87 | "not ie <= 8" 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /html-web/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /html-web/src/assets/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/imgs-upload-srv/a14f57f9f9ee7bac6f939a74017a4d0ef6a58731/html-web/src/assets/images/bg.jpg -------------------------------------------------------------------------------- /html-web/src/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/imgs-upload-srv/a14f57f9f9ee7bac6f939a74017a4d0ef6a58731/html-web/src/assets/images/favicon.ico -------------------------------------------------------------------------------- /html-web/src/assets/scripts/common.js: -------------------------------------------------------------------------------- 1 | import { Message } from 'element-ui'; 2 | import Cookies from "js-cookie"; 3 | import router from '../../router'; 4 | 5 | function formatTime (time) { 6 | time = new Date(parseInt(time, 10)); 7 | return time.getFullYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate() + " " + (time.getHours() < 10 ? "0" + time.getHours() : time.getHours()) + ":" + (time.getMinutes() < 10 ? "0" + time.getMinutes() : time.getMinutes()) + ":" + (time.getSeconds() < 10 ? "0" + time.getSeconds() : time.getSeconds()); 8 | } 9 | 10 | function trimString (value) { 11 | return (value || '').replace(/\s/g, ''); 12 | } 13 | function catchError(error) { 14 | let response = error.response || {}; 15 | let message = response.data && response.data.message || ''; 16 | if (response && response.status && response.status == '401') { 17 | // 登录态失效 18 | Cookies.set('uinfo', ''); 19 | console.log('router: ', router); 20 | 21 | router.push('/'); 22 | } else if (response && response.status && response.status == '400') { 23 | // 参数错误 24 | Message.error({ 25 | message: message ? message : '参数有误,请重试', 26 | center: true 27 | }); 28 | } else if (response && response.status && response.status == '502') { 29 | Message.error({ 30 | message: '网络异常,请重试', 31 | center: true 32 | }); 33 | } else { 34 | Message.error({ 35 | message: '网络异常,请重试', 36 | center: true 37 | }); 38 | } 39 | } 40 | export default { 41 | formatTime, 42 | trimString, 43 | catchError 44 | }; -------------------------------------------------------------------------------- /html-web/src/assets/styles/cropper.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Cropper v4.0.0 3 | * https://github.com/fengyuanchen/cropper 4 | * 5 | * Copyright (c) 2014-2018 Chen Fengyuan 6 | * Released under the MIT license 7 | * 8 | * Date: 2018-04-01T06:26:32.417Z 9 | */ 10 | 11 | .cropper-container { 12 | direction: ltr; 13 | font-size: 0; 14 | line-height: 0; 15 | position: relative; 16 | -ms-touch-action: none; 17 | touch-action: none; 18 | -webkit-user-select: none; 19 | -moz-user-select: none; 20 | -ms-user-select: none; 21 | user-select: none; 22 | } 23 | 24 | .cropper-container img {/*Avoid margin top issue (Occur only when margin-top <= -height) 25 | */ 26 | display: block; 27 | height: 100%; 28 | image-orientation: 0deg; 29 | max-height: none !important; 30 | max-width: none !important; 31 | min-height: 0 !important; 32 | min-width: 0 !important; 33 | width: 100%; 34 | } 35 | 36 | .cropper-wrap-box, 37 | .cropper-canvas, 38 | .cropper-drag-box, 39 | .cropper-crop-box, 40 | .cropper-modal { 41 | bottom: 0; 42 | left: 0; 43 | position: absolute; 44 | right: 0; 45 | top: 0; 46 | } 47 | 48 | .cropper-wrap-box, 49 | .cropper-canvas { 50 | overflow: hidden; 51 | } 52 | 53 | .cropper-drag-box { 54 | background-color: #fff; 55 | opacity: 0; 56 | } 57 | 58 | .cropper-modal { 59 | background-color: #000; 60 | opacity: .5; 61 | } 62 | 63 | .cropper-view-box { 64 | display: block; 65 | height: 100%; 66 | outline-color: rgba(51, 153, 255, 0.75); 67 | outline: 1px solid #39f; 68 | overflow: hidden; 69 | width: 100%; 70 | } 71 | 72 | .cropper-dashed { 73 | border: 0 dashed #eee; 74 | display: block; 75 | opacity: .5; 76 | position: absolute; 77 | } 78 | 79 | .cropper-dashed.dashed-h { 80 | border-bottom-width: 1px; 81 | border-top-width: 1px; 82 | height: 33.33333%; 83 | left: 0; 84 | top: 33.33333%; 85 | width: 100%; 86 | } 87 | 88 | .cropper-dashed.dashed-v { 89 | border-left-width: 1px; 90 | border-right-width: 1px; 91 | height: 100%; 92 | left: 33.33333%; 93 | top: 0; 94 | width: 33.33333%; 95 | } 96 | 97 | .cropper-center { 98 | display: block; 99 | height: 0; 100 | left: 50%; 101 | opacity: .75; 102 | position: absolute; 103 | top: 50%; 104 | width: 0; 105 | } 106 | 107 | .cropper-center:before, 108 | .cropper-center:after { 109 | background-color: #eee; 110 | content: ' '; 111 | display: block; 112 | position: absolute; 113 | } 114 | 115 | .cropper-center:before { 116 | height: 1px; 117 | left: -3px; 118 | top: 0; 119 | width: 7px; 120 | } 121 | 122 | .cropper-center:after { 123 | height: 7px; 124 | left: 0; 125 | top: -3px; 126 | width: 1px; 127 | } 128 | 129 | .cropper-face, 130 | .cropper-line, 131 | .cropper-point { 132 | display: block; 133 | height: 100%; 134 | opacity: .1; 135 | position: absolute; 136 | width: 100%; 137 | } 138 | 139 | .cropper-face { 140 | background-color: #fff; 141 | left: 0; 142 | top: 0; 143 | } 144 | 145 | .cropper-line { 146 | background-color: #39f; 147 | } 148 | 149 | .cropper-line.line-e { 150 | cursor: ew-resize; 151 | right: -3px; 152 | top: 0; 153 | width: 5px; 154 | } 155 | 156 | .cropper-line.line-n { 157 | cursor: ns-resize; 158 | height: 5px; 159 | left: 0; 160 | top: -3px; 161 | } 162 | 163 | .cropper-line.line-w { 164 | cursor: ew-resize; 165 | left: -3px; 166 | top: 0; 167 | width: 5px; 168 | } 169 | 170 | .cropper-line.line-s { 171 | bottom: -3px; 172 | cursor: ns-resize; 173 | height: 5px; 174 | left: 0; 175 | } 176 | 177 | .cropper-point { 178 | background-color: #39f; 179 | height: 5px; 180 | opacity: .75; 181 | width: 5px; 182 | } 183 | 184 | .cropper-point.point-e { 185 | cursor: ew-resize; 186 | margin-top: -3px; 187 | right: -3px; 188 | top: 50%; 189 | } 190 | 191 | .cropper-point.point-n { 192 | cursor: ns-resize; 193 | left: 50%; 194 | margin-left: -3px; 195 | top: -3px; 196 | } 197 | 198 | .cropper-point.point-w { 199 | cursor: ew-resize; 200 | left: -3px; 201 | margin-top: -3px; 202 | top: 50%; 203 | } 204 | 205 | .cropper-point.point-s { 206 | bottom: -3px; 207 | cursor: s-resize; 208 | left: 50%; 209 | margin-left: -3px; 210 | } 211 | 212 | .cropper-point.point-ne { 213 | cursor: nesw-resize; 214 | right: -3px; 215 | top: -3px; 216 | } 217 | 218 | .cropper-point.point-nw { 219 | cursor: nwse-resize; 220 | left: -3px; 221 | top: -3px; 222 | } 223 | 224 | .cropper-point.point-sw { 225 | bottom: -3px; 226 | cursor: nesw-resize; 227 | left: -3px; 228 | } 229 | 230 | .cropper-point.point-se { 231 | bottom: -3px; 232 | cursor: nwse-resize; 233 | height: 20px; 234 | opacity: 1; 235 | right: -3px; 236 | width: 20px; 237 | } 238 | 239 | @media (min-width: 768px) { 240 | .cropper-point.point-se { 241 | height: 15px; 242 | width: 15px; 243 | } 244 | } 245 | 246 | @media (min-width: 992px) { 247 | .cropper-point.point-se { 248 | height: 10px; 249 | width: 10px; 250 | } 251 | } 252 | 253 | @media (min-width: 1200px) { 254 | .cropper-point.point-se { 255 | height: 5px; 256 | opacity: .75; 257 | width: 5px; 258 | } 259 | } 260 | 261 | .cropper-point.point-se:before { 262 | background-color: #39f; 263 | bottom: -50%; 264 | content: ' '; 265 | display: block; 266 | height: 200%; 267 | opacity: 0; 268 | position: absolute; 269 | right: -50%; 270 | width: 200%; 271 | } 272 | 273 | .cropper-invisible { 274 | opacity: 0; 275 | } 276 | 277 | .cropper-bg { 278 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC'); 279 | } 280 | 281 | .cropper-hide { 282 | display: block; 283 | height: 0; 284 | position: absolute; 285 | width: 0; 286 | } 287 | 288 | .cropper-hidden { 289 | display: none !important; 290 | } 291 | 292 | .cropper-move { 293 | cursor: move; 294 | } 295 | 296 | .cropper-crop { 297 | cursor: crosshair; 298 | } 299 | 300 | .cropper-disabled .cropper-drag-box, 301 | .cropper-disabled .cropper-face, 302 | .cropper-disabled .cropper-line, 303 | .cropper-disabled .cropper-point { 304 | cursor: not-allowed; 305 | } 306 | -------------------------------------------------------------------------------- /html-web/src/assets/styles/iconfonts/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "element-icons"; 2 | src: url('iconfont.eot?t=1559788950846'); /* IE9 */ 3 | src: url('iconfont.eot?t=1559788950846#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAA6UAAsAAAAAHCQAAA5GAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCGZAqlIJ4JATYCJANoCzYABCAFhSkHgX8bjhczo/aStBJB9n844MYQrKG+O1iwCDXtY7NCsY6PknBMAasQzsKVsSVwpwX+cRzKpXxUnZSNP55+Sp5SRFRD3deHUhLQ7/e/PfZNLRQBEkPCQ6SEl5BkUjqh0QntQzuIhXivpzl/791dxBELRBDzIAkykOCaIDUPlKt56lD/FbEwn1DRUPdQEUkAzMvmJ4GvWOW10J6KQjhR57aFzZPNCXTtmSBnIhRgG2ydozpOUX7N5kdIvCkommiZ9luvPYGHnLDMshcDYU5J/4VOeL0cQADuAACNsZNz/1oAtL1dh934PJ7/Tibpu9+myYN8zog/EQo/N7m59AOCIzUhdxOGhdz5CTflBymXLpm/VmH7zZ6x+Z6sGqsYe2b+yJUJ6DJnR7j4zCoAhuxmBBR6lDS1wPDFyZLgoMPWJpgxCy+g6IjPkBHPza8ffjsKHUQlua3qllreALJ+rGkSMrce4hPhTXYcm5lI2EEmzghfhrFk4x2TdIlKZr+ALQvqYXfVWDvrCE644U86uQxnZl1We5+339of8EPx6Z/56/9PbxoVo0UfxtF1xj3aGuOWEmzZuUffc+B6dOfJ4dNvyIBePdpNijoN6jChT5AM69Jt1Lg2Y7LC//OENuotPRBujNKnqBgU+qlIYYjKVBigWin0Uq0VeqiOCu1UJ4VJqpshK+oPA2g6mEHQXDAdoMPBTIDOANMHOhNMAF0GJoH2QmGY721Ml0nbwXSDroXCKD+AGQdVgGkDTQczBpoJJoOuh0Lh//RLZH3P8p6AkY6LYyLCcP5yRfsXN79PD5PCcbg7R8s7wNMkqqS8F+CyBQH9opSOhMoxnW6UmlqIQ5eUUxW3BKpTncdQeJC9Iswq8nwnTpqUOwgkuVEU08x7sWjS1CKdFhvaCdNZdfq+/IJri5USt2qtuJ1BNs9rEnYAEAlnv7RcQYA5oQzJcAlFOY31TbTXPqO0iWMtkq7T0qnnGhjXSKy3PobF8ou04U4mE1PP3Zo9Fu3B9wrs//fbp84+BJz1gF92+j66hx9YD3tTn4E8HBTxLDRJdeRgUDRKp8HsCnwnHsd4WQ3ScYTRM0w82IpYuCYRH3m8iAMrXBkHJjWq1iMtSaxZ0GsaZz9W1jhWSPKrs+IbxDOv8Y7S6Ti69pk14Soh8YtsyTPJGZCV4NfEZFZMg6g3vG7wqZbRcDxEe29h/QiaoIiiJJkJb0V87ogMPTnAflHED+pZ7CE5X2T4oHLw/WIWfG+JBkq4CXSsix1Pd9a5ykVyn4lG6GHOo9xKqK3eC/c2hggNaz7P9V2ddsn85heQsKxAw60ozpmcZtuI5nVKEw2Fg4pqQKesumfpschOumgTnnn2qjDvXNTKrnamws1EvZv0XHgxDk0y6NagGUdaWklPQ+8Ju/JJDN3oBnu6Z0leP33YbLvEdnJLckaqBw+D+MsoUQ33SHAtp3HlAHsFFjo3vEN5MxqtunpyHYu3pUdWt1rK5jV3E2H9CkX76SRArYdaXvUwS23VZh+GVp+26AKZOsAFTPnUvULNpHKqNZ1FwKo1u2UnANzqMqSsZZG3l5YTXe68ykZ+lSrolISWAkeajwz/9WCWmIffdFGCK2C6wtWLY9FeN4dp8gyLTXG8xvDAKYMa5mqVDBGUK9K9sKA2+r7E0ILeP5Fo1vI3vrQAR78OFK5/bs6s+LQAewzkCwIJAbACfrWsK9clzWtCx2bYi5grwJ23YD5XoIOHOY7m53DMAmeOE8ZoEiS56vxGYmbbDA1gkqMk/yiheYai6vJaNj8w05ovmoG3LzAWL2vLYKl9G68AAmdsb2AsVbByxbbWHWhNX002v1auQUUqpIha2SDBkqXYuc0MlZesQREv4xjUEkuqwHHzglH5+HrO90VdcKnZRU41vB/PaFhsOZda1HmT75PcUK11Al7EXq6G+qYbpIh2LMOmw85MWkPfY7qry5mr7soPsdN1AbAedBCvjUTzx+2XU4GoYUsgkIeiRw62OzQPV37SAUrda0Ih9ZmMbGVFUblrLXU/dE71AhPxQuaCpOmo7c0s80TcI4Z3e16F7Wd+hsfRp6jZ8d7nB0faQmWl+51Q+uEaNzxaVu7E43YDpP31ajUaZJcO+/nXVV1Mumdj5y4js5071YUzl9X/Wr9tm2o5X7Jf8mkwCw6YY6VfpX7aA0H4SJhv31emDn2Sm7Z/yszsTHXUN9lpuQVdr/08tWtuYWHVvLNrW6wrnFjRfPXlbtfa3GrReuxYdj1bj1u1aGUvelbTYrZhfWOGaoFaN1lfbFOU9/6d6Zvpx8uUHYUBuGO4INQXCsJ9MuWcOLU6bo4yxXfc0I8f9W5j3Iy9hN1Y3xTl3LjGSH1FAoFPkF0QPyhQpQoYOE5AQC3H7k305j7dbboFZPVi0NGxDr95Ey99HRDV3GwbO9CmTQi9wxAaE4ryN1t219Z2O3c5n9/D9TnP6C8AqdpDLWVeCyiOANkIRwdSXNYCh5Vy+eLFFDVvXsrMGBWsxW/dwtdCUerC/QXVKkX5CRhO3JZKoe068Q785TP6jvajF0M8nzZh55YJaBLatRuf3ADAE4ZVSgakZZVO8M/Yhb96TewWkHgXDk75JKzH797FN7DAu/fW4/IxvrbJaTHFomAtWrcetSEtB6tFY7G1q2ljpu88tuvYSPraO9fX4gCxuvwjR+0xBw8HLPgj47X5dVgQxvBo1/y02H4cPoV/muhh8AdKh9JyGxkfgqHYeJORjurLfQhlV69cTlMIHSorFQpI7HjEBzGk1O6y5Bo6SyJTUUyW8Kw8u6aMDUcXPYsPWbJ6XmoWKzt5D4riQPz7vXBEQcPOtybsonx/98IW8ShgkZvHBZONYzMaZ+SVzOgtLrEtfttTkKmKsEUTfMQ+js1unvJhKSvkJNn88uEpnoorbX6qkCva1PZdkputiRU9ELMH4xF62SeZnjAv9NHb3NPds5nKmPfA58HJs+9p92veDhm+M9pB7LwTva25T3sPUjzNQAAQhmAAmuZ+A5veSGn9nbt3/2+QhOuQwaAJUzaCdOGSBog1ONrQrTrdO60w9rxTeh2aj07qX4IFF0C3ZlG6k0OC5XEjQI5Grlwx0jrqgIVAStQQ21EBnWf+XgWqlhYVloOBsTUA5wAghMgrht+7U9mxbPnuc8qFTrFOBeMxqA2rCzueeMwg4PY3x3badzbF9nMB1v4oxUaPxkpdld4xMTOELsLOktLW1k6HEGFXS8uMzEw7f0ZGecW6r24kEGBNUNooDk+Wu6aIxHl5jx9PLDaPDPTisug1E1mTWFFrMlXKwZwhqhXnisKLDt7gk33+9+z4N8qPk0p3P3UJWUz6q8On1twipVLyloGhprnFuAuvnyE4HAL9jCE0Nhp0rDZQhQwDo5AyP/gigWfBM/CB9SRKu9icfIMQ+5jS6RpNTAWhIc+TGuIcqSYV1KCiadNSWKElKKqiFDQ17RypsZCmIRUMU+H0aa4sV14XmhgKAT4n5Qqqiu4TyuaDhqRlYD2XmVjBNDXC3yl3CFdX4k5EubrcIbmLC2mIXFy5+7q7id+/iG7Y79/aA+LXb7f4r1/d7lWorQ2hV3WIaiz7cBlWWACyAHbIMHhBAWYACXuO8kP5clTZ1l6JydHeXkI5P1TiypMj7YYN2jYpWr8B18p5ofDNK4rZzfTjnGLqTUBrOwJe2xnhRqbvX8mXeb7o9ZiqMQ5P5SgEyZ+eNAVIsuW1N2rDc+p8GeeZSWMiE3P8QvwTsiO1SczzDF+GkbocMHhpsuMtx+SlAYMvU0aIqM+9pOeWcLovLWZy9ZeMnuEFeKXxQyq/hJ8qMAoYD5J26cYHwTdnfrp0j8NcLEgdP+dSiDiUZ88LFYdcslvL3I666g8Usky6t+w+qf5DH7ONr+G4baMPvc61OEOhbEqql/axH9i+Y/ex3rGofu4/P5E/2YHeTf/SnnTkqRnOQ+lDTwq5GppbZK1fV+fooHfWmoEDk9lvY3w+yQMHjrY2W+/t6lLbQF+DMtelYmNQaqrrqkHnmalMWUW9jNtg76eU3pco2cxjhd6pboEph1zKc709hHSh7XzM5C2498LKPclzbh3WJQE6EpfELXPLHnPzjcDb9LtwFEf5iqssGAXDzQ8HMA9Ia9B2U1ppOj5CWuwGLpPuuoNLpB/JgNOG3+4zXgkAgB9Fr5VQA+p16b3A3E+BC6Ve2/BE6bAzePr72/TghdTg0ein9MgaXIpPyErGmX82z4zwiUjpSPdwCtJOV/C8Bzr7sywAgCeiL39sjQTDTVWaan7cVwb7/683t8WrlpgwG7cuNViH6PYo8KcVI0Di9E87x7D4qXz8l3ivzc+wWuiZKa2OlIiuezf+tHo65GvVrKNkpONen2UjR+2W5KTXpnN2P5OLbudym1435S7bHszebdjjJORObPmoyMGUz3LU50pOpvx2yv6hyMVYmMdtIvdTucttTLXAbmtr8Ic3hhE4fX3qZCn7HELW6NAjyy7wyfqNMHSG9zU9X/oPromyPu2PoswvZHC2k9RbOIt47Zl6/ZnD6tB1pAtTAyv7KFIuh4NXN7G31I/WvTGMwOmuT/3mZCn7nLIOHXfukd++k5as3wjDhphs6kWppxD/bzCbJPkf6k7/TNaTSr7CiF5rlXPWWzizFPGaY2YZve6zEWgDSCNdEfMGSJN9MUFKuXS4t+apZF+wtZ9XpJFb5+2QRyBRaAwWhycQSWQKlaYbpmU7rq+WS/d8qTMVVe6uLF9E/Y4GmVtmKashO9owSQ8X2HXwsr1ug1OIsnR0zx0ZN6F20SMP5P3cPJSskJzMH8m8TJpbky262VDBRfUmddMWz2lRhnoxl+rpBvUkZePQIeUlWyrPmR9eMSk8kswLuFIejQAA') format('woff2'), 5 | url('iconfont.woff?t=1559788950846') format('woff'), 6 | url('iconfont.ttf?t=1559788950846') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1559788950846#element-icons') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .element-icons { 11 | font-family: "element-icons" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .el-icon-crop:before { 19 | content: "\e68a"; 20 | } 21 | 22 | .el-icon-pwdp:before { 23 | content: "\e624"; 24 | } 25 | 26 | .el-icon-zoom-out:before { 27 | content: "\e69a"; 28 | } 29 | 30 | .el-icon-zoomin:before { 31 | content: "\ef88"; 32 | } 33 | 34 | .el-icon-undo:before { 35 | content: "\e8a4"; 36 | } 37 | 38 | .el-icon-rotate-left:before { 39 | content: "\e846"; 40 | } 41 | 42 | .el-icon-rotate-right:before { 43 | content: "\e61b"; 44 | } 45 | 46 | .el-icon-download:before { 47 | content: "\e651"; 48 | } 49 | 50 | .el-icon-ok:before { 51 | content: "\e62d"; 52 | } 53 | 54 | .el-icon-menuoff:before { 55 | content: "\e77c"; 56 | } 57 | 58 | .el-icon-menuon:before { 59 | content: "\e77d"; 60 | } 61 | 62 | .el-icon-eidt:before { 63 | content: "\e614"; 64 | } 65 | 66 | .el-icon-xiazai:before { 67 | content: "\e613"; 68 | } 69 | 70 | .el-icon-cancel:before { 71 | content: "\e689"; 72 | } 73 | 74 | .el-icon-userp:before { 75 | content: "\e601"; 76 | } 77 | 78 | .el-icon-mail:before { 79 | content: "\e609"; 80 | } 81 | 82 | .el-icon-key:before { 83 | content: "\e648"; 84 | } 85 | 86 | .el-icon-pwd:before { 87 | content: "\e602"; 88 | } 89 | 90 | .el-icon-menu:before { 91 | content: "\e6c1"; 92 | } 93 | 94 | .el-icon-move:before { 95 | content: "\e8952"; 96 | } 97 | 98 | .el-icon-user:before { 99 | content: "\e7a2"; 100 | } 101 | 102 | .el-icon-delete-copy:before { 103 | content: "\e848"; 104 | } 105 | 106 | .el-icon-zoom-in:before { 107 | content: "\e84a"; 108 | } 109 | 110 | .el-icon-fuzhi:before { 111 | content: "\e800"; 112 | } 113 | 114 | .el-icon-exit:before { 115 | content: "\e673"; 116 | } 117 | 118 | .el-icon-person:before { 119 | content: "\e61c"; 120 | } 121 | 122 | -------------------------------------------------------------------------------- /html-web/src/assets/styles/iconfonts/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/imgs-upload-srv/a14f57f9f9ee7bac6f939a74017a4d0ef6a58731/html-web/src/assets/styles/iconfonts/iconfont.eot -------------------------------------------------------------------------------- /html-web/src/assets/styles/iconfonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/imgs-upload-srv/a14f57f9f9ee7bac6f939a74017a4d0ef6a58731/html-web/src/assets/styles/iconfonts/iconfont.ttf -------------------------------------------------------------------------------- /html-web/src/assets/styles/iconfonts/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/imgs-upload-srv/a14f57f9f9ee7bac6f939a74017a4d0ef6a58731/html-web/src/assets/styles/iconfonts/iconfont.woff -------------------------------------------------------------------------------- /html-web/src/assets/styles/iconfonts/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/imgs-upload-srv/a14f57f9f9ee7bac6f939a74017a4d0ef6a58731/html-web/src/assets/styles/iconfonts/iconfont.woff2 -------------------------------------------------------------------------------- /html-web/src/assets/styles/index.css: -------------------------------------------------------------------------------- 1 | @import './reset.css'; 2 | @import './iconfonts/iconfont.css'; 3 | @import './cropper.css'; -------------------------------------------------------------------------------- /html-web/src/assets/styles/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } -------------------------------------------------------------------------------- /html-web/src/components/Login.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 137 | 138 | 139 | 192 | -------------------------------------------------------------------------------- /html-web/src/components/Person.vue: -------------------------------------------------------------------------------- 1 | 28 | 115 | -------------------------------------------------------------------------------- /html-web/src/components/common/CommonFooter.vue: -------------------------------------------------------------------------------- 1 | 7 | 18 | -------------------------------------------------------------------------------- /html-web/src/components/common/CommonHeader.vue: -------------------------------------------------------------------------------- 1 | 51 | 127 | -------------------------------------------------------------------------------- /html-web/src/components/common/ImgUpload.vue: -------------------------------------------------------------------------------- 1 | 17 | 144 | -------------------------------------------------------------------------------- /html-web/src/components/common/Person/HeadImg.vue: -------------------------------------------------------------------------------- 1 | 41 | 136 | -------------------------------------------------------------------------------- /html-web/src/components/common/Person/Token.vue: -------------------------------------------------------------------------------- 1 | 54 | 142 | -------------------------------------------------------------------------------- /html-web/src/components/common/Search.vue: -------------------------------------------------------------------------------- 1 | 42 | 130 | 131 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /html-web/src/components/common/Timer.vue: -------------------------------------------------------------------------------- 1 | 7 | 32 | -------------------------------------------------------------------------------- /html-web/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import './assets/styles/index.css'; 7 | import './assets/styles/animate.min.css'; 8 | import store from './store' 9 | import $axios from 'axios'; 10 | import Commonjs from './assets/scripts/common.js'; 11 | // 全局配置对象,size用于改变组件的默认尺寸,zIndex设置弹框的初始z-index 12 | Vue.prototype.$ELEMENT = { size: 'small', zIndex: 3000 }; 13 | 14 | Vue.config.productionTip = false 15 | 16 | Vue.prototype.catchError = Commonjs.catchError; 17 | 18 | /* eslint-disable no-new */ 19 | import ElementUI from 'element-ui'; 20 | import 'element-ui/lib/theme-chalk/index.css' 21 | import { createNamespacedHelpers } from 'vuex'; 22 | $axios.interceptors.response.use(function (response) { 23 | // Do something with response data 24 | return response; 25 | }, function (error) { 26 | console.log('interceptorserror: ', error); 27 | Commonjs.catchError(error); 28 | // Do something with response error 29 | return Promise.reject(error); 30 | }); 31 | $axios.defaults.timeout = 60000; 32 | 33 | 34 | Vue.prototype.$axios = $axios; 35 | Vue.use(ElementUI) 36 | // Vue.use($axios); 37 | new Vue({ 38 | el: '#app', 39 | router, 40 | store, 41 | components: { App }, 42 | template: '' 43 | }) -------------------------------------------------------------------------------- /html-web/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Login from '@/components/Login' 4 | import Index from '@/components/Index' 5 | import Person from '@/components/Person' 6 | import Category from "@/components/Category"; 7 | import HeadImg from "@/components/common/Person/HeadImg"; 8 | import MailCode from "@/components/common/Person/MailCode"; 9 | import Token from "@/components/common/Person/Token"; 10 | import Share from "@/components/Share"; 11 | Vue.use(Router) 12 | 13 | export default new Router({ 14 | routes: [{ 15 | path: '/', 16 | name: 'login', 17 | component: Login 18 | }, 19 | { 20 | path: '/index', 21 | name: 'index', 22 | component: Index 23 | }, 24 | { 25 | path: '/person', 26 | name: 'person', 27 | component: Person 28 | }, 29 | { 30 | path: '/category', 31 | name: 'category', 32 | component: Category 33 | }, 34 | { 35 | path: '/share', 36 | name: 'share', 37 | component: Share 38 | }, 39 | { 40 | path: '*', 41 | redirect: '/' 42 | } 43 | ], 44 | mode: 'history' 45 | }) -------------------------------------------------------------------------------- /html-web/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import vuex from 'vuex' 3 | import $axios from 'axios' 4 | 5 | Vue.use(vuex); 6 | 7 | export default new vuex.Store({ 8 | state: { 9 | email: "", 10 | headImg: "", 11 | id: "", 12 | nickname: "", 13 | username: "", 14 | sortList: [] 15 | }, 16 | mutations: { 17 | changeInfo(state, data) { 18 | for (var item in data) { 19 | localStorage.setItem(item, (data[item] ? data[item] : '')); 20 | state[item] = data[item]; 21 | } 22 | }, 23 | updateSortList(state, sortList) { 24 | state.sortList = sortList; 25 | } 26 | }, 27 | actions: { 28 | getSortList({ state, commit }, params) { 29 | return new Promise((resolve, reject) => { 30 | let sortList = [...state.sortList] || []; 31 | // type==get获取分类的操作 32 | // type=update编辑新增分类的操作,需要重新获取分类列表 33 | if (params.type == 'get' && sortList.length > 0) { 34 | commit('updateSortList', sortList); 35 | resolve(sortList); 36 | } else { 37 | $axios.get('/api/sort/list', { params: { sortId: params.sortId, sortName: params.sortName } }).then((res) => { 38 | let sortList = []; 39 | // if (params.type == 'get') { 40 | // sortList = [{ sortId: "", sortName: "全部分类" }, ...(res.data && res.data.data && res.data.data.list || [])]; 41 | // } else { 42 | // } 43 | sortList = [...(res.data && res.data.data && res.data.data.list || [])]; 44 | 45 | commit('updateSortList', sortList); 46 | resolve(sortList); 47 | }) 48 | } 49 | 50 | }) 51 | } 52 | } 53 | }) -------------------------------------------------------------------------------- /html-web/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/imgs-upload-srv/a14f57f9f9ee7bac6f939a74017a4d0ef6a58731/html-web/static/favicon.ico -------------------------------------------------------------------------------- /html-web/update.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 在线图床服务 8 | 31 | 32 | 33 | 34 |
35 |

网络调整,图床服务升级中...

36 |

提示:升级期间不影响外链使用哟~

37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /node-srv/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/11 4 | * @Description: app.js 5 | */ 6 | require('./extends'); 7 | 8 | const basicConfig = require('./config/basic'); 9 | const mongodb = require('./lib/mongodb'); 10 | const Koa = require('koa'); 11 | const bodyParser = require('koa-bodyparser'); 12 | const routerScanner = require('./routers/routerScanner'); 13 | 14 | mongodb.open(function () { 15 | 16 | const app = new Koa(); 17 | 18 | app.proxy = true; 19 | 20 | app.use(bodyParser({ 21 | enableTypes: ['json', 'form'] 22 | })); 23 | 24 | app.use(require('./middleware/basic')); 25 | app.use(require('./middleware/auth')); 26 | 27 | app.use(routerScanner.routes()); 28 | app.use(routerScanner.allowedMethods()); 29 | 30 | app.listen(basicConfig.port, () => { 31 | console.log('=> koa: '.cyan + 'listened port = '.grey + String(basicConfig.port).blue); 32 | }); 33 | }); -------------------------------------------------------------------------------- /node-srv/config/mailTemplate/ForgetCode.html: -------------------------------------------------------------------------------- 1 | 21 |
22 |
23 | 25 | 26 | 27 | 112 | 113 | 114 |
28 | 30 | 31 | 32 | 104 | 105 | 106 |
33 |
34 |

亲爱的 {0}

36 |

37 |         你正在找回密码。 38 |

39 | 41 | 42 | 43 | 74 | 75 | 76 |
44 |

45 | 验证码

46 | 48 | 49 | 50 | 53 | 56 | 59 | 60 | 61 | 64 | 67 | 70 | 71 | 72 |
51 | 账号 52 | 54 | 验证码 55 | 57 | 有效时间 58 |
62 | {1} 63 | 65 | {2} 66 | 68 | 15分钟 69 |
73 |
77 |
78 |

本站为非盈利性服务,意在为你提供在线存储解决方案。

79 |

请勿上传任何违反国家法律的图片!

80 |

互联网为开放网络,如果你泄漏你的在线图片地址,其他任何人均能访问。

81 |

为了你的个人隐私安全,请勿上传敏感信息。

82 |

如果你有任何的建议或反馈,可邮件至 team@thankjava.com

83 |

科技向善,互利互助。

84 |

85 |

邮件由系统自动发送,请勿回复。
86 |

87 | 89 | 90 | 91 | 100 | 101 | 102 |
92 |
93 | 94 | 96 | 98 | 99 |
103 |
107 |

108 |

110 | © Copyright (C) 2018-2021 , All rights reserved .

111 |
115 | -------------------------------------------------------------------------------- /node-srv/config/mailTemplate/RegisteCode.html: -------------------------------------------------------------------------------- 1 | 21 |
22 |
23 | 25 | 26 | 27 | 112 | 113 | 114 |
28 | 30 | 31 | 32 | 104 | 105 | 106 |
33 |
34 |

亲爱的 {0}

36 |

37 |         你正在注册图床服务(https://imgs.acexy.cn)。 38 |

39 | 41 | 42 | 43 | 74 | 75 | 76 |
44 |

45 | 验证码

46 | 48 | 49 | 50 | 53 | 56 | 59 | 60 | 61 | 64 | 67 | 70 | 71 | 72 |
51 | 注册账号 52 | 54 | 验证码 55 | 57 | 有效时间 58 |
62 | {1} 63 | 65 | {2} 66 | 68 | 15分钟 69 |
73 |
77 |
78 |

本站为非盈利性服务,意在为你提供在线存储解决方案。

79 |

请勿上传任何违反国家法律的图片!

80 |

互联网为开放网络,如果你泄漏你的在线图片地址,其他任何人均能访问。

81 |

为了你的个人隐私安全,请勿上传敏感信息。

82 |

如果你有任何的建议或反馈,可邮件至 team@thankjava.com

83 |

科技向善,互利互助。

84 |

85 |

邮件由系统自动发送,请勿回复。
86 |

87 | 89 | 90 | 91 | 100 | 101 | 102 |
92 |
93 | 94 | 96 | 98 | 99 |
103 |
107 |

108 |

110 | © Copyright (C) 2018-2021 , All rights reserved .

111 |
115 | -------------------------------------------------------------------------------- /node-srv/config/mailTemplate/UpdateMailCode.html: -------------------------------------------------------------------------------- 1 | 21 |
22 |
23 | 25 | 26 | 27 | 112 | 113 | 114 |
28 | 30 | 31 | 32 | 104 | 105 | 106 |
33 |
34 |

亲爱的 {0}

36 |

37 |         你正在修改邮箱地址。 38 |

39 | 41 | 42 | 43 | 74 | 75 | 76 |
44 |

45 | 验证码

46 | 48 | 49 | 50 | 53 | 56 | 59 | 60 | 61 | 64 | 67 | 70 | 71 | 72 |
51 | 当前邮箱 52 | 54 | 验证码 55 | 57 | 有效时间 58 |
62 | {1} 63 | 65 | {2} 66 | 68 | 15分钟 69 |
73 |
77 |
78 |

本站为非盈利性服务,意在为你提供在线存储解决方案。

79 |

请勿上传任何违反国家法律的图片!

80 |

互联网为开放网络,如果你泄漏你的在线图片地址,其他任何人均能访问。

81 |

为了你的个人隐私安全,请勿上传敏感信息。

82 |

如果你有任何的建议或反馈,可邮件至 team@thankjava.com

83 |

科技向善,互利互助。

84 |

85 |

邮件由系统自动发送,请勿回复。
86 |

87 | 89 | 90 | 91 | 100 | 101 | 102 |
92 |
93 | 94 | 96 | 98 | 99 |
103 |
107 |

108 |

110 | © Copyright (C) 2018-2021 , All rights reserved .

111 |
115 | -------------------------------------------------------------------------------- /node-srv/const/allowedMimeType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/19 4 | * @Description: 允许上传的文件MimeType 5 | */ 6 | module.exports = [ 7 | "image/png", 8 | "image/jpeg", 9 | "image/gif" 10 | ]; -------------------------------------------------------------------------------- /node-srv/const/code.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/13 4 | * @Description: 5 | */ 6 | module.exports = { 7 | 8 | 9 | INVALID_ACCOUNT: "1000", // 不存在的用户账号 10 | PASSWORD_ERROR: "1001", // 密码错误 11 | // THE_SAME_PASSWORD: "1002", // 修改的密码和原密码相同 12 | // INVALID_NEW_PASSWORD: "1003", // 密码长度过低/过高 13 | EXISTING_MAIL: "1004", // 存在的邮箱地址 14 | EXISTING_USERNAME: "1005", // 存在的用户名 15 | SEND_MAIL_FAILED: "1006", // 邮件发送失败 16 | EXPIRED_MAIL_CODE: "1007", // 验证码过期 17 | INVALID_MAIL_CODE: "1008", // 验证码错误 18 | ERROR_REGISTE_DATA: "1009", // 邮箱验证数据异常 19 | ERROR_ACCOUNT_OR_EMAIL: "1010", // 无效的邮箱或者帐号 20 | 21 | UNKNOWN_SORT_ID: "1011", // 不存在的分类ID 22 | BAD_OBJECT_ID: "1012", // 无效的object_id 23 | UNKNOWN_IMG_ID: "1013", // 无效的图片id 24 | MAX_IMG_TAG: "1014", // 标签超出上限 25 | EXISTS_IMG_TAG: "1015", // 已存在的标签 26 | EXISTING_SORT_NAME: "1016", // 已存在的分类 27 | SHARED_SORT_ID:"1017", // 该分类已经分享 28 | NOT_EXISTED_ANY_IMG:'1018', // 该分类下无任何图片 29 | INVALID_SHARE_ID:'1019', // 无效的请求分享ID 30 | EXPIRED_SHARE:'1020', // 该分享已失效 31 | 32 | REPEAT_CONFIRMED: '1021', // 重复申请恢复图片访问 33 | }; -------------------------------------------------------------------------------- /node-srv/const/cookiesName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/4 4 | * @Description: cookie name 5 | */ 6 | module.exports = { 7 | COOKIE_NAME_TOKEN: 'token', 8 | COOKIE_NAME_UINFO: 'uinfo', 9 | }; -------------------------------------------------------------------------------- /node-srv/const/mailType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/7/16 4 | * @Description: 5 | */ 6 | module.exports.config = { 7 | // 注册邮件验证码 8 | REGISTE_CODE: { 9 | title: "图床服务:注册", 10 | templateName: "RegisteCode.html" 11 | }, 12 | FORGET_CODE: { 13 | title: "图床服务:找回密码", 14 | templateName: "ForgetCode.html" 15 | }, 16 | UPDATE_MAIL_CODE: { 17 | title: '图床服务:修改邮箱', 18 | templateName: "UpdateMailCode.html" 19 | } 20 | }; 21 | 22 | 23 | module.exports.type = { 24 | REGISTE_CODE: 'REGISTE_CODE', 25 | FORGET_CODE: 'FORGET_CODE', 26 | UPDATE_MAIL_CODE: 'UPDATE_MAIL_CODE' 27 | }; -------------------------------------------------------------------------------- /node-srv/const/redisKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/1 4 | * @Description: redisKey管理 5 | */ 6 | const util = require('util'); 7 | 8 | module.exports.AUTH_TOKEN = function (token) { // string 9 | return util.format('imgs-upload-srv:auth_token:%s', token); 10 | }; // 用户token信息鉴权缓存 11 | 12 | module.exports.REG_MAIL_CODE = token => { 13 | return util.format('imgs-upload-srv:reg_mail_code:%s', token); 14 | };// 用户注册邮件验证码 15 | 16 | module.exports.FORGET_MAIL_CODE = token => { 17 | return util.format('imgs-upload-srv:forget_mail_code:%s', token); 18 | };// 找回密码邮件验证码 19 | 20 | module.exports.UPDATE_MAIL_CODE = token => { 21 | return util.format('imgs-upload-srv:update_mail_code:%s', token); 22 | };// 修改邮箱验证码 23 | 24 | // 图片编号 25 | module.exports.IMG_INCR_NO = _ => "imgs-upload-srv:img_incr_no"; -------------------------------------------------------------------------------- /node-srv/const/reg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/7/16 4 | * @Description: 5 | */ 6 | module.exports = { 7 | // 判断邮箱的正则 8 | IS_MAIL: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{1,4}){1,2})$/, 9 | // 用户账号校验正则 10 | USERNAME: /^[a-zA-Z0-9_-]{4,16}$/, 11 | PASSWORD: /^[a-zA-Z0-9_-]{6,20}$/, 12 | // token 脱敏 13 | TOKEN_ENCODE: /^(\w{3})\w*(\w{4})$/, 14 | // token 脱敏 15 | USERNAME_ENCODE: /^(\w{2})\w*(\w{1})$/, 16 | // 邮箱脱敏 17 | MAIL_ENCODE: /(.{2}).+(.{2}@.+)/, 18 | }; -------------------------------------------------------------------------------- /node-srv/const/unlogPathname.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 不允许系统记录出入参数日志的链接 3 | * @type {string[]} 4 | */ 5 | module.exports = [ 6 | '/login', 7 | '/registe', 8 | '/forget_pwd', 9 | '/reset_pwd', 10 | '/view', 11 | ]; -------------------------------------------------------------------------------- /node-srv/const/whitePathname.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/4 4 | * @Description: 不进行token校验的url前缀汇总 5 | */ 6 | module.exports = [ 7 | '/login', 8 | '/logout', // 登出不鉴权 9 | '/send_reg_mail_code', 10 | '/registe', 11 | '/forget_pwd', 12 | '/reset_pwd', 13 | '/view', 14 | '/share/query', 15 | ]; -------------------------------------------------------------------------------- /node-srv/controller/baseController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/5/28 4 | * @Description: 5 | */ 6 | 7 | const code = require('../const/code'); 8 | const reg = require('../const/reg'); 9 | 10 | /** 11 | * 移除cookie 12 | * @param ctx 13 | * @param cookieNames 14 | */ 15 | module.exports.removeCookie = (ctx, cookieNames) => { 16 | if (Array.isArray(cookieNames)) { 17 | cookieNames.every((element) => { 18 | ctx.cookies.set(element, null); 19 | return true; 20 | }) 21 | } 22 | ctx.cookies.set(cookieNames, null); 23 | }; 24 | 25 | /** 26 | * 获取cookie 27 | * @param ctx 28 | * @param cookieName 29 | * @returns {*} 30 | */ 31 | module.exports.getCookie = (ctx, cookieName) => ctx.cookies.get(cookieName); 32 | 33 | // 设置cookie 34 | module.exports.setCookie = (ctx, cookieName, value, option) => { 35 | ctx.cookies.set(cookieName, value, option); 36 | }; 37 | 38 | /** 39 | * 响应请求参数错误 40 | * @param ctx 41 | * @param message 42 | */ 43 | module.exports.response400 = (ctx, message) => { 44 | ctx.response.status = 400; 45 | ctx.body = responseBuilder(undefined, message ? message : '请求参数错误'); 46 | }; 47 | 48 | /** 49 | * 响应处理失败的请求 50 | * @param ctx 51 | * @param message 52 | */ 53 | module.exports.response500 = (ctx, message) => { 54 | ctx.response.status = 500; 55 | ctx.body = responseBuilder(undefined, message ? message : '系统异常'); 56 | }; 57 | 58 | /** 59 | * 响应正常请求 60 | * @param ctx 61 | * @param message 62 | * @param data 63 | */ 64 | module.exports.response = (ctx, message, data) => { 65 | ctx.body = responseBuilder(undefined, message, data); 66 | }; 67 | 68 | /** 69 | * 响应含有code的正常请求 70 | * @param ctx 71 | * @param code 72 | * @param message 73 | * @param data 74 | */ 75 | module.exports.responseWithCode = (ctx, code, message, data) => { 76 | ctx.body = responseBuilder(code, message, data); 77 | }; 78 | 79 | // 创建返回对象数据 80 | const responseBuilder = (code, message, data) => { 81 | 82 | if (!data && typeof message !== 'string') { 83 | data = message; 84 | message = undefined; 85 | } 86 | 87 | return { 88 | code: code, 89 | message: message || '请求完成', 90 | data: data 91 | }; 92 | }; 93 | 94 | module.exports.CODE = code; 95 | module.exports.REG = reg; 96 | 97 | module.exports.CONSTS = { 98 | AUTH_COOKIE_EXPIRES_DAY: 30, // 认证cookie的过期时间 (天) 99 | SHORT_AUTH_COOKIE_EXPIRES_DAY: 0.1, // 非长期登录的token过期时间 100 | REG_MAIL_MINUTE: 15, // 注册邮件验证码邮箱时间 101 | FORGET_MAIL_MINUTE: 15, // 忘记密码邮件验证码邮箱时间 102 | MAX_LIVING_TOKEN_NUMBER: 10, // 最大长期登录的token数 103 | UPDATE_MAIL_MINUTE: 15, // 修改邮箱验证时间 104 | }; -------------------------------------------------------------------------------- /node-srv/controller/share.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2019/4/25 4 | * @Description: 处理分享 5 | */ 6 | const Router = require('koa-router'); 7 | const imagesModel = require('../models/images'); 8 | const shareListModel = require('../models/shareList'); 9 | const shareImgModel = require('../models/shareImg'); 10 | const sortsModel = require('../models/sorts'); 11 | const userModel = require('../models/user'); 12 | 13 | const baseController = require('./baseController'); 14 | const util = require('../lib/util'); 15 | const baseConfig = require('../config/basic'); 16 | 17 | const SHARE_PREFIX = '/share/'; 18 | 19 | const SHARE_PARAM_PREFIX = '?shareId='; 20 | 21 | module.exports = new Router( 22 | 23 | ).post('create_imgs', async ctx => { 24 | 25 | let params = ctx.request.body; 26 | if (!params || !params.imgIds || !Array.isArray(params.imgIds) || params.imgIds.length === 0) 27 | return baseController.response400(ctx, '缺失参数或参数错误: imgIds'); 28 | 29 | let imgs = await imagesModel.selectOwnByIds(params.imgIds, ctx.state.authInfo.id); 30 | if (!imgs || imgs.length !== params.imgIds.length) return baseController.response400(ctx, '存在无效的imgId 或指定的imgId无权操作'); 31 | 32 | // 保存分类清单 33 | let shareId = (await shareListModel.save({ 34 | userId: ctx.state.authInfo.id, 35 | type: 'image', 36 | status: true 37 | }))._id; 38 | 39 | if (!shareId) return baseController.response500(ctx); 40 | 41 | for (let index = 0; index < imgs.length; index++) { 42 | await shareImgModel.save({ 43 | imgId: imgs[index]._id, 44 | shareId: shareId, 45 | status: true, 46 | urn: util.md5(imgs[index].urn + Date.now()) 47 | }); 48 | } 49 | 50 | baseController.response(ctx, '处理完成', { 51 | shareId: shareId, 52 | url: baseConfig.imgUri + SHARE_PREFIX + shareId 53 | }) 54 | 55 | }).post('create_sort', async ctx => { 56 | 57 | let params = ctx.request.body; 58 | if (!params || !params.sortId) return baseController.response400(ctx, '缺失参数或参数错误: sortId'); 59 | 60 | let sort = await sortsModel.selectByCondition({ 61 | userId: ctx.state.authInfo.id, 62 | _id: params.sortId 63 | }); 64 | 65 | if (!sort) { 66 | return baseController.responseWithCode(ctx, baseController.CODE.UNKNOWN_SORT_ID, '无效的sortId'); 67 | } 68 | 69 | if (sort.shared) { // 严格意义上这里存在非线程安全的并发问题 70 | return baseController.responseWithCode(ctx, baseController.CODE.SHARED_SORT_ID, '该分类已经分享', { 71 | shareId: shareId, 72 | url: baseConfig.shareUri + SHARE_PARAM_PREFIX + shareId 73 | }); 74 | } 75 | 76 | let images = await imagesModel.selectByCondition({ 77 | userId: ctx.state.authInfo.id, 78 | sortId: params.sortId 79 | }); 80 | 81 | if (!images || images.length === 0) { 82 | return baseController.responseWithCode(ctx, baseController.CODE.NOT_EXISTED_ANY_IMG, '该分类下无任何图片'); 83 | } 84 | 85 | let shareId = (await shareListModel.save({ 86 | userId: ctx.state.authInfo.id, 87 | type: 'sort', 88 | status: true, 89 | sortId: params.sortId 90 | }))._id; 91 | 92 | // 更新分类分享状态为已分享 93 | await sortsModel.updateOwnById({ 94 | shared: true, 95 | shareId: shareId, 96 | }, params.sortId, ctx.state.authInfo.id); 97 | 98 | let array = []; 99 | for (let len = images.length, index = 0; index < len; index++) { 100 | array.push({ 101 | imgId: images[index]._id, 102 | shareId: shareId, 103 | status: true, 104 | urn: util.md5(images[index].urn + Date.now()) 105 | }); 106 | } 107 | 108 | await shareImgModel.saveMany(array); 109 | 110 | baseController.response(ctx, '分享成功(仅分享分类当前含有的图片, 后续图片不会自动分享)', { 111 | shareId: shareId, 112 | url: baseConfig.shareUri + SHARE_PARAM_PREFIX + shareId 113 | }); 114 | 115 | }).get('query', async ctx => { // 通过分享Id查询该分享的具体分享图片 116 | 117 | let params = ctx.query; 118 | 119 | if (!params || !params.shareId) return baseController.response400(ctx, '无效的shareId'); 120 | if (params.shareId.length !== 12 && params.shareId.length !== 24) { 121 | return baseController.response400(ctx, '无效的shareId'); 122 | } 123 | 124 | let shareList = await shareListModel.selectOneById(params.shareId); 125 | if (!shareList) { 126 | return baseController.responseWithCode(ctx, baseController.CODE.INVALID_SHARE_ID, '无效的shareId'); 127 | } 128 | 129 | if (!shareList.status) { 130 | return baseController.responseWithCode(ctx, baseController.CODE.EXPIRED_SHARE, '该分享已经失效'); 131 | } 132 | 133 | 134 | let shareUser = await userModel.selectById(shareList.userId); 135 | let shareUsername; 136 | 137 | if (!shareUser.nickname) { 138 | shareUsername = shareUser.username.replace(baseController.REG.USERNAME_ENCODE, "$1**$2"); 139 | } else { 140 | shareUsername = shareUser.nickname; 141 | } 142 | 143 | let shareImgs = await shareImgModel.selectManyByCondition({ 144 | shareId: params.shareId, 145 | status: true 146 | }); 147 | 148 | let array = []; 149 | let imgObj; 150 | for (let len = shareImgs.length, i = 0; i < len; i++) { 151 | imgObj = {}; 152 | imgObj.uri = baseConfig.imgUri + SHARE_PREFIX + shareImgs[i].urn; 153 | imgObj.tags = (await imagesModel.selectById(shareImgs[i].imgId)).tags; 154 | array.push(imgObj); 155 | } 156 | 157 | baseController.response(ctx, { 158 | sharedImgs: array, 159 | shareUser: shareUsername 160 | }); 161 | 162 | }).delete('del', async ctx => { 163 | 164 | let params = ctx.query; 165 | if (!params || !params.shareId) return baseController.response400(ctx, '无效的shareId'); 166 | 167 | if (params.shareId.length !== 12 && params.shareId.length !== 24) { 168 | return baseController.response400(ctx, '不合法的shareId'); 169 | } 170 | 171 | let shareList = await shareListModel.selectOneOfOwnById(params.shareId, ctx.state.authInfo.id); 172 | if (!shareList.status) { 173 | return baseController.responseWithCode(ctx, baseController.CODE.EXPIRED_SHARE, '该分享已经失效'); 174 | } 175 | 176 | await shareImgModel.updateManyByShareId(params.shareId, { 177 | status: false 178 | }); 179 | 180 | if (shareList.type === 'sort') { 181 | await sortsModel.updateForDeleteShareId(shareList.sortId); 182 | } 183 | 184 | await shareListModel.updateById({ 185 | status: false 186 | }, params.shareId); 187 | 188 | baseController.response(ctx); 189 | }).routes(); -------------------------------------------------------------------------------- /node-srv/controller/sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2019/4/25 4 | * @Description: 处理分类 5 | */ 6 | const Router = require('koa-router'); 7 | const sortsModel = require('../models/sorts'); 8 | const imagesModel = require('../models/images'); 9 | const defaultLoadSortIdModel = require('../models/defaultLoadSortId'); 10 | const baseController = require('./baseController'); 11 | const baseConfig = require('../config/basic'); 12 | 13 | const SHARE_PARAM_PREFIX = '?shareId='; 14 | 15 | let defaultSort; 16 | module.exports = new Router( 17 | 18 | ).post('add', async ctx => { 19 | 20 | let params = ctx.request.body; 21 | if (!params) return baseController.response400(ctx); 22 | if (!params.sortName) return baseController.response400(ctx, '缺失参数: sortName'); 23 | 24 | let sorts = await sortsModel.selectByCondition({ 25 | userId: ctx.state.authInfo.id, 26 | sortName: params.sortName 27 | }); 28 | if (sorts && sorts.length > 0) { 29 | return baseController.response(ctx, baseController.CODE.EXISTING_SORT_NAME, '已存在的分类名称'); 30 | } 31 | await sortsModel.save({ 32 | userId: ctx.state.authInfo.id, 33 | sortName: params.sortName 34 | }); 35 | 36 | baseController.response(ctx); 37 | 38 | }).post('update', async ctx => { 39 | 40 | let params = ctx.request.body; 41 | if (!params) return baseController.response400(ctx); 42 | if (!params.sortName || !params.sortId) return baseController.response400(ctx, '缺失参数: sortName | sortId'); 43 | 44 | if (params.sortId.length !== 12 && params.sortId.length !== 24) { 45 | return baseController.responseWithCode(ctx, baseController.CODE.BAD_OBJECT_ID, '不合法的sortId') 46 | } 47 | let sort = await sortsModel.selectById(params.sortId); 48 | if (!sort) { 49 | return baseController.responseWithCode(ctx, baseController.CODE.UNKNOWN_SORT_ID, '无效的sortId'); 50 | } 51 | 52 | await sortsModel.updateOwnById({ 53 | sortName: params.sortName, 54 | }, params.sortId, ctx.state.authInfo.id); 55 | 56 | 57 | baseController.response(ctx); 58 | 59 | }).delete('del', async ctx => { 60 | let params = ctx.query; 61 | if (!params) return baseController.response400(ctx); 62 | if (!params.sortId) return baseController.response400(ctx, '缺失参数: sortId'); 63 | if (params.sortId.length !== 12 && params.sortId.length !== 24) { 64 | return baseController.responseWithCode(ctx, baseController.CODE.BAD_OBJECT_ID, '不合法的sortId') 65 | } 66 | 67 | 68 | let imgs = await imagesModel.selectByCondition({ 69 | userId: ctx.state.authInfo.id, 70 | sortId: params.sortId 71 | }); 72 | 73 | let sort = await sortsModel.selectById(params.sortId); 74 | if (sort && sort.shared) { 75 | return baseController.responseWithCode(ctx, baseController.CODE.SHARED_SORT_ID,'该分类正在分享不能直接删除'); 76 | } 77 | 78 | let message = '删除成功'; 79 | 80 | if (imgs && imgs.length > 0) { 81 | if (!defaultSort) { 82 | defaultSort = await sortsModel.selectOneByUserId('system'); 83 | } 84 | await imagesModel.updateByCondition({ 85 | sortId: defaultSort._id 86 | }, { 87 | userId: ctx.state.authInfo.id, 88 | sortId: params.sortId 89 | }); 90 | message = '删除成功,相关图片已归档至默认分类'; 91 | } 92 | await sortsModel.removeOwnById(params.sortId, ctx.state.authInfo.id); 93 | 94 | baseController.response(ctx, message); 95 | }).get('list', async ctx => { 96 | 97 | let params = ctx.query; 98 | if (!params) return baseController.response400(ctx); 99 | let userId = ctx.state.authInfo.id; 100 | let condition = { 101 | userId: userId 102 | }; 103 | if (params.sortName) { 104 | condition.sortName = {$regex: '/' + params.sortName + '/'} 105 | } 106 | if (params.sortId) { 107 | condition.sortId = params.sortId; 108 | } 109 | if (!defaultSort) { 110 | defaultSort = await sortsModel.selectOneByUserId('system'); 111 | } 112 | let array = [{ 113 | sortId: defaultSort._id, 114 | sortName: defaultSort.sortName, 115 | createTime: defaultSort.createTime, 116 | shared: false 117 | }]; 118 | 119 | let sorts = await sortsModel.selectByCondition(condition); 120 | if (sorts && sorts.length > 0) { 121 | 122 | for (let i = 0; i < sorts.length; i++) { 123 | let obj = { 124 | sortId: sorts[i]._id, 125 | sortName: sorts[i].sortName, 126 | createTime: sorts[i].createTime, 127 | shared: sorts[i].shared == null ? false : sorts[i].shared, 128 | shareId: sorts[i].shareId 129 | }; 130 | if (obj.shared) { 131 | obj.shareUrl = baseConfig.shareUri + SHARE_PARAM_PREFIX + sorts[i].shareId 132 | } 133 | array.push(obj) 134 | } 135 | } 136 | 137 | let defaultLoadSort = await defaultLoadSortIdModel.selectOneByOwnId(userId); 138 | return baseController.response(ctx, { 139 | list: array, 140 | defaultLoadSortId: defaultLoadSort ? defaultLoadSort.sortId : null 141 | }); 142 | 143 | }).post('set_default_load_sort_id', async ctx => { 144 | 145 | let params = ctx.request.body; 146 | if (!params) return baseController.response400(ctx); 147 | let userId = ctx.state.authInfo.id; 148 | 149 | if (!params.sortId) { 150 | await defaultLoadSortIdModel.removeOwnById(userId); 151 | return baseController.response(ctx); 152 | } 153 | let sortId = params.sortId; 154 | 155 | 156 | let defaultLoadSort = await defaultLoadSortIdModel.selectOneByOwnId(userId); 157 | if (defaultLoadSort && defaultLoadSort.sortId) { 158 | console.log('已存在默认设置执行更新: ' + defaultLoadSort.sortId); 159 | await defaultLoadSortIdModel.updateOwnById(sortId, userId); 160 | } else { 161 | console.log('不存在默认设置, 保存当前配置' + sortId); 162 | await defaultLoadSortIdModel.save(userId, sortId); 163 | } 164 | 165 | baseController.response(ctx); 166 | 167 | }).routes(); -------------------------------------------------------------------------------- /node-srv/controller/tag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2019/4/25 4 | * @Description: 处理分类 5 | */ 6 | const Router = require('koa-router'); 7 | const imagesModel = require('../models/images'); 8 | const baseController = require('./baseController'); 9 | 10 | module.exports = new Router( 11 | 12 | ).post('add', async ctx => { 13 | 14 | let params = ctx.request.body; 15 | 16 | if (!params) return baseController.response400(ctx); 17 | if (!params.tags || params.tags.length === 0) return baseController.response400(ctx, '缺失参数: tags'); 18 | if (!params.imgId) return baseController.response400(ctx, '缺失参数: imgId'); 19 | if (params.imgId.length !== 12 && params.imgId.length !== 24) { 20 | return baseController.responseWithCode(ctx, baseController.CODE.BAD_OBJECT_ID, '不合法的imgId') 21 | } 22 | 23 | let image = await imagesModel.selectOwnByIds(params.imgId, ctx.state.authInfo.id); 24 | if (!image || image.length === 0) return baseController.responseWithCode(ctx, baseController.CODE.UNKNOWN_IMG_ID, '无效的imgId'); 25 | 26 | image = image[0]; 27 | 28 | for (let i = 0; i < params.tags.length; i++) { 29 | if (image.tags.indexOf(params.tags[i]) !== -1) { 30 | return baseController.responseWithCode(ctx, baseController.CODE.EXISTS_IMG_TAG, '已存在的标签: ' + params.tags[i]); 31 | } 32 | } 33 | 34 | if (image.tags.length !== 0) { 35 | 36 | if (image.tags.length >= 3 || image.tags.length + params.tags.length > 3) { 37 | return baseController.responseWithCode(ctx, baseController.CODE.MAX_IMG_TAG, "标签超出上限"); 38 | } 39 | 40 | params.tags.push.apply(params.tags, image.tags); 41 | 42 | } else { 43 | if (params.tags.length >= 3) { 44 | return baseController.responseWithCode(ctx, baseController.CODE.MAX_IMG_TAG, "标签超出上限"); 45 | } 46 | } 47 | 48 | await imagesModel.updateById({ 49 | tags: params.tags 50 | }, params.imgId); 51 | 52 | baseController.response(ctx); 53 | 54 | }).routes(); -------------------------------------------------------------------------------- /node-srv/controller/token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/7/26 4 | * @Description: 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const authTokenModel = require('../models/authToken'); 10 | const baseController = require('./baseController'); 11 | const asyncRedisClient = require('../lib/asyncRedis').client; 12 | const redisKey = require('../const/redisKey'); 13 | 14 | module.exports = new Router( 15 | 16 | ).get('list', async ctx => { 17 | 18 | let userId = ctx.state.authInfo.id; 19 | let tokens = await authTokenModel.selectByUserIdSortByCreateTimeDesc(userId); 20 | if (tokens) { 21 | 22 | let now = Date.now(); 23 | let expirationTokenId = []; 24 | let expirationToken = []; 25 | 26 | for (let index in tokens) { 27 | if (now >= tokens[index].expiration) { 28 | expirationTokenId.push(tokens[index]._id.toString()); 29 | expirationToken.push(tokens[index].token); 30 | delete tokens[index]; 31 | continue; 32 | } 33 | tokens[index].token = tokens[index].token.replace(baseController.REG.TOKEN_ENCODE, '$1******$2'); 34 | delete tokens[index].userId; 35 | } 36 | 37 | tokens = tokens.filter(token => { 38 | if (token) return token; 39 | }); 40 | 41 | if (expirationTokenId.length > 0) { 42 | await authTokenModel.removeOwnByIds(expirationTokenId, userId); 43 | for (let index in expirationToken) { 44 | await asyncRedisClient.delAsync(redisKey.AUTH_TOKEN(expirationToken[index].token)); 45 | } 46 | } 47 | 48 | return baseController.response(ctx, {list: tokens}); 49 | } 50 | baseController.response(ctx); 51 | }).post('del', async ctx => { 52 | let params = ctx.request.body; 53 | if (!params || !params.ids) return baseController.response400(ctx); 54 | let userId = ctx.state.authInfo.id; 55 | let tokens = await authTokenModel.selectOwnByIds(params.ids, userId); 56 | for (let index in tokens) { 57 | await asyncRedisClient.delAsync(redisKey.AUTH_TOKEN(tokens[index].token)); 58 | } 59 | await authTokenModel.removeOwnByIds(params.ids, userId); 60 | baseController.response(ctx); 61 | }).routes(); -------------------------------------------------------------------------------- /node-srv/controller/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/7/9 4 | * @Description: 5 | */ 6 | const Router = require('koa-router'); 7 | const baseController = require('./baseController'); 8 | const userModel = require('../models/user'); 9 | const util = require('../lib/util'); 10 | const mailSender = require('../lib/mailSender'); 11 | const mailType = require('../const/mailType'); 12 | const asyncRedisClient = require('../lib/asyncRedis').client; 13 | const redisKey = require('../const/redisKey'); 14 | const busboyUpload = require('../lib/busboyUpload'); 15 | const uploadConfig = require('../config/upload'); 16 | const baseConfig = require('../config/basic'); 17 | 18 | 19 | module.exports = new Router( 20 | 21 | ).post('change_pwd', async ctx => { 22 | 23 | let params = ctx.request.body; 24 | if (!params) return baseController.response400(ctx); 25 | if (!params.oldPassword || !params.newPassword || !params.verifyPassword) return baseController.response400(ctx); 26 | if (!baseController.REG.PASSWORD.test(params.password)) return baseController.response400(ctx, '密码不合法'); 27 | if (params.newPassword !== params.verifyPassword) return baseController.response400(ctx, '两次密码不一致'); 28 | if (params.oldPassword === params.newPassword) return baseController.response400(ctx, '原密码和新密码相同'); 29 | let authInfo = ctx.state.authInfo; 30 | let user = await userModel.selectById(authInfo.id); 31 | if (user.password !== util.md5(user.username + params.newPassword)) return baseController.responseWithCode(ctx, baseController.CODE.PASSWORD_ERROR, '原密码错误'); 32 | await userModel.updatePasswordByUsername(user.username, util.md5(user.username + params.newPassword)); 33 | baseController.response(ctx); 34 | 35 | }).post('send_upload_mail_code', async ctx => { 36 | 37 | let params = ctx.request.body; 38 | if (!params || !params.mail) return baseController.response400(ctx); 39 | let user = await userModel.selectByUsernameOrEmail(params.mail); 40 | if (user) return baseController.responseWithCode(ctx, baseController.CODE.EXISTING_MAIL, '该邮箱地址已存在'); 41 | 42 | user = await userModel.selectById(ctx.state.authInfo.id); 43 | let code = util.randomNum(6); 44 | let data = [ 45 | user.nickname, 46 | user.email.replace(baseController.REG.MAIL_ENCODE, '$1****$2'), 47 | code 48 | ]; 49 | 50 | let token = util.uuid(); 51 | 52 | let flag = await mailSender.send(mailType.type.UPDATE_MAIL_CODE, params.mail, data); 53 | if (flag) { 54 | await asyncRedisClient.setAsync(redisKey.UPDATE_MAIL_CODE(token), code + '|' + params.mail, 'EX', baseController.CONSTS.UPDATE_MAIL_MINUTE * 60); 55 | baseController.response(ctx, {token: token}); 56 | } else { 57 | baseController.responseWithCode(ctx, baseController.CODE.SEND_MAIL_FAILED, '邮件发送失败,请检查你的邮箱或重试'); 58 | } 59 | 60 | }).post('update_uinfo', async ctx => { 61 | 62 | let uploadResult = await busboyUpload.upload(ctx); 63 | if (!uploadResult.flag) return baseController.response500(ctx, '图片上传异常'); 64 | 65 | let params = uploadResult.fields; 66 | 67 | if ((params.token && !params.mail && !params.mailCode) || (params.mail && !params.token && !params.mailCode) || (params.mailCode && !params.token && !params.mail)) 68 | return baseController.response400(ctx); 69 | 70 | uploadResult = uploadResult.uploadResult; 71 | let update = {}; 72 | let headImg = null; 73 | if (uploadResult) { 74 | if (uploadResult.length > 0) { 75 | let result = uploadResult[0]; 76 | if (!result.flag) return baseController.response500(ctx, '图片上传异常'); 77 | headImg = result.path; 78 | update.headImg = headImg; 79 | } 80 | } 81 | 82 | let user = await userModel.selectById(ctx.state.authInfo.id); 83 | if (headImg && user.headImg) { 84 | util.fsDel([uploadConfig.path + user.headImg]); 85 | } 86 | 87 | if (params.nickname) { 88 | update.nickname = params.nickname; 89 | } 90 | 91 | if (params.token && params.mail && params.mailCode) { 92 | let authMail = await asyncRedisClient.getAsync(redisKey.UPDATE_MAIL_CODE(params.token)); 93 | if (!authMail) return baseController.responseWithCode(ctx, baseController.CODE.EXPIRED_MAIL_CODE, '验证码已过期或未获取验证码'); 94 | authMail = authMail.split('|'); 95 | if (params.mailCode !== authMail[0]) { 96 | return baseController.responseWithCode(ctx, baseController.CODE.INVALID_MAIL_CODE, '邮箱验证码错误'); 97 | } else if (params.mail !== authMail[1]) { 98 | return baseController.responseWithCode(ctx, baseController.CODE.ERROR_REGISTE_DATA, '该验证码仅可用于验证指定的邮箱操作'); 99 | } 100 | await asyncRedisClient.delAsync(redisKey.UPDATE_MAIL_CODE(params.token)); 101 | update.email = params.mail; 102 | } 103 | 104 | if (Object.keys(update).length > 0) { 105 | await userModel.updateByUsername(update, user.username); 106 | } 107 | 108 | baseController.response(ctx, await getUserInfo(ctx)); 109 | 110 | }).get('uinfo', async ctx => { 111 | baseController.response(ctx, await getUserInfo(ctx)); 112 | }).routes(); 113 | 114 | 115 | const getUserInfo = async ctx => { 116 | let user = await userModel.selectById(ctx.state.authInfo.id); 117 | return { 118 | id: user._id, 119 | username: user.username, 120 | nickname: user.nickname, 121 | email: user.email.replace(baseController.REG.MAIL_ENCODE, '$1****$2'), 122 | headImg: user.headImg ? baseConfig.headImgUri + user.headImg : null 123 | }; 124 | }; -------------------------------------------------------------------------------- /node-srv/extends/date.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/19 4 | * @Description: Date 函数拓展 5 | */ -------------------------------------------------------------------------------- /node-srv/extends/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/19 4 | * @Description: 5 | */ 6 | require('./date'); 7 | require('colour'); -------------------------------------------------------------------------------- /node-srv/lib/algorithm10to64.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 10位 <=> 64 位 3 | */ 4 | 5 | const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-'.split(''); 6 | const radix = 64; 7 | 8 | /** 9 | * 10进制数字转64进制 10 | * @param number10 11 | * @returns {string} 12 | */ 13 | const number10to64 = number10 => { 14 | if (isNaN(number10)) return null; 15 | number10 = +number10; 16 | let arr = []; 17 | let mod; 18 | do { 19 | mod = number10 % radix; 20 | number10 = (number10 - mod) / radix; 21 | arr.unshift(chars[mod]); 22 | } while (number10); 23 | return arr.join(''); 24 | }; 25 | 26 | 27 | const string64to10 = string64 => { 28 | string64 = String(string64); 29 | let len = string64.length, 30 | i = 0, 31 | number = 0; 32 | while (i < len) { 33 | number += Math.pow(radix, i++) * chars.indexOf(string64.charAt(len - i) || 0); 34 | } 35 | return number; 36 | }; 37 | 38 | module.exports = { 39 | number10to64: number10to64, 40 | string64to10: string64to10 41 | }; -------------------------------------------------------------------------------- /node-srv/lib/asyncRedis.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/1 4 | * @Description:redis 5 | */ 6 | const redis = require('redis'); 7 | const bluebird = require('bluebird'); 8 | 9 | bluebird.promisifyAll(redis.RedisClient.prototype); 10 | bluebird.promisifyAll(redis.Multi.prototype); 11 | 12 | const client = redis.createClient(require('../config/redis.json')); 13 | 14 | console.log('=> redis: '.magenta + 'connecting'.grey); 15 | module.exports.client = client; 16 | module.exports.closeClient = () => client.quit(); 17 | 18 | client.on('error', (err) => { 19 | console.log('=> redis: '.magenta + 'error'.red); 20 | console.error(err); 21 | }); -------------------------------------------------------------------------------- /node-srv/lib/busboyUpload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/14 4 | * @Description:通过busboy解析post数据完成上传动作 5 | */ 6 | const Busboy = require('busboy'); 7 | 8 | const fs = require('fs'); 9 | const uploadConfig = require('../config/upload'); 10 | const util = require('../lib/util'); 11 | const path = require('path'); 12 | const allowedMimeType = require('../const/allowedMimeType'); 13 | 14 | module.exports.upload = (ctx) => new Promise(resolve => { 15 | let uploadResult = []; 16 | let nodeHttpReq = ctx.req; 17 | let busboy = new Busboy({headers: nodeHttpReq.headers}); 18 | 19 | busboy.on('file', function (fieldname, file, filename, encoding, mimetype) { // 每次接收文件就触发 20 | 21 | if (allowedMimeType.indexOf(mimetype) !== -1) { 22 | 23 | console.log('=> busboyUpload'.cyan + ' begin to upload filename = '.grey + filename.blue + ' mimetype = '.grey + mimetype.blue); 24 | 25 | let result = { 26 | fileName: filename, 27 | uploadTime: Date.now(), 28 | }; 29 | 30 | let basePath = uploadConfig.path; 31 | let date = new Date(); 32 | let dirPath = path.join(String(date.getFullYear()), String(date.getMonth() + 1), String(date.getDate())); 33 | let filePath = path.join(basePath, dirPath); 34 | mkdirsSync(filePath); 35 | let fileName = util.md5(util.uuid() + Date.now()) + '.' + filenameSuffix(filename); 36 | let uriPath = path.join(dirPath, fileName); 37 | let absPath = path.join(filePath, fileName); 38 | 39 | 40 | // ------------------------------------------------- 41 | // 由于前端使用blob方式不再使用当前模式 42 | // file.pipe(fs.createWriteStream(absPath)); 43 | 44 | let bfs = []; 45 | file.on('data', (chunk) => { 46 | bfs.push(chunk); 47 | }); 48 | // ------------------------------------------------- 49 | 50 | file.on('end', () => { 51 | 52 | let buf = Buffer.concat(bfs); 53 | let imgBase64 = buf.toString(); 54 | fs.writeFileSync(absPath, Buffer.from(imgBase64, 'base64')); 55 | 56 | if (uploadConfig.maxFileSize) { 57 | if (fs.statSync(absPath).size > uploadConfig.maxFileSize) { 58 | 59 | util.fsDel(absPath); 60 | 61 | result.flag = false; 62 | result.message = '图片超过最大限制'; 63 | uploadResult.push(result); 64 | 65 | return; 66 | } 67 | } 68 | 69 | result.path = path.sep + uriPath; 70 | result.flag = true; 71 | result.absPath = absPath; 72 | result.message = '上传完成'; 73 | console.log('=> busboyUpload'.cyan + ' finished to upload filename = '.grey + filename.blue + ' path = '.grey + absPath.blue); 74 | 75 | uploadResult.push(result); 76 | }); 77 | } else { 78 | console.log('=> busboyUpload'.cyan + ' not allowed mimetype filename = '.grey + filename.blue + ' mimetype = '.grey + mimetype.blue); 79 | file.resume(); // 丢弃数据, 在监听了file事件后必须要处理file流,否则busboy不会触发finish事件 80 | uploadResult.push({ 81 | flag: false, 82 | fileName: filename, 83 | message: '不允许的的文件上传格式' 84 | }); 85 | } 86 | 87 | }); 88 | 89 | let fields = {}; 90 | 91 | busboy.on('field', function (fieldname, val, fieldNameTruncated, valTruncated, encoding, mimetype) { 92 | if (fields[fieldname]) { 93 | if (Array.isArray(fields[fieldname])) { 94 | let array = fields[fieldname]; 95 | array.push(val); 96 | fields[fieldname] = array; 97 | } else { 98 | fields[fieldname] = [fields[fieldname], val]; 99 | } 100 | } else { 101 | fields[fieldname] = val; 102 | } 103 | }); 104 | 105 | busboy.on('finish', function () { 106 | resolve({ 107 | flag: true, 108 | uploadResult: uploadResult, 109 | fields: fields 110 | }); 111 | 112 | }); 113 | 114 | busboy.on('error', function (err) { 115 | console.log(err); 116 | resolve({ 117 | flag: false, 118 | }); 119 | }); 120 | 121 | nodeHttpReq.pipe(busboy); 122 | }); 123 | 124 | /** 125 | * 解析文件名后缀 126 | * @param filename 127 | * @returns {*|string} 128 | */ 129 | const filenameSuffix = filename => { 130 | let array = filename.split('.'); 131 | return array[array.length - 1]; 132 | }; 133 | 134 | const mkdirsSync = dirname => { 135 | if (fs.existsSync(dirname)) { 136 | return true 137 | } else { 138 | if (mkdirsSync(path.dirname(dirname))) { 139 | fs.mkdirSync(dirname); 140 | return true 141 | } 142 | } 143 | }; -------------------------------------------------------------------------------- /node-srv/lib/httpRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2017/12/25 4 | * @Description: 5 | */ 6 | const request = require('request'); 7 | 8 | module.exports.doRequestBuffer = requestParam => new Promise(resolve => { 9 | let bufs = []; 10 | let data = {flag: false}; 11 | 12 | request(requestParam, (err, response, body) => { 13 | if (err) { 14 | resolve(data); 15 | } else { 16 | data.flag = true; 17 | } 18 | }).on('data', buf => { 19 | bufs.push(buf); 20 | }).on('end', () => { 21 | data.buffer = Buffer.concat(bufs); 22 | resolve(data); 23 | }); 24 | }); 25 | 26 | module.exports.doRequestString = requestParam => new Promise(resolve => { 27 | request(requestParam, (err, response, body) => { 28 | let data = {flag: false}; 29 | if (err) { 30 | data.error = err; 31 | resolve(data); 32 | } else { 33 | data.flag = true; 34 | data.body = body; 35 | resolve(data); 36 | } 37 | }); 38 | }); -------------------------------------------------------------------------------- /node-srv/lib/mailSender.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/7/16 4 | * @Description: 5 | */ 6 | const nodemailer = require('nodemailer'); 7 | const mailConfig = require('../config/mail'); 8 | const mailType = require('../const/mailType'); 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | 12 | let transporter = nodemailer.createTransport({ 13 | host: mailConfig.host, 14 | port: mailConfig.port, 15 | secure: mailConfig.secure, 16 | auth: mailConfig.auth 17 | }); 18 | 19 | /** 20 | * 21 | * @param mailType 22 | * @param toMail 23 | * @param content { title:"", mailType:"", params:{} } 24 | * @returns {Promise} 25 | */ 26 | module.exports.send = (type, toMail, params) => new Promise(resolve => { 27 | 28 | let config = mailType.config[type]; 29 | 30 | let mailOptions = { 31 | from: mailConfig.auth.user, 32 | to: toMail, 33 | subject: config.title, 34 | html: buildContent(config, params), 35 | }; 36 | 37 | transporter.sendMail(mailOptions, (error, info) => { 38 | 39 | if (error) { 40 | console.log('=> nodeemailer: '.magenta + ' send failed toMail = '.red + toMail.blue); 41 | console.log(error); 42 | resolve(false); 43 | return; 44 | } 45 | console.log('=> nodeemailer: '.magenta + ' send success info = '.grey + JSON.stringify(info).blue); 46 | 47 | resolve(true); 48 | }); 49 | }); 50 | 51 | const templates = new Map(); 52 | 53 | const buildContent = (config, params) => { 54 | let template = templates.get(config.templateName); 55 | if (!template) { 56 | path.join(__dirname, '..', 'config', 'mailTemplate', config.templateName); 57 | template = fs.readFileSync(path.join(__dirname, '..', 'config', 'mailTemplate', config.templateName)).toString(); 58 | templates.set(config.templateName, template); 59 | } 60 | if (params) { 61 | for (let index = 0; index < params.length; index++) { 62 | template = template.replace('{' + index + '}', params[index]); 63 | } 64 | } 65 | return template; 66 | }; 67 | -------------------------------------------------------------------------------- /node-srv/lib/mongodb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/15 4 | * @Description: mongoose 5 | */ 6 | const mongoose = require('mongoose'); 7 | 8 | const mongoConfig = require('../config/mongo'); 9 | 10 | module.exports.open = (cb) => { 11 | 12 | const connStr = 'mongodb://' + mongoConfig.username + ':' + mongoConfig.password + '@' + mongoConfig.host + ':' + mongoConfig.port + '/' + mongoConfig.dbname; 13 | mongoose.connect(connStr, {useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true,}); 14 | 15 | let db = mongoose.connection; 16 | 17 | db.on('error', function (err) { 18 | console.error('=> mongoose: '.green + ' error'.red); 19 | console.error(err); 20 | }); 21 | 22 | db.once('open', function () { 23 | console.log('=> mongoose: '.green + 'connected'.grey); 24 | cb(); 25 | }); 26 | }; 27 | 28 | const models = new Map(); 29 | 30 | module.exports.loadModel = (modelName, schema) => { 31 | let model = models.get(modelName); 32 | if (model) return model; 33 | console.log('=> mongoose: '.green + 'loadModel modelName = '.grey + modelName.blue); 34 | model = mongoose.model(modelName, schema, modelName); 35 | models.set(modelName, model); 36 | return model; 37 | }; -------------------------------------------------------------------------------- /node-srv/lib/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/5/28 4 | * @Description:utils 5 | */ 6 | const md5 = require('md5'); 7 | const fs = require('fs'); 8 | 9 | const uuid = require('uuid'); 10 | const UA = require('ua-device'); 11 | const sharp = require('sharp'); 12 | const path = require('path'); 13 | 14 | const httpRequest = require('./httpRequest'); 15 | const imagesModel = require('../models/images'); 16 | const basicConfig = require('../config/basic'); 17 | 18 | /** 19 | * md5 20 | * @param content 21 | * @returns {*} 22 | */ 23 | module.exports.md5 = content => md5(content); 24 | /** 25 | * uuid生成器 26 | * @returns {string} 27 | */ 28 | module.exports.uuid = () => String(uuid.v4()).replace(/-/g, ''); 29 | 30 | /** 31 | * 文件删除 32 | */ 33 | module.exports.fsDel = uris => { 34 | for (let i in uris) { 35 | fs.rename(uris[i], uris[i] + '.del', err => { 36 | if (err) { 37 | } 38 | }); 39 | } 40 | }; 41 | 42 | module.exports.fsDelReal = uris => { 43 | for (let i in uris) { 44 | fs.unlink(uris[i], err => { 45 | if (err) { 46 | } 47 | }); 48 | } 49 | }; 50 | 51 | /** 52 | * 生成指定范围随机数 53 | * @param min 54 | * @param max 55 | * @returns {number} 56 | */ 57 | const randomMin2Max = (min, max) => { 58 | return Math.floor(Math.random() * (max - min + 1) + min); 59 | }; 60 | 61 | module.exports.randomMin2Max = randomMin2Max; 62 | 63 | /** 64 | * 生成指定长度的随机数 0-9 65 | * @param length 66 | * @returns {string} 67 | */ 68 | module.exports.randomNum = length => { 69 | let code = ''; 70 | for (let index = 0; index < length; index++) { 71 | code += randomMin2Max(0, 9); 72 | } 73 | return code; 74 | }; 75 | 76 | /** 77 | * 格式化 User-Agent 78 | * @param uaString 79 | * @returns {null|{browserVersion: *, browserName: *, osName: *}} 80 | */ 81 | module.exports.ua = uaString => { 82 | if (!uaString) return null; 83 | let ua = new UA(uaString); 84 | if (ua) { 85 | return { 86 | osName: ua.os.name, 87 | browserName: ua.browser.name, 88 | browserVersion: ua.browser.version.original 89 | } 90 | } 91 | return null; 92 | }; 93 | 94 | module.exports.createThumb = async (absPath) => { 95 | let dirPath = path.join(absPath, '..'); 96 | let fileName = absPath.replace(dirPath + '/', ''); 97 | await sharp(fs.readFileSync(absPath)) 98 | .resize({width: 160}) 99 | .toFile(dirPath + '/thumb-' + fileName); 100 | return '/thumb/' + md5(fileName); 101 | }; 102 | 103 | module.exports.imageCheck = (imgUri, urn) => { 104 | 105 | httpRequest.doRequestString(basicConfig.imageVerify + encodeURIComponent(imgUri)).then(res => { 106 | if (res.flag) { 107 | let obj = JSON.parse(res.body); 108 | let update = { 109 | sysScyLevel: obj.rating_index, 110 | sysScyLevelTime: Date.now(), 111 | sysScyLevelDetail: JSON.stringify(obj.predictions), 112 | sysScyCode: obj.error_code 113 | }; 114 | if (obj.error_code === 0) { 115 | if (obj.rating_index === 3) { 116 | update.status = '01'; 117 | } 118 | } else { 119 | update.sysScyLevelDetail = res.body; 120 | } 121 | imagesModel.updateByCondition(update, {urn: urn}); 122 | console.log('图片自动分级完成 urn =', urn); 123 | } else { 124 | console.error('图片自动分级异常 urn =', urn, res.error); 125 | } 126 | }); 127 | }; 128 | 129 | module.exports.changeToWebp = absPath => { 130 | 131 | let dirPath = path.join(absPath, '..'); 132 | let fileName = absPath.replace(dirPath + '/', ''); 133 | fileName = fileName.split('.')[0] + '.webp'; 134 | 135 | sharp(fs.readFileSync(absPath)) 136 | .webp({lossless: false}) 137 | .toFile(path.join(dirPath, fileName), (err, info) => { 138 | console.log(info) 139 | }); 140 | }; -------------------------------------------------------------------------------- /node-srv/middleware/auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/12 4 | * @Description:鉴权中间件 5 | */ 6 | 7 | const url = require('url'); 8 | 9 | const asyncRedisClient = require('../lib/asyncRedis').client; 10 | const redisKey = require('../const/redisKey'); 11 | const cookiesName = require('../const/cookiesName'); 12 | const whitePathname = require('../const/whitePathname'); 13 | 14 | module.exports = async (ctx, next) => { 15 | 16 | if (!auth(url.parse(ctx.url, true).pathname)) { 17 | // 是否需要鉴权 18 | let token = ctx.cookies.get(cookiesName.COOKIE_NAME_TOKEN); 19 | let uinfo = ctx.cookies.get(cookiesName.COOKIE_NAME_UINFO); 20 | if (token && uinfo) { 21 | 22 | let userInfo = await asyncRedisClient.getAsync(redisKey.AUTH_TOKEN(token)); 23 | if (!userInfo) { // 鉴权失败 24 | ctx.body = {message: 'unauthorized'}; 25 | ctx.status = 401; 26 | return; 27 | } 28 | 29 | userInfo = JSON.parse(userInfo); 30 | 31 | if (JSON.parse(decodeURI(uinfo)).id !== userInfo.id) { 32 | ctx.body = {message: 'unauthorized'}; 33 | ctx.status = 401; 34 | return; 35 | } 36 | 37 | ctx.state.authInfo = userInfo; 38 | 39 | } else { 40 | // 鉴权参数不足 41 | ctx.body = {message: 'unauthorized'}; 42 | ctx.status = 401; 43 | return; 44 | } 45 | } 46 | 47 | // 鉴权完成的用户上下文中添加用户信息 48 | await next(); 49 | }; 50 | 51 | const auth = pathname => { 52 | for (let index in whitePathname) { 53 | if (pathname.startsWith(whitePathname[index])) { 54 | return true; 55 | } 56 | } 57 | return false; 58 | }; -------------------------------------------------------------------------------- /node-srv/middleware/basic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/12 4 | * @Description:基础中间件 5 | */ 6 | 7 | const unlogPathname = require('../const/unlogPathname'); 8 | const url = require('url'); 9 | 10 | 11 | module.exports = async (ctx, next) => { 12 | 13 | let startTime = beforeController(ctx); 14 | await next(); 15 | afterController(ctx, startTime); 16 | 17 | }; 18 | 19 | const beforeController = ctx => { 20 | let requestContent = ''; 21 | if (ctx.method === "GET") { 22 | requestContent = ctx.query ? JSON.stringify(ctx.query) : 'null'; 23 | } else if (ctx.method === "POST") { 24 | requestContent = ctx.request.body ? JSON.stringify(ctx.request.body) : 'null'; 25 | } 26 | 27 | if (!unPrintLog(url.parse(ctx.url, true).pathname)) { 28 | let logInfo = '=> koa: '.cyan + '<<< requestContent = '.grey + requestContent.blue + ' | url = '.grey + ctx.url.blue; 29 | console.log(logInfo); 30 | } 31 | 32 | return Date.now(); 33 | }; 34 | 35 | /** 36 | * 公共处理 controller 完毕后执行 37 | * @param ctx 38 | * @param startTime 39 | */ 40 | const afterController = (ctx, startTime) => { 41 | 42 | if (ctx.response.status === 404) { 43 | ctx.body = {message: '404 Or Invalid Response'}; 44 | } else if (ctx.response.status === 405) { 45 | ctx.body = {message: '405 Method Not Allowed'}; 46 | } 47 | 48 | if (!unPrintLog(url.parse(ctx.url, true).pathname)) { 49 | let logInfo = '=> koa: '.cyan + '>>> responseContent = '.grey + '%s' + ' | url = '.grey + '%s'.blue; 50 | logInfo += ' | status = '.grey + '%s'.blue + ' | costTime = '.grey + '%s'.blue + ' ms'.grey; 51 | 52 | console.log(logInfo, ctx.body ? JSON.stringify(ctx.body).blue : 'no data response'.red, ctx.url, ctx.response.status, Date.now() - startTime); 53 | } 54 | 55 | }; 56 | 57 | const unPrintLog = pathname => { 58 | for (let index in unlogPathname) { 59 | if (pathname.startsWith(unlogPathname[index])) { 60 | return true; 61 | } 62 | } 63 | return false; 64 | }; -------------------------------------------------------------------------------- /node-srv/models/authToken.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/19 4 | * @Description: 5 | */ 6 | const baseModel = require('./basic.js'); 7 | 8 | class authToken extends baseModel { 9 | Schema() { 10 | return { 11 | userId: {type: String, required: true}, 12 | token: {type: String, required: true, unique: true}, 13 | osName: {type: String, required: false}, 14 | browserName: {type: String, required: false}, 15 | browserVersion: {type: String, required: false}, 16 | expiration: {type: Number, required: true}, 17 | remark: {type: String, required: false}, 18 | }; 19 | }; 20 | 21 | SchemaName() { 22 | return 'auth_token'; 23 | }; 24 | 25 | // 存储 26 | save(authToken) { 27 | return new this.model(authToken).save(); 28 | } 29 | 30 | removeOwnByIds(ids, userId) { 31 | let condition = {userId: userId}; 32 | if (typeof ids === 'string') { 33 | condition._id = ids; 34 | } else if (Array.isArray(ids)) { 35 | condition._id = { 36 | '$in': ids 37 | }; 38 | } else { 39 | return false; 40 | } 41 | return this.model.deleteMany(condition).exec(); 42 | } 43 | 44 | removeOwnByTokens(tokens, userId) { 45 | let condition = {userId: userId}; 46 | if (typeof tokens === 'string') { 47 | condition.token = tokens; 48 | } else if (Array.isArray(tokens)) { 49 | condition.token = { 50 | '$in': tokens 51 | }; 52 | } else { 53 | return false; 54 | } 55 | return this.model.deleteMany(condition).exec(); 56 | } 57 | 58 | selectByUserIdSortByCreateTimeDesc(userId) { 59 | return this.model.find({userId: userId}).sort({createTime: -1}).exec(); 60 | } 61 | 62 | selectByUserIdSortByCreateTimeAsc(userId) { 63 | return this.model.find({userId: userId}).sort({createTime: 1}).exec(); 64 | } 65 | 66 | selectOwnByIds(ids, userId) { 67 | let condition = {userId: userId}; 68 | if (typeof ids === 'string') { 69 | condition._id = ids; 70 | } else if (Array.isArray(ids)) { 71 | condition._id = { 72 | '$in': ids 73 | }; 74 | } else { 75 | return false; 76 | } 77 | return this.model.find(condition).exec(); 78 | } 79 | } 80 | 81 | module.exports = new authToken(); -------------------------------------------------------------------------------- /node-srv/models/basic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/15 4 | * @Description: 5 | */ 6 | const mongoose = require('mongoose'); 7 | const schema = mongoose.Schema; 8 | const mongodb = require('../lib/mongodb'); 9 | 10 | class baseModel { 11 | 12 | constructor() { 13 | 14 | this.schema = new schema(addDefaultField(this.Schema()), {versionKey: false}); 15 | this.name = this.SchemaName(); 16 | this.model = mongodb.loadModel(this.name, this.schema); 17 | 18 | // 添加默认字段 19 | function addDefaultField(customizeSchema) { 20 | 21 | customizeSchema.createTime = {type: Number, default: Date.now}; 22 | 23 | return customizeSchema; 24 | } 25 | } 26 | 27 | Schema() { 28 | }; 29 | 30 | SchemaName() { 31 | }; 32 | } 33 | 34 | module.exports = baseModel; 35 | 36 | /** 37 | * 提供统一分页查询 38 | * @param page {pageSize:,pageNumber,query:{},sort:{}} query: 查询条件 sort: 排序方式 {field:-1/1} 同mongo标准 39 | * @param model 40 | * @returns {Promise} 41 | */ 42 | module.exports.selectByPage = (page, model) => new Promise(resolve => { 43 | 44 | let query = {}; 45 | 46 | let sort = {createTime: -1}; // 默认排序规则 按时间降序 47 | if (page.query) { 48 | query = page.query; 49 | } 50 | 51 | if (page.sorts) { 52 | sort = page.sorts; 53 | } 54 | 55 | let fields = null; 56 | if (page.fields) { 57 | fields = page.fields; 58 | } 59 | 60 | model.countDocuments(query, (err, count) => { 61 | if (err) { 62 | console.error(err); 63 | resolve(null); 64 | } else { 65 | page.pageNumber = page.pageNumber || 1; 66 | page.pageSize = page.pageSize || 10; 67 | let response = { 68 | pageSize: page.pageSize, 69 | pageNumber: page.pageNumber, 70 | pageCount: Math.ceil(count / page.pageSize), 71 | totalCount: count, 72 | hasNext: false 73 | }; 74 | if (count === 0) { 75 | resolve(response); 76 | } else { 77 | model.find(query, fields, {sort: sort}).skip(page.pageSize * (page.pageNumber - 1)).limit(page.pageSize).exec((err, docs) => { 78 | if (err) { 79 | console.error(err); 80 | resolve(null); 81 | } else { 82 | response.list = docs; 83 | response.hasNext = count > page.pageSize * page.pageNumber; 84 | resolve(response); 85 | } 86 | }); 87 | } 88 | } 89 | }); 90 | }); 91 | // 92 | module.exports.typeMixed = schema.Types.Mixed; 93 | module.exports.typeObject = mongoose.Types.ObjectId; 94 | module.exports.typeDecimal128 = schema.Types.Decimal128; 95 | -------------------------------------------------------------------------------- /node-srv/models/defaultLoadSortId.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * @Date: 2019-04-25 4 | * @Description: 处理sort分类的mongo数据层 5 | **/ 6 | const baseModel = require('./basic.js'); 7 | 8 | class defaultLoadSortId extends baseModel { 9 | 10 | Schema() { 11 | return { 12 | userId: {type: String, required: true}, 13 | sortId: {type: String}, 14 | }; 15 | }; 16 | 17 | SchemaName() { 18 | return 'default_load_sort'; 19 | }; 20 | 21 | save(userId, sortId) { 22 | return new this.model({userId: userId, sortId: sortId}).save(); 23 | } 24 | 25 | updateOwnById(sortId, userId) { 26 | return this.model.updateOne({userId: userId}, {sortId: sortId}).exec(); 27 | } 28 | 29 | removeOwnById(userId) { 30 | return this.model.deleteOne({userId: userId}).exec(); 31 | } 32 | 33 | selectOneByOwnId(userId) { 34 | return this.model.findOne({userId: userId}).exec(); 35 | } 36 | 37 | } 38 | 39 | module.exports = new defaultLoadSortId(); -------------------------------------------------------------------------------- /node-srv/models/images.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/19 4 | * @Description:图片模块数据库 5 | */ 6 | const baseModel = require('./basic.js'); 7 | 8 | class images extends baseModel { 9 | Schema() { 10 | return { 11 | userId: {type: String, required: true}, 12 | url: {type: String, required: true}, 13 | // 当图片不允许访问时将返回该字段对应当资源展示 14 | violationUrl: {type: String, require: false}, 15 | urn: {type: String, unique: true}, 16 | thumbUrn: {type: String, unique: true}, 17 | sortId: {type: String, required: true}, 18 | tags: {type: Array, required: false}, 19 | // 00 图片状态正常 20 | // 01 违规 成人图片 21 | status: {type: String, default: '00'}, 22 | // 图片系统自动分级 23 | sysScyCode: {type: Number}, 24 | // everyone | teen | adult 25 | sysScyLevel: {type: Number, default: 1}, 26 | sysScyLevelTime: {type: Number, required: false}, 27 | sysScyLevelDetail: {type: String, required: false}, 28 | confirmScyLevel: {type: String, required: false}, 29 | confirmScyLevelTime: {type: Number, required: false}, 30 | confirmedByUser: {type: Boolean, required: false}, 31 | }; 32 | }; 33 | 34 | SchemaName() { 35 | return 'images'; 36 | }; 37 | 38 | save(images) { 39 | return new this.model(images).save(); 40 | } 41 | 42 | saveMany(imagesArray) { 43 | return this.model.insertMany(imagesArray); 44 | } 45 | 46 | selectByPage(page) { 47 | return baseModel.selectByPage(page, this.model); 48 | } 49 | 50 | selectOwnByIds(ids, userId) { 51 | let condition = {userId: userId}; 52 | if (typeof ids === 'string') { 53 | condition._id = ids; 54 | } else if (Array.isArray(ids)) { 55 | condition._id = { 56 | '$in': ids 57 | }; 58 | } else { 59 | return false; 60 | } 61 | return this.model.find(condition).exec(); 62 | } 63 | 64 | removeOwnManyById(ids, userId) { 65 | let condition = {userId: userId}; 66 | if (typeof ids === 'string') { 67 | condition._id = ids; 68 | } else if (Array.isArray(ids)) { 69 | condition._id = { 70 | '$in': ids 71 | }; 72 | } else { 73 | return false; 74 | } 75 | return this.model.deleteMany(condition).exec(); 76 | } 77 | 78 | selectByUrn(urn) { 79 | return this.model.findOne({urn: urn}).exec(); 80 | } 81 | 82 | selectById(id) { 83 | return this.model.findOne({_id: id}).exec(); 84 | } 85 | 86 | selectByUrnOwn(urn, userId) { 87 | return this.model.findOne({urn: urn, userId: userId}).exec(); 88 | 89 | } 90 | 91 | updateById(update, id) { 92 | return this.model.updateOne({_id: id}, update).exec(); 93 | } 94 | 95 | selectByCondition(condition) { 96 | return this.model.find(condition).exec(); 97 | } 98 | 99 | selectByConditionOne(condition) { 100 | return this.model.findOne(condition).exec(); 101 | } 102 | 103 | updateByCondition(update, condition) { 104 | return this.model.updateOne(condition, update).exec(); 105 | } 106 | 107 | } 108 | 109 | module.exports = new images(); -------------------------------------------------------------------------------- /node-srv/models/shareImg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/15 4 | * @Description:分享图片 5 | */ 6 | 7 | const baseModel = require('./basic.js'); 8 | 9 | class shareImg extends baseModel { 10 | 11 | Schema() { 12 | return { 13 | imgId: {type: String, required: true}, 14 | shareId: {type: String, required: true}, 15 | urn: {type: String, required: true, unique: true}, 16 | status: {type: Boolean, required: true, default: true} 17 | }; 18 | }; 19 | 20 | SchemaName() { 21 | return 'share_images'; 22 | }; 23 | 24 | save(shareImg) { 25 | return new this.model(shareImg).save(); 26 | } 27 | 28 | saveMany(shareImgs) { 29 | return this.model.insertMany(shareImgs); 30 | } 31 | 32 | selectManyByCondition(condition) { 33 | return this.model.find(condition).exec(); 34 | } 35 | 36 | selectOneByUrn(urn) { 37 | return this.model.findOne({urn: urn}).exec(); 38 | } 39 | 40 | updateManyByShareId(shareId, condition) { 41 | return this.model.updateMany({shareId: shareId}, condition).exec(); 42 | } 43 | 44 | updateManyByImgId(imgIds, condition) { 45 | let query; 46 | if (typeof imgIds === 'string') { 47 | query = {imgId: imgIds} 48 | } else if (Array.isArray(imgIds)) { 49 | query = { 50 | imgId: {'$in': imgIds} 51 | }; 52 | } 53 | return this.model.updateMany(query, condition).exec(); 54 | } 55 | } 56 | 57 | module.exports = new shareImg(); -------------------------------------------------------------------------------- /node-srv/models/shareList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/15 4 | * @Description:分享清单 5 | */ 6 | 7 | const baseModel = require('./basic.js'); 8 | 9 | class shareList extends baseModel { 10 | 11 | Schema() { 12 | return { 13 | userId: {type: String, required: true}, 14 | type: {type: String, required: true}, 15 | status: {type: Boolean, required: true, default: true}, 16 | sortId: {type: String, required: true} 17 | }; 18 | }; 19 | 20 | SchemaName() { 21 | return 'share_list'; 22 | }; 23 | 24 | save(shareList) { 25 | return new this.model(shareList).save(); 26 | } 27 | 28 | selectOneById(shareId) { 29 | return this.model.findOne({_id: shareId}).exec(); 30 | } 31 | 32 | selectOneOfOwnById(shareId, userId) { 33 | return this.model.findOne({_id: shareId, userId: userId}).exec(); 34 | } 35 | 36 | updateById(condition, shareId) { 37 | return this.model.updateOne({_id: shareId}, condition).exec(); 38 | } 39 | } 40 | 41 | module.exports = new shareList(); -------------------------------------------------------------------------------- /node-srv/models/sorts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * @Date: 2019-04-25 4 | * @Description: 处理sort分类的mongo数据层 5 | **/ 6 | const baseModel = require('./basic.js'); 7 | 8 | class sort extends baseModel { 9 | 10 | Schema() { 11 | return { 12 | sortName: {type: String, required: true}, 13 | userId: {type: String, required: true}, 14 | shared: {type: Boolean, default: false}, 15 | shareId: {type: String, required: false} 16 | }; 17 | }; 18 | 19 | SchemaName() { 20 | return 'sorts'; 21 | }; 22 | 23 | save(sort) { 24 | return new this.model(sort).save(); 25 | } 26 | 27 | selectById(id) { 28 | return this.model.findOne({_id: id}).exec(); 29 | } 30 | 31 | selectOwnById(sortId, userId) { 32 | return this.model.findOne({_id: sortId, userId: userId}).exec(); 33 | } 34 | 35 | updateOwnById(condition, sortId, userId) { 36 | return this.model.updateOne({_id: sortId, userId: userId}, condition).exec(); 37 | } 38 | 39 | removeOwnById(sortId, userId) { 40 | return this.model.deleteOne({_id: sortId, userId: userId}).exec(); 41 | } 42 | 43 | selectOneByUserId(userId) { 44 | return this.model.findOne({userId: userId}).exec(); 45 | } 46 | 47 | selectByCondition(condition) { 48 | return this.model.find(condition).exec(); 49 | } 50 | 51 | /** 52 | * 删除已经分享的分类 53 | * @param sortId 54 | * @returns {*|RegExpExecArray} 55 | */ 56 | updateForDeleteShareId(sortId) { 57 | return this.model.updateOne({_id: sortId}, {shared: false, "$unset": {shareId: ""}}).exec(); 58 | } 59 | } 60 | 61 | module.exports = new sort(); -------------------------------------------------------------------------------- /node-srv/models/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/15 4 | * @Description:用户模块数据库 5 | */ 6 | 7 | const baseModel = require('./basic.js'); 8 | 9 | class user extends baseModel { 10 | 11 | Schema() { 12 | return { 13 | username: {type: String, required: true, unique: true}, 14 | password: {type: String, required: true}, 15 | email: {type: String, required: true, unique: true}, 16 | nickname: {type: String, required: true}, 17 | headImg: {type: String, required: false} 18 | }; 19 | }; 20 | 21 | SchemaName() { 22 | return 'user'; 23 | }; 24 | 25 | save(user) { 26 | return new this.model(user).save(); 27 | } 28 | 29 | /** 30 | * 通过邮件或者帐号查询用户 31 | * @param loginId 32 | * @returns {Promise} 33 | */ 34 | selectByUsernameOrEmail(loginId) { 35 | let query = {}; 36 | if (loginId.indexOf('@') !== -1) { 37 | query.email = loginId; 38 | } else { 39 | query.username = loginId; 40 | } 41 | return this.model.findOne(query).exec(); 42 | } 43 | 44 | /** 45 | * 条件查询用户信息 46 | * @param condition 47 | * @returns {Promise} 48 | */ 49 | selectByConditionOnlyOne(condition) { 50 | return this.model.findOne(condition).exec(); 51 | } 52 | 53 | /** 54 | * 通过帐号更新密码 55 | * @param username 56 | * @param password 57 | * @returns {Promise} 58 | */ 59 | updatePasswordByUsername(username, password) { 60 | return this.model.updateOne({username: username}, {password: password}).exec(); 61 | } 62 | 63 | selectById(id) { 64 | return this.model.findOne({_id: baseModel.typeObject(id)}).exec(); 65 | } 66 | 67 | updateByUsername(condition, username) { 68 | return this.model.updateOne({username: username}, condition).exec(); 69 | } 70 | } 71 | 72 | module.exports = new user(); -------------------------------------------------------------------------------- /node-srv/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imgs-node-srv", 3 | "version": "1.0.7", 4 | "description": "图床服务端", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/lazy-koala/imgs-upload-srv.git" 12 | }, 13 | "keywords": [ 14 | "node", 15 | "srv" 16 | ], 17 | "dependencies": { 18 | "bluebird": "^3.7.2", 19 | "busboy": "^0.3.1", 20 | "colour": "^0.7.1", 21 | "koa": "^2.11.0", 22 | "koa-bodyparser": "^4.2.1", 23 | "koa-router": "^8.0.8", 24 | "md5": "^2.2.1", 25 | "mongoose": "^5.9.5", 26 | "nodemailer": "^6.4.5", 27 | "redis": "^3.0.2", 28 | "request": "^2.88.2", 29 | "sharp": "^0.25.4", 30 | "ua-device": "^0.1.10", 31 | "uuid": "^7.0.2" 32 | }, 33 | "author": "acexy@thankjava.com", 34 | "license": "ISC", 35 | "bugs": { 36 | "url": "https://github.com/lazy-koala/imgs-upload-srv/issues" 37 | }, 38 | "homepage": "https://github.com/lazy-koala/imgs-upload-srv#readme" 39 | } 40 | -------------------------------------------------------------------------------- /node-srv/routers/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/13 4 | * @Description: 普通路由匹配 /* 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const router = new Router({ 10 | prefix: '/' 11 | }); 12 | 13 | router.use(require('../controller/common')); 14 | 15 | module.exports = router.routes(); -------------------------------------------------------------------------------- /node-srv/routers/imgs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/14 4 | * @Description: 图片相关路由器 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const router = new Router({ 10 | prefix: '/imgs/' 11 | }); 12 | 13 | router.use(require('../controller/imgs')); 14 | 15 | module.exports = router.routes(); -------------------------------------------------------------------------------- /node-srv/routers/routerScanner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/12 4 | * @Description:基础路由扫描器 5 | */ 6 | const Router = require('koa-router'); 7 | const path = require('path'); 8 | const fs = require('fs'); 9 | 10 | // 自动扫描业务的路由定制 11 | const scanRouters = () => { 12 | 13 | let router = new Router(); 14 | let routersDirPath = __dirname; 15 | let routers = findFiles(routersDirPath); 16 | 17 | if (routers.length > 0) { 18 | console.log('=> koa: '.cyan + 'load routers:'.grey); 19 | routers.forEach((routerFilePath) => { 20 | console.log(' > '.black + path.relative(routersDirPath, routerFilePath).blue); 21 | router.use(require(routerFilePath)); 22 | }); 23 | } 24 | 25 | return router; 26 | }; 27 | 28 | const findFiles = (dirpath) => { 29 | let filesPath = []; 30 | 31 | // 提供了子目录扫描 32 | // function doFind(_path) { 33 | // let files = fs.readdirSync(_path); 34 | // files.forEach((val, index) => { 35 | // let fPath = path.join(_path, val); 36 | // let stats = fs.statSync(fPath); 37 | // if (stats.isDirectory()) doFind(fPath); 38 | // if (stats.isFile()) filesPath.push(fPath); 39 | // }); 40 | // 41 | // } 42 | 43 | function doFind(_path) { 44 | let files = fs.readdirSync(_path); 45 | files.forEach((val, index) => { 46 | let fPath = path.join(_path, val); 47 | let stats = fs.statSync(fPath); 48 | if (stats.isFile() && fPath !== __filename) filesPath.push(fPath); 49 | }); 50 | } 51 | 52 | doFind(dirpath); 53 | return filesPath; 54 | }; 55 | 56 | module.exports = scanRouters(); -------------------------------------------------------------------------------- /node-srv/routers/share.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2019/4/22 4 | * @Description: 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const router = new Router({ 10 | prefix: '/share/' 11 | }); 12 | 13 | router.use(require('../controller/share')); 14 | 15 | module.exports = router.routes(); -------------------------------------------------------------------------------- /node-srv/routers/sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2019/4/22 4 | * @Description: 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const router = new Router({ 10 | prefix: '/sort/' 11 | }); 12 | 13 | router.use(require('../controller/sort')); 14 | 15 | module.exports = router.routes(); -------------------------------------------------------------------------------- /node-srv/routers/tag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2019/4/22 4 | * @Description: 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const router = new Router({ 10 | prefix: '/tag/' 11 | }); 12 | 13 | router.use(require('../controller/tag')); 14 | 15 | module.exports = router.routes(); -------------------------------------------------------------------------------- /node-srv/routers/token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/7/26 4 | * @Description: 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const router = new Router({ 10 | prefix: '/token/' 11 | }); 12 | 13 | router.use(require('../controller/token')); 14 | 15 | module.exports = router.routes(); -------------------------------------------------------------------------------- /node-srv/routers/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/7/9 4 | * @Description:处理用户相关模块 5 | */ 6 | const Router = require('koa-router'); 7 | 8 | const router = new Router({ 9 | prefix: '/user/' 10 | }); 11 | 12 | router.use(require('../controller/user')); 13 | 14 | module.exports = router.routes(); --------------------------------------------------------------------------------