Selaa lähdekoodia

多租户设计

1811872455@163.com 2 kuukautta sitten
vanhempi
sitoutus
cc6ad62f6d
59 muutettua tiedostoa jossa 2359 lisäystä ja 28 poistoa
  1. 148 0
      docs/intelligent-leads-dashboard.canvas.tsx
  2. 21 0
      java/storlead-account/pom.xml
  3. 29 0
      java/storlead-account/storlead-account-api/pom.xml
  4. 53 0
      java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/platform/admin/controller/PlatformTenantEnterpriseEntityController.java
  5. 53 0
      java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/platform/admin/controller/PlatformTenantEnterpriseUserRelEntityController.java
  6. 52 0
      java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/system/controller/AuthController.java
  7. 97 0
      java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/system/controller/SystemUserEntityController.java
  8. 53 0
      java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/tenant/controller/TenantEnterpriseEntityController.java
  9. 53 0
      java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/tenant/controller/TenantEnterpriseUserRelEntityController.java
  10. 23 0
      java/storlead-account/storlead-account-biz/pom.xml
  11. 5 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/platform/admin/package-info.java
  12. 16 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/dto/SystemLoginDTO.java
  13. 14 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/dto/SystemUserQueryDTO.java
  14. 37 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/dto/SystemUserSaveDTO.java
  15. 19 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/dto/TenantLoginDTO.java
  16. 47 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/entity/SystemUserEntity.java
  17. 9 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/mapper/SystemUserEntityMapper.java
  18. 14 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/service/AuthLoginService.java
  19. 7 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/service/SystemUserEntityService.java
  20. 140 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/service/impl/AuthLoginServiceImpl.java
  21. 13 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/service/impl/SystemUserEntityServiceImpl.java
  22. 19 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/dto/TenantEnterpriseQueryDTO.java
  23. 37 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/dto/TenantEnterpriseSaveDTO.java
  24. 20 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/dto/TenantEnterpriseUserRelQueryDTO.java
  25. 28 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/dto/TenantEnterpriseUserRelSaveDTO.java
  26. 49 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/entity/TenantEnterpriseEntity.java
  27. 37 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/entity/TenantEnterpriseUserRelEntity.java
  28. 9 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/mapper/TenantEnterpriseEntityMapper.java
  29. 9 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/mapper/TenantEnterpriseUserRelEntityMapper.java
  30. 4 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/package-info.java
  31. 7 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/service/TenantEnterpriseEntityService.java
  32. 7 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/service/TenantEnterpriseUserRelEntityService.java
  33. 13 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/service/impl/TenantEnterpriseEntityServiceImpl.java
  34. 13 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/service/impl/TenantEnterpriseUserRelEntityServiceImpl.java
  35. 133 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/web/TenantEnterpriseEntityWebDelegate.java
  36. 134 0
      java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/web/TenantEnterpriseUserRelEntityWebDelegate.java
  37. 5 0
      java/storlead-account/storlead-account-biz/src/main/resources/mapper/SystemUserEntityMapper.xml
  38. 5 0
      java/storlead-account/storlead-account-biz/src/main/resources/mapper/TenantEnterpriseEntityMapper.xml
  39. 5 0
      java/storlead-account/storlead-account-biz/src/main/resources/mapper/TenantEnterpriseUserRelEntityMapper.xml
  40. 41 0
      java/storlead-api/pom.xml
  41. 41 0
      java/storlead-api/src/main/java/com/storlead/api/StorleadTradeApplication.java
  42. 162 0
      java/storlead-api/src/main/resources/application-dev.yml
  43. 220 0
      java/storlead-api/src/main/resources/application-prod.yml
  44. 144 0
      java/storlead-api/src/main/resources/application-test.yml
  45. 52 0
      java/storlead-api/src/main/resources/application.yml
  46. 5 0
      java/storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/LoginUser.java
  47. 8 0
      java/storlead-framework/storlead-auth/src/main/java/com/storlead/framework/util/LoginUserUtil.java
  48. 3 1
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/properties/UrlChainDefinitionPorperties.java
  49. 25 0
      java/storlead-framework/storlead-core/src/main/java/com/storlead/framework/core/tenant/TenantConstants.java
  50. 60 0
      java/storlead-framework/storlead-core/src/main/java/com/storlead/framework/core/tenant/TenantContext.java
  51. 4 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/entity/SysBaseField.java
  52. 10 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/handler/MyMetaObjectHandler.java
  53. 17 7
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/interceptor/MybatisPlusConfig.java
  54. 55 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/tenant/StorleadTenantHandler.java
  55. 25 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/tenant/TenantProperties.java
  56. 18 7
      java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/config/Swagger2Config.java
  57. 1 1
      java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/filter/AllRequestFilter.java
  58. 26 0
      java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/filter/AuthRequestFilter.java
  59. 5 12
      pom.xml

+ 148 - 0
docs/intelligent-leads-dashboard.canvas.tsx

