该体育直播系统系统由东莞梦幻网络科技开发,使用 ThinkPHP 作为后端,Vue.js 作为 PC/H5 端框架,Java 和 Objective-C 分别用于安卓和 iOS 开发。
1、前端实现 (Vue.js)
<template>
<div class="anchor-certification">
<div class="header">
<h1>主播认证</h1>
</div>
<div class="form-container">
<el-form :model="form" :rules="rules" ref="form" label-width="120px">
<!-- 直播信息 -->
<el-form-item label="直播赛事" prop="liveType">
<el-checkbox-group v-model="form.liveType">
<el-checkbox label="足球"></el-checkbox>
<el-checkbox label="篮球"></el-checkbox>
<el-checkbox label="电竞"></el-checkbox>
<el-checkbox label="其他"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="直播经验" prop="experience">
<el-select v-model="form.experience" placeholder="请选择">
<el-option label="1个月" value="1"></el-option>
<el-option label="3个月" value="3"></el-option>
<el-option label="6个月" value="6"></el-option>
<el-option label="1年" value="12"></el-option>
<el-option label="2年" value="24"></el-option>
<el-option label="3年以上" value="36"></el-option>
</el-select>
</el-form-item>
<!-- 个人信息 -->
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入您的真实姓名"></el-input>
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="form.idCard" placeholder="请输入您身份证号码"></el-input>
</el-form-item>
<el-form-item label="身份照片" prop="idCardPhoto">
<el-upload
action="/api/upload/idCard"
:limit="1"
:on-success="handleIdCardSuccess"
:before-upload="beforeIdCardUpload"
:file-list="idCardFileList">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">请上传身份证正面照片,支持jpg/png格式,大小不超过5MB</div>
</el-upload>
</el-form-item>
<!-- 联系方式 -->
<el-form-item label="联系方式">
<el-input v-model="form.qq" placeholder="QQ号"></el-input>
<el-input v-model="form.wechat" placeholder="微信号"></el-input>
<el-input v-model="form.phone" placeholder="电话号码"></el-input>
</el-form-item>
<!-- 个人简介 -->
<el-form-item label="个人简介" prop="introduction">
<el-input
type="textarea"
:rows="5"
v-model="form.introduction"
placeholder="请填写您的个人简介,不少于15字"
:minlength="15">
</el-input>
<div class="intro-tips">
<p>建议包含以下内容:</p>
<p>1. 主播经验几年</p>
<p>2. 在哪些平台上担任过主播</p>
<p>3. 曾经获得过哪些荣誉</p>
<p>4. 擅长直播哪类赛事(足球、篮球、电竞、娱乐、其他)</p>
</div>
</el-form-item>
<!-- 邀请人 -->
<el-form-item label="邀请人">
<el-input v-model="form.inviter" placeholder="请输入邀请人(选填)"></el-input>
<el-input v-model="form.inviterPhone" placeholder="+86 请输入邀请人电话(选填)"></el-input>
</el-form-item>
<!-- 验证码 -->
<el-form-item label="验证码" prop="captcha">
<el-input v-model="form.captcha" placeholder="请输入验证码" style="width: 200px"></el-input>
<el-button @click="getCaptcha" :disabled="captchaDisabled">
{{ captchaBtnText }}
</el-button>
</el-form-item>
<!-- 协议 -->
<el-form-item prop="agreed">
<el-checkbox v-model="form.agreed">
我已阅读并同意<a href="/protocol/anchor" target="_blank">《体育直播协议》</a>
</el-checkbox>
</el-form-item>
<!-- 提交按钮 -->
<el-form-item>
<el-button type="primary" @click="submitForm" :loading="submitting">提交认证</el-button>
</el-form-item>
</el-form>
</div>
<div class="footer">
<div class="links">
<a href="#">关于我们</a>
<a href="#">联系方式</a>
<a href="#">帮助中心</a>
</div>
<div class="copyright">
Copyright © 2020-2025 龙牙直播 ALL Rights Reserved
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
form: {
liveType: [],
experience: '',
realName: '',
idCard: '',
idCardPhoto: '',
qq: '',
wechat: '',
phone: '',
introduction: '',
inviter: '',
inviterPhone: '',
captcha: '',
agreed: false
},
rules: {
liveType: [
{ type: 'array', required: true, message: '请至少选择一项直播赛事', trigger: 'change' }
],
experience: [
{ required: true, message: '请选择直播经验', trigger: 'change' }
],
realName: [
{ required: true, message: '请输入真实姓名', trigger: 'blur' }
],
idCard: [
{ required: true, message: '请输入身份证号码', trigger: 'blur' },
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '身份证号格式不正确' }
],
idCardPhoto: [
{ required: true, message: '请上传身份证照片', trigger: 'blur' }
],
introduction: [
{ required: true, message: '请输入个人简介', trigger: 'blur' },
{ min: 15, message: '个人简介不能少于15个字符', trigger: 'blur' }
],
captcha: [
{ required: true, message: '请输入验证码', trigger: 'blur' }
],
agreed: [
{ validator: (rule, value, callback) => {
if (!value) {
callback(new Error('请阅读并同意协议'));
} else {
callback();
}
}, trigger: 'change' }
]
},
idCardFileList: [],
captchaDisabled: false,
captchaBtnText: '获取验证码',
countdown: 60,
submitting: false
};
},
methods: {
handleIdCardSuccess(response, file) {
this.form.idCardPhoto = response.data.url;
},
beforeIdCardUpload(file) {
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG/PNG 格式!');
}
if (!isLt5M) {
this.$message.error('上传头像图片大小不能超过 5MB!');
}
return isJPG && isLt5M;
},
getCaptcha() {
if (!this.form.phone) {
this.$message.error('请输入手机号码');
return;
}
// 调用API获取验证码
this.$http.post('/api/captcha/sms', { phone: this.form.phone })
.then(() => {
this.$message.success('验证码已发送');
this.startCountdown();
})
.catch(error => {
this.$message.error(error.message || '验证码发送失败');
});
},
startCountdown() {
this.captchaDisabled = true;
this.captchaBtnText = `${this.countdown}秒后重新获取`;
const timer = setInterval(() => {
this.countdown -= 1;
this.captchaBtnText = `${this.countdown}秒后重新获取`;
if (this.countdown <= 0) {
clearInterval(timer);
this.captchaDisabled = false;
this.captchaBtnText = '获取验证码';
this.countdown = 60;
}
}, 1000);
},
submitForm() {
this.$refs.form.validate(valid => {
if (valid) {
this.submitting = true;
this.$http.post('/api/anchor/certification', this.form)
.then(response => {
this.$message.success('提交成功,请等待审核');
this.$router.push('/certification/result');
})
.catch(error => {
this.$message.error(error.message || '提交失败');
})
.finally(() => {
this.submitting = false;
});
} else {
return false;
}
});
}
}
};
</script>
<style scoped>
.anchor-certification {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.form-container {
background: #fff;
padding: 30px;
border-radius: 5px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.intro-tips {
color: #999;
font-size: 12px;
margin-top: 5px;
}
.footer {
margin-top: 30px;
text-align: center;
color: #999;
font-size: 12px;
}
.links {
margin-bottom: 10px;
}
.links a {
margin: 0 10px;
color: #999;
text-decoration: none;
}
.links a:hover {
color: #409EFF;
}
</style>
2、后端实现 (PHP/ThinkPHP)
<?php
namespace app\api\controller;
use think\Controller;
use think\Request;
use think\Validate;
class AnchorCertification extends Controller
{
// 提交主播认证
public function submit()
{
$request = Request::instance();
$data = $request->post();
// 验证规则
$rules = [
'liveType' => 'require|array',
'experience' => 'require',
'realName' => 'require|chs',
'idCard' => 'require|idCard',
'idCardPhoto' => 'require|url',
'introduction' => 'require|min:15',
'captcha' => 'require',
'agreed' => 'require|accepted'
];
// 验证消息
$messages = [
'liveType.require' => '请选择直播赛事',
'experience.require' => '请选择直播经验',
'realName.require' => '请输入真实姓名',
'realName.chs' => '姓名只能为中文',
'idCard.require' => '请输入身份证号码',
'idCard.idCard' => '身份证号格式不正确',
'idCardPhoto.require' => '请上传身份证照片',
'idCardPhoto.url' => '身份证照片URL格式不正确',
'introduction.require' => '请输入个人简介',
'introduction.min' => '个人简介不能少于15个字符',
'captcha.require' => '请输入验证码',
'agreed.require' => '请阅读并同意协议',
'agreed.accepted' => '请阅读并同意协议'
];
$validate = new Validate($rules, $messages);
if (!$validate->check($data)) {
return json([
'code' => 400,
'message' => $validate->getError()
]);
}
// 验证验证码
if (!$this->checkCaptcha($data['phone'], $data['captcha'])) {
return json([
'code' => 400,
'message' => '验证码错误或已过期'
]);
}
// 保存认证信息
$certification = [
'user_id' => $request->userId, // 从token中获取的用户ID
'live_type' => implode(',', $data['liveType']),
'experience' => $data['experience'],
'real_name' => $data['realName'],
'id_card' => $data['idCard'],
'id_card_photo' => $data['idCardPhoto'],
'qq' => $data['qq'] ?? '',
'wechat' => $data['wechat'] ?? '',
'phone' => $data['phone'] ?? '',
'introduction' => $data['introduction'],
'inviter' => $data['inviter'] ?? '',
'inviter_phone' => $data['inviterPhone'] ?? '',
'status' => 0, // 0-待审核 1-已通过 2-已拒绝
'create_time' => time(),
'update_time' => time()
];
try {
$id = db('anchor_certification')->insertGetId($certification);
return json([
'code' => 200,
'message' => '提交成功,请等待审核',
'data' => ['id' => $id]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'message' => '提交失败: ' . $e->getMessage()
]);
}
}
// 获取验证码
public function sendCaptcha()
{
$request = Request::instance();
$phone = $request->post('phone');
if (empty($phone)) {
return json([
'code' => 400,
'message' => '请输入手机号码'
]);
}
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
return json([
'code' => 400,
'message' => '手机号码格式不正确'
]);
}
// 生成验证码
$captcha = mt_rand(100000, 999999);
// 保存验证码到缓存,有效期5分钟
cache('captcha_' . $phone, $captcha, 300);
// TODO: 实际项目中这里应该调用短信服务发送验证码
// $this->sendSms($phone, $captcha);
return json([
'code' => 200,
'message' => '验证码已发送'
]);
}
// 验证验证码
private function checkCaptcha($phone, $captcha)
{
$cacheCaptcha = cache('captcha_' . $phone);
if ($cacheCaptcha && $cacheCaptcha == $captcha) {
// 验证成功后删除验证码
cache('captcha_' . $phone, null);
return true;
}
return false;
}
// 上传身份证照片
public function uploadIdCard()
{
$file = request()->file('file');
if (empty($file)) {
return json([
'code' => 400,
'message' => '请选择上传文件'
]);
}
// 验证文件类型和大小
$info = $file->validate([
'size' => 5242880, // 5MB
'ext' => 'jpg,jpeg,png'
])->move(ROOT_PATH . 'public' . DS . 'uploads' . DS . 'idcards');
if ($info) {
$url = '/uploads/idcards/' . $info->getSaveName();
return json([
'code' => 200,
'message' => '上传成功',
'data' => ['url' => $url]
]);
} else {
return json([
'code' => 400,
'message' => $file->getError()
]);
}
}
}
3、安卓实现 (Java)
// AnchorCertificationActivity.java
public class AnchorCertificationActivity extends AppCompatActivity {
private EditText etRealName, etIdCard, etQQ, etWechat, etPhone, etIntroduction, etInviter, etInviterPhone, etCaptcha;
private CheckBox cbFootball, cbBasketball, cbEsports, cbOther;
private Spinner spExperience;
private ImageView ivIdCard;
private Button btnGetCaptcha, btnSubmit;
private CheckBox cbAgree;
private String idCardPhotoUrl;
private int countdown = 60;
private boolean isCountdownRunning = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_anchor_certification);
initViews();
setupSpinner();
}
private void initViews() {
etRealName = findViewById(R.id.et_real_name);
etIdCard = findViewById(R.id.et_id_card);
etQQ = findViewById(R.id.et_qq);
etWechat = findViewById(R.id.et_wechat);
etPhone = findViewById(R.id.et_phone);
etIntroduction = findViewById(R.id.et_introduction);
etInviter = findViewById(R.id.et_inviter);
etInviterPhone = findViewById(R.id.et_inviter_phone);
etCaptcha = findViewById(R.id.et_captcha);
cbFootball = findViewById(R.id.cb_football);
cbBasketball = findViewById(R.id.cb_basketball);
cbEsports = findViewById(R.id.cb_esports);
cbOther = findViewById(R.id.cb_other);
spExperience = findViewById(R.id.sp_experience);
ivIdCard = findViewById(R.id.iv_id_card);
ivIdCard.setOnClickListener(v -> uploadIdCard());
btnGetCaptcha = findViewById(R.id.btn_get_captcha);
btnGetCaptcha.setOnClickListener(v -> getCaptcha());
btnSubmit = findViewById(R.id.btn_submit);
btnSubmit.setOnClickListener(v -> submitCertification());
cbAgree = findViewById(R.id.cb_agree);
}
private void setupSpinner() {
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.experience_options, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spExperience.setAdapter(adapter);
}
private void uploadIdCard() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, 1);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1 && resultCode == RESULT_OK && data != null) {
Uri uri = data.getData();
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
ivIdCard.setImageBitmap(bitmap);
// 实际上传逻辑
uploadImageToServer(uri);
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "图片加载失败", Toast.LENGTH_SHORT).show();
}
}
}
private void uploadImageToServer(Uri uri) {
// 这里实现实际上传逻辑,使用OkHttp或其他网络库
// 上传成功后保存返回的URL到idCardPhotoUrl
}
private void getCaptcha() {
String phone = etPhone.getText().toString().trim();
if (phone.isEmpty()) {
Toast.makeText(this, "请输入手机号码", Toast.LENGTH_SHORT).show();
return;
}
if (!Patterns.PHONE.matcher(phone).matches()) {
Toast.makeText(this, "手机号码格式不正确", Toast.LENGTH_SHORT).show();
return;
}
// 调用API获取验证码
getCaptchaFromServer(phone);
// 开始倒计时
startCountdown();
}
private void getCaptchaFromServer(String phone) {
// 使用Retrofit或Volley调用API
// 成功回调后显示提示
Toast.makeText(this, "验证码已发送", Toast.LENGTH_SHORT).show();
}
private void startCountdown() {
isCountdownRunning = true;
btnGetCaptcha.setEnabled(false);
new CountDownTimer(60000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
btnGetCaptcha.setText(countdown + "秒后重新获取");
countdown--;
}
@Override
public void onFinish() {
btnGetCaptcha.setEnabled(true);
btnGetCaptcha.setText("获取验证码");
countdown = 60;
isCountdownRunning = false;
}
}.start();
}
private void submitCertification() {
String realName = etRealName.getText().toString().trim();
String idCard = etIdCard.getText().toString().trim();
String introduction = etIntroduction.getText().toString().trim();
String captcha = etCaptcha.getText().toString().trim();
// 验证必填项
if (realName.isEmpty()) {
Toast.makeText(this, "请输入真实姓名", Toast.LENGTH_SHORT).show();
return;
}
if (idCard.isEmpty() || !validateIdCard(idCard)) {
Toast.makeText(this, "请输入正确的身份证号码", Toast.LENGTH_SHORT).show();
return;
}
if (idCardPhotoUrl == null || idCardPhotoUrl.isEmpty()) {
Toast.makeText(this, "请上传身份证照片", Toast.LENGTH_SHORT).show();
return;
}
if (introduction.isEmpty() || introduction.length() < 15) {
Toast.makeText(this, "个人简介不能少于15字", Toast.LENGTH_SHORT).show();
return;
}
if (captcha.isEmpty()) {
Toast.makeText(this, "请输入验证码", Toast.LENGTH_SHORT).show();
return;
}
if (!cbAgree.isChecked()) {
Toast.makeText(this, "请阅读并同意协议", Toast.LENGTH_SHORT).show();
return;
}
// 收集直播类型
List<String> liveTypes = new ArrayList<>();
if (cbFootball.isChecked()) liveTypes.add("足球");
if (cbBasketball.isChecked()) liveTypes.add("篮球");
if (cbEsports.isChecked()) liveTypes.add("电竞");
if (cbOther.isChecked()) liveTypes.add("其他");
if (liveTypes.isEmpty()) {
Toast.makeText(this, "请至少选择一项直播赛事", Toast.LENGTH_SHORT).show();
return;
}
// 收集其他信息
String experience = spExperience.getSelectedItem().toString();
String qq = etQQ.getText().toString().trim();
String wechat = etWechat.getText().toString().trim();
String phone = etPhone.getText().toString().trim();
String inviter = etInviter.getText().toString().trim();
String inviterPhone = etInviterPhone.getText().toString().trim();
// 提交到服务器
submitToServer(liveTypes, experience, realName, idCard, idCardPhotoUrl,
qq, wechat, phone, introduction, inviter, inviterPhone, captcha);
}
private boolean validateIdCard(String idCard) {
// 简单的身份证验证
return idCard.matches("(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$");
}
private void submitToServer(List<String> liveTypes, String experience, String realName,
String idCard, String idCardPhoto, String qq, String wechat, String phone,
String introduction, String inviter, String inviterPhone, String captcha) {
// 使用Retrofit或Volley提交数据
// 显示加载中
ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setMessage("提交中...");
progressDialog.setCancelable(false);
progressDialog.show();
// 构造请求体
JSONObject requestBody = new JSONObject();
try {
requestBody.put("liveType", new JSONArray(liveTypes));
requestBody.put("experience", experience);
requestBody.put("realName", realName);
requestBody.put("idCard", idCard);
requestBody.put("idCardPhoto", idCardPhoto);
requestBody.put("qq", qq);
requestBody.put("wechat", wechat);
requestBody.put("phone", phone);
requestBody.put("introduction", introduction);
requestBody.put("inviter", inviter);
requestBody.put("inviterPhone", inviterPhone);
requestBody.put("captcha", captcha);
requestBody.put("agreed", true);
} catch (JSONException e) {
e.printStackTrace();
}
// 实际网络请求
// 成功回调
progressDialog.dismiss();
Toast.makeText(this, "提交成功,请等待审核", Toast.LENGTH_SHORT).show();
finish();
}
}
4、iOS实现 (Objective-C)
// AnchorCertificationViewController.h
#import <UIKit/UIKit.h>
@interface AnchorCertificationViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate>
@end
// AnchorCertificationViewController.m
#import "AnchorCertificationViewController.h"
@interface AnchorCertificationViewController ()
@property (weak, nonatomic) IBOutlet UITextField *realNameField;
@property (weak, nonatomic) IBOutlet UITextField *idCardField;
@property (weak, nonatomic) IBOutlet UITextField *qqField;
@property (weak, nonatomic) IBOutlet UITextField *wechatField;
@property (weak, nonatomic) IBOutlet UITextField *phoneField;
@property (weak, nonatomic) IBOutlet UITextView *introductionView;
@property (weak, nonatomic) IBOutlet UITextField *inviterField;
@property (weak, nonatomic) IBOutlet UITextField *inviterPhoneField;
@property (weak, nonatomic) IBOutlet UITextField *captchaField;
@property (weak, nonatomic) IBOutlet UIButton *footballBtn;
@property (weak, nonatomic) IBOutlet UIButton *basketballBtn;
@property (weak, nonatomic) IBOutlet UIButton *esportsBtn;
@property (weak, nonatomic) IBOutlet UIButton *otherBtn;
@property (weak, nonatomic) IBOutlet UIPickerView *experiencePicker;
@property (weak, nonatomic) IBOutlet UIImageView *idCardImageView;
@property (weak, nonatomic) IBOutlet UIButton *getCaptchaBtn;
@property (weak, nonatomic) IBOutlet UIButton *submitBtn;
@property (weak, nonatomic) IBOutlet UIButton *agreeBtn;
@property (strong, nonatomic) NSArray *experienceOptions;
@property (strong, nonatomic) NSString *idCardPhotoUrl;
@property (assign, nonatomic) NSInteger countdown;
@property (strong, nonatomic) NSTimer *countdownTimer;
@end
@implementation AnchorCertificationViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
[self setupData];
}
- (void)setupUI {
self.introductionView.layer.borderWidth = 1.0;
self.introductionView.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.introductionView.layer.cornerRadius = 5.0;
self.idCardImageView.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(uploadIdCard)];
[self.idCardImageView addGestureRecognizer:tap];
self.experiencePicker.delegate = self;
self.experiencePicker.dataSource = self;
}
- (void)setupData {
self.experienceOptions = @[@"1个月", @"3个月", @"6个月", @"1年", @"2年", @"3年以上"];
self.countdown = 60;
}
- (IBAction)liveTypeSelected:(UIButton *)sender {
sender.selected = !sender.selected;
}
- (IBAction)getCaptchaTapped:(id)sender {
NSString *phone = self.phoneField.text;
if (phone.length == 0) {
[self showAlertWithTitle:@"提示" message:@"请输入手机号码"];
return;
}
// 简单的手机号验证
NSString *phoneRegex = @"^1[3-9]\\d{9}$";
NSPredicate *phoneTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", phoneRegex];
if (![phoneTest evaluateWithObject:phone]) {
[self showAlertWithTitle:@"提示" message:@"手机号码格式不正确"];
return;
}
[self getCaptchaFromServer:phone];
[self startCountdown];
}
- (void)getCaptchaFromServer:(NSString *)phone {
// 实际项目中这里应该调用API获取验证码
[self showAlertWithTitle:@"提示" message:@"验证码已发送"];
}
- (void)startCountdown {
self.getCaptchaBtn.enabled = NO;
self.countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateCountdown) userInfo:nil repeats:YES];
}
- (void)updateCountdown {
if (self.countdown > 0) {
[self.getCaptchaBtn setTitle:[NSString stringWithFormat:@"%ld秒后重新获取", (long)self.countdown] forState:UIControlStateDisabled];
self.countdown--;
} else {
[self.countdownTimer invalidate];
self.countdownTimer = nil;
self.getCaptchaBtn.enabled = YES;
[self.getCaptchaBtn setTitle:@"获取验证码" forState:UIControlStateNormal];
self.countdown = 60;
}
}
- (void)uploadIdCard {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
picker.allowsEditing = YES;
[self presentViewController:picker animated:YES completion:nil];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
UIImage *image = info[UIImagePickerControllerEditedImage] ?: info[UIImagePickerControllerOriginalImage];
self.idCardImageView.image = image;
// 实际上传逻辑
[self uploadImageToServer:image];
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)uploadImageToServer:(UIImage *)image {
// 这里实现实际上传逻辑,使用AFNetworking或其他网络库
// 上传成功后保存返回的URL到self.idCardPhotoUrl
}
- (IBAction)submitTapped:(id)sender {
NSString *realName = self.realNameField.text;
NSString *idCard = self.idCardField.text;
NSString *introduction = self.introductionView.text;
NSString *captcha = self.captchaField.text;
// 验证必填项
if (realName.length == 0) {
[self showAlertWithTitle:@"提示" message:@"请输入真实姓名"];
return;
}
if (idCard.length == 0 || ![self validateIdCard:idCard]) {
[self showAlertWithTitle:@"提示" message:@"请输入正确的身份证号码"];
return;
}
if (self.idCardPhotoUrl.length == 0) {
[self showAlertWithTitle:@"提示" message:@"请上传身份证照片"];
return;
}
if (introduction.length < 15) {
[self showAlertWithTitle:@"提示" message:@"个人简介不能少于15字"];
return;
}
if (captcha.length == 0) {
[self showAlertWithTitle:@"提示" message:@"请输入验证码"];
return;
}
if (!self.agreeBtn.selected) {
[self showAlertWithTitle:@"提示" message:@"请阅读并同意协议"];
return;
}
// 收集直播类型
NSMutableArray *liveTypes = [NSMutableArray array];
if (self.footballBtn.selected) [liveTypes addObject:@"足球"];
if (self.basketballBtn.selected) [liveTypes addObject:@"篮球"];
if (self.esportsBtn.selected) [liveTypes addObject:@"电竞"];
if (self.otherBtn.selected) [liveTypes addObject:@"其他"];
if (liveTypes.count == 0) {
[self showAlertWithTitle:@"提示" message:@"请至少选择一项直播赛事"];
return;
}
// 收集其他信息
NSInteger selectedRow = [self.experiencePicker selectedRowInComponent:0];
NSString *experience = self.experienceOptions[selectedRow];
NSString *qq = self.qqField.text;
NSString *wechat = self.wechatField.text;
NSString *phone = self.phoneField.text;
NSString *inviter = self.inviterField.text;
NSString *inviterPhone = self.inviterPhoneField.text;
// 提交到服务器
[self submitToServerWithLiveTypes:liveTypes experience:experience realName:realName
idCard:idCard idCardPhoto:self.idCardPhotoUrl qq:qq
wechat:wechat phone:phone introduction:introduction
inviter:inviter inviterPhone:inviterPhone captcha:captcha];
}
- (BOOL)validateIdCard:(NSString *)idCard {
NSString *regex = @"(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
return [predicate evaluateWithObject:idCard];
}
- (void)submitToServerWithLiveTypes:(NSArray *)liveTypes experience:(NSString *)experience
realName:(NSString *)realName idCard:(NSString *)idCard
idCardPhoto:(NSString *)idCardPhoto qq:(NSString *)qq
wechat:(NSString *)wechat phone:(NSString *)phone
introduction:(NSString *)introduction inviter:(NSString *)inviter
inviterPhone:(NSString *)inviterPhone captcha:(NSString *)captcha {
// 显示加载中
UIAlertController *loadingAlert = [UIAlertController alertControllerWithTitle:nil message:@"提交中..." preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:loadingAlert animated:YES completion:nil];
// 构造请求参数
NSDictionary *params = @{
@"liveType": liveTypes,
@"experience": experience,
@"realName": realName,
@"idCard": idCard,
@"idCardPhoto": idCardPhoto,
@"qq": qq ?: @"",
@"wechat": wechat ?: @"",
@"phone": phone ?: @"",
@"introduction": introduction,
@"inviter": inviter ?: @"",
@"inviterPhone": inviterPhone ?: @"",
@"captcha": captcha,
@"agreed": @YES
};
// 实际网络请求
// 使用AFNetworking或NSURLSession
// 成功回调
[loadingAlert dismissViewControllerAnimated:YES completion:^{
[self showAlertWithTitle:@"提示" message:@"提交成功,请等待审核"];
5、数据库设计 (MySQL)
CREATE TABLE `anchor_certification` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL COMMENT '主播真实姓名',
`identity_card` VARCHAR(18) NOT NULL COMMENT '身份证号码',
`identity_photo_front` VARCHAR(255) NOT NULL COMMENT '身份证正面照片路径',
`identity_photo_back` VARCHAR(255) NOT NULL COMMENT '身份证反面照片路径',
`identity_photo_handheld` VARCHAR(255) NOT NULL COMMENT '手持身份证照片路径',
`contact_type` ENUM('QQ', '微信', '电话') DEFAULT NULL COMMENT '联系方式类型',
`contact_value` VARCHAR(50) DEFAULT NULL COMMENT '联系方式内容',
`live_experience` ENUM('1个月', '3个月', '1年', '3年', '5年以上') NOT NULL COMMENT '直播经验',
`live_type` SET('足球', '篮球', '电竞', '娱乐', '其他') NOT NULL COMMENT '擅长的直播类型',
`personal_intro` TEXT NOT NULL COMMENT '个人简介',
`invite_code` VARCHAR(20) DEFAULT NULL COMMENT '邀请人邀请码',
`status` ENUM('待审核', '已通过', '已拒绝') DEFAULT '待审核' COMMENT '审核状态',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主播认证信息表';