init: create project
This commit is contained in:
139
server/service/data.go
Normal file
139
server/service/data.go
Normal file
@ -0,0 +1,139 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Data interface {
|
||||
String() string
|
||||
Masking() string
|
||||
}
|
||||
|
||||
var (
|
||||
_ Data = Address("")
|
||||
_ Data = Nickname("")
|
||||
_ Data = PhoneNumber(0)
|
||||
_ Data = IDNumber("")
|
||||
_ Data = QQNumber(0)
|
||||
_ Data = Password("")
|
||||
_ Data = Email("")
|
||||
_ Data = Address("")
|
||||
)
|
||||
|
||||
type (
|
||||
Name string
|
||||
Nickname string
|
||||
PhoneNumber int64
|
||||
IDNumber string
|
||||
QQNumber int64
|
||||
Password string
|
||||
Email string
|
||||
Address string
|
||||
)
|
||||
|
||||
func (address Address) Masking() string {
|
||||
return maskLeft(string(address), 1)
|
||||
}
|
||||
|
||||
func (address Address) String() string {
|
||||
return string(address)
|
||||
}
|
||||
|
||||
func (email Email) Masking() string {
|
||||
return maskLeft(email.String(), len(strings.Split(string(email), "@")[0])+1)
|
||||
}
|
||||
|
||||
func (email Email) String() string {
|
||||
return string(email)
|
||||
}
|
||||
|
||||
func (password Password) Masking() string {
|
||||
return maskLeft(password.String(), 4)
|
||||
}
|
||||
|
||||
func (password Password) String() string {
|
||||
return string(password)
|
||||
}
|
||||
|
||||
func (qqNumber QQNumber) Masking() string {
|
||||
return maskLeft(qqNumber.String(), 3)
|
||||
}
|
||||
|
||||
func (qqNumber QQNumber) String() string {
|
||||
return strconv.Itoa(int(qqNumber))
|
||||
}
|
||||
|
||||
func (idNumber IDNumber) Masking() string {
|
||||
return mask(maskLeft(idNumber.String(), 3), 16, 16)
|
||||
}
|
||||
|
||||
func (idNumber IDNumber) String() string {
|
||||
return string(idNumber)
|
||||
}
|
||||
|
||||
func (phoneNumber PhoneNumber) Masking() string {
|
||||
return mask(phoneNumber.String(), 3, 6)
|
||||
}
|
||||
|
||||
func (phoneNumber PhoneNumber) String() string {
|
||||
return strconv.Itoa(int(phoneNumber))
|
||||
}
|
||||
|
||||
func (nickname Nickname) Masking() string {
|
||||
return maskLeft(nickname.String(), 1)
|
||||
}
|
||||
|
||||
func (nickname Nickname) String() string {
|
||||
return string(nickname)
|
||||
}
|
||||
|
||||
func (name Name) Masking() string {
|
||||
return maskLeft(name.String(), 1)
|
||||
}
|
||||
|
||||
func (name Name) String() string {
|
||||
return string(name)
|
||||
}
|
||||
|
||||
func isPhoneNumber(number int) bool {
|
||||
numberStr := strconv.Itoa(number)
|
||||
if len(numberStr) != 11 {
|
||||
return false
|
||||
}
|
||||
// https://www.miit.gov.cn/n1146295/n1652858/n1652930/n3757020/c5623267/part/5623278.doc
|
||||
s := []int{
|
||||
130, 131, 132, 133, 134, 135, 136, 137, 138, 139,
|
||||
145, 146, 147, 148, 148,
|
||||
150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
|
||||
161, 162, 164, 165, 167,
|
||||
170, 172, 173, 175, 176, 177, 178, 179,
|
||||
181, 182, 183, 184, 185, 186, 187, 188, 189,
|
||||
190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
|
||||
}
|
||||
header, _ := strconv.Atoi(numberStr[:3])
|
||||
for _, num := range s {
|
||||
if num == header {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func whatType(value string) (result []interface{}) {
|
||||
if number, err := strconv.ParseInt(value, 10, 64); err == nil {
|
||||
if number <= 19999999999 && isPhoneNumber(int(number)) {
|
||||
result = append(result, PhoneNumber(number))
|
||||
}
|
||||
if number <= 99999999999 && len(strconv.Itoa(int(number))) >= 5 {
|
||||
result = append(result, QQNumber(number))
|
||||
}
|
||||
}
|
||||
if len(value) == 18 {
|
||||
result = append(result, IDNumber(value))
|
||||
}
|
||||
if strings.Contains(value, "@") {
|
||||
result = append(result, Email(value))
|
||||
}
|
||||
return result
|
||||
}
|
42
server/service/errors.go
Normal file
42
server/service/errors.go
Normal file
@ -0,0 +1,42 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
OK = errors.New("ok")
|
||||
UnknownError = errors.New("unknown error")
|
||||
InvalidParameterError = errors.New("invalid parameter error")
|
||||
)
|
||||
|
||||
var errorMap = map[error]int{
|
||||
OK: 0,
|
||||
UnknownError: 10001,
|
||||
InvalidParameterError: 10002,
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func NewResponse(ctx echo.Context, err error, data interface{}) error {
|
||||
if err == nil {
|
||||
err = OK
|
||||
}
|
||||
code, ok := errorMap[err]
|
||||
if !ok {
|
||||
err = UnknownError
|
||||
code = errorMap[err]
|
||||
}
|
||||
return ctx.JSONPretty(http.StatusOK, &Response{
|
||||
Code: code,
|
||||
Message: strings.ToLower(err.Error()),
|
||||
Data: data,
|
||||
}, "\x20\x20")
|
||||
}
|
87
server/service/handler.go
Normal file
87
server/service/handler.go
Normal file
@ -0,0 +1,87 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type QueryResponse struct {
|
||||
Names []string `json:"names"`
|
||||
Nicknames []string `json:"nicknames"`
|
||||
PhoneNumbers []string `json:"phone_numbers"`
|
||||
IDNumbers []string `json:"id_numbers"`
|
||||
QQNumbers []string `json:"qq_numbers"`
|
||||
Passwords []string `json:"passwords"`
|
||||
Emails []string `json:"emails"`
|
||||
Addresses []string `json:"addresses"`
|
||||
}
|
||||
|
||||
func NewQueryResponse() *QueryResponse {
|
||||
return &QueryResponse{
|
||||
Names: make([]string, 0),
|
||||
Nicknames: make([]string, 0),
|
||||
PhoneNumbers: make([]string, 0),
|
||||
IDNumbers: make([]string, 0),
|
||||
QQNumbers: make([]string, 0),
|
||||
Passwords: make([]string, 0),
|
||||
Emails: make([]string, 0),
|
||||
Addresses: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *Service) queryHandlerFunc(ctx echo.Context) error {
|
||||
value := ctx.QueryParam("value")
|
||||
if value == "" {
|
||||
return InvalidParameterError
|
||||
}
|
||||
result := NewQueryResult()
|
||||
types := whatType(value)
|
||||
for _, t := range types {
|
||||
switch value := t.(type) {
|
||||
case QQNumber:
|
||||
result.addQQNumber(int64(value))
|
||||
case PhoneNumber:
|
||||
result.addPhoneNumber(int64(value))
|
||||
case Email:
|
||||
result.addEmail(string(value))
|
||||
case IDNumber:
|
||||
result.addIDNumber(string(value))
|
||||
}
|
||||
}
|
||||
ok := false
|
||||
for !ok {
|
||||
for qqNumber, checked := range result.QQNumbers {
|
||||
if !checked {
|
||||
if err := result.queryQQNumber(svc.databases, int64(qqNumber)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
for phoneNumber, checked := range result.PhoneNumbers {
|
||||
if !checked {
|
||||
if err := result.queryPhoneNumber(svc.databases, int64(phoneNumber)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
for idNumber, checked := range result.IDNumbers {
|
||||
if !checked {
|
||||
if err := result.queryIDNumber(svc.databases, string(idNumber)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
for email, checked := range result.Emails {
|
||||
if !checked {
|
||||
if err := result.queryEmail(svc.databases, string(email)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
ok = true
|
||||
}
|
||||
return NewResponse(ctx, nil, result.Build(svc.config.Mask))
|
||||
}
|
36
server/service/mask.go
Normal file
36
server/service/mask.go
Normal file
@ -0,0 +1,36 @@
|
||||
package service
|
||||
|
||||
func mask(str string, start, end int) string {
|
||||
maskLen := end - start
|
||||
if maskLen < 0 {
|
||||
panic("end cannot be greater than start")
|
||||
}
|
||||
var maskStr string
|
||||
for i := 0; i <= maskLen; i++ {
|
||||
maskStr += "*"
|
||||
}
|
||||
runes := []rune(str)
|
||||
return string(append(runes[:start], append([]rune(maskStr), runes[end+1:]...)...))
|
||||
}
|
||||
|
||||
func maskLeft(str string, reserve int) string {
|
||||
runes := []rune(str)
|
||||
if len(runes)-reserve < 0 {
|
||||
panic("length of reserved string is out of range")
|
||||
}
|
||||
for i := 0; i < len(runes)-reserve; i++ {
|
||||
runes[i] = '*'
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func maskRight(str string, reserve int) string {
|
||||
runes := []rune(str)
|
||||
if len(runes)-reserve < 0 {
|
||||
panic("length of reserved string is out of range")
|
||||
}
|
||||
for i := len(runes) - 1; i > len(runes)-reserve; i-- {
|
||||
runes[i] = '*'
|
||||
}
|
||||
return string(runes)
|
||||
}
|
274
server/service/result.go
Normal file
274
server/service/result.go
Normal file
@ -0,0 +1,274 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/kallydev/privacy/database"
|
||||
)
|
||||
|
||||
type Result interface {
|
||||
queryPhoneNumber(databases []database.Database, phoneNumber int64) error
|
||||
queryIDNumber(databases []database.Database, idNumber string) error
|
||||
queryQQNumber(databases []database.Database, qqNumber int64) error
|
||||
queryEmail(databases []database.Database, email string) error
|
||||
|
||||
addName(name string)
|
||||
addNickname(nickname string)
|
||||
addPhoneNumber(phoneNumber int64)
|
||||
addIDNumber(idNumber string)
|
||||
addQQNumber(qqNumber int64)
|
||||
addPassword(password string)
|
||||
addEmail(email string)
|
||||
addAddress(address string)
|
||||
}
|
||||
|
||||
var (
|
||||
_ Result = &QueryResult{}
|
||||
)
|
||||
|
||||
type QueryResult struct {
|
||||
Names map[Name]bool `json:"names"`
|
||||
Nicknames map[Nickname]bool `json:"nicknames"`
|
||||
PhoneNumbers map[PhoneNumber]bool `json:"phone_numbers"`
|
||||
IDNumbers map[IDNumber]bool `json:"id_numbers"`
|
||||
QQNumbers map[QQNumber]bool `json:"qq_numbers"`
|
||||
Passwords map[Password]bool `json:"passwords"`
|
||||
Emails map[Email]bool `json:"emails"`
|
||||
Addresses map[Address]bool `json:"addresses"`
|
||||
}
|
||||
|
||||
func NewQueryResult() *QueryResult {
|
||||
return &QueryResult{
|
||||
Names: make(map[Name]bool),
|
||||
Nicknames: make(map[Nickname]bool),
|
||||
PhoneNumbers: make(map[PhoneNumber]bool),
|
||||
IDNumbers: make(map[IDNumber]bool),
|
||||
QQNumbers: make(map[QQNumber]bool),
|
||||
Passwords: make(map[Password]bool),
|
||||
Emails: make(map[Email]bool),
|
||||
Addresses: make(map[Address]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (result *QueryResult) addModel(model database.Model) {
|
||||
if name, valid := model.GetName(); valid {
|
||||
result.addName(name)
|
||||
}
|
||||
if nickname, valid := model.GetNickname(); valid {
|
||||
result.addNickname(nickname)
|
||||
}
|
||||
if phoneNumber, valid := model.GetPhoneNumber(); valid {
|
||||
result.addPhoneNumber(phoneNumber)
|
||||
}
|
||||
if idNumber, valid := model.GetIDNumber(); valid {
|
||||
result.addIDNumber(idNumber)
|
||||
}
|
||||
if qqNumber, valid := model.GetQQNumber(); valid {
|
||||
result.addQQNumber(qqNumber)
|
||||
}
|
||||
if password, valid := model.GetPassword(); valid {
|
||||
result.addPassword(password)
|
||||
}
|
||||
if email, valid := model.GetEmail(); valid {
|
||||
result.addEmail(email)
|
||||
}
|
||||
if address, valid := model.GetAddress(); valid {
|
||||
result.addAddress(address)
|
||||
}
|
||||
}
|
||||
|
||||
func (result *QueryResult) queryPhoneNumber(databases []database.Database, phoneNumber int64) error {
|
||||
for _, db := range databases {
|
||||
models, err := db.QueryByPhoneNumber(context.Background(), phoneNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, model := range models {
|
||||
result.addModel(model)
|
||||
}
|
||||
}
|
||||
result.PhoneNumbers[PhoneNumber(phoneNumber)] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (result *QueryResult) queryIDNumber(databases []database.Database, idNumber string) error {
|
||||
for _, db := range databases {
|
||||
models, err := db.QueryByIDNumber(context.Background(), idNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, model := range models {
|
||||
result.addModel(model)
|
||||
}
|
||||
}
|
||||
result.IDNumbers[IDNumber(idNumber)] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (result *QueryResult) queryQQNumber(databases []database.Database, qqNumber int64) error {
|
||||
for _, db := range databases {
|
||||
models, err := db.QueryByQQNumber(context.Background(), qqNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, model := range models {
|
||||
result.addModel(model)
|
||||
}
|
||||
}
|
||||
result.QQNumbers[QQNumber(qqNumber)] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (result *QueryResult) queryEmail(databases []database.Database, email string) error {
|
||||
for _, db := range databases {
|
||||
models, err := db.QueryByEmail(context.Background(), email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, model := range models {
|
||||
result.addModel(model)
|
||||
}
|
||||
}
|
||||
result.Emails[Email(email)] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (result *QueryResult) addName(value string) {
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
name := Name(value)
|
||||
if _, ok := result.Names[name]; !ok {
|
||||
result.Names[name] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (result *QueryResult) addNickname(value string) {
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
nickname := Nickname(value)
|
||||
if _, ok := result.Nicknames[nickname]; !ok {
|
||||
result.Nicknames[nickname] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (result *QueryResult) addPhoneNumber(value int64) {
|
||||
if value == 0 {
|
||||
return
|
||||
}
|
||||
phoneNumber := PhoneNumber(value)
|
||||
if _, ok := result.PhoneNumbers[phoneNumber]; !ok {
|
||||
result.PhoneNumbers[phoneNumber] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (result *QueryResult) addIDNumber(value string) {
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
idNumber := IDNumber(value)
|
||||
if _, ok := result.IDNumbers[idNumber]; !ok {
|
||||
result.IDNumbers[idNumber] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (result *QueryResult) addQQNumber(value int64) {
|
||||
if value == 0 {
|
||||
return
|
||||
}
|
||||
qqNumber := QQNumber(value)
|
||||
if _, ok := result.QQNumbers[qqNumber]; !ok {
|
||||
result.QQNumbers[qqNumber] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (result *QueryResult) addPassword(value string) {
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
password := Password(value)
|
||||
if _, ok := result.Passwords[password]; !ok {
|
||||
result.Passwords[password] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (result *QueryResult) addEmail(value string) {
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
email := Email(value)
|
||||
if _, ok := result.Emails[email]; !ok {
|
||||
result.Emails[email] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (result *QueryResult) addAddress(value string) {
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
address := Address(value)
|
||||
if _, ok := result.Addresses[address]; !ok {
|
||||
result.Addresses[address] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (result *QueryResult) Build(mask bool) *QueryResponse {
|
||||
response := NewQueryResponse()
|
||||
for name := range result.Names {
|
||||
result := name.String()
|
||||
if mask {
|
||||
result = name.Masking()
|
||||
}
|
||||
response.Names = append(response.Names, result)
|
||||
}
|
||||
for nickname := range result.Nicknames {
|
||||
result := nickname.String()
|
||||
if mask {
|
||||
result = nickname.Masking()
|
||||
}
|
||||
response.Nicknames = append(response.Nicknames, result)
|
||||
}
|
||||
for phoneNumber := range result.PhoneNumbers {
|
||||
result := phoneNumber.String()
|
||||
if mask {
|
||||
result = phoneNumber.Masking()
|
||||
}
|
||||
response.PhoneNumbers = append(response.PhoneNumbers, result)
|
||||
}
|
||||
for idNumber := range result.IDNumbers {
|
||||
result := idNumber.String()
|
||||
if mask {
|
||||
result = idNumber.Masking()
|
||||
}
|
||||
response.IDNumbers = append(response.IDNumbers, result)
|
||||
}
|
||||
for qqNumber := range result.QQNumbers {
|
||||
result := qqNumber.String()
|
||||
if mask {
|
||||
result = qqNumber.Masking()
|
||||
}
|
||||
response.QQNumbers = append(response.QQNumbers, result)
|
||||
}
|
||||
for password := range result.Passwords {
|
||||
result := password.String()
|
||||
if mask {
|
||||
result = password.Masking()
|
||||
}
|
||||
response.Passwords = append(response.Passwords, result)
|
||||
}
|
||||
for email := range result.Emails {
|
||||
result := email.String()
|
||||
if mask {
|
||||
result = email.Masking()
|
||||
}
|
||||
response.Emails = append(response.Emails, result)
|
||||
}
|
||||
for address := range result.Addresses {
|
||||
result := address.String()
|
||||
if mask {
|
||||
result = address.Masking()
|
||||
}
|
||||
response.Addresses = append(response.Addresses, result)
|
||||
}
|
||||
return response
|
||||
}
|
87
server/service/service.go
Normal file
87
server/service/service.go
Normal file
@ -0,0 +1,87 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kallydev/privacy/config"
|
||||
"github.com/kallydev/privacy/database"
|
||||
"github.com/kallydev/privacy/database/table"
|
||||
"github.com/kallydev/privacy/ent"
|
||||
"github.com/labstack/echo/v4"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
client *ent.Client
|
||||
databases []database.Database
|
||||
config *config.Config
|
||||
instance *echo.Echo
|
||||
}
|
||||
|
||||
func NewService(configPath string) *Service {
|
||||
conf, err := config.NewConfig(configPath)
|
||||
if err != nil {
|
||||
log.Panicln("failed to load config file")
|
||||
}
|
||||
instance := echo.New()
|
||||
instance.HidePort = true
|
||||
instance.HideBanner = true
|
||||
return &Service{
|
||||
config: conf,
|
||||
instance: instance,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *Service) loadRouter() {
|
||||
instance := svc.instance
|
||||
instance.HTTPErrorHandler = func(err error, ctx echo.Context) {
|
||||
_ = NewResponse(ctx, UnknownError, err)
|
||||
}
|
||||
instance.Static("/", "../website/build")
|
||||
apiGroup := instance.Group("/api")
|
||||
{
|
||||
apiGroup.GET("/query", svc.queryHandlerFunc)
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *Service) LoadDatabase() (err error) {
|
||||
svc.client, err = ent.Open("sqlite3", fmt.Sprintf("file:%s", svc.config.Database.Path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tablesConfig := svc.config.Database.Tables
|
||||
if tablesConfig.QQ {
|
||||
svc.databases = append(svc.databases, &table.QQDatabase{
|
||||
Client: svc.client,
|
||||
})
|
||||
}
|
||||
if tablesConfig.JD {
|
||||
svc.databases = append(svc.databases, &table.JDDatabase{
|
||||
Client: svc.client,
|
||||
})
|
||||
}
|
||||
if tablesConfig.SF {
|
||||
svc.databases = append(svc.databases, &table.SFDatabase{
|
||||
Client: svc.client,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) Start() (err error) {
|
||||
if err := svc.LoadDatabase(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = svc.client.Close()
|
||||
}()
|
||||
svc.loadRouter()
|
||||
httpConfig := svc.config.HttpConfig
|
||||
address := net.JoinHostPort(httpConfig.Host, strconv.Itoa(int(httpConfig.Port)))
|
||||
if httpConfig.TLS != nil {
|
||||
return svc.instance.StartTLS(address, httpConfig.TLS.CertPath, httpConfig.TLS.KeyPath)
|
||||
}
|
||||
return svc.instance.Start(address)
|
||||
}
|
Reference in New Issue
Block a user