@@ -0,0 +1,148 @@
+import React from "react";
+
+type Metric = {
+  label: string;
+  value: string;
+  sub: string;
+};
+
+type TaskCard = {
+  name: string;
+  region: string;
+  completed: number;
+  assigned: number;
+  converted: number;
+  progress: number;
+  due: string;
+  source: string;
+};
+
+const metrics: Metric[] = [
+  { label: "已发现线索", value: "1,286", sub: "较昨日 +6.2%" },
+  { label: "已生成客户", value: "372", sub: "客户转化率 28.9%" },
+  { label: "高意向客户", value: "94", sub: "7日提升 +14" },
+  { label: "运行中任务", value: "5", sub: "2 个高优先级" },
+  { label: "平均触达成本", value: "¥18.7", sub: "较上周 -9.4%" },
+];
+
+const tasks: TaskCard[] = [
+  {
+    name: "中东SSD寻客",
+    region: "中东 · 区域渠道",
+    completed: 168,
+    assigned: 840,
+    converted: 26,
+    progress: 65,
+    due: "2026-03-18 20:00",
+    source: "LinkedIn",
+  },
+  {
+    name: "欧洲企业级存储",
+    region: "欧洲 · 企业分销",
+    completed: 93,
+    assigned: 520,
+    converted: 14,
+    progress: 43,
+    due: "2026-03-17 14:00",
+    source: "Google Leads",
+  },
+  {
+    name: "东南亚分销商",
+    region: "东南亚 · 行业扩展",
+    completed: 47,
+    assigned: 310,
+    converted: 9,
+    progress: 29,
+    due: "2026-03-18 18:00",
+    source: "Alibaba",
+  },
+];
+
+const styles = {
+  page: {
+    background: "#0B1220",
+    color: "#D7E1F6",
+    minHeight: "100vh",
+    padding: 20,
+    fontFamily: "Inter, PingFang SC, Microsoft YaHei, sans-serif",
+  },
+  sectionTitle: { fontSize: 16, fontWeight: 700, margin: "8px 0 12px 0" },
+  cardGrid: { display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 12 },
+  metricCard: {
+    background: "#111B32",
+    border: "1px solid #253458",
+    borderRadius: 12,
+    padding: 14,
+  },
+  value: { fontSize: 24, fontWeight: 800, color: "#F4F7FF", margin: "6px 0" },
+  sub: { color: "#8CA0C9", fontSize: 12 },
+  board: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12, marginTop: 12 },
+  taskCard: {
+    background: "#111B32",
+    border: "1px solid #253458",
+    borderRadius: 12,
+    padding: 14,
+  },
+  progressBg: {
+    height: 8,
+    borderRadius: 999,
+    background: "#1B2745",
+    overflow: "hidden",
+    marginTop: 8,
+  },
+  progressBar: (p: number) => ({
+    width: `${p}%`,
+    height: "100%",
+    background: "linear-gradient(90deg,#6A5CFF,#5AA3FF)",
+  }),
+  chip: {
+    display: "inline-block",
+    border: "1px solid #33508A",
+    color: "#8FB1FF",
+    borderRadius: 999,
+    padding: "2px 8px",
+    fontSize: 11,
+    marginTop: 8,
+  },
+};
+
+export default function IntelligentLeadsDashboardCanvas() {
+  return (
+    <div style={styles.page}>
+      <div style={styles.sectionTitle}>智能寻客 - 经营总览</div>
+      <div style={styles.cardGrid}>
+        {metrics.map((m) => (
+          <div key={m.label} style={styles.metricCard}>
+            <div style={{ color: "#9AB0DA", fontSize: 12 }}>{m.label}</div>
+            <div style={styles.value}>{m.value}</div>
+            <div style={styles.sub}>{m.sub}</div>
+          </div>
+        ))}
+      </div>
+
+      <div style={{ ...styles.sectionTitle, marginTop: 18 }}>进行中的任务</div>
+      <div style={styles.board}>
+        {tasks.map((t) => (
+          <div key={t.name} style={styles.taskCard}>
+            <div style={{ fontSize: 15, fontWeight: 700, color: "#F0F4FF" }}>{t.name}</div>
+            <div style={{ color: "#8CA0C9", fontSize: 12, marginTop: 4 }}>{t.region}</div>
+            <div style={{ display: "flex", gap: 14, marginTop: 12, fontSize: 12 }}>
+              <span>已采集 {t.completed}</span>
+              <span>已分配 {t.assigned}</span>
+              <span>已转化 {t.converted}</span>
+            </div>
+            <div style={styles.progressBg}>
+              <div style={styles.progressBar(t.progress)} />
+            </div>
+            <div style={{ display: "flex", justifyContent: "space-between", marginTop: 8, fontSize: 12, color: "#8CA0C9" }}>
+              <span>进度 {t.progress}%</span>
+              <span>截止 {t.due}</span>
+            </div>
+            <span style={styles.chip}>来源: {t.source}</span>
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}
+

+ 21 - 0
java/storlead-account/pom.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-smarttrade-platform</artifactId>
+        <version>1.0</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-account</artifactId>
+    <packaging>pom</packaging>
+    <name>storlead-account</name>
+
+    <modules>
+        <module>storlead-account-biz</module>
+        <module>storlead-account-api</module>
+    </modules>
+</project>

+ 29 - 0
java/storlead-account/storlead-account-api/pom.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-account</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-account-api</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-account-biz</artifactId>
+            <version>1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 53 - 0
java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/platform/admin/controller/PlatformTenantEnterpriseEntityController.java

@@ -0,0 +1,53 @@
+package com.storlead.account.platform.admin.controller;
+
+import com.storlead.account.tenant.dto.TenantEnterpriseQueryDTO;
+import com.storlead.account.tenant.dto.TenantEnterpriseSaveDTO;
+import com.storlead.account.tenant.web.TenantEnterpriseEntityWebDelegate;
+import com.storlead.framework.web.assemble.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 平台系统管理员侧:跨租户维护企业(后续应加独立鉴权,如仅平台运营账号可访问)。
+ */
+@RestController
+@RequestMapping("/platform/admin/tenant/enterprise")
+@Api(tags = "平台侧-租户企业管理")
+public class PlatformTenantEnterpriseEntityController {
+
+    @Resource
+    private TenantEnterpriseEntityWebDelegate enterpriseWebDelegate;
+
+    @PostMapping("/page")
+    @ApiOperation("平台侧企业分页")
+    public Result<Object> page(@RequestBody TenantEnterpriseQueryDTO query) {
+        return enterpriseWebDelegate.page(query);
+    }
+
+    @GetMapping("/detail")
+    @ApiOperation("平台侧企业详情")
+    public Result<Object> detail(@RequestParam("id") Long id) {
+        return enterpriseWebDelegate.detail(id);
+    }
+
+    @PostMapping("/save")
+    @ApiOperation("平台侧新增企业")
+    public Result<Object> save(@RequestBody TenantEnterpriseSaveDTO dto) {
+        return enterpriseWebDelegate.save(dto);
+    }
+
+    @PostMapping("/update")
+    @ApiOperation("平台侧修改企业")
+    public Result<Object> update(@RequestBody TenantEnterpriseSaveDTO dto) {
+        return enterpriseWebDelegate.update(dto);
+    }
+
+    @PostMapping("/delete")
+    @ApiOperation("平台侧删除企业")
+    public Result<Object> delete(@RequestParam("id") Long id) {
+        return enterpriseWebDelegate.delete(id);
+    }
+}

+ 53 - 0
java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/platform/admin/controller/PlatformTenantEnterpriseUserRelEntityController.java

@@ -0,0 +1,53 @@
+package com.storlead.account.platform.admin.controller;
+
+import com.storlead.account.tenant.dto.TenantEnterpriseUserRelQueryDTO;
+import com.storlead.account.tenant.dto.TenantEnterpriseUserRelSaveDTO;
+import com.storlead.account.tenant.web.TenantEnterpriseUserRelEntityWebDelegate;
+import com.storlead.framework.web.assemble.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 平台系统管理员侧:跨租户维护企业-用户关系。
+ */
+@RestController
+@RequestMapping("/platform/admin/tenant/enterprise-user-rel")
+@Api(tags = "平台侧-租户企业用户关系管理")
+public class PlatformTenantEnterpriseUserRelEntityController {
+
+    @Resource
+    private TenantEnterpriseUserRelEntityWebDelegate userRelWebDelegate;
+
+    @PostMapping("/page")
+    @ApiOperation("平台侧企业用户关系分页")
+    public Result<Object> page(@RequestBody TenantEnterpriseUserRelQueryDTO query) {
+        return userRelWebDelegate.page(query);
+    }
+
+    @GetMapping("/detail")
+    @ApiOperation("平台侧企业用户关系详情")
+    public Result<Object> detail(@RequestParam("id") Long id) {
+        return userRelWebDelegate.detail(id);
+    }
+
+    @PostMapping("/save")
+    @ApiOperation("平台侧新增企业用户关系")
+    public Result<Object> save(@RequestBody TenantEnterpriseUserRelSaveDTO dto) {
+        return userRelWebDelegate.save(dto);
+    }
+
+    @PostMapping("/update")
+    @ApiOperation("平台侧修改企业用户关系")
+    public Result<Object> update(@RequestBody TenantEnterpriseUserRelSaveDTO dto) {
+        return userRelWebDelegate.update(dto);
+    }
+
+    @PostMapping("/delete")
+    @ApiOperation("平台侧删除企业用户关系")
+    public Result<Object> delete(@RequestParam("id") Long id) {
+        return userRelWebDelegate.delete(id);
+    }
+}

+ 52 - 0
java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/system/controller/AuthController.java

@@ -0,0 +1,52 @@
+package com.storlead.account.system.controller;
+
+import com.storlead.account.system.dto.SystemLoginDTO;
+import com.storlead.account.system.dto.TenantLoginDTO;
+import com.storlead.account.system.service.AuthLoginService;
+import com.storlead.framework.common.constant.DefContants;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.web.assemble.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+@RestController
+@RequestMapping("/account/auth")
+@Api(tags = "账户认证")
+public class AuthController {
+
+    @Resource
+    private AuthLoginService authLoginService;
+
+    @PostMapping("/system/login")
+    @ApiOperation("系统用户登录")
+    public Result<Object> systemLogin(@RequestBody SystemLoginDTO dto, HttpServletRequest request) {
+        return authLoginService.systemLogin(dto, request.getRemoteAddr());
+    }
+
+    @PostMapping("/tenant/login")
+    @ApiOperation("租户用户登录")
+    public Result<Object> tenantLogin(@RequestBody TenantLoginDTO dto, HttpServletRequest request) {
+        return authLoginService.tenantLogin(dto, request.getRemoteAddr());
+    }
+
+    @PostMapping("/logout")
+    @ApiOperation("退出登录")
+    public Result<Object> logout(HttpServletRequest request) {
+        String token = request.getHeader(DefContants.ACCESS_TOKEN);
+        if (StringUtils.isBlank(token)) {
+            token = request.getParameter(DefContants.ACCESS_TOKEN);
+        }
+        return authLoginService.logout(token);
+    }
+
+    @GetMapping("/me")
+    @ApiOperation("获取当前登录用户")
+    public Result<Object> me() {
+        return Result.ok(LoginUserUtil.getLoginUser());
+    }
+}

+ 97 - 0
java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/system/controller/SystemUserEntityController.java

@@ -0,0 +1,97 @@
+package com.storlead.account.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.account.system.dto.SystemUserQueryDTO;
+import com.storlead.account.system.dto.SystemUserSaveDTO;
+import com.storlead.account.system.entity.SystemUserEntity;
+import com.storlead.account.system.service.SystemUserEntityService;
+import com.storlead.framework.common.ecode.BCryptPasswordEncoder;
+import com.storlead.framework.web.assemble.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.Objects;
+
+@RestController
+@RequestMapping("/platform/admin/system-user")
+@Api(tags = "平台系统用户管理")
+public class SystemUserEntityController {
+
+    @Resource
+    private SystemUserEntityService systemUserService;
+    @Resource
+    private BCryptPasswordEncoder passwordEncoder;
+
+    @PostMapping("/page")
+    @ApiOperation("系统用户分页查询")
+    public Result<Object> page(@RequestBody SystemUserQueryDTO query) {
+        Page<SystemUserEntity> page = new Page<>(query.getPageIndex(), query.getPageSize());
+        LambdaQueryWrapper<SystemUserEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SystemUserEntity::getIsDelete, 0);
+        if (StringUtils.isNotBlank(query.getBlurry())) {
+            wrapper.and(w -> w.like(SystemUserEntity::getUsername, query.getBlurry())
+                    .or().like(SystemUserEntity::getMobile, query.getBlurry())
+                    .or().like(SystemUserEntity::getEmail, query.getBlurry())
+                    .or().like(SystemUserEntity::getNickName, query.getBlurry()));
+        }
+        wrapper.orderByAsc(SystemUserEntity::getSort).orderByDesc(SystemUserEntity::getCreateTime);
+        return Result.ok(systemUserService.page(page, wrapper));
+    }
+
+    @GetMapping("/detail")
+    @ApiOperation("系统用户详情")
+    public Result<Object> detail(@RequestParam("id") Long id) {
+        return Result.ok(systemUserService.getById(id));
+    }
+
+    @PostMapping("/save")
+    @ApiOperation("新增系统用户")
+    public Result<Object> save(@RequestBody SystemUserSaveDTO dto) {
+        if (StringUtils.isBlank(dto.getUsername()) || StringUtils.isBlank(dto.getPassword())) {
+            return Result.error("username和password不能为空");
+        }
+        SystemUserEntity entity = new SystemUserEntity();
+        BeanUtils.copyProperties(dto, entity);
+        entity.setId(null);
+        entity.setPasswordHash(passwordEncoder.encode(dto.getPassword()));
+        entity.setIsDelete(0);
+        if (entity.getEnabled() == null) {
+            entity.setEnabled(true);
+        }
+        if (entity.getSort() == null) {
+            entity.setSort(0);
+        }
+        return Result.ok(systemUserService.save(entity));
+    }
+
+    @PostMapping("/update")
+    @ApiOperation("修改系统用户")
+    public Result<Object> update(@RequestBody SystemUserSaveDTO dto) {
+        if (Objects.isNull(dto.getId())) {
+            return Result.error("id不能为空");
+        }
+        SystemUserEntity entity = systemUserService.getById(dto.getId());
+        if (entity == null) {
+            return Result.error("记录不存在");
+        }
+        BeanUtils.copyProperties(dto, entity, "passwordHash", "createTime", "createBy", "ownerBy");
+        if (StringUtils.isNotBlank(dto.getPassword())) {
+            entity.setPasswordHash(passwordEncoder.encode(dto.getPassword()));
+        }
+        if (dto.getEnabled() != null) {
+            entity.setEnabled(dto.getEnabled() == 1);
+        }
+        return Result.ok(systemUserService.updateById(entity));
+    }
+
+    @PostMapping("/delete")
+    @ApiOperation("删除系统用户")
+    public Result<Object> delete(@RequestParam("id") Long id) {
+        return Result.ok(systemUserService.lgDelete(id));
+    }
+}

+ 53 - 0
java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/tenant/controller/TenantEnterpriseEntityController.java

@@ -0,0 +1,53 @@
+package com.storlead.account.tenant.controller;
+
+import com.storlead.account.tenant.dto.TenantEnterpriseQueryDTO;
+import com.storlead.account.tenant.dto.TenantEnterpriseSaveDTO;
+import com.storlead.account.tenant.web.TenantEnterpriseEntityWebDelegate;
+import com.storlead.framework.web.assemble.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 企业租户侧:企业信息维护(后续应强制使用登录上下文中的 tenant_id,禁止越权查询其它租户)。
+ */
+@RestController
+@RequestMapping("/account/tenant/enterprise")
+@Api(tags = "租户侧-企业管理")
+public class TenantEnterpriseEntityController {
+
+    @Resource
+    private TenantEnterpriseEntityWebDelegate enterpriseWebDelegate;
+
+    @PostMapping("/page")
+    @ApiOperation("租户侧企业分页")
+    public Result<Object> page(@RequestBody TenantEnterpriseQueryDTO query) {
+        return enterpriseWebDelegate.tenantPage(query);
+    }
+
+    @GetMapping("/detail")
+    @ApiOperation("租户侧企业详情")
+    public Result<Object> detail(@RequestParam("id") Long id) {
+        return enterpriseWebDelegate.tenantDetail(id);
+    }
+
+    @PostMapping("/save")
+    @ApiOperation("租户侧新增企业")
+    public Result<Object> save(@RequestBody TenantEnterpriseSaveDTO dto) {
+        return enterpriseWebDelegate.tenantSave(dto);
+    }
+
+    @PostMapping("/update")
+    @ApiOperation("租户侧修改企业")
+    public Result<Object> update(@RequestBody TenantEnterpriseSaveDTO dto) {
+        return enterpriseWebDelegate.tenantUpdate(dto);
+    }
+
+    @PostMapping("/delete")
+    @ApiOperation("租户侧删除企业")
+    public Result<Object> delete(@RequestParam("id") Long id) {
+        return enterpriseWebDelegate.tenantDelete(id);
+    }
+}

+ 53 - 0
java/storlead-account/storlead-account-api/src/main/java/com/storlead/account/tenant/controller/TenantEnterpriseUserRelEntityController.java

@@ -0,0 +1,53 @@
+package com.storlead.account.tenant.controller;
+
+import com.storlead.account.tenant.dto.TenantEnterpriseUserRelQueryDTO;
+import com.storlead.account.tenant.dto.TenantEnterpriseUserRelSaveDTO;
+import com.storlead.account.tenant.web.TenantEnterpriseUserRelEntityWebDelegate;
+import com.storlead.framework.web.assemble.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 企业租户侧:本企业成员关系维护。
+ */
+@RestController
+@RequestMapping("/account/tenant/enterprise-user-rel")
+@Api(tags = "租户侧-企业用户关系管理")
+public class TenantEnterpriseUserRelEntityController {
+
+    @Resource
+    private TenantEnterpriseUserRelEntityWebDelegate userRelWebDelegate;
+
+    @PostMapping("/page")
+    @ApiOperation("租户侧企业用户关系分页")
+    public Result<Object> page(@RequestBody TenantEnterpriseUserRelQueryDTO query) {
+        return userRelWebDelegate.tenantPage(query);
+    }
+
+    @GetMapping("/detail")
+    @ApiOperation("租户侧企业用户关系详情")
+    public Result<Object> detail(@RequestParam("id") Long id) {
+        return userRelWebDelegate.tenantDetail(id);
+    }
+
+    @PostMapping("/save")
+    @ApiOperation("租户侧新增企业用户关系")
+    public Result<Object> save(@RequestBody TenantEnterpriseUserRelSaveDTO dto) {
+        return userRelWebDelegate.tenantSave(dto);
+    }
+
+    @PostMapping("/update")
+    @ApiOperation("租户侧修改企业用户关系")
+    public Result<Object> update(@RequestBody TenantEnterpriseUserRelSaveDTO dto) {
+        return userRelWebDelegate.tenantUpdate(dto);
+    }
+
+    @PostMapping("/delete")
+    @ApiOperation("租户侧删除企业用户关系")
+    public Result<Object> delete(@RequestParam("id") Long id) {
+        return userRelWebDelegate.tenantDelete(id);
+    }
+}

+ 23 - 0
java/storlead-account/storlead-account-biz/pom.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-account</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-account-biz</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-web</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 5 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/platform/admin/package-info.java

@@ -0,0 +1,5 @@
+/**
+ * 平台系统管理员侧:可跨租户维护企业与成员关系(后续应单独鉴权,如仅 {@code isAdmin} 或独立角色体系),
+ * 与 {@link com.storlead.account.tenant} 入口分离,避免与企业用户混用同一套 URL。
+ */
+package com.storlead.account.platform.admin;

+ 16 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/dto/SystemLoginDTO.java

@@ -0,0 +1,16 @@
+package com.storlead.account.system.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "SystemLoginDTO", description = "系统用户登录入参")
+public class SystemLoginDTO {
+
+    @ApiModelProperty(value = "登录账号", required = true)
+    private String username;
+
+    @ApiModelProperty(value = "密码", required = true)
+    private String password;
+}

+ 14 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/dto/SystemUserQueryDTO.java

@@ -0,0 +1,14 @@
+package com.storlead.account.system.dto;
+
+import com.storlead.framework.mybatis.page.Page;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "SystemUserQueryDTO", description = "系统用户分页查询条件")
+public class SystemUserQueryDTO extends Page {
+
+    @ApiModelProperty(value = "模糊关键字(账号/手机号/邮箱/昵称)")
+    private String blurry;
+}

+ 37 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/dto/SystemUserSaveDTO.java

@@ -0,0 +1,37 @@
+package com.storlead.account.system.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "SystemUserSaveDTO", description = "系统用户新增/修改入参")
+public class SystemUserSaveDTO {
+
+    @ApiModelProperty(value = "主键ID,新增不传,修改必传")
+    private Long id;
+
+    @ApiModelProperty(value = "登录账号")
+    private String username;
+
+    @ApiModelProperty(value = "手机号")
+    private String mobile;
+
+    @ApiModelProperty(value = "邮箱")
+    private String email;
+
+    @ApiModelProperty(value = "密码(新增必传,修改可选)")
+    private String password;
+
+    @ApiModelProperty(value = "昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "头像")
+    private String avatar;
+
+    @ApiModelProperty(value = "是否启用:1启用 0禁用")
+    private Integer enabled;
+
+    @ApiModelProperty(value = "显示排序")
+    private Integer sort;
+}

+ 19 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/dto/TenantLoginDTO.java

@@ -0,0 +1,19 @@
+package com.storlead.account.system.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "TenantLoginDTO", description = "租户用户登录入参")
+public class TenantLoginDTO {
+
+    @ApiModelProperty(value = "登录账号", required = true)
+    private String username;
+
+    @ApiModelProperty(value = "密码", required = true)
+    private String password;
+
+    @ApiModelProperty(value = "租户ID(绑定多个租户时建议传)")
+    private Long tenantId;
+}

