Explorar o código

增加机构管理、部署管理、授权管理等功能

luoshi %!s(int64=2) %!d(string=hai) anos
pai
achega
96c302706d
Modificáronse 37 ficheiros con 1530 adicións e 14 borrados
  1. 4 0
      pom.xml
  2. 3 0
      src/main/java/com/qmth/ops/api/constants/OpsApiConstants.java
  3. 118 0
      src/main/java/com/qmth/ops/api/controller/admin/DeployController.java
  4. 3 3
      src/main/java/com/qmth/ops/api/controller/admin/EnvController.java
  5. 72 0
      src/main/java/com/qmth/ops/api/controller/admin/OrgController.java
  6. 2 2
      src/main/java/com/qmth/ops/api/controller/admin/PropertyController.java
  7. 2 2
      src/main/java/com/qmth/ops/api/controller/admin/UserController.java
  8. 25 0
      src/main/java/com/qmth/ops/api/controller/open/OpenAppDeployController.java
  9. 97 0
      src/main/java/com/qmth/ops/api/dto/DeployForm.java
  10. 23 0
      src/main/java/com/qmth/ops/api/security/AccessDeploy.java
  11. 31 0
      src/main/java/com/qmth/ops/api/security/OpenAuthorizationService.java
  12. 43 0
      src/main/java/com/qmth/ops/api/vo/AppVO.java
  13. 4 4
      src/main/java/com/qmth/ops/api/vo/CodeNameVO.java
  14. 24 0
      src/main/java/com/qmth/ops/api/vo/DeployPageVO.java
  15. 124 0
      src/main/java/com/qmth/ops/api/vo/DeployVO.java
  16. 1 1
      src/main/java/com/qmth/ops/api/vo/EnvVO.java
  17. 25 0
      src/main/java/com/qmth/ops/api/vo/OrgTypeVO.java
  18. 18 0
      src/main/java/com/qmth/ops/api/vo/SuccessVO.java
  19. 1 1
      src/main/java/com/qmth/ops/api/vo/UserVO.java
  20. 8 0
      src/main/java/com/qmth/ops/biz/dao/DeployDao.java
  21. 8 0
      src/main/java/com/qmth/ops/biz/dao/DeployDeviceDao.java
  22. 8 0
      src/main/java/com/qmth/ops/biz/dao/DeployOrgDao.java
  23. 15 0
      src/main/java/com/qmth/ops/biz/dao/OrgDao.java
  24. 120 0
      src/main/java/com/qmth/ops/biz/domain/Deploy.java
  25. 47 0
      src/main/java/com/qmth/ops/biz/domain/DeployDevice.java
  26. 20 0
      src/main/java/com/qmth/ops/biz/domain/DeployMode.java
  27. 27 0
      src/main/java/com/qmth/ops/biz/domain/DeployOrg.java
  28. 140 0
      src/main/java/com/qmth/ops/biz/domain/Org.java
  29. 57 0
      src/main/java/com/qmth/ops/biz/query/DeployQuery.java
  30. 82 0
      src/main/java/com/qmth/ops/biz/query/OrgQuery.java
  31. 56 0
      src/main/java/com/qmth/ops/biz/service/DeployService.java
  32. 70 0
      src/main/java/com/qmth/ops/biz/service/FileService.java
  33. 107 0
      src/main/java/com/qmth/ops/biz/service/LicenseService.java
  34. 53 0
      src/main/java/com/qmth/ops/biz/service/OrgService.java
  35. 57 0
      src/main/java/com/qmth/ops/biz/utils/OrgSubTypeSetHandler.java
  36. 3 1
      src/main/resources/application.properties
  37. 32 0
      src/main/resources/mapper/OrgMapper.xml

+ 4 - 0
pom.xml

@@ -38,6 +38,10 @@
             <groupId>com.qmth.boot</groupId>
             <artifactId>core-fss</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-solar</artifactId>
+        </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>

+ 3 - 0
src/main/java/com/qmth/ops/api/constants/OpsApiConstants.java

@@ -7,4 +7,7 @@ public interface OpsApiConstants {
     String ADMIN_URI_PREFIX = ApiConstant.DEFAULT_URI_PREFIX + "/admin";
 
     String EXPORT_URI_PREFIX = ApiConstant.DEFAULT_URI_PREFIX + "/export";
+
+    String OPEN_URI_PREFIX = ApiConstant.DEFAULT_URI_PREFIX + "/open";
+
 }

+ 118 - 0
src/main/java/com/qmth/ops/api/controller/admin/DeployController.java

