新版可用
This commit is contained in:
117
backend/pkg/database/db.go
Normal file
117
backend/pkg/database/db.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// DB 数据库实例
|
||||
type DB struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
// InitDB 初始化数据库
|
||||
func InitDB(dbPath string) (*DB, error) {
|
||||
// 确保数据库目录存在
|
||||
dbDir := filepath.Dir(dbPath)
|
||||
if err := os.MkdirAll(dbDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("创建数据库目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 打开数据库连接(使用modernc.org/sqlite驱动)
|
||||
db, err := sql.Open("sqlite", dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("打开数据库失败: %v", err)
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
if err := db.Ping(); err != nil {
|
||||
return nil, fmt.Errorf("数据库连接测试失败: %v", err)
|
||||
}
|
||||
|
||||
// 创建表
|
||||
if err := createTables(db); err != nil {
|
||||
return nil, fmt.Errorf("创建数据表失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("✅ 数据库初始化成功:", dbPath)
|
||||
return &DB{db}, nil
|
||||
}
|
||||
|
||||
// createTables 创建数据表
|
||||
func createTables(db *sql.DB) error {
|
||||
// 公众号表
|
||||
officialAccountTable := `
|
||||
CREATE TABLE IF NOT EXISTS official_accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
biz TEXT NOT NULL UNIQUE,
|
||||
nickname TEXT NOT NULL,
|
||||
homepage TEXT,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_biz ON official_accounts(biz);
|
||||
CREATE INDEX IF NOT EXISTS idx_nickname ON official_accounts(nickname);
|
||||
`
|
||||
|
||||
// 文章表
|
||||
articleTable := `
|
||||
CREATE TABLE IF NOT EXISTS articles (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
official_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
author TEXT,
|
||||
link TEXT UNIQUE,
|
||||
publish_time TEXT,
|
||||
create_time TEXT,
|
||||
comment_id TEXT,
|
||||
read_num INTEGER DEFAULT 0,
|
||||
like_num INTEGER DEFAULT 0,
|
||||
share_num INTEGER DEFAULT 0,
|
||||
content_preview TEXT,
|
||||
paragraph_count INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (official_id) REFERENCES official_accounts(id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_official_id ON articles(official_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_title ON articles(title);
|
||||
CREATE INDEX IF NOT EXISTS idx_publish_time ON articles(publish_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_link ON articles(link);
|
||||
`
|
||||
|
||||
// 文章内容表
|
||||
articleContentTable := `
|
||||
CREATE TABLE IF NOT EXISTS article_contents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
article_id INTEGER NOT NULL UNIQUE,
|
||||
html_content TEXT,
|
||||
text_content TEXT,
|
||||
paragraphs TEXT,
|
||||
images TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_id ON article_contents(article_id);
|
||||
`
|
||||
|
||||
// 执行创建表语句
|
||||
tables := []string{officialAccountTable, articleTable, articleContentTable}
|
||||
for _, table := range tables {
|
||||
if _, err := db.Exec(table); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close 关闭数据库连接
|
||||
func (db *DB) Close() error {
|
||||
return db.DB.Close()
|
||||
}
|
||||
76
backend/pkg/database/models.go
Normal file
76
backend/pkg/database/models.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// OfficialAccount 公众号信息
|
||||
type OfficialAccount struct {
|
||||
ID int64 `json:"id"`
|
||||
Biz string `json:"biz"` // 公众号唯一标识
|
||||
Nickname string `json:"nickname"` // 公众号名称
|
||||
Homepage string `json:"homepage"` // 公众号主页链接
|
||||
Description string `json:"description"` // 公众号描述
|
||||
CreatedAt time.Time `json:"created_at"` // 创建时间
|
||||
UpdatedAt time.Time `json:"updated_at"` // 更新时间
|
||||
}
|
||||
|
||||
// Article 文章信息
|
||||
type Article struct {
|
||||
ID int64 `json:"id"`
|
||||
OfficialID int64 `json:"official_id"` // 关联的公众号ID
|
||||
Title string `json:"title"` // 文章标题
|
||||
Author string `json:"author"` // 作者
|
||||
Link string `json:"link"` // 文章链接
|
||||
PublishTime string `json:"publish_time"` // 发布时间
|
||||
CreateTime string `json:"create_time"` // 创建时间(抓取时间)
|
||||
CommentID string `json:"comment_id"` // 评论ID
|
||||
ReadNum int `json:"read_num"` // 阅读数
|
||||
LikeNum int `json:"like_num"` // 点赞数
|
||||
ShareNum int `json:"share_num"` // 分享数
|
||||
ContentPreview string `json:"content_preview"` // 内容预览(前200字)
|
||||
ParagraphCount int `json:"paragraph_count"` // 段落数
|
||||
CreatedAt time.Time `json:"created_at"` // 数据库创建时间
|
||||
UpdatedAt time.Time `json:"updated_at"` // 数据库更新时间
|
||||
}
|
||||
|
||||
// ArticleContent 文章详细内容
|
||||
type ArticleContent struct {
|
||||
ID int64 `json:"id"`
|
||||
ArticleID int64 `json:"article_id"` // 关联的文章ID
|
||||
HtmlContent string `json:"html_content"` // HTML原始内容
|
||||
TextContent string `json:"text_content"` // 纯文本内容
|
||||
Paragraphs string `json:"paragraphs"` // 段落内容(JSON数组)
|
||||
Images string `json:"images"` // 图片链接(JSON数组)
|
||||
CreatedAt time.Time `json:"created_at"` // 创建时间
|
||||
}
|
||||
|
||||
// ArticleListItem 文章列表项(用于API返回)
|
||||
type ArticleListItem struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Author string `json:"author"`
|
||||
PublishTime string `json:"publish_time"`
|
||||
ReadNum int `json:"read_num"`
|
||||
LikeNum int `json:"like_num"`
|
||||
OfficialName string `json:"official_name"`
|
||||
ContentPreview string `json:"content_preview"`
|
||||
}
|
||||
|
||||
// ArticleDetail 文章详情(用于API返回)
|
||||
type ArticleDetail struct {
|
||||
Article
|
||||
OfficialName string `json:"official_name"`
|
||||
HtmlContent string `json:"html_content"`
|
||||
TextContent string `json:"text_content"`
|
||||
Paragraphs []string `json:"paragraphs"`
|
||||
Images []string `json:"images"`
|
||||
}
|
||||
|
||||
// Statistics 统计信息
|
||||
type Statistics struct {
|
||||
TotalOfficials int `json:"total_officials"` // 公众号总数
|
||||
TotalArticles int `json:"total_articles"` // 文章总数
|
||||
TotalReadNum int `json:"total_read_num"` // 总阅读数
|
||||
TotalLikeNum int `json:"total_like_num"` // 总点赞数
|
||||
}
|
||||
455
backend/pkg/database/repository.go
Normal file
455
backend/pkg/database/repository.go
Normal file
@@ -0,0 +1,455 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OfficialAccountRepository 公众号数据仓库
|
||||
type OfficialAccountRepository struct {
|
||||
db *DB
|
||||
}
|
||||
|
||||
// NewOfficialAccountRepository 创建公众号仓库
|
||||
func NewOfficialAccountRepository(db *DB) *OfficialAccountRepository {
|
||||
return &OfficialAccountRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建公众号
|
||||
func (r *OfficialAccountRepository) Create(account *OfficialAccount) (int64, error) {
|
||||
result, err := r.db.Exec(`
|
||||
INSERT INTO official_accounts (biz, nickname, homepage, description)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`, account.Biz, account.Nickname, account.Homepage, account.Description)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
// GetByBiz 根据Biz获取公众号
|
||||
func (r *OfficialAccountRepository) GetByBiz(biz string) (*OfficialAccount, error) {
|
||||
account := &OfficialAccount{}
|
||||
err := r.db.QueryRow(`
|
||||
SELECT id, biz, nickname, homepage, description, created_at, updated_at
|
||||
FROM official_accounts WHERE biz = ?
|
||||
`, biz).Scan(&account.ID, &account.Biz, &account.Nickname, &account.Homepage,
|
||||
&account.Description, &account.CreatedAt, &account.UpdatedAt)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取公众号
|
||||
func (r *OfficialAccountRepository) GetByID(id int64) (*OfficialAccount, error) {
|
||||
account := &OfficialAccount{}
|
||||
err := r.db.QueryRow(`
|
||||
SELECT id, biz, nickname, homepage, description, created_at, updated_at
|
||||
FROM official_accounts WHERE id = ?
|
||||
`, id).Scan(&account.ID, &account.Biz, &account.Nickname, &account.Homepage,
|
||||
&account.Description, &account.CreatedAt, &account.UpdatedAt)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// List 获取所有公众号列表
|
||||
func (r *OfficialAccountRepository) List() ([]*OfficialAccount, error) {
|
||||
rows, err := r.db.Query(`
|
||||
SELECT id, biz, nickname, homepage, description, created_at, updated_at
|
||||
FROM official_accounts ORDER BY created_at DESC
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var accounts []*OfficialAccount
|
||||
for rows.Next() {
|
||||
account := &OfficialAccount{}
|
||||
err := rows.Scan(&account.ID, &account.Biz, &account.Nickname, &account.Homepage,
|
||||
&account.Description, &account.CreatedAt, &account.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accounts = append(accounts, account)
|
||||
}
|
||||
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
// Update 更新公众号信息
|
||||
func (r *OfficialAccountRepository) Update(account *OfficialAccount) error {
|
||||
_, err := r.db.Exec(`
|
||||
UPDATE official_accounts
|
||||
SET nickname = ?, homepage = ?, description = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`, account.Nickname, account.Homepage, account.Description, account.ID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ArticleRepository 文章数据仓库
|
||||
type ArticleRepository struct {
|
||||
db *DB
|
||||
}
|
||||
|
||||
// NewArticleRepository 创建文章仓库
|
||||
func NewArticleRepository(db *DB) *ArticleRepository {
|
||||
return &ArticleRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建文章
|
||||
func (r *ArticleRepository) Create(article *Article) (int64, error) {
|
||||
result, err := r.db.Exec(`
|
||||
INSERT INTO articles (
|
||||
official_id, title, author, link, publish_time, create_time,
|
||||
comment_id, read_num, like_num, share_num, content_preview, paragraph_count
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, article.OfficialID, article.Title, article.Author, article.Link,
|
||||
article.PublishTime, article.CreateTime, article.CommentID,
|
||||
article.ReadNum, article.LikeNum, article.ShareNum,
|
||||
article.ContentPreview, article.ParagraphCount)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取文章
|
||||
func (r *ArticleRepository) GetByID(id int64) (*Article, error) {
|
||||
article := &Article{}
|
||||
err := r.db.QueryRow(`
|
||||
SELECT id, official_id, title, author, link, publish_time, create_time,
|
||||
comment_id, read_num, like_num, share_num, content_preview,
|
||||
paragraph_count, created_at, updated_at
|
||||
FROM articles WHERE id = ?
|
||||
`, id).Scan(&article.ID, &article.OfficialID, &article.Title, &article.Author,
|
||||
&article.Link, &article.PublishTime, &article.CreateTime, &article.CommentID,
|
||||
&article.ReadNum, &article.LikeNum, &article.ShareNum, &article.ContentPreview,
|
||||
&article.ParagraphCount, &article.CreatedAt, &article.UpdatedAt)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return article, nil
|
||||
}
|
||||
|
||||
// GetByLink 根据链接获取文章
|
||||
func (r *ArticleRepository) GetByLink(link string) (*Article, error) {
|
||||
article := &Article{}
|
||||
err := r.db.QueryRow(`
|
||||
SELECT id, official_id, title, author, link, publish_time, create_time,
|
||||
comment_id, read_num, like_num, share_num, content_preview,
|
||||
paragraph_count, created_at, updated_at
|
||||
FROM articles WHERE link = ?
|
||||
`, link).Scan(&article.ID, &article.OfficialID, &article.Title, &article.Author,
|
||||
&article.Link, &article.PublishTime, &article.CreateTime, &article.CommentID,
|
||||
&article.ReadNum, &article.LikeNum, &article.ShareNum, &article.ContentPreview,
|
||||
&article.ParagraphCount, &article.CreatedAt, &article.UpdatedAt)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return article, nil
|
||||
}
|
||||
|
||||
// List 获取文章列表(分页)
|
||||
func (r *ArticleRepository) List(officialID int64, page, pageSize int) ([]*ArticleListItem, int, error) {
|
||||
// 构建查询条件
|
||||
whereClause := ""
|
||||
args := []interface{}{}
|
||||
|
||||
if officialID > 0 {
|
||||
whereClause = "WHERE a.official_id = ?"
|
||||
args = append(args, officialID)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
countQuery := fmt.Sprintf("SELECT COUNT(*) FROM articles a %s", whereClause)
|
||||
var total int
|
||||
err := r.db.QueryRow(countQuery, args...).Scan(&total)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
offset := (page - 1) * pageSize
|
||||
listQuery := fmt.Sprintf(`
|
||||
SELECT a.id, a.title, a.author, a.publish_time, a.read_num, a.like_num,
|
||||
a.content_preview, o.nickname
|
||||
FROM articles a
|
||||
LEFT JOIN official_accounts o ON a.official_id = o.id
|
||||
%s
|
||||
ORDER BY a.publish_time DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`, whereClause)
|
||||
|
||||
args = append(args, pageSize, offset)
|
||||
rows, err := r.db.Query(listQuery, args...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []*ArticleListItem
|
||||
for rows.Next() {
|
||||
item := &ArticleListItem{}
|
||||
err := rows.Scan(&item.ID, &item.Title, &item.Author, &item.PublishTime,
|
||||
&item.ReadNum, &item.LikeNum, &item.ContentPreview, &item.OfficialName)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// Search 搜索文章
|
||||
func (r *ArticleRepository) Search(keyword string, page, pageSize int) ([]*ArticleListItem, int, error) {
|
||||
keyword = "%" + keyword + "%"
|
||||
|
||||
// 获取总数
|
||||
var total int
|
||||
err := r.db.QueryRow(`
|
||||
SELECT COUNT(*) FROM articles WHERE title LIKE ? OR author LIKE ?
|
||||
`, keyword, keyword).Scan(&total)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
offset := (page - 1) * pageSize
|
||||
rows, err := r.db.Query(`
|
||||
SELECT a.id, a.title, a.author, a.publish_time, a.read_num, a.like_num,
|
||||
a.content_preview, o.nickname
|
||||
FROM articles a
|
||||
LEFT JOIN official_accounts o ON a.official_id = o.id
|
||||
WHERE a.title LIKE ? OR a.author LIKE ?
|
||||
ORDER BY a.publish_time DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`, keyword, keyword, pageSize, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []*ArticleListItem
|
||||
for rows.Next() {
|
||||
item := &ArticleListItem{}
|
||||
err := rows.Scan(&item.ID, &item.Title, &item.Author, &item.PublishTime,
|
||||
&item.ReadNum, &item.LikeNum, &item.ContentPreview, &item.OfficialName)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// Update 更新文章信息
|
||||
func (r *ArticleRepository) Update(article *Article) error {
|
||||
_, err := r.db.Exec(`
|
||||
UPDATE articles
|
||||
SET read_num = ?, like_num = ?, share_num = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`, article.ReadNum, article.LikeNum, article.ShareNum, article.ID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ArticleContentRepository 文章内容数据仓库
|
||||
type ArticleContentRepository struct {
|
||||
db *DB
|
||||
}
|
||||
|
||||
// NewArticleContentRepository 创建文章内容仓库
|
||||
func NewArticleContentRepository(db *DB) *ArticleContentRepository {
|
||||
return &ArticleContentRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建文章内容
|
||||
func (r *ArticleContentRepository) Create(content *ArticleContent) (int64, error) {
|
||||
result, err := r.db.Exec(`
|
||||
INSERT INTO article_contents (article_id, html_content, text_content, paragraphs, images)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`, content.ArticleID, content.HtmlContent, content.TextContent,
|
||||
content.Paragraphs, content.Images)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
// GetByArticleID 根据文章ID获取内容
|
||||
func (r *ArticleContentRepository) GetByArticleID(articleID int64) (*ArticleContent, error) {
|
||||
content := &ArticleContent{}
|
||||
err := r.db.QueryRow(`
|
||||
SELECT id, article_id, html_content, text_content, paragraphs, images, created_at
|
||||
FROM article_contents WHERE article_id = ?
|
||||
`, articleID).Scan(&content.ID, &content.ArticleID, &content.HtmlContent,
|
||||
&content.TextContent, &content.Paragraphs, &content.Images, &content.CreatedAt)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// GetArticleDetail 获取文章详情(包含内容)
|
||||
func (r *ArticleContentRepository) GetArticleDetail(articleID int64) (*ArticleDetail, error) {
|
||||
detail := &ArticleDetail{}
|
||||
var paragraphsJSON, imagesJSON string
|
||||
|
||||
err := r.db.QueryRow(`
|
||||
SELECT a.id, a.official_id, a.title, a.author, a.link, a.publish_time,
|
||||
a.create_time, a.comment_id, a.read_num, a.like_num, a.share_num,
|
||||
a.content_preview, a.paragraph_count, a.created_at, a.updated_at,
|
||||
o.nickname, c.html_content, c.text_content, c.paragraphs, c.images
|
||||
FROM articles a
|
||||
LEFT JOIN official_accounts o ON a.official_id = o.id
|
||||
LEFT JOIN article_contents c ON a.id = c.article_id
|
||||
WHERE a.id = ?
|
||||
`, articleID).Scan(
|
||||
&detail.ID, &detail.OfficialID, &detail.Title, &detail.Author,
|
||||
&detail.Link, &detail.PublishTime, &detail.CreateTime, &detail.CommentID,
|
||||
&detail.ReadNum, &detail.LikeNum, &detail.ShareNum, &detail.ContentPreview,
|
||||
&detail.ParagraphCount, &detail.CreatedAt, &detail.UpdatedAt,
|
||||
&detail.OfficialName, &detail.HtmlContent, &detail.TextContent,
|
||||
¶graphsJSON, &imagesJSON,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 解析JSON数组
|
||||
if paragraphsJSON != "" {
|
||||
json.Unmarshal([]byte(paragraphsJSON), &detail.Paragraphs)
|
||||
}
|
||||
if imagesJSON != "" {
|
||||
json.Unmarshal([]byte(imagesJSON), &detail.Images)
|
||||
}
|
||||
|
||||
return detail, nil
|
||||
}
|
||||
|
||||
// GetStatistics 获取统计信息
|
||||
func (db *DB) GetStatistics() (*Statistics, error) {
|
||||
stats := &Statistics{}
|
||||
|
||||
err := db.QueryRow(`
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM official_accounts) as total_officials,
|
||||
(SELECT COUNT(*) FROM articles) as total_articles,
|
||||
(SELECT COALESCE(SUM(read_num), 0) FROM articles) as total_read_num,
|
||||
(SELECT COALESCE(SUM(like_num), 0) FROM articles) as total_like_num
|
||||
`).Scan(&stats.TotalOfficials, &stats.TotalArticles, &stats.TotalReadNum, &stats.TotalLikeNum)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// BatchInsertArticles 批量插入文章
|
||||
func (r *ArticleRepository) BatchInsertArticles(articles []*Article) error {
|
||||
if len(articles) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
tx, err := r.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
stmt, err := tx.Prepare(`
|
||||
INSERT OR IGNORE INTO articles (
|
||||
official_id, title, author, link, publish_time, create_time,
|
||||
comment_id, read_num, like_num, share_num, content_preview, paragraph_count
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, article := range articles {
|
||||
_, err = stmt.Exec(
|
||||
article.OfficialID, article.Title, article.Author, article.Link,
|
||||
article.PublishTime, article.CreateTime, article.CommentID,
|
||||
article.ReadNum, article.LikeNum, article.ShareNum,
|
||||
article.ContentPreview, article.ParagraphCount,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// Helper function: 将字符串数组转换为JSON字符串
|
||||
func StringsToJSON(strs []string) string {
|
||||
if len(strs) == 0 {
|
||||
return "[]"
|
||||
}
|
||||
data, _ := json.Marshal(strs)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// Helper function: 生成内容预览
|
||||
func GeneratePreview(content string, maxLen int) string {
|
||||
if len(content) <= maxLen {
|
||||
return content
|
||||
}
|
||||
// 移除换行符和多余空格
|
||||
content = strings.ReplaceAll(content, "\n", " ")
|
||||
content = strings.ReplaceAll(content, "\r", "")
|
||||
content = strings.Join(strings.Fields(content), " ")
|
||||
|
||||
if len(content) <= maxLen {
|
||||
return content
|
||||
}
|
||||
return content[:maxLen] + "..."
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user