+ 47 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/entity/SystemUserEntity.java

@@ -0,0 +1,47 @@
+package com.storlead.account.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sp_system_user")
+@ApiModel(value = "SystemUserEntity", description = "系统用户(全局账号)")
+public class SystemUserEntity extends SysBaseField {
+
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty(value = "主键ID")
+    private Long id;
+
+    @ApiModelProperty(value = "登录账号")
+    private String username;
+
+    @ApiModelProperty(value = "手机号")
+    private String mobile;
+
+    @ApiModelProperty(value = "邮箱")
+    private String email;
+
+    @ApiModelProperty(value = "密码哈希")
+    private String passwordHash;
+
+    @ApiModelProperty(value = "昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "头像")
+    private String avatar;
+
+    @ApiModelProperty(value = "最近登录时间")
+    private Date lastLoginAt;
+
+    @ApiModelProperty(value = "最近登录IP")
+    private String lastLoginIp;
+}

+ 9 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/mapper/SystemUserEntityMapper.java

@@ -0,0 +1,9 @@
+package com.storlead.account.system.mapper;
+
+import com.storlead.account.system.entity.SystemUserEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SystemUserEntityMapper extends MyBaseMapper<SystemUserEntity> {
+}

+ 14 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/service/AuthLoginService.java

@@ -0,0 +1,14 @@
+package com.storlead.account.system.service;
+
+import com.storlead.account.system.dto.SystemLoginDTO;
+import com.storlead.account.system.dto.TenantLoginDTO;
+import com.storlead.framework.web.assemble.Result;
+
+public interface AuthLoginService {
+
+    Result<Object> systemLogin(SystemLoginDTO dto, String ip);
+
+    Result<Object> tenantLogin(TenantLoginDTO dto, String ip);
+
+    Result<Object> logout(String token);
+}

+ 7 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/service/SystemUserEntityService.java

@@ -0,0 +1,7 @@
+package com.storlead.account.system.service;
+
+import com.storlead.account.system.entity.SystemUserEntity;
+import com.storlead.framework.mybatis.service.MyBaseService;
+
+public interface SystemUserEntityService extends MyBaseService<SystemUserEntity> {
+}

+ 140 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/service/impl/AuthLoginServiceImpl.java

@@ -0,0 +1,140 @@
+package com.storlead.account.system.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.account.system.dto.SystemLoginDTO;
+import com.storlead.account.system.dto.TenantLoginDTO;
+import com.storlead.account.system.entity.SystemUserEntity;
+import com.storlead.account.system.service.AuthLoginService;
+import com.storlead.account.system.service.SystemUserEntityService;
+import com.storlead.account.tenant.entity.TenantEnterpriseUserRelEntity;
+import com.storlead.account.tenant.service.TenantEnterpriseUserRelEntityService;
+import com.storlead.framework.auth.vo.LoginUser;
+import com.storlead.framework.common.ecode.BCryptPasswordEncoder;
+import com.storlead.framework.redis.RedisService;
+import com.storlead.framework.web.assemble.Result;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class AuthLoginServiceImpl implements AuthLoginService {
+
+    private static final long TOKEN_EXPIRE_SECONDS = 7L * 24L * 60L * 60L;
+
+    @Resource
+    private SystemUserEntityService systemUserService;
+    @Resource
+    private TenantEnterpriseUserRelEntityService tenantUserRelService;
+    @Resource
+    private BCryptPasswordEncoder passwordEncoder;
+    @Resource
+    private RedisService redisService;
+
+    @Override
+    public Result<Object> systemLogin(SystemLoginDTO dto, String ip) {
+        if (StringUtils.isBlank(dto.getUsername()) || StringUtils.isBlank(dto.getPassword())) {
+            return Result.error("用户名或密码不能为空");
+        }
+        SystemUserEntity user = getEnabledSystemUser(dto.getUsername());
+        if (user == null || !passwordEncoder.matches(dto.getPassword(), user.getPasswordHash())) {
+            return Result.error("用户名或密码错误");
+        }
+        LoginUser loginUser = toLoginUser(user, true, null);
+        String token = issueToken(loginUser);
+        updateLastLogin(user, ip);
+        return loginResult(token, loginUser);
+    }
+
+    @Override
+    public Result<Object> tenantLogin(TenantLoginDTO dto, String ip) {
+        if (StringUtils.isBlank(dto.getUsername()) || StringUtils.isBlank(dto.getPassword())) {
+            return Result.error("用户名或密码不能为空");
+        }
+        SystemUserEntity user = getEnabledSystemUser(dto.getUsername());
+        if (user == null || !passwordEncoder.matches(dto.getPassword(), user.getPasswordHash())) {
+            return Result.error("用户名或密码错误");
+        }
+
+        LambdaQueryWrapper<TenantEnterpriseUserRelEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TenantEnterpriseUserRelEntity::getUserId, user.getId())
+                .eq(TenantEnterpriseUserRelEntity::getIsDelete, 0)
+                .eq(TenantEnterpriseUserRelEntity::getEnabled, true);
+        if (dto.getTenantId() != null) {
+            wrapper.eq(TenantEnterpriseUserRelEntity::getTenantId, dto.getTenantId());
+        }
+        List<TenantEnterpriseUserRelEntity> relList = tenantUserRelService.list(wrapper);
+        if (relList == null || relList.isEmpty()) {
+            return Result.error("当前账号未绑定可登录租户");
+        }
+        if (dto.getTenantId() == null && relList.size() > 1) {
+            return Result.error("账号绑定多个租户,请传tenantId");
+        }
+
+        TenantEnterpriseUserRelEntity rel = relList.get(0);
+        LoginUser loginUser = toLoginUser(user, false, rel.getTenantId());
+        String token = issueToken(loginUser);
+        updateLastLogin(user, ip);
+        return loginResult(token, loginUser);
+    }
+
+    @Override
+    public Result<Object> logout(String token) {
+        if (StringUtils.isBlank(token)) {
+            return Result.error("token不能为空");
+        }
+        redisService.deleteObject(token);
+        return Result.ok(true);
+    }
+
+    private SystemUserEntity getEnabledSystemUser(String username) {
+        return systemUserService.getOne(new LambdaQueryWrapper<SystemUserEntity>()
+                .eq(SystemUserEntity::getUsername, username)
+                .eq(SystemUserEntity::getIsDelete, 0)
+                .eq(SystemUserEntity::getEnabled, true)
+                .last("limit 1"));
+    }
+
+    private LoginUser toLoginUser(SystemUserEntity user, boolean isAdmin, Long tenantId) {
+        LoginUser loginUser = new LoginUser();
+        loginUser.setId(user.getId());
+        loginUser.setUserName(user.getUsername());
+        loginUser.setNickName(user.getNickName());
+        loginUser.setAvatar(user.getAvatar());
+        loginUser.setEmail(user.getEmail());
+        loginUser.setMobile(user.getMobile());
+        loginUser.setEnabled(Boolean.TRUE.equals(user.getEnabled()) ? 1 : 0);
+        loginUser.setIsAdmin(isAdmin);
+        loginUser.setTenantId(tenantId);
+        loginUser.setCompanyId(tenantId);
+        return loginUser;
+    }
+
+    private String issueToken(LoginUser loginUser) {
+        String token = UUID.randomUUID().toString().replace("-", "");
+        redisService.setCacheObject(token, JSONUtil.toJsonStr(loginUser), TOKEN_EXPIRE_SECONDS, TimeUnit.SECONDS);
+        return token;
+    }
+
+    private Result<Object> loginResult(String token, LoginUser loginUser) {
+        Map<String, Object> data = new HashMap<>();
+        data.put("token", token);
+        data.put("user", loginUser);
+        return Result.ok(data);
+    }
+
+    private void updateLastLogin(SystemUserEntity user, String ip) {
+        SystemUserEntity update = new SystemUserEntity();
+        update.setId(user.getId());
+        update.setLastLoginAt(new Date());
+        update.setLastLoginIp(ip);
+        systemUserService.updateById(update);
+    }
+}

