package main import ( "C" "encoding/json" "fmt" "newsffi/model" // "time" // This is not used directly in main.go "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" ) // --- Helper Structs for JSON RPC --- type NewsListParams struct { Where map[string]interface{} `json:"where"` Order string `json:"order"` Page int `json:"page"` Limit int `json:"limit"` } type NewsListResult struct { Total int64 `json:"total"` Data []model.News `json:"data"` } // Config struct to map the yaml structure type Config struct { Database DatabaseConfig `yaml:"database"` } type DatabaseConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` User string `yaml:"user"` Password string `yaml:"password"` DbName string `yaml:"dbname"` Charset string `yaml:"charset"` } // Global database connection pool var db *gorm.DB // --- Initialization Function --- //export Initialize func Initialize(host, user, password, dbname, charset *C.char, port C.int) *C.char { // Prevent re-initialization if db != nil { return C.CString(`{"status": "ok", "message": "already initialized"}`) } goHost := C.GoString(host) goUser := C.GoString(user) goPassword := C.GoString(password) goDbname := C.GoString(dbname) goCharset := C.GoString(charset) goPort := int(port) var err error dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local", goUser, goPassword, goHost, goPort, goDbname, goCharset, ) gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), // Use Silent to avoid excessive logging }) if err != nil { db = nil // Ensure db is nil on failure return C.CString(fmt.Sprintf(`{"error": "failed to connect database: %v"}`, err)) } db = gormDB // Auto-migrate schema on first successful initialization err = db.AutoMigrate(&model.NewsCategory{}, &model.News{}) if err != nil { db = nil // Rollback on failure return C.CString(fmt.Sprintf(`{"error": "database migration failed: %v"}`, err)) } return C.CString(`{"status": "ok"}`) } // --- Category Service Functions --- //export GetCategoryList func GetCategoryList(whereJson *C.char) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } var where map[string]interface{} goWhereJson := C.GoString(whereJson) if goWhereJson != "" && goWhereJson != "{}" { if err := json.Unmarshal([]byte(goWhereJson), &where); err != nil { return C.CString(fmt.Sprintf(`{"error": "invalid where json: %s"}`, err.Error())) } } var categories []model.NewsCategory query := db.Model(&model.NewsCategory{}) if len(where) > 0 { query = query.Where(where) } if err := query.Order("sort asc, id desc").Find(&categories).Error; err != nil { return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error())) } resultJson, err := json.Marshal(categories) if err != nil { return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error())) } return C.CString(string(resultJson)) } //export GetCategoryByID func GetCategoryByID(id C.int) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } var category model.NewsCategory if err := db.First(&category, int(id)).Error; err != nil { if err == gorm.ErrRecordNotFound { return C.CString(fmt.Sprintf(`{"error": "category with id %d not found"}`, id)) } return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error())) } resultJson, err := json.Marshal(category) if err != nil { return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error())) } return C.CString(string(resultJson)) } //export CreateCategory func CreateCategory(dataJson *C.char) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } var category model.NewsCategory goDataJson := C.GoString(dataJson) if err := json.Unmarshal([]byte(goDataJson), &category); err != nil { return C.CString(fmt.Sprintf(`{"error": "invalid data json: %s"}`, err.Error())) } category.ID = 0 if err := db.Create(&category).Error; err != nil { return C.CString(fmt.Sprintf(`{"error": "create failed: %s"}`, err.Error())) } resultJson, err := json.Marshal(category) if err != nil { return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error())) } return C.CString(string(resultJson)) } //export UpdateCategory func UpdateCategory(id C.int, dataJson *C.char) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } var category model.NewsCategory if err := db.First(&category, int(id)).Error; err != nil { if err == gorm.ErrRecordNotFound { return C.CString(fmt.Sprintf(`{"error": "category with id %d not found"}`, id)) } return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error())) } var updateData map[string]interface{} goDataJson := C.GoString(dataJson) if err := json.Unmarshal([]byte(goDataJson), &updateData); err != nil { return C.CString(fmt.Sprintf(`{"error": "invalid data json: %s"}`, err.Error())) } delete(updateData, "id") delete(updateData, "ID") if err := db.Model(&category).Updates(updateData).Error; err != nil { return C.CString(fmt.Sprintf(`{"error": "update failed: %s"}`, err.Error())) } resultJson, err := json.Marshal(category) if err != nil { return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error())) } return C.CString(string(resultJson)) } //export DeleteCategory func DeleteCategory(id C.int) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } result := db.Delete(&model.NewsCategory{}, int(id)) if result.Error != nil { return C.CString(fmt.Sprintf(`{"error": "delete failed: %s"}`, result.Error.Error())) } if result.RowsAffected == 0 { return C.CString(fmt.Sprintf(`{"error": "category with id %d not found or already deleted"}`, id)) } return C.CString(`{"status": "ok"}`) } // --- News Service Functions --- //export GetNewsList func GetNewsList(paramsJson *C.char) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } var params NewsListParams goParamsJson := C.GoString(paramsJson) if goParamsJson != "" && goParamsJson != "{}" { if err := json.Unmarshal([]byte(goParamsJson), ¶ms); err != nil { return C.CString(fmt.Sprintf(`{"error": "invalid params json: %s"}`, err.Error())) } } if params.Page <= 0 { params.Page = 1 } if params.Limit <= 0 { params.Limit = 20 } if params.Order == "" { params.Order = "is_top desc, id desc" } var news []model.News var total int64 query := db.Model(&model.News{}) if len(params.Where) > 0 { query = query.Where(params.Where) } if err := query.Count(&total).Error; err != nil { return C.CString(fmt.Sprintf(`{"error": "count query failed: %s"}`, err.Error())) } offset := (params.Page - 1) * params.Limit if err := query.Order(params.Order).Offset(offset).Limit(params.Limit).Find(&news).Error; err != nil { return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error())) } result := NewsListResult{ Total: total, Data: news, } resultJson, err := json.Marshal(result) if err != nil { return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error())) } return C.CString(string(resultJson)) } //export GetNewsByID func GetNewsByID(id C.int) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } var news model.News if err := db.First(&news, int(id)).Error; err != nil { if err == gorm.ErrRecordNotFound { return C.CString(fmt.Sprintf(`{"error": "news with id %d not found"}`, id)) } return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error())) } resultJson, err := json.Marshal(news) if err != nil { return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error())) } return C.CString(string(resultJson)) } //export CreateNews func CreateNews(dataJson *C.char) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } var news model.News goDataJson := C.GoString(dataJson) if err := json.Unmarshal([]byte(goDataJson), &news); err != nil { return C.CString(fmt.Sprintf(`{"error": "invalid data json: %s"}`, err.Error())) } news.ID = 0 if err := db.Create(&news).Error; err != nil { return C.CString(fmt.Sprintf(`{"error": "create failed: %s"}`, err.Error())) } resultJson, err := json.Marshal(news) if err != nil { return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error())) } return C.CString(string(resultJson)) } //export UpdateNews func UpdateNews(id C.int, dataJson *C.char) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } var news model.News if err := db.First(&news, int(id)).Error; err != nil { if err == gorm.ErrRecordNotFound { return C.CString(fmt.Sprintf(`{"error": "news with id %d not found"}`, id)) } return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error())) } var updateData map[string]interface{} goDataJson := C.GoString(dataJson) if err := json.Unmarshal([]byte(goDataJson), &updateData); err != nil { return C.CString(fmt.Sprintf(`{"error": "invalid data json: %s"}`, err.Error())) } delete(updateData, "id") delete(updateData, "ID") if err := db.Model(&news).Updates(updateData).Error; err != nil { return C.CString(fmt.Sprintf(`{"error": "update failed: %s"}`, err.Error())) } resultJson, err := json.Marshal(news) if err != nil { return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error())) } return C.CString(string(resultJson)) } //export DeleteNews func DeleteNews(id C.int) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } result := db.Delete(&model.News{}, int(id)) if result.Error != nil { return C.CString(fmt.Sprintf(`{"error": "delete failed: %s"}`, result.Error.Error())) } if result.RowsAffected == 0 { return C.CString(fmt.Sprintf(`{"error": "news with id %d not found or already deleted"}`, id)) } return C.CString(`{"status": "ok"}`) } //export IncrementNewsViews func IncrementNewsViews(id C.int) *C.char { if db == nil { return C.CString(`{"error": "database connection is not initialized"}`) } // 使用原生SQL更新views字段,避免并发问题 result := db.Exec("UPDATE cy_news SET views = views + 1 WHERE id = ? AND delete_time IS NULL", int(id)) if result.Error != nil { return C.CString(fmt.Sprintf(`{"error": "increment views failed: %s"}`, result.Error.Error())) } if result.RowsAffected == 0 { return C.CString(fmt.Sprintf(`{"error": "news with id %d not found"}`, id)) } // 返回更新后的文章信息 var news model.News if err := db.First(&news, int(id)).Error; err != nil { // 即使查询失败,views已经更新成功,返回成功状态 return C.CString(`{"status": "ok", "message": "views incremented successfully"}`) } resultJson, err := json.Marshal(news) if err != nil { return C.CString(`{"status": "ok", "message": "views incremented successfully"}`) } return C.CString(string(resultJson)) } // main is required for building a C shared library. func main() {}