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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package biz
import (
"context"
"fmt"
"runtime"
"strings"
"time"
pb "moredoc/api/v1"
"moredoc/middleware/auth"
"moredoc/model"
"moredoc/util"
"moredoc/util/device"
"github.com/PuerkitoBio/goquery"
"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())
}
doesUpdateSEO := false
isEmail := false
for idx, cfg := range cfgs {
if cfg.Value == "******" {
// 6个星号不修改原值
exist, _ := s.dbModel.GetConfigByNameCategory(cfg.Name, cfg.Category)
cfgs[idx].Value = exist.Value
}
isEmail = isEmail || cfg.Category == model.ConfigCategoryEmail
if cfg.Category == model.ConfigCategorySystem {
doesUpdateSEO = true
}
}
err = s.dbModel.UpdateConfigs(cfgs, "value")
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
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())
}
}
}
if doesUpdateSEO {
s.dbModel.InitSEO()
}
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 = "******"
}
}
var pbConfigs []*pb.Config
util.CopyStruct(&configs, &pbConfigs)
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()
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()
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()
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
}
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,
Version: util.Version,
Hash: util.Hash,
BuildAt: util.BuildAt,
}
res.Os, _ = util.GetOSRelease()
res.UserCount, _ = s.dbModel.CountUser()
res.UserCount += s.dbModel.GetConfigOfDisplay(model.ConfigDisplayVirtualRegisterCount).VirtualRegisterCount
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",
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
}
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
}