Browse Source

初始化平台项目

gemer cheung 4 years ago
commit
20a81cc841
100 changed files with 5988 additions and 0 deletions
  1. 3 0
      .eslintignore
  2. 20 0
      .eslintrc.js
  3. 39 0
      .gitignore
  4. 12 0
      Dockerfile
  5. 201 0
      LICENSE
  6. 18 0
      README.md
  7. 1 0
      app.js
  8. 47 0
      bin/start_example.sh
  9. 12 0
      changelog.md
  10. 47 0
      docker-compose.yml
  11. 1 0
      env.js
  12. 5 0
      gulpfile.js
  13. 116 0
      index.js
  14. 1 0
      logs/readme.md
  15. 2 0
      mongo/data/db/WiredTiger
  16. 1 0
      mongo/data/db/WiredTiger.lock
  17. 6 0
      mongo/data/db/WiredTiger.turtle
  18. BIN
      mongo/data/db/WiredTiger.wt
  19. BIN
      mongo/data/db/WiredTigerHS.wt
  20. BIN
      mongo/data/db/_mdb_catalog.wt
  21. BIN
      mongo/data/db/collection-0--6205273365790363205.wt
  22. BIN
      mongo/data/db/collection-0-7189198554662802764.wt
  23. BIN
      mongo/data/db/collection-1--6205273365790363205.wt
  24. BIN
      mongo/data/db/collection-2--6205273365790363205.wt
  25. BIN
      mongo/data/db/collection-2-7189198554662802764.wt
  26. BIN
      mongo/data/db/collection-20--6205273365790363205.wt
  27. BIN
      mongo/data/db/collection-3--6205273365790363205.wt
  28. BIN
      mongo/data/db/collection-4-7189198554662802764.wt
  29. BIN
      mongo/data/db/collection-6--6205273365790363205.wt
  30. BIN
      mongo/data/db/collection-7-7189198554662802764.wt
  31. BIN
      mongo/data/db/collection-9--6205273365790363205.wt
  32. BIN
      mongo/data/db/diagnostic.data/metrics.2021-02-17T14-51-32Z-00000
  33. BIN
      mongo/data/db/diagnostic.data/metrics.2021-02-17T14-52-12Z-00000
  34. BIN
      mongo/data/db/diagnostic.data/metrics.interim
  35. BIN
      mongo/data/db/index-1-7189198554662802764.wt
  36. BIN
      mongo/data/db/index-10--6205273365790363205.wt
  37. BIN
      mongo/data/db/index-11--6205273365790363205.wt
  38. BIN
      mongo/data/db/index-12--6205273365790363205.wt
  39. BIN
      mongo/data/db/index-13--6205273365790363205.wt
  40. BIN
      mongo/data/db/index-14--6205273365790363205.wt
  41. BIN
      mongo/data/db/index-15--6205273365790363205.wt
  42. BIN
      mongo/data/db/index-16--6205273365790363205.wt
  43. BIN
      mongo/data/db/index-17--6205273365790363205.wt
  44. BIN
      mongo/data/db/index-18--6205273365790363205.wt
  45. BIN
      mongo/data/db/index-19--6205273365790363205.wt
  46. BIN
      mongo/data/db/index-21--6205273365790363205.wt
  47. BIN
      mongo/data/db/index-3-7189198554662802764.wt
  48. BIN
      mongo/data/db/index-4--6205273365790363205.wt
  49. BIN
      mongo/data/db/index-5--6205273365790363205.wt
  50. BIN
      mongo/data/db/index-5-7189198554662802764.wt
  51. BIN
      mongo/data/db/index-6-7189198554662802764.wt
  52. BIN
      mongo/data/db/index-7--6205273365790363205.wt
  53. BIN
      mongo/data/db/index-8--6205273365790363205.wt
  54. BIN
      mongo/data/db/index-8-7189198554662802764.wt
  55. BIN
      mongo/data/db/index-9-7189198554662802764.wt
  56. BIN
      mongo/data/db/journal/WiredTigerLog.0000000003
  57. BIN
      mongo/data/db/journal/WiredTigerPreplog.0000000001
  58. BIN
      mongo/data/db/journal/WiredTigerPreplog.0000000002
  59. 1 0
      mongo/data/db/mongod.lock
  60. BIN
      mongo/data/db/sizeStorer.wt
  61. BIN
      mongo/data/db/storage.bson
  62. 69 0
      package.json
  63. 1 0
      public/index.html
  64. 748 0
      public/resource/docs/README.html
  65. 281 0
      public/resource/docs/README.md
  66. BIN
      public/resource/docs/图标管理平台结构设计.png
  67. 2 0
      public/static/css/app.512930305bcaabae8342c9870485a0b1.css
  68. 1 0
      public/static/css/app.512930305bcaabae8342c9870485a0b1.css.map
  69. BIN
      public/static/fonts/ionicons.05acfdb.woff
  70. BIN
      public/static/fonts/ionicons.24712f6.ttf
  71. BIN
      public/static/fonts/ionicons.2c2ae06.eot
  72. 2230 0
      public/static/fonts/ionicons.621bd38.svg
  73. 2 0
      public/static/js/app.99b5077633f395f72be1.js
  74. 1 0
      public/static/js/app.99b5077633f395f72be1.js.map
  75. 2 0
      public/static/js/manifest.7c73e181da5eacf1a236.js
  76. 1 0
      public/static/js/manifest.7c73e181da5eacf1a236.js.map
  77. 23 0
      public/static/js/vendor.0340c7d1b0e2e05b8117.js
  78. 1 0
      public/static/js/vendor.0340c7d1b0e2e05b8117.js.map
  79. 108 0
      server/config/avatarConfig.js
  80. 12 0
      server/config/config.js
  81. 10 0
      server/config/constant.js
  82. 19 0
      server/config/dbConfig.js
  83. 4 0
      server/config/loggerConfig.js
  84. 9 0
      server/config/redisConfig.js
  85. 299 0
      server/controller/iconController.js
  86. 210 0
      server/controller/iconDraftController.js
  87. 780 0
      server/controller/repoController.js
  88. 321 0
      server/controller/userController.js
  89. 34 0
      server/database/connect.js
  90. 141 0
      server/database/index.js
  91. 7 0
      server/database/model/counter.js
  92. 10 0
      server/database/model/icon.js
  93. 12 0
      server/database/model/iconBelongToRepo.js
  94. 12 0
      server/database/model/iconDraft.js
  95. 24 0
      server/database/model/iconRepo.js
  96. 7 0
      server/database/model/repoRecommend.js
  97. 19 0
      server/database/model/user.js
  98. 39 0
      server/database/redisStorage.js
  99. 16 0
      server/middleware/auth.js
  100. 0 0
      server/middleware/getUserInfo.js

+ 3 - 0
.eslintignore

@@ -0,0 +1,3 @@
+node_modules/*.js
+ui
+public

+ 20 - 0
.eslintrc.js

@@ -0,0 +1,20 @@
+module.exports = {
+  // parser: "@babel/eslint-parser",
+  // extends: ["plugin:prettier/recommended"],
+  extends: ["eslint:recommended", "plugin:prettier/recommended"],
+  env: {
+    browser: true,
+    amd: true,
+    node: true,
+  },
+  // vuecli3.0 的
+  // extends: ['plugin:vue/essential', '@vue/prettier'],
+  // required to lint *.vue files 使用 html参数
+  plugins: ["prettier"],
+  // rules 规则就按照各家写法。
+  // rules: {
+  //   "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
+  //   "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
+  //   // 'prettier/prettier': 'error',
+  // },
+};

+ 39 - 0
.gitignore

@@ -0,0 +1,39 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+package-lock.json
+lib/
+logs/*.log
+.cache/
+public/resource/repository/
+.vscode/
+
+# reserve default service
+#login service
+server/service/login/
+!server/service/login/default/
+!server/service/login/github/
+!server/service/login/github_qiniu/
+server/service/login/index.js
+
+
+#upload service
+server/upload/login/
+!server/upload/login/default/
+!server/upload/login/qiniu/
+!server/upload/login/github_qiniu/
+server/upload/login/index.js
+
+# personal start script
+start.sh
+start.bat
+
+# Editor directories and files
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln

+ 12 - 0
Dockerfile

@@ -0,0 +1,12 @@
+FROM node:erbium-buster
+
+WORKDIR /nicon/app
+COPY ./ /nicon/app
+
+RUN npm install
+# RUN npm install--prefix frontend
+# RUN npm run start
+
+EXPOSE 4843
+
+CMD ["node", "index.js"]

+ 201 - 0
LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

File diff suppressed because it is too large
+ 18 - 0
README.md


+ 1 - 0
app.js

@@ -0,0 +1 @@
+console.log("debug");

+ 47 - 0
bin/start_example.sh

@@ -0,0 +1,47 @@
+#!/bin/bash
+
+# DB config
+
+# mongodb
+export MONGODB_NAME=iconRepo;
+export MONGODB_HOST=127.0.0.1;
+export MONGODB_PORT=27017;
+export MONGODB_USERNAME='';
+export MONGODB_PASSWORD='';
+
+
+# redis
+export REDIS_FAMILY=4;
+export REDIS_HOST=127.0.0.1;
+export REDIS_PORT=6379;
+export REDIS_PASSWORD='';
+export REDIS_DB=0;
+
+
+# config your website host
+export productHost='icon.bolin.site';
+
+
+# if you want login by github and upload by qiniu, set productType
+export productType='github_qiniu';
+
+
+# Login config
+
+# github openid login
+export GITHUB_LOGIN_CLIENT_ID='';
+export GITHUB_LOGIN_CLIENT_SECRET='';
+export GITHUB_LOGIN_REDIRECT_URI='';
+
+
+
+# Upload config
+
+# qiniu
+export QINIU_UPLOAD_ACCESS_KEY='';
+export QINIU_UPLOAD_SECRET_KEY='';
+export QINIU_UPLOAD_BUCKET='';
+export QINIU_UPLOAD_CDN_HOST='';
+
+# start command
+node index.js

+ 12 - 0
changelog.md

@@ -0,0 +1,12 @@
+## 2018-05-27
+
+1. 添加4种默认登录&上传服务类型productType
+2. 支持登录&上传服务类型自定义,代码自定义功能
+3. 图标源文件在本地保留,实时添加、删除图标
+4. .gitignore中保留默认登录&上传服务文件夹,用户自定义生成的忽略
+5. 添加installApp 中间件,启动程序时判断启动脚本是否存在而决定是否链接数据库
+
+## 2018-06-07
+
+1. 添加可视化配置、自动重启
+2. 更新添加可视化配置readme

+ 47 - 0
docker-compose.yml

@@ -0,0 +1,47 @@
+version: "3.3"
+services:
+  nicon:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    environment:
+      MONGODB_NAME: "iconRepo"
+      MONGODB_HOST: "mongo"
+      MONGODB_PORT: "27017"
+      MONGODB_USERNAME: "4dage"
+      MONGODB_PASSWORD: "4dage168"
+      MONGO_AUTHSOURCE: "admin"
+      REDIS_HOST: "redis"
+      REDIS_PORT: "6379"
+      REDIS_PASSWORD: "redis769394"
+      productHost: "localhost"
+      productType: "default"
+    
+    ports:
+      - "4843:4843" # frontend port mapping 前端端口映射
+    depends_on:
+      - mongo
+      - redis
+    volumes:
+      - "/usr/local/var/log/nicon:/var/logs/nicon" # log persistent 日志持久化
+  mongo:
+    image: mongo:latest
+    restart: always
+    environment:
+      MONGO_INITDB_DATABASE: iconRepo
+      MONGO_INITDB_ROOT_USERNAME: 4dage
+      MONGO_INITDB_ROOT_PASSWORD: 4dage168
+    volumes:
+      - "./mongo/data/db:/data/db" # make data persistent 持久化
+    # command: mongod --auth
+    ports:
+      - "27128:27017" # expose port to host machine 暴露接口到宿主机
+  redis:
+    image: redis:latest
+    restart: always
+    command: redis-server --requirepass "redis769394" # set redis password 设置 Redis 密码
+    volumes:
+      - "./redis/data:/data" # make data persistent 持久化
+    ports:
+      - "6383:6379" # expose port to host machine 暴露接口到宿主机
+

+ 1 - 0
env.js

@@ -0,0 +1 @@
+console.log(process.env);

+ 5 - 0
gulpfile.js

@@ -0,0 +1,5 @@
+var gulp = require("gulp");
+
+require("./server/tool/icon");
+
+module.exports = gulp;

+ 116 - 0
index.js

@@ -0,0 +1,116 @@
+let Koa = require("koa");
+let app = new Koa();
+const KoaStatic = require("koa-static");
+let fileUtil = require("./server/util/fileUtil");
+let installApp = require("./server/middleware/install");
+let log = require("./server/util/log");
+let bodyParser = require("koa-bodyparser");
+let koaBody = require("koa-body");
+let config = require("./server/config/config");
+let responseFormat = require("./server/util/responseFormat");
+let path = require("path");
+let startFilePath = path.resolve(__dirname, "./bin/start.sh");
+
+/**
+ * register router and connect db to expose serve
+ *
+ * @return   {void}
+ */
+async function startServe() {
+  if (await fileUtil.exists(startFilePath)) {
+    let AppRouter = require("./server/router/index");
+    let connectDB = require("./server/database/connect");
+    let redis = require("./server/database/redisStorage.js");
+    let session = require("koa-session2");
+
+    // redis记录session
+    app.use(
+      session({
+        store: redis,
+      })
+    );
+
+    await connectDB();
+
+    // register router
+    new AppRouter(app);
+  } else {
+    app.use(installApp());
+  }
+}
+
+/**
+ * entrance
+ *
+ * @return   {void}
+ */
+async function start() {
+  // 处理全局错误
+  app.use(async (ctx, next) => {
+    try {
+      const start = +new Date();
+      const result = await next();
+      const spendTime = +new Date() - start;
+      const normalTTL = 350;
+      const requestStatus = spendTime > normalTTL ? "optimize" : "normal";
+      log.debug(
+        `[${requestStatus}] request [${ctx.originalUrl}] spend time is ${spendTime}ms ...`
+      );
+      return result;
+    } catch (error) {
+      // todo 做日志处理
+      // 统一错误出口
+      let er = error || {};
+      ctx.status = 200;
+      let message = er.message;
+      if (er.code === 11000) {
+        message = (message.match(/"(.*)"/) || [])[1] + " 名称重复,请修改!";
+      }
+      let stack = er.stack || er;
+      log.error(stack);
+      ctx.body = responseFormat.responseFormat(500, message || stack, false);
+    }
+  });
+
+  // 设置全局变量
+  app.use(async (ctx, next) => {
+    global.globalConfig = {};
+    Object.assign(global.globalConfig, {
+      ctx: ctx,
+      nowTime: +new Date(),
+      // 数据库能暴露给客户端的字段,当查询条件用
+      iconRepoExportFields:
+        "repoId iconPrefix repoName createTime iconIds fontPath ownerId",
+      iconExportFields: "iconId iconName iconContent ownerId",
+      iconDraftExportFields: "iconId iconName iconContent",
+      userExportFields: "userId userName nickName repos avatar",
+    });
+    await next();
+  });
+
+  // 文件上传multiple/from-data 解析到req.body
+  app.use(
+    koaBody({
+      multipart: true,
+      urlencoded: true,
+    })
+  );
+
+  // 请求参数解析
+  app.use(bodyParser());
+
+  app.use(KoaStatic(path.join(__dirname, "public"), {}));
+
+  await startServe();
+
+  app.listen(config.ICON_APP_PORT, () => {
+    log.debug(`app listen on port ${config.ICON_APP_PORT} ...`);
+  });
+
+  app.on("error", (err) => {
+    log.error(err);
+    process.exit(1);
+  });
+}
+
+start();