+ 13 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/system/service/impl/SystemUserEntityServiceImpl.java

@@ -0,0 +1,13 @@
+package com.storlead.account.system.service.impl;
+
+import com.storlead.account.system.entity.SystemUserEntity;
+import com.storlead.account.system.mapper.SystemUserEntityMapper;
+import com.storlead.account.system.service.SystemUserEntityService;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SystemUserEntityServiceImpl
+        extends MyBaseServiceImpl<SystemUserEntityMapper, SystemUserEntity>
+        implements SystemUserEntityService {
+}

+ 19 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/dto/TenantEnterpriseQueryDTO.java

@@ -0,0 +1,19 @@
+package com.storlead.account.tenant.dto;
+
+import com.storlead.framework.mybatis.page.Page;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "TenantEnterpriseQueryDTO", description = "企业分页查询条件")
+public class TenantEnterpriseQueryDTO extends Page {
+
+    /** 模糊匹配:企业名称、编码、信用代码 */
+    @ApiModelProperty(value = "模糊关键字(企业名称/编码/信用代码)")
+    private String blurry;
+
+    /** 按租户过滤;企业租户端建议由服务端强制覆盖为当前登录租户 */
+    @ApiModelProperty(value = "租户ID(企业维度 tenant_id)")
+    private Long tenantId;
+}

+ 37 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/dto/TenantEnterpriseSaveDTO.java

@@ -0,0 +1,37 @@
+package com.storlead.account.tenant.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "TenantEnterpriseSaveDTO", description = "企业新增/修改入参")
+public class TenantEnterpriseSaveDTO {
+
+    @ApiModelProperty(value = "主键ID,新增不传,修改必传")
+    private Long id;
+
+    @ApiModelProperty(value = "租户ID(企业维度 tenant_id)")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "企业编码")
+    private String enterpriseCode;
+
+    @ApiModelProperty(value = "企业名称")
+    private String enterpriseName;
+
+    @ApiModelProperty(value = "统一社会信用代码")
+    private String licenseNo;
+
+    @ApiModelProperty(value = "联系人姓名")
+    private String contactName;
+
+    @ApiModelProperty(value = "联系人手机")
+    private String contactPhone;
+
+    @ApiModelProperty(value = "联系人邮箱")
+    private String contactEmail;
+
+    @ApiModelProperty(value = "显示排序,数值越小越靠前")
+    private Integer sort;
+}

+ 20 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/dto/TenantEnterpriseUserRelQueryDTO.java

@@ -0,0 +1,20 @@
+package com.storlead.account.tenant.dto;
+
+import com.storlead.framework.mybatis.page.Page;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "TenantEnterpriseUserRelQueryDTO", description = "企业-用户关系分页查询条件")
+public class TenantEnterpriseUserRelQueryDTO extends Page {
+
+    @ApiModelProperty(value = "租户ID(企业维度 tenant_id)")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "企业主表ID(sp_tenant_enterprise.id)")
+    private Long enterpriseId;
+
+    @ApiModelProperty(value = "平台用户ID")
+    private Long userId;
+}

+ 28 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/dto/TenantEnterpriseUserRelSaveDTO.java

@@ -0,0 +1,28 @@
+package com.storlead.account.tenant.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "TenantEnterpriseUserRelSaveDTO", description = "企业-用户关系新增/修改入参")
+public class TenantEnterpriseUserRelSaveDTO {
+
+    @ApiModelProperty(value = "主键ID,新增不传,修改必传")
+    private Long id;
+
+    @ApiModelProperty(value = "租户ID(企业维度 tenant_id)")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "企业主表ID(sp_tenant_enterprise.id)")
+    private Long enterpriseId;
+
+    @ApiModelProperty(value = "平台用户ID")
+    private Long userId;
+
+    @ApiModelProperty(value = "是否企业管理员:0否 1是")
+    private Integer isAdmin;
+
+    @ApiModelProperty(value = "显示排序")
+    private Integer sort;
+}

+ 49 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/entity/TenantEnterpriseEntity.java

@@ -0,0 +1,49 @@
+package com.storlead.account.tenant.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 企业主体(账户体系-企业维度),映射表 sp_tenant_enterprise。
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sp_tenant_enterprise")
+@ApiModel(value = "TenantEnterpriseEntity", description = "企业主体,对应表 sp_tenant_enterprise")
+public class TenantEnterpriseEntity extends SysBaseField {
+
+    /** 主键 */
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty(value = "主键ID")
+    private Long id;
+
+    /** 企业编码,业务侧唯一标识(与库表 enterprise_code 一致) */
+    @ApiModelProperty(value = "企业编码")
+    private String enterpriseCode;
+
+    /** 企业名称 */
+    @ApiModelProperty(value = "企业名称")
+    private String enterpriseName;
+
+    /** 统一社会信用代码,可空 */
+    @ApiModelProperty(value = "统一社会信用代码")
+    private String licenseNo;
+
+    /** 联系人姓名 */
+    @ApiModelProperty(value = "联系人姓名")
+    private String contactName;
+
+    /** 联系人手机 */
+    @ApiModelProperty(value = "联系人手机")
+    private String contactPhone;
+
+    /** 联系人邮箱 */
+    @ApiModelProperty(value = "联系人邮箱")
+    private String contactEmail;
+}

+ 37 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/entity/TenantEnterpriseUserRelEntity.java

@@ -0,0 +1,37 @@
+package com.storlead.account.tenant.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 企业下用户绑定关系,映射表 sp_tenant_user_rel。
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sp_tenant_user_rel")
+@ApiModel(value = "TenantEnterpriseUserRelEntity", description = "企业-用户绑定关系,对应表 sp_tenant_user_rel")
+public class TenantEnterpriseUserRelEntity extends SysBaseField {
+
+    /** 主键 */
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty(value = "主键ID")
+    private Long id;
+
+    /** 关联企业主表主键 sp_tenant_enterprise.id */
+    @ApiModelProperty(value = "企业主表ID(sp_tenant_enterprise.id)")
+    private Long enterpriseId;
+
+    /** 平台用户主键 */
+    @ApiModelProperty(value = "平台用户ID")
+    private Long userId;
+
+    /** 是否该企业管理员:0 否,1 是 */
+    @ApiModelProperty(value = "是否企业管理员:0否 1是")
+    private Integer isAdmin;
+}

+ 9 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/mapper/TenantEnterpriseEntityMapper.java

