423 lines
12 KiB
Go
423 lines
12 KiB
Go
package repositories
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"log"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/niangaodev/art-code/config"
|
||
"github.com/niangaodev/art-code/models"
|
||
)
|
||
|
||
// unescapeJSONString 解码转义的 JSON 字符串
|
||
// 处理两种情况:
|
||
// 1. 被引号包裹的转义 JSON 字符串:`"[\"Vue 3\",...]"`
|
||
// 2. 包含转义引号的 JSON 字符串:`[\"Vue 3\",...]`
|
||
func unescapeJSONString(s string) (string, error) {
|
||
// 如果字符串以引号开头和结尾,说明是被引号包裹的转义 JSON 字符串
|
||
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
|
||
unquoted, err := strconv.Unquote(s)
|
||
if err != nil {
|
||
return s, err
|
||
}
|
||
return unquoted, nil
|
||
}
|
||
|
||
// 如果字符串包含转义的引号 \",需要将其转换为普通引号
|
||
// 例如:[\"Vue 3\",...] -> ["Vue 3",...]
|
||
if len(s) > 0 {
|
||
// 尝试直接解析,如果失败则尝试替换转义引号
|
||
var test interface{}
|
||
if err := json.Unmarshal([]byte(s), &test); err != nil {
|
||
// 如果解析失败,尝试将 \" 替换为 "
|
||
unescaped := s
|
||
// 替换转义的反斜杠+引号
|
||
// 注意:这里需要小心处理,因为 \\" 应该变成 \"
|
||
// 但 \" 应该变成 "
|
||
// 使用正则表达式或字符串替换
|
||
// 简单方法:将 \" 替换为 "(但需要确保不会误替换 \\")
|
||
// 更安全的方法:使用 json.Unmarshal 两次解析
|
||
// 或者使用 strings.ReplaceAll 但需要小心
|
||
|
||
// 尝试将 \" 替换为 "
|
||
unescaped = strings.ReplaceAll(unescaped, `\"`, `"`)
|
||
// 如果替换后能解析,返回替换后的字符串
|
||
if err2 := json.Unmarshal([]byte(unescaped), &test); err2 == nil {
|
||
return unescaped, nil
|
||
}
|
||
} else {
|
||
// 如果能直接解析,返回原字符串
|
||
return s, nil
|
||
}
|
||
}
|
||
|
||
// 如果都失败,返回原字符串
|
||
return s, nil
|
||
}
|
||
|
||
// GetPrimaryAboutProfile 获取主页个人资料
|
||
func GetPrimaryAboutProfile() (*models.AboutProfile, error) {
|
||
query := `
|
||
SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at
|
||
FROM about_profiles
|
||
WHERE is_primary = TRUE AND deleted_at = 0
|
||
LIMIT 1
|
||
`
|
||
row := config.DB.QueryRow(query)
|
||
|
||
var profile models.AboutProfile
|
||
if err := row.Scan(
|
||
&profile.ID,
|
||
&profile.Name,
|
||
&profile.Avatar,
|
||
&profile.Location,
|
||
&profile.Bio,
|
||
&profile.Email,
|
||
&profile.Wechat,
|
||
&profile.TechStack,
|
||
&profile.ExperiencesStr,
|
||
&profile.IsPrimary,
|
||
&profile.CreatedAt,
|
||
&profile.UpdatedAt,
|
||
&profile.DeletedAt,
|
||
); err != nil {
|
||
if err == sql.ErrNoRows {
|
||
// If no primary profile, try to get the first one
|
||
return GetFirstAboutProfile()
|
||
}
|
||
log.Printf("Error scanning primary about profile: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
// Unmarshal JSON - 确保 TechList 和 ExperienceList 始终是数组而不是 nil
|
||
profile.TechList = []string{}
|
||
profile.ExperienceList = []models.Experience{}
|
||
|
||
if profile.TechStack != "" {
|
||
if err := json.Unmarshal([]byte(profile.TechStack), &profile.TechList); err != nil {
|
||
log.Printf("Error unmarshaling techStack: %v, raw: %s", err, profile.TechStack)
|
||
profile.TechList = []string{}
|
||
}
|
||
}
|
||
|
||
if profile.ExperiencesStr != "" {
|
||
if err := json.Unmarshal([]byte(profile.ExperiencesStr), &profile.ExperienceList); err != nil {
|
||
log.Printf("Error unmarshaling experiences: %v, raw: %s", err, profile.ExperiencesStr)
|
||
profile.ExperienceList = []models.Experience{}
|
||
}
|
||
}
|
||
|
||
return &profile, nil
|
||
}
|
||
|
||
// GetFirstAboutProfile 获取第一个个人资料(备用)
|
||
func GetFirstAboutProfile() (*models.AboutProfile, error) {
|
||
query := `
|
||
SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at
|
||
FROM about_profiles
|
||
WHERE deleted_at = 0
|
||
ORDER BY id ASC
|
||
LIMIT 1
|
||
`
|
||
row := config.DB.QueryRow(query)
|
||
|
||
var profile models.AboutProfile
|
||
if err := row.Scan(
|
||
&profile.ID,
|
||
&profile.Name,
|
||
&profile.Avatar,
|
||
&profile.Location,
|
||
&profile.Bio,
|
||
&profile.Email,
|
||
&profile.Wechat,
|
||
&profile.TechStack,
|
||
&profile.ExperiencesStr,
|
||
&profile.IsPrimary,
|
||
&profile.CreatedAt,
|
||
&profile.UpdatedAt,
|
||
&profile.DeletedAt,
|
||
); err != nil {
|
||
if err == sql.ErrNoRows {
|
||
return nil, nil
|
||
}
|
||
log.Printf("Error scanning first about profile: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
// Unmarshal JSON - 确保 TechList 和 ExperienceList 始终是数组而不是 nil
|
||
profile.TechList = []string{}
|
||
profile.ExperienceList = []models.Experience{}
|
||
|
||
if profile.TechStack != "" {
|
||
// 先尝试解码转义的 JSON 字符串
|
||
unescaped, err := unescapeJSONString(profile.TechStack)
|
||
if err != nil {
|
||
log.Printf("Error unescaping techStack: %v, raw: %s", err, profile.TechStack)
|
||
} else {
|
||
if err := json.Unmarshal([]byte(unescaped), &profile.TechList); err != nil {
|
||
log.Printf("Error unmarshaling techStack: %v, raw: %s, unescaped: %s", err, profile.TechStack, unescaped)
|
||
profile.TechList = []string{}
|
||
}
|
||
}
|
||
}
|
||
|
||
if profile.ExperiencesStr != "" {
|
||
// 先尝试解码转义的 JSON 字符串
|
||
unescaped, err := unescapeJSONString(profile.ExperiencesStr)
|
||
if err != nil {
|
||
log.Printf("Error unescaping experiences: %v, raw: %s", err, profile.ExperiencesStr)
|
||
} else {
|
||
if err := json.Unmarshal([]byte(unescaped), &profile.ExperienceList); err != nil {
|
||
log.Printf("Error unmarshaling experiences: %v, raw: %s, unescaped: %s", err, profile.ExperiencesStr, unescaped)
|
||
profile.ExperienceList = []models.Experience{}
|
||
}
|
||
}
|
||
}
|
||
|
||
return &profile, nil
|
||
}
|
||
|
||
// GetAboutProfileByID 根据 ID 获取个人资料
|
||
func GetAboutProfileByID(id uint) (*models.AboutProfile, error) {
|
||
query := `
|
||
SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at
|
||
FROM about_profiles
|
||
WHERE id = ? AND deleted_at = 0
|
||
LIMIT 1
|
||
`
|
||
row := config.DB.QueryRow(query, id)
|
||
|
||
var profile models.AboutProfile
|
||
if err := row.Scan(
|
||
&profile.ID,
|
||
&profile.Name,
|
||
&profile.Avatar,
|
||
&profile.Location,
|
||
&profile.Bio,
|
||
&profile.Email,
|
||
&profile.Wechat,
|
||
&profile.TechStack,
|
||
&profile.ExperiencesStr,
|
||
&profile.IsPrimary,
|
||
&profile.CreatedAt,
|
||
&profile.UpdatedAt,
|
||
&profile.DeletedAt,
|
||
); err != nil {
|
||
if err == sql.ErrNoRows {
|
||
return nil, nil
|
||
}
|
||
log.Printf("Error scanning about profile by ID: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
// Unmarshal JSON - 确保 TechList 和 ExperienceList 始终是数组而不是 nil
|
||
profile.TechList = []string{}
|
||
profile.ExperienceList = []models.Experience{}
|
||
|
||
if profile.TechStack != "" {
|
||
// 先尝试解码转义的 JSON 字符串
|
||
unescaped, err := unescapeJSONString(profile.TechStack)
|
||
if err != nil {
|
||
log.Printf("Error unescaping techStack: %v, raw: %s", err, profile.TechStack)
|
||
} else {
|
||
if err := json.Unmarshal([]byte(unescaped), &profile.TechList); err != nil {
|
||
log.Printf("Error unmarshaling techStack: %v, raw: %s, unescaped: %s", err, profile.TechStack, unescaped)
|
||
profile.TechList = []string{}
|
||
}
|
||
}
|
||
}
|
||
|
||
if profile.ExperiencesStr != "" {
|
||
// 先尝试解码转义的 JSON 字符串
|
||
unescaped, err := unescapeJSONString(profile.ExperiencesStr)
|
||
if err != nil {
|
||
log.Printf("Error unescaping experiences: %v, raw: %s", err, profile.ExperiencesStr)
|
||
} else {
|
||
if err := json.Unmarshal([]byte(unescaped), &profile.ExperienceList); err != nil {
|
||
log.Printf("Error unmarshaling experiences: %v, raw: %s, unescaped: %s", err, profile.ExperiencesStr, unescaped)
|
||
profile.ExperienceList = []models.Experience{}
|
||
}
|
||
}
|
||
}
|
||
|
||
return &profile, nil
|
||
}
|
||
|
||
// GetAllAboutProfiles 获取所有个人资料(管理用)
|
||
func GetAllAboutProfiles() ([]models.AboutProfile, error) {
|
||
query := "SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at FROM about_profiles WHERE deleted_at = 0"
|
||
rows, err := config.DB.Query(query)
|
||
if err != nil {
|
||
log.Printf("Error querying about profiles: %v", err)
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
var profiles []models.AboutProfile
|
||
for rows.Next() {
|
||
var p models.AboutProfile
|
||
if err := rows.Scan(
|
||
&p.ID,
|
||
&p.Name,
|
||
&p.Avatar,
|
||
&p.Location,
|
||
&p.Bio,
|
||
&p.Email,
|
||
&p.Wechat,
|
||
&p.TechStack,
|
||
&p.ExperiencesStr,
|
||
&p.IsPrimary,
|
||
&p.CreatedAt,
|
||
&p.UpdatedAt,
|
||
&p.DeletedAt,
|
||
); err != nil {
|
||
continue
|
||
}
|
||
// Unmarshal JSON - 确保 TechList 和 ExperienceList 始终是数组而不是 nil
|
||
p.TechList = []string{}
|
||
p.ExperienceList = []models.Experience{}
|
||
|
||
if p.TechStack != "" {
|
||
// 先尝试解码转义的 JSON 字符串
|
||
unescaped, err := unescapeJSONString(p.TechStack)
|
||
if err != nil {
|
||
log.Printf("Error unescaping techStack: %v, raw: %s", err, p.TechStack)
|
||
} else {
|
||
if err := json.Unmarshal([]byte(unescaped), &p.TechList); err != nil {
|
||
log.Printf("Error unmarshaling techStack: %v, raw: %s, unescaped: %s", err, p.TechStack, unescaped)
|
||
p.TechList = []string{}
|
||
}
|
||
}
|
||
}
|
||
|
||
if p.ExperiencesStr != "" {
|
||
// 先尝试解码转义的 JSON 字符串
|
||
unescaped, err := unescapeJSONString(p.ExperiencesStr)
|
||
if err != nil {
|
||
log.Printf("Error unescaping experiences: %v, raw: %s", err, p.ExperiencesStr)
|
||
} else {
|
||
if err := json.Unmarshal([]byte(unescaped), &p.ExperienceList); err != nil {
|
||
log.Printf("Error unmarshaling experiences: %v, raw: %s, unescaped: %s", err, p.ExperiencesStr, unescaped)
|
||
p.ExperienceList = []models.Experience{}
|
||
}
|
||
}
|
||
}
|
||
profiles = append(profiles, p)
|
||
}
|
||
return profiles, nil
|
||
}
|
||
|
||
// CreateAboutProfile 创建个人资料
|
||
func CreateAboutProfile(profile *models.AboutProfile) error {
|
||
// 确保 TechList 和 ExperienceList 不为 nil
|
||
if profile.TechList == nil {
|
||
profile.TechList = []string{}
|
||
}
|
||
if profile.ExperienceList == nil {
|
||
profile.ExperienceList = []models.Experience{}
|
||
}
|
||
|
||
// Marshal JSON
|
||
techBytes, _ := json.Marshal(profile.TechList)
|
||
profile.TechStack = string(techBytes)
|
||
|
||
expBytes, _ := json.Marshal(profile.ExperienceList)
|
||
profile.ExperiencesStr = string(expBytes)
|
||
|
||
now := time.Now().Unix()
|
||
query := `
|
||
INSERT INTO about_profiles (name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
|
||
`
|
||
result, err := config.DB.Exec(
|
||
query,
|
||
profile.Name,
|
||
profile.Avatar,
|
||
profile.Location,
|
||
profile.Bio,
|
||
profile.Email,
|
||
profile.Wechat,
|
||
profile.TechStack,
|
||
profile.ExperiencesStr,
|
||
profile.IsPrimary,
|
||
now,
|
||
now,
|
||
)
|
||
if err != nil {
|
||
log.Printf("Error creating about profile: %v", err)
|
||
return err
|
||
}
|
||
|
||
id, err := result.LastInsertId()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
profile.ID = uint(id)
|
||
profile.CreatedAt = now
|
||
profile.UpdatedAt = now
|
||
|
||
// 确保返回的数据包含 TechList 和 ExperienceList(已从 JSON 解析)
|
||
// 这些字段已经在上面被 Marshal 了,现在需要确保它们被正确设置
|
||
// 由于我们已经 Marshal 了,TechList 和 ExperienceList 应该保持原样
|
||
return nil
|
||
}
|
||
|
||
// UpdateAboutProfile 更新个人资料
|
||
func UpdateAboutProfile(profile *models.AboutProfile) error {
|
||
// 确保 TechList 和 ExperienceList 不为 nil
|
||
if profile.TechList == nil {
|
||
profile.TechList = []string{}
|
||
}
|
||
if profile.ExperienceList == nil {
|
||
profile.ExperienceList = []models.Experience{}
|
||
}
|
||
|
||
// Marshal JSON
|
||
techBytes, _ := json.Marshal(profile.TechList)
|
||
profile.TechStack = string(techBytes)
|
||
|
||
expBytes, _ := json.Marshal(profile.ExperienceList)
|
||
profile.ExperiencesStr = string(expBytes)
|
||
|
||
now := time.Now().Unix()
|
||
query := `
|
||
UPDATE about_profiles
|
||
SET name = ?, avatar = ?, location = ?, bio = ?, email = ?, wechat = ?, tech_stack = ?, experiences = ?, is_primary = ?, updated_at = ?
|
||
WHERE id = ? AND deleted_at = 0
|
||
`
|
||
_, err := config.DB.Exec(
|
||
query,
|
||
profile.Name,
|
||
profile.Avatar,
|
||
profile.Location,
|
||
profile.Bio,
|
||
profile.Email,
|
||
profile.Wechat,
|
||
profile.TechStack,
|
||
profile.ExperiencesStr,
|
||
profile.IsPrimary,
|
||
now,
|
||
profile.ID,
|
||
)
|
||
if err != nil {
|
||
log.Printf("Error updating about profile: %v", err)
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// DeleteAboutProfile 删除个人资料 (Soft Delete)
|
||
func DeleteAboutProfile(id uint) error {
|
||
now := time.Now().Unix()
|
||
query := "UPDATE about_profiles SET deleted_at = ? WHERE id = ?"
|
||
_, err := config.DB.Exec(query, now, id)
|
||
if err != nil {
|
||
log.Printf("Error deleting about profile: %v", err)
|
||
return err
|
||
}
|
||
return nil
|
||
}
|