a
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
好的,作为一名专业的商业分析顾问,我将基于您提供的“中国開発コンクリート株式会社”的信息,运用商业画布模型进行深度分析,并评估其潜在商业价值。
|
||||
|
||||
**免责声明:** 本分析报告基于公开的有限信息进行框架性推演,旨在提供一种结构化的分析视角,不构成投资或合作决策的直接依据。深度合作前需进行详尽的尽职调查。
|
||||
|
||||
---
|
||||
|
||||
### **中国開発コンクリート株式会社商业画布分析报告**
|
||||
|
||||
#### **1. 客户细分 (Customer Segments)**
|
||||
1. **核心内容分析:** 作为一家生混凝土及混凝土制品制造商,其客户群体高度集中于B2B领域。主要客户包括:**建筑总承包商**(负责住宅、商业楼宇、公共设施建设)、**土木工程公司**(负责道路、桥梁、水利等基础设施建设)、**预制构件厂**(作为其原材料供应商)、以及**房地产开发商**和**地方政府**(公共项目发包方)。
|
||||
2. **优势/特点:** 客户群体明确且集中,需求相对稳定且可预测。作为地方性企业(山口县岩国市),可能在本地区域拥有深厚的客户网络和口碑,与本地建筑商建立了长期关系,具备一定的地域壁垒。
|
||||
3. **潜在风险或关注点:** 客户高度依赖日本国内建筑业景气周期,宏观经济下行或建筑投资减少会直接冲击业务。客户集中度可能较高,若少数大客户流失将带来显著风险。面对全国性大型建材供应商的竞争压力。
|
||||
4. **评分:7分**。定位清晰,但受制于行业周期性且面临竞争。
|
||||
|
||||
#### **2. 价值主张 (Value Propositions)**
|
||||
1. **核心内容分析:** 核心价值在于提供**可靠、及时、符合规格的混凝土产品**。具体可能包括:**产品质量稳定**(强度、耐久性达标)、**供应及时性**(配合建筑工地施工进度,生混凝土具有极强时效性)、**本地化服务与快速响应**(距离近,运输和售后支持快捷)、以及可能的**定制化产品**(满足特殊工程需求)。
|
||||
2. **优势/特点:** “本地生产、本地供应”是其最核心的优势,能最大程度减少运输时间,保障生混凝土的新鲜度和施工质量。长期经营积累的技术经验能确保产品符合严苛的日本工业标准(JIS)。
|
||||
3. **潜在风险或关注点:** 价值主张同质化较高,与竞争对手差异可能不大,容易陷入价格竞争。对新技术(如环保混凝土、高流动性混凝土)的研发和应用能力未知,可能缺乏高端差异化价值。
|
||||
4. **评分:6分**。价值扎实但缺乏显著独特性,易受成本竞争影响。
|
||||
|
||||
#### **3. 渠道通路 (Channels)**
|
||||
1. **核心内容分析:** 渠道相对传统和直接。主要包括:**直销团队**(销售人员直接对接承包商和开发商)、**电话/邮件订单**(针对老客户的重复采购)、**企业官网**(cec-n.co.jp,可能用于信息展示、资质公示和初步接洽)。生混凝土的销售严重依赖**物流车队**(混凝土搅拌车)这一物理渠道。
|
||||
2. **优势/特点:** 直销模式有利于维护客户关系,深入理解客户需求。自有或合作的物流车队是确保价值交付的关键实体渠道,控制物流意味着控制服务质量和时效。
|
||||
3. **潜在风险或关注点:** 渠道单一,对线下销售团队依赖度高。线上渠道可能仅作为信息窗口,未发挥营销和获客作用。物流成本(油价、司机人力)和效率管理是持续挑战。
|
||||
4. **评分:6分**。渠道与业务匹配,但缺乏数字化和多元化拓展。
|
||||
|
||||
#### **4. 客户关系 (Customer Relationships)**
|
||||
1. **核心内容分析:** 客户关系模式以**长期项目合作和基于信任的重复交易**为主。通过销售人员的定期拜访、可靠的产品交付、及时的问题处理来维护关系。可能涉及**签订年度供应框架协议**。
|
||||
2. **优势/特点:** 建筑行业特性决定了客户粘性较高,一旦建立合格供应商资格,合作关系会比较稳定。本地化企业更容易建立基于社区和口碑的深度信任关系。
|
||||
3. **潜在风险或关注点:** 关系维护成本较高(人力密集型)。如果竞争对手提供显著更优的价格或技术方案,客户也可能转移。对关键客户代表的人际关系过度依赖存在风险。
|
||||
4. **评分:7分**。行业属性利于长期关系,但维系成本不低且存在切换风险。
|
||||
|
||||
#### **5. 收入来源 (Revenue Streams)**
|
||||
1. **核心内容分析:** 收入来源较为单一,主要来自:**生混凝土销售**(按立方米计价)、**预制混凝土制品销售**(如混凝土砌块、管桩等,按件或规格计价)。可能还有少量的**运输服务收入**(如果运费单独计算)。
|
||||
2. **优势/特点:** 收入模式简单清晰,与工程量直接挂钩,容易核算。
|
||||
3. **潜在风险或关注点:** **收入极度依赖单一产品线(混凝土)和单一行业(建筑业)**,抗风险能力弱。定价权可能较弱,受原材料成本波动和市场竞争影响大。缺乏经常性收入或高利润的服务性收入。
|
||||
4. **评分:5分**。收入来源过于集中,波动性大,盈利稳定性差。
|
||||
|
||||
#### **6. 核心资源 (Key Resources)**
|
||||
1. **核心内容分析:** 核心资源包括:**物理资产**(混凝土搅拌站、生产设备、实验室、运输车队)、**地理位置**(靠近料源和客户群的工厂用地)、**资质与认证**(JIS等质量认证)、**经验丰富的生产与技术人员**、以及**本地客户网络**。
|
||||
2. **优势/特点:** 生产设施和地理位置是重资产壁垒,一旦建成难以被复制。技术工人的经验和稳定的质量体系是运营保障。本地网络是无形资产。
|
||||
3. **潜在风险或关注点:** 属于重资产模式,设备折旧和维护成本高。对关键技术人员依赖性强。品牌影响力可能仅限于区域,难以向外扩张。
|
||||
4. **评分:7分**。拥有实质性核心资产,但沉重且灵活性不足。
|
||||
|
||||
#### **7. 关键业务 (Key Activities)**
|
||||
1. **核心内容分析:** 最关键的业务活动包括:**生产运营**(精确配料、搅拌、质量控制)、**物流调度**(高效安排搅拌车,确保准时送达)、**销售与客户管理**(获取订单、维护关系)、**质量控制与测试**(确保每批产品达标)。
|
||||
2. **优势/特点:** 专注于生产与交付的核心流程,在区域范围内可能实现了较高的运营效率。质量控制是生命线。
|
||||
3. **潜在风险或关注点:** 业务活动创新不足,如生产流程优化、物流智能化(如调度系统)的投入可能不够。研发活动(新产品、新工艺)可能薄弱。
|
||||
4. **评分:7分**。关键业务执行到位是生存基础,但增值活动有限。
|
||||
|
||||
#### **8. 重要合作 (Key Partnerships)**
|
||||
1. **核心内容分析:** 重要合作伙伴包括:**原材料供应商**(水泥、骨料、外加剂厂商)、**物流合作伙伴**(如果部分车队外包)、**设备供应商**(维护与更新)、**金融机构**(提供运营资金)、**行业协会与标准机构**。
|
||||
2. **优势/特点:** 与本地或区域性原材料供应商可能建立了长期稳定的合作,有利于保障供应和获得一定的账期支持。
|
||||
3. **潜在风险或关注点:** 对上游供应商(尤其是水泥)议价能力弱,原材料成本波动会直接传导至利润。供应链的稳定性受自然灾害、供应商经营状况影响。合作伙伴结构传统,缺乏与科技公司、研发机构的合作以推动创新。
|
||||
4. **评分:6分**。合作网络支撑现有业务,但议价能力弱且缺乏战略型伙伴。
|
||||
|
||||
#### **9. 成本结构 (Cost Structure)**
|
||||
1. **核心内容分析:** 成本结构以**变动成本为主**,主要包括:**原材料成本**(水泥、砂石等,占比最高)、**能源成本**(电力、燃料)、**运输成本**(车辆油耗、维护、司机薪酬)、**人力成本**(生产、物流、管理人员)。固定成本包括**设备折旧、厂房租金/摊销、管理费用**。
|
||||
2. **优势/特点:** 成本结构清晰,与产量高度相关。规模效应在一定区域内能摊薄固定成本。
|
||||
3. **潜在风险或关注点:** **利润受原材料市场价格波动影响极大**,成本转嫁能力有限。人力成本和能源成本呈上升趋势。重资产模式导致较高的固定成本负担,在需求下滑时盈利能力会急剧恶化。
|
||||
4. **评分:5分**。成本刚性大,对上游价格敏感,利润空间易受挤压。
|
||||
|
||||
---
|
||||
|
||||
### **总体评估与建议**
|
||||
|
||||
- **总体评分:62/100分**
|
||||
- 该企业是一家典型的区域性、重资产的建筑材料制造商。其在本地市场拥有稳定的客户基础和运营能力,商业模式清晰且经过验证。然而,其价值主张同质化、收入来源单一、成本结构脆弱且高度依赖周期性行业,这些因素严重制约了其成长潜力和抗风险能力。它更像一个“经济状况晴雨表”下的稳健运营者,而非具有高增长潜力的创新者。
|
||||
|
||||
- **合作建议:**
|
||||
- **作为潜在客户(采购方):可以推荐合作,但需谨慎评估。** 如果您的业务位于其辐射区域(山口县及周边),且需要稳定、可靠的混凝土供应,该公司是一个合格的本地供应商选择。合作前应重点考察其产品质量历史记录、准时交付能力、价格竞争力以及财务健康状况。
|
||||
- **作为战略投资者或收购方:不优先推荐。** 除非您看好日本特定区域的中长期基建市场,并希望通过整合来提升区域市场占有率或优化供应链,否则该类型企业增长天花板明显,资本回报率可能不高,且面临行业周期风险。
|
||||
- **作为技术或创新合作伙伴:不推荐。** 该公司业务模式传统,对新技术、新材料的应用动力和场景可能有限。
|
||||
|
||||
- **关键关注要点:**
|
||||
1. **行业周期与区域需求:** 必须深入研究日本及山口县未来5-10年的建筑业、公共工程投资规划,判断其市场容量是增长、停滞还是萎缩。
|
||||
2. **成本控制与议价能力:** 需分析其应对水泥等原材料价格上涨的具体策略(如长期采购合同、成本加成定价模式),评估其毛利率的稳定性。
|
||||
3. **财务健康状况:** 获取其财务报表,重点关注现金流、资产负债率(杠杆水平)、以及应付账款/应收账款周期,评估其在行业下行期的生存能力。
|
||||
4. **管理层战略与创新意识:** 了解管理层是否有向环保建材(如低碳混凝土)、高附加值制品、或周边服务(如混凝土回收、技术咨询)拓展的计划,以判断企业能否突破现有模式。
|
||||
5. **竞争格局:** 分析其在岩国市及山口县的市场份额,以及面对全国性巨头(如太平洋水泥旗下企业)竞争时的核心防御能力是什么。
|
||||
@@ -0,0 +1,103 @@
|
||||
好的,作为一名专业的商业分析顾问,我将基于您提供的“中野開発株式会社”信息,运用商业画布模型进行深度分析,并评估其潜在商业价值。
|
||||
|
||||
**免责声明:** 本分析报告完全基于公开的有限信息进行模型推演和行业常识判断,旨在提供框架性分析思路,不构成任何投资或合作决策的直接依据。实际评估需基于详尽的财务数据、市场调研及实地考察。
|
||||
|
||||
---
|
||||
|
||||
### **中野開発株式会社商业画布分析与价值评估报告**
|
||||
|
||||
#### **1. 客户细分 (Customer Segments)**
|
||||
1. **核心内容分析:** 该企业属于典型的B2B模式。其客户主要来自公共部门和私营企业两大板块。
|
||||
* **公共部门客户:** 地方政府(岡山市及周边)、国土交通省下属机构、其他公共事业体。他们是土木工程、国土建设项目的发包方。
|
||||
* **私营企业客户:** 大型建筑承包商、房地产开发公司、产生产业废弃物的制造型企业(如工厂)、需要进行土地改良的业主方。
|
||||
2. **企业的优势/特点:** 客户群体明确且刚需性强。公共项目通常具有预算稳定、规模较大的特点;私营企业客户则对处理效率、合规性和成本敏感。企业深耕岡山地区超过50年,可能积累了深厚的本地政企关系网络。
|
||||
3. **潜在风险或关注点:** 客户集中度风险。如果过度依赖少数几个大型公共项目或承包商,其收入波动性会增大。同时,公共项目投标竞争激烈,利润率可能被挤压。
|
||||
4. **评分:** 7/10。客户基础清晰且需求稳定,但存在集中度和竞争压力风险。
|
||||
|
||||
#### **2. 价值主张 (Value Propositions)**
|
||||
1. **核心内容分析:** 企业提供“一站式循环建设解决方案”。核心价值主张是 **“变废为宝,赋能建设”** 。具体体现在:
|
||||
* **合规与环保价值:** 为客户(特别是产废企业)提供合法、可靠的产业废弃物处理与运输服务,解决其最大的合规性痛点。
|
||||
* **资源与成本价值:** 将废弃物(建设发生土、废石料)再生为可用的“改良土”、“再生砕石/砂”,为客户提供低于天然材料的成本选择,并解决建筑渣土处置难题。
|
||||
* **综合服务价值:** 结合土木工程施工能力,提供从拆除、废料清运、再生到土方工程的全链条服务,提升客户项目效率。
|
||||
2. **企业的优势/特点:** 价值主张紧扣日本社会“循环经济”和“碳中和”的国家战略,具有政策顺应性。将传统成本中心(废弃物处理)转化为潜在利润中心(再生材料销售),商业模式有创新性。
|
||||
3. **潜在风险或关注点:** 再生材料的市场接受度和技术标准可能构成壁垒。客户可能对再生材料的性能、长期稳定性存疑。价值实现高度依赖于再生技术的先进性和稳定性。
|
||||
4. **评分:** 8/10。价值主张清晰,符合长期趋势,且具有差异化潜力。
|
||||
|
||||
#### **3. 渠道通路 (Channels)**
|
||||
1. **核心内容分析:** 渠道相对传统,以直接销售和关系营销为主。
|
||||
* **公共项目渠道:** 通过参与政府公开招标获取项目。
|
||||
* **私营项目渠道:** 依靠销售团队直接对接建筑公司、开发商;通过行业口碑、协会活动、现有客户推荐获取业务。
|
||||
* **品牌与信息渠道:** 公司官网(信息展示)、行业名录、可能参与地方产业展会。
|
||||
2. **企业的优势/特点:** 在本地市场,长期的经营历史和成功案例本身就是最强的渠道信用。直接销售模式有利于维护客户关系、深入理解需求。
|
||||
3. **潜在风险或关注点:** 渠道拓展可能受限。过于依赖传统关系和本地市场,在向新区域扩张时,渠道建设成本高、周期长。线上营销和数字化渠道利用可能不足。
|
||||
4. **评分:** 6/10。渠道与业务模式匹配,但略显传统,增长潜力可能受限。
|
||||
|
||||
#### **4. 客户关系 (Customer Relationships)**
|
||||
1. **核心内容分析:** 以 **“长期项目合作与战略伙伴关系”** 为主。土木工程和废弃物处理合同通常是项目制,但一个项目的良好合作会带来后续项目和推荐。对于产废企业,目标是签订长期的处理/运输协议。
|
||||
2. **企业的优势/特点:** 行业属性决定了客户关系重在可靠、信任与合规。50年的历史是建立信任的基石。提供综合解决方案有助于增强客户粘性,从交易关系升级为伙伴关系。
|
||||
3. **潜在风险或关注点:** 维系关系成本较高,严重依赖关键客户经理和公司高层的人脉。如果服务出现重大失误(如环保事故),将对客户关系造成毁灭性打击。
|
||||
4. **评分:** 7/10。客户关系模式符合行业特性,历史积淀是优势,但对人力和口碑依赖过重。
|
||||
|
||||
#### **5. 收入来源 (Revenue Streams)**
|
||||
1. **核心内容分析:** 收入来源多元化,但均围绕核心业务展开:
|
||||
* **工程服务收入:** 土木构造物施工的项目合同款。
|
||||
* **处理服务收入:** 产业废弃物处理、运输的服务费。
|
||||
* **再生材料销售收入:** 改良土、再生碎石/砂的销售。
|
||||
* **可能的其他收入:** 设备租赁、技术咨询等。
|
||||
2. **企业的优势/特点:** 收入流之间具有协同效应。工程施工产生废料,废料处理再生后可作为材料用于新工程或销售,形成了内部小循环,提升了整体利润空间。
|
||||
3. **潜在风险或关注点:** 各项收入受宏观经济和建筑行业周期影响巨大。再生材料销售的定价权和利润率受原材料(废弃物)获取成本及天然材料市场价格双重影响。
|
||||
4. **评分:** 8/10。收入结构合理,具有协同性和抗风险潜力,但整体仍属周期性行业。
|
||||
|
||||
#### **6. 核心资源 (Key Resources)**
|
||||
1. **核心内容分析:**
|
||||
* **物理资源:** 土地(处理厂、堆放场)、再生处理设备、施工机械、运输车队。
|
||||
* **无形资产:** 超过50年的品牌声誉、各类**处理许可资质**(这是最关键资源)、再生技术诀窍、本地政企关系网络。
|
||||
* **人力资源:** 经验丰富的施工团队、持有专业执照的操作与管理人员。
|
||||
2. **企业的优势/特点:** **政府颁发的废弃物处理/运输许可证是极高的准入壁垒**,是公司的“护城河”。长期运营积累的场地和工艺知识也是重要资产。
|
||||
3. **潜在风险或关注点:** 核心资源重资产化,设备维护和更新成本高。对关键技术人员和资质维护的依赖性强。品牌影响力可能具有地域局限性。
|
||||
4. **评分:** 8/10。资质壁垒高,资源组合支撑其商业模式,但资产较重。
|
||||
|
||||
#### **7. 关键业务 (Key Activities)**
|
||||
1. **核心内容分析:**
|
||||
* **生产活动:** 废弃物接收、分拣、破碎、筛选、再生加工;改良土/再生骨料的生产。
|
||||
* **项目运营活动:** 土木工程项目的施工管理、现场作业。
|
||||
* **物流活动:** 废弃物的收集与运输网络管理。
|
||||
* **合规与研发活动:** 确保所有操作符合环保法规;可能进行的再生工艺改进。
|
||||
2. **企业的优势/特点:** 业务活动形成了从“输入”(废弃物)到“输出”(工程+材料)的完整闭环运营能力,内部协同效率是核心竞争力之一。
|
||||
3. **潜在风险或关注点:** 运营复杂性高,涉及环保、安全、施工等多重监管,管理难度大。任何环节的失误都可能导致项目延误、成本超支或法律风险。
|
||||
4. **评分:** 7/10。关键业务链条完整,但运营复杂性和合规风险突出。
|
||||
|
||||
#### **8. 重要合作 (Key Partnerships)**
|
||||
1. **核心内容分析:**
|
||||
* **供应商:** 重型设备供应商、车辆供应商、燃油供应商、零部件服务商。
|
||||
* **合作伙伴:** 其他专业分包商(如特殊结构施工)、实验室(用于材料检测认证)、行业协会、地方环保监管机构。
|
||||
* **客户即伙伴:** 长期提供废弃物的制造企业、重复发包的建筑总包商。
|
||||
2. **企业的优势/特点:** 在本地经营多年,供应链和合作伙伴网络应较为稳定。与产废企业建立的长期供应关系确保了再生业务的“原料”稳定。
|
||||
3. **潜在风险或关注点:** 供应链集中度风险,如依赖单一设备品牌。环保政策的变动直接来自监管机构合作伙伴,政策风险不可控。原材料(废弃物)的供应量和成分可能波动。
|
||||
4. **评分:** 7/10。合作关系网络是运营基础,但外部依赖性强。
|
||||
|
||||
#### **9. 成本结构 (Cost Structure)**
|
||||
1. **核心内容分析:** 属于**重资产、高固定成本**结构。
|
||||
* **固定成本:** 设备折旧、厂房/场地租金或摊销、核心人员工资、资质维护费、保险。
|
||||
* **可变成本:** 项目人工费、燃油费、能源消耗(处理过程)、运输费、原材料(外购辅料)、分包费用。
|
||||
2. **企业的优势/特点:** 一旦固定成本被覆盖,新增项目的边际利润会较高。再生业务能将部分可变成本(废弃物处理)转化为收入,优化成本结构。
|
||||
3. **潜在风险或关注点:** 高经营杠杆意味着在业务量下滑时,亏损压力巨大。燃油和能源价格波动对成本影响显著。环保合规成本逐年上升是长期趋势。
|
||||
4. **评分:** 6/10。成本结构是行业固有特点,企业通过业务协同有所优化,但整体财务弹性较低。
|
||||
|
||||
---
|
||||
|
||||
### **总体评估与建议**
|
||||
|
||||
* **总体评分:72/100**
|
||||
* **评分解读:** 该企业是一家根基扎实、商业模式清晰且符合可持续发展趋势的地方性龙头企业。其核心优势在于高准入壁垒(资质)、超过50年的本地信誉、以及“工程-处理-再生”的闭环协同模式。这使其在区域内具备较强的竞争力和客户粘性。扣分项主要在于其业务的强周期性、重资产模式带来的财务风险、地域局限性以及行业固有的运营与合规风险。
|
||||
|
||||
* **合作建议:**
|
||||
* **作为供应商/服务商:推荐合作。** 特别是在岡山及周边地区有土木工程或废弃物处理需求时,该公司是可靠的选择。其一站式服务和再生理念能带来综合价值。
|
||||
* **作为投资或深度战略合作对象:谨慎推荐,需进一步尽调。** 其商业模式有价值,但必须深入考察其**财务健康状况**(负债率、现金流)、**具体的技术工艺水平**、**市场份额的稳定性**以及**管理层应对行业下行周期的能力**。员工规模为0的信息存疑,需核实是否为数据缺失或公司已转为轻资产运营/外包模式。
|
||||
|
||||
* **关键关注要点:**
|
||||
1. **财务稳健性:** 必须获取其近年财务报表,重点关注经营性现金流、资产负债率(判断重资产负担)以及在行业低谷期的盈利情况。
|
||||
2. **资质与合规记录:** 核实其持有的所有处理、运输许可证的有效期及范围;调查是否有环保违规、安全事故或法律诉讼的历史。
|
||||
3. **市场与客户集中度:** 分析其前五大客户贡献的收入占比,评估对公共项目或单一大型客户的依赖程度。
|
||||
4. **技术工艺与产能:** 了解其再生材料的产品标准、认证情况、市场接受度及实际产能利用率。这是其价值主张能否兑现的关键。
|
||||
5. **战略与传承:** 作为一家1970年成立的企业,需了解其下一代管理层接班计划以及未来业务扩张(地域或产业链延伸)的战略方向。
|
||||
@@ -0,0 +1,199 @@
|
||||
好的,作为您的商业分析顾问,我将基于您提供的“Tsujii English School”信息,运用商业画布模型进行深度分析,并评估其潜在商业价值。
|
||||
|
||||
---
|
||||
|
||||
# **Tsujii English School 商业画布分析与价值评估报告**
|
||||
|
||||
## **1. 客户细分 (Customer Segments)**
|
||||
|
||||
1. **核心内容分析**:该企业采用**多元化客户细分策略**,同时服务B2C和B2B市场。
|
||||
* **B2C个人客户**:
|
||||
* **学生群体**:涵盖从入门到中级的普通英语学习者、备考TOEIC/TOEFL的学生。
|
||||
* **职场人士**:需要提升商务英语能力的在职人员。
|
||||
* **儿童**:儿童英语学习者。
|
||||
* **多语种学习者**:对中文、韩语、西班牙语等小语种有兴趣的本地居民。
|
||||
* **外国人**:在日外国人(日语课程学习者)。
|
||||
* **B2B企业客户**:
|
||||
* 需要“讲师派遣”和“研修委托”服务的本地企业或机构。
|
||||
|
||||
2. **优势/特点**:
|
||||
* **覆盖全面**:从年龄(儿童到成人)、目的(兴趣、考试、商务)、语种(英语+多语种)等多个维度覆盖了广泛的语言学习需求,降低了单一市场波动的风险。
|
||||
* **地理位置精准**:位于土浦站东口步行3分钟,精准定位本地社区居民及通勤者,获客便利。
|
||||
|
||||
3. **潜在风险或关注点**:
|
||||
* **资源分散**:过宽的客户群可能导致营销资源、师资力量分散,难以在任一细分领域建立绝对优势或品牌深度。
|
||||
* **竞争激烈**:个人语言培训市场(尤其英语)竞争激烈,面临来自全国性连锁机构(如AEON, GABA)和在线平台(如DMM英会话)的双重挤压。
|
||||
|
||||
4. **评分**:**7/10**。策略清晰,覆盖广泛,但需警惕“大而全”带来的聚焦不足问题。
|
||||
|
||||
## **2. 价值主张 (Value Propositions)**
|
||||
|
||||
1. **核心内容分析**:提供 **“一站式、便捷、本地化”的语言学习解决方案**。
|
||||
* **一站式学习**:在一个地点满足从英语到多种小语种、从个人学习到企业培训的几乎所有语言相关需求。
|
||||
* **便捷性与可达性**:“车站步行3分钟”和“停车场完备”解决了通勤者和驾车者的核心痛点,提供了线下学习最重要的便利性。
|
||||
* **本地化与个性化服务**:作为本地语校,可能比大型连锁机构提供更灵活、个性化的课程安排和更亲切的师生关系。
|
||||
* **实用技能导向**:课程设置(商务英语、TOEIC等)紧密对接就业、升学和职场晋升等实用目标。
|
||||
|
||||
2. **优势/特点**:
|
||||
* **综合便利性**是最大卖点,将地理位置优势与产品多样性结合,形成了独特的本地竞争力。
|
||||
* **B2B服务**(讲师派遣)是其区别于纯个人培训机构的差异化价值点。
|
||||
|
||||
3. **潜在风险或关注点**:
|
||||
* 价值主张在“教学质量”和“学习效果”上表述模糊,缺乏量化或独特的教学法支撑,在激烈竞争中可能不够突出。
|
||||
* “多语种”可能仅是课程列表,若师资不强,则价值主张空心化。
|
||||
|
||||
4. **评分**:**6.5/10**。便利性突出,但核心教学价值主张不够锐利和独特。
|
||||
|
||||
## **3. 渠道通路 (Channels)**
|
||||
|
||||
1. **核心内容分析**:渠道相对传统,以**线下自然流量和口碑**为主。
|
||||
* **线下实体渠道**:学校本身是最主要的展示和销售渠道。优越的地理位置带来稳定的自然到访客流。
|
||||
* **数字渠道**:拥有官方网站 (`www.tiacom.com`),但域名与企业名不符,可能影响品牌专业度和线上引流效果。
|
||||
* **口碑与推荐**:作为社区型机构,老学员推荐和本地口碑至关重要。
|
||||
* **B2B直销**:可能通过销售团队或负责人直接对接本地企业。
|
||||
|
||||
2. **优势/特点**:
|
||||
* **黄金地段**带来的自然流量是低成本且高效的获客渠道,是其生存的基石。
|
||||
* 线下渠道与“便捷性”价值主张高度契合。
|
||||
|
||||
3. **潜在风险或关注点**:
|
||||
* **线上渠道薄弱**:官网域名可疑,且未提及任何社交媒体(如Instagram, LINE官方账号)或本地生活平台(如Hot Pepper)运营,在数字化营销时代存在明显短板。
|
||||
* **渠道单一**:过度依赖线下自然流量,抗风险能力(如疫情等)较弱,且难以触达更广范围的潜在客户。
|
||||
|
||||
4. **评分**:**5/10**。线下渠道优势明显,但线上渠道存在严重缺陷,渠道结构不平衡。
|
||||
|
||||
## **4. 客户关系 (Customer Relationships)**
|
||||
|
||||
1. **核心内容分析**:推测以**长期服务关系**为主,辅以交易型关系。
|
||||
* **长期课程关系**:语言学习具有持续性,通过定期上课、进度跟踪与教师互动,建立长期的师生/客户关系。
|
||||
* **社区化关系**:作为本地学校,可能通过活动、文化交流等方式营造社区归属感,增强粘性。
|
||||
* **B2B项目制关系**:与企业客户可能是基于项目的长期合作或定期研修委托。
|
||||
|
||||
2. **优势/特点**:
|
||||
* 线下面对面教学天然有利于建立深厚、个性化的客户关系,客户忠诚度和生命周期价值(LTV)可能较高。
|
||||
* 本地化属性便于组织线下活动,强化关系。
|
||||
|
||||
3. **潜在风险或关注点**:
|
||||
* 未提及系统化的客户关系管理(CRM)体系,如学员学习进度数字化管理、定期学习反馈、续费提醒等,关系维护可能依赖教师个人,不够标准化和可持续。
|
||||
* 缺乏激活沉睡客户或促进转介绍的机制设计。
|
||||
|
||||
4. **评分**:**6/10**。关系模式健康,但缺乏系统化、主动化的管理手段。
|
||||
|
||||
## **5. 收入来源 (Revenue Streams)**
|
||||
|
||||
1. **核心内容分析**:收入来源**多元化**,但主要依赖课程学费。
|
||||
* **课程学费**:各类语言课程(英/日/多语种)按课程包或课时收费,是核心收入。
|
||||
* **考试辅导费**:TOEIC/TOEFL等专项备考课程费用。
|
||||
* **B2B服务费**:“讲师派遣”和“研修委托”产生的项目制或合同制收入。
|
||||
* **其他可能收入**:教材销售、留学中介佣金等。
|
||||
|
||||
2. **优势/特点**:
|
||||
* **收入结构稳健**:B2C学费提供稳定现金流,B2B项目贡献大额收入和利润,两者结合能平滑季节性波动。
|
||||
* **客户生命周期价值高**:一个学员可能从儿童英语学到成人商务英语,或学习多门语言,产生持续收入。
|
||||
|
||||
3. **潜在风险或关注点**:
|
||||
* B2C收入受招生情况影响大,存在空置率风险。
|
||||
* B2B收入受本地经济和企业培训预算影响,波动性可能较大。
|
||||
* 未提及任何订阅制、会员制等更稳定的收入模式。
|
||||
|
||||
4. **评分**:**7/10**。结构合理,抗风险能力较强,但模式传统,创新不足。
|
||||
|
||||
## **6. 核心资源 (Key Resources)**
|
||||
|
||||
1. **核心内容分析**:核心资源是**实体资产、人力资源和本地品牌**。
|
||||
* **实体资产**:位于车站附近的校舍及停车场,是业务开展的物理基础和最重资产。
|
||||
* **人力资源**:多语种教师团队(尤其是能教商务英语和小语种的教师)和可能存在的B2B销售/课程顾问。
|
||||
* **本地品牌与口碑**:在土浦市及周边积累的声誉和客户信任。
|
||||
* **客户资源**:积累的学员和企业客户名单。
|
||||
|
||||
2. **优势/特点**:
|
||||
* 地理位置是不可复制或极难替代的核心资源。
|
||||
* 稳定的师资队伍是服务质量的根本。
|
||||
|
||||
3. **潜在风险或关注点**:
|
||||
* **师资依赖风险**:优秀教师流失会直接影响教学质量和口碑,尤其在小语种领域。
|
||||
* **资产沉重**:固定成本高(租金、维护),灵活性差。
|
||||
* **品牌地域局限**:品牌影响力基本局限于本地,扩张困难。
|
||||
|
||||
4. **评分**:**6/10**。资源扎实但偏重,且存在关键人员依赖风险。
|
||||
|
||||
## **7. 关键业务 (Key Activities)**
|
||||
|
||||
1. **核心内容分析**:关键业务围绕**教学交付和客户运营**展开。
|
||||
* **课程设计与教学交付**:开发并执行各类语言课程,保证教学质量。
|
||||
* **师资招募与培训**:持续寻找、培训并留住合格教师。
|
||||
* **招生与市场推广**:吸引新学员和企业客户。
|
||||
* **客户服务与关系维护**:处理咨询、安排课程、维护学员与企业关系。
|
||||
* **B2B项目洽谈与执行**:针对企业客户的需求定制方案并交付。
|
||||
|
||||
2. **优势/特点**:
|
||||
* 业务链条完整,从营销到交付形成闭环。
|
||||
* B2B与B2C业务协同,可共享师资和管理资源。
|
||||
|
||||
3. **潜在风险或关注点**:
|
||||
* 所有业务均重度依赖人工,标准化和规模化难度大,效率天花板明显。
|
||||
* 未提及利用技术提升教学体验或运营效率的活动(如在线练习平台、管理系统)。
|
||||
|
||||
4. **评分**:**6/10**。业务模式成熟,但缺乏效率提升和创新性的关键活动。
|
||||
|
||||
## **8. 重要合作 (Key Partnerships)**
|
||||
|
||||
1. **核心内容分析**:合作伙伴信息不明,但可推断存在以下几类:
|
||||
* **师资供应方**:与自由职业教师、大学或专业机构合作,确保师资来源。
|
||||
* **B2B客户**:本地企业是重要的合作伙伴兼客户。
|
||||
* **留学中介机构**:如果提供留学服务,需与海外院校或中介合作。
|
||||
* **教材供应商**:与出版社的合作。
|
||||
|
||||
2. **优势/特点**:
|
||||
* 与本地企业的合作关系是其B2B业务的基石,具有排他性和深度。
|
||||
* 灵活的教师合作模式有助于控制固定人力成本。
|
||||
|
||||
3. **潜在风险或关注点**:
|
||||
* **供应链(师资)脆弱**:合作伙伴(教师)多为松散合作,忠诚度低,管理难度大,存在服务质量不一致和突然流失的风险。
|
||||
* **合作伙伴单一**:未提及与在线平台、技术公司或更广泛的教育机构的合作,模式封闭。
|
||||
|
||||
4. **评分**:**5/10**。合作网络存在且必要,但结构松散,稳定性不足,缺乏战略级伙伴。
|
||||
|
||||
## **9. 成本结构 (Cost Structure)**
|
||||
|
||||
1. **核心内容分析**:成本结构以**高固定成本**为特征。
|
||||
* **固定成本**:
|
||||
* **场地成本**:租金/房贷、物业费、停车场维护费。
|
||||
* **人力固定部分**:核心管理团队、全职教师的工资社保。
|
||||
* **设施折旧与维护**:教学设备、装修摊销。
|
||||
* **变动成本**:
|
||||
* **教师课时费**:兼职或按课时结算的教师薪酬。
|
||||
* **营销费用**:广告、宣传材料。
|
||||
* **教材与耗材**:采购成本。
|
||||
* **公用事业费**:水电网络。
|
||||
|
||||
2. **优势/特点**:
|
||||
* 成本结构清晰,易于预测和管理。
|
||||
* 通过使用兼职教师,可以将部分人力成本转化为变动成本,控制风险。
|
||||
|
||||
3. **潜在风险或关注点**:
|
||||
* **经营杠杆高**:高额固定成本意味着需要维持较高的招生率(座位利用率)才能实现盈亏平衡,抗萧条能力弱。
|
||||
* **规模不经济**:扩张需要新增场地和固定投入,边际成本下降不明显。
|
||||
|
||||
4. **评分**:**5/10**。结构典型,但沉重,在生源波动时压力巨大。
|
||||
|
||||
---
|
||||
|
||||
## **总体评估与建议**
|
||||
|
||||
### **总体评分:60/100分**
|
||||
|
||||
**评价**:Tsujii English School 是一家典型的、运营成熟的本地化线下语言培训机构。其核心优势在于**卓越的地理位置**和**全面的一站式课程服务**,这使其在土浦本地市场建立了稳固的立足点,并形成了B2C与B2B结合的稳健收入模式。然而,其商业模式**传统、沉重且数字化程度低**,在面对在线教育竞争和潜在市场波动时,表现出明显的脆弱性。它是一家“好生意”,但并非具有高增长潜力的“现代企业”。
|
||||
|
||||
### **合作建议:谨慎推荐,需附加条件**
|
||||
|
||||
* **作为供应商/服务接收方(如寻求语言培训)**:**推荐**。对于土浦本地及周边的个人或企业,它是一个可靠、便捷的选择,尤其看重线下互动和地理位置时。
|
||||
* **作为战略投资或深度业务合作伙伴**:**不推荐**。其商业模式天花板明显,增长潜力有限,且数字化转型难度大,投资回报率可能不高。除非合作方案能直接解决其线上化、效率提升或地域扩张的核心瓶颈。
|
||||
|
||||
### **关键关注要点**
|
||||
|
||||
1. **数字化能力缺失**:官网问题、线上营销与客户关系管理的空白是其最大短板。在疫情后时代,缺乏线上线下融合(OMO)能力是致命伤。
|
||||
2. **商业模式沉重,灵活性差**:高固定成本结构和对实体位置的依赖,使其利润容易被租金和空置率侵蚀,难以快速适应市场变化。
|
||||
3. **师资管理与教学质量标准化**:作为服务核心,师资的稳定性和教学质量的一致性缺乏体系保障,是长期发展的隐患。
|
||||
4. **品牌与业务地域局限**:所有优势都局限于土浦本地,缺乏可复制性和规模效应,企业价值增长空间受限。
|
||||
5. **价值主张不够尖锐**:在“教学质量”和“学习效果”上缺乏独特、可感知的差异化标签,在竞争中易陷入价格战或沦为“地理便利”的普通选择。
|
||||
@@ -0,0 +1,84 @@
|
||||
好的,作为一名专业的商业分析顾问,我将基于您提供的“Ht Active Software”企业信息,运用商业画布模型进行深度分析。需要特别指出的是,您提供的“企业描述”与公司名称、官网显示的行业(计算机软件)存在严重不一致,描述内容指向茶叶电商。因此,本次分析将主要基于公司名称、行业、官网等可验证信息进行推断性分析,并对信息矛盾处提出警示。
|
||||
|
||||
---
|
||||
|
||||
### **企业商业画布分析报告:Ht Active Software**
|
||||
|
||||
**报告摘要:** Ht Active Software 是一家位于越南岘港的软件公司,成立于2016年,处于成长期。其核心业务推测为软件定制开发、技术解决方案或IT外包服务。分析显示,该公司具备区域成本优势和一定的技术交付能力,但面临激烈的市场竞争和客户集中风险。其商业价值中等,适合对成本敏感且需求明确的中小企业客户。
|
||||
|
||||
**重要声明:** 分析基于有限公开信息及行业惯例进行合理推断。“企业描述”部分关于茶叶的内容与核心信息严重不符,可能为信息录入错误,**本报告将忽略该部分内容**,并强烈建议在决策前通过其官网(htactive.com)及直接沟通核实其真实业务。
|
||||
|
||||
#### **1. 客户细分 (Customer Segments)**
|
||||
1. **核心内容分析:** 作为一家软件公司,其客户可能分为两类:**B2B(企业客户)** 和 **B2C(通过为客户开发面向消费者的产品间接服务)**。主要客户群体可能包括:需要定制化软件开发的中小企业、寻求数字化转型的传统企业、初创公司(MVP产品开发)、以及可能需要IT外包或团队扩充的其他科技公司。
|
||||
2. **优势/特点:** 位于越南岘港,该市是越南重要的IT外包和创业中心,享有“越南硅谷”美誉。这使其能有效触达本地及亚太地区对**性价比敏感**的B2B客户。员工规模(51-200人)表明其具备承接中型项目的能力。
|
||||
3. **潜在风险或关注点:** 客户群体可能较为分散,缺乏明确的行业聚焦,导致品牌认知度不高。如果过度依赖少数大客户,会带来收入不稳定风险。与全球及印度的外包公司竞争激烈。
|
||||
4. **评分:6分** - 市场定位清晰(亚太区B2B软件服务),但细分市场的竞争壁垒和客户忠诚度可能不高。
|
||||
|
||||
#### **2. 价值主张 (Value Propositions)**
|
||||
1. **核心内容分析:** 核心价值主张很可能围绕 **“高性价比的定制化软件开发与技术服务”**。具体可能包括:相比欧美公司更低的开发成本、相比本地小团队更规范的项目管理和交付质量、敏捷的开发流程、以及针对特定技术栈(需核实,如Web、移动端、云服务等)的专业能力。
|
||||
2. **优势/特点:** **成本优势**是核心。越南工程师薪资水平具有国际竞争力。成立时间(2016年)表明其已积累一定的项目经验和公司运营流程。岘港的IT生态圈有助于其获取技术和人才。
|
||||
3. **潜在风险或关注点:** “性价比”价值主张易被复制,难以形成长期护城河。技术迭代快,需要持续投入学习。沟通和文化差异可能影响与远端客户(如欧美)的项目效率和满意度。
|
||||
4. **评分:6.5分** - 价值主张明确且具有市场吸引力,但差异化程度不足,易陷入价格竞争。
|
||||
|
||||
#### **3. 渠道通路 (Channels)**
|
||||
1. **核心内容分析:** 渠道可能包括:**直接销售**(官网询盘、销售团队拓展)、**线上营销**(LinkedIn、技术社区、SEO/SEM推广其官网htactive.com)、**口碑与转介绍**(通过成功项目案例)、**合作伙伴推荐**(与咨询公司、设计公司等合作)、以及可能参与的**行业展会或创业活动**。
|
||||
2. **优势/特点:** 官网作为核心数字门户存在。地处IT社区,便于通过线下网络和本地活动获取客户。对于东南亚市场,地缘和文化接近性是其天然渠道优势。
|
||||
3. **潜在风险或关注点:** 线上渠道(如官网)的营销专业度和内容吸引力直接影响获客。若过度依赖老板或少数销售的个人关系,渠道可持续性弱。在国际市场缺乏品牌知名度,开拓远程客户渠道成本较高。
|
||||
4. **评分:6分** - 渠道基础存在,但缺乏显性的、高效的规模化获客渠道策略。
|
||||
|
||||
#### **4. 客户关系 (Customer Relationships)**
|
||||
1. **核心内容分析:** 关系模式应是 **“基于项目的长期服务关系”**。可能包括:项目前期的咨询与需求分析、项目中的敏捷沟通与协作(使用Jira, Slack等)、项目交付后的维护与技术支持、以及争取客户的后续增购或新项目。
|
||||
2. **优势/特点:** 中型团队规模允许其组建专属项目团队,提供相对个性化的服务。以成功交付为目标,关系通常较为紧密。长期服务合同(如年度维护、持续迭代)是维系关系的关键。
|
||||
3. **潜在风险或关注点:** 项目制业务模式导致客户关系存在周期性断裂风险。高度依赖项目经理和核心技术人员的人际关系,人员流失可能直接导致客户流失。在竞争下,客户价格敏感度高,关系粘性可能不足。
|
||||
4. **评分:6分** - 属于行业标准模式,但构建超越项目交易本身的战略合作伙伴关系能力未知。
|
||||
|
||||
#### **5. 收入来源 (Revenue Streams)**
|
||||
1. **核心内容分析:** 主要收入来源应为:**1) 项目制开发合同**:按固定价格或时间和材料(T&M)收费;**2) 人力外包(Staff Augmentation)**:按工程师人/月收费;**3) 软件维护与升级服务**:定期收取服务费;**4) 可能的SaaS产品订阅收入**(如果自有产品)。
|
||||
2. **优势/特点:** 收入模式清晰,现金流在项目周期内相对可预测。多种收费模式可以组合,满足不同客户需求。
|
||||
3. **潜在风险或关注点:** **项目制收入不稳定**,存在“有项目忙死,没项目饿死”的波动风险。利润率受人力成本上升和客户压价双重挤压。若缺乏复购或长期服务收入,企业价值天花板较低。
|
||||
4. **评分:6分** - 收入模式传统且健康,但增长潜力和利润率受限,对规模经济依赖度高。
|
||||
|
||||
#### **6. 核心资源 (Key Resources)**
|
||||
1. **核心内容分析:** 最重要的资源是 **“技术团队”**(软件工程师、项目经理、架构师)。其次是 **“知识产权”**(积累的代码库、开发框架、方法论)、**“客户案例与声誉”**、以及公司的**品牌与官网**。
|
||||
2. **优势/特点:** 51-200人的技术团队是其最核心的资产。越南拥有年轻且受过良好教育的工程师群体,人才供给有优势。8年的运营积累了宝贵的项目经验和内部知识体系。
|
||||
3. **潜在风险或关注点:** 人力资源是主要成本也是最大风险点(流失率、技能过时)。缺乏有影响力的专利或独家技术,资源可替代性强。品牌资产薄弱,在区域外知名度低。
|
||||
4. **评分:7分** - 核心资源(人才)明确,且位于有优势的区域,但需持续投入以保留和升级该资源。
|
||||
|
||||
#### **7. 关键业务 (Key Activities)**
|
||||
1. **核心内容分析:** 关键业务活动包括:**软件定制开发与交付**(需求、设计、编码、测试、部署)、**业务开发与销售**(寻找新客户、提案、谈判)、**人才招聘与团队建设**、以及**技术研究与学习**以保持竞争力。
|
||||
2. **优势/特点:** 专注于软件开发交付这一核心,业务流程可能已初步标准化。在特定技术栈或行业(需核实)可能形成了深度能力。
|
||||
3. **潜在风险或关注点:** 业务活动高度项目驱动,管理复杂度随项目增多而提升。销售和招聘是持续的压力来源。若不能将项目经验转化为可复用的产品或解决方案,业务效率难以大幅提升。
|
||||
4. **评分:6.5分** - 关键业务清晰,执行能力是成败关键,但模式本身创新性有限。
|
||||
|
||||
#### **8. 重要合作 (Key Partnerships)**
|
||||
1. **核心内容分析:** 合作伙伴可能包括:**技术合作伙伴**(云服务商如AWS/Azure、软件工具提供商)、**渠道与推荐合作伙伴**(设计公司、商业咨询公司)、**人才渠道伙伴**(本地大学、培训机构)、以及为特定项目结盟的**互补型技术公司**。
|
||||
2. **优势/特点:** 身处岘港IT生态圈,易于建立本地协作网络。与全球云厂商的合作能提升其技术交付的现代性和可信度。
|
||||
3. **潜在风险或关注点:** 合作关系可能较为松散,非排他性,带来的业务流量不稳定。供应链(主要指人才供应链)受本地市场波动影响。缺乏能带来战略性大客户的强力合作伙伴。
|
||||
4. **评分:5.5分** - 存在常规合作,但未体现出能显著增强其竞争优势的深度战略联盟。
|
||||
|
||||
#### **9. 成本结构 (Cost Structure)**
|
||||
1. **核心内容分析:** 成本结构以 **“人力成本为主的可变成本”** 为核心。具体包括:**员工薪酬与福利**(最大头)、**办公场地与设施租金**、**销售与市场费用**、**软件工具与云服务订阅费**、以及行政管理费用。
|
||||
2. **优势/特点:** 主要成本(人力)与核心收入(项目/人力外包)直接挂钩,模型简单清晰。越南的整体运营成本(薪资、租金)仍低于许多发达国家,这是其利润空间的基础。
|
||||
3. **潜在风险或关注点:** 对人力成本极度敏感。越南IT人才薪资上涨是长期趋势,将持续挤压利润率。项目空档期会产生人员闲置成本。销售和获客成本可能随着竞争加剧而上升。
|
||||
4. **评分:7分** - 成本结构与其商业模式高度匹配,且享有区域成本优势,但该优势正在随时间减弱。
|
||||
|
||||
---
|
||||
|
||||
### **总体评估与建议**
|
||||
|
||||
- **总体评分:62/100分**
|
||||
- 该企业是一家典型的、处于成长期的软件服务公司,商业模式清晰且健康,在区域市场具备一定的成本竞争力和交付能力。然而,其模式缺乏显著的差异化与护城河,增长受限于项目制天花板,在激烈的全球外包市场中面临持续压力。价值中等偏上,但非颠覆性或高增长型。
|
||||
|
||||
- **合作建议:**
|
||||
- **谨慎推荐,适用于特定场景的合作。** 如果您是以下类型的客户,可以考虑将其作为供应商之一进行接触:
|
||||
1. 预算有限、需要定制化软件开发的中小企业或初创公司。
|
||||
2. 项目需求明确、范围清晰,且您具备较强的项目管理或技术沟通能力。
|
||||
3. 业务重点在东南亚市场,需要本地化支持或理解。
|
||||
- **不推荐**将其用于对技术创新、品牌溢价、战略级合作有极高要求的项目。
|
||||
|
||||
- **关键关注要点:**
|
||||
1. **信息真实性核实:** **首要任务**是直接联系Ht Active Software,访问其官网,审查其真实的服务案例、技术栈和客户评价,彻底澄清“企业描述”中的矛盾信息。
|
||||
2. **交付能力与流程审计:** 深入评估其项目管理流程、质量保证体系、过往项目案例(特别是与您需求类似的)以及核心团队的稳定性。要求进行技术方案演示和团队面试。
|
||||
3. **成本与定价的可持续性:** 明确其报价模式,评估在越南人力成本上升背景下的长期合作价格稳定性。警惕过低报价可能对应的质量或隐性风险。
|
||||
4. **沟通与风险管控:** 建立清晰的沟通机制、里程碑验收标准和知识产权归属协议。考虑从小型试点项目开始合作,以实际检验其能力与文化适配度。
|
||||
5. **其自身战略方向:** 了解该公司未来是继续专注于人力外包和项目制,还是有向产品化、解决方案化或垂直行业深耕转型的计划,这会影响其长期服务您的能力和意愿。
|
||||
@@ -0,0 +1,85 @@
|
||||
好的,作为一名专业的商业分析顾问,我将基于您提供的“Ht Active Software”企业信息,运用商业画布模型进行深度分析与价值评估。
|
||||
|
||||
**重要说明:** 您提供的“企业描述”部分内容(关于大红袍岩茶)与公司名称、官网、行业(计算机软件)存在严重矛盾。这可能是信息录入错误。为完成本次分析,我将以 **“Ht Active Software”是一家位于越南岘港的计算机软件公司** 为前提进行合理推断。若“大红袍茶叶”是其真实业务,则本报告的分析基础将完全失效,需要重新评估。
|
||||
|
||||
---
|
||||
|
||||
### **企业商业画布分析报告:Ht Active Software**
|
||||
|
||||
#### **1. 客户细分 (Customer Segments)**
|
||||
1. **核心内容分析:** 作为一家软件公司,其客户可能分为B2B和B2C两类。B2B客户可能包括需要定制软件开发、技术外包、IT解决方案的中小企业或初创公司。B2C客户则可能是其自主开发并运营的软件产品(如SaaS工具、移动应用)的终端用户。
|
||||
2. **优势/特点:** 位于越南岘港,该地是东南亚新兴的IT外包和创业中心,拥有成本优势和人才池。公司规模(51-200人)表明其具备承接中型项目的能力。可能专注于特定行业(如电商、金融科技、教育科技)或技术栈(如Web开发、移动端)。
|
||||
3. **潜在风险或关注点:** 客户群体可能较为分散,缺乏明确的行业聚焦,导致品牌认知度不高。如果以B2B外包为主,客户获取成本高,且项目制收入不稳定。B2C产品则面临激烈的市场竞争。
|
||||
4. **评分:** 6分。需要更明确的客户聚焦来提升价值。
|
||||
|
||||
#### **2. 价值主张 (Value Propositions)**
|
||||
1. **核心内容分析:** 价值主张可能围绕“高性价比的软件开发服务”、“敏捷的开发流程”、“特定技术领域的专业能力”或“解决某一特定业务痛点的软件产品”。
|
||||
2. **优势/特点:** 相比欧美或中国的一线开发团队,越南团队能提供有竞争力的价格和良好的英语沟通能力(推测)。敏捷开发模式能快速响应客户需求。
|
||||
3. **潜在风险或关注点:** 价值主张可能不够独特,易陷入“价格战”。如果缺乏核心技术壁垒或成功的标杆案例,难以吸引高端客户。
|
||||
4. **评分:** 6分。价值主张的清晰度和独特性是提升的关键。
|
||||
|
||||
#### **3. 渠道通路 (Channels)**
|
||||
1. **核心内容分析:** 渠道可能包括:官网(htactive.com)作为主要展示和获客窗口;LinkedIn等专业社交平台;通过本地或国际的合作伙伴推荐;参与行业展会或技术社区活动。
|
||||
2. **优势/特点:** 拥有独立官网,是建立专业形象的基础。利用数字渠道进行全球化营销成本较低。
|
||||
3. **潜在风险或关注点:** 渠道可能过于依赖线上被动获客,缺乏主动的销售网络或强大的渠道合作伙伴。官网内容需要专业、案例丰富才能有效转化。
|
||||
4. **评分:** 5分。渠道建设相对基础,需要体系化和主动拓展。
|
||||
|
||||
#### **4. 客户关系 (Customer Relationships)**
|
||||
1. **核心内容分析:** 对于B2B项目,关系模式可能是“长期项目合作”或“持续的技术支持与维护”。对于B2C产品,则通过用户社区、客服、定期更新来维护。
|
||||
2. **优势/特点:** 如果能建立以客户成功为导向的服务模式,有机会将项目客户转化为长期维护客户或复购客户,提升客户生命周期价值。
|
||||
3. **潜在风险或关注点:** 软件外包行业客户忠诚度较低,容易因价格或人员变动而流失。如果客户关系仅停留在项目交付层面,则价值有限。
|
||||
4. **评分:** 6分。建立深度、信任的客户关系是挑战也是机遇。
|
||||
|
||||
#### **5. 收入来源 (Revenue Streams)**
|
||||
1. **核心内容分析:** 主要收入可能来自:1)**项目制开发**:按人天或固定价格收费;2)**产品授权/销售**:一次性售卖软件许可;3)**订阅服务(SaaS)**:按月/年收取服务费;4)**维护与支持服务**:收取年费。
|
||||
2. **优势/特点:** 收入模式多样。如果能有SaaS产品,将带来可预测的经常性收入,估值更高。
|
||||
3. **潜在风险或关注点:** 若过度依赖项目制收入,则现金流波动大,增长受限于人力规模。产品化转型需要前期投入和市场营销能力。
|
||||
4. **评分:** 7分。收入结构的健康度取决于其产品化程度。
|
||||
|
||||
#### **6. 核心资源 (Key Resources)**
|
||||
1. **核心内容分析:** 最重要的资源是**技术团队**(开发人员、项目经理、设计师)。其次是**知识产权**(自有产品的代码、算法、品牌)和**客户案例/声誉**。
|
||||
2. **优势/特点:** 越南拥有年轻、成本效益高的IT人才。稳定的核心团队是关键资产。
|
||||
3. **潜在风险或关注点:** 人才竞争激烈,核心人员流失风险高。如果缺乏有竞争力的自有IP或品牌,企业价值将停留在“人力作坊”层面。
|
||||
4. **评分:** 7分。人才是基础,但需向“知识产权”和“品牌”升级。
|
||||
|
||||
#### **7. 关键业务 (Key Activities)**
|
||||
1. **核心内容分析:** 核心活动包括:**软件开发与测试**、**产品设计与规划**、**项目管理和客户沟通**、**技术研究与创新**。
|
||||
2. **优势/特点:** 软件开发执行是其日常主业,流程和经验在持续积累。
|
||||
3. **潜在风险或关注点:** 如果业务忙于交付客户项目,可能会挤压在自有产品研发、技术沉淀和市场营销上的投入,陷入“忙碌但增长缓慢”的循环。
|
||||
4. **评分:** 7分。执行能力是保障,但需平衡短期交付与长期投资。
|
||||
|
||||
#### **8. 重要合作 (Key Partnerships)**
|
||||
1. **核心内容分析:** 合作伙伴可能包括:**技术合作伙伴**(云服务商如AWS/Azure、软件工具提供商);**业务渠道伙伴**(本地或海外的咨询公司、数字营销机构);**人才渠道**(高校、培训机构)。
|
||||
2. **优势/特点:** 与全球云服务商的合作能提升技术交付能力。建立稳定的招聘渠道保障团队扩张。
|
||||
3. **潜在风险或关注点:** 合作伙伴网络可能不够强大或战略化,未能通过合作显著拓展市场或提升能力。
|
||||
4. **评分:** 6分。合作关系有待深化,以创造协同效应。
|
||||
|
||||
#### **9. 成本结构 (Cost Structure)**
|
||||
1. **核心内容分析:** 主要成本为**人力成本**(工资、福利),这是最大的变动成本。其次是**办公场地租金**、**云服务与软件工具费用**、**市场营销和销售费用**。
|
||||
2. **优势/特点:** 越南整体人力成本相对于产出具有竞争力,这是其商业模式的基础。成本结构相对清晰。
|
||||
3. **潜在风险或关注点:** 人力成本刚性上涨是长期趋势。如果项目管理不善,人力利用率低会导致利润率急剧下降。过度控制营销成本可能限制增长。
|
||||
4. **评分:** 7分。成本优势明显,但需精细化管理以保持利润率。
|
||||
|
||||
---
|
||||
|
||||
### **总体评估与建议**
|
||||
|
||||
* **总体评分:63/100分**
|
||||
* **解读**:Ht Active Software 是一家典型的、处于成长期的软件服务公司。它依托越南的成本和人才优势,建立了可行的商业模式。但其价值主要体现在“执行能力”上,在**价值独特性、客户聚焦、产品化和品牌建设**方面存在明显短板。目前更像一个“可靠的供应商”,而非一个“有高增长潜力的科技产品公司”。
|
||||
|
||||
* **合作建议:有条件推荐作为潜在客户深度合作**
|
||||
* **推荐合作场景**:
|
||||
1. **作为软件开发外包供应商**:如果您有明确、非核心的软件开发需求(如网站开发、移动应用开发、测试),且对成本敏感,Ht Active可以作为一个备选评估对象。建议进行严格的技术能力评估和案例考察。
|
||||
2. **作为技术补充团队**:在项目急需扩充人手时,可考虑其作为离岸开发团队的补充。
|
||||
* **需谨慎的场景**:
|
||||
1. **涉及核心业务逻辑或机密算法的开发**:需重点考察其信息安全管理和合同约束力。
|
||||
2. **寻求战略级技术合作伙伴**:如果您的合作需要对方具备深刻的行业洞察和产品规划能力,该公司可能并非最佳选择。
|
||||
|
||||
* **关键关注要点(对合作方及企业自身而言):**
|
||||
1. **明确的核心竞争力与案例**:要求对方提供最相关的、成功的项目案例,并了解其在该领域的深度。企业自身需打造标杆案例,明确宣传其最擅长的领域。
|
||||
2. **项目管理与沟通流程**:深入考察其项目管理工具、沟通频率、交付质量标准以及应对需求变更的流程。这是外包合作成败的关键。
|
||||
3. **团队稳定性与核心人员背景**:了解核心技术人员和项目经理的资历、在职时间,评估人员流失风险。稳定的团队是项目质量的保障。
|
||||
4. **知识产权(IP)归属与数据安全**:在合同中必须清晰界定开发成果的IP归属、源代码保管权限以及数据保密条款。
|
||||
5. **财务健康与长期存续能力**:间接了解其客户构成和付款情况,评估其作为长期合作伙伴的财务稳定性。对于企业自身,需规划从“项目驱动”向“产品驱动”的转型路径,以提升长期价值。
|
||||
|
||||
**免责声明**:本报告基于有限公开信息及合理推断生成,不构成投资建议或合作担保。在做出任何决策前,请务必对目标企业进行独立的尽职调查。
|
||||
@@ -0,0 +1,86 @@
|
||||
好的,作为一名专业的商业分析顾问,我将基于您提供的“Ht Active Software”企业信息,运用商业画布模型进行深度分析,并评估其潜在商业价值。
|
||||
|
||||
**重要说明:** 您提供的“企业描述”部分内容(关于大红袍茶叶)与公司名称、官网及行业(计算机软件)存在明显矛盾。这可能是信息录入错误。为进行有效分析,我将**以“Ht Active Software”作为一家越南的计算机软件公司**为基准,结合其官网(htactive.com)可推断的业务方向(通常为软件开发、IT服务)进行分析。若“企业描述”为真实业务,则本报告完全不适用。
|
||||
|
||||
---
|
||||
|
||||
### **企业商业画布分析报告:Ht Active Software**
|
||||
|
||||
#### **1. 客户细分 (Customer Segments)**
|
||||
1. **核心内容分析:** 作为一家越南的软件公司,其客户可能分为两类:**B2B(企业客户)** 和 **B2C(终端消费者)**。B2B客户可能包括需要定制软件开发、数字化转型咨询、IT外包服务的本地及国际中小企业或初创公司。B2C客户则可能是其开发的特定软件产品(如移动应用、SaaS工具)的直接用户。
|
||||
2. **优势/特点:** 位于越南岘港,拥有**人力成本优势**,能向欧美、日韩等市场提供高性价比的软件外包服务。同时,对东南亚本土市场有文化和地理上的理解优势。
|
||||
3. **潜在风险或关注点:** 客户群体可能较为分散,不够聚焦。如果同时服务B2B和B2C,可能导致资源分散,价值主张不清晰。面临来自印度、菲律宾等其他外包目的地以及全球远程开发团队的激烈竞争。
|
||||
4. **评分:** 6分。需进一步明确其核心目标客户画像。
|
||||
|
||||
#### **2. 价值主张 (Value Propositions)**
|
||||
1. **核心内容分析:** 价值主张可能围绕 **“高性价比的优质软件开发服务”** 或 **“解决特定痛点的软件产品”**。例如:为中小企业提供快速、低成本的MVP开发;为特定行业提供定制化解决方案;或拥有一款用户体验出色的工具型应用。
|
||||
2. **优势/特点:** 成本优势是显著特点。如果团队技术扎实、英语沟通能力强,能形成 **“优质不贵”** 的价值组合。敏捷开发、快速响应也可能是其优势。
|
||||
3. **潜在风险或关注点:** “性价比”定位容易陷入价格战,品牌溢价能力弱。如果缺乏独特的技术壁垒或深刻的行业洞察,价值主张容易被复制。
|
||||
4. **评分:** 6分。价值主张清晰度不足,需从其官网或案例中提炼更具体的独特价值。
|
||||
|
||||
#### **3. 渠道通路 (Channels)**
|
||||
1. **核心内容分析:** 渠道可能包括:**线上渠道**(公司官网、LinkedIn、Clutch等外包平台、社交媒体营销);**线下渠道**(本地行业会议、客户推荐、销售团队直接拓展);以及**合作伙伴渠道**(与咨询公司、设计机构合作获客)。
|
||||
2. **优势/特点:** 有效利用全球性外包平台(如Clutch, Upwork)可以低成本触达国际客户。建立官网展示作品集是标准且必要的做法。
|
||||
3. **潜在风险或关注点:** 过度依赖第三方平台会导致渠道费用高(平台佣金)和客户关系不稳定。自主获客能力(如内容营销、SEO)的强弱是关键。
|
||||
4. **评分:** 7分。渠道结构符合行业常规,但需评估其主动营销能力。
|
||||
|
||||
#### **4. 客户关系 (Customer Relationships)**
|
||||
1. **核心内容分析:** 对于B2B项目制业务,客户关系通常是 **“长期项目合作与维护”** 模式,通过客户成功经理、技术支持来维护。对于B2C产品,则通过社区运营、客户服务、定期更新来提升用户粘性。
|
||||
2. **优势/特点:** 越南团队通常以勤奋、配合度高著称,有利于建立稳定的长期服务关系。如果能提供超出预期的沟通和交付体验,能形成良好口碑。
|
||||
3. **潜在风险或关注点:** 跨文化、跨时区沟通可能产生摩擦,影响客户满意度。项目结束后,如何从“一次性交易”转化为“持续服务”关系是挑战。
|
||||
4. **评分:** 7分。客户关系维护是外包公司的生命线,其效果高度依赖于团队的执行力。
|
||||
|
||||
#### **5. 收入来源 (Revenue Streams)**
|
||||
1. **核心内容分析:** 主要收入模式可能包括:**项目制收费**(固定价格或按人天计价)、**长期人力外包**(按月收取人员服务费)、以及可能的**产品授权或订阅费**(如果拥有自有软件产品)。
|
||||
2. **优势/特点:** 项目制和人力外包模式现金流相对可预测。如果拥有SaaS产品,则能产生更可持续的经常性收入。
|
||||
3. **潜在风险或关注点:** 项目制收入波动性大,且存在“有活干才有收入”的瓶颈。严重依赖人力时间售卖,规模化天花板明显,利润率受人工成本上涨挤压。
|
||||
4. **评分:** 6分。收入模式传统,缺乏高利润的增值服务或产品收入。
|
||||
|
||||
#### **6. 核心资源 (Key Resources)**
|
||||
1. **核心内容分析:** 最核心的资源无疑是 **“技术团队”**——包括软件开发人员、项目经理、UI/UX设计师。其次可能是**知识产权**(自有产品的代码、算法)、**品牌声誉**(在特定平台或地区的口碑)以及**客户案例**。
|
||||
2. **优势/特点:** 51-200人的规模在越南属于中型团队,具备承接一定复杂度项目的能力。团队年轻,学习新技术速度快。
|
||||
3. **潜在风险或关注点:** 人才是最大资产也是最大风险。越南IT人才市场竞争加剧,人员流动率可能较高。如果缺乏核心技术专利或品牌,护城河较浅。
|
||||
4. **评分:** 7分。人力资源是基础,但需关注其团队稳定性和技术栈的领先性。
|
||||
|
||||
#### **7. 关键业务 (Key Activities)**
|
||||
1. **核心内容分析:** 关键业务活动包括:**软件开发与交付**(核心生产活动)、**业务拓展与销售**、**项目管理与客户沟通**、以及**技术研究与学习**。
|
||||
2. **优势/特点:** 专注于软件交付的全流程执行。如果流程规范(如采用敏捷开发),能保证交付质量和效率。
|
||||
3. **潜在风险或关注点:** 业务可能陷入“交付工厂”模式,忙于执行而缺乏对前沿技术和产品设计的战略性投入。销售能力决定了业务天花板。
|
||||
4. **评分:** 7分。关键业务明确,但需向价值链上游(咨询、设计)或下游(运营、维护)延伸以提升价值。
|
||||
|
||||
#### **8. 重要合作 (Key Partnerships)**
|
||||
1. **核心内容分析:** 合作伙伴可能包括:**技术合作伙伴**(云服务商如AWS、Azure,软件工具提供商);**渠道合作伙伴**(海外营销机构、本地商会);**人才合作伙伴**(当地大学、培训机构)。
|
||||
2. **优势/特点:** 与全球主流云服务商合作,能保证技术栈的先进性和可靠性。与高校合作有助于稳定的人才输送。
|
||||
3. **潜在风险或关注点:** 合作伙伴关系可能较为松散,未形成战略协同。供应链(主要指人才供应)的稳定性面临挑战。
|
||||
4. **评分:** 6分。合作关系网络有待深化,以构建更稳固的生态。
|
||||
|
||||
#### **9. 成本结构 (Cost Structure)**
|
||||
1. **核心内容分析:** 成本以**变动成本为主**,核心是**人力成本**(员工薪资、福利)。其次是**固定成本**,如办公室租金、云服务器费用、营销费用和管理费用。
|
||||
2. **优势/特点:** 越南的整体人力成本相对于欧美仍有优势,这是其商业模式成立的基础。成本结构相对简单透明。
|
||||
3. **潜在风险或关注点:** 人力成本刚性上涨是长期趋势,直接侵蚀利润。项目空档期(Bench Time)会导致人力资源闲置,产生直接损失。对关键技术人员依赖大,其薪资上涨压力显著。
|
||||
4. **评分:** 7分。成本结构清晰,但主要成本项面临持续上涨压力。
|
||||
|
||||
---
|
||||
|
||||
### **总体评估与建议**
|
||||
|
||||
- **总体评分:65/100分**
|
||||
- **评分解读:** Ht Active Software 是一家典型的、处于成长期的越南软件服务公司。其商业模式清晰,优势(成本、地理位置)和劣势(模式传统、竞争激烈)都非常明显。它具备一定的执行力和市场生存能力,但在创新性、价值壁垒和盈利潜力上表现平平,面临行业同质化竞争的普遍挑战。
|
||||
|
||||
- **合作建议:**
|
||||
- **谨慎推荐,适用于特定场景的合作。** 不建议作为战略级或创新型核心产品的唯一技术伙伴。
|
||||
- **推荐场景:**
|
||||
1. 需要高性价比开发资源的**非核心业务模块**、**MVP验证**或**已有产品的功能增补**。
|
||||
2. 对成本敏感,且项目需求明确、沟通文档清晰的中小型项目。
|
||||
3. 作为辅助开发团队,补充现有技术力量的不足。
|
||||
- **不推荐场景:**
|
||||
1. 涉及核心商业机密或尖端技术的战略级产品开发。
|
||||
2. 需求极度不明确、需要深度业务咨询和共同探索的创新型项目。
|
||||
3. 对响应速度、文化无缝衔接要求极高的核心业务支持。
|
||||
|
||||
- **关键关注要点:**
|
||||
1. **核实真实业务与能力:** 必须彻底澄清其业务描述的矛盾之处。通过审查其官网作品集(Portfolio)、客户案例、技术栈,并进行技术面试或POC测试,验证其实际技术能力和行业经验。
|
||||
2. **评估团队稳定性与沟通能力:** 重点考察其核心团队成员(尤其是项目经理和技术负责人)的任职时间、英文沟通流畅度以及项目管理流程。高流动率是项目交付的重大风险。
|
||||
3. **明确合作模式与知识产权:** 在合同中对交付物标准、验收流程、知识产权归属(特别是源代码)进行极其清晰的定义。建议采用分阶段交付和付款的方式以控制风险。
|
||||
4. **管理预期与沟通成本:** 预先建立高效的跨文化、跨时区沟通机制(如每日站会、协作工具)。对可能存在的理解偏差和进度延迟做好心理与管理准备。
|
||||
5. **关注其长期战略:** 了解该公司是否在向产品化、垂直行业解决方案等更高价值模式转型。这有助于判断其未来的稳定性和成长潜力,决定是否值得建立长期伙伴关系。
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
好的,作为一名专业的商业分析顾问,我将基于您提供的“嘉博文”企业信息,运用商业画布模型进行深度分析与价值评估。
|
||||
|
||||
**报告声明:** 本分析报告基于公开可得的企业描述信息进行框架性分析,不构成投资或合作决策的直接依据。深度合作前需进行详尽的尽职调查。
|
||||
|
||||
---
|
||||
|
||||
### **企业商业画布分析报告:Beijing Goldenway Bio Tech Co Ltd (嘉博文)**
|
||||
|
||||
#### **1. 客户细分 (Customer Segments)**
|
||||
1. **核心内容分析:** 企业业务模式高度聚焦于B2B及B2G(政府)市场。客户群体可细分为:
|
||||
* **地方政府与市政部门:** 作为城市有机废弃物(餐厨垃圾、污泥等)处理的责任主体,是其土壤修复与生态农业项目的关键客户和项目发起方。
|
||||
* **大型农业企业/农场/合作社:** 需要提升土壤肥力、进行有机种植或生态修复的规模化农业生产者。
|
||||
* **大型食品企业或拥有自有农场的品牌商:** 为确保上游原料的有机、安全与可持续性,寻求全程解决方案。
|
||||
* **工业园区或污染场地业主:** 需要进行工业污染场地修复的客户。
|
||||
2. **优势/特点:** 客户定位清晰,聚焦于有支付能力、有政策驱动(如“无废城市”、土壤污染防治法)的机构客户,市场准入壁垒高,单客户价值大。
|
||||
3. **潜在风险或关注点:** 客户决策周期长,受政策预算影响大;客户集中度可能较高,依赖少数大型项目;向中小型农户渗透的渠道和成本是挑战。
|
||||
4. **评分:** 8分。定位精准,但客户拓展速度和抗政策波动能力待考。
|
||||
|
||||
#### **2. 价值主张 (Value Propositions)**
|
||||
1. **核心内容分析:** 提供“废弃物资源化 - 土壤修复 - 生态农业”的一体化闭环解决方案。核心价值在于:
|
||||
* **环境价值:** 解决城市有机废弃物处置难题,变废为宝。
|
||||
* **农业价值:** 修复退化或污染土壤,显著提升土壤有机质和肥力,助力有机/绿色农产品生产。
|
||||
* **经济价值:** 为客户降低废弃物处置成本、减少化肥投入、提升农产品溢价。
|
||||
* **技术与品牌价值:** 以“国家技术发明奖”、“全球清洁技术100强”等背书,提供高技术壁垒的可靠解决方案。
|
||||
2. **优势/特点:** 价值主张独特且强大,将环保痛点与农业升级需求创造性结合,符合国家“双碳”与乡村振兴战略,社会效益与经济效益并重。
|
||||
3. **潜在风险或关注点:** 技术效果的显现需要时间,客户需要教育;与传统化肥或简单堆肥相比,初期成本可能较高,需清晰核算长期投资回报。
|
||||
4. **评分:** 9分。价值主张具有高度差异化和时代契合度。
|
||||
|
||||
#### **3. 渠道通路 (Channels)**
|
||||
1. **核心内容分析:** 渠道可能包括:
|
||||
* **直销团队:** 针对地方政府、大型企业的项目型销售,是核心渠道。
|
||||
* **行业峰会与政府招标:** 参与环保、农业领域的专业会议,竞标政府公开采购项目。
|
||||
* **合作伙伴推荐:** 与规划设计院、环保工程公司等形成合作,共同承接项目。
|
||||
* **线上平台:** 官网作为品牌与技术展示窗口,但复杂解决方案的成交主要在线下。
|
||||
2. **优势/特点:** 针对大客户和项目制,直销模式直接、高效,能深度理解客户需求。
|
||||
3. **潜在风险或关注点:** 渠道相对单一,对核心销售团队能力依赖度高;品牌在更广泛的农业市场中的渠道下沉能力未知;官网信息更新与营销转化功能可能不强。
|
||||
4. **评分:** 7分。渠道与客户匹配,但拓展性和抗风险性一般。
|
||||
|
||||
#### **4. 客户关系 (Customer Relationships)**
|
||||
1. **核心内容分析:** 以 **“长期战略合作”** 为核心。关系模式包括:
|
||||
* **共同开发:** 与地方政府共建循环经济示范项目。
|
||||
* **定制化服务:** 为大型农场提供全程技术指导与服务。
|
||||
* **持续运营服务:** 在废弃物处理项目中可能涉及长期的运营维护。
|
||||
2. **优势/特点:** 深度绑定的客户关系能带来稳定的收入流和高的客户切换成本,易于建立行业口碑和案例壁垒。
|
||||
3. **潜在风险或关注点:** 维护深度客户关系需要投入大量的高级人才和时间成本;项目周期长,回款速度可能较慢。
|
||||
4. **评分:** 8分。客户关系模式坚固,但对公司运营能力要求高。
|
||||
|
||||
#### **5. 收入来源 (Revenue Streams)**
|
||||
1. **核心内容分析:** 收入来源可能多元化:
|
||||
* **项目工程收入:** 土壤修复、废弃物处理厂建设等EPC(设计、采购、施工)收入。
|
||||
* **技术服务与特许权使用费:** 输出核心工艺技术、设备或菌剂。
|
||||
* **运营服务收入:** 托管运营废弃物处理设施,按处理量收费。
|
||||
* **产品销售收入:** 销售自主研发的土壤调理剂、生物有机肥等产品。
|
||||
* **解决方案咨询费:** 提供生态农业全程规划与咨询服务。
|
||||
2. **优势/特点:** 收入组合良好,兼具一次性项目收入和持续性运营/产品收入,有利于平滑现金流。
|
||||
3. **潜在风险或关注点:** 不同收入来源的占比和毛利率差异大;项目制收入不稳定且波动大;产品销售面临传统农资市场的激烈竞争。
|
||||
4. **评分:** 8分。盈利模式设计合理,但需关注收入结构的健康度。
|
||||
|
||||
#### **6. 核心资源 (Key Resources)**
|
||||
1. **核心内容分析:** 核心资源排序如下:
|
||||
* **专利与技术:** “国家技术发明奖”、“中国专利金奖”是其最核心的竞争壁垒。
|
||||
* **品牌与资质:** “全球清洁技术100强”、“高新技术企业”等荣誉是重要的信任状。
|
||||
* **人才团队:** 研发科学家、懂技术和商务的复合型项目人才是关键。
|
||||
* **示范项目与案例:** 成功的标杆项目是说服新客户的最有力资源。
|
||||
2. **优势/特点:** 技术驱动特征明显,核心资源壁垒高,难以被复制。
|
||||
3. **潜在风险或关注点:** 技术迭代风险;核心人才流失风险;轻资产运营模式下,对专利保护和技术秘密管理要求极高。
|
||||
4. **评分:** 9分。核心资源非常突出,是公司价值的基石。
|
||||
|
||||
#### **7. 关键业务 (Key Activities)**
|
||||
1. **核心内容分析:** 最重要的活动包括:
|
||||
* **研发与技术创新:** 持续优化废弃物处理工艺和土壤修复产品。
|
||||
* **项目设计与方案整合:** 为客户定制一体化技术解决方案。
|
||||
* **商务拓展与政府关系维护:** 获取大型项目和政府支持。
|
||||
* **项目运营管理与服务:** 确保已落地项目的稳定高效运行。
|
||||
2. **优势/特点:** 业务活动紧密围绕其价值主张展开,研发与方案设计是价值链的顶端。
|
||||
3. **潜在风险或关注点:** 业务活动重,从研发到销售到运营,对组织协同和管理能力挑战大;容易陷入具体项目而分散对长期技术研发的投入。
|
||||
4. **评分:** 8分。关键业务清晰,但执行复杂度高。
|
||||
|
||||
#### **8. 重要合作 (Key Partnerships)**
|
||||
1. **核心内容分析:** 关键合作伙伴可能包括:
|
||||
* **科研机构/大学:** 进行联合技术研发。
|
||||
* **地方政府/园区管委会:** 项目落地的最重要合作伙伴。
|
||||
* **上下游企业:** 设备供应商、工程建设商、农产品收购或销售企业。
|
||||
* **金融机构:** 为大型项目提供融资支持。
|
||||
2. **优势/特点:** 与政府的合作是其商业模式成功的关键,具有排他性和稳定性潜力。
|
||||
3. **潜在风险或关注点:** 对政府合作伙伴的依赖性过强;供应链的稳定性(如特定设备)可能受影响;合作伙伴的履约能力会影响项目整体进度。
|
||||
4. **评分:** 7分。合作生态必要但存在单点依赖风险。
|
||||
|
||||
#### **9. 成本结构 (Cost Structure)**
|
||||
1. **核心内容分析:** 预计为 **“价值驱动型”** 成本结构,主要成本包括:
|
||||
* **研发成本:** 高比例的研发人员薪酬、实验费用。
|
||||
* **人力成本:** 高素质的技术、商务和项目管理团队薪酬。
|
||||
* **项目执行成本:** 外包工程、设备采购、物料等变动成本,与项目规模正相关。
|
||||
* **销售与市场费用:** 针对大客户的商务拓展费用。
|
||||
2. **优势/特点:** 成本投入聚焦于创造高价值的研发和人才,符合技术型公司特征。轻资产模式可能使固定资产折旧较低。
|
||||
3. **潜在风险或关注点:** 人力成本刚性且持续上涨;项目成本控制能力直接影响利润率;研发投入具有不确定性,若不能持续产出,则形成沉没成本。
|
||||
4. **评分:** 7分。成本结构合理,但需严格控制项目毛利和研发投入效率。
|
||||
|
||||
---
|
||||
|
||||
### **总体评估与建议**
|
||||
|
||||
* **总体评分:81/100分**
|
||||
* **评分解读:** 嘉博文是一家典型的“技术驱动、政策受益、解决方案型”企业。其商业画布整体设计优秀,价值主张独特且具有高度社会责任感,核心资源(技术专利)构筑了坚实的护城河。主要得分点在于清晰的客户定位、强大的价值主张和核心资源。扣分点主要在于渠道的拓展性、对单一类型合作伙伴(政府)的依赖以及成本结构中对项目和人力成本的控制挑战。
|
||||
|
||||
* **合作建议:推荐作为潜在客户进行深度接触与评估。**
|
||||
* **理由:** 该公司赛道前景广阔,符合国家长期战略;商业模式经过验证(拥有奖项和标杆案例);具备解决复杂环境-农业问题的系统能力。对于需要处理有机废弃物、改善土壤或发展生态农业的政府或大型企业客户而言,嘉博文是一个值得重点考察的技术解决方案提供商。
|
||||
|
||||
* **关键关注要点:**
|
||||
1. **财务健康度与项目现金流:** 必须核查其历史项目的盈利能力、应收账款周期及现金流状况。项目制公司常面临回款压力。
|
||||
2. **技术可复制性与最新进展:** 需深入了解其核心专利的保护范围、技术在不同地域和废弃物类型上的适配性,以及研发管线的最新成果。
|
||||
3. **核心团队稳定性与执行力:** 评估创始人及核心技术与商务团队的背景、稳定性,以及公司过往项目的交付记录和客户口碑。
|
||||
4. **市场拓展战略:** 了解其未来是继续深耕政府大型项目,还是计划将产品和服务标准化,向更广阔的农业市场下沉。这决定了其增长天花板。
|
||||
5. **政策风险应对:** 评估其对地方政府财政预算、环保政策重点转移等风险的应对策略和预案。
|
||||
@@ -0,0 +1,77 @@
|
||||
好的,作为一名专业的商业分析顾问,我将基于您提供的“Trade Link Nepal”企业信息,运用商业画布模型进行深度分析,并评估其潜在商业价值。
|
||||
|
||||
**重要说明:** 您提供的“企业基本信息”与“企业描述”存在明显矛盾。基本信息显示其为尼泊尔的摄影摄像公司,而企业描述则详细描述了一家名为“卓致力天”的中国道路工程检测设备公司。鉴于“企业描述”部分信息极为详尽且专业,而基本信息中的“摄影摄像”行业与描述内容完全不符,我判断“企业描述”部分更可能反映了该企业的真实业务。因此,本次分析将主要基于“卓致力天”的业务描述进行,并假设“Trade Link Nepal”可能是其海外分支机构或信息录入有误。
|
||||
|
||||
---
|
||||
|
||||
### **商业画布模型分析报告:卓致力天 (假设主体)**
|
||||
|
||||
#### **1. 客户细分 (Customer Segments)**
|
||||
1. **核心内容分析:** 企业服务于B2B市场,客户高度专业化且集中。主要客户群体包括:中国各级**公路建设与养护单位**(如省市级公路局、高速公路公司)、**大型土木工程施工企业**、**工程设计院与科研院所**、**第三方工程质量检测机构**。
|
||||
2. **优势/特点:** 客户群体明确且支付能力强,属于基础设施建设的核心参与方,需求刚性且与政府投资、大型项目周期紧密相关。专注于高价值、高技术门槛的细分市场,客户忠诚度一旦建立,迁移成本较高。
|
||||
3. **潜在风险或关注点:** 客户高度依赖国家基础设施投资政策与预算周期,宏观经济波动和财政紧缩会直接影响需求。客户数量相对有限,大客户依赖风险可能存在。
|
||||
4. **评分:** 8分。定位清晰,市场刚需,但受宏观政策影响大。
|
||||
|
||||
#### **2. 价值主张 (Value Propositions)**
|
||||
1. **核心内容分析:** 提供“**一站式高端道路检测解决方案**”。核心价值在于:**产品组合的全面性**(从进口到国产,从现场到实验室)、**技术的先进性与权威性**(代理欧美顶尖品牌)、**服务的系统性**(售前咨询、售后支持、全面解决方案)。
|
||||
2. **优势/特点:** 解决了客户在采购高端、精密检测设备时面临的“选择难、集成难、维护难”的痛点。通过代理+自产的模式,既能满足对国际顶级技术的需求,又能通过国产化设备提供高性价比选项和更灵活的服务。
|
||||
3. **潜在风险或关注点:** 价值主张高度依赖其代理品牌的声誉和技术优势。若核心代理权发生变更,价值主张将受到严重冲击。同时,需要持续投入以保持国产设备的技术竞争力。
|
||||
4. **评分:** 9分。价值主张清晰、有力,且构建了较高的竞争壁垒。
|
||||
|
||||
#### **3. 渠道通路 (Channels)**
|
||||
1. **核心内容分析:** 渠道以**直接销售**和**行业深度营销**为主。可能包括:参与行业展会、技术研讨会;通过官网和行业媒体进行内容营销;销售团队直接对接大型客户;与设计院、科研机构合作进行技术推广。
|
||||
2. **优势/特点:** 直销模式有利于建立深厚的客户关系,深入理解客户需求,并提供复杂的定制化解决方案。在专业圈子内的口碑和专家推荐是极其有效的渠道。
|
||||
3. **潜在风险或关注点:** 渠道覆盖面可能有限,过于依赖销售人员个人能力和行业人脉。线上渠道(如官网)可能更多是品牌展示和信息发布,而非主要获客渠道。
|
||||
4. **评分:** 7分。渠道与业务匹配,但可能增长效率受限,数字化营销潜力待挖掘。
|
||||
|
||||
#### **4. 客户关系 (Customer Relationships)**
|
||||
1. **核心内容分析:** 致力于建立**长期、深度的合作伙伴关系**。模式是“设备销售+持续服务”,通过完善的售后服务体系(安装、培训、校准、维修)将一次性交易转化为长期服务合同,嵌入客户的日常运营中。
|
||||
2. **优势/特点:** 高客户粘性。检测设备属于生产工具,持续、稳定的技术支持是客户的核心需求。这种关系能带来持续的耗材、配件、升级服务等后续收入。
|
||||
3. **潜在风险或关注点:** 维护深度客户关系需要高昂的人力与时间成本。服务团队的专业能力和响应速度是维系关系的关键,任何服务短板都可能导致客户流失。
|
||||
4. **评分:** 8分。客户关系模式是其商业模式的核心优势之一。
|
||||
|
||||
#### **5. 收入来源 (Revenue Streams)**
|
||||
1. **核心内容分析:** 收入来源多元化:**1) 高价值检测设备销售**(主要收入);**2) 售后服务与技术支持年费**(稳定现金流);**3) 设备租赁或检测服务**(可能);**4) 耗材与配件销售**(持续性收入)。
|
||||
2. **优势/特点:** 收入结构健康,不仅有一次性的项目收入,更有持续性的服务收入,能平滑项目周期带来的波动。代理和自产结合可能带来差异化的利润率。
|
||||
3. **潜在风险或关注点:** 核心收入仍严重依赖大型设备销售,该部分受单个项目预算影响大,订单可能呈现“脉冲式”特征,不够平稳。
|
||||
4. **评分:** 8分。收入模式设计良好,但需关注主营收入的波动性。
|
||||
|
||||
#### **6. 核心资源 (Key Resources)**
|
||||
1. **核心内容分析:** **1) 品牌与代理权资源**:与PaveTesting, MATEST等国际品牌的独家或核心代理关系是首要核心资源。**2) 技术与知识资源**:对道路检测技术的深刻理解和应用经验。**3) 人才资源**:专业的销售工程师、技术服务团队。**4) 客户资源与行业声誉**。
|
||||
2. **优势/特点:** 构建了强大的资源壁垒。国际品牌代理权是稀缺资源,难以被复制。多年积累的行业知识和客户案例是无形的重要资产。
|
||||
3. **潜在风险或关注点:** 核心资源存在外部依赖性。代理协议的不稳定性是重大风险。核心人才的流失也可能导致关键技术和服务能力下降。
|
||||
4. **评分:** 8分。资源壁垒高,但存在外部依赖风险。
|
||||
|
||||
#### **7. 关键业务 (Key Activities)**
|
||||
1. **核心内容分析:** **1) 供应链管理与产品整合**:管理复杂的国际采购、国产化生产及产品线组合。**2) 技术营销与解决方案销售**:而非简单产品推销。**3) 全生命周期客户服务**:从售前咨询到售后支持。**4) 国产设备的研发与生产**(如果涉及)。
|
||||
2. **优势/特点:** 业务活动紧密围绕其价值主张展开,形成了从“选型”到“使用”的完整业务闭环,增强了客户控制力。
|
||||
3. **潜在风险或关注点:** 业务链条较长,管理复杂度高。需要平衡进口代理与国产研发的资源投入。市场变化要求其关键业务必须保持灵活性和前瞻性。
|
||||
4. **评分:** 8分。关键业务清晰且协同性强,但运营复杂度高。
|
||||
|
||||
#### **8. 重要合作 (Key Partnerships)**
|
||||
1. **核心内容分析:** **1) 国际设备供应商**:如英国的PaveTesting、意大利的MATEST等,是最重要的上游合作伙伴。**2) 国产设备制造商/OEM厂商**。**3) 行业机构与标准制定单位**。**4) 金融机构**(可能为客户提供融资租赁服务)。
|
||||
2. **优势/特点:** 与国际顶尖厂商的合作是其品牌和技术信誉的基石。多元化的合作伙伴网络有助于提供更完整的解决方案。
|
||||
3. **潜在风险或关注点:** 供应链高度国际化,可能受地缘政治、汇率波动、国际物流等因素影响。对单一品牌代理的过度依赖风险极高。
|
||||
4. **评分:** 7分。合作伙伴质量高,但供应链风险集中且不可控因素多。
|
||||
|
||||
#### **9. 成本结构 (Cost Structure)**
|
||||
1. **核心内容分析:** 以**高额变动成本为主**,兼有重要固定成本。主要包括:**1) 商品采购成本**(进口设备);**2) 生产成本**(国产设备);**3) 人力成本**(高薪的专业技术及销售团队);**4) 营销与展会费用**;**5) 仓储物流及售后服务成本**。
|
||||
2. **优势/特点:** 规模效应明显。随着销量上升,采购成本、单位营销成本可能下降。服务收入毛利率通常较高,有助于改善整体利润结构。
|
||||
3. **潜在风险或关注点:** 资金占用大(库存、应收账款),现金流管理压力大。人力成本刚性增长。汇率波动直接影响进口成本。
|
||||
4. **评分:** 7分。成本结构符合行业特点,但资金和汇率风险需审慎管理。
|
||||
|
||||
---
|
||||
|
||||
### **总体评估与建议**
|
||||
|
||||
- **总体评分:78 / 100分**
|
||||
该企业(基于“卓致力天”业务描述)商业模式清晰且稳固,在细分市场中建立了显著的优势。其“高端代理+国产方案+深度服务”的组合拳构建了较强的竞争壁垒,客户关系紧密,收入来源相对健康。主要扣分点在于对外部核心资源(国际代理权)的依赖、受宏观政策影响的周期性波动以及潜在的供应链风险。
|
||||
|
||||
- **合作建议:推荐作为潜在客户进行深度合作。**
|
||||
**理由**:该企业处于产业链的关键环节,服务的是国家基础设施建设的核心需求,行业前景长期看好。其商业模式成熟,在业内应有良好的声誉和稳定的客户基础。无论是作为供应商(提供高端设备)、合作伙伴(共同开发市场)还是服务对象(如需融资),其基本盘是扎实的。**但务必核实“Trade Link Nepal”与“卓致力天”的确切关系**。
|
||||
|
||||
- **关键关注要点:**
|
||||
1. **代理权稳定性与供应链风险**:必须核实其与PaveTesting、MATEST等核心品牌代理协议的有效性、排他性及续约条件。评估其国际供应链的抗风险能力。
|
||||
2. **国产化能力与研发投入**:考察其国产设备的技术水平、市场竞争力及营收占比。这是其降低外部依赖、提升利润率的关键。
|
||||
3. **财务健康度与现金流**:由于其业务模式资金占用大,需重点关注其应收账款周期、库存周转率及现金流状况,评估其财务稳健性。
|
||||
4. **“Trade Link Nepal”实体核查**:这是本次分析最大的不确定性。必须厘清尼泊尔这个实体与中国“卓致力天”是母子公司、代理关系、还是完全独立的两个实体(信息错误)。这直接影响所有评估结论的有效性。
|
||||
@@ -0,0 +1,137 @@
|
||||
好的,作为一名专业的商业分析顾问,我将基于您提供的企业信息,运用商业画布模型对芯智控股有限公司进行深度分析与价值评估。
|
||||
|
||||
---
|
||||
|
||||
# **芯智控股有限公司商业画布分析与价值评估报告**
|
||||
|
||||
**报告摘要:** 芯智控股是一家成立于2005年的香港半导体分销与方案提供商,专注于数字电视、移动互联等领域的芯片代理与技术增值服务。本报告通过商业画布九维度模型,剖析其商业模式的核心要素、优势、风险及潜在价值。
|
||||
|
||||
### **1. 客户细分 (Customer Segments)**
|
||||
|
||||
1. **核心内容分析:** 企业明确服务于**B2B客户**。客户群体主要为需要特定芯片及配套技术方案的**电子产品制造商**,尤其是在数字电视、机顶盒、移动互联设备、网络接入及存储设备领域的制造商。这些客户通常是国内外知名品牌或其ODM/OEM厂商。
|
||||
2. **优势/特点:**
|
||||
* **聚焦高增长领域:** 所服务的数字电视、移动互联、接入与存储均为持续发展的电子消费与基础设施领域,市场潜力大。
|
||||
* **客户质量较高:** 服务“国内外多家知名客户”,表明其已建立一定的市场信誉和客户基础,客户粘性和支付能力相对较强。
|
||||
3. **潜在风险或关注点:**
|
||||
* **客户集中度风险:** 若主要客户过于集中,其订单波动将对公司营收产生重大影响。信息未披露具体客户构成。
|
||||
* **行业周期性风险:** 半导体及下游电子制造业具有周期性,需求波动会影响分销商业绩。
|
||||
4. **评分:7分**
|
||||
* 评分理由:目标市场明确且具有前景,客户基础优质,但存在行业周期性和潜在的客户依赖风险。
|
||||
|
||||
### **2. 价值主张 (Value Propositions)**
|
||||
|
||||
1. **核心内容分析:** 企业提供双重价值主张:**“桥梁”价值**与**“技术增值”价值**。不仅作为芯片的**贸易代理**,解决客户元器件供应链问题,更作为**解决方案提供商**,提供技术支持、方案设计等服务,帮助客户缩短产品研发周期、降低技术门槛。
|
||||
2. **优势/特点:**
|
||||
* **差异化竞争:** 在纯贸易型分销商中脱颖而出,通过技术增值服务构建了更高的竞争壁垒和客户粘性。
|
||||
* **解决核心痛点:** 精准解决了中小型制造商面临的“芯片选型难、方案开发慢、供应链不稳定”等痛点。
|
||||
3. **潜在风险或关注点:**
|
||||
* **技术深度与持续投入:** 技术增值服务需要持续的研发和技术团队投入,对仅有2名员工的公司而言,其技术能力的深度和广度存疑,可能依赖于外部合作或核心技术人员。
|
||||
* **价值可替代性:** 原厂直供、大型综合分销商(如艾睿、安富利)也在提供类似服务,竞争激烈。
|
||||
4. **评分:7.5分**
|
||||
* 评分理由:价值主张清晰且具有差异化,符合行业趋势,但执行该价值主张所需的核心资源(技术团队)规模与公司体量存在不匹配,是主要疑虑点。
|
||||
|
||||
### **3. 渠道通路 (Channels)**
|
||||
|
||||
1. **核心内容分析:** 渠道以**直接销售**为主,可能辅以线上渠道(官网)作为信息窗口和初步接触点。作为B2B分销商,其核心渠道是销售团队通过行业展会、客户拜访、口碑推荐等方式直接触达并服务终端制造商客户。
|
||||
2. **优势/特点:**
|
||||
* **直接、高效:** 直接面对客户,能深刻理解需求,快速响应,并提供贴身的技术支持服务。
|
||||
* **官网作为品牌门户:** 官网(smart-core.com.hk)提供了公司信息和联系窗口,有助于建立专业形象。
|
||||
3. **潜在风险或关注点:**
|
||||
* **渠道覆盖有限:** 仅2名员工的规模严重限制了其市场开拓和客户覆盖的广度与深度,可能仅能维护少数核心客户。
|
||||
* **对关键人员依赖度高:** 销售与技术渠道高度依赖于创始人或核心员工,存在人员风险。
|
||||
4. **评分:5分**
|
||||
* 评分理由:渠道模式与业务性质匹配,但受制于极小的团队规模,渠道拓展能力和抗风险能力非常薄弱,是商业模式的明显短板。
|
||||
|
||||
### **4. 客户关系 (Customer Relationships)**
|
||||
|
||||
1. **核心内容分析:** 致力于建立**长期、深度的战略合作伙伴关系**。通过提供持续的技术支持、供应链保障和定制化方案,与客户绑定,从一次性交易转向持续性服务收费。
|
||||
2. **优势/特点:**
|
||||
* **高客户粘性:** 技术增值服务一旦嵌入客户产品设计,替换成本高,容易形成长期合作。
|
||||
* **顾问式销售:** 以解决方案提供商的角色出现,关系更紧密,议价能力相对更强。
|
||||
3. **潜在风险或关注点:**
|
||||
* **服务能力瓶颈:** 小团队能否为多家“知名客户”提供持续、高质量的深度服务是最大挑战。可能存在服务过度拉伸的风险。
|
||||
* **关系个人化:** 客户关系可能高度维系在个别核心员工身上,公司化、体系化的客户关系管理能力可能不足。
|
||||
4. **评分:6分**
|
||||
* 评分理由:客户关系策略正确且能创造高价值,但公司的资源规模(2人)与宣称的“服务多家知名客户”的深度关系模式存在巨大矛盾,执行落地风险极高。
|
||||
|
||||
### **5. 收入来源 (Revenue Streams)**
|
||||
|
||||
1. **核心内容分析:** 收入来源主要包括两部分:**芯片代理销售的价差利润** 和 **技术增值服务费**(可能以设计服务费、授权费、或打包在芯片价格中的形式体现)。
|
||||
2. **优势/特点:**
|
||||
* **收入多元化:** 相比纯分销商,增加了技术服务收入,提升了毛利率和收入稳定性。
|
||||
* **盈利模式清晰:** 分销行业的盈利模式成熟,现金流模式相对明确(先款后货或账期管理)。
|
||||
3. **潜在风险或关注点:**
|
||||
* **毛利率压力:** 代理分销业务毛利率受上游原厂和下游客户两头挤压,市场竞争激烈。
|
||||
* **技术服务收入占比与可持续性:** 技术服务收入的规模、占比及是否可持续是评估其价值主张是否兑现的关键。信息未披露。
|
||||
4. **评分:6.5分**
|
||||
* 评分理由:收入结构设计合理,具备提升盈利能力的潜力,但具体财务数据缺失,无法判断各来源的健康度和占比,且主业面临毛利率压力。
|
||||
|
||||
### **6. 核心资源 (Key Resources)**
|
||||
|
||||
1. **核心内容分析:** 核心资源包括:**上游原厂的代理权/合作关系**、**关键技术团队与方案积累**、**长期合作的优质客户资源**、以及**供应链管理与资金能力**。
|
||||
2. **优势/特点:**
|
||||
* **无形资产价值高:** 代理权、客户关系和技术方案是其主要壁垒,而非有形资产。
|
||||
* **轻资产运营:** 作为分销商,无需重资产投入,模式灵活。
|
||||
3. **潜在风险或关注点:**
|
||||
* **资源高度脆弱:** 所有核心资源均高度依赖“人”。代理权可能随关键人员变动而丢失;技术团队仅2人,是巨大风险点;客户关系也可能个人化。
|
||||
* **资金链风险:** 芯片分销需要垫付大量资金,公司资本实力和融资能力是关键,2人公司的资金实力存疑。
|
||||
4. **评分:4分**
|
||||
* 评分理由:理论上核心资源明确,但“2名员工”这一事实使其所有核心资源的稳定性、深度和广度都面临严峻质疑。这是评估该公司最大的“硬伤”。
|
||||
|
||||
### **7. 关键业务 (Key Activities)**
|
||||
|
||||
1. **核心内容分析:** 关键业务活动包括:**供应链管理与物流**(确保芯片稳定供应和交付)、**技术方案开发与支持**(为客户提供设计服务)、**销售与客户关系管理**、以及**市场需求分析与产品线规划**。
|
||||
2. **优势/特点:**
|
||||
* **业务链条完整:** 覆盖了从供应链到技术交付的全流程,能力较全面。
|
||||
* **活动聚焦价值主张:** 技术开发与支持活动直接支撑其差异化竞争策略。
|
||||
3. **潜在风险或关注点:**
|
||||
* **业务活动与资源严重不匹配:** 如此广泛且专业的关键业务活动,需要采购、物流、技术、销售、市场等多职能团队支撑,2人团队几乎不可能有效完成,极可能外包或严重依赖合作伙伴,导致控制力弱。
|
||||
* **执行质量风险:** 每项业务活动的执行深度和质量都可能因人手不足而大打折扣。
|
||||
4. **评分:4.5分**
|
||||
* 评分理由:关键业务定义准确,但以现有公司规模,这些业务要么无法有效开展,要么深度不足,商业模式在执行层面存在根本性挑战。
|
||||
|
||||
### **8. 重要合作 (Key Partnerships)**
|
||||
|
||||
1. **核心内容分析:** 最重要的合作伙伴是**上游芯片原厂**(如主控芯片、存储芯片供应商),其次是**物流与仓储服务商**,可能还包括**第三方设计公司或技术顾问**以补充自身技术能力。
|
||||
2. **优势/特点:**
|
||||
* **合作模式成熟:** 与原厂的代理合作是行业标准模式。
|
||||
* **借助外部能力:** 通过合作弥补自身在物流、特定技术领域的不足。
|
||||
3. **潜在风险或关注点:**
|
||||
* **对原厂依赖性极强:** 代理资质、供货优先级、价格支持完全受制于原厂策略。原厂直供客户或调整渠道策略会对其造成毁灭性打击。
|
||||
* **合作伙伴稳定性:** 若核心合作伙伴(原厂)发生变动,业务可能瞬间停滞。小规模公司在这种合作中议价能力很低。
|
||||
4. **评分:5.5分**
|
||||
* 评分理由:合作关系是业务的基石,但也是主要风险来源。公司规模小,在关键合作中处于弱势地位,供应链稳定性风险高。
|
||||
|
||||
### **9. 成本结构 (Cost Structure)**
|
||||
|
||||
1. **核心内容分析:** 成本主要为**变动成本**,包括:**采购芯片的商品成本**(最大头)、**人力成本**、**物流仓储费用**、**市场营销及客户维护费用**以及**技术研发外包或工具费用**。固定成本相对较低(如办公室租金、基本行政开支)。
|
||||
2. **优势/特点:**
|
||||
* **成本结构灵活:** 以变动成本为主,在业务收缩时能较快调整,经营杠杆较低。
|
||||
* **轻资产:** 无生产制造的重资产折旧,固定成本负担小。
|
||||
3. **潜在风险或关注点:**
|
||||
* **资金成本压力:** 存货占用大量流动资金,资金成本(利息或机会成本)是隐形成本。
|
||||
* **规模不经济:** 由于采购规模小,可能无法获得最优的原厂价格和支持,导致商品成本偏高。
|
||||
* **人均成本高企:** 若要维持技术增值服务,必须雇佣高薪技术人才,在2人规模下,人均成本会非常高,侵蚀利润。
|
||||
4. **评分:6分**
|
||||
* 评分理由:成本结构符合行业特性,但小规模运营可能导致其在采购成本控制和承受资金压力方面处于劣势。
|
||||
|
||||
---
|
||||
|
||||
### **总体评估与建议**
|
||||
|
||||
* **总体评分:52/100分**
|
||||
* **评分解读:** 该企业商业模式设计在**理念层面**具有亮点,特别是在价值主张上试图通过技术增值实现差异化。然而,其最大的、也是致命的问题在于**商业画布各模块与“2名员工”这一核心事实严重脱节**。其宣称的“服务多家知名客户”、“技术增值”、“领先的解决方案提供商”等定位,缺乏相应的核心资源(团队、资金)和关键业务执行能力作为支撑。因此,其商业模式在纸上可行,但在现实中可持续性和可扩展性存在巨大疑问。
|
||||
|
||||
* **合作建议:谨慎评估,暂不推荐作为战略级潜在客户进行深度合作。**
|
||||
* **理由:** 作为客户或合作伙伴,需要评估其长期稳定供货、持续技术支持和财务稳健的能力。该公司目前规模显示的风险过高:
|
||||
1. **履约风险高:** 难以保证在需求波动或供应链紧张时优先保障您的供应。
|
||||
2. **服务连续性风险:** 核心人员流失可能导致服务中断或技术断档。
|
||||
3. **财务风险:** 小公司抗风险能力弱,可能因一笔坏账或库存跌价而陷入经营困境。
|
||||
|
||||
* **关键关注要点:**
|
||||
1. **团队规模与能力的真实性:** 必须核实“2名员工”是否仅为法律注册信息,实际运营团队是否另有实体或更大规模。了解核心技术人员背景、数量及稳定性。
|
||||
2. **财务与运营数据:** 要求提供近年的营收、毛利率、客户集中度、前五大供应商及客户名单、技术服务收入占比等关键数据,以验证其商业模式的实际运行状况。
|
||||
3. **代理资质与供应链深度:** 核实其宣称代理的核心产品线是否拥有原厂正式授权,以及其库存深度、供货渠道的稳定性。
|
||||
4. **法律与股权结构:** 厘清香港公司与可能存在的内地运营实体之间的关系,明确合作签约主体与责任方。
|
||||
|
||||
**结论:** 芯智控股描述了一个有吸引力的“分销+技术”故事,但当前呈现的企业基本面(尤其是人员规模)与这个故事所需的支撑要素严重不符。建议将其视为一个**需要极高尽职调查门槛的潜在合作方**,在未澄清上述关键关注要点前,不宜进行大规模或战略性的合作投入。
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
fumamxCRM Backend - FastAPI Application
|
||||
后端独立服务,端口 10001
|
||||
"""
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from database import init_db, SessionLocal, Base, engine
|
||||
from auth import init_admin_user
|
||||
from routers import auth, customers, discover, settings, import_tasks, potential_customers, deep_analysis
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""应用生命周期管理"""
|
||||
print("🚀 初始化数据库...")
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
init_admin_user(db)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
print("✓ 后端服务启动完成")
|
||||
yield
|
||||
print("👋 服务关闭")
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="fumamxCRM API",
|
||||
description="fumamx 客户关系管理系统 API",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan,
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
openapi_url="/openapi.json"
|
||||
)
|
||||
|
||||
# CORS 配置 - 允许前端访问
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[
|
||||
"http://129.211.173.87:10000",
|
||||
"http://localhost:10000",
|
||||
"http://localhost:5173", # Vite dev server
|
||||
],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# API 路由
|
||||
app.include_router(auth.router)
|
||||
app.include_router(customers.router)
|
||||
app.include_router(discover.router)
|
||||
app.include_router(settings.router)
|
||||
app.include_router(import_tasks.router)
|
||||
app.include_router(potential_customers.router)
|
||||
app.include_router(deep_analysis.router)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {
|
||||
"name": "fumamxCRM API",
|
||||
"version": "1.0.0",
|
||||
"status": "running",
|
||||
"frontend": "http://129.211.173.87:10000"
|
||||
}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "healthy"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=10001)
|
||||
@@ -0,0 +1,84 @@
|
||||
"""
|
||||
JWT 认证相关
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from database import get_db
|
||||
from models import User
|
||||
|
||||
# 配置
|
||||
SECRET_KEY = "fumamx-crm-secret-key-2024" # 生产环境应使用环境变量
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_DAYS = 7
|
||||
|
||||
# 密码加密
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
security = HTTPBearer()
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""验证密码"""
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""获取密码哈希"""
|
||||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||
"""创建 JWT Token"""
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(days=ACCESS_TOKEN_EXPIRE_DAYS)
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
db: Session = Depends(get_db)
|
||||
) -> User:
|
||||
"""获取当前登录用户"""
|
||||
token = credentials.credentials
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="无效的认证凭据",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
|
||||
user = db.query(User).filter(User.username == username).first()
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def init_admin_user(db: Session):
|
||||
"""初始化管理员账号"""
|
||||
admin = db.query(User).filter(User.username == "admin").first()
|
||||
if not admin:
|
||||
admin = User(
|
||||
username="admin",
|
||||
hashed_password=get_password_hash("abcd1234")
|
||||
)
|
||||
db.add(admin)
|
||||
db.commit()
|
||||
print("✓ 管理员账号已创建: admin / abcd1234")
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
数据库连接配置
|
||||
"""
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
import os
|
||||
|
||||
# 数据库文件路径
|
||||
DATABASE_PATH = os.path.join(os.path.dirname(__file__), "fumamx_crm.db")
|
||||
SQLALCHEMY_DATABASE_URL = f"sqlite:///{DATABASE_PATH}"
|
||||
|
||||
engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URL,
|
||||
connect_args={"check_same_thread": False}
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def get_db():
|
||||
"""获取数据库会话"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def init_db():
|
||||
"""初始化数据库表"""
|
||||
Base.metadata.create_all(bind=engine)
|
||||
@@ -0,0 +1,167 @@
|
||||
"""
|
||||
fumamxCLI 调用封装
|
||||
"""
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
CLI_PATH = "/home/ubuntu/nanocode/fumamxCLI/fumamx"
|
||||
COOKIE_FILE = "/home/ubuntu/fumamx_cookies.txt"
|
||||
|
||||
|
||||
def run_cli_command(args: List[str]) -> Dict:
|
||||
"""运行 fumamxCLI 命令"""
|
||||
cmd = [CLI_PATH] + args
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
cwd="/home/ubuntu/nanocode/fumamxCLI"
|
||||
)
|
||||
|
||||
# 提取 JSON 输出
|
||||
output = result.stdout
|
||||
|
||||
# 尝试解析 JSON
|
||||
try:
|
||||
# 查找 JSON 部分(通常在 ╭─ 和 ╰─ 之间)
|
||||
json_match = re.search(r'[╭┌].*?[┐╮]([\s\S]*?)[╰└].*?[┘╯]', output)
|
||||
if json_match:
|
||||
json_str = json_match.group(1).strip()
|
||||
# 清理边框字符 (│ ║ ┃ ┆ ┇ etc.) 和 ANSI 转义序列
|
||||
json_str = re.sub(r'[│║┃┆┇┊┋]', '', json_str)
|
||||
json_str = re.sub(r'\x1b\[[0-9;]*m', '', json_str)
|
||||
json_str = json_str.strip()
|
||||
return json.loads(json_str)
|
||||
|
||||
# 直接尝试解析整个输出(用于 --raw 模式)
|
||||
# 先清理 ANSI 转义序列
|
||||
cleaned_output = re.sub(r'\x1b\[[0-9;]*m', '', output)
|
||||
# 查找 JSON 对象(从第一个 { 到最后一个 })
|
||||
json_start = cleaned_output.find('{')
|
||||
json_end = cleaned_output.rfind('}')
|
||||
if json_start != -1 and json_end != -1 and json_end > json_start:
|
||||
json_str = cleaned_output[json_start:json_end+1]
|
||||
return json.loads(json_str)
|
||||
|
||||
return json.loads(cleaned_output)
|
||||
except json.JSONDecodeError:
|
||||
return {
|
||||
"success": result.returncode == 0,
|
||||
"output": output,
|
||||
"error": result.stderr
|
||||
}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"error": "命令执行超时"}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
def search_companies(keyword: str, page: int = 1, page_size: int = 10) -> Dict:
|
||||
"""搜索公司
|
||||
|
||||
返回: {
|
||||
"total": int,
|
||||
"results": [{...}],
|
||||
"keyword": str
|
||||
}
|
||||
"""
|
||||
result = run_cli_command(["company", "search", keyword, "--page", str(page), "--page-size", str(page_size), "--raw"])
|
||||
|
||||
if result.get("code") == "0" and "data" in result:
|
||||
data = result["data"]
|
||||
return {
|
||||
"total": int(data.get("total", 0)),
|
||||
"results": data.get("results", []),
|
||||
"keyword": keyword
|
||||
}
|
||||
|
||||
return {"total": 0, "results": [], "keyword": keyword}
|
||||
|
||||
|
||||
def get_company_detail(name: str, country: str = "") -> Dict:
|
||||
"""获取公司详情(进口+出口数据)"""
|
||||
# 获取进口数据
|
||||
import_result = run_cli_command(["import-", "enterprise", name, "--country", country])
|
||||
|
||||
# 获取出口数据
|
||||
export_result = run_cli_command(["export", "enterprise", name, "--country", country])
|
||||
|
||||
# 获取统计
|
||||
import_stats = run_cli_command(["import-", "stats", name])
|
||||
export_stats = run_cli_command(["export", "stats", name])
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"country": country,
|
||||
"import_data": import_result if import_result.get("code") == "0" else None,
|
||||
"export_data": export_result if export_result.get("code") == "0" else None,
|
||||
"import_stats": import_stats if import_stats.get("code") == "0" else None,
|
||||
"export_stats": export_stats if export_stats.get("code") == "0" else None,
|
||||
}
|
||||
|
||||
|
||||
def update_cookie(cookie_content: str) -> bool:
|
||||
"""更新 cookie 文件"""
|
||||
try:
|
||||
with open(COOKIE_FILE, 'w') as f:
|
||||
f.write(cookie_content)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"保存 cookie 失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def check_cookie_valid() -> bool:
|
||||
"""检查 cookie 是否有效"""
|
||||
result = run_cli_command(["status"])
|
||||
return result.get("code") == "0" or "API 连接正常" in str(result)
|
||||
|
||||
|
||||
def get_countries() -> List[Dict]:
|
||||
"""获取国家列表"""
|
||||
result = run_cli_command(["country", "list"])
|
||||
|
||||
if result.get("code") == "0" and "data" in result:
|
||||
return result["data"]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class FumamxCLIWrapper:
|
||||
"""fumamxCLI 包装类 - 直接方法调用"""
|
||||
|
||||
def search_company(self, keyword: str, page: int = 1, page_size: int = 10) -> Dict:
|
||||
"""搜索公司"""
|
||||
return run_cli_command([
|
||||
"company", "search", keyword,
|
||||
"--page", str(page),
|
||||
"--page-size", str(page_size),
|
||||
"--raw"
|
||||
])
|
||||
|
||||
def get_company_info(self, company_id: str, domain: str) -> Dict:
|
||||
"""获取公司详细信息"""
|
||||
return run_cli_command([
|
||||
"company", "info", company_id, domain,
|
||||
"--raw"
|
||||
])
|
||||
|
||||
def get_company_contacts(self, company_id: str, domain: str) -> Dict:
|
||||
"""获取公司联系人列表"""
|
||||
return run_cli_command([
|
||||
"company", "contacts", company_id, domain,
|
||||
"--raw"
|
||||
])
|
||||
|
||||
def get_company_phones(self, domain: str) -> Dict:
|
||||
"""获取公司电话/图片信息"""
|
||||
return run_cli_command([
|
||||
"company", "phones", domain,
|
||||
"--raw"
|
||||
])
|
||||
@@ -0,0 +1,353 @@
|
||||
"""
|
||||
潜在客户导入服务
|
||||
后台任务:分页调用 company API,逐个获取详情并存储到本地数据库
|
||||
"""
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from database import SessionLocal
|
||||
from models import ImportTask, PotentialCustomer, PotentialContact
|
||||
from fumamx_cli import FumamxCLIWrapper
|
||||
|
||||
|
||||
class ImportService:
|
||||
"""导入服务单例类"""
|
||||
_instance = None
|
||||
_lock = threading.Lock()
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._init()
|
||||
return cls._instance
|
||||
|
||||
def _init(self):
|
||||
"""初始化"""
|
||||
self.cli = FumamxCLIWrapper()
|
||||
self.current_task_id: Optional[int] = None
|
||||
self._cancel_event = threading.Event()
|
||||
self._thread: Optional[threading.Thread] = None
|
||||
|
||||
def get_running_task(self) -> Optional[Dict]:
|
||||
"""获取当前运行的任务"""
|
||||
if self.current_task_id is None:
|
||||
return None
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
task = db.query(ImportTask).filter(
|
||||
ImportTask.id == self.current_task_id
|
||||
).first()
|
||||
if task and task.status == "running":
|
||||
return task.to_dict()
|
||||
else:
|
||||
# 任务已完成或不存在
|
||||
self.current_task_id = None
|
||||
return None
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def get_latest_task(self) -> Optional[Dict]:
|
||||
"""获取最后一次任务(无论状态)"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
task = db.query(ImportTask).order_by(
|
||||
ImportTask.created_at.desc()
|
||||
).first()
|
||||
return task.to_dict() if task else None
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def start_import(self, keyword: str, page_size: int = 10) -> Dict:
|
||||
"""启动导入任务"""
|
||||
# 检查是否有运行中的任务
|
||||
if self.current_task_id is not None:
|
||||
running = self.get_running_task()
|
||||
if running:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "已有导入任务正在运行",
|
||||
"task": running
|
||||
}
|
||||
|
||||
# 创建新任务
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 先测试搜索,获取总数量
|
||||
search_result = self.cli.search_company(keyword, page=1, page_size=page_size)
|
||||
if search_result.get("code") != "0":
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"搜索失败: {search_result.get('msg', '未知错误')}"
|
||||
}
|
||||
|
||||
total_count = int(search_result.get("data", {}).get("total", 0))
|
||||
if total_count == 0:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "未找到匹配的企业"
|
||||
}
|
||||
|
||||
total_pages = (total_count + page_size - 1) // page_size
|
||||
|
||||
# 创建任务记录
|
||||
task = ImportTask(
|
||||
keyword=keyword,
|
||||
status="pending",
|
||||
total_pages=total_pages,
|
||||
page_size=page_size,
|
||||
total_count=total_count,
|
||||
processed_count=0,
|
||||
success_count=0,
|
||||
failed_count=0,
|
||||
duplicate_count=0
|
||||
)
|
||||
db.add(task)
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
|
||||
# 启动后台线程
|
||||
self.current_task_id = task.id
|
||||
self._cancel_event.clear()
|
||||
self._thread = threading.Thread(
|
||||
target=self._import_worker,
|
||||
args=(task.id, keyword, total_pages, page_size),
|
||||
daemon=True
|
||||
)
|
||||
self._thread.start()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"task": task.to_dict()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"启动失败: {str(e)}"
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def cancel_import(self) -> Dict:
|
||||
"""取消当前导入任务"""
|
||||
if self.current_task_id is None:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "没有正在运行的任务"
|
||||
}
|
||||
|
||||
self._cancel_event.set()
|
||||
|
||||
# 更新任务状态
|
||||
db = SessionLocal()
|
||||
try:
|
||||
task = db.query(ImportTask).filter(
|
||||
ImportTask.id == self.current_task_id
|
||||
).first()
|
||||
if task:
|
||||
task.status = "cancelled"
|
||||
task.completed_at = datetime.now()
|
||||
db.commit()
|
||||
return {
|
||||
"success": True,
|
||||
"task": task.to_dict()
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
return {"success": True}
|
||||
|
||||
def _import_worker(self, task_id: int, keyword: str, total_pages: int, page_size: int):
|
||||
"""后台导入工作线程"""
|
||||
db = SessionLocal()
|
||||
error_logs = [] # 收集错误日志
|
||||
|
||||
try:
|
||||
# 更新任务状态为运行中
|
||||
task = db.query(ImportTask).filter(ImportTask.id == task_id).first()
|
||||
if not task:
|
||||
return
|
||||
|
||||
task.status = "running"
|
||||
task.started_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
# 逐页处理
|
||||
for page in range(1, total_pages + 1):
|
||||
# 检查取消标志
|
||||
if self._cancel_event.is_set():
|
||||
task.status = "cancelled"
|
||||
task.completed_at = datetime.now()
|
||||
task.error_msg = "\n".join(error_logs[-10:]) if error_logs else None # 保留最后10条错误
|
||||
db.commit()
|
||||
break
|
||||
|
||||
# 更新当前页码
|
||||
task.current_page = page
|
||||
db.commit()
|
||||
|
||||
try:
|
||||
# 1. 搜索当前页企业列表
|
||||
search_result = self.cli.search_company(keyword, page=page, page_size=page_size)
|
||||
if search_result.get("code") != "0":
|
||||
error_msg = f"第{page}页搜索失败: {search_result.get('msg', '未知错误')}"
|
||||
error_logs.append(error_msg)
|
||||
print(error_msg)
|
||||
task.failed_count += page_size
|
||||
task.processed_count += page_size
|
||||
db.commit()
|
||||
continue
|
||||
|
||||
companies = search_result.get("data", {}).get("results", [])
|
||||
|
||||
# 逐个处理企业
|
||||
for company in companies:
|
||||
# 检查取消标志
|
||||
if self._cancel_event.is_set():
|
||||
break
|
||||
|
||||
domain = company.get('domain', 'unknown')
|
||||
try:
|
||||
self._process_company(db, task, company, keyword)
|
||||
task.success_count += 1
|
||||
except Exception as e:
|
||||
task.failed_count += 1
|
||||
error_detail = f"[{domain}] {type(e).__name__}: {str(e)}"
|
||||
error_logs.append(error_detail)
|
||||
print(f"处理企业失败: {error_detail}")
|
||||
# 实时更新错误信息到数据库(最多保留最近10条)
|
||||
task.error_msg = "\n".join(error_logs[-10:])
|
||||
|
||||
task.processed_count += 1
|
||||
db.commit()
|
||||
|
||||
# API 限速:每家企业处理完后等待 4 秒(1秒搜索 + 3个详情API各1秒)
|
||||
time.sleep(4)
|
||||
|
||||
if self._cancel_event.is_set():
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
page_error = f"第{page}页处理失败: {type(e).__name__}: {str(e)}"
|
||||
error_logs.append(page_error)
|
||||
print(page_error)
|
||||
task.failed_count += page_size # 粗略估算
|
||||
task.processed_count += page_size
|
||||
task.error_msg = "\n".join(error_logs[-10:])
|
||||
db.commit()
|
||||
|
||||
# 每页之间额外等待
|
||||
time.sleep(2)
|
||||
|
||||
# 任务完成
|
||||
if task.status == "running":
|
||||
task.status = "completed"
|
||||
task.completed_at = datetime.now()
|
||||
task.error_msg = "\n".join(error_logs[-10:]) if error_logs else None
|
||||
db.commit()
|
||||
|
||||
except Exception as e:
|
||||
task.status = "failed"
|
||||
error_logs.append(f"任务异常: {type(e).__name__}: {str(e)}")
|
||||
task.error_msg = "\n".join(error_logs[-10:])
|
||||
task.completed_at = datetime.now()
|
||||
db.commit()
|
||||
finally:
|
||||
self.current_task_id = None
|
||||
db.close()
|
||||
|
||||
def _process_company(self, db: Session, task: ImportTask, company: Dict, keyword: str):
|
||||
"""处理单个企业"""
|
||||
external_id = company.get("id")
|
||||
domain = company.get("domain")
|
||||
|
||||
if not external_id or not domain:
|
||||
return
|
||||
|
||||
# 检查是否已存在(去重)
|
||||
existing = db.query(PotentialCustomer).filter(
|
||||
PotentialCustomer.domain == domain,
|
||||
PotentialCustomer.external_id == external_id
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
task.duplicate_count += 1
|
||||
return
|
||||
|
||||
# 2. 获取企业详情
|
||||
info_result = self.cli.get_company_info(external_id, domain)
|
||||
info_data = info_result.get("data", {}) if info_result.get("code") == 0 else {}
|
||||
|
||||
# 3. 获取电话/图片信息(可选,失败不影响主流程)
|
||||
try:
|
||||
phone_result = self.cli.get_company_phones(domain)
|
||||
except:
|
||||
phone_result = {}
|
||||
|
||||
# 4. 获取联系人列表
|
||||
contacts_result = self.cli.get_company_contacts(external_id, domain)
|
||||
contacts_data = contacts_result.get("data", {}) if contacts_result.get("code") == 0 else {}
|
||||
contacts_list = contacts_data.get("list") or []
|
||||
|
||||
# 创建企业记录
|
||||
customer = PotentialCustomer(
|
||||
external_id=external_id,
|
||||
domain=domain,
|
||||
name=company.get("title") or info_data.get("name"),
|
||||
country=company.get("country") or info_data.get("country"),
|
||||
country_en=company.get("countryEn") or info_data.get("countryEn"),
|
||||
country_cn=company.get("countryCnName") or info_data.get("countryCn"),
|
||||
industry=info_data.get("industry"),
|
||||
employees=info_data.get("employees"),
|
||||
founded=info_data.get("founded"),
|
||||
address=info_data.get("address"),
|
||||
phone=info_data.get("phone"),
|
||||
description=company.get("description") or info_data.get("description"),
|
||||
website=company.get("website"),
|
||||
linkedin_url=info_data.get("linkedin_url"),
|
||||
procurement_frequency=company.get("procurementFrequency"),
|
||||
annual_purchase_amount=company.get("annualPurchaseAmount"),
|
||||
hs_codes=company.get("hsCode") or [],
|
||||
hs_entities=company.get("hsEntity") or [],
|
||||
keyword=keyword,
|
||||
source_score=company.get("score"),
|
||||
source_db=company.get("sourceDb"),
|
||||
import_task_id=task.id,
|
||||
contact_count=len(contacts_list)
|
||||
)
|
||||
|
||||
db.add(customer)
|
||||
db.flush() # 获取 customer.id
|
||||
|
||||
# 创建联系人记录
|
||||
for contact in contacts_list:
|
||||
pc = PotentialContact(
|
||||
customer_id=customer.id,
|
||||
name=contact.get("name"),
|
||||
position=contact.get("position"),
|
||||
email=contact.get("email"),
|
||||
phone=contact.get("phone") or contact.get("realWhatsApp"),
|
||||
linkedin=contact.get("linkedin"),
|
||||
facebook=contact.get("facebook"),
|
||||
twitter=contact.get("twitter"),
|
||||
whatsapp=contact.get("whatsapp"),
|
||||
score=contact.get("score"),
|
||||
is_executive=bool(contact.get("isExecutive")),
|
||||
is_kp=bool(contact.get("isKp")),
|
||||
is_purchase=bool(contact.get("isPurchase")),
|
||||
email_check_status=contact.get("emailCheckStatus"),
|
||||
whatsapp_status=contact.get("whatsAppStatus")
|
||||
)
|
||||
db.add(pc)
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
# 全局导入服务实例
|
||||
import_service = ImportService()
|
||||
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
初始化国家数据
|
||||
"""
|
||||
import json
|
||||
from database import SessionLocal, engine, Base
|
||||
from models import Country
|
||||
|
||||
|
||||
def init_countries():
|
||||
# 读取国家数据
|
||||
with open('/home/ubuntu/nanocode/fumamxCRM/countryinfo.json', 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
countries_data = data.get('data', [])
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 清空现有数据
|
||||
db.query(Country).delete()
|
||||
|
||||
# 插入新数据
|
||||
for item in countries_data:
|
||||
country = Country(
|
||||
country_id=item.get('countryid'),
|
||||
continent_id=item.get('continentid'),
|
||||
sort_order=item.get('sortorder', 0),
|
||||
cn_name=item.get('cnname'),
|
||||
en_name=item.get('enname'),
|
||||
en_name_ali=item.get('ennameAli'),
|
||||
short_name=item.get('shortname'),
|
||||
short_name_ali=item.get('shortnameAli'),
|
||||
pinyin=item.get('pinyin'),
|
||||
del_state=item.get('delstate', 0)
|
||||
)
|
||||
db.add(country)
|
||||
|
||||
db.commit()
|
||||
print(f"✅ 成功导入 {len(countries_data)} 个国家/地区")
|
||||
|
||||
# 显示前10个
|
||||
print("\n前10个国家示例:")
|
||||
for c in db.query(Country).limit(10).all():
|
||||
print(f" {c.cn_name} ({c.short_name}) - {c.en_name}")
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"❌ 导入失败: {e}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 创建表
|
||||
Base.metadata.create_all(bind=engine)
|
||||
print("📦 国家表创建完成")
|
||||
|
||||
# 导入数据
|
||||
init_countries()
|
||||
@@ -0,0 +1,303 @@
|
||||
"""
|
||||
数据库模型
|
||||
"""
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Text, JSON, Float, Boolean, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy.sql import func
|
||||
from database import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""管理员用户"""
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
username = Column(String, unique=True, index=True)
|
||||
hashed_password = Column(String)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
|
||||
class Customer(Base):
|
||||
"""本地客户"""
|
||||
__tablename__ = "customers"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String, index=True) # 公司名称
|
||||
country = Column(String) # 国家
|
||||
source_country = Column(String) # 数据来源国家
|
||||
import_data = Column(JSON, default=dict) # 进口数据
|
||||
export_data = Column(JSON, default=dict) # 出口数据
|
||||
raw_data = Column(JSON, default=dict) # 原始数据
|
||||
notes = Column(Text) # 备注
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
|
||||
class Country(Base):
|
||||
"""国家信息表"""
|
||||
__tablename__ = "countries"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
country_id = Column(Integer, unique=True, index=True) # API中的countryid
|
||||
continent_id = Column(Integer, index=True) # 大洲ID
|
||||
sort_order = Column(Integer, default=0) # 排序
|
||||
cn_name = Column(String(100), index=True) # 中文名
|
||||
en_name = Column(String(100)) # 英文名
|
||||
en_name_ali = Column(String(100)) # 英文别名
|
||||
short_name = Column(String(10)) # 简称(如CN)
|
||||
short_name_ali = Column(String(10)) # 简称别名
|
||||
pinyin = Column(String(10)) # 拼音首字母
|
||||
del_state = Column(Integer, default=0) # 删除状态 0=正常 1=删除
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"country_id": self.country_id,
|
||||
"continent_id": self.continent_id,
|
||||
"cn_name": self.cn_name,
|
||||
"en_name": self.en_name,
|
||||
"short_name": self.short_name,
|
||||
"pinyin": self.pinyin,
|
||||
}
|
||||
|
||||
|
||||
class ImportTask(Base):
|
||||
"""潜在客户导入任务"""
|
||||
__tablename__ = "import_tasks"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
keyword = Column(String(200), index=True) # 搜索关键词
|
||||
status = Column(String(20), default="pending", index=True) # pending/running/completed/failed/cancelled
|
||||
|
||||
# 分页信息
|
||||
total_pages = Column(Integer, default=0) # 总页数
|
||||
current_page = Column(Integer, default=0) # 当前处理页码
|
||||
page_size = Column(Integer, default=10) # 每页数量
|
||||
|
||||
# 统计信息
|
||||
total_count = Column(Integer, default=0) # 预计总企业数
|
||||
processed_count = Column(Integer, default=0) # 已处理企业数
|
||||
success_count = Column(Integer, default=0) # 成功导入企业数
|
||||
failed_count = Column(Integer, default=0) # 失败企业数
|
||||
duplicate_count = Column(Integer, default=0) # 重复跳过数
|
||||
|
||||
# 错误信息
|
||||
error_msg = Column(Text) # 错误信息
|
||||
|
||||
# 时间
|
||||
started_at = Column(DateTime(timezone=True)) # 开始时间
|
||||
completed_at = Column(DateTime(timezone=True)) # 完成时间
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"keyword": self.keyword,
|
||||
"status": self.status,
|
||||
"total_pages": self.total_pages,
|
||||
"current_page": self.current_page,
|
||||
"page_size": self.page_size,
|
||||
"total_count": self.total_count,
|
||||
"processed_count": self.processed_count,
|
||||
"success_count": self.success_count,
|
||||
"failed_count": self.failed_count,
|
||||
"duplicate_count": self.duplicate_count,
|
||||
"error_msg": self.error_msg,
|
||||
"started_at": self.started_at.isoformat() if self.started_at else None,
|
||||
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"progress_percent": round((self.processed_count / self.total_count * 100), 1) if self.total_count > 0 else 0,
|
||||
}
|
||||
|
||||
|
||||
class PotentialCustomer(Base):
|
||||
"""潜在客户企业"""
|
||||
__tablename__ = "potential_customers"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
external_id = Column(String(64), index=True) # fumamx API 返回的 id
|
||||
domain = Column(String(200), index=True) # 域名
|
||||
name = Column(String(500)) # 公司名称
|
||||
country = Column(String(10)) # 国家代码
|
||||
country_en = Column(String(100)) # 英文国家名
|
||||
country_cn = Column(String(100)) # 中文国家名
|
||||
|
||||
# 企业信息
|
||||
industry = Column(String(200)) # 行业
|
||||
employees = Column(String(50)) # 员工数
|
||||
founded = Column(String(20)) # 成立时间
|
||||
address = Column(String(500)) # 地址
|
||||
phone = Column(String(100)) # 主电话
|
||||
description = Column(Text) # 描述
|
||||
|
||||
# 在线资料
|
||||
website = Column(String(300)) # 网站
|
||||
linkedin_url = Column(String(500)) # LinkedIn
|
||||
|
||||
# 贸易数据
|
||||
procurement_frequency = Column(Integer, default=0) # 采购频次
|
||||
annual_purchase_amount = Column(Float, default=0) # 年采购额
|
||||
hs_codes = Column(JSON, default=list) # HS编码列表
|
||||
hs_entities = Column(JSON, default=list) # HS商品名列表
|
||||
|
||||
# 来源信息
|
||||
keyword = Column(String(200), index=True) # 搜索关键词
|
||||
source_score = Column(Float, default=0) # 匹配分数
|
||||
source_db = Column(Integer) # 数据源
|
||||
import_task_id = Column(Integer, ForeignKey("import_tasks.id")) # 导入任务ID
|
||||
|
||||
# 关联的联系人数量(冗余统计)
|
||||
contact_count = Column(Integer, default=0)
|
||||
|
||||
# 深度分析状态(冗余字段,避免级联查询)
|
||||
analysis_status = Column(String(20), default="none", index=True) # none/pending/running/completed/failed
|
||||
analysis_count = Column(Integer, default=0) # 分析次数
|
||||
last_analyzed_at = Column(DateTime(timezone=True)) # 最后分析时间
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# 联合唯一索引:domain + external_id
|
||||
__table_args__ = (
|
||||
UniqueConstraint('domain', 'external_id', name='uix_customer_domain_external'),
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"external_id": self.external_id,
|
||||
"domain": self.domain,
|
||||
"name": self.name,
|
||||
"country": self.country,
|
||||
"country_en": self.country_en,
|
||||
"country_cn": self.country_cn,
|
||||
"industry": self.industry,
|
||||
"employees": self.employees,
|
||||
"address": self.address,
|
||||
"phone": self.phone,
|
||||
"description": self.description,
|
||||
"website": self.website,
|
||||
"linkedin_url": self.linkedin_url,
|
||||
"procurement_frequency": self.procurement_frequency,
|
||||
"annual_purchase_amount": self.annual_purchase_amount,
|
||||
"hs_codes": self.hs_codes,
|
||||
"hs_entities": self.hs_entities,
|
||||
"keyword": self.keyword,
|
||||
"source_score": self.source_score,
|
||||
"contact_count": self.contact_count,
|
||||
"analysis_status": self.analysis_status or "none", # 深度分析状态(反范式设计)
|
||||
"analysis_count": self.analysis_count or 0,
|
||||
"last_analyzed_at": self.last_analyzed_at.isoformat() if self.last_analyzed_at else None,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
}
|
||||
|
||||
|
||||
class PotentialContact(Base):
|
||||
"""潜在客户联系人"""
|
||||
__tablename__ = "potential_contacts"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
customer_id = Column(Integer, ForeignKey("potential_customers.id"), index=True)
|
||||
|
||||
# 联系人信息
|
||||
name = Column(String(200)) # 姓名
|
||||
position = Column(String(300)) # 职位
|
||||
email = Column(String(300), index=True) # 邮箱
|
||||
phone = Column(String(100)) # 电话
|
||||
|
||||
# 社交资料
|
||||
linkedin = Column(String(500)) # LinkedIn URL
|
||||
facebook = Column(String(500)) # Facebook
|
||||
twitter = Column(String(500)) # Twitter
|
||||
whatsapp = Column(String(100)) # WhatsApp
|
||||
|
||||
# 评分标签
|
||||
score = Column(Integer, default=0) # 匹配分数
|
||||
is_executive = Column(Boolean, default=False) # 是否高管
|
||||
is_kp = Column(Boolean, default=False) # 是否关键人
|
||||
is_purchase = Column(Boolean, default=False) # 是否采购负责人
|
||||
|
||||
# 验证状态
|
||||
email_check_status = Column(String(50)) # 邮箱验证状态
|
||||
whatsapp_status = Column(String(50)) # WhatsApp状态
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"customer_id": self.customer_id,
|
||||
"name": self.name,
|
||||
"position": self.position,
|
||||
"email": self.email,
|
||||
"phone": self.phone,
|
||||
"linkedin": self.linkedin,
|
||||
"score": self.score,
|
||||
"is_executive": self.is_executive,
|
||||
"is_kp": self.is_kp,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
}
|
||||
|
||||
|
||||
class DeepAnalysis(Base):
|
||||
"""深度分析记录"""
|
||||
__tablename__ = "deep_analyses"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
customer_id = Column(Integer, ForeignKey("potential_customers.id"), index=True)
|
||||
company_name = Column(String(500)) # 企业名称(冗余)
|
||||
|
||||
# 分析状态
|
||||
status = Column(String(20), default="pending", index=True) # pending/processing/completed/failed
|
||||
|
||||
# 报告文件
|
||||
report_file_path = Column(String(500)) # 报告文件路径
|
||||
|
||||
# 使用的模型
|
||||
model_used = Column(String(50))
|
||||
|
||||
# 商业画布9个维度的摘要(可选,用于快速预览)
|
||||
canvas_summary = Column(JSON, default=dict)
|
||||
|
||||
# 错误信息
|
||||
error_message = Column(Text)
|
||||
|
||||
# 时间
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
completed_at = Column(DateTime(timezone=True))
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"customer_id": self.customer_id,
|
||||
"company_name": self.company_name,
|
||||
"status": self.status,
|
||||
"report_file_path": self.report_file_path,
|
||||
"model_used": self.model_used,
|
||||
"canvas_summary": self.canvas_summary,
|
||||
"error_message": self.error_message,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
|
||||
}
|
||||
|
||||
|
||||
class SystemSetting(Base):
|
||||
"""系统设置"""
|
||||
__tablename__ = "system_settings"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
key = Column(String(100), unique=True, index=True) # 设置项key
|
||||
value = Column(Text) # 设置项value(加密存储敏感信息)
|
||||
description = Column(String(500)) # 描述
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"key": self.key,
|
||||
"value": self.value,
|
||||
"description": self.description,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fastapi==0.110.0
|
||||
uvicorn[standard]==0.27.0
|
||||
python-jose[cryptography]==3.3.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
sqlalchemy==2.0.27
|
||||
pydantic==2.6.1
|
||||
python-multipart==0.0.9
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
认证路由
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import timedelta
|
||||
|
||||
from database import get_db
|
||||
from models import User
|
||||
from schemas import Token, UserLogin, UserChangePassword
|
||||
from auth import (
|
||||
verify_password,
|
||||
get_password_hash,
|
||||
create_access_token,
|
||||
get_current_user
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["认证"])
|
||||
security = HTTPBearer()
|
||||
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
def login(credentials: UserLogin, db: Session = Depends(get_db)):
|
||||
"""用户登录"""
|
||||
user = db.query(User).filter(User.username == credentials.username).first()
|
||||
|
||||
if not user or not verify_password(credentials.password, user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="用户名或密码错误",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
access_token = create_access_token(data={"sub": user.username})
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
|
||||
@router.post("/change-password")
|
||||
def change_password(
|
||||
data: UserChangePassword,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""修改密码"""
|
||||
# 验证旧密码
|
||||
if not verify_password(data.old_password, current_user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="旧密码错误"
|
||||
)
|
||||
|
||||
# 更新密码
|
||||
current_user.hashed_password = get_password_hash(data.new_password)
|
||||
db.commit()
|
||||
|
||||
return {"message": "密码修改成功"}
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
def get_me(current_user: User = Depends(get_current_user)):
|
||||
"""获取当前用户信息"""
|
||||
return {
|
||||
"username": current_user.username,
|
||||
"created_at": current_user.created_at
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
客户管理路由
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from database import get_db
|
||||
from models import User, Customer
|
||||
from schemas import CustomerCreate, CustomerResponse
|
||||
from auth import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/customers", tags=["客户管理"])
|
||||
|
||||
|
||||
@router.get("", response_model=List[CustomerResponse])
|
||||
def list_customers(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""获取客户列表"""
|
||||
customers = db.query(Customer).offset(skip).limit(limit).all()
|
||||
return customers
|
||||
|
||||
|
||||
@router.get("/{customer_id}", response_model=CustomerResponse)
|
||||
def get_customer(
|
||||
customer_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""获取客户详情"""
|
||||
customer = db.query(Customer).filter(Customer.id == customer_id).first()
|
||||
if not customer:
|
||||
raise HTTPException(status_code=404, detail="客户不存在")
|
||||
return customer
|
||||
|
||||
|
||||
@router.post("", response_model=CustomerResponse)
|
||||
def create_customer(
|
||||
data: CustomerCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""添加客户"""
|
||||
# 检查是否已存在
|
||||
existing = db.query(Customer).filter(Customer.name == data.name).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="客户已存在")
|
||||
|
||||
customer = Customer(**data.dict())
|
||||
db.add(customer)
|
||||
db.commit()
|
||||
db.refresh(customer)
|
||||
return customer
|
||||
|
||||
|
||||
@router.put("/{customer_id}", response_model=CustomerResponse)
|
||||
def update_customer(
|
||||
customer_id: int,
|
||||
data: CustomerCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""更新客户"""
|
||||
customer = db.query(Customer).filter(Customer.id == customer_id).first()
|
||||
if not customer:
|
||||
raise HTTPException(status_code=404, detail="客户不存在")
|
||||
|
||||
for key, value in data.dict().items():
|
||||
setattr(customer, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(customer)
|
||||
return customer
|
||||
|
||||
|
||||
@router.delete("/{customer_id}")
|
||||
def delete_customer(
|
||||
customer_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""删除客户"""
|
||||
customer = db.query(Customer).filter(Customer.id == customer_id).first()
|
||||
if not customer:
|
||||
raise HTTPException(status_code=404, detail="客户不存在")
|
||||
|
||||
db.delete(customer)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
@@ -0,0 +1,657 @@
|
||||
"""
|
||||
深度分析 API 路由
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import desc
|
||||
|
||||
from database import get_db, SessionLocal
|
||||
from models import DeepAnalysis, PotentialCustomer, SystemSetting
|
||||
from auth import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/deep-analysis", tags=["deep-analysis"])
|
||||
|
||||
# 报告存储目录
|
||||
REPORTS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "analysis_reports")
|
||||
os.makedirs(REPORTS_DIR, exist_ok=True)
|
||||
|
||||
# 后台任务锁和队列
|
||||
task_lock = threading.Lock()
|
||||
analysis_queue: List[dict] = []
|
||||
current_task: Optional[dict] = None
|
||||
stop_flag = False
|
||||
|
||||
|
||||
def restore_pending_tasks():
|
||||
"""服务启动时从数据库恢复等待中的任务"""
|
||||
global analysis_queue
|
||||
try:
|
||||
db = SessionLocal()
|
||||
pending_analyses = db.query(DeepAnalysis).filter(
|
||||
DeepAnalysis.status == "pending"
|
||||
).order_by(DeepAnalysis.id.asc()).all()
|
||||
|
||||
for analysis in pending_analyses:
|
||||
analysis_queue.append({
|
||||
"analysis_id": analysis.id,
|
||||
"customer_id": analysis.customer_id,
|
||||
})
|
||||
|
||||
print(f"[DeepAnalysis] 恢复 {len(analysis_queue)} 个等待中的任务到队列")
|
||||
db.close()
|
||||
except Exception as e:
|
||||
print(f"[DeepAnalysis] 恢复任务失败: {e}")
|
||||
|
||||
|
||||
# 启动时恢复队列
|
||||
restore_pending_tasks()
|
||||
|
||||
|
||||
# ============ Pydantic Models ============
|
||||
|
||||
class CreateAnalysisRequest(BaseModel):
|
||||
customer_ids: List[int]
|
||||
|
||||
|
||||
class AnalysisResponse(BaseModel):
|
||||
id: int
|
||||
customer_id: int
|
||||
company_name: str
|
||||
status: str
|
||||
model_used: Optional[str]
|
||||
created_at: str
|
||||
completed_at: Optional[str]
|
||||
|
||||
|
||||
class AnalysisDetailResponse(AnalysisResponse):
|
||||
report_content: Optional[str]
|
||||
error_message: Optional[str]
|
||||
|
||||
|
||||
class ModelConfigRequest(BaseModel):
|
||||
model: str # "kimi-k2.5" | "deepseek-chat"
|
||||
api_key: str
|
||||
|
||||
|
||||
class ModelConfigResponse(BaseModel):
|
||||
model: str
|
||||
api_key_masked: str # 脱敏显示
|
||||
|
||||
|
||||
# ============ Helper Functions ============
|
||||
|
||||
def get_model_config(db: Session) -> dict:
|
||||
"""获取模型配置"""
|
||||
model = db.query(SystemSetting).filter(SystemSetting.key == "llm_model").first()
|
||||
api_key = db.query(SystemSetting).filter(SystemSetting.key == "llm_api_key").first()
|
||||
return {
|
||||
"model": model.value if model else "deepseek-chat",
|
||||
"api_key": api_key.value if api_key else "",
|
||||
}
|
||||
|
||||
|
||||
def mask_api_key(key: str) -> str:
|
||||
"""脱敏显示API Key"""
|
||||
if len(key) <= 8:
|
||||
return "****"
|
||||
return key[:4] + "****" + key[-4:]
|
||||
|
||||
|
||||
def generate_report_filename(customer_id: int, company_name: str) -> str:
|
||||
"""生成报告文件名"""
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
safe_name = "".join(c for c in company_name if c.isalnum() or c in ('-', '_'))[:50]
|
||||
return f"{customer_id}_{safe_name}_{timestamp}.md"
|
||||
|
||||
|
||||
def build_analysis_prompt(customer: PotentialCustomer) -> str:
|
||||
"""构建分析Prompt"""
|
||||
# 整理企业信息
|
||||
info_parts = []
|
||||
if customer.name:
|
||||
info_parts.append(f"公司名称: {customer.name}")
|
||||
if customer.industry:
|
||||
info_parts.append(f"行业: {customer.industry}")
|
||||
if customer.country_cn:
|
||||
info_parts.append(f"国家/地区: {customer.country_cn}")
|
||||
if customer.employees:
|
||||
info_parts.append(f"员工规模: {customer.employees}")
|
||||
if customer.founded:
|
||||
info_parts.append(f"成立时间: {customer.founded}")
|
||||
if customer.address:
|
||||
info_parts.append(f"地址: {customer.address}")
|
||||
if customer.description:
|
||||
info_parts.append(f"企业描述: {customer.description}")
|
||||
if customer.website:
|
||||
info_parts.append(f"官网: {customer.website}")
|
||||
if customer.hs_entities:
|
||||
products = ", ".join(customer.hs_entities[:10])
|
||||
info_parts.append(f"主营产品: {products}")
|
||||
if customer.annual_purchase_amount:
|
||||
info_parts.append(f"年采购额: {customer.annual_purchase_amount}")
|
||||
if customer.procurement_frequency:
|
||||
info_parts.append(f"采购频次: {customer.procurement_frequency}")
|
||||
|
||||
company_info = "\n".join(info_parts) if info_parts else "暂无详细信息"
|
||||
|
||||
prompt = f"""请基于以下企业信息,使用商业画布(Business Model Canvas)的9个维度进行深度分析,评估该企业的潜在商业价值。
|
||||
|
||||
## 企业基本信息
|
||||
{company_info}
|
||||
|
||||
## 分析要求
|
||||
请从以下9个维度进行分析,每个维度需要提供:
|
||||
1. 该维度的核心内容分析
|
||||
2. 该企业的优势/特点
|
||||
3. 潜在风险或关注点
|
||||
4. 对该企业的评分(1-10分)
|
||||
|
||||
## 商业画布9个维度
|
||||
|
||||
### 1. 客户细分 (Customer Segments)
|
||||
分析该企业面向的主要客户群体是谁?B2B还是B2C?客户特征如何?
|
||||
|
||||
### 2. 价值主张 (Value Propositions)
|
||||
该企业为客户提供什么独特价值?解决什么痛点?
|
||||
|
||||
### 3. 渠道通路 (Channels)
|
||||
通过什么渠道触达客户?销售渠道、营销渠道有哪些?
|
||||
|
||||
### 4. 客户关系 (Customer Relationships)
|
||||
如何维护客户关系?是长期合作还是一次性交易?
|
||||
|
||||
### 5. 收入来源 (Revenue Streams)
|
||||
主要收入来源是什么?盈利模式如何?
|
||||
|
||||
### 6. 核心资源 (Key Resources)
|
||||
企业的核心资产是什么?技术、品牌、渠道还是人才?
|
||||
|
||||
### 7. 关键业务 (Key Activities)
|
||||
企业最重要的业务活动是什么?生产、研发还是服务?
|
||||
|
||||
### 8. 重要合作 (Key Partnerships)
|
||||
重要的供应商、合作伙伴有哪些?供应链稳定性如何?
|
||||
|
||||
### 9. 成本结构 (Cost Structure)
|
||||
主要成本构成是什么?固定成本还是变动成本为主?
|
||||
|
||||
## 输出格式
|
||||
请用Markdown格式输出,每个维度使用二级标题,包含上述4个方面的内容。最后请给出:
|
||||
- 总体评分(1-100分)
|
||||
- 合作建议(是否推荐作为潜在客户深度合作)
|
||||
- 关键关注要点(3-5条)
|
||||
"""
|
||||
return prompt
|
||||
|
||||
|
||||
def call_llm_api(prompt: str, model: str, api_key: str) -> str:
|
||||
"""调用大模型API"""
|
||||
try:
|
||||
import openai
|
||||
|
||||
if model.startswith("kimi"):
|
||||
base_url = "https://api.moonshot.cn/v1"
|
||||
else:
|
||||
base_url = "https://api.deepseek.com"
|
||||
|
||||
client = openai.OpenAI(api_key=api_key, base_url=base_url)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=model,
|
||||
messages=[
|
||||
{"role": "system", "content": "你是一位专业的商业分析顾问,擅长基于商业画布模型进行企业价值评估。请用中文输出专业的分析报告。"},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=8000,
|
||||
)
|
||||
|
||||
return response.choices[0].message.content
|
||||
except Exception as e:
|
||||
raise Exception(f"LLM API调用失败: {str(e)}")
|
||||
|
||||
|
||||
def extract_summary(report_content: str) -> dict:
|
||||
"""从报告中提取摘要信息"""
|
||||
summary = {
|
||||
"overall_score": None,
|
||||
"recommendation": None,
|
||||
"key_points": [],
|
||||
}
|
||||
|
||||
lines = report_content.split("\n")
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
# 提取总体评分
|
||||
if "总体评分" in line or "总分" in line:
|
||||
import re
|
||||
scores = re.findall(r'(\d{2,3})\s*分', line)
|
||||
if scores:
|
||||
summary["overall_score"] = int(scores[0])
|
||||
# 提取合作建议
|
||||
if "合作建议" in line and ":" in line:
|
||||
summary["recommendation"] = line.split(":", 1)[1].strip()
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
# ============ Background Task Worker ============
|
||||
|
||||
def analysis_worker():
|
||||
"""后台分析任务工作线程"""
|
||||
global current_task, stop_flag
|
||||
|
||||
from database import SessionLocal
|
||||
|
||||
print("🔄 深度分析工作线程已启动")
|
||||
|
||||
while not stop_flag:
|
||||
task = None
|
||||
with task_lock:
|
||||
if analysis_queue:
|
||||
task = analysis_queue.pop(0)
|
||||
|
||||
if not task:
|
||||
current_task = None
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
analysis_id = task["analysis_id"]
|
||||
current_task = task
|
||||
db = SessionLocal()
|
||||
customer = None
|
||||
|
||||
try:
|
||||
# 获取分析记录
|
||||
analysis = db.query(DeepAnalysis).filter(DeepAnalysis.id == analysis_id).first()
|
||||
if not analysis:
|
||||
db.close()
|
||||
continue
|
||||
|
||||
# 获取客户信息(必须先获取,才能更新状态)
|
||||
customer = db.query(PotentialCustomer).filter(
|
||||
PotentialCustomer.id == analysis.customer_id
|
||||
).first()
|
||||
|
||||
if not customer:
|
||||
analysis.status = "failed"
|
||||
analysis.error_message = "客户信息不存在"
|
||||
db.commit()
|
||||
db.close()
|
||||
continue
|
||||
|
||||
# 更新状态为处理中
|
||||
analysis.status = "processing"
|
||||
|
||||
# 同步更新客户表状态
|
||||
customer.analysis_status = "processing"
|
||||
customer.last_analyzed_at = datetime.now()
|
||||
|
||||
db.commit()
|
||||
|
||||
# 获取模型配置
|
||||
config = get_model_config(db)
|
||||
if not config["api_key"]:
|
||||
analysis.status = "failed"
|
||||
analysis.error_message = "未配置API Key"
|
||||
db.commit()
|
||||
db.close()
|
||||
print(f"⚠️ 分析任务 {analysis_id} 失败: 未配置API Key")
|
||||
continue
|
||||
|
||||
print(f"🤖 开始分析客户: {customer.name} (ID: {customer.id})")
|
||||
|
||||
# 构建Prompt并调用API
|
||||
prompt = build_analysis_prompt(customer)
|
||||
report_content = call_llm_api(prompt, config["model"], config["api_key"])
|
||||
|
||||
# 保存报告文件
|
||||
filename = generate_report_filename(customer.id, customer.name or "未知企业")
|
||||
filepath = os.path.join(REPORTS_DIR, filename)
|
||||
with open(filepath, "w", encoding="utf-8") as f:
|
||||
f.write(report_content)
|
||||
|
||||
# 提取摘要
|
||||
canvas_summary = extract_summary(report_content)
|
||||
|
||||
# 更新分析记录
|
||||
analysis.status = "completed"
|
||||
analysis.report_file_path = filepath
|
||||
analysis.model_used = config["model"]
|
||||
analysis.canvas_summary = canvas_summary
|
||||
analysis.completed_at = datetime.now()
|
||||
|
||||
# 同步更新客户表的分析状态(反范式设计,避免级联查询)
|
||||
customer.analysis_status = "completed"
|
||||
customer.analysis_count = (customer.analysis_count or 0) + 1
|
||||
customer.last_analyzed_at = datetime.now()
|
||||
|
||||
db.commit()
|
||||
print(f"✅ 客户分析完成: {customer.name}")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
print(f"❌ 分析任务 {analysis_id} 失败: {error_msg}")
|
||||
try:
|
||||
analysis.status = "failed"
|
||||
analysis.error_message = error_msg[:500] # 限制错误信息长度
|
||||
|
||||
# 同步更新客户表状态为失败
|
||||
if customer:
|
||||
customer.analysis_status = "failed"
|
||||
customer.analysis_count = (customer.analysis_count or 0) + 1
|
||||
customer.last_analyzed_at = datetime.now()
|
||||
|
||||
db.commit()
|
||||
except Exception as commit_error:
|
||||
print(f"❌ 保存失败状态时出错: {commit_error}")
|
||||
finally:
|
||||
try:
|
||||
db.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# 启动后台线程
|
||||
worker_thread = threading.Thread(target=analysis_worker, daemon=True)
|
||||
worker_thread.start()
|
||||
|
||||
|
||||
# ============ API Routes ============
|
||||
|
||||
@router.post("/create")
|
||||
def create_analysis(
|
||||
request: CreateAnalysisRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
"""创建深度分析任务"""
|
||||
created_ids = []
|
||||
|
||||
for customer_id in request.customer_ids:
|
||||
# 检查客户是否存在
|
||||
customer = db.query(PotentialCustomer).filter(
|
||||
PotentialCustomer.id == customer_id
|
||||
).first()
|
||||
|
||||
if not customer:
|
||||
continue
|
||||
|
||||
with task_lock:
|
||||
# 检查是否已在队列中或正在处理(去重)
|
||||
already_queued = any(
|
||||
t["customer_id"] == customer_id
|
||||
for t in analysis_queue
|
||||
)
|
||||
already_processing = (
|
||||
current_task and
|
||||
current_task.get("customer_id") == customer_id
|
||||
)
|
||||
|
||||
# 检查是否有未完成的分析记录
|
||||
existing_pending = db.query(DeepAnalysis).filter(
|
||||
DeepAnalysis.customer_id == customer_id,
|
||||
DeepAnalysis.status.in_(["pending", "processing"])
|
||||
).first()
|
||||
|
||||
if already_queued or already_processing or existing_pending:
|
||||
continue # 跳过已在处理中的
|
||||
|
||||
# 创建新的分析记录(允许已有completed记录的再次分析)
|
||||
analysis = DeepAnalysis(
|
||||
customer_id=customer_id,
|
||||
company_name=customer.name or "未知企业",
|
||||
status="pending",
|
||||
)
|
||||
db.add(analysis)
|
||||
db.commit()
|
||||
db.refresh(analysis)
|
||||
|
||||
# 加入队列
|
||||
analysis_queue.append({
|
||||
"analysis_id": analysis.id,
|
||||
"customer_id": customer_id,
|
||||
})
|
||||
created_ids.append(analysis.id)
|
||||
|
||||
# 同步更新客户表状态为 pending(反范式设计)
|
||||
customer.analysis_status = "pending"
|
||||
customer.last_analyzed_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"created_count": len(created_ids),
|
||||
"analysis_ids": created_ids,
|
||||
"queued_count": len(analysis_queue),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/list")
|
||||
def list_analyses(
|
||||
status: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
"""获取深度分析列表(按创建时间倒序)"""
|
||||
query = db.query(DeepAnalysis)
|
||||
|
||||
if status:
|
||||
query = query.filter(DeepAnalysis.status == status)
|
||||
|
||||
total = query.count()
|
||||
analyses = query.order_by(desc(DeepAnalysis.created_at)).offset(
|
||||
(page - 1) * page_size
|
||||
).limit(page_size).all()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"items": [a.to_dict() for a in analyses],
|
||||
}
|
||||
|
||||
|
||||
@router.get("/detail/{analysis_id}")
|
||||
def get_analysis_detail(
|
||||
analysis_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
"""获取分析详情(包含报告内容)"""
|
||||
analysis = db.query(DeepAnalysis).filter(DeepAnalysis.id == analysis_id).first()
|
||||
|
||||
if not analysis:
|
||||
raise HTTPException(status_code=404, detail="分析记录不存在")
|
||||
|
||||
result = analysis.to_dict()
|
||||
|
||||
# 读取报告内容
|
||||
if analysis.report_file_path and os.path.exists(analysis.report_file_path):
|
||||
try:
|
||||
with open(analysis.report_file_path, "r", encoding="utf-8") as f:
|
||||
result["report_content"] = f.read()
|
||||
except Exception as e:
|
||||
result["report_content"] = f"读取报告失败: {str(e)}"
|
||||
else:
|
||||
result["report_content"] = None
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/status/{customer_id}")
|
||||
def get_customer_analysis_status(
|
||||
customer_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
"""获取某个客户的分析状态(用于显示按钮状态)"""
|
||||
# 查找最新的分析记录
|
||||
analysis = db.query(DeepAnalysis).filter(
|
||||
DeepAnalysis.customer_id == customer_id
|
||||
).order_by(desc(DeepAnalysis.created_at)).first()
|
||||
|
||||
if not analysis:
|
||||
return {"has_analysis": False, "status": None}
|
||||
|
||||
# 检查是否在队列中
|
||||
with task_lock:
|
||||
in_queue = any(
|
||||
t["customer_id"] == customer_id
|
||||
for t in analysis_queue
|
||||
)
|
||||
is_processing = (
|
||||
current_task and
|
||||
current_task.get("customer_id") == customer_id
|
||||
)
|
||||
|
||||
return {
|
||||
"has_analysis": analysis.status == "completed",
|
||||
"status": analysis.status,
|
||||
"in_queue": in_queue or is_processing,
|
||||
"analysis_id": analysis.id,
|
||||
"completed_at": analysis.completed_at.isoformat() if analysis.completed_at else None,
|
||||
}
|
||||
|
||||
|
||||
@router.post("/batch-status")
|
||||
def get_batch_analysis_status(
|
||||
request: dict,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
"""批量获取多个客户的分析状态(解决N+1查询问题)"""
|
||||
customer_ids = request.get("customer_ids", [])
|
||||
if not customer_ids:
|
||||
return {}
|
||||
|
||||
# 一次性查询所有相关分析记录
|
||||
analyses = db.query(DeepAnalysis).filter(
|
||||
DeepAnalysis.customer_id.in_(customer_ids)
|
||||
).order_by(desc(DeepAnalysis.created_at)).all()
|
||||
|
||||
# 按 customer_id 分组,取最新的一条
|
||||
latest_analysis = {}
|
||||
for a in analyses:
|
||||
if a.customer_id not in latest_analysis:
|
||||
latest_analysis[a.customer_id] = a
|
||||
|
||||
# 获取队列状态
|
||||
with task_lock:
|
||||
queue_customer_ids = {t["customer_id"] for t in analysis_queue}
|
||||
processing_customer_id = current_task.get("customer_id") if current_task else None
|
||||
|
||||
# 构建结果(使用整数key,与前端customer.id类型一致)
|
||||
result = {}
|
||||
for cid in customer_ids:
|
||||
analysis = latest_analysis.get(cid)
|
||||
if not analysis:
|
||||
result[cid] = {"has_analysis": False, "status": None}
|
||||
else:
|
||||
in_queue = cid in queue_customer_ids or cid == processing_customer_id
|
||||
result[cid] = {
|
||||
"has_analysis": analysis.status == "completed",
|
||||
"status": analysis.status,
|
||||
"in_queue": in_queue,
|
||||
"analysis_id": analysis.id,
|
||||
"completed_at": analysis.completed_at.isoformat() if analysis.completed_at else None,
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.delete("/delete/{analysis_id}")
|
||||
def delete_analysis(
|
||||
analysis_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
"""删除分析记录及报告文件"""
|
||||
analysis = db.query(DeepAnalysis).filter(DeepAnalysis.id == analysis_id).first()
|
||||
|
||||
if not analysis:
|
||||
raise HTTPException(status_code=404, detail="分析记录不存在")
|
||||
|
||||
# 删除报告文件
|
||||
if analysis.report_file_path and os.path.exists(analysis.report_file_path):
|
||||
try:
|
||||
os.remove(analysis.report_file_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
db.delete(analysis)
|
||||
db.commit()
|
||||
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.get("/queue/status")
|
||||
def get_queue_status(
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
"""获取队列状态"""
|
||||
with task_lock:
|
||||
return {
|
||||
"queued_count": len(analysis_queue),
|
||||
"is_processing": current_task is not None,
|
||||
"current_task": current_task,
|
||||
}
|
||||
|
||||
|
||||
# ============ Model Config APIs ============
|
||||
|
||||
@router.get("/config/model")
|
||||
def get_model_config_api(
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
"""获取模型配置(脱敏)"""
|
||||
config = get_model_config(db)
|
||||
return {
|
||||
"model": config["model"],
|
||||
"api_key_masked": mask_api_key(config["api_key"]),
|
||||
}
|
||||
|
||||
|
||||
@router.post("/config/model")
|
||||
def set_model_config_api(
|
||||
request: ModelConfigRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
"""设置模型配置"""
|
||||
# 验证模型名称
|
||||
valid_models = ["kimi-k2.5", "kimi-latest", "deepseek-chat"]
|
||||
if request.model not in valid_models:
|
||||
raise HTTPException(status_code=400, detail=f"不支持的模型: {request.model}")
|
||||
|
||||
# 保存模型设置
|
||||
model_setting = db.query(SystemSetting).filter(SystemSetting.key == "llm_model").first()
|
||||
if not model_setting:
|
||||
model_setting = SystemSetting(key="llm_model", description="LLM模型名称")
|
||||
db.add(model_setting)
|
||||
model_setting.value = request.model
|
||||
|
||||
# 保存API Key(这里简单存储,生产环境建议加密)
|
||||
key_setting = db.query(SystemSetting).filter(SystemSetting.key == "llm_api_key").first()
|
||||
if not key_setting:
|
||||
key_setting = SystemSetting(key="llm_api_key", description="LLM API密钥")
|
||||
db.add(key_setting)
|
||||
key_setting.value = request.api_key
|
||||
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"model": request.model,
|
||||
"api_key_masked": mask_api_key(request.api_key),
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
发现/搜索路由
|
||||
"""
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from database import get_db
|
||||
from models import User
|
||||
from schemas import SearchRequest, SearchResult, CompanyDetailRequest
|
||||
from auth import get_current_user
|
||||
from fumamx_cli import search_companies, get_company_detail, get_countries
|
||||
|
||||
router = APIRouter(prefix="/discover", tags=["发现"])
|
||||
|
||||
|
||||
@router.post("/search")
|
||||
def search(
|
||||
req: SearchRequest,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""搜索公司"""
|
||||
result = search_companies(req.keyword, page=req.page, page_size=req.page_size)
|
||||
return {
|
||||
"keyword": result["keyword"],
|
||||
"results": result["results"],
|
||||
"total": result["total"],
|
||||
"page": req.page,
|
||||
"page_size": req.page_size
|
||||
}
|
||||
|
||||
|
||||
@router.post("/detail")
|
||||
def detail(
|
||||
req: CompanyDetailRequest,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""获取公司详情"""
|
||||
detail = get_company_detail(req.name, req.country or "")
|
||||
return detail
|
||||
|
||||
|
||||
@router.get("/countries")
|
||||
def countries(current_user: User = Depends(get_current_user)):
|
||||
"""获取国家列表"""
|
||||
countries = get_countries()
|
||||
return {"countries": countries}
|
||||
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
导入任务管理 API
|
||||
"""
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
|
||||
from database import get_db
|
||||
from auth import get_current_user
|
||||
from models import ImportTask, PotentialCustomer, PotentialContact
|
||||
from import_service import import_service
|
||||
|
||||
router = APIRouter(prefix="/import-tasks", tags=["import-tasks"])
|
||||
|
||||
|
||||
class ImportRequest(BaseModel):
|
||||
keyword: str
|
||||
page_size: int = 10
|
||||
|
||||
|
||||
@router.post("/start")
|
||||
def start_import(
|
||||
request: ImportRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""启动导入任务"""
|
||||
result = import_service.start_import(request.keyword, request.page_size)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/cancel")
|
||||
def cancel_import(
|
||||
request: Optional[dict] = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""取消当前导入任务"""
|
||||
print(f"[DEBUG] Cancel request received, user: {current_user.username if current_user else 'None'}")
|
||||
result = import_service.cancel_import()
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
def get_import_status(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
获取导入任务状态
|
||||
返回当前运行的任务或最后一次任务
|
||||
"""
|
||||
# 先检查是否有运行中的任务
|
||||
running = import_service.get_running_task()
|
||||
if running:
|
||||
return {
|
||||
"has_running_task": True,
|
||||
"task": running
|
||||
}
|
||||
|
||||
# 返回最后一次任务
|
||||
latest = import_service.get_latest_task()
|
||||
return {
|
||||
"has_running_task": False,
|
||||
"task": latest
|
||||
}
|
||||
|
||||
|
||||
@router.get("/history")
|
||||
def get_import_history(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(10, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取导入任务历史列表"""
|
||||
total = db.query(ImportTask).count()
|
||||
|
||||
tasks = db.query(ImportTask).order_by(
|
||||
ImportTask.created_at.desc()
|
||||
).offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"tasks": [t.to_dict() for t in tasks]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{task_id}")
|
||||
def get_task_detail(
|
||||
task_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取任务详情"""
|
||||
task = db.query(ImportTask).filter(ImportTask.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
|
||||
return task.to_dict()
|
||||
@@ -0,0 +1,203 @@
|
||||
"""
|
||||
潜在客户管理 API
|
||||
"""
|
||||
from typing import Optional, List
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import or_, desc
|
||||
from pydantic import BaseModel
|
||||
|
||||
from database import get_db
|
||||
from auth import get_current_user
|
||||
from models import PotentialCustomer, PotentialContact
|
||||
|
||||
router = APIRouter(prefix="/potential-customers", tags=["potential-customers"])
|
||||
|
||||
|
||||
class CustomerFilter(BaseModel):
|
||||
keyword: Optional[str] = None
|
||||
country: Optional[str] = None
|
||||
industry: Optional[str] = None
|
||||
has_contacts: Optional[bool] = None
|
||||
|
||||
|
||||
@router.get("/")
|
||||
def list_potential_customers(
|
||||
keyword: Optional[str] = Query(None),
|
||||
country: Optional[str] = Query(None),
|
||||
industry: Optional[str] = Query(None),
|
||||
has_contacts: Optional[bool] = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取潜在客户列表(类似发现模块的展示方式)"""
|
||||
query = db.query(PotentialCustomer)
|
||||
|
||||
# 关键词搜索(公司名称、域名、描述)
|
||||
if keyword:
|
||||
search = f"%{keyword}%"
|
||||
query = query.filter(
|
||||
or_(
|
||||
PotentialCustomer.name.ilike(search),
|
||||
PotentialCustomer.domain.ilike(search),
|
||||
PotentialCustomer.description.ilike(search)
|
||||
)
|
||||
)
|
||||
|
||||
# 国家筛选
|
||||
if country:
|
||||
query = query.filter(PotentialCustomer.country == country)
|
||||
|
||||
# 行业筛选
|
||||
if industry:
|
||||
query = query.filter(PotentialCustomer.industry.ilike(f"%{industry}%"))
|
||||
|
||||
# 是否有联系人筛选
|
||||
if has_contacts is not None:
|
||||
if has_contacts:
|
||||
query = query.filter(PotentialCustomer.contact_count > 0)
|
||||
else:
|
||||
query = query.filter(PotentialCustomer.contact_count == 0)
|
||||
|
||||
# 统计总数
|
||||
total = query.count()
|
||||
|
||||
# 分页查询
|
||||
customers = query.order_by(
|
||||
desc(PotentialCustomer.source_score)
|
||||
).offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"results": [c.to_dict() for c in customers]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/countries")
|
||||
def get_customer_countries(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取所有客户所在国家列表(用于筛选)"""
|
||||
countries = db.query(
|
||||
PotentialCustomer.country,
|
||||
PotentialCustomer.country_cn,
|
||||
PotentialCustomer.country_en
|
||||
).distinct().filter(
|
||||
PotentialCustomer.country.isnot(None)
|
||||
).all()
|
||||
|
||||
return [
|
||||
{
|
||||
"code": c.country,
|
||||
"cn_name": c.country_cn,
|
||||
"en_name": c.country_en
|
||||
}
|
||||
for c in countries if c.country
|
||||
]
|
||||
|
||||
|
||||
@router.get("/industries")
|
||||
def get_customer_industries(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取所有行业列表(用于筛选)"""
|
||||
industries = db.query(
|
||||
PotentialCustomer.industry
|
||||
).distinct().filter(
|
||||
PotentialCustomer.industry.isnot(None)
|
||||
).all()
|
||||
|
||||
return [i.industry for i in industries if i.industry]
|
||||
|
||||
|
||||
@router.get("/{customer_id}")
|
||||
def get_customer_detail(
|
||||
customer_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取客户详情"""
|
||||
customer = db.query(PotentialCustomer).filter(
|
||||
PotentialCustomer.id == customer_id
|
||||
).first()
|
||||
|
||||
if not customer:
|
||||
raise HTTPException(status_code=404, detail="客户不存在")
|
||||
|
||||
# 获取联系人
|
||||
contacts = db.query(PotentialContact).filter(
|
||||
PotentialContact.customer_id == customer_id
|
||||
).order_by(desc(PotentialContact.score)).all()
|
||||
|
||||
result = customer.to_dict()
|
||||
result["contacts"] = [c.to_dict() for c in contacts]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/{customer_id}/contacts")
|
||||
def get_customer_contacts(
|
||||
customer_id: int,
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取客户的联系人列表"""
|
||||
# 验证客户存在
|
||||
customer = db.query(PotentialCustomer).filter(
|
||||
PotentialCustomer.id == customer_id
|
||||
).first()
|
||||
|
||||
if not customer:
|
||||
raise HTTPException(status_code=404, detail="客户不存在")
|
||||
|
||||
# 查询联系人
|
||||
query = db.query(PotentialContact).filter(
|
||||
PotentialContact.customer_id == customer_id
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
|
||||
contacts = query.order_by(
|
||||
desc(PotentialContact.score)
|
||||
).offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"contacts": [c.to_dict() for c in contacts]
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/{customer_id}")
|
||||
def delete_customer(
|
||||
customer_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""删除客户(同时删除关联的联系人)"""
|
||||
customer = db.query(PotentialCustomer).filter(
|
||||
PotentialCustomer.id == customer_id
|
||||
).first()
|
||||
|
||||
if not customer:
|
||||
raise HTTPException(status_code=404, detail="客户不存在")
|
||||
|
||||
# 删除联系人
|
||||
db.query(PotentialContact).filter(
|
||||
PotentialContact.customer_id == customer_id
|
||||
).delete()
|
||||
|
||||
# 删除客户
|
||||
db.delete(customer)
|
||||
db.commit()
|
||||
|
||||
return {"success": True, "message": "删除成功"}
|
||||
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
设置路由
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
|
||||
from database import get_db
|
||||
from models import User
|
||||
from schemas import CookieSetting, SystemStatus
|
||||
from auth import get_current_user
|
||||
from fumamx_cli import update_cookie, COOKIE_FILE, check_cookie_valid
|
||||
|
||||
router = APIRouter(prefix="/settings", tags=["设置"])
|
||||
|
||||
# Token 文件路径
|
||||
TOKEN_CONFIG_FILE = "/home/ubuntu/nanocode/fumamxCLI/src/config.py"
|
||||
|
||||
|
||||
class TokenSetting(BaseModel):
|
||||
access_token: str
|
||||
individual_access_token: str
|
||||
|
||||
|
||||
def read_tokens_from_config():
|
||||
"""从 config.py 读取当前 token"""
|
||||
tokens = {
|
||||
"access_token": "",
|
||||
"individual_access_token": ""
|
||||
}
|
||||
try:
|
||||
with open(TOKEN_CONFIG_FILE, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# 解析 ACCESS_TOKEN
|
||||
import re
|
||||
access_match = re.search(r'ACCESS_TOKEN\s*=\s*["\']([^"\']+)["\']', content)
|
||||
if access_match:
|
||||
tokens["access_token"] = access_match.group(1)
|
||||
|
||||
# 解析 INDIVIDUAL_ACCESS_TOKEN
|
||||
individual_match = re.search(r'INDIVIDUAL_ACCESS_TOKEN\s*=\s*["\']([^"\']+)["\']', content)
|
||||
if individual_match:
|
||||
tokens["individual_access_token"] = individual_match.group(1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"读取 token 失败: {e}")
|
||||
|
||||
return tokens
|
||||
|
||||
|
||||
def write_tokens_to_config(access_token: str, individual_access_token: str):
|
||||
"""写入 token 到 config.py"""
|
||||
try:
|
||||
with open(TOKEN_CONFIG_FILE, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
import re
|
||||
# 替换 ACCESS_TOKEN
|
||||
content = re.sub(
|
||||
r'ACCESS_TOKEN\s*=\s*["\'][^"\']*["\']',
|
||||
f'ACCESS_TOKEN = "{access_token}"',
|
||||
content
|
||||
)
|
||||
|
||||
# 替换 INDIVIDUAL_ACCESS_TOKEN
|
||||
content = re.sub(
|
||||
r'INDIVIDUAL_ACCESS_TOKEN\s*=\s*["\'][^"\']*["\']',
|
||||
f'INDIVIDUAL_ACCESS_TOKEN = "{individual_access_token}"',
|
||||
content
|
||||
)
|
||||
|
||||
with open(TOKEN_CONFIG_FILE, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"写入 token 失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def check_tokens_valid() -> bool:
|
||||
"""检查 token 是否有效"""
|
||||
tokens = read_tokens_from_config()
|
||||
return bool(tokens["access_token"] and tokens["individual_access_token"])
|
||||
|
||||
|
||||
@router.get("/status", response_model=SystemStatus)
|
||||
def get_status(current_user: User = Depends(get_current_user)):
|
||||
"""获取系统状态"""
|
||||
cookie_valid = check_cookie_valid()
|
||||
tokens_valid = check_tokens_valid()
|
||||
return {
|
||||
"logged_in": True,
|
||||
"user": current_user.username,
|
||||
"cookie_valid": cookie_valid,
|
||||
"tokens_valid": tokens_valid
|
||||
}
|
||||
|
||||
|
||||
@router.post("/cookie")
|
||||
def set_cookie(
|
||||
data: CookieSetting,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""设置 cookie"""
|
||||
try:
|
||||
# 验证是否为有效的 JSON
|
||||
import json
|
||||
cookie_data = json.loads(data.cookie_content)
|
||||
|
||||
# 保存到文件
|
||||
with open(COOKIE_FILE, 'w') as f:
|
||||
json.dump(cookie_data, f, indent=2)
|
||||
|
||||
# 验证 cookie 有效性
|
||||
if check_cookie_valid():
|
||||
return {"message": "Cookie 设置成功并验证通过"}
|
||||
else:
|
||||
return {"message": "Cookie 已保存,但验证失败,请检查内容"}
|
||||
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(status_code=400, detail="无效的 JSON 格式")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"保存失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/cookie")
|
||||
def get_cookie(current_user: User = Depends(get_current_user)):
|
||||
"""获取当前 cookie(脱敏)"""
|
||||
try:
|
||||
with open(COOKIE_FILE, 'r') as f:
|
||||
import json
|
||||
cookies = json.load(f)
|
||||
|
||||
# 脱敏处理
|
||||
safe_cookies = []
|
||||
for c in cookies:
|
||||
safe_cookies.append({
|
||||
"name": c.get("name"),
|
||||
"domain": c.get("domain"),
|
||||
"value_preview": c.get("value", "")[:10] + "..." if len(c.get("value", "")) > 10 else c.get("value")
|
||||
})
|
||||
|
||||
return {"cookies": safe_cookies}
|
||||
except Exception as e:
|
||||
return {"cookies": [], "error": str(e)}
|
||||
|
||||
|
||||
@router.get("/tokens")
|
||||
def get_tokens(current_user: User = Depends(get_current_user)):
|
||||
"""获取当前 Token(脱敏显示)"""
|
||||
tokens = read_tokens_from_config()
|
||||
# 脱敏处理
|
||||
return {
|
||||
"access_token": tokens["access_token"][:8] + "..." if len(tokens["access_token"]) > 8 else tokens["access_token"],
|
||||
"individual_access_token": tokens["individual_access_token"][:8] + "..." if len(tokens["individual_access_token"]) > 8 else tokens["individual_access_token"],
|
||||
"has_tokens": bool(tokens["access_token"] and tokens["individual_access_token"])
|
||||
}
|
||||
|
||||
|
||||
@router.post("/tokens")
|
||||
def set_tokens(
|
||||
data: TokenSetting,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""设置 Token"""
|
||||
if not data.access_token.strip() or not data.individual_access_token.strip():
|
||||
raise HTTPException(status_code=400, detail="Token 不能为空")
|
||||
|
||||
if write_tokens_to_config(data.access_token.strip(), data.individual_access_token.strip()):
|
||||
return {"message": "Token 设置成功"}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Token 保存失败")
|
||||
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Pydantic 数据模型
|
||||
"""
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# ============ 认证相关 ============
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class UserLogin(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
|
||||
|
||||
class UserChangePassword(BaseModel):
|
||||
old_password: str
|
||||
new_password: str
|
||||
|
||||
|
||||
# ============ 客户相关 ============
|
||||
|
||||
class CustomerBase(BaseModel):
|
||||
name: str
|
||||
country: Optional[str] = None
|
||||
source_country: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class CustomerCreate(CustomerBase):
|
||||
import_data: Optional[Dict] = None
|
||||
export_data: Optional[Dict] = None
|
||||
raw_data: Optional[Dict] = None
|
||||
|
||||
|
||||
class CustomerResponse(CustomerBase):
|
||||
id: int
|
||||
import_data: Optional[Dict] = None
|
||||
export_data: Optional[Dict] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ============ 发现/搜索相关 ============
|
||||
|
||||
class SearchRequest(BaseModel):
|
||||
keyword: str
|
||||
page: int = 1
|
||||
page_size: int = 10
|
||||
|
||||
|
||||
class SearchResult(BaseModel):
|
||||
name: str
|
||||
country: Optional[str] = None
|
||||
source: str = "fumamx"
|
||||
|
||||
|
||||
class CompanyDetailRequest(BaseModel):
|
||||
name: str
|
||||
country: Optional[str] = ""
|
||||
|
||||
|
||||
# ============ 设置相关 ============
|
||||
|
||||
class CookieSetting(BaseModel):
|
||||
cookie_content: str
|
||||
|
||||
|
||||
class SystemStatus(BaseModel):
|
||||
logged_in: bool
|
||||
user: Optional[str] = None
|
||||
cookie_valid: bool
|
||||
tokens_valid: bool = False
|
||||
Reference in New Issue
Block a user