Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Реализация взаимодействия нескольких сайтов на Python c сайтом на Go

Anna | 16.06.2014 | нет комментариев

В данной статье хочу рассказать про реализацию системы цельного входа на форумы “Сети Знаний”.

Входные данные. Имеется система форумов вопросов и результатов, движок которых написан на Python. Всякий форум — это отдельное веб-приложение со своей базой данных. Все форумы работают из одних исходников.

Задача. Реализовать вероятность входа пользователей на форумы, на которых они еще не зарегистрированы, по имеющимся данным с иного форума.

Первое приближение.

Когда о задачи было сказано впервой, хотелось сделать все без дополнительной базы данных и форм авторизации.

Для того, Дабы войти зарегистрированному пользователю, довольно вести ник и пароль, либо воспользоваться OpenID провайдером. Хотелось применять схему, когда пользователь вводит данные с всякого форума в эти поля. В этом случае в коде движка мы перехватываем данные формы, и если пользователь не зарегистрирован, запрашиваем данные со всех форумов динамически. Позднее мы отказались от данного подхода.

Нынешнее решение.

В сегодняшнем решении применяется иной подход — применение всеобщей базы данных. Для входа пользователю нужно применять дополнительную форму.

Трудности:

  • слияние баз данных всех сайтов вместе;
  • поддержание актуальности цельной базы;
  • синхронизация профилей;
  • дублирование данных.

Для идентификации участника на стороне форума я добавил новое поле в табличку пользователей — SeznID. В него мы будем записывать ID пользователя из цельной базы.
В рамках одного форума уникальным является ник (имя пользователя), но на различных форумах могут быть идентичные ники, которые принадлежат различным участникам (на дынный момент в “Сети Знаний” зарегистрировано много пользователей имеющих аккаунты фактически на всех 12 форумах).

Нужно сделать цельную базу данных таким образом, Дабы пользователю принадлежали только его профили.

Для слияния баз форумов с sezn.ru уходит запрос на все форумы по очереди на приобретение пользователей. Для всякого участника мы проверяем, есть ли он в базе, и после этого отправляем обратно на форум его ID.

func load_forum_users(forumUrl string) {
	usersCount := get_users_count(forumUrl, false)
	for i := 0; i < usersCount; i  = UsersCountPerRequest {
		users := get_forum_users(forumUrl, i, UsersCountPerRequest, false)
		for _, user := range users {
			forumId := user.Id
			exist, selectedUser := mmgr.ModelLoader.IsExist(user)	
			if !exist {
				if err := add_new_froum_user(user); err == nil {
					setup_forum_user_profile (user.Id, forumId, forumUrl)	
					user.Sync(forumUrl)
				} 								
			} else {
				setup_forum_user_profile (selectedUser.Id, forumId, forumUrl)
			}	
		} 
	}
}

На стороне форума обработка запроса на добавление SeznID выглядит приблизительно так:

def setup_user_sezn_id(request, uid, sid):
    try:
        user = User.objects.get(id=uid)
        user.sid = sid
        user.save()

        json = simplejson.dumps({"Result": "Success"})
    except:
        json = simplejson.dumps({"Error": "Invalid sezn id or user id"})

    return HttpResponse(json, mimetype='application/json')

При регистрации нового пользователя, всякий форум отправляет запрос на sezn.ru. В результате приходитSeznID, тот, что будет присвоен пользователю.

Код на стороен sezn.ru

func forumUserRegisterHandler(w http.ResponseWriter, r *http.Request) {		
	w.Header().Set("Content-Type", "application/json")
	regData := get_register_data(w, r)		
	user, err := create_forum_user(regData)
	if err != nil || user == nil {
		b, _ := json.Marshal(JsonErrorResponse{"Invalid input data"})
		w.Write(b)
		return
	}

	b, _ := json.Marshal(user.UserModel)
	w.Write(b)	
}

В цельной базе данные хранятся в такой же форме, как и на форумах. Интерес представляет метод аутентификации. Когда пользователь вводит пароль и ник, на форуме работает типовой механизм Django: мы находим пользователя по нику и проверяем введенный и имеющийся пароль.

