init: create project

This commit is contained in:
kallydev
2020-11-28 18:32:36 +08:00
commit a970ad7653
83 changed files with 24196 additions and 0 deletions

139
server/service/data.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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)
}