+ 1 - 0
logs/readme.md

@@ -0,0 +1 @@
+## logs 文件夹必须存在, 否则在linux中运行sh ./bin/start.sh 时会报错

+ 2 - 0
mongo/data/db/WiredTiger

@@ -0,0 +1,2 @@
+WiredTiger
+WiredTiger 10.0.0: (March 18, 2020)

+ 1 - 0
mongo/data/db/WiredTiger.lock

@@ -0,0 +1 @@
+WiredTiger lock file

File diff suppressed because it is too large
+ 6 - 0
mongo/data/db/WiredTiger.turtle


BIN
mongo/data/db/WiredTiger.wt


BIN
mongo/data/db/WiredTigerHS.wt


BIN
mongo/data/db/_mdb_catalog.wt


BIN
mongo/data/db/collection-0--6205273365790363205.wt


BIN
mongo/data/db/collection-0-7189198554662802764.wt


BIN
mongo/data/db/collection-1--6205273365790363205.wt


BIN
mongo/data/db/collection-2--6205273365790363205.wt


BIN
mongo/data/db/collection-2-7189198554662802764.wt


BIN
mongo/data/db/collection-20--6205273365790363205.wt


BIN
mongo/data/db/collection-3--6205273365790363205.wt


BIN
mongo/data/db/collection-4-7189198554662802764.wt


BIN
mongo/data/db/collection-6--6205273365790363205.wt


BIN
mongo/data/db/collection-7-7189198554662802764.wt


BIN
mongo/data/db/collection-9--6205273365790363205.wt


BIN
mongo/data/db/diagnostic.data/metrics.2021-02-17T14-51-32Z-00000


BIN
mongo/data/db/diagnostic.data/metrics.2021-02-17T14-52-12Z-00000


BIN
mongo/data/db/diagnostic.data/metrics.interim


BIN
mongo/data/db/index-1-7189198554662802764.wt


BIN
mongo/data/db/index-10--6205273365790363205.wt


BIN
mongo/data/db/index-11--6205273365790363205.wt


BIN
mongo/data/db/index-12--6205273365790363205.wt


BIN
mongo/data/db/index-13--6205273365790363205.wt


BIN
mongo/data/db/index-14--6205273365790363205.wt


BIN
mongo/data/db/index-15--6205273365790363205.wt


BIN
mongo/data/db/index-16--6205273365790363205.wt


BIN
mongo/data/db/index-17--6205273365790363205.wt


BIN
mongo/data/db/index-18--6205273365790363205.wt


BIN
mongo/data/db/index-19--6205273365790363205.wt


BIN
mongo/data/db/index-21--6205273365790363205.wt


BIN
mongo/data/db/index-3-7189198554662802764.wt


BIN
mongo/data/db/index-4--6205273365790363205.wt


BIN
mongo/data/db/index-5--6205273365790363205.wt


BIN
mongo/data/db/index-5-7189198554662802764.wt


BIN
mongo/data/db/index-6-7189198554662802764.wt


BIN
mongo/data/db/index-7--6205273365790363205.wt


BIN
mongo/data/db/index-8--6205273365790363205.wt


BIN
mongo/data/db/index-8-7189198554662802764.wt


BIN
mongo/data/db/index-9-7189198554662802764.wt


BIN
mongo/data/db/journal/WiredTigerLog.0000000003


BIN
mongo/data/db/journal/WiredTigerPreplog.0000000001


BIN
mongo/data/db/journal/WiredTigerPreplog.0000000002


+ 1 - 0
mongo/data/db/mongod.lock

@@ -0,0 +1 @@
+1

BIN
mongo/data/db/sizeStorer.wt


BIN
mongo/data/db/storage.bson


+ 69 - 0
package.json

@@ -0,0 +1,69 @@
+{
+  "name": "icon-server",
+  "version": "1.0.0",
+  "description": "字体图标管理平台",
+  "main": "index.js",
+  "scripts": {
+    "publish": "./node_modules/pm2/bin/pm2 start index.js --name iconServer -f -e ./logs/icon.err.log -o ./logs/icon.log",
+    "stop": "./node_modules/pm2/bin/pm2 delete iconServer",
+    "lint": "eslint --ext .js ./ --fix",
+    "start": "node index.js",
+    "reset": "./node_modules/pm2/bin/pm2 reset iconServer && ./node_modules/pm2/bin/pm2 updatePM2",
+    "restart": "./node_modules/pm2/bin/pm2 restart iconServer --update-env"
+  },
+  "author": "",
+  "license": "MIT",
+  "dependencies": {
+    "crypto-js": "^3.1.9-1",
+    "ejs": "^2.5.7",
+    "fs-extra": "^4.0.1",
+    "gulp": "^3.9.1",
+    "gulp-consolidate": "^0.2.0",
+    "gulp-iconfont": "^9.0.0",
+    "gulp-iconfont-css": "^2.1.0",
+    "gulp-rename": "^1.2.2",
+    "gulp-sass": "^3.1.0",
+    "gulp-sourcemaps": "^2.6.0",
+    "ioredis": "^3.2.1",
+    "jwt-decode": "^2.2.0",
+    "koa": "^2.3.0",
+    "koa-body": "^2.5.0",
+    "koa-bodyparser": "^2.5.0",
+    "koa-cookie": "^1.0.0",
+    "koa-cors": "0.0.16",
+    "koa-router": "^7.2.1",
+    "koa-session": "^5.5.0",
+    "koa-session2": "^2.2.5",
+    "koa-static": "^5.0.0",
+    "koa-static-cache": "^5.1.2",
+    "lodash": "^4.17.4",
+    "mkdirp": "^0.5.1",
+    "mongoose": "^4.12.1",
+    "mongoose-unique-array": "^0.2.0",
+    "pinyin": "^2.8.3",
+    "qiniu": "^7.1.2",
+    "querystring": "^0.2.0",
+    "request": "^2.83.0",
+    "request-promise": "^4.2.2",
+    "rimraf": "^2.6.2",
+    "safe-buffer": "^5.1.1",
+    "svgo": "^1.0.3",
+    "thunkify-wrap": "^1.0.4",
+    "winston": "^2.4.0",
+    "winston-daily-rotate-file": "^1.7.2"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.12.16",
+    "@babel/eslint-parser": "^7.12.16",
+    "eslint": "^7.20.0",
+    "eslint-config-prettier": "^7.2.0",
+    "eslint-plugin-prettier": "^3.3.1",
+    "gulp-rev": "^8.1.1",
+    "node-debug": "^0.1.0",
+    "pm2": "^2.10.4",
+    "prettier": "2.2.1"
+  },
+  "resolutions": {
+    "graceful-fs": "^4.2.4"
+  }
+}

File diff suppressed because it is too large
+ 1 - 0
public/index.html


File diff suppressed because it is too large
+ 748 - 0
public/resource/docs/README.html


File diff suppressed because it is too large
+ 281 - 0
public/resource/docs/README.md


BIN
public/resource/docs/图标管理平台结构设计.png


File diff suppressed because it is too large
+ 2 - 0
public/static/css/app.512930305bcaabae8342c9870485a0b1.css


File diff suppressed because it is too large
+ 1 - 0
public/static/css/app.512930305bcaabae8342c9870485a0b1.css.map


BIN
public/static/fonts/ionicons.05acfdb.woff


BIN
public/static/fonts/ionicons.24712f6.ttf


BIN
public/static/fonts/ionicons.2c2ae06.eot


File diff suppressed because it is too large
+ 2230 - 0
public/static/fonts/ionicons.621bd38.svg


File diff suppressed because it is too large
+ 2 - 0
public/static/js/app.99b5077633f395f72be1.js


File diff suppressed because it is too large
+ 1 - 0
public/static/js/app.99b5077633f395f72be1.js.map


File diff suppressed because it is too large
+ 2 - 0
public/static/js/manifest.7c73e181da5eacf1a236.js


File diff suppressed because it is too large
+ 1 - 0
public/static/js/manifest.7c73e181da5eacf1a236.js.map


File diff suppressed because it is too large
+ 23 - 0
public/static/js/vendor.0340c7d1b0e2e05b8117.js


File diff suppressed because it is too large
+ 1 - 0
public/static/js/vendor.0340c7d1b0e2e05b8117.js.map


+ 108 - 0
server/config/avatarConfig.js