@@ -0,0 +1,118 @@
+package com.qmth.ops.api.controller.admin;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.annotation.BOOL;
+import com.qmth.boot.api.exception.ApiException;
+import com.qmth.boot.tools.device.DeviceInfo;
+import com.qmth.ops.api.constants.OpsApiConstants;
+import com.qmth.ops.api.dto.DeployForm;
+import com.qmth.ops.api.vo.CodeNameVO;
+import com.qmth.ops.api.vo.DeployPageVO;
+import com.qmth.ops.api.vo.DeployVO;
+import com.qmth.ops.api.vo.SuccessVO;
+import com.qmth.ops.biz.domain.Deploy;
+import com.qmth.ops.biz.domain.DeployMode;
+import com.qmth.ops.biz.query.DeployQuery;
+import com.qmth.ops.biz.service.AppService;
+import com.qmth.ops.biz.service.DeployService;
+import com.qmth.ops.biz.service.FileService;
+import com.qmth.ops.biz.service.LicenseService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+
+@RestController
+@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX + "/deploy")
+public class DeployController {
+
+    @Resource
+    private DeployService deployService;
+
+    @Resource
+    private AppService appService;
+
+    @Resource
+    private FileService fileService;
+
+    @Resource
+    private LicenseService licenseService;
+
+    @RequestMapping("/modes")
+    @Aac(auth = BOOL.FALSE)
+    public Object getModes() {
+        return Arrays.stream(DeployMode.values()).map(item -> new CodeNameVO(item.getCode(), item.getName())).toArray();
+    }
+
+    @RequestMapping("/query")
+    public DeployPageVO query(DeployQuery query) {
+        return new DeployPageVO(deployService.query(query), appService);
+    }
+
+    @RequestMapping("/insert")
+    public DeployVO insert(@Validated(DeployForm.InsertGroup.class) @RequestBody DeployForm form) {
+        return new DeployVO(deployService.insert(form.build()), appService);
+    }
+
+    @RequestMapping("/update")
+    public DeployVO update(@Validated(DeployForm.UpdateGroup.class) @RequestBody DeployForm form) {
+        return new DeployVO(deployService.update(form.build()), appService);
+    }
+
+    @RequestMapping("/device/save")
+    public Object saveDevice(@RequestParam Long id, @RequestParam MultipartFile deviceInfo,
+            @RequestParam(required = false) String remark) throws Exception {
+        Deploy deploy = deployService.findById(id);
+        if (deploy != null) {
+            DeviceInfo info = licenseService.parseDeviceInfo(deviceInfo);
+            if (info == null) {
+                throw new ApiException(HttpStatus.BAD_REQUEST, 400200, "DeviceInfo parse faile", null);
+            }
+            String deviceId = info.uuid();
+            fileService.uploadDeviceInfo(deviceId, info);
+            deployService.bindDevice(id, deviceId, StringUtils.trimToEmpty(remark));
+            return new SuccessVO(true);
+        } else {
+            throw new ApiException(HttpStatus.BAD_REQUEST, 400100, "deploy unexists", null);
+        }
+    }
+
+    @RequestMapping("/device/delete")
+    public Object deleteDevice(@RequestParam Long id, @RequestParam String deviceId) throws Exception {
+        Deploy deploy = deployService.findById(id);
+        if (deploy != null) {
+            deployService.unbindDevice(appId, deviceId);
+            return new SuccessVO(true);
+        } else {
+            throw new ApiException(HttpStatus.BAD_REQUEST, 400100, "deploy unexists", null);
+        }
+    }
+
+    @RequestMapping("/device/list")
+    public Object listDevice(@RequestParam Long id) throws Exception {
+        return deployService.listDevice(query);
+    }
+
+    @RequestMapping("/device/info")
+    public Object getDeviceInfo(@RequestParam String deviceId) throws Exception {
+        return fileService.getDeviceInfo(deviceId).toString();
+    }
+
+    @RequestMapping("/license/download")
+    public void licenseDownload(HttpServletResponse response, @RequestParam Long id,
+            @RequestParam(required = false) String deviceId, @RequestParam(required = false) String version)
+            throws Exception {
+        response.setContentType("application/octet-stream; charset=utf-8");
+        response.setHeader("Content-Disposition", "attachment; filename=app.lic");
+        licenseService.buildLicense(deployService.findById(id), deviceId, version, response.getOutputStream());
+    }
+
+}

+ 3 - 3
src/main/java/com/qmth/ops/api/controller/admin/EnvController.java

@@ -3,9 +3,9 @@ package com.qmth.ops.api.controller.admin;
 import com.qmth.boot.api.annotation.Aac;
 import com.qmth.boot.api.annotation.BOOL;
 import com.qmth.ops.api.constants.OpsApiConstants;
-import com.qmth.ops.api.dto.CodeNameBean;
-import com.qmth.ops.api.dto.EnvVO;
 import com.qmth.ops.api.security.AdminSession;
+import com.qmth.ops.api.vo.CodeNameVO;
+import com.qmth.ops.api.vo.EnvVO;
 import com.qmth.ops.biz.domain.Env;
 import com.qmth.ops.biz.domain.EnvType;
 import com.qmth.ops.biz.domain.Role;
@@ -38,7 +38,7 @@ public class EnvController {
     @RequestMapping("/types")
     @Aac(auth = BOOL.FALSE)
     public Object types() {
-        return Arrays.stream(EnvType.values()).map(item -> new CodeNameBean(item.getCode(), item.getName())).toArray();
+        return Arrays.stream(EnvType.values()).map(item -> new CodeNameVO(item.getCode(), item.getName())).toArray();
     }
 
     @PostMapping("/insert")

+ 72 - 0
src/main/java/com/qmth/ops/api/controller/admin/OrgController.java

@@ -0,0 +1,72 @@
+package com.qmth.ops.api.controller.admin;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.annotation.BOOL;
+import com.qmth.boot.core.solar.enums.OrgType;
+import com.qmth.ops.api.constants.OpsApiConstants;
+import com.qmth.ops.api.vo.OrgTypeVO;
+import com.qmth.ops.api.vo.SuccessVO;
+import com.qmth.ops.biz.domain.Org;
+import com.qmth.ops.biz.query.OrgQuery;
+import com.qmth.ops.biz.service.FileService;
+import com.qmth.ops.biz.service.OrgService;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+
+@RestController
+@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX + "/org")
+public class OrgController {
+
+    @Resource
+    private OrgService orgService;
+
+    @Resource
+    private FileService fileService;
+
+    @RequestMapping("/types")
+    @Aac(auth = BOOL.FALSE)
+    public Object getTypes() {
+        return Arrays.stream(OrgType.values()).map(OrgTypeVO::new).toArray();
+    }
+
+    @RequestMapping("/query")
+    public OrgQuery query(OrgQuery query) {
+        query = orgService.findByQuery(query);
+        query.getRecords().forEach(org -> org.setAccessSecret("***"));
+        return query;
+    }
+
+    @RequestMapping("/insert")
+    public Org insert(Org org, @RequestParam(required = false) MultipartFile logoFile,
+            @RequestParam(required = false) String logoMd5) throws Exception {
+        org = orgService.insert(org);
+        if (logoFile != null && logoMd5 != null) {
+            fileService.uploadOrgLogo(org, logoFile, logoMd5);
+            org = orgService.update(org);
+        }
+        org.setAccessSecret("***");
+        return org;
+    }
+
+    @RequestMapping("/update")
+    public Object update(Org org, @RequestParam(required = false) MultipartFile logoFile,
+            @RequestParam(required = false) String logoMd5) throws Exception {
+        org = orgService.update(org);
+        if (logoFile != null && logoMd5 != null) {
+            fileService.uploadOrgLogo(org, logoFile, logoMd5);
+            org = orgService.update(org);
+        }
+        org.setAccessSecret("***");
+        return org;
+    }
+
+    @RequestMapping("/toggle")
+    public Object toggle(@RequestParam Long id, @RequestParam Boolean enable) {
+        return new SuccessVO(orgService.toggle(id, enable));
+    }
+}

