├── .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 | 
2 | 
3 | 
4 | 
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 | [](https://github.com/lazy-koala/)
68 |
69 | [](https://github.com/qazyuan/) [](https://github.com/acexy/)
70 |
71 | > #### Thanks [Jetbrains Ides](https://www.jetbrains.com/?from=imgs-upload-srv)
72 |
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
在线图床服务
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
29 |
30 |
31 |
32 | 登录
33 |
34 |
35 |
41 |
42 |
43 |
44 |
52 |
53 |
54 |
55 |
56 |
57 |
137 |
138 |
139 |
192 |
--------------------------------------------------------------------------------
/html-web/src/components/Person.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
21 |
22 |
25 |
26 |
27 |
28 |
115 |
--------------------------------------------------------------------------------
/html-web/src/components/common/CommonFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
--------------------------------------------------------------------------------
/html-web/src/components/common/CommonHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
50 |
51 |
127 |
--------------------------------------------------------------------------------
/html-web/src/components/common/ImgUpload.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
144 |
--------------------------------------------------------------------------------
/html-web/src/components/common/Person/HeadImg.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
![]()
7 |
10 |
11 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
25 |
26 |
27 |
28 | 修改昵称
29 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
136 |
--------------------------------------------------------------------------------
/html-web/src/components/common/Person/Token.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
15 |
16 |
19 |
20 |
23 |
24 |
27 |
28 |
32 |
33 |
36 |
37 |
38 |
39 | 批量删除
40 |
41 |
46 | 是否确认删除token?
47 |
51 |
52 |
53 |
54 |
142 |
--------------------------------------------------------------------------------
/html-web/src/components/common/Search.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
24 | {{tag}}
25 |
26 |
35 |
36 |
37 |
38 | 搜索
39 |
40 |
41 |
42 |
130 |
131 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/html-web/src/components/common/Timer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 获取验证码
4 | 倒计时
5 |
6 |
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 |
28 |
30 |
31 |
32 |
33 |
34 | 亲爱的 {0}:
36 |
37 | 你正在找回密码。
38 |
39 |
41 |
42 |
43 |
44 |
45 | 验证码
46 |
48 |
49 |
50 |
51 | 账号
52 | |
53 |
54 | 验证码
55 | |
56 |
57 | 有效时间
58 | |
59 |
60 |
61 |
62 | {1}
63 | |
64 |
65 | {2}
66 | |
67 |
68 | 15分钟
69 | |
70 |
71 |
72 |
73 | |
74 |
75 |
76 |
77 |
78 | 本站为非盈利性服务,意在为你提供在线存储解决方案。
79 | 请勿上传任何违反国家法律的图片!
80 | 互联网为开放网络,如果你泄漏你的在线图片地址,其他任何人均能访问。
81 | 为了你的个人隐私安全,请勿上传敏感信息。
82 | 如果你有任何的建议或反馈,可邮件至 team@thankjava.com
83 | 科技向善,互利互助。
84 |
85 | 邮件由系统自动发送,请勿回复。
86 |
87 |
89 |
90 |
91 |
92 |
93 |
94 |
96 |
98 |
99 | |
100 |
101 |
102 |
103 | |
104 |
105 |
106 |
107 |
108 | 
110 | © Copyright (C) 2018-2021 , All rights reserved .
111 | |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/node-srv/config/mailTemplate/RegisteCode.html:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
30 |
31 |
32 |
33 |
34 | 亲爱的 {0}:
36 |
37 | 你正在注册图床服务(https://imgs.acexy.cn)。
38 |
39 |
41 |
42 |
43 |
44 |
45 | 验证码
46 |
48 |
49 |
50 |
51 | 注册账号
52 | |
53 |
54 | 验证码
55 | |
56 |
57 | 有效时间
58 | |
59 |
60 |
61 |
62 | {1}
63 | |
64 |
65 | {2}
66 | |
67 |
68 | 15分钟
69 | |
70 |
71 |
72 |
73 | |
74 |
75 |
76 |
77 |
78 | 本站为非盈利性服务,意在为你提供在线存储解决方案。
79 | 请勿上传任何违反国家法律的图片!
80 | 互联网为开放网络,如果你泄漏你的在线图片地址,其他任何人均能访问。
81 | 为了你的个人隐私安全,请勿上传敏感信息。
82 | 如果你有任何的建议或反馈,可邮件至 team@thankjava.com
83 | 科技向善,互利互助。
84 |
85 | 邮件由系统自动发送,请勿回复。
86 |
87 |
89 |
90 |
91 |
92 |
93 |
94 |
96 |
98 |
99 | |
100 |
101 |
102 |
103 | |
104 |
105 |
106 |
107 |
108 | 
110 | © Copyright (C) 2018-2021 , All rights reserved .
111 | |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/node-srv/config/mailTemplate/UpdateMailCode.html:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
30 |
31 |
32 |
33 |
34 | 亲爱的 {0}:
36 |
37 | 你正在修改邮箱地址。
38 |
39 |
41 |
42 |
43 |
44 |
45 | 验证码
46 |
48 |
49 |
50 |
51 | 当前邮箱
52 | |
53 |
54 | 验证码
55 | |
56 |
57 | 有效时间
58 | |
59 |
60 |
61 |
62 | {1}
63 | |
64 |
65 | {2}
66 | |
67 |
68 | 15分钟
69 | |
70 |
71 |
72 |
73 | |
74 |
75 |
76 |
77 |
78 | 本站为非盈利性服务,意在为你提供在线存储解决方案。
79 | 请勿上传任何违反国家法律的图片!
80 | 互联网为开放网络,如果你泄漏你的在线图片地址,其他任何人均能访问。
81 | 为了你的个人隐私安全,请勿上传敏感信息。
82 | 如果你有任何的建议或反馈,可邮件至 team@thankjava.com
83 | 科技向善,互利互助。
84 |
85 | 邮件由系统自动发送,请勿回复。
86 |
87 |
89 |
90 |
91 |
92 |
93 |
94 |
96 |
98 |
99 | |
100 |
101 |
102 |
103 | |
104 |
105 |
106 |
107 |
108 | 
110 | © Copyright (C) 2018-2021 , All rights reserved .
111 | |
112 |
113 |
114 |
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();
--------------------------------------------------------------------------------