@@ -0,0 +1,108 @@
+module.exports = [
+  "//icon.nosdn.127.net/74ff70a4c2a054c7f2eab674b511deba",
+  "//icon.nosdn.127.net/31e45bcc3a794553256b274b20d494ef",
+  "//icon.nosdn.127.net/8d94c2eecb1f045ccf872d6fa081a6ff",
+  "//icon.nosdn.127.net/0537ab0ff283978bbb183d68389485d2",
+  "//icon.nosdn.127.net/0793ee87d2c376156df02b0e0786269a",
+  "//icon.nosdn.127.net/bfd1d1b65d61925ad27727ab1b80a753",
+  "//icon.nosdn.127.net/e382c72316d51feb7c6ed5ab254e68d7",
+  "//icon.nosdn.127.net/70649365f06f8f34bd566eefa4dcee6c",
+  "//icon.nosdn.127.net/fc101ec82c99973f21754047d2024b29",
+  "//icon.nosdn.127.net/7bb31dae08568ca823c7d371a3dd3483",
+  "//icon.nosdn.127.net/d0bc11c28b0e501143bad8e5f8662eeb",
+  "//icon.nosdn.127.net/29d66b22b89286f634ba6290b2eb134c",
+  "//icon.nosdn.127.net/db7a224ccba9e338525f9e9bc685a6c9",
+  "//icon.nosdn.127.net/d7a0441d41de561ecae81ec181d3d194",
+  "//icon.nosdn.127.net/476cebb716c4cbd1beef84679b9e6f0b",
+  "//icon.nosdn.127.net/7f397c5b199e9db72922c0f7a37f0e89",
+  "//icon.nosdn.127.net/cbb152013e3e47292d4f7a619b198c7c",
+  "//icon.nosdn.127.net/406065f2164d4d4f134e1b2becb7e6b4",
+  "//icon.nosdn.127.net/b7a2948c55d6ee32fb8f4d7df99aa035",
+  "//icon.nosdn.127.net/59b21feb85dcdd0a0e8617cc6e247269",
+  "//icon.nosdn.127.net/ffc233aa63e6e981c4c305274e5477ab",
+  "//icon.nosdn.127.net/8e4fa2a6b84754086d9947dae3b008c6",
+  "//icon.nosdn.127.net/6718ea5d1dffb36e6c5b95383d328081",
+  "//icon.nosdn.127.net/ac97bd15df21f6cece2eaf5382cdbd58",
+  "//icon.nosdn.127.net/448976cf0dd00fea873bb3e45e3e0f4a",
+  "//icon.nosdn.127.net/fb6acd49dccba3b66ec08626f653958a",
+  "//icon.nosdn.127.net/96ab65db3d70682b1f1de6fdd75d0921",
+  "//icon.nosdn.127.net/37d20f3829b02d4af2332b4e489613b0",
+  "//icon.nosdn.127.net/44bb5df2f63552f4ae98390ee7d0358d",
+  "//icon.nosdn.127.net/78b7291e42258007890efbe4ac518547",
+  "//icon.nosdn.127.net/13244903ad50f24390e35c59f973c96f",
+  "//icon.nosdn.127.net/bc91ad3c800e4b9b0b2ecd133747cde5",
+  "//icon.nosdn.127.net/f1d04ced1f29e28c2fa03304517f55a4",
+  "//icon.nosdn.127.net/bae805be565aa32b4586943f841cff44",
+  "//icon.nosdn.127.net/8a118bfe93f7cfba4bf81a155705683c",
+  "//icon.nosdn.127.net/8df83918f6fbf0abd19f500756855064",
+  "//icon.nosdn.127.net/5152950ea14d3a1d7520c17aaff55c83",
+  "//icon.nosdn.127.net/8010a701851a8e3cc70560bf81da018a",
+  "//icon.nosdn.127.net/f5267dd645330c952d1b175cdc6435cc",
+  "//icon.nosdn.127.net/268002a52e1ca573490ad91e93455c11",
+  "//icon.nosdn.127.net/d91373c185825a568ee5ad795e7f631d",
+  "//icon.nosdn.127.net/cdc2b8b25ea9582bf1dbf26fe5e01647",
+  "//icon.nosdn.127.net/05ec48a2de4a8429cbe4c32551d79712",
+  "//icon.nosdn.127.net/f83f7398693edca5470afde4ef03f3b5",
+  "//icon.nosdn.127.net/d8aae68efb685bd556dfc42e8321565e",
+  "//icon.nosdn.127.net/6336f8be5834d4df95d065b9c25c0cb6",
+  "//icon.nosdn.127.net/16d9e2646bc52e445ab969c00a3b747f",
+  "//icon.nosdn.127.net/9e70348f5b1b9a7d04e0154408405fdd",
+  "//icon.nosdn.127.net/8830976b2471ed7fa52db9b4ae93193f",
+  "//icon.nosdn.127.net/b154b9ab96a0eb225a74dc87b3a66f75",
+  "//icon.nosdn.127.net/0a10e719d927a7d64aba5d9c7250a7f0",
+  "//icon.nosdn.127.net/721a10447489d966a83a25ba03f17c4f",
+  "//icon.nosdn.127.net/e73f6b1d0b7204ef58bb73e1428044c0",
+  "//icon.nosdn.127.net/258959d5e0e21343c8b55d44c2b9365f",
+  "//icon.nosdn.127.net/06cb51cb5afc0eaa921b6b526243490b",
+  "//icon.nosdn.127.net/7cb7ddcda3a60b43d968419168299cba",
+  "//icon.nosdn.127.net/550048cbfbcd116c2b8bce9b98b84a54",
+  "//icon.nosdn.127.net/55553202f358b5f6361445f774ccfc46",
+  "//icon.nosdn.127.net/ce81c2e64d1b782edae8ddec90132a32",
+  "//icon.nosdn.127.net/b8b962dd5a5271094f9f0bcadf99c2c4",
+  "//icon.nosdn.127.net/66e714455c7fb54c621acec6faa3bbea",
+  "//icon.nosdn.127.net/3660c6c0941d3bd941d7ad3411bbd909",
+  "//icon.nosdn.127.net/916213cf0d133e282cc8aa0127be21b5",
+  "//icon.nosdn.127.net/801a346f284f80a03104550e4d1264c0",
+  "//icon.nosdn.127.net/504b21962807e1c6d9d25588f72904ae",
+  "//icon.nosdn.127.net/09c6eab14b0eaf23821723bc2b82c21c",
+  "//icon.nosdn.127.net/4aeab8f07900fdfa2d4296d0e232b11a",
+  "//icon.nosdn.127.net/8c6950b574911de9ab2803d0cee1094b",
+  "//icon.nosdn.127.net/c29c53061f55ca4f0b4fcc81a9a5ae17",
+  "//icon.nosdn.127.net/364a1a8dd9c6600e7383ce18d58a0533",
+  "//icon.nosdn.127.net/7968e0b401678c249c20dd370d54d766",
+  "//icon.nosdn.127.net/823fe206b1cf7da7dc04fce3d64eb36e",
+  "//icon.nosdn.127.net/55c5e33270de743bca9e87e09f8828e5",
+  "//icon.nosdn.127.net/f787db5e338008efd6d0726c186700b7",
+  "//icon.nosdn.127.net/133d5e9f44132f9ece51183c3235ef4c",
+  "//icon.nosdn.127.net/0159151835306b98040221dd680186f8",
+  "//icon.nosdn.127.net/43da43a5c1b24636be064fc3cd8320f1",
+  "//icon.nosdn.127.net/01810020a366d8338ab14934126c10ef",
+  "//icon.nosdn.127.net/1dbf8662aed403534e29551149600c56",
+  "//icon.nosdn.127.net/bc1bd578ccd53cd2764605f669c29d0d",
+  "//icon.nosdn.127.net/5bd842e727a1a173c44dd155fb760f51",
+  "//icon.nosdn.127.net/103963b0468efe2d119f8c566df2216f",
+  "//icon.nosdn.127.net/0ffdbc69299db4cc9529bd6aeefed57b",
+  "//icon.nosdn.127.net/dee17a44a5c71bc6220b727db8f98213",
+  "//icon.nosdn.127.net/f8b6cc78c2c5ba5e4d5e29aa2c064851",
+  "//icon.nosdn.127.net/944a19103f5bf896e8a6cf471cc76629",
+  "//icon.nosdn.127.net/d7559259a90b1ba927bd4ec2c2df8a30",
+  "//icon.nosdn.127.net/b7b2f95561fd6bcd69f45ea4b3655073",
+  "//icon.nosdn.127.net/883beb2b53f5f973021eff8b411d23a2",
+  "//icon.nosdn.127.net/c953960cc02fa5a6d287717f3d768c48",
+  "//icon.nosdn.127.net/1f477a7df6fe61a45636187a8accc9b5",
+  "//icon.nosdn.127.net/f8f785d96345a2db97b99742b7e6d0b8",
+  "//icon.nosdn.127.net/266078b792aa536d22e95d9aa41d7468",
+  "//icon.nosdn.127.net/1d9c486f99d38d4581b2f7c3d36ba769",
+  "//icon.nosdn.127.net/60bad35f40c2076ea1cb8b92488f0ddd",
+  "//icon.nosdn.127.net/3ffb28770313e3357d36908798d7dd47",
+  "//icon.nosdn.127.net/d7ee3f5565bba6e4d3cdae70d4dc607f",
+  "//icon.nosdn.127.net/2955e6d78c4eb60938cedea77fcb1db9",
+  "//icon.nosdn.127.net/f0edc71120c451e1b197503869d4adda",
+  "//icon.nosdn.127.net/236af46f22b3ad7816a8431c854d2a08",
+  "//icon.nosdn.127.net/4bea4c719f8f320ab2f969e612b901ba",
+  "//icon.nosdn.127.net/a4c2965d9429f46be5b97f9e0e83c3d5",
+  "//icon.nosdn.127.net/acc1d1c438c6ff767596efa5fd9561a4",
+  "//icon.nosdn.127.net/8d26e9858a1f41791f3dfb3881a8df7a",
+  "//icon.nosdn.127.net/89fa32c141130d5772292c10c8865dfd",
+  "//icon.nosdn.127.net/3d0bff5b0b0f1a396799d7cd72bc09e9",
+];

+ 12 - 0
server/config/config.js

@@ -0,0 +1,12 @@
+const path = require("path");
+const host = process.env.productHost || "localhost";
+module.exports = {
+  productType: process.env.productType || "default",
+  url: `http://${host}`,
+  host: `${host}`,
+  ICON_APP_PORT: process.env.ICON_APP_PORT || 4843,
+  rootRepoPath: path.resolve(__dirname, "../../public/resource/repository"),
+  salt: "NXArUDVwNlg1cGl2NUxpcTVhU241YmlGNllDOA==",
+  autoLoginSessionExpires: 7 * 24 * 60 * 60 * 1000, // 7天
+  defaultExpiresTime: +new Date("01-Jan-1970 00:00:10"),
+};

+ 10 - 0
server/config/constant.js

@@ -0,0 +1,10 @@
+module.exports = {
+  /* 添加成员输入类型-用户ID */
+  MEMBER_ACCOUNT_TYPE_USER_ID: 1,
+
+  /* 添加成员输入类型-邮箱 */
+  MEMBER_ACCOUNT_TYPE_USER_NAME: 5,
+
+  /* 图标仓库列表图标库返回图标个数 */
+  REPO_LIST_CONTAIN_ICON_COUNT_PER_REPO: 15,
+};

+ 19 - 0
server/config/dbConfig.js

@@ -0,0 +1,19 @@
+let pe = process.env;
+
+module.exports = {
+  dbName: pe.MONGODB_NAME || "iconRepo",
+  host: pe.MONGODB_HOST || "127.0.0.1",
+  port: pe.MONGODB_PORT || 27017,
+  username: pe.MONGODB_USERNAME || "",
+  password: pe.MONGODB_PASSWORD || "",
+  authSource: pe.MONGO_AUTHSOURCE || "admin",
+};
+
+// module.exports = {
+//   dbName: "ipResp",
+//   host: "localhost",
+//   port: 27017,
+//   username: "gemer",
+//   password: "sglove520",
+//   authSource: "admin",
+// };

+ 4 - 0
server/config/loggerConfig.js

@@ -0,0 +1,4 @@
+module.exports = {
+  level: "debug",
+  root: "../logs/",
+};

+ 9 - 0
server/config/redisConfig.js

@@ -0,0 +1,9 @@
+let pe = process.env;
+
+module.exports = {
+    port: pe.REDIS_PORT || 6379,
+    host: pe.REDIS_HOST || '127.0.0.1',
+    family: pe.REDIS_FAMILY || 4,
+    password: pe.REDIS_PASSWORD || '',
+    db: pe.REDIS_DB || 0
+};

+ 299 - 0
server/controller/iconController.js

@@ -0,0 +1,299 @@
+/**
+ * 字体图标CURD Controller
+ *
+ */
+
+let responseFormat = require("../util/responseFormat");
+let db = require("../database");
+let log = require("../util/log");
+let incUtil = require("../util/incUtil");
+
+class IconController {
+  constructor(config) {
+    this.config = config;
+  }
+
+  /**
+   * 保存字体图标到数据库
+   *
+   * @param    {String}           iconName                    字体图标名称
+   * @param    {String}           iconContent                 svg的xml内容字符串
+   * @param    {Object}           userInfo                    用户基本信息
+   * @return   {Object}                                       字体图标完整信息对象
+   */
+  async saveIcon(iconName, iconContent, userInfo) {
+    // 获取唯一自增Id
+    let iconId = await incUtil.getIncId({ model: "icon", field: "iconId" });
+
+    // 构建完整数据
+    let iconData = {
+      iconName: iconName.replace(".svg", ""),
+      iconContent: iconContent,
+      createTime: global.globalConfig.nowTime,
+      updateTime: global.globalConfig.nowTime,
+      ownerId: userInfo.userId,
+      iconId: iconId,
+    };
+
+    let iconAddResult = await db.icon.add(iconData);
+    return iconAddResult.toObject();
+  }
+
+  /**
+   * 获取各种条件下的字体图标列表
+   *
+   * @param    {Object}           ctx                     请求对象
+   * @return   {void}
+   */
+  async getIconList(ctx) {
+    let params = ctx.params;
+    let query = Object.assign(
+      {
+        pageIndex: 1,
+        pageSize: 20,
+      },
+      ctx.request.query
+    );
+    let iconIds = [];
+    let result = [];
+    query.q = new RegExp(`.*${query.q || ""}.*`);
+
+    // 获取仓库下的字体图标Id数组, 倒序
+    if (query.repoId) {
+      iconIds = (
+        (await this.getRepoIconIds(query, query.unique)) || []
+      ).reverse();
+    }
+
+    if (!query.unique && !query.repoId) {
+      // 获取用户的所有字体图标列表
+      if (params.userId === "all") {
+        result = await this.getAllIconList(query);
+      } else {
+        result = await this.getIconListByUserId(params, query);
+      }
+    } else if (query.unique) {
+      // 判断当前用户不起作用
+      // await auth();
+      // 获取用户的所有字体图标列表,过滤掉当前图标库已经有的
+      result = await this.getIconListNotInRepoByUnique(params, query, iconIds);
+    } else {
+      // 获取该图标库下的所有字体图标列表
+      result = await this.getIconListByRepoId(query.repoId, query, iconIds);
+    }
+
+    ctx.body = responseFormat.responseFormatList(200, "", result, query);
+  }
+
+  /**
+   * 获取该仓库下所有字体图标的iconId数组
+   *
+   * @param    {Object}           query                       request query
+   * @param    {Boolean}          notFilter                  get all icons of repoId
+   * @return   {Array}                                        字体图标数组
+   */
+  async getRepoIconIds(query, notFilter) {
+    let iconRepoItem = await db.iconRepo.findOne({
+      repoId: query.repoId,
+    });
+    if (!notFilter) {
+      iconRepoItem.iconIds = iconRepoItem.iconIds.filter((icon) => {
+        return icon.iconName.match(query.q);
+      });
+    }
+    return iconRepoItem.iconIds.map((icon) => {
+      return icon.iconId;
+    });
+  }
+
+  /**
+   * 获取当前用户的字体图标列表
+   *
+   * @param    {Object}           userInfo                    用户基本信息
+   * @param    {Object}           query                       请求参数对象
+   * @return   {Array}                                        字体图标对象数组
+   */
+  async getIconListByUserId(userInfo, query) {
+    let result = await db.icon.find(
+      {
+        ownerId: userInfo.userId,
+        iconName: query.q,
+      },
+      global.globalConfig.iconExportFields,
+      {
+        limit: parseInt(query.pageSize),
+        skip: parseInt((query.pageIndex - 1) * query.pageSize),
+        sort: {
+          createTime: -1,
+        },
+      }
+    );
+    query.totalCount = await db.icon.count({
+      ownerId: userInfo.userId,
+      iconName: query.q,
+    });
+    return result;
+  }
+
+  /**
+   * 获取当前用户的字体图标列表
+   *
+   * @param    {Object}           userInfo                    用户基本信息
+   * @param    {Object}           query                       请求参数对象
+   * @return   {Array}                                        字体图标对象数组
+   */
+  async getAllIconList(query) {
+    let result = await db.icon.find(
+      {
+        iconName: query.q,
+      },
+      global.globalConfig.iconExportFields,
+      {
+        limit: parseInt(query.pageSize),
+        skip: parseInt((query.pageIndex - 1) * query.pageSize),
+        sort: {
+          createTime: -1,
+        },
+      }
+    );
+    query.totalCount = await db.icon.count({
+      iconName: query.q,
+    });
+    return result;
+  }
+
+  /**
+   * 获取当前图标库下的字体图标列表
+   *
+   * @param    {Number}           repoId                  图标库Id
+   * @param    {Object}           query                       请求参数对象
+   * @param    {Array}            iconIds                     字体图标id数组
+   * @return   {Array}                                        字体图标对象数组
+   */
+  async getIconListByRepoId(repoId, query, iconIds) {
+    let result = [];
+    for (
+      let i = (query.pageIndex - 1) * query.pageSize || 0;
+      i < Math.min(iconIds.length, query.pageIndex * query.pageSize);
+      i++
+    ) {
+      let iconItem = await db.icon.findOne(
+        {
+          iconId: iconIds[i],
+        },
+        global.globalConfig.iconExportFields
+      );
+      result.push(iconItem);
+    }
+    query.totalCount = iconIds.length;
+    return result;
+  }
+
+  /**
+   * 获取当前用户的字体图标列表
+   *
+   * @param    {Object}           userInfo                    用户基本信息
+   * @param    {Object}           query                       请求参数对象
+   * @param    {Array}            iconIds                     字体图标id数组
+   * @return   {Array}                                        字体图标对象数组
+   */
+  async getIconListNotInRepoByUnique(userInfo, query, iconIds) {
+    let result = await db.icon.find(
+      {
+        ownerId: userInfo.userId,
+        iconId: {
+          $nin: iconIds,
+        },
+        iconName: query.q,
+      },
+      global.globalConfig.iconExportFields,
+      {
+        limit: parseInt(query.pageSize),
+        skip: parseInt((query.pageIndex - 1) * query.pageSize),
+        sort: {
+          createTime: -1,
+        },
+      }
+    );
+    query.totalCount = await db.icon.count({
+      ownerId: userInfo.userId,
+      iconId: {
+        $nin: iconIds,
+      },
+      iconName: query.q,
+    });
+    return result;
+  }
+
+  /**
+   * 删除字体图标
+   *
+   * @param    {Object}           ctx                     请求对象
+   * @return   {void}
+   */
+  async deleteIcon(ctx) {
+    let userInfo = ctx.userInfo;
+    let params = ctx.params;
+    let iconItem = await db.icon.findOne({
+      iconId: params.iconId,
+    });
+    if (!iconItem) {
+      ctx.body = responseFormat.responseFormat(500, "无此图标!", false);
+      return;
+    }
+    // check privilege
+    if (userInfo.userId !== iconItem.ownerId) {
+      ctx.body = responseFormat.responseFormat(403, "无权限", false);
+      return;
+    }
+
+    // check dependence
+    let iconRelationshipItem = await db.iconBelongToRepo.findOne({
+      iconId: params.iconId,
+    });
+
+    if (iconRelationshipItem && iconRelationshipItem.repos.length > 0) {
+      let message = "该图标已经加入图标库: ";
+      for (let repo of iconRelationshipItem.repos) {
+        message += repo.repoName + "、";
+      }
+      message += ", 请移除后再删除!";
+      ctx.body = responseFormat.responseFormat(500, message, false);
+      return;
+    }
+
+    // delete icon
+    await db.icon.delete({
+      ownerId: userInfo.userId,
+      iconId: params.iconId,
+    });
+    log.debug(`user ${userInfo.userId} delete icon ${params.iconId}`);
+    ctx.body = responseFormat.responseFormat(200, "删除成功!", false);
+  }
+
+  /**
+   * 下载字体图标
+   *
+   * @param    {Object}           ctx                     请求对象
+   * @return   {void}
+   */
+  async downloadIcon(ctx) {
+    let params = ctx.params || {};
+    let iconItem = await db.icon.findOne({
+      iconId: params.iconId,
+    });
+    if (!iconItem) {
+      ctx.body = responseFormat.responseFormat(200, "无此图标!", false);
+      return;
+    }
+    // 强制客户端直接下载svg headers
+    ctx.set("Content-Type", "application/force-download");
+    ctx.set(
+      "Content-disposition",
+      "attachment; filename=" + iconItem.iconName + ".svg"
+    );
+    ctx.body = iconItem.iconContent;
+  }
+}
+
+module.exports = IconController;

+ 210 - 0
server/controller/iconDraftController.js

