Bladeren bron

鉴权相关

yingp 7 jaren geleden
bovenliggende
commit
bf6381b30a
75 gewijzigde bestanden met toevoegingen van 1581 en 339 verwijderingen
  1. 8 0
      README.md
  2. 2 0
      applications/commons/commons-server/src/main/java/com/usoftchina/saas/commons/CommonsApplication.java
  3. 2 1
      applications/document/document-server/src/main/java/com/usoftchina/saas/document/DocumentApplication.java
  4. 2 1
      applications/money/money-server/src/main/java/com/usoftchina/saas/money/MoneyApplicatiion.java
  5. 2 1
      applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/PurchaseApplication.java
  6. 2 0
      applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/SaleApplication.java
  7. 2 1
      applications/storage/storage-server/src/main/java/com/usoftchina/saas/storage/StorageApplication.java
  8. 2 2
      base-servers/account/README.md
  9. 4 0
      base-servers/account/account-api/pom.xml
  10. 10 0
      base-servers/account/account-api/src/main/java/com/usoftchina/saas/account/api/AccountApi.java
  11. 26 0
      base-servers/account/account-api/src/main/java/com/usoftchina/saas/account/api/ResourceApi.java
  12. 65 0
      base-servers/account/account-api/src/main/java/com/usoftchina/saas/account/cache/AccountCache.java
  13. 68 0
      base-servers/account/account-api/src/main/java/com/usoftchina/saas/account/cache/ResourceCache.java
  14. 54 6
      base-servers/account/account-dto/src/main/java/com/usoftchina/saas/account/dto/AccountDTO.java
  15. 27 1
      base-servers/account/account-dto/src/main/java/com/usoftchina/saas/account/dto/RoleBaseDTO.java
  16. 11 1
      base-servers/account/account-dto/src/main/java/com/usoftchina/saas/account/dto/UrlResourceDTO.java
  17. 1 1
      base-servers/account/account-server/pom.xml
  18. 74 7
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/controller/AccountController.java
  19. 1 1
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/controller/ResourceController.java
  20. 22 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/controller/RoleController.java
  21. 10 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/AccountMapper.java
  22. 9 1
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/AccountRoleMapper.java
  23. 16 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/ResourceMapper.java
  24. 11 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/RoleMapper.java
  25. 22 1
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/RoleResourceMapper.java
  26. 12 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/po/Account.java
  27. 14 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/po/AccountRole.java
  28. 14 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/po/Resource.java
  29. 14 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/po/ResourceGroup.java
  30. 26 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/po/RoleResource.java
  31. 33 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/AccountService.java
  32. 16 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/ResourceService.java
  33. 12 1
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/RoleService.java
  34. 37 1
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/impl/AccountServiceImpl.java
  35. 14 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/impl/ResourceServiceImpl.java
  36. 24 1
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/impl/RoleServiceImpl.java
  37. 3 0
      base-servers/account/account-server/src/main/resources/application.yml
  38. 7 1
      base-servers/account/account-server/src/main/resources/mapper/AccountMapper.xml
  39. 4 1
      base-servers/account/account-server/src/main/resources/mapper/AccountRoleMapper.xml
  40. 9 1
      base-servers/account/account-server/src/main/resources/mapper/ResourceMapper.xml
  41. 6 1
      base-servers/account/account-server/src/main/resources/mapper/RoleMapper.xml
  42. 15 1
      base-servers/account/account-server/src/main/resources/mapper/RoleResourceMapper.xml
  43. 65 6
      base-servers/account/account-server/src/test/java/com/usoftchina/saas/account/controller/AccountControllerTest.java
  44. 0 4
      base-servers/auth/auth-api/pom.xml
  45. 8 0
      base-servers/auth/auth-api/src/main/java/com/usoftchina/saas/auth/api/AuthApi.java
  46. 0 47
      base-servers/auth/auth-api/src/main/java/com/usoftchina/saas/auth/cache/TokenCache.java
  47. 19 0
      base-servers/auth/auth-server/src/main/java/com/usoftchina/saas/auth/controller/AuthController.java
  48. 4 0
      base-servers/gateway-server/pom.xml
  49. 4 1
      base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/GatewayApplication.java
  50. 53 1
      base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/config/AuthFilter.java
  51. 18 0
      base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/context/SpringContextListener.java
  52. 20 0
      base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/error/PermissionException.java
  53. 177 0
      base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/util/AntPathRequestMatcher.java
  54. 3 0
      base-servers/gateway-server/src/main/resources/application.yml
  55. 2 0
      base-servers/ui-server/src/main/java/com/usoftchina/saas/ui/UiApplication.java
  56. 0 4
      base-servers/ui-server/src/main/java/com/usoftchina/saas/ui/controller/co/CoViewController.java
  57. 4 2
      framework/core/pom.xml
  58. 8 11
      framework/core/src/main/java/com/usoftchina/saas/base/Result.java
  59. 1 0
      framework/core/src/main/java/com/usoftchina/saas/exception/ExceptionCode.java
  60. 112 5
      framework/core/src/main/java/com/usoftchina/saas/utils/CollectionUtils.java
  61. 44 1
      framework/core/src/main/java/com/usoftchina/saas/utils/JsonUtils.java
  62. 0 1
      frontend/saas-web/app/Application.scss
  63. 0 26
      frontend/saas-web/app/model/Account.js
  64. 73 21
      frontend/saas-web/app/model/Session.js
  65. 54 26
      frontend/saas-web/app/view/auth/LoginController.js
  66. 0 15
      frontend/saas-web/app/view/main/List.js
  67. 27 11
      frontend/saas-web/app/view/main/Main.js
  68. 29 0
      frontend/saas-web/app/view/main/MainController.js
  69. 0 1
      frontend/saas-web/app/view/main/MainModel.js
  70. 39 53
      frontend/saas-web/app/view/viewport/ViewportController.js
  71. 14 1
      frontend/saas-web/app/view/viewport/ViewportModel.js
  72. 10 1
      frontend/saas-web/overrides/data/Connection.js
  73. 1 0
      frontend/saas-web/packages/local/theme-default/sass/var/all.scss
  74. 6 0
      pom.xml
  75. 73 68
      script/mysql/init/account.sql

+ 8 - 0
README.md

@@ -13,12 +13,20 @@
 │  |  |  |─document-dto-----------------------基础资料数据传输对象
 │  |  |  |─document-server--------------------基础资料服务
 │  |  ├─money---------------------------------资金
+│  |  |  |─money-api--------------------------资金服务api
+│  |  |  |─money-dto--------------------------资金服务数据传输对象
+│  |  |  |─money-server-----------------------资金服务
 │  |  ├─purchase------------------------------采购
 │  |  |  |─purchase-api-----------------------采购服务api
 │  |  |  |─purchase-dto-----------------------采购服务数据传输对象
 │  |  |  |─purchase-server--------------------采购服务
 │  |  ├─sale----------------------------------销售
+│  |  |  |─sale-dto---------------------------销售服务数据传输对象
+│  |  |  |─sale-server------------------------销售服务
 │  |  ├─storage-------------------------------库存
+│  |  |  |─storage-api------------------------库存服务api
+│  |  |  |─storage-dto------------------------库存服务数据传输对象
+│  |  |  |─storage-server---------------------库存服务
 │  │ 
 │  ├─base-servers-----------------------------基础服务
 │  |  ├─admin-server--------------------------spring-boot-admin监控中心

+ 2 - 0
applications/commons/commons-server/src/main/java/com/usoftchina/saas/commons/CommonsApplication.java

@@ -1,5 +1,6 @@
 package com.usoftchina.saas.commons;
 
+import com.usoftchina.saas.auth.client.EnableAuthClient;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -16,6 +17,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 @EnableTransactionManagement
 @EnableFeignClients("com.usoftchina.saas")
 @MapperScan("com.usoftchina.saas.commons.mapper")