@@ -0,0 +1,9 @@
+package com.storlead.account.tenant.mapper;
+
+import com.storlead.account.tenant.entity.TenantEnterpriseEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface TenantEnterpriseEntityMapper extends MyBaseMapper<TenantEnterpriseEntity> {
+}

+ 9 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/mapper/TenantEnterpriseUserRelEntityMapper.java

@@ -0,0 +1,9 @@
+package com.storlead.account.tenant.mapper;
+
+import com.storlead.account.tenant.entity.TenantEnterpriseUserRelEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface TenantEnterpriseUserRelEntityMapper extends MyBaseMapper<TenantEnterpriseUserRelEntity> {
+}

+ 4 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 企业租户侧账户能力:操作范围应限定在当前登录用户所属 {@code tenant_id}(后续可接数据权限/网关校验)。
+ */
+package com.storlead.account.tenant;

+ 7 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/service/TenantEnterpriseEntityService.java

@@ -0,0 +1,7 @@
+package com.storlead.account.tenant.service;
+
+import com.storlead.account.tenant.entity.TenantEnterpriseEntity;
+import com.storlead.framework.mybatis.service.MyBaseService;
+
+public interface TenantEnterpriseEntityService extends MyBaseService<TenantEnterpriseEntity> {
+}

+ 7 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/service/TenantEnterpriseUserRelEntityService.java

@@ -0,0 +1,7 @@
+package com.storlead.account.tenant.service;
+
+import com.storlead.account.tenant.entity.TenantEnterpriseUserRelEntity;
+import com.storlead.framework.mybatis.service.MyBaseService;
+
+public interface TenantEnterpriseUserRelEntityService extends MyBaseService<TenantEnterpriseUserRelEntity> {
+}

+ 13 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/service/impl/TenantEnterpriseEntityServiceImpl.java

@@ -0,0 +1,13 @@
+package com.storlead.account.tenant.service.impl;
+
+import com.storlead.account.tenant.entity.TenantEnterpriseEntity;
+import com.storlead.account.tenant.mapper.TenantEnterpriseEntityMapper;
+import com.storlead.account.tenant.service.TenantEnterpriseEntityService;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TenantEnterpriseEntityServiceImpl
+        extends MyBaseServiceImpl<TenantEnterpriseEntityMapper, TenantEnterpriseEntity>
+        implements TenantEnterpriseEntityService {
+}

+ 13 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/service/impl/TenantEnterpriseUserRelEntityServiceImpl.java

@@ -0,0 +1,13 @@
+package com.storlead.account.tenant.service.impl;
+
+import com.storlead.account.tenant.entity.TenantEnterpriseUserRelEntity;
+import com.storlead.account.tenant.mapper.TenantEnterpriseUserRelEntityMapper;
+import com.storlead.account.tenant.service.TenantEnterpriseUserRelEntityService;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TenantEnterpriseUserRelEntityServiceImpl
+        extends MyBaseServiceImpl<TenantEnterpriseUserRelEntityMapper, TenantEnterpriseUserRelEntity>
+        implements TenantEnterpriseUserRelEntityService {
+}

+ 133 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/web/TenantEnterpriseEntityWebDelegate.java

@@ -0,0 +1,133 @@
+package com.storlead.account.tenant.web;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.account.tenant.dto.TenantEnterpriseQueryDTO;
+import com.storlead.account.tenant.dto.TenantEnterpriseSaveDTO;
+import com.storlead.account.tenant.entity.TenantEnterpriseEntity;
+import com.storlead.account.tenant.service.TenantEnterpriseEntityService;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.web.assemble.Result;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Objects;
+
+@Component
+public class TenantEnterpriseEntityWebDelegate {
+
+    @Resource
+    private TenantEnterpriseEntityService enterpriseService;
+
+    public Result<Object> tenantPage(TenantEnterpriseQueryDTO query) {
+        Long currentTenantId = LoginUserUtil.getCurrentTenantId();
+        if (currentTenantId == null) {
+            return Result.error("当前登录上下文未绑定租户");
+        }
+        query.setTenantId(currentTenantId);
+        return page(query);
+    }
+
+    public Result<Object> page(TenantEnterpriseQueryDTO query) {
+        Page<TenantEnterpriseEntity> page = new Page<>(query.getPageIndex(), query.getPageSize());
+        LambdaQueryWrapper<TenantEnterpriseEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TenantEnterpriseEntity::getIsDelete, 0);
+        if (query.getTenantId() != null) {
+            wrapper.eq(TenantEnterpriseEntity::getTenantId, query.getTenantId());
+        }
+        if (StringUtils.isNotBlank(query.getBlurry())) {
+            wrapper.and(w -> w.like(TenantEnterpriseEntity::getEnterpriseName, query.getBlurry())
+                    .or()
+                    .like(TenantEnterpriseEntity::getEnterpriseCode, query.getBlurry())
+                    .or()
+                    .like(TenantEnterpriseEntity::getLicenseNo, query.getBlurry()));
+        }
+        wrapper.orderByAsc(TenantEnterpriseEntity::getSort).orderByDesc(TenantEnterpriseEntity::getCreateTime);
+        return Result.ok(enterpriseService.page(page, wrapper));
+    }
+
+    public Result<Object> detail(Long id) {
+        return Result.ok(enterpriseService.getById(id));
+    }
+
+    public Result<Object> tenantDetail(Long id) {
+        Long currentTenantId = LoginUserUtil.getCurrentTenantId();
+        if (currentTenantId == null) {
+            return Result.error("当前登录上下文未绑定租户");
+        }
+        TenantEnterpriseEntity entity = enterpriseService.getById(id);
+        if (entity == null || !currentTenantId.equals(entity.getTenantId())) {
+            return Result.error("无权查看该数据");
+        }
+        return Result.ok(entity);
+    }
+
+    public Result<Object> save(TenantEnterpriseSaveDTO dto) {
+        TenantEnterpriseEntity entity = new TenantEnterpriseEntity();
+        BeanUtils.copyProperties(dto, entity);
+        entity.setId(null);
+        entity.setIsDelete(0);
+        if (entity.getEnabled() == null) {
+            entity.setEnabled(true);
+        }
+        if (entity.getSort() == null) {
+            entity.setSort(0);
+        }
+        return Result.ok(enterpriseService.save(entity));
+    }
+
+    public Result<Object> tenantSave(TenantEnterpriseSaveDTO dto) {
+        Long currentTenantId = LoginUserUtil.getCurrentTenantId();
+        if (currentTenantId == null) {
+            return Result.error("当前登录上下文未绑定租户");
+        }
+        dto.setTenantId(currentTenantId);
+        return save(dto);
+    }
+
+    public Result<Object> update(TenantEnterpriseSaveDTO dto) {
+        if (Objects.isNull(dto.getId())) {
+            return Result.error("id不能为空");
+        }
+        TenantEnterpriseEntity entity = enterpriseService.getById(dto.getId());
+        if (entity == null) {
+            return Result.error("记录不存在");
+        }
+        BeanUtils.copyProperties(dto, entity);
+        return Result.ok(enterpriseService.updateById(entity));
+    }
+
+    public Result<Object> tenantUpdate(TenantEnterpriseSaveDTO dto) {
+        Long currentTenantId = LoginUserUtil.getCurrentTenantId();
+        if (currentTenantId == null) {
+            return Result.error("当前登录上下文未绑定租户");
+        }
+        if (Objects.isNull(dto.getId())) {
+            return Result.error("id不能为空");
+        }
+        TenantEnterpriseEntity entity = enterpriseService.getById(dto.getId());
+        if (entity == null || !currentTenantId.equals(entity.getTenantId())) {
+            return Result.error("无权修改该数据");
+        }
+        dto.setTenantId(currentTenantId);
+        return update(dto);
+    }
+
+    public Result<Object> delete(Long id) {
+        return Result.ok(enterpriseService.lgDelete(id));
+    }
+
+    public Result<Object> tenantDelete(Long id) {
+        Long currentTenantId = LoginUserUtil.getCurrentTenantId();
+        if (currentTenantId == null) {
+            return Result.error("当前登录上下文未绑定租户");
+        }
+        TenantEnterpriseEntity entity = enterpriseService.getById(id);
+        if (entity == null || !currentTenantId.equals(entity.getTenantId())) {
+            return Result.error("无权删除该数据");
+        }
+        return delete(id);
+    }
+}

+ 134 - 0
java/storlead-account/storlead-account-biz/src/main/java/com/storlead/account/tenant/web/TenantEnterpriseUserRelEntityWebDelegate.java

