From 8301091cd800c34fd22095e571874a2b347de896 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 9 Jan 2026 11:14:28 +0800 Subject: [PATCH 01/17] =?UTF-8?q?chore(docker):=20=E6=9B=B4=E6=96=B0nginx?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/nginx/sites-enabled/app.conf | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docker/nginx/sites-enabled/app.conf b/docker/nginx/sites-enabled/app.conf index c7b433b2f..552557af2 100644 --- a/docker/nginx/sites-enabled/app.conf +++ b/docker/nginx/sites-enabled/app.conf @@ -37,8 +37,19 @@ } # --- REWRITE-END --- + # --- 子目录 hwapp 及 hwappx 的配置,请勿删除,支持子目录网站,刷新,重定位 --- + location ~ ^/hwapp/(.*)$ { + try_files $uri $uri/ /hwapp/index.html; + } + + location ~ ^/hwappx/([^/]+)/(.*)$ { + try_files $uri $uri/ /hwappx/$1/index.html; + } + # --- 子目录 hwapp 及 hwappx 配置结束 --- + + #禁止访问的文件或目录 - location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md) + location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)$ { return 404; } From ad9aec21c5235dc0e00ef6f3064d7f9d8dca0d63 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 9 Jan 2026 11:36:18 +0800 Subject: [PATCH 02/17] =?UTF-8?q?chore(doc):=20=E6=9B=B4=E6=96=B0=E5=8D=87?= =?UTF-8?q?=E7=BA=A7ByAI=E7=9A=84=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/UPGRADE_BY_AI.md | 1257 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1257 insertions(+) create mode 100644 docs/UPGRADE_BY_AI.md diff --git a/docs/UPGRADE_BY_AI.md b/docs/UPGRADE_BY_AI.md new file mode 100644 index 000000000..b69d8cd37 --- /dev/null +++ b/docs/UPGRADE_BY_AI.md @@ -0,0 +1,1257 @@ +我来为您提供一个纯AI重构的最佳方案。首先让我更深入地了解项目的复杂度。 +完美!现在我对项目有了全面的了解。让我为您提供**纯AI重构的最佳方案**。 + +--- + +# 🤖 纯AI重构最佳方案:3个月完成 + +基于对项目的深入分析(500+ PHP文件、约4000个函数、267个模型类、复杂的电商业务逻辑),我为您设计了一套**全自动AI重构方案**。 + +--- + +## 📋 项目规模精确定位 + +```yaml +代码复杂度分析: + 核心业务文件: 500+ PHP文件 + 函数总数: ~4000个 + 模型类: 267个 + 控制器: 100+个 + 配置文件: 24个 + 模板文件: 157个 + 业务模块: 15+个 + 代码行数估算: 15万-20万行 + 技术债务: 中等(存在重复代码、复杂SQL查询) +``` + +--- + +## 🎯 纯AI重构方案概览 + +### 核心策略:**分层自动化 + 持续验证 + 渐进交付** + +```mermaid +graph TD + A[PHP代码库] --> B[AI代码分析器] + B --> C[架构转换器] + C --> D[Go代码生成器] + D --> E[自动化测试生成] + E --> F[性能对比验证] + F --> G[生产就绪代码] + + style A fill:#ff6b6b + style B fill:#4ecdc4 + style C fill:#45b7d1 + style D fill:#96ceb4 + style E fill:#ffeaa7 + style F fill:#dfe6e9 + style G fill:#00b894 +``` + +--- + +## 📅 3个月实施计划(90天) + +### **阶段一:智能分析与架构设计(第1-15天)** + +#### Day 1-5:代码深度扫描与依赖分析 + +```bash +# AI自动化分析任务清单 +任务1: 代码结构分析 + ├─ 识别所有模块依赖关系 + ├─ 分析数据库表结构(约100+张表) + ├─ 提取业务规则和约束条件 + └─ 生成依赖关系图 + +任务2: API接口分析 + ├─ 提取所有REST API端点(约200+个) + ├─ 分析请求/响应格式 + ├─ 识别认证和授权逻辑 + └─ 生成API文档 + +任务3: 第三方服务依赖分析 + ├─ 微信支付(Native、H5、小程序、APP) + ├─ 短信服务(阿里云、腾讯云) + ├─ 物流查询(100快递、快递鸟) + ├─ 文件存储(阿里云OSS、本地) + └─ 消息推送 +``` + +**AI工具链:** +```yaml +静态分析: + - PHP-CS-Fixer(代码规范检查) + - PHPStan(静态类型分析) + - Phan(错误检测) + - 自研AI分析器(依赖关系提取) + +文档生成: + - PHPDoc提取(生成Go注释) + - API文档生成器 + - 数据库ER图生成器 +``` + +#### Day 6-10:Go架构设计与技术栈选型 + +```go +// 推荐的Go项目架构 +shop-go/ +├── cmd/ # 应用入口 +│ ├── api/ # API服务 +│ ├── admin/ # 管理后台 +│ ├── merchant/ # 商户端 +│ └── job/ # 定时任务 +├── internal/ # 私有代码 +│ ├── api/ # API处理器 +│ ├── biz/ # 业务逻辑层 +│ ├── ├── goods/ # 商品模块 +│ │ ├── order/ # 订单模块 +│ │ ├── member/ # 用户模块 +│ │ └── ... +│ ├── dal/ # 数据访问层 +│ ├── data/ # 数据模型 +│ ├── pkg/ # 公共包 +│ │ ├── cache/ # 缓存 +│ │ ├── logger/ # 日志 +│ │ ├── middleware/ # 中间件 +│ │ └── ... +│ └── service/ # 服务层 +├── api/ # API定义 +│ └── proto/ # Protobuf定义 +├── configs/ # 配置文件 +├── scripts/ # 脚本 +└── pkg/ # 公共库(可独立) +``` + +**技术栈决策:** +```yaml +Web框架: Gin(性能优秀,生态成熟) +ORM: GORM(功能强大,迁移简单) +缓存: go-redis +消息队列: Asynq(支持Redis) +配置管理: Viper +日志: zap(高性能) +验证: validator +文档: Swagger + swaggo +监控: Prometheus + Grafana +链路追踪: Jaeger +容器: Docker + K8s +``` + +#### Day 11-15:数据库迁移方案设计 + +```sql +-- AI自动生成数据库迁移脚本 +-- 示例:商品表迁移 +CREATE TABLE IF NOT EXISTS goods ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + goods_id VARCHAR(50) UNIQUE NOT NULL, + site_id BIGINT NOT NULL, + goods_name VARCHAR(200) NOT NULL, + goods_image TEXT, + goods_price DECIMAL(10,2) NOT NULL, + goods_stock INT DEFAULT 0, + goods_state TINYINT DEFAULT 1, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_site_id (site_id), + INDEX idx_goods_state (goods_state), + INDEX idx_create_time (create_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +--- + +### **阶段二:AI驱动代码生成(第16-60天)** + +#### Day 16-30:核心基础设施自动化生成 + +**1. 配置管理系统** + +```go +// AI生成的配置管理代码(pkg/config/config.go) +package config + +import ( + "github.com/spf13/viper" +) + +type Config struct { + Server ServerConfig `mapstructure:"server"` + Database DatabaseConfig `mapstructure:"database"` + Redis RedisConfig `mapstructure:"redis"` + Wechat WechatConfig `mapstructure:"wechat"` + // ... 更多配置 +} + +type ServerConfig struct { + Port int `mapstructure:"port"` + Mode string `mapstructure:"mode"` + ReadTimeout int `mapstructure:"read_timeout"` + WriteTimeout int `mapstructure:"write_timeout"` +} + +// LoadConfig 从文件加载配置 +func LoadConfig(path string) (*Config, error) { + viper.SetConfigFile(path) + viper.SetConfigType("yaml") + + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + + var cfg Config + if err := viper.Unmarshal(&cfg); err != nil { + return nil, err + } + + return &cfg, nil +} +``` + +**2. 数据库连接池** + +```go +// AI生成的数据库初始化代码(pkg/database/mysql.go) +package database + +import ( + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var DB *gorm.DB + +// Init 初始化数据库连接 +func Init(cfg *config.DatabaseConfig) error { + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", + cfg.Username, + cfg.Password, + cfg.Host, + cfg.Port, + cfg.Database, + ) + + var err error + DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) + + if err != nil { + return err + } + + sqlDB, err := DB.DB() + if err != nil { + return err + } + + // 设置连接池参数 + sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) + sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) + sqlDB.SetConnMaxLifetime(time.Hour) + + return nil +} +``` + +**3. 缓存管理** + +```go +// AI生成的Redis缓存代码(pkg/cache/redis.go) +package cache + +import ( + "context" + "encoding/json" + "time" + "github.com/go-redis/redis/v8" +) + +type Cache struct { + client *redis.Client +} + +// New 创建新的缓存实例 +func New(addr, password string, db int) *Cache { + return &Cache{ + client: redis.NewClient(&redis.Options{ + Addr: addr, + Password: password, + DB: db, + }), + } +} + +// Get 获取缓存 +func (c *Cache) Get(ctx context.Context, key string, dest interface{}) error { + val, err := c.client.Get(ctx, key).Result() + if err != nil { + return err + } + return json.Unmarshal([]byte(val), dest) +} + +// Set 设置缓存 +func (c *Cache) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error { + data, err := json.Marshal(value) + if err != nil { + return err + } + return c.client.Set(ctx, key, data, expiration).Err() +} + +// Delete 删除缓存 +func (c *Cache) Delete(ctx context.Context, key string) error { + return c.client.Del(ctx, key).Err() +} +``` + +#### Day 31-60:业务模块批量生成 + +**AI自动化流程:** + +```mermaid +graph LR + A[PHP Model] --> B[AST解析] + B --> C[业务逻辑提取] + C --> D[Go Model生成] + D --> E[Repository层生成] + E --> F[Service层生成] + F --> G[API Handler生成] + G --> H[单元测试生成] +``` + +**示例:商品模块AI生成** + +**1. Go Model(从PHP Model自动转换)** + +```go +// internal/biz/goods/model/goods.go +package model + +import "time" + +// Goods 商品实体 +type Goods struct { + ID int64 `json:"id" gorm:"primaryKey;autoIncrement"` + GoodsID string `json:"goods_id" gorm:"uniqueIndex;size:50"` + SiteID int64 `json:"site_id" gorm:"index"` + GoodsName string `json:"goods_name" gorm:"size:200;not null"` + GoodsImage string `json:"goods_image" gorm:"type:text"` + GoodsPrice float64 `json:"goods_price" gorm:"type:decimal(10,2);not null"` + GoodsStock int `json:"goods_stock" gorm:"default:0"` + GoodsState int `json:"goods_state" gorm:"default:1"` + GoodsDesc string `json:"goods_desc" gorm:"type:text"` + CategoryID int64 `json:"category_id" gorm:"index"` + BrandID int64 `json:"brand_id"` + LabelID int64 `json:"label_id"` + CreateTime time.Time `json:"create_time" gorm:"autoCreateTime"` + UpdateTime time.Time `json:"update_time" gorm:"autoUpdateTime"` +} + +// TableName 指定表名 +func (Goods) TableName() string { + return "goods" +} +``` + +**2. Repository层(数据访问)** + +```go +// internal/dal/goods/goods_repository.go +package goods + +import ( + "context" + "gorm.io/gorm" + "shop-go/internal/biz/goods/model" +) + +type Repository interface { + Create(ctx context.Context, goods *model.Goods) error + Update(ctx context.Context, goods *model.Goods) error + Delete(ctx context.Context, id int64) error + GetByID(ctx context.Context, id int64) (*model.Goods, error) + List(ctx context.Context, query *QueryParams) ([]*model.Goods, int64, error) +} + +type QueryParams struct { + SiteID int64 + CategoryID int64 + GoodsState int + Page int + PageSize int +} + +type repository struct { + db *gorm.DB +} + +// NewRepository 创建新的Repository +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +// Create 创建商品 +func (r *repository) Create(ctx context.Context, goods *model.Goods) error { + return r.db.WithContext(ctx).Create(goods).Error +} + +// Update 更新商品 +func (r *repository) Update(ctx context.Context, goods *model.Goods) error { + return r.db.WithContext(ctx).Save(goods).Error +} + +// Delete 删除商品 +func (r *repository) Delete(ctx context.Context, id int64) error { + return r.db.WithContext(ctx).Delete(&model.Goods{}, id).Error +} + +// GetByID 根据ID获取商品 +func (r *repository) GetByID(ctx context.Context, id int64) (*model.Goods, error) { + var goods model.Goods + err := r.db.WithContext(ctx).First(&goods, id).Error + if err != nil { + return nil, err + } + return &goods, nil +} + +// List 获取商品列表 +func (r *repository) List(ctx context.Context, query *QueryParams) ([]*model.Goods, int64, error) { + db := r.db.WithContext(ctx).Model(&model.Goods{}) + + if query.SiteID > 0 { + db = db.Where("site_id = ?", query.SiteID) + } + if query.CategoryID > 0 { + db = db.Where("category_id = ?", query.CategoryID) + } + if query.GoodsState >= 0 { + db = db.Where("goods_state = ?", query.GoodsState) + } + + var total int64 + if err := db.Count(&total).Error; err != nil { + return nil, 0, err + } + + var list []*model.Goods + offset := (query.Page - 1) * query.PageSize + if err := db.Offset(offset).Limit(query.PageSize).Find(&list).Error; err != nil { + return nil, 0, err + } + + return list, total, nil +} +``` + +**3. Service层(业务逻辑)** + +```go +// internal/biz/goods/service/goods_service.go +package service + +import ( + "context" + "errors" + "shop-go/internal/biz/goods/model" + "shop-go/internal/dal/goods" +) + +type GoodsService interface { + CreateGoods(ctx context.Context, req *CreateGoodsRequest) (*model.Goods, error) + UpdateGoods(ctx context.Context, req *UpdateGoodsRequest) error + DeleteGoods(ctx context.Context, id int64) error + GetGoods(ctx context.Context, id int64) (*model.Goods, error) + ListGoods(ctx context.Context, req *ListGoodsRequest) ([]*model.Goods, int64, error) +} + +type CreateGoodsRequest struct { + SiteID int64 `json:"site_id" binding:"required"` + GoodsName string `json:"goods_name" binding:"required"` + GoodsImage string `json:"goods_image"` + GoodsPrice float64 `json:"goods_price" binding:"required"` + GoodsStock int `json:"goods_stock"` + CategoryID int64 `json:"category_id"` + BrandID int64 `json:"brand_id"` + LabelID int64 `json:"label_id"` + GoodsDesc string `json:"goods_desc"` +} + +type UpdateGoodsRequest struct { + ID int64 `json:"id" binding:"required"` + GoodsName string `json:"goods_name"` + GoodsImage string `json:"goods_image"` + GoodsPrice float64 `json:"goods_price"` + GoodsStock int `json:"goods_stock"` + GoodsState int `json:"goods_state"` + CategoryID int64 `json:"category_id"` + BrandID int64 `json:"brand_id"` + LabelID int64 `json:"label_id"` + GoodsDesc string `json:"goods_desc"` +} + +type ListGoodsRequest struct { + SiteID int64 `form:"site_id"` + CategoryID int64 `form:"category_id"` + GoodsState int `form:"goods_state"` + Page int `form:"page" binding:"min=1"` + PageSize int `form:"page_size" binding:"min=1,max=100"` +} + +type service struct { + repo goods.Repository +} + +// NewGoodsService 创建商品服务 +func NewGoodsService(repo goods.Repository) GoodsService { + return &service{repo: repo} +} + +// CreateGoods 创建商品 +func (s *service) CreateGoods(ctx context.Context, req *CreateGoodsRequest) (*model.Goods, error) { + // 生成商品ID + goodsID := generateGoodsID(req.SiteID) + + goods := &model.Goods{ + GoodsID: goodsID, + SiteID: req.SiteID, + GoodsName: req.GoodsName, + GoodsImage: req.GoodsImage, + GoodsPrice: req.GoodsPrice, + GoodsStock: req.GoodsStock, + GoodsState: 1, // 默认为销售中 + CategoryID: req.CategoryID, + BrandID: req.BrandID, + LabelID: req.LabelID, + GoodsDesc: req.GoodsDesc, + } + + if err := s.repo.Create(ctx, goods); err != nil { + return nil, err + } + + return goods, nil +} + +// UpdateGoods 更新商品 +func (s *service) UpdateGoods(ctx context.Context, req *UpdateGoodsRequest) error { + goods, err := s.repo.GetByID(ctx, req.ID) + if err != nil { + return err + } + + // 更新字段 + if req.GoodsName != "" { + goods.GoodsName = req.GoodsName + } + if req.GoodsImage != "" { + goods.GoodsImage = req.GoodsImage + } + if req.GoodsPrice > 0 { + goods.GoodsPrice = req.GoodsPrice + } + if req.GoodsStock >= 0 { + goods.GoodsStock = req.GoodsStock + } + if req.GoodsState >= 0 { + goods.GoodsState = req.GoodsState + } + if req.CategoryID > 0 { + goods.CategoryID = req.CategoryID + } + if req.BrandID > 0 { + goods.BrandID = req.BrandID + } + if req.LabelID > 0 { + goods.LabelID = req.LabelID + } + if req.GoodsDesc != "" { + goods.GoodsDesc = req.GoodsDesc + } + + return s.repo.Update(ctx, goods) +} + +// DeleteGoods 删除商品 +func (s *service) DeleteGoods(ctx context.Context, id int64) error { + // 检查商品是否存在 + _, err := s.repo.GetByID(ctx, id) + if err != nil { + return err + } + + // TODO: 检查是否有关联订单 + + return s.repo.Delete(ctx, id) +} + +// GetGoods 获取商品详情 +func (s *service) GetGoods(ctx context.Context, id int64) (*model.Goods, error) { + return s.repo.GetByID(ctx, id) +} + +// ListGoods 获取商品列表 +func (s *service) ListGoods(ctx context.Context, req *ListGoodsRequest) ([]*model.Goods, int64, error) { + query := &goods.QueryParams{ + SiteID: req.SiteID, + CategoryID: req.CategoryID, + GoodsState: req.GoodsState, + Page: req.Page, + PageSize: req.PageSize, + } + + return s.repo.List(ctx, query) +} + +// generateGoodsID 生成商品ID +func generateGoodsID(siteID int64) string { + // 实现雪花算法或其他ID生成策略 + return "" +} +``` + +**4. API Handler层** + +```go +// internal/api/goods/handler.go +package goods + +import ( + "net/http" + "shop-go/internal/biz/goods/model" + "shop-go/internal/biz/goods/service" + + "github.com/gin-gonic/gin" +) + +type Handler struct { + service service.GoodsService +} + +// NewHandler 创建Handler +func NewHandler(svc service.GoodsService) *Handler { + return &Handler{service: svc} +} + +// CreateGoods 创建商品 +// @Summary 创建商品 +// @Tags 商品 +// @Accept json +// @Produce json +// @Param request body service.CreateGoodsRequest true "商品信息" +// @Success 200 {object} Response{data=model.Goods} +// @Router /api/goods [post] +func (h *Handler) CreateGoods(c *gin.Context) { + var req service.CreateGoodsRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + goods, err := h.service.CreateGoods(c.Request.Context(), &req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, Response{ + Code: 0, + Msg: "成功", + Data: goods, + }) +} + +// UpdateGoods 更新商品 +func (h *Handler) UpdateGoods(c *gin.Context) { + var req service.UpdateGoodsRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.UpdateGoods(c.Request.Context(), &req); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, Response{ + Code: 0, + Msg: "成功", + }) +} + +// DeleteGoods 删除商品 +func (h *Handler) DeleteGoods(c *gin.Context) { + id := c.Param("id") + var goodsID int64 + if _, err := fmt.Sscanf(id, "%d", &goodsID); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"}) + return + } + + if err := h.service.DeleteGoods(c.Request.Context(), goodsID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, Response{ + Code: 0, + Msg: "成功", + }) +} + +// GetGoods 获取商品详情 +func (h *Handler) GetGoods(c *gin.Context) { + id := c.Param("id") + var goodsID int64 + if _, err := fmt.Sscanf(id, "%d", &goodsID); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"}) + return + } + + goods, err := h.service.GetGoods(c.Request.Context(), goodsID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, Response{ + Code: 0, + Msg: "成功", + Data: goods, + }) +} + +// ListGoods 获取商品列表 +func (h *Handler) ListGoods(c *gin.Context) { + var req service.ListGoodsRequest + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // 设置默认值 + if req.Page == 0 { + req.Page = 1 + } + if req.PageSize == 0 { + req.PageSize = 20 + } + + list, total, err := h.service.ListGoods(c.Request.Context(), &req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, Response{ + Code: 0, + Msg: "成功", + Data: gin.H{ + "list": list, + "total": total, + "page": req.Page, + "page_size": req.PageSize, + }, + }) +} + +// Response 统一响应格式 +type Response struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` +} +``` + +**5. 自动化测试生成** + +```go +// internal/biz/goods/service/goods_service_test.go +package service_test + +import ( + "context" + "testing" + "shop-go/internal/biz/goods/model" + "shop-go/internal/biz/goods/service" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// MockRepository 模拟Repository +type MockRepository struct { + mock.Mock +} + +func (m *MockRepository) Create(ctx context.Context, goods *model.Goods) error { + args := m.Called(ctx, goods) + return args.Error(0) +} + +func (m *MockRepository) Update(ctx context.Context, goods *model.Goods) error { + args := m.Called(ctx, goods) + return args.Error(0) +} + +func (m *MockRepository) Delete(ctx context.Context, id int64) error { + args := m.Called(ctx, id) + return args.Error(0) +} + +func (m *MockRepository) GetByID(ctx context.Context, id int64) (*model.Goods, error) { + args := m.Called(ctx, id) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*model.Goods), args.Error(1) +} + +func (m *MockRepository) List(ctx context.Context, query interface{}) ([]*model.Goods, int64, error) { + args := m.Called(ctx, query) + return args.Get(0).([]*model.Goods), args.Get(1).(int64), args.Error(2) +} + +// TestCreateGoods 测试创建商品 +func TestCreateGoods(t *testing.T) { + mockRepo := new(MockRepository) + svc := service.NewGoodsService(mockRepo) + + req := &service.CreateGoodsRequest{ + SiteID: 1, + GoodsName: "测试商品", + GoodsPrice: 99.99, + GoodsStock: 100, + } + + mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*model.Goods")).Return(nil) + + goods, err := svc.CreateGoods(context.Background(), req) + + assert.NoError(t, err) + assert.NotNil(t, goods) + assert.Equal(t, "测试商品", goods.GoodsName) + mockRepo.AssertExpectations(t) +} + +// TestUpdateGoods 测试更新商品 +func TestUpdateGoods(t *testing.T) { + mockRepo := new(MockRepository) + svc := service.NewGoodsService(mockRepo) + + existingGoods := &model.Goods{ + ID: 1, + GoodsName: "原商品名", + GoodsPrice: 99.99, + } + + req := &service.UpdateGoodsRequest{ + ID: 1, + GoodsName: "新商品名", + GoodsPrice: 199.99, + } + + mockRepo.On("GetByID", mock.Anything, int64(1)).Return(existingGoods, nil) + mockRepo.On("Update", mock.Anything, mock.AnythingOfType("*model.Goods")).Return(nil) + + err := svc.UpdateGoods(context.Background(), req) + + assert.NoError(t, err) + mockRepo.AssertExpectations(t) +} +``` + +--- + +### **阶段三:第三方服务集成与测试(第61-75天)** + +#### Day 61-70:第三方SDK集成 + +```go +// 内部微信支付SDK封装 +// internal/pkg/wechat/pay.go +package wechat + +import ( + "context" + "github.com/wechatpay-apiv3/wechatpay-go/core" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments" +) + +type PayClient struct { + client *core.Client +} + +func NewPayClient(mchid, serialNo, privateKey, apiv3Key string) (*PayClient, error) { + // 初始化微信支付客户端 + opts := []core.ClientOption{ + core.WithMerchantCredential(mchid, serialNo, privateKey), + core.WithAutoVerifyToken(core.AutoVerifyToken(apiV3Key)), + } + + client, err := core.NewClient(opts...) + if err != nil { + return nil, err + } + + return &PayClient{client: client}, nil +} + +// CreateNativePay 创建Native支付订单 +func (c *PayClient) CreateNativePay(ctx context.Context, req *payments.NativePayRequest) (*payments.NativePayResponse, error) { + svc := &payments.NativePayApiService{Client: c.client} + return svc.CreateNativePay(ctx, req) +} + +// QueryOrder 查询订单 +func (c *PayClient) QueryOrder(ctx context.Context, outTradeNo string) (*payments.TransactionResponse, error) { + svc := &payments.OrderApiService{Client: c.client} + return svc.QueryByOutTradeNo(ctx, outTradeNo) +} + +// Refund 退款 +func (c *PayClient) Refund(ctx context.Context, req *payments.RefundRequest) (*payments.RefundResponse, error) { + svc := &payments.RefundApiService{Client: c.client} + return svc.CreateRefund(ctx, req) +} +``` + +#### Day 71-75:集成测试与性能对比 + +```yaml +测试策略: + 单元测试: + - 覆盖率目标: 80%+ + - AI自动生成测试用例 + - Mock所有外部依赖 + + 集成测试: + - 数据库集成测试 + - Redis缓存测试 + - 第三方服务集成测试 + - API端到端测试 + + 性能测试: + - 并发测试: 1000 QPS + - 响应时间: P99 < 100ms + - 对比PHP版本性能 +``` + +--- + +### **阶段四:部署优化与文档(第76-90天)** + +#### Day 76-85:Docker容器化与部署 + +```dockerfile +# Dockerfile +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +# 复制依赖文件 +COPY go.mod go.sum ./ +RUN go mod download + +# 复制源码 +COPY . . + +# 编译 +RUN CGO_ENABLED=0 GOOS=linux go build -o shop-api ./cmd/api + +# 运行阶段 +FROM alpine:latest + +RUN apk --no-cache add ca-certificates tzdata + +WORKDIR /root/ + +COPY --from=builder /app/shop-api . +COPY configs ./configs + +EXPOSE 8080 + +CMD ["./shop-api"] +``` + +```yaml +# docker-compose.yml +version: '3.8' + +services: + mysql: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: shop + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + + redis: + image: redis:7-alpine + ports: + - "6379:6379" + volumes: + - redis_data:/data + + api: + build: . + ports: + - "8080:8080" + depends_on: + - mysql + - redis + environment: + - GIN_MODE=release + volumes: + - ./configs:/app/configs + +volumes: + mysql_data: + redis_data: +``` + +#### Day 86-90:文档生成与交付 + +```bash +# 自动生成API文档 +swag init -g cmd/api/main.go -o docs + +# 生成架构文档 +# 使用AI工具从代码提取架构信息 + +# 生成部署文档 +# 使用AI根据docker-compose生成部署说明 +``` + +--- + +## 🛠️ AI工具链配置 + +### **核心AI工具** + +```yaml +代码分析: + - PHP Parser (AST解析) + - Phan (静态分析) + - Psalm (类型检查) + - 自研依赖关系分析器 + +代码生成: + - GPT-4/ Claude (模型层生成) + - Template引擎 (Service/Handler生成) + - Mock生成器 (测试用例生成) + +测试生成: + - testify (测试框架) + - gomock (Mock生成) + - AI测试用例生成器 + +文档生成: + - swaggo (API文档) + - godoc (代码文档) + - AI架构文档生成器 +``` + +### **自动化CI/CD流程** + +```yaml +# .github/workflows/ci.yml +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install dependencies + run: go mod download + + - name: Run tests + run: go test -v -race -coverprofile=coverage.out ./... + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + files: ./coverage.out + + - name: Build + run: go build -v ./... + + - name: Run linters + uses: golangci/golangci-lint-action@v3 +``` + +--- + +## 📊 风险控制与质量保证 + +### **代码质量标准** + +```yaml +代码规范: + - gofmt (格式化) + - go vet (静态检查) + - golangci-lint (综合检查) + +测试覆盖: + - 单元测试覆盖率 > 80% + - 集成测试覆盖核心业务 + - 性能测试: P99 < 100ms + +文档要求: + - API文档自动生成 + - 代码注释完整 + - 架构设计文档 + +性能指标: + - 响应时间: P50 < 50ms, P99 < 100ms + - 吞吐量: > 1000 QPS + - 内存使用: < 500MB +``` + +### **回滚策略** + +```yaml +灰度发布: + - 第一阶段: 5% 流量 + - 第二阶段: 20% 流量 + - 第三阶段: 50% 流量 + - 第四阶段: 100% 流量 + +监控告警: + - 错误率 > 1% 立即告警 + - 响应时间 P99 > 200ms 告警 + - 内存使用 > 80% 告警 + +回滚条件: + - 错误率持续上升 + - 性能严重下降 + - 核心业务异常 +``` + +--- + +## 💰 成本估算 + +```yaml +AI工具成本: + - OpenAI API: $300-500/月 + - Claude API: $200-300/月 + - 总计: $500-800/月 + +云服务成本: + - 服务器: $100-200/月 + - 数据库: $100-150/月 + - 存储: $50/月 + - 总计: $250-400/月 + +总计: $750-1200/月 (约5000-8000元/月) +``` + +--- + +## 🎯 预期成果 + +### **交付物清单** + +```yaml +代码交付: + ✅ 完整Go代码库 + ✅ 单元测试覆盖率 > 80% + ✅ 集成测试套件 + ✅ API文档 + +部署交付: + ✅ Docker镜像 + ✅ Docker Compose配置 + ✅ Kubernetes部署文件 + ✅ 监控配置 + +文档交付: + ✅ 架构设计文档 + ✅ API接口文档 + ✅ 部署运维文档 + ✅ 代码注释文档 +``` + +### **性能提升预期** + +```yaml +性能对比: + 响应时间: 提升 3-5倍 + 并发能力: 提升 10倍+ + 内存占用: 降低 30-50% + CPU利用率: 提升 2-3倍 + +稳定性提升: + - 更好的错误处理 + - 更强的类型安全 + - 更完善的监控 + - 更快的部署速度 +``` + +--- + +## 🚀 开始实施 + +### **立即行动清单** + +```bash +# 第1周任务 +1. 设置AI开发环境 +2. 配置代码分析工具链 +3. 运行PHP代码分析 +4. 生成项目架构文档 + +# 第2周任务 +5. 创建Go项目结构 +6. 配置数据库连接 +7. 实现基础框架代码 +8. 设置CI/CD流程 + +# 第3-4周任务 +9. 开始批量生成Model层 +10. 生成Repository层 +11. 生成Service层 +12. 生成API Handler层 +``` + +--- + +## 📞 技术支持 + +如果您决定启动这个纯AI重构项目,我建议: + +1. **前期准备(1-2天)**:详细分析PHP代码,建立完整的技术债务清单 +2. **架构评审(3天)**:确保Go架构设计满足业务需求 +3. **分阶段交付**:按模块逐步交付,每2周一个里程碑 +4. **持续监控**:建立完善的监控和告警机制 +5. **性能基准**:建立PHP版本的性能基准,用于对比验证 + +--- + +**总结:通过这个纯AI重构方案,您可以在3个月内将整个PHP电商系统重写为Go语言,同时保证代码质量、测试覆盖率和生产稳定性。关键在于充分利用AI工具的自动化能力,建立完善的测试和监控体系。** + +您是否需要我详细展开某个具体阶段的技术细节,或者开始准备第一阶段的代码分析工作? \ No newline at end of file From b6e77071f178f88bf753d917d485d6b466b5f83b Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Sat, 10 Jan 2026 08:37:53 +0800 Subject: [PATCH 03/17] =?UTF-8?q?chore:=20=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E5=8F=B7=E5=9F=BA=E6=9C=AC=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/diy/RADEME.md | 33 +- src/addon/personnel/config/menu_shop.php | 39 +- src/addon/personnel/data/install.sql | 32 +- src/addon/personnel/data/uninstall.sql | 6 + src/addon/personnel/model/Enterprise.php | 103 +++++ .../personnel/shop/controller/Enterprise.php | 103 ++++- .../shop/view/enterprise/channel/add.html | 364 ++++++++++++++++++ .../shop/view/enterprise/channel/edit.html | 360 +++++++++++++++++ .../shop/view/enterprise/channel/lists.html | 203 ++++++++++ .../shop/view/public/img/mingpian.png | Bin 14387 -> 9574 bytes .../component/controller/WechatChannel.php | 17 + .../view/wechat_channel/css/design.css | 65 ++++ .../component/view/wechat_channel/design.html | 45 +++ .../view/wechat_channel/js/design.js | 91 +++++ 14 files changed, 1455 insertions(+), 6 deletions(-) create mode 100644 src/addon/personnel/shop/view/enterprise/channel/add.html create mode 100644 src/addon/personnel/shop/view/enterprise/channel/edit.html create mode 100644 src/addon/personnel/shop/view/enterprise/channel/lists.html create mode 100644 src/app/component/controller/WechatChannel.php create mode 100644 src/app/component/view/wechat_channel/css/design.css create mode 100644 src/app/component/view/wechat_channel/design.html create mode 100644 src/app/component/view/wechat_channel/js/design.js diff --git a/docs/diy/RADEME.md b/docs/diy/RADEME.md index c9a6a0b2a..e25d92996 100644 --- a/docs/diy/RADEME.md +++ b/docs/diy/RADEME.md @@ -24,4 +24,35 @@ create table if not exists lucky_diy_view_util constraint name unique (name) ) -``` \ No newline at end of file +``` + +## 页面设计及组件展示 + +- src\app\model\web\DiyView.php +- src\app\shop\view\diy\edit.html +- src\public\static\ext\diyview\js\components.js + +## 如何添加新组件 + + +### 1. 添加组件到数据表中 + +```sql +insert into lucky_diy_view_util (name, title, type, value, addon_name, sort, support_diy_view, max_count, is_delete, icon, icon_type) +values ('test', '测试', 'SYSTEM', '{"test": "test"}', '', 0, '', 0, 0, '', 0); +``` + + +### 2. 建立组件的控制器 +在 `src\app\component\controller` 目录下创建对应的控制器文件,处理组件的业务逻辑。 + +例如:创建 `src\app\component\controller\TestController.php` 文件,用于处理测试组件的业务逻辑。 + +### 3. 建立组件的视图 +在 src\app\component\view 目录下创建对应的视图文件,处理组件的前端展示。 + +例如:创建 `src\app\component\view\test.php` 文件,用于展示测试组件。 + + +### 4. 在前端页面中使用组件 +在前端页面中使用组件,需要在页面中添加对应的组件标签。 \ No newline at end of file diff --git a/src/addon/personnel/config/menu_shop.php b/src/addon/personnel/config/menu_shop.php index 1decf3c0e..5159956b0 100644 --- a/src/addon/personnel/config/menu_shop.php +++ b/src/addon/personnel/config/menu_shop.php @@ -112,13 +112,46 @@ return [ ], ] ], + [ + 'name' => 'PERSONNEL_CHANNEL_LIST', + 'title' => '视频号', + 'url' => 'personnel://shop/enterprise/channellists', + 'is_show' => 1, + 'sort' => 4, + 'child_list' => [ + [ + 'name' => 'PERSONNEL_CHANNEL_ADD', + 'title' => '添加视频号', + 'url' => 'personnel://shop/enterprise/channeladd', + 'sort' => 1, + 'is_show' => 0, + 'is_control' => 1, + ], + [ + 'name' => 'PERSONNEL_CHANNEL_EDIT', + 'title' => '编辑视频号', + 'url' => 'personnel://shop/enterprise/channeledit', + 'sort' => 1, + 'is_show' => 0, + 'is_control' => 1, + ], + [ + 'name' => 'PERSONNEL_CHANNEL_DELETE', + 'title' => '删除视频号', + 'url' => 'personnel://shop/enterprise/channeldelete', + 'sort' => 2, + 'is_show' => 0, + 'is_control' => 1, + ], + ] + ], [ 'name' => 'MESSAGE_ROOT', 'title' => '留言列表', 'url' => 'personnel://shop/personnel/message', 'is_show' => 1, - 'sort' => 4, + 'sort' => 5, 'child_list' => [ ], @@ -128,7 +161,7 @@ return [ 'title' => '电子名片', 'url' => 'personnel://shop/personnel/diy', 'is_show' => 1, - 'sort' => 5, + 'sort' => 6, 'child_list' => [ ], @@ -138,7 +171,7 @@ return [ 'title' => '设置', 'url' => 'personnel://shop/personnel/set', 'is_show' => 1, - 'sort' => 6, + 'sort' => 7, 'child_list' => [ ], diff --git a/src/addon/personnel/data/install.sql b/src/addon/personnel/data/install.sql index 22c382dd4..3dc13a20c 100644 --- a/src/addon/personnel/data/install.sql +++ b/src/addon/personnel/data/install.sql @@ -1 +1,31 @@ -SET NAMES 'utf8'; \ No newline at end of file +SET NAMES 'utf8'; + +-- 创建视频号表 +CREATE TABLE IF NOT EXISTS `lucky_personnel_channel` +( + `channel_id` int auto_increment + primary key, + `site_id` int null, + `channel_type` varchar(50) default 'wechat' null comment '视频号类型:wechat-微信视频号,douyin-抖音,kuaishou-快手,redbook-小红书,bilibili-B站', + `channel_name` varchar(255) null, + `avatar_image_type` varchar(20) default 'upload' null comment '头像类型:upload-上传图片,url-URL', + `avatar_url` varchar(255) null, + `video_title` varchar(255) null, + `feed_id` varchar(255) null, + `cover_image_type` varchar(20) default 'upload' null comment '封面类型:upload-上传图片,url-URL', + `cover_url` varchar(255) null, + `sort` int default 0 null comment '排序', + `is_show` tinyint(1) default 1 null comment '是否显示:0-隐藏,1-显示', + `create_time` int null +) + engine = MyISAM + collate = utf8_unicode_ci + comment = '电子名片视频号资源表'; + +-- 插入视频号菜单权限(使用 IGNORE 避免重复插入报错) +INSERT IGNORE INTO `lucky_menu` (`app_module`, `addon`, `title`, `name`, `parent`, `level`, `url`, `is_show`, `sort`, `desc`, `is_icon`, `picture`, `picture_select`, `is_control`) +VALUES +('shop', 'personnel', '视频号', 'PERSONNEL_CHANNEL_LIST', 'PERSONNEL_ROOT', 4, 'personnel://shop/enterprise/channellists', 1, 4, '', 0, '', '', 1), +('shop', 'personnel', '添加视频号', 'PERSONNEL_CHANNEL_ADD', 'PERSONNEL_CHANNEL_LIST', 5, 'personnel://shop/enterprise/channeladd', 0, 1, '', 0, '', '', 1), +('shop', 'personnel', '编辑视频号', 'PERSONNEL_CHANNEL_EDIT', 'PERSONNEL_CHANNEL_LIST', 5, 'personnel://shop/enterprise/channeledit', 0, 1, '', 0, '', '', 1), +('shop', 'personnel', '删除视频号', 'PERSONNEL_CHANNEL_DELETE', 'PERSONNEL_CHANNEL_LIST', 5, 'personnel://shop/enterprise/channeldelete', 0, 2, '', 0, '', '', 1); \ No newline at end of file diff --git a/src/addon/personnel/data/uninstall.sql b/src/addon/personnel/data/uninstall.sql index 0b4cf1b2f..c14f8bb57 100644 --- a/src/addon/personnel/data/uninstall.sql +++ b/src/addon/personnel/data/uninstall.sql @@ -1 +1,7 @@ SET NAMES 'utf8'; + +-- 删除视频号菜单权限 +DELETE FROM `lucky_menu` WHERE `name` IN ('PERSONNEL_CHANNEL_LIST', 'PERSONNEL_CHANNEL_ADD', 'PERSONNEL_CHANNEL_EDIT', 'PERSONNEL_CHANNEL_DELETE'); + +-- 删除视频号表 +DROP TABLE IF EXISTS `lucky_personnel_channel`; \ No newline at end of file diff --git a/src/addon/personnel/model/Enterprise.php b/src/addon/personnel/model/Enterprise.php index 3c877a5d5..70fd166c7 100644 --- a/src/addon/personnel/model/Enterprise.php +++ b/src/addon/personnel/model/Enterprise.php @@ -264,4 +264,107 @@ class Enterprise extends BaseModel return $this->success($res); } + //视频号 + /** + * 获取视频号分页列表 + * @param array $condition + * @param number $page + * @param string $page_size + * @param string $order + * @param string $field + */ + public function getChannelPageList($condition = [], $page = 1, $page_size = PAGE_LIST_ROWS, $order = 'create_time desc', $field = '*') + { + $check_condition = array_column($condition, 2, 0); + $site_id = $check_condition['site_id'] ?? ''; + if ($site_id === '') { + return $this->error('', 'REQUEST_SITE_ID'); + } + + $list = model('personnel_channel')->pageList($condition, $field, $order, $page, $page_size); + return $this->success($list); + } + + /** + * 获取视频号列表 + * @param array $condition + * @param string $field + * @param string $order + * @param string $limit + */ + public function getChannelList($condition = [], $field = '*', $order = '', $limit = null) + { + $list = model('personnel_channel')->getList($condition, $field, $order, '', '', '', $limit); + return $this->success($list); + } + + /** + * 添加视频号 + * @param array $data + */ + public function addChannel($data) + { + $site_id = $data['site_id'] ?? ''; + if ($site_id === '') { + return $this->error('', 'REQUEST_SITE_ID'); + } + + $data['create_time'] = time(); + // 默认视频号类型为微信 + if (!isset($data['channel_type']) || empty($data['channel_type'])) { + $data['channel_type'] = 'wechat'; + } + $channel_id = model('personnel_channel')->add($data); + return $this->success($channel_id); + } + + /** + * 获取视频号信息 + * @param array $condition + * @param string $field + */ + public function getChannelInfo($condition = [], $field = '*') + { + $list = model('personnel_channel')->getInfo($condition, $field); + return $this->success($list); + } + + /** + * 修改视频号 + * @param array $data + */ + public function editChannel($data) + { + $site_id = $data['site_id'] ?? ''; + if ($site_id === '') { + return $this->error('', 'REQUEST_SITE_ID'); + } + + model('personnel_channel')->startTrans(); + try { + model('personnel_channel')->update($data, [ [ 'site_id', '=', $data[ 'site_id' ] ], [ 'channel_id', '=', $data[ 'channel_id' ] ] ]); + model('personnel_channel')->commit(); + return $this->success(); + } catch (\Exception $e) { + model('personnel_channel')->rollback(); + return $this->error('', $e->getMessage()); + } + } + + /** + * 删除视频号 + * @param array $condition + */ + public function deleteChannel($condition) + { + $check_condition = array_column($condition, 2, 0); + $site_id = $check_condition['site_id'] ?? ''; + if ($site_id === '') { + return $this->error('', 'REQUEST_SITE_ID'); + } + + $res = model('personnel_channel')->delete($condition); + return $this->success($res); + } + } \ No newline at end of file diff --git a/src/addon/personnel/shop/controller/Enterprise.php b/src/addon/personnel/shop/controller/Enterprise.php index fb0a35253..8e423200f 100644 --- a/src/addon/personnel/shop/controller/Enterprise.php +++ b/src/addon/personnel/shop/controller/Enterprise.php @@ -168,7 +168,108 @@ class Enterprise extends BaseShop if (request()->isJson()) { $video_id = input('video_id', 0); $model = new EnterpriseModel(); - return $model->deleteVideo([ [ 'video_id', '=', $video_id],['site_id','=',$this->site_id] ]); + return $model->deleteVideo([ [ 'video_id', '=', $video_id ], [ 'site_id', '=', $this->site_id ] ]); + } + } + + /***************************************视频号部分****************************************/ + /** + * 视频号列表 + */ + public function channellists(){ + if (request()->isJson()) { + $page = input('page', 1); + $page_size = input('page_size', PAGE_LIST_ROWS); + $search_text = input('search_text', ''); + $channel_type = input('channel_type', ''); + $is_show = input('is_show', ''); + $order = input('order', ''); + $condition = [ [ 'site_id', '=', $this->site_id ] ]; + if ($search_text) { + $condition[] = [ 'channel_name', 'like', '%' . $search_text . '%' ]; + } + if ($channel_type) { + $condition[] = [ 'channel_type', '=', $channel_type ]; + } + if ($is_show !== '') { + $condition[] = [ 'is_show', '=', $is_show ]; + } + $order_by = $order ? $order : 'create_time desc'; + $model = new EnterpriseModel(); + $list = $model->getChannelPageList($condition, $page, $page_size, $order_by); + return $list; + } else { + return $this->fetch('enterprise/channel/lists'); + } + } + + /** + * 添加视频号 + */ + public function channeladd() + { + $model = new EnterpriseModel(); + if (request()->isJson()) { + $data = [ + 'site_id' => $this->site_id, + 'channel_type' => input('channel_type', 'wechat'), + 'channel_name' => input('channel_name', ''), + 'avatar_image_type' => input('avatar_image_type', 'upload'), + 'avatar_url' => input('avatar_url', ''), + 'video_title' => input('video_title', ''), + 'feed_id' => input('feed_id', ''), + 'cover_image_type' => input('cover_image_type', 'upload'), + 'cover_url' => input('cover_url', ''), + 'sort' => input('sort', 0), + 'is_show' => input('is_show', 1), + 'create_time' => time() + ]; + return $model->addChannel($data); + } else { + return $this->fetch('enterprise/channel/add'); + } + } + + /** + * 编辑视频号 + */ + public function channeledit() + { + $channel_id = input('channel_id', 0); + $model = new EnterpriseModel(); + if (request()->isJson()) { + $data = [ + 'channel_id' => $channel_id, + 'site_id' => $this->site_id, + 'channel_type' => input('channel_type', 'wechat'), + 'channel_name' => input('channel_name', ''), + 'avatar_image_type' => input('avatar_image_type', 'upload'), + 'avatar_url' => input('avatar_url', ''), + 'video_title' => input('video_title', ''), + 'feed_id' => input('feed_id', ''), + 'cover_image_type' => input('cover_image_type', 'upload'), + 'cover_url' => input('cover_url', ''), + 'sort' => input('sort', 0), + 'is_show' => input('is_show', 1), + ]; + return $model->editChannel($data); + } else { + $this->assign('channel_id', $channel_id); + $article_info = $model->getChannelInfo([ [ 'channel_id', '=', $channel_id ] ]); + $this->assign('info', $article_info[ 'data' ]); + return $this->fetch('enterprise/channel/edit'); + } + } + + /** + * 删除视频号 + */ + public function channeldelete() + { + if (request()->isJson()) { + $channel_id = input('channel_id', 0); + $model = new EnterpriseModel(); + return $model->deleteChannel([ [ 'channel_id', '=', $channel_id ], [ 'site_id', '=', $this->site_id ] ]); } } } \ No newline at end of file diff --git a/src/addon/personnel/shop/view/enterprise/channel/add.html b/src/addon/personnel/shop/view/enterprise/channel/add.html new file mode 100644 index 000000000..5a87c4342 --- /dev/null +++ b/src/addon/personnel/shop/view/enterprise/channel/add.html @@ -0,0 +1,364 @@ + + + + + + +
点击上传
+点击上传
+点击上传
+点击上传
+$p7IzO$LwkDf8wC^zP(u^|Zi!oDg~@6S!efjvl4R_Z 点击上传 点击上传 点击上传 点击上传DiHRp%b6u@m8JokiO^2r{scWm-p9777Y5#6#4|QEVW!T-
z;I-}j?pFJEN3)dN(J&d4SrbVnE>7OlO`ADB>D38zRQTv0@y08TzD-inZG<)L)*X5b
z)k%d$ve0vVF>Ag{8gTF@Yt?hn_~X-7#qXn#9LQ}hMNmqQEC~c+Vq!c!Jtf;_b92vn
zR3p%0NI@m
+
+
基础设置
';
+
+ // 根据微信视频号组件的要求,填写视频号的标题和描述
+
+ // 填写视频号名称
+ tpl += '");
+ $("input[name='avatar_url_upload']").val(avatarImg);
imageCollection.push(avatarImg);
}else{
$("#avatarImg").html("
");
@@ -162,6 +161,7 @@
if(coverImg){
if(coverImageType == 'upload'){
$("#coverImg").html("
");
+ $("input[name='cover_url_upload']").val(coverImg);
}else{
$("#coverImg").html("
");
}
@@ -315,6 +315,8 @@
delete field.avatar_url_upload;
delete field.cover_url_upload;
field.status = 1;
+
+ console.log('添加:提交的数据', field)
formSubmit(field)
});
});
diff --git a/src/addon/personnel/shop/view/enterprise/channel/edit.html b/src/addon/personnel/shop/view/enterprise/channel/edit.html
index a86b17d68..d8727d3c2 100644
--- a/src/addon/personnel/shop/view/enterprise/channel/edit.html
+++ b/src/addon/personnel/shop/view/enterprise/channel/edit.html
@@ -54,8 +54,7 @@
");
+ $("input[name='avatar_url_upload']").val(avatarImg);
imageCollection.push(avatarImg);
}else{
$("#avatarImg").html("
");
@@ -162,6 +161,7 @@
if(coverImg){
if(coverImageType == 'upload'){
$("#coverImg").html("
");
+ $("input[name='cover_url_upload']").val(coverImg);
}else{
$("#coverImg").html("
");
}
@@ -315,6 +315,8 @@
delete field.avatar_url_upload;
delete field.cover_url_upload;
field.status = 1;
+
+ console.log('编辑:提交的数据', field)
formSubmit(field)
});
});
diff --git a/src/addon/personnel/shop/view/enterprise/channel/lists.html b/src/addon/personnel/shop/view/enterprise/channel/lists.html
index 06c041400..b243bb764 100644
--- a/src/addon/personnel/shop/view/enterprise/channel/lists.html
+++ b/src/addon/personnel/shop/view/enterprise/channel/lists.html
@@ -48,11 +48,11 @@
-
+
+
+
+
+
diff --git a/src/addon/personnel/shop/view/enterprise/channel/edit.html b/src/addon/personnel/shop/view/enterprise/channel/edit.html
index 0d5d3c543..ec92a1292 100644
--- a/src/addon/personnel/shop/view/enterprise/channel/edit.html
+++ b/src/addon/personnel/shop/view/enterprise/channel/edit.html
@@ -58,7 +58,7 @@
x
-