def check_password(raw_password, enc_password):
    algo, salt, hsh = enc_password.split('$')
    return hsh == get_hexdigest(algo, salt, raw_password)

Функция get_hexdigest применяется ипри создании пароля.

def get_hexdigest(algorithm, salt, raw_password):
    raw_password, salt = smart_str(raw_password), smart_str(salt)
    if algorithm == 'crypt':
        try:
            import crypt
        except ImportError:
            raise ValueError('"crypt" password algorithm not supported in this environment')
        return crypt.crypt(raw_password, salt)

    if algorithm == 'md5':
        return md5_constructor(salt   raw_password).hexdigest()
    elif algorithm == 'sha1':
        return sha_constructor(salt   raw_password).hexdigest()
    raise ValueError("Got unknown password algorithm type in password.")

В Go я сделал схожее. Функция авторизации будет выглядеть так:

func user_by_email_and_raw_pass(email, pass string) (*User, error) {
	user, err := mmgr.ModelLoader.LoadUserByEmail(email)
	if user == nil || err != nil {
		return nil, err
	}	 

	if err = check_password(&user.UserModel, pass); err == nil {
		return &user, nil			
	}

	return nil, errors.New(“User does not exist.”)			
}

Функция check_password:

func check_password(model *UserModel, rawPassword string) error {
	pass := strings.Split(model.Password, Divider)

	if len(pass) != 3 {
		return errors.New("Invalid format”)
	}

	hash := SHA1Passwd(pass[0], pass[1], rawPassword)

	if hash != model.Password {
		return errors.New("Invalid password")
	}
	return nil
}

Функция SHA1Passwd:

func SHA1Passwd(alg, salt, rawPassword string) string {
	h := sha1.New()
	io.WriteString(h, salt rawPassword)

	return fmt.Sprintf("%s$%s$%x", alg, salt, h.Sum(nil))
}

В процессе пользователи могут изменять данные. Для нас самым основным является ник, почта, пароль и OpenID провайдеры. При изменении этих данных на одном форуме, мы будем изменять данные в цельной базе, а также на других форумах. Тонкость в том, что зачинатель запроса не отправляет данные с сервера на сервер. Процесс выглядит так:

image

Каждый остальной API для обмена данными построен на таком же тезисе. В всеобщем, мы добавили перехваты:

  • регистрации;
  • входа на форум;
  • метаморфоза профиля пользователя;
  • метаморфоза пароля;
  • добавление/удаление OpenID провайдеров.

На этом хочу завершить данную статью. Дальше сегмент Q&A.

Пожалуйста, задавайте ваши вопросы в комментариях к посту, на форуме ХэшКод c метками “Go” и “Python”либо в личных сообщениях. Лучшие вопросы я буду рассматривать в конце всякой статьи.

1. Пользователь antarx в комментарии к предыдущему посту рекомендовал применять перегрузку функций в сочетании с наследованием для избежания разрастания кода запросов к базе.

На самом деле, крепко делу такой подход не поможет. Предположим, что у нас есть функция запроса объектов из базы по ID пользователя — GetQuestionByUserId(id int). Задача в том, что если мы захотим предпочесть вопросы по пользователю до какой-то даты, то нужно писать еще одну функцию —GetQuestionByUserIdAndDate(id int, date time.Time). После этого мы придумаем еще одну сверх резкую выборку по пользователю, дате и голосам за — GetQuestionByUserIdAndDateAndScore(id int, date time.Time, score int). В грядущем мы захотим делать выборку по дате и очкам для все пользователей. И так до бесконечности. В случае с предложенным подходом у нас будут дублироваться конструкции. Кода в этом случае будет еще огромнее. Выходом будет создание конструкции запроса. Скажем,

type QuestionQuery struct {
	UserId int
	Date time.Time
	Score int 
	// И так далее	
}

Каждая логика запроса переносится в GetQuestion(query QuestionQuery). Минус — если написать логику функции нехорошо, продуктивность может крепко просесть.

2. Дальнейший вопрос от того же пользователя antarx про применение паник как исключений.

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB