This commit is contained in:
2026-09-03 17:55:42 +08:00
parent 7c38e84e30
commit c80a3d4a2d
35 changed files with 6795 additions and 597 deletions
+220 -11
View File
@@ -1,6 +1,8 @@
package service
import (
"errors"
"fmt"
"photowall/internal/model"
"gorm.io/gorm"
@@ -14,21 +16,34 @@ func NewRegionService(db *gorm.DB) *RegionService {
return &RegionService{db: db}
}
// ListByParent 根据父级ID查子地区;parentID=0 查国家
func (s *RegionService) ListByParent(parentID uint) ([]model.Region, error) {
// GetByID 根据 ID 查询地区
func (s *RegionService) GetByID(id uint) (*model.Region, error) {
var r model.Region
if err := s.db.First(&r, id).Error; err != nil {
return nil, errors.New("地区不存在")
}
return &r, nil
}
// ListByParent 根据父级ID查子地区;parentID=0 查国家;可选按层级筛选
func (s *RegionService) ListByParent(parentID uint, level ...model.RegionLevel) ([]model.Region, error) {
query := s.db.Where("parent_id = ?", parentID)
if len(level) > 0 && level[0] != "" {
query = query.Where("level = ?", level[0])
}
var list []model.Region
err := s.db.Where("parent_id = ?", parentID).Order("sort_order, name").Find(&list).Error
err := query.Order("sort_order, id").Find(&list).Error
return list, err
}
// ListCountries 查所有国家
func (s *RegionService) ListCountries() ([]model.Region, error) {
var list []model.Region
err := s.db.Where("level = ?", model.LevelCountry).Order("sort_order, name").Find(&list).Error
err := s.db.Where("level = ?", model.LevelCountry).Order("sort_order, id").Find(&list).Error
return list, err
}
// GetPath 获取某地区的完整路径(国家->省->县)
// GetPath 获取某地区的完整路径(国家->省->市->县)
func (s *RegionService) GetPath(regionID uint) ([]model.Region, error) {
var r model.Region
if err := s.db.First(&r, regionID).Error; err != nil {
@@ -41,28 +56,222 @@ func (s *RegionService) GetPath(regionID uint) ([]model.Region, error) {
if err := s.db.First(&parent, cur.ParentID).Error; err != nil {
break
}
// 插到前面
path = append([]model.Region{parent}, path...)
cur = parent
}
return path, nil
}
// Search 按名称搜索地区(用于快速定位)
// Search 按名称或代码搜索地区
func (s *RegionService) Search(keyword string, limit int) ([]model.Region, error) {
if limit <= 0 {
limit = 20
limit = 50
}
var list []model.Region
err := s.db.Where("name LIKE ? OR name_en LIKE ?", "%"+keyword+"%", "%"+keyword+"%").
Order("level, sort_order, name").Limit(limit).Find(&list).Error
err := s.db.Where("name LIKE ? OR name_en LIKE ? OR code LIKE ?", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%").
Order("level, sort_order, id").Limit(limit).Find(&list).Error
return list, err
}
// BatchCreate 平台管理员批量录入地区(导入完整行政区划数据用)
// Create 创建地区
func (s *RegionService) Create(r *model.Region) error {
if r.Name == "" {
return errors.New("地区名称不能为空")
}
if r.Level == "" {
return errors.New("请选择地区层级")
}
if r.ParentID > 0 {
var parent model.Region
if err := s.db.First(&parent, r.ParentID).Error; err != nil {
return errors.New("所选上级地区不存在")
}
}
return s.db.Create(r).Error
}
// Update 更新地区信息
func (s *RegionService) Update(id uint, updates map[string]interface{}) error {
res := s.db.Model(&model.Region{}).Where("id = ?", id).Updates(updates)
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
return errors.New("地区不存在")
}
return nil
}
// Delete 删除地区(包含子项/学校引用防误删检查)
func (s *RegionService) Delete(id uint) error {
// 检查是否有子地区
var childCount int64
s.db.Model(&model.Region{}).Where("parent_id = ?", id).Count(&childCount)
if childCount > 0 {
return errors.New("该地区下仍有下级地区,请先处理下级地区")
}
// 检查是否有学校关联
var schoolCount int64
s.db.Model(&model.School{}).Where("region_id = ?", id).Count(&schoolCount)
if schoolCount > 0 {
return errors.New("该地区下已有关联学校,无法直接删除")
}
return s.db.Delete(&model.Region{}, id).Error
}
// GetTree 获取地区层级树(parentID=0 表示从顶级国家开始)
func (s *RegionService) GetTree(parentID uint) ([]*model.RegionTree, error) {
var all []model.Region
if err := s.db.Order("sort_order, id").Find(&all).Error; err != nil {
return nil, err
}
return buildRegionTree(all, parentID), nil
}
func buildRegionTree(list []model.Region, parentID uint) []*model.RegionTree {
var tree []*model.RegionTree
for i := range list {
if list[i].ParentID == parentID {
node := &model.RegionTree{
Region: list[i],
Children: buildRegionTree(list, list[i].ID),
}
tree = append(tree, node)
}
}
return tree
}
// BatchCreate 批量录入地区
func (s *RegionService) BatchCreate(regions []model.Region) error {
if len(regions) == 0 {
return nil
}
return s.db.CreateInBatches(regions, 100).Error
}
// InitChinaRegions 生成/初始化整个中国的地域选项(34个省/直辖市/自治区、地级市、区县)
func (s *RegionService) InitChinaRegions() (createdCount int, totalCount int, err error) {
// 1. 预加载所有已存在的地区,极大提升比对性能(避免数千次单条查询导致超时)
var allExisting []model.Region
if err := s.db.Select("id, name, level, parent_id, code").Find(&allExisting).Error; err != nil {
return 0, 0, err
}
regMap := make(map[string]uint, len(allExisting)+3500)
for _, r := range allExisting {
key := fmt.Sprintf("%d:%s:%s", r.ParentID, r.Level, r.Name)
regMap[key] = r.ID
}
// 2. 查找或创建国家:中国
chinaKey := fmt.Sprintf("0:%s:中国", model.LevelCountry)
chinaID, exists := regMap[chinaKey]
if !exists {
china := model.Region{
Name: "中国",
NameEn: "China",
Level: model.LevelCountry,
ParentID: 0,
Code: "CN",
SortOrder: 1,
}
if err := s.db.Create(&china).Error; err != nil {
return 0, 0, err
}
chinaID = china.ID
regMap[chinaKey] = chinaID
createdCount++
}
totalCount++
// 2.1 清理历史残留错误数据:挂在中国省级下的直接 county 记录(如无学校关联)
var wrongProvCounties []model.Region
s.db.Raw(`
SELECT c.id FROM yz_pw_regions c
JOIN yz_pw_regions p ON c.parent_id = p.id
WHERE c.level = 'county' AND p.level = 'province' AND p.parent_id = ?
`, chinaID).Scan(&wrongProvCounties)
if len(wrongProvCounties) > 0 {
var toDel []uint
for _, wc := range wrongProvCounties {
var scCount int64
s.db.Model(&model.School{}).Where("region_id = ?", wc.ID).Count(&scCount)
if scCount == 0 {
toDel = append(toDel, wc.ID)
}
}
if len(toDel) > 0 {
s.db.Where("id IN ?", toDel).Delete(&model.Region{})
}
}
// 3. 遍历全国省市县数据并批量写入
for pIdx, pData := range chinaProvincesData {
provKey := fmt.Sprintf("%d:%s:%s", chinaID, model.LevelProvince, pData.Name)
provID, provExists := regMap[provKey]
if !provExists {
prov := model.Region{
Name: pData.Name,
Level: model.LevelProvince,
ParentID: chinaID,
Code: pData.Code,
SortOrder: pIdx + 1,
}
if err := s.db.Create(&prov).Error; err != nil {
return createdCount, totalCount, err
}
provID = prov.ID
regMap[provKey] = provID
createdCount++
}
totalCount++
for cIdx, cData := range pData.Cities {
cityKey := fmt.Sprintf("%d:%s:%s", provID, model.LevelCity, cData.Name)
cityID, cityExists := regMap[cityKey]
if !cityExists {
city := model.Region{
Name: cData.Name,
Level: model.LevelCity,
ParentID: provID,
Code: cData.Code,
SortOrder: cIdx + 1,
}
if err := s.db.Create(&city).Error; err != nil {
return createdCount, totalCount, err
}
cityID = city.ID
regMap[cityKey] = cityID
createdCount++
}
totalCount++
var missingCounties []model.Region
for kIdx, countyName := range cData.Counties {
totalCount++
countyKey := fmt.Sprintf("%d:%s:%s", cityID, model.LevelCounty, countyName)
if _, ctyExists := regMap[countyKey]; !ctyExists {
missingCounties = append(missingCounties, model.Region{
Name: countyName,
Level: model.LevelCounty,
ParentID: cityID,
SortOrder: kIdx + 1,
})
regMap[countyKey] = 1
}
}
if len(missingCounties) > 0 {
if err := s.db.CreateInBatches(missingCounties, 100).Error; err != nil {
return createdCount, totalCount, err
}
createdCount += len(missingCounties)
}
}
}
return createdCount, totalCount, nil
}