+@EnableAuthClient
 public class CommonsApplication {
     public static void main(String[] args) {
         SpringApplication.run(CommonsApplication.class, args);

+ 2 - 1
applications/document/document-server/src/main/java/com/usoftchina/saas/document/DocumentApplication.java

@@ -1,5 +1,6 @@
 package com.usoftchina.saas.document;
 
+import com.usoftchina.saas.auth.client.EnableAuthClient;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -9,7 +10,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 @SpringBootApplication
 @EnableEurekaClient
-//@EnableAuthClient
+@EnableAuthClient
 @EnableTransactionManagement
 @EnableFeignClients("com.usoftchina.saas")
 @MapperScan("com.usoftchina.saas.document.mapper")

+ 2 - 1
applications/money/money-server/src/main/java/com/usoftchina/saas/money/MoneyApplicatiion.java

@@ -1,5 +1,6 @@
 package com.usoftchina.saas.money;
 
+import com.usoftchina.saas.auth.client.EnableAuthClient;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -16,7 +17,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 @EnableEurekaClient
 @EnableTransactionManagement
 @EnableFeignClients("com.usoftchina.saas")
-//@EnableAuthClient
+@EnableAuthClient
 @MapperScan("com.usoftchina.saas.money.mapper")
 public class MoneyApplicatiion {
     public static void main(String[] args) {

+ 2 - 1
applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/PurchaseApplication.java

@@ -1,6 +1,7 @@
 package com.usoftchina.saas.purchase;
 
 
+import com.usoftchina.saas.auth.client.EnableAuthClient;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -18,7 +19,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
 @EnableEurekaClient
 @EnableTransactionManagement
 @EnableFeignClients("com.usoftchina.saas")
-//@EnableAuthClient
+@EnableAuthClient
 @MapperScan("com.usoftchina.saas.purchase.mapper")
 public class PurchaseApplication   extends WebMvcConfigurerAdapter{
     public static void main(String[] args) {

+ 2 - 0
applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/SaleApplication.java

@@ -1,5 +1,6 @@
 package com.usoftchina.saas.sale;
 
+import com.usoftchina.saas.auth.client.EnableAuthClient;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -15,6 +16,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
 @MapperScan("com.usoftchina.saas.sale.mapper")
 @EnableEurekaClient
 @EnableFeignClients("com.usoftchina.saas")
+@EnableAuthClient
 public class SaleApplication{
     public static void main(String[] args) {
         SpringApplication.run(SaleApplication.class, args);

+ 2 - 1
applications/storage/storage-server/src/main/java/com/usoftchina/saas/storage/StorageApplication.java

@@ -1,5 +1,6 @@
 package com.usoftchina.saas.storage;
 
+import com.usoftchina.saas.auth.client.EnableAuthClient;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -9,7 +10,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 @SpringBootApplication
 @EnableEurekaClient
-//@EnableAuthClient
+@EnableAuthClient
 @EnableTransactionManagement
 @EnableFeignClients("com.usoftchina.saas")
 @MapperScan("com.usoftchina.saas.storage.mapper")

+ 2 - 2
base-servers/account/README.md

@@ -21,8 +21,8 @@
 | :----:   | ---------  |
 | id | 主键,自动生成  |
 | group_id | 资源分组ID,参考ac_resource_group、ac_resource_module表 |
-| name | 名称,统一叫法:查询、新增、修改、审核、反审核、删除、打印、(其它,字数≤5) |
+| name | 名称,统一叫法:查询、新增、修改、审核、反审核、删除、打印、导入、导出、(其它,字数≤5) |
 | type | 类型,MENU(Navigation的资源)、BUTTON (页面按钮资源)、API... |
 | url | 请求url |
 | method | http请求头方法:GET, POST, PUT, DELETE |
-| classify | 归类:QUERY, ADD, UPDATE, AUDIT, UNAUDIT, DELETE, PRINT, OTHER  |
+| classify | 归类:QUERY, ADD, UPDATE, AUDIT, UNAUDIT, DELETE, PRINT, IMPORT, EXPORT, OTHER  |

+ 4 - 0
base-servers/account/account-api/pom.xml

@@ -25,6 +25,10 @@
             <groupId>com.usoftchina.saas</groupId>
             <artifactId>core</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 10 - 0
base-servers/account/account-api/src/main/java/com/usoftchina/saas/account/api/AccountApi.java

@@ -4,6 +4,7 @@ import com.usoftchina.saas.account.dto.AccountDTO;
 import com.usoftchina.saas.base.Result;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestParam;
 
 /**
@@ -30,4 +31,13 @@ public interface AccountApi {
      */
     @GetMapping(value = "/account/read")
     Result<AccountDTO> getAccount(@RequestParam(value = "username") String username);
+
+    /**
+     * 按用户名查找账户
+     *
+     * @param id
+     * @return
+     */
+    @GetMapping(value = "/account/{id}")
+    Result<AccountDTO> getAccountById(@PathVariable("id") Long id);
 }

+ 26 - 0
base-servers/account/account-api/src/main/java/com/usoftchina/saas/account/api/ResourceApi.java

@@ -0,0 +1,26 @@
+package com.usoftchina.saas.account.api;
+
+import com.usoftchina.saas.account.dto.UrlResourceDTO;
+import com.usoftchina.saas.base.Result;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2018/11/1
+ */
+@FeignClient(name = "account-server")
+public interface ResourceApi {
+
+    /**
+     * 查找应用的全部url资源
+     *
+     * @param appId
+     * @return
+     */
+    @GetMapping(value = "/resource/url/list")
+    Result<List<UrlResourceDTO>> getUrlResourcesByAppId(@RequestParam(value = "appId") String appId);
+}

+ 65 - 0
base-servers/account/account-api/src/main/java/com/usoftchina/saas/account/cache/AccountCache.java

@@ -0,0 +1,65 @@
+package com.usoftchina.saas.account.cache;
+
+import com.usoftchina.saas.account.api.AccountApi;
+import com.usoftchina.saas.account.dto.AccountDTO;
+import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.cache.RedisHashCache;
+import com.usoftchina.saas.context.SpringContextHolder;
+import com.usoftchina.saas.exception.BizException;
+import com.usoftchina.saas.utils.JsonUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/**
+ * @author yingp
+ * @date 2018/11/1
+ */
+public class AccountCache extends RedisHashCache<String, String, String> {
+    private final long id;
+
+    private static final RedisTemplate<String, String> REDIS_TEMPLATE = SpringContextHolder.getBean("redisTemplate", RedisTemplate.class);
+    private AccountApi accountApi;
+
+    private AccountCache(long id) {
+        super(() -> REDIS_TEMPLATE);
+        this.id = id;
+    }
+
+    public static AccountCache of(long id) {
+        return new AccountCache(id);
+    }
+
+    @Override
+    protected String field() {
+        return String.valueOf(id);
+    }
+
+    @Override
+    protected String key() {
+        return generateKey("account", "account");
+    }
+
+    @Override
+    protected Supplier<String> getSupplier() {
+        return () -> {
+            if (null == accountApi) {
+                accountApi = SpringContextHolder.getBean(AccountApi.class);
+            }
+            Result<AccountDTO> result = accountApi.getAccountById(id);
+            if (result.isSuccess()) {
+                return JsonUtils.toJsonString(result.getData());
+            }
+            throw new BizException(result.getCode(), result.getMessage());
+        };
+    }
+
+    public AccountDTO getAccount() {
+        Optional<String> value = get();
+        if (value.isPresent()) {
+            return JsonUtils.fromJsonString(value.get(), AccountDTO.class);
+        }
+        return null;
+    }
+}

+ 68 - 0
base-servers/account/account-api/src/main/java/com/usoftchina/saas/account/cache/ResourceCache.java

@@ -0,0 +1,68 @@
+package com.usoftchina.saas.account.cache;
+
+import com.usoftchina.saas.account.api.ResourceApi;
+import com.usoftchina.saas.account.dto.UrlResourceDTO;
+import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.cache.RedisHashCache;
+import com.usoftchina.saas.context.SpringContextHolder;
+import com.usoftchina.saas.exception.BizException;
+import com.usoftchina.saas.utils.JsonUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/**
+ * @author yingp
+ * @date 2018/11/1
+ */
+public class ResourceCache extends RedisHashCache<String, String, String> {
+
+    private final String appId;
+
+    private static final RedisTemplate<String, String> REDIS_TEMPLATE = SpringContextHolder.getBean("redisTemplate", RedisTemplate.class);
+    private ResourceApi resourceApi;
+
+    private ResourceCache(String appId) {
+        super(() -> REDIS_TEMPLATE);
+        this.appId = appId;
+    }
+
+    public static ResourceCache of(String appId) {
+        return new ResourceCache(appId);
+    }
+
+    @Override
+    protected String field() {
+        return appId;
+    }
+
+    @Override
+    protected String key() {
+        return generateKey("account", "resource");
+    }
+
+    @Override
+    protected Supplier<String> getSupplier() {
+        return () -> {
+			if (null == resourceApi) {
+				resourceApi = SpringContextHolder.getBean(ResourceApi.class);
+			}
+            Result<List<UrlResourceDTO>> result = resourceApi.getUrlResourcesByAppId(appId);
+            if (result.isSuccess()) {
+                return JsonUtils.toJsonString(result.getData());
+            }
+            throw new BizException(result.getCode(), result.getMessage());
+        };
+    }
+
+    public List<UrlResourceDTO> getUrlResources() {
+        Optional<String> value = get();
+        if (value.isPresent()) {
+            return JsonUtils.fromJsonArray(value.get(), UrlResourceDTO.class);
+        }
+        return null;
+    }
+
+}

+ 54 - 6
base-servers/account/account-dto/src/main/java/com/usoftchina/saas/account/dto/AccountDTO.java

@@ -1,10 +1,14 @@
 package com.usoftchina.saas.account.dto;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.usoftchina.saas.account.constant.AccountType;
+import com.usoftchina.saas.utils.CollectionUtils;
 import io.swagger.annotations.ApiModel;
 
 import java.io.Serializable;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * @author yingp
@@ -17,6 +21,10 @@ public class AccountDTO implements Serializable {
     private String realname;
     private String email;
     private String mobile;
+    /**
+     * 头像
+     */
+    private String avatarUrl;
     /**
      * 账号类型 0 - 管理员
      */
@@ -32,9 +40,13 @@ public class AccountDTO implements Serializable {
     /**
      * 所拥有的资源
      *
+     * <String> appId
+     * <Long> companyId
+     * <Long> resourceId
+     *
      * @return
      */
-    private Map<Long, List<UrlResourceDTO>> resourcesMap;
+    private Map<String, Map<Long, Set<Long>>> resourcesTable;
 
     public Long getId() {
         return id;
@@ -76,6 +88,14 @@ public class AccountDTO implements Serializable {
         this.mobile = mobile;
     }
 
+    public String getAvatarUrl() {
+        return avatarUrl;
+    }
+
+    public void setAvatarUrl(String avatarUrl) {
+        this.avatarUrl = avatarUrl;
+    }
+
     public Integer getType() {
         return type;
     }
@@ -100,12 +120,39 @@ public class AccountDTO implements Serializable {
         this.rolesMap = rolesMap;
     }
 
-    public Map<Long, List<UrlResourceDTO>> getResourcesMap() {
-        return resourcesMap;
+    public Map<String, Map<Long, Set<Long>>> getResourcesTable() {
+        return resourcesTable;
     }
 
-    public void setResourcesMap(Map<Long, List<UrlResourceDTO>> resourcesMap) {
-        this.resourcesMap = resourcesMap;
+    public void setResourcesTable(Map<String, Map<Long, Set<Long>>> resourcesTable) {
+        this.resourcesTable = resourcesTable;
+    }
+
+    /**
+     * 是否管理账户
+     *
+     * @param companyId
+     * @return
+     */
+    @JsonIgnore
+    public boolean isAdmin(Long companyId) {
+        boolean is = null != type && type == AccountType.ADMIN.getType();
+        if (!is && null != companyId && null != rolesMap) {
+            // 是否管理角色
+            List<RoleBaseDTO> roles = rolesMap.get(companyId);
+            if (!CollectionUtils.isEmpty(roles)) {
+                return roles.stream().anyMatch(RoleBaseDTO::isAdmin);
+            }
+        }
+        return is;
+    }
+
+    @JsonIgnore
+    public Set<Long> getResources(String appId, Long companyId) {
+        if (null != resourcesTable && resourcesTable.containsKey(appId)) {
+            return resourcesTable.get(appId).get(companyId);
+        }
+        return null;
     }
 
     @Override
@@ -116,10 +163,11 @@ public class AccountDTO implements Serializable {
                 ", realname='" + realname + '\'' +
                 ", email='" + email + '\'' +
                 ", mobile='" + mobile + '\'' +
+                ", avatarUrl='" + avatarUrl + '\'' +
                 ", type=" + type +
                 ", companies=" + companies +
                 ", rolesMap=" + rolesMap +
-                ", resourcesMap=" + resourcesMap +
+                ", resourcesTable=" + resourcesTable +
                 '}';
     }
 }

+ 27 - 1
base-servers/account/account-dto/src/main/java/com/usoftchina/saas/account/dto/RoleBaseDTO.java

@@ -1,5 +1,8 @@
 package com.usoftchina.saas.account.dto;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.usoftchina.saas.account.constant.RoleType;
+
 import java.io.Serializable;
 
 /**
@@ -8,7 +11,7 @@ import java.io.Serializable;
  * @author yingp
  * @date 2018/10/10
  */
-public class RoleBaseDTO implements Serializable{
+public class RoleBaseDTO implements Serializable {
     private Long id;
     /**
      * 公司
@@ -22,6 +25,10 @@ public class RoleBaseDTO implements Serializable{
      * 角色名称
      */
     private String name;
+    /**
+     * 类型
+     */
+    private Integer type;
 
     public Long getId() {
         return id;
@@ -55,6 +62,24 @@ public class RoleBaseDTO implements Serializable{
         this.name = name;
     }
 
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    /**
+     * 是否管理角色
+     *
+     * @return
+     */
+    @JsonIgnore
+    public boolean isAdmin() {
+        return null != type && type == RoleType.ADMIN.getType();
+    }
+
     @Override
     public String toString() {
         return "RoleBaseDTO{" +
@@ -62,6 +87,7 @@ public class RoleBaseDTO implements Serializable{
                 ", companyId=" + companyId +
                 ", code='" + code + '\'' +
                 ", name='" + name + '\'' +
+                ", type=" + type +
                 '}';
     }
 }

+ 11 - 1
base-servers/account/account-dto/src/main/java/com/usoftchina/saas/account/dto/UrlResourceDTO.java

@@ -9,6 +9,7 @@ import java.io.Serializable;
  * @date 2018/10/26
  */
 public class UrlResourceDTO implements Serializable{
+    private Long id;
     /**
      * 名称
      */
@@ -22,6 +23,14 @@ public class UrlResourceDTO implements Serializable{
      */
     private String method;
 
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
     public String getName() {
         return name;
     }
@@ -49,7 +58,8 @@ public class UrlResourceDTO implements Serializable{
     @Override
     public String toString() {
         return "UrlResourceDTO{" +
-                "name='" + name + '\'' +
+                "id=" + id +
+                ", name='" + name + '\'' +
                 ", url='" + url + '\'' +
                 ", method='" + method + '\'' +
                 '}';

+ 1 - 1
base-servers/account/account-server/pom.xml

@@ -19,7 +19,7 @@
         </dependency>
         <dependency>
             <groupId>com.usoftchina.saas</groupId>
-            <artifactId>account-dto</artifactId>
+            <artifactId>account-api</artifactId>
         </dependency>
         <!-- db -->
         <dependency>

+ 74 - 7
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/controller/AccountController.java

@@ -1,20 +1,28 @@
 package com.usoftchina.saas.account.controller;
 
+import com.usoftchina.saas.account.cache.AccountCache;
 import com.usoftchina.saas.account.dto.AccountDTO;
 import com.usoftchina.saas.account.dto.AccountRegDTO;
 import com.usoftchina.saas.account.dto.CompanyBaseDTO;
+import com.usoftchina.saas.account.dto.RoleBaseDTO;
 import com.usoftchina.saas.account.po.Account;
+import com.usoftchina.saas.account.po.RoleResource;
 import com.usoftchina.saas.account.service.AccountService;
 import com.usoftchina.saas.account.service.CompanyService;
+import com.usoftchina.saas.account.service.RoleService;
 import com.usoftchina.saas.account.vo.CompanyBaseVO;
 import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.context.BaseContextHolder;
 import com.usoftchina.saas.exception.ExceptionCode;
 import com.usoftchina.saas.utils.BeanMapper;
+import com.usoftchina.saas.utils.CollectionUtils;
 import com.usoftchina.saas.utils.RegexpUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * @author yingp
@@ -30,6 +38,9 @@ public class AccountController {
     @Autowired
     private CompanyService companyService;
 
+    @Autowired
+    private RoleService roleService;
+
     /**
      * 注册
      *
@@ -84,14 +95,41 @@ public class AccountController {
             return Result.error(ExceptionCode.USER_PWD_ERROR);
         }
 
+        return Result.success(getAccountDTO(account));
+    }
+
+    /**
+     * 账户 + 账户的全部绑定信息
+     *
+     * @param account
+     * @return
+     */
+    private AccountDTO getAccountDTO(Account account) {
         AccountDTO accountDTO = BeanMapper.map(account, AccountDTO.class);
         // 绑定的公司
         List<CompanyBaseVO> companyBaseVOS = companyService.findBaseByAccountId(account.getId());
         accountDTO.setCompanies(BeanMapper.mapList(companyBaseVOS, CompanyBaseDTO.class));
-
-        return Result.success(accountDTO);
+        List<RoleBaseDTO> roleBaseDTOS = roleService.findByAccountId(accountDTO.getId());
+        if (!CollectionUtils.isEmpty(roleBaseDTOS)) {
+            // 绑定的 公司->角色
+            accountDTO.setRolesMap(CollectionUtils.groupBy(roleBaseDTOS, RoleBaseDTO::getCompanyId));
+            List<RoleResource> roleResourceVOS = accountService.findRoleResourcesByAccountId(accountDTO.getId());
+            if (!CollectionUtils.isEmpty(roleBaseDTOS)) {
+                // 绑定的 应用+公司->资源
+                Map<String, Map<Long, Set<Long>>> resourcesTable = CollectionUtils.distinctBy(roleResourceVOS,
+                        RoleResource::getAppId, RoleResource::getCompanyId, RoleResource::getResourceId);
+                accountDTO.setResourcesTable(resourcesTable);
+            }
+        }
+        return accountDTO;
     }
 
+    /**
+     * 匹配前端多种输入账号类型
+     *
+     * @param username
+     * @return
+     */
     private Account getAccountByUsername(String username) {
         Account account;
         if (RegexpUtils.isMobile(username)) {
@@ -117,12 +155,23 @@ public class AccountController {
             return Result.error(ExceptionCode.USER_NOT_EXIST);
         }
 
-        AccountDTO accountDTO = BeanMapper.map(account, AccountDTO.class);
-        // 绑定的公司
-        List<CompanyBaseVO> companyBaseVOS = companyService.findBaseByAccountId(account.getId());
-        accountDTO.setCompanies(BeanMapper.mapList(companyBaseVOS, CompanyBaseDTO.class));
+        return Result.success(getAccountDTO(account));
+    }
+
+    /**
+     * 按ID查找账户
+     *
+     * @param id
+     * @return
+     */
+    @GetMapping("/{id}")
+    public Result<AccountDTO> getAccountById(@PathVariable Long id) {
+        Account account = accountService.findByPrimaryKey(id);
+        if (null == account) {
+            return Result.error(ExceptionCode.USER_NOT_EXIST);
+        }
 
-        return Result.success(accountDTO);
+        return Result.success(getAccountDTO(account));
     }
 
     /**
@@ -135,6 +184,7 @@ public class AccountController {
     @PostMapping("/bind/company")
     public Result bindCompany(@RequestParam long accountId, @RequestParam long companyId) {
         accountService.bindCompany(accountId, companyId);
+        accountService.clearCache(accountId);
         return Result.success();
     }
 
@@ -148,6 +198,7 @@ public class AccountController {
     @PostMapping("/unbind/company")
     public Result unbindCompany(@RequestParam long accountId, @RequestParam long companyId) {
         accountService.unbindCompany(accountId, companyId);
+        accountService.clearCache(accountId);
         return Result.success();
     }
 
@@ -161,6 +212,7 @@ public class AccountController {
     @PostMapping("/bind/role")
     public Result bindRole(@RequestParam long accountId, @RequestParam long roleId) {
         accountService.bindRole(accountId, roleId);
+        accountService.clearCache(accountId);
         return Result.success();
     }
 
@@ -174,6 +226,7 @@ public class AccountController {
     @PostMapping("/unbind/role")
     public Result unbindRole(@RequestParam long accountId, @RequestParam long roleId) {
         accountService.unbindRole(accountId, roleId);
+        accountService.clearCache(accountId);
         return Result.success();
     }
 
@@ -186,6 +239,7 @@ public class AccountController {
     @PostMapping("/disable")
     public Result disableAccount(@RequestParam long accountId) {
         accountService.disable(accountId);
+        accountService.clearCache(accountId);
         return Result.success();
     }
 
@@ -198,6 +252,7 @@ public class AccountController {
     @PostMapping("/enable")
     public Result enableAccount(@RequestParam long accountId) {
         accountService.enable(accountId);
+        accountService.clearCache(accountId);
         return Result.success();
     }
 
@@ -209,7 +264,19 @@ public class AccountController {
      */
     @PostMapping("/delete/{id}")
     public Result deleteAccount(@PathVariable Long id) {
+        accountService.clearCache(id);
         accountService.removeByPrimaryKey(id);
         return Result.success();
     }
+
+    /**
+     * 账户缓存清除
+     *
+     * @return
+     */
+    @GetMapping("/cache/clear")
+    public Result clearCache() {
+        accountService.clearCache(BaseContextHolder.getUserId());
+        return Result.success();
+    }
 }

+ 1 - 1
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/controller/ResourcesController.java → base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/controller/ResourceController.java

@@ -19,7 +19,7 @@ import java.util.List;
  */
 @RestController
 @RequestMapping("/resource")
-public class ResourcesController {
+public class ResourceController {
 
     @Autowired
     private ResourceService resourcesService;

+ 22 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/controller/RoleController.java

@@ -1,9 +1,12 @@
 package com.usoftchina.saas.account.controller;
 
+import com.usoftchina.saas.account.cache.AccountCache;
 import com.usoftchina.saas.account.dto.RoleDTO;
 import com.usoftchina.saas.account.dto.RoleSaveDTO;
 import com.usoftchina.saas.account.dto.RoleUpdateDTO;
+import com.usoftchina.saas.account.po.Account;
 import com.usoftchina.saas.account.po.Role;
+import com.usoftchina.saas.account.service.AccountService;
 import com.usoftchina.saas.account.service.RoleService;
 import com.usoftchina.saas.base.Result;
 import com.usoftchina.saas.commons.dto.DocBaseDTO;
@@ -27,6 +30,9 @@ public class RoleController {
     @Autowired
     private RoleService roleService;
 
+    @Autowired
+    private AccountService accountService;
+
     /**
      * 保存
      *
@@ -50,6 +56,7 @@ public class RoleController {
     public Result update(@RequestBody RoleUpdateDTO roleUpdateDTO) {
         Role role = BeanMapper.map(roleUpdateDTO, Role.class);
         roleService.updateByPrimaryKey(role);
+        clearAccountCacheByRoleId(roleUpdateDTO.getId());
         return Result.success();
     }
 
@@ -61,6 +68,7 @@ public class RoleController {
      */
     @PostMapping("/delete/{id}")
     public Result delete(@PathVariable Long id) {
+        clearAccountCacheByRoleId(id);
         roleService.removeByPrimaryKey(id);
         return Result.success();
     }
@@ -104,6 +112,7 @@ public class RoleController {
     @PostMapping("/bind/resource")
     public Result bindResource(@RequestParam long roleId, @RequestParam long resourceId) {
         roleService.bindResource(roleId, resourceId);
+        clearAccountCacheByRoleId(roleId);
         return Result.success();
     }
 
@@ -117,6 +126,19 @@ public class RoleController {
     @PostMapping("/unbind/resource")
     public Result unbindResource(@RequestParam long roleId, @RequestParam long resourceId) {
         roleService.unbindResource(roleId, resourceId);
+        clearAccountCacheByRoleId(roleId);
         return Result.success();
     }
+
+    /**
+     * 清除角色相关所有账户的缓存
+     *
+     * @param roleId
+     */
+    private void clearAccountCacheByRoleId(long roleId) {
+        List<Account> accounts = accountService.findByRoleId(roleId);
+        if (!CollectionUtils.isEmpty(accounts)) {
+            accounts.forEach(account -> accountService.clearCache(account.getId()));
+        }
+    }
 }

+ 10 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/AccountMapper.java

@@ -3,6 +3,8 @@ package com.usoftchina.saas.account.mapper;
 import com.usoftchina.saas.account.po.Account;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.List;
+
 /**
  * @author yingp
  * @date 2018/10/2
@@ -56,6 +58,14 @@ public interface AccountMapper {
      */
     Account selectByEmail(@Param("email") String email);
 
+    /**
+     * 按角色查找
+     *
+     * @param roleId
+     * @return
+     */
+    List<Account> selectByRoleId(@Param("roleId") Long roleId);
+
     /**
      * 删除
      *

+ 9 - 1
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/AccountRoleMapper.java

@@ -10,11 +10,12 @@ public interface AccountRoleMapper {
     /**
      * 新增
      *
+     * @param companyId
      * @param accountId
      * @param roleId
      * @return
      */
-    int insert(@Param("accountId") Long accountId, @Param("roleId") Long roleId);
+    int insert(@Param("companyId") Long companyId, @Param("accountId") Long accountId, @Param("roleId") Long roleId);
 
     /**
      * 删除
@@ -31,6 +32,13 @@ public interface AccountRoleMapper {
      */
     void deleteByAccountId(@Param("accountId") Long accountId);
 
+    /**
+     * 按公司删除
+     *
+     * @param companyId
+     */
+    void deleteByCompanyId(@Param("companyId") Long companyId);
+
     /**
      * 按角色删除
      *

+ 16 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/ResourceMapper.java

@@ -26,4 +26,20 @@ public interface ResourceMapper {
      * @return
      */
     List<Resource> selectByAppId(@Param("appId") String appId);
+
+    /**
+     * 按角色查找
+     *
+     * @param roleId
+     * @return
+     */
+    List<Resource> selectByRoleId(@Param("roleId") Long roleId);
+
+    /**
+     * 按主键查找
+     *
+     * @param id
+     * @return
+     */
+    Resource selectByPrimaryKey(@Param("id") Long id);
 }

+ 11 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/RoleMapper.java

@@ -2,10 +2,21 @@ package com.usoftchina.saas.account.mapper;
 
 import com.usoftchina.saas.account.po.Role;
 import com.usoftchina.saas.base.mapper.CommonBaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 /**
  * @author yingp
  * @date 2018/10/2
  */
 public interface RoleMapper extends CommonBaseMapper<Role> {
+
+    /**
+     * 按账户查找绑定的角色
+     *
+     * @param accountId
+     * @return
+     */
+    List<Role> selectByAccountId(@Param("accountId") long accountId);
 }

+ 22 - 1
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/RoleResourceMapper.java

@@ -1,7 +1,10 @@
 package com.usoftchina.saas.account.mapper;
 
+import com.usoftchina.saas.account.po.RoleResource;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.List;
+
 /**
  * @author yingp
  * @date 2018/10/2
@@ -10,11 +13,13 @@ public interface RoleResourceMapper {
     /**
      * 新增
      *
+     * @param appId
+     * @param companyId
      * @param roleId
      * @param resourceId
      * @return
      */
-    int insert(@Param("roleId") Long roleId, @Param("resourceId") Long resourceId);
+    int insert(@Param("appId") String appId, @Param("companyId") Long companyId, @Param("roleId") Long roleId, @Param("resourceId") Long resourceId);
 
     /**
      * 删除
@@ -32,4 +37,20 @@ public interface RoleResourceMapper {
      * @return
      */
     int deleteByRoleId(@Param("roleId") Long roleId);
+
+    /**
+     * 按公司删除
+     *
+     * @param companyId
+     * @return
+     */
+    int deleteByCompanyId(@Param("companyId") Long companyId);
+
+    /**
+     * 按账户查询绑定的角色+资源
+     *
+     * @param accountId
+     * @return
+     */
+    List<RoleResource> selectByAccountId(@Param("accountId") Long accountId);
 }

+ 12 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/po/Account.java

@@ -17,6 +17,10 @@ public class Account implements Serializable {
     private String realname;
     private String email;
     private String mobile;
+    /**
+     * 头像
+     */
+    private String avatarUrl;
     /**
      * 账号类型 0 - 管理员
      */
@@ -75,6 +79,14 @@ public class Account implements Serializable {
         this.mobile = mobile;
     }
 
+    public String getAvatarUrl() {
+        return avatarUrl;
+    }
+
+    public void setAvatarUrl(String avatarUrl) {
+        this.avatarUrl = avatarUrl;
+    }
+
     public Integer getType() {
         return type;
     }

+ 14 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/po/AccountRole.java

@@ -11,6 +11,12 @@ import java.io.Serializable;
 public class AccountRole implements Serializable {
     private long accountId;
     private long roleId;
+    /**
+     * 角色所属公司
+     *
+     * <p>冗余字段,方便查询</p>
+     */
+    private long companyId;
 
     public long getAccountId() {
         return accountId;
@@ -27,4 +33,12 @@ public class AccountRole implements Serializable {
     public void setRoleId(long roleId) {
         this.roleId = roleId;
     }
+
+    public long getCompanyId() {
+        return companyId;
+    }
+
+    public void setCompanyId(long companyId) {
+        this.companyId = companyId;
+    }
 }

+ 14 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/po/Resource.java

@@ -10,6 +10,12 @@ import java.io.Serializable;
  */
 public class Resource implements Serializable {
     private Long id;
+    /**
+     * 归属应用
+     *
+     * <p>冗余字段,方便查询</p>
+     */
+    private String appId;
     /**
      * 资源组
      */
@@ -51,6 +57,14 @@ public class Resource implements Serializable {
         this.id = id;
     }
 
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
     public String getType() {
         return type;
     }

+ 14 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/po/ResourceGroup.java

@@ -11,6 +11,12 @@ import java.io.Serializable;
  */
 public class ResourceGroup implements Serializable {
     private Long id;
+    /**
+     * 归属应用
+     *
+     * <p>冗余字段,方便查询</p>
+     */
+    private String appId;
     private Long moduleId;
     private String name;
 
@@ -22,6 +28,14 @@ public class ResourceGroup implements Serializable {
         this.id = id;
     }
 
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
     public Long getModuleId() {
         return moduleId;
     }

+ 26 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/po/RoleResource.java

@@ -10,7 +10,33 @@ import java.io.Serializable;
  */
 public class RoleResource implements Serializable {
     private long roleId;
+    /**
+     * 资源所属应用
+     * <p>冗余字段,方便查询</p>
+     */
+    private String appId;
     private long resourceId;
+    /**
+     * 角色所属公司
+     * <p>冗余字段,方便查询</p>
+     */
+    private long companyId;
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public long getCompanyId() {
+        return companyId;
+    }
+
+    public void setCompanyId(long companyId) {
+        this.companyId = companyId;
+    }
 
     public long getRoleId() {
         return roleId;

+ 33 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/AccountService.java

@@ -1,6 +1,9 @@
 package com.usoftchina.saas.account.service;
 
 import com.usoftchina.saas.account.po.Account;
+import com.usoftchina.saas.account.po.RoleResource;
+
+import java.util.List;
 
 /**
  * @author yingp
@@ -39,6 +42,22 @@ public interface AccountService {
      */
     Account findByEmail(String email);
 
+    /**
+     * 按主键查找
+     *
+     * @param id
+     * @return
+     */
+    Account findByPrimaryKey(Long id);
+
+    /**
+     * 按角色查找
+     *
+     * @param roleId
+     * @return
+     */
+    List<Account> findByRoleId(Long roleId);
+
     /**
      * 校验密码
      *
@@ -111,4 +130,18 @@ public interface AccountService {
      */
     void enable(Long accountId);
 
+    /**
+     * 按账户查找绑定的角色+资源
+     *
+     * @param accountId
+     * @return
+     */
+    List<RoleResource> findRoleResourcesByAccountId(Long accountId);
+
+    /**
+     * 清除账户相关缓存
+     *
+     * @param accountId
+     */
+    void clearCache(Long accountId);
 }

+ 16 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/ResourceService.java

@@ -26,4 +26,20 @@ public interface ResourceService {
      * @return
      */
     List<UrlResourceDTO> findUrlResourcesByAppId(String appId);
+
+    /**
+     * 按角色查找绑定的资源
+     *
+     * @param roleId
+     * @return
+     */
+    List<UrlResourceDTO> findUrlResourcesByRoleId(Long roleId);
+
+    /**
+     * 按主键查找资源
+     *
+     * @param id
+     * @return
+     */
+    Resource findByPrimaryKey(Long id);
 }

+ 12 - 1
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/RoleService.java

@@ -1,14 +1,17 @@
 package com.usoftchina.saas.account.service;
 
+import com.usoftchina.saas.account.dto.RoleBaseDTO;
 import com.usoftchina.saas.account.mapper.RoleMapper;
 import com.usoftchina.saas.account.po.Role;
 import com.usoftchina.saas.base.service.CommonBaseService;
 
+import java.util.List;
+
 /**
  * @author yingp
  * @date 2018/10/24
  */
-public interface RoleService extends CommonBaseService<RoleMapper, Role>{
+public interface RoleService extends CommonBaseService<RoleMapper, Role> {
 
     /**
      * 角色绑定资源
@@ -25,4 +28,12 @@ public interface RoleService extends CommonBaseService<RoleMapper, Role>{
      * @param resourceId
      */
     void unbindResource(long roleId, long resourceId);
+
+    /**
+     * 按账户查找绑定角色
+     *
+     * @param accountId
+     * @return
+     */
+    List<RoleBaseDTO> findByAccountId(Long accountId);
 }

+ 37 - 1
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/impl/AccountServiceImpl.java

@@ -1,15 +1,22 @@
 package com.usoftchina.saas.account.service.impl;
 
+import com.usoftchina.saas.account.cache.AccountCache;
 import com.usoftchina.saas.account.mapper.AccountCompanyMapper;
 import com.usoftchina.saas.account.mapper.AccountMapper;
 import com.usoftchina.saas.account.mapper.AccountRoleMapper;
+import com.usoftchina.saas.account.mapper.RoleResourceMapper;
 import com.usoftchina.saas.account.po.Account;
+import com.usoftchina.saas.account.po.Role;
+import com.usoftchina.saas.account.po.RoleResource;
 import com.usoftchina.saas.account.service.AccountService;
+import com.usoftchina.saas.account.service.RoleService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.DigestUtils;
 
+import java.util.List;
+
 /**
  * @author yingp
  * @date 2018/10/2
@@ -26,6 +33,12 @@ public class AccountServiceImpl implements AccountService {
     @Autowired
     private AccountRoleMapper accountRoleMapper;
 
+    @Autowired
+    private RoleService roleService;
+
+    @Autowired
+    private RoleResourceMapper roleResourceMapper;
+
     @Override
     public boolean save(Account account) {
         return accountMapper.insert(account) > 0;
@@ -46,6 +59,11 @@ public class AccountServiceImpl implements AccountService {
         return accountMapper.selectByEmail(email);
     }
 
+    @Override
+    public Account findByPrimaryKey(Long id) {
+        return accountMapper.selectByPrimaryKey(id);
+    }
+
     @Override
     public boolean checkPwd(Account account, String plainPassword) {
         return getEncryptedPassword(plainPassword, account.getSalt()).equals(account.getPassword());
@@ -75,7 +93,10 @@ public class AccountServiceImpl implements AccountService {
 
     @Override
     public void bindRole(Long accountId, Long roleId) {
-        accountRoleMapper.insert(accountId, roleId);
+        Role role = roleService.findByPrimaryKey(roleId);
+        if (null != role) {
+            accountRoleMapper.insert(role.getCompanyId(), accountId, roleId);
+        }
     }
 
     @Override
@@ -106,4 +127,19 @@ public class AccountServiceImpl implements AccountService {
             accountMapper.updateEnabled(accountId, false);
         }
     }
+
+    @Override
+    public List<RoleResource> findRoleResourcesByAccountId(Long accountId) {
+        return roleResourceMapper.selectByAccountId(accountId);
+    }
+
+    @Override
+    public List<Account> findByRoleId(Long roleId) {
+        return accountMapper.selectByRoleId(roleId);
+    }
+
+    @Override
+    public void clearCache(Long accountId) {
+        AccountCache.of(accountId).hdel();
+    }
 }

+ 14 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/impl/ResourceServiceImpl.java

@@ -101,4 +101,18 @@ public class ResourceServiceImpl implements ResourceService {
         }
         return null;
     }
+
+    @Override
+    public List<UrlResourceDTO> findUrlResourcesByRoleId(Long roleId) {
+        List<Resource> resources = resourceMapper.selectByRoleId(roleId);
+        if (!CollectionUtils.isEmpty(resources)) {
+            return BeanMapper.mapList(resources, UrlResourceDTO.class);
+        }
+        return null;
+    }
+
+    @Override
+    public Resource findByPrimaryKey(Long id) {
+        return resourceMapper.selectByPrimaryKey(id);
+    }
 }

+ 24 - 1
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/impl/RoleServiceImpl.java

@@ -1,18 +1,25 @@
 package com.usoftchina.saas.account.service.impl;
 
 import com.usoftchina.saas.account.constant.RoleType;
+import com.usoftchina.saas.account.dto.RoleBaseDTO;
 import com.usoftchina.saas.account.mapper.AccountRoleMapper;
 import com.usoftchina.saas.account.mapper.RoleMapper;
 import com.usoftchina.saas.account.mapper.RoleResourceMapper;
+import com.usoftchina.saas.account.po.Resource;
 import com.usoftchina.saas.account.po.Role;
+import com.usoftchina.saas.account.service.ResourceService;
 import com.usoftchina.saas.account.service.RoleService;
 import com.usoftchina.saas.base.service.CommonBaseServiceImpl;
+import com.usoftchina.saas.utils.BeanMapper;
+import com.usoftchina.saas.utils.CollectionUtils;
 import com.usoftchina.saas.utils.StringUtils;
 import org.apache.commons.lang.RandomStringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.List;
+
 /**
  * @author yingp
  * @date 2018/10/24
@@ -28,6 +35,9 @@ public class RoleServiceImpl extends CommonBaseServiceImpl<RoleMapper, Role> imp
     @Autowired
     private AccountRoleMapper accountRoleMapper;
 
+    @Autowired
+    private ResourceService resourceService;
+
     @Override
     public boolean save(Role role) {
         if (StringUtils.isEmpty(role.getCode())) {
@@ -40,7 +50,11 @@ public class RoleServiceImpl extends CommonBaseServiceImpl<RoleMapper, Role> imp
 
     @Override
     public void bindResource(long roleId, long resourceId) {
-        roleResourceMapper.insert(roleId, resourceId);
+        Role role = findByPrimaryKey(roleId);
+        Resource resource = resourceService.findByPrimaryKey(resourceId);
+        if (null != role && null != resource) {
+            roleResourceMapper.insert(resource.getAppId(), role.getCompanyId(), roleId, resourceId);
+        }
     }
 
     @Override
@@ -55,4 +69,13 @@ public class RoleServiceImpl extends CommonBaseServiceImpl<RoleMapper, Role> imp
         roleResourceMapper.deleteByRoleId(id);
         return super.removeByPrimaryKey(id);
     }
+
+    @Override
+    public List<RoleBaseDTO> findByAccountId(Long accountId) {
+        List<Role> roles = getMapper().selectByAccountId(accountId);
+        if (!CollectionUtils.isEmpty(roles)) {
+            return BeanMapper.mapList(roles, RoleBaseDTO.class);
+        }
+        return null;
+    }
 }

+ 3 - 0
base-servers/account/account-server/src/main/resources/application.yml

@@ -35,6 +35,9 @@ spring:
       connection-timeout: 30000
   messages:
     basename: i18n/messages
+  redis:
+    host: 192.168.253.12
+    port: 6379
 eureka:
   instance:
     leaseRenewalIntervalInSeconds: 10

+ 7 - 1
base-servers/account/account-server/src/main/resources/mapper/AccountMapper.xml

@@ -17,7 +17,9 @@
         <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
     </resultMap>
     <sql id="baseColumns">
-        id,username,password,salt,realname,email,mobile,type,enabled,creator_id,create_time,updater_id,update_time
+        ac_account.id,ac_account.username,ac_account.password,ac_account.salt,ac_account.realname,ac_account.email,
+        ac_account.mobile,ac_account.type,ac_account.enabled,ac_account.creator_id,ac_account.create_time,
+        ac_account.updater_id,ac_account.update_time
     </sql>
     <insert id="insert" parameterType="com.usoftchina.saas.account.po.Account">
         insert into ac_account(username,password,salt,realname,email,mobile,type,enabled,creator_id,create_time,updater_id,update_time)
@@ -116,6 +118,10 @@
     <select id="selectByEmail" parameterType="java.lang.String" resultMap="BaseResultMap">
         select <include refid="baseColumns"/> from ac_account where email=#{email}
     </select>
+    <select id="selectByRoleId" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from ac_account,ac_account_role where
+        ac_account.id=ac_account_role.account_id and ac_account_role.role_id=#{roleId,jdbcType=BIGINT}
+    </select>
     <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
         delete from ac_account where id=#{id}
     </delete>

+ 4 - 1
base-servers/account/account-server/src/main/resources/mapper/AccountRoleMapper.xml

@@ -2,7 +2,7 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
 <mapper namespace="com.usoftchina.saas.account.mapper.AccountRoleMapper">
     <insert id="insert">
-        insert into ac_account_role(account_id,role_id) values (#{accountId}, #{roleId})
+        insert into ac_account_role(company_id,account_id,role_id) values (#{companyId}, #{accountId}, #{roleId})
     </insert>
     <delete id="delete">
         delete from ac_account_role where account_id=#{accountId} and role_id=#{roleId}
@@ -13,4 +13,7 @@
     <delete id="deleteByRoleId" parameterType="java.lang.Long">
         delete from ac_account_role where role_id=#{roleId}
     </delete>
+    <delete id="deleteByCompanyId" parameterType="java.lang.Long">
+        delete from ac_account_role where company_id=#{companyId}
+    </delete>
 </mapper>

+ 9 - 1
base-servers/account/account-server/src/main/resources/mapper/ResourceMapper.xml

@@ -3,6 +3,7 @@
 <mapper namespace="com.usoftchina.saas.account.mapper.ResourceMapper">
     <resultMap id="BaseResultMap" type="com.usoftchina.saas.account.po.Resource">
         <id column="id" jdbcType="BIGINT" property="id"/>
+        <result column="app_id" jdbcType="VARCHAR" property="appId"/>
         <result column="name" jdbcType="VARCHAR" property="name"/>
         <result column="group_id" jdbcType="BIGINT" property="groupId"/>
         <result column="type" jdbcType="VARCHAR" property="type"/>
@@ -11,7 +12,7 @@
         <result column="classify" jdbcType="VARCHAR" property="classify"/>
     </resultMap>
     <sql id="baseColumns">
-        ac_resource.id,ac_resource.name,ac_resource.group_id,ac_resource.type,ac_resource.url,ac_resource.method,ac_resource.classify
+        ac_resource.id,ac_resource.app_id,ac_resource.name,ac_resource.group_id,ac_resource.type,ac_resource.url,ac_resource.method,ac_resource.classify
     </sql>
     <select id="selectByGroupId" parameterType="java.lang.Long" resultMap="BaseResultMap">
         select <include refid="baseColumns"/> from ac_resource where group_id=#{groupId,jdbcType=BIGINT}
@@ -21,4 +22,11 @@
         ac_resource_module.id=ac_resource_group.module_id and ac_resource_group.id=ac_resource.group_id
         and ac_resource_module.app_id=#{appId,jdbcType=VARCHAR}
     </select>
+    <select id="selectByRoleId" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from ac_resource,ac_role_resource where
+        ac_resource.id=ac_role_resource.resource_id and ac_role_resource.role_id=#{roleId,jdbcType=BIGINT}
+    </select>
+    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from ac_resource where id=#{id,jdbcType=BIGINT}
+    </select>
 </mapper>

+ 6 - 1
base-servers/account/account-server/src/main/resources/mapper/RoleMapper.xml

@@ -14,7 +14,8 @@
         <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
     </resultMap>
     <sql id="baseColumns">
-        id,code,name,description,type,company_id,creator_id,create_time,updater_id,update_time
+        ac_role.id,ac_role.code,ac_role.name,ac_role.description,ac_role.type,ac_role.company_id,
+        ac_role.creator_id,ac_role.create_time,ac_role.updater_id,ac_role.update_time
     </sql>
     <insert id="insert" parameterType="com.usoftchina.saas.account.po.Role"
             useGeneratedKeys="true" keyProperty="id">
@@ -99,4 +100,8 @@
     <select id="selectByCompanyId" parameterType="java.lang.Long" resultMap="BaseResultMap">
         select <include refid="baseColumns"/> from ac_role where company_id=#{companyId,jdbcType=BIGINT}
     </select>
+    <select id="selectByAccountId" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from ac_role,ac_account_role
+        where ac_role.id=ac_account_role.role_id and ac_account_role.account_id=#{accountId,jdbcType=BIGINT}
+    </select>
 </mapper>

+ 15 - 1
base-servers/account/account-server/src/main/resources/mapper/RoleResourceMapper.xml

@@ -2,7 +2,7 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
 <mapper namespace="com.usoftchina.saas.account.mapper.RoleResourceMapper">
     <insert id="insert">
-        insert into ac_role_resource(role_id,resource_id) values (#{roleId}, #{resourceId})
+        insert into ac_role_resource(app_id,company_id,role_id,resource_id) values (#{appId}, #{companyId}, #{roleId}, #{resourceId})
     </insert>
     <delete id="delete">
         delete from ac_role_resource where role_id=#{roleId} and resource_id=#{resourceId}
@@ -10,4 +10,18 @@
     <delete id="deleteByRoleId" parameterType="java.lang.Long">
         delete from ac_role_resource where role_id=#{roleId}
     </delete>
+    <delete id="deleteByCompanyId" parameterType="java.lang.Long">
+        delete from ac_role_resource where company_id=#{companyId}
+    </delete>
+    <resultMap id="BaseResultMap" type="com.usoftchina.saas.account.po.RoleResource">
+        <result column="role_id" jdbcType="BIGINT" property="roleId"/>
+        <result column="app_id" jdbcType="VARCHAR" property="appId"/>
+        <result column="company_id" jdbcType="BIGINT" property="companyId"/>
+        <result column="resource_id" jdbcType="BIGINT" property="resourceId"/>
+    </resultMap>
+    <select id="selectByAccountId" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        select ac_role_resource.* from ac_account_role,ac_role_resource
+        where ac_account_role.role_id=ac_role_resource.role_id
+        and ac_account_role.account_id=#{accountId,jdbcType=BIGINT}
+    </select>
 </mapper>

+ 65 - 6
base-servers/account/account-server/src/test/java/com/usoftchina/saas/account/controller/AccountControllerTest.java

@@ -3,7 +3,9 @@ package com.usoftchina.saas.account.controller;
 import com.usoftchina.saas.account.constant.AccountType;
 import com.usoftchina.saas.account.dto.AccountDTO;
 import com.usoftchina.saas.account.dto.AccountRegDTO;
+import com.usoftchina.saas.account.dto.RoleSaveDTO;
 import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.commons.dto.DocBaseDTO;
 import com.usoftchina.saas.test.BaseControllerTest;
 import com.usoftchina.saas.test.TestConstant;
 import org.junit.*;
@@ -58,14 +60,53 @@ public class AccountControllerTest extends BaseControllerTest {
                 .andExpect(isFail());
     }
 
-    @Test
-    public void testD_getAccount() throws Exception {
-        AccountDTO accountDTO = getAccountDTO();
-        Assert.assertEquals(accountDTO.getMobile(), mobile);
+    private DocBaseDTO createRole() throws Exception {
+        RoleSaveDTO role = new RoleSaveDTO();
+        role.setName("Tester");
+        role.setDescription("测试员");
+
+        MvcResult mvcResult = mockMvc.perform(requestBody("/role/save", role))
+                .andExpect(isSuccess())
+                .andReturn();
+        Result<DocBaseDTO> result = result(mvcResult, DocBaseDTO.class);
+        return result.getData();
+    }
+
+    private void deleteRole(Long roleId) throws Exception {
+        mockMvc.perform(post("/role/delete/{id}", roleId))
+                .andExpect(isSuccess());
+    }
+
+    private void roleBindResource(Long roleId, Long resourceId) throws Exception{
+        mockMvc.perform(post("/role/bind/resource")
+                .param("roleId", String.valueOf(roleId))
+                .param("resourceId", String.valueOf(resourceId)))
+                .andExpect(isSuccess());
+    }
+
+    private void roleUnbindResource(Long roleId, Long resourceId) throws Exception{
+        mockMvc.perform(post("/role/unbind/resource")
+                .param("roleId", String.valueOf(roleId))
+                .param("resourceId", String.valueOf(resourceId)))
+                .andExpect(isSuccess());
+    }
+
+    private void accountBindRole(Long accountId, Long roleId) throws Exception{
+        mockMvc.perform(post("/account/bind/role")
+                .param("accountId", String.valueOf(accountId))
+                .param("roleId", String.valueOf(roleId)))
+                .andExpect(isSuccess());
+    }
+
+    private void accountUnbindRole(Long accountId, Long roleId) throws Exception{
+        mockMvc.perform(post("/account/unbind/role")
+                .param("accountId", String.valueOf(accountId))
+                .param("roleId", String.valueOf(roleId)))
+                .andExpect(isSuccess());
     }
 
     private AccountDTO getAccountDTO() throws Exception {
-        MvcResult mvcResult = mockMvc.perform(get("/account")
+        MvcResult mvcResult = mockMvc.perform(get("/account/read")
                 .param("username", mobile))
                 .andExpect(isSuccess())
                 .andReturn();
@@ -75,7 +116,7 @@ public class AccountControllerTest extends BaseControllerTest {
     }
 
     @Test
-    public void testE_bindCompany() throws Exception {
+    public void testD_bindCompany() throws Exception {
         AccountDTO accountDTO = getAccountDTO();
         mockMvc.perform(post("/account/bind/company")
                 .param("accountId", String.valueOf(accountDTO.getId()))
@@ -83,6 +124,24 @@ public class AccountControllerTest extends BaseControllerTest {
                 .andExpect(isSuccess());
     }
 
+    @Test
+    public void testE_bindRole() throws Exception {
+        AccountDTO accountDTO = getAccountDTO();
+        DocBaseDTO role = createRole();
+        roleBindResource(role.getId(), 1L);
+        roleBindResource(role.getId(), 2L);
+        accountBindRole(accountDTO.getId(), role.getId());
+
+        getAccountDTO();
+
+        accountUnbindRole(accountDTO.getId(), role.getId());
+        roleUnbindResource(role.getId(), 2L);
+        roleUnbindResource(role.getId(), 1L);
+        deleteRole(role.getId());
+
+        getAccountDTO();
+    }
+
     @Test
     public void testF_unbindCompany() throws Exception {
         AccountDTO accountDTO = getAccountDTO();

+ 0 - 4
base-servers/auth/auth-api/pom.xml

@@ -25,10 +25,6 @@
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-openfeign</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-redis</artifactId>
-        </dependency>
     </dependencies>
 
 </project>

+ 8 - 0
base-servers/auth/auth-api/src/main/java/com/usoftchina/saas/auth/api/AuthApi.java

@@ -32,4 +32,12 @@ public interface AuthApi {
      */
     @GetMapping(value = "/api/auth/switch/company")
     Result<TokenDTO> switchCompany(@RequestParam(value = "companyId") String companyId);
+
+    /**
+     * 获取auth信息
+     *
+     * @return
+     */
+    @GetMapping("/info")
+    Result<AuthDTO> getInfo();
 }

+ 0 - 47
base-servers/auth/auth-api/src/main/java/com/usoftchina/saas/auth/cache/TokenCache.java

@@ -1,47 +0,0 @@
-package com.usoftchina.saas.auth.cache;
-
-import com.usoftchina.saas.auth.api.AuthApi;
-import com.usoftchina.saas.cache.RedisHashCache;
-import com.usoftchina.saas.context.SpringContextHolder;
-import org.springframework.data.redis.core.RedisTemplate;
-
-import java.util.function.Supplier;
-
-/**
- * @author yingp
- * @date 2018/9/30
- */
-public class TokenCache extends RedisHashCache<String, String, String> {
-
-    private final String account;
-
-    private final String password;
-
-    private final AuthApi authApi;
-
-    private TokenCache(String account, String password) {
-        super(() -> SpringContextHolder.getBean(RedisTemplate.class));
-        this.account = account;
-        this.password = password;
-        this.authApi = SpringContextHolder.getBean(AuthApi.class);
-    }
-
-    public static TokenCache of(String account, String password) {
-        return new TokenCache(account, password);
-    }
-
-    @Override
-    protected String key() {
-        return generateKey("auth", "token");
-    }
-
-    @Override
-    protected String field() {
-        return String.valueOf(account);
-    }
-
-    @Override
-    protected Supplier<String> getSupplier() {
-        return () -> authApi.authorize(account, password).getData().getToken().getToken();
-    }
-}

+ 19 - 0
base-servers/auth/auth-server/src/main/java/com/usoftchina/saas/auth/controller/AuthController.java

@@ -87,6 +87,25 @@ public class AuthController {
         return Result.error(ExceptionCode.COMPANY_NOT_BIND);
     }
 
+    /**
+     * 获取auth信息
+     *
+     * @param request
+     * @return
+     */
+    @GetMapping("/info")
+    public Result<AuthDTO> getInfo(HttpServletRequest request) {
+        String token = request.getHeader(authHeader);
+        JwtInfo infoFromToken = JwtHelper.getInfoFromToken(token, publicKeyPath);
+        Result<AccountDTO> result = accountApi.getAccount(infoFromToken.getUserName());
+        if (result.isSuccess()) {
+            TokenVO tokenVO = JwtHelper.generateToken(infoFromToken, privateKeyPath, expire);
+            TokenDTO tokenDTO = BeanMapper.map(tokenVO, TokenDTO.class);
+            return Result.success(new AuthDTO(tokenDTO, result.getData()));
+        }
+        return Result.error(result);
+    }
+
     /**
      * 指定公司是否可用:
      * 公司是否存在 + 是否已绑定

+ 4 - 0
base-servers/gateway-server/pom.xml

@@ -68,6 +68,10 @@
             <groupId>com.usoftchina.saas</groupId>
             <artifactId>auth-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>account-api</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <plugins>

+ 4 - 1
base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/GatewayApplication.java

@@ -11,7 +11,10 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
  */
 @SpringBootApplication
 @EnableEurekaClient
-@EnableFeignClients(basePackages = "com.usoftchina.saas.auth.api")
+@EnableFeignClients(basePackages = {
+        "com.usoftchina.saas.account.api",
+        "com.usoftchina.saas.auth.api"
+})
 public class GatewayApplication {
     public static void main(String[] args) {
         SpringApplication.run(GatewayApplication.class, args);

+ 53 - 1
base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/config/AuthFilter.java

@@ -1,9 +1,16 @@
 package com.usoftchina.saas.gateway.config;
 
+import com.usoftchina.saas.account.cache.AccountCache;
+import com.usoftchina.saas.account.cache.ResourceCache;
+import com.usoftchina.saas.account.dto.AccountDTO;
+import com.usoftchina.saas.account.dto.UrlResourceDTO;
 import com.usoftchina.saas.auth.common.jwt.JwtHelper;
 import com.usoftchina.saas.auth.common.jwt.JwtInfo;
 import com.usoftchina.saas.exception.BizException;
 import com.usoftchina.saas.exception.ExceptionCode;
+import com.usoftchina.saas.gateway.error.PermissionException;
+import com.usoftchina.saas.gateway.util.AntPathRequestMatcher;
+import com.usoftchina.saas.utils.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.cloud.gateway.filter.GatewayFilterChain;
@@ -15,6 +22,8 @@ import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Mono;
 
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
 
 /**
  * 全局过滤器鉴权
@@ -33,12 +42,55 @@ public class AuthFilter implements GlobalFilter, Ordered {
     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
         if (!isIgnore(exchange.getRequest())) {
             // 鉴别身份信息
-//            JwtInfo jwt = getJwtInfoFromHeader(exchange.getRequest());
+            JwtInfo jwt = getJwtInfoFromHeader(exchange.getRequest());
+            AccountDTO accountDTO = AccountCache.of(jwt.getUserId()).getAccount();
+            if (null == accountDTO) {
+                throw new BizException(ExceptionCode.USER_NOT_EXIST);
+            }
             // 鉴别角色权限
+            checkPermission(exchange.getRequest(), jwt, accountDTO);
         }
         return chain.filter(exchange);
     }
 
+    /**
+     * 鉴别角色权限
+     *
+     * @param request
+     * @param jwt
+     * @param accountDTO
+     */
+    private void checkPermission(ServerHttpRequest request, JwtInfo jwt, AccountDTO accountDTO) {
+        if (!accountDTO.isAdmin(jwt.getCompanyId())) {
+            // 非管理账户,需要鉴权
+            List<UrlResourceDTO> resources = ResourceCache.of(jwt.getAppId()).getUrlResources();
+            if (!CollectionUtils.isEmpty(resources)) {
+                // 本次请求相关的资源
+                Stream<UrlResourceDTO> permissions = resources.parallelStream().filter(resource -> {
+                    AntPathRequestMatcher matcher = new AntPathRequestMatcher(resource.getUrl(), resource.getMethod());
+                    return matcher.matches(request);
+                });
+                if (permissions.count() > 0) {
+                    Set<Long> resourceIds = accountDTO.getResources(jwt.getAppId(), jwt.getCompanyId());
+                    boolean permitted = false;
+                    if (null != resourceIds) {
+                        // 权限匹配
+                        permitted = permissions.anyMatch(resource -> resourceIds.contains(resource.getId()));
+                    }
+                    if (!permitted) {
+                        throw new PermissionException(permissions.findFirst().get());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 是否设置为忽略鉴权的请求
+     *
+     * @param request
+     * @return
+     */
     private boolean isIgnore(ServerHttpRequest request) {
         String path = request.getPath().value();
         return authConfig.getIgnores().stream().anyMatch(ignore -> ignore.equals(path));

+ 18 - 0
base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/context/SpringContextListener.java

@@ -0,0 +1,18 @@
+package com.usoftchina.saas.gateway.context;
+
+import com.usoftchina.saas.context.SpringContextHolder;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.ContextRefreshedEvent;
+
+/**
+ * @author yingp
+ * @date 2018/7/18
+ */
+@Configuration
+public class SpringContextListener implements ApplicationListener<ContextRefreshedEvent> {
+    @Override
+    public void onApplicationEvent(ContextRefreshedEvent event) {
+        SpringContextHolder.setContext(event.getApplicationContext());
+    }
+}

+ 20 - 0
base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/error/PermissionException.java

@@ -0,0 +1,20 @@
+package com.usoftchina.saas.gateway.error;
+
+import com.usoftchina.saas.account.dto.UrlResourceDTO;
+import com.usoftchina.saas.exception.BaseUncheckedException;
+import com.usoftchina.saas.exception.ExceptionCode;
+
+/**
+ * @author yingp
+ * @date 2018/11/2
+ */
+public class PermissionException extends BaseUncheckedException {
+    public PermissionException(int code, String message) {
+        super(code, message);
+    }
+
+    public PermissionException(UrlResourceDTO resource) {
+        super(ExceptionCode.MISSING_PERMISSIONS.getCode(),
+                String.format("没有 %s 权限", resource.getName()));
+    }
+}

+ 177 - 0
base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/util/AntPathRequestMatcher.java

@@ -0,0 +1,177 @@
+package com.usoftchina.saas.gateway.util;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * @author yingp
+ * @date 2018/11/1
+ */
+public final class AntPathRequestMatcher {
+    private static final Log logger = LogFactory.getLog(AntPathRequestMatcher.class);
+    private static final String MATCH_ALL = "/**";
+    private final Matcher matcher;
+    private final String pattern;
+    private final HttpMethod httpMethod;
+    private final boolean caseSensitive;
+
+    public AntPathRequestMatcher(String pattern) {
+        this(pattern, null);
+    }
+
+    public AntPathRequestMatcher(String pattern, String httpMethod) {
+        this(pattern, httpMethod, true);
+    }
+
+    public AntPathRequestMatcher(String pattern, String httpMethod, boolean caseSensitive) {
+        Assert.hasText(pattern, "Pattern cannot be null or empty");
+        this.caseSensitive = caseSensitive;
+
+        if (pattern.equals(MATCH_ALL) || pattern.equals("**")) {
+            pattern = MATCH_ALL;
+            this.matcher = null;
+        }
+        else {
+            // If the pattern ends with {@code /**} and has no other wildcards or path
+            // variables, then optimize to a sub-path match
+            if (pattern.endsWith(MATCH_ALL)
+                    && (pattern.indexOf('?') == -1 && pattern.indexOf('{') == -1
+                    && pattern.indexOf('}') == -1)
+                    && pattern.indexOf("*") == pattern.length() - 2) {
+                this.matcher = new SubpathMatcher(pattern.substring(0, pattern.length() - 3), caseSensitive);
+            }
+            else {
+                this.matcher = new SpringAntMatcher(pattern, caseSensitive);
+            }
+        }
+
+        this.pattern = pattern;
+        this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod.resolve(httpMethod)
+                : null;
+    }
+
+    public boolean matches(ServerHttpRequest request) {
+        if (this.httpMethod != null && this.httpMethod != request.getMethod()) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Request '" + request.getMethodValue() + " "
+                        + getRequestPath(request) + "'" + " doesn't match '"
+                        + this.httpMethod + " " + this.pattern);
+            }
+            return false;
+        }
+
+        if (this.pattern.equals(MATCH_ALL)) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Request '" + getRequestPath(request)
+                        + "' matched by universal pattern '/**'");
+            }
+
+            return true;
+        }
+
+        String url = getRequestPath(request);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Checking match of request : '" + url + "'; against '"
+                    + this.pattern + "'");
+        }
+
+        return this.matcher.matches(url);
+    }
+
+    private String getRequestPath(ServerHttpRequest request) {
+        return request.getPath().value();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof AntPathRequestMatcher)) {
+            return false;
+        }
+
+        AntPathRequestMatcher other = (AntPathRequestMatcher) obj;
+        return this.pattern.equals(other.pattern) && this.httpMethod == other.httpMethod
+                && this.caseSensitive == other.caseSensitive;
+    }
+
+    @Override
+    public int hashCode() {
+        int code = 31 ^ this.pattern.hashCode();
+        if (this.httpMethod != null) {
+            code ^= this.httpMethod.hashCode();
+        }
+        return code;
+    }
+
+    private interface Matcher {
+        boolean matches(String path);
+
+        Map<String, String> extractUriTemplateVariables(String path);
+    }
+
+    /**
+     * Optimized matcher for trailing wildcards
+     */
+    private static class SubpathMatcher implements Matcher {
+        private final String subpath;
+        private final int length;
+        private final boolean caseSensitive;
+
+        private SubpathMatcher(String subpath, boolean caseSensitive) {
+            assert!subpath.contains("*");
+            this.subpath = caseSensitive ? subpath : subpath.toLowerCase();
+            this.length = subpath.length();
+            this.caseSensitive = caseSensitive;
+        }
+
+        @Override
+        public boolean matches(String path) {
+            if (!this.caseSensitive) {
+                path = path.toLowerCase();
+            }
+            return path.startsWith(this.subpath)
+                    && (path.length() == this.length || path.charAt(this.length) == '/');
+        }
+
+        @Override
+        public Map<String, String> extractUriTemplateVariables(String path) {
+            return Collections.emptyMap();
+        }
+    }
+
+    private static class SpringAntMatcher implements Matcher {
+        private final AntPathMatcher antMatcher;
+
+        private final String pattern;
+
+        private SpringAntMatcher(String pattern, boolean caseSensitive) {
+            this.pattern = pattern;
+            this.antMatcher = createMatcher(caseSensitive);
+        }
+
+        @Override
+        public boolean matches(String path) {
+            return this.antMatcher.match(this.pattern, path);
+        }
+
+        @Override
+        public Map<String, String> extractUriTemplateVariables(String path) {
+            return this.antMatcher.extractUriTemplateVariables(this.pattern, path);
+        }
+
+        private static AntPathMatcher createMatcher(boolean caseSensitive) {
+            AntPathMatcher matcher = new AntPathMatcher();
+            matcher.setTrimTokens(false);
+            matcher.setCaseSensitive(caseSensitive);
+            return matcher;
+        }
+    }
+}

+ 3 - 0
base-servers/gateway-server/src/main/resources/application.yml

@@ -104,6 +104,9 @@ spring:
         - Path=/api/commons/**
         filters:
         - RewritePath=/api/commons/(?<segment>.*), /$\{segment}
+  redis:
+    host: 192.168.253.12
+    port: 6379
 server:
   port: 8560
   tomcat:

+ 2 - 0
base-servers/ui-server/src/main/java/com/usoftchina/saas/ui/UiApplication.java

@@ -1,5 +1,6 @@
 package com.usoftchina.saas.ui;
 
+import com.usoftchina.saas.auth.client.EnableAuthClient;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cache.annotation.EnableCaching;
@@ -14,6 +15,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 @EnableEurekaClient
 @EnableTransactionManagement
 @EnableCaching
+@EnableAuthClient
 public class UiApplication {
     public static void main(String[] args) {
         SpringApplication.run(UiApplication.class, args);

+ 0 - 4
base-servers/ui-server/src/main/java/com/usoftchina/saas/ui/controller/co/CoViewController.java

@@ -40,10 +40,6 @@ public class CoViewController {
             coViewService.cacheEvict(name);
             viewService.cacheEvict(name);
         }
-        // TODO 开发默认设置,及时去掉
-        if (null == BaseContextHolder.getCompanyId()) {
-            BaseContextHolder.setCompanyId(0);
-        }
         Object config = coViewService.getDeepConfig(name);
         if (null == config && null != useDefault && useDefault) {
             // 企业配置不存在则取标准配置

+ 4 - 2
framework/core/pom.xml

@@ -47,12 +47,14 @@
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-core</artifactId>
-            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
-            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
         </dependency>
     </dependencies>
 

+ 8 - 11
framework/core/src/main/java/com/usoftchina/saas/base/Result.java

@@ -1,13 +1,10 @@
 package com.usoftchina.saas.base;
 
-import com.fasterxml.jackson.core.type.TypeReference;
 import com.usoftchina.saas.exception.BaseException;
 import com.usoftchina.saas.exception.BaseExceptionCode;
 import com.usoftchina.saas.utils.JsonUtils;
-import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
 
 import java.io.Serializable;
-import java.lang.reflect.Type;
 
 /**
  * 结果
@@ -89,6 +86,13 @@ public class Result<T> implements Serializable {
         return result;
     }
 
+    public static Result error(Result from) {
+        Result result = error();
+        result.setCode(from.getCode());
+        result.setMessage(from.getMessage());
+        return result;
+    }
+
     public static Result error(int code, String message) {
         Result result = error();
         result.setCode(code);
@@ -135,13 +139,6 @@ public class Result<T> implements Serializable {
      * @return
      */
     public static <T> Result<T> fromJsonString(String jsonString, Class<T> targetCls) {
-        Type[] types = new Type[]{targetCls};
-        final ParameterizedTypeImpl type = ParameterizedTypeImpl.make(Result.class, types, Result.class.getDeclaringClass());
-        return JsonUtils.fromJsonString(jsonString, new TypeReference<Result<T>>() {
-            @Override
-            public Type getType() {
-                return type;
-            }
-        });
+        return JsonUtils.fromJsonString(jsonString, Result.class, targetCls);
     }
 }

+ 1 - 0
framework/core/src/main/java/com/usoftchina/saas/exception/ExceptionCode.java

@@ -35,6 +35,7 @@ public enum ExceptionCode implements BaseExceptionCode {
     USER_NOT_EXIST(53005, "用户不存在"),
     USER_NOT_ENABLE(53006, "用户禁止使用"),
     ROLE_NOT_EXIST(53020, "角色不存在"),
+    MISSING_PERMISSIONS(53030, "权限缺失"),
 
     // 文件相关
     FOLDER_NULL(55000, "文件夹为空"),

+ 112 - 5
framework/core/src/main/java/com/usoftchina/saas/utils/CollectionUtils.java

@@ -1,16 +1,18 @@
 package com.usoftchina.saas.utils;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
+
+import java.util.*;
 import java.util.function.Function;
 
 /**
  * @author yingp
  * @date 2018/10/10
  */
-public abstract class CollectionUtils extends org.springframework.util.CollectionUtils{
+public abstract class CollectionUtils extends org.springframework.util.CollectionUtils {
     /**
      * 按指定方法,将list分组返回map
      *
@@ -36,4 +38,109 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
         }
         return map;
     }
+
+    /**
+     * 按两个指定方法,将list分组返回复合键map
+     *
+     * @param sources
+     * @param rKeyGetter
+     * @param cKeyGetter
+     * @param <R>
+     * @param <C>
+     * @param <V>
+     * @return
+     */
+    public static <R, C, V> Table<R, C, List<V>> groupBy(List<V> sources, Function<V, R> rKeyGetter, Function<V, C> cKeyGetter) {
+        Table<R, C, List<V>> table = HashBasedTable.create();
+        if (!isEmpty(sources)) {
+            sources.forEach(source -> {
+                R r = rKeyGetter.apply(source);
+                C c = cKeyGetter.apply(source);
+                if (table.contains(r, c)) {
+                    table.get(r, c).add(source);
+                } else {
+                    List<V> childList = new ArrayList<>();
+                    childList.add(source);
+                    table.put(r, c, childList);
+                }
+            });
+        }
+        return table;
+    }
+
+    /**
+     * 按三个指定方法,将list分组返回复合键map
+     *
+     * @param sources
+     * @param rKeyGetter
+     * @param cKeyGetter
+     * @param valueGetter
+     * @param <R>
+     * @param <C>
+     * @param <V>
+     * @param <S>
+     * @return
+     */
+    public static <R, C, V, S> Map<R, Map<C, List<V>>> groupBy(List<S> sources, Function<S, R> rKeyGetter, Function<S, C> cKeyGetter, Function<S, V> valueGetter) {
+        Map<R, Map<C, List<V>>> table = new HashMap<>(1);
+        if (!isEmpty(sources)) {
+            sources.forEach(source -> {
+                R r = rKeyGetter.apply(source);
+                C c = cKeyGetter.apply(source);
+                V v = valueGetter.apply(source);
+
+                if (table.containsKey(r)) {
+                    Map<C, List<V>> rows = table.get(r);
+                    if (rows.containsKey(c)) {
+                        rows.get(c).add(v);
+                    } else {
+                        rows.put(c, Lists.newArrayList(v));
+                    }
+                } else {
+                    Map<C, List<V>> rows = new HashMap<>(1);
+                    rows.put(c, Lists.newArrayList(v));
+                    table.put(r, rows);
+                }
+            });
+        }
+        return table;
+    }
+
+    /**
+     * 按三个指定方法,将list分组返回复合键map
+     *
+     * @param sources
+     * @param rKeyGetter
+     * @param cKeyGetter
+     * @param valueGetter
+     * @param <R>
+     * @param <C>
+     * @param <V>
+     * @param <S>
+     * @return
+     */
+    public static <R, C, V, S> Map<R, Map<C, Set<V>>> distinctBy(List<S> sources, Function<S, R> rKeyGetter, Function<S, C> cKeyGetter, Function<S, V> valueGetter) {
+        Map<R, Map<C, Set<V>>> table = new HashMap<>(1);
+        if (!isEmpty(sources)) {
+            sources.forEach(source -> {
+                R r = rKeyGetter.apply(source);
+                C c = cKeyGetter.apply(source);
+                V v = valueGetter.apply(source);
+
+                if (table.containsKey(r)) {
+                    Map<C, Set<V>> rows = table.get(r);
+                    if (rows.containsKey(c)) {
+                        rows.get(c).add(v);
+                    } else {
+                        rows.put(c, Sets.newHashSet(v));
+                    }
+                } else {
+                    Map<C, Set<V>> rows = new HashMap<>(1);
+                    rows.put(c, Sets.newHashSet(v));
+                    table.put(r, rows);
+                }
+            });
+        }
+        return table;
+    }
 }

+ 44 - 1
framework/core/src/main/java/com/usoftchina/saas/utils/JsonUtils.java

@@ -1,8 +1,10 @@
 package com.usoftchina.saas.utils;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JavaType;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import org.springframework.util.StringUtils;
+
+import java.util.List;
 
 /**
  * @author yingp
@@ -38,6 +40,47 @@ public class JsonUtils {
         try {
             return mapper.readValue(json, valueTypeRef);
         } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static JavaType getJavaType(Class<?> targetClass, Class<?>... elementClasses) {
+        return mapper.getTypeFactory().constructParametricType(targetClass, elementClasses);
+    }
+
+    public static <T> T fromJsonString(String json, Class<?> targetClass, Class<?>... elementClasses) {
+        if (StringUtils.isEmpty(json)) {
+            return null;
+        }
+        try {
+            return mapper.readValue(json, getJavaType(targetClass, elementClasses));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static <T> T fromJsonString(String json, JavaType type) {
+        if (StringUtils.isEmpty(json)) {
+            return null;
+        }
+        try {
+            return mapper.readValue(json, type);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static <T> List<T> fromJsonArray(String json, Class<T> targetCls) {
+        if (StringUtils.isEmpty(json)) {
+            return null;
+        }
+        try {
+            return mapper.readValue(json, getJavaType(List.class, targetCls));
+        } catch (Exception e) {
+            e.printStackTrace();
             return null;
         }
     }

+ 0 - 1
frontend/saas-web/app/Application.scss

@@ -63,7 +63,6 @@ html, body {
   
   .x-fa {
     display: inline-block;
-    font: normal normal normal 14px/1 $font-family;
   }
   
   .alignRight {

+ 0 - 26
frontend/saas-web/app/model/Account.js

@@ -1,26 +0,0 @@
-Ext.define('saas.model.Account', {
-    extend: 'saas.model.Base',
-
-    fields: [
-        { name: 'username', type: 'string' },
-        { name: 'realname', type: 'string' },
-        { name: 'email', type: 'string' },
-        { name: 'mobile', type: 'string' }
-    ],
-
-    proxy: {
-        api: {
-            prefix: 'Server.people'
-        }
-    },
-
-    statics: {
-        load: function(id, options, session) {
-            var record = Ext.create('saas.model.Account');
-            record.setSession(session);
-            record.load(
-                Ext.apply({ params: { id: id } }, options)
-            );
-        }
-    }
-});

+ 73 - 21
frontend/saas-web/app/model/Session.js

@@ -3,43 +3,95 @@ Ext.define('saas.model.Session', {
 
     fields: [
         { name: 'token', type: 'string' },
-        { name: 'expires', type: 'number' },
+        { name: 'expire', type: 'number' },
         { name: 'timestamp', type: 'number' },
-        { name: 'account', reference: 'Account' }
+        { name: 'span', type: 'number' },
+        { name: 'account' }
     ],
 
     statics: {
         login: function(username, password) {
             return new Ext.Promise(function (resolve, reject) {
-                Server.auth.login({
-                    username: username,
-                    password: password
-                }, function(result, response, success) {
-                    if (!success) {
-                        return reject(result.message);
+				Ext.Ajax.request({
+                    url: '/api/auth/authorize',
+                    params: {
+                        username: username,
+                        password: password
+                    },
+                    method: 'POST',
+                    success: function (response) {
+                        var res = Ext.decode(response.responseText);
+                        if (res.success) {
+                            var session = saas.model.Session.loadData(res.data.token);
+                            session.set('account', res.data.account);
+                            // 服务端与本地存在时间差
+                            session.set('span', session.get('timestamp') - new Date().getTime());
+                            if (!session.isValid()) {
+                                reject({
+                                    message: '登录失败,无效身份令牌'
+                                });
+                            } else {
+                                resolve(session);
+                            }
+                        } else {
+                            Ext.log.error('request failure with code: ', res.code, ', message: ', res.message);
+                            reject(res);
+                        }
+                    },
+                    failure: function (response) {
+                        Ext.log.error('server-side failure with status code: ', response.status);
+                        reject(response);
                     }
-
-                    var session = saas.model.Session.loadData(result);
-                    if (!session.isValid()) {
-                        return reject({ errors: {
-                            username: 'Login failed: invalid session'
-                        }});
-                    }
-
-                    resolve(session);
                 });
             });
+        },
+        switchCompany: function(oldSession, companyId) {
+            return new Ext.Promise(function (resolve, reject) {
+				Ext.Ajax.request({
+                    url: '/api/auth/switch/company',
+                    params: {
+                        companyId: companyId
+                    },
+                    method: 'GET',
+                    headers: {
+                        'Authorization': oldSession.get('token')
+                    },
+                    success: function (response) {
+                        var res = Ext.decode(response.responseText);
+                        if (res.success) {
+                            var newSession = saas.model.Session.loadData(res.data);
+                            newSession.set('account', oldSession.get('account'));
+                            // 服务端与本地存在时间差
+                            newSession.set('span', newSession.get('timestamp') - new Date().getTime());
+                            if (!newSession.isValid()) {
+                                reject({
+                                    message: '切换失败,无效身份令牌'
+                                });
+                            } else {
+                                resolve(newSession);
+                            }
+                        } else {
+                            Ext.log.error('request failure with code: ', res.code, ', message: ', res.message);
+                            reject(res);
+                        }
+                    },
+                    failure: function (response) {
+                        Ext.log.error('server-side failure with status code: ', response.status);
+                        reject(response);
+                    }
+                });
+            });            
         }
     },
 
     isValid: function() {
-        return !Ext.isEmpty(this.get('token'))
-            && this.get('timestamp') + this.get('expires') * 1000 > new Date().getTime();
+        return !Ext.isEmpty(this.get('token')) && this.get('token').length > 128
+            && this.get('timestamp') + this.get('expire') * 1000 > new Date().getTime() + this.get('span');
     },
-
+    
     logout: function() {
         return new Ext.Promise(function (resolve, reject) {
-            Server.auth.logout({}, resolve);
+            resolve({});
         });
     }
 });

+ 54 - 26
frontend/saas-web/app/view/auth/LoginController.js

@@ -7,38 +7,66 @@ Ext.define('saas.view.auth.LoginController', {
     },
 
     onLoginButton: function() {
-        // var me = this,
-        //     form = me.lookup('authdialog'),
-        //     values = form.getValues();
-
-        // me.getView().setMasked(true);
-
-        // saas.model.Session.login(values.username, values.password)
-        //     .then(function(session) {
-        //         me.fireEvent('login', session);
-        //     })
-        //     .catch(function(error) {
-        //         showToast(error);
-        //     })
-        //     .then(function(session) {
-        //         me.getView().setMasked(false);
-        //     });
-        this.fireEvent('login', new saas.model.Session({
-            token: 'XXXXXX',
-            expires: 180000,
-            timestamp: new Date().getTime(),
-            account: new saas.model.Account({
-                username: 'XXXXXX',
-                realname: '张三',
-                email: 'zhangs@gmail.com',
-                mobile: '15812345678'
+        var me = this, view = me.getView(),
+            form = me.lookup('authdialog'),
+            values = form.getValues();
+
+        view.mask('请稍等...');
+
+        saas.model.Session.login(values.username, values.password)
+            .then(function(session) {
+                view.isMasked() && view.unmask();
+                me.getViewModel().set('session', session);
+				var cos = session.get("account").companies;
+				if (cos && cos.length) {
+                    if (cos.length == 1) {
+                        session.get('account').companyId = cos[0].id;
+                        me.fireEvent('login', session);
+                    } else {
+                        Ext.create({
+                            xtype: 'companypicker',
+                            viewModel: {
+                                stores: {
+                                    companies: Ext.create('saas.store.Company', {
+                                        data: cos
+                                    })
+                                }
+                            }
+                        });
+                    }
+				} else {
+                    me.fireEvent('login', session);
+                }
             })
-        }));
+            .catch(function(error) {
+                view.isMasked() && view.unmask();
+                showToast(error.message);
+            });
     },
 
     onWeixinLogin : function() {
     },
 
     onNewAccount:  function() {
+    },
+
+    selectCompany: function(view, record) {
+        var me = this, view = me.getView(), oldSession = me.getViewModel().get('session'),
+            companyId = record.get('id');
+        
+        view.mask('请稍等...');
+
+        saas.model.Session.switchCompany(oldSession, companyId)
+            .then(function(newSession) {
+                newSession.get('account').companyId = companyId;
+				me.fireEvent('login', newSession);
+            })
+            .catch(function(error) {
+                showToast(error.message);
+            })
+            .then(function() {
+                view.isMasked() && view.unmask();
+                view.ownerCt.destroy();
+            });        
     }
 });

+ 0 - 15
frontend/saas-web/app/view/main/List.js

@@ -1,15 +0,0 @@
-/**
- * This view is an example list of people.
- */
-Ext.define('saas.view.main.List', {
-    extend: 'Ext.grid.Panel',
-    xtype: 'mainlist',
-
-    title: 'Personnel',
-
-    columns: [
-        { text: 'Name',  dataIndex: 'name' },
-        { text: 'Email', dataIndex: 'email', flex: 1 },
-        { text: 'Phone', dataIndex: 'phone', flex: 1 }
-    ]
-});

+ 27 - 11
frontend/saas-web/app/view/main/Main.js

@@ -40,39 +40,55 @@ Ext.define('saas.view.main.Main', {
                     id: 'main-navigation-btn',
                     handler: 'onToggleNavigationSize'
                 },
+                {
+                    margin: '0 0 0 8',
+                    xtype: 'tbtext',
+                    bind: {
+                        html: '{company.name}'
+                    }
+                },
                 '->',
                 {
                     iconCls:'x-fa fa-search',
                     ui: 'header',
-                    tooltip: 'See latest search'
+                    tooltip: '搜索'
                 },
                 {
                     iconCls:'x-fa fa-bell',
                     ui: 'header',
-                    tooltip: 'Check your messages'
+                    tooltip: '消息'
                 },
                 {
                     iconCls:'x-fa fa-question',
                     ui: 'header',
-                    tooltip: 'Help / FAQ\'s'
+                    tooltip: '帮助'
                 },
                 {
+                    reference: 'mainprofile',
                     iconCls:'x-fa fa-th-large',
+                    arrowVisible: false,
                     ui: 'header',
-                    tooltip: 'See your profile'
-                },
-                {
-                    xtype: 'tbtext',
-                    text: 'administrator',
-                    cls: 'top-user-name'
+                    tooltip: '账户',
+                    bind: {
+                        text: '{account.realname}'
+                    },
+                    menu: {
+                        items: [{
+                            xtype: 'menuseparator'
+                        }, {
+                            text: '退出',
+                            handler: 'onLogout'
+                        }]
+                    }
                 },
                 {
                     xtype: 'image',
                     cls: 'header-right-profile-image',
                     height: 35,
                     width: 35,
-                    alt:'current user image',
-                    src: 'resources/images/default/user-profile-default.png'
+                    bind: {
+                        src: '{avatarUrl}'
+                    }
                 }
             ]
         },

+ 29 - 0
frontend/saas-web/app/view/main/MainController.js

@@ -7,6 +7,27 @@ Ext.define('saas.view.main.MainController', {
 
     alias: 'controller.main',
 
+    init: function() {
+        this.setCompanyMenu();
+    },
+
+    setCompanyMenu: function() {
+        var me = this, view = me.getView(), viewModel = me.getViewModel(),
+            account = viewModel.get('account'), companies = account && account.companies,
+            companyMenu = view.lookup('mainprofile').getMenu(), items = [];
+        if (companies) {
+            items = companies.map(function(c){
+                return {
+                    text: c.name,
+                    value: c.id,
+                    handler: 'selectCompany',
+                    iconCls: c.id == account.companyId ? 'x-fa fa-check' : ''
+                }
+            });
+        }
+        companyMenu.insert(0, items);
+    },
+
     onToggleNavigationSize: function () {
         var me = this,
         refs = me.getReferences(),
@@ -22,5 +43,13 @@ Ext.define('saas.view.main.MainController', {
         navigationList.el[ope]('nav-collapsed');
 
         navigationList.navCollapsed = navCollapsed;
+    },
+
+    selectCompany: function(item) {
+        this.fireEvent('selectCompany', item.value);
+    },
+
+    onLogout: function() {
+        this.fireEvent('logout');
     }
 });

+ 0 - 1
frontend/saas-web/app/view/main/MainModel.js

@@ -7,6 +7,5 @@ Ext.define('saas.view.main.MainModel', {
     alias: 'viewmodel.main',
 
     data: {
-        
     }
 });

+ 39 - 53
frontend/saas-web/app/view/viewport/ViewportController.js

@@ -7,6 +7,7 @@ Ext.define('saas.view.viewport.ViewportController', {
             '*': {
                 login: 'onLogin',
                 logout: 'onLogout',
+                selectCompany: 'onSelectCompany',
                 unmatchedroute: 'handleUnmatchedRoute'
             }
         }
@@ -17,7 +18,7 @@ Ext.define('saas.view.viewport.ViewportController', {
     },
 
     init: function() {
-        this.initDirect();
+        this.originalRoute = saas.getApplication().getDefaultToken();
         this.restoreSession();
     },
 
@@ -41,7 +42,8 @@ Ext.define('saas.view.viewport.ViewportController', {
     },
 
     showMain: function() {
-        this.showView('main');
+        var me = this;
+        me.showView('main');
     },
 
     // ROUTING
@@ -75,51 +77,14 @@ Ext.define('saas.view.viewport.ViewportController', {
         }
     },
 
-    // EXT DIRECT
-
-    initDirect: function() {
-        // var api = Server.API;
-        // if (!api) {
-        //     Ext.raise('Failed to load Direct API');
-        // }
-
-        // Ext.direct.Manager.addProvider(Ext.applyIf({
-        //     id: 'server',
-        //     listeners: {
-        //         data: 'onDirectData',
-        //         scope: this
-        //     }
-        // }, api));
-    },
-
-    setDirectToken: function(token) {
-        // var provider = Ext.direct.Manager.getProvider('server'),
-        //     headers = provider.getHeaders() || {};
-
-        // if (token) {
-        //     headers['Authorization'] = 'Bearer ' + token;
-        // } else {
-        //     delete headers['Authorization'];
-        // }
-
-        // provider.setHeaders(headers);
-    },
-
-    onDirectData: function(provider, e) {
-        if (e.type !== 'exception') {
-            return;
-        }
-
-        var message = e.message || {};
-        switch (message.code) {
-        case -32098:    // AuthTokenExpired
-        case -32097:    // AuthTokenInvalid
-            // Defer user deauthentication until the current direct transaction is done.
-            Ext.asap(this.terminateSession, this);
-            break;
-        default:
-            break;
+    setRequestToken: function(token) {
+        var headers = Ext.Ajax.getDefaultHeaders() || {};
+        if (token) {
+            headers['Authorization'] = token;
+        } else {
+            delete headers['Authorization'];
         }
+        Ext.Ajax.setDefaultHeaders(headers);
     },
 
     // SESSION MANAGEMENT
@@ -138,20 +103,20 @@ Ext.define('saas.view.viewport.ViewportController', {
     },
 
     initiateSession: function(session) {
-        this.setDirectToken(session.get('token'));
+        this.setRequestToken(session.get('token'));
         this.saveSession(session);
         this.showMain();
     },
 
     terminateSession: function() {
-        this.setDirectToken(null);
+        this.setRequestToken(null);
         this.saveSession(null);
         this.showAuth();
     },
 
     saveSession: function(session) {
         saas.util.State.set('session', session && session.getData(true));
-        // this.getViewModel().set('account', session && session.getAccount(false).getData(true));
+        this.getViewModel().set('account', session && session.get('account'));
         this.session = session;
     },
 
@@ -175,15 +140,36 @@ Ext.define('saas.view.viewport.ViewportController', {
             return false;
         }
 
-        view.setMasked(true);
-        session.logout().catch(function() {
-            // TODO handle errors
+        view.mask();
+        session.logout().catch(function(error) {
+            showToast(error.message);
         }).then(function() {
             me.originalRoute = Ext.History.getToken();
             me.terminateSession();
-            view.setMasked(false);
+            view.unmask();
             me.redirectTo('login', {replace: true});
         });
+    },
+
+    onSelectCompany: function(companyId) {
+        var me = this, view = me.getView(), viewModel = me.getViewModel(), 
+            oldSession = me.session, company = viewModel.get('company');
+
+        if (company.id != companyId) {
+            view.mask('请稍等...');
+
+            saas.model.Session.switchCompany(oldSession, companyId)
+                .then(function(newSession) {
+                    newSession.get('account').companyId = companyId;
+                    me.initiateSession(newSession);
+                })
+                .catch(function(error) {
+                    showToast(error.message);
+                })
+                .then(function() {
+                    view.isMasked() && view.unmask();
+                }); 
+        }        
     }
 });
 

+ 14 - 1
frontend/saas-web/app/view/viewport/ViewportModel.js

@@ -4,5 +4,18 @@ Ext.define('saas.view.viewport.ViewportModel', {
 
     data: {
         account: null
-    }
+    },
+
+    formulas: {
+        company: function (get) {
+            var account = get('account');
+            return account && account.companies.find(function(c){
+                return c.id == account.companyId;
+            });
+        },
+        avatarUrl: function (get) {
+            var account = get('account');
+            return (account && account.avatarUrl) || 'resources/images/default/user-profile-default.png'
+        }
+    }    
 });

+ 10 - 1
frontend/saas-web/overrides/data/Connection.js

@@ -7,13 +7,22 @@ Ext.define('saas.override.data.Connection', {
  
     urlRegexp: /(http|ftp|https):\/\//,
 
+    config: {
+        /**
+         * @cfg {Object} defaultServerHeaders
+         * 与defaultHeaders有区别,只在调用server api的时候才添加的headers
+         */
+        defaultServerHeaders: null
+    },
+
     privates: {
         setupServerOptions: function(options) {
             var serverOptions = Ext.manifest.server, originUrl = options.url;
             if (serverOptions && serverOptions.basePath && !this.urlRegexp.test(originUrl) &&
               (!serverOptions.urlPattern || new RegExp(serverOptions.urlPattern).test(originUrl))) {
-                Ext.apply(options, {
+                Ext.Object.merge(options, {
                     url: serverOptions.basePath + (originUrl.indexOf('/') == 0 ? '' : '/') + originUrl,
+                    headers: this.getDefaultServerHeaders()
                 });
             }
         }

+ 1 - 0
frontend/saas-web/packages/local/theme-default/sass/var/all.scss

@@ -53,3 +53,4 @@ $blank-page-color: dynamic(#cacaca);
 $lightest-color: dynamic(#fff);
 $circle-border-radius: dynamic(50%);
 $base-border-color: dynamic(#ddd);
+$base-over-color: dynamic(#eee);

+ 6 - 0
pom.xml

@@ -40,6 +40,7 @@
         <commons.fileupload.version>1.3.3</commons.fileupload.version>
         <docker.repository>192.168.253.3:4000</docker.repository>
         <docker.registry.name>saas</docker.registry.name>
+        <guava.version>18.0</guava.version>
     </properties>
 
     <repositories>
@@ -322,6 +323,11 @@
                 <artifactId>fastjson</artifactId>
                 <version>${fastjson.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>${guava.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

+ 73 - 68
script/mysql/init/account.sql

@@ -55,6 +55,7 @@ create table `ac_role` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色';
 
 create table `ac_account_role` (
+  company_id int unsigned,
   account_id int unsigned,
   role_id int unsigned,
   primary key(account_id, role_id)
@@ -68,12 +69,14 @@ create table `ac_resource_module` (
 
 create table `ac_resource_group` (
   id int unsigned primary key not null auto_increment,
+  app_id varchar(100) comment '应用',
   module_id int unsigned not null,
   name varchar(100) comment '名称'
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='资源分组';
 
 create table `ac_resource` (
   id int unsigned primary key not null auto_increment,
+  app_id varchar(100) comment '应用',
   group_id int unsigned not null,
   name varchar(1000) comment '名称',
   type varchar(100) comment '类型 MENU,BUTTON,API',
@@ -83,6 +86,8 @@ create table `ac_resource` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='资源';
 
 create table `ac_role_resource` (
+  app_id varchar(100) comment '应用',
+  company_id int unsigned,
   role_id int unsigned,
   resource_id int unsigned,
   primary key(role_id, resource_id)
@@ -108,7 +113,7 @@ insert into ac_role(id,code,name,description,type,company_id,creator_id,create_t
     values (1, 'ROLE_ADMIN', '管理员', '公司管理人员,拥有所有权限', 0, 1, 1, now(), 1, now());
 insert into ac_role(id,code,name,description,type,company_id,creator_id,create_time,updater_id,update_time)
 values (2, 'ROLE_NORMAL', '普通用户', '公司普通用户', 0, 1, 1, now(), 1, now());
-insert into ac_account_role(account_id, role_id) values (1, 1);
+insert into ac_account_role(company_id, account_id, role_id) values (1, 1, 1);
 
 # 资源模块
 insert into ac_resource_module(id, app_id, name) values (1, 'trade-app', '采购');
@@ -117,72 +122,72 @@ insert into ac_resource_module(id, app_id, name) values (3, 'trade-app', '库存
 insert into ac_resource_module(id, app_id, name) values (4, 'trade-app', '资金');
 insert into ac_resource_module(id, app_id, name) values (5, 'trade-app', '资料');
 # 资源组
-insert into ac_resource_group(id, module_id, name) values (1, 1, '采购单');
-insert into ac_resource_group(id, module_id, name) values (2, 1, '采购验收单');
-insert into ac_resource_group(id, module_id, name) values (3, 1, '采购验退单');
-insert into ac_resource_group(id, module_id, name) values (4, 1, '采购询价单');
-insert into ac_resource_group(id, module_id, name) values (5, 1, '采购明细表');
-insert into ac_resource_group(id, module_id, name) values (6, 1, '采购付款一览表');
-insert into ac_resource_group(id, module_id, name) values (7, 2, '销售订单');
-insert into ac_resource_group(id, module_id, name) values (8, 2, '销售出货单');
-insert into ac_resource_group(id, module_id, name) values (9, 2, '销售退货单');
-insert into ac_resource_group(id, module_id, name) values (10, 2, '以销定购');
-insert into ac_resource_group(id, module_id, name) values (11, 2, '销售明细表');
-insert into ac_resource_group(id, module_id, name) values (12, 2, '销售收款一览表');
-insert into ac_resource_group(id, module_id, name) values (13, 2, '销售利润表');
-insert into ac_resource_group(id, module_id, name) values (14, 3, '调拨单');
-insert into ac_resource_group(id, module_id, name) values (15, 3, '制造单');
-insert into ac_resource_group(id, module_id, name) values (16, 3, '其它入库单');
-insert into ac_resource_group(id, module_id, name) values (17, 3, '其它出库单');
-insert into ac_resource_group(id, module_id, name) values (18, 3, '盘点单');
-insert into ac_resource_group(id, module_id, name) values (19, 3, '物料出入库明细表');
-insert into ac_resource_group(id, module_id, name) values (20, 3, '物料收发汇总表');
-insert into ac_resource_group(id, module_id, name) values (21, 3, '物料库存数量金额表');
-insert into ac_resource_group(id, module_id, name) values (22, 4, '付款单');
-insert into ac_resource_group(id, module_id, name) values (23, 4, '收款单');
-insert into ac_resource_group(id, module_id, name) values (24, 4, '核销单');
-insert into ac_resource_group(id, module_id, name) values (25, 4, '其它收支单');
-insert into ac_resource_group(id, module_id, name) values (26, 4, '资金转存');
-insert into ac_resource_group(id, module_id, name) values (27, 4, '供应商对账单');
-insert into ac_resource_group(id, module_id, name) values (28, 4, '应付账款明细表');
-insert into ac_resource_group(id, module_id, name) values (29, 4, '客户对账单');
-insert into ac_resource_group(id, module_id, name) values (30, 4, '应收款明细表');
-insert into ac_resource_group(id, module_id, name) values (31, 4, '资金账户余额表');
-insert into ac_resource_group(id, module_id, name) values (32, 5, '客户资料');
-insert into ac_resource_group(id, module_id, name) values (33, 5, '供应商管理');
-insert into ac_resource_group(id, module_id, name) values (34, 5, '商品管理');
-insert into ac_resource_group(id, module_id, name) values (35, 5, '仓库管理');
-insert into ac_resource_group(id, module_id, name) values (36, 5, '职员管理');
-insert into ac_resource_group(id, module_id, name) values (37, 5, '账户管理');
-insert into ac_resource_group(id, module_id, name) values (38, 5, '发货地址管理');
-insert into ac_resource_group(id, module_id, name) values (39, 5, '客户类别');
-insert into ac_resource_group(id, module_id, name) values (40, 5, '供应商类别');
-insert into ac_resource_group(id, module_id, name) values (41, 5, '商品类别');
-insert into ac_resource_group(id, module_id, name) values (42, 5, '支出类别');
-insert into ac_resource_group(id, module_id, name) values (43, 5, '收入类别');
-insert into ac_resource_group(id, module_id, name) values (44, 5, '物料品牌');
-insert into ac_resource_group(id, module_id, name) values (45, 5, '计量单位');
-insert into ac_resource_group(id, module_id, name) values (46, 5, '结算方式');
-insert into ac_resource_group(id, module_id, name) values (47, 5, '辅助属性');
-insert into ac_resource_group(id, module_id, name) values (48, 5, '客户物料编码');
-insert into ac_resource_group(id, module_id, name) values (49, 5, '单据编码规则');
+insert into ac_resource_group(id, app_id, module_id, name) values (1, 'trade-app', 1, '采购单');
+insert into ac_resource_group(id, app_id, module_id, name) values (2, 'trade-app', 1, '采购验收单');
+insert into ac_resource_group(id, app_id, module_id, name) values (3, 'trade-app', 1, '采购验退单');
+insert into ac_resource_group(id, app_id, module_id, name) values (4, 'trade-app', 1, '采购询价单');
+insert into ac_resource_group(id, app_id, module_id, name) values (5, 'trade-app', 1, '采购明细表');
+insert into ac_resource_group(id, app_id, module_id, name) values (6, 'trade-app', 1, '采购付款一览表');
+insert into ac_resource_group(id, app_id, module_id, name) values (7, 'trade-app', 2, '销售订单');
+insert into ac_resource_group(id, app_id, module_id, name) values (8, 'trade-app', 2, '销售出货单');
+insert into ac_resource_group(id, app_id, module_id, name) values (9, 'trade-app', 2, '销售退货单');
+insert into ac_resource_group(id, app_id, module_id, name) values (10, 'trade-app', 2, '以销定购');
+insert into ac_resource_group(id, app_id, module_id, name) values (11, 'trade-app', 2, '销售明细表');
+insert into ac_resource_group(id, app_id, module_id, name) values (12, 'trade-app', 2, '销售收款一览表');
+insert into ac_resource_group(id, app_id, module_id, name) values (13, 'trade-app', 2, '销售利润表');
+insert into ac_resource_group(id, app_id, module_id, name) values (14, 'trade-app', 3, '调拨单');
+insert into ac_resource_group(id, app_id, module_id, name) values (15, 'trade-app', 3, '制造单');
+insert into ac_resource_group(id, app_id, module_id, name) values (16, 'trade-app', 3, '其它入库单');
+insert into ac_resource_group(id, app_id, module_id, name) values (17, 'trade-app', 3, '其它出库单');
+insert into ac_resource_group(id, app_id, module_id, name) values (18, 'trade-app', 3, '盘点单');
+insert into ac_resource_group(id, app_id, module_id, name) values (19, 'trade-app', 3, '物料出入库明细表');
+insert into ac_resource_group(id, app_id, module_id, name) values (20, 'trade-app', 3, '物料收发汇总表');
+insert into ac_resource_group(id, app_id, module_id, name) values (21, 'trade-app', 3, '物料库存数量金额表');
+insert into ac_resource_group(id, app_id, module_id, name) values (22, 'trade-app', 4, '付款单');
+insert into ac_resource_group(id, app_id, module_id, name) values (23, 'trade-app', 4, '收款单');
+insert into ac_resource_group(id, app_id, module_id, name) values (24, 'trade-app', 4, '核销单');
+insert into ac_resource_group(id, app_id, module_id, name) values (25, 'trade-app', 4, '其它收支单');
+insert into ac_resource_group(id, app_id, module_id, name) values (26, 'trade-app', 4, '资金转存');
+insert into ac_resource_group(id, app_id, module_id, name) values (27, 'trade-app', 4, '供应商对账单');
+insert into ac_resource_group(id, app_id, module_id, name) values (28, 'trade-app', 4, '应付账款明细表');
+insert into ac_resource_group(id, app_id, module_id, name) values (29, 'trade-app', 4, '客户对账单');
+insert into ac_resource_group(id, app_id, module_id, name) values (30, 'trade-app', 4, '应收款明细表');
+insert into ac_resource_group(id, app_id, module_id, name) values (31, 'trade-app', 4, '资金账户余额表');
+insert into ac_resource_group(id, app_id, module_id, name) values (32, 'trade-app', 5, '客户资料');
+insert into ac_resource_group(id, app_id, module_id, name) values (33, 'trade-app', 5, '供应商管理');
+insert into ac_resource_group(id, app_id, module_id, name) values (34, 'trade-app', 5, '商品管理');
+insert into ac_resource_group(id, app_id, module_id, name) values (35, 'trade-app', 5, '仓库管理');
+insert into ac_resource_group(id, app_id, module_id, name) values (36, 'trade-app', 5, '职员管理');
+insert into ac_resource_group(id, app_id, module_id, name) values (37, 'trade-app', 5, '账户管理');
+insert into ac_resource_group(id, app_id, module_id, name) values (38, 'trade-app', 5, '发货地址管理');
+insert into ac_resource_group(id, app_id, module_id, name) values (39, 'trade-app', 5, '客户类别');
+insert into ac_resource_group(id, app_id, module_id, name) values (40, 'trade-app', 5, '供应商类别');
+insert into ac_resource_group(id, app_id, module_id, name) values (41, 'trade-app', 5, '商品类别');
+insert into ac_resource_group(id, app_id, module_id, name) values (42, 'trade-app', 5, '支出类别');
+insert into ac_resource_group(id, app_id, module_id, name) values (43, 'trade-app', 5, '收入类别');
+insert into ac_resource_group(id, app_id, module_id, name) values (44, 'trade-app', 5, '物料品牌');
+insert into ac_resource_group(id, app_id, module_id, name) values (45, 'trade-app', 5, '计量单位');
+insert into ac_resource_group(id, app_id, module_id, name) values (46, 'trade-app', 5, '结算方式');
+insert into ac_resource_group(id, app_id, module_id, name) values (47, 'trade-app', 5, '辅助属性');
+insert into ac_resource_group(id, app_id, module_id, name) values (48, 'trade-app', 5, '客户物料编码');
+insert into ac_resource_group(id, app_id, module_id, name) values (49, 'trade-app', 5, '单据编码规则');
 # 资源
-insert into ac_resource(id, name, group_id, type, url, method, classify) values
-  (1,'查询',1,'MENU','/api/purchase/purchase/list','GET','QUERY');
-insert into ac_resource(id, name, group_id, type, url, method, classify) values
-  (2,'新增',1,'MENU','/api/purchase/purchase/save','POST','ADD');
-insert into ac_resource(id, name, group_id, type, url, method, classify) values
-  (3,'修改',1,'BUTTON','/api/purchase/purchase/save','POST','UPDATE');
-insert into ac_resource(id, name, group_id, type, url, method, classify) values
-  (4,'审核',1,'BUTTON','/api/purchase/purchase/audit','POST','AUDIT');
-insert into ac_resource(id, name, group_id, type, url, method, classify) values
-  (5,'审核',1,'BUTTON','/api/purchase/purchase/batchAudit','POST','AUDIT');
-insert into ac_resource(id, name, group_id, type, url, method, classify) values
-  (6,'反审核',1,'BUTTON','/api/purchase/purchase/unAudit','POST','UNAUDIT');
-insert into ac_resource(id, name, group_id, type, url, method, classify) values
-  (7,'反审核',1,'BUTTON','/api/purchase/purchase/batchUnAudit','POST','UNAUDIT');
-insert into ac_resource(id, name, group_id, type, url, method, classify) values
-  (8,'删除',1,'BUTTON','/api/purchase/purchase/delete','GET','DELETE');
-insert into ac_resource(id, name, group_id, type, url, method, classify) values
-  (9,'打印',1,'BUTTON','/api/purchase/purchase/print','GET','PRINT');
+insert into ac_resource(id, app_id, name, group_id, type, url, method, classify) values
+  (1,'trade-app', '查询',1,'MENU','/api/purchase/purchase/list','GET','QUERY');
+insert into ac_resource(id, app_id, name, group_id, type, url, method, classify) values
+  (2,'trade-app', '新增',1,'MENU','/api/purchase/purchase/save','POST','ADD');
+insert into ac_resource(id, app_id, name, group_id, type, url, method, classify) values
+  (3,'trade-app', '修改',1,'BUTTON','/api/purchase/purchase/save','POST','UPDATE');
+insert into ac_resource(id, app_id, name, group_id, type, url, method, classify) values
+  (4,'trade-app', '审核',1,'BUTTON','/api/purchase/purchase/audit','POST','AUDIT');
+insert into ac_resource(id, app_id, name, group_id, type, url, method, classify) values
+  (5,'trade-app', '审核',1,'BUTTON','/api/purchase/purchase/batchAudit','POST','AUDIT');
+insert into ac_resource(id, app_id, name, group_id, type, url, method, classify) values
+  (6,'trade-app', '反审核',1,'BUTTON','/api/purchase/purchase/unAudit','POST','UNAUDIT');
+insert into ac_resource(id, app_id, name, group_id, type, url, method, classify) values
+  (7,'trade-app', '反审核',1,'BUTTON','/api/purchase/purchase/batchUnAudit','POST','UNAUDIT');
+insert into ac_resource(id, app_id, name, group_id, type, url, method, classify) values
+  (8,'trade-app', '删除',1,'BUTTON','/api/purchase/purchase/delete','GET','DELETE');
+insert into ac_resource(id, app_id, name, group_id, type, url, method, classify) values
+  (9,'trade-app', '打印',1,'BUTTON','/api/purchase/purchase/print','GET','PRINT');