@@ -0,0 +1,134 @@
+package com.storlead.account.tenant.web;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.account.tenant.dto.TenantEnterpriseUserRelQueryDTO;
+import com.storlead.account.tenant.dto.TenantEnterpriseUserRelSaveDTO;
+import com.storlead.account.tenant.entity.TenantEnterpriseUserRelEntity;
+import com.storlead.account.tenant.service.TenantEnterpriseUserRelEntityService;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.web.assemble.Result;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Objects;
+
+@Component
+public class TenantEnterpriseUserRelEntityWebDelegate {
+
+    @Resource
+    private TenantEnterpriseUserRelEntityService userRelService;
+
+    public Result<Object> tenantPage(TenantEnterpriseUserRelQueryDTO query) {
+        Long currentTenantId = LoginUserUtil.getCurrentTenantId();
+        if (currentTenantId == null) {
+            return Result.error("当前登录上下文未绑定租户");
+        }
+        query.setTenantId(currentTenantId);
+        return page(query);
+    }
+
+    public Result<Object> page(TenantEnterpriseUserRelQueryDTO query) {
+        Page<TenantEnterpriseUserRelEntity> page = new Page<>(query.getPageIndex(), query.getPageSize());
+        LambdaQueryWrapper<TenantEnterpriseUserRelEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TenantEnterpriseUserRelEntity::getIsDelete, 0);
+        if (query.getTenantId() != null) {
+            wrapper.eq(TenantEnterpriseUserRelEntity::getTenantId, query.getTenantId());
+        }
+        if (query.getEnterpriseId() != null) {
+            wrapper.eq(TenantEnterpriseUserRelEntity::getEnterpriseId, query.getEnterpriseId());
+        }
+        if (query.getUserId() != null) {
+            wrapper.eq(TenantEnterpriseUserRelEntity::getUserId, query.getUserId());
+        }
+        wrapper.orderByAsc(TenantEnterpriseUserRelEntity::getSort).orderByDesc(TenantEnterpriseUserRelEntity::getCreateTime);
+        return Result.ok(userRelService.page(page, wrapper));
+    }
+
+    public Result<Object> detail(Long id) {
+        return Result.ok(userRelService.getById(id));
+    }
+
+    public Result<Object> tenantDetail(Long id) {
+        Long currentTenantId = LoginUserUtil.getCurrentTenantId();
+        if (currentTenantId == null) {
+            return Result.error("当前登录上下文未绑定租户");
+        }
+        TenantEnterpriseUserRelEntity entity = userRelService.getById(id);
+        if (entity == null || !currentTenantId.equals(entity.getTenantId())) {
+            return Result.error("无权查看该数据");
+        }
+        return Result.ok(entity);
+    }
+
+    public Result<Object> save(TenantEnterpriseUserRelSaveDTO dto) {
+        TenantEnterpriseUserRelEntity entity = new TenantEnterpriseUserRelEntity();
+        BeanUtils.copyProperties(dto, entity);
+        entity.setId(null);
+        entity.setIsDelete(0);
+        if (entity.getEnabled() == null) {
+            entity.setEnabled(true);
+        }
+        if (entity.getSort() == null) {
+            entity.setSort(0);
+        }
+        if (entity.getIsAdmin() == null) {
+            entity.setIsAdmin(0);
+        }
+        return Result.ok(userRelService.save(entity));
+    }
+
+    public Result<Object> tenantSave(TenantEnterpriseUserRelSaveDTO dto) {
+        Long currentTenantId = LoginUserUtil.getCurrentTenantId();
+        if (currentTenantId == null) {
+            return Result.error("当前登录上下文未绑定租户");
+        }
+        dto.setTenantId(currentTenantId);
+        return save(dto);
+    }
+
+    public Result<Object> update(TenantEnterpriseUserRelSaveDTO dto) {
+        if (Objects.isNull(dto.getId())) {
+            return Result.error("id不能为空");
+        }
+        TenantEnterpriseUserRelEntity entity = userRelService.getById(dto.getId());
+        if (entity == null) {
+            return Result.error("记录不存在");
+        }
+        BeanUtils.copyProperties(dto, entity);
+        return Result.ok(userRelService.updateById(entity));
+    }
+
+    public Result<Object> tenantUpdate(TenantEnterpriseUserRelSaveDTO dto) {
+        Long currentTenantId = LoginUserUtil.getCurrentTenantId();
+        if (currentTenantId == null) {
+            return Result.error("当前登录上下文未绑定租户");
+        }
+        if (Objects.isNull(dto.getId())) {
+            return Result.error("id不能为空");
+        }
+        TenantEnterpriseUserRelEntity entity = userRelService.getById(dto.getId());
+        if (entity == null || !currentTenantId.equals(entity.getTenantId())) {
+            return Result.error("无权修改该数据");
+        }
+        dto.setTenantId(currentTenantId);
+        return update(dto);
+    }
+
+    public Result<Object> delete(Long id) {
+        return Result.ok(userRelService.lgDelete(id));
+    }
+
+    public Result<Object> tenantDelete(Long id) {
+        Long currentTenantId = LoginUserUtil.getCurrentTenantId();
+        if (currentTenantId == null) {
+            return Result.error("当前登录上下文未绑定租户");
+        }
+        TenantEnterpriseUserRelEntity entity = userRelService.getById(id);
+        if (entity == null || !currentTenantId.equals(entity.getTenantId())) {
+            return Result.error("无权删除该数据");
+        }
+        return delete(id);
+    }
+}

+ 5 - 0
java/storlead-account/storlead-account-biz/src/main/resources/mapper/SystemUserEntityMapper.xml

@@ -0,0 +1,5 @@
+<?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.storlead.account.system.mapper.SystemUserEntityMapper">
+    <!-- 默认 CRUD 由 MyBatis-Plus BaseMapper 提供 -->
+</mapper>

+ 5 - 0
java/storlead-account/storlead-account-biz/src/main/resources/mapper/TenantEnterpriseEntityMapper.xml

@@ -0,0 +1,5 @@
+<?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.storlead.account.tenant.mapper.TenantEnterpriseEntityMapper">
+    <!-- 自定义 SQL 在此扩展;默认 CRUD 由 MyBatis-Plus BaseMapper 提供 -->
+</mapper>

+ 5 - 0
java/storlead-account/storlead-account-biz/src/main/resources/mapper/TenantEnterpriseUserRelEntityMapper.xml

@@ -0,0 +1,5 @@
+<?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.storlead.account.tenant.mapper.TenantEnterpriseUserRelEntityMapper">
+    <!-- 自定义 SQL 在此扩展;默认 CRUD 由 MyBatis-Plus BaseMapper 提供 -->
+</mapper>

+ 41 - 0
java/storlead-api/pom.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-smarttrade-platform</artifactId>
+        <version>1.0</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-api</artifactId>
+
+    <name>storlead-api</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-account-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 41 - 0
java/storlead-api/src/main/java/com/storlead/api/StorleadTradeApplication.java

@@ -0,0 +1,41 @@
+package com.storlead.api;
+
+import lombok.extern.slf4j.Slf4j;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.env.Environment;
+
+import java.net.InetAddress;
+
+@SpringBootApplication(scanBasePackages = "com.storlead")
+@MapperScan({"com.storlead.account.tenant.mapper", "com.storlead.account.system.mapper"})
+@Slf4j
+public class StorleadTradeApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(StorleadTradeApplication.class, args);
+    }
+
+    @Bean
+    public ApplicationRunner startupEndpointLogger(Environment env) {
+        return new ApplicationRunner() {
+            @Override
+            public void run(ApplicationArguments args) throws Exception {
+                String port = env.getProperty("server.port", "8080");
+                String contextPath = env.getProperty("server.servlet.context-path", "");
+                String hostAddress = InetAddress.getLocalHost().getHostAddress();
+
+                String base = "http://" + hostAddress + ":" + port + contextPath;
+                log.info("==================================================");
+                log.info("API Base URL: {}/", base);
+                log.info("Swagger URL : {}/doc.html", base);
+                log.info("Swagger URL : {}/swagger-ui.html", base);
+                log.info("==================================================");
+            }
+        };
+    }
+}

+ 162 - 0
java/storlead-api/src/main/resources/application-dev.yml

@@ -0,0 +1,162 @@
+server:
+  port: 10010
+  tomcat:
+    max-swallow-size: -1
+    basedir: /app/temp
+  servlet:
+    context-path: /api
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 20MB
+      max-request-size: 20MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+  redis:
+    host: test1.storlead.com #test1.storlead.com
+    port: 59394
+    database: 10
+    lettuce:
+      pool:
+        max-wait: 100000
+        max-idle: 10
+        max-active: 100
+    timeout: 5000
+    password: bnoCWkyDqYA*ecT7FoL7
+
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_smart_trade_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+          driver-class-name: com.mysql.jdbc.Driver
+#          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_tems_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+#          username: root
+#          password: rCgRgLjH99Xvg5BN
+#          driver-class-name: com.mysql.jdbc.Driver
+#        master:
+#          url: jdbc:mysql://139.159.206.64:65369/sp_tems_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+#          username: storlead_tems
+#          password: DW8YRN*5!6u&Ajswgjt
+#          driver-class-name: com.mysql.jdbc.Driver
+#        project:
+#          driver-class-name: com.mysql.jdbc.Driver
+#          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_project_dev?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+#          username: root
+#          password: rCgRgLjH99Xvg5BN
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package:
+  typeEnumsPackage:
+#  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+#    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /app/sp/${spring.application.name}/log
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: debug
+
+
+environment: test
+

+ 220 - 0
java/storlead-api/src/main/resources/application-prod.yml

@@ -0,0 +1,220 @@
+#开发模式
+debug: false
+server:
+  port: 10086
+  tomcat:
+    max-swallow-size: -1
+    basedir: /app/sp/sp-tems/temp
+  servlet:
+    context-path: /api
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 20MB
+      max-request-size: 20MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+  redis:
+    database: 14
+    host: 192.168.0.210
+    lettuce:
+      pool:
+        max-active: 8   #最大连接数据库连接数,设 0 为没有限制
+        max-idle: 8     #最大等待连接中的数量,设 0 为没有限制
+        max-wait: -1ms  #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
+        min-idle: 0     #最小等待连接中的数量,设 0 为没有限制
+      shutdown-timeout: 100ms
+    password: 'B3f@NT4y%etekQaDkufd'
+    port: 56379
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/sp_tems_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+          username: storlead_tems
+          password: DW8YRN*5!6u&Ajswgjt
+        project:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/storlead_project_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_project
+          password: DW8YRN*5!6u&Ajswgjt
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_ecology
+          password: 3raNoDvbo7jqbwtedQGQ
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.tems.modules.*.entity
+  type-enums-package: com.storlead.tems.modules.console.enums;com.storlead.tems.modules.perform.enums;com.storlead.tems.modules.project.enums
+    #  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  #    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#应用专用配置
+lingcun :
+  path :
+    #文件上传根目录 设置
+    upload: /app/upload
+    #webapp文件路径
+    webapp: /app/upload
+  shiro:
+    excludeUrls: /lingcun/login,/lingcun/logout,/lingcun/getCheckCode
+  # 表单设计器配置
+  desform:
+    # 主题颜色(仅支持 16进制颜色代码)
+    theme-color: "#1890ff"
+  # 在线预览文件服务器地址配置
+  file-view-domain: http://localhost:8080/api/dist/app/view/
+  #
+sp:
+  oss:
+    endpoint: oss-cn-shenzhen.aliyuncs.com
+    accessKey: LTAI5t5n6K9ramopnY4KNQnM
+    secretKey: xmA6wObeUAKDux92DX3qfLNWAQAQZm
+    bucketName: sp-tems
+
+
+storlead:
+  project:
+    baseUrl: project.storlead.com
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /app/sp/${spring.application.name}/log
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: debug
+
+
+# 企业微信
+corp-wechat:
+  corpId: ww5323bd8ab4394132
+  # 这个是通讯录secret只能用来调用通讯录相关API使用
+  corpAddressSecret: FE-ofiE08oeT8DsccbigqPFWl6Nk8LRKBFffpL76Z-M
+  corpAgentId: 1000025
+  corpAgentSecret: MqG2_t0HB4L2KN6MsOVdpL48XFB26VkUyTsKHT0T6y0
+
+environment: prod
+
+system:
+  config:
+    orgSyncMode: 0  # 0:系统维护 1:同步OA, 2:同步企业微信
+
+  license:
+    httpUrl: http://localhost:10010${server.servlet.context-path}
+    activationCode: "activationCode"
+
+file:
+  mode: 1
+  filePath: /files
+  active: prod
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /app/files/
+    avatar: /app/files/avatar/
+  windows:
+    path: D:\images\file\
+    avatar: D:\images\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5

+ 144 - 0
java/storlead-api/src/main/resources/application-test.yml

@@ -0,0 +1,144 @@
+server:
+  port: 10020
+  tomcat:
+    max-swallow-size: -1
+    basedir: /app/sp/smarttrade/temp/${spring.profiles.active}
+  servlet:
+    context-path: /api
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 20MB
+      max-request-size: 20MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+  redis:
+    host: test1.storlead.com
+    port: 59394
+    database: 14
+    lettuce:
+      pool:
+        max-wait: 100000
+        max-idle: 10
+        max-active: 100
+    timeout: 5000
+    password: bnoCWkyDqYA*ecT7FoL7
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_smart_trade_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+          driver-class-name: com.mysql.jdbc.Driver
+
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.tems.modules.*.entity
+  type-enums-package:
+    #  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  #    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /app/sp/okr/${spring.profiles.active}/log
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: debug

+ 52 - 0
java/storlead-api/src/main/resources/application.yml

@@ -0,0 +1,52 @@
+spring:
+  profiles:
+    active: dev
+
+  mvc:
+    pathmatch:
+      matching-strategy: ant_path_matcher
+  #  security:
+  #    jwt:
+  #      # 自定义 secretKey
+  #      secretKey: VzMzUUNCY0g2cnRVQkR6OU5kTnVUY2tkZWlodFdkc0dpdVRwTmk4dnRWc2lKYmllRnEyekVLV29NWEJIM2IzSm1wRllacWdndFZmZFY0UTk0RmhxQm4zR1R4
+  #      expiryInHours: 2400
+  application:
+    name: sp-smarttrade
+  main:
+    allow-circular-references: true
+    allow-bean-definition-overriding: true
+  # 定时任务
+  quartz:
+    jdbc:
+      initialize-schema: never
+    job-store-type: jdbc
+    properties:
+      org:
+        quartz:
+          scheduler:
+            instanceId: AUTO
+            instanceName: clusteredScheduler
+          jobStore:
+            useProperties: true
+            isClustered: true
+            maxMisfiresToHandleAtATime: 1
+            tablePrefix: qrtz_
+   #         class: org.quartz.impl.jdbcjobstore.LocalDataSourceJobStore
+            class: org.quartz.impl.jdbcjobstore.JobStoreTX
+            clusterCheckinInterval: 3600000
+            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+          threadPool:
+            threadCount: 10
+            threadPriority: 5
+            threadsInheritContextClassLoaderOfInitializingThread: true
+            class: org.quartz.simpl.SimpleThreadPool
+  # 在上下文中没有Executor bean的情况下,Spring Boot会自动配置ThreadPoolTaskExecutor,并使用合理的默认值,这些默认值可以自动关联到异步任务执行(@EnableAsync)和Spring MVC异步请求处理。
+  #如果您已经在上下文中定义了一个自定义执行器,那么常规任务执行(即@EnableAsync)将透明地使用它,但是Spring MVC支持将不会被配置,因为它需要一个AsyncTaskExecutor实现(名为applicationTaskExecutor)。根据您的目标安排,您可以将执行程序更改为ThreadPoolTaskExecutor,或者定义一个ThreadPoolTaskExecutor和一个包装自定义执行程序的AsyncConfigurer。
+  task:
+    execution:
+      pool:
+        core-size: 10
+        max-size: 100
+        queue-capacity: 50
+
+

+ 5 - 0
java/storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/LoginUser.java

@@ -38,6 +38,11 @@ public class LoginUser implements Serializable {
      */
     private Long companyId;
 
+    /**
+     * 租户(企业)ID,与数据行 tenant_id 对齐;未单独下发时可由业务等同 companyId。
+     */
+    private Long tenantId;
+
     /**
      * 分公司ID
      */

+ 8 - 0
java/storlead-framework/storlead-auth/src/main/java/com/storlead/framework/util/LoginUserUtil.java

@@ -3,6 +3,7 @@ package com.storlead.framework.util;
 import com.storlead.framework.auth.vo.LoginUser;
 import com.storlead.framework.common.constant.UserCacheKeyConstants;
 import com.storlead.framework.core.context.Context;
+import com.storlead.framework.core.tenant.TenantContext;
 import lombok.extern.log4j.Log4j2;
 
 /**
@@ -45,4 +46,11 @@ public class LoginUserUtil {
         }
     }
 
+    /**
+     * 当前租户 ID(请求线程内);未登录或未绑定时为 null。
+     */
+    public static Long getCurrentTenantId() {
+        return TenantContext.getTenantId();
+    }
+
 }

+ 3 - 1
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/properties/UrlChainDefinitionPorperties.java

@@ -82,6 +82,8 @@ public class UrlChainDefinitionPorperties {
 			"/mail/download_url",
 			"/wxapi/wxclientmenu/**",
 			"/wxapi/wxclientmenu/*",
-			"/wxapi/getauth"
+			"/wxapi/getauth",
+			"/account/auth/system/login",
+			"/account/auth/tenant/login"
 	};
 }

+ 25 - 0
java/storlead-framework/storlead-core/src/main/java/com/storlead/framework/core/tenant/TenantConstants.java

@@ -0,0 +1,25 @@
+package com.storlead.framework.core.tenant;
+
+/**
+ * 多租户(企业)上下文键名与 HTTP 头约定。
+ */
+public final class TenantConstants {
+
+    private TenantConstants() {
+    }
+
+    /**
+     * 当前请求线程内解析出的租户 ID(一般与登录用户所属企业一致)。
+     */
+    public static final String CURRENT_TENANT_ID = "current_tenant_id";
+
+    /**
+     * 为 true 时跳过多租户行拦截(仅用于平台任务、脚本等可信场景)。
+     */
+    public static final String IGNORE_TENANT_LINE = "ignore_tenant_line";
+
+    /**
+     * 可选:网关或服务间透传租户(业务需在可信链路上使用并自行校验权限)。
+     */
+    public static final String HTTP_HEADER_TENANT_ID = "X-Tenant-Id";
+}

+ 60 - 0
java/storlead-framework/storlead-core/src/main/java/com/storlead/framework/core/tenant/TenantContext.java

@@ -0,0 +1,60 @@
+package com.storlead.framework.core.tenant;
+
+import com.storlead.framework.core.context.Context;
+import com.storlead.framework.core.context.IContext;
+
+/**
+ * 基于 {@link Context} 的请求级租户 ID;供 Web、MyBatis 插件、业务代码统一读取。
+ */
+public final class TenantContext {
+
+    private TenantContext() {
+    }
+
+    /**
+     * 写入当前租户(通常在登录鉴权通过后调用)。
+     */
+    public static void bindTenantId(Long tenantId) {
+        IContext ctx = Context.getContext();
+        if (ctx != null && tenantId != null) {
+            ctx.setAttribute(TenantConstants.CURRENT_TENANT_ID, tenantId);
+        }
+    }
+
+    /**
+     * 当前线程上下文中解析出的租户 ID;白名单匿名请求等场景可能为 null。
+     */
+    public static Long getTenantId() {
+        if (isIgnoreTenantLine()) {
+            return null;
+        }
+        IContext ctx = Context.getContext();
+        if (ctx == null) {
+            return null;
+        }
+        return ctx.getAttribute(TenantConstants.CURRENT_TENANT_ID, Long.class);
+    }
+
+    public static boolean isIgnoreTenantLine() {
+        IContext ctx = Context.getContext();
+        if (ctx == null) {
+            return false;
+        }
+        return Boolean.TRUE.equals(ctx.getAttribute(TenantConstants.IGNORE_TENANT_LINE, Boolean.class));
+    }
+
+    /**
+     * 跳过多租户 SQL 条件注入与填充(必须仅在可信代码路径使用)。
+     */
+    public static void setIgnoreTenantLine(boolean ignore) {
+        IContext ctx = Context.getContext();
+        if (ctx == null) {
+            return;
+        }
+        if (ignore) {
+            ctx.setAttribute(TenantConstants.IGNORE_TENANT_LINE, Boolean.TRUE);
+        } else {
+            ctx.removeAttribute(TenantConstants.IGNORE_TENANT_LINE);
+        }
+    }
+}

+ 4 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/entity/SysBaseField.java

@@ -28,6 +28,10 @@ public class SysBaseField implements Serializable {
     @TableField(fill = FieldFill.INSERT)
     private Long ownerBy;
 
+    @ApiModelProperty(value = "租户(企业)ID")
+    @TableField(value = "tenant_id", fill = FieldFill.INSERT)
+    private Long tenantId;
+
     @TableField(fill = FieldFill.INSERT)
     private Long createBy;
 

+ 10 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/handler/MyMetaObjectHandler.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
 import com.storlead.framework.auth.vo.LoginUser;
 import com.storlead.framework.common.constant.UserCacheKeyConstants;
 import com.storlead.framework.core.context.Context;
+import com.storlead.framework.core.tenant.TenantContext;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.reflection.MetaObject;
 
@@ -55,6 +56,15 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
                 this.setInsertFieldValByName("ownerBy", loginUser.getId(), metaObject);
             }
         }