+ 2 - 2
src/main/java/com/qmth/ops/api/controller/admin/PropertyController.java

@@ -4,8 +4,8 @@ import com.qmth.boot.api.annotation.Aac;
 import com.qmth.boot.api.annotation.BOOL;
 import com.qmth.ops.api.binder.FileFormatBinder;
 import com.qmth.ops.api.constants.OpsApiConstants;
-import com.qmth.ops.api.dto.CodeNameBean;
 import com.qmth.ops.api.security.AdminSession;
+import com.qmth.ops.api.vo.CodeNameVO;
 import com.qmth.ops.biz.domain.*;
 import com.qmth.ops.biz.service.*;
 import org.springframework.web.bind.WebDataBinder;
@@ -55,7 +55,7 @@ public class PropertyController {
     @RequestMapping("/modes")
     @Aac(auth = BOOL.FALSE)
     public Object listModes() {
-        return Arrays.stream(PropertyMode.values()).map(model -> new CodeNameBean(model.getCode(), model.getName()))
+        return Arrays.stream(PropertyMode.values()).map(model -> new CodeNameVO(model.getCode(), model.getName()))
                 .toArray();
     }
 

+ 2 - 2
src/main/java/com/qmth/ops/api/controller/admin/UserController.java

@@ -5,10 +5,10 @@ import com.qmth.boot.api.annotation.Aac;
 import com.qmth.boot.api.annotation.BOOL;
 import com.qmth.boot.core.exception.ParameterException;
 import com.qmth.ops.api.constants.OpsApiConstants;
-import com.qmth.ops.api.dto.CodeNameBean;
 import com.qmth.ops.api.dto.LoginResult;
 import com.qmth.ops.api.dto.UserForm;
 import com.qmth.ops.api.security.AdminSession;
+import com.qmth.ops.api.vo.CodeNameVO;
 import com.qmth.ops.biz.domain.Role;
 import com.qmth.ops.biz.domain.User;
 import com.qmth.ops.biz.query.UserQuery;
@@ -50,7 +50,7 @@ public class UserController {
     @RequestMapping("/roles")
     @Aac(auth = BOOL.FALSE)
     public Object getRoles() {
-        return Arrays.stream(Role.values()).map(role -> new CodeNameBean(role.getCode(), role.getName())).toArray();
+        return Arrays.stream(Role.values()).map(role -> new CodeNameVO(role.getCode(), role.getName())).toArray();
     }
 
     @PostMapping("/query")

+ 25 - 0
src/main/java/com/qmth/ops/api/controller/open/OpenAppDeployController.java

@@ -0,0 +1,25 @@
+package com.qmth.ops.api.controller.open;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.annotation.BOOL;
+import com.qmth.boot.core.solar.model.AppInfo;
+import com.qmth.boot.tools.signature.SignatureType;
+import com.qmth.ops.api.constants.OpsApiConstants;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(OpsApiConstants.OPEN_URI_PREFIX)
+@Aac(auth = BOOL.TRUE, signType = SignatureType.SECRET)
+public class OpenAppDeployController {
+
+    @PostMapping("/app/info")
+    @Aac(auth = BOOL.FALSE)
+    public AppInfo getAppInfo() {
+        AppInfo info = new AppInfo();
+        info.setId(123L);
+        info.setName("测试应用");
+        return info;
+    }
+}

+ 97 - 0
src/main/java/com/qmth/ops/api/dto/DeployForm.java

@@ -0,0 +1,97 @@
+package com.qmth.ops.api.dto;
+
+import com.qmth.boot.core.solar.model.AppControl;
+import com.qmth.ops.biz.domain.Deploy;
+import com.qmth.ops.biz.domain.DeployMode;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Set;
+
+@Validated
+public class DeployForm {
+
+    public interface InsertGroup {
+
+    }
+
+    public interface UpdateGroup {
+
+    }
+
+    @NotNull(message = "id不能为空", groups = UpdateGroup.class)
+    private Long id;
+
+    @NotBlank(message = "appId不能为空", groups = InsertGroup.class)
+    private Long appId;
+
+    @NotBlank(message = "name不能为空", groups = InsertGroup.class)
+    private String name;
+
+    @NotBlank(message = "mode不能为空", groups = InsertGroup.class)
+    private DeployMode mode;
+
+    private AppControl control;
+
+    private Set<String> ipAllow;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getAppId() {
+        return appId;
+    }
+
+    public void setAppId(Long appId) {
+        this.appId = appId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public DeployMode getMode() {
+        return mode;
+    }
+
+    public void setMode(DeployMode mode) {
+        this.mode = mode;
+    }
+
+    public AppControl getControl() {
+        return control;
+    }
+
+    public void setControl(AppControl control) {
+        this.control = control;
+    }
+
+    public Set<String> getIpAllow() {
+        return ipAllow;
+    }
+
+    public void setIpAllow(Set<String> ipAllow) {
+        this.ipAllow = ipAllow;
+    }
+
+    public Deploy build() {
+        Deploy deploy = new Deploy();
+        deploy.setId(id);
+        deploy.setAppId(appId);
+        deploy.setName(name);
+        deploy.setMode(mode);
+        deploy.setControl(control);
+        deploy.setIpAllow(ipAllow);
+        return deploy;
+    }
+}

+ 23 - 0
src/main/java/com/qmth/ops/api/security/AccessDeploy.java

@@ -0,0 +1,23 @@
+package com.qmth.ops.api.security;
+
+import com.qmth.boot.core.security.model.AccessEntity;
+import com.qmth.ops.biz.domain.Deploy;
+
+public class AccessDeploy implements AccessEntity {
+
+    private Deploy deploy;
+
+    public AccessDeploy(Deploy deploy) {
+        this.deploy = deploy;
+    }
+
+    @Override
+    public String getIdentity() {
+        return deploy.getAccessKey();
+    }
+
+    @Override
+    public String getSecret() {
+        return deploy.getAccessSecret();
+    }
+}

+ 31 - 0
src/main/java/com/qmth/ops/api/security/OpenAuthorizationService.java

@@ -0,0 +1,31 @@
+package com.qmth.ops.api.security;
+
+import com.qmth.boot.core.security.annotation.AuthorizationComponent;
+import com.qmth.boot.core.security.service.AuthorizationService;
+import com.qmth.boot.tools.signature.SignatureType;
+import com.qmth.ops.api.constants.OpsApiConstants;
+import com.qmth.ops.biz.domain.Deploy;
+import com.qmth.ops.biz.service.DeployService;
+
+import javax.annotation.Resource;
+
+@AuthorizationComponent(prefix = OpsApiConstants.OPEN_URI_PREFIX, type = SignatureType.SECRET)
+public class OpenAuthorizationService implements AuthorizationService<AccessDeploy>, OpsApiConstants {
+
+    @Resource
+    private DeployService deployService;
+
+    @Override
+    public AccessDeploy findByIdentity(String identity, SignatureType signatureType, String path) {
+        Deploy deploy = deployService.findByAccessKey(identity);
+        if (deploy != null) {
+            return new AccessDeploy(deploy);
+        }
+        return null;
+    }
+
+    @Override
+    public boolean hasPermission(AccessDeploy accessDeploy, String path) {
+        return true;
+    }
+}

+ 43 - 0
src/main/java/com/qmth/ops/api/vo/AppVO.java

@@ -0,0 +1,43 @@
+package com.qmth.ops.api.vo;
+
+import com.qmth.ops.biz.domain.App;
+
+public class AppVO {
+
+    private Long id;
+
+    private String code;
+
+    private String name;
+
+    public AppVO(App app) {
+        this.id = app.getId();
+        this.code = app.getCode();
+        this.name = app.getName();
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+}

+ 4 - 4
src/main/java/com/qmth/ops/api/dto/CodeNameBean.java → src/main/java/com/qmth/ops/api/vo/CodeNameVO.java

@@ -1,16 +1,16 @@
-package com.qmth.ops.api.dto;
+package com.qmth.ops.api.vo;
 
-public class CodeNameBean {
+public class CodeNameVO {
 
     private String code;
 
     private String name;
 
-    public CodeNameBean() {
+    public CodeNameVO() {
 
     }
 
-    public CodeNameBean(String code, String name) {
+    public CodeNameVO(String code, String name) {
         this.code = code;
         this.name = name;
     }

+ 24 - 0
src/main/java/com/qmth/ops/api/vo/DeployPageVO.java

@@ -0,0 +1,24 @@
+package com.qmth.ops.api.vo;
+
+import com.qmth.boot.mybatis.query.BaseQuery;
+import com.qmth.ops.biz.query.DeployQuery;
+import com.qmth.ops.biz.service.AppService;
+
+import java.util.stream.Collectors;
+
+public class DeployPageVO extends BaseQuery<DeployVO> {
+
+    private static final long serialVersionUID = -6041621112178518570L;
+
+    public DeployPageVO(DeployQuery query, AppService appService) {
+        setRecords(query.getRecords().stream().map(deploy -> new DeployVO(deploy, appService))
+                .collect(Collectors.toList()));
+        setPageNumber(query.getPageNumber());
+        setPageSize(query.getPageSize());
+        setTotal(query.getTotal());
+        setCurrent(query.getCurrent());
+        setPages(query.getPages());
+        setSize(query.getSize());
+    }
+
+}

+ 124 - 0
src/main/java/com/qmth/ops/api/vo/DeployVO.java

@@ -0,0 +1,124 @@
+package com.qmth.ops.api.vo;
+
+import com.qmth.boot.core.solar.model.AppControl;
+import com.qmth.ops.biz.domain.Deploy;
+import com.qmth.ops.biz.domain.DeployMode;
+import com.qmth.ops.biz.service.AppService;
+
+import java.util.Set;
+
+public class DeployVO {
+
+    private Long id;
+
+    private AppVO app;
+
+    private String name;
+
+    private DeployMode mode;
+
+    private String accessKey;
+
+    private String accessSecret;
+
+    private AppControl control;
+
+    private Set<String> ipAllow;
+
+    private Long createTime;
+
+    private Long updateTime;
+
+    public DeployVO(Deploy deploy, AppService appService) {
+        this.id = deploy.getId();
+        this.app = new AppVO(appService.getById(deploy.getAppId()));
+        this.name = deploy.getName();
+        this.mode = deploy.getMode();
+        this.accessKey = deploy.getAccessKey();
+        this.accessSecret = deploy.getAccessSecret();
+        this.control = deploy.getControl();
+        this.ipAllow = deploy.getIpAllow();
+        this.createTime = deploy.getCreateTime();
+        this.updateTime = deploy.getUpdateTime();
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public AppVO getApp() {
+        return app;
+    }
+
+    public void setApp(AppVO app) {
+        this.app = app;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public DeployMode getMode() {
+        return mode;
+    }
+
+    public void setMode(DeployMode mode) {
+        this.mode = mode;
+    }
+
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    public void setAccessKey(String accessKey) {
+        this.accessKey = accessKey;
+    }
+
+    public String getAccessSecret() {
+        return accessSecret;
+    }
+
+    public void setAccessSecret(String accessSecret) {
+        this.accessSecret = accessSecret;
+    }
+
+    public AppControl getControl() {
+        return control;
+    }
+
+    public void setControl(AppControl control) {
+        this.control = control;
+    }
+
+    public Set<String> getIpAllow() {
+        return ipAllow;
+    }
+
+    public void setIpAllow(Set<String> ipAllow) {
+        this.ipAllow = ipAllow;
+    }
+
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+
+    public Long getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Long updateTime) {
+        this.updateTime = updateTime;
+    }
+}

+ 1 - 1
src/main/java/com/qmth/ops/api/dto/EnvVO.java → src/main/java/com/qmth/ops/api/vo/EnvVO.java

@@ -1,4 +1,4 @@
-package com.qmth.ops.api.dto;
+package com.qmth.ops.api.vo;
 
 import com.qmth.ops.biz.domain.Env;
 import com.qmth.ops.biz.domain.EnvType;

+ 25 - 0
src/main/java/com/qmth/ops/api/vo/OrgTypeVO.java

@@ -0,0 +1,25 @@
+package com.qmth.ops.api.vo;
+
+import com.qmth.boot.core.solar.enums.OrgSubType;
+import com.qmth.boot.core.solar.enums.OrgType;
+
+import java.util.Arrays;
+
+public class OrgTypeVO extends CodeNameVO {
+
+    private CodeNameVO[] subTypes;
+
+    public OrgTypeVO(OrgType type) {
+        super(type.toString(), type.getName());
+        this.subTypes = Arrays.stream(OrgSubType.findByParent(type))
+                .map(sub -> new CodeNameVO(sub.toString(), sub.getName())).toArray(CodeNameVO[]::new);
+    }
+
+    public CodeNameVO[] getSubTypes() {
+        return subTypes;
+    }
+
+    public void setSubTypes(CodeNameVO[] subTypes) {
+        this.subTypes = subTypes;
+    }
+}

+ 18 - 0
src/main/java/com/qmth/ops/api/vo/SuccessVO.java

@@ -0,0 +1,18 @@
+package com.qmth.ops.api.vo;
+
+public class SuccessVO {
+
+    private boolean success;
+
+    public SuccessVO(boolean success) {
+        this.success = success;
+    }
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public void setSuccess(boolean success) {
+        this.success = success;
+    }
+}

+ 1 - 1
src/main/java/com/qmth/ops/api/dto/UserVO.java → src/main/java/com/qmth/ops/api/vo/UserVO.java

@@ -1,4 +1,4 @@
-package com.qmth.ops.api.dto;
+package com.qmth.ops.api.vo;
 
 import com.qmth.ops.biz.domain.Role;
 import com.qmth.ops.biz.domain.User;

+ 8 - 0
src/main/java/com/qmth/ops/biz/dao/DeployDao.java

@@ -0,0 +1,8 @@
+package com.qmth.ops.biz.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.ops.biz.domain.Deploy;
+
+public interface DeployDao extends BaseMapper<Deploy> {
+
+}

+ 8 - 0
src/main/java/com/qmth/ops/biz/dao/DeployDeviceDao.java

@@ -0,0 +1,8 @@
+package com.qmth.ops.biz.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.ops.biz.domain.DeployDevice;
+
+public interface DeployDeviceDao extends BaseMapper<DeployDevice> {
+
+}

+ 8 - 0
src/main/java/com/qmth/ops/biz/dao/DeployOrgDao.java

@@ -0,0 +1,8 @@
+package com.qmth.ops.biz.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.ops.biz.domain.DeployOrg;
+
+public interface DeployOrgDao extends BaseMapper<DeployOrg> {
+
+}

+ 15 - 0
src/main/java/com/qmth/ops/biz/dao/OrgDao.java

@@ -0,0 +1,15 @@
+package com.qmth.ops.biz.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.ops.biz.domain.Org;
+import com.qmth.ops.biz.query.OrgQuery;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
+
+public interface OrgDao extends BaseMapper<Org> {
+
+    @Update("update org set enable=#{enable} where id=#{id}")
+    int toggle(@Param("id") Long id, @Param("enable") boolean enable);
+
+    OrgQuery findByQuery(OrgQuery query);
+}

+ 120 - 0
src/main/java/com/qmth/ops/biz/domain/Deploy.java

@@ -0,0 +1,120 @@
+package com.qmth.ops.biz.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.qmth.boot.core.solar.model.AppControl;
+
+import java.io.Serializable;
+import java.util.Set;
+
+@TableName("deploy")
+public class Deploy implements Serializable {
+
+    private static final long serialVersionUID = -1049563484657628978L;
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long appId;
+
+    private String name;
+
+    private DeployMode mode;
+
+    private String accessKey;
+
+    private String accessSecret;
+
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private AppControl control;
+
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private Set<String> ipAllow;
+
+    private Long createTime;
+
+    private Long updateTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getAppId() {
+        return appId;
+    }
+
+    public void setAppId(Long appId) {
+        this.appId = appId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public DeployMode getMode() {
+        return mode;
+    }
+
+    public void setMode(DeployMode mode) {
+        this.mode = mode;
+    }
+
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    public void setAccessKey(String accessKey) {
+        this.accessKey = accessKey;
+    }
+
+    public String getAccessSecret() {
+        return accessSecret;
+    }
+
+    public void setAccessSecret(String accessSecret) {
+        this.accessSecret = accessSecret;
+    }
+
+    public AppControl getControl() {
+        return control;
+    }
+
+    public void setControl(AppControl control) {
+        this.control = control;
+    }
+
+    public Set<String> getIpAllow() {
+        return ipAllow;
+    }
+
+    public void setIpAllow(Set<String> ipAllow) {
+        this.ipAllow = ipAllow;
+    }
+
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+
+    public Long getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Long updateTime) {
+        this.updateTime = updateTime;
+    }
+}

+ 47 - 0
src/main/java/com/qmth/ops/biz/domain/DeployDevice.java

@@ -0,0 +1,47 @@
+package com.qmth.ops.biz.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+
+@TableName(value = "deploy_device", autoResultMap = true)
+public class DeployDevice {
+
+    private Long deployId;
+
+    private String deviceId;
+
+    private String remark;
+
+    private Long createTime;
+
+    public Long getDeployId() {
+        return deployId;
+    }
+
+    public void setDeployId(Long deployId) {
+        this.deployId = deployId;
+    }
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+}

+ 20 - 0
src/main/java/com/qmth/ops/biz/domain/DeployMode.java

@@ -0,0 +1,20 @@
+package com.qmth.ops.biz.domain;
+
+public enum DeployMode {
+
+    CLOUD("云端"), LOCAL("本地");
+
+    private String name;
+
+    DeployMode(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getCode() {
+        return toString().toUpperCase();
+    }
+}

+ 27 - 0
src/main/java/com/qmth/ops/biz/domain/DeployOrg.java

@@ -0,0 +1,27 @@
+package com.qmth.ops.biz.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+
+@TableName(value = "deploy_device", autoResultMap = true)
+public class DeployOrg {
+
+    private Long deployId;
+
+    private Long orgId;
+
+    public Long getDeployId() {
+        return deployId;
+    }
+
+    public void setDeployId(Long deployId) {
+        this.deployId = deployId;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+}

+ 140 - 0
src/main/java/com/qmth/ops/biz/domain/Org.java

@@ -0,0 +1,140 @@
+package com.qmth.ops.biz.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qmth.boot.core.solar.enums.OrgSubType;
+import com.qmth.boot.core.solar.enums.OrgType;
+import com.qmth.boot.core.solar.model.OrgInfo;
+import com.qmth.ops.biz.utils.OrgSubTypeSetHandler;
+
+import java.util.Set;
+
+@TableName(value = "org", autoResultMap = true)
+public class Org {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String code;
+
+    private String name;
+
+    private OrgType type;
+
+    @TableField(typeHandler = OrgSubTypeSetHandler.class)
+    private Set<OrgSubType> subTypes;
+
+    private String logo;
+
+    private boolean enable;
+
+    private String accessKey;
+
+    private String accessSecret;
+
+    private Long createTime;
+
+    private Long updateTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public OrgType getType() {
+        return type;
+    }
+
+    public void setType(OrgType type) {
+        this.type = type;
+    }
+
+    public Set<OrgSubType> getSubTypes() {
+        return subTypes;
+    }
+
+    public void setSubTypes(Set<OrgSubType> subTypes) {
+        this.subTypes = subTypes;
+    }
+
+    public String getLogo() {
+        return logo;
+    }
+
+    public void setLogo(String logo) {
+        this.logo = logo;
+    }
+
+    public boolean isEnable() {
+        return enable;
+    }
+
+    public void setEnable(boolean enable) {
+        this.enable = enable;
+    }
+
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    public void setAccessKey(String accessKey) {
+        this.accessKey = accessKey;
+    }
+
+    public String getAccessSecret() {
+        return accessSecret;
+    }
+
+    public void setAccessSecret(String accessSecret) {
+        this.accessSecret = accessSecret;
+    }
+
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+
+    public Long getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Long updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public OrgInfo buildOrgInfo(String server) {
+        OrgInfo info = new OrgInfo();
+        info.setId(getId());
+        info.setCode(getCode());
+        info.setName(getName());
+        info.setLogo(logo != null ? server.concat(getLogo()) : null);
+        info.setType(getType());
+        info.setAccessKey(getAccessKey());
+        info.setAccessSecret(getAccessSecret());
+        return info;
+    }
+}

+ 57 - 0
src/main/java/com/qmth/ops/biz/query/DeployQuery.java

@@ -0,0 +1,57 @@
+package com.qmth.ops.biz.query;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.qmth.boot.mybatis.query.BaseQuery;
+import com.qmth.ops.biz.domain.Deploy;
+import com.qmth.ops.biz.domain.DeployMode;
+
+public class DeployQuery extends BaseQuery<Deploy> {
+
+    private static final long serialVersionUID = 5282212967865197163L;
+
+    private Long id;
+
+    private Long appId;
+
+    private String nameStartWith;
+
+    private DeployMode mode;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getAppId() {
+        return appId;
+    }
+
+    public void setAppId(Long appId) {
+        this.appId = appId;
+    }
+
+    public String getNameStartWith() {
+        return nameStartWith;
+    }
+
+    public void setNameStartWith(String nameStartWith) {
+        this.nameStartWith = nameStartWith;
+    }
+
+    public DeployMode getMode() {
+        return mode;
+    }
+
+    public void setMode(DeployMode mode) {
+        this.mode = mode;
+    }
+
+    public LambdaQueryWrapper<Deploy> build() {
+        return new LambdaQueryWrapper<Deploy>().eq(id != null, Deploy::getId, id)
+                .eq(appId != null, Deploy::getAppId, appId).eq(mode != null, Deploy::getMode, mode)
+                .likeRight(nameStartWith != null, Deploy::getName, nameStartWith).orderByAsc(Deploy::getId);
+    }
+}

+ 82 - 0
src/main/java/com/qmth/ops/biz/query/OrgQuery.java

@@ -0,0 +1,82 @@
+package com.qmth.ops.biz.query;
+
+import com.qmth.boot.core.solar.enums.OrgSubType;
+import com.qmth.boot.core.solar.enums.OrgType;
+import com.qmth.boot.mybatis.query.BaseQuery;
+import com.qmth.ops.biz.domain.Org;
+
+public class OrgQuery extends BaseQuery<Org> {
+
+    private static final long serialVersionUID = 1865451038975724572L;
+
+    private Long id;
+
+    private String code;
+
+    private String name;
+
+    private String nameStartWith;
+
+    private OrgType type;
+
+    private OrgSubType subType;
+
+    private Boolean enable;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getNameStartWith() {
+        return nameStartWith;
+    }
+
+    public void setNameStartWith(String nameStartWith) {
+        this.nameStartWith = nameStartWith;
+    }
+
+    public OrgType getType() {
+        return type;
+    }
+
+    public void setType(OrgType type) {
+        this.type = type;
+    }
+
+    public OrgSubType getSubType() {
+        return subType;
+    }
+
+    public void setSubType(OrgSubType subType) {
+        this.subType = subType;
+    }
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+}

+ 56 - 0
src/main/java/com/qmth/ops/biz/service/DeployService.java

@@ -0,0 +1,56 @@
+package com.qmth.ops.biz.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.ops.biz.dao.DeployDao;
+import com.qmth.ops.biz.domain.Deploy;
+import com.qmth.ops.biz.query.DeployQuery;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.UUID;
+
+@Service
+public class DeployService extends ServiceImpl<DeployDao, Deploy> {
+
+    @Resource
+    private DeployDao deployDao;
+
+    @Transactional
+    public Deploy insert(Deploy deploy) {
+        deploy.setAccessKey(UUID.randomUUID().toString().replaceAll("-", ""));
+        deploy.setAccessSecret(RandomStringUtils.random(32, true, true));
+        deploy.setCreateTime(System.currentTimeMillis());
+        deploy.setUpdateTime(deploy.getCreateTime());
+        deployDao.insert(deploy);
+        return deploy;
+    }
+
+    @Transactional
+    public Deploy update(Deploy deploy) {
+        deployDao.update(deploy,
+                new LambdaUpdateWrapper<Deploy>().set(deploy.getName() != null, Deploy::getName, deploy.getName())
+                        .set(deploy.getMode() != null, Deploy::getMode, deploy.getMode())
+                        .set(deploy.getControl() != null, Deploy::getControl, deploy.getControl())
+                        .set(deploy.getIpAllow() != null, Deploy::getIpAllow, deploy.getIpAllow())
+                        .set(Deploy::getUpdateTime, System.currentTimeMillis()).eq(Deploy::getId, deploy.getId()));
+        return deployDao.selectById(deploy.getId());
+    }
+
+    public DeployQuery query(DeployQuery query) {
+        return deployDao.selectPage(query, query.build());
+    }
+
+    public Deploy findByAccessKey(String accessKey) {
+        return deployDao.selectOne(new LambdaQueryWrapper<Deploy>().eq(Deploy::getAccessKey, accessKey));
+    }
+
+    public Deploy findById(Long id) {
+        return deployDao.selectById(id);
+    }
+
+}
+

+ 70 - 0
src/main/java/com/qmth/ops/biz/service/FileService.java

@@ -0,0 +1,70 @@
+package com.qmth.ops.biz.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.qmth.boot.core.fss.store.FileStore;
+import com.qmth.boot.core.fss.utils.FssUtils;
+import com.qmth.boot.tools.device.DeviceInfo;
+import com.qmth.boot.tools.models.ByteArray;
+import com.qmth.ops.biz.domain.Org;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+@Service
+public class FileService {
+
+    private static String ORG_LOGO_DIR = FssUtils.buildPath("org", "logo");
+
+    private static String DEVICE_INFO_DIR = FssUtils.buildPath("device");
+
+    private FileStore fileStore;
+
+    public FileService(FileStore fileStore) {
+        this.fileStore = fileStore;
+    }
+
+    public String getServer() {
+        return fileStore.getServer();
+    }
+
+    public void uploadOrgLogo(Org org, MultipartFile file, String md5) throws Exception {
+        String extension = getExtension(file.getOriginalFilename());
+        String path = FssUtils
+                .buildPath(ORG_LOGO_DIR, org.getId().toString() + (extension != null ? ("." + extension) : ""));
+        fileStore.write(path, file.getInputStream(), md5);
+        org.setLogo(path);
+    }
+
+    public ByteArray getOrgLogo(Org org) throws Exception {
+        if (org.getLogo() != null) {
+            InputStream ins = fileStore.read(org.getLogo());
+            ByteArray data = ByteArray.fromInputStream(ins);
+            ins.close();
+            return data;
+        } else {
+            return ByteArray.fromArray(null);
+        }
+    }
+
+    public void uploadDeviceInfo(String deviceId, DeviceInfo info) throws Exception {
+        byte[] data = new ObjectMapper().writeValueAsBytes(info);
+        String path = FssUtils.buildPath(DEVICE_INFO_DIR, deviceId + ".json");
+        fileStore.write(path, new ByteArrayInputStream(data), ByteArray.md5(data).toBase64());
+    }
+
+    public ByteArray getDeviceInfo(String deviceId) throws Exception {
+        String path = FssUtils.buildPath(DEVICE_INFO_DIR, deviceId + ".json");
+        InputStream ins = fileStore.read(path);
+        ByteArray data = ByteArray.fromInputStream(ins);
+        ins.close();
+        return data;
+    }
+
+    public String getExtension(String filename) {
+        int index = filename != null ? filename.lastIndexOf(".") : -1;
+        return index > -1 ? StringUtils.trimToNull(filename.substring(index + 1)) : null;
+    }
+}

+ 107 - 0
src/main/java/com/qmth/ops/biz/service/LicenseService.java

@@ -0,0 +1,107 @@
+package com.qmth.ops.biz.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.qmth.boot.core.solar.model.AppLicense;
+import com.qmth.boot.core.solar.model.OrgInfo;
+import com.qmth.boot.tools.codec.CodecUtils;
+import com.qmth.boot.tools.crypto.AES;
+import com.qmth.boot.tools.crypto.RSA;
+import com.qmth.boot.tools.crypto.RsaHelper;
+import com.qmth.boot.tools.device.DeviceInfo;
+import com.qmth.boot.tools.models.ByteArray;
+import com.qmth.ops.biz.domain.Deploy;
+import com.qmth.ops.biz.domain.Org;
+import com.qmth.ops.biz.query.OrgQuery;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.validation.constraints.NotNull;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class LicenseService {
+
+    private RsaHelper privateKey;
+
+    @Resource
+    private DeployService deployService;
+
+    @Resource
+    private AppService appService;
+
+    @Resource
+    private OrgService orgService;
+
+    @Resource
+    private FileService fileService;
+
+    public LicenseService(@Value("${admin.private-key}") String privateKeyFile) throws IOException {
+        privateKey = RSA.getPrivateKey(ByteArray.fromFile(new File(privateKeyFile)).value());
+    }
+
+    public DeviceInfo parseDeviceInfo(MultipartFile file) throws Exception {
+        return new ObjectMapper().readValue(privateKey.decrypt(file.getBytes()).value(), DeviceInfo.class);
+    }
+
+    public void buildLicense(@NotNull Deploy deploy, String deviceId, String version, @NotNull OutputStream ous)
+            throws Exception {
+        //build app info
+        AppLicense license = new AppLicense();
+        app.buildAppInfo(license);
+        license.setOrgs(new ArrayList<>());
+        //build org info list
+        OrgQuery query = new OrgQuery();
+        query.setAppId(app.getId());
+        query.setEnable(true);
+        query.setPageNumber(1);
+        query.setPageSize(100);
+        while (true) {
+            List<Org> orgs = orgService.listByQuery(query);
+            if (orgs == null || orgs.isEmpty()) {
+                break;
+            }
+            for (Org org : orgs) {
+                OrgInfo info = org.buildOrgInfo(fileService.getServer());
+                info.setLogo(fileService.getOrgLogo(org).toBase64());
+                license.getOrgs().add(info);
+            }
+            query.setPageNumber(query.getPageNumber() + 1);
+        }
+        byte[] data = new ObjectMapper().writeValueAsBytes(license);
+        ByteArrayOutputStream temp = new ByteArrayOutputStream();
+        temp.write(CodecUtils.md5(data));
+        temp.write(privateKey.encrypt(data).value());
+        //不限设备与版本
+        if (deviceId == null && version == null) {
+            ous.write((byte) 0);
+            ous.write(temp.toByteArray());
+        }
+        //限制设备,不限版本
+        else if (deviceId != null && version == null) {
+            ous.write((byte) 1);
+            ous.write(AES.encrypt(temp.toByteArray(), deviceId.substring(0, 16), deviceId.substring(16)).value());
+        }
+        //不限设置,限制版本
+        else if (deviceId == null && version != null) {
+            String versionMd5 = ByteArray.md5(version).toHexString();
+            ous.write((byte) 2);
+            ous.write(AES.encrypt(temp.toByteArray(), versionMd5.substring(0, 16), versionMd5.substring(16)).value());
+        }
+        //限制设备与版本
+        else {
+            String versionMd5 = ByteArray.md5(version).toHexString();
+            ous.write((byte) 3);
+            ous.write(AES.encrypt(
+                    AES.encrypt(temp.toByteArray(), deviceId.substring(0, 16), deviceId.substring(16)).value(),
+                    versionMd5.substring(0, 16), versionMd5.substring(16)).value());
+        }
+        ous.close();
+    }
+}

+ 53 - 0
src/main/java/com/qmth/ops/biz/service/OrgService.java

@@ -0,0 +1,53 @@
+package com.qmth.ops.biz.service;
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.qmth.ops.biz.dao.OrgDao;
+import com.qmth.ops.biz.domain.Org;
+import com.qmth.ops.biz.query.OrgQuery;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.UUID;
+
+@Service
+public class OrgService {
+
+    @Resource
+    private OrgDao orgDao;
+
+    public Org findById(Long id) {
+        return orgDao.selectById(id);
+    }
+
+    public OrgQuery findByQuery(OrgQuery query) {
+        return orgDao.findByQuery(query);
+    }
+
+    @Transactional
+    public boolean toggle(Long id, boolean enable) {
+        return orgDao.toggle(id, enable) > 0;
+    }
+
+    @Transactional
+    public Org insert(Org org) {
+        org.setAccessKey(UUID.randomUUID().toString().replaceAll("-", ""));
+        org.setAccessSecret(RandomStringUtils.random(32, true, true));
+        org.setCreateTime(System.currentTimeMillis());
+        org.setUpdateTime(org.getCreateTime());
+        orgDao.insert(org);
+        return org;
+    }
+
+    @Transactional
+    public Org update(Org org) {
+        orgDao.update(org, new LambdaUpdateWrapper<Org>().set(org.getCode() != null, Org::getCode, org.getCode())
+                .set(org.getName() != null, Org::getName, org.getName())
+                .set(org.getType() != null, Org::getType, org.getType())
+                .set(org.getSubTypes() != null, Org::getSubTypes, org.getSubTypes())
+                .set(org.getLogo() != null, Org::getLogo, org.getLogo())
+                .set(Org::getUpdateTime, System.currentTimeMillis()).eq(Org::getId, org.getId()));
+        return orgDao.selectById(org.getId());
+    }
+}

+ 57 - 0
src/main/java/com/qmth/ops/biz/utils/OrgSubTypeSetHandler.java

@@ -0,0 +1,57 @@
+package com.qmth.ops.biz.utils;
+
+import com.qmth.boot.core.solar.enums.OrgSubType;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashSet;
+import java.util.Set;
+
+public class OrgSubTypeSetHandler extends BaseTypeHandler<Set<OrgSubType>> {
+
+    public static final String JOINER = ",";
+
+    @Override
+    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Set<OrgSubType> orgTypes,
+            JdbcType jdbcType) throws SQLException {
+        preparedStatement.setString(i, toString(orgTypes));
+    }
+
+    @Override
+    public Set<OrgSubType> getNullableResult(ResultSet resultSet, String s) throws SQLException {
+        return toSet(resultSet.getString(s));
+    }
+
+    @Override
+    public Set<OrgSubType> getNullableResult(ResultSet resultSet, int i) throws SQLException {
+        return toSet(resultSet.getString(i));
+    }
+
+    @Override
+    public Set<OrgSubType> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
+        return toSet(callableStatement.getString(i));
+    }
+
+    private String toString(Set<OrgSubType> set) {
+        return set != null ? StringUtils.join(set, JOINER) : StringUtils.EMPTY;
+    }
+
+    private Set<OrgSubType> toSet(String text) {
+        Set<OrgSubType> set = new HashSet<>();
+        String[] values = StringUtils.split(text, JOINER);
+        if (values != null) {
+            for (String value : values) {
+                try {
+                    set.add(OrgSubType.valueOf(value));
+                } catch (Exception ignored) {
+                }
+            }
+        }
+        return set;
+    }
+}

+ 3 - 1
src/main/resources/application.properties

@@ -4,4 +4,6 @@ com.qmth.api.global-auth=false
 
 com.qmth.datasource.url=jdbc:mysql://192.168.10.83:3306/ops_db?useUnicode=true&characterEncoding=UTF-8
 com.qmth.datasource.username=scan
-com.qmth.datasource.password=scan
+com.qmth.datasource.password=scan
+
+admin.private-key=

+ 32 - 0
src/main/resources/mapper/OrgMapper.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qmth.ops.biz.dao.OrgDao">
+
+    <!--MybatisPlus内置规则,开启autoResultMap后,名称为mybatis-plus_XXX-->
+    <select id="findByQuery" resultMap="mybatis-plus_Org">
+        select g.* from org g
+        <where>
+            <if test="id != null">
+                and g.id=#{id}
+            </if>
+            <if test="code != null">
+                and g.code=#{code}
+            </if>
+            <if test="name != null">
+                and g.name=#{name}
+            </if>
+            <if test="nameStartWith != null">
+                and g.name like concat(#{nameStartWith},'%')
+            </if>
+            <if test="type != null">
+                and g.type=#{type}
+            </if>
+            <if test="subType != null">
+                and find_in_set(#{subType}, g.sub_types)
+            </if>
+            <if test="enable != null">
+                and g.enable=#{enable}
+            </if>
+        </where>
+    </select>
+</mapper>