You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

324 lines
9.6 KiB

2 years ago
package biz
import (
"context"
"fmt"
1 year ago
"runtime"
"strings"
"time"
2 years ago
pb "moredoc/api/v1"
"moredoc/middleware/auth"
"moredoc/model"
"moredoc/util"
"moredoc/util/device"
2 years ago
"github.com/PuerkitoBio/goquery"
2 years ago
"go.uber.org/zap"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
type ConfigAPIService struct {
pb.UnimplementedConfigAPIServer
dbModel *model.DBModel
logger *zap.Logger
}
func NewConfigAPIService(dbModel *model.DBModel, logger *zap.Logger) (service *ConfigAPIService) {
return &ConfigAPIService{dbModel: dbModel, logger: logger.Named("ConfigAPIService")}
}
func (s *ConfigAPIService) checkPermission(ctx context.Context) (userClaims *auth.UserClaims, err error) {
return checkGRPCPermission(s.dbModel, ctx)
}
// UpdateConfig 更新配置
func (s *ConfigAPIService) UpdateConfig(ctx context.Context, req *pb.Configs) (*emptypb.Empty, error) {
_, err := s.checkPermission(ctx)
if err != nil {
return nil, err
}
var cfgs []*model.Config
err = util.CopyStruct(req.Config, &cfgs)
if err != nil {
s.logger.Error("util.CopyStruct", zap.Any("req", req), zap.Any("cfgs", cfgs), zap.Error(err))
fmt.Println(err.Error())
}
1 year ago
doesUpdateSEO := false
1 year ago
isEmail := false
for idx, cfg := range cfgs {
if cfg.Value == "******" {
// 6个星号不修改原值
exist, _ := s.dbModel.GetConfigByNameCategory(cfg.Name, cfg.Category)
cfgs[idx].Value = exist.Value
1 year ago
}
isEmail = isEmail || cfg.Category == model.ConfigCategoryEmail
1 year ago
if cfg.Category == model.ConfigCategorySystem {
doesUpdateSEO = true
}
1 year ago
}
2 years ago
err = s.dbModel.UpdateConfigs(cfgs, "value")
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
1 year ago
if isEmail {
cfgEmail := s.dbModel.GetConfigOfEmail(model.ConfigEmailEnable, model.ConfigEmailTestEmail)
if cfgEmail.Enable && cfgEmail.TestEmail != "" {
err = s.dbModel.SendMail("测试邮件", cfgEmail.TestEmail, "这是一封测试邮件")
if err != nil {
return nil, status.Error(codes.Internal, "邮件发送失败:"+err.Error())
}
}
}
1 year ago
if doesUpdateSEO {
s.dbModel.InitSEO()
}
2 years ago
return &emptypb.Empty{}, nil
}
// ListConfig 查询配置
func (s *ConfigAPIService) ListConfig(ctx context.Context, req *pb.ListConfigRequest) (*pb.Configs, error) {
_, err := s.checkPermission(ctx)
if err != nil {
return nil, err
}
opt := &model.OptionGetConfigList{
QueryIn: map[string][]interface{}{
"category": util.Slice2Interface(req.Category),
},
}
configs, err := s.dbModel.GetConfigList(opt)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
for idx, cfg := range configs {
if cfg.IsSecret && cfg.Value != "" {
configs[idx].Value = "******"
1 year ago
}
}
var pbConfigs []*pb.Config
util.CopyStruct(&configs, &pbConfigs)
2 years ago
return &pb.Configs{Config: pbConfigs}, nil
}
// GetSettings 获取公开配置
func (s *ConfigAPIService) GetSettings(ctx context.Context, req *emptypb.Empty) (*pb.Settings, error) {
res := &pb.Settings{
// Captcha: &pb.ConfigCaptcha{},
System: &pb.ConfigSystem{},
Footer: &pb.ConfigFooter{},
Security: &pb.ConfigSecurity{},
Display: &pb.ConfigDisplay{},
}
// captcha := s.dbModel.GetConfigOfCaptcha()
// util.CopyStruct(&captcha, res.Captcha)
system := s.dbModel.GetConfigOfSystem()
2 years ago
if err := util.CopyStruct(&system, res.System); err != nil {
s.logger.Error("util.CopyStruct", zap.Any("system", system), zap.Any("res.System", res.System), zap.Error(err))
}
system.Analytics = strings.TrimSpace(system.Analytics)
if system.Analytics != "" {
gq, errGQ := goquery.NewDocumentFromReader(strings.NewReader(system.Analytics))
if errGQ == nil {
var texts []string
gq.Find("script").Each(func(i int, selection *goquery.Selection) {
if text := strings.TrimSpace(selection.Text()); text != "" {
texts = append(texts, text)
}
})
if len(texts) > 0 {
res.System.Analytics = strings.Join(texts, "\n")
}
}
}
res.System.Version = util.Version
res.System.CreditName = s.dbModel.GetConfigOfScore(model.ConfigScoreCreditName).CreditName
footer := s.dbModel.GetConfigOfFooter()
2 years ago
if err := util.CopyStruct(&footer, res.Footer); err != nil {
s.logger.Error("util.CopyStruct", zap.Any("footer", footer), zap.Any("res.Footer", res.Footer), zap.Error(err))
}
security := s.dbModel.GetConfigOfSecurity()
2 years ago
if err := util.CopyStruct(&security, res.Security); err != nil {
s.logger.Error("util.CopyStruct", zap.Any("security", security), zap.Any("res.Security", res.Security), zap.Error(err))
}
display := s.dbModel.GetConfigOfDisplay()
if err := util.CopyStruct(&display, res.Display); err != nil {
s.logger.Error("util.CopyStruct", zap.Any("display", display), zap.Any("res.Display", res.Display), zap.Error(err))
}
return res, nil
}
1 year ago
func (s *ConfigAPIService) GetStats(ctx context.Context, req *emptypb.Empty) (res *pb.Stats, err error) {
res = &pb.Stats{
UserCount: 0,
DocumentCount: 0,
CategoryCount: 0,
ArticleCount: 0,
CommentCount: 0,
BannerCount: 0,
FriendlinkCount: 0,
Os: runtime.GOOS,
1 year ago
Version: util.Version,
Hash: util.Hash,
BuildAt: util.BuildAt,
1 year ago
}
res.Os, _ = util.GetOSRelease()
1 year ago
res.UserCount, _ = s.dbModel.CountUser()
res.UserCount += s.dbModel.GetConfigOfDisplay(model.ConfigDisplayVirtualRegisterCount).VirtualRegisterCount
1 year ago
res.DocumentCount, _ = s.dbModel.CountDocument()
_, errPermission := s.checkPermission(ctx)
if errPermission == nil {
res.CategoryCount, _ = s.dbModel.CountCategory()
res.ArticleCount, _ = s.dbModel.CountArticle()
res.CommentCount, _ = s.dbModel.CountComment()
res.BannerCount, _ = s.dbModel.CountBanner()
res.FriendlinkCount, _ = s.dbModel.CountFriendlink()
res.ReportCount, _ = s.dbModel.CountReport()
}
return
}
// UpdateSitemap 更新站点地图
func (s *ConfigAPIService) UpdateSitemap(ctx context.Context, req *emptypb.Empty) (*emptypb.Empty, error) {
_, err := s.checkPermission(ctx)
if err != nil {
return nil, err
}
err = s.dbModel.UpdateSitemap()
if err != nil {
s.logger.Error("UpdateSitemap", zap.Error(err))
return nil, status.Error(codes.Internal, err.Error())
}
return &emptypb.Empty{}, nil
}
func (s *ConfigAPIService) GetEnvs(ctx context.Context, req *emptypb.Empty) (res *pb.Envs, err error) {
res = &pb.Envs{}
_, errPermission := s.checkPermission(ctx)
if errPermission != nil {
return
}
envs := []*pb.EnvDependent{
{
Name: "LibreOffice",
Description: "LibreOffice是由文档基金会开发的自由及开放源代码的办公套件。魔豆文库用于将office等文档转为pdf。",
Cmd: "soffice",
IsRequired: true,
},
{
Name: "Calibre",
Description: "calibre是一个自由开源的电子书软件套装。魔豆文库用于将epub、mobi等电子书转为pdf。",
Cmd: "ebook-convert",
IsRequired: true,
},
{
// mupdf
Name: "MuPDF",
Description: "MuPDF是一款以C语言编写的自由及开放源代码软件库是PDF和XPS解析和渲染引擎。魔豆文库用于将PDF转为svg、png等图片。",
Cmd: "mutool",
IsRequired: true,
},
{
// mupdf
Name: "ImageMagick",
Description: "ImageMagick是一个用于创建、编辑、合成和转换位图图像的自由软件套件。魔豆文库用于将PDF转为svg、png、jpg等图片。",
Cmd: "convert",
IsRequired: true,
},
{
// inkscape
Name: "Inkscape",
Description: "Inkscape是一个自由开源的矢量图形编辑器。在mupdf处理PDF出现兼容问题失败时自动切换inkscape来处理。",
Cmd: "inkscape",
6 months ago
IsRequired: true,
},
{
Name: "SVGO",
Description: "SVGO 是一个基于 Node.js 的工具,用于优化 SVG 矢量图形文件。魔豆文库用于压缩svg图片大小。",
Cmd: "svgo",
IsRequired: false,
},
{
Name: "PM2",
Description: "PM2是JavaScript运行时Node.js的进程管理器。用于做魔豆文库的系统守护进程。Windows下建议使用PM2。",
Cmd: "pm2",
IsRequired: false,
}, {
Name: "Supervisor",
Description: "Supervisor是一个客户端/服务器系统用于监视进程状态当进程不再运行时自动重启它们。用于做魔豆文库的系统守护进程。Linux下建议使用Supervisor。",
Cmd: "supervisorctl",
IsRequired: false,
},
}
for i := 0; i < len(envs); i++ {
now := time.Now()
err := util.CheckCommandExists(envs[i].Cmd)
envs[i].IsInstalled = err == nil
envs[i].CheckedAt = &now
envs[i].Version = util.GetCommandVersion(envs[i].Cmd)
if err != nil {
envs[i].Error = err.Error()
}
}
res.Envs = envs
return
}
func (s *ConfigAPIService) GetDeviceInfo(ctx context.Context, req *emptypb.Empty) (res *pb.DeviceInfo, err error) {
res = &pb.DeviceInfo{
Cpu: &pb.CPUInfo{},
Memory: &pb.MemoryInfo{},
}
cpu := device.GetCPU()
err = util.CopyStruct(&cpu, res.Cpu)
if err != nil {
s.logger.Error("util.CopyStruct", zap.Any("cpu", cpu), zap.Any("res.Cpu", res.Cpu), zap.Error(err))
return
}
1 year ago
res.Cpu.Cores = int32(runtime.NumCPU())
mem := device.GetMemory()
err = util.CopyStruct(&mem, res.Memory)
if err != nil {
s.logger.Error("util.CopyStruct", zap.Any("mem", mem), zap.Any("res.Memory", res.Memory), zap.Error(err))
return
}
res.Memory.Free = res.Memory.Total - res.Memory.Used
disks := device.GetDisk()
if len(disks) > 0 {
for _, disk := range disks {
pbDisk := &pb.DiskInfo{}
err = util.CopyStruct(&disk, pbDisk)
if err != nil {
s.logger.Error("util.CopyStruct", zap.Any("disk", disk), zap.Any("res.Disk", res.Disk), zap.Error(err))
return
}
res.Disk = append(res.Disk, pbDisk)
}
}
return res, nil
}