From df84f7bae25d11564072932db12989f1da5a9be2 Mon Sep 17 00:00:00 2001
From: bigfengyu <kuailewudi@yeah.net>
Date: Thu, 2 Apr 2020 10:59:21 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?=
 =?UTF-8?q?=E5=8A=A0=E5=BC=BA=E3=80=81swagger=20=E5=AF=BC=E5=85=A5?=
 =?UTF-8?q?=E5=8A=A0=E5=BC=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/routes/export.ts        |   6 +-
 src/routes/organization.ts  |   8 +-
 src/routes/repository.ts    | 187 ++++++-----
 src/routes/utils/access.ts  |  60 +++-
 src/scripts/dev.ts          |   2 +-
 src/service/mail.ts         | 161 +++++++++-
 src/service/migrate.ts      | 611 ++++++++++++++++++++++++++++++------
 src/service/organization.ts |   6 +-
 src/service/repository.ts   |  12 +-
 9 files changed, 833 insertions(+), 220 deletions(-)

diff --git a/src/routes/export.ts b/src/routes/export.ts
index f8dc557..2b4d2f6 100644
--- a/src/routes/export.ts
+++ b/src/routes/export.ts
@@ -12,7 +12,7 @@ router.get('/export/postman', async ctx => {
   const repoId = +ctx.query.id
   if (
     !(await AccessUtils.canUserAccess(
-      ACCESS_TYPE.REPOSITORY,
+      ACCESS_TYPE.REPOSITORY_GET,
       ctx.session.id,
       repoId
     ))
@@ -41,7 +41,7 @@ router.get('/export/markdown', async ctx => {
   const repoId = +ctx.query.id
   if (
     !(await AccessUtils.canUserAccess(
-      ACCESS_TYPE.REPOSITORY,
+      ACCESS_TYPE.REPOSITORY_GET,
       ctx.session.id,
       repoId
     ))
@@ -59,7 +59,7 @@ router.get('/export/docx', async ctx => {
   const repoId = +ctx.query.id
   if (
     !(await AccessUtils.canUserAccess(
-      ACCESS_TYPE.REPOSITORY,
+      ACCESS_TYPE.REPOSITORY_GET,
       ctx.session.id,
       repoId
     ))
diff --git a/src/routes/organization.ts b/src/routes/organization.ts
index 59bbefc..172b0bc 100644
--- a/src/routes/organization.ts
+++ b/src/routes/organization.ts
@@ -113,7 +113,7 @@ router.get('/organization/joined', isLoggedIn, async (ctx) => {
 })
 router.get('/organization/get', async (ctx) => {
   const organizationId = +ctx.query.id
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION, ctx.session.id, organizationId)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_GET, ctx.session.id, organizationId)) {
     ctx.body = COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -144,7 +144,7 @@ router.post('/organization/create', isLoggedIn, async (ctx) => {
 router.post('/organization/update', isLoggedIn, async (ctx, next) => {
   let body = Object.assign({}, ctx.request.body)
   const organizationId = +body.id
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION, ctx.session.id, organizationId)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_SET, ctx.session.id, organizationId)) {
     ctx.body = COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -188,7 +188,7 @@ router.post('/organization/update', isLoggedIn, async (ctx, next) => {
 router.post('/organization/transfer',  isLoggedIn, async (ctx) => {
   let { id, ownerId } = ctx.request.body
   const organizationId = +id
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION, ctx.session.id, organizationId)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_SET, ctx.session.id, organizationId)) {
     ctx.body = COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -201,7 +201,7 @@ router.post('/organization/transfer',  isLoggedIn, async (ctx) => {
 router.get('/organization/remove', isLoggedIn, async (ctx, next) => {
   let { id } = ctx.query
   const organizationId = +id
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION, ctx.session.id, organizationId)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_SET, ctx.session.id, organizationId)) {
     ctx.body = COMMON_ERROR_RES.ACCESS_DENY
     return
   }
diff --git a/src/routes/repository.ts b/src/routes/repository.ts
index a714d2b..07d4153 100644
--- a/src/routes/repository.ts
+++ b/src/routes/repository.ts
@@ -47,7 +47,7 @@ router.get('/repository/list', async (ctx) => {
   let { name, user, organization } = ctx.query
 
   if (+organization > 0) {
-    const access = await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION, ctx.session.id, organization)
+    const access = await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_GET, ctx.session.id, organization)
 
     if (access === false) {
       ctx.body = {
@@ -74,10 +74,11 @@ router.get('/repository/list', async (ctx) => {
     include: [
       QueryInclude.Creator,
       QueryInclude.Owner,
-      QueryInclude.Locker,
-    ],
-  } as any)
-  let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100)
+      QueryInclude.Locker
+    ]
+  })
+  let limit = Math.min(ctx.query.limit ?? 10, 100)
+  let pagination = new Pagination(total, ctx.query.cursor || 1, limit)
   let repositories = await Repository.findAll({
     where,
     attributes: { exclude: [] },
@@ -91,11 +92,22 @@ router.get('/repository/list', async (ctx) => {
     ],
     offset: pagination.start,
     limit: pagination.limit,
-    order: [['updatedAt', 'DESC']],
-  } as any)
+    order: [['updatedAt', 'DESC']]
+  })
+  let repoData = await Promise.all(repositories.map(async (repo) => {
+    const canUserEdit = await AccessUtils.canUserAccess(
+      ACCESS_TYPE.REPOSITORY_SET,
+      ctx.session.id,
+      repo.id,
+    )
+    return {
+      ...repo.toJSON(),
+      canUserEdit
+    }
+  }))
   ctx.body = {
     isOk: true,
-    data: repositories,
+    data: repoData,
     pagination: pagination,
   }
 })
@@ -113,8 +125,7 @@ router.get('/repository/owned', isLoggedIn, async (ctx) => {
   }
 
   let auth: User = await User.findByPk(ctx.query.user || ctx.session.id)
-  // let total = await auth.countOwnedRepositories({ where })
-  // let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100)
+
   let repositories = await auth.$get('ownedRepositories', {
     where,
     include: [
@@ -125,12 +136,16 @@ router.get('/repository/owned', isLoggedIn, async (ctx) => {
       QueryInclude.Organization,
       QueryInclude.Collaborators,
     ],
-    // offset: pagination.start,
-    // limit: pagination.limit,
-    order: [['updatedAt', 'DESC']],
+    order: [['updatedAt', 'DESC']]
+  })
+  let repoData = repositories.map(repo => {
+    return {
+      ...repo.toJSON(),
+      canUserEdit: true
+    }
   })
   ctx.body = {
-    data: repositories,
+    data: repoData,
     pagination: undefined,
   }
 })
@@ -148,8 +163,6 @@ router.get('/repository/joined', isLoggedIn, async (ctx) => {
   }
 
   let auth = await User.findByPk(ctx.query.user || ctx.session.id)
-  // let total = await auth.countJoinedRepositories({ where })
-  // let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100)
   let repositories = await auth.$get('joinedRepositories', {
     where,
     attributes: { exclude: [] },
@@ -161,19 +174,23 @@ router.get('/repository/joined', isLoggedIn, async (ctx) => {
       QueryInclude.Organization,
       QueryInclude.Collaborators,
     ],
-    // offset: pagination.start,
-    // limit: pagination.limit,
-    order: [['updatedAt', 'DESC']],
+    order: [['updatedAt', 'DESC']]
+  })
+  let repoData = repositories.map(repo => {
+    return {
+      ...repo.toJSON(),
+      canUserEdit: true,
+    }
   })
   ctx.body = {
-    data: repositories,
-    pagination: undefined,
+    data: repoData,
+    pagination: undefined
   }
 })
 
 router.get('/repository/get', async (ctx) => {
   const access = await AccessUtils.canUserAccess(
-    ACCESS_TYPE.REPOSITORY,
+    ACCESS_TYPE.REPOSITORY_GET,
     ctx.session.id,
     ctx.query.id,
     ctx.query.token
@@ -186,54 +203,52 @@ router.get('/repository/get', async (ctx) => {
     return
   }
   const excludeProperty = ctx.query.excludeProperty || false
-  const cacheKey = excludeProperty ? CACHE_KEY.REPOSITORY_GET_EXCLUDE_PROPERTY : CACHE_KEY.REPOSITORY_GET
-  const tryCache = await RedisService.getCache(cacheKey, ctx.query.id)
-
-  let repository: Partial<Repository>
-  if (tryCache && !excludeProperty) {
-    repository = JSON.parse(tryCache)
-  } else {
-    // 分开查询减少
-    let [repositoryOmitModules, repositoryModules] = await Promise.all([
-      Repository.findByPk(ctx.query.id, {
-        attributes: { exclude: [] },
-        include: [
-          QueryInclude.Creator,
-          QueryInclude.Owner,
-          QueryInclude.Locker,
-          QueryInclude.Members,
-          QueryInclude.Organization,
-          QueryInclude.Collaborators,
-        ],
-      }),
-      Repository.findByPk(ctx.query.id, {
-        attributes: { exclude: [] },
-        include: [
-          excludeProperty
-            ? QueryInclude.RepositoryHierarchyExcludeProperty
-            : QueryInclude.RepositoryHierarchy,
-        ],
-        order: [
-          [{ model: Module, as: 'modules' }, 'priority', 'asc'],
-          [
-            { model: Module, as: 'modules' },
-            { model: Interface, as: 'interfaces' },
-            'priority',
-            'asc',
-          ],
+  const canUserEdit = await AccessUtils.canUserAccess(
+    ACCESS_TYPE.REPOSITORY_SET,
+    ctx.session.id,
+    ctx.query.id,
+    ctx.query.token,
+  )
+  let repository: Partial<Repository> & {
+    canUserEdit: boolean
+  }
+  // 分开查询减少查询时间
+  let [repositoryOmitModules, repositoryModules] = await Promise.all([
+    Repository.findByPk(ctx.query.id, {
+      attributes: { exclude: [] },
+      include: [
+        QueryInclude.Creator,
+        QueryInclude.Owner,
+        QueryInclude.Locker,
+        QueryInclude.Members,
+        QueryInclude.Organization,
+        QueryInclude.Collaborators,
+      ],
+    }),
+    Repository.findByPk(ctx.query.id, {
+      attributes: { exclude: [] },
+      include: [
+        excludeProperty
+          ? QueryInclude.RepositoryHierarchyExcludeProperty
+          : QueryInclude.RepositoryHierarchy,
+      ],
+      order: [
+        [{ model: Module, as: 'modules' }, 'priority', 'asc'],
+        [
+          { model: Module, as: 'modules' },
+          { model: Interface, as: 'interfaces' },
+          'priority',
+          'asc',
         ],
-      }),
-    ])
-    repository = {
-      ...repositoryOmitModules.toJSON(),
-      ...repositoryModules.toJSON()
-    }
-    await RedisService.setCache(
-      cacheKey,
-      JSON.stringify(repository),
-      ctx.query.id
-    )
+      ],
+    }),
+  ])
+  repository = {
+    ...repositoryOmitModules.toJSON(),
+    ...repositoryModules.toJSON(),
+    canUserEdit
   }
+
   ctx.body = {
     data: repository,
   }
@@ -281,7 +296,7 @@ router.post('/repository/create', isLoggedIn, async (ctx, next) => {
 
 router.post('/repository/update', isLoggedIn, async (ctx, next) => {
   const body = Object.assign({}, ctx.request.body)
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY, ctx.session.id, body.id)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, body.id)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -354,7 +369,7 @@ router.post('/repository/update', isLoggedIn, async (ctx, next) => {
 
 router.post('/repository/transfer', isLoggedIn, async (ctx) => {
   let { id, ownerId, organizationId } = ctx.request.body
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION, ctx.session.id, organizationId)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_SET, ctx.session.id, organizationId)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -372,7 +387,7 @@ router.post('/repository/transfer', isLoggedIn, async (ctx) => {
 
 router.get('/repository/remove', isLoggedIn, async (ctx, next) => {
   const id = +ctx.query.id
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY, ctx.session.id, id)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, id)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -397,7 +412,7 @@ router.get('/repository/remove', isLoggedIn, async (ctx, next) => {
 // TOEO 锁定/解锁仓库 待测试
 router.post('/repository/lock', isLoggedIn, async (ctx) => {
   const id = +ctx.request.body.id
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY, ctx.session.id, id)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, id)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -475,7 +490,7 @@ router.post('/module/create', isLoggedIn, async (ctx, next) => {
 
 router.post('/module/update', isLoggedIn, async (ctx, next) => {
   const { id, name, description } = ctx.request.body
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.MODULE, ctx.session.id, +id)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.MODULE_SET, ctx.session.id, +id)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -521,7 +536,7 @@ router.post('/module/move', isLoggedIn, async ctx => {
 
 router.get('/module/remove', isLoggedIn, async (ctx, next) => {
   let { id } = ctx.query
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.MODULE, ctx.session.id, +id)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.MODULE_SET, ctx.session.id, +id)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -570,7 +585,7 @@ router.get('/interface/count', async (ctx) => {
 router.get('/interface/list', async (ctx) => {
   let where: any = {}
   let { repositoryId, moduleId, name } = ctx.query
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY, ctx.session.id, +repositoryId)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_GET, ctx.session.id, +repositoryId)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -594,7 +609,7 @@ router.get('/repository/defaultVal/get/:id', async (ctx) => {
 
 router.post('/repository/defaultVal/update/:id', async (ctx) => {
   const repositoryId: number = ctx.params.id
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY, ctx.session.id, repositoryId)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, repositoryId)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -643,7 +658,7 @@ router.get('/interface/get', async (ctx) => {
 
   if (
     !(await AccessUtils.canUserAccess(
-      ACCESS_TYPE.REPOSITORY,
+      ACCESS_TYPE.REPOSITORY_GET,
       ctx.session.id,
       itf.repositoryId
     ))
@@ -699,7 +714,7 @@ router.post('/interface/create', isLoggedIn, async (ctx, next) => {
 
 router.post('/interface/update', isLoggedIn, async (ctx, next) => {
   let body = ctx.request.body
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE, ctx.session.id, +body.id)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_SET, ctx.session.id, +body.id)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -744,7 +759,7 @@ router.post('/interface/move', isLoggedIn, async ctx => {
 
 router.get('/interface/remove', async (ctx, next) => {
   let { id } = ctx.query
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE, ctx.session.id, +id)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_SET, ctx.session.id, +id)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -783,7 +798,7 @@ router.post('/interface/lock', async (ctx, next) => {
   }
 
   let { id } = ctx.request.body
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE, ctx.session.id, +id)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_SET, ctx.session.id, +id)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -820,7 +835,7 @@ router.post('/interface/unlock', async (ctx) => {
   }
 
   let { id } = ctx.request.body
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE, ctx.session.id, +id)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_SET, ctx.session.id, +id)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -919,7 +934,7 @@ router.post('/properties/update', isLoggedIn, async (ctx, next) => {
   properties = Array.isArray(properties) ? properties : [properties]
 
   let itf = await Interface.findByPk(itfId)
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE, ctx.session.id, itfId)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_SET, ctx.session.id, itfId)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -1014,7 +1029,7 @@ router.post('/properties/update', isLoggedIn, async (ctx, next) => {
 
 router.get('/property/remove', isLoggedIn, async (ctx) => {
   let { id } = ctx.query
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.PROPERTY, ctx.session.id, id)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.PROPERTY_SET, ctx.session.id, id)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -1027,7 +1042,7 @@ router.get('/property/remove', isLoggedIn, async (ctx) => {
 
 router.post('/repository/import', isLoggedIn, async (ctx) => {
   const { docUrl, orgId } = ctx.request.body
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION, ctx.session.id, orgId)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_SET, ctx.session.id, orgId)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -1044,7 +1059,7 @@ router.post('/repository/import', isLoggedIn, async (ctx) => {
 router.post('/repository/importswagger', isLoggedIn, async (ctx) => {
   const { orgId, repositoryId, swagger, version = 1, mode = 'manual'} = ctx.request.body
   // 权限判断
-  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION, ctx.session.id, orgId)) {
+  if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, repositoryId)) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
@@ -1063,7 +1078,7 @@ router.post('/repository/importswagger', isLoggedIn, async (ctx) => {
 router.post('/repository/importJSON', isLoggedIn , async ctx => {
   const { data } = ctx.request.body
 
-  if (!(await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY, ctx.session.id, data.id))) {
+  if (!(await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, data.id))) {
     ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
     return
   }
diff --git a/src/routes/utils/access.ts b/src/routes/utils/access.ts
index b13146b..c2d6b08 100644
--- a/src/routes/utils/access.ts
+++ b/src/routes/utils/access.ts
@@ -2,27 +2,61 @@ import OrganizationService from '../../service/organization'
 import RepositoryService from '../../service/repository'
 import { Module, Interface, Property } from '../../models'
 
-export enum ACCESS_TYPE { ORGANIZATION, REPOSITORY, MODULE, INTERFACE, PROPERTY, USER, ADMIN }
+export enum ACCESS_TYPE {
+  ORGANIZATION_GET,
+  ORGANIZATION_SET,
+  REPOSITORY_GET,
+  REPOSITORY_SET,
+  MODULE_GET,
+  MODULE_SET,
+  INTERFACE_GET,
+  INTERFACE_SET,
+  PROPERTY_GET,
+  PROPERTY_SET,
+  USER,
+  ADMIN,
+}
 const inTestMode = process.env.TEST_MODE === 'true'
 
 export class AccessUtils {
-  public static async canUserAccess(accessType: ACCESS_TYPE, curUserId: number, entityId: number, token?: string): Promise<boolean> {
+  public static async canUserAccess(
+    accessType: ACCESS_TYPE,
+    curUserId: number,
+    entityId: number,
+    token?: string,
+  ): Promise<boolean> {
+    // 测试模式无权限
     if (inTestMode) {
       return true
     }
-    if (accessType === ACCESS_TYPE.ORGANIZATION) {
-      return await OrganizationService.canUserAccessOrganization(curUserId, entityId)
-    } else if (accessType === ACCESS_TYPE.REPOSITORY) {
-      return await RepositoryService.canUserAccessRepository(curUserId, entityId, token)
-    } else if (accessType === ACCESS_TYPE.MODULE) {
+
+    // 无 session 时拒绝写操作
+    if (!curUserId) {
+      return false
+    }
+
+    if (
+      accessType === ACCESS_TYPE.ORGANIZATION_GET ||
+      accessType === ACCESS_TYPE.ORGANIZATION_SET
+    ) {
+      return OrganizationService.canUserAccessOrganization(curUserId, entityId)
+    } else if (
+      accessType === ACCESS_TYPE.REPOSITORY_GET ||
+      accessType === ACCESS_TYPE.REPOSITORY_SET
+    ) {
+      return RepositoryService.canUserAccessRepository(curUserId, entityId, token)
+    } else if (accessType === ACCESS_TYPE.MODULE_GET || accessType === ACCESS_TYPE.MODULE_SET) {
       const mod = await Module.findByPk(entityId)
-      return await RepositoryService.canUserAccessRepository(curUserId, mod.repositoryId, token)
-    } else if (accessType === ACCESS_TYPE.INTERFACE) {
+      return RepositoryService.canUserAccessRepository(curUserId, mod.repositoryId, token)
+    } else if (
+      accessType === ACCESS_TYPE.INTERFACE_GET ||
+      accessType === ACCESS_TYPE.INTERFACE_SET
+    ) {
       const itf = await Interface.findByPk(entityId)
-      return await RepositoryService.canUserAccessRepository(curUserId, itf.repositoryId, token)
-    } else if (accessType === ACCESS_TYPE.PROPERTY) {
+      return RepositoryService.canUserAccessRepository(curUserId, itf.repositoryId, token)
+    } else if (accessType === ACCESS_TYPE.PROPERTY_GET || accessType === ACCESS_TYPE.PROPERTY_SET) {
       const p = await Property.findByPk(entityId)
-      return await RepositoryService.canUserAccessRepository(curUserId, p.repositoryId, token)
+      return RepositoryService.canUserAccessRepository(curUserId, p.repositoryId, token)
     }
     return false
   }
@@ -33,4 +67,4 @@ export class AccessUtils {
     }
     return curUserId === 1
   }
-}
\ No newline at end of file
+}
diff --git a/src/scripts/dev.ts b/src/scripts/dev.ts
index 002621e..029c54e 100644
--- a/src/scripts/dev.ts
+++ b/src/scripts/dev.ts
@@ -8,7 +8,7 @@ const start = () => {
   let open = false
   console.log('----------------------------------------')
   app.listen(port, () => {
-    console.log(`rap2-dolores is running as ${url}`)
+    console.log(`rap2-delos is running as ${url}`)
     if (!open) return
     try {
       execSync(`osascript openChrome.applescript ${url}`, { cwd: __dirname, stdio: 'ignore' })
diff --git a/src/service/mail.ts b/src/service/mail.ts
index 920291d..9bf1713 100644
--- a/src/service/mail.ts
+++ b/src/service/mail.ts
@@ -2,26 +2,158 @@ import * as nodemailer from 'nodemailer'
 import config from '../config'
 
 export default class MailService {
+  public static async sendMail(mailOptions: nodemailer.SendMailOptions) {
+    const transporter = nodemailer.createTransport(config.mail)
 
-  public static send(to: string, subject: string, html: string) {
+    return new Promise((resolve, reject) => {
+      transporter.verify(function(error) {
+        if (error) {
+          reject(error)
+        } else {
+          transporter.sendMail(mailOptions, function(error, info) {
+            if (error) {
+              reject(error)
+            } else {
+              resolve(info.response)
+            }
+          })
+        }
+      })
+    })
+  }
 
-    nodemailer.createTestAccount((_err, _account) => {
-      const transporter = nodemailer.createTransport(config.mail)
+  public static send(to: string | string[], subject: string, html: string) {
+    const transporter = nodemailer.createTransport(config.mail)
 
-      // setup email data with unicode symbols
-      const mailOptions = {
-        from: `"RAP2 Notifier" <${config.mailSender}>`, // sender address
-        to,
-        subject,
-        html,
-      }
+    const mailOptions = {
+      from: `"RAP2 Notifier" <${config.mailSender}>`,
+      to,
+      subject,
+      html,
+    }
 
-      // send mail with defined transport object
-      transporter.sendMail(mailOptions)
+    return new Promise((resolve, reject) => {
+      transporter.verify(function(error) {
+        if (error) {
+          reject(error)
+        } else {
+          transporter.sendMail(mailOptions, function(error, info) {
+            if (error) {
+              reject(error)
+            } else {
+              resolve(info.response)
+            }
+          })
+        }
+      })
     })
   }
 
-public static mailFindpwdTemp = `<head>
+  public static mailNoticeTemp = `<head>
+  <base target="_blank" />
+  </head>
+  <body>
+  <div id="content" bgcolor="#f0f0f0" style="background-color:#f0f0f0;padding:10px">
+    <table width="100%" border="0" cellpadding="0" cellspacing="0">
+      <tr>
+        <td valign="top">
+          <table class="full-width" align="center" width="700" border="0" cellpadding="0" cellspacing="0"
+            bgcolor="#ffffff" style="background-color:#ffffff; width:700px;">
+            <tr>
+              <td style="vertical-align:top;">
+                <table class="full-width" align="center" width="700" border="0" cellpadding="0" cellspacing="0"
+                  style="width:700px;">
+                  <tr>
+                    <td valign="top">
+                      <table class="full-width" width="549" border="0" cellpadding="0" cellspacing="0">
+                        <tr>
+                          <td class="mobile-spacer" width="30" style="width:30px;">&nbsp;</td>
+                          <td width="32" valign="middle" style="padding-top:30px; padding-bottom:32px; width:32px;"><img width="140" height="34" src="http://img.alicdn.com/tfs/TB1vtTllHH1gK0jSZFwXXc7aXXa-140-34.png"></td>
+                          <td valign="middle"></td>
+                          <td width="10"></td>
+                        </tr>
+                      </table>
+                    </td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+            <tr>
+              <td valign="top" style="padding-top:10px ;">
+                <table class="full-width" align="center" width="700" border="0" cellpadding="0" cellspacing="0"
+                  bgcolor="#ffffff" style="background-color:#ffffff; width:700px;">
+                  <tr>
+                    <td class="mobile-spacer" width="30" style="width:30px;">&nbsp;</td>
+                    <td class="mobile-headline"
+                      style="color:#000000; font-size:24px; line-height:26px;">{=TITLE=}</td>
+                    <td class="mobile-spacer" width="30" style="width:30px;">&nbsp;</td>
+                  </tr>
+                  <tr>
+                    <td class="mobile-spacer" width="30" style="width:30px;">&nbsp;</td>
+                    <td
+                      style="color:#555555; font-size:15px; line-height:20px; padding-top:30px;">
+                      <ul style="margin-bottom:0px; margin-top:0px;">
+                        {=CONTENT=}
+                      </ul>
+                    </td>
+                    <td class="mobile-spacer" width="30" style="width:30px;">&nbsp;</td>
+                  </tr>
+                </table>
+                <table class="full-width" align="center" width="700" border="0" cellpadding="0" cellspacing="0"
+                  bgcolor="#F7F8F9" style="background-color:#F7F8F9; width:700px;">
+                  <tr>
+                    <td class="mobile-spacer" width="30" style="width:30px;">&nbsp;</td>
+                    <td
+                      style="color:#555555; font-size:15px; line-height:20px; padding-top:30px; padding-bottom:30px;">
+                      <strong>此邮件为系统发送,请勿直接回复</strong>
+                      </td>
+                    <td class="mobile-spacer" width="30" style="width:30px;">&nbsp;</td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+            <tr>
+              <td valign="top">
+                <table class="full-width" align="center" width="700" border="0" cellpadding="0" cellspacing="0"
+                  bgcolor="#3f51b5" style="background-color:#3f51b5; width:700px;">
+                  <tr>
+                    <td width="22">&nbsp;</td>
+                    <td align="center"
+                      style="color:#ffffff; font-size:11px; line-height:14px; padding-top:20px;text-align:center;">
+                      <strong><a href="http://rap2.alibaba-inc.com/"
+                      style="color:#ffffff; font-weight:bold; text-decoration:none;">RAP2</a> | <a
+                          href="https://github.com/thx/rap2-delos"
+                          style="color:#ffffff; font-weight:bold; text-decoration:none;">GitHub</a></strong>
+                    </td>
+                    <td width="22">&nbsp;</td>
+                  </tr>
+                  <tr>
+                    <td width="22" colspan="3">&nbsp;</td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+          </table>
+        </td>
+      </tr>
+    </table>
+  </div>
+  <style type="text/css">
+    body {
+      font-size: 14px;
+      font-family: "Roboto", "Helvetica", "Arial", sans-serif;
+      line-height: 1.666;
+      padding: 0;
+      margin: 0;
+      overflow: auto;
+      white-space: normal;
+      word-wrap: break-word;
+      min-height: 100px
+    }
+  </style>
+  </body>`
+
+  public static mailFindpwdTemp = `<head>
 <base target="_blank" />
 </head>
 <body>
@@ -84,6 +216,7 @@ public static mailFindpwdTemp = `<head>
                     style="color:#555555; font-size:15px; line-height:20px; padding-top:30px; padding-bottom:30px;">
                     <strong>为什么我会收到这封邮件?</strong>您在RAP2系统使用了密码找回功能,我们发送这封邮件,以协助您重设密码。
                     <br><br>如果您没有使用此功能,请忽略此邮件
+                    <br><br>如果您不想收到我们的邮件<a href="http://rap2.taobao.org/account/unsubscribe" style="color:#1473E6; font-weight:bold; text-decoration:none;">点击退订</a>
                     </td>
                   <td class="mobile-spacer" width="30" style="width:30px;">&nbsp;</td>
                 </tr>
@@ -130,4 +263,4 @@ public static mailFindpwdTemp = `<head>
   }
 </style>
 </body>`
-}
\ No newline at end of file
+}
diff --git a/src/service/migrate.ts b/src/service/migrate.ts
index de569d4..8aeb98b 100644
--- a/src/service/migrate.ts
+++ b/src/service/migrate.ts
@@ -1,25 +1,30 @@
 import { Repository, Module, Interface, Property, User, QueryInclude } from '../models'
 import { SCOPES } from "../models/bo/property"
-import * as md5 from 'md5'
-import * as querystring from 'querystring'
-import * as rp from 'request-promise'
-const isMd5 = require('is-md5')
 import Tree from '../routes/utils/tree'
 import * as JSON5 from 'json5'
+import * as querystring from 'querystring'
+import * as rp from 'request-promise'
+import { Op } from 'sequelize'
 import RedisService, { CACHE_KEY } from "./redis"
+import MailService from './mail'
+import * as md5 from 'md5'
+const isMd5 = require('is-md5')
 import * as _ from 'lodash'
 
 const SWAGGER_VERSION = {
   1: '2.0'
 }
 
-
-
+/**
+ * swagger json结构转化的数组转化为树形结构
+ * @param list
+ */
 const arrayToTree = (list) => {
   const parseChildren = (list, parent) => {
     list.forEach((item) => {
       if (item.parent === parent.id) {
         item.depth = parent.depth + 1
+        item.parentName = parent.name
         item.children = item.children || []
         parent.children.push(item)
         parseChildren(list, item)
@@ -36,6 +41,50 @@ const arrayToTree = (list) => {
   })
 }
 
+/**
+ * swagger json结构转化的数组转化的树形结构转化为数组
+ * @param tree
+ */
+const treeToArray = (tree: any) => {
+  const parseChildren = (parent: any, result: any) => {
+    if (!parent.children) { return result }
+    parent.children.forEach((item: any) => {
+      result.push(item)
+      parseChildren(item, result)
+      delete item.children
+    })
+    return result
+  }
+  return parseChildren(tree, [])
+}
+
+/**
+ * 接口属性-数组结构转化为树形结构
+ * @param list
+ */
+const arrayToTreeProperties = (list: any) => {
+  const parseChildren = (list: any, parent: any) => {
+    list.forEach((item: any) => {
+      if (item.parentId === parent.id) {
+        item.depth = parent.depth + 1
+        item.children = item.children || []
+        parent.children.push(item)
+        parseChildren(list, item)
+      }
+    })
+    return parent
+  }
+  return parseChildren(list, {
+    id: -1,
+    name: 'root',
+    children: [],
+    depth: -1,
+  })
+}
+
+/**
+ * 参数请求类型枚举
+ */
 const REQUEST_TYPE_POS = {
   path: 2,
   query: 2,
@@ -44,13 +93,21 @@ const REQUEST_TYPE_POS = {
   body: 3
 }
 
+let checkSwaggerResult = []
+let changeTip = '' // 变更信息
+
 /**
+ * Swagger JSON 参数递归处理成数组
  * @param parameters 参数列表数组
- * @param parent 父级
- * @param result swagger转化为数组结果
- * @param definitions swagger $ref definitions
+ * @param parent 父级id
+ * @param parentName 父级属性name
+ * @param depth parameters list 中每个属性的深度
+ * @param result swagger转化为数组结果 -- 对swagger参数处理结果
+ * @param definitions swagger $ref definitions, 额外传递过来的swagger的definitions数据, 非计算核心算法
+ * @param scope 参数类型 -- 【暂不用】用于参数校验后提示
+ * @param apiInfo 接口信息 -- 【暂不用】用于参数校验后提示
  */
-const parse = (parameters, parent, result, definitions) => {
+const parse = (parameters, parent, parentName, depth, result, definitions, scope, apiInfo) => {
   for (let key = 0, len = parameters.length; key < len; key++) {
     const param = parameters[key]
 
@@ -58,6 +115,8 @@ const parse = (parameters, parent, result, definitions) => {
       // 非对象或者数组的基础类型
       result.push({
         ...param, parent,
+        parentName,
+        depth,
         id: `${parent}-${key}`
       })
     } else {
@@ -68,17 +127,20 @@ const parse = (parameters, parent, result, definitions) => {
 
       result.push({
         ...param, parent,
+        parentName,
+        depth,
         id: `${parent}-${key}`,
         type: paramType
       })
 
       let refName
-
       if (!param.items) {
+        // 对象
         refName = param.$ref.split('#/definitions/')[1]
         delete result.find(item => item.id === `${parent}-${key}`)['$ref']
       }
       if (param.items) {
+        // 数组
         refName = param.items.$ref.split('#/definitions/')[1]
         delete result.find(item => item.id === `${parent}-${key}`).items
       }
@@ -87,20 +149,194 @@ const parse = (parameters, parent, result, definitions) => {
       if (ref && ref.properties) {
         const properties = ref.properties
         const list = []
+
         for (const key in properties) {
-          list.push({
-            name: key,
-            ...properties[key],
-            in: param.in, // response 无所谓,不使用但是request 使用
-            required: (ref.required || []).indexOf(key) >= 0
-          })
+          // swagger文档中对definition定义属性又引用自身的情况处理-死循环
+          if (properties[key].$ref) {
+            if (properties[key].$ref.split('#/definitions/')[1] === refName) {
+              // delete properties[key].$ref
+              list.push({
+                name: key,
+                parentName: param.name,
+                depth: depth + 1,
+                ...properties[key],
+                $ref: null,
+                type: 'object',
+                in: param.in,
+                required: (ref.required || []).indexOf(key) >= 0,
+                description: `【递归父级属性】${properties[key].description || ''}`
+              })
+            } else {
+              list.push({
+                name: key,
+                parentName: param.name,
+                depth: depth + 1,
+                ...properties[key],
+                in: param.in,
+                required: (ref.required || []).indexOf(key) >= 0,
+              })
+            }
+          } else if ((properties[key].items || {}).$ref)  {
+            if (properties[key].items.$ref.split('#/definitions/')[1] === refName) {
+              // delete properties[key].items.$ref
+              list.push({
+                name: key,
+                parentName: param.name,
+                depth: depth + 1,
+                ...properties[key],
+                type: 'array',
+                $ref: null,
+                in: param.in,
+                required: (ref.required || []).indexOf(key) >= 0,
+                description: `【递归父级属性】${properties[key].description || ''}`
+              })
+            } else {
+              list.push({
+                name: key,
+                parentName: param.name,
+                depth: depth + 1,
+                ...properties[key],
+                in: param.in,
+                required: (ref.required || []).indexOf(key) >= 0,
+              })
+            }
+
+          } else {
+            list.push({
+              name: key,
+              parentName: param.name,
+              depth: depth + 1,
+              ...properties[key],
+              in: param.in, // response 无所谓,不使用但是request 使用
+              required: (ref.required || []).indexOf(key) >= 0,
+            })
+          }
         }
-        parse(list, `${parent}-${key}`, result, definitions)
+        parse(list, `${parent}-${key}`, param.name, depth + 1, result, definitions, scope, apiInfo)
       }
     }
   }
 }
 
+const transformRapParams = (p) => {
+  let rule = '', description = '', value = p.default || ''
+
+  // 类型转化处理
+  let type = (p.type || 'string')
+  if (type === 'integer') type = 'number'
+  if (type === 'file') type = 'string'
+  type = type[0].toUpperCase() + type.slice(1)
+
+  // 规则属性说明处理
+  if (p.type === 'string' && p.minLength && p.maxLength ) {
+    rule = `${p.minLength}-${p.maxLength}`
+    description = `${description}|长度限制: ${p.minLength}-${p.maxLength}`
+  } else if (p.type === 'string' && p.minLength && !p.maxLength) {
+    rule = `${p.minLength}`
+    description = `${description}|长度限制:最小值: ${p.minLength}`
+  } else if (p.type === 'string' && !p.minLength && p.maxLength) {
+    rule = `${p.required ? '1' : '0'}-${p.maxLength}`
+    description = `${description}|长度限制:最大值: ${p.maxLength}`
+  }
+  if (p.type === 'string' && p.enum && p.enum.length > 0) {
+    description = `${description}|枚举值: ${p.enum.join()}`
+  }
+  if ((p.type === 'integer' || p.type === 'number') && p.minimum && p.maxinum) {
+    rule = `${p.minimum}-${p.maxinum}`
+    description = `${description}|数据范围: ${p.minimum}-${p.maxinum}`
+  }
+  if ((p.type === 'integer' || p.type === 'number') && p.minimum && !p.maxinum) {
+    rule = `${p.minimum}`
+    description = `${description}|数据范围: 最小值:${p.minimum}`
+  }
+  if ((p.type === 'integer' || p.type === 'number') && !p.minimum && p.maxinum) {
+    rule = `${p.required ? '1' : '0'}-${p.maxinum}`
+    description = `${description}|数据范围: 最大值:${p.maxinum}`
+  }
+
+  // 默认值转化处理
+  value = p.default || ''
+  if (!p.default && p.type === 'string') value = '@ctitle'
+  if (!p.default && (p.type === 'number' || p.type === 'integer')) value = '@integer(0, 100000)'
+  if (p.type === 'boolean') { value = (p.default === true || p.default === false) ? p.default.toString() : 'false'}
+  if (p.enum && (p.enum || []).length > 0) value = p.enum[0]
+  if (p.type === 'string' && p.format === 'date-time') value = '@datetime'
+  if (p.type === 'string' && p.format === 'date') value = '@date'
+
+  if (p.type === 'array' && p.default) {
+    value = typeof(p.default) === 'object' ? JSON.stringify(p.default) : p.default.toString()
+  }
+  if (/^function/.test(value)) type = 'Function' // @mock=function(){} => Function
+  if (/^\$order/.test(value)) { // $order => Array|+1
+    type = 'Array'
+    rule = '+1'
+    let orderArgs = /\$order\((.+)\)/.exec(value)
+    if (orderArgs) value = `[${orderArgs[1]}]`
+  }
+
+  return {
+    type, rule, description: description.length > 0 ? description.substring(1) : '', value
+  }
+}
+
+const propertiesUpdateService = async (properties, itfId) => {
+  properties = Array.isArray(properties) ? properties : [properties]
+  let itf = await Interface.findByPk(itfId)
+
+  let existingProperties = properties.filter((item: any) => !item.memory)
+  let result = await Property.destroy({
+    where: {
+      id: { [Op.notIn]: existingProperties.map((item: any) => item.id) },
+      interfaceId: itfId
+    }
+  })
+
+  // 更新已存在的属性
+  for (let item of existingProperties) {
+    let affected = await Property.update(item, {
+      where: { id: item.id }
+    })
+    result += affected[0]
+  }
+  // 插入新增加的属性
+  let newProperties = properties.filter((item: any) => item.memory)
+  let memoryIdsMap: any = {}
+  for (let item of newProperties) {
+    let created = await Property.create(Object.assign({}, item, {
+      id: undefined,
+      parentId: -1,
+      priority: item.priority || Date.now()
+    }))
+    memoryIdsMap[item.id] = created.id
+    item.id = created.id
+    result += 1
+  }
+  // 同步 parentId
+  for (let item of newProperties) {
+    let parentId = memoryIdsMap[item.parentId] || item.parentId
+    await Property.update({ parentId }, {
+      where: { id: item.id }
+    })
+  }
+  itf = await Interface.findByPk(itfId, {
+    include: (QueryInclude.RepositoryHierarchy as any).include[0].include,
+  })
+  return {
+    data: {
+      result,
+      properties: itf.properties,
+    }
+  }
+}
+
+const sendMailTemplate = (changeTip) => {
+  let html = MailService.mailNoticeTemp
+          .replace('{=TITLE=}', '您相关的接口存在如下变更:(请注意代码是否要调整)')
+          .replace('{=CONTENT=}', (changeTip.split('<br/>') || []).map(one => {
+            return one ? `<li style="margin-bottom: 20px;">${one}</li>` : ''
+          }).join(''))
+  return html
+}
 export default class MigrateService {
   public static async importRepoFromRAP1ProjectData(orgId: number, curUserId: number, projectData: any): Promise<boolean> {
     if (!projectData || !projectData.id || !projectData.name) return false
@@ -160,7 +396,6 @@ export default class MigrateService {
             let description = []
             if (p.name) description.push(p.name)
             if (p.remark && remarkWithoutMock) description.push(remarkWithoutMock)
-
             const pCreated = await Property.create({
               scope,
               name,
@@ -226,12 +461,13 @@ export default class MigrateService {
   }
 
  /** 请求参对象->数组->标准树形对象 @param swagger @param parameters */
-  public static async swaggerToModelRequest(swagger: SwaggerData, parameters: Array<any>, method: string): Promise<any> {
-    const { definitions } = swagger
+  public static async swaggerToModelRequest(swagger: SwaggerData, parameters: Array<any>, method: string, apiInfo: any): Promise<any> {
+    let { definitions } = swagger
     const result = []
+    definitions = JSON.parse(JSON.stringify(definitions)) // 防止接口之间数据处理相互影响
 
     if (method === 'get' || method === 'GET') {
-      parse(parameters, 'root', result, definitions)
+      parse(parameters, 'root', 'root', 0, result, definitions, 'request', apiInfo)
     } else if (method === 'post' || method === 'POST') {
       let list = [] // 外层处理参数数据结果
       const bodyObj = parameters.find(item => item.in === 'body') // body unique
@@ -263,7 +499,7 @@ export default class MigrateService {
           }
         }
       }
-      parse(list, 'root', result, definitions)
+      parse(list, 'root', 'root', 0, result, definitions, 'request', apiInfo)
     }
 
     const tree = arrayToTree(JSON.parse(JSON.stringify(result)))
@@ -277,8 +513,10 @@ export default class MigrateService {
    * @param swagger
    * @param response
    */
-  public static async swaggerToModelRespnse (swagger: SwaggerData, response: object): Promise<any> {
-    const { definitions } = swagger
+  public static async swaggerToModelRespnse (swagger: SwaggerData, response: object, apiInfo: any): Promise<any> {
+    let { definitions = {} } = swagger
+    definitions = JSON.parse(JSON.stringify(definitions)) // 防止接口之间数据处理相互影响
+
     const successObj = response['200']
     if (!successObj) return []
 
@@ -295,85 +533,62 @@ export default class MigrateService {
       const properties = ref.properties
 
       for (const key in properties) {
+        // 公共返回参数描述信息设置
+        let  description = ''
+        if (!properties[key].description && key === 'errorCode') {
+          description = '错误码'
+        }
+        if (!properties[key].description && key === 'errorMessage') {
+          description = '错误描述'
+        }
+        if (!properties[key].description && key === 'success') {
+          description = '请求业务结果'
+        }
+
         parameters.push({
           name: key,
           ...properties[key],
           in: 'body',
-          required: (ref.required || []).indexOf(key) >= 0
+          required: key === 'success' ? true : (ref.required || []).indexOf(key) >= 0,
+          default: key === 'success' ? true : (properties[key].default || false),
+          description: properties[key].description || description,
         })
       }
     }
 
     const result = []
-    parse(parameters, 'root', result, definitions)
-
+    parse(parameters, 'root', 'root', 0, result, definitions, 'response', apiInfo)
     const tree = arrayToTree(JSON.parse(JSON.stringify(result)))
     return tree
   }
 
-  public static async importRepoFromSwaggerProjectData(repositoryId: number, curUserId: number, swagger: SwaggerData): Promise<boolean> {
+public static async importRepoFromSwaggerProjectData(repositoryId: number, curUserId: number, swagger: SwaggerData): Promise<boolean> {
+    checkSwaggerResult = []
     if (!swagger.paths || !swagger.swagger || !swagger.host) return false
 
     let mCounter = 1 // 模块优先级顺序
     let iCounter = 1 // 接口优先级顺序
     let pCounter = 1 // 参数优先级顺序
 
+    /**
+     * 接口创建并批量创建属性,规则,默认值,说明等处理
+     * @param p
+     * @param scope
+     * @param interfaceId
+     * @param moduleId
+     * @param parentId
+     */
     async function processParam(p: SwaggerParameter, scope: SCOPES, interfaceId: number, moduleId: number, parentId?: number, ) {
-      const name = p.name
-      let description = ''
-
-      // 规则转化处理
-      let rule = ''
-      if (p.type === 'string' && p.minLength && p.maxLength ) {
-        rule = `${p.minLength}-${p.maxLength}`
-      } else if (p.type === 'string' && p.minLength && !p.maxLength) {
-        rule = `${p.minLength}`
-      } else if (p.type === 'string' && !p.minLength && p.maxLength) {
-        rule = `${p.required ? '1' : '0'}-${p.maxLength}`
-      }
-      if (p.type === 'string' && p.enum && p.enum.length > 0) {
-        description = `${description} 枚举值: ${p.enum.join()}`
-      }
-
-      if (p.type === 'integer' && p.minimum && p.maxinum) {
-        rule = `${p.minimum}-${p.maxinum}`
-      }
-      if (p.type === 'integer' && p.minimum && !p.maxinum) {
-        rule = `${p.minimum}`
-      }
-      if (p.type === 'integer' && !p.minimum && p.maxinum) {
-        rule = `${p.required ? '1' : '0'}-${p.maxinum}`
-      }
-
-      // 类型转化处理
-      let type = (p.type || 'string')
-      if (type === 'integer') type = 'number'
-      type = type[0].toUpperCase() + type.slice(1) // foo => Foo 首字母转化为大写
-
-      // 默认值转化处理
-      let value = p.default || ''
-      if (p.type === 'boolean') {
-        value = (p.default === true || p.default === false) ? p.default.toString() : ''
-      }
-      if (p.type === 'array' && p.default) {
-        value = typeof(p.default) === 'object' ? JSON.stringify(p.default) : p.default.toString()
-      }
-      if (/^function/.test(value)) type = 'Function' // @mock=function(){} => Function
-      if (/^\$order/.test(value)) { // $order => Array|+1
-        type = 'Array'
-        rule = '+1'
-        let orderArgs = /\$order\((.+)\)/.exec(value)
-        if (orderArgs) value = `[${orderArgs[1]}]`
-      }
-
+      const { rule, value, type, description } = transformRapParams(p)
+      const joinDescription = `${p.description || ''}${((p.description || '') && (description || '')) ? '|' : ''}${description || ''}`
       const pCreated = await Property.create({
         scope,
-        name,
+        name: p.name,
         rule,
         value,
         type,
         required: p.required,
-        description: `${p.description || ''} ${description ? `|${description}` : ''}`,
+        description: joinDescription,
         priority: pCounter++,
         interfaceId: interfaceId,
         creatorId: curUserId,
@@ -392,7 +607,7 @@ export default class MigrateService {
     let { tags = [], paths = {}, host = '' } = swagger
     let pathTag: SwaggerTag[] = []
 
-    // 处理root tag中没有的情况
+    // 获取所有的TAG: 处理ROOT TAG中没有的情况
     for (const action in paths) {
       const apiObj = paths[action][Object.keys(paths[action])[0]]
       const index = pathTag.findIndex((it: SwaggerTag) => {
@@ -402,7 +617,11 @@ export default class MigrateService {
     }
     tags = pathTag
 
+    if (checkSwaggerResult.length > 0) return false
+
     for (const tag of tags) {
+      if (checkSwaggerResult.length > 0) break
+
       let repository: Partial<Repository>
       let [repositoryModules] = await Promise.all([
         Repository.findByPk(repositoryId, {
@@ -436,12 +655,12 @@ export default class MigrateService {
       } else {
         mod = repository.modules[findIndex]
       }
-
       for (const action in paths) {
         const apiObj = paths[action][Object.keys(paths[action])[0]]
         const method = Object.keys(paths[action])[0]
         const actionTags0 = apiObj.tags[0]
         const url = action
+        const summary = apiObj.summary
 
         if (actionTags0 === tag.name) {
           // 判断接口是否存在在该模块中,如果不存在则创建接口,存在则更新接口信息
@@ -464,8 +683,10 @@ export default class MigrateService {
             ...repositoryModules.toJSON()
           }
 
-          const request = await this.swaggerToModelRequest(swagger, apiObj.parameters || {}, method)
-          const response = await this.swaggerToModelRespnse(swagger, apiObj.responses || {})
+          const request = await this.swaggerToModelRequest(swagger, apiObj.parameters || {}, method, { url,  summary })
+          const response = await this.swaggerToModelRespnse(swagger, apiObj.responses || {}, { url,  summary })
+          // 处理完每个接口请求参数后,如果-遇到第一个存在接口不符合规范就全部返回
+          if (checkSwaggerResult.length > 0) break
 
           // 判断对应模块是否存在该接口
           const index = repository.modules.findIndex(item => {
@@ -504,18 +725,177 @@ export default class MigrateService {
               method: method.toUpperCase()
             },  { where: { id: findApi.id } })
 
-            await Property.destroy({ where: { interfaceId: findApi.id } })
+            // 获取已经存在的接口的属性信息,并处理深度和parentName
+            let A_ExistsPropertiesOld = JSON.parse(JSON.stringify(findApi.properties))
+            A_ExistsPropertiesOld = JSON.parse(JSON.stringify(treeToArray((arrayToTreeProperties(A_ExistsPropertiesOld)))))
+            let A_ExistsProperties = A_ExistsPropertiesOld.map(property => {
+              return {
+                ...property,
+                parentName: (A_ExistsPropertiesOld.find(item => item.id === property.parentId) || {}).name || 'root',
+              }
+            })
 
-            for (const p of (request.children || [])) {
-              await processParam(p, SCOPES.REQUEST, findApi.id, mod.id)
+            const B_SwaggerProperties_Request = treeToArray(request)
+            const B_SwaggerProperties_Response = treeToArray(response)
+            let PropertyId = 0, PriorityId = 0
+
+            let maxDepth_Request = 0, maxDepth_Response = 0, maxDepth_A_ExistsProperties = 0
+            // 计算B的最大深度-- request
+            B_SwaggerProperties_Request.map(item => {
+              if (item.depth  > maxDepth_Request) {
+                maxDepth_Request = item.depth
+              }
+              return item
+            })
+
+            // 计算B的最大深度-- response
+            B_SwaggerProperties_Response.map(item => {
+              if (item.depth  > maxDepth_Response) {
+                maxDepth_Response = item.depth
+              }
+              return item
+            })
+
+            // 计算A的最大深度
+            A_ExistsProperties.map(item => {
+              if (item.depth  > maxDepth_A_ExistsProperties) {
+                maxDepth_A_ExistsProperties = item.depth
+              }
+              return item
+            })
+
+            const properties = []
+
+            /**
+             * 批量更新接口属性名称,类型,规则,默认值等处理
+             * @param BFilterByDepth
+             * @param depth
+             * @param scope
+             */
+            const updateProperties = (BFilterByDepth, depth, scope) => {
+              for (const key in BFilterByDepth) {
+                const bValue = BFilterByDepth[key]
+                const index = A_ExistsProperties.findIndex(item => (item.name === bValue.name && item.depth === bValue.depth && item.parentName === bValue.parentName && item.scope === scope))
+                const { type, description, rule, value} = transformRapParams(bValue)
+                const joinDescription = `${bValue.description || ''}${((bValue.description || '') && (description || '')) ? '|' : ''}${description || ''}`
+
+                if (index >= 0) {
+                  // 属性存在 ---修改:类型;是否必填;属性说明;不修改规则和默认值(前端可能正在mock)
+                  // 如何判断有更新
+                  if (type !== A_ExistsProperties[index].type) {
+                    // 类型变更了
+                    changeTip = `${changeTip}<br/>接口名称:${apiObj.summary} [更新属性:${A_ExistsProperties[index].name}类型由“${A_ExistsProperties[index].type}”变更为“${type}]”`
+                  }
+                  if (!!bValue.required !== A_ExistsProperties[index].required) {
+                    // 是否必填变更
+                    changeTip = `${changeTip}<br/>接口名称:${apiObj.summary} [更新属性:${A_ExistsProperties[index].name}是否必填由“${A_ExistsProperties[index].required}”变更为“${bValue.required}]”`
+                  }
+
+                  if (joinDescription !== A_ExistsProperties[index].description && bValue.name !== 'success' && bValue.name !== 'errorCode' && bValue.name !== 'errorMessage') {
+                    // 描述信息变更
+                    changeTip = `${changeTip}<br/>接口名称:${apiObj.summary} [更新属性:${A_ExistsProperties[index].name}属性简介由“${A_ExistsProperties[index].description || '无'}”变更为“${joinDescription}”]`
+                  }
+
+                  properties.push({
+                    ...A_ExistsProperties[index],
+                    rule: (!A_ExistsProperties[index].rule && !A_ExistsProperties[index].value) ? rule : A_ExistsProperties[index].rule,
+                    value: (!A_ExistsProperties[index].rule && !A_ExistsProperties[index].value) ? value : A_ExistsProperties[index].value,
+                    type,
+                    required: !!bValue.required, // 是否必填更改
+                    description: `${joinDescription}`,
+                  })
+                } else {
+                  changeTip = `${changeTip}<br/>接口名称:${apiObj.summary} [属性添加:${bValue.name};类型:${type} ;简介: ${bValue.description || ''}${(bValue.description || '') ? '|' : ''}${description || ''} ]`
+                  // 属性不存在
+                  if (depth === 0) {
+                    properties.push({
+                      id: `memory-${++PropertyId}`,
+                      scope,
+                      type,
+                      pos: REQUEST_TYPE_POS[bValue.in],
+                      name: bValue.name,
+                      rule,
+                      value,
+                      description: `${joinDescription}`,
+                      parentId: -1 ,
+                      priority: `${++PriorityId}`,
+                      interfaceId: findApi.id,
+                      moduleId: mod.id,
+                      repositoryId,
+                      memory: true,
+                      depth: bValue.depth,
+                      changeType: 'add',
+                    })
+
+                  } else {
+                    // 找到父级属性信息
+                    const parent = properties.find(it => (it.depth === bValue.depth - 1 && it.name === bValue.parentName && it.scope === scope))
+                    properties.push({
+                      id: `memory-${++PropertyId}`,
+                      scope,
+                      type,
+                      pos: REQUEST_TYPE_POS[bValue.in],
+                      name: bValue.name,
+                      rule,
+                      value,
+                      description: `${joinDescription}`,
+                      parentId: parent.id,
+                      priority: `${++PriorityId}`,
+                      interfaceId: findApi.id,
+                      moduleId: mod.id,
+                      repositoryId,
+                      memory: true,
+                      depth: bValue.depth,
+                      changeType: 'add',
+                    })
+                  }
+                }
+              }
             }
-            for (const p of (response.children || [])) {
-              await processParam(p, SCOPES.RESPONSE, findApi.id, mod.id)
+
+            /** 删除属性计算 */
+            const deleteProperties =  (AFilterByDepth, scope) => {
+              for (const key in AFilterByDepth) {
+                const aValue = AFilterByDepth[key]
+                let index = -1
+                if (scope === 'request') {
+                  index = B_SwaggerProperties_Request.findIndex(item => (item.name === aValue.name && item.depth === aValue.depth && item.parentName === aValue.parentName))
+                } else if (scope === 'response') {
+                  index = B_SwaggerProperties_Response.findIndex(item => (item.name === aValue.name && item.depth === aValue.depth && item.parentName === aValue.parentName))
+                }
+
+                const { type, description } = transformRapParams(aValue)
+                if (index < 0) {
+                  // A 存在,B不存在
+                  changeTip = `${changeTip} <br/> 接口名称:${apiObj.summary} [属性删除:${aValue.name};类型:${type} ;简介: ${aValue.description || ''} ${description ? `${aValue.description ? '|' : '' }${description}` : ''} ]`
+                }
+              }
+            }
+            for (let depth = 0; depth <= maxDepth_A_ExistsProperties; depth++) {
+              const AFilterByDepth = A_ExistsProperties.filter(item => item.depth === depth && item.scope === 'request')
+              deleteProperties(AFilterByDepth, 'request')
             }
+            for (let depth = 0; depth <= maxDepth_A_ExistsProperties; depth++) {
+              const AFilterByDepth = A_ExistsProperties.filter(item => item.depth === depth && item.scope === 'response')
+              deleteProperties(AFilterByDepth, 'response')
+            }
+
+            for (let depth = 0; depth <= maxDepth_Request; depth++) {
+              const BFilterByDepth = B_SwaggerProperties_Request.filter(item => item.depth === depth)
+              updateProperties(BFilterByDepth, depth, 'request')
+            }
+
+            for (let depth = 0; depth <= maxDepth_Response; depth++) {
+              const BFilterByDepth = B_SwaggerProperties_Response.filter(item => item.depth === depth)
+              updateProperties(BFilterByDepth, depth, 'response')
+            }
+            await propertiesUpdateService(properties, findApi.id)
           }
         }
       }
     }
+
+    if (checkSwaggerResult.length > 0) return false
     return true
   }
 
@@ -527,10 +907,21 @@ export default class MigrateService {
 
         if (swagger.swagger === SWAGGER_VERSION[version]) {
           let result
+          let mailRepositoryName = '', mailRepositoryId = 0, mailRepositoryMembers = []
 
           if (mode === 'manual') {
-            const repos = await Repository.findByPk(repositoryId)
+            const repos = await Repository.findByPk(repositoryId, {
+              attributes: { exclude: [] },
+              include: [
+                QueryInclude.Creator,
+                QueryInclude.Owner,
+                QueryInclude.Members,
+                QueryInclude.Organization,
+                QueryInclude.Collaborators,
+              ],
+            })
             const { creatorId, members, collaborators, ownerId, name } = repos
+
             const body = {
               creatorId: creatorId,
               organizationId: orgId,
@@ -543,7 +934,12 @@ export default class MigrateService {
               description: `[host=${host}]${info.title || ''}`,
             }
             result = await Repository.update(body, { where: { id: repositoryId } })
+
+            mailRepositoryName = name
+            mailRepositoryMembers = members
+            mailRepositoryId = repositoryId
           } else if (mode === 'auto') {
+            // 团队下直接导入功能作废,此处不用执行
             result = await Repository.create({
               id: 0,
               name: info.title || 'swagger导入仓库',
@@ -562,13 +958,46 @@ export default class MigrateService {
 
           if (result[0] || result.id) {
             const bol = await this.importRepoFromSwaggerProjectData(mode === 'manual' ? repositoryId : result.id, curUserId, swagger)
-            await RedisService.delCache(CACHE_KEY.REPOSITORY_GET, result.id)
-            return { result: bol, code: 'success' }
+            if (!bol) {
+              return {result: checkSwaggerResult, code: 'checkSwagger'}
+            } else {
+              await RedisService.delCache(CACHE_KEY.REPOSITORY_GET, result.id)
+              if (changeTip.length > 0) {
+                const to = mailRepositoryMembers.map(item => {
+                  return `"${item.fullname}" ${item.email},`
+                })
+
+                MailService.send(
+                  to,
+                  `仓库:${mailRepositoryName}(${mailRepositoryId})接口更新同步`,
+                  sendMailTemplate(changeTip)
+                  ).then(() => {})
+                  .catch(() => {})
+
+                // 钉钉消息发送
+                // const dingMsg = {
+                //   msgtype: 'action_card',
+                //   action_card: {
+                //     title: `仓库:${mailRepositoryName}(${mailRepositoryId})接口更新同步`,
+                //     markdown: "支持markdown格式的正文内容",
+                //     single_title: "查看仓库更新", // swagger 批量导入跳转至仓库, 如果后期只要接口更新就通知相关人的话,需要设置具体接口链接
+                //     single_url: `https://rap2.alibaba-inc.com/repository/editor?id=${repositoryId}`
+                //   }
+                // }
+
+                // DingPushService.dingPush(mailRepositoryMembers.map(item => item.empId).join(), dingMsg)
+                // .catch((err) => { console.log(err) })
+              }
+              changeTip = ''
+              return { result: bol, code: 'success' }
+            }
+
           }
         } else {
           return { result: true, code: 'version'}
         }
     } catch (err) {
+      console.log(err)
       return { result: false, code: 'error'}
     }
   }
@@ -746,6 +1175,8 @@ interface OldParameter {
   remark: string
   dataType: string
   parameterList: OldParameter[]
+  parentName: string,
+  depth: number
 }
 
 interface SwaggerParameter {
diff --git a/src/service/organization.ts b/src/service/organization.ts
index 33c3803..6fda7ac 100644
--- a/src/service/organization.ts
+++ b/src/service/organization.ts
@@ -7,7 +7,7 @@ export default class OrganizationService {
       SELECT COUNT(id) AS num FROM (
         SELECT o.id, o.name
         FROM Organizations o
-        WHERE visibility = ${1} OR creatorId = ${userId} OR ownerId = ${userId}
+        WHERE ownerId = ${userId}
         UNION
         SELECT o.id, o.name
         FROM Organizations o
@@ -31,7 +31,7 @@ export default class OrganizationService {
       SELECT id FROM (
         SELECT o.id, o.name
         FROM Organizations o
-        WHERE visibility = ${1} OR creatorId = ${curUserId} OR ownerId = ${curUserId}
+        WHERE visibility = ${1} OR ownerId = ${curUserId}
         UNION
         SELECT o.id, o.name
         FROM Organizations o
@@ -54,7 +54,7 @@ export default class OrganizationService {
       SELECT count(*) AS num FROM (
         SELECT o.id, o.name
         FROM Organizations o
-        WHERE visibility = ${1} OR creatorId = ${curUserId} OR ownerId = ${curUserId}
+        WHERE visibility = ${1} OR ownerId = ${curUserId}
         UNION
         SELECT o.id, o.name
         FROM Organizations o
diff --git a/src/service/repository.ts b/src/service/repository.ts
index a1a5ed8..a494991 100644
--- a/src/service/repository.ts
+++ b/src/service/repository.ts
@@ -13,7 +13,7 @@ export default class RepositoryService {
     const repo = await Repository.findByPk(repositoryId)
     if (token && repo.token === token) return true
     if (!repo) return false
-    if (repo.creatorId === userId || repo.ownerId === userId) return true
+    if (repo.ownerId === userId) return true
     const memberExistsNum = await RepositoriesMembers.count({
       where: {
         userId,
@@ -31,16 +31,16 @@ export default class RepositoryService {
     destModuleId: number,
   ) {
     return (
-      AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE, userId, itfId) &&
-      AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY, userId, destRepoId) &&
-      AccessUtils.canUserAccess(ACCESS_TYPE.MODULE, userId, destModuleId)
+      AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_GET, userId, itfId) &&
+      AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, userId, destRepoId) &&
+      AccessUtils.canUserAccess(ACCESS_TYPE.MODULE_SET, userId, destModuleId)
     )
   }
 
   public static async canUserMoveModule(userId: number, modId: number, destRepoId: number) {
     return (
-      AccessUtils.canUserAccess(ACCESS_TYPE.MODULE, userId, modId) &&
-      AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY, userId, destRepoId)
+      AccessUtils.canUserAccess(ACCESS_TYPE.MODULE_GET, userId, modId) &&
+      AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, userId, destRepoId)
     )
   }