diff --git a/api/v1/report.proto b/api/v1/report.proto
new file mode 100644
index 0000000..0dff240
--- /dev/null
+++ b/api/v1/report.proto
@@ -0,0 +1,71 @@
+syntax = "proto3";
+
+import "google/protobuf/timestamp.proto";
+import "gogoproto/gogo.proto";
+// import "validate/validate.proto";
+import "google/api/annotations.proto";
+import "google/protobuf/empty.proto";
+
+package api.v1;
+
+option go_package = "moredoc/api/v1;v1";
+option java_multiple_files = true;
+option java_package = "api.v1";
+
+// 这里是proto文件中的结构体,可以根据需要删除或者调整
+message Report {
+ int64 id = 1;
+ int64 document_id = 2;
+ int64 user_id = 3;
+ int32 reason = 4;
+ bool status = 5;
+ google.protobuf.Timestamp created_at = 6 [ (gogoproto.stdtime) = true ];
+ google.protobuf.Timestamp updated_at = 7 [ (gogoproto.stdtime) = true ];
+ string document_title = 8;
+ string remark = 9;
+ string username = 10;
+}
+
+message DeleteReportRequest { repeated int64 id = 1; }
+
+message ListReportRequest {
+ int64 page = 1;
+ int64 size = 2;
+ string wd = 3;
+ repeated string field = 4;
+ string order = 5;
+ repeated bool status = 6;
+}
+
+message ListReportReply {
+ int64 total = 1;
+ repeated Report report = 2;
+}
+
+service ReportAPI {
+ rpc CreateReport(Report) returns (google.protobuf.Empty) {
+ option (google.api.http) = {
+ post : '/api/v1/report',
+ body : '*',
+ };
+ }
+
+ rpc UpdateReport(Report) returns (google.protobuf.Empty) {
+ option (google.api.http) = {
+ put : '/api/v1/report',
+ body : '*',
+ };
+ }
+
+ rpc DeleteReport(DeleteReportRequest) returns (google.protobuf.Empty) {
+ option (google.api.http) = {
+ delete : '/api/v1/report',
+ };
+ }
+
+ rpc ListReport(ListReportRequest) returns (ListReportReply) {
+ option (google.api.http) = {
+ get : '/api/v1/report/list',
+ };
+ }
+}
\ No newline at end of file
diff --git a/biz/report.go b/biz/report.go
new file mode 100644
index 0000000..c5fb3ce
--- /dev/null
+++ b/biz/report.go
@@ -0,0 +1,123 @@
+package biz
+
+import (
+ "context"
+
+ pb "moredoc/api/v1"
+ "moredoc/middleware/auth"
+ "moredoc/model"
+ "moredoc/util"
+
+ "go.uber.org/zap"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+ "google.golang.org/protobuf/types/known/emptypb"
+ "gorm.io/gorm"
+)
+
+type ReportAPIService struct {
+ pb.UnimplementedReportAPIServer
+ dbModel *model.DBModel
+ logger *zap.Logger
+}
+
+func NewReportAPIService(dbModel *model.DBModel, logger *zap.Logger) (service *ReportAPIService) {
+ return &ReportAPIService{dbModel: dbModel, logger: logger.Named("ReportAPIService")}
+}
+
+func (s *ReportAPIService) checkLogin(ctx context.Context) (*auth.UserClaims, error) {
+ return checkGRPCLogin(s.dbModel, ctx)
+}
+
+func (s *ReportAPIService) checkPermission(ctx context.Context) (*auth.UserClaims, error) {
+ return checkGRPCPermission(s.dbModel, ctx)
+}
+
+func (s *ReportAPIService) CreateReport(ctx context.Context, req *pb.Report) (*emptypb.Empty, error) {
+ UserClaims, err := s.checkLogin(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ if req.DocumentId == 0 {
+ return nil, status.Error(codes.InvalidArgument, "文档参数不正确")
+ }
+
+ report, _ := s.dbModel.GetReportByDocUser(req.DocumentId, UserClaims.UserId)
+ if report.Id > 0 {
+ return nil, status.Error(codes.AlreadyExists, "您已举报过当前文档")
+ }
+
+ util.CopyStruct(req, &report)
+ report.UserId = UserClaims.UserId
+ err = s.dbModel.CreateReport(&report)
+ if err != nil {
+ return nil, status.Error(codes.Internal, "创建举报失败")
+ }
+
+ return &emptypb.Empty{}, nil
+}
+
+func (s *ReportAPIService) UpdateReport(ctx context.Context, req *pb.Report) (*emptypb.Empty, error) {
+ _, err := s.checkPermission(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ report := &model.Report{}
+ util.CopyStruct(req, report)
+ err = s.dbModel.UpdateReport(report, "status", "remark")
+ if err != nil {
+ return nil, status.Error(codes.Internal, "更新举报失败")
+ }
+
+ return &emptypb.Empty{}, nil
+}
+
+func (s *ReportAPIService) DeleteReport(ctx context.Context, req *pb.DeleteReportRequest) (*emptypb.Empty, error) {
+ _, err := s.checkPermission(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ err = s.dbModel.DeleteReport(req.Id)
+ if err != nil {
+ return nil, status.Error(codes.Internal, "删除举报失败")
+ }
+
+ return &emptypb.Empty{}, nil
+}
+
+func (s *ReportAPIService) ListReport(ctx context.Context, req *pb.ListReportRequest) (*pb.ListReportReply, error) {
+ _, err := s.checkPermission(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ opt := &model.OptionGetReportList{
+ WithCount: true,
+ Page: int(req.Page),
+ Size: int(req.Size_),
+ QueryLike: make(map[string][]interface{}),
+ QueryIn: make(map[string][]interface{}),
+ }
+
+ if req.Wd != "" {
+ opt.QueryLike["document_title"] = []interface{}{req.Wd}
+ }
+
+ if len(req.Status) > 0 {
+ opt.QueryIn["status"] = util.Slice2Interface(req.Status)
+ }
+
+ reports, total, err := s.dbModel.GetReportList(opt)
+ if err != nil && err != gorm.ErrRecordNotFound {
+ return nil, status.Error(codes.Internal, "获取举报列表失败")
+ }
+
+ pbReport := &pb.ListReportReply{
+ Total: total,
+ Report: reports,
+ }
+ return pbReport, nil
+}
diff --git a/model/init.go b/model/init.go
index 7735560..6b0e377 100644
--- a/model/init.go
+++ b/model/init.go
@@ -174,6 +174,7 @@ func (m *DBModel) SyncDB() (err error) {
&Comment{},
&Dynamic{},
&Sign{},
+ &Report{},
}
if err = m.db.AutoMigrate(tableModels...); err != nil {
m.logger.Fatal("SyncDB", zap.Error(err))
diff --git a/model/report.go b/model/report.go
new file mode 100644
index 0000000..1d773c4
--- /dev/null
+++ b/model/report.go
@@ -0,0 +1,134 @@
+package model
+
+import (
+ v1 "moredoc/api/v1"
+ "time"
+
+ "go.uber.org/zap"
+ "gorm.io/gorm"
+)
+
+type Report struct {
+ Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
+ DocumentId int64 `form:"document_id" json:"document_id,omitempty" gorm:"column:document_id;type:bigint(20);size:20;default:0;comment:文档ID;index:idx_document_id;"`
+ DocumentTitle string `form:"document_title" json:"document_title,omitempty" gorm:"column:document_title;type:varchar(255);size:255;default:'';comment:文档标题;"`
+ UserId int64 `form:"user_id" json:"user_id,omitempty" gorm:"column:user_id;type:bigint(20);size:20;default:0;comment:用户ID;index:idx_user_id;"`
+ Username string `form:"username" json:"username,omitempty" gorm:"column:username;type:varchar(64);size:64;default:'';comment:用户名;"`
+ Reason int `form:"reason" json:"reason,omitempty" gorm:"column:reason;type:int(11);size:11;default:0;comment:举报原因;"`
+ Status bool `form:"status" json:"status,omitempty" gorm:"column:status;type:tinyint(4);size:4;default:0;comment:是否已处理;index:idx_status;"`
+ Remark string `form:"remark" json:"remark,omitempty" gorm:"column:remark;type:varchar(255);size:255;default:'';comment:备注;"`
+ CreatedAt *time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;comment:创建时间;"`
+ UpdatedAt *time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;comment:更新时间;"`
+}
+
+func (Report) TableName() string {
+ return tablePrefix + "report"
+}
+
+// CreateReport 创建Report
+func (m *DBModel) CreateReport(report *Report) (err error) {
+ doc, _ := m.GetDocument(report.DocumentId, "id", "title")
+ report.DocumentTitle = doc.Title
+ user, _ := m.GetUser(report.UserId, "id", "username")
+ report.Username = user.Username
+ err = m.db.Create(report).Error
+ if err != nil {
+ m.logger.Error("CreateReport", zap.Error(err))
+ return
+ }
+ return
+}
+
+// UpdateReport 更新Report,如果需要更新指定字段,则请指定updateFields参数
+func (m *DBModel) UpdateReport(report *Report, updateFields ...string) (err error) {
+ db := m.db.Model(report)
+ tableName := Report{}.TableName()
+
+ updateFields = m.FilterValidFields(tableName, updateFields...)
+ if len(updateFields) > 0 { // 更新指定字段
+ db = db.Select(updateFields)
+ } else { // 更新全部字段,包括零值字段
+ db = db.Select(m.GetTableFields(tableName))
+ }
+
+ err = db.Where("id = ?", report.Id).Updates(report).Error
+ if err != nil {
+ m.logger.Error("UpdateReport", zap.Error(err))
+ }
+ return
+}
+
+// GetReport 根据id获取Report
+func (m *DBModel) GetReport(id interface{}, fields ...string) (report Report, err error) {
+ db := m.db
+
+ fields = m.FilterValidFields(Report{}.TableName(), fields...)
+ if len(fields) > 0 {
+ db = db.Select(fields)
+ }
+
+ err = db.Where("id = ?", id).First(&report).Error
+ return
+}
+
+type OptionGetReportList struct {
+ Page int
+ Size int
+ WithCount bool // 是否返回总数
+ Ids []interface{} // id列表
+ SelectFields []string // 查询字段
+ QueryRange map[string][2]interface{} // map[field][]{min,max}
+ QueryIn map[string][]interface{} // map[field][]{value1,value2,...}
+ QueryLike map[string][]interface{} // map[field][]{value1,value2,...}
+ Sort []string
+}
+
+// GetReportList 获取Report列表
+func (m *DBModel) GetReportList(opt *OptionGetReportList) (reportList []*v1.Report, total int64, err error) {
+ tableName := Report{}.TableName()
+ db := m.db.Model(&Report{})
+ db = m.generateQueryRange(db, tableName, opt.QueryRange)
+ db = m.generateQueryIn(db, tableName, opt.QueryIn)
+ db = m.generateQueryLike(db, tableName, opt.QueryLike)
+
+ if len(opt.Ids) > 0 {
+ db = db.Where("id in (?)", opt.Ids)
+ }
+
+ if opt.WithCount {
+ err = db.Count(&total).Error
+ if err != nil {
+ m.logger.Error("GetReportList", zap.Error(err))
+ return
+ }
+ }
+
+ opt.SelectFields = m.FilterValidFields(tableName, opt.SelectFields...)
+ if len(opt.SelectFields) > 0 {
+ db = db.Select(opt.SelectFields)
+ }
+
+ db = m.generateQuerySort(db, tableName, opt.Sort)
+
+ db = db.Offset((opt.Page - 1) * opt.Size).Limit(opt.Size)
+
+ err = db.Find(&reportList).Error
+ if err != nil && err != gorm.ErrRecordNotFound {
+ m.logger.Error("GetReportList", zap.Error(err))
+ }
+ return
+}
+
+// DeleteReport 删除数据
+func (m *DBModel) DeleteReport(ids []int64) (err error) {
+ err = m.db.Where("id in (?)", ids).Delete(&Report{}).Error
+ if err != nil {
+ m.logger.Error("DeleteReport", zap.Error(err))
+ }
+ return
+}
+
+func (m *DBModel) GetReportByDocUser(docId, userId int64) (report Report, err error) {
+ err = m.db.Where("doc_id = ? and user_id = ?", docId, userId).First(&report).Error
+ return
+}
diff --git a/service/serve/registerGRPCService.go b/service/serve/registerGRPCService.go
index 7909764..0b1d374 100644
--- a/service/serve/registerGRPCService.go
+++ b/service/serve/registerGRPCService.go
@@ -130,5 +130,14 @@ func RegisterGRPCService(dbModel *model.DBModel, logger *zap.Logger, endpoint st
return
}
+ // 举报服务
+ reportAPIService := biz.NewReportAPIService(dbModel, logger)
+ v1.RegisterReportAPIServer(grpcServer, reportAPIService)
+ err = v1.RegisterReportAPIHandlerFromEndpoint(context.Background(), gwmux, endpoint, dialOpts)
+ if err != nil {
+ logger.Error("RegisterReportAPIHandlerFromEndpoint", zap.Error(err))
+ return
+ }
+
return
}
diff --git a/web/api/report.js b/web/api/report.js
new file mode 100644
index 0000000..650f125
--- /dev/null
+++ b/web/api/report.js
@@ -0,0 +1,35 @@
+import service from '~/utils/request'
+
+export const createReport = (data) => {
+ return service({
+ url: '/api/v1/report',
+ method: 'post',
+ data,
+ })
+}
+
+export const updateReport = (data) => {
+ return service({
+ url: '/api/v1/report',
+ method: 'put',
+ data,
+ })
+}
+
+export const deleteReport = (params) => {
+ return service({
+ url: '/api/v1/report',
+ method: 'delete',
+ params,
+ })
+}
+
+export const listReport = (params) => {
+ return service({
+ url: '/api/v1/report/list',
+ method: 'get',
+ params,
+ })
+}
+
+
diff --git a/web/components/FormReport.vue b/web/components/FormReport.vue
new file mode 100644
index 0000000..e2961da
--- /dev/null
+++ b/web/components/FormReport.vue
@@ -0,0 +1,137 @@
+
+