+        if (metaObject.hasSetter("tenantId")) {
+            Object tenantIdVal = getFieldValByName("tenantId", metaObject);
+            if (Objects.isNull(tenantIdVal)) {
+                Long tid = TenantContext.getTenantId();
+                if (Objects.nonNull(tid)) {
+                    this.setInsertFieldValByName("tenantId", tid, metaObject);
+                }
+            }
+        }
         Object updateBy = getFieldValByName("updateBy", metaObject);
         if (Objects.isNull(updateBy)) {
             if (Objects.nonNull(loginUser)) {

+ 17 - 7
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/interceptor/MybatisPlusConfig.java

@@ -4,15 +4,20 @@ import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS;
 import com.baomidou.mybatisplus.core.MybatisConfiguration;
 import com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
+import com.baomidou.mybatisplus.core.parser.ISqlParser;
 import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
 import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
 import com.storlead.framework.mybatis.handler.MyMetaObjectHandler;
+import com.storlead.framework.mybatis.tenant.StorleadTenantHandler;
+import com.storlead.framework.mybatis.tenant.TenantProperties;
 import lombok.extern.log4j.Log4j2;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.ibatis.plugin.Interceptor;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
 import org.springframework.context.ApplicationContext;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Primary;
@@ -32,16 +37,22 @@ import java.util.List;
  */
 @Configuration
 @MapperScan(value={"com.storlead.*.mapper"})
+@EnableConfigurationProperties(TenantProperties.class)
 @Log4j2
 public class MybatisPlusConfig {
 
     /**
-     **  分页插件
+     **  分页 + 多租户行(tenant_id)SQL 解析
      */
     @Bean
-    public PaginationInterceptor paginationInterceptor() {
-        // 设置sql的limit为无限制,默认是500
-        return new PaginationInterceptor().setLimit(-1);
+    public PaginationInterceptor paginationInterceptor(TenantProperties tenantProperties) {
+        PaginationInterceptor interceptor = new PaginationInterceptor().setLimit(-1);
+        TenantSqlParser tenantSqlParser = new TenantSqlParser();
+        tenantSqlParser.setTenantHandler(new StorleadTenantHandler(tenantProperties));
+        List<ISqlParser> sqlParserList = new ArrayList<>();
+        sqlParserList.add(tenantSqlParser);
+        interceptor.setSqlParserList(sqlParserList);
+        return interceptor;
     }
 //
 //    @Bean
@@ -58,7 +69,7 @@ public class MybatisPlusConfig {
 
     @Primary
     @Bean
-    public MybatisSqlSessionFactoryBean sqlSessionFactory(DataSource dataSource, ApplicationContext appContext) throws Exception {
+    public MybatisSqlSessionFactoryBean sqlSessionFactory(DataSource dataSource, ApplicationContext appContext, PaginationInterceptor paginationInterceptor) throws Exception {
 
         log.error("sqlSessionFactory--先走");
 
@@ -69,8 +80,7 @@ public class MybatisPlusConfig {
         Resource[] maps = mapperLocations(appContext);
         mybatisPlus.setMapperLocations(maps);
 
-        Interceptor[] plugins = {paginationInterceptor()};
-//        Interceptor[] plugins = {paginationInterceptor()};
+        Interceptor[] plugins = {paginationInterceptor};
         mybatisPlus.setPlugins(plugins);
 
         MybatisConfiguration mc = new MybatisConfiguration();

+ 55 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/tenant/StorleadTenantHandler.java

@@ -0,0 +1,55 @@
+package com.storlead.framework.mybatis.tenant;
+
+import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
+import com.storlead.framework.core.tenant.TenantContext;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.LongValue;
+
+import java.util.Locale;
+
+/**
+ * MyBatis-Plus 3.1.x {@link com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser} 使用的租户解析器。
+ */
+public class StorleadTenantHandler implements TenantHandler {
+
+    private final TenantProperties properties;
+
+    public StorleadTenantHandler(TenantProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public Expression getTenantId() {
+        if (TenantContext.isIgnoreTenantLine()) {
+            return null;
+        }
+        Long id = TenantContext.getTenantId();
+        if (id == null) {
+            return null;
+        }
+        return new LongValue(String.valueOf(id));
+    }
+
+    @Override
+    public String getTenantIdColumn() {
+        return properties.getColumnName();
+    }
+
+    @Override
+    public boolean doTableFilter(String tableName) {
+        if (tableName == null) {
+            return true;
+        }
+        String normalized = tableName.replace("`", "").toLowerCase(Locale.ROOT);
+        for (String raw : properties.getIgnoreTables()) {
+            if (raw == null) {
+                continue;
+            }
+            String t = raw.replace("`", "").toLowerCase(Locale.ROOT);
+            if (t.equals(normalized)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 25 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/tenant/TenantProperties.java

@@ -0,0 +1,25 @@
+package com.storlead.framework.mybatis.tenant;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 多租户行拦截配置:忽略无 tenant_id 列的表等。
+ */
+@Data
+@ConfigurationProperties(prefix = "storlead.tenant")
+public class TenantProperties {
+
+    /**
+     * 数据库列名,需与实体字段映射一致(默认 tenant_id)。
+     */
+    private String columnName = "tenant_id";
+
+    /**
+     * 不参与租户条件注入的表名(小写,不含库名)。
+     */
+    private List<String> ignoreTables = new ArrayList<>();
+}

+ 18 - 7
java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/config/Swagger2Config.java

@@ -75,14 +75,25 @@ public class Swagger2Config implements WebMvcConfigurer {
     }
 
     @Bean
-    public Docket createRestApi() {
-        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
-                // 此包路径下的类,才生成接口文档
+    public Docket platformAdminApi() {
+        return buildGroupedDocket("platform-admin", "/platform/admin/.*");
+    }
+
+    @Bean
+    public Docket tenantAccountApi() {
+        return buildGroupedDocket("tenant-account", "/account/.*");
+    }
+
+    private Docket buildGroupedDocket(String groupName, String pathRegex) {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .groupName(groupName)
+                .apiInfo(apiInfo())
+                .select()
                 .apis(RequestHandlerSelectors.basePackage("com.storlead"))
-                // 加了ApiOperation注解的类,才生成接口文档
-                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)).paths(PathSelectors.any())
-                .build().securityContexts(Arrays.asList(securityContext()))
-                // ApiKey的name需与SecurityReference的reference保持一致
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                .paths(PathSelectors.regex(pathRegex))
+                .build()
+                .securityContexts(Arrays.asList(securityContext()))
                 .securitySchemes(Arrays.asList(new ApiKey("token", "token", io.swagger.v3.oas.models.security.SecurityScheme.In.HEADER.name())));
     }
 

+ 1 - 1
java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/filter/AllRequestFilter.java

@@ -33,7 +33,7 @@ public class AllRequestFilter implements Filter, Ordered {
         //这里要加上content-type
         response.setHeader("Access-Control-Allow-Credentials", "true");
 
-        response.setHeader("Access-Control-Allow-Headers","x-requested-with,Authorization,Sec-WebSocket-Protocol,token,method,Content-Type, x-requested-with, X-Custom-Header,x-access-token");
+        response.setHeader("Access-Control-Allow-Headers","x-requested-with,Authorization,Sec-WebSocket-Protocol,token,method,Content-Type, x-requested-with, X-Custom-Header,X-Tenant-Id,x-access-token");
         response.setHeader("Access-Control-Max-Age", "3600");
         response.setHeader("Keep-Alive", "60s");
         response.setHeader("Connection", "upgrade, keep-alive");

+ 26 - 0
java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/filter/AuthRequestFilter.java

@@ -16,6 +16,8 @@ import com.storlead.framework.common.util.SystemUtils;
 import com.storlead.framework.common.util.UrlChainBlackAndWhiteUtil;
 import com.storlead.framework.core.context.Context;
 import com.storlead.framework.core.context.IContext;
+import com.storlead.framework.core.tenant.TenantConstants;
+import com.storlead.framework.core.tenant.TenantContext;
 import com.storlead.framework.redis.RedisService;
 import com.storlead.framework.web.assemble.Result;
 import com.storlead.framework.web.enums.ResultCode;
@@ -162,6 +164,30 @@ public class AuthRequestFilter implements Filter, Ordered {
                     context.setAttribute(UserCacheKeyConstants.LOGIN_USER_INFO_TOKEN_KEY, token);
                     context.setAttribute(UserCacheKeyConstants.LOGIN_USER_INFO_ID_KEY, loginUserInfo.getId());
 
+                    Long tenantId = loginUserInfo.getTenantId();
+                    if (tenantId == null) {
+                        tenantId = loginUserInfo.getCompanyId();
+                    }
+                    if (tenantId != null) {
+                        TenantContext.bindTenantId(tenantId);
+                    }
+                    String headerTenant = req.getHeader(TenantConstants.HTTP_HEADER_TENANT_ID);
+                    if (StringUtils.isNotBlank(headerTenant)) {
+                        try {
+                            Long headerTid = Long.valueOf(headerTenant.trim());
+                            if (tenantId != null && !tenantId.equals(headerTid)) {
+                                log.warn("X-Tenant-Id {} 与登录租户 {} 不一致,已以登录信息为准", headerTid, tenantId);
+                            } else if (tenantId == null && Boolean.TRUE.equals(loginUserInfo.getIsAdmin())) {
+                                TenantContext.bindTenantId(headerTid);
+                            }
+                        } catch (NumberFormatException e) {
+                            log.warn("非法 X-Tenant-Id 请求头: {}", headerTenant);
+                        }
+                    }
+                    if (TenantContext.getTenantId() == null) {
+                        log.warn("已登录用户未解析到租户 ID(LoginUser.tenantId / companyId 均为空),多租户条件将不生效: userId={}", loginUserInfo.getId());
+                    }
+
                     // 修改失效时间,超过24小时没有使用,
                     Long expire = redisService.getCacheExpire(token,TimeUnit.SECONDS);
                     log.debug("getCacheExpire-----------当前用户信息 = "+loginUserInfo);

+ 5 - 12
pom.xml

@@ -18,6 +18,8 @@
 
     <modules>
         <module>java/storlead-framework</module>
+        <module>java/storlead-api</module>
+        <module>java/storlead-account</module>
     </modules>
 
     <properties>
@@ -128,19 +130,19 @@
 
         <dependency>
             <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-user</artifactId>
+            <artifactId>storlead-web</artifactId>
             <version>1.0</version>
         </dependency>
 
         <dependency>
             <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-web</artifactId>
+            <artifactId>storlead-account-api</artifactId>
             <version>1.0</version>
         </dependency>
 
         <dependency>
             <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-sms</artifactId>
+            <artifactId>storlead-account-biz</artifactId>
             <version>1.0</version>
         </dependency>
 
@@ -155,15 +157,6 @@
             <artifactId>storlead-common</artifactId>
             <version>1.0</version>
         </dependency>
-
-
-
-        <dependency>
-            <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-mail-service</artifactId>
-            <version>1.0</version>
-        </dependency>
-
         <!--            &lt;!&ndash;swagger&ndash;&gt;-->
         <dependency>
             <groupId>io.springfox</groupId>