@@ -0,0 +1,210 @@
+/**
+ * 字体图标CURD Controller
+ *
+ */
+
+let responseFormat = require('../util/responseFormat');
+let db = require('../database');
+let fileUtil = require('../util/fileUtil');
+let incUtil = require('../util/incUtil');
+let IconController = require('./iconController');
+let iconControllerIns = new IconController();
+let RepoController = require('./repoController');
+let repoControllerIns = new RepoController();
+let log = require('../util/log');
+let pinyin = require('pinyin');
+
+class IconDraftController {
+    /**
+     * 获取当前用户的草稿字体图标列表
+     *
+     * @param    {Object}           ctx                     请求对象
+     * @return   {void}
+     */
+    async getIconDraftList (ctx) {
+        let userInfo = ctx.userInfo;
+        let query = ctx.request.query || {};
+        let result = await db.iconDraft.find({
+            ownerId: userInfo.userId
+        }, global.globalConfig.iconDraftExportFields,
+        {
+            limit: parseInt(query.pageSize),
+            skip: parseInt((query.pageIndex - 1) * query.pageSize)
+        }
+        );
+        query.totalCount = await db.iconDraft.count({ownerId: userInfo.userId});
+        ctx.body = responseFormat.responseFormatList(200, '', result, query);
+    }
+
+    /**
+     * 上传字体图标
+     *
+     * @param    {Object}           ctx                     请求对象
+     * @return   {void}
+     */
+    async saveDraftIcon (ctx) {
+        let userInfo = ctx.userInfo;
+        let fileParams = (ctx.request.body || {}).files;
+
+        if (!/\.svg$/.exec(fileParams.file.name)) {
+            ctx.body = responseFormat.responseFormat(200, '请上传svg格式图片!', false);
+            return;
+        } else {
+            fileParams.file.name = fileParams.file.name.replace('.svg', '')
+        }
+        // 获取文件内容,再删掉
+        let fileContent = await fileUtil.readFile(fileParams.file.path, {encoding: 'utf8'});
+        await fileUtil.deleteFile(fileParams.file.path);
+        // 对svg进行处理,重新绘制
+        let iconInfo = await fileUtil.formatSvgFile(fileContent);
+        await this.saveDraftIconToDB(fileParams.file.name, iconInfo, userInfo, ctx);
+    }
+
+    async collectIcon (ctx) {
+        let userInfo = ctx.userInfo;
+        let params = ctx.request.body;
+
+        await this.saveDraftIconToDB(params.iconName, params, userInfo, ctx);
+    }
+
+    async saveDraftIconToDB (iconName, iconInfo, userInfo, ctx) {
+        // 获取唯一自增Id
+        let iconId = await incUtil.getIncId({model: 'iconDraft', field: 'iconId'});
+        // 构建完整数据
+        let params = {
+            iconName: iconName,
+            ownerId: userInfo.userId,
+            iconContent: iconInfo.iconContent,
+            iconOriginContent: iconInfo.iconOriginContent,
+            svgPath: iconInfo.svgPath,
+            createTime: global.globalConfig.nowTime,
+            updateTime: global.globalConfig.nowTime,
+            iconId: iconId
+        };
+        await db.iconDraft.add(params);
+        ctx.body = responseFormat.responseFormat(200, '', {
+            iconContent: iconInfo.iconContent,
+            name: iconName
+        });
+    }
+
+    /**
+     * 删除草稿字体图标
+     *
+     * @param    {Object}           ctx                     请求对象
+     * @return   {void}
+     */
+    async deleteDraftIcon (ctx) {
+        let userInfo = ctx.userInfo;
+        let params = ctx.request.body;
+
+        await db.iconDraft.delete({
+            iconId: params.iconId,
+            ownerId: userInfo.userId
+        });
+        log.debug(`[%s.deleteDraftIcon] delete iconDraft success--${params.iconId}`, this.constructor.name)
+        ctx.body = responseFormat.responseFormat(200, '删除成功!', true);
+    }
+
+    /**
+     * 删除草稿字体图标
+     *
+     * @param    {Object}           ctx                     请求对象
+     * @return   {void}
+     */
+    async updateDraftIcon (ctx) {
+        let userInfo = ctx.userInfo;
+        let params = ctx.request.body;
+
+        await db.iconDraft.update({
+            iconId: params.iconId,
+            ownerId: userInfo.userId
+        }, {
+            iconName: params.iconName
+        });
+
+        ctx.body = responseFormat.responseFormat(200, '更新成功!', true);
+    }
+
+    /**
+     * 下载字体图标
+     *
+     * @param    {Object}           ctx                     请求对象
+     * @return   {void}
+     */
+    async downloadIcon (ctx) {
+        let params = ctx.params || {};
+        let iconItem = await db.iconDraft.findOne({
+            iconId: params.iconId
+        });
+        if (!iconItem) {
+            ctx.body = responseFormat.responseFormat(200, '无此图标!', false);
+            return;
+        }
+        // 强制客户端直接下载svg headers
+        ctx.set('Content-Type', 'application/force-download');
+        ctx.set('Content-disposition', 'attachment; filename=' + iconItem.iconName + '.svg');
+        ctx.body = iconItem.iconContent;
+    }
+
+    /**
+     * 提交草稿转换成正式字体图标并删除
+     *
+     * @param    {Object}           ctx                     请求对象
+     * @return   {void}
+     */
+    async changeDraft2Icon (ctx) {
+        let userInfo = ctx.userInfo;
+        let iconItems = await db.iconDraft.find({
+            ownerId: userInfo.userId
+        });
+        let newIcons = [];
+        // 允许名称重复,但名称不可修改
+        // for (let i = 0; i < iconItems.length; i++) {
+        //     let iconItem = await db.icon.findOne({
+        //         iconName: iconItems[i].iconName,
+        //         ownerId: userInfo.userId
+        //     });
+        //     if (iconItem) {
+        //         throw new Error(`${iconItems[i].iconName}图标已经存在,请修改名称!`)
+        //     }
+        // }
+        // 验证完成之后再保存
+        for (let i = 0; i < iconItems.length; i++) {
+            let iconName = this.getName(iconItems[i].iconName);
+            if (ctx.request.body.resetColor) {
+                let iconInfo = await fileUtil.formatSvgFile(iconItems[i].iconContent);
+                iconItems[i].iconContent = iconInfo.iconContent;
+            }
+            let iconItem = await iconControllerIns.saveIcon(iconName, iconItems[i].iconContent, userInfo);
+            newIcons.push(iconItem);
+        }
+        // 添加到指定图标库中
+        if (ctx.request.body.repoId) {
+            ctx.request.body.icons = newIcons;
+            await repoControllerIns.addIcon2Repo(ctx);
+        }
+        // 转化完之后删除
+        await db.iconDraft.delete({
+            ownerId: userInfo.userId
+        });
+        ctx.body = responseFormat.responseFormat(200, '', true);
+    }
+    /**
+     * 转化中文为拼音
+     *
+     * @param    {String}           name                    图标名称
+     * @return   {void}
+     */
+    getName (name) {
+        return pinyin(name, {
+            style: pinyin.STYLE_NORMAL,
+            heteronym: true
+        }).reduce(function (sum, val) {
+            console.log(sum, val);
+            return sum + val[0];
+        }, '').replace(/\s+|、|&/g, '-');
+    }
+};
+
+module.exports = IconDraftController;

+ 780 - 0
server/controller/repoController.js

@@ -0,0 +1,780 @@
+/**
+ * icon repo Controller
+ *
+ */
+let config = require("../config/config");
+let constant = require("../config/constant");
+let responseFormat = require("../util/responseFormat");
+let repoInfoRules = require("../validation/repoInfoRules");
+let validator = require("../util/validator");
+let db = require("../database");
+let log = require("../util/log");
+let path = require("path");
+let fileUtil = require("../util/fileUtil");
+let incUtil = require("../util/incUtil");
+let icon = require("../tool/icon");
+let svgSprite = require("../tool/svgSprite");
+let uploadService = require("../service/upload");
+
+class RepoController {
+  /**
+   * add icon repo
+   *
+   * @param    {Object}           ctx                     request object
+   * @return   {void}
+   */
+  async addRepo(ctx) {
+    let params = ctx.request.body || {};
+    let userInfo = ctx.userInfo;
+
+    // validate completely
+    validator.validateParamsField(params, repoInfoRules, ctx);
+
+    // validate unique
+    await this.iconRepoExist(params, userInfo);
+
+    // get increment repoId
+    let repoId = await incUtil.getIncId({ model: "iconRepo", field: "repoId" });
+
+    // build repo data
+    Object.assign(params, {
+      createTime: global.globalConfig.nowTime,
+      updateTime: global.globalConfig.nowTime,
+      ownerId: userInfo.userId,
+      repoId: repoId,
+    });
+    log.debug(`add repo data: ${JSON.stringify(params)}`);
+
+    // save icon repo data to database
+    await db.iconRepo.add(params);
+
+    // add repoId to owner repos field
+    await db.user.update(
+      {
+        userId: userInfo.userId,
+      },
+      {
+        $push: {
+          repos: {
+            repoId: repoId,
+            repoName: params.repoName,
+          },
+        },
+      }
+    );
+
+    ctx.body = responseFormat.responseFormat(200, "save success!", true);
+  }
+
+  /**
+   * add icon repo
+   *
+   * @param    {Object}           ctx                     request object
+   * @return   {void}
+   */
+  async updateRepoInfo(ctx) {
+    let data = ctx.request.body || {};
+    let params = ctx.params;
+    let userInfo = ctx.userInfo;
+
+    // validate completely
+    validator.validateParamsField(params, repoInfoRules, ctx);
+
+    // validate privilege, only owner
+    let repoItem = await db.iconRepo.findOne({
+      ownerId: userInfo.userId,
+      repoId: params.repoId,
+    });
+
+    if (!repoItem) {
+      throw new Error("no privilege!");
+    }
+
+    // validate unique
+    // await this.iconRepoExist(data, userInfo);
+
+    // build repo data
+    data = Object.assign(repoItem, data, {
+      updateTime: global.globalConfig.nowTime,
+      ownerId: userInfo.userId,
+      unSync: true,
+      repoId: parseInt(params.repoId),
+    });
+    log.debug(
+      `user ${userInfo.userId} update repo data: ${JSON.stringify(params)}`
+    );
+
+    // update icon repo data to database
+    await db.iconRepo.update({ repoId: params.repoId }, data);
+
+    // add repoId to owner repos field, todo
+    // await db.user.update({
+    //     userId: userInfo.userId
+    // }, {
+    //     "$set": {
+    //         "repos.$[element]": {
+    //             repoId: params.repoId,
+    //             repoName: data.repoName
+    //         }
+    //     }
+    // }, {
+    //     arrayFilters: [{
+    //         repoId: params.repoId,
+    //         repoName: repoItem.repoName
+    //     }]
+    // });
+
+    ctx.body = responseFormat.responseFormat(200, "save success!", true);
+  }
+
+  /**
+   * get repo list
+   *
+   * @param    {Object}           ctx                     request object
+   * @return   {void}
+   */
+  async getRepoList(ctx) {
+    let params = ctx.params;
+    let query = ctx.request.query || {};
+    let repoList = [];
+
+    if (parseInt(params.userId)) {
+      repoList = await this.getRepoListByUserId(params, query, ctx);
+    } else {
+      repoList = await this.getAllRepoList(query);
+    }
+    ctx.body = responseFormat.responseFormatList(200, "", repoList, query);
+  }
+
+  /**
+   * get all repo list
+   *
+   * @param    {Object}           query                   pagination object
+   * @return   {void}
+   */
+  async getAllRepoList(query) {
+    let result = await db.iconRepo.find(
+      {},
+      global.globalConfig.iconRepoExportFields,
+      {
+        limit: parseInt(query.pageSize),
+        skip: parseInt((query.pageIndex - 1) * query.pageSize),
+      }
+    );
+    query.totalCount = await db.iconRepo.count({});
+    log.debug(`get all repos and count: ${query.totalCount}`);
+    // traverse repo list for finding icon that belong to repo
+    for (let i = 0; i < (result || []).length; i++) {
+      result[i].icons = [];
+      result[i].iconCount = (result[i].iconIds || []).length;
+      for (
+        let j = 0;
+        j <
+        Math.min(
+          result[i].iconIds.length,
+          constant.REPO_LIST_CONTAIN_ICON_COUNT_PER_REPO
+        );
+        j++
+      ) {
+        let iconItem = await db.icon.findOne(
+          {
+            iconId: result[i].iconIds[j].iconId,
+          },
+          global.globalConfig.iconExportFields
+        );
+        result[i].icons.push(iconItem || {});
+      }
+    }
+    return result;
+  }
+
+  /**
+   * get repo list of specific user
+   *
+   * @param    {Object}           params                  query object of url
+   * @param    {Object}           query                   pagination object
+   * @return   {void}
+   */
+  async getRepoListByUserId(params, query) {
+    let userItem = await db.user.findOne({
+      userId: params.userId,
+    });
+
+    query.totalCount = userItem.repos.length;
+    log.debug(
+      `get user ${params.userId} repo list and count: ${query.totalCount}`
+    );
+    let repoList = [];
+    for (
+      let r = (query.pageIndex - 1) * query.pageSize;
+      r < Math.min(query.pageIndex * query.pageSize, userItem.repos.length);
+      r++
+    ) {
+      let repoItem = await db.iconRepo.findOne(
+        {
+          repoId: userItem.repos[r].repoId,
+        },
+        global.globalConfig.iconRepoExportFields
+      );
+
+      // traverse repo list for finding icon that belong to repo
+      repoItem.icons = [];
+      repoItem.iconCount = (repoItem.iconIds || []).length;
+      for (
+        let j = 0;
+        j <
+        Math.min(
+          repoItem.iconIds.length,
+          constant.REPO_LIST_CONTAIN_ICON_COUNT_PER_REPO
+        );
+        j++
+      ) {
+        let iconItem = await db.icon.findOne(
+          {
+            iconId: repoItem.iconIds[j].iconId,
+          },
+          global.globalConfig.iconExportFields
+        );
+        repoItem.icons.push(iconItem || {});
+      }
+      repoList.push(repoItem);
+    }
+    return repoList;
+  }
+
+  /**
+   * get repo info
+   *
+   * @param    {Object}           ctx                     request object
+   * @return   {void}
+   */
+  async getRepoInfo(ctx) {
+    let userInfo = ctx.userInfo;
+    let params = ctx.params;
+    // find info first
+    let result = await db.iconRepo.findOne({
+      repoId: params.repoId,
+    });
+    // pre login
+    if (userInfo.userId) {
+      // check user has repoId in repos field
+      let userItem = await db.user.findOne({
+        userId: userInfo.userId,
+      });
+      // isMember if exist repoId
+      userItem.repos.forEach((item) => {
+        if (item.repoId === parseInt(params.repoId)) {
+          result.isMember = true;
+        }
+      });
+      // is owner if repo's ownerId equal to userId
+      if (result.ownerId === userInfo.userId) {
+        result.isOwner = true;
+      }
+    }
+
+    ctx.body = responseFormat.responseFormat(200, "", result);
+  }
+
+  /**
+   * get repo css or svg resource
+   *
+   * @param    {Object}           ctx                     request object
+   * @return   {void}
+   */
+  async getRepoResource(ctx) {
+    let params = ctx.params;
+    let type = (ctx.request.query || {}).type;
+    // find info first
+    let repoItem = await db.iconRepo.findOne({
+      repoId: params.repoId,
+    });
+    // pre login
+    let result = null;
+    if (repoItem) {
+      // type can be cssUrl or cssContent
+      if (repoItem[type]) {
+        result = repoItem[type];
+      } else {
+        let iconIds = await this.getRepoIconIds(repoItem.repoId);
+        result = [];
+        for (let iconId of iconIds) {
+          let iconItem = await db.icon.findOne({ iconId: iconId });
+          result.push({
+            iconId: iconItem.iconId,
+            iconContent: iconItem.iconContent,
+            iconName: iconItem.iconName,
+          });
+        }
+      }
+    }
+
+    ctx.body = responseFormat.responseFormat(200, "", result);
+  }
+
+  /**
+   * get iconIds that belong to repo
+   *
+   * @param    {Number}           repoId                  repoId
+   * @return   {Array}                                    all iconIds that belong to repo
+   */
+  async getRepoIconIds(repoId) {
+    let iconRepoItem = await db.iconRepo.findOne({
+      repoId: repoId,
+    });
+    let iconIds = [];
+    iconRepoItem.iconIds.map((icon) => {
+      iconIds.push(icon.iconId);
+    });
+    return iconIds;
+  }
+
+  /**
+   * add iconId to repo's iconIds field
+   *
+   * @param    {Object}           ctx                     request object
+   * @return   {void}
+   */
+  async addIcon2Repo(ctx) {
+    let params = ctx.request.body || {};
+    let userInfo = ctx.userInfo;
+
+    // judge unique from upload icons
+    let tmpMap = {};
+    for (let icon of params.icons) {
+      if (tmpMap[icon.iconName]) {
+        throw new Error(`upload repeat icon ${icon.iconName}`);
+      }
+      tmpMap[icon.iconName] = true;
+    }
+
+    // has privilege or not
+    let userItem = await db.user.findOne({
+      userId: userInfo.userId,
+    });
+    if (!userItem || !this.hasRepo(userItem.repos, params.repoId)) {
+      throw new Error("no privilege!");
+    }
+    let repoItem = await db.iconRepo.findOne({
+      repoId: params.repoId,
+    });
+    // judge unique to avoid repeat between upload and database
+    for (let icon of params.icons) {
+      for (let existIcon of repoItem.iconIds) {
+        if (
+          icon.iconId === existIcon.iconId ||
+          icon.iconName === existIcon.iconName
+        ) {
+          throw new Error(
+            `repo ${repoItem.repoName} has contain icon ${icon.iconName}`
+          );
+        }
+      }
+    }
+    // add many icon one times, need to traverse
+    for (let icon of params.icons) {
+      await db.iconRepo.update(
+        {
+          repoId: params.repoId,
+        },
+        {
+          $push: {
+            iconIds: {
+              iconId: icon.iconId,
+              iconName: icon.iconName,
+            },
+          },
+          unSync: true,
+        }
+      );
+      log.debug(
+        `user ${userInfo.userId} add icon ${icon.iconId}-${icon.iconName} to repo ${params.repoId}`
+      );
+
+      await db.iconBelongToRepo.update(
+        {
+          iconId: icon.iconId,
+        },
+        {
+          iconId: icon.iconId,
+          iconName: icon.iconName,
+          $push: {
+            repos: {
+              repoId: repoItem.repoId,
+              repoName: repoItem.repoName,
+            },
+          },
+        },
+        {
+          upsert: true,
+        }
+      );
+    }
+
+    ctx.body = responseFormat.responseFormat(200, "add success!", true);
+  }
+
+  /**
+   * is member or master of repoId
+   *
+   * @param    {Array}            repos                       repo array
+   * @param    {Number}           repoId                      repo id
+   * @return   {void}
+   */
+  hasRepo(repos = [], repoId) {
+    for (let repo of repos) {
+      if (repo.repoId === parseInt(repoId)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * delete iconId from repo
+   *
+   * @param    {Object}           ctx                     request object
+   * @return   {void}
+   */
+  async deleteIconFromRepo(ctx) {
+    let params = ctx.params || {};
+    let userInfo = ctx.userInfo;
+    let isMember = false;
+
+    // check user has repoId in repos field
+    let userItem = await db.user.findOne({
+      userId: userInfo.userId,
+    });
+    // isMember if exist repoId
+    userItem.repos.forEach((item) => {
+      if (item.repoId === params.repoId) {
+        isMember = true;
+      }
+    });
+
+    if (!isMember) {
+      ctx.body = responseFormat.responseFormat(403, "no privilege!", false);
+    }
+
+    await db.iconRepo.update(
+      {
+        repoId: params.repoId,
+      },
+      {
+        $pull: {
+          iconIds: {
+            iconId: params.iconId,
+          },
+        },
+        unSync: true,
+      }
+    );
+    // remove relationship for delete operation
+    await db.iconBelongToRepo.update(
+      {
+        iconId: params.iconId,
+      },
+      {
+        $pull: {
+          repos: {
+            repoId: params.repoId,
+          },
+        },
+      }
+    );
+
+    // remove svg file from resource
+    let repoItem = await db.iconRepo.findOne({
+      repoId: params.repoId,
+    });
+    let iconItem = await db.icon.findOne({
+      iconId: params.iconId,
+    });
+    let iconPath = path.resolve(
+      __dirname,
+      `../../public/resource/repository/${repoItem.ownerId}-${repoItem.iconPrefix}/svg/${iconItem.iconName}.svg`
+    );
+    await fileUtil.deleteFile(iconPath);
+    log.debug(
+      `user ${userInfo.userId} delete icon ${params.iconId} from repo ${params.repoId}`
+    );
+
+    ctx.body = responseFormat.responseFormat(200, "delete success!", true);
+  }
+
+  /**
+   * sync database and generate css files
+   *
+   * @param    {Object}           ctx                         request object
+   * @return   {void}
+   */
+  async syncRepo(ctx) {
+    let params = ctx.params;
+    let userInfo = ctx.userInfo;
+    let isRepoMember = false;
+
+    // judge privilege
+    let userItem = await db.user.findOne({
+      userId: userInfo.userId,
+    });
+    userItem.repos.map((item) => {
+      if (item.userId === params.userId) {
+        isRepoMember = true;
+      }
+    });
+    if (!isRepoMember) {
+      ctx.body = responseFormat.responseFormat(403, "no privilege!", false);
+      return;
+    }
+
+    let repoItem = await db.iconRepo.findOne({
+      repoId: params.repoId,
+    });
+    // judge update or not
+    if (repoItem.unSync) {
+      let repoPath = path.join(
+        config.rootRepoPath,
+        `./${repoItem.ownerId}-${repoItem.iconPrefix}`
+      );
+      // clean all svg for avoid cache or change iconPrefix, maintain if default
+      // if (config.productType === 'default') {
+      //     await fileUtil.deleteDirector(repoPath);
+      // }
+      let repoSvgPath = path.join(repoPath, "./svg/");
+      let repoIcons = [];
+      await fileUtil.createDirector(repoSvgPath);
+
+      // create svg file recursive
+      for (let k = 0; k < repoItem.iconIds.length; k++) {
+        let iconItem =
+          (await db.icon.findOne({
+            iconId: repoItem.iconIds[k].iconId,
+          })) || {};
+        repoIcons.push(iconItem);
+        await fileUtil.createFile(
+          path.join(repoSvgPath, iconItem.iconName + ".svg"),
+          iconItem.iconContent
+        );
+      }
+      let uploadResult = {};
+      let svgSpriteResult = "";
+      // must has svg
+      if (repoItem.iconIds.length > 0) {
+        await icon.compileSvg2Icon(
+          repoPath,
+          repoItem.iconPrefix,
+          repoItem.fontPath
+        );
+        // output to server and return css url and css content
+        uploadResult = await uploadService.upload(repoPath);
+
+        // svg sprite
+        svgSpriteResult = svgSprite(repoItem.iconPrefix, repoIcons);
+      } else {
+        uploadResult = { url: "", cssContent: "" };
+        svgSpriteResult = "";
+      }
+      // update sync status
+      await db.iconRepo.update(
+        {
+          repoId: params.repoId,
+        },
+        {
+          unSync: false,
+          cssUrl: uploadResult.url,
+          cssContent: uploadResult.cssContent,
+          svgSpriteContent: svgSpriteResult,
+        }
+      );
+      log.debug(`user ${userInfo.userId} sync repo ${params.repoId} success`);
+
+      // clean all svg for avoid cache or change iconPrefix, delete if access thirdly upload
+      // if (config.productType !== 'default') {
+      //     await fileUtil.deleteDirector(repoPath);
+      // }
+      ctx.body = responseFormat.responseFormat(
+        200,
+        "update success!",
+        uploadResult
+      );
+      return;
+    }
+
+    ctx.body = responseFormat.responseFormat(200, "update success!", true);
+  }
+
+  /**
+   * judge repo already exist
+   *
+   * @param    {Object}           params                      post request object
+   * @param    {Object}           userInfo                    userInfo
+   * @return   {void}
+   */
+  async iconRepoExist(params, userInfo) {
+    let iconLibItem = await db.iconRepo.findOne({
+      iconPrefix: params.iconPrefix,
+      ownerId: userInfo.userId,
+    });
+
+    if (iconLibItem) {
+      throw new Error(`${params.iconPrefix} already exist!`);
+    }
+  }
+
+  /**
+   * add member of repo
+   *
+   * @param    {Object}           ctx                         request object
+   * @return   {void}
+   */
+  async addMember(ctx) {
+    let params = ctx.params;
+    let userInfo = ctx.userInfo;
+    let data = ctx.request.body || {};
+
+    // only master can add member
+    let repo = await db.iconRepo.findOne({
+      ownerId: userInfo.userId,
+      repoId: params.repoId,
+    });
+    if (!repo) {
+      ctx.body = responseFormat.responseFormat(403, "no privilege!", false);
+      return;
+    }
+    // user exist
+    let user = null;
+    if (parseInt(data.accountType) === constant.MEMBER_ACCOUNT_TYPE_USER_ID) {
+      user = await db.user.findOne({
+        userId: data.account,
+      });
+    } else {
+      user = await db.user.findOne({
+        userName: data.account,
+      });
+    }
+    if (!user) {
+      ctx.body = responseFormat.responseFormat(
+        200,
+        "user not exist, make sure has login the site",
+        false
+      );
+      return;
+    }
+    // add member of repo
+    await db.user.update(
+      {
+        userId: user.userId,
+      },
+      {
+        $push: {
+          repos: {
+            repoId: repo.repoId,
+            repoName: repo.repoName,
+          },
+        },
+      }
+    );
+    log.debug(
+      `user ${userInfo.userId} add member ${user.userId} of repo ${repo.repoId}`
+    );
+    ctx.body = responseFormat.responseFormat(200, "添加成功!", true);
+  }
+
+  /**
+   * get recommend repo list
+   *
+   * @param    {Object}           ctx                     request object
+   * @return   {void}
+   */
+  async getRecommendRepoList(ctx) {
+    let recommendRepos = await db.repoRecommend.find({});
+    let result = [];
+    let query = ctx.request.query || {};
+    // find all recommend repo
+    for (let repo of recommendRepos) {
+      let repoItem = await db.iconRepo.findOne(
+        {
+          repoId: repo.repoId,
+        },
+        global.globalConfig.iconRepoExportFields,
+        {
+          lean: true,
+        }
+      );
+      // traverse repo list for finding icon that belong to repo
+      repoItem.icons = [];
+      repoItem.iconCount = (repoItem.iconIds || []).length;
+      for (let icon of repoItem.iconIds) {
+        if (
+          repoItem.icons.length >=
+          constant.REPO_LIST_CONTAIN_ICON_COUNT_PER_REPO
+        ) {
+          break;
+        }
+        let iconItem =
+          (await db.icon.findOne(
+            {
+              iconId: icon.iconId,
+            },
+            global.globalConfig.iconExportFields
+          )) || {};
+        repoItem.icons.push(iconItem);
+      }
+      result.push(repoItem);
+    }
+
+    ctx.body = responseFormat.responseFormatList(200, "", result, query);
+  }
+
+  /**
+   * add recommend repo
+   *
+   * @param    {Object}           ctx                     request object
+   * @return   {void}
+   */
+  async addRecommendRepo(ctx) {
+    let params = ctx.request.body || {};
+    if (params.key === config.salt) {
+      for (let item of params.repos) {
+        let repo = await db.iconRepo.findOne({
+          repoId: item.repoId,
+        });
+        if (repo) {
+          // get increment repoId
+          let id = await incUtil.getIncId({
+            model: "repoRecommend",
+            field: "id",
+          });
+          await db.repoRecommend.add({
+            id: id,
+            repoId: repo.repoId,
+          });
+        }
+      }
+    }
+
+    ctx.body = responseFormat.responseFormat(200, "save success!", true);
+  }
+
+  /**
+   * add recommend repo
+   *
+   * @param    {Object}           ctx                     request object
+   * @return   {void}
+   */
+  async deleteRecommendRepo(ctx) {
+    let params = ctx.request.body || {};
+
+    if (params.key === config.salt) {
+      for (let item of params.repos) {
+        await db.repoRecommend.delete({
+          repoId: item.repoId,
+        });
+      }
+    }
+
+    ctx.body = responseFormat.responseFormat(200, "delete success!", true);
+  }
+}
+
+module.exports = RepoController;

+ 321 - 0
server/controller/userController.js

@@ -0,0 +1,321 @@
+/**
+ * user login & register & logout operation
+ *
+ */
+
+let config = require("../config/config");
+let log = require("../util/log");
+let responseFormat = require("../util/responseFormat");
+let userRegisterRules = require("../validation/userRegisterRules");
+let userLoginRules = require("../validation/userLoginRules");
+let validator = require("../util/validator");
+let db = require("../database");
+let redis = require("../database/redisStorage");
+let incUtil = require("../util/incUtil");
+let cryptoUtil = require("../util/cryptoUtil");
+let userUtil = require("../util/userUtil");
+let avatarConfig = require("../config/avatarConfig");
+let loginService = require("../service/login");
+
+class UserController {
+  /**
+   * user openid login
+   *
+   * @param    {Object}           ctx                         request object
+   */
+  async userOpenIdLogin(ctx) {
+    let userInfo = await loginService.login(ctx);
+    if (!userInfo) {
+      throw new Error("login fail, no userInfo return");
+    }
+    if (!userInfo.userName) {
+      throw new Error("login fail, userInfo.userName is required");
+    }
+    await this.appThirdLogin(ctx, userInfo);
+  }
+
+  /**
+   * app login
+   *
+   * @param    {Object}           ctx                         request object
+   * @param    {Object}           userInfo                    user info
+   * @return   {void}
+   */
+  async appThirdLogin(ctx, userInfo) {
+    let existUser = await db.user.findOne(
+      {
+        userName: userInfo.userName,
+      },
+      global.globalConfig.userExportFields
+    );
+    // exist go login or go register
+    if (existUser) {
+      await this.userThirdLogin(ctx, existUser);
+    } else {
+      await this.userThirdRegister(ctx, {
+        userName: userInfo.userName,
+        password: userInfo.password,
+        email: userInfo.email,
+        nickName: userInfo.nickname,
+        fullName: userInfo.fullname,
+      });
+    }
+  }
+
+  /**
+   * app third user login
+   *
+   * @param    {Object}           ctx                         request object
+   * @param    {Object}           userInfo                    user info from app database
+   */
+  async userThirdLogin(ctx, userInfo) {
+    let sessionId = await userUtil.setIconSession(userInfo.userId, ctx);
+    let autoLoginSessionId = await userUtil.setIconAutoLoginSession(
+      userInfo.userId,
+      ctx
+    );
+    // generate session to redis and set cookie to client
+    userUtil.setIconSessionCookie(sessionId, false, ctx);
+    userUtil.setIconAutoLoginSessionCookie(autoLoginSessionId, false, ctx);
+    // redirect to index page after login success
+    // todo redirect to where request come from
+    ctx.redirect(config.url);
+  }
+
+  /**
+   * app third user register use user info from openid login
+   *
+   * @param    {Object}           ctx                         request object
+   * @param    {Object}           params                      openid userInfo
+   */
+  async userThirdRegister(ctx, params) {
+    // get increment userId
+    let userId = await incUtil.getIncId({ model: "user", field: "userId" });
+    // set random avatar
+    let index = parseInt(Math.random() * 105);
+    let avatar = avatarConfig[index];
+
+    // build register userInfo
+    Object.assign(params, {
+      createTime: global.globalConfig.nowTime,
+      updateTime: global.globalConfig.nowTime,
+      avatar: avatar,
+      userId: userId,
+    });
+    log.debug(`user register and info: ${JSON.stringify(params)}`);
+
+    // save userInfo to app database
+    await db.user.add(params);
+    let sessionId = await userUtil.setIconSession(userId, ctx);
+    let autoLoginSessionId = await userUtil.setIconAutoLoginSession(
+      userId,
+      ctx
+    );
+    // generate session to redis and set cookie to client
+    userUtil.setIconSessionCookie(sessionId, false, ctx);
+    userUtil.setIconAutoLoginSessionCookie(autoLoginSessionId, false, ctx);
+    ctx.redirect(config.url);
+  }
+
+  /**
+   * 用户登录
+   *
+   * @param    {Object}           ctx                         请求对象
+   */
+  async userLogin(ctx) {
+    let params = ctx.request.body || {};
+    // 验证数据完整性
+    validator.validateParamsField(params, userLoginRules, ctx);
+
+    // 查询数据库,判断唯一性
+    let userInfo = await db.user.findOne({
+      userName: params.userName,
+    });
+    if (userInfo) {
+      // 密码校验,正确则生成session
+      if (userInfo.password === params.password) {
+        let sessionId = await userUtil.setIconSession(userInfo.userId, ctx);
+        let autoLoginSessionId = await userUtil.setIconAutoLoginSession(
+          userInfo.userId,
+          ctx
+        );
+        // 生成session后给给客户端写cookie
+        userUtil.setIconSessionCookie(sessionId, false, ctx);
+        userUtil.setIconAutoLoginSessionCookie(autoLoginSessionId, false, ctx);
+        delete userInfo.password;
+
+        ctx.body = responseFormat.responseFormat(200, "", userInfo);
+      } else {
+        ctx.body = responseFormat.responseFormat(
+          200,
+          {
+            password: {
+              message: `密码错误`,
+              success: false,
+            },
+          },
+          false
+        );
+      }
+    } else {
+      ctx.body = responseFormat.responseFormat(
+        200,
+        {
+          userName: {
+            message: `账号不存在!`,
+            success: false,
+          },
+        },
+        false
+      );
+    }
+  }
+
+  /**
+   * 用户注册
+   *
+   * @param  {Object}   ctx  请求对象
+   */
+  async userRegister(ctx) {
+    let params = ctx.request.body || {};
+    // 验证数据完整性
+    validator.validateParamsField(params, userRegisterRules, ctx);
+
+    // 查询数据库,判断唯一性
+    let userResult = await db.user.findOne({
+      userName: params.userName,
+    });
+    if (userResult) {
+      ctx.body = responseFormat.responseFormat(
+        200,
+        {
+          userName: {
+            message: `用户名已经存在, 请直接登录`,
+            success: false,
+          },
+        },
+        false
+      );
+      return;
+    }
+
+    // 获取唯一自增Id
+    let userId = await incUtil.getIncId({ model: "user", field: "userId" });
+    // set random avatar
+    let index = parseInt(Math.random() * 105);
+    let avatar = avatarConfig[index];
+
+    // 构建完整用户注册数据
+    Object.assign(params, {
+      createTime: global.globalConfig.nowTime,
+      updateTime: global.globalConfig.nowTime,
+      avatar: avatar,
+      userId: userId,
+      userName: String(params.userName),
+    });
+
+    // 保存用户信息到数据库
+    await db.user.add(params);
+    let sessionId = await userUtil.setIconSession(userId, ctx);
+    let autoLoginSessionId = await userUtil.setIconAutoLoginSession(
+      userId,
+      ctx
+    );
+    // 生成session后给给客户端写cookie
+    userUtil.setIconSessionCookie(sessionId, false, ctx);
+    userUtil.setIconAutoLoginSessionCookie(autoLoginSessionId, false, ctx);
+
+    delete params.password;
+    ctx.body = responseFormat.responseFormat(200, "", params);
+  }
+
+  /**
+   * app user logout
+   *
+   * @param    {Object}           ctx                         request object
+   */
+  async userLogout(ctx) {
+    const sessionId = ctx.cookies.get("ICON_SESSION", {
+      domain: config.host,
+      path: "/",
+      httpOnly: true,
+    });
+    const autoLoginSessionId = ctx.cookies.get("ICON_AUTO_LOGIN_SESSION", {
+      domain: config.host,
+      path: "/",
+      httpOnly: true,
+    });
+    await redis.destroy(sessionId);
+    await redis.destroy(autoLoginSessionId);
+    userUtil.setIconSessionCookie(sessionId, true, ctx);
+    userUtil.setIconAutoLoginSessionCookie(autoLoginSessionId, true, ctx);
+    ctx.body = responseFormat.responseFormat(200, "", {
+      loginUrl: loginService.config.loginUrl,
+    });
+  }
+
+  /**
+   * get current login userInfo
+   *
+   * @param    {Object}           ctx                         request object
+   */
+  async getCurLoginUserInfo(ctx) {
+    const autoLoginSessionId = ctx.cookies.get("ICON_AUTO_LOGIN_SESSION", {
+      domain: config.host,
+      path: "/",
+      httpOnly: true,
+    });
+    // return loginUrl if not login
+    let userInfo = {
+      loginUrl: loginService.config.loginUrl,
+    };
+    if (autoLoginSessionId) {
+      // return userInfo and cookie ICON_SESSION if user exist
+      let exist = await redis.get(autoLoginSessionId);
+      // decrypt cookie for get userId
+      let userInfoCur = cryptoUtil.decrypt(autoLoginSessionId, "");
+      userInfoCur = await db.user.findOne(
+        { userId: userInfoCur.userId },
+        global.globalConfig.userExportFields
+      );
+      if (userInfoCur) {
+        userInfo = userInfoCur;
+      }
+      if (exist && userInfoCur && userInfoCur.userId) {
+        // set cookie to client
+        let sessionId = await userUtil.setIconSession(userInfoCur.userId, ctx);
+        userUtil.setIconSessionCookie(sessionId, false, ctx);
+      } else if (userInfoCur && userInfoCur.userId) {
+        // user exist but session expired in redis, need to generate session to redis then auto login
+        let autoLoginSessionId = await userUtil.setIconAutoLoginSession(
+          userInfoCur.userId,
+          ctx
+        );
+        let sessionId = await userUtil.setIconSession(userInfoCur.userId, ctx);
+        userUtil.setIconSessionCookie(sessionId, false, ctx);
+        userUtil.setIconAutoLoginSessionCookie(autoLoginSessionId, false, ctx);
+      }
+    }
+    ctx.body = responseFormat.responseFormat(200, "", userInfo);
+  }
+
+  /**
+   * get specific userInfo
+   *
+   * @param    {Object}           ctx                         request object
+   */
+  async getUserInfo(ctx) {
+    let params = ctx.params;
+    let userInfo = await db.user.findOne(
+      { userId: params.userId },
+      global.globalConfig.userExportFields
+    );
+    if (!userInfo) {
+      ctx.body = responseFormat.responseFormat(500, "user not exist", false);
+    } else {
+      ctx.body = responseFormat.responseFormat(200, "", userInfo);
+    }
+  }
+}
+
+module.exports = UserController;

+ 34 - 0
server/database/connect.js

@@ -0,0 +1,34 @@
+let mongoose = require('mongoose');
+let dbConfig = require('../config/dbConfig');
+let log = require('../util/log');
+
+// 启动数据库
+module.exports = function () {
+    return new Promise(function (resolve) {
+        let dbUrl = `mongodb://${dbConfig.username}:${dbConfig.password}@${dbConfig.host}:${dbConfig.port}/${dbConfig.dbName}`;
+        // let dbUrl = `mongodb://${dbConfig.host}:${dbConfig.port}/${dbConfig.dbName}`;
+        // if (!dbConfig.username) {
+        //     dbUrl = `mongodb://${dbConfig.host}:${dbConfig.port}/${dbConfig.dbName}`;
+        // }
+        log.debug(`dbUrl: ${dbUrl}`);
+        mongoose.connect(dbUrl, {
+            poolSize: 20,
+            useMongoClient: true,
+            authSource: dbConfig.authSource
+        });
+        mongoose.connection.on('connected', function () {
+            log.debug(`connect to mongodb success, dbUrl: ${dbUrl}`);
+            resolve();
+        });
+
+        mongoose.connection.on('error', function (err) {
+            log.error(`connect to mongodb error, err: ${err} dbUrl: ${dbUrl}`);
+            process.exit(1);
+        });
+
+        mongoose.connection.on('disconnected', function (err) {
+            log.debug(`disconnect mongodb, dbUrl: ${dbUrl}, reason: ${err}`);
+            process.exit(1);
+        });
+    })
+};

+ 141 - 0
server/database/index.js

@@ -0,0 +1,141 @@
+let mongoose = require('mongoose');
+let Schema = mongoose.Schema;
+const userModel = require('./model/user');
+const iconModel = require('./model/icon');
+const iconDraftModel = require('./model/iconDraft');
+const iconLibModel = require('./model/iconRepo');
+const counterModel = require('./model/counter');
+const iconBelongToRepoModel = require('./model/iconBelongToRepo');
+const repoRecommendModel = require('./model/repoRecommend');
+class MongoDB {
+    /**
+     * 构造函数
+     *
+     * @param    {String}        name               集合名称
+     * @param    {Object}        model              集合model模型
+     * @return   {Object}                           集合对象
+     */
+    constructor (name, model) {
+        if (!name || !model) {
+            return;
+        }
+        this.model = this.schema(name, model);
+    }
+
+    /**
+     * 单例模式,获取某一个collection对象
+     * @param    {String}        name               集合名称
+     * @param    {String}        model              数据模型
+     * @return   {Object}                           集合对象
+     */
+    static getModel (name, model) {
+        if (!!name && !!model) {
+            return new this(name, model);
+        }
+    }
+
+    /**
+     * 添加记录,返回添加成功对象
+     *
+     * @param    {Object}        data               数据对象
+     * @return   {Object}                           集合对象
+     */
+    async add (data) {
+        return await this.model.create(data);
+    }
+
+    /**
+     * 删除记录,返回添加成功对象
+     *
+     * @param    {Object}        data               数据对象
+      */
+    async delete (data) {
+        return await this.model.remove(data);
+    }
+
+    /**
+     * 更新记录,返回添加更新对象
+     *
+     * @param    {Object}        condition          查询条件
+     * @param    {Object}        data               数据对象
+     * @param    {Object}        options            更新配置
+     * @return   {Object}                           集合对象
+     */
+    async update (condition, data, options = {}) {
+        return await this.model.update(condition, data, options)
+    }
+
+    /**
+     * 查找,返回对象数组
+     *
+     * @param    {Object}        data               查询条件
+     * @param    {String}        fields             需要返回的字段, 多个字段空格分开,默认全部
+     * @param    {Object}        options            选择配置{lean: true}
+     * @return   {Object}                           集合对象
+     */
+    async find (data, fields = '', options = {}) {
+        options = Object.assign({lean: true}, options);
+        return await this.model.find(data, fields, options);
+    }
+
+    /**
+     * 查找,返回第一个数据对象
+     *
+     * @param    {Object}        data               查询条件
+     * @param    {String}        fields             需要返回的字段, 多个字段空格分开,默认全部
+     * @param    {Object}        options            选择配置{lean: true}
+     * @return   {Object}                           集合对象
+     */
+    async findOne (data, fields = '', options = {lean: true}) {
+        let result = await this.model.find(data, fields, options);
+        return (result[0] || null)
+    }
+
+    /**
+     * 查找并更新,返回第一个数据对象
+     *
+     * @param    {Object}        data               查询条件
+     * @param    {Object}        update             更新的字段
+     * @param    {Object}        options            选择配置{lean: true}
+     * @return   {Object}                           集合对象
+     */
+    async findOneAndUpdate (data, update, options = {lean: true}) {
+        let result = await this.model.findOneAndUpdate(data, update, options);
+        return (result || null)
+    }
+
+    /**
+     * 查找该条件下的所有条目,返回数量
+     *
+     * @param    {Object}        data               查询条件
+     * @param    {String}        fields             需要返回的字段, 多个字段空格分开,默认全部
+     * @param    {Object}        options            选择配置{lean: true}
+     * @return   {Object}                           集合对象
+     */
+    async count (data) {
+        return await this.model.count(data);
+    }
+
+    /**
+     * 生成mongoose collection实例
+     *
+     * @param    {String}        name               集合名称
+     * @param    {Object}        model              集合model模型
+     * @return   {Object}                           集合对象
+     */
+    schema (name, model) {
+        let schema = new Schema(model);
+        // 保证嵌套数组中 unique起作用
+        return mongoose.model(name, schema);
+    }
+}
+
+module.exports = {
+    user: MongoDB.getModel('user', userModel),
+    icon: MongoDB.getModel('icon', iconModel),
+    iconRepo: MongoDB.getModel('iconRepo', iconLibModel),
+    counter: MongoDB.getModel('counter', counterModel),
+    iconDraft: MongoDB.getModel('iconDraft', iconDraftModel),
+    iconBelongToRepo: MongoDB.getModel('iconBelongToRepo', iconBelongToRepoModel),
+    repoRecommend: MongoDB.getModel('repoRecommend', repoRecommendModel)
+};

+ 7 - 0
server/database/model/counter.js

@@ -0,0 +1,7 @@
+// 计数集合实现
+
+module.exports = {
+  model: String, // model的名称,modelName是保留字
+  field: String, // 自增的字段
+  seq: { type: Number, default: 0 }, // 该字段的计数
+};

+ 10 - 0
server/database/model/icon.js

@@ -0,0 +1,10 @@
+// 字体图标信息表
+
+module.exports = {
+  iconId: { type: Number, unique: true }, // 字体图标Id, 唯一
+  iconName: { type: String }, // 字体图标名称
+  iconContent: String, // 字体图标内容
+  ownerId: Number, // 归属者用户Id
+  createTime: Date, // 创建时间
+  updateTime: Date, // 最后更新时间
+};

+ 12 - 0
server/database/model/iconBelongToRepo.js

@@ -0,0 +1,12 @@
+// icon and repo relationship, for delete operation
+
+module.exports = {
+  iconId: { type: Number, unique: true },
+  iconName: { type: String },
+  repos: [
+    {
+      repoId: Number,
+      repoName: String,
+    },
+  ],
+};

+ 12 - 0
server/database/model/iconDraft.js

@@ -0,0 +1,12 @@
+// 字体图标信息表
+
+module.exports = {
+  iconId: { type: Number, unique: true }, // 字体图标Id, 唯一
+  iconName: String, // 字体图标名称
+  iconOriginContent: String, // 字体图标原内容
+  iconContent: String, // 字体图标显示内容
+  svgPath: String, // svg绘制path
+  ownerId: Number, // 归属者用户Id
+  createTime: Date, // 创建时间
+  updateTime: Date, // 最后更新时间
+};

+ 24 - 0
server/database/model/iconRepo.js

@@ -0,0 +1,24 @@
+// 字体图标库信息表
+
+module.exports = {
+  repoId: { type: Number, unique: true }, // 字体图标库Id, 唯一
+  repoUrl: String, // 字体图标库git地址
+  repoName: String, // 字体图标库名称
+  repoDescription: String, // 字体图标库描述
+  iconPrefix: { type: String }, // 字体图标前缀
+  fontPath: String, // css文件中引用字体文件的路径
+  createTime: Date, // 创建时间
+  updateTime: Date, // 最后更新时间
+  isPublic: Boolean, // 是否公开
+  unSync: Boolean, // 是否有未更新
+  cssUrl: String, // css的nos链接
+  cssContent: String, // css的内容
+  svgSpriteContent: String, // svgSprite的内容
+  iconIds: [
+    {
+      iconId: { type: Number },
+      iconName: { type: String },
+    },
+  ],
+  ownerId: Number, // 归属者用户Id
+};

+ 7 - 0
server/database/model/repoRecommend.js

@@ -0,0 +1,7 @@
+// index page recommend repo
+
+module.exports = {
+  id: { type: Number, unique: true }, // recommend id
+  repoId: { type: Number, unique: true }, // repo id
+  repoName: String, // repo name
+};

+ 19 - 0
server/database/model/user.js

@@ -0,0 +1,19 @@
+// 用户个人信息表
+
+module.exports = {
+  userId: { type: Number, unique: true }, // 用户Id, 唯一
+  userName: { type: String, unique: true }, // 用户名称, 唯一,账号
+  nickName: String, // 昵称
+  fullName: String, // 全名
+  password: String, // 密码
+  email: String, // 邮箱
+  avatar: String, // 头像链接
+  repos: [
+    {
+      repoId: { type: Number }, // 拥有的图标库Id
+      repoName: String,
+    },
+  ],
+  createTime: Date, // 创建时间
+  updateTime: Date, // 最后更新时间
+};

+ 39 - 0
server/database/redisStorage.js

@@ -0,0 +1,39 @@
+const Redis = require('ioredis');
+const redisConfig = require('../config/redisConfig');
+const { Store } = require('koa-session2');
+const cryptoUtil = require('../util/cryptoUtil');
+const log = require('../util/log');
+
+class RedisStore extends Store {
+    constructor () {
+        super();
+        this.redis = new Redis(redisConfig);
+        this.redis.on('connect', () => {
+            log.debug(`connect redis success`);
+        })
+    }
+
+    async get (sid, salt, ctx) {
+        let data = await this.redis.get(`SESSION:${sid}`) || null;
+        // log.debug(`[%s.get] get session success, sid: ${sid}`, this.constructor.name);
+        return JSON.parse(data);
+    }
+
+    async set (session, { sid = cryptoUtil.encrypt(session), maxAge = 100000000 } = {}, ctx) {
+        try {
+            // Use redis set EX to automatically drop expired sessions
+            await this.redis.set(`SESSION:${sid}`, JSON.stringify(session), 'EX', maxAge / 1000);
+            log.debug(`[%s.set] set session success, sid: ${sid}`, this.constructor.name);
+        } catch (e) {
+            throw new Error(e);
+        }
+        return sid;
+    }
+
+    async destroy (sid, ctx) {
+        log.debug(`destroy session ${sid}`);
+        return await this.redis.del(`SESSION:${sid}`);
+    }
+}
+
+module.exports = new RedisStore();

+ 16 - 0
server/middleware/auth.js

@@ -0,0 +1,16 @@
+const responseFormat = require("../util/responseFormat");
+const userUtil = require("../util/userUtil");
+let redis = require("../database/redisStorage");
+
+module.exports = function () {
+  return async function (ctx, next) {
+    const sessionId = userUtil.getIconSessionCookie(ctx);
+    let userInfo = await redis.get(sessionId);
+    if (!userInfo) {
+      ctx.body = responseFormat.responseFormat(401, "无权限", null);
+    } else {
+      ctx.userInfo = userInfo;
+      await next();
+    }
+  };
+};

+ 0 - 0
server/middleware/getUserInfo.js


Some files were not shown because